@driftdev/cli 1.3.0 → 1.5.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 (3) hide show
  1. package/README.md +10 -8
  2. package/dist/drift.js +674 -205
  3. package/package.json +23 -19
package/dist/drift.js CHANGED
@@ -1,13 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
3
  var __defProp = Object.defineProperty;
4
+ var __returnValue = (v) => v;
5
+ function __exportSetter(name, newValue) {
6
+ this[name] = __returnValue.bind(null, newValue);
7
+ }
4
8
  var __export = (target, all) => {
5
9
  for (var name in all)
6
10
  __defProp(target, name, {
7
11
  get: all[name],
8
12
  enumerable: true,
9
13
  configurable: true,
10
- set: (newValue) => all[name] = () => newValue
14
+ set: __exportSetter.bind(all, name)
11
15
  });
12
16
  };
13
17
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -48,7 +52,7 @@ var init_global = () => {};
48
52
 
49
53
  // src/drift.ts
50
54
  import { readFileSync as readFileSync22 } from "node:fs";
51
- import * as path29 from "node:path";
55
+ import * as path30 from "node:path";
52
56
  import { fileURLToPath as fileURLToPath2 } from "node:url";
53
57
  import { Command } from "commander";
54
58
 
@@ -1090,7 +1094,7 @@ function getVersion() {
1090
1094
  } catch {
1091
1095
  cached = "0.0.0";
1092
1096
  }
1093
- return cached;
1097
+ return cached ?? "0.0.0";
1094
1098
  }
1095
1099
 
1096
1100
  // src/commands/breaking.ts
@@ -1161,26 +1165,6 @@ function registerBreakingCommand(program) {
1161
1165
  });
1162
1166
  }
1163
1167
 
1164
- // src/commands/commands.ts
1165
- var GROUPS = {
1166
- Composed: ["scan", "ci", "health"],
1167
- Analysis: ["coverage", "lint", "examples"],
1168
- Extraction: ["extract", "list", "get"],
1169
- Comparison: ["diff", "breaking", "semver", "changelog"],
1170
- Setup: ["init", "config", "context"],
1171
- Plumbing: ["validate", "filter", "cache", "report", "release"]
1172
- };
1173
- function registerCommandsCommand(program) {
1174
- program.command("commands").description("List all available commands grouped by category").action(() => {
1175
- const maxGroup = Math.max(...Object.keys(GROUPS).map((g) => g.length));
1176
- for (const [group, cmds] of Object.entries(GROUPS)) {
1177
- const pad2 = " ".repeat(maxGroup - group.length);
1178
- process.stdout.write(` ${group}${pad2} ${cmds.join(", ")}
1179
- `);
1180
- }
1181
- });
1182
- }
1183
-
1184
1168
  // src/formatters/cache.ts
1185
1169
  function renderCacheStatus(data) {
1186
1170
  const lines = [];
@@ -1974,6 +1958,26 @@ function registerCiCommand(program) {
1974
1958
  });
1975
1959
  }
1976
1960
 
1961
+ // src/commands/commands.ts
1962
+ var GROUPS = {
1963
+ Composed: ["scan", "ci", "health"],
1964
+ Analysis: ["coverage", "lint", "examples"],
1965
+ Extraction: ["extract", "list", "get"],
1966
+ Comparison: ["diff", "breaking", "semver", "changelog"],
1967
+ Setup: ["init", "config", "context"],
1968
+ Plumbing: ["validate", "filter", "cache", "report", "release"]
1969
+ };
1970
+ function registerCommandsCommand(program) {
1971
+ program.command("commands").description("List all available commands grouped by category").action(() => {
1972
+ const maxGroup = Math.max(...Object.keys(GROUPS).map((g) => g.length));
1973
+ for (const [group, cmds] of Object.entries(GROUPS)) {
1974
+ const pad2 = " ".repeat(maxGroup - group.length);
1975
+ process.stdout.write(` ${group}${pad2} ${cmds.join(", ")}
1976
+ `);
1977
+ }
1978
+ });
1979
+ }
1980
+
1977
1981
  // src/commands/config.ts
1978
1982
  init_global();
1979
1983
  import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "node:fs";
@@ -2255,7 +2259,7 @@ function registerContextCommand(program) {
2255
2259
  }
2256
2260
 
2257
2261
  // src/commands/coverage.ts
2258
- import * as path15 from "node:path";
2262
+ import * as path16 from "node:path";
2259
2263
 
2260
2264
  // src/formatters/coverage.ts
2261
2265
  function renderCoverage(data) {
@@ -2282,12 +2286,114 @@ function renderCoverage(data) {
2282
2286
  `);
2283
2287
  }
2284
2288
 
2289
+ // src/utils/load-spec.ts
2290
+ import { existsSync as existsSync12, readFileSync as readFileSync13 } from "node:fs";
2291
+ import * as path15 from "node:path";
2292
+ import { fromSource } from "@driftdev/clarity-adapter";
2293
+ import { fromDocument } from "@driftdev/openapi-adapter";
2294
+ function getPackageInfo(cwd) {
2295
+ const pkgPath = path15.join(cwd, "package.json");
2296
+ if (!existsSync12(pkgPath))
2297
+ return {};
2298
+ try {
2299
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
2300
+ return { name: pkg.name, version: pkg.version };
2301
+ } catch (err) {
2302
+ formatWarning(`Could not parse package.json${err instanceof Error ? `: ${err.message}` : ""}`);
2303
+ return {};
2304
+ }
2305
+ }
2306
+ function resolveLang(opts) {
2307
+ if (opts.lang) {
2308
+ if (opts.lang !== "typescript" && opts.lang !== "clarity" && opts.lang !== "openapi") {
2309
+ throw new Error(`Unknown language: ${opts.lang}`);
2310
+ }
2311
+ return opts.lang;
2312
+ }
2313
+ if (opts.spec)
2314
+ return "openapi";
2315
+ if (opts.abi || opts.entry?.endsWith(".clar"))
2316
+ return "clarity";
2317
+ return "typescript";
2318
+ }
2319
+ function isUrl(value) {
2320
+ return /^https?:\/\//.test(value);
2321
+ }
2322
+ async function readSpecDocument(specPath) {
2323
+ if (isUrl(specPath)) {
2324
+ const res = await fetch(specPath, { signal: AbortSignal.timeout(30000) });
2325
+ if (!res.ok)
2326
+ throw new Error(`Failed to fetch spec: ${res.status} ${res.statusText} (${specPath})`);
2327
+ return res.text();
2328
+ }
2329
+ if (!existsSync12(specPath))
2330
+ throw new Error(`Spec file not found: ${specPath}`);
2331
+ return readFileSync13(specPath, "utf-8");
2332
+ }
2333
+ async function resolveTruth(opts) {
2334
+ const lang = resolveLang(opts);
2335
+ if (lang === "openapi") {
2336
+ if (!opts.spec)
2337
+ throw new Error("--spec is required when --lang openapi");
2338
+ const specPath = isUrl(opts.spec) ? opts.spec : path15.resolve(process.cwd(), opts.spec);
2339
+ const document = await readSpecDocument(specPath);
2340
+ const name = isUrl(specPath) ? new URL(specPath).pathname.split("/").pop()?.replace(/\.[^.]*$/, "") || "openapi" : path15.basename(specPath, path15.extname(specPath));
2341
+ const apiSpec = fromDocument(document);
2342
+ if (!apiSpec.meta.name || apiSpec.meta.name === "openapi")
2343
+ apiSpec.meta.name = name;
2344
+ return {
2345
+ apiSpec,
2346
+ packageName: apiSpec.meta.name,
2347
+ packageVersion: apiSpec.meta.version,
2348
+ lang
2349
+ };
2350
+ }
2351
+ if (lang === "clarity") {
2352
+ if (!opts.abi)
2353
+ throw new Error("--abi is required when --lang clarity");
2354
+ if (!opts.entry)
2355
+ throw new Error("Entry file required for --lang clarity");
2356
+ const entryFile = path15.resolve(process.cwd(), opts.entry);
2357
+ const abiPath = path15.resolve(process.cwd(), opts.abi);
2358
+ if (!existsSync12(entryFile))
2359
+ throw new Error(`Source file not found: ${entryFile}`);
2360
+ if (!existsSync12(abiPath))
2361
+ throw new Error(`ABI file not found: ${abiPath}`);
2362
+ const source = readFileSync13(entryFile, "utf-8");
2363
+ const abi = JSON.parse(readFileSync13(abiPath, "utf-8"));
2364
+ const name = path15.basename(entryFile, path15.extname(entryFile));
2365
+ const pkg2 = getPackageInfo(process.cwd());
2366
+ const apiSpec = fromSource(source, abi, { name, version: pkg2.version });
2367
+ return { apiSpec, packageName: pkg2.name ?? name, packageVersion: pkg2.version, lang };
2368
+ }
2369
+ if (!opts.entry)
2370
+ throw new Error("Entry file required");
2371
+ const { spec } = await cachedExtract(opts.entry);
2372
+ const pkg = getPackageInfo(process.cwd());
2373
+ return {
2374
+ apiSpec: spec,
2375
+ packageName: pkg.name,
2376
+ packageVersion: pkg.version,
2377
+ lang
2378
+ };
2379
+ }
2380
+
2285
2381
  // src/commands/coverage.ts
2286
2382
  function registerCoverageCommand(program) {
2287
- program.command("coverage [entry]").description("Measure documentation coverage for a TypeScript entry file").option("--min <n>", "Minimum coverage threshold (exit 1 if below)").option("--all", "Run across all workspace packages").option("--private", "Include private packages in --all mode").action(async (entry, options) => {
2383
+ program.command("coverage [entry]").description("Measure documentation coverage").option("--min <n>", "Minimum coverage 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)").action(async (entry, options) => {
2288
2384
  const startTime = Date.now();
2289
2385
  const version = getVersion();
2290
2386
  try {
2387
+ const lang = resolveLang({
2388
+ entry,
2389
+ lang: options.lang,
2390
+ spec: options.spec,
2391
+ abi: options.abi
2392
+ });
2393
+ if (lang !== "typescript" && options.all) {
2394
+ formatError("coverage", `Batch mode (--all) not yet supported for ${lang}`, startTime, version);
2395
+ return;
2396
+ }
2291
2397
  if (options.all) {
2292
2398
  const allPackages = discoverPackages(process.cwd());
2293
2399
  if (!allPackages || allPackages.length === 0) {
@@ -2329,8 +2435,16 @@ function registerCoverageCommand(program) {
2329
2435
  return;
2330
2436
  }
2331
2437
  const { config } = loadConfig();
2332
- const entryFile = entry ? path15.resolve(process.cwd(), entry) : config.entry ? path15.resolve(process.cwd(), config.entry) : detectEntry();
2333
- const { spec } = await cachedExtract(entryFile);
2438
+ let entryFile = entry ? path16.resolve(process.cwd(), entry) : undefined;
2439
+ if (lang === "typescript" && !entryFile) {
2440
+ entryFile = config.entry ? path16.resolve(process.cwd(), config.entry) : detectEntry();
2441
+ }
2442
+ const { apiSpec: spec } = await resolveTruth({
2443
+ entry: entryFile,
2444
+ lang,
2445
+ spec: options.spec,
2446
+ abi: options.abi
2447
+ });
2334
2448
  const exports = spec.exports ?? [];
2335
2449
  const total = exports.length;
2336
2450
  const undocumented = [];
@@ -2378,7 +2492,7 @@ function registerCoverageCommand(program) {
2378
2492
  }
2379
2493
 
2380
2494
  // src/commands/diff.ts
2381
- import * as path16 from "node:path";
2495
+ import * as path17 from "node:path";
2382
2496
  import { extract as extract5 } from "@openpkg-ts/sdk";
2383
2497
  import { categorizeBreakingChanges as categorizeBreakingChanges4, diffSpec as diffSpec4, normalize as normalize5 } from "@openpkg-ts/spec";
2384
2498
 
@@ -2452,7 +2566,7 @@ function registerDiffCommand(program) {
2452
2566
  let totalAdded = 0;
2453
2567
  let totalChanged = 0;
2454
2568
  for (const pkg of packages) {
2455
- const relEntry = path16.relative(cwd, pkg.entry);
2569
+ const relEntry = path17.relative(cwd, pkg.entry);
2456
2570
  const oldSpec2 = await extractSpecFromRef(options.base, relEntry, cwd);
2457
2571
  const newSpec2 = options.head ? await extractSpecFromRef(options.head, relEntry, cwd) : normalize5((await extract5({ entryFile: pkg.entry })).spec);
2458
2572
  const diff2 = diffSpec4(oldSpec2, newSpec2);
@@ -2522,8 +2636,8 @@ function registerDiffCommand(program) {
2522
2636
  }
2523
2637
 
2524
2638
  // src/commands/examples.ts
2525
- import { readFileSync as readFileSync13 } from "node:fs";
2526
- import * as path17 from "node:path";
2639
+ import { readFileSync as readFileSync14 } from "node:fs";
2640
+ import * as path18 from "node:path";
2527
2641
  import { validateExamples } from "@driftdev/sdk";
2528
2642
 
2529
2643
  // src/formatters/examples.ts
@@ -2602,16 +2716,16 @@ function renderExamples(data) {
2602
2716
 
2603
2717
  // src/commands/examples.ts
2604
2718
  function findPackagePath(entryFile) {
2605
- let dir = path17.dirname(entryFile);
2606
- while (dir !== path17.dirname(dir)) {
2719
+ let dir = path18.dirname(entryFile);
2720
+ while (dir !== path18.dirname(dir)) {
2607
2721
  try {
2608
- readFileSync13(path17.join(dir, "package.json"), "utf-8");
2722
+ readFileSync14(path18.join(dir, "package.json"), "utf-8");
2609
2723
  return dir;
2610
2724
  } catch {
2611
- dir = path17.dirname(dir);
2725
+ dir = path18.dirname(dir);
2612
2726
  }
2613
2727
  }
2614
- return path17.dirname(entryFile);
2728
+ return path18.dirname(entryFile);
2615
2729
  }
2616
2730
  function registerExamplesCommand(program) {
2617
2731
  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) => {
@@ -2679,7 +2793,7 @@ function registerExamplesCommand(program) {
2679
2793
  return;
2680
2794
  }
2681
2795
  const { config } = loadConfig();
2682
- const entryFile = entry ? path17.resolve(process.cwd(), entry) : config.entry ? path17.resolve(process.cwd(), config.entry) : detectEntry();
2796
+ const entryFile = entry ? path18.resolve(process.cwd(), entry) : config.entry ? path18.resolve(process.cwd(), config.entry) : detectEntry();
2683
2797
  const { spec } = await cachedExtract(entryFile);
2684
2798
  const exports = spec.exports ?? [];
2685
2799
  const packagePath = findPackagePath(entryFile);
@@ -2714,7 +2828,7 @@ function registerExamplesCommand(program) {
2714
2828
  }
2715
2829
 
2716
2830
  // src/commands/extract.ts
2717
- import * as path18 from "node:path";
2831
+ import * as path19 from "node:path";
2718
2832
  import { Drift } from "@driftdev/sdk";
2719
2833
  import { normalize as normalize6 } from "@openpkg-ts/spec";
2720
2834
 
@@ -2731,10 +2845,37 @@ function renderExtract(data) {
2731
2845
 
2732
2846
  // src/commands/extract.ts
2733
2847
  function registerExtractCommand(program) {
2734
- program.command("extract [entry]").description("Extract OpenPkg spec from TypeScript entry file").option("-o, --output <file>", "Write JSON to file instead of stdout").option("--only <patterns>", "Include exports matching glob (comma-separated)").option("--ignore <patterns>", "Exclude exports matching glob (comma-separated)").option("--max-depth <n>", "Max type resolution depth", "10").option("--all", "Extract from all workspace packages").option("--private", "Include private packages in --all mode").action(async (entry, options) => {
2848
+ program.command("extract [entry]").description("Extract API spec from a source of truth (TypeScript, Clarity, OpenAPI)").option("-o, --output <file>", "Write JSON to file instead of stdout").option("--only <patterns>", "Include exports matching glob (comma-separated)").option("--ignore <patterns>", "Exclude exports matching glob (comma-separated)").option("--max-depth <n>", "Max type resolution depth", "10").option("--all", "Extract from 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)").action(async (entry, options) => {
2735
2849
  const startTime = Date.now();
2736
2850
  const version = getVersion();
2737
2851
  try {
2852
+ const lang = resolveLang({
2853
+ entry,
2854
+ lang: options.lang,
2855
+ spec: options.spec,
2856
+ abi: options.abi
2857
+ });
2858
+ if (lang !== "typescript") {
2859
+ if (options.all || options.only || options.ignore) {
2860
+ formatError("extract", `--all/--only/--ignore not yet supported for ${lang}`, startTime, version);
2861
+ return;
2862
+ }
2863
+ const { apiSpec } = await resolveTruth({
2864
+ entry,
2865
+ lang,
2866
+ spec: options.spec,
2867
+ abi: options.abi
2868
+ });
2869
+ if (options.output) {
2870
+ const { writeFileSync: writeFileSync5 } = await import("node:fs");
2871
+ writeFileSync5(options.output, JSON.stringify(apiSpec, null, 2));
2872
+ process.stderr.write(`drift extract: wrote ${options.output}
2873
+ `);
2874
+ } else {
2875
+ formatOutput("extract", apiSpec, startTime, version, renderExtract);
2876
+ }
2877
+ return;
2878
+ }
2738
2879
  if (options.all) {
2739
2880
  const allPackages = discoverPackages(process.cwd());
2740
2881
  if (!allPackages || allPackages.length === 0) {
@@ -2755,7 +2896,7 @@ function registerExtractCommand(program) {
2755
2896
  formatOutput("extract", { packages: specs, ...skipped.length > 0 ? { skipped } : {} }, startTime, version);
2756
2897
  return;
2757
2898
  }
2758
- const entryFile = entry ? path18.resolve(process.cwd(), entry) : detectEntry();
2899
+ const entryFile = entry ? path19.resolve(process.cwd(), entry) : detectEntry();
2759
2900
  const hasFilters = !!(options.only || options.ignore);
2760
2901
  let spec;
2761
2902
  if (hasFilters) {
@@ -2794,8 +2935,8 @@ function registerExtractCommand(program) {
2794
2935
  }
2795
2936
 
2796
2937
  // src/commands/filter.ts
2797
- import { readFileSync as readFileSync14 } from "node:fs";
2798
- import * as path19 from "node:path";
2938
+ import { readFileSync as readFileSync15 } from "node:fs";
2939
+ import * as path20 from "node:path";
2799
2940
  import { filterSpec } from "@openpkg-ts/sdk";
2800
2941
 
2801
2942
  // src/formatters/filter.ts
@@ -2826,8 +2967,8 @@ function registerFilterCommand(program) {
2826
2967
  const startTime = Date.now();
2827
2968
  const version = getVersion();
2828
2969
  try {
2829
- const filePath = path19.resolve(process.cwd(), file);
2830
- const content = readFileSync14(filePath, "utf-8");
2970
+ const filePath = path20.resolve(process.cwd(), file);
2971
+ const content = readFileSync15(filePath, "utf-8");
2831
2972
  const spec = JSON.parse(content);
2832
2973
  const criteria = {};
2833
2974
  if (options.kind) {
@@ -2853,13 +2994,14 @@ function registerFilterCommand(program) {
2853
2994
  }
2854
2995
 
2855
2996
  // src/commands/get.ts
2856
- import * as path20 from "node:path";
2997
+ import * as path21 from "node:path";
2857
2998
  import { getExport, listExports } from "@openpkg-ts/sdk";
2858
2999
 
2859
3000
  // src/formatters/get.ts
2860
3001
  function renderGet(data) {
2861
3002
  const lines = [""];
2862
3003
  const exp = data.export;
3004
+ const schema = typeof exp.schema === "object" && exp.schema !== null ? exp.schema : undefined;
2863
3005
  lines.push(indent(`${c.bold(exp.name)}${" ".repeat(Math.max(2, 50 - exp.name.length))}${c.gray(exp.kind)}`));
2864
3006
  if (exp.deprecated) {
2865
3007
  lines.push(indent(c.yellow("deprecated")));
@@ -2872,24 +3014,24 @@ function renderGet(data) {
2872
3014
  lines.push(indent(` ${exp.signature}`));
2873
3015
  lines.push("");
2874
3016
  }
2875
- const params = exp.parameters ?? extractParams(exp.schema);
3017
+ const params = exp.parameters ?? extractParams(schema);
2876
3018
  if (params.length > 0) {
2877
3019
  lines.push(indent(c.gray(" PARAMETERS")));
2878
3020
  for (const p of params) {
2879
3021
  const req = p.required ? "required" : "optional";
2880
3022
  const type = p.type ?? "unknown";
2881
3023
  const desc = p.description ? ` ${c.dim(JSON.stringify(p.description))}` : "";
2882
- lines.push(indent(` ${padRight(p.name, 16)}${padRight(type, 24)}${c.gray(req)}${desc}`));
3024
+ lines.push(indent(` ${padRight(p.name ?? "", 16)}${padRight(type, 24)}${c.gray(req)}${desc}`));
2883
3025
  }
2884
3026
  lines.push("");
2885
3027
  }
2886
- const returns = exp.returns ?? extractReturns(exp.schema);
3028
+ const returns = exp.returns ?? extractReturns(schema);
2887
3029
  if (returns) {
2888
3030
  lines.push(indent(c.gray(" RETURNS")));
2889
3031
  lines.push(indent(` ${returns.type ?? "void"}`));
2890
3032
  lines.push("");
2891
3033
  }
2892
- const members = exp.members ?? extractMembers(exp.schema);
3034
+ const members = exp.members ?? extractMembers(schema);
2893
3035
  if (members.length > 0) {
2894
3036
  const shown = members.slice(0, 50);
2895
3037
  const remaining = members.length - shown.length;
@@ -2897,7 +3039,7 @@ function renderGet(data) {
2897
3039
  for (const m of shown) {
2898
3040
  const req = m.required ? "required" : "optional";
2899
3041
  const type = m.type ?? "";
2900
- lines.push(indent(` ${padRight(m.name, 20)}${padRight(type, 20)}${c.gray(req)}`));
3042
+ lines.push(indent(` ${padRight(m.name ?? "", 20)}${padRight(type, 20)}${c.gray(req)}`));
2901
3043
  }
2902
3044
  if (remaining > 0) {
2903
3045
  lines.push(indent(c.gray(` ... ${remaining} more`)));
@@ -2962,6 +3104,8 @@ function extractPropsFromSchema(schema) {
2962
3104
  function formatType(schema) {
2963
3105
  if (!schema)
2964
3106
  return "unknown";
3107
+ if (typeof schema === "string")
3108
+ return schema;
2965
3109
  if (schema.$ref)
2966
3110
  return schema.$ref.replace("#/types/", "");
2967
3111
  if (schema.type === "array" && schema.items)
@@ -3033,14 +3177,72 @@ function looksLikeFilePath(s) {
3033
3177
 
3034
3178
  // src/commands/get.ts
3035
3179
  function registerGetCommand(program) {
3036
- program.command("get <nameOrEntry> [name]").description("Get detailed spec for a single export").action(async (nameOrEntry, name) => {
3180
+ program.command("get <nameOrEntry> [name]").description("Get detailed spec for a single export").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)").action(async (nameOrEntry, name, options = {}) => {
3037
3181
  const startTime = Date.now();
3038
3182
  const version = getVersion();
3039
3183
  try {
3184
+ const lang = resolveLang({
3185
+ entry: name ? nameOrEntry : undefined,
3186
+ lang: options.lang,
3187
+ spec: options.spec,
3188
+ abi: options.abi
3189
+ });
3190
+ if (lang !== "typescript") {
3191
+ const entryArg = name ? nameOrEntry : undefined;
3192
+ const exportName2 = name ?? nameOrEntry;
3193
+ const { apiSpec } = await resolveTruth({
3194
+ entry: entryArg,
3195
+ lang,
3196
+ spec: options.spec,
3197
+ abi: options.abi
3198
+ });
3199
+ const allExports = apiSpec.exports ?? [];
3200
+ const exp = allExports.find((e) => e.name === exportName2 || e.id === exportName2);
3201
+ if (!exp) {
3202
+ const suggestions = fuzzyTop(exportName2, allExports);
3203
+ renderNotFound(exportName2, suggestions, startTime, version);
3204
+ return;
3205
+ }
3206
+ const sig = exp.signatures?.[0];
3207
+ const data = {
3208
+ export: {
3209
+ name: exp.name,
3210
+ kind: exp.kind,
3211
+ ...exp.description ? { description: exp.description } : {},
3212
+ ...exp.deprecated ? { deprecated: true } : {},
3213
+ ...sig?.parameters ? {
3214
+ parameters: sig.parameters.map((p) => ({
3215
+ name: p.name,
3216
+ type: schemaTypeString(p.schema),
3217
+ required: p.required,
3218
+ ...p.description ? { description: p.description } : {},
3219
+ schema: p.schema
3220
+ }))
3221
+ } : {},
3222
+ ...sig?.returns ? {
3223
+ returns: {
3224
+ type: schemaTypeString(sig.returns.schema),
3225
+ ...sig.returns.description ? { description: sig.returns.description } : {},
3226
+ schema: sig.returns.schema
3227
+ }
3228
+ } : {},
3229
+ ...exp.members ? {
3230
+ members: exp.members.map((m) => ({
3231
+ name: m.name,
3232
+ ...m.description ? { description: m.description } : {}
3233
+ }))
3234
+ } : {},
3235
+ ...exp.schema ? { schema: exp.schema } : {},
3236
+ ...exp.flags ? { flags: exp.flags } : {}
3237
+ }
3238
+ };
3239
+ formatOutput("get", data, startTime, version, renderGet);
3240
+ return;
3241
+ }
3040
3242
  let entryFile;
3041
3243
  let exportName;
3042
3244
  if (name) {
3043
- entryFile = path20.resolve(process.cwd(), nameOrEntry);
3245
+ entryFile = path21.resolve(process.cwd(), nameOrEntry);
3044
3246
  exportName = name;
3045
3247
  } else {
3046
3248
  entryFile = detectEntry();
@@ -3050,39 +3252,63 @@ function registerGetCommand(program) {
3050
3252
  if (!result.export) {
3051
3253
  const listResult = await listExports({ entryFile });
3052
3254
  const suggestions = fuzzyTop(exportName, listResult.exports);
3053
- if (suggestions.length > 0 && shouldRenderHuman()) {
3054
- const lines = [
3055
- "",
3056
- indent(`${c.red("x")} Export "${exportName}" not found.`),
3057
- "",
3058
- indent(c.gray("Similar:"))
3059
- ];
3060
- for (const s of suggestions) {
3061
- lines.push(indent(` ${s}`));
3062
- }
3063
- lines.push("");
3064
- lines.push(indent(`drift get ${suggestions[0]}`));
3065
- lines.push("");
3066
- process.stdout.write(lines.join(`
3067
- `));
3068
- process.exitCode = 1;
3069
- } else if (suggestions.length > 0) {
3070
- formatError("get", `Export '${exportName}' not found. Similar: ${suggestions.join(", ")}`, startTime, version);
3071
- } else {
3072
- formatError("get", `Export '${exportName}' not found`, startTime, version);
3073
- }
3255
+ renderNotFound(exportName, suggestions, startTime, version);
3074
3256
  return;
3075
3257
  }
3076
- formatOutput("get", { export: result.export, types: result.types }, startTime, version, renderGet);
3258
+ const types = Object.fromEntries((result.types ?? []).map((t) => [
3259
+ t.name,
3260
+ t.schema ?? {}
3261
+ ]));
3262
+ formatOutput("get", { export: result.export, types }, startTime, version, renderGet);
3077
3263
  } catch (err) {
3078
3264
  formatError("get", err instanceof Error ? err.message : String(err), startTime, version);
3079
3265
  }
3080
3266
  });
3081
3267
  }
3268
+ function schemaTypeString(schema) {
3269
+ if (schema === undefined || schema === null)
3270
+ return;
3271
+ if (typeof schema === "string")
3272
+ return schema;
3273
+ if (typeof schema === "object") {
3274
+ const s = schema;
3275
+ if (typeof s.$ref === "string")
3276
+ return s.$ref.split("/").pop();
3277
+ if (typeof s.type === "string")
3278
+ return s.type;
3279
+ if (Array.isArray(s.oneOf))
3280
+ return "oneOf";
3281
+ if (Array.isArray(s.anyOf))
3282
+ return "anyOf";
3283
+ }
3284
+ return;
3285
+ }
3286
+ function renderNotFound(exportName, suggestions, startTime, version) {
3287
+ if (suggestions.length > 0 && shouldRenderHuman()) {
3288
+ const lines = [
3289
+ "",
3290
+ indent(`${c.red("x")} Export "${exportName}" not found.`),
3291
+ "",
3292
+ indent(c.gray("Similar:"))
3293
+ ];
3294
+ for (const s of suggestions) {
3295
+ lines.push(indent(` ${s}`));
3296
+ }
3297
+ lines.push("");
3298
+ lines.push(indent(`drift get ${suggestions[0]}`));
3299
+ lines.push("");
3300
+ process.stdout.write(lines.join(`
3301
+ `));
3302
+ process.exitCode = 1;
3303
+ } else if (suggestions.length > 0) {
3304
+ formatError("get", `Export '${exportName}' not found. Similar: ${suggestions.join(", ")}`, startTime, version);
3305
+ } else {
3306
+ formatError("get", `Export '${exportName}' not found`, startTime, version);
3307
+ }
3308
+ }
3082
3309
 
3083
3310
  // src/commands/health.ts
3084
- import { existsSync as existsSync12, readFileSync as readFileSync15 } from "node:fs";
3085
- import * as path21 from "node:path";
3311
+ import * as path22 from "node:path";
3086
3312
  import { computeDrift as computeDrift3 } from "@driftdev/sdk";
3087
3313
 
3088
3314
  // src/formatters/health.ts
@@ -3149,23 +3375,21 @@ function computeHealth(totalExports, documented, issues) {
3149
3375
  }
3150
3376
 
3151
3377
  // src/commands/health.ts
3152
- function getPackageInfo(cwd) {
3153
- const pkgPath = path21.join(cwd, "package.json");
3154
- if (!existsSync12(pkgPath))
3155
- return {};
3156
- try {
3157
- const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
3158
- return { name: pkg.name, version: pkg.version };
3159
- } catch (err) {
3160
- formatWarning(`Could not parse package.json${err instanceof Error ? `: ${err.message}` : ""}`);
3161
- return {};
3162
- }
3163
- }
3164
3378
  function registerHealthCommand(program) {
3165
- program.command("health [entry]").description("Show documentation health score (default command)").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").action(async (entry, options) => {
3379
+ program.command("health [entry]").description("Show documentation health score").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)").action(async (entry, options) => {
3166
3380
  const startTime = Date.now();
3167
3381
  const version = getVersion();
3168
3382
  try {
3383
+ const lang = resolveLang({
3384
+ entry,
3385
+ lang: options.lang,
3386
+ spec: options.spec,
3387
+ abi: options.abi
3388
+ });
3389
+ if (lang !== "typescript" && options.all) {
3390
+ formatError("health", `Batch mode (--all) not yet supported for ${lang}`, startTime, version);
3391
+ return;
3392
+ }
3169
3393
  if (options.all) {
3170
3394
  const allPackages = discoverPackages(process.cwd());
3171
3395
  if (!allPackages || allPackages.length === 0) {
@@ -3181,8 +3405,8 @@ function registerHealthCommand(program) {
3181
3405
  const rows = [];
3182
3406
  let totalDoc = 0;
3183
3407
  let totalAll = 0;
3184
- for (const pkg2 of packages) {
3185
- const { spec: spec2 } = await cachedExtract(pkg2.entry);
3408
+ for (const pkg of packages) {
3409
+ const { spec: spec2 } = await cachedExtract(pkg.entry);
3186
3410
  const exps = spec2.exports ?? [];
3187
3411
  let doc = 0;
3188
3412
  for (const e of exps) {
@@ -3190,7 +3414,7 @@ function registerHealthCommand(program) {
3190
3414
  doc++;
3191
3415
  }
3192
3416
  const score = exps.length > 0 ? Math.round(doc / exps.length * 100) : 100;
3193
- rows.push({ name: pkg2.name, exports: exps.length, score });
3417
+ rows.push({ name: pkg.name, exports: exps.length, score });
3194
3418
  totalDoc += doc;
3195
3419
  totalAll += exps.length;
3196
3420
  }
@@ -3204,8 +3428,15 @@ function registerHealthCommand(program) {
3204
3428
  return;
3205
3429
  }
3206
3430
  const { config } = loadConfig();
3207
- const entryFile = entry ? path21.resolve(process.cwd(), entry) : config.entry ? path21.resolve(process.cwd(), config.entry) : detectEntry();
3208
- const { spec } = await cachedExtract(entryFile);
3431
+ let entryFile = entry ? path22.resolve(process.cwd(), entry) : undefined;
3432
+ if (lang === "typescript" && !entryFile) {
3433
+ entryFile = config.entry ? path22.resolve(process.cwd(), config.entry) : detectEntry();
3434
+ }
3435
+ const {
3436
+ apiSpec: spec,
3437
+ packageName,
3438
+ packageVersion
3439
+ } = await resolveTruth({ entry: entryFile, lang, spec: options.spec, abi: options.abi });
3209
3440
  const exports = spec.exports ?? [];
3210
3441
  const total = exports.length;
3211
3442
  let documented = 0;
@@ -3222,11 +3453,10 @@ function registerHealthCommand(program) {
3222
3453
  }
3223
3454
  }
3224
3455
  const health = computeHealth(total, documented, issues);
3225
- const pkg = getPackageInfo(process.cwd());
3226
3456
  const data = {
3227
3457
  ...health,
3228
- packageName: pkg.name,
3229
- packageVersion: pkg.version
3458
+ packageName,
3459
+ packageVersion
3230
3460
  };
3231
3461
  let min = options.min ? parseInt(options.min, 10) : config.coverage?.min;
3232
3462
  if (min !== undefined && config.coverage?.ratchet) {
@@ -3263,7 +3493,7 @@ function registerHealthCommand(program) {
3263
3493
  // src/commands/init.ts
3264
3494
  init_global();
3265
3495
  import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync16, writeFileSync as writeFileSync5 } from "node:fs";
3266
- import * as path22 from "node:path";
3496
+ import * as path23 from "node:path";
3267
3497
  import { extract as extract6 } from "@openpkg-ts/sdk";
3268
3498
  import { normalize as normalize7 } from "@openpkg-ts/spec";
3269
3499
 
@@ -3569,10 +3799,10 @@ ${detailLine}`);
3569
3799
 
3570
3800
  // src/commands/init.ts
3571
3801
  async function scanPackage(cwd, pkgDir) {
3572
- const absDir = path22.join(cwd, pkgDir);
3802
+ const absDir = path23.join(cwd, pkgDir);
3573
3803
  if (!existsSync13(absDir))
3574
3804
  return null;
3575
- const pkgPath = path22.join(absDir, "package.json");
3805
+ const pkgPath = path23.join(absDir, "package.json");
3576
3806
  let name = pkgDir;
3577
3807
  if (existsSync13(pkgPath)) {
3578
3808
  try {
@@ -3594,7 +3824,7 @@ async function scanPackage(cwd, pkgDir) {
3594
3824
  }
3595
3825
  const coverage = total > 0 ? Math.round(documented / total * 100) : 100;
3596
3826
  const health = Math.round(coverage * 0.5 + 100 * 0.5);
3597
- return { name, entry: path22.relative(cwd, entryFile), exports: total, coverage, health };
3827
+ return { name, entry: path23.relative(cwd, entryFile), exports: total, coverage, health };
3598
3828
  } catch {
3599
3829
  return null;
3600
3830
  }
@@ -3630,7 +3860,7 @@ function registerInitCommand(program) {
3630
3860
  return;
3631
3861
  }
3632
3862
  const config = generateConfig(packages);
3633
- const configPath = opts.project ? path22.resolve(cwd, "drift.config.json") : getGlobalConfigPath();
3863
+ const configPath = opts.project ? path23.resolve(cwd, "drift.config.json") : getGlobalConfigPath();
3634
3864
  if (!opts.project) {
3635
3865
  const globalDir = getGlobalDir();
3636
3866
  if (!existsSync13(globalDir))
@@ -3655,7 +3885,7 @@ function registerInitCommand(program) {
3655
3885
 
3656
3886
  // src/commands/lint.ts
3657
3887
  import { readFileSync as readFileSync17 } from "node:fs";
3658
- import * as path23 from "node:path";
3888
+ import * as path24 from "node:path";
3659
3889
  import {
3660
3890
  buildExportRegistry,
3661
3891
  computeDrift as computeDrift4,
@@ -3699,10 +3929,20 @@ function renderLint(data, next) {
3699
3929
 
3700
3930
  // src/commands/lint.ts
3701
3931
  function registerLintCommand(program) {
3702
- program.command("lint [entry]").description("Cross-reference JSDoc against code for accuracy issues").option("--all", "Run across all workspace packages").option("--private", "Include private packages in --all mode").action(async (entry, options) => {
3932
+ 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)").action(async (entry, options) => {
3703
3933
  const startTime = Date.now();
3704
3934
  const version = getVersion();
3705
3935
  try {
3936
+ const lang = resolveLang({
3937
+ entry,
3938
+ lang: options.lang,
3939
+ spec: options.spec,
3940
+ abi: options.abi
3941
+ });
3942
+ if (lang !== "typescript" && options.all) {
3943
+ formatError("lint", `Batch mode (--all) not yet supported for ${lang}`, startTime, version);
3944
+ return;
3945
+ }
3706
3946
  if (options.all) {
3707
3947
  const allPackages = discoverPackages(process.cwd());
3708
3948
  if (!allPackages || allPackages.length === 0) {
@@ -3741,8 +3981,16 @@ function registerLintCommand(program) {
3741
3981
  formatOutput("lint", { issues: [], count: 0 }, startTime, version, renderLint);
3742
3982
  return;
3743
3983
  }
3744
- const entryFile = entry ? path23.resolve(process.cwd(), entry) : config.entry ? path23.resolve(process.cwd(), config.entry) : detectEntry();
3745
- const { spec } = await cachedExtract(entryFile);
3984
+ let entryFile = entry ? path24.resolve(process.cwd(), entry) : undefined;
3985
+ if (lang === "typescript" && !entryFile) {
3986
+ entryFile = config.entry ? path24.resolve(process.cwd(), config.entry) : detectEntry();
3987
+ }
3988
+ const { apiSpec: spec } = await resolveTruth({
3989
+ entry: entryFile,
3990
+ lang,
3991
+ spec: options.spec,
3992
+ abi: options.abi
3993
+ });
3746
3994
  const driftResult = computeDrift4(spec);
3747
3995
  const issues = [];
3748
3996
  for (const [exportName, drifts] of driftResult.exports) {
@@ -3756,27 +4004,28 @@ function registerLintCommand(program) {
3756
4004
  });
3757
4005
  }
3758
4006
  }
3759
- try {
3760
- const pkgJsonPath = path23.resolve(process.cwd(), "package.json");
3761
- const pkgJson = JSON.parse(readFileSync17(pkgJsonPath, "utf-8"));
3762
- const packageName = pkgJson.name;
3763
- if (packageName) {
3764
- const registry = buildExportRegistry(spec);
3765
- const markdownFiles = discoverMarkdownFiles(process.cwd(), config.docs);
3766
- const proseDrifts = detectProseDrift({ packageName, markdownFiles, registry });
3767
- for (const drift of proseDrifts) {
3768
- issues.push({
3769
- export: drift.target ?? "",
3770
- issue: drift.issue,
3771
- ...drift.suggestion ? { location: drift.suggestion } : {},
3772
- filePath: drift.filePath,
3773
- line: drift.line
3774
- });
4007
+ if (lang === "typescript")
4008
+ try {
4009
+ const pkgJsonPath = path24.resolve(process.cwd(), "package.json");
4010
+ const pkgJson = JSON.parse(readFileSync17(pkgJsonPath, "utf-8"));
4011
+ const packageName = pkgJson.name;
4012
+ if (packageName) {
4013
+ const registry = buildExportRegistry(spec);
4014
+ const markdownFiles = discoverMarkdownFiles(process.cwd(), config.docs);
4015
+ const proseDrifts = detectProseDrift({ packageName, markdownFiles, registry });
4016
+ for (const drift of proseDrifts) {
4017
+ issues.push({
4018
+ export: drift.target ?? "",
4019
+ issue: drift.issue,
4020
+ ...drift.suggestion ? { location: drift.suggestion } : {},
4021
+ filePath: drift.filePath,
4022
+ line: drift.line
4023
+ });
4024
+ }
3775
4025
  }
4026
+ } catch (err) {
4027
+ formatWarning(`Prose drift skipped: ${err instanceof Error ? err.message : String(err)}`);
3776
4028
  }
3777
- } catch (err) {
3778
- formatWarning(`Prose drift skipped: ${err instanceof Error ? err.message : String(err)}`);
3779
- }
3780
4029
  const data = { issues, count: issues.length };
3781
4030
  const next = issues.length > 0 ? {
3782
4031
  suggested: "drift-fix skill",
@@ -3800,7 +4049,7 @@ function registerLintCommand(program) {
3800
4049
  }
3801
4050
 
3802
4051
  // src/commands/list.ts
3803
- import * as path24 from "node:path";
4052
+ import * as path25 from "node:path";
3804
4053
  import { computeDrift as computeDrift5 } from "@driftdev/sdk";
3805
4054
  import { listExports as listExports2 } from "@openpkg-ts/sdk";
3806
4055
 
@@ -3857,10 +4106,20 @@ function renderList(data) {
3857
4106
 
3858
4107
  // src/commands/list.ts
3859
4108
  function registerListCommand(program) {
3860
- program.command("list [searchOrEntry]").description("List exports (positional arg = search term or entry file)").option("--kind <kinds>", "Filter by kind (comma-separated)").option("--undocumented", "Only exports missing JSDoc").option("--drifted", "Only exports with stale JSDoc").option("--full", "Show full list (no truncation)").option("--all", "Run across all workspace packages").action(async (searchOrEntry, options) => {
4109
+ program.command("list [searchOrEntry]").description("List exports (positional arg = search term or entry file)").option("--kind <kinds>", "Filter by kind (comma-separated)").option("--undocumented", "Only exports missing docs").option("--drifted", "Only exports with stale docs").option("--full", "Show full list (no truncation)").option("--all", "Run across all workspace packages").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)").action(async (searchOrEntry, options) => {
3861
4110
  const startTime = Date.now();
3862
4111
  const version = getVersion();
3863
4112
  try {
4113
+ const lang = resolveLang({
4114
+ entry: searchOrEntry,
4115
+ lang: options.lang,
4116
+ spec: options.spec,
4117
+ abi: options.abi
4118
+ });
4119
+ if (lang !== "typescript" && options.all) {
4120
+ formatError("list", `Batch mode (--all) not yet supported for ${lang}`, startTime, version);
4121
+ return;
4122
+ }
3864
4123
  if (options.all) {
3865
4124
  const packages = discoverPackages(process.cwd());
3866
4125
  if (!packages || packages.length === 0) {
@@ -3880,23 +4139,50 @@ function registerListCommand(program) {
3880
4139
  formatOutput("list", { packages: rows, filter: filter2 }, startTime, version, renderBatchList);
3881
4140
  return;
3882
4141
  }
3883
- let entryFile;
3884
4142
  let searchTerm;
3885
- if (searchOrEntry && looksLikeFilePath(searchOrEntry)) {
3886
- entryFile = path24.resolve(process.cwd(), searchOrEntry);
3887
- } else if (searchOrEntry) {
3888
- entryFile = detectEntry();
3889
- searchTerm = searchOrEntry;
4143
+ let exports;
4144
+ let driftedNames;
4145
+ if (lang !== "typescript") {
4146
+ const entryArg = lang === "clarity" ? searchOrEntry : undefined;
4147
+ if (lang === "openapi")
4148
+ searchTerm = searchOrEntry;
4149
+ const { apiSpec } = await resolveTruth({
4150
+ entry: entryArg,
4151
+ lang,
4152
+ spec: options.spec,
4153
+ abi: options.abi
4154
+ });
4155
+ exports = (apiSpec.exports ?? []).map((e) => ({
4156
+ name: e.name,
4157
+ kind: e.kind,
4158
+ description: e.description,
4159
+ ...e.deprecated ? { deprecated: true } : {}
4160
+ }));
4161
+ if (options.drifted) {
4162
+ driftedNames = new Set(computeDrift5(apiSpec).exports.keys());
4163
+ }
3890
4164
  } else {
3891
- entryFile = detectEntry();
4165
+ let entryFile;
4166
+ if (searchOrEntry && looksLikeFilePath(searchOrEntry)) {
4167
+ entryFile = path25.resolve(process.cwd(), searchOrEntry);
4168
+ } else if (searchOrEntry) {
4169
+ entryFile = detectEntry();
4170
+ searchTerm = searchOrEntry;
4171
+ } else {
4172
+ entryFile = detectEntry();
4173
+ }
4174
+ const result = await listExports2({ entryFile });
4175
+ exports = result.exports.map((e) => ({
4176
+ name: e.name,
4177
+ kind: e.kind,
4178
+ description: e.description,
4179
+ ...e.deprecated ? { deprecated: true } : {}
4180
+ }));
4181
+ if (options.drifted) {
4182
+ const { spec } = await cachedExtract(entryFile);
4183
+ driftedNames = new Set(computeDrift5(spec).exports.keys());
4184
+ }
3892
4185
  }
3893
- const result = await listExports2({ entryFile });
3894
- let exports = result.exports.map((e) => ({
3895
- name: e.name,
3896
- kind: e.kind,
3897
- description: e.description,
3898
- ...e.deprecated ? { deprecated: true } : {}
3899
- }));
3900
4186
  if (options.kind) {
3901
4187
  const kinds = new Set(options.kind.split(",").map((k) => k.trim().toLowerCase()));
3902
4188
  exports = exports.filter((e) => kinds.has(e.kind));
@@ -3904,11 +4190,9 @@ function registerListCommand(program) {
3904
4190
  if (options.undocumented) {
3905
4191
  exports = exports.filter((e) => !e.description || e.description.trim().length === 0);
3906
4192
  }
3907
- if (options.drifted) {
3908
- const { spec } = await cachedExtract(entryFile);
3909
- const driftResult = computeDrift5(spec);
3910
- const driftedNames = new Set(driftResult.exports.keys());
3911
- exports = exports.filter((e) => driftedNames.has(e.name));
4193
+ if (driftedNames) {
4194
+ const names = driftedNames;
4195
+ exports = exports.filter((e) => names.has(e.name));
3912
4196
  }
3913
4197
  if (searchTerm) {
3914
4198
  const matches = fuzzySearch(searchTerm, exports);
@@ -3933,10 +4217,166 @@ function registerListCommand(program) {
3933
4217
  });
3934
4218
  }
3935
4219
 
4220
+ // src/commands/mcp.ts
4221
+ import { spawn } from "node:child_process";
4222
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4223
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4224
+ import { z } from "zod";
4225
+ var truthShape = {
4226
+ cwd: z.string().optional().describe("Directory to run in (project root; where package.json/config live)"),
4227
+ entry: z.string().optional().describe("Entry file: TypeScript entry or Clarity .clar source. Omit for OpenAPI specs."),
4228
+ lang: z.enum(["typescript", "clarity", "openapi"]).optional().describe("Source language override; inferred from spec/abi/.clar extension otherwise"),
4229
+ spec: z.string().optional().describe("OpenAPI 3.x JSON document — local path or https URL. Implies lang=openapi."),
4230
+ abi: z.string().optional().describe("Clarity ABI JSON path (required for Clarity sources)")
4231
+ };
4232
+ function truthFlags(args) {
4233
+ const out = [];
4234
+ if (args.lang)
4235
+ out.push("--lang", args.lang);
4236
+ if (args.spec)
4237
+ out.push("--spec", args.spec);
4238
+ if (args.abi)
4239
+ out.push("--abi", args.abi);
4240
+ return out;
4241
+ }
4242
+ function runDrift(cliArgs, cwd) {
4243
+ return new Promise((resolve18) => {
4244
+ const child = spawn(process.execPath, [process.argv[1], ...cliArgs, "--json"], {
4245
+ cwd: cwd ?? process.cwd(),
4246
+ env: { ...process.env, NO_COLOR: "1" },
4247
+ stdio: ["ignore", "pipe", "pipe"]
4248
+ });
4249
+ let stdout = "";
4250
+ let stderr = "";
4251
+ child.stdout.on("data", (d) => {
4252
+ stdout += d;
4253
+ });
4254
+ child.stderr.on("data", (d) => {
4255
+ stderr += d;
4256
+ });
4257
+ child.on("close", (code) => {
4258
+ const text = stdout.trim() || stderr.trim() || `drift exited with code ${code}`;
4259
+ let ok = code === 0;
4260
+ try {
4261
+ ok = JSON.parse(stdout).ok === true;
4262
+ } catch {}
4263
+ resolve18({ text, ok });
4264
+ });
4265
+ child.on("error", (err) => resolve18({ text: `Failed to run drift: ${err.message}`, ok: false }));
4266
+ });
4267
+ }
4268
+ function toResult({ text, ok }) {
4269
+ return { content: [{ type: "text", text }], isError: !ok };
4270
+ }
4271
+ function registerMcpCommand(program) {
4272
+ program.command("mcp").description("Run an MCP stdio server exposing drift tools to agents").action(async () => {
4273
+ const server = new McpServer({ name: "drift", version: getVersion() });
4274
+ server.registerTool("drift_extract", {
4275
+ title: "Extract API spec",
4276
+ description: "Extract the full machine-readable API spec from a source of truth: a TypeScript package (entry auto-detected from cwd), an OpenAPI 3.x document (spec path/URL), or a Clarity contract (entry .clar + abi). Returns every export with signatures, types, and docs. This is ground truth — use it instead of assuming what an API looks like.",
4277
+ inputSchema: truthShape
4278
+ }, async (args) => toResult(await runDrift(["extract", ...args.entry ? [args.entry] : [], ...truthFlags(args)], args.cwd)));
4279
+ server.registerTool("drift_list", {
4280
+ title: "List exports/operations",
4281
+ description: "List every export (TypeScript), operation (OpenAPI), or function (Clarity) in an API surface: name, kind, one-line description, deprecated flag. Cheap way to check what exists before drilling in with drift_get. Supports filtering to undocumented or drifted items.",
4282
+ inputSchema: {
4283
+ ...truthShape,
4284
+ search: z.string().optional().describe("Fuzzy search term"),
4285
+ kind: z.string().optional().describe("Filter by kind (comma-separated)"),
4286
+ undocumented: z.boolean().optional().describe("Only items missing docs"),
4287
+ drifted: z.boolean().optional().describe("Only items whose docs drifted")
4288
+ }
4289
+ }, async (args) => {
4290
+ const cli = ["list"];
4291
+ if (args.entry)
4292
+ cli.push(args.entry);
4293
+ else if (args.search)
4294
+ cli.push(args.search);
4295
+ if (args.kind)
4296
+ cli.push("--kind", args.kind);
4297
+ if (args.undocumented)
4298
+ cli.push("--undocumented");
4299
+ if (args.drifted)
4300
+ cli.push("--drifted");
4301
+ cli.push("--full");
4302
+ return toResult(await runDrift([...cli, ...truthFlags(args)], args.cwd));
4303
+ });
4304
+ server.registerTool("drift_get", {
4305
+ title: "Get one export/operation",
4306
+ description: "Get the authoritative definition of a single export, endpoint operation, or contract function by name: parameters with types/required/descriptions, return shape, deprecation, referenced types. Use this to verify every claim a docs page makes — one drift_get per claim, never from memory. Unknown names return fuzzy suggestions.",
4307
+ inputSchema: {
4308
+ ...truthShape,
4309
+ name: z.string().describe("Export/operation name (e.g. candidateInfo, transfer, createClient)")
4310
+ }
4311
+ }, async (args) => {
4312
+ const cli = ["get"];
4313
+ if (args.entry)
4314
+ cli.push(args.entry, args.name);
4315
+ else
4316
+ cli.push(args.name);
4317
+ return toResult(await runDrift([...cli, ...truthFlags(args)], args.cwd));
4318
+ });
4319
+ server.registerTool("drift_scan", {
4320
+ title: "Scan docs health",
4321
+ description: "Full docs-drift scan of an API surface: coverage score, drift issues with file/line locations, health score. The one-shot summary — use drift_list/drift_get for targeted follow-up.",
4322
+ inputSchema: {
4323
+ ...truthShape,
4324
+ min: z.number().optional().describe("Minimum health threshold (result.pass=false below it)")
4325
+ }
4326
+ }, async (args) => {
4327
+ const cli = ["scan"];
4328
+ if (args.entry)
4329
+ cli.push(args.entry);
4330
+ if (args.min !== undefined)
4331
+ cli.push("--min", String(args.min));
4332
+ return toResult(await runDrift([...cli, ...truthFlags(args)], args.cwd));
4333
+ });
4334
+ server.registerTool("drift_diff", {
4335
+ title: "Diff two API specs",
4336
+ description: "Diff two extracted API specs (TypeScript only today): spec files or git refs. Reports added/removed/changed exports. Useful for changelog and release-note verification.",
4337
+ inputSchema: {
4338
+ cwd: truthShape.cwd,
4339
+ old: z.string().optional().describe("Old spec file path"),
4340
+ new: z.string().optional().describe("New spec file path"),
4341
+ base: z.string().optional().describe("Git ref for old spec (alternative to files)"),
4342
+ head: z.string().optional().describe("Git ref for new spec (default: working tree)"),
4343
+ entry: z.string().optional().describe("Entry file for git ref extraction")
4344
+ }
4345
+ }, async (args) => toResult(await runDrift(diffArgs("diff", args), args.cwd)));
4346
+ server.registerTool("drift_breaking", {
4347
+ title: "Detect breaking changes",
4348
+ description: "Detect breaking API changes between two specs or git refs (TypeScript only today). Use to check whether docs claiming compatibility are still true.",
4349
+ inputSchema: {
4350
+ cwd: truthShape.cwd,
4351
+ old: z.string().optional().describe("Old spec file path"),
4352
+ new: z.string().optional().describe("New spec file path"),
4353
+ base: z.string().optional().describe("Git ref for old spec (alternative to files)"),
4354
+ head: z.string().optional().describe("Git ref for new spec (default: working tree)"),
4355
+ entry: z.string().optional().describe("Entry file for git ref extraction")
4356
+ }
4357
+ }, async (args) => toResult(await runDrift(diffArgs("breaking", args), args.cwd)));
4358
+ await server.connect(new StdioServerTransport);
4359
+ });
4360
+ }
4361
+ function diffArgs(command, args) {
4362
+ const cli = [command];
4363
+ if (args.old)
4364
+ cli.push(args.old);
4365
+ if (args.new)
4366
+ cli.push(args.new);
4367
+ if (args.base)
4368
+ cli.push("--base", args.base);
4369
+ if (args.head)
4370
+ cli.push("--head", args.head);
4371
+ if (args.entry)
4372
+ cli.push("--entry", args.entry);
4373
+ return cli;
4374
+ }
4375
+
3936
4376
  // src/commands/release.ts
3937
4377
  import { execSync as execSync4 } from "node:child_process";
3938
4378
  import { existsSync as existsSync14, readFileSync as readFileSync18 } from "node:fs";
3939
- import * as path25 from "node:path";
4379
+ import * as path26 from "node:path";
3940
4380
  import { computeDrift as computeDrift6 } from "@driftdev/sdk";
3941
4381
 
3942
4382
  // src/formatters/release.ts
@@ -3984,7 +4424,7 @@ function getLastTag() {
3984
4424
  }
3985
4425
  }
3986
4426
  function getPackageVersion(cwd) {
3987
- const pkgPath = path25.join(cwd, "package.json");
4427
+ const pkgPath = path26.join(cwd, "package.json");
3988
4428
  if (!existsSync14(pkgPath))
3989
4429
  return null;
3990
4430
  try {
@@ -4000,7 +4440,7 @@ function registerReleaseCommand(program) {
4000
4440
  const cwd = process.cwd();
4001
4441
  try {
4002
4442
  const { config } = loadConfig();
4003
- const entryFile = entry ? path25.resolve(cwd, entry) : config.entry ? path25.resolve(cwd, config.entry) : detectEntry();
4443
+ const entryFile = entry ? path26.resolve(cwd, entry) : config.entry ? path26.resolve(cwd, config.entry) : detectEntry();
4004
4444
  const { spec } = await cachedExtract(entryFile);
4005
4445
  const exports = spec.exports ?? [];
4006
4446
  const total = exports.length;
@@ -4125,7 +4565,7 @@ function renderReport(data) {
4125
4565
  // src/utils/scan-packages.ts
4126
4566
  import { execSync as execSync5 } from "node:child_process";
4127
4567
  import { existsSync as existsSync15, readFileSync as readFileSync19 } from "node:fs";
4128
- import * as path26 from "node:path";
4568
+ import * as path27 from "node:path";
4129
4569
  import { computeDrift as computeDrift7 } from "@driftdev/sdk";
4130
4570
  function detectPackageDirs2(cwd) {
4131
4571
  const workspaces = detectWorkspaces(cwd);
@@ -4144,11 +4584,11 @@ async function scanAllPackages(cwd) {
4144
4584
  const packageDirs = detectPackageDirs2(cwd);
4145
4585
  const results = [];
4146
4586
  for (const dir of packageDirs) {
4147
- const absDir = dir === "." ? cwd : path26.join(cwd, dir);
4587
+ const absDir = dir === "." ? cwd : path27.join(cwd, dir);
4148
4588
  if (!existsSync15(absDir))
4149
4589
  continue;
4150
4590
  let name = dir;
4151
- const pkgPath = path26.join(absDir, "package.json");
4591
+ const pkgPath = path27.join(absDir, "package.json");
4152
4592
  if (existsSync15(pkgPath)) {
4153
4593
  try {
4154
4594
  const pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
@@ -4247,8 +4687,8 @@ function registerReportCommand(program) {
4247
4687
  }
4248
4688
 
4249
4689
  // src/commands/scan.ts
4250
- import { existsSync as existsSync16, readFileSync as readFileSync20 } from "node:fs";
4251
- import * as path27 from "node:path";
4690
+ import { readFileSync as readFileSync20 } from "node:fs";
4691
+ import * as path28 from "node:path";
4252
4692
  import {
4253
4693
  buildExportRegistry as buildExportRegistry2,
4254
4694
  computeDrift as computeDrift8,
@@ -4315,23 +4755,29 @@ function renderBatchScan(data) {
4315
4755
  }
4316
4756
 
4317
4757
  // src/commands/scan.ts
4318
- function getPackageInfo2(cwd) {
4319
- const pkgPath = path27.join(cwd, "package.json");
4320
- if (!existsSync16(pkgPath))
4321
- return {};
4322
- try {
4323
- const pkg = JSON.parse(readFileSync20(pkgPath, "utf-8"));
4324
- return { name: pkg.name, version: pkg.version };
4325
- } catch (err) {
4326
- formatWarning(`Could not parse package.json${err instanceof Error ? `: ${err.message}` : ""}`);
4327
- return {};
4328
- }
4329
- }
4330
4758
  function registerScanCommand(program) {
4331
- 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").action(async (entry, options) => {
4759
+ 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)").action(async (entry, options) => {
4332
4760
  const startTime = Date.now();
4333
4761
  const version = getVersion();
4334
4762
  try {
4763
+ const lang = resolveLang({
4764
+ entry,
4765
+ lang: options.lang,
4766
+ spec: options.spec,
4767
+ abi: options.abi
4768
+ });
4769
+ if (lang !== "typescript" && options.all) {
4770
+ formatError("scan", `Batch mode (--all) not yet supported for ${lang}`, startTime, version);
4771
+ return;
4772
+ }
4773
+ if (lang === "clarity" && !options.abi) {
4774
+ formatError("scan", "--abi is required when --lang clarity", startTime, version);
4775
+ return;
4776
+ }
4777
+ if (lang === "openapi" && !options.spec) {
4778
+ formatError("scan", "--spec is required when --lang openapi", startTime, version);
4779
+ return;
4780
+ }
4335
4781
  if (options.all) {
4336
4782
  const allPackages = discoverPackages(process.cwd());
4337
4783
  if (!allPackages || allPackages.length === 0) {
@@ -4346,16 +4792,16 @@ function registerScanCommand(program) {
4346
4792
  }
4347
4793
  const rows = [];
4348
4794
  let anyFail = false;
4349
- for (const pkg2 of packages) {
4350
- const { spec: spec2 } = await cachedExtract(pkg2.entry);
4351
- const exps = spec2.exports ?? [];
4795
+ for (const pkg of packages) {
4796
+ const { spec } = await cachedExtract(pkg.entry);
4797
+ const exps = spec.exports ?? [];
4352
4798
  let documented2 = 0;
4353
4799
  for (const e of exps) {
4354
4800
  if (e.description?.trim())
4355
4801
  documented2++;
4356
4802
  }
4357
4803
  const coverage = exps.length > 0 ? Math.round(documented2 / exps.length * 100) : 100;
4358
- const driftResult2 = computeDrift8(spec2);
4804
+ const driftResult2 = computeDrift8(spec);
4359
4805
  const issues2 = [];
4360
4806
  for (const [exportName, drifts] of driftResult2.exports) {
4361
4807
  for (const d of drifts)
@@ -4366,7 +4812,7 @@ function registerScanCommand(program) {
4366
4812
  if (min2 !== undefined && h2.health < min2)
4367
4813
  anyFail = true;
4368
4814
  rows.push({
4369
- name: pkg2.name,
4815
+ name: pkg.name,
4370
4816
  exports: exps.length,
4371
4817
  coverage,
4372
4818
  lintIssues: issues2.length,
@@ -4385,9 +4831,17 @@ function registerScanCommand(program) {
4385
4831
  return;
4386
4832
  }
4387
4833
  const { config } = loadConfig();
4388
- const entryFile = entry ? path27.resolve(process.cwd(), entry) : config.entry ? path27.resolve(process.cwd(), config.entry) : detectEntry();
4389
- const { spec } = await cachedExtract(entryFile);
4390
- const exports = spec.exports ?? [];
4834
+ let entryFile = entry ? path28.resolve(process.cwd(), entry) : undefined;
4835
+ if (lang === "typescript" && !entryFile) {
4836
+ entryFile = config.entry ? path28.resolve(process.cwd(), config.entry) : detectEntry();
4837
+ }
4838
+ const { apiSpec, packageName, packageVersion } = await resolveTruth({
4839
+ entry: entryFile,
4840
+ lang,
4841
+ spec: options.spec,
4842
+ abi: options.abi
4843
+ });
4844
+ const exports = apiSpec.exports ?? [];
4391
4845
  const total = exports.length;
4392
4846
  let documented = 0;
4393
4847
  for (const exp of exports) {
@@ -4395,7 +4849,7 @@ function registerScanCommand(program) {
4395
4849
  documented++;
4396
4850
  }
4397
4851
  const coverageScore = total > 0 ? Math.round(documented / total * 100) : 100;
4398
- const driftResult = computeDrift8(spec);
4852
+ const driftResult = computeDrift8(apiSpec);
4399
4853
  const issues = [];
4400
4854
  for (const [exportName, drifts] of driftResult.exports) {
4401
4855
  for (const drift of drifts) {
@@ -4408,30 +4862,35 @@ function registerScanCommand(program) {
4408
4862
  });
4409
4863
  }
4410
4864
  }
4411
- try {
4412
- const pkgJsonPath = path27.resolve(process.cwd(), "package.json");
4413
- const pkgJson = JSON.parse(readFileSync20(pkgJsonPath, "utf-8"));
4414
- const packageName = pkgJson.name;
4415
- if (packageName) {
4416
- const registry = buildExportRegistry2(spec);
4417
- const markdownFiles = discoverMarkdownFiles2(process.cwd(), config.docs);
4418
- const proseDrifts = detectProseDrift2({ packageName, markdownFiles, registry });
4419
- for (const drift of proseDrifts) {
4420
- issues.push({
4421
- export: drift.target ?? "",
4422
- issue: drift.issue,
4423
- ...drift.suggestion ? { location: drift.suggestion } : {},
4424
- filePath: drift.filePath,
4425
- line: drift.line
4865
+ if (lang === "typescript") {
4866
+ try {
4867
+ const pkgJsonPath = path28.resolve(process.cwd(), "package.json");
4868
+ const pkgJson = JSON.parse(readFileSync20(pkgJsonPath, "utf-8"));
4869
+ const pkgName = pkgJson.name;
4870
+ if (pkgName) {
4871
+ const registry = buildExportRegistry2(apiSpec);
4872
+ const markdownFiles = discoverMarkdownFiles2(process.cwd(), config.docs);
4873
+ const proseDrifts = detectProseDrift2({
4874
+ packageName: pkgName,
4875
+ markdownFiles,
4876
+ registry
4426
4877
  });
4878
+ for (const drift of proseDrifts) {
4879
+ issues.push({
4880
+ export: drift.target ?? "",
4881
+ issue: drift.issue,
4882
+ ...drift.suggestion ? { location: drift.suggestion } : {},
4883
+ filePath: drift.filePath,
4884
+ line: drift.line
4885
+ });
4886
+ }
4427
4887
  }
4888
+ } catch (err) {
4889
+ formatWarning(`Prose drift skipped: ${err instanceof Error ? err.message : String(err)}`);
4428
4890
  }
4429
- } catch (err) {
4430
- formatWarning(`Prose drift skipped: ${err instanceof Error ? err.message : String(err)}`);
4431
4891
  }
4432
4892
  const healthIssues = issues.map((i) => ({ export: i.export, issue: i.issue }));
4433
4893
  const h = computeHealth(total, documented, healthIssues);
4434
- const pkg = getPackageInfo2(process.cwd());
4435
4894
  let min = options.min ? parseInt(options.min, 10) : config.coverage?.min;
4436
4895
  if (min !== undefined && config.coverage?.ratchet) {
4437
4896
  const ratchet = computeRatchetMin(min);
@@ -4443,8 +4902,8 @@ function registerScanCommand(program) {
4443
4902
  lint: { issues, count: issues.length },
4444
4903
  health: h.health,
4445
4904
  pass,
4446
- packageName: pkg.name,
4447
- packageVersion: pkg.version
4905
+ packageName,
4906
+ packageVersion
4448
4907
  };
4449
4908
  let next;
4450
4909
  if (issues.length > 0) {
@@ -4513,7 +4972,7 @@ function registerSemverCommand(program) {
4513
4972
 
4514
4973
  // src/commands/validate.ts
4515
4974
  import { readFileSync as readFileSync21 } from "node:fs";
4516
- import * as path28 from "node:path";
4975
+ import * as path29 from "node:path";
4517
4976
  import { validateSpec } from "@openpkg-ts/spec";
4518
4977
 
4519
4978
  // src/formatters/validate.ts
@@ -4539,7 +4998,7 @@ function registerValidateCommand(program) {
4539
4998
  const startTime = Date.now();
4540
4999
  const version = getVersion();
4541
5000
  try {
4542
- const filePath = path28.resolve(process.cwd(), file);
5001
+ const filePath = path29.resolve(process.cwd(), file);
4543
5002
  const content = readFileSync21(filePath, "utf-8");
4544
5003
  const spec = JSON.parse(content);
4545
5004
  const result = validateSpec(spec);
@@ -4645,24 +5104,33 @@ function extractCapabilities(program) {
4645
5104
  }
4646
5105
  ],
4647
5106
  workflows: {
4648
- "detect-drift": { steps: ["extract", "lint"], description: "Find stale JSDoc and prose drift" },
5107
+ "detect-drift": {
5108
+ steps: ["extract", "lint"],
5109
+ description: "Find stale JSDoc and prose drift"
5110
+ },
4649
5111
  "full-scan": { steps: ["scan"], description: "Coverage + lint + prose in one pass" },
4650
- "detect-and-enrich": { steps: ["scan", "context"], description: "Scan and generate agent context" },
5112
+ "detect-and-enrich": {
5113
+ steps: ["scan", "context"],
5114
+ description: "Scan and generate agent context"
5115
+ },
4651
5116
  "ci-pipeline": { steps: ["ci"], description: "Run CI checks on changed packages" },
4652
- "pre-release": { steps: ["scan", "breaking", "release"], description: "Full pre-release quality gate" }
5117
+ "pre-release": {
5118
+ steps: ["scan", "breaking", "release"],
5119
+ description: "Full pre-release quality gate"
5120
+ }
4653
5121
  }
4654
5122
  };
4655
5123
  }
4656
5124
 
4657
5125
  // src/drift.ts
4658
5126
  var __filename2 = fileURLToPath2(import.meta.url);
4659
- var __dirname3 = path29.dirname(__filename2);
4660
- var packageJson = JSON.parse(readFileSync22(path29.join(__dirname3, "../package.json"), "utf-8"));
5127
+ var __dirname3 = path30.dirname(__filename2);
5128
+ var packageJson = JSON.parse(readFileSync22(path30.join(__dirname3, "../package.json"), "utf-8"));
4661
5129
  var program = new Command;
4662
- program.name("drift").description("drift — documentation quality for TypeScript").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) => {
5130
+ 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) => {
4663
5131
  const opts = program.opts();
4664
5132
  if (opts.cwd) {
4665
- process.chdir(path29.resolve(opts.cwd));
5133
+ process.chdir(path30.resolve(opts.cwd));
4666
5134
  }
4667
5135
  setOutputMode({ json: opts.json, human: opts.human });
4668
5136
  setConfigPath(opts.config);
@@ -4691,6 +5159,7 @@ registerConfigCommand(program);
4691
5159
  registerContextCommand(program);
4692
5160
  registerCacheCommand(program);
4693
5161
  registerCommandsCommand(program);
5162
+ registerMcpCommand(program);
4694
5163
  var HUMAN_COMMANDS = new Set(["scan", "ci", "init", "commands"]);
4695
5164
  for (const cmd of program.commands) {
4696
5165
  if (!HUMAN_COMMANDS.has(cmd.name())) {