@doccov/cli 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,22 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire } from "node:module";
3
- var __create = Object.create;
4
- var __getProtoOf = Object.getPrototypeOf;
5
- var __defProp = Object.defineProperty;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __toESM = (mod, isNodeMode, target) => {
9
- target = mod != null ? __create(__getProtoOf(mod)) : {};
10
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
- for (let key of __getOwnPropNames(mod))
12
- if (!__hasOwnProp.call(to, key))
13
- __defProp(to, key, {
14
- get: () => mod[key],
15
- enumerable: true
16
- });
17
- return to;
18
- };
19
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
20
2
 
21
3
  // src/config/doccov-config.ts
22
4
  import { access } from "node:fs/promises";
@@ -178,8 +160,8 @@ ${formatIssues(issues)}`);
178
160
  // src/config/index.ts
179
161
  var defineConfig = (config) => config;
180
162
  // src/cli.ts
181
- import { readFileSync as readFileSync4 } from "node:fs";
182
- import * as path10 from "node:path";
163
+ import { readFileSync as readFileSync3 } from "node:fs";
164
+ import * as path8 from "node:path";
183
165
  import { fileURLToPath } from "node:url";
184
166
  import { Command } from "commander";
185
167
 
@@ -206,7 +188,7 @@ import {
206
188
  import {
207
189
  DRIFT_CATEGORIES as DRIFT_CATEGORIES2
208
190
  } from "@openpkg-ts/spec";
209
- import chalk2 from "chalk";
191
+ import chalk3 from "chalk";
210
192
 
211
193
  // src/reports/diff-markdown.ts
212
194
  import * as path2 from "node:path";
@@ -788,6 +770,57 @@ async function parseAssertionsWithLLM(code) {
788
770
  }
789
771
  }
790
772
 
773
+ // src/utils/progress.ts
774
+ import chalk2 from "chalk";
775
+ class StepProgress {
776
+ steps;
777
+ currentStep = 0;
778
+ startTime;
779
+ stepStartTime;
780
+ constructor(steps) {
781
+ this.steps = steps;
782
+ this.startTime = Date.now();
783
+ this.stepStartTime = Date.now();
784
+ }
785
+ start(stepIndex) {
786
+ this.currentStep = stepIndex ?? 0;
787
+ this.stepStartTime = Date.now();
788
+ this.render();
789
+ }
790
+ next() {
791
+ this.completeCurrentStep();
792
+ this.currentStep++;
793
+ if (this.currentStep < this.steps.length) {
794
+ this.stepStartTime = Date.now();
795
+ this.render();
796
+ }
797
+ }
798
+ complete(message) {
799
+ this.completeCurrentStep();
800
+ if (message) {
801
+ const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);
802
+ console.log(`${chalk2.green("✓")} ${message} ${chalk2.dim(`(${elapsed}s)`)}`);
803
+ }
804
+ }
805
+ render() {
806
+ const step = this.steps[this.currentStep];
807
+ if (!step)
808
+ return;
809
+ const label = step.activeLabel ?? step.label;
810
+ const prefix = chalk2.dim(`[${this.currentStep + 1}/${this.steps.length}]`);
811
+ process.stdout.write(`\r${prefix} ${chalk2.cyan(label)}...`);
812
+ }
813
+ completeCurrentStep() {
814
+ const step = this.steps[this.currentStep];
815
+ if (!step)
816
+ return;
817
+ const elapsed = ((Date.now() - this.stepStartTime) / 1000).toFixed(1);
818
+ const prefix = chalk2.dim(`[${this.currentStep + 1}/${this.steps.length}]`);
819
+ process.stdout.write(`\r${" ".repeat(80)}\r`);
820
+ console.log(`${prefix} ${step.label} ${chalk2.green("✓")} ${chalk2.dim(`(${elapsed}s)`)}`);
821
+ }
822
+ }
823
+
791
824
  // src/utils/validation.ts
792
825
  function clampPercentage(value, fallback = 80) {
793
826
  if (Number.isNaN(value))
@@ -830,24 +863,32 @@ function registerCheckCommand(program, dependencies = {}) {
830
863
  };
831
864
  program.command("check [entry]").description("Check documentation coverage and output reports").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--min-coverage <percentage>", "Minimum docs coverage percentage (0-100)", (value) => Number(value)).option("--max-drift <percentage>", "Maximum drift percentage allowed (0-100)", (value) => Number(value)).option("--examples [mode]", "Example validation: presence, typecheck, run (comma-separated). Bare flag runs all.").option("--skip-resolve", "Skip external type resolution from node_modules").option("--fix", "Auto-fix drift issues").option("--write", "Alias for --fix").option("--dry-run", "Preview fixes without writing (requires --fix)").option("--format <format>", "Output format: text, json, markdown, html, github", "text").option("-o, --output <file>", "Custom output path (overrides default .doccov/ path)").option("--stdout", "Output to stdout instead of writing to .doccov/").option("--update-snapshot", "Force regenerate .doccov/report.json").option("--limit <n>", "Max exports to show in report tables", "20").option("--max-type-depth <number>", "Maximum depth for type conversion (default: 20)").option("--no-cache", "Bypass spec cache and force regeneration").action(async (entry, options) => {
832
865
  try {
866
+ const validations = parseExamplesFlag(options.examples);
867
+ const hasExamples = validations.length > 0;
868
+ const stepList = [
869
+ { label: "Resolved target", activeLabel: "Resolving target" },
870
+ { label: "Loaded config", activeLabel: "Loading config" },
871
+ { label: "Generated spec", activeLabel: "Generating spec" },
872
+ { label: "Enriched spec", activeLabel: "Enriching spec" },
873
+ ...hasExamples ? [{ label: "Validated examples", activeLabel: "Validating examples" }] : [],
874
+ { label: "Processed results", activeLabel: "Processing results" }
875
+ ];
876
+ const steps = new StepProgress(stepList);
877
+ steps.start();
833
878
  const fileSystem = new NodeFileSystem(options.cwd);
834
879
  const resolved = await resolveTarget(fileSystem, {
835
880
  cwd: options.cwd,
836
881
  package: options.package,
837
882
  entry
838
883
  });
839
- const { targetDir, entryFile, packageInfo, entryPointInfo } = resolved;
840
- if (packageInfo) {
841
- log(chalk2.gray(`Found package at ${packageInfo.path}`));
842
- }
843
- if (!entry) {
844
- log(chalk2.gray(`Auto-detected entry point: ${entryPointInfo.path} (from ${entryPointInfo.source})`));
845
- }
884
+ const { targetDir, entryFile } = resolved;
885
+ steps.next();
846
886
  const config = await loadDocCovConfig(targetDir);
847
887
  const minCoverageRaw = options.minCoverage ?? config?.check?.minCoverage;
848
888
  const minCoverage = minCoverageRaw !== undefined ? clampPercentage(minCoverageRaw) : undefined;
849
889
  const maxDriftRaw = options.maxDrift ?? config?.check?.maxDrift;
850
890
  const maxDrift = maxDriftRaw !== undefined ? clampPercentage(maxDriftRaw) : undefined;
891
+ steps.next();
851
892
  const resolveExternalTypes = !options.skipResolve;
852
893
  let specResult;
853
894
  const doccov = createDocCov({
@@ -857,14 +898,13 @@ function registerCheckCommand(program, dependencies = {}) {
857
898
  cwd: options.cwd
858
899
  });
859
900
  specResult = await doccov.analyzeFileWithDiagnostics(entryFile);
860
- if (specResult.fromCache) {
861
- log(chalk2.gray("Using cached spec"));
862
- }
863
901
  if (!specResult) {
864
902
  throw new Error("Failed to analyze documentation coverage.");
865
903
  }
904
+ steps.next();
866
905
  const spec = enrichSpec(specResult.spec);
867
906
  const format = options.format ?? "text";
907
+ steps.next();
868
908
  const specWarnings = specResult.diagnostics.filter((d) => d.severity === "warning");
869
909
  const specInfos = specResult.diagnostics.filter((d) => d.severity === "info");
870
910
  const shouldFix = options.fix || options.write;
@@ -874,11 +914,10 @@ function registerCheckCommand(program, dependencies = {}) {
874
914
  violations.push({ exportName: exp.name, violation: v });
875
915
  }
876
916
  }
877
- const validations = parseExamplesFlag(options.examples);
878
917
  let exampleResult;
879
918
  const typecheckErrors = [];
880
919
  const runtimeDrifts = [];
881
- if (validations.length > 0) {
920
+ if (hasExamples) {
882
921
  exampleResult = await validateExamples(spec.exports ?? [], {
883
922
  validations,
884
923
  packagePath: targetDir,
@@ -909,22 +948,23 @@ function registerCheckCommand(program, dependencies = {}) {
909
948
  });
910
949
  }
911
950
  }
951
+ steps.next();
912
952
  }
913
953
  const coverageScore = spec.docs?.coverageScore ?? 0;
914
954
  const allDriftExports = [...collectDrift(spec.exports ?? []), ...runtimeDrifts];
915
- let driftExports = validations.length > 0 ? allDriftExports : allDriftExports.filter((d) => d.category !== "example");
955
+ let driftExports = hasExamples ? allDriftExports : allDriftExports.filter((d) => d.category !== "example");
916
956
  const fixedDriftKeys = new Set;
917
957
  if (shouldFix && driftExports.length > 0) {
918
958
  const allDrifts = collectDriftsFromExports(spec.exports ?? []);
919
959
  if (allDrifts.length > 0) {
920
960
  const { fixable, nonFixable } = categorizeDrifts(allDrifts.map((d) => d.drift));
921
961
  if (fixable.length === 0) {
922
- log(chalk2.yellow(`Found ${nonFixable.length} drift issue(s), but none are auto-fixable.`));
962
+ log(chalk3.yellow(`Found ${nonFixable.length} drift issue(s), but none are auto-fixable.`));
923
963
  } else {
924
964
  log("");
925
- log(chalk2.bold(`Found ${fixable.length} fixable issue(s)`));
965
+ log(chalk3.bold(`Found ${fixable.length} fixable issue(s)`));
926
966
  if (nonFixable.length > 0) {
927
- log(chalk2.gray(`(${nonFixable.length} non-fixable issue(s) skipped)`));
967
+ log(chalk3.gray(`(${nonFixable.length} non-fixable issue(s) skipped)`));
928
968
  }
929
969
  log("");
930
970
  const groupedDrifts = groupByExport(allDrifts.filter((d) => fixable.includes(d.drift)));
@@ -932,22 +972,22 @@ function registerCheckCommand(program, dependencies = {}) {
932
972
  const editsByFile = new Map;
933
973
  for (const [exp, drifts] of groupedDrifts) {
934
974
  if (!exp.source?.file) {
935
- log(chalk2.gray(` Skipping ${exp.name}: no source location`));
975
+ log(chalk3.gray(` Skipping ${exp.name}: no source location`));
936
976
  continue;
937
977
  }
938
978
  if (exp.source.file.endsWith(".d.ts")) {
939
- log(chalk2.gray(` Skipping ${exp.name}: declaration file`));
979
+ log(chalk3.gray(` Skipping ${exp.name}: declaration file`));
940
980
  continue;
941
981
  }
942
982
  const filePath = path4.resolve(targetDir, exp.source.file);
943
983
  if (!fs2.existsSync(filePath)) {
944
- log(chalk2.gray(` Skipping ${exp.name}: file not found`));
984
+ log(chalk3.gray(` Skipping ${exp.name}: file not found`));
945
985
  continue;
946
986
  }
947
987
  const sourceFile = createSourceFile(filePath);
948
988
  const location = findJSDocLocation(sourceFile, exp.name, exp.source.line);
949
989
  if (!location) {
950
- log(chalk2.gray(` Skipping ${exp.name}: could not find declaration`));
990
+ log(chalk3.gray(` Skipping ${exp.name}: could not find declaration`));
951
991
  continue;
952
992
  }
953
993
  let existingPatch = {};
@@ -980,26 +1020,26 @@ function registerCheckCommand(program, dependencies = {}) {
980
1020
  }
981
1021
  if (edits.length > 0) {
982
1022
  if (options.dryRun) {
983
- log(chalk2.bold("Dry run - changes that would be made:"));
1023
+ log(chalk3.bold("Dry run - changes that would be made:"));
984
1024
  log("");
985
1025
  for (const [filePath, fileEdits] of editsByFile) {
986
1026
  const relativePath = path4.relative(targetDir, filePath);
987
- log(chalk2.cyan(` ${relativePath}:`));
1027
+ log(chalk3.cyan(` ${relativePath}:`));
988
1028
  for (const { export: exp, edit, fixes } of fileEdits) {
989
1029
  const lineInfo = edit.hasExisting ? `lines ${edit.startLine + 1}-${edit.endLine + 1}` : `line ${edit.startLine + 1}`;
990
- log(` ${chalk2.bold(exp.name)} [${lineInfo}]`);
1030
+ log(` ${chalk3.bold(exp.name)} [${lineInfo}]`);
991
1031
  for (const fix of fixes) {
992
- log(chalk2.green(` + ${fix.description}`));
1032
+ log(chalk3.green(` + ${fix.description}`));
993
1033
  }
994
1034
  }
995
1035
  log("");
996
1036
  }
997
- log(chalk2.gray("Run without --dry-run to apply these changes."));
1037
+ log(chalk3.gray("Run without --dry-run to apply these changes."));
998
1038
  } else {
999
1039
  const applyResult = await applyEdits(edits);
1000
1040
  if (applyResult.errors.length > 0) {
1001
1041
  for (const err of applyResult.errors) {
1002
- error(chalk2.red(` ${err.file}: ${err.error}`));
1042
+ error(chalk3.red(` ${err.file}: ${err.error}`));
1003
1043
  }
1004
1044
  }
1005
1045
  }
@@ -1010,6 +1050,7 @@ function registerCheckCommand(program, dependencies = {}) {
1010
1050
  driftExports = driftExports.filter((d) => !fixedDriftKeys.has(`${d.name}:${d.issue}`));
1011
1051
  }
1012
1052
  }
1053
+ steps.complete("Check complete");
1013
1054
  if (format !== "text") {
1014
1055
  const limit = parseInt(options.limit, 10) || 20;
1015
1056
  const stats = computeStats(spec);
@@ -1069,15 +1110,15 @@ function registerCheckCommand(program, dependencies = {}) {
1069
1110
  if (specWarnings.length > 0 || specInfos.length > 0) {
1070
1111
  log("");
1071
1112
  for (const diag of specWarnings) {
1072
- log(chalk2.yellow(`⚠ ${diag.message}`));
1113
+ log(chalk3.yellow(`⚠ ${diag.message}`));
1073
1114
  if (diag.suggestion) {
1074
- log(chalk2.gray(` ${diag.suggestion}`));
1115
+ log(chalk3.gray(` ${diag.suggestion}`));
1075
1116
  }
1076
1117
  }
1077
1118
  for (const diag of specInfos) {
1078
- log(chalk2.cyan(`ℹ ${diag.message}`));
1119
+ log(chalk3.cyan(`ℹ ${diag.message}`));
1079
1120
  if (diag.suggestion) {
1080
- log(chalk2.gray(` ${diag.suggestion}`));
1121
+ log(chalk3.gray(` ${diag.suggestion}`));
1081
1122
  }
1082
1123
  }
1083
1124
  }
@@ -1087,23 +1128,23 @@ function registerCheckCommand(program, dependencies = {}) {
1087
1128
  const errorCount = violations.filter((v) => v.violation.severity === "error").length;
1088
1129
  const warnCount = violations.filter((v) => v.violation.severity === "warn").length;
1089
1130
  log("");
1090
- log(chalk2.bold(`${pkgName}${pkgVersion ? `@${pkgVersion}` : ""}`));
1131
+ log(chalk3.bold(`${pkgName}${pkgVersion ? `@${pkgVersion}` : ""}`));
1091
1132
  log("");
1092
1133
  log(` Exports: ${totalExports}`);
1093
1134
  if (minCoverage !== undefined) {
1094
1135
  if (coverageFailed) {
1095
- log(chalk2.red(` Coverage: ✗ ${coverageScore}%`) + chalk2.dim(` (min ${minCoverage}%)`));
1136
+ log(chalk3.red(` Coverage: ✗ ${coverageScore}%`) + chalk3.dim(` (min ${minCoverage}%)`));
1096
1137
  } else {
1097
- log(chalk2.green(` Coverage: ✓ ${coverageScore}%`) + chalk2.dim(` (min ${minCoverage}%)`));
1138
+ log(chalk3.green(` Coverage: ✓ ${coverageScore}%`) + chalk3.dim(` (min ${minCoverage}%)`));
1098
1139
  }
1099
1140
  } else {
1100
1141
  log(` Coverage: ${coverageScore}%`);
1101
1142
  }
1102
1143
  if (maxDrift !== undefined) {
1103
1144
  if (driftFailed) {
1104
- log(chalk2.red(` Drift: ✗ ${driftScore}%`) + chalk2.dim(` (max ${maxDrift}%)`));
1145
+ log(chalk3.red(` Drift: ✗ ${driftScore}%`) + chalk3.dim(` (max ${maxDrift}%)`));
1105
1146
  } else {
1106
- log(chalk2.green(` Drift: ✓ ${driftScore}%`) + chalk2.dim(` (max ${maxDrift}%)`));
1147
+ log(chalk3.green(` Drift: ✓ ${driftScore}%`) + chalk3.dim(` (max ${maxDrift}%)`));
1107
1148
  }
1108
1149
  } else {
1109
1150
  log(` Drift: ${driftScore}%`);
@@ -1113,7 +1154,7 @@ function registerCheckCommand(program, dependencies = {}) {
1113
1154
  if (typecheckCount > 0) {
1114
1155
  log(` Examples: ${typecheckCount} type errors`);
1115
1156
  } else {
1116
- log(chalk2.green(` Examples: ✓ validated`));
1157
+ log(chalk3.green(` Examples: ✓ validated`));
1117
1158
  }
1118
1159
  }
1119
1160
  if (errorCount > 0 || warnCount > 0) {
@@ -1135,24 +1176,24 @@ function registerCheckCommand(program, dependencies = {}) {
1135
1176
  thresholdParts.push(`drift ${driftScore}% ≤ ${maxDrift}%`);
1136
1177
  }
1137
1178
  if (thresholdParts.length > 0) {
1138
- log(chalk2.green(`✓ Check passed (${thresholdParts.join(", ")})`));
1179
+ log(chalk3.green(`✓ Check passed (${thresholdParts.join(", ")})`));
1139
1180
  } else {
1140
- log(chalk2.green("✓ Check passed"));
1141
- log(chalk2.dim(" No thresholds configured. Use --min-coverage or --max-drift to enforce."));
1181
+ log(chalk3.green("✓ Check passed"));
1182
+ log(chalk3.dim(" No thresholds configured. Use --min-coverage or --max-drift to enforce."));
1142
1183
  }
1143
1184
  return;
1144
1185
  }
1145
1186
  if (hasQualityErrors) {
1146
- log(chalk2.red(`✗ ${errorCount} quality errors`));
1187
+ log(chalk3.red(`✗ ${errorCount} quality errors`));
1147
1188
  }
1148
1189
  if (hasTypecheckErrors) {
1149
- log(chalk2.red(`✗ ${typecheckErrors.length} example type errors`));
1190
+ log(chalk3.red(`✗ ${typecheckErrors.length} example type errors`));
1150
1191
  }
1151
1192
  log("");
1152
- log(chalk2.dim("Use --format json or --format markdown for detailed reports"));
1193
+ log(chalk3.dim("Use --format json or --format markdown for detailed reports"));
1153
1194
  process.exit(1);
1154
1195
  } catch (commandError) {
1155
- error(chalk2.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
1196
+ error(chalk3.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
1156
1197
  process.exit(1);
1157
1198
  }
1158
1199
  });
@@ -1189,7 +1230,7 @@ import {
1189
1230
  hashString,
1190
1231
  parseMarkdownFiles
1191
1232
  } from "@doccov/sdk";
1192
- import chalk3 from "chalk";
1233
+ import chalk4 from "chalk";
1193
1234
  import { glob } from "glob";
1194
1235
 
1195
1236
  // src/utils/docs-impact-ai.ts
@@ -1335,8 +1376,8 @@ function registerDiffCommand(program, dependencies = {}) {
1335
1376
  silent: true
1336
1377
  });
1337
1378
  }
1338
- const cacheNote = fromCache ? chalk3.cyan(" (cached)") : "";
1339
- log(chalk3.dim(`Report: ${jsonPath}`) + cacheNote);
1379
+ const cacheNote = fromCache ? chalk4.cyan(" (cached)") : "";
1380
+ log(chalk4.dim(`Report: ${jsonPath}`) + cacheNote);
1340
1381
  }
1341
1382
  break;
1342
1383
  case "json": {
@@ -1394,18 +1435,18 @@ function registerDiffCommand(program, dependencies = {}) {
1394
1435
  checks
1395
1436
  });
1396
1437
  if (failures.length > 0) {
1397
- log(chalk3.red(`
1438
+ log(chalk4.red(`
1398
1439
  ✗ Check failed`));
1399
1440
  for (const f of failures) {
1400
- log(chalk3.red(` - ${f}`));
1441
+ log(chalk4.red(` - ${f}`));
1401
1442
  }
1402
1443
  process.exitCode = 1;
1403
1444
  } else if (options.strict || minCoverage !== undefined || maxDrift !== undefined) {
1404
- log(chalk3.green(`
1445
+ log(chalk4.green(`
1405
1446
  ✓ All checks passed`));
1406
1447
  }
1407
1448
  } catch (commandError) {
1408
- error(chalk3.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
1449
+ error(chalk4.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
1409
1450
  process.exitCode = 1;
1410
1451
  }
1411
1452
  });
@@ -1432,7 +1473,7 @@ async function generateDiff(baseSpec, headSpec, options, config, log) {
1432
1473
  if (!docsPatterns || docsPatterns.length === 0) {
1433
1474
  if (config?.docs?.include) {
1434
1475
  docsPatterns = config.docs.include;
1435
- log(chalk3.gray(`Using docs patterns from config: ${docsPatterns.join(", ")}`));
1476
+ log(chalk4.gray(`Using docs patterns from config: ${docsPatterns.join(", ")}`));
1436
1477
  }
1437
1478
  }
1438
1479
  if (docsPatterns && docsPatterns.length > 0) {
@@ -1455,46 +1496,46 @@ function loadSpec(filePath, readFileSync2) {
1455
1496
  }
1456
1497
  function printSummary(diff, baseName, headName, fromCache, log) {
1457
1498
  log("");
1458
- const cacheIndicator = fromCache ? chalk3.cyan(" (cached)") : "";
1459
- log(chalk3.bold(`Comparing: ${baseName} → ${headName}`) + cacheIndicator);
1499
+ const cacheIndicator = fromCache ? chalk4.cyan(" (cached)") : "";
1500
+ log(chalk4.bold(`Comparing: ${baseName} → ${headName}`) + cacheIndicator);
1460
1501
  log("─".repeat(40));
1461
1502
  log("");
1462
- const coverageColor = diff.coverageDelta > 0 ? chalk3.green : diff.coverageDelta < 0 ? chalk3.red : chalk3.gray;
1503
+ const coverageColor = diff.coverageDelta > 0 ? chalk4.green : diff.coverageDelta < 0 ? chalk4.red : chalk4.gray;
1463
1504
  const coverageSign = diff.coverageDelta > 0 ? "+" : "";
1464
1505
  log(` Coverage: ${diff.oldCoverage}% → ${diff.newCoverage}% ${coverageColor(`(${coverageSign}${diff.coverageDelta}%)`)}`);
1465
1506
  const breakingCount = diff.breaking.length;
1466
1507
  const highSeverity = diff.categorizedBreaking?.filter((c) => c.severity === "high").length ?? 0;
1467
1508
  if (breakingCount > 0) {
1468
- const severityNote = highSeverity > 0 ? chalk3.red(` (${highSeverity} high severity)`) : "";
1469
- log(` Breaking: ${chalk3.red(breakingCount)} changes${severityNote}`);
1509
+ const severityNote = highSeverity > 0 ? chalk4.red(` (${highSeverity} high severity)`) : "";
1510
+ log(` Breaking: ${chalk4.red(breakingCount)} changes${severityNote}`);
1470
1511
  } else {
1471
- log(` Breaking: ${chalk3.green("0")} changes`);
1512
+ log(` Breaking: ${chalk4.green("0")} changes`);
1472
1513
  }
1473
1514
  const newCount = diff.nonBreaking.length;
1474
1515
  const undocCount = diff.newUndocumented.length;
1475
1516
  if (newCount > 0) {
1476
- const undocNote = undocCount > 0 ? chalk3.yellow(` (${undocCount} undocumented)`) : "";
1477
- log(` New: ${chalk3.green(newCount)} exports${undocNote}`);
1517
+ const undocNote = undocCount > 0 ? chalk4.yellow(` (${undocCount} undocumented)`) : "";
1518
+ log(` New: ${chalk4.green(newCount)} exports${undocNote}`);
1478
1519
  }
1479
1520
  if (diff.driftIntroduced > 0 || diff.driftResolved > 0) {
1480
1521
  const parts = [];
1481
1522
  if (diff.driftIntroduced > 0)
1482
- parts.push(chalk3.red(`+${diff.driftIntroduced}`));
1523
+ parts.push(chalk4.red(`+${diff.driftIntroduced}`));
1483
1524
  if (diff.driftResolved > 0)
1484
- parts.push(chalk3.green(`-${diff.driftResolved}`));
1525
+ parts.push(chalk4.green(`-${diff.driftResolved}`));
1485
1526
  log(` Drift: ${parts.join(", ")}`);
1486
1527
  }
1487
1528
  log("");
1488
1529
  }
1489
1530
  async function printAISummary(diff, log) {
1490
1531
  if (!isAIDocsAnalysisAvailable()) {
1491
- log(chalk3.yellow(`
1532
+ log(chalk4.yellow(`
1492
1533
  ⚠ AI analysis unavailable (set OPENAI_API_KEY or ANTHROPIC_API_KEY)`));
1493
1534
  return;
1494
1535
  }
1495
1536
  if (!diff.docsImpact)
1496
1537
  return;
1497
- log(chalk3.gray(`
1538
+ log(chalk4.gray(`
1498
1539
  Generating AI summary...`));
1499
1540
  const impacts = diff.docsImpact.impactedFiles.flatMap((f) => f.references.map((r) => ({
1500
1541
  file: f.file,
@@ -1505,8 +1546,8 @@ Generating AI summary...`));
1505
1546
  const summary = await generateImpactSummary(impacts);
1506
1547
  if (summary) {
1507
1548
  log("");
1508
- log(chalk3.bold("AI Summary"));
1509
- log(chalk3.cyan(` ${summary}`));
1549
+ log(chalk4.bold("AI Summary"));
1550
+ log(chalk4.cyan(` ${summary}`));
1510
1551
  }
1511
1552
  }
1512
1553
  function validateDiff(diff, headSpec, options) {
@@ -1587,7 +1628,7 @@ function printGitHubAnnotations(diff, log) {
1587
1628
 
1588
1629
  // src/commands/info.ts
1589
1630
  import { DocCov as DocCov2, enrichSpec as enrichSpec2, NodeFileSystem as NodeFileSystem2, resolveTarget as resolveTarget2 } from "@doccov/sdk";
1590
- import chalk4 from "chalk";
1631
+ import chalk5 from "chalk";
1591
1632
  function registerInfoCommand(program) {
1592
1633
  program.command("info [entry]").description("Show brief documentation coverage summary").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--skip-resolve", "Skip external type resolution from node_modules").action(async (entry, options) => {
1593
1634
  try {
@@ -1609,14 +1650,14 @@ function registerInfoCommand(program) {
1609
1650
  const spec = enrichSpec2(specResult.spec);
1610
1651
  const stats = computeStats(spec);
1611
1652
  console.log("");
1612
- console.log(chalk4.bold(`${stats.packageName}@${stats.version}`));
1653
+ console.log(chalk5.bold(`${stats.packageName}@${stats.version}`));
1613
1654
  console.log("");
1614
- console.log(` Exports: ${chalk4.bold(stats.totalExports.toString())}`);
1615
- console.log(` Coverage: ${chalk4.bold(`${stats.coverageScore}%`)}`);
1616
- console.log(` Drift: ${chalk4.bold(`${stats.driftScore}%`)}`);
1655
+ console.log(` Exports: ${chalk5.bold(stats.totalExports.toString())}`);
1656
+ console.log(` Coverage: ${chalk5.bold(`${stats.coverageScore}%`)}`);
1657
+ console.log(` Drift: ${chalk5.bold(`${stats.driftScore}%`)}`);
1617
1658
  console.log("");
1618
1659
  } catch (err) {
1619
- console.error(chalk4.red("Error:"), err instanceof Error ? err.message : err);
1660
+ console.error(chalk5.red("Error:"), err instanceof Error ? err.message : err);
1620
1661
  process.exit(1);
1621
1662
  }
1622
1663
  });
@@ -1625,7 +1666,7 @@ function registerInfoCommand(program) {
1625
1666
  // src/commands/init.ts
1626
1667
  import * as fs4 from "node:fs";
1627
1668
  import * as path6 from "node:path";
1628
- import chalk5 from "chalk";
1669
+ import chalk6 from "chalk";
1629
1670
  var defaultDependencies3 = {
1630
1671
  fileExists: fs4.existsSync,
1631
1672
  writeFileSync: fs4.writeFileSync,
@@ -1642,31 +1683,31 @@ function registerInitCommand(program, dependencies = {}) {
1642
1683
  const cwd = path6.resolve(options.cwd);
1643
1684
  const formatOption = String(options.format ?? "auto").toLowerCase();
1644
1685
  if (!isValidFormat(formatOption)) {
1645
- error(chalk5.red(`Invalid format "${formatOption}". Use auto, mjs, js, or cjs.`));
1686
+ error(chalk6.red(`Invalid format "${formatOption}". Use auto, mjs, js, or cjs.`));
1646
1687
  process.exitCode = 1;
1647
1688
  return;
1648
1689
  }
1649
1690
  const existing = findExistingConfig(cwd, fileExists2);
1650
1691
  if (existing) {
1651
- error(chalk5.red(`A DocCov config already exists at ${path6.relative(cwd, existing) || "./doccov.config.*"}.`));
1692
+ error(chalk6.red(`A DocCov config already exists at ${path6.relative(cwd, existing) || "./doccov.config.*"}.`));
1652
1693
  process.exitCode = 1;
1653
1694
  return;
1654
1695
  }
1655
1696
  const packageType = detectPackageType(cwd, fileExists2, readFileSync3);
1656
1697
  const targetFormat = resolveFormat(formatOption, packageType);
1657
1698
  if (targetFormat === "js" && packageType !== "module") {
1658
- log(chalk5.yellow('Package is not marked as "type": "module"; creating doccov.config.js may require enabling ESM.'));
1699
+ log(chalk6.yellow('Package is not marked as "type": "module"; creating doccov.config.js may require enabling ESM.'));
1659
1700
  }
1660
1701
  const fileName = `doccov.config.${targetFormat}`;
1661
1702
  const outputPath = path6.join(cwd, fileName);
1662
1703
  if (fileExists2(outputPath)) {
1663
- error(chalk5.red(`Cannot create ${fileName}; file already exists.`));
1704
+ error(chalk6.red(`Cannot create ${fileName}; file already exists.`));
1664
1705
  process.exitCode = 1;
1665
1706
  return;
1666
1707
  }
1667
1708
  const template = buildTemplate(targetFormat);
1668
1709
  writeFileSync3(outputPath, template, { encoding: "utf8" });
1669
- log(chalk5.green(`✓ Created ${path6.relative(process.cwd(), outputPath)}`));
1710
+ log(chalk6.green(`✓ Created ${path6.relative(process.cwd(), outputPath)}`));
1670
1711
  });
1671
1712
  }
1672
1713
  var isValidFormat = (value) => {
@@ -1775,508 +1816,14 @@ var buildTemplate = (format) => {
1775
1816
  `);
1776
1817
  };
1777
1818
 
1778
- // src/commands/scan.ts
1779
- import * as fs6 from "node:fs";
1780
- import * as fsPromises from "node:fs/promises";
1781
- import * as os from "node:os";
1782
- import * as path8 from "node:path";
1783
- import {
1784
- buildCloneUrl,
1785
- buildDisplayUrl,
1786
- DocCov as DocCov3,
1787
- detectBuildInfo,
1788
- detectEntryPoint,
1789
- detectMonorepo,
1790
- detectPackageManager,
1791
- extractSpecSummary,
1792
- findPackageByName,
1793
- formatPackageList,
1794
- getInstallCommand,
1795
- NodeFileSystem as NodeFileSystem3,
1796
- parseGitHubUrl
1797
- } from "@doccov/sdk";
1798
- import {
1799
- DRIFT_CATEGORIES as DRIFT_CATEGORIES3,
1800
- DRIFT_CATEGORY_LABELS as DRIFT_CATEGORY_LABELS2
1801
- } from "@openpkg-ts/spec";
1802
- import chalk6 from "chalk";
1803
- import { simpleGit } from "simple-git";
1804
-
1805
- // src/utils/llm-build-plan.ts
1819
+ // src/commands/spec.ts
1806
1820
  import * as fs5 from "node:fs";
1807
1821
  import * as path7 from "node:path";
1808
- import { createAnthropic as createAnthropic3 } from "@ai-sdk/anthropic";
1809
- import { createOpenAI as createOpenAI3 } from "@ai-sdk/openai";
1810
- import { generateObject as generateObject3 } from "ai";
1811
- import { z as z4 } from "zod";
1812
- var BuildPlanSchema = z4.object({
1813
- installCommand: z4.string().optional().describe("Additional install command if needed"),
1814
- buildCommands: z4.array(z4.string()).describe('Build steps to run, e.g. ["npm run build:wasm"]'),
1815
- entryPoint: z4.string().describe("Path to TS/TSX entry file after build"),
1816
- notes: z4.string().optional().describe("Caveats or warnings")
1817
- });
1818
- var CONTEXT_FILES = [
1819
- "package.json",
1820
- "README.md",
1821
- "README",
1822
- "tsconfig.json",
1823
- "Cargo.toml",
1824
- ".nvmrc",
1825
- ".node-version",
1826
- "pnpm-workspace.yaml",
1827
- "lerna.json",
1828
- "wasm-pack.json"
1829
- ];
1830
- var MAX_FILE_CHARS = 2000;
1831
- function getModel3() {
1832
- const provider = process.env.DOCCOV_LLM_PROVIDER?.toLowerCase();
1833
- if (provider === "anthropic" || process.env.ANTHROPIC_API_KEY) {
1834
- const anthropic = createAnthropic3();
1835
- return anthropic("claude-sonnet-4-20250514");
1836
- }
1837
- const openai = createOpenAI3();
1838
- return openai("gpt-4o-mini");
1839
- }
1840
- async function gatherContextFiles(repoDir) {
1841
- const sections = [];
1842
- for (const fileName of CONTEXT_FILES) {
1843
- const filePath = path7.join(repoDir, fileName);
1844
- if (fs5.existsSync(filePath)) {
1845
- try {
1846
- let content = fs5.readFileSync(filePath, "utf-8");
1847
- if (content.length > MAX_FILE_CHARS) {
1848
- content = `${content.slice(0, MAX_FILE_CHARS)}
1849
- ... (truncated)`;
1850
- }
1851
- sections.push(`--- ${fileName} ---
1852
- ${content}`);
1853
- } catch {}
1854
- }
1855
- }
1856
- return sections.join(`
1857
-
1858
- `);
1859
- }
1860
- var BUILD_PLAN_PROMPT = (context) => `Analyze this project to determine how to build it for TypeScript API analysis.
1861
-
1862
- The standard entry detection failed. This might be a WASM project, unusual monorepo, or require a build step before the TypeScript entry point exists.
1863
-
1864
- <files>
1865
- ${context}
1866
- </files>
1867
-
1868
- Return:
1869
- - buildCommands: Commands to run in order (e.g., ["npm run build:wasm", "npm run build"]). Empty array if no build needed.
1870
- - entryPoint: Path to the TypeScript entry file AFTER build completes (e.g., "src/index.ts" or "pkg/index.d.ts")
1871
- - installCommand: Additional install command if needed beyond what was already run
1872
- - notes: Any caveats (e.g., "requires Rust/wasm-pack installed")
1873
-
1874
- Important:
1875
- - Look for build scripts in package.json that might generate TypeScript bindings
1876
- - Check README for build instructions
1877
- - For WASM projects, look for wasm-pack or similar tooling
1878
- - The entry point should be a .ts, .tsx, or .d.ts file`;
1879
- async function generateBuildPlan(repoDir) {
1880
- const hasApiKey = process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY;
1881
- if (!hasApiKey) {
1882
- return null;
1883
- }
1884
- const context = await gatherContextFiles(repoDir);
1885
- if (!context.trim()) {
1886
- return null;
1887
- }
1888
- const model = getModel3();
1889
- const { object } = await generateObject3({
1890
- model,
1891
- schema: BuildPlanSchema,
1892
- prompt: BUILD_PLAN_PROMPT(context)
1893
- });
1894
- return object;
1895
- }
1896
-
1897
- // src/commands/scan.ts
1898
- var defaultDependencies4 = {
1899
- createDocCov: (options) => new DocCov3(options),
1900
- log: console.log,
1901
- error: console.error
1902
- };
1903
- function registerScanCommand(program, dependencies = {}) {
1904
- const { createDocCov, log, error } = {
1905
- ...defaultDependencies4,
1906
- ...dependencies
1907
- };
1908
- program.command("scan <url>").description("Analyze docs coverage for any public GitHub repository").option("--ref <branch>", "Branch or tag to analyze").option("--package <name>", "Target package in monorepo").option("--output <format>", "Output format: text or json", "text").option("--no-cleanup", "Keep cloned repo (for debugging)").option("--skip-install", "Skip dependency installation (faster, but may limit type resolution)").option("--skip-resolve", "Skip external type resolution from node_modules").option("--save-spec <path>", "Save full OpenPkg spec to file").action(async (url, options) => {
1909
- let tempDir;
1910
- try {
1911
- const parsed = parseGitHubUrl(url, options.ref ?? "main");
1912
- const cloneUrl = buildCloneUrl(parsed);
1913
- const displayUrl = buildDisplayUrl(parsed);
1914
- log("");
1915
- log(chalk6.bold(`Scanning ${displayUrl}`));
1916
- log(chalk6.gray(`Branch/tag: ${parsed.ref}`));
1917
- log("");
1918
- tempDir = path8.join(os.tmpdir(), `doccov-scan-${Date.now()}-${Math.random().toString(36).slice(2)}`);
1919
- fs6.mkdirSync(tempDir, { recursive: true });
1920
- process.stdout.write(chalk6.cyan(`> Cloning ${parsed.owner}/${parsed.repo}...
1921
- `));
1922
- try {
1923
- const git = simpleGit({
1924
- timeout: {
1925
- block: 30000
1926
- }
1927
- });
1928
- const originalEnv = { ...process.env };
1929
- process.env.GIT_TERMINAL_PROMPT = "0";
1930
- process.env.GIT_ASKPASS = "echo";
1931
- try {
1932
- await git.clone(cloneUrl, tempDir, [
1933
- "--depth",
1934
- "1",
1935
- "--branch",
1936
- parsed.ref,
1937
- "--single-branch"
1938
- ]);
1939
- } finally {
1940
- process.env = originalEnv;
1941
- }
1942
- process.stdout.write(chalk6.green(`✓ Cloned ${parsed.owner}/${parsed.repo}
1943
- `));
1944
- } catch (cloneError) {
1945
- process.stdout.write(chalk6.red(`✗ Failed to clone repository
1946
- `));
1947
- const message = cloneError instanceof Error ? cloneError.message : String(cloneError);
1948
- if (message.includes("Authentication failed") || message.includes("could not read Username") || message.includes("terminal prompts disabled") || message.includes("Invalid username or password") || message.includes("Permission denied")) {
1949
- throw new Error(`Authentication required: This repository appears to be private. ` + `Public repositories only are currently supported.
1950
- ` + `Repository: ${displayUrl}`);
1951
- }
1952
- if (message.includes("not found") || message.includes("404")) {
1953
- throw new Error(`Repository not accessible or does not exist: ${displayUrl}
1954
- ` + `Note: Private repositories are not currently supported.`);
1955
- }
1956
- if (message.includes("Could not find remote branch")) {
1957
- throw new Error(`Branch or tag not found: ${parsed.ref}`);
1958
- }
1959
- throw new Error(`Clone failed: ${message}`);
1960
- }
1961
- const fileSystem = new NodeFileSystem3(tempDir);
1962
- if (options.skipInstall) {
1963
- log(chalk6.gray("Skipping dependency installation (--skip-install)"));
1964
- } else {
1965
- process.stdout.write(chalk6.cyan(`> Installing dependencies...
1966
- `));
1967
- const installErrors = [];
1968
- try {
1969
- const { execSync } = await import("node:child_process");
1970
- const pmInfo = await detectPackageManager(fileSystem);
1971
- const installCmd = getInstallCommand(pmInfo);
1972
- const cmdString = installCmd.join(" ");
1973
- let installed = false;
1974
- if (pmInfo.lockfile) {
1975
- try {
1976
- execSync(cmdString, {
1977
- cwd: tempDir,
1978
- stdio: "pipe",
1979
- timeout: 180000
1980
- });
1981
- installed = true;
1982
- } catch (cmdError) {
1983
- const stderr = cmdError?.stderr?.toString() ?? "";
1984
- const msg = cmdError instanceof Error ? cmdError.message : String(cmdError);
1985
- installErrors.push(`[${cmdString}] ${stderr.slice(0, 150) || msg.slice(0, 150)}`);
1986
- }
1987
- }
1988
- if (!installed) {
1989
- try {
1990
- execSync("bun install", {
1991
- cwd: tempDir,
1992
- stdio: "pipe",
1993
- timeout: 120000
1994
- });
1995
- installed = true;
1996
- } catch (bunError) {
1997
- const stderr = bunError?.stderr?.toString() ?? "";
1998
- const msg = bunError instanceof Error ? bunError.message : String(bunError);
1999
- installErrors.push(`[bun install] ${stderr.slice(0, 150) || msg.slice(0, 150)}`);
2000
- try {
2001
- execSync("npm install --legacy-peer-deps --ignore-scripts", {
2002
- cwd: tempDir,
2003
- stdio: "pipe",
2004
- timeout: 180000
2005
- });
2006
- installed = true;
2007
- } catch (npmError) {
2008
- const npmStderr = npmError?.stderr?.toString() ?? "";
2009
- const npmMsg = npmError instanceof Error ? npmError.message : String(npmError);
2010
- installErrors.push(`[npm install] ${npmStderr.slice(0, 150) || npmMsg.slice(0, 150)}`);
2011
- }
2012
- }
2013
- }
2014
- if (installed) {
2015
- process.stdout.write(chalk6.green(`✓ Dependencies installed
2016
- `));
2017
- } else {
2018
- process.stdout.write(chalk6.yellow(`⚠ Could not install dependencies (analysis may be limited)
2019
- `));
2020
- for (const err of installErrors) {
2021
- log(chalk6.gray(` ${err}`));
2022
- }
2023
- }
2024
- } catch (outerError) {
2025
- const msg = outerError instanceof Error ? outerError.message : String(outerError);
2026
- process.stdout.write(chalk6.yellow(`⚠ Could not install dependencies: ${msg.slice(0, 100)}
2027
- `));
2028
- for (const err of installErrors) {
2029
- log(chalk6.gray(` ${err}`));
2030
- }
2031
- }
2032
- }
2033
- let targetDir = tempDir;
2034
- let packageName;
2035
- const mono = await detectMonorepo(fileSystem);
2036
- if (mono.isMonorepo) {
2037
- if (!options.package) {
2038
- error("");
2039
- error(chalk6.red(`Monorepo detected with ${mono.packages.length} packages. Specify target with --package:`));
2040
- error("");
2041
- error(formatPackageList(mono.packages));
2042
- error("");
2043
- throw new Error("Monorepo requires --package flag");
2044
- }
2045
- const pkg = findPackageByName(mono.packages, options.package);
2046
- if (!pkg) {
2047
- error("");
2048
- error(chalk6.red(`Package "${options.package}" not found. Available packages:`));
2049
- error("");
2050
- error(formatPackageList(mono.packages));
2051
- error("");
2052
- throw new Error(`Package not found: ${options.package}`);
2053
- }
2054
- targetDir = path8.join(tempDir, pkg.path);
2055
- packageName = pkg.name;
2056
- log(chalk6.gray(`Analyzing package: ${packageName}`));
2057
- }
2058
- process.stdout.write(chalk6.cyan(`> Detecting entry point...
2059
- `));
2060
- let entryPath;
2061
- const targetFs = mono.isMonorepo ? new NodeFileSystem3(targetDir) : fileSystem;
2062
- let buildFailed = false;
2063
- const runLlmFallback = async (reason) => {
2064
- process.stdout.write(chalk6.cyan(`> ${reason}, trying LLM fallback...
2065
- `));
2066
- const plan = await generateBuildPlan(targetDir);
2067
- if (!plan) {
2068
- return null;
2069
- }
2070
- if (plan.buildCommands.length > 0) {
2071
- const { execSync } = await import("node:child_process");
2072
- for (const cmd of plan.buildCommands) {
2073
- log(chalk6.gray(` Running: ${cmd}`));
2074
- try {
2075
- execSync(cmd, { cwd: targetDir, stdio: "pipe", timeout: 300000 });
2076
- } catch (buildError) {
2077
- buildFailed = true;
2078
- const msg = buildError instanceof Error ? buildError.message : String(buildError);
2079
- if (msg.includes("rustc") || msg.includes("cargo") || msg.includes("wasm-pack")) {
2080
- log(chalk6.yellow(` ⚠ Build requires Rust toolchain (not available)`));
2081
- } else if (msg.includes("rimraf") || msg.includes("command not found")) {
2082
- log(chalk6.yellow(` ⚠ Build failed: missing dependencies`));
2083
- } else {
2084
- log(chalk6.yellow(` ⚠ Build failed: ${msg.slice(0, 80)}`));
2085
- }
2086
- }
2087
- }
2088
- }
2089
- if (plan.notes) {
2090
- log(chalk6.gray(` Note: ${plan.notes}`));
2091
- }
2092
- return plan.entryPoint;
2093
- };
2094
- try {
2095
- const entry = await detectEntryPoint(targetFs);
2096
- const buildInfo = await detectBuildInfo(targetFs);
2097
- const needsBuildStep = entry.isDeclarationOnly && buildInfo.exoticIndicators.wasm;
2098
- if (needsBuildStep) {
2099
- process.stdout.write(chalk6.cyan(`> Detected .d.ts entry with WASM indicators...
2100
- `));
2101
- const llmEntry = await runLlmFallback("WASM project detected");
2102
- if (llmEntry) {
2103
- entryPath = path8.join(targetDir, llmEntry);
2104
- if (buildFailed) {
2105
- process.stdout.write(chalk6.green(`✓ Entry point: ${llmEntry} (using pre-committed declarations)
2106
- `));
2107
- log(chalk6.gray(" Coverage may be limited - generated .d.ts files typically lack JSDoc"));
2108
- } else {
2109
- process.stdout.write(chalk6.green(`✓ Entry point: ${llmEntry} (from LLM fallback - WASM project)
2110
- `));
2111
- }
2112
- } else {
2113
- entryPath = path8.join(targetDir, entry.path);
2114
- process.stdout.write(chalk6.green(`✓ Entry point: ${entry.path} (from ${entry.source})
2115
- `));
2116
- log(chalk6.yellow(" ⚠ WASM project detected but no API key - analysis may be limited"));
2117
- }
2118
- } else {
2119
- entryPath = path8.join(targetDir, entry.path);
2120
- process.stdout.write(chalk6.green(`✓ Entry point: ${entry.path} (from ${entry.source})
2121
- `));
2122
- }
2123
- } catch (entryError) {
2124
- const llmEntry = await runLlmFallback("Heuristics failed");
2125
- if (llmEntry) {
2126
- entryPath = path8.join(targetDir, llmEntry);
2127
- process.stdout.write(chalk6.green(`✓ Entry point: ${llmEntry} (from LLM fallback)
2128
- `));
2129
- } else {
2130
- process.stdout.write(chalk6.red(`✗ Could not detect entry point (set OPENAI_API_KEY for smart fallback)
2131
- `));
2132
- throw entryError;
2133
- }
2134
- }
2135
- process.stdout.write(chalk6.cyan(`> Analyzing documentation coverage...
2136
- `));
2137
- let result;
2138
- try {
2139
- const resolveExternalTypes = !options.skipResolve;
2140
- const doccov = createDocCov({ resolveExternalTypes });
2141
- result = await doccov.analyzeFileWithDiagnostics(entryPath);
2142
- process.stdout.write(chalk6.green(`✓ Analysis complete
2143
- `));
2144
- } catch (analysisError) {
2145
- process.stdout.write(chalk6.red(`✗ Analysis failed
2146
- `));
2147
- throw analysisError;
2148
- }
2149
- const spec = result.spec;
2150
- if (options.saveSpec) {
2151
- const specPath = path8.resolve(process.cwd(), options.saveSpec);
2152
- fs6.writeFileSync(specPath, JSON.stringify(spec, null, 2));
2153
- log(chalk6.green(`✓ Saved spec to ${options.saveSpec}`));
2154
- }
2155
- const summary = extractSpecSummary(spec);
2156
- const scanResult = {
2157
- owner: parsed.owner,
2158
- repo: parsed.repo,
2159
- ref: parsed.ref,
2160
- packageName,
2161
- coverage: summary.coverage,
2162
- exportCount: summary.exportCount,
2163
- typeCount: summary.typeCount,
2164
- driftCount: summary.driftCount,
2165
- undocumented: summary.undocumented,
2166
- drift: summary.drift
2167
- };
2168
- if (options.output === "json") {
2169
- log(JSON.stringify(scanResult, null, 2));
2170
- } else {
2171
- printTextResult(scanResult, log);
2172
- }
2173
- } catch (commandError) {
2174
- error(chalk6.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
2175
- process.exitCode = 1;
2176
- } finally {
2177
- if (tempDir && options.cleanup !== false) {
2178
- fsPromises.rm(tempDir, { recursive: true, force: true }).catch(() => {});
2179
- } else if (tempDir) {
2180
- log(chalk6.gray(`Repo preserved at: ${tempDir}`));
2181
- }
2182
- }
2183
- });
2184
- }
2185
- function categorizeDriftIssues(drift) {
2186
- const byCategory = {
2187
- structural: [],
2188
- semantic: [],
2189
- example: []
2190
- };
2191
- for (const d of drift) {
2192
- const category = DRIFT_CATEGORIES3[d.type] ?? "semantic";
2193
- byCategory[category].push(d);
2194
- }
2195
- return {
2196
- summary: {
2197
- total: drift.length,
2198
- byCategory: {
2199
- structural: byCategory.structural.length,
2200
- semantic: byCategory.semantic.length,
2201
- example: byCategory.example.length
2202
- }
2203
- },
2204
- byCategory
2205
- };
2206
- }
2207
- function formatDriftSummary(summary) {
2208
- if (summary.total === 0) {
2209
- return "No drift detected";
2210
- }
2211
- const parts = [];
2212
- if (summary.byCategory.structural > 0) {
2213
- parts.push(`${summary.byCategory.structural} structural`);
2214
- }
2215
- if (summary.byCategory.semantic > 0) {
2216
- parts.push(`${summary.byCategory.semantic} semantic`);
2217
- }
2218
- if (summary.byCategory.example > 0) {
2219
- parts.push(`${summary.byCategory.example} example`);
2220
- }
2221
- return `${summary.total} issues (${parts.join(", ")})`;
2222
- }
2223
- function printTextResult(result, log) {
2224
- log("");
2225
- log(chalk6.bold("DocCov Scan Results"));
2226
- log("─".repeat(40));
2227
- const repoName = result.packageName ? `${result.owner}/${result.repo} (${result.packageName})` : `${result.owner}/${result.repo}`;
2228
- log(`Repository: ${chalk6.cyan(repoName)}`);
2229
- log(`Branch: ${chalk6.gray(result.ref)}`);
2230
- log("");
2231
- const coverageColor = result.coverage >= 80 ? chalk6.green : result.coverage >= 50 ? chalk6.yellow : chalk6.red;
2232
- log(chalk6.bold("Coverage"));
2233
- log(` ${coverageColor(`${result.coverage}%`)}`);
2234
- log("");
2235
- log(chalk6.bold("Stats"));
2236
- log(` ${result.exportCount} exports`);
2237
- log(` ${result.typeCount} types`);
2238
- log(` ${result.undocumented.length} undocumented`);
2239
- const categorized = categorizeDriftIssues(result.drift);
2240
- const driftColor = result.driftCount > 0 ? chalk6.yellow : chalk6.green;
2241
- log(` ${driftColor(formatDriftSummary(categorized.summary))}`);
2242
- if (result.undocumented.length > 0) {
2243
- log("");
2244
- log(chalk6.bold("Undocumented Exports"));
2245
- for (const name of result.undocumented.slice(0, 10)) {
2246
- log(chalk6.yellow(` ! ${name}`));
2247
- }
2248
- if (result.undocumented.length > 10) {
2249
- log(chalk6.gray(` ... and ${result.undocumented.length - 10} more`));
2250
- }
2251
- }
2252
- if (result.drift.length > 0) {
2253
- log("");
2254
- log(chalk6.bold("Drift Issues"));
2255
- const categories = ["structural", "semantic", "example"];
2256
- for (const category of categories) {
2257
- const issues = categorized.byCategory[category];
2258
- if (issues.length === 0)
2259
- continue;
2260
- const label = DRIFT_CATEGORY_LABELS2[category];
2261
- log("");
2262
- log(chalk6.dim(` ${label} (${issues.length})`));
2263
- for (const d of issues.slice(0, 3)) {
2264
- log(chalk6.red(` • ${d.export}: ${d.issue}`));
2265
- }
2266
- if (issues.length > 3) {
2267
- log(chalk6.gray(` ... and ${issues.length - 3} more`));
2268
- }
2269
- }
2270
- }
2271
- log("");
2272
- }
2273
-
2274
- // src/commands/spec.ts
2275
- import * as fs7 from "node:fs";
2276
- import * as path9 from "node:path";
2277
- import { DocCov as DocCov4, NodeFileSystem as NodeFileSystem4, resolveTarget as resolveTarget3 } from "@doccov/sdk";
1822
+ import { DocCov as DocCov3, NodeFileSystem as NodeFileSystem3, resolveTarget as resolveTarget3 } from "@doccov/sdk";
2278
1823
  import { normalize, validateSpec } from "@openpkg-ts/spec";
2279
1824
  import chalk8 from "chalk";
1825
+ // package.json
1826
+ var version = "0.13.0";
2280
1827
 
2281
1828
  // src/utils/filter-options.ts
2282
1829
  import { mergeFilters, parseListFlag } from "@doccov/sdk";
@@ -2310,9 +1857,9 @@ var mergeFilterOptions = (config, cliOptions) => {
2310
1857
  };
2311
1858
 
2312
1859
  // src/commands/spec.ts
2313
- var defaultDependencies5 = {
2314
- createDocCov: (options) => new DocCov4(options),
2315
- writeFileSync: fs7.writeFileSync,
1860
+ var defaultDependencies4 = {
1861
+ createDocCov: (options) => new DocCov3(options),
1862
+ writeFileSync: fs5.writeFileSync,
2316
1863
  log: console.log,
2317
1864
  error: console.error
2318
1865
  };
@@ -2321,82 +1868,76 @@ function getArrayLength(value) {
2321
1868
  }
2322
1869
  function formatDiagnosticOutput(prefix, diagnostic, baseDir) {
2323
1870
  const location = diagnostic.location;
2324
- const relativePath = location?.file ? path9.relative(baseDir, location.file) || location.file : undefined;
1871
+ const relativePath = location?.file ? path7.relative(baseDir, location.file) || location.file : undefined;
2325
1872
  const locationText = location && relativePath ? chalk8.gray(`${relativePath}:${location.line ?? 1}:${location.column ?? 1}`) : null;
2326
1873
  const locationPrefix = locationText ? `${locationText} ` : "";
2327
1874
  return `${prefix} ${locationPrefix}${diagnostic.message}`;
2328
1875
  }
2329
1876
  function registerSpecCommand(program, dependencies = {}) {
2330
- const { createDocCov, writeFileSync: writeFileSync5, log, error } = {
2331
- ...defaultDependencies5,
1877
+ const { createDocCov, writeFileSync: writeFileSync4, log, error } = {
1878
+ ...defaultDependencies4,
2332
1879
  ...dependencies
2333
1880
  };
2334
- program.command("spec [entry]").description("Generate OpenPkg specification (JSON)").option("--cwd <dir>", "Working directory", process.cwd()).option("-p, --package <name>", "Target package name (for monorepos)").option("-o, --output <file>", "Output file path", "openpkg.json").option("--include <patterns>", "Include exports matching pattern (comma-separated)").option("--exclude <patterns>", "Exclude exports matching pattern (comma-separated)").option("--skip-resolve", "Skip external type resolution from node_modules").option("--max-type-depth <n>", "Maximum depth for type conversion", "20").option("--no-cache", "Bypass spec cache and force regeneration").option("--show-diagnostics", "Show TypeScript compiler diagnostics").action(async (entry, options) => {
1881
+ program.command("spec [entry]").description("Generate OpenPkg specification (JSON)").option("--cwd <dir>", "Working directory", process.cwd()).option("-p, --package <name>", "Target package name (for monorepos)").option("-o, --output <file>", "Output file path", "openpkg.json").option("--include <patterns>", "Include exports matching pattern (comma-separated)").option("--exclude <patterns>", "Exclude exports matching pattern (comma-separated)").option("--skip-resolve", "Skip external type resolution from node_modules").option("--max-type-depth <n>", "Maximum depth for type conversion", "20").option("--no-cache", "Bypass spec cache and force regeneration").option("--show-diagnostics", "Show TypeScript compiler diagnostics").option("--verbose", "Show detailed generation metadata").action(async (entry, options) => {
2335
1882
  try {
2336
- const fileSystem = new NodeFileSystem4(options.cwd);
1883
+ const steps = new StepProgress([
1884
+ { label: "Resolved target", activeLabel: "Resolving target" },
1885
+ { label: "Loaded config", activeLabel: "Loading config" },
1886
+ { label: "Generated spec", activeLabel: "Generating spec" },
1887
+ { label: "Validated schema", activeLabel: "Validating schema" },
1888
+ { label: "Wrote output", activeLabel: "Writing output" }
1889
+ ]);
1890
+ steps.start();
1891
+ const fileSystem = new NodeFileSystem3(options.cwd);
2337
1892
  const resolved = await resolveTarget3(fileSystem, {
2338
1893
  cwd: options.cwd,
2339
1894
  package: options.package,
2340
1895
  entry
2341
1896
  });
2342
1897
  const { targetDir, entryFile, packageInfo, entryPointInfo } = resolved;
2343
- if (packageInfo) {
2344
- log(chalk8.gray(`Found package at ${packageInfo.path}`));
2345
- }
2346
- if (!entry) {
2347
- log(chalk8.gray(`Auto-detected entry point: ${entryPointInfo.path} (from ${entryPointInfo.source})`));
2348
- }
1898
+ steps.next();
2349
1899
  let config = null;
2350
1900
  try {
2351
1901
  config = await loadDocCovConfig(targetDir);
2352
- if (config?.filePath) {
2353
- log(chalk8.gray(`Loaded configuration from ${path9.relative(targetDir, config.filePath)}`));
2354
- }
2355
1902
  } catch (configError) {
2356
1903
  error(chalk8.red("Failed to load DocCov config:"), configError instanceof Error ? configError.message : configError);
2357
1904
  process.exit(1);
2358
1905
  }
1906
+ steps.next();
2359
1907
  const cliFilters = {
2360
1908
  include: parseListFlag(options.include),
2361
1909
  exclude: parseListFlag(options.exclude)
2362
1910
  };
2363
1911
  const resolvedFilters = mergeFilterOptions(config, cliFilters);
2364
- for (const message of resolvedFilters.messages) {
2365
- log(chalk8.gray(`${message}`));
2366
- }
2367
1912
  const resolveExternalTypes = !options.skipResolve;
2368
- process.stdout.write(chalk8.cyan(`> Generating OpenPkg spec...
2369
- `));
2370
- let result;
2371
- try {
2372
- const doccov = createDocCov({
2373
- resolveExternalTypes,
2374
- maxDepth: options.maxTypeDepth ? parseInt(options.maxTypeDepth, 10) : undefined,
2375
- useCache: options.cache !== false,
2376
- cwd: options.cwd
2377
- });
2378
- const analyzeOptions = resolvedFilters.include || resolvedFilters.exclude ? {
2379
- filters: {
2380
- include: resolvedFilters.include,
2381
- exclude: resolvedFilters.exclude
2382
- }
2383
- } : {};
2384
- result = await doccov.analyzeFileWithDiagnostics(entryFile, analyzeOptions);
2385
- if (result.fromCache) {
2386
- process.stdout.write(chalk8.gray(`> Using cached spec
2387
- `));
2388
- } else {
2389
- process.stdout.write(chalk8.green(`> Generated OpenPkg spec
2390
- `));
2391
- }
2392
- } catch (generationError) {
2393
- process.stdout.write(chalk8.red(`> Failed to generate spec
2394
- `));
2395
- throw generationError;
2396
- }
1913
+ const doccov = createDocCov({
1914
+ resolveExternalTypes,
1915
+ maxDepth: options.maxTypeDepth ? parseInt(options.maxTypeDepth, 10) : undefined,
1916
+ useCache: options.cache !== false,
1917
+ cwd: options.cwd
1918
+ });
1919
+ const generationInput = {
1920
+ entryPoint: path7.relative(targetDir, entryFile),
1921
+ entryPointSource: entryPointInfo.source,
1922
+ isDeclarationOnly: entryPointInfo.isDeclarationOnly ?? false,
1923
+ generatorName: "@doccov/cli",
1924
+ generatorVersion: version,
1925
+ packageManager: packageInfo?.packageManager,
1926
+ isMonorepo: resolved.isMonorepo,
1927
+ targetPackage: packageInfo?.name
1928
+ };
1929
+ const analyzeOptions = resolvedFilters.include || resolvedFilters.exclude ? {
1930
+ filters: {
1931
+ include: resolvedFilters.include,
1932
+ exclude: resolvedFilters.exclude
1933
+ },
1934
+ generationInput
1935
+ } : { generationInput };
1936
+ const result = await doccov.analyzeFileWithDiagnostics(entryFile, analyzeOptions);
2397
1937
  if (!result) {
2398
1938
  throw new Error("Failed to produce an OpenPkg spec.");
2399
1939
  }
1940
+ steps.next();
2400
1941
  const normalized = normalize(result.spec);
2401
1942
  const validation = validateSpec(normalized);
2402
1943
  if (!validation.ok) {
@@ -2406,11 +1947,49 @@ function registerSpecCommand(program, dependencies = {}) {
2406
1947
  }
2407
1948
  process.exit(1);
2408
1949
  }
2409
- const outputPath = path9.resolve(process.cwd(), options.output);
2410
- writeFileSync5(outputPath, JSON.stringify(normalized, null, 2));
2411
- log(chalk8.green(`> Wrote ${options.output}`));
1950
+ steps.next();
1951
+ const outputPath = path7.resolve(process.cwd(), options.output);
1952
+ writeFileSync4(outputPath, JSON.stringify(normalized, null, 2));
1953
+ steps.complete(`Generated ${options.output}`);
2412
1954
  log(chalk8.gray(` ${getArrayLength(normalized.exports)} exports`));
2413
1955
  log(chalk8.gray(` ${getArrayLength(normalized.types)} types`));
1956
+ if (options.verbose && normalized.generation) {
1957
+ const gen = normalized.generation;
1958
+ log("");
1959
+ log(chalk8.bold("Generation Info"));
1960
+ log(chalk8.gray(` Timestamp: ${gen.timestamp}`));
1961
+ log(chalk8.gray(` Generator: ${gen.generator.name}@${gen.generator.version}`));
1962
+ log(chalk8.gray(` Entry point: ${gen.analysis.entryPoint}`));
1963
+ log(chalk8.gray(` Detected via: ${gen.analysis.entryPointSource}`));
1964
+ log(chalk8.gray(` Declaration only: ${gen.analysis.isDeclarationOnly ? "yes" : "no"}`));
1965
+ log(chalk8.gray(` External types: ${gen.analysis.resolvedExternalTypes ? "resolved" : "skipped"}`));
1966
+ if (gen.analysis.maxTypeDepth) {
1967
+ log(chalk8.gray(` Max type depth: ${gen.analysis.maxTypeDepth}`));
1968
+ }
1969
+ log("");
1970
+ log(chalk8.bold("Environment"));
1971
+ log(chalk8.gray(` node_modules: ${gen.environment.hasNodeModules ? "found" : "not found"}`));
1972
+ if (gen.environment.packageManager) {
1973
+ log(chalk8.gray(` Package manager: ${gen.environment.packageManager}`));
1974
+ }
1975
+ if (gen.environment.isMonorepo) {
1976
+ log(chalk8.gray(` Monorepo: yes`));
1977
+ }
1978
+ if (gen.environment.targetPackage) {
1979
+ log(chalk8.gray(` Target package: ${gen.environment.targetPackage}`));
1980
+ }
1981
+ if (gen.issues.length > 0) {
1982
+ log("");
1983
+ log(chalk8.bold("Issues"));
1984
+ for (const issue of gen.issues) {
1985
+ const prefix = issue.severity === "error" ? chalk8.red(">") : issue.severity === "warning" ? chalk8.yellow(">") : chalk8.cyan(">");
1986
+ log(`${prefix} [${issue.code}] ${issue.message}`);
1987
+ if (issue.suggestion) {
1988
+ log(chalk8.gray(` ${issue.suggestion}`));
1989
+ }
1990
+ }
1991
+ }
1992
+ }
2414
1993
  if (options.showDiagnostics && result.diagnostics.length > 0) {
2415
1994
  log("");
2416
1995
  log(chalk8.bold("Diagnostics"));
@@ -2428,8 +2007,8 @@ function registerSpecCommand(program, dependencies = {}) {
2428
2007
 
2429
2008
  // src/cli.ts
2430
2009
  var __filename2 = fileURLToPath(import.meta.url);
2431
- var __dirname2 = path10.dirname(__filename2);
2432
- var packageJson = JSON.parse(readFileSync4(path10.join(__dirname2, "../package.json"), "utf-8"));
2010
+ var __dirname2 = path8.dirname(__filename2);
2011
+ var packageJson = JSON.parse(readFileSync3(path8.join(__dirname2, "../package.json"), "utf-8"));
2433
2012
  var program = new Command;
2434
2013
  program.name("doccov").description("DocCov - Documentation coverage and drift detection for TypeScript").version(packageJson.version);
2435
2014
  registerCheckCommand(program);
@@ -2437,7 +2016,6 @@ registerInfoCommand(program);
2437
2016
  registerSpecCommand(program);
2438
2017
  registerDiffCommand(program);
2439
2018
  registerInitCommand(program);
2440
- registerScanCommand(program);
2441
2019
  program.command("*", { hidden: true }).action(() => {
2442
2020
  program.outputHelp();
2443
2021
  });
@@ -1,22 +1,3 @@
1
- import { createRequire } from "node:module";
2
- var __create = Object.create;
3
- var __getProtoOf = Object.getPrototypeOf;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __toESM = (mod, isNodeMode, target) => {
8
- target = mod != null ? __create(__getProtoOf(mod)) : {};
9
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
- for (let key of __getOwnPropNames(mod))
11
- if (!__hasOwnProp.call(to, key))
12
- __defProp(to, key, {
13
- get: () => mod[key],
14
- enumerable: true
15
- });
16
- return to;
17
- };
18
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
-
20
1
  // src/config/doccov-config.ts
21
2
  import { access } from "node:fs/promises";
22
3
  import path from "node:path";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doccov/cli",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "DocCov CLI - Documentation coverage and drift detection for TypeScript",
5
5
  "keywords": [
6
6
  "typescript",
@@ -48,14 +48,13 @@
48
48
  "dependencies": {
49
49
  "@ai-sdk/anthropic": "^1.0.0",
50
50
  "@ai-sdk/openai": "^1.0.0",
51
- "@doccov/sdk": "^0.12.0",
51
+ "@doccov/sdk": "^0.13.0",
52
52
  "@inquirer/prompts": "^7.8.0",
53
- "@openpkg-ts/spec": "^0.8.0",
53
+ "@openpkg-ts/spec": "^0.9.0",
54
54
  "ai": "^4.0.0",
55
55
  "chalk": "^5.4.1",
56
56
  "commander": "^14.0.0",
57
57
  "glob": "^11.0.0",
58
- "ora": "^9.0.0",
59
58
  "simple-git": "^3.27.0",
60
59
  "zod": "^3.25.0"
61
60
  },