@driftdev/cli 1.5.0 → 1.7.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 +1 -1
  2. package/dist/drift.js +133 -73
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -123,7 +123,7 @@ drift scan --all --private # include private packages
123
123
 
124
124
  ## lint
125
125
 
126
- Cross-reference JSDoc against code signatures. Detects 16 drift types across 4 categories (structural, semantic, example, prose). Prose detection scans markdown files for broken import references and method calls that don't exist on any exported type.
126
+ Cross-reference JSDoc against code signatures. Detects 17 drift types across 4 categories (structural, semantic, example, prose). Prose detection scans markdown files for broken import references, method calls that don't exist on any exported type, and references to deprecated APIs with no deprecation note.
127
127
 
128
128
  ```bash
129
129
  drift lint # single package
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 readFileSync22 } from "node:fs";
55
- import * as path30 from "node:path";
54
+ import { readFileSync as readFileSync21 } from "node:fs";
55
+ import * as path31 from "node:path";
56
56
  import { fileURLToPath as fileURLToPath2 } from "node:url";
57
57
  import { Command } from "commander";
58
58
 
@@ -2260,13 +2260,15 @@ function registerContextCommand(program) {
2260
2260
 
2261
2261
  // src/commands/coverage.ts
2262
2262
  import * as path16 from "node:path";
2263
+ import { isExternalExport } from "@driftdev/sdk";
2263
2264
 
2264
2265
  // src/formatters/coverage.ts
2265
2266
  function renderCoverage(data) {
2266
2267
  const lines = [""];
2267
2268
  const color = coverageColor(data.score);
2268
2269
  const bar = progressBar(data.score);
2269
- lines.push(indent(`Coverage ${color(`${data.score}%`)} ${bar} (${data.documented}/${data.total})`));
2270
+ const externalNote = data.external ? c.gray(` +${data.external} external (not resolvable here)`) : "";
2271
+ lines.push(indent(`Coverage ${color(`${data.score}%`)} ${bar} (${data.documented}/${data.total})${externalNote}`));
2270
2272
  lines.push("");
2271
2273
  if (data.undocumented.length > 0) {
2272
2274
  lines.push(indent(c.gray("UNDOCUMENTED")));
@@ -2411,7 +2413,7 @@ function registerCoverageCommand(program) {
2411
2413
  let totalAll = 0;
2412
2414
  for (const pkg of packages) {
2413
2415
  const { spec: spec2 } = await cachedExtract(pkg.entry);
2414
- const exps = spec2.exports ?? [];
2416
+ const exps = (spec2.exports ?? []).filter((e) => !isExternalExport(e));
2415
2417
  let doc = 0;
2416
2418
  for (const e of exps) {
2417
2419
  if (e.description?.trim())
@@ -2445,7 +2447,9 @@ function registerCoverageCommand(program) {
2445
2447
  spec: options.spec,
2446
2448
  abi: options.abi
2447
2449
  });
2448
- const exports = spec.exports ?? [];
2450
+ const allExports = spec.exports ?? [];
2451
+ const exports = allExports.filter((exp) => !isExternalExport(exp));
2452
+ const external = allExports.length - exports.length;
2449
2453
  const total = exports.length;
2450
2454
  const undocumented = [];
2451
2455
  for (const exp of exports) {
@@ -2455,7 +2459,13 @@ function registerCoverageCommand(program) {
2455
2459
  }
2456
2460
  const documented = total - undocumented.length;
2457
2461
  const score = total > 0 ? Math.round(documented / total * 100) : 100;
2458
- const data = { score, documented, total, undocumented };
2462
+ const data = {
2463
+ score,
2464
+ documented,
2465
+ total,
2466
+ undocumented,
2467
+ ...external > 0 ? { external } : {}
2468
+ };
2459
2469
  const next = undocumented.length > 0 ? {
2460
2470
  suggested: "drift-enrich skill",
2461
2471
  reason: `${undocumented.length} exports lack documentation`
@@ -2998,6 +3008,9 @@ import * as path21 from "node:path";
2998
3008
  import { getExport, listExports } from "@openpkg-ts/sdk";
2999
3009
 
3000
3010
  // src/formatters/get.ts
3011
+ function col(text, width) {
3012
+ return padRight(text, Math.max(width, text.length + 2));
3013
+ }
3001
3014
  function renderGet(data) {
3002
3015
  const lines = [""];
3003
3016
  const exp = data.export;
@@ -3021,7 +3034,7 @@ function renderGet(data) {
3021
3034
  const req = p.required ? "required" : "optional";
3022
3035
  const type = p.type ?? "unknown";
3023
3036
  const desc = p.description ? ` ${c.dim(JSON.stringify(p.description))}` : "";
3024
- lines.push(indent(` ${padRight(p.name ?? "", 16)}${padRight(type, 24)}${c.gray(req)}${desc}`));
3037
+ lines.push(indent(` ${col(p.name ?? "", 16)}${col(type, 24)}${c.gray(req)}${desc}`));
3025
3038
  }
3026
3039
  lines.push("");
3027
3040
  }
@@ -3039,7 +3052,7 @@ function renderGet(data) {
3039
3052
  for (const m of shown) {
3040
3053
  const req = m.required ? "required" : "optional";
3041
3054
  const type = m.type ?? "";
3042
- lines.push(indent(` ${padRight(m.name ?? "", 20)}${padRight(type, 20)}${c.gray(req)}`));
3055
+ lines.push(indent(` ${col(m.name ?? "", 20)}${col(type, 20)}${c.gray(req)}`));
3043
3056
  }
3044
3057
  if (remaining > 0) {
3045
3058
  lines.push(indent(c.gray(` ... ${remaining} more`)));
@@ -3265,22 +3278,29 @@ function registerGetCommand(program) {
3265
3278
  }
3266
3279
  });
3267
3280
  }
3268
- function schemaTypeString(schema) {
3281
+ function schemaTypeString(schema, depth = 0) {
3269
3282
  if (schema === undefined || schema === null)
3270
3283
  return;
3271
3284
  if (typeof schema === "string")
3272
3285
  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
- }
3286
+ if (typeof schema !== "object" || depth > 3)
3287
+ return;
3288
+ const s = schema;
3289
+ if (typeof s.$ref === "string")
3290
+ return s.$ref.split("/").pop();
3291
+ const composite = s.oneOf ?? s.anyOf;
3292
+ if (Array.isArray(composite)) {
3293
+ const arms = composite.map((arm) => schemaTypeString(arm, depth + 1) ?? "unknown").filter((v, i, a) => a.indexOf(v) === i);
3294
+ return arms.join(" | ");
3295
+ }
3296
+ if (s.type === "array") {
3297
+ const item = schemaTypeString(s.items, depth + 1);
3298
+ return item ? `${item}[]` : "array";
3299
+ }
3300
+ if (typeof s.title === "string" && (s.type === "object" || s.type === undefined))
3301
+ return s.title;
3302
+ if (typeof s.type === "string")
3303
+ return s.type;
3284
3304
  return;
3285
3305
  }
3286
3306
  function renderNotFound(exportName, suggestions, startTime, version) {
@@ -3309,7 +3329,7 @@ function renderNotFound(exportName, suggestions, startTime, version) {
3309
3329
 
3310
3330
  // src/commands/health.ts
3311
3331
  import * as path22 from "node:path";
3312
- import { computeDrift as computeDrift3 } from "@driftdev/sdk";
3332
+ import { computeDrift as computeDrift3, isExternalExport as isExternalExport2 } from "@driftdev/sdk";
3313
3333
 
3314
3334
  // src/formatters/health.ts
3315
3335
  function renderHealth(data, next) {
@@ -3324,7 +3344,8 @@ function renderHealth(data, next) {
3324
3344
  lines.push(indent(`${sym.branch} completeness ${data.completeness}% (${data.undocumented} missing docs)`));
3325
3345
  lines.push(indent(`${sym.end} accuracy ${data.accuracy}% (${data.drifted} stale signatures)`));
3326
3346
  lines.push("");
3327
- lines.push(indent(`${data.totalExports} exports ${c.gray(sym.dot)} ${data.documented} documented ${c.gray(sym.dot)} ${data.drifted} drifted`));
3347
+ const externalNote = data.external ? ` ${c.gray(sym.dot)} ${c.gray(`${data.external} external (not resolvable here)`)}` : "";
3348
+ lines.push(indent(`${data.totalExports} exports ${c.gray(sym.dot)} ${data.documented} documented ${c.gray(sym.dot)} ${data.drifted} drifted${externalNote}`));
3328
3349
  lines.push("");
3329
3350
  if (data.issues.length > 0) {
3330
3351
  lines.push(indent("Top issues"));
@@ -3407,7 +3428,7 @@ function registerHealthCommand(program) {
3407
3428
  let totalAll = 0;
3408
3429
  for (const pkg of packages) {
3409
3430
  const { spec: spec2 } = await cachedExtract(pkg.entry);
3410
- const exps = spec2.exports ?? [];
3431
+ const exps = (spec2.exports ?? []).filter((e) => !isExternalExport2(e));
3411
3432
  let doc = 0;
3412
3433
  for (const e of exps) {
3413
3434
  if (e.description?.trim())
@@ -3437,7 +3458,9 @@ function registerHealthCommand(program) {
3437
3458
  packageName,
3438
3459
  packageVersion
3439
3460
  } = await resolveTruth({ entry: entryFile, lang, spec: options.spec, abi: options.abi });
3440
- const exports = spec.exports ?? [];
3461
+ const allExports = spec.exports ?? [];
3462
+ const exports = allExports.filter((exp) => !isExternalExport2(exp));
3463
+ const external = allExports.length - exports.length;
3441
3464
  const total = exports.length;
3442
3465
  let documented = 0;
3443
3466
  for (const exp of exports) {
@@ -3455,6 +3478,7 @@ function registerHealthCommand(program) {
3455
3478
  const health = computeHealth(total, documented, issues);
3456
3479
  const data = {
3457
3480
  ...health,
3481
+ ...external > 0 ? { external } : {},
3458
3482
  packageName,
3459
3483
  packageVersion
3460
3484
  };
@@ -3884,14 +3908,8 @@ function registerInitCommand(program) {
3884
3908
  }
3885
3909
 
3886
3910
  // src/commands/lint.ts
3887
- import { readFileSync as readFileSync17 } from "node:fs";
3888
- import * as path24 from "node:path";
3889
- import {
3890
- buildExportRegistry,
3891
- computeDrift as computeDrift4,
3892
- detectProseDrift,
3893
- discoverMarkdownFiles
3894
- } from "@driftdev/sdk";
3911
+ import * as path25 from "node:path";
3912
+ import { buildExportRegistry, computeDrift as computeDrift4, detectProseDrift } from "@driftdev/sdk";
3895
3913
 
3896
3914
  // src/formatters/lint.ts
3897
3915
  function renderLint(data, next) {
@@ -3927,9 +3945,47 @@ function renderLint(data, next) {
3927
3945
  `);
3928
3946
  }
3929
3947
 
3948
+ // src/utils/docs-corpus.ts
3949
+ import { readFileSync as readFileSync17, statSync as statSync2 } from "node:fs";
3950
+ import * as path24 from "node:path";
3951
+ import { discoverMarkdownFiles } from "@driftdev/sdk";
3952
+ function resolveDocsCorpus(cwd, docsPatterns, configDocs) {
3953
+ if (!docsPatterns || docsPatterns.length === 0) {
3954
+ return discoverMarkdownFiles(cwd, configDocs);
3955
+ }
3956
+ const include = [];
3957
+ for (const pattern of docsPatterns) {
3958
+ if (isDirectory(path24.resolve(cwd, pattern))) {
3959
+ include.push(path24.join(pattern, "**/*.md"), path24.join(pattern, "**/*.mdx"));
3960
+ } else {
3961
+ include.push(pattern);
3962
+ }
3963
+ }
3964
+ const files = discoverMarkdownFiles(cwd, { include });
3965
+ if (files.length === 0) {
3966
+ formatWarning(`--docs matched no markdown files: ${docsPatterns.join(", ")}`);
3967
+ }
3968
+ return files;
3969
+ }
3970
+ function isDirectory(p) {
3971
+ try {
3972
+ return statSync2(p).isDirectory();
3973
+ } catch {
3974
+ return false;
3975
+ }
3976
+ }
3977
+ function readPackageName(cwd = process.cwd()) {
3978
+ try {
3979
+ const pkgJson = JSON.parse(readFileSync17(path24.resolve(cwd, "package.json"), "utf-8"));
3980
+ return typeof pkgJson.name === "string" ? pkgJson.name : undefined;
3981
+ } catch {
3982
+ return;
3983
+ }
3984
+ }
3985
+
3930
3986
  // src/commands/lint.ts
3931
3987
  function registerLintCommand(program) {
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) => {
3988
+ 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)").action(async (entry, options) => {
3933
3989
  const startTime = Date.now();
3934
3990
  const version = getVersion();
3935
3991
  try {
@@ -3981,9 +4037,9 @@ function registerLintCommand(program) {
3981
4037
  formatOutput("lint", { issues: [], count: 0 }, startTime, version, renderLint);
3982
4038
  return;
3983
4039
  }
3984
- let entryFile = entry ? path24.resolve(process.cwd(), entry) : undefined;
4040
+ let entryFile = entry ? path25.resolve(process.cwd(), entry) : undefined;
3985
4041
  if (lang === "typescript" && !entryFile) {
3986
- entryFile = config.entry ? path24.resolve(process.cwd(), config.entry) : detectEntry();
4042
+ entryFile = config.entry ? path25.resolve(process.cwd(), config.entry) : detectEntry();
3987
4043
  }
3988
4044
  const { apiSpec: spec } = await resolveTruth({
3989
4045
  entry: entryFile,
@@ -4004,14 +4060,12 @@ function registerLintCommand(program) {
4004
4060
  });
4005
4061
  }
4006
4062
  }
4007
- if (lang === "typescript")
4063
+ if (lang === "typescript" || options.docs)
4008
4064
  try {
4009
- const pkgJsonPath = path24.resolve(process.cwd(), "package.json");
4010
- const pkgJson = JSON.parse(readFileSync17(pkgJsonPath, "utf-8"));
4011
- const packageName = pkgJson.name;
4065
+ const packageName = readPackageName() ?? spec.meta?.name;
4012
4066
  if (packageName) {
4013
4067
  const registry = buildExportRegistry(spec);
4014
- const markdownFiles = discoverMarkdownFiles(process.cwd(), config.docs);
4068
+ const markdownFiles = resolveDocsCorpus(process.cwd(), options.docs, config.docs);
4015
4069
  const proseDrifts = detectProseDrift({ packageName, markdownFiles, registry });
4016
4070
  for (const drift of proseDrifts) {
4017
4071
  issues.push({
@@ -4049,7 +4103,7 @@ function registerLintCommand(program) {
4049
4103
  }
4050
4104
 
4051
4105
  // src/commands/list.ts
4052
- import * as path25 from "node:path";
4106
+ import * as path26 from "node:path";
4053
4107
  import { computeDrift as computeDrift5 } from "@driftdev/sdk";
4054
4108
  import { listExports as listExports2 } from "@openpkg-ts/sdk";
4055
4109
 
@@ -4164,7 +4218,7 @@ function registerListCommand(program) {
4164
4218
  } else {
4165
4219
  let entryFile;
4166
4220
  if (searchOrEntry && looksLikeFilePath(searchOrEntry)) {
4167
- entryFile = path25.resolve(process.cwd(), searchOrEntry);
4221
+ entryFile = path26.resolve(process.cwd(), searchOrEntry);
4168
4222
  } else if (searchOrEntry) {
4169
4223
  entryFile = detectEntry();
4170
4224
  searchTerm = searchOrEntry;
@@ -4240,7 +4294,7 @@ function truthFlags(args) {
4240
4294
  return out;
4241
4295
  }
4242
4296
  function runDrift(cliArgs, cwd) {
4243
- return new Promise((resolve18) => {
4297
+ return new Promise((resolve19) => {
4244
4298
  const child = spawn(process.execPath, [process.argv[1], ...cliArgs, "--json"], {
4245
4299
  cwd: cwd ?? process.cwd(),
4246
4300
  env: { ...process.env, NO_COLOR: "1" },
@@ -4260,9 +4314,9 @@ function runDrift(cliArgs, cwd) {
4260
4314
  try {
4261
4315
  ok = JSON.parse(stdout).ok === true;
4262
4316
  } catch {}
4263
- resolve18({ text, ok });
4317
+ resolve19({ text, ok });
4264
4318
  });
4265
- child.on("error", (err) => resolve18({ text: `Failed to run drift: ${err.message}`, ok: false }));
4319
+ child.on("error", (err) => resolve19({ text: `Failed to run drift: ${err.message}`, ok: false }));
4266
4320
  });
4267
4321
  }
4268
4322
  function toResult({ text, ok }) {
@@ -4376,7 +4430,7 @@ function diffArgs(command, args) {
4376
4430
  // src/commands/release.ts
4377
4431
  import { execSync as execSync4 } from "node:child_process";
4378
4432
  import { existsSync as existsSync14, readFileSync as readFileSync18 } from "node:fs";
4379
- import * as path26 from "node:path";
4433
+ import * as path27 from "node:path";
4380
4434
  import { computeDrift as computeDrift6 } from "@driftdev/sdk";
4381
4435
 
4382
4436
  // src/formatters/release.ts
@@ -4424,7 +4478,7 @@ function getLastTag() {
4424
4478
  }
4425
4479
  }
4426
4480
  function getPackageVersion(cwd) {
4427
- const pkgPath = path26.join(cwd, "package.json");
4481
+ const pkgPath = path27.join(cwd, "package.json");
4428
4482
  if (!existsSync14(pkgPath))
4429
4483
  return null;
4430
4484
  try {
@@ -4440,7 +4494,7 @@ function registerReleaseCommand(program) {
4440
4494
  const cwd = process.cwd();
4441
4495
  try {
4442
4496
  const { config } = loadConfig();
4443
- const entryFile = entry ? path26.resolve(cwd, entry) : config.entry ? path26.resolve(cwd, config.entry) : detectEntry();
4497
+ const entryFile = entry ? path27.resolve(cwd, entry) : config.entry ? path27.resolve(cwd, config.entry) : detectEntry();
4444
4498
  const { spec } = await cachedExtract(entryFile);
4445
4499
  const exports = spec.exports ?? [];
4446
4500
  const total = exports.length;
@@ -4565,7 +4619,7 @@ function renderReport(data) {
4565
4619
  // src/utils/scan-packages.ts
4566
4620
  import { execSync as execSync5 } from "node:child_process";
4567
4621
  import { existsSync as existsSync15, readFileSync as readFileSync19 } from "node:fs";
4568
- import * as path27 from "node:path";
4622
+ import * as path28 from "node:path";
4569
4623
  import { computeDrift as computeDrift7 } from "@driftdev/sdk";
4570
4624
  function detectPackageDirs2(cwd) {
4571
4625
  const workspaces = detectWorkspaces(cwd);
@@ -4584,11 +4638,11 @@ async function scanAllPackages(cwd) {
4584
4638
  const packageDirs = detectPackageDirs2(cwd);
4585
4639
  const results = [];
4586
4640
  for (const dir of packageDirs) {
4587
- const absDir = dir === "." ? cwd : path27.join(cwd, dir);
4641
+ const absDir = dir === "." ? cwd : path28.join(cwd, dir);
4588
4642
  if (!existsSync15(absDir))
4589
4643
  continue;
4590
4644
  let name = dir;
4591
- const pkgPath = path27.join(absDir, "package.json");
4645
+ const pkgPath = path28.join(absDir, "package.json");
4592
4646
  if (existsSync15(pkgPath)) {
4593
4647
  try {
4594
4648
  const pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
@@ -4687,13 +4741,12 @@ function registerReportCommand(program) {
4687
4741
  }
4688
4742
 
4689
4743
  // src/commands/scan.ts
4690
- import { readFileSync as readFileSync20 } from "node:fs";
4691
- import * as path28 from "node:path";
4744
+ import * as path29 from "node:path";
4692
4745
  import {
4693
4746
  buildExportRegistry as buildExportRegistry2,
4694
4747
  computeDrift as computeDrift8,
4695
4748
  detectProseDrift as detectProseDrift2,
4696
- discoverMarkdownFiles as discoverMarkdownFiles2
4749
+ isExternalExport as isExternalExport3
4697
4750
  } from "@driftdev/sdk";
4698
4751
 
4699
4752
  // src/formatters/scan.ts
@@ -4706,7 +4759,8 @@ function renderScan(data, next) {
4706
4759
  }
4707
4760
  const hColor = coverageColor(data.health);
4708
4761
  lines.push(indent(`Health ${hColor(`${data.health}%`)}`));
4709
- lines.push(indent(`Coverage ${coverageColor(data.coverage.score)(`${data.coverage.score}%`)} (${data.coverage.documented}/${data.coverage.total} exports)`));
4762
+ const externalNote = data.coverage.external ? c.gray(` +${data.coverage.external} external (not resolvable here)`) : "";
4763
+ lines.push(indent(`Coverage ${coverageColor(data.coverage.score)(`${data.coverage.score}%`)} (${data.coverage.documented}/${data.coverage.total} exports)${externalNote}`));
4710
4764
  lines.push(indent(`Lint ${data.lint.count === 0 ? c.green("0 issues") : c.red(`${data.lint.count} issues`)}`));
4711
4765
  lines.push("");
4712
4766
  if (data.lint.count > 0) {
@@ -4756,7 +4810,7 @@ function renderBatchScan(data) {
4756
4810
 
4757
4811
  // src/commands/scan.ts
4758
4812
  function registerScanCommand(program) {
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) => {
4813
+ 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) => {
4760
4814
  const startTime = Date.now();
4761
4815
  const version = getVersion();
4762
4816
  try {
@@ -4794,7 +4848,7 @@ function registerScanCommand(program) {
4794
4848
  let anyFail = false;
4795
4849
  for (const pkg of packages) {
4796
4850
  const { spec } = await cachedExtract(pkg.entry);
4797
- const exps = spec.exports ?? [];
4851
+ const exps = (spec.exports ?? []).filter((e) => !isExternalExport3(e));
4798
4852
  let documented2 = 0;
4799
4853
  for (const e of exps) {
4800
4854
  if (e.description?.trim())
@@ -4831,9 +4885,9 @@ function registerScanCommand(program) {
4831
4885
  return;
4832
4886
  }
4833
4887
  const { config } = loadConfig();
4834
- let entryFile = entry ? path28.resolve(process.cwd(), entry) : undefined;
4888
+ let entryFile = entry ? path29.resolve(process.cwd(), entry) : undefined;
4835
4889
  if (lang === "typescript" && !entryFile) {
4836
- entryFile = config.entry ? path28.resolve(process.cwd(), config.entry) : detectEntry();
4890
+ entryFile = config.entry ? path29.resolve(process.cwd(), config.entry) : detectEntry();
4837
4891
  }
4838
4892
  const { apiSpec, packageName, packageVersion } = await resolveTruth({
4839
4893
  entry: entryFile,
@@ -4841,7 +4895,9 @@ function registerScanCommand(program) {
4841
4895
  spec: options.spec,
4842
4896
  abi: options.abi
4843
4897
  });
4844
- const exports = apiSpec.exports ?? [];
4898
+ const allExports = apiSpec.exports ?? [];
4899
+ const exports = allExports.filter((exp) => !isExternalExport3(exp));
4900
+ const external = allExports.length - exports.length;
4845
4901
  const total = exports.length;
4846
4902
  let documented = 0;
4847
4903
  for (const exp of exports) {
@@ -4862,14 +4918,12 @@ function registerScanCommand(program) {
4862
4918
  });
4863
4919
  }
4864
4920
  }
4865
- if (lang === "typescript") {
4921
+ if (lang === "typescript" || options.docs) {
4866
4922
  try {
4867
- const pkgJsonPath = path28.resolve(process.cwd(), "package.json");
4868
- const pkgJson = JSON.parse(readFileSync20(pkgJsonPath, "utf-8"));
4869
- const pkgName = pkgJson.name;
4923
+ const pkgName = readPackageName() ?? packageName;
4870
4924
  if (pkgName) {
4871
4925
  const registry = buildExportRegistry2(apiSpec);
4872
- const markdownFiles = discoverMarkdownFiles2(process.cwd(), config.docs);
4926
+ const markdownFiles = resolveDocsCorpus(process.cwd(), options.docs, config.docs);
4873
4927
  const proseDrifts = detectProseDrift2({
4874
4928
  packageName: pkgName,
4875
4929
  markdownFiles,
@@ -4898,7 +4952,13 @@ function registerScanCommand(program) {
4898
4952
  }
4899
4953
  const pass = min === undefined || h.health >= min;
4900
4954
  const data = {
4901
- coverage: { score: coverageScore, documented, total, undocumented: total - documented },
4955
+ coverage: {
4956
+ score: coverageScore,
4957
+ documented,
4958
+ total,
4959
+ undocumented: total - documented,
4960
+ ...external > 0 ? { external } : {}
4961
+ },
4902
4962
  lint: { issues, count: issues.length },
4903
4963
  health: h.health,
4904
4964
  pass,
@@ -4971,8 +5031,8 @@ function registerSemverCommand(program) {
4971
5031
  }
4972
5032
 
4973
5033
  // src/commands/validate.ts
4974
- import { readFileSync as readFileSync21 } from "node:fs";
4975
- import * as path29 from "node:path";
5034
+ import { readFileSync as readFileSync20 } from "node:fs";
5035
+ import * as path30 from "node:path";
4976
5036
  import { validateSpec } from "@openpkg-ts/spec";
4977
5037
 
4978
5038
  // src/formatters/validate.ts
@@ -4998,8 +5058,8 @@ function registerValidateCommand(program) {
4998
5058
  const startTime = Date.now();
4999
5059
  const version = getVersion();
5000
5060
  try {
5001
- const filePath = path29.resolve(process.cwd(), file);
5002
- const content = readFileSync21(filePath, "utf-8");
5061
+ const filePath = path30.resolve(process.cwd(), file);
5062
+ const content = readFileSync20(filePath, "utf-8");
5003
5063
  const spec = JSON.parse(content);
5004
5064
  const result = validateSpec(spec);
5005
5065
  const data = {
@@ -5124,13 +5184,13 @@ function extractCapabilities(program) {
5124
5184
 
5125
5185
  // src/drift.ts
5126
5186
  var __filename2 = fileURLToPath2(import.meta.url);
5127
- var __dirname3 = path30.dirname(__filename2);
5128
- var packageJson = JSON.parse(readFileSync22(path30.join(__dirname3, "../package.json"), "utf-8"));
5187
+ var __dirname3 = path31.dirname(__filename2);
5188
+ var packageJson = JSON.parse(readFileSync21(path31.join(__dirname3, "../package.json"), "utf-8"));
5129
5189
  var program = new Command;
5130
5190
  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) => {
5131
5191
  const opts = program.opts();
5132
5192
  if (opts.cwd) {
5133
- process.chdir(path30.resolve(opts.cwd));
5193
+ process.chdir(path31.resolve(opts.cwd));
5134
5194
  }
5135
5195
  setOutputMode({ json: opts.json, human: opts.human });
5136
5196
  setConfigPath(opts.config);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@driftdev/cli",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "Drift CLI - detect when your docs drift from your code",
5
5
  "keywords": [
6
6
  "typescript",
@@ -45,8 +45,8 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@driftdev/clarity-adapter": "^1.0.1",
48
- "@driftdev/openapi-adapter": "^1.0.0",
49
- "@driftdev/sdk": "^1.4.0",
48
+ "@driftdev/openapi-adapter": "^1.0.1",
49
+ "@driftdev/sdk": "^1.7.0",
50
50
  "@modelcontextprotocol/sdk": "^1.29.0",
51
51
  "@openpkg-ts/sdk": "^0.38.0",
52
52
  "@openpkg-ts/spec": "^0.37.0",