@driftdev/cli 0.41.0 → 1.0.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 +112 -63
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -95,7 +95,7 @@ Entry auto-detects from `package.json` (`types`, `exports`, `main`, `module`, `b
95
95
 
96
96
  ```bash
97
97
  # Machine-readable list of all commands + flags
98
- drift --capabilities
98
+ drift --tools
99
99
  ```
100
100
 
101
101
  ## Global Options
package/dist/drift.js CHANGED
@@ -578,7 +578,7 @@ function formatOutput(command, data, startTime, version, humanRenderer, next) {
578
578
  ...next ? { next } : {}
579
579
  };
580
580
  if (humanRenderer && shouldRenderHuman()) {
581
- process.stdout.write(humanRenderer(data));
581
+ process.stdout.write(humanRenderer(data, next));
582
582
  process.stdout.write(`
583
583
  `);
584
584
  } else {
@@ -589,12 +589,16 @@ function formatOutput(command, data, startTime, version, humanRenderer, next) {
589
589
  }
590
590
  return envelope;
591
591
  }
592
- function formatError(command, error, startTime, version) {
592
+ function formatError(command, error, startTime, version, suggestion) {
593
593
  const duration = Date.now() - startTime;
594
594
  if (shouldRenderHuman()) {
595
595
  process.stdout.write(`
596
596
  ${c.red("x")} ${error}
597
-
597
+ `);
598
+ if (suggestion)
599
+ process.stdout.write(` ${c.gray(suggestion)}
600
+ `);
601
+ process.stdout.write(`
598
602
  `);
599
603
  } else {
600
604
  const envelope = {
@@ -609,6 +613,15 @@ function formatError(command, error, startTime, version) {
609
613
  }
610
614
  process.exitCode = 1;
611
615
  }
616
+ function formatWarning(message) {
617
+ if (shouldRenderHuman()) {
618
+ process.stderr.write(` ${c.yellow("!")} ${message}
619
+ `);
620
+ } else {
621
+ process.stderr.write(`warning: ${message}
622
+ `);
623
+ }
624
+ }
612
625
 
613
626
  // src/utils/resolve-specs.ts
614
627
  import { readFileSync as readFileSync5 } from "node:fs";
@@ -845,35 +858,43 @@ function filterPublic(packages) {
845
858
  // src/utils/detect-entry.ts
846
859
  function detectEntry(cwd = process.cwd()) {
847
860
  const pkgPath = path6.join(cwd, "package.json");
861
+ const tried = [];
862
+ function tryResolve(source, filePath) {
863
+ const resolved = resolveToSource(cwd, filePath);
864
+ if (resolved)
865
+ return resolved;
866
+ tried.push(`${source}: ${filePath} -> not found`);
867
+ return null;
868
+ }
848
869
  if (existsSync6(pkgPath)) {
849
870
  try {
850
871
  const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
851
872
  const typesField = pkg.types || pkg.typings;
852
873
  if (typesField && typeof typesField === "string") {
853
- const resolved = resolveToSource(cwd, typesField);
874
+ const resolved = tryResolve("types", typesField);
854
875
  if (resolved)
855
876
  return resolved;
856
877
  }
857
878
  if (pkg.exports && typeof pkg.exports === "object") {
858
879
  const dot = pkg.exports["."];
859
880
  for (const candidate of collectExportCandidates(dot)) {
860
- const resolved = resolveToSource(cwd, candidate);
881
+ const resolved = tryResolve('exports["."]', candidate);
861
882
  if (resolved)
862
883
  return resolved;
863
884
  }
864
885
  }
865
886
  if (pkg.main && typeof pkg.main === "string") {
866
- const resolved = resolveToSource(cwd, pkg.main);
887
+ const resolved = tryResolve("main", pkg.main);
867
888
  if (resolved)
868
889
  return resolved;
869
890
  }
870
891
  if (pkg.module && typeof pkg.module === "string") {
871
- const resolved = resolveToSource(cwd, pkg.module);
892
+ const resolved = tryResolve("module", pkg.module);
872
893
  if (resolved)
873
894
  return resolved;
874
895
  }
875
896
  for (const binPath of collectBinCandidates(pkg.bin)) {
876
- const resolved = resolveToSource(cwd, binPath);
897
+ const resolved = tryResolve("bin", binPath);
877
898
  if (resolved)
878
899
  return resolved;
879
900
  }
@@ -892,22 +913,28 @@ function detectEntry(cwd = process.cwd()) {
892
913
  const full = path6.join(cwd, f);
893
914
  if (existsSync6(full))
894
915
  return full;
916
+ tried.push(`fallback: ${f} -> not found`);
895
917
  }
896
918
  const workspaces = detectWorkspaces(cwd);
897
919
  if (workspaces) {
898
920
  const dirs = resolveGlobs(cwd, workspaces);
899
- const lines = ["Monorepo detected. Use --cwd <pkg> or --all.", "", " Packages:"];
921
+ const lines2 = ["Monorepo detected. Use --cwd <pkg> or --all.", "", " Packages:"];
900
922
  for (const dir of dirs) {
901
- lines.push(` ${dir}`);
923
+ lines2.push(` ${dir}`);
902
924
  }
903
- lines.push("", " Examples:", ` drift coverage --cwd ${dirs[0] ?? "packages/foo"}`, " drift coverage --all");
904
- throw new Error(lines.join(`
925
+ lines2.push("", " Examples:", ` drift coverage --cwd ${dirs[0] ?? "packages/foo"}`, " drift coverage --all");
926
+ throw new Error(lines2.join(`
905
927
  `));
906
928
  }
907
- throw new Error(`Could not detect entry point.
908
-
909
- Try: drift list src/index.ts
910
- or: cd my-package && drift list`);
929
+ const lines = ["Could not detect entry point."];
930
+ if (tried.length > 0) {
931
+ lines.push("", " Tried:");
932
+ for (const t of tried)
933
+ lines.push(` ${t}`);
934
+ }
935
+ lines.push("", " Try: drift scan ./path/to/entry.ts");
936
+ throw new Error(lines.join(`
937
+ `));
911
938
  }
912
939
  function resolveToSource(cwd, filePath) {
913
940
  const normalized = filePath.replace(/^\.\//, "");
@@ -1134,6 +1161,26 @@ function registerBreakingCommand(program) {
1134
1161
  });
1135
1162
  }
1136
1163
 
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
+
1137
1184
  // src/formatters/cache.ts
1138
1185
  function renderCacheStatus(data) {
1139
1186
  const lines = [];
@@ -1392,11 +1439,11 @@ function renderContextMarkdown(data) {
1392
1439
  if (pkg.issues && pkg.issues.length > 0) {
1393
1440
  lines.push(`### ${pkg.name} — Issues`);
1394
1441
  lines.push("");
1395
- lines.push("| Export | Type | Location | Fixable |");
1396
- lines.push("|--------|------|----------|---------|");
1442
+ lines.push("| Export | Type | Location |");
1443
+ lines.push("|--------|------|----------|");
1397
1444
  for (const issue of pkg.issues) {
1398
1445
  const loc = issue.filePath ? `${issue.filePath}${issue.line ? `:${issue.line}` : ""}` : "—";
1399
- lines.push(`| ${issue.export} | ${issue.type} | ${loc} | ${issue.fixable ? "yes" : "no"} |`);
1446
+ lines.push(`| ${issue.export} | ${issue.type} | ${loc} |`);
1400
1447
  }
1401
1448
  lines.push("");
1402
1449
  }
@@ -1778,7 +1825,9 @@ function registerCiCommand(program) {
1778
1825
  name = pkg.name;
1779
1826
  if (pkg.private === true)
1780
1827
  isPrivate = true;
1781
- } catch {}
1828
+ } catch (err) {
1829
+ formatWarning(`Could not parse package.json for ${dir}${err instanceof Error ? `: ${err.message}` : ""}`);
1830
+ }
1782
1831
  }
1783
1832
  if (isPrivate && !options.private) {
1784
1833
  skipped.push(name);
@@ -1850,7 +1899,8 @@ function registerCiCommand(program) {
1850
1899
  diff,
1851
1900
  undocumented
1852
1901
  });
1853
- } catch {
1902
+ } catch (err) {
1903
+ formatWarning(`Analysis failed for ${name}: ${err instanceof Error ? err.message : String(err)}`);
1854
1904
  results.push({
1855
1905
  name,
1856
1906
  coverage: 0,
@@ -2063,7 +2113,7 @@ function registerConfigCommand(program) {
2063
2113
  import { execSync as execSync3 } from "node:child_process";
2064
2114
  import { readFileSync as readFileSync12 } from "node:fs";
2065
2115
  import * as path14 from "node:path";
2066
- import { computeDrift as computeDrift2, isFixableDrift } from "@driftdev/sdk";
2116
+ import { computeDrift as computeDrift2 } from "@driftdev/sdk";
2067
2117
 
2068
2118
  // src/formatters/context.ts
2069
2119
  function renderContext(data) {
@@ -2116,8 +2166,7 @@ function buildPackageContext(name, spec) {
2116
2166
  type: drift.type,
2117
2167
  issue: drift.issue,
2118
2168
  filePath: drift.filePath ?? exp?.source?.file,
2119
- line: drift.line ?? exp?.source?.line,
2120
- fixable: isFixableDrift(drift)
2169
+ line: drift.line ?? exp?.source?.line
2121
2170
  });
2122
2171
  }
2123
2172
  }
@@ -2150,7 +2199,8 @@ function registerContextCommand(program) {
2150
2199
  try {
2151
2200
  const { spec } = await cachedExtract(pkg.entry);
2152
2201
  packages.push(buildPackageContext(pkg.name, spec));
2153
- } catch {
2202
+ } catch (err) {
2203
+ formatWarning(`Extraction failed for ${pkg.name}: ${err instanceof Error ? err.message : String(err)}`);
2154
2204
  packages.push({
2155
2205
  name: pkg.name,
2156
2206
  coverage: 0,
@@ -3036,7 +3086,7 @@ import * as path21 from "node:path";
3036
3086
  import { computeDrift as computeDrift3 } from "@driftdev/sdk";
3037
3087
 
3038
3088
  // src/formatters/health.ts
3039
- function renderHealth(data) {
3089
+ function renderHealth(data, next) {
3040
3090
  const lines = [""];
3041
3091
  if (data.packageName) {
3042
3092
  const ver = data.packageVersion ? ` v${data.packageVersion}` : "";
@@ -3069,8 +3119,8 @@ function renderHealth(data) {
3069
3119
  } else {
3070
3120
  lines.push(indent(`${c.green(sym.ok)} Health ${data.health}%`));
3071
3121
  }
3072
- if (data.health < 100) {
3073
- lines.push(indent(c.gray("Fix with /drift-fix in Claude Code, or see details: drift list --drifted")));
3122
+ if (next) {
3123
+ lines.push(indent(c.gray(`-> Next: ${next.suggested} (${next.reason})`)));
3074
3124
  }
3075
3125
  lines.push("");
3076
3126
  return lines.join(`
@@ -3106,7 +3156,8 @@ function getPackageInfo(cwd) {
3106
3156
  try {
3107
3157
  const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
3108
3158
  return { name: pkg.name, version: pkg.version };
3109
- } catch {
3159
+ } catch (err) {
3160
+ formatWarning(`Could not parse package.json${err instanceof Error ? `: ${err.message}` : ""}`);
3110
3161
  return {};
3111
3162
  }
3112
3163
  }
@@ -3609,12 +3660,11 @@ import {
3609
3660
  buildExportRegistry,
3610
3661
  computeDrift as computeDrift4,
3611
3662
  detectProseDrift,
3612
- discoverMarkdownFiles,
3613
- isFixableDrift as isFixableDrift2
3663
+ discoverMarkdownFiles
3614
3664
  } from "@driftdev/sdk";
3615
3665
 
3616
3666
  // src/formatters/lint.ts
3617
- function renderLint(data) {
3667
+ function renderLint(data, next) {
3618
3668
  const lines = [""];
3619
3669
  if (data.count === 0) {
3620
3670
  lines.push(indent(`${c.green("ok")} No issues found`));
@@ -3639,6 +3689,9 @@ function renderLint(data) {
3639
3689
  lines.push("");
3640
3690
  }
3641
3691
  lines.push(indent(`${data.count} issue${data.count === 1 ? "" : "s"} found`));
3692
+ if (next) {
3693
+ lines.push(indent(c.gray(`-> Next: ${next.suggested} (${next.reason})`)));
3694
+ }
3642
3695
  lines.push("");
3643
3696
  return lines.join(`
3644
3697
  `);
@@ -3721,18 +3774,13 @@ function registerLintCommand(program) {
3721
3774
  });
3722
3775
  }
3723
3776
  }
3724
- } catch {}
3725
- const data = { issues, count: issues.length };
3726
- let fixableCount = 0;
3727
- for (const [, drifts] of driftResult.exports) {
3728
- for (const d of drifts) {
3729
- if (isFixableDrift2(d))
3730
- fixableCount++;
3731
- }
3777
+ } catch (err) {
3778
+ formatWarning(`Prose drift skipped: ${err instanceof Error ? err.message : String(err)}`);
3732
3779
  }
3780
+ const data = { issues, count: issues.length };
3733
3781
  const next = issues.length > 0 ? {
3734
3782
  suggested: "drift-fix skill",
3735
- reason: `${fixableCount} of ${issues.length} issues are auto-fixable`
3783
+ reason: `${issues.length} issue${issues.length === 1 ? "" : "s"} found`
3736
3784
  } : undefined;
3737
3785
  formatOutput("lint", data, startTime, version, renderLint, next);
3738
3786
  if (issues.length > 0) {
@@ -4205,12 +4253,11 @@ import {
4205
4253
  buildExportRegistry as buildExportRegistry2,
4206
4254
  computeDrift as computeDrift8,
4207
4255
  detectProseDrift as detectProseDrift2,
4208
- discoverMarkdownFiles as discoverMarkdownFiles2,
4209
- isFixableDrift as isFixableDrift3
4256
+ discoverMarkdownFiles as discoverMarkdownFiles2
4210
4257
  } from "@driftdev/sdk";
4211
4258
 
4212
4259
  // src/formatters/scan.ts
4213
- function renderScan(data) {
4260
+ function renderScan(data, next) {
4214
4261
  const lines = [""];
4215
4262
  if (data.packageName) {
4216
4263
  const ver = data.packageVersion ? ` v${data.packageVersion}` : "";
@@ -4241,6 +4288,9 @@ function renderScan(data) {
4241
4288
  } else {
4242
4289
  lines.push(indent(`${c.red(sym.x)} Scan failed`));
4243
4290
  }
4291
+ if (next) {
4292
+ lines.push(indent(c.gray(`-> Next: ${next.suggested} (${next.reason})`)));
4293
+ }
4244
4294
  lines.push("");
4245
4295
  return lines.join(`
4246
4296
  `);
@@ -4272,7 +4322,8 @@ function getPackageInfo2(cwd) {
4272
4322
  try {
4273
4323
  const pkg = JSON.parse(readFileSync20(pkgPath, "utf-8"));
4274
4324
  return { name: pkg.name, version: pkg.version };
4275
- } catch {
4325
+ } catch (err) {
4326
+ formatWarning(`Could not parse package.json${err instanceof Error ? `: ${err.message}` : ""}`);
4276
4327
  return {};
4277
4328
  }
4278
4329
  }
@@ -4375,7 +4426,9 @@ function registerScanCommand(program) {
4375
4426
  });
4376
4427
  }
4377
4428
  }
4378
- } catch {}
4429
+ } catch (err) {
4430
+ formatWarning(`Prose drift skipped: ${err instanceof Error ? err.message : String(err)}`);
4431
+ }
4379
4432
  const healthIssues = issues.map((i) => ({ export: i.export, issue: i.issue }));
4380
4433
  const h = computeHealth(total, documented, healthIssues);
4381
4434
  const pkg = getPackageInfo2(process.cwd());
@@ -4395,16 +4448,9 @@ function registerScanCommand(program) {
4395
4448
  };
4396
4449
  let next;
4397
4450
  if (issues.length > 0) {
4398
- let fixableCount = 0;
4399
- for (const [, drs] of driftResult.exports) {
4400
- for (const d of drs) {
4401
- if (isFixableDrift3(d))
4402
- fixableCount++;
4403
- }
4404
- }
4405
4451
  next = {
4406
4452
  suggested: "drift-fix skill",
4407
- reason: `${fixableCount} of ${issues.length} issues are auto-fixable`
4453
+ reason: `${issues.length} issue${issues.length === 1 ? "" : "s"} found`
4408
4454
  };
4409
4455
  } else if (total - documented > 0) {
4410
4456
  next = {
@@ -4599,11 +4645,11 @@ function extractCapabilities(program) {
4599
4645
  }
4600
4646
  ],
4601
4647
  workflows: {
4602
- "detect-drift": ["extract", "lint"],
4603
- "full-scan": ["scan"],
4604
- "detect-and-enrich": ["scan", "context"],
4605
- "ci-pipeline": ["ci"],
4606
- "pre-release": ["scan", "breaking", "release"]
4648
+ "detect-drift": { steps: ["extract", "lint"], description: "Find stale JSDoc and prose drift" },
4649
+ "full-scan": { steps: ["scan"], description: "Coverage + lint + prose in one pass" },
4650
+ "detect-and-enrich": { steps: ["scan", "context"], description: "Scan and generate agent context" },
4651
+ "ci-pipeline": { steps: ["ci"], description: "Run CI checks on changed packages" },
4652
+ "pre-release": { steps: ["scan", "breaking", "release"], description: "Full pre-release quality gate" }
4607
4653
  }
4608
4654
  };
4609
4655
  }
@@ -4644,12 +4690,17 @@ registerInitCommand(program);
4644
4690
  registerConfigCommand(program);
4645
4691
  registerContextCommand(program);
4646
4692
  registerCacheCommand(program);
4647
- var HUMAN_COMMANDS = new Set(["scan", "ci", "init"]);
4693
+ registerCommandsCommand(program);
4694
+ var HUMAN_COMMANDS = new Set(["scan", "ci", "init", "commands"]);
4648
4695
  for (const cmd of program.commands) {
4649
4696
  if (!HUMAN_COMMANDS.has(cmd.name())) {
4650
4697
  cmd._hidden = true;
4651
4698
  }
4652
4699
  }
4700
+ program.addHelpText("after", `
4701
+ Agent mode:
4702
+ drift --tools JSON manifest of all commands for agent use
4703
+ `);
4653
4704
  if (process.argv.includes("--tools")) {
4654
4705
  const caps = extractCapabilities(program);
4655
4706
  process.stdout.write(`${JSON.stringify(caps, null, 2)}
@@ -4660,9 +4711,7 @@ var rawArgs = process.argv.slice(2);
4660
4711
  var hasHelpOrVersion = rawArgs.some((a) => ["-h", "--help", "-V", "--version", "--tools"].includes(a));
4661
4712
  var userArgs = rawArgs.filter((a) => !a.startsWith("-"));
4662
4713
  if (userArgs.length === 0 && !hasHelpOrVersion) {
4663
- const { configPath } = loadConfig();
4664
- const subcommand = configPath ? "scan" : "init";
4665
- process.argv.splice(2, 0, subcommand);
4714
+ process.argv.splice(2, 0, "scan");
4666
4715
  }
4667
4716
  program.parseAsync().catch(() => {
4668
4717
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@driftdev/cli",
3
- "version": "0.41.0",
3
+ "version": "1.0.0",
4
4
  "description": "Drift CLI - Documentation coverage and drift detection for TypeScript",
5
5
  "keywords": [
6
6
  "typescript",
@@ -43,7 +43,7 @@
43
43
  "dist"
44
44
  ],
45
45
  "dependencies": {
46
- "@driftdev/sdk": "^0.41.0",
46
+ "@driftdev/sdk": "^1.0.0",
47
47
  "@openpkg-ts/sdk": "^0.37.0",
48
48
  "@openpkg-ts/spec": "^0.37.0",
49
49
  "chalk": "^5.4.1",