@driftdev/cli 1.11.0 → 1.12.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/drift.js +523 -143
- package/package.json +2 -2
- package/schemas/drift.docs-map.schema.json +75 -0
package/dist/drift.js
CHANGED
|
@@ -51,8 +51,8 @@ function getGlobalConfigPath() {
|
|
|
51
51
|
var init_global = () => {};
|
|
52
52
|
|
|
53
53
|
// src/drift.ts
|
|
54
|
-
import { readFileSync as
|
|
55
|
-
import * as
|
|
54
|
+
import { readFileSync as readFileSync23 } from "node:fs";
|
|
55
|
+
import * as path34 from "node:path";
|
|
56
56
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
57
57
|
import { Command } from "commander";
|
|
58
58
|
|
|
@@ -63,13 +63,32 @@ import {
|
|
|
63
63
|
existsSync as existsSync2,
|
|
64
64
|
mkdirSync as mkdirSync2,
|
|
65
65
|
readdirSync,
|
|
66
|
-
readFileSync,
|
|
66
|
+
readFileSync as readFileSync2,
|
|
67
67
|
rmSync,
|
|
68
68
|
statSync,
|
|
69
69
|
writeFileSync
|
|
70
70
|
} from "node:fs";
|
|
71
71
|
import * as os2 from "node:os";
|
|
72
72
|
import * as path2 from "node:path";
|
|
73
|
+
|
|
74
|
+
// src/utils/version.ts
|
|
75
|
+
import { readFileSync } from "node:fs";
|
|
76
|
+
import { dirname, join as join2 } from "node:path";
|
|
77
|
+
import { fileURLToPath } from "node:url";
|
|
78
|
+
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
79
|
+
var cached;
|
|
80
|
+
function getVersion() {
|
|
81
|
+
if (cached)
|
|
82
|
+
return cached;
|
|
83
|
+
try {
|
|
84
|
+
cached = JSON.parse(readFileSync(join2(__dirname2, "../../package.json"), "utf-8")).version ?? "0.0.0";
|
|
85
|
+
} catch {
|
|
86
|
+
cached = "0.0.0";
|
|
87
|
+
}
|
|
88
|
+
return cached ?? "0.0.0";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/cache/spec-cache.ts
|
|
73
92
|
var _noCache = false;
|
|
74
93
|
function setNoCache(value) {
|
|
75
94
|
_noCache = value;
|
|
@@ -141,7 +160,7 @@ function buildCacheKey(input) {
|
|
|
141
160
|
const pkgJson = findPackageJson(absEntry);
|
|
142
161
|
const pkgMtime = pkgJson ? getMtime(pkgJson) : 0;
|
|
143
162
|
const srcMtime = getSourceMaxMtime(absEntry);
|
|
144
|
-
const parts = [absEntry, String(entryMtime), String(pkgMtime), String(srcMtime)];
|
|
163
|
+
const parts = [getVersion(), absEntry, String(entryMtime), String(pkgMtime), String(srcMtime)];
|
|
145
164
|
if (input.configHash)
|
|
146
165
|
parts.push(input.configHash);
|
|
147
166
|
return hashString(parts.join("|"));
|
|
@@ -154,7 +173,7 @@ function getCachedSpec(input) {
|
|
|
154
173
|
if (!existsSync2(cacheFile))
|
|
155
174
|
return null;
|
|
156
175
|
try {
|
|
157
|
-
const raw = JSON.parse(
|
|
176
|
+
const raw = JSON.parse(readFileSync2(cacheFile, "utf-8"));
|
|
158
177
|
return raw;
|
|
159
178
|
} catch {
|
|
160
179
|
return null;
|
|
@@ -177,7 +196,7 @@ function getConfigHash(configPath) {
|
|
|
177
196
|
if (!configPath || !existsSync2(configPath))
|
|
178
197
|
return;
|
|
179
198
|
try {
|
|
180
|
-
const content =
|
|
199
|
+
const content = readFileSync2(configPath, "utf-8");
|
|
181
200
|
return hashString(content);
|
|
182
201
|
} catch {
|
|
183
202
|
return;
|
|
@@ -633,13 +652,13 @@ function formatWarning(message) {
|
|
|
633
652
|
}
|
|
634
653
|
|
|
635
654
|
// src/utils/resolve-specs.ts
|
|
636
|
-
import { readFileSync as
|
|
655
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
637
656
|
import * as path7 from "node:path";
|
|
638
657
|
import { extract as extract2 } from "@openpkg-ts/sdk";
|
|
639
658
|
import { normalize as normalize2 } from "@openpkg-ts/spec";
|
|
640
659
|
|
|
641
660
|
// src/config/loader.ts
|
|
642
|
-
import { existsSync as existsSync4, readFileSync as
|
|
661
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
643
662
|
import * as path4 from "node:path";
|
|
644
663
|
|
|
645
664
|
// src/config/drift-config.ts
|
|
@@ -731,7 +750,7 @@ function loadConfig(cwd = process.cwd()) {
|
|
|
731
750
|
if (!existsSync4(absPath)) {
|
|
732
751
|
throw new Error(`Config file not found: ${absPath}`);
|
|
733
752
|
}
|
|
734
|
-
const raw = JSON.parse(
|
|
753
|
+
const raw = JSON.parse(readFileSync3(absPath, "utf-8"));
|
|
735
754
|
const result = validateConfig(raw);
|
|
736
755
|
if (!result.ok) {
|
|
737
756
|
throw new Error(`Invalid config at ${absPath}: ${result.errors.join(", ")}`);
|
|
@@ -744,7 +763,7 @@ function loadConfig(cwd = process.cwd()) {
|
|
|
744
763
|
const driftConfigPath = path4.join(current, "drift.config.json");
|
|
745
764
|
if (existsSync4(driftConfigPath)) {
|
|
746
765
|
try {
|
|
747
|
-
const raw = JSON.parse(
|
|
766
|
+
const raw = JSON.parse(readFileSync3(driftConfigPath, "utf-8"));
|
|
748
767
|
const result = validateConfig(raw);
|
|
749
768
|
if (!result.ok) {
|
|
750
769
|
throw new Error(`Invalid config at ${driftConfigPath}: ${result.errors.join(", ")}`);
|
|
@@ -760,7 +779,7 @@ function loadConfig(cwd = process.cwd()) {
|
|
|
760
779
|
const pkgPath = path4.join(current, "package.json");
|
|
761
780
|
if (existsSync4(pkgPath)) {
|
|
762
781
|
try {
|
|
763
|
-
const pkg = JSON.parse(
|
|
782
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
764
783
|
if (pkg.drift && typeof pkg.drift === "object") {
|
|
765
784
|
const result = validateConfig(pkg.drift);
|
|
766
785
|
if (!result.ok) {
|
|
@@ -781,7 +800,7 @@ function loadConfig(cwd = process.cwd()) {
|
|
|
781
800
|
const globalPath = getGlobalConfigPath();
|
|
782
801
|
if (existsSync4(globalPath)) {
|
|
783
802
|
try {
|
|
784
|
-
const raw = JSON.parse(
|
|
803
|
+
const raw = JSON.parse(readFileSync3(globalPath, "utf-8"));
|
|
785
804
|
const result = validateConfig(raw);
|
|
786
805
|
if (result.ok) {
|
|
787
806
|
return { config: result.config, configPath: globalPath };
|
|
@@ -792,18 +811,18 @@ function loadConfig(cwd = process.cwd()) {
|
|
|
792
811
|
}
|
|
793
812
|
|
|
794
813
|
// src/utils/detect-entry.ts
|
|
795
|
-
import { existsSync as existsSync6, readFileSync as
|
|
814
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
|
|
796
815
|
import * as path6 from "node:path";
|
|
797
816
|
|
|
798
817
|
// src/utils/workspaces.ts
|
|
799
|
-
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as
|
|
818
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "node:fs";
|
|
800
819
|
import * as path5 from "node:path";
|
|
801
820
|
function detectWorkspaces(cwd) {
|
|
802
821
|
const pkgPath = path5.join(cwd, "package.json");
|
|
803
822
|
if (!existsSync5(pkgPath))
|
|
804
823
|
return null;
|
|
805
824
|
try {
|
|
806
|
-
const pkg = JSON.parse(
|
|
825
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
807
826
|
if (Array.isArray(pkg.workspaces))
|
|
808
827
|
return pkg.workspaces;
|
|
809
828
|
if (pkg.workspaces?.packages && Array.isArray(pkg.workspaces.packages))
|
|
@@ -847,7 +866,7 @@ function discoverPackages(cwd) {
|
|
|
847
866
|
const pkgPath = path5.join(absDir, "package.json");
|
|
848
867
|
if (existsSync5(pkgPath)) {
|
|
849
868
|
try {
|
|
850
|
-
const pkg = JSON.parse(
|
|
869
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
851
870
|
if (pkg.name)
|
|
852
871
|
name = pkg.name;
|
|
853
872
|
if (pkg.private === true)
|
|
@@ -878,7 +897,7 @@ function detectEntry(cwd = process.cwd()) {
|
|
|
878
897
|
}
|
|
879
898
|
if (existsSync6(pkgPath)) {
|
|
880
899
|
try {
|
|
881
|
-
const pkg = JSON.parse(
|
|
900
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
882
901
|
const typesField = pkg.types || pkg.typings;
|
|
883
902
|
if (typesField && typeof typesField === "string") {
|
|
884
903
|
const resolved = tryResolve("types", typesField);
|
|
@@ -1039,7 +1058,7 @@ function collectStringValues(value, depth = 0) {
|
|
|
1039
1058
|
|
|
1040
1059
|
// src/utils/resolve-specs.ts
|
|
1041
1060
|
function loadSpec(filePath) {
|
|
1042
|
-
return JSON.parse(
|
|
1061
|
+
return JSON.parse(readFileSync6(path7.resolve(process.cwd(), filePath), "utf-8"));
|
|
1043
1062
|
}
|
|
1044
1063
|
async function resolveSpecs(opts) {
|
|
1045
1064
|
const { config } = loadConfig();
|
|
@@ -1086,23 +1105,6 @@ async function resolveSpecs(opts) {
|
|
|
1086
1105
|
throw new Error("Provide two spec files, or use --base <ref>");
|
|
1087
1106
|
}
|
|
1088
1107
|
|
|
1089
|
-
// src/utils/version.ts
|
|
1090
|
-
import { readFileSync as readFileSync6 } from "node:fs";
|
|
1091
|
-
import { dirname as dirname4, join as join7 } from "node:path";
|
|
1092
|
-
import { fileURLToPath } from "node:url";
|
|
1093
|
-
var __dirname2 = dirname4(fileURLToPath(import.meta.url));
|
|
1094
|
-
var cached;
|
|
1095
|
-
function getVersion() {
|
|
1096
|
-
if (cached)
|
|
1097
|
-
return cached;
|
|
1098
|
-
try {
|
|
1099
|
-
cached = JSON.parse(readFileSync6(join7(__dirname2, "../../package.json"), "utf-8")).version ?? "0.0.0";
|
|
1100
|
-
} catch {
|
|
1101
|
-
cached = "0.0.0";
|
|
1102
|
-
}
|
|
1103
|
-
return cached ?? "0.0.0";
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
1108
|
// src/commands/breaking.ts
|
|
1107
1109
|
function registerBreakingCommand(program) {
|
|
1108
1110
|
program.command("breaking [old] [new]").description("Detect breaking changes between two specs").option("--base <ref>", "Git ref for old spec").option("--head <ref>", "Git ref for new spec (default: working tree)").option("--entry <file>", "Entry file for git ref extraction").option("--all", "Run across all workspace packages").option("--private", "Include private packages in --all mode").action(async (oldPath, newPath, options) => {
|
|
@@ -2651,9 +2653,358 @@ function registerDiffCommand(program) {
|
|
|
2651
2653
|
});
|
|
2652
2654
|
}
|
|
2653
2655
|
|
|
2654
|
-
// src/commands/
|
|
2655
|
-
import {
|
|
2656
|
+
// src/commands/docs-map.ts
|
|
2657
|
+
import { writeFileSync as writeFileSync5 } from "node:fs";
|
|
2658
|
+
import * as path21 from "node:path";
|
|
2659
|
+
import { collectTypeKeys, extractDocumentedKeys as extractDocumentedKeys2 } from "@driftdev/sdk";
|
|
2660
|
+
|
|
2661
|
+
// src/config/docs-map.ts
|
|
2662
|
+
import { existsSync as existsSync13, readFileSync as readFileSync14 } from "node:fs";
|
|
2656
2663
|
import * as path18 from "node:path";
|
|
2664
|
+
var ANNOTATIONS = new Set(["prose-documented", "internal-by-convention", "ignore"]);
|
|
2665
|
+
function isStringArray(v) {
|
|
2666
|
+
return Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
2667
|
+
}
|
|
2668
|
+
function validateDocsMap(raw) {
|
|
2669
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
2670
|
+
return { ok: false, errors: ["Docs map must be a JSON object"] };
|
|
2671
|
+
}
|
|
2672
|
+
const errors = [];
|
|
2673
|
+
const obj = raw;
|
|
2674
|
+
if (obj.version !== 1)
|
|
2675
|
+
errors.push('"version" must be 1');
|
|
2676
|
+
if (!Array.isArray(obj.pages)) {
|
|
2677
|
+
errors.push('"pages" must be an array');
|
|
2678
|
+
return { ok: false, errors };
|
|
2679
|
+
}
|
|
2680
|
+
obj.pages.forEach((p, i) => {
|
|
2681
|
+
const at = `"pages[${i}]"`;
|
|
2682
|
+
if (typeof p !== "object" || p === null) {
|
|
2683
|
+
errors.push(`${at} must be an object`);
|
|
2684
|
+
return;
|
|
2685
|
+
}
|
|
2686
|
+
const page = p;
|
|
2687
|
+
if (typeof page.page !== "string" || !page.page)
|
|
2688
|
+
errors.push(`${at}.page must be a string`);
|
|
2689
|
+
if (typeof page.type !== "string" || !page.type)
|
|
2690
|
+
errors.push(`${at}.type must be a string`);
|
|
2691
|
+
if (page.spec !== undefined && typeof page.spec !== "string")
|
|
2692
|
+
errors.push(`${at}.spec must be a string`);
|
|
2693
|
+
if (page.entry !== undefined && typeof page.entry !== "string")
|
|
2694
|
+
errors.push(`${at}.entry must be a string`);
|
|
2695
|
+
if (page.spec !== undefined && page.entry !== undefined)
|
|
2696
|
+
errors.push(`${at} must set at most one of "spec"/"entry" (omit both to use the scan target)`);
|
|
2697
|
+
if (page.sectionRe !== undefined) {
|
|
2698
|
+
if (typeof page.sectionRe !== "string") {
|
|
2699
|
+
errors.push(`${at}.sectionRe must be a string`);
|
|
2700
|
+
} else {
|
|
2701
|
+
try {
|
|
2702
|
+
new RegExp(page.sectionRe);
|
|
2703
|
+
} catch {
|
|
2704
|
+
errors.push(`${at}.sectionRe is not a valid regex: ${page.sectionRe}`);
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
for (const field of ["extraPages", "internal", "deprecated"]) {
|
|
2709
|
+
if (page[field] !== undefined && !isStringArray(page[field]))
|
|
2710
|
+
errors.push(`${at}.${field} must be an array of strings`);
|
|
2711
|
+
}
|
|
2712
|
+
if (page.replacements !== undefined) {
|
|
2713
|
+
const r = page.replacements;
|
|
2714
|
+
if (typeof r !== "object" || r === null || Array.isArray(r)) {
|
|
2715
|
+
errors.push(`${at}.replacements must be an object of oldKey → newKey strings`);
|
|
2716
|
+
} else if (!Object.values(r).every((v) => typeof v === "string")) {
|
|
2717
|
+
errors.push(`${at}.replacements values must be strings`);
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
if (page.annotations !== undefined) {
|
|
2721
|
+
const a = page.annotations;
|
|
2722
|
+
if (typeof a !== "object" || a === null || Array.isArray(a)) {
|
|
2723
|
+
errors.push(`${at}.annotations must be an object of key → annotation`);
|
|
2724
|
+
} else {
|
|
2725
|
+
for (const [k, v] of Object.entries(a)) {
|
|
2726
|
+
if (typeof v !== "string" || !ANNOTATIONS.has(v))
|
|
2727
|
+
errors.push(`${at}.annotations["${k}"] must be one of: ${[...ANNOTATIONS].join(", ")} (got ${JSON.stringify(v)})`);
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
if (page.baselineGaps !== undefined && (typeof page.baselineGaps !== "number" || page.baselineGaps < 0 || !Number.isInteger(page.baselineGaps)))
|
|
2732
|
+
errors.push(`${at}.baselineGaps must be a non-negative integer`);
|
|
2733
|
+
});
|
|
2734
|
+
if (errors.length > 0)
|
|
2735
|
+
return { ok: false, errors };
|
|
2736
|
+
return { ok: true, map: obj };
|
|
2737
|
+
}
|
|
2738
|
+
function loadDocsMap(mapPath, cwd = process.cwd()) {
|
|
2739
|
+
const absPath = path18.resolve(cwd, mapPath);
|
|
2740
|
+
if (!existsSync13(absPath))
|
|
2741
|
+
throw new Error(`Docs map not found: ${absPath}`);
|
|
2742
|
+
let raw;
|
|
2743
|
+
try {
|
|
2744
|
+
raw = JSON.parse(readFileSync14(absPath, "utf-8"));
|
|
2745
|
+
} catch {
|
|
2746
|
+
throw new Error(`Invalid JSON in ${absPath}`);
|
|
2747
|
+
}
|
|
2748
|
+
const result = validateDocsMap(raw);
|
|
2749
|
+
if (!result.ok) {
|
|
2750
|
+
throw new Error(`Invalid docs map at ${absPath}: ${result.errors.join("; ")}`);
|
|
2751
|
+
}
|
|
2752
|
+
return { map: result.map, mapPath: absPath, dir: path18.dirname(absPath) };
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
// src/utils/docs-corpus.ts
|
|
2756
|
+
import { readFileSync as readFileSync15, statSync as statSync2 } from "node:fs";
|
|
2757
|
+
import * as path19 from "node:path";
|
|
2758
|
+
import { discoverMarkdownFiles } from "@driftdev/sdk";
|
|
2759
|
+
function resolveDocsCorpus(cwd, docsPatterns, configDocs) {
|
|
2760
|
+
if (!docsPatterns || docsPatterns.length === 0) {
|
|
2761
|
+
return discoverMarkdownFiles(cwd, configDocs);
|
|
2762
|
+
}
|
|
2763
|
+
const include = [];
|
|
2764
|
+
for (const pattern of docsPatterns) {
|
|
2765
|
+
if (isDirectory(path19.resolve(cwd, pattern))) {
|
|
2766
|
+
include.push(path19.join(pattern, "**/*.md"), path19.join(pattern, "**/*.mdx"));
|
|
2767
|
+
} else {
|
|
2768
|
+
include.push(pattern);
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
const files = discoverMarkdownFiles(cwd, { include });
|
|
2772
|
+
if (files.length === 0) {
|
|
2773
|
+
formatWarning(`--docs matched no markdown files: ${docsPatterns.join(", ")}`);
|
|
2774
|
+
}
|
|
2775
|
+
return files;
|
|
2776
|
+
}
|
|
2777
|
+
function isDirectory(p) {
|
|
2778
|
+
try {
|
|
2779
|
+
return statSync2(p).isDirectory();
|
|
2780
|
+
} catch {
|
|
2781
|
+
return false;
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
function readPackageName(cwd = process.cwd()) {
|
|
2785
|
+
try {
|
|
2786
|
+
const pkgJson = JSON.parse(readFileSync15(path19.resolve(cwd, "package.json"), "utf-8"));
|
|
2787
|
+
return typeof pkgJson.name === "string" ? pkgJson.name : undefined;
|
|
2788
|
+
} catch {
|
|
2789
|
+
return;
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
// src/utils/key-coverage-runner.ts
|
|
2794
|
+
import { globSync, readFileSync as readFileSync16 } from "node:fs";
|
|
2795
|
+
import * as path20 from "node:path";
|
|
2796
|
+
import {
|
|
2797
|
+
computeKeyCoverage,
|
|
2798
|
+
DEFAULT_SECTION_RE,
|
|
2799
|
+
extractDocumentedKeys
|
|
2800
|
+
} from "@driftdev/sdk";
|
|
2801
|
+
function resolvePages(page, dir) {
|
|
2802
|
+
const files = [path20.resolve(dir, page.page)];
|
|
2803
|
+
for (const pattern of page.extraPages ?? []) {
|
|
2804
|
+
for (const match of globSync(pattern, { cwd: dir })) {
|
|
2805
|
+
files.push(path20.resolve(dir, match));
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
return files.map((p) => ({ path: p, content: readFileSync16(p, "utf-8") }));
|
|
2809
|
+
}
|
|
2810
|
+
async function resolvePageSpec(page, dir, fallback) {
|
|
2811
|
+
if (page.spec) {
|
|
2812
|
+
const specPath = path20.resolve(dir, page.spec);
|
|
2813
|
+
const raw = JSON.parse(readFileSync16(specPath, "utf-8"));
|
|
2814
|
+
const spec = raw && typeof raw === "object" && "data" in raw && "ok" in raw ? raw.data : raw;
|
|
2815
|
+
if (!Array.isArray(spec?.exports) && !Array.isArray(spec?.types)) {
|
|
2816
|
+
throw new Error(`${page.spec}: not a spec file (no exports/types arrays)`);
|
|
2817
|
+
}
|
|
2818
|
+
return spec;
|
|
2819
|
+
}
|
|
2820
|
+
if (page.entry) {
|
|
2821
|
+
const { apiSpec } = await resolveTruth({ entry: path20.resolve(dir, page.entry) });
|
|
2822
|
+
return apiSpec;
|
|
2823
|
+
}
|
|
2824
|
+
if (!fallback) {
|
|
2825
|
+
throw new Error(`page "${page.page}": no "spec"/"entry" in map and no scan target to fall back to`);
|
|
2826
|
+
}
|
|
2827
|
+
return fallback;
|
|
2828
|
+
}
|
|
2829
|
+
async function runDocsCoverage(loaded, fallbackSpec) {
|
|
2830
|
+
const pages = [];
|
|
2831
|
+
const errors = [];
|
|
2832
|
+
const warnings = [];
|
|
2833
|
+
for (const page of loaded.map.pages) {
|
|
2834
|
+
const spec = await resolvePageSpec(page, loaded.dir, fallbackSpec);
|
|
2835
|
+
const corpus = extractDocumentedKeys(resolvePages(page, loaded.dir), page.sectionRe ? new RegExp(page.sectionRe, "i") : DEFAULT_SECTION_RE);
|
|
2836
|
+
const result = computeKeyCoverage(spec, page.type, corpus, {
|
|
2837
|
+
internal: page.internal,
|
|
2838
|
+
deprecated: page.deprecated,
|
|
2839
|
+
replacements: page.replacements,
|
|
2840
|
+
annotations: page.annotations
|
|
2841
|
+
});
|
|
2842
|
+
if (!result) {
|
|
2843
|
+
throw new Error(`page "${page.page}": type "${page.type}" not found in spec`);
|
|
2844
|
+
}
|
|
2845
|
+
const baseline = page.baselineGaps ?? 0;
|
|
2846
|
+
const failures = [];
|
|
2847
|
+
const pageWarnings = [];
|
|
2848
|
+
for (const ghost of result.ghosts) {
|
|
2849
|
+
failures.push(`ghost option \`${ghost.key}\` — documented but not in ${page.type}`);
|
|
2850
|
+
const loc = ghost.locations[0];
|
|
2851
|
+
errors.push({
|
|
2852
|
+
export: ghost.key,
|
|
2853
|
+
issue: `ghost option \`${ghost.key}\` — documented but does not exist on ${page.type} (or any spec type)`,
|
|
2854
|
+
filePath: loc?.file ?? path20.resolve(loaded.dir, page.page),
|
|
2855
|
+
line: loc?.line
|
|
2856
|
+
});
|
|
2857
|
+
}
|
|
2858
|
+
const gapCount = result.counts.gapsUserFacing;
|
|
2859
|
+
if (gapCount > baseline) {
|
|
2860
|
+
const newGaps = result.gaps.userFacing.slice(0, 10).map((g) => g.key);
|
|
2861
|
+
failures.push(`${gapCount} undocumented options (baseline ${baseline}) — drift grew: ${newGaps.join(", ")}${gapCount > 10 ? "…" : ""}`);
|
|
2862
|
+
errors.push({
|
|
2863
|
+
export: page.type,
|
|
2864
|
+
issue: `${gapCount} undocumented ${page.type} options (baseline ${baseline})`,
|
|
2865
|
+
filePath: path20.resolve(loaded.dir, page.page)
|
|
2866
|
+
});
|
|
2867
|
+
} else if (gapCount > 0) {
|
|
2868
|
+
pageWarnings.push(`${gapCount} known undocumented options (baseline ${baseline})`);
|
|
2869
|
+
}
|
|
2870
|
+
for (const inv of result.inversions) {
|
|
2871
|
+
pageWarnings.push(`documents deprecated \`${inv.documented}\` but not its replacement \`${inv.replacement}\``);
|
|
2872
|
+
warnings.push({
|
|
2873
|
+
export: inv.documented,
|
|
2874
|
+
issue: `documents deprecated \`${inv.documented}\` but not its replacement \`${inv.replacement}\``,
|
|
2875
|
+
filePath: path20.resolve(loaded.dir, page.page)
|
|
2876
|
+
});
|
|
2877
|
+
}
|
|
2878
|
+
pages.push({
|
|
2879
|
+
page: page.page,
|
|
2880
|
+
type: page.type,
|
|
2881
|
+
baselineGaps: baseline,
|
|
2882
|
+
status: failures.length > 0 ? "fail" : pageWarnings.length > 0 ? "warn" : "pass",
|
|
2883
|
+
failures,
|
|
2884
|
+
warnings: pageWarnings,
|
|
2885
|
+
result
|
|
2886
|
+
});
|
|
2887
|
+
}
|
|
2888
|
+
return {
|
|
2889
|
+
pages,
|
|
2890
|
+
pass: pages.every((p) => p.status !== "fail"),
|
|
2891
|
+
annotations: { errors, warnings }
|
|
2892
|
+
};
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
// src/commands/docs-map.ts
|
|
2896
|
+
var MATCH_ALL = /(?:)/;
|
|
2897
|
+
var MIN_PAGE_KEYS = 3;
|
|
2898
|
+
var MIN_OVERLAP = 3;
|
|
2899
|
+
function typeKeySets(spec) {
|
|
2900
|
+
const out = new Map;
|
|
2901
|
+
for (const entry of [...spec.exports, ...spec.types ?? []]) {
|
|
2902
|
+
const keys = new Set(collectTypeKeys(entry).keys());
|
|
2903
|
+
if (keys.size >= MIN_OVERLAP && !out.has(entry.name))
|
|
2904
|
+
out.set(entry.name, keys);
|
|
2905
|
+
}
|
|
2906
|
+
return out;
|
|
2907
|
+
}
|
|
2908
|
+
function registerDocsMapCommand(program) {
|
|
2909
|
+
const docsMap = program.command("docs-map").description("Docs-map lifecycle: scaffold and ratchet the page→type artifact");
|
|
2910
|
+
docsMap.command("stub").description("Scaffold a docs map: option-doc pages + type candidates ranked by key overlap").option("--docs <patterns...>", "Docs corpus: glob patterns or directories").option("--lang <language>", "Source language (inferred otherwise)").option("--abi <path>", "ABI JSON file (Clarity)").option("--spec <path>", "OpenAPI document path or URL").option("--out <file>", "Write the stub map to a file (default: stdout only)").action(async (options) => {
|
|
2911
|
+
const startTime = Date.now();
|
|
2912
|
+
const version = getVersion();
|
|
2913
|
+
try {
|
|
2914
|
+
const lang = resolveLang({ lang: options.lang, spec: options.spec, abi: options.abi });
|
|
2915
|
+
const { config } = loadConfig();
|
|
2916
|
+
const entryFile = lang === "typescript" ? config.entry ? path21.resolve(process.cwd(), config.entry) : detectEntry() : undefined;
|
|
2917
|
+
const { apiSpec } = await resolveTruth({
|
|
2918
|
+
entry: entryFile,
|
|
2919
|
+
lang,
|
|
2920
|
+
spec: options.spec,
|
|
2921
|
+
abi: options.abi
|
|
2922
|
+
});
|
|
2923
|
+
const types = typeKeySets(apiSpec);
|
|
2924
|
+
const corpus = resolveDocsCorpus(process.cwd(), options.docs, config.docs);
|
|
2925
|
+
const pages = [];
|
|
2926
|
+
for (const file of corpus) {
|
|
2927
|
+
const extraction = extractDocumentedKeys2([{ path: file.path, content: file.content ?? "" }], MATCH_ALL);
|
|
2928
|
+
const pageKeys = new Set(extraction.documented.keys());
|
|
2929
|
+
if (pageKeys.size < MIN_PAGE_KEYS)
|
|
2930
|
+
continue;
|
|
2931
|
+
const ranked = [...types.entries()].map(([name, keys]) => ({
|
|
2932
|
+
type: name,
|
|
2933
|
+
overlap: [...pageKeys].filter((k) => keys.has(k)).length,
|
|
2934
|
+
keys: keys.size
|
|
2935
|
+
})).filter((c2) => c2.overlap >= MIN_OVERLAP).sort((a, b) => b.overlap - a.overlap).slice(0, 3);
|
|
2936
|
+
if (ranked.length === 0)
|
|
2937
|
+
continue;
|
|
2938
|
+
pages.push({
|
|
2939
|
+
page: path21.relative(process.cwd(), file.path),
|
|
2940
|
+
keys: pageKeys.size,
|
|
2941
|
+
type: ranked[0].type,
|
|
2942
|
+
candidates: ranked
|
|
2943
|
+
});
|
|
2944
|
+
}
|
|
2945
|
+
const stub = {
|
|
2946
|
+
$schema: "https://unpkg.com/@driftdev/cli/schemas/drift.docs-map.schema.json",
|
|
2947
|
+
version: 1,
|
|
2948
|
+
pages: pages.map((p) => ({
|
|
2949
|
+
page: p.page,
|
|
2950
|
+
type: p.type,
|
|
2951
|
+
baselineGaps: 0
|
|
2952
|
+
}))
|
|
2953
|
+
};
|
|
2954
|
+
if (options.out) {
|
|
2955
|
+
writeFileSync5(path21.resolve(process.cwd(), options.out), `${JSON.stringify(stub, null, 2)}
|
|
2956
|
+
`);
|
|
2957
|
+
}
|
|
2958
|
+
formatOutput("docs-map stub", { candidates: pages, stub, ...options.out ? { written: options.out } : {} }, startTime, version, undefined, {
|
|
2959
|
+
suggested: "drift-docs-map skill",
|
|
2960
|
+
reason: "review type mappings, add sectionRe/annotations, then set baselines"
|
|
2961
|
+
});
|
|
2962
|
+
} catch (err) {
|
|
2963
|
+
formatError("docs-map stub", err instanceof Error ? err.message : String(err), startTime, version);
|
|
2964
|
+
}
|
|
2965
|
+
});
|
|
2966
|
+
docsMap.command("baseline").description("Tighten baselineGaps to current gap counts (ratchet — never raises)").argument("<map>", "Docs map file").action(async (mapArg) => {
|
|
2967
|
+
const startTime = Date.now();
|
|
2968
|
+
const version = getVersion();
|
|
2969
|
+
try {
|
|
2970
|
+
const loaded = loadDocsMap(mapArg);
|
|
2971
|
+
let fallback;
|
|
2972
|
+
try {
|
|
2973
|
+
const { config } = loadConfig();
|
|
2974
|
+
const entryFile = config.entry ? path21.resolve(process.cwd(), config.entry) : detectEntry();
|
|
2975
|
+
fallback = (await resolveTruth({ entry: entryFile })).apiSpec;
|
|
2976
|
+
} catch {}
|
|
2977
|
+
const run = await runDocsCoverage(loaded, fallback);
|
|
2978
|
+
const changes = [];
|
|
2979
|
+
for (const result of run.pages) {
|
|
2980
|
+
const entry = loaded.map.pages.find((p) => p.page === result.page);
|
|
2981
|
+
if (!entry)
|
|
2982
|
+
continue;
|
|
2983
|
+
const current = result.result.counts.gapsUserFacing;
|
|
2984
|
+
const existing = entry.baselineGaps;
|
|
2985
|
+
if (existing === undefined || current < existing) {
|
|
2986
|
+
changes.push({ page: result.page, from: existing ?? current, to: current });
|
|
2987
|
+
entry.baselineGaps = current;
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
if (changes.length > 0) {
|
|
2991
|
+
const out = {
|
|
2992
|
+
$schema: loaded.map.$schema,
|
|
2993
|
+
...loaded.map
|
|
2994
|
+
};
|
|
2995
|
+
writeFileSync5(loaded.mapPath, `${JSON.stringify(out, null, 2)}
|
|
2996
|
+
`);
|
|
2997
|
+
}
|
|
2998
|
+
formatOutput("docs-map baseline", { changes, map: loaded.mapPath }, startTime, version);
|
|
2999
|
+
} catch (err) {
|
|
3000
|
+
formatError("docs-map baseline", err instanceof Error ? err.message : String(err), startTime, version);
|
|
3001
|
+
}
|
|
3002
|
+
});
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
// src/commands/examples.ts
|
|
3006
|
+
import { readFileSync as readFileSync17 } from "node:fs";
|
|
3007
|
+
import * as path22 from "node:path";
|
|
2657
3008
|
import { validateExamples } from "@driftdev/sdk";
|
|
2658
3009
|
|
|
2659
3010
|
// src/formatters/examples.ts
|
|
@@ -2732,16 +3083,16 @@ function renderExamples(data) {
|
|
|
2732
3083
|
|
|
2733
3084
|
// src/commands/examples.ts
|
|
2734
3085
|
function findPackagePath(entryFile) {
|
|
2735
|
-
let dir =
|
|
2736
|
-
while (dir !==
|
|
3086
|
+
let dir = path22.dirname(entryFile);
|
|
3087
|
+
while (dir !== path22.dirname(dir)) {
|
|
2737
3088
|
try {
|
|
2738
|
-
|
|
3089
|
+
readFileSync17(path22.join(dir, "package.json"), "utf-8");
|
|
2739
3090
|
return dir;
|
|
2740
3091
|
} catch {
|
|
2741
|
-
dir =
|
|
3092
|
+
dir = path22.dirname(dir);
|
|
2742
3093
|
}
|
|
2743
3094
|
}
|
|
2744
|
-
return
|
|
3095
|
+
return path22.dirname(entryFile);
|
|
2745
3096
|
}
|
|
2746
3097
|
function registerExamplesCommand(program) {
|
|
2747
3098
|
program.command("examples [entry]").description("Validate @example blocks on exports").option("--typecheck", "Type-check examples with TypeScript").option("--run", "Execute examples at runtime (implies --typecheck)").option("--all", "Run across all workspace packages").option("--private", "Include private packages in --all mode").option("--min <n>", "Minimum presence threshold (exit 1 if below)").action(async (entry, options) => {
|
|
@@ -2809,7 +3160,7 @@ function registerExamplesCommand(program) {
|
|
|
2809
3160
|
return;
|
|
2810
3161
|
}
|
|
2811
3162
|
const { config } = loadConfig();
|
|
2812
|
-
const entryFile = entry ?
|
|
3163
|
+
const entryFile = entry ? path22.resolve(process.cwd(), entry) : config.entry ? path22.resolve(process.cwd(), config.entry) : detectEntry();
|
|
2813
3164
|
const { spec } = await cachedExtract(entryFile);
|
|
2814
3165
|
const exports = spec.exports ?? [];
|
|
2815
3166
|
const packagePath = findPackagePath(entryFile);
|
|
@@ -2844,7 +3195,7 @@ function registerExamplesCommand(program) {
|
|
|
2844
3195
|
}
|
|
2845
3196
|
|
|
2846
3197
|
// src/commands/extract.ts
|
|
2847
|
-
import * as
|
|
3198
|
+
import * as path23 from "node:path";
|
|
2848
3199
|
import { Drift } from "@driftdev/sdk";
|
|
2849
3200
|
import { normalize as normalize6 } from "@openpkg-ts/spec";
|
|
2850
3201
|
|
|
@@ -2883,8 +3234,8 @@ function registerExtractCommand(program) {
|
|
|
2883
3234
|
abi: options.abi
|
|
2884
3235
|
});
|
|
2885
3236
|
if (options.output) {
|
|
2886
|
-
const { writeFileSync:
|
|
2887
|
-
|
|
3237
|
+
const { writeFileSync: writeFileSync6 } = await import("node:fs");
|
|
3238
|
+
writeFileSync6(options.output, JSON.stringify(apiSpec, null, 2));
|
|
2888
3239
|
process.stderr.write(`drift extract: wrote ${options.output}
|
|
2889
3240
|
`);
|
|
2890
3241
|
} else {
|
|
@@ -2912,7 +3263,7 @@ function registerExtractCommand(program) {
|
|
|
2912
3263
|
formatOutput("extract", { packages: specs, ...skipped.length > 0 ? { skipped } : {} }, startTime, version);
|
|
2913
3264
|
return;
|
|
2914
3265
|
}
|
|
2915
|
-
const entryFile = entry ?
|
|
3266
|
+
const entryFile = entry ? path23.resolve(process.cwd(), entry) : detectEntry();
|
|
2916
3267
|
const hasFilters = !!(options.only || options.ignore);
|
|
2917
3268
|
let spec;
|
|
2918
3269
|
if (hasFilters) {
|
|
@@ -2937,8 +3288,8 @@ function registerExtractCommand(program) {
|
|
|
2937
3288
|
spec = result.spec;
|
|
2938
3289
|
}
|
|
2939
3290
|
if (options.output) {
|
|
2940
|
-
const { writeFileSync:
|
|
2941
|
-
|
|
3291
|
+
const { writeFileSync: writeFileSync6 } = await import("node:fs");
|
|
3292
|
+
writeFileSync6(options.output, JSON.stringify(spec, null, 2));
|
|
2942
3293
|
process.stderr.write(`drift extract: wrote ${options.output}
|
|
2943
3294
|
`);
|
|
2944
3295
|
} else {
|
|
@@ -2951,8 +3302,8 @@ function registerExtractCommand(program) {
|
|
|
2951
3302
|
}
|
|
2952
3303
|
|
|
2953
3304
|
// src/commands/filter.ts
|
|
2954
|
-
import { readFileSync as
|
|
2955
|
-
import * as
|
|
3305
|
+
import { readFileSync as readFileSync18 } from "node:fs";
|
|
3306
|
+
import * as path24 from "node:path";
|
|
2956
3307
|
import { filterSpec } from "@openpkg-ts/sdk";
|
|
2957
3308
|
|
|
2958
3309
|
// src/formatters/filter.ts
|
|
@@ -2983,8 +3334,8 @@ function registerFilterCommand(program) {
|
|
|
2983
3334
|
const startTime = Date.now();
|
|
2984
3335
|
const version = getVersion();
|
|
2985
3336
|
try {
|
|
2986
|
-
const filePath =
|
|
2987
|
-
const content =
|
|
3337
|
+
const filePath = path24.resolve(process.cwd(), file);
|
|
3338
|
+
const content = readFileSync18(filePath, "utf-8");
|
|
2988
3339
|
const spec = JSON.parse(content);
|
|
2989
3340
|
const criteria = {};
|
|
2990
3341
|
if (options.kind) {
|
|
@@ -3010,7 +3361,7 @@ function registerFilterCommand(program) {
|
|
|
3010
3361
|
}
|
|
3011
3362
|
|
|
3012
3363
|
// src/commands/get.ts
|
|
3013
|
-
import * as
|
|
3364
|
+
import * as path25 from "node:path";
|
|
3014
3365
|
import { getExport, listExports } from "@openpkg-ts/sdk";
|
|
3015
3366
|
|
|
3016
3367
|
// src/formatters/get.ts
|
|
@@ -3261,7 +3612,7 @@ function registerGetCommand(program) {
|
|
|
3261
3612
|
let entryFile;
|
|
3262
3613
|
let exportName;
|
|
3263
3614
|
if (name) {
|
|
3264
|
-
entryFile =
|
|
3615
|
+
entryFile = path25.resolve(process.cwd(), nameOrEntry);
|
|
3265
3616
|
exportName = name;
|
|
3266
3617
|
} else {
|
|
3267
3618
|
entryFile = detectEntry();
|
|
@@ -3334,7 +3685,7 @@ function renderNotFound(exportName, suggestions, startTime, version) {
|
|
|
3334
3685
|
}
|
|
3335
3686
|
|
|
3336
3687
|
// src/commands/health.ts
|
|
3337
|
-
import * as
|
|
3688
|
+
import * as path26 from "node:path";
|
|
3338
3689
|
import { computeDrift as computeDrift3, isExternalExport as isExternalExport2 } from "@driftdev/sdk";
|
|
3339
3690
|
|
|
3340
3691
|
// src/formatters/health.ts
|
|
@@ -3455,9 +3806,9 @@ function registerHealthCommand(program) {
|
|
|
3455
3806
|
return;
|
|
3456
3807
|
}
|
|
3457
3808
|
const { config } = loadConfig();
|
|
3458
|
-
let entryFile = entry ?
|
|
3809
|
+
let entryFile = entry ? path26.resolve(process.cwd(), entry) : undefined;
|
|
3459
3810
|
if (lang === "typescript" && !entryFile) {
|
|
3460
|
-
entryFile = config.entry ?
|
|
3811
|
+
entryFile = config.entry ? path26.resolve(process.cwd(), config.entry) : detectEntry();
|
|
3461
3812
|
}
|
|
3462
3813
|
const {
|
|
3463
3814
|
apiSpec: spec,
|
|
@@ -3522,8 +3873,8 @@ function registerHealthCommand(program) {
|
|
|
3522
3873
|
|
|
3523
3874
|
// src/commands/init.ts
|
|
3524
3875
|
init_global();
|
|
3525
|
-
import { existsSync as
|
|
3526
|
-
import * as
|
|
3876
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync6, readFileSync as readFileSync19, writeFileSync as writeFileSync6 } from "node:fs";
|
|
3877
|
+
import * as path27 from "node:path";
|
|
3527
3878
|
import { extract as extract6 } from "@openpkg-ts/sdk";
|
|
3528
3879
|
import { normalize as normalize7 } from "@openpkg-ts/spec";
|
|
3529
3880
|
|
|
@@ -3829,14 +4180,14 @@ ${detailLine}`);
|
|
|
3829
4180
|
|
|
3830
4181
|
// src/commands/init.ts
|
|
3831
4182
|
async function scanPackage(cwd, pkgDir) {
|
|
3832
|
-
const absDir =
|
|
3833
|
-
if (!
|
|
4183
|
+
const absDir = path27.join(cwd, pkgDir);
|
|
4184
|
+
if (!existsSync14(absDir))
|
|
3834
4185
|
return null;
|
|
3835
|
-
const pkgPath =
|
|
4186
|
+
const pkgPath = path27.join(absDir, "package.json");
|
|
3836
4187
|
let name = pkgDir;
|
|
3837
|
-
if (
|
|
4188
|
+
if (existsSync14(pkgPath)) {
|
|
3838
4189
|
try {
|
|
3839
|
-
const pkg = JSON.parse(
|
|
4190
|
+
const pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
|
|
3840
4191
|
if (pkg.name)
|
|
3841
4192
|
name = pkg.name;
|
|
3842
4193
|
} catch {}
|
|
@@ -3854,7 +4205,7 @@ async function scanPackage(cwd, pkgDir) {
|
|
|
3854
4205
|
}
|
|
3855
4206
|
const coverage = total > 0 ? Math.round(documented / total * 100) : 100;
|
|
3856
4207
|
const health = Math.round(coverage * 0.5 + 100 * 0.5);
|
|
3857
|
-
return { name, entry:
|
|
4208
|
+
return { name, entry: path27.relative(cwd, entryFile), exports: total, coverage, health };
|
|
3858
4209
|
} catch {
|
|
3859
4210
|
return null;
|
|
3860
4211
|
}
|
|
@@ -3891,13 +4242,13 @@ function registerInitCommand(program) {
|
|
|
3891
4242
|
return;
|
|
3892
4243
|
}
|
|
3893
4244
|
const config = generateConfig(packages);
|
|
3894
|
-
const configPath = opts.project ?
|
|
4245
|
+
const configPath = opts.project ? path27.resolve(cwd, "drift.config.json") : getGlobalConfigPath();
|
|
3895
4246
|
if (!opts.project) {
|
|
3896
4247
|
const globalDir = getGlobalDir();
|
|
3897
|
-
if (!
|
|
4248
|
+
if (!existsSync14(globalDir))
|
|
3898
4249
|
mkdirSync6(globalDir, { recursive: true });
|
|
3899
4250
|
}
|
|
3900
|
-
|
|
4251
|
+
writeFileSync6(configPath, `${JSON.stringify(config, null, 2)}
|
|
3901
4252
|
`);
|
|
3902
4253
|
ensureProjectDir(cwd);
|
|
3903
4254
|
const data = {
|
|
@@ -3915,7 +4266,7 @@ function registerInitCommand(program) {
|
|
|
3915
4266
|
}
|
|
3916
4267
|
|
|
3917
4268
|
// src/commands/lint.ts
|
|
3918
|
-
import * as
|
|
4269
|
+
import * as path28 from "node:path";
|
|
3919
4270
|
import { buildExportRegistry, computeDrift as computeDrift4, detectProseDrift } from "@driftdev/sdk";
|
|
3920
4271
|
|
|
3921
4272
|
// src/formatters/lint.ts
|
|
@@ -3972,44 +4323,6 @@ function emitAnnotations(issues, level = "error") {
|
|
|
3972
4323
|
}
|
|
3973
4324
|
}
|
|
3974
4325
|
|
|
3975
|
-
// src/utils/docs-corpus.ts
|
|
3976
|
-
import { readFileSync as readFileSync17, statSync as statSync2 } from "node:fs";
|
|
3977
|
-
import * as path24 from "node:path";
|
|
3978
|
-
import { discoverMarkdownFiles } from "@driftdev/sdk";
|
|
3979
|
-
function resolveDocsCorpus(cwd, docsPatterns, configDocs) {
|
|
3980
|
-
if (!docsPatterns || docsPatterns.length === 0) {
|
|
3981
|
-
return discoverMarkdownFiles(cwd, configDocs);
|
|
3982
|
-
}
|
|
3983
|
-
const include = [];
|
|
3984
|
-
for (const pattern of docsPatterns) {
|
|
3985
|
-
if (isDirectory(path24.resolve(cwd, pattern))) {
|
|
3986
|
-
include.push(path24.join(pattern, "**/*.md"), path24.join(pattern, "**/*.mdx"));
|
|
3987
|
-
} else {
|
|
3988
|
-
include.push(pattern);
|
|
3989
|
-
}
|
|
3990
|
-
}
|
|
3991
|
-
const files = discoverMarkdownFiles(cwd, { include });
|
|
3992
|
-
if (files.length === 0) {
|
|
3993
|
-
formatWarning(`--docs matched no markdown files: ${docsPatterns.join(", ")}`);
|
|
3994
|
-
}
|
|
3995
|
-
return files;
|
|
3996
|
-
}
|
|
3997
|
-
function isDirectory(p) {
|
|
3998
|
-
try {
|
|
3999
|
-
return statSync2(p).isDirectory();
|
|
4000
|
-
} catch {
|
|
4001
|
-
return false;
|
|
4002
|
-
}
|
|
4003
|
-
}
|
|
4004
|
-
function readPackageName(cwd = process.cwd()) {
|
|
4005
|
-
try {
|
|
4006
|
-
const pkgJson = JSON.parse(readFileSync17(path24.resolve(cwd, "package.json"), "utf-8"));
|
|
4007
|
-
return typeof pkgJson.name === "string" ? pkgJson.name : undefined;
|
|
4008
|
-
} catch {
|
|
4009
|
-
return;
|
|
4010
|
-
}
|
|
4011
|
-
}
|
|
4012
|
-
|
|
4013
4326
|
// src/commands/lint.ts
|
|
4014
4327
|
function registerLintCommand(program) {
|
|
4015
4328
|
program.command("lint [entry]").description("Cross-reference docs against the API surface for accuracy issues").option("--all", "Run across all workspace packages").option("--private", "Include private packages in --all mode").option("--lang <language>", "Source language (inferred from --spec/--abi/.clar; default typescript)").option("--abi <path>", "ABI JSON file (required for --lang clarity)").option("--spec <path>", "OpenAPI document: path or URL (implies --lang openapi)").option("--docs <patterns...>", "Markdown corpus for prose drift: glob patterns or directories (overrides repo-local defaults)").option("--annotations", "Emit GitHub Actions ::error annotations for findings").action(async (entry, options) => {
|
|
@@ -4064,9 +4377,9 @@ function registerLintCommand(program) {
|
|
|
4064
4377
|
formatOutput("lint", { issues: [], count: 0 }, startTime, version, renderLint);
|
|
4065
4378
|
return;
|
|
4066
4379
|
}
|
|
4067
|
-
let entryFile = entry ?
|
|
4380
|
+
let entryFile = entry ? path28.resolve(process.cwd(), entry) : undefined;
|
|
4068
4381
|
if (lang === "typescript" && !entryFile) {
|
|
4069
|
-
entryFile = config.entry ?
|
|
4382
|
+
entryFile = config.entry ? path28.resolve(process.cwd(), config.entry) : detectEntry();
|
|
4070
4383
|
}
|
|
4071
4384
|
const { apiSpec: spec } = await resolveTruth({
|
|
4072
4385
|
entry: entryFile,
|
|
@@ -4132,7 +4445,7 @@ function registerLintCommand(program) {
|
|
|
4132
4445
|
}
|
|
4133
4446
|
|
|
4134
4447
|
// src/commands/list.ts
|
|
4135
|
-
import * as
|
|
4448
|
+
import * as path29 from "node:path";
|
|
4136
4449
|
import { computeDrift as computeDrift5 } from "@driftdev/sdk";
|
|
4137
4450
|
import { listExports as listExports2 } from "@openpkg-ts/sdk";
|
|
4138
4451
|
|
|
@@ -4247,7 +4560,7 @@ function registerListCommand(program) {
|
|
|
4247
4560
|
} else {
|
|
4248
4561
|
let entryFile;
|
|
4249
4562
|
if (searchOrEntry && looksLikeFilePath(searchOrEntry)) {
|
|
4250
|
-
entryFile =
|
|
4563
|
+
entryFile = path29.resolve(process.cwd(), searchOrEntry);
|
|
4251
4564
|
} else if (searchOrEntry) {
|
|
4252
4565
|
entryFile = detectEntry();
|
|
4253
4566
|
searchTerm = searchOrEntry;
|
|
@@ -4323,7 +4636,7 @@ function truthFlags(args) {
|
|
|
4323
4636
|
return out;
|
|
4324
4637
|
}
|
|
4325
4638
|
function runDrift(cliArgs, cwd) {
|
|
4326
|
-
return new Promise((
|
|
4639
|
+
return new Promise((resolve22) => {
|
|
4327
4640
|
const child = spawn(process.execPath, [process.argv[1], ...cliArgs, "--json"], {
|
|
4328
4641
|
cwd: cwd ?? process.cwd(),
|
|
4329
4642
|
env: { ...process.env, NO_COLOR: "1" },
|
|
@@ -4343,9 +4656,9 @@ function runDrift(cliArgs, cwd) {
|
|
|
4343
4656
|
try {
|
|
4344
4657
|
ok = JSON.parse(stdout).ok === true;
|
|
4345
4658
|
} catch {}
|
|
4346
|
-
|
|
4659
|
+
resolve22({ text, ok });
|
|
4347
4660
|
});
|
|
4348
|
-
child.on("error", (err) =>
|
|
4661
|
+
child.on("error", (err) => resolve22({ text: `Failed to run drift: ${err.message}`, ok: false }));
|
|
4349
4662
|
});
|
|
4350
4663
|
}
|
|
4351
4664
|
function toResult({ text, ok }) {
|
|
@@ -4503,8 +4816,8 @@ function diffArgs(command, args) {
|
|
|
4503
4816
|
|
|
4504
4817
|
// src/commands/release.ts
|
|
4505
4818
|
import { execSync as execSync4 } from "node:child_process";
|
|
4506
|
-
import { existsSync as
|
|
4507
|
-
import * as
|
|
4819
|
+
import { existsSync as existsSync15, readFileSync as readFileSync20 } from "node:fs";
|
|
4820
|
+
import * as path30 from "node:path";
|
|
4508
4821
|
import { computeDrift as computeDrift6 } from "@driftdev/sdk";
|
|
4509
4822
|
|
|
4510
4823
|
// src/formatters/release.ts
|
|
@@ -4552,11 +4865,11 @@ function getLastTag() {
|
|
|
4552
4865
|
}
|
|
4553
4866
|
}
|
|
4554
4867
|
function getPackageVersion(cwd) {
|
|
4555
|
-
const pkgPath =
|
|
4556
|
-
if (!
|
|
4868
|
+
const pkgPath = path30.join(cwd, "package.json");
|
|
4869
|
+
if (!existsSync15(pkgPath))
|
|
4557
4870
|
return null;
|
|
4558
4871
|
try {
|
|
4559
|
-
return JSON.parse(
|
|
4872
|
+
return JSON.parse(readFileSync20(pkgPath, "utf-8")).version ?? null;
|
|
4560
4873
|
} catch {
|
|
4561
4874
|
return null;
|
|
4562
4875
|
}
|
|
@@ -4568,7 +4881,7 @@ function registerReleaseCommand(program) {
|
|
|
4568
4881
|
const cwd = process.cwd();
|
|
4569
4882
|
try {
|
|
4570
4883
|
const { config } = loadConfig();
|
|
4571
|
-
const entryFile = entry ?
|
|
4884
|
+
const entryFile = entry ? path30.resolve(cwd, entry) : config.entry ? path30.resolve(cwd, config.entry) : detectEntry();
|
|
4572
4885
|
const { spec } = await cachedExtract(entryFile);
|
|
4573
4886
|
const exports = spec.exports ?? [];
|
|
4574
4887
|
const total = exports.length;
|
|
@@ -4692,8 +5005,8 @@ function renderReport(data) {
|
|
|
4692
5005
|
|
|
4693
5006
|
// src/utils/scan-packages.ts
|
|
4694
5007
|
import { execSync as execSync5 } from "node:child_process";
|
|
4695
|
-
import { existsSync as
|
|
4696
|
-
import * as
|
|
5008
|
+
import { existsSync as existsSync16, readFileSync as readFileSync21 } from "node:fs";
|
|
5009
|
+
import * as path31 from "node:path";
|
|
4697
5010
|
import { computeDrift as computeDrift7 } from "@driftdev/sdk";
|
|
4698
5011
|
function detectPackageDirs2(cwd) {
|
|
4699
5012
|
const workspaces = detectWorkspaces(cwd);
|
|
@@ -4712,14 +5025,14 @@ async function scanAllPackages(cwd) {
|
|
|
4712
5025
|
const packageDirs = detectPackageDirs2(cwd);
|
|
4713
5026
|
const results = [];
|
|
4714
5027
|
for (const dir of packageDirs) {
|
|
4715
|
-
const absDir = dir === "." ? cwd :
|
|
4716
|
-
if (!
|
|
5028
|
+
const absDir = dir === "." ? cwd : path31.join(cwd, dir);
|
|
5029
|
+
if (!existsSync16(absDir))
|
|
4717
5030
|
continue;
|
|
4718
5031
|
let name = dir;
|
|
4719
|
-
const pkgPath =
|
|
4720
|
-
if (
|
|
5032
|
+
const pkgPath = path31.join(absDir, "package.json");
|
|
5033
|
+
if (existsSync16(pkgPath)) {
|
|
4721
5034
|
try {
|
|
4722
|
-
const pkg = JSON.parse(
|
|
5035
|
+
const pkg = JSON.parse(readFileSync21(pkgPath, "utf-8"));
|
|
4723
5036
|
if (pkg.name)
|
|
4724
5037
|
name = pkg.name;
|
|
4725
5038
|
} catch {}
|
|
@@ -4815,7 +5128,7 @@ function registerReportCommand(program) {
|
|
|
4815
5128
|
}
|
|
4816
5129
|
|
|
4817
5130
|
// src/commands/scan.ts
|
|
4818
|
-
import * as
|
|
5131
|
+
import * as path32 from "node:path";
|
|
4819
5132
|
import {
|
|
4820
5133
|
buildExportRegistry as buildExportRegistry2,
|
|
4821
5134
|
computeDrift as computeDrift8,
|
|
@@ -4851,6 +5164,20 @@ function renderScan(data, next) {
|
|
|
4851
5164
|
}
|
|
4852
5165
|
lines.push("");
|
|
4853
5166
|
}
|
|
5167
|
+
if (data.docsCoverage) {
|
|
5168
|
+
lines.push(indent("Docs coverage"));
|
|
5169
|
+
lines.push(indent(c.gray(separator())));
|
|
5170
|
+
for (const page of data.docsCoverage.pages) {
|
|
5171
|
+
const mark = page.status === "fail" ? c.red(sym.x) : page.status === "warn" ? c.yellow("!") : c.green(sym.ok);
|
|
5172
|
+
const cts = page.counts;
|
|
5173
|
+
lines.push(indent(`${mark} ${page.page} ${c.dim(`${page.type}: ${cts.documented} documented, ${cts.code} in code, ${cts.gapsUserFacing} gaps, ${cts.ghosts} ghosts, ${cts.inversions} inversions`)}`));
|
|
5174
|
+
for (const f of page.failures)
|
|
5175
|
+
lines.push(indent(` ${c.red(f)}`));
|
|
5176
|
+
for (const w of page.warnings)
|
|
5177
|
+
lines.push(indent(` ${c.yellow(w)}`));
|
|
5178
|
+
}
|
|
5179
|
+
lines.push("");
|
|
5180
|
+
}
|
|
4854
5181
|
if (data.pass) {
|
|
4855
5182
|
lines.push(indent(`${c.green(sym.ok)} Scan passed`));
|
|
4856
5183
|
} else {
|
|
@@ -4884,7 +5211,7 @@ function renderBatchScan(data) {
|
|
|
4884
5211
|
|
|
4885
5212
|
// src/commands/scan.ts
|
|
4886
5213
|
function registerScanCommand(program) {
|
|
4887
|
-
program.command("scan [entry]").description("Run coverage + lint + prose drift in one pass").option("--min <n>", "Minimum health threshold (exit 1 if below)").option("--all", "Run across all workspace packages").option("--private", "Include private packages in --all mode").option("--lang <language>", "Source language (inferred from --spec/--abi/.clar; default typescript)").option("--abi <path>", "ABI JSON file (required for --lang clarity)").option("--spec <path>", "OpenAPI document: path or URL (implies --lang openapi)").option("--docs <patterns...>", "Markdown corpus for prose drift: glob patterns or directories (overrides repo-local defaults)").action(async (entry, options) => {
|
|
5214
|
+
program.command("scan [entry]").description("Run coverage + lint + prose drift in one pass").option("--min <n>", "Minimum health threshold (exit 1 if below)").option("--all", "Run across all workspace packages").option("--private", "Include private packages in --all mode").option("--lang <language>", "Source language (inferred from --spec/--abi/.clar; default typescript)").option("--abi <path>", "ABI JSON file (required for --lang clarity)").option("--spec <path>", "OpenAPI document: path or URL (implies --lang openapi)").option("--docs <patterns...>", "Markdown corpus for prose drift: glob patterns or directories (overrides repo-local defaults)").option("--docs-map <file>", "Docs map (page→type) activating key-coverage mode: gaps/ghosts/inversions per page").option("--annotations", "Emit GitHub Actions ::error/::warning annotations for findings").action(async (entry, options) => {
|
|
4888
5215
|
const startTime = Date.now();
|
|
4889
5216
|
const version = getVersion();
|
|
4890
5217
|
try {
|
|
@@ -4898,6 +5225,10 @@ function registerScanCommand(program) {
|
|
|
4898
5225
|
formatError("scan", `Batch mode (--all) not yet supported for ${lang}`, startTime, version);
|
|
4899
5226
|
return;
|
|
4900
5227
|
}
|
|
5228
|
+
if (options.all && options.docsMap) {
|
|
5229
|
+
formatError("scan", "--docs-map is not supported with --all", startTime, version);
|
|
5230
|
+
return;
|
|
5231
|
+
}
|
|
4901
5232
|
if (lang === "clarity" && !options.abi) {
|
|
4902
5233
|
formatError("scan", "--abi is required when --lang clarity", startTime, version);
|
|
4903
5234
|
return;
|
|
@@ -4959,9 +5290,9 @@ function registerScanCommand(program) {
|
|
|
4959
5290
|
return;
|
|
4960
5291
|
}
|
|
4961
5292
|
const { config } = loadConfig();
|
|
4962
|
-
let entryFile = entry ?
|
|
5293
|
+
let entryFile = entry ? path32.resolve(process.cwd(), entry) : undefined;
|
|
4963
5294
|
if (lang === "typescript" && !entryFile) {
|
|
4964
|
-
entryFile = config.entry ?
|
|
5295
|
+
entryFile = config.entry ? path32.resolve(process.cwd(), config.entry) : detectEntry();
|
|
4965
5296
|
}
|
|
4966
5297
|
const { apiSpec, packageName, packageVersion } = await resolveTruth({
|
|
4967
5298
|
entry: entryFile,
|
|
@@ -5017,6 +5348,11 @@ function registerScanCommand(program) {
|
|
|
5017
5348
|
formatWarning(`Prose drift skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
5018
5349
|
}
|
|
5019
5350
|
}
|
|
5351
|
+
let docsCoverage;
|
|
5352
|
+
if (options.docsMap) {
|
|
5353
|
+
const loaded = loadDocsMap(options.docsMap);
|
|
5354
|
+
docsCoverage = await runDocsCoverage(loaded, apiSpec);
|
|
5355
|
+
}
|
|
5020
5356
|
const healthIssues = issues.map((i) => ({ export: i.export, issue: i.issue }));
|
|
5021
5357
|
const h = computeHealth(total, documented, healthIssues);
|
|
5022
5358
|
let min = options.min ? parseInt(options.min, 10) : config.coverage?.min;
|
|
@@ -5024,7 +5360,7 @@ function registerScanCommand(program) {
|
|
|
5024
5360
|
const ratchet = computeRatchetMin(min);
|
|
5025
5361
|
min = ratchet.effectiveMin;
|
|
5026
5362
|
}
|
|
5027
|
-
const pass = min === undefined || h.health >= min;
|
|
5363
|
+
const pass = (min === undefined || h.health >= min) && (docsCoverage?.pass ?? true);
|
|
5028
5364
|
const data = {
|
|
5029
5365
|
coverage: {
|
|
5030
5366
|
score: coverageScore,
|
|
@@ -5037,7 +5373,26 @@ function registerScanCommand(program) {
|
|
|
5037
5373
|
health: h.health,
|
|
5038
5374
|
pass,
|
|
5039
5375
|
packageName,
|
|
5040
|
-
packageVersion
|
|
5376
|
+
packageVersion,
|
|
5377
|
+
...docsCoverage ? {
|
|
5378
|
+
docsCoverage: {
|
|
5379
|
+
pass: docsCoverage.pass,
|
|
5380
|
+
pages: docsCoverage.pages.map((p) => ({
|
|
5381
|
+
page: p.page,
|
|
5382
|
+
type: p.type,
|
|
5383
|
+
status: p.status,
|
|
5384
|
+
baselineGaps: p.baselineGaps,
|
|
5385
|
+
counts: p.result.counts,
|
|
5386
|
+
failures: p.failures,
|
|
5387
|
+
warnings: p.warnings,
|
|
5388
|
+
gaps: p.result.gaps,
|
|
5389
|
+
ghosts: p.result.ghosts,
|
|
5390
|
+
inversions: p.result.inversions,
|
|
5391
|
+
documentedKeysFromOtherTypes: p.result.documentedKeysFromOtherTypes,
|
|
5392
|
+
annotated: p.result.annotated
|
|
5393
|
+
}))
|
|
5394
|
+
}
|
|
5395
|
+
} : {}
|
|
5041
5396
|
};
|
|
5042
5397
|
let next;
|
|
5043
5398
|
if (issues.length > 0) {
|
|
@@ -5052,9 +5407,16 @@ function registerScanCommand(program) {
|
|
|
5052
5407
|
};
|
|
5053
5408
|
}
|
|
5054
5409
|
formatOutput("scan", data, startTime, version, renderScan, next);
|
|
5410
|
+
if (options.annotations && docsCoverage) {
|
|
5411
|
+
emitAnnotations(docsCoverage.annotations.errors, "error");
|
|
5412
|
+
emitAnnotations(docsCoverage.annotations.warnings, "warning");
|
|
5413
|
+
}
|
|
5414
|
+
if (options.annotations && issues.length > 0)
|
|
5415
|
+
emitAnnotations(issues);
|
|
5055
5416
|
if (!pass) {
|
|
5056
5417
|
if (!shouldRenderHuman()) {
|
|
5057
|
-
|
|
5418
|
+
const covFails = docsCoverage ? docsCoverage.pages.flatMap((p) => p.failures.map((f) => `${p.page}: ${f}`)) : [];
|
|
5419
|
+
process.stderr.write(`scan failed: health ${h.health}%${min !== undefined ? ` (need ${min}%)` : ""}, ${issues.length} issues${covFails.length > 0 ? `; docs coverage: ${covFails.join(" | ")}` : ""}
|
|
5058
5420
|
`);
|
|
5059
5421
|
}
|
|
5060
5422
|
process.exitCode = 1;
|
|
@@ -5105,8 +5467,8 @@ function registerSemverCommand(program) {
|
|
|
5105
5467
|
}
|
|
5106
5468
|
|
|
5107
5469
|
// src/commands/validate.ts
|
|
5108
|
-
import { readFileSync as
|
|
5109
|
-
import * as
|
|
5470
|
+
import { readFileSync as readFileSync22 } from "node:fs";
|
|
5471
|
+
import * as path33 from "node:path";
|
|
5110
5472
|
import { validateSpec } from "@openpkg-ts/spec";
|
|
5111
5473
|
|
|
5112
5474
|
// src/formatters/validate.ts
|
|
@@ -5132,8 +5494,8 @@ function registerValidateCommand(program) {
|
|
|
5132
5494
|
const startTime = Date.now();
|
|
5133
5495
|
const version = getVersion();
|
|
5134
5496
|
try {
|
|
5135
|
-
const filePath =
|
|
5136
|
-
const content =
|
|
5497
|
+
const filePath = path33.resolve(process.cwd(), file);
|
|
5498
|
+
const content = readFileSync22(filePath, "utf-8");
|
|
5137
5499
|
const spec = JSON.parse(content);
|
|
5138
5500
|
const result = validateSpec(spec);
|
|
5139
5501
|
const data = {
|
|
@@ -5184,7 +5546,11 @@ var COMMAND_EXAMPLES = {
|
|
|
5184
5546
|
validate: ["drift validate spec.json --json"],
|
|
5185
5547
|
filter: ["drift filter spec.json --kind function --json"],
|
|
5186
5548
|
report: ["drift report --json"],
|
|
5187
|
-
cache: ["drift cache status", "drift cache clear"]
|
|
5549
|
+
cache: ["drift cache status", "drift cache clear"],
|
|
5550
|
+
"docs-map": [
|
|
5551
|
+
"drift docs-map stub --docs docs/ --out drift.docs-map.json",
|
|
5552
|
+
"drift docs-map baseline drift.docs-map.json"
|
|
5553
|
+
]
|
|
5188
5554
|
};
|
|
5189
5555
|
function extractCapabilities(program) {
|
|
5190
5556
|
const commands = [];
|
|
@@ -5235,6 +5601,15 @@ function extractCapabilities(program) {
|
|
|
5235
5601
|
name: "examples",
|
|
5236
5602
|
description: "Example validation results",
|
|
5237
5603
|
operations: { read: "examples" }
|
|
5604
|
+
},
|
|
5605
|
+
{
|
|
5606
|
+
name: "docs-map",
|
|
5607
|
+
description: "Committed page→type map for docs key-coverage mode",
|
|
5608
|
+
operations: {
|
|
5609
|
+
create: "docs-map stub",
|
|
5610
|
+
update: "docs-map baseline",
|
|
5611
|
+
read: "scan --docs-map <file>"
|
|
5612
|
+
}
|
|
5238
5613
|
}
|
|
5239
5614
|
],
|
|
5240
5615
|
workflows: {
|
|
@@ -5251,6 +5626,10 @@ function extractCapabilities(program) {
|
|
|
5251
5626
|
"pre-release": {
|
|
5252
5627
|
steps: ["scan", "breaking", "release"],
|
|
5253
5628
|
description: "Full pre-release quality gate"
|
|
5629
|
+
},
|
|
5630
|
+
"docs-key-coverage": {
|
|
5631
|
+
steps: ["docs-map stub", "scan --docs-map", "docs-map baseline"],
|
|
5632
|
+
description: "Gap/ghost/inversion gate: docs pages vs spec type keys"
|
|
5254
5633
|
}
|
|
5255
5634
|
}
|
|
5256
5635
|
};
|
|
@@ -5258,13 +5637,13 @@ function extractCapabilities(program) {
|
|
|
5258
5637
|
|
|
5259
5638
|
// src/drift.ts
|
|
5260
5639
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
5261
|
-
var __dirname3 =
|
|
5262
|
-
var packageJson = JSON.parse(
|
|
5640
|
+
var __dirname3 = path34.dirname(__filename2);
|
|
5641
|
+
var packageJson = JSON.parse(readFileSync23(path34.join(__dirname3, "../package.json"), "utf-8"));
|
|
5263
5642
|
var program = new Command;
|
|
5264
5643
|
program.name("drift").description("drift — detect when your docs drift from your code").version(packageJson.version).option("--json", "Force JSON output (default when piped)").option("--human", "Force human-readable output (default in terminal)").option("--config <path>", "Path to drift config file").option("--cwd <dir>", "Run as if started in <dir>").option("--no-cache", "Bypass spec cache").option("--tools", "List all available tools for agent use (JSON)").hook("preAction", (_thisCommand) => {
|
|
5265
5644
|
const opts = program.opts();
|
|
5266
5645
|
if (opts.cwd) {
|
|
5267
|
-
process.chdir(
|
|
5646
|
+
process.chdir(path34.resolve(opts.cwd));
|
|
5268
5647
|
}
|
|
5269
5648
|
setOutputMode({ json: opts.json, human: opts.human });
|
|
5270
5649
|
setConfigPath(opts.config);
|
|
@@ -5276,6 +5655,7 @@ registerExtractCommand(program);
|
|
|
5276
5655
|
registerListCommand(program);
|
|
5277
5656
|
registerGetCommand(program);
|
|
5278
5657
|
registerValidateCommand(program);
|
|
5658
|
+
registerDocsMapCommand(program);
|
|
5279
5659
|
registerFilterCommand(program);
|
|
5280
5660
|
registerCoverageCommand(program);
|
|
5281
5661
|
registerExamplesCommand(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@driftdev/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "Drift CLI - detect when your docs drift from your code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@driftdev/clarity-adapter": "^1.0.1",
|
|
49
49
|
"@driftdev/openapi-adapter": "^1.0.1",
|
|
50
|
-
"@driftdev/sdk": "^1.
|
|
50
|
+
"@driftdev/sdk": "^1.12.0",
|
|
51
51
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
52
52
|
"@openpkg-ts/sdk": "^0.43.0",
|
|
53
53
|
"@openpkg-ts/spec": "^0.43.0",
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://unpkg.com/@driftdev/cli/schemas/drift.docs-map.schema.json",
|
|
4
|
+
"title": "Drift docs map",
|
|
5
|
+
"description": "Page→type mapping for docs key-coverage mode (drift scan --docs-map). The committed artifact of the LLM-writes-the-map / machine-runs-the-map split: an agent may propose it, a human commits it, CI runs it deterministically.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["version", "pages"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": { "type": "string" },
|
|
10
|
+
"version": { "const": 1 },
|
|
11
|
+
"pages": {
|
|
12
|
+
"type": "array",
|
|
13
|
+
"items": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"required": ["page", "type"],
|
|
16
|
+
"properties": {
|
|
17
|
+
"page": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Docs page path, relative to this file's directory"
|
|
20
|
+
},
|
|
21
|
+
"extraPages": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"items": { "type": "string" },
|
|
24
|
+
"description": "Additional pages/globs merged into the same corpus (e.g. _snippets/*.mdx)"
|
|
25
|
+
},
|
|
26
|
+
"type": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "Spec type whose property keys this page documents"
|
|
29
|
+
},
|
|
30
|
+
"spec": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "Committed spec file to diff against (relative to this file). Mutually exclusive with entry; omit both to use the scan target."
|
|
33
|
+
},
|
|
34
|
+
"entry": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Entry file to extract the spec from. Mutually exclusive with spec."
|
|
37
|
+
},
|
|
38
|
+
"sectionRe": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "Case-insensitive heading regex opening an options section (default: option|config)"
|
|
41
|
+
},
|
|
42
|
+
"internal": {
|
|
43
|
+
"type": "array",
|
|
44
|
+
"items": { "type": "string" },
|
|
45
|
+
"description": "Internal keys beyond the _-prefix convention (excluded from user-facing gaps)"
|
|
46
|
+
},
|
|
47
|
+
"deprecated": {
|
|
48
|
+
"type": "array",
|
|
49
|
+
"items": { "type": "string" },
|
|
50
|
+
"description": "Deprecated override — auto-derived from spec metadata when omitted"
|
|
51
|
+
},
|
|
52
|
+
"replacements": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"additionalProperties": { "type": "string" },
|
|
55
|
+
"description": "deprecatedKey → replacementKey override — auto-derived from spec deprecation reasons when omitted"
|
|
56
|
+
},
|
|
57
|
+
"annotations": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"additionalProperties": {
|
|
60
|
+
"enum": ["prose-documented", "internal-by-convention", "ignore"]
|
|
61
|
+
},
|
|
62
|
+
"description": "Agent-proposed, human-committed key classifications. prose-documented: genuinely documented in prose, excluded from gap FAIL; internal-by-convention: treated as internal; ignore: excluded entirely (state the reason in a nearby comment key)."
|
|
63
|
+
},
|
|
64
|
+
"baselineGaps": {
|
|
65
|
+
"type": "integer",
|
|
66
|
+
"minimum": 0,
|
|
67
|
+
"description": "Gap ratchet: CI fails when user-facing gaps exceed this committed count. Drift shrinks, never grows — use `drift docs-map baseline` to tighten."
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"additionalProperties": true
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"additionalProperties": true
|
|
75
|
+
}
|