@doccov/cli 0.5.7 → 0.6.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
@@ -33,11 +33,22 @@ var docsConfigSchema = z.object({
33
33
  include: stringList.optional(),
34
34
  exclude: stringList.optional()
35
35
  });
36
+ var lintSeveritySchema = z.enum(["error", "warn", "off"]);
37
+ var checkConfigSchema = z.object({
38
+ lint: z.boolean().optional(),
39
+ typecheck: z.boolean().optional(),
40
+ exec: z.boolean().optional()
41
+ });
42
+ var lintConfigSchema = z.object({
43
+ rules: z.record(lintSeveritySchema).optional()
44
+ });
36
45
  var docCovConfigSchema = z.object({
37
46
  include: stringList.optional(),
38
47
  exclude: stringList.optional(),
39
48
  plugins: z.array(z.unknown()).optional(),
40
- docs: docsConfigSchema.optional()
49
+ docs: docsConfigSchema.optional(),
50
+ check: checkConfigSchema.optional(),
51
+ lint: lintConfigSchema.optional()
41
52
  });
42
53
  var normalizeList = (value) => {
43
54
  if (!value) {
@@ -61,11 +72,27 @@ var normalizeConfig = (input) => {
61
72
  };
62
73
  }
63
74
  }
75
+ let check;
76
+ if (input.check) {
77
+ check = {
78
+ lint: input.check.lint,
79
+ typecheck: input.check.typecheck,
80
+ exec: input.check.exec
81
+ };
82
+ }
83
+ let lint;
84
+ if (input.lint) {
85
+ lint = {
86
+ rules: input.lint.rules
87
+ };
88
+ }
64
89
  return {
65
90
  include,
66
91
  exclude,
67
92
  plugins: input.plugins,
68
- docs
93
+ docs,
94
+ check,
95
+ lint
69
96
  };
70
97
  };
71
98
 
@@ -142,7 +169,7 @@ ${formatIssues(issues)}`);
142
169
  var defineConfig = (config) => config;
143
170
  // src/cli.ts
144
171
  import { readFileSync as readFileSync5 } from "node:fs";
145
- import * as path9 from "node:path";
172
+ import * as path11 from "node:path";
146
173
  import { fileURLToPath } from "node:url";
147
174
  import { Command } from "commander";
148
175
 
@@ -158,16 +185,19 @@ import {
158
185
  detectExampleAssertionFailures,
159
186
  detectExampleRuntimeErrors,
160
187
  detectMonorepo,
161
- findPackageByName,
162
188
  findJSDocLocation,
189
+ findPackageByName,
163
190
  generateFixesForExport,
191
+ getDefaultConfig as getLintDefaultConfig,
164
192
  hasNonAssertionComments,
193
+ lintExport,
165
194
  mergeFixes,
166
195
  NodeFileSystem,
167
196
  parseAssertions,
168
197
  parseJSDocToPatch,
169
198
  runExamplesWithPackage,
170
- serializeJSDoc
199
+ serializeJSDoc,
200
+ typecheckExamples
171
201
  } from "@doccov/sdk";
172
202
  import chalk from "chalk";
173
203
 
@@ -274,7 +304,7 @@ function registerCheckCommand(program, dependencies = {}) {
274
304
  ...defaultDependencies,
275
305
  ...dependencies
276
306
  };
277
- program.command("check [entry]").description("Fail if documentation coverage falls below a threshold").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("--require-examples", "Require at least one @example for every export").option("--run-examples", "Execute @example blocks and fail on runtime errors").option("--ignore-drift", "Do not fail on documentation drift").option("--skip-resolve", "Skip external type resolution from node_modules").option("--write", "Auto-fix drift issues").option("--only <types>", "Only fix specific drift types (comma-separated)").option("--dry-run", "Preview fixes without writing (requires --write)").action(async (entry, options) => {
307
+ program.command("check [entry]").description("Fail if documentation coverage falls below a threshold").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("--require-examples", "Require at least one @example for every export").option("--exec", "Execute @example blocks at runtime").option("--no-lint", "Skip lint checks").option("--no-typecheck", "Skip example type checking").option("--ignore-drift", "Do not fail on documentation drift").option("--skip-resolve", "Skip external type resolution from node_modules").option("--fix", "Auto-fix drift and lint issues").option("--write", "Alias for --fix").option("--only <types>", "Only fix specific drift types (comma-separated)").option("--dry-run", "Preview fixes without writing (requires --fix)").action(async (entry, options) => {
278
308
  try {
279
309
  let targetDir = options.cwd;
280
310
  let entryFile = entry;
@@ -344,8 +374,59 @@ function registerCheckCommand(program, dependencies = {}) {
344
374
  }
345
375
  log("");
346
376
  }
377
+ const shouldFix = options.fix || options.write;
378
+ const lintViolations = [];
379
+ if (options.lint !== false) {
380
+ process.stdout.write(chalk.cyan(`> Running lint checks...
381
+ `));
382
+ const lintConfig = getLintDefaultConfig();
383
+ for (const exp of spec.exports ?? []) {
384
+ const violations = lintExport(exp, undefined, lintConfig);
385
+ for (const violation of violations) {
386
+ lintViolations.push({ exportName: exp.name, violation });
387
+ }
388
+ }
389
+ if (lintViolations.length === 0) {
390
+ process.stdout.write(chalk.green(`✓ No lint issues
391
+ `));
392
+ } else {
393
+ const errors = lintViolations.filter((v) => v.violation.severity === "error").length;
394
+ const warns = lintViolations.filter((v) => v.violation.severity === "warn").length;
395
+ process.stdout.write(chalk.yellow(`⚠ ${lintViolations.length} lint issue(s) (${errors} error, ${warns} warn)
396
+ `));
397
+ }
398
+ }
399
+ const typecheckErrors = [];
400
+ if (options.typecheck !== false) {
401
+ const allExamplesForTypecheck = [];
402
+ for (const exp of spec.exports ?? []) {
403
+ if (exp.examples && exp.examples.length > 0) {
404
+ allExamplesForTypecheck.push({
405
+ exportName: exp.name,
406
+ examples: exp.examples
407
+ });
408
+ }
409
+ }
410
+ if (allExamplesForTypecheck.length > 0) {
411
+ process.stdout.write(chalk.cyan(`> Type-checking examples...
412
+ `));
413
+ for (const { exportName, examples } of allExamplesForTypecheck) {
414
+ const result = typecheckExamples(examples, targetDir);
415
+ for (const err of result.errors) {
416
+ typecheckErrors.push({ exportName, error: err });
417
+ }
418
+ }
419
+ if (typecheckErrors.length === 0) {
420
+ process.stdout.write(chalk.green(`✓ All examples type-check
421
+ `));
422
+ } else {
423
+ process.stdout.write(chalk.red(`✗ ${typecheckErrors.length} type error(s)
424
+ `));
425
+ }
426
+ }
427
+ }
347
428
  const runtimeDrifts = [];
348
- if (options.runExamples) {
429
+ if (options.exec) {
349
430
  const allExamples = [];
350
431
  for (const entry2 of spec.exports ?? []) {
351
432
  if (entry2.examples && entry2.examples.length > 0) {
@@ -454,7 +535,7 @@ function registerCheckCommand(program, dependencies = {}) {
454
535
  const missingExamples = options.requireExamples ? failingExports.filter((item) => item.missing?.includes("examples")) : [];
455
536
  let driftExports = [...collectDrift(spec.exports ?? []), ...runtimeDrifts];
456
537
  const fixedDriftKeys = new Set;
457
- if (options.write && driftExports.length > 0) {
538
+ if (shouldFix && driftExports.length > 0) {
458
539
  const allDrifts = collectDriftsFromExports(spec.exports ?? []);
459
540
  const filteredDrifts = filterDriftsByType(allDrifts, options.only);
460
541
  if (filteredDrifts.length === 0 && options.only) {
@@ -567,7 +648,9 @@ function registerCheckCommand(program, dependencies = {}) {
567
648
  const coverageFailed = coverageScore < minCoverage;
568
649
  const hasMissingExamples = missingExamples.length > 0;
569
650
  const hasDrift = !options.ignoreDrift && driftExports.length > 0;
570
- if (!coverageFailed && !hasMissingExamples && !hasDrift) {
651
+ const hasLintErrors = lintViolations.filter((v) => v.violation.severity === "error").length > 0;
652
+ const hasTypecheckErrors = typecheckErrors.length > 0;
653
+ if (!coverageFailed && !hasMissingExamples && !hasDrift && !hasLintErrors && !hasTypecheckErrors) {
571
654
  log(chalk.green(`✓ Docs coverage ${coverageScore}% (min ${minCoverage}%)`));
572
655
  if (failingExports.length > 0) {
573
656
  log(chalk.gray("Some exports have partial docs:"));
@@ -594,6 +677,20 @@ function registerCheckCommand(program, dependencies = {}) {
594
677
  if (hasMissingExamples) {
595
678
  error(chalk.red(`${missingExamples.length} export(s) missing examples (required via --require-examples)`));
596
679
  }
680
+ if (hasLintErrors) {
681
+ error("");
682
+ error(chalk.bold("Lint errors:"));
683
+ for (const { exportName, violation } of lintViolations.filter((v) => v.violation.severity === "error").slice(0, 10)) {
684
+ error(chalk.red(` • ${exportName}: ${violation.message}`));
685
+ }
686
+ }
687
+ if (hasTypecheckErrors) {
688
+ error("");
689
+ error(chalk.bold("Type errors in examples:"));
690
+ for (const { exportName, error: err } of typecheckErrors.slice(0, 10)) {
691
+ error(chalk.red(` • ${exportName} @example ${err.exampleIndex + 1}, line ${err.line}: ${err.message}`));
692
+ }
693
+ }
597
694
  if (failingExports.length > 0 || driftExports.length > 0) {
598
695
  error("");
599
696
  error(chalk.bold("Missing documentation details:"));
@@ -795,7 +892,6 @@ function registerDiffCommand(program, dependencies = {}) {
795
892
  case "report":
796
893
  log(generateHTMLReport(diff));
797
894
  break;
798
- case "text":
799
895
  default:
800
896
  printTextDiff(diff, log, error);
801
897
  if (options.ai && diff.docsImpact && hasDocsImpact(diff)) {
@@ -933,7 +1029,7 @@ function printAPIChanges(diff, log) {
933
1029
  }
934
1030
  const added = changes.filter((c) => c.changeType === "added");
935
1031
  if (added.length > 0) {
936
- const addedNames = added.map((a) => a.memberName + "()").join(", ");
1032
+ const addedNames = added.map((a) => `${a.memberName}()`).join(", ");
937
1033
  log(chalk2.green(` + ${addedNames}`));
938
1034
  }
939
1035
  }
@@ -1594,17 +1690,225 @@ var buildTemplate = (format) => {
1594
1690
  `);
1595
1691
  };
1596
1692
 
1597
- // src/commands/report.ts
1693
+ // src/commands/lint.ts
1598
1694
  import * as fs5 from "node:fs";
1599
1695
  import * as path6 from "node:path";
1600
1696
  import {
1697
+ applyEdits as applyEdits2,
1698
+ createSourceFile as createSourceFile2,
1601
1699
  DocCov as DocCov3,
1602
1700
  detectEntryPoint as detectEntryPoint3,
1603
1701
  detectMonorepo as detectMonorepo3,
1702
+ findJSDocLocation as findJSDocLocation2,
1604
1703
  findPackageByName as findPackageByName3,
1605
- NodeFileSystem as NodeFileSystem3
1704
+ getDefaultConfig,
1705
+ getRule,
1706
+ lintExport as lintExport2,
1707
+ NodeFileSystem as NodeFileSystem3,
1708
+ serializeJSDoc as serializeJSDoc2
1606
1709
  } from "@doccov/sdk";
1607
1710
  import chalk6 from "chalk";
1711
+ var defaultDependencies5 = {
1712
+ createDocCov: (options) => new DocCov3(options),
1713
+ log: console.log,
1714
+ error: console.error
1715
+ };
1716
+ function getRawJSDoc(exp, targetDir) {
1717
+ if (!exp.source?.file)
1718
+ return;
1719
+ const filePath = path6.resolve(targetDir, exp.source.file);
1720
+ if (!fs5.existsSync(filePath))
1721
+ return;
1722
+ try {
1723
+ const sourceFile = createSourceFile2(filePath);
1724
+ const location = findJSDocLocation2(sourceFile, exp.name, exp.source.line);
1725
+ return location?.existingJSDoc;
1726
+ } catch {
1727
+ return;
1728
+ }
1729
+ }
1730
+ function registerLintCommand(program, dependencies = {}) {
1731
+ const { createDocCov, log, error } = {
1732
+ ...defaultDependencies5,
1733
+ ...dependencies
1734
+ };
1735
+ program.command("lint [entry]").description("Lint documentation for style and quality issues").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--fix", "Auto-fix fixable issues").option("--write", "Alias for --fix").option("--rule <name>", "Run only a specific rule").option("--skip-resolve", "Skip external type resolution").action(async (entry, options) => {
1736
+ try {
1737
+ let targetDir = options.cwd;
1738
+ let entryFile = entry;
1739
+ const fileSystem = new NodeFileSystem3(options.cwd);
1740
+ if (options.package) {
1741
+ const mono = await detectMonorepo3(fileSystem);
1742
+ if (!mono.isMonorepo) {
1743
+ throw new Error("Not a monorepo. Remove --package flag.");
1744
+ }
1745
+ const pkg = findPackageByName3(mono.packages, options.package);
1746
+ if (!pkg) {
1747
+ const available = mono.packages.map((p) => p.name).join(", ");
1748
+ throw new Error(`Package "${options.package}" not found. Available: ${available}`);
1749
+ }
1750
+ targetDir = path6.join(options.cwd, pkg.path);
1751
+ log(chalk6.gray(`Found package at ${pkg.path}`));
1752
+ }
1753
+ if (!entryFile) {
1754
+ const targetFs = new NodeFileSystem3(targetDir);
1755
+ const detected = await detectEntryPoint3(targetFs);
1756
+ entryFile = path6.join(targetDir, detected.path);
1757
+ log(chalk6.gray(`Auto-detected entry point: ${detected.path}`));
1758
+ } else {
1759
+ entryFile = path6.resolve(targetDir, entryFile);
1760
+ if (fs5.existsSync(entryFile) && fs5.statSync(entryFile).isDirectory()) {
1761
+ targetDir = entryFile;
1762
+ const dirFs = new NodeFileSystem3(entryFile);
1763
+ const detected = await detectEntryPoint3(dirFs);
1764
+ entryFile = path6.join(entryFile, detected.path);
1765
+ log(chalk6.gray(`Auto-detected entry point: ${detected.path}`));
1766
+ }
1767
+ }
1768
+ const resolveExternalTypes = !options.skipResolve;
1769
+ process.stdout.write(chalk6.cyan(`> Analyzing documentation...
1770
+ `));
1771
+ const doccov = createDocCov({ resolveExternalTypes });
1772
+ const specResult = await doccov.analyzeFileWithDiagnostics(entryFile);
1773
+ if (!specResult) {
1774
+ throw new Error("Failed to analyze documentation.");
1775
+ }
1776
+ process.stdout.write(chalk6.cyan(`> Running lint rules...
1777
+ `));
1778
+ let config = getDefaultConfig();
1779
+ if (options.rule) {
1780
+ const rule = getRule(options.rule);
1781
+ if (!rule) {
1782
+ throw new Error(`Unknown rule: ${options.rule}`);
1783
+ }
1784
+ const rules = {};
1785
+ for (const key of Object.keys(config.rules)) {
1786
+ rules[key] = "off";
1787
+ }
1788
+ rules[options.rule] = rule.defaultSeverity === "off" ? "warn" : rule.defaultSeverity;
1789
+ config = { rules };
1790
+ }
1791
+ const exportsWithJSDoc = [];
1792
+ for (const exp of specResult.spec.exports ?? []) {
1793
+ const rawJSDoc = getRawJSDoc(exp, targetDir);
1794
+ exportsWithJSDoc.push({
1795
+ export: exp,
1796
+ rawJSDoc,
1797
+ filePath: exp.source?.file ? path6.resolve(targetDir, exp.source.file) : undefined
1798
+ });
1799
+ }
1800
+ const allViolations = [];
1801
+ for (const { export: exp, rawJSDoc, filePath } of exportsWithJSDoc) {
1802
+ const violations = lintExport2(exp, rawJSDoc, config);
1803
+ for (const violation of violations) {
1804
+ allViolations.push({ export: exp, violation, filePath, rawJSDoc });
1805
+ }
1806
+ }
1807
+ const shouldFix = options.fix || options.write;
1808
+ const fixableViolations = allViolations.filter((v) => v.violation.fixable);
1809
+ if (shouldFix && fixableViolations.length > 0) {
1810
+ process.stdout.write(chalk6.cyan(`> Applying fixes...
1811
+ `));
1812
+ const edits = [];
1813
+ for (const { export: exp, rawJSDoc, filePath } of fixableViolations) {
1814
+ if (!filePath || !rawJSDoc)
1815
+ continue;
1816
+ if (filePath.endsWith(".d.ts"))
1817
+ continue;
1818
+ const sourceFile = createSourceFile2(filePath);
1819
+ const location = findJSDocLocation2(sourceFile, exp.name, exp.source?.line);
1820
+ if (!location)
1821
+ continue;
1822
+ const rule = getRule("consistent-param-style");
1823
+ if (!rule?.fix)
1824
+ continue;
1825
+ const patch = rule.fix(exp, rawJSDoc);
1826
+ if (!patch)
1827
+ continue;
1828
+ const newJSDoc = serializeJSDoc2(patch, location.indent);
1829
+ edits.push({
1830
+ filePath,
1831
+ symbolName: exp.name,
1832
+ startLine: location.startLine,
1833
+ endLine: location.endLine,
1834
+ hasExisting: location.hasExisting,
1835
+ existingJSDoc: location.existingJSDoc,
1836
+ newJSDoc,
1837
+ indent: location.indent
1838
+ });
1839
+ }
1840
+ if (edits.length > 0) {
1841
+ const result = await applyEdits2(edits);
1842
+ if (result.errors.length > 0) {
1843
+ for (const err of result.errors) {
1844
+ error(chalk6.red(` ${err.file}: ${err.error}`));
1845
+ }
1846
+ } else {
1847
+ process.stdout.write(chalk6.green(`✓ Fixed ${result.editsApplied} issue(s) in ${result.filesModified} file(s)
1848
+ `));
1849
+ }
1850
+ const fixedExports = new Set(edits.map((e) => e.symbolName));
1851
+ const remaining = allViolations.filter((v) => !v.violation.fixable || !fixedExports.has(v.export.name));
1852
+ allViolations.length = 0;
1853
+ allViolations.push(...remaining);
1854
+ }
1855
+ }
1856
+ if (allViolations.length === 0) {
1857
+ log(chalk6.green("✓ No lint issues found"));
1858
+ return;
1859
+ }
1860
+ const byFile = new Map;
1861
+ for (const v of allViolations) {
1862
+ const file = v.filePath ?? "unknown";
1863
+ const existing = byFile.get(file) ?? [];
1864
+ existing.push(v);
1865
+ byFile.set(file, existing);
1866
+ }
1867
+ log("");
1868
+ for (const [filePath, violations] of byFile) {
1869
+ const relativePath = path6.relative(targetDir, filePath);
1870
+ log(chalk6.underline(relativePath));
1871
+ for (const { export: exp, violation } of violations) {
1872
+ const line = exp.source?.line ?? 0;
1873
+ const severity = violation.severity === "error" ? chalk6.red("error") : chalk6.yellow("warning");
1874
+ const fixable = violation.fixable ? chalk6.gray(" (fixable)") : "";
1875
+ log(` ${line}:1 ${severity} ${violation.message} ${chalk6.gray(violation.rule)}${fixable}`);
1876
+ }
1877
+ log("");
1878
+ }
1879
+ const errorCount = allViolations.filter((v) => v.violation.severity === "error").length;
1880
+ const warnCount = allViolations.filter((v) => v.violation.severity === "warn").length;
1881
+ const fixableCount = allViolations.filter((v) => v.violation.fixable).length;
1882
+ const summary = [];
1883
+ if (errorCount > 0)
1884
+ summary.push(chalk6.red(`${errorCount} error(s)`));
1885
+ if (warnCount > 0)
1886
+ summary.push(chalk6.yellow(`${warnCount} warning(s)`));
1887
+ if (fixableCount > 0 && !shouldFix) {
1888
+ summary.push(chalk6.gray(`${fixableCount} fixable with --fix`));
1889
+ }
1890
+ log(summary.join(", "));
1891
+ if (errorCount > 0) {
1892
+ process.exit(1);
1893
+ }
1894
+ } catch (commandError) {
1895
+ error(chalk6.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
1896
+ process.exit(1);
1897
+ }
1898
+ });
1899
+ }
1900
+
1901
+ // src/commands/report.ts
1902
+ import * as fs6 from "node:fs";
1903
+ import * as path7 from "node:path";
1904
+ import {
1905
+ DocCov as DocCov4,
1906
+ detectEntryPoint as detectEntryPoint4,
1907
+ detectMonorepo as detectMonorepo4,
1908
+ findPackageByName as findPackageByName4,
1909
+ NodeFileSystem as NodeFileSystem4
1910
+ } from "@doccov/sdk";
1911
+ import chalk7 from "chalk";
1608
1912
 
1609
1913
  // src/reports/markdown.ts
1610
1914
  function bar(pct, width = 10) {
@@ -1781,42 +2085,42 @@ function registerReportCommand(program) {
1781
2085
  try {
1782
2086
  let spec;
1783
2087
  if (options.spec) {
1784
- const specPath = path6.resolve(options.cwd, options.spec);
1785
- spec = JSON.parse(fs5.readFileSync(specPath, "utf-8"));
2088
+ const specPath = path7.resolve(options.cwd, options.spec);
2089
+ spec = JSON.parse(fs6.readFileSync(specPath, "utf-8"));
1786
2090
  } else {
1787
2091
  let targetDir = options.cwd;
1788
2092
  let entryFile = entry;
1789
- const fileSystem = new NodeFileSystem3(options.cwd);
2093
+ const fileSystem = new NodeFileSystem4(options.cwd);
1790
2094
  if (options.package) {
1791
- const mono = await detectMonorepo3(fileSystem);
2095
+ const mono = await detectMonorepo4(fileSystem);
1792
2096
  if (!mono.isMonorepo) {
1793
2097
  throw new Error(`Not a monorepo. Remove --package flag for single-package repos.`);
1794
2098
  }
1795
- const pkg = findPackageByName3(mono.packages, options.package);
2099
+ const pkg = findPackageByName4(mono.packages, options.package);
1796
2100
  if (!pkg) {
1797
2101
  const available = mono.packages.map((p) => p.name).join(", ");
1798
2102
  throw new Error(`Package "${options.package}" not found. Available: ${available}`);
1799
2103
  }
1800
- targetDir = path6.join(options.cwd, pkg.path);
2104
+ targetDir = path7.join(options.cwd, pkg.path);
1801
2105
  }
1802
2106
  if (!entryFile) {
1803
- const targetFs = new NodeFileSystem3(targetDir);
1804
- const detected = await detectEntryPoint3(targetFs);
1805
- entryFile = path6.join(targetDir, detected.path);
2107
+ const targetFs = new NodeFileSystem4(targetDir);
2108
+ const detected = await detectEntryPoint4(targetFs);
2109
+ entryFile = path7.join(targetDir, detected.path);
1806
2110
  } else {
1807
- entryFile = path6.resolve(targetDir, entryFile);
2111
+ entryFile = path7.resolve(targetDir, entryFile);
1808
2112
  }
1809
- process.stdout.write(chalk6.cyan(`> Analyzing...
2113
+ process.stdout.write(chalk7.cyan(`> Analyzing...
1810
2114
  `));
1811
2115
  try {
1812
2116
  const resolveExternalTypes = !options.skipResolve;
1813
- const doccov = new DocCov3({ resolveExternalTypes });
2117
+ const doccov = new DocCov4({ resolveExternalTypes });
1814
2118
  const result = await doccov.analyzeFileWithDiagnostics(entryFile);
1815
- process.stdout.write(chalk6.green(`✓ Analysis complete
2119
+ process.stdout.write(chalk7.green(`✓ Analysis complete
1816
2120
  `));
1817
2121
  spec = result.spec;
1818
2122
  } catch (analysisError) {
1819
- process.stdout.write(chalk6.red(`✗ Analysis failed
2123
+ process.stdout.write(chalk7.red(`✗ Analysis failed
1820
2124
  `));
1821
2125
  throw analysisError;
1822
2126
  }
@@ -1833,35 +2137,36 @@ function registerReportCommand(program) {
1833
2137
  output = renderMarkdown(stats, { limit });
1834
2138
  }
1835
2139
  if (options.out) {
1836
- const outPath = path6.resolve(options.cwd, options.out);
1837
- fs5.writeFileSync(outPath, output);
1838
- console.log(chalk6.green(`Report written to ${outPath}`));
2140
+ const outPath = path7.resolve(options.cwd, options.out);
2141
+ fs6.writeFileSync(outPath, output);
2142
+ console.log(chalk7.green(`Report written to ${outPath}`));
1839
2143
  } else {
1840
2144
  console.log(output);
1841
2145
  }
1842
2146
  } catch (err) {
1843
- console.error(chalk6.red("Error:"), err instanceof Error ? err.message : err);
2147
+ console.error(chalk7.red("Error:"), err instanceof Error ? err.message : err);
1844
2148
  process.exitCode = 1;
1845
2149
  }
1846
2150
  });
1847
2151
  }
1848
2152
 
1849
2153
  // src/commands/scan.ts
1850
- import * as fs7 from "node:fs";
2154
+ import * as fs8 from "node:fs";
2155
+ import * as fsPromises from "node:fs/promises";
1851
2156
  import * as os from "node:os";
1852
- import * as path8 from "node:path";
2157
+ import * as path9 from "node:path";
1853
2158
  import {
1854
- DocCov as DocCov4,
2159
+ DocCov as DocCov5,
1855
2160
  detectBuildInfo,
1856
- detectEntryPoint as detectEntryPoint4,
1857
- detectMonorepo as detectMonorepo4,
2161
+ detectEntryPoint as detectEntryPoint5,
2162
+ detectMonorepo as detectMonorepo5,
1858
2163
  detectPackageManager,
1859
- findPackageByName as findPackageByName4,
2164
+ findPackageByName as findPackageByName5,
1860
2165
  formatPackageList,
1861
2166
  getInstallCommand,
1862
- NodeFileSystem as NodeFileSystem4
2167
+ NodeFileSystem as NodeFileSystem5
1863
2168
  } from "@doccov/sdk";
1864
- import chalk7 from "chalk";
2169
+ import chalk8 from "chalk";
1865
2170
  import { simpleGit } from "simple-git";
1866
2171
 
1867
2172
  // src/utils/github-url.ts
@@ -1895,8 +2200,8 @@ function buildDisplayUrl(parsed) {
1895
2200
  }
1896
2201
 
1897
2202
  // src/utils/llm-build-plan.ts
1898
- import * as fs6 from "node:fs";
1899
- import * as path7 from "node:path";
2203
+ import * as fs7 from "node:fs";
2204
+ import * as path8 from "node:path";
1900
2205
  import { createAnthropic as createAnthropic3 } from "@ai-sdk/anthropic";
1901
2206
  import { createOpenAI as createOpenAI3 } from "@ai-sdk/openai";
1902
2207
  import { generateObject as generateObject3 } from "ai";
@@ -1932,10 +2237,10 @@ function getModel3() {
1932
2237
  async function gatherContextFiles(repoDir) {
1933
2238
  const sections = [];
1934
2239
  for (const fileName of CONTEXT_FILES) {
1935
- const filePath = path7.join(repoDir, fileName);
1936
- if (fs6.existsSync(filePath)) {
2240
+ const filePath = path8.join(repoDir, fileName);
2241
+ if (fs7.existsSync(filePath)) {
1937
2242
  try {
1938
- let content = fs6.readFileSync(filePath, "utf-8");
2243
+ let content = fs7.readFileSync(filePath, "utf-8");
1939
2244
  if (content.length > MAX_FILE_CHARS) {
1940
2245
  content = `${content.slice(0, MAX_FILE_CHARS)}
1941
2246
  ... (truncated)`;
@@ -1987,14 +2292,14 @@ async function generateBuildPlan(repoDir) {
1987
2292
  }
1988
2293
 
1989
2294
  // src/commands/scan.ts
1990
- var defaultDependencies5 = {
1991
- createDocCov: (options) => new DocCov4(options),
2295
+ var defaultDependencies6 = {
2296
+ createDocCov: (options) => new DocCov5(options),
1992
2297
  log: console.log,
1993
2298
  error: console.error
1994
2299
  };
1995
2300
  function registerScanCommand(program, dependencies = {}) {
1996
2301
  const { createDocCov, log, error } = {
1997
- ...defaultDependencies5,
2302
+ ...defaultDependencies6,
1998
2303
  ...dependencies
1999
2304
  };
2000
2305
  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) => {
@@ -2004,12 +2309,12 @@ function registerScanCommand(program, dependencies = {}) {
2004
2309
  const cloneUrl = buildCloneUrl(parsed);
2005
2310
  const displayUrl = buildDisplayUrl(parsed);
2006
2311
  log("");
2007
- log(chalk7.bold(`Scanning ${displayUrl}`));
2008
- log(chalk7.gray(`Branch/tag: ${parsed.ref}`));
2312
+ log(chalk8.bold(`Scanning ${displayUrl}`));
2313
+ log(chalk8.gray(`Branch/tag: ${parsed.ref}`));
2009
2314
  log("");
2010
- tempDir = path8.join(os.tmpdir(), `doccov-scan-${Date.now()}-${Math.random().toString(36).slice(2)}`);
2011
- fs7.mkdirSync(tempDir, { recursive: true });
2012
- process.stdout.write(chalk7.cyan(`> Cloning ${parsed.owner}/${parsed.repo}...
2315
+ tempDir = path9.join(os.tmpdir(), `doccov-scan-${Date.now()}-${Math.random().toString(36).slice(2)}`);
2316
+ fs8.mkdirSync(tempDir, { recursive: true });
2317
+ process.stdout.write(chalk8.cyan(`> Cloning ${parsed.owner}/${parsed.repo}...
2013
2318
  `));
2014
2319
  try {
2015
2320
  const git = simpleGit({
@@ -2031,10 +2336,10 @@ function registerScanCommand(program, dependencies = {}) {
2031
2336
  } finally {
2032
2337
  process.env = originalEnv;
2033
2338
  }
2034
- process.stdout.write(chalk7.green(`✓ Cloned ${parsed.owner}/${parsed.repo}
2339
+ process.stdout.write(chalk8.green(`✓ Cloned ${parsed.owner}/${parsed.repo}
2035
2340
  `));
2036
2341
  } catch (cloneError) {
2037
- process.stdout.write(chalk7.red(`✗ Failed to clone repository
2342
+ process.stdout.write(chalk8.red(`✗ Failed to clone repository
2038
2343
  `));
2039
2344
  const message = cloneError instanceof Error ? cloneError.message : String(cloneError);
2040
2345
  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")) {
@@ -2050,11 +2355,11 @@ function registerScanCommand(program, dependencies = {}) {
2050
2355
  }
2051
2356
  throw new Error(`Clone failed: ${message}`);
2052
2357
  }
2053
- const fileSystem = new NodeFileSystem4(tempDir);
2358
+ const fileSystem = new NodeFileSystem5(tempDir);
2054
2359
  if (options.skipInstall) {
2055
- log(chalk7.gray("Skipping dependency installation (--skip-install)"));
2360
+ log(chalk8.gray("Skipping dependency installation (--skip-install)"));
2056
2361
  } else {
2057
- process.stdout.write(chalk7.cyan(`> Installing dependencies...
2362
+ process.stdout.write(chalk8.cyan(`> Installing dependencies...
2058
2363
  `));
2059
2364
  const installErrors = [];
2060
2365
  try {
@@ -2104,56 +2409,56 @@ function registerScanCommand(program, dependencies = {}) {
2104
2409
  }
2105
2410
  }
2106
2411
  if (installed) {
2107
- process.stdout.write(chalk7.green(`✓ Dependencies installed
2412
+ process.stdout.write(chalk8.green(`✓ Dependencies installed
2108
2413
  `));
2109
2414
  } else {
2110
- process.stdout.write(chalk7.yellow(`⚠ Could not install dependencies (analysis may be limited)
2415
+ process.stdout.write(chalk8.yellow(`⚠ Could not install dependencies (analysis may be limited)
2111
2416
  `));
2112
2417
  for (const err of installErrors) {
2113
- log(chalk7.gray(` ${err}`));
2418
+ log(chalk8.gray(` ${err}`));
2114
2419
  }
2115
2420
  }
2116
2421
  } catch (outerError) {
2117
2422
  const msg = outerError instanceof Error ? outerError.message : String(outerError);
2118
- process.stdout.write(chalk7.yellow(`⚠ Could not install dependencies: ${msg.slice(0, 100)}
2423
+ process.stdout.write(chalk8.yellow(`⚠ Could not install dependencies: ${msg.slice(0, 100)}
2119
2424
  `));
2120
2425
  for (const err of installErrors) {
2121
- log(chalk7.gray(` ${err}`));
2426
+ log(chalk8.gray(` ${err}`));
2122
2427
  }
2123
2428
  }
2124
2429
  }
2125
2430
  let targetDir = tempDir;
2126
2431
  let packageName;
2127
- const mono = await detectMonorepo4(fileSystem);
2432
+ const mono = await detectMonorepo5(fileSystem);
2128
2433
  if (mono.isMonorepo) {
2129
2434
  if (!options.package) {
2130
2435
  error("");
2131
- error(chalk7.red(`Monorepo detected with ${mono.packages.length} packages. Specify target with --package:`));
2436
+ error(chalk8.red(`Monorepo detected with ${mono.packages.length} packages. Specify target with --package:`));
2132
2437
  error("");
2133
2438
  error(formatPackageList(mono.packages));
2134
2439
  error("");
2135
2440
  throw new Error("Monorepo requires --package flag");
2136
2441
  }
2137
- const pkg = findPackageByName4(mono.packages, options.package);
2442
+ const pkg = findPackageByName5(mono.packages, options.package);
2138
2443
  if (!pkg) {
2139
2444
  error("");
2140
- error(chalk7.red(`Package "${options.package}" not found. Available packages:`));
2445
+ error(chalk8.red(`Package "${options.package}" not found. Available packages:`));
2141
2446
  error("");
2142
2447
  error(formatPackageList(mono.packages));
2143
2448
  error("");
2144
2449
  throw new Error(`Package not found: ${options.package}`);
2145
2450
  }
2146
- targetDir = path8.join(tempDir, pkg.path);
2451
+ targetDir = path9.join(tempDir, pkg.path);
2147
2452
  packageName = pkg.name;
2148
- log(chalk7.gray(`Analyzing package: ${packageName}`));
2453
+ log(chalk8.gray(`Analyzing package: ${packageName}`));
2149
2454
  }
2150
- process.stdout.write(chalk7.cyan(`> Detecting entry point...
2455
+ process.stdout.write(chalk8.cyan(`> Detecting entry point...
2151
2456
  `));
2152
2457
  let entryPath;
2153
- const targetFs = mono.isMonorepo ? new NodeFileSystem4(targetDir) : fileSystem;
2458
+ const targetFs = mono.isMonorepo ? new NodeFileSystem5(targetDir) : fileSystem;
2154
2459
  let buildFailed = false;
2155
2460
  const runLlmFallback = async (reason) => {
2156
- process.stdout.write(chalk7.cyan(`> ${reason}, trying LLM fallback...
2461
+ process.stdout.write(chalk8.cyan(`> ${reason}, trying LLM fallback...
2157
2462
  `));
2158
2463
  const plan = await generateBuildPlan(targetDir);
2159
2464
  if (!plan) {
@@ -2162,88 +2467,88 @@ function registerScanCommand(program, dependencies = {}) {
2162
2467
  if (plan.buildCommands.length > 0) {
2163
2468
  const { execSync } = await import("node:child_process");
2164
2469
  for (const cmd of plan.buildCommands) {
2165
- log(chalk7.gray(` Running: ${cmd}`));
2470
+ log(chalk8.gray(` Running: ${cmd}`));
2166
2471
  try {
2167
2472
  execSync(cmd, { cwd: targetDir, stdio: "pipe", timeout: 300000 });
2168
2473
  } catch (buildError) {
2169
2474
  buildFailed = true;
2170
2475
  const msg = buildError instanceof Error ? buildError.message : String(buildError);
2171
2476
  if (msg.includes("rustc") || msg.includes("cargo") || msg.includes("wasm-pack")) {
2172
- log(chalk7.yellow(` ⚠ Build requires Rust toolchain (not available)`));
2477
+ log(chalk8.yellow(` ⚠ Build requires Rust toolchain (not available)`));
2173
2478
  } else if (msg.includes("rimraf") || msg.includes("command not found")) {
2174
- log(chalk7.yellow(` ⚠ Build failed: missing dependencies`));
2479
+ log(chalk8.yellow(` ⚠ Build failed: missing dependencies`));
2175
2480
  } else {
2176
- log(chalk7.yellow(` ⚠ Build failed: ${msg.slice(0, 80)}`));
2481
+ log(chalk8.yellow(` ⚠ Build failed: ${msg.slice(0, 80)}`));
2177
2482
  }
2178
2483
  }
2179
2484
  }
2180
2485
  }
2181
2486
  if (plan.notes) {
2182
- log(chalk7.gray(` Note: ${plan.notes}`));
2487
+ log(chalk8.gray(` Note: ${plan.notes}`));
2183
2488
  }
2184
2489
  return plan.entryPoint;
2185
2490
  };
2186
2491
  try {
2187
- const entry = await detectEntryPoint4(targetFs);
2492
+ const entry = await detectEntryPoint5(targetFs);
2188
2493
  const buildInfo = await detectBuildInfo(targetFs);
2189
2494
  const needsBuildStep = entry.isDeclarationOnly && buildInfo.exoticIndicators.wasm;
2190
2495
  if (needsBuildStep) {
2191
- process.stdout.write(chalk7.cyan(`> Detected .d.ts entry with WASM indicators...
2496
+ process.stdout.write(chalk8.cyan(`> Detected .d.ts entry with WASM indicators...
2192
2497
  `));
2193
2498
  const llmEntry = await runLlmFallback("WASM project detected");
2194
2499
  if (llmEntry) {
2195
- entryPath = path8.join(targetDir, llmEntry);
2500
+ entryPath = path9.join(targetDir, llmEntry);
2196
2501
  if (buildFailed) {
2197
- process.stdout.write(chalk7.green(`✓ Entry point: ${llmEntry} (using pre-committed declarations)
2502
+ process.stdout.write(chalk8.green(`✓ Entry point: ${llmEntry} (using pre-committed declarations)
2198
2503
  `));
2199
- log(chalk7.gray(" Coverage may be limited - generated .d.ts files typically lack JSDoc"));
2504
+ log(chalk8.gray(" Coverage may be limited - generated .d.ts files typically lack JSDoc"));
2200
2505
  } else {
2201
- process.stdout.write(chalk7.green(`✓ Entry point: ${llmEntry} (from LLM fallback - WASM project)
2506
+ process.stdout.write(chalk8.green(`✓ Entry point: ${llmEntry} (from LLM fallback - WASM project)
2202
2507
  `));
2203
2508
  }
2204
2509
  } else {
2205
- entryPath = path8.join(targetDir, entry.path);
2206
- process.stdout.write(chalk7.green(`✓ Entry point: ${entry.path} (from ${entry.source})
2510
+ entryPath = path9.join(targetDir, entry.path);
2511
+ process.stdout.write(chalk8.green(`✓ Entry point: ${entry.path} (from ${entry.source})
2207
2512
  `));
2208
- log(chalk7.yellow(" ⚠ WASM project detected but no API key - analysis may be limited"));
2513
+ log(chalk8.yellow(" ⚠ WASM project detected but no API key - analysis may be limited"));
2209
2514
  }
2210
2515
  } else {
2211
- entryPath = path8.join(targetDir, entry.path);
2212
- process.stdout.write(chalk7.green(`✓ Entry point: ${entry.path} (from ${entry.source})
2516
+ entryPath = path9.join(targetDir, entry.path);
2517
+ process.stdout.write(chalk8.green(`✓ Entry point: ${entry.path} (from ${entry.source})
2213
2518
  `));
2214
2519
  }
2215
2520
  } catch (entryError) {
2216
2521
  const llmEntry = await runLlmFallback("Heuristics failed");
2217
2522
  if (llmEntry) {
2218
- entryPath = path8.join(targetDir, llmEntry);
2219
- process.stdout.write(chalk7.green(`✓ Entry point: ${llmEntry} (from LLM fallback)
2523
+ entryPath = path9.join(targetDir, llmEntry);
2524
+ process.stdout.write(chalk8.green(`✓ Entry point: ${llmEntry} (from LLM fallback)
2220
2525
  `));
2221
2526
  } else {
2222
- process.stdout.write(chalk7.red(`✗ Could not detect entry point (set OPENAI_API_KEY for smart fallback)
2527
+ process.stdout.write(chalk8.red(`✗ Could not detect entry point (set OPENAI_API_KEY for smart fallback)
2223
2528
  `));
2224
2529
  throw entryError;
2225
2530
  }
2226
2531
  }
2227
- process.stdout.write(chalk7.cyan(`> Analyzing documentation coverage...
2532
+ process.stdout.write(chalk8.cyan(`> Analyzing documentation coverage...
2228
2533
  `));
2229
2534
  let result;
2230
2535
  try {
2231
2536
  const resolveExternalTypes = !options.skipResolve;
2232
2537
  const doccov = createDocCov({ resolveExternalTypes });
2233
2538
  result = await doccov.analyzeFileWithDiagnostics(entryPath);
2234
- process.stdout.write(chalk7.green(`✓ Analysis complete
2539
+ process.stdout.write(chalk8.green(`✓ Analysis complete
2235
2540
  `));
2236
2541
  } catch (analysisError) {
2237
- process.stdout.write(chalk7.red(`✗ Analysis failed
2542
+ process.stdout.write(chalk8.red(`✗ Analysis failed
2238
2543
  `));
2239
2544
  throw analysisError;
2240
2545
  }
2241
2546
  const spec = result.spec;
2242
2547
  const coverageScore = spec.docs?.coverageScore ?? 0;
2243
2548
  if (options.saveSpec) {
2244
- const specPath = path8.resolve(process.cwd(), options.saveSpec);
2245
- fs7.writeFileSync(specPath, JSON.stringify(spec, null, 2));
2246
- log(chalk7.green(`✓ Saved spec to ${options.saveSpec}`));
2549
+ const specPath = path9.resolve(process.cwd(), options.saveSpec);
2550
+ fs8.writeFileSync(specPath, JSON.stringify(spec, null, 2));
2551
+ log(chalk8.green(`✓ Saved spec to ${options.saveSpec}`));
2247
2552
  }
2248
2553
  const undocumented = [];
2249
2554
  const driftIssues = [];
@@ -2280,73 +2585,186 @@ function registerScanCommand(program, dependencies = {}) {
2280
2585
  printTextResult(scanResult, log);
2281
2586
  }
2282
2587
  } catch (commandError) {
2283
- error(chalk7.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
2588
+ error(chalk8.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
2284
2589
  process.exitCode = 1;
2285
2590
  } finally {
2286
2591
  if (tempDir && options.cleanup !== false) {
2287
- const { spawn } = await import("node:child_process");
2288
- spawn("rm", ["-rf", tempDir], {
2289
- detached: true,
2290
- stdio: "ignore"
2291
- }).unref();
2592
+ fsPromises.rm(tempDir, { recursive: true, force: true }).catch(() => {});
2292
2593
  } else if (tempDir) {
2293
- log(chalk7.gray(`Repo preserved at: ${tempDir}`));
2594
+ log(chalk8.gray(`Repo preserved at: ${tempDir}`));
2294
2595
  }
2295
2596
  }
2296
2597
  });
2297
2598
  }
2298
2599
  function printTextResult(result, log) {
2299
2600
  log("");
2300
- log(chalk7.bold("DocCov Scan Results"));
2601
+ log(chalk8.bold("DocCov Scan Results"));
2301
2602
  log("─".repeat(40));
2302
2603
  const repoName = result.packageName ? `${result.owner}/${result.repo} (${result.packageName})` : `${result.owner}/${result.repo}`;
2303
- log(`Repository: ${chalk7.cyan(repoName)}`);
2304
- log(`Branch: ${chalk7.gray(result.ref)}`);
2604
+ log(`Repository: ${chalk8.cyan(repoName)}`);
2605
+ log(`Branch: ${chalk8.gray(result.ref)}`);
2305
2606
  log("");
2306
- const coverageColor = result.coverage >= 80 ? chalk7.green : result.coverage >= 50 ? chalk7.yellow : chalk7.red;
2307
- log(chalk7.bold("Coverage"));
2607
+ const coverageColor = result.coverage >= 80 ? chalk8.green : result.coverage >= 50 ? chalk8.yellow : chalk8.red;
2608
+ log(chalk8.bold("Coverage"));
2308
2609
  log(` ${coverageColor(`${result.coverage}%`)}`);
2309
2610
  log("");
2310
- log(chalk7.bold("Stats"));
2611
+ log(chalk8.bold("Stats"));
2311
2612
  log(` ${result.exportCount} exports`);
2312
2613
  log(` ${result.typeCount} types`);
2313
2614
  log(` ${result.undocumented.length} undocumented`);
2314
2615
  log(` ${result.driftCount} drift issues`);
2315
2616
  if (result.undocumented.length > 0) {
2316
2617
  log("");
2317
- log(chalk7.bold("Undocumented Exports"));
2618
+ log(chalk8.bold("Undocumented Exports"));
2318
2619
  for (const name of result.undocumented.slice(0, 10)) {
2319
- log(chalk7.yellow(` ! ${name}`));
2620
+ log(chalk8.yellow(` ! ${name}`));
2320
2621
  }
2321
2622
  if (result.undocumented.length > 10) {
2322
- log(chalk7.gray(` ... and ${result.undocumented.length - 10} more`));
2623
+ log(chalk8.gray(` ... and ${result.undocumented.length - 10} more`));
2323
2624
  }
2324
2625
  }
2325
2626
  if (result.drift.length > 0) {
2326
2627
  log("");
2327
- log(chalk7.bold("Drift Issues"));
2628
+ log(chalk8.bold("Drift Issues"));
2328
2629
  for (const d of result.drift.slice(0, 5)) {
2329
- log(chalk7.red(` • ${d.export}: ${d.issue}`));
2630
+ log(chalk8.red(` • ${d.export}: ${d.issue}`));
2330
2631
  }
2331
2632
  if (result.drift.length > 5) {
2332
- log(chalk7.gray(` ... and ${result.drift.length - 5} more`));
2633
+ log(chalk8.gray(` ... and ${result.drift.length - 5} more`));
2333
2634
  }
2334
2635
  }
2335
2636
  log("");
2336
2637
  }
2337
2638
 
2639
+ // src/commands/typecheck.ts
2640
+ import * as fs9 from "node:fs";
2641
+ import * as path10 from "node:path";
2642
+ import {
2643
+ DocCov as DocCov6,
2644
+ detectEntryPoint as detectEntryPoint6,
2645
+ detectMonorepo as detectMonorepo6,
2646
+ findPackageByName as findPackageByName6,
2647
+ NodeFileSystem as NodeFileSystem6,
2648
+ typecheckExamples as typecheckExamples2
2649
+ } from "@doccov/sdk";
2650
+ import chalk9 from "chalk";
2651
+ var defaultDependencies7 = {
2652
+ createDocCov: (options) => new DocCov6(options),
2653
+ log: console.log,
2654
+ error: console.error
2655
+ };
2656
+ function registerTypecheckCommand(program, dependencies = {}) {
2657
+ const { createDocCov, log, error } = {
2658
+ ...defaultDependencies7,
2659
+ ...dependencies
2660
+ };
2661
+ program.command("typecheck [entry]").description("Type-check @example blocks without executing them").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--skip-resolve", "Skip external type resolution").action(async (entry, options) => {
2662
+ try {
2663
+ let targetDir = options.cwd;
2664
+ let entryFile = entry;
2665
+ const fileSystem = new NodeFileSystem6(options.cwd);
2666
+ if (options.package) {
2667
+ const mono = await detectMonorepo6(fileSystem);
2668
+ if (!mono.isMonorepo) {
2669
+ throw new Error("Not a monorepo. Remove --package flag.");
2670
+ }
2671
+ const pkg = findPackageByName6(mono.packages, options.package);
2672
+ if (!pkg) {
2673
+ const available = mono.packages.map((p) => p.name).join(", ");
2674
+ throw new Error(`Package "${options.package}" not found. Available: ${available}`);
2675
+ }
2676
+ targetDir = path10.join(options.cwd, pkg.path);
2677
+ log(chalk9.gray(`Found package at ${pkg.path}`));
2678
+ }
2679
+ if (!entryFile) {
2680
+ const targetFs = new NodeFileSystem6(targetDir);
2681
+ const detected = await detectEntryPoint6(targetFs);
2682
+ entryFile = path10.join(targetDir, detected.path);
2683
+ log(chalk9.gray(`Auto-detected entry point: ${detected.path}`));
2684
+ } else {
2685
+ entryFile = path10.resolve(targetDir, entryFile);
2686
+ if (fs9.existsSync(entryFile) && fs9.statSync(entryFile).isDirectory()) {
2687
+ targetDir = entryFile;
2688
+ const dirFs = new NodeFileSystem6(entryFile);
2689
+ const detected = await detectEntryPoint6(dirFs);
2690
+ entryFile = path10.join(entryFile, detected.path);
2691
+ log(chalk9.gray(`Auto-detected entry point: ${detected.path}`));
2692
+ }
2693
+ }
2694
+ const resolveExternalTypes = !options.skipResolve;
2695
+ process.stdout.write(chalk9.cyan(`> Analyzing documentation...
2696
+ `));
2697
+ const doccov = createDocCov({ resolveExternalTypes });
2698
+ const specResult = await doccov.analyzeFileWithDiagnostics(entryFile);
2699
+ if (!specResult) {
2700
+ throw new Error("Failed to analyze documentation.");
2701
+ }
2702
+ const allExamples = [];
2703
+ for (const exp of specResult.spec.exports ?? []) {
2704
+ if (exp.examples && exp.examples.length > 0) {
2705
+ allExamples.push({ exportName: exp.name, examples: exp.examples });
2706
+ }
2707
+ }
2708
+ if (allExamples.length === 0) {
2709
+ log(chalk9.gray("No @example blocks found"));
2710
+ return;
2711
+ }
2712
+ const totalExamples = allExamples.reduce((sum, e) => sum + e.examples.length, 0);
2713
+ process.stdout.write(chalk9.cyan(`> Type-checking ${totalExamples} example(s)...
2714
+ `));
2715
+ const allErrors = [];
2716
+ let passed = 0;
2717
+ let failed = 0;
2718
+ for (const { exportName, examples } of allExamples) {
2719
+ const result = typecheckExamples2(examples, targetDir);
2720
+ for (const err of result.errors) {
2721
+ allErrors.push({ exportName, error: err });
2722
+ }
2723
+ passed += result.passed;
2724
+ failed += result.failed;
2725
+ }
2726
+ if (allErrors.length === 0) {
2727
+ log(chalk9.green(`✓ All ${totalExamples} example(s) passed type checking`));
2728
+ return;
2729
+ }
2730
+ log("");
2731
+ const byExport = new Map;
2732
+ for (const { exportName, error: err } of allErrors) {
2733
+ const existing = byExport.get(exportName) ?? [];
2734
+ existing.push(err);
2735
+ byExport.set(exportName, existing);
2736
+ }
2737
+ for (const [exportName, errors] of byExport) {
2738
+ log(chalk9.red(`✗ ${exportName}`));
2739
+ for (const err of errors) {
2740
+ log(chalk9.gray(` @example block ${err.exampleIndex + 1}, line ${err.line}:`));
2741
+ log(chalk9.red(` ${err.message}`));
2742
+ }
2743
+ log("");
2744
+ }
2745
+ log(chalk9.red(`${failed} example(s) failed`) + chalk9.gray(`, ${passed} passed`));
2746
+ process.exit(1);
2747
+ } catch (commandError) {
2748
+ error(chalk9.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
2749
+ process.exit(1);
2750
+ }
2751
+ });
2752
+ }
2753
+
2338
2754
  // src/cli.ts
2339
2755
  var __filename2 = fileURLToPath(import.meta.url);
2340
- var __dirname2 = path9.dirname(__filename2);
2341
- var packageJson = JSON.parse(readFileSync5(path9.join(__dirname2, "../package.json"), "utf-8"));
2756
+ var __dirname2 = path11.dirname(__filename2);
2757
+ var packageJson = JSON.parse(readFileSync5(path11.join(__dirname2, "../package.json"), "utf-8"));
2342
2758
  var program = new Command;
2343
2759
  program.name("doccov").description("DocCov - Documentation coverage and drift detection for TypeScript").version(packageJson.version);
2344
2760
  registerGenerateCommand(program);
2345
2761
  registerCheckCommand(program);
2346
2762
  registerDiffCommand(program);
2347
2763
  registerInitCommand(program);
2764
+ registerLintCommand(program);
2348
2765
  registerReportCommand(program);
2349
2766
  registerScanCommand(program);
2767
+ registerTypecheckCommand(program);
2350
2768
  program.command("*", { hidden: true }).action(() => {
2351
2769
  program.outputHelp();
2352
2770
  });
@@ -7,22 +7,50 @@ declare const docsConfigSchema: z.ZodObject<{
7
7
  include: z.ZodOptional<typeof stringList>
8
8
  exclude: z.ZodOptional<typeof stringList>
9
9
  }>;
10
+ /** Lint severity levels */
11
+ declare const lintSeveritySchema: z.ZodEnum<["error", "warn", "off"]>;
12
+ /**
13
+ * Check command configuration schema
14
+ */
15
+ declare const checkConfigSchema: z.ZodObject<{
16
+ lint: z.ZodOptional<z.ZodBoolean>
17
+ typecheck: z.ZodOptional<z.ZodBoolean>
18
+ exec: z.ZodOptional<z.ZodBoolean>
19
+ }>;
20
+ /**
21
+ * Lint configuration schema
22
+ */
23
+ declare const lintConfigSchema: z.ZodObject<{
24
+ rules: z.ZodOptional<z.ZodRecord<z.ZodString, typeof lintSeveritySchema>>
25
+ }>;
10
26
  declare const docCovConfigSchema: z.ZodObject<{
11
27
  include: z.ZodOptional<typeof stringList>
12
28
  exclude: z.ZodOptional<typeof stringList>
13
29
  plugins: z.ZodOptional<z.ZodArray<z.ZodUnknown>>
14
30
  docs: z.ZodOptional<typeof docsConfigSchema>
31
+ check: z.ZodOptional<typeof checkConfigSchema>
32
+ lint: z.ZodOptional<typeof lintConfigSchema>
15
33
  }>;
16
34
  type DocCovConfigInput = z.infer<typeof docCovConfigSchema>;
17
35
  interface DocsConfig {
18
36
  include?: string[];
19
37
  exclude?: string[];
20
38
  }
39
+ interface CheckConfig {
40
+ lint?: boolean;
41
+ typecheck?: boolean;
42
+ exec?: boolean;
43
+ }
44
+ interface LintRulesConfig {
45
+ rules?: Record<string, "error" | "warn" | "off">;
46
+ }
21
47
  interface NormalizedDocCovConfig {
22
48
  include?: string[];
23
49
  exclude?: string[];
24
50
  plugins?: unknown[];
25
51
  docs?: DocsConfig;
52
+ check?: CheckConfig;
53
+ lint?: LintRulesConfig;
26
54
  }
27
55
  declare const DOCCOV_CONFIG_FILENAMES: readonly ["doccov.config.ts", "doccov.config.mts", "doccov.config.cts", "doccov.config.js", "doccov.config.mjs", "doccov.config.cjs"];
28
56
  interface LoadedDocCovConfig extends NormalizedDocCovConfig {
@@ -30,4 +58,4 @@ interface LoadedDocCovConfig extends NormalizedDocCovConfig {
30
58
  }
31
59
  declare const loadDocCovConfig: (cwd: string) => Promise<LoadedDocCovConfig | null>;
32
60
  declare const defineConfig: (config: DocCovConfigInput) => DocCovConfigInput;
33
- export { loadDocCovConfig, defineConfig, NormalizedDocCovConfig, LoadedDocCovConfig, DocsConfig, DocCovConfigInput, DOCCOV_CONFIG_FILENAMES };
61
+ export { loadDocCovConfig, defineConfig, NormalizedDocCovConfig, LoadedDocCovConfig, LintRulesConfig, DocsConfig, DocCovConfigInput, DOCCOV_CONFIG_FILENAMES, CheckConfig };
@@ -32,11 +32,22 @@ var docsConfigSchema = z.object({
32
32
  include: stringList.optional(),
33
33
  exclude: stringList.optional()
34
34
  });
35
+ var lintSeveritySchema = z.enum(["error", "warn", "off"]);
36
+ var checkConfigSchema = z.object({
37
+ lint: z.boolean().optional(),
38
+ typecheck: z.boolean().optional(),
39
+ exec: z.boolean().optional()
40
+ });
41
+ var lintConfigSchema = z.object({
42
+ rules: z.record(lintSeveritySchema).optional()
43
+ });
35
44
  var docCovConfigSchema = z.object({
36
45
  include: stringList.optional(),
37
46
  exclude: stringList.optional(),
38
47
  plugins: z.array(z.unknown()).optional(),
39
- docs: docsConfigSchema.optional()
48
+ docs: docsConfigSchema.optional(),
49
+ check: checkConfigSchema.optional(),
50
+ lint: lintConfigSchema.optional()
40
51
  });
41
52
  var normalizeList = (value) => {
42
53
  if (!value) {
@@ -60,11 +71,27 @@ var normalizeConfig = (input) => {
60
71
  };
61
72
  }
62
73
  }
74
+ let check;
75
+ if (input.check) {
76
+ check = {
77
+ lint: input.check.lint,
78
+ typecheck: input.check.typecheck,
79
+ exec: input.check.exec
80
+ };
81
+ }
82
+ let lint;
83
+ if (input.lint) {
84
+ lint = {
85
+ rules: input.lint.rules
86
+ };
87
+ }
63
88
  return {
64
89
  include,
65
90
  exclude,
66
91
  plugins: input.plugins,
67
- docs
92
+ docs,
93
+ check,
94
+ lint
68
95
  };
69
96
  };
70
97
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doccov/cli",
3
- "version": "0.5.7",
3
+ "version": "0.6.0",
4
4
  "description": "DocCov CLI - Documentation coverage and drift detection for TypeScript",
5
5
  "keywords": [
6
6
  "typescript",
@@ -49,14 +49,14 @@
49
49
  "@ai-sdk/anthropic": "^1.0.0",
50
50
  "@ai-sdk/openai": "^1.0.0",
51
51
  "@inquirer/prompts": "^7.8.0",
52
- "@doccov/sdk": "^0.5.7",
53
- "@openpkg-ts/spec": "^0.4.0",
52
+ "@doccov/sdk": "^0.6.0",
53
+ "@openpkg-ts/spec": "^0.4.1",
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
58
  "simple-git": "^3.27.0",
59
- "zod": "^3.23.8"
59
+ "zod": "^3.25.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/bun": "latest",