@h-rig/docs-drift-plugin 0.0.6-alpha.156

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/README.md ADDED
@@ -0,0 +1 @@
1
+ # @h-rig/docs-drift-plugin
@@ -0,0 +1,11 @@
1
+ import type { DriftFinding, DriftReport } from "@rig/contracts";
2
+ import { type DriftGit } from "./git-adapter";
3
+ export interface DriftDetectOptions {
4
+ readonly projectRoot: string;
5
+ readonly docsGlobs?: readonly string[];
6
+ readonly ignoreGlobs?: readonly string[];
7
+ readonly git?: DriftGit;
8
+ }
9
+ export declare function detectDeletedReferences(projectRoot: string, docPath: string, git?: DriftGit): Promise<readonly DriftFinding[]>;
10
+ export declare function detectStaleAnchors(projectRoot: string, docPath: string, git?: DriftGit): Promise<readonly DriftFinding[]>;
11
+ export declare function detectDrift(options: DriftDetectOptions): Promise<DriftReport>;
@@ -0,0 +1,299 @@
1
+ // @bun
2
+ // packages/docs-drift-plugin/src/drift/detect.ts
3
+ import { existsSync } from "fs";
4
+ import { readdir, readFile, stat } from "fs/promises";
5
+ import { basename, extname, relative, resolve } from "path";
6
+
7
+ // packages/docs-drift-plugin/src/drift/extract-refs.ts
8
+ var INLINE_CODE = /`([^`\n]+)`/g;
9
+ var MARKDOWN_LINK = /\[[^\]]+\]\(([^)\s]+)\)/g;
10
+ var SYMBOL_REF = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)?$/;
11
+ var PATH_REF = /^(?:\.\.?\/)?(?:[A-Za-z0-9_.-]+\/)+[A-Za-z0-9_.-]+$|^[A-Za-z0-9_.-]+\.(?:ts|tsx|js|jsx|mjs|cjs|json|md|mdx|css|scss|html|yml|yaml|toml|rs|go|py|rb|java|kt|swift|c|cc|cpp|h|hpp)$/;
12
+ function stripFenceLines(markdown) {
13
+ const lines = markdown.split(/\r?\n/);
14
+ let fenced = false;
15
+ return lines.map((line) => {
16
+ if (/^\s*(```|~~~)/.test(line)) {
17
+ fenced = !fenced;
18
+ return "";
19
+ }
20
+ return fenced ? "" : line;
21
+ });
22
+ }
23
+ function normalizeToken(raw) {
24
+ return raw.trim().replace(/^['"]|['"]$/g, "").replace(/[),.;:]+$/g, "").replace(/#L\d+(?:-L\d+)?$/i, "");
25
+ }
26
+ function classifyReference(raw) {
27
+ if (raw.startsWith("@"))
28
+ return null;
29
+ if (PATH_REF.test(raw))
30
+ return "path";
31
+ if (SYMBOL_REF.test(raw))
32
+ return "symbol";
33
+ return null;
34
+ }
35
+ function pushReference(refs, seen, raw, line) {
36
+ const value = normalizeToken(raw);
37
+ if (!value)
38
+ return;
39
+ const kind = classifyReference(value);
40
+ if (!kind)
41
+ return;
42
+ const key = `${kind}:${value}:${line}`;
43
+ if (seen.has(key))
44
+ return;
45
+ seen.add(key);
46
+ refs.push({ kind, value, line });
47
+ }
48
+ function extractDriftReferences(markdown) {
49
+ const refs = [];
50
+ const seen = new Set;
51
+ const lines = stripFenceLines(markdown);
52
+ for (const [index, line] of lines.entries()) {
53
+ const lineNumber = index + 1;
54
+ for (const match of line.matchAll(INLINE_CODE)) {
55
+ pushReference(refs, seen, match[1] ?? "", lineNumber);
56
+ }
57
+ for (const match of line.matchAll(MARKDOWN_LINK)) {
58
+ pushReference(refs, seen, match[1] ?? "", lineNumber);
59
+ }
60
+ }
61
+ return refs;
62
+ }
63
+
64
+ // packages/docs-drift-plugin/src/drift/git-adapter.ts
65
+ import { execFile } from "child_process";
66
+ import { promisify } from "util";
67
+ var execFileAsync = promisify(execFile);
68
+ function processError(value) {
69
+ return value && typeof value === "object" ? value : null;
70
+ }
71
+ function lineCount(output) {
72
+ const trimmed = output.trim();
73
+ return trimmed ? trimmed.split(/\r?\n/).length : 0;
74
+ }
75
+ function makeDriftGit(projectRoot) {
76
+ async function git(args) {
77
+ const result = await execFileAsync("git", [...args], {
78
+ cwd: projectRoot,
79
+ encoding: "utf8",
80
+ maxBuffer: 10 * 1024 * 1024
81
+ });
82
+ return String(result.stdout);
83
+ }
84
+ async function grepCountAt(symbolOrPath, commit) {
85
+ try {
86
+ return lineCount(await git(["grep", "-F", "-n", "-e", symbolOrPath, commit, "--"]));
87
+ } catch (error) {
88
+ const detail = processError(error);
89
+ if (detail?.code === 1)
90
+ return 0;
91
+ throw error;
92
+ }
93
+ }
94
+ return {
95
+ async lastCommitTouching(path) {
96
+ const commit = (await git(["log", "-n", "1", "--format=%H", "--", path])).trim();
97
+ return commit || "HEAD";
98
+ },
99
+ async grepCount(symbolOrPath) {
100
+ return grepCountAt(symbolOrPath, "HEAD");
101
+ },
102
+ async grepCountAtCommit(symbolOrPath, commit) {
103
+ return grepCountAt(symbolOrPath, commit);
104
+ },
105
+ async wasRenamed(symbolOrPath, sinceCommit) {
106
+ if (!symbolOrPath.includes("/") && !symbolOrPath.includes("."))
107
+ return false;
108
+ try {
109
+ const output = await git(["log", "--name-status", "--format=", `${sinceCommit}..HEAD`]);
110
+ return output.split(/\r?\n/).some((line) => {
111
+ const match = line.match(/^R\d*\s+(.+?)\s+(.+)$/);
112
+ return Boolean(match && (match[1] === symbolOrPath || match[2] === symbolOrPath));
113
+ });
114
+ } catch (error) {
115
+ const detail = processError(error);
116
+ if (detail?.code === 128)
117
+ return false;
118
+ throw error;
119
+ }
120
+ }
121
+ };
122
+ }
123
+
124
+ // packages/docs-drift-plugin/src/drift/detect.ts
125
+ var DEFAULT_IGNORED_DIRS = {
126
+ ".git": true,
127
+ node_modules: true,
128
+ dist: true,
129
+ build: true,
130
+ coverage: true,
131
+ ".next": true,
132
+ vendor: true
133
+ };
134
+ var SOURCE_EXTENSIONS = {
135
+ ".ts": true,
136
+ ".tsx": true,
137
+ ".js": true,
138
+ ".jsx": true,
139
+ ".mjs": true,
140
+ ".cjs": true,
141
+ ".rs": true,
142
+ ".go": true,
143
+ ".py": true,
144
+ ".rb": true,
145
+ ".java": true,
146
+ ".kt": true,
147
+ ".swift": true,
148
+ ".c": true,
149
+ ".cc": true,
150
+ ".cpp": true,
151
+ ".h": true,
152
+ ".hpp": true,
153
+ ".json": true,
154
+ ".toml": true,
155
+ ".yml": true,
156
+ ".yaml": true
157
+ };
158
+ function globLikeMatch(path, pattern) {
159
+ if (pattern === path)
160
+ return true;
161
+ if (pattern.startsWith("**/*"))
162
+ return path.endsWith(pattern.slice(4));
163
+ if (pattern.endsWith("/**"))
164
+ return path.startsWith(pattern.slice(0, -3));
165
+ if (pattern.includes("*")) {
166
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
167
+ return new RegExp(`^${escaped}$`).test(path);
168
+ }
169
+ return path.startsWith(pattern);
170
+ }
171
+ function isDefaultDoc(path) {
172
+ const lower = basename(path).toLowerCase();
173
+ return (path.endsWith(".md") || path.endsWith(".mdx")) && !lower.startsWith("changelog") && !lower.includes("generated");
174
+ }
175
+ function isIgnored(path, patterns) {
176
+ return (patterns ?? []).some((pattern) => globLikeMatch(path, pattern));
177
+ }
178
+ async function collectFiles(root, options) {
179
+ const files = [];
180
+ async function visit(dir) {
181
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
182
+ if (entry.isDirectory() && DEFAULT_IGNORED_DIRS[entry.name])
183
+ continue;
184
+ const absolute = resolve(dir, entry.name);
185
+ const rel = relative(root, absolute).replace(/\\/g, "/");
186
+ if (isIgnored(rel, options.ignore))
187
+ continue;
188
+ if (entry.isDirectory()) {
189
+ await visit(absolute);
190
+ continue;
191
+ }
192
+ if (!entry.isFile())
193
+ continue;
194
+ if (options.docs) {
195
+ const matchesConfigured = options.patterns && options.patterns.length > 0 ? options.patterns.some((pattern) => globLikeMatch(rel, pattern)) : isDefaultDoc(rel);
196
+ if (matchesConfigured)
197
+ files.push(rel);
198
+ continue;
199
+ }
200
+ if (SOURCE_EXTENSIONS[extname(entry.name)])
201
+ files.push(rel);
202
+ }
203
+ }
204
+ await visit(root);
205
+ return files.sort();
206
+ }
207
+ async function sourceReferenceCount(projectRoot, reference, docPath) {
208
+ if (reference.kind === "path")
209
+ return existsSync(resolve(projectRoot, reference.value)) ? 1 : 0;
210
+ let count = 0;
211
+ const sourceFiles = await collectFiles(projectRoot, { docs: false });
212
+ for (const sourceFile of sourceFiles) {
213
+ if (sourceFile === docPath)
214
+ continue;
215
+ const text = await readFile(resolve(projectRoot, sourceFile), "utf8").catch(() => "");
216
+ if (text.includes(reference.value))
217
+ count += 1;
218
+ }
219
+ return count;
220
+ }
221
+ function deletedReferenceFinding(docPath, reference) {
222
+ return {
223
+ kind: "deleted-reference",
224
+ docPath,
225
+ line: reference.line,
226
+ reference: reference.value,
227
+ detail: `Documented reference "${reference.value}" no longer exists in the source tree.`,
228
+ confidence: "high"
229
+ };
230
+ }
231
+ function staleAnchorFinding(docPath, reference) {
232
+ return {
233
+ kind: "stale-anchor",
234
+ docPath,
235
+ line: reference.line,
236
+ reference: reference.value,
237
+ detail: `Documented path "${reference.value}" changed after this doc was last updated.`,
238
+ confidence: "medium"
239
+ };
240
+ }
241
+ async function detectDeletedReferences(projectRoot, docPath, git = makeDriftGit(projectRoot)) {
242
+ const markdown = await readFile(resolve(projectRoot, docPath), "utf8");
243
+ const docCommit = await git.lastCommitTouching(docPath);
244
+ const findings = [];
245
+ for (const reference of extractDriftReferences(markdown)) {
246
+ if (await sourceReferenceCount(projectRoot, reference, docPath) > 0)
247
+ continue;
248
+ if (await git.wasRenamed(reference.value, docCommit))
249
+ continue;
250
+ findings.push(deletedReferenceFinding(docPath, reference));
251
+ }
252
+ return findings;
253
+ }
254
+ async function detectStaleAnchors(projectRoot, docPath, git = makeDriftGit(projectRoot)) {
255
+ const markdown = await readFile(resolve(projectRoot, docPath), "utf8");
256
+ const docCommit = await git.lastCommitTouching(docPath);
257
+ const findings = [];
258
+ for (const reference of extractDriftReferences(markdown).filter((ref) => ref.kind === "path")) {
259
+ if (!existsSync(resolve(projectRoot, reference.value)))
260
+ continue;
261
+ const sourceStat = await stat(resolve(projectRoot, reference.value)).catch(() => null);
262
+ if (!sourceStat?.isFile())
263
+ continue;
264
+ const sourceCommit = await git.lastCommitTouching(reference.value);
265
+ if (sourceCommit !== docCommit && !await git.wasRenamed(reference.value, docCommit)) {
266
+ findings.push(staleAnchorFinding(docPath, reference));
267
+ }
268
+ }
269
+ return findings;
270
+ }
271
+ async function detectDrift(options) {
272
+ const git = options.git ?? makeDriftGit(options.projectRoot);
273
+ const docs = await collectFiles(options.projectRoot, {
274
+ docs: true,
275
+ ...options.docsGlobs !== undefined ? { patterns: options.docsGlobs } : {},
276
+ ...options.ignoreGlobs !== undefined ? { ignore: options.ignoreGlobs } : {}
277
+ });
278
+ const findings = [];
279
+ let degraded = false;
280
+ for (const docPath of docs) {
281
+ try {
282
+ findings.push(...await detectDeletedReferences(options.projectRoot, docPath, git));
283
+ findings.push(...await detectStaleAnchors(options.projectRoot, docPath, git));
284
+ } catch {
285
+ degraded = true;
286
+ }
287
+ }
288
+ return {
289
+ generatedAt: new Date().toISOString(),
290
+ scanned: docs.length,
291
+ degraded,
292
+ findings
293
+ };
294
+ }
295
+ export {
296
+ detectStaleAnchors,
297
+ detectDrift,
298
+ detectDeletedReferences
299
+ };
@@ -0,0 +1,7 @@
1
+ export type DriftReferenceKind = "symbol" | "path";
2
+ export interface DriftReference {
3
+ readonly kind: DriftReferenceKind;
4
+ readonly value: string;
5
+ readonly line: number;
6
+ }
7
+ export declare function extractDriftReferences(markdown: string): readonly DriftReference[];
@@ -0,0 +1,60 @@
1
+ // @bun
2
+ // packages/docs-drift-plugin/src/drift/extract-refs.ts
3
+ var INLINE_CODE = /`([^`\n]+)`/g;
4
+ var MARKDOWN_LINK = /\[[^\]]+\]\(([^)\s]+)\)/g;
5
+ var SYMBOL_REF = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)?$/;
6
+ var PATH_REF = /^(?:\.\.?\/)?(?:[A-Za-z0-9_.-]+\/)+[A-Za-z0-9_.-]+$|^[A-Za-z0-9_.-]+\.(?:ts|tsx|js|jsx|mjs|cjs|json|md|mdx|css|scss|html|yml|yaml|toml|rs|go|py|rb|java|kt|swift|c|cc|cpp|h|hpp)$/;
7
+ function stripFenceLines(markdown) {
8
+ const lines = markdown.split(/\r?\n/);
9
+ let fenced = false;
10
+ return lines.map((line) => {
11
+ if (/^\s*(```|~~~)/.test(line)) {
12
+ fenced = !fenced;
13
+ return "";
14
+ }
15
+ return fenced ? "" : line;
16
+ });
17
+ }
18
+ function normalizeToken(raw) {
19
+ return raw.trim().replace(/^['"]|['"]$/g, "").replace(/[),.;:]+$/g, "").replace(/#L\d+(?:-L\d+)?$/i, "");
20
+ }
21
+ function classifyReference(raw) {
22
+ if (raw.startsWith("@"))
23
+ return null;
24
+ if (PATH_REF.test(raw))
25
+ return "path";
26
+ if (SYMBOL_REF.test(raw))
27
+ return "symbol";
28
+ return null;
29
+ }
30
+ function pushReference(refs, seen, raw, line) {
31
+ const value = normalizeToken(raw);
32
+ if (!value)
33
+ return;
34
+ const kind = classifyReference(value);
35
+ if (!kind)
36
+ return;
37
+ const key = `${kind}:${value}:${line}`;
38
+ if (seen.has(key))
39
+ return;
40
+ seen.add(key);
41
+ refs.push({ kind, value, line });
42
+ }
43
+ function extractDriftReferences(markdown) {
44
+ const refs = [];
45
+ const seen = new Set;
46
+ const lines = stripFenceLines(markdown);
47
+ for (const [index, line] of lines.entries()) {
48
+ const lineNumber = index + 1;
49
+ for (const match of line.matchAll(INLINE_CODE)) {
50
+ pushReference(refs, seen, match[1] ?? "", lineNumber);
51
+ }
52
+ for (const match of line.matchAll(MARKDOWN_LINK)) {
53
+ pushReference(refs, seen, match[1] ?? "", lineNumber);
54
+ }
55
+ }
56
+ return refs;
57
+ }
58
+ export {
59
+ extractDriftReferences
60
+ };
@@ -0,0 +1,7 @@
1
+ export interface DriftGit {
2
+ lastCommitTouching(path: string): Promise<string>;
3
+ grepCount(symbolOrPath: string): Promise<number>;
4
+ grepCountAtCommit(symbolOrPath: string, commit: string): Promise<number>;
5
+ wasRenamed(symbolOrPath: string, sinceCommit: string): Promise<boolean>;
6
+ }
7
+ export declare function makeDriftGit(projectRoot: string): DriftGit;
@@ -0,0 +1,63 @@
1
+ // @bun
2
+ // packages/docs-drift-plugin/src/drift/git-adapter.ts
3
+ import { execFile } from "child_process";
4
+ import { promisify } from "util";
5
+ var execFileAsync = promisify(execFile);
6
+ function processError(value) {
7
+ return value && typeof value === "object" ? value : null;
8
+ }
9
+ function lineCount(output) {
10
+ const trimmed = output.trim();
11
+ return trimmed ? trimmed.split(/\r?\n/).length : 0;
12
+ }
13
+ function makeDriftGit(projectRoot) {
14
+ async function git(args) {
15
+ const result = await execFileAsync("git", [...args], {
16
+ cwd: projectRoot,
17
+ encoding: "utf8",
18
+ maxBuffer: 10 * 1024 * 1024
19
+ });
20
+ return String(result.stdout);
21
+ }
22
+ async function grepCountAt(symbolOrPath, commit) {
23
+ try {
24
+ return lineCount(await git(["grep", "-F", "-n", "-e", symbolOrPath, commit, "--"]));
25
+ } catch (error) {
26
+ const detail = processError(error);
27
+ if (detail?.code === 1)
28
+ return 0;
29
+ throw error;
30
+ }
31
+ }
32
+ return {
33
+ async lastCommitTouching(path) {
34
+ const commit = (await git(["log", "-n", "1", "--format=%H", "--", path])).trim();
35
+ return commit || "HEAD";
36
+ },
37
+ async grepCount(symbolOrPath) {
38
+ return grepCountAt(symbolOrPath, "HEAD");
39
+ },
40
+ async grepCountAtCommit(symbolOrPath, commit) {
41
+ return grepCountAt(symbolOrPath, commit);
42
+ },
43
+ async wasRenamed(symbolOrPath, sinceCommit) {
44
+ if (!symbolOrPath.includes("/") && !symbolOrPath.includes("."))
45
+ return false;
46
+ try {
47
+ const output = await git(["log", "--name-status", "--format=", `${sinceCommit}..HEAD`]);
48
+ return output.split(/\r?\n/).some((line) => {
49
+ const match = line.match(/^R\d*\s+(.+?)\s+(.+)$/);
50
+ return Boolean(match && (match[1] === symbolOrPath || match[2] === symbolOrPath));
51
+ });
52
+ } catch (error) {
53
+ const detail = processError(error);
54
+ if (detail?.code === 128)
55
+ return false;
56
+ throw error;
57
+ }
58
+ }
59
+ };
60
+ }
61
+ export {
62
+ makeDriftGit
63
+ };
@@ -0,0 +1,19 @@
1
+ import type { DriftFinding } from "@rig/contracts";
2
+ export interface DriftJudgeInput {
3
+ readonly docPath: string;
4
+ readonly docSnippet: string;
5
+ readonly codeSnippet: string;
6
+ readonly reference?: string;
7
+ }
8
+ export interface DriftJudgeMismatch {
9
+ readonly detail: string;
10
+ readonly reference?: string;
11
+ readonly line?: number | null;
12
+ readonly confidence?: "high" | "medium" | "low";
13
+ }
14
+ export interface DriftJudgeProvider {
15
+ judge(input: DriftJudgeInput): Promise<{
16
+ readonly mismatches: readonly DriftJudgeMismatch[];
17
+ }>;
18
+ }
19
+ export declare function judgeDocumentationDrift(provider: DriftJudgeProvider, input: DriftJudgeInput): Promise<readonly DriftFinding[]>;
@@ -0,0 +1,16 @@
1
+ // @bun
2
+ // packages/docs-drift-plugin/src/drift/judge.ts
3
+ async function judgeDocumentationDrift(provider, input) {
4
+ const result = await provider.judge(input);
5
+ return result.mismatches.map((mismatch) => ({
6
+ kind: "semantic-mismatch",
7
+ docPath: input.docPath,
8
+ line: mismatch.line ?? null,
9
+ reference: mismatch.reference ?? input.reference ?? null,
10
+ detail: mismatch.detail,
11
+ confidence: mismatch.confidence ?? "medium"
12
+ }));
13
+ }
14
+ export {
15
+ judgeDocumentationDrift
16
+ };
@@ -0,0 +1,13 @@
1
+ import { type StageMutation, type ValidatorRegistration } from "@rig/contracts";
2
+ export interface DocsDriftPluginOptions {
3
+ readonly docsGlobs?: readonly string[];
4
+ readonly ignoreGlobs?: readonly string[];
5
+ readonly failOnDrift?: boolean;
6
+ }
7
+ export declare const DOCS_DRIFT_VALIDATOR_ID = "std:docs-drift";
8
+ export declare const DOCS_DRIFT_CLI_ID = "std:drift";
9
+ export declare const DOCS_DRIFT_STAGE_ID = "docs-drift";
10
+ export declare const DOCS_DRIFT_CAPABILITY_ID = "std:docs-drift-capability";
11
+ export declare const DOCS_DRIFT_VALIDATOR: ValidatorRegistration;
12
+ export declare const DOCS_DRIFT_STAGE_MUTATION: StageMutation;
13
+ export declare const DOCS_DRIFT_CLI_COMMAND = "rig drift [--docs <csv>] [--ignore <csv>] [--fail-on-drift] [--json]";
@@ -0,0 +1,35 @@
1
+ // @bun
2
+ // packages/docs-drift-plugin/src/drift/metadata.ts
3
+ import { Schema } from "effect";
4
+ import { StageMutation as StageMutationSchema } from "@rig/contracts";
5
+ var DOCS_DRIFT_VALIDATOR_ID = "std:docs-drift";
6
+ var DOCS_DRIFT_CLI_ID = "std:drift";
7
+ var DOCS_DRIFT_STAGE_ID = "docs-drift";
8
+ var DOCS_DRIFT_CAPABILITY_ID = "std:docs-drift-capability";
9
+ var DOCS_DRIFT_VALIDATOR = {
10
+ id: DOCS_DRIFT_VALIDATOR_ID,
11
+ category: "regression",
12
+ description: "Detect documentation references that drifted from the source tree."
13
+ };
14
+ var DOCS_DRIFT_STAGE_MUTATION = Schema.decodeUnknownSync(StageMutationSchema)({
15
+ op: "insert",
16
+ stage: {
17
+ id: DOCS_DRIFT_STAGE_ID,
18
+ kind: "gate",
19
+ before: ["merge-gate"],
20
+ after: ["open-pr"],
21
+ priority: 0,
22
+ protected: false
23
+ },
24
+ contributedBy: DOCS_DRIFT_STAGE_ID
25
+ });
26
+ var DOCS_DRIFT_CLI_COMMAND = "rig drift [--docs <csv>] [--ignore <csv>] [--fail-on-drift] [--json]";
27
+ export {
28
+ DOCS_DRIFT_VALIDATOR_ID,
29
+ DOCS_DRIFT_VALIDATOR,
30
+ DOCS_DRIFT_STAGE_MUTATION,
31
+ DOCS_DRIFT_STAGE_ID,
32
+ DOCS_DRIFT_CLI_ID,
33
+ DOCS_DRIFT_CLI_COMMAND,
34
+ DOCS_DRIFT_CAPABILITY_ID
35
+ };
@@ -0,0 +1,53 @@
1
+ import type { DriftFinding, DriftReport, StageResult, StageRun } from "@rig/contracts";
2
+ import type { Validator, CliCommand, RuntimeCliContext, ValidatorResult } from "@rig/core/config";
3
+ import { type DriftDetectOptions } from "./detect";
4
+ export { DOCS_DRIFT_CAPABILITY_ID, DOCS_DRIFT_CLI_COMMAND, DOCS_DRIFT_CLI_ID, DOCS_DRIFT_STAGE_ID, DOCS_DRIFT_STAGE_MUTATION, DOCS_DRIFT_VALIDATOR, DOCS_DRIFT_VALIDATOR_ID, type DocsDriftPluginOptions, } from "./metadata";
5
+ import { type DocsDriftPluginOptions } from "./metadata";
6
+ export declare function highConfidenceDriftFindings(report: DriftReport): readonly DriftFinding[];
7
+ export declare function driftGateResult(report: DriftReport, mode?: "observe" | "enforce"): StageResult;
8
+ /**
9
+ * Executable for the `docs-drift` pre-merge gate stage (inserted by
10
+ * DOCS_DRIFT_STAGE_MUTATION before `merge-gate`). Reads the project root from the
11
+ * stage context metadata, runs deletion-survival drift detection, and blocks the
12
+ * pipeline on high-confidence drift when enforcing (`failOnDrift`), else observes.
13
+ */
14
+ export declare function createDocsDriftGateStage(options?: DocsDriftPluginOptions): StageRun;
15
+ export declare function runDocsDriftValidation(options: DriftDetectOptions & {
16
+ failOnDrift?: boolean;
17
+ }): Promise<ValidatorResult>;
18
+ export declare function createDocsDriftValidator(options?: DocsDriftPluginOptions): Validator;
19
+ export interface RunDriftCliOptions {
20
+ readonly projectRoot?: string;
21
+ readonly write?: (message: string) => void;
22
+ readonly writeError?: (message: string) => void;
23
+ }
24
+ export declare function executeDrift(context: RuntimeCliContext, args: readonly string[], options?: DocsDriftPluginOptions): Promise<{
25
+ ok: boolean;
26
+ group: string;
27
+ command: string;
28
+ details: {
29
+ report: {
30
+ readonly degraded: boolean;
31
+ readonly generatedAt: string;
32
+ readonly findings: readonly {
33
+ readonly kind: "deleted-reference" | "stale-anchor" | "semantic-mismatch" | "issue-mismatch";
34
+ readonly detail: string;
35
+ readonly line: number | null;
36
+ readonly reference: string | null;
37
+ readonly docPath: string;
38
+ readonly confidence: "high" | "medium" | "low";
39
+ }[];
40
+ readonly scanned: number;
41
+ };
42
+ summary: {
43
+ total: number;
44
+ highConfidence: number;
45
+ degraded: boolean;
46
+ };
47
+ failOnDrift: boolean;
48
+ failed: boolean;
49
+ };
50
+ }>;
51
+ export declare function createDocsDriftRuntimeCliCommand(options?: DocsDriftPluginOptions): CliCommand;
52
+ export declare const DOCS_DRIFT_RUNTIME_CLI_COMMAND: CliCommand;
53
+ export declare function runDriftCli(args: readonly string[], options?: RunDriftCliOptions): Promise<number>;