@doccov/cli 0.5.7 → 0.5.8

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
 
@@ -161,13 +188,16 @@ import {
161
188
  findPackageByName,
162
189
  findJSDocLocation,
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,56 @@ 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({ exportName: exp.name, examples: exp.examples });
405
+ }
406
+ }
407
+ if (allExamplesForTypecheck.length > 0) {
408
+ process.stdout.write(chalk.cyan(`> Type-checking examples...
409
+ `));
410
+ for (const { exportName, examples } of allExamplesForTypecheck) {
411
+ const result = typecheckExamples(examples, targetDir);
412
+ for (const err of result.errors) {
413
+ typecheckErrors.push({ exportName, error: err });
414
+ }
415
+ }
416
+ if (typecheckErrors.length === 0) {
417
+ process.stdout.write(chalk.green(`✓ All examples type-check
418
+ `));
419
+ } else {
420
+ process.stdout.write(chalk.red(`✗ ${typecheckErrors.length} type error(s)
421
+ `));
422
+ }
423
+ }
424
+ }
347
425
  const runtimeDrifts = [];
348
- if (options.runExamples) {
426
+ if (options.exec) {
349
427
  const allExamples = [];
350
428
  for (const entry2 of spec.exports ?? []) {
351
429
  if (entry2.examples && entry2.examples.length > 0) {
@@ -454,7 +532,7 @@ function registerCheckCommand(program, dependencies = {}) {
454
532
  const missingExamples = options.requireExamples ? failingExports.filter((item) => item.missing?.includes("examples")) : [];
455
533
  let driftExports = [...collectDrift(spec.exports ?? []), ...runtimeDrifts];
456
534
  const fixedDriftKeys = new Set;
457
- if (options.write && driftExports.length > 0) {
535
+ if (shouldFix && driftExports.length > 0) {
458
536
  const allDrifts = collectDriftsFromExports(spec.exports ?? []);
459
537
  const filteredDrifts = filterDriftsByType(allDrifts, options.only);
460
538
  if (filteredDrifts.length === 0 && options.only) {
@@ -567,7 +645,9 @@ function registerCheckCommand(program, dependencies = {}) {
567
645
  const coverageFailed = coverageScore < minCoverage;
568
646
  const hasMissingExamples = missingExamples.length > 0;
569
647
  const hasDrift = !options.ignoreDrift && driftExports.length > 0;
570
- if (!coverageFailed && !hasMissingExamples && !hasDrift) {
648
+ const hasLintErrors = lintViolations.filter((v) => v.violation.severity === "error").length > 0;
649
+ const hasTypecheckErrors = typecheckErrors.length > 0;
650
+ if (!coverageFailed && !hasMissingExamples && !hasDrift && !hasLintErrors && !hasTypecheckErrors) {
571
651
  log(chalk.green(`✓ Docs coverage ${coverageScore}% (min ${minCoverage}%)`));
572
652
  if (failingExports.length > 0) {
573
653
  log(chalk.gray("Some exports have partial docs:"));
@@ -594,6 +674,20 @@ function registerCheckCommand(program, dependencies = {}) {
594
674
  if (hasMissingExamples) {
595
675
  error(chalk.red(`${missingExamples.length} export(s) missing examples (required via --require-examples)`));
596
676
  }
677
+ if (hasLintErrors) {
678
+ error("");
679
+ error(chalk.bold("Lint errors:"));
680
+ for (const { exportName, violation } of lintViolations.filter((v) => v.violation.severity === "error").slice(0, 10)) {
681
+ error(chalk.red(` • ${exportName}: ${violation.message}`));
682
+ }
683
+ }
684
+ if (hasTypecheckErrors) {
685
+ error("");
686
+ error(chalk.bold("Type errors in examples:"));
687
+ for (const { exportName, error: err } of typecheckErrors.slice(0, 10)) {
688
+ error(chalk.red(` • ${exportName} @example ${err.exampleIndex + 1}, line ${err.line}: ${err.message}`));
689
+ }
690
+ }
597
691
  if (failingExports.length > 0 || driftExports.length > 0) {
598
692
  error("");
599
693
  error(chalk.bold("Missing documentation details:"));
@@ -1594,17 +1688,225 @@ var buildTemplate = (format) => {
1594
1688
  `);
1595
1689
  };
1596
1690
 
1597
- // src/commands/report.ts
1691
+ // src/commands/lint.ts
1598
1692
  import * as fs5 from "node:fs";
1599
1693
  import * as path6 from "node:path";
1600
1694
  import {
1601
- DocCov as DocCov3,
1695
+ applyEdits as applyEdits2,
1696
+ createSourceFile as createSourceFile2,
1602
1697
  detectEntryPoint as detectEntryPoint3,
1603
1698
  detectMonorepo as detectMonorepo3,
1699
+ DocCov as DocCov3,
1700
+ findJSDocLocation as findJSDocLocation2,
1604
1701
  findPackageByName as findPackageByName3,
1605
- NodeFileSystem as NodeFileSystem3
1702
+ getDefaultConfig,
1703
+ getRule,
1704
+ lintExport as lintExport2,
1705
+ NodeFileSystem as NodeFileSystem3,
1706
+ serializeJSDoc as serializeJSDoc2
1606
1707
  } from "@doccov/sdk";
1607
1708
  import chalk6 from "chalk";
1709
+ var defaultDependencies5 = {
1710
+ createDocCov: (options) => new DocCov3(options),
1711
+ log: console.log,
1712
+ error: console.error
1713
+ };
1714
+ function getRawJSDoc(exp, targetDir) {
1715
+ if (!exp.source?.file)
1716
+ return;
1717
+ const filePath = path6.resolve(targetDir, exp.source.file);
1718
+ if (!fs5.existsSync(filePath))
1719
+ return;
1720
+ try {
1721
+ const sourceFile = createSourceFile2(filePath);
1722
+ const location = findJSDocLocation2(sourceFile, exp.name, exp.source.line);
1723
+ return location?.existingJSDoc;
1724
+ } catch {
1725
+ return;
1726
+ }
1727
+ }
1728
+ function registerLintCommand(program, dependencies = {}) {
1729
+ const { createDocCov, log, error } = {
1730
+ ...defaultDependencies5,
1731
+ ...dependencies
1732
+ };
1733
+ 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) => {
1734
+ try {
1735
+ let targetDir = options.cwd;
1736
+ let entryFile = entry;
1737
+ const fileSystem = new NodeFileSystem3(options.cwd);
1738
+ if (options.package) {
1739
+ const mono = await detectMonorepo3(fileSystem);
1740
+ if (!mono.isMonorepo) {
1741
+ throw new Error("Not a monorepo. Remove --package flag.");
1742
+ }
1743
+ const pkg = findPackageByName3(mono.packages, options.package);
1744
+ if (!pkg) {
1745
+ const available = mono.packages.map((p) => p.name).join(", ");
1746
+ throw new Error(`Package "${options.package}" not found. Available: ${available}`);
1747
+ }
1748
+ targetDir = path6.join(options.cwd, pkg.path);
1749
+ log(chalk6.gray(`Found package at ${pkg.path}`));
1750
+ }
1751
+ if (!entryFile) {
1752
+ const targetFs = new NodeFileSystem3(targetDir);
1753
+ const detected = await detectEntryPoint3(targetFs);
1754
+ entryFile = path6.join(targetDir, detected.path);
1755
+ log(chalk6.gray(`Auto-detected entry point: ${detected.path}`));
1756
+ } else {
1757
+ entryFile = path6.resolve(targetDir, entryFile);
1758
+ if (fs5.existsSync(entryFile) && fs5.statSync(entryFile).isDirectory()) {
1759
+ targetDir = entryFile;
1760
+ const dirFs = new NodeFileSystem3(entryFile);
1761
+ const detected = await detectEntryPoint3(dirFs);
1762
+ entryFile = path6.join(entryFile, detected.path);
1763
+ log(chalk6.gray(`Auto-detected entry point: ${detected.path}`));
1764
+ }
1765
+ }
1766
+ const resolveExternalTypes = !options.skipResolve;
1767
+ process.stdout.write(chalk6.cyan(`> Analyzing documentation...
1768
+ `));
1769
+ const doccov = createDocCov({ resolveExternalTypes });
1770
+ const specResult = await doccov.analyzeFileWithDiagnostics(entryFile);
1771
+ if (!specResult) {
1772
+ throw new Error("Failed to analyze documentation.");
1773
+ }
1774
+ process.stdout.write(chalk6.cyan(`> Running lint rules...
1775
+ `));
1776
+ let config = getDefaultConfig();
1777
+ if (options.rule) {
1778
+ const rule = getRule(options.rule);
1779
+ if (!rule) {
1780
+ throw new Error(`Unknown rule: ${options.rule}`);
1781
+ }
1782
+ const rules = {};
1783
+ for (const key of Object.keys(config.rules)) {
1784
+ rules[key] = "off";
1785
+ }
1786
+ rules[options.rule] = rule.defaultSeverity === "off" ? "warn" : rule.defaultSeverity;
1787
+ config = { rules };
1788
+ }
1789
+ const exportsWithJSDoc = [];
1790
+ for (const exp of specResult.spec.exports ?? []) {
1791
+ const rawJSDoc = getRawJSDoc(exp, targetDir);
1792
+ exportsWithJSDoc.push({
1793
+ export: exp,
1794
+ rawJSDoc,
1795
+ filePath: exp.source?.file ? path6.resolve(targetDir, exp.source.file) : undefined
1796
+ });
1797
+ }
1798
+ const allViolations = [];
1799
+ for (const { export: exp, rawJSDoc, filePath } of exportsWithJSDoc) {
1800
+ const violations = lintExport2(exp, rawJSDoc, config);
1801
+ for (const violation of violations) {
1802
+ allViolations.push({ export: exp, violation, filePath, rawJSDoc });
1803
+ }
1804
+ }
1805
+ const shouldFix = options.fix || options.write;
1806
+ const fixableViolations = allViolations.filter((v) => v.violation.fixable);
1807
+ if (shouldFix && fixableViolations.length > 0) {
1808
+ process.stdout.write(chalk6.cyan(`> Applying fixes...
1809
+ `));
1810
+ const edits = [];
1811
+ for (const { export: exp, rawJSDoc, filePath } of fixableViolations) {
1812
+ if (!filePath || !rawJSDoc)
1813
+ continue;
1814
+ if (filePath.endsWith(".d.ts"))
1815
+ continue;
1816
+ const sourceFile = createSourceFile2(filePath);
1817
+ const location = findJSDocLocation2(sourceFile, exp.name, exp.source?.line);
1818
+ if (!location)
1819
+ continue;
1820
+ const rule = getRule("consistent-param-style");
1821
+ if (!rule?.fix)
1822
+ continue;
1823
+ const patch = rule.fix(exp, rawJSDoc);
1824
+ if (!patch)
1825
+ continue;
1826
+ const newJSDoc = serializeJSDoc2(patch, location.indent);
1827
+ edits.push({
1828
+ filePath,
1829
+ symbolName: exp.name,
1830
+ startLine: location.startLine,
1831
+ endLine: location.endLine,
1832
+ hasExisting: location.hasExisting,
1833
+ existingJSDoc: location.existingJSDoc,
1834
+ newJSDoc,
1835
+ indent: location.indent
1836
+ });
1837
+ }
1838
+ if (edits.length > 0) {
1839
+ const result = await applyEdits2(edits);
1840
+ if (result.errors.length > 0) {
1841
+ for (const err of result.errors) {
1842
+ error(chalk6.red(` ${err.file}: ${err.error}`));
1843
+ }
1844
+ } else {
1845
+ process.stdout.write(chalk6.green(`✓ Fixed ${result.editsApplied} issue(s) in ${result.filesModified} file(s)
1846
+ `));
1847
+ }
1848
+ const fixedExports = new Set(edits.map((e) => e.symbolName));
1849
+ const remaining = allViolations.filter((v) => !v.violation.fixable || !fixedExports.has(v.export.name));
1850
+ allViolations.length = 0;
1851
+ allViolations.push(...remaining);
1852
+ }
1853
+ }
1854
+ if (allViolations.length === 0) {
1855
+ log(chalk6.green("✓ No lint issues found"));
1856
+ return;
1857
+ }
1858
+ const byFile = new Map;
1859
+ for (const v of allViolations) {
1860
+ const file = v.filePath ?? "unknown";
1861
+ const existing = byFile.get(file) ?? [];
1862
+ existing.push(v);
1863
+ byFile.set(file, existing);
1864
+ }
1865
+ log("");
1866
+ for (const [filePath, violations] of byFile) {
1867
+ const relativePath = path6.relative(targetDir, filePath);
1868
+ log(chalk6.underline(relativePath));
1869
+ for (const { export: exp, violation } of violations) {
1870
+ const line = exp.source?.line ?? 0;
1871
+ const severity = violation.severity === "error" ? chalk6.red("error") : chalk6.yellow("warning");
1872
+ const fixable = violation.fixable ? chalk6.gray(" (fixable)") : "";
1873
+ log(` ${line}:1 ${severity} ${violation.message} ${chalk6.gray(violation.rule)}${fixable}`);
1874
+ }
1875
+ log("");
1876
+ }
1877
+ const errorCount = allViolations.filter((v) => v.violation.severity === "error").length;
1878
+ const warnCount = allViolations.filter((v) => v.violation.severity === "warn").length;
1879
+ const fixableCount = allViolations.filter((v) => v.violation.fixable).length;
1880
+ const summary = [];
1881
+ if (errorCount > 0)
1882
+ summary.push(chalk6.red(`${errorCount} error(s)`));
1883
+ if (warnCount > 0)
1884
+ summary.push(chalk6.yellow(`${warnCount} warning(s)`));
1885
+ if (fixableCount > 0 && !shouldFix) {
1886
+ summary.push(chalk6.gray(`${fixableCount} fixable with --fix`));
1887
+ }
1888
+ log(summary.join(", "));
1889
+ if (errorCount > 0) {
1890
+ process.exit(1);
1891
+ }
1892
+ } catch (commandError) {
1893
+ error(chalk6.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
1894
+ process.exit(1);
1895
+ }
1896
+ });
1897
+ }
1898
+
1899
+ // src/commands/report.ts
1900
+ import * as fs6 from "node:fs";
1901
+ import * as path7 from "node:path";
1902
+ import {
1903
+ DocCov as DocCov4,
1904
+ detectEntryPoint as detectEntryPoint4,
1905
+ detectMonorepo as detectMonorepo4,
1906
+ findPackageByName as findPackageByName4,
1907
+ NodeFileSystem as NodeFileSystem4
1908
+ } from "@doccov/sdk";
1909
+ import chalk7 from "chalk";
1608
1910
 
1609
1911
  // src/reports/markdown.ts
1610
1912
  function bar(pct, width = 10) {
@@ -1781,42 +2083,42 @@ function registerReportCommand(program) {
1781
2083
  try {
1782
2084
  let spec;
1783
2085
  if (options.spec) {
1784
- const specPath = path6.resolve(options.cwd, options.spec);
1785
- spec = JSON.parse(fs5.readFileSync(specPath, "utf-8"));
2086
+ const specPath = path7.resolve(options.cwd, options.spec);
2087
+ spec = JSON.parse(fs6.readFileSync(specPath, "utf-8"));
1786
2088
  } else {
1787
2089
  let targetDir = options.cwd;
1788
2090
  let entryFile = entry;
1789
- const fileSystem = new NodeFileSystem3(options.cwd);
2091
+ const fileSystem = new NodeFileSystem4(options.cwd);
1790
2092
  if (options.package) {
1791
- const mono = await detectMonorepo3(fileSystem);
2093
+ const mono = await detectMonorepo4(fileSystem);
1792
2094
  if (!mono.isMonorepo) {
1793
2095
  throw new Error(`Not a monorepo. Remove --package flag for single-package repos.`);
1794
2096
  }
1795
- const pkg = findPackageByName3(mono.packages, options.package);
2097
+ const pkg = findPackageByName4(mono.packages, options.package);
1796
2098
  if (!pkg) {
1797
2099
  const available = mono.packages.map((p) => p.name).join(", ");
1798
2100
  throw new Error(`Package "${options.package}" not found. Available: ${available}`);
1799
2101
  }
1800
- targetDir = path6.join(options.cwd, pkg.path);
2102
+ targetDir = path7.join(options.cwd, pkg.path);
1801
2103
  }
1802
2104
  if (!entryFile) {
1803
- const targetFs = new NodeFileSystem3(targetDir);
1804
- const detected = await detectEntryPoint3(targetFs);
1805
- entryFile = path6.join(targetDir, detected.path);
2105
+ const targetFs = new NodeFileSystem4(targetDir);
2106
+ const detected = await detectEntryPoint4(targetFs);
2107
+ entryFile = path7.join(targetDir, detected.path);
1806
2108
  } else {
1807
- entryFile = path6.resolve(targetDir, entryFile);
2109
+ entryFile = path7.resolve(targetDir, entryFile);
1808
2110
  }
1809
- process.stdout.write(chalk6.cyan(`> Analyzing...
2111
+ process.stdout.write(chalk7.cyan(`> Analyzing...
1810
2112
  `));
1811
2113
  try {
1812
2114
  const resolveExternalTypes = !options.skipResolve;
1813
- const doccov = new DocCov3({ resolveExternalTypes });
2115
+ const doccov = new DocCov4({ resolveExternalTypes });
1814
2116
  const result = await doccov.analyzeFileWithDiagnostics(entryFile);
1815
- process.stdout.write(chalk6.green(`✓ Analysis complete
2117
+ process.stdout.write(chalk7.green(`✓ Analysis complete
1816
2118
  `));
1817
2119
  spec = result.spec;
1818
2120
  } catch (analysisError) {
1819
- process.stdout.write(chalk6.red(`✗ Analysis failed
2121
+ process.stdout.write(chalk7.red(`✗ Analysis failed
1820
2122
  `));
1821
2123
  throw analysisError;
1822
2124
  }
@@ -1833,35 +2135,35 @@ function registerReportCommand(program) {
1833
2135
  output = renderMarkdown(stats, { limit });
1834
2136
  }
1835
2137
  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}`));
2138
+ const outPath = path7.resolve(options.cwd, options.out);
2139
+ fs6.writeFileSync(outPath, output);
2140
+ console.log(chalk7.green(`Report written to ${outPath}`));
1839
2141
  } else {
1840
2142
  console.log(output);
1841
2143
  }
1842
2144
  } catch (err) {
1843
- console.error(chalk6.red("Error:"), err instanceof Error ? err.message : err);
2145
+ console.error(chalk7.red("Error:"), err instanceof Error ? err.message : err);
1844
2146
  process.exitCode = 1;
1845
2147
  }
1846
2148
  });
1847
2149
  }
1848
2150
 
1849
2151
  // src/commands/scan.ts
1850
- import * as fs7 from "node:fs";
2152
+ import * as fs8 from "node:fs";
1851
2153
  import * as os from "node:os";
1852
- import * as path8 from "node:path";
2154
+ import * as path9 from "node:path";
1853
2155
  import {
1854
- DocCov as DocCov4,
2156
+ DocCov as DocCov5,
1855
2157
  detectBuildInfo,
1856
- detectEntryPoint as detectEntryPoint4,
1857
- detectMonorepo as detectMonorepo4,
2158
+ detectEntryPoint as detectEntryPoint5,
2159
+ detectMonorepo as detectMonorepo5,
1858
2160
  detectPackageManager,
1859
- findPackageByName as findPackageByName4,
2161
+ findPackageByName as findPackageByName5,
1860
2162
  formatPackageList,
1861
2163
  getInstallCommand,
1862
- NodeFileSystem as NodeFileSystem4
2164
+ NodeFileSystem as NodeFileSystem5
1863
2165
  } from "@doccov/sdk";
1864
- import chalk7 from "chalk";
2166
+ import chalk8 from "chalk";
1865
2167
  import { simpleGit } from "simple-git";
1866
2168
 
1867
2169
  // src/utils/github-url.ts
@@ -1895,8 +2197,8 @@ function buildDisplayUrl(parsed) {
1895
2197
  }
1896
2198
 
1897
2199
  // src/utils/llm-build-plan.ts
1898
- import * as fs6 from "node:fs";
1899
- import * as path7 from "node:path";
2200
+ import * as fs7 from "node:fs";
2201
+ import * as path8 from "node:path";
1900
2202
  import { createAnthropic as createAnthropic3 } from "@ai-sdk/anthropic";
1901
2203
  import { createOpenAI as createOpenAI3 } from "@ai-sdk/openai";
1902
2204
  import { generateObject as generateObject3 } from "ai";
@@ -1932,10 +2234,10 @@ function getModel3() {
1932
2234
  async function gatherContextFiles(repoDir) {
1933
2235
  const sections = [];
1934
2236
  for (const fileName of CONTEXT_FILES) {
1935
- const filePath = path7.join(repoDir, fileName);
1936
- if (fs6.existsSync(filePath)) {
2237
+ const filePath = path8.join(repoDir, fileName);
2238
+ if (fs7.existsSync(filePath)) {
1937
2239
  try {
1938
- let content = fs6.readFileSync(filePath, "utf-8");
2240
+ let content = fs7.readFileSync(filePath, "utf-8");
1939
2241
  if (content.length > MAX_FILE_CHARS) {
1940
2242
  content = `${content.slice(0, MAX_FILE_CHARS)}
1941
2243
  ... (truncated)`;
@@ -1987,14 +2289,14 @@ async function generateBuildPlan(repoDir) {
1987
2289
  }
1988
2290
 
1989
2291
  // src/commands/scan.ts
1990
- var defaultDependencies5 = {
1991
- createDocCov: (options) => new DocCov4(options),
2292
+ var defaultDependencies6 = {
2293
+ createDocCov: (options) => new DocCov5(options),
1992
2294
  log: console.log,
1993
2295
  error: console.error
1994
2296
  };
1995
2297
  function registerScanCommand(program, dependencies = {}) {
1996
2298
  const { createDocCov, log, error } = {
1997
- ...defaultDependencies5,
2299
+ ...defaultDependencies6,
1998
2300
  ...dependencies
1999
2301
  };
2000
2302
  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 +2306,12 @@ function registerScanCommand(program, dependencies = {}) {
2004
2306
  const cloneUrl = buildCloneUrl(parsed);
2005
2307
  const displayUrl = buildDisplayUrl(parsed);
2006
2308
  log("");
2007
- log(chalk7.bold(`Scanning ${displayUrl}`));
2008
- log(chalk7.gray(`Branch/tag: ${parsed.ref}`));
2309
+ log(chalk8.bold(`Scanning ${displayUrl}`));
2310
+ log(chalk8.gray(`Branch/tag: ${parsed.ref}`));
2009
2311
  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}...
2312
+ tempDir = path9.join(os.tmpdir(), `doccov-scan-${Date.now()}-${Math.random().toString(36).slice(2)}`);
2313
+ fs8.mkdirSync(tempDir, { recursive: true });
2314
+ process.stdout.write(chalk8.cyan(`> Cloning ${parsed.owner}/${parsed.repo}...
2013
2315
  `));
2014
2316
  try {
2015
2317
  const git = simpleGit({
@@ -2031,10 +2333,10 @@ function registerScanCommand(program, dependencies = {}) {
2031
2333
  } finally {
2032
2334
  process.env = originalEnv;
2033
2335
  }
2034
- process.stdout.write(chalk7.green(`✓ Cloned ${parsed.owner}/${parsed.repo}
2336
+ process.stdout.write(chalk8.green(`✓ Cloned ${parsed.owner}/${parsed.repo}
2035
2337
  `));
2036
2338
  } catch (cloneError) {
2037
- process.stdout.write(chalk7.red(`✗ Failed to clone repository
2339
+ process.stdout.write(chalk8.red(`✗ Failed to clone repository
2038
2340
  `));
2039
2341
  const message = cloneError instanceof Error ? cloneError.message : String(cloneError);
2040
2342
  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 +2352,11 @@ function registerScanCommand(program, dependencies = {}) {
2050
2352
  }
2051
2353
  throw new Error(`Clone failed: ${message}`);
2052
2354
  }
2053
- const fileSystem = new NodeFileSystem4(tempDir);
2355
+ const fileSystem = new NodeFileSystem5(tempDir);
2054
2356
  if (options.skipInstall) {
2055
- log(chalk7.gray("Skipping dependency installation (--skip-install)"));
2357
+ log(chalk8.gray("Skipping dependency installation (--skip-install)"));
2056
2358
  } else {
2057
- process.stdout.write(chalk7.cyan(`> Installing dependencies...
2359
+ process.stdout.write(chalk8.cyan(`> Installing dependencies...
2058
2360
  `));
2059
2361
  const installErrors = [];
2060
2362
  try {
@@ -2104,56 +2406,56 @@ function registerScanCommand(program, dependencies = {}) {
2104
2406
  }
2105
2407
  }
2106
2408
  if (installed) {
2107
- process.stdout.write(chalk7.green(`✓ Dependencies installed
2409
+ process.stdout.write(chalk8.green(`✓ Dependencies installed
2108
2410
  `));
2109
2411
  } else {
2110
- process.stdout.write(chalk7.yellow(`⚠ Could not install dependencies (analysis may be limited)
2412
+ process.stdout.write(chalk8.yellow(`⚠ Could not install dependencies (analysis may be limited)
2111
2413
  `));
2112
2414
  for (const err of installErrors) {
2113
- log(chalk7.gray(` ${err}`));
2415
+ log(chalk8.gray(` ${err}`));
2114
2416
  }
2115
2417
  }
2116
2418
  } catch (outerError) {
2117
2419
  const msg = outerError instanceof Error ? outerError.message : String(outerError);
2118
- process.stdout.write(chalk7.yellow(`⚠ Could not install dependencies: ${msg.slice(0, 100)}
2420
+ process.stdout.write(chalk8.yellow(`⚠ Could not install dependencies: ${msg.slice(0, 100)}
2119
2421
  `));
2120
2422
  for (const err of installErrors) {
2121
- log(chalk7.gray(` ${err}`));
2423
+ log(chalk8.gray(` ${err}`));
2122
2424
  }
2123
2425
  }
2124
2426
  }
2125
2427
  let targetDir = tempDir;
2126
2428
  let packageName;
2127
- const mono = await detectMonorepo4(fileSystem);
2429
+ const mono = await detectMonorepo5(fileSystem);
2128
2430
  if (mono.isMonorepo) {
2129
2431
  if (!options.package) {
2130
2432
  error("");
2131
- error(chalk7.red(`Monorepo detected with ${mono.packages.length} packages. Specify target with --package:`));
2433
+ error(chalk8.red(`Monorepo detected with ${mono.packages.length} packages. Specify target with --package:`));
2132
2434
  error("");
2133
2435
  error(formatPackageList(mono.packages));
2134
2436
  error("");
2135
2437
  throw new Error("Monorepo requires --package flag");
2136
2438
  }
2137
- const pkg = findPackageByName4(mono.packages, options.package);
2439
+ const pkg = findPackageByName5(mono.packages, options.package);
2138
2440
  if (!pkg) {
2139
2441
  error("");
2140
- error(chalk7.red(`Package "${options.package}" not found. Available packages:`));
2442
+ error(chalk8.red(`Package "${options.package}" not found. Available packages:`));
2141
2443
  error("");
2142
2444
  error(formatPackageList(mono.packages));
2143
2445
  error("");
2144
2446
  throw new Error(`Package not found: ${options.package}`);
2145
2447
  }
2146
- targetDir = path8.join(tempDir, pkg.path);
2448
+ targetDir = path9.join(tempDir, pkg.path);
2147
2449
  packageName = pkg.name;
2148
- log(chalk7.gray(`Analyzing package: ${packageName}`));
2450
+ log(chalk8.gray(`Analyzing package: ${packageName}`));
2149
2451
  }
2150
- process.stdout.write(chalk7.cyan(`> Detecting entry point...
2452
+ process.stdout.write(chalk8.cyan(`> Detecting entry point...
2151
2453
  `));
2152
2454
  let entryPath;
2153
- const targetFs = mono.isMonorepo ? new NodeFileSystem4(targetDir) : fileSystem;
2455
+ const targetFs = mono.isMonorepo ? new NodeFileSystem5(targetDir) : fileSystem;
2154
2456
  let buildFailed = false;
2155
2457
  const runLlmFallback = async (reason) => {
2156
- process.stdout.write(chalk7.cyan(`> ${reason}, trying LLM fallback...
2458
+ process.stdout.write(chalk8.cyan(`> ${reason}, trying LLM fallback...
2157
2459
  `));
2158
2460
  const plan = await generateBuildPlan(targetDir);
2159
2461
  if (!plan) {
@@ -2162,88 +2464,88 @@ function registerScanCommand(program, dependencies = {}) {
2162
2464
  if (plan.buildCommands.length > 0) {
2163
2465
  const { execSync } = await import("node:child_process");
2164
2466
  for (const cmd of plan.buildCommands) {
2165
- log(chalk7.gray(` Running: ${cmd}`));
2467
+ log(chalk8.gray(` Running: ${cmd}`));
2166
2468
  try {
2167
2469
  execSync(cmd, { cwd: targetDir, stdio: "pipe", timeout: 300000 });
2168
2470
  } catch (buildError) {
2169
2471
  buildFailed = true;
2170
2472
  const msg = buildError instanceof Error ? buildError.message : String(buildError);
2171
2473
  if (msg.includes("rustc") || msg.includes("cargo") || msg.includes("wasm-pack")) {
2172
- log(chalk7.yellow(` ⚠ Build requires Rust toolchain (not available)`));
2474
+ log(chalk8.yellow(` ⚠ Build requires Rust toolchain (not available)`));
2173
2475
  } else if (msg.includes("rimraf") || msg.includes("command not found")) {
2174
- log(chalk7.yellow(` ⚠ Build failed: missing dependencies`));
2476
+ log(chalk8.yellow(` ⚠ Build failed: missing dependencies`));
2175
2477
  } else {
2176
- log(chalk7.yellow(` ⚠ Build failed: ${msg.slice(0, 80)}`));
2478
+ log(chalk8.yellow(` ⚠ Build failed: ${msg.slice(0, 80)}`));
2177
2479
  }
2178
2480
  }
2179
2481
  }
2180
2482
  }
2181
2483
  if (plan.notes) {
2182
- log(chalk7.gray(` Note: ${plan.notes}`));
2484
+ log(chalk8.gray(` Note: ${plan.notes}`));
2183
2485
  }
2184
2486
  return plan.entryPoint;
2185
2487
  };
2186
2488
  try {
2187
- const entry = await detectEntryPoint4(targetFs);
2489
+ const entry = await detectEntryPoint5(targetFs);
2188
2490
  const buildInfo = await detectBuildInfo(targetFs);
2189
2491
  const needsBuildStep = entry.isDeclarationOnly && buildInfo.exoticIndicators.wasm;
2190
2492
  if (needsBuildStep) {
2191
- process.stdout.write(chalk7.cyan(`> Detected .d.ts entry with WASM indicators...
2493
+ process.stdout.write(chalk8.cyan(`> Detected .d.ts entry with WASM indicators...
2192
2494
  `));
2193
2495
  const llmEntry = await runLlmFallback("WASM project detected");
2194
2496
  if (llmEntry) {
2195
- entryPath = path8.join(targetDir, llmEntry);
2497
+ entryPath = path9.join(targetDir, llmEntry);
2196
2498
  if (buildFailed) {
2197
- process.stdout.write(chalk7.green(`✓ Entry point: ${llmEntry} (using pre-committed declarations)
2499
+ process.stdout.write(chalk8.green(`✓ Entry point: ${llmEntry} (using pre-committed declarations)
2198
2500
  `));
2199
- log(chalk7.gray(" Coverage may be limited - generated .d.ts files typically lack JSDoc"));
2501
+ log(chalk8.gray(" Coverage may be limited - generated .d.ts files typically lack JSDoc"));
2200
2502
  } else {
2201
- process.stdout.write(chalk7.green(`✓ Entry point: ${llmEntry} (from LLM fallback - WASM project)
2503
+ process.stdout.write(chalk8.green(`✓ Entry point: ${llmEntry} (from LLM fallback - WASM project)
2202
2504
  `));
2203
2505
  }
2204
2506
  } else {
2205
- entryPath = path8.join(targetDir, entry.path);
2206
- process.stdout.write(chalk7.green(`✓ Entry point: ${entry.path} (from ${entry.source})
2507
+ entryPath = path9.join(targetDir, entry.path);
2508
+ process.stdout.write(chalk8.green(`✓ Entry point: ${entry.path} (from ${entry.source})
2207
2509
  `));
2208
- log(chalk7.yellow(" ⚠ WASM project detected but no API key - analysis may be limited"));
2510
+ log(chalk8.yellow(" ⚠ WASM project detected but no API key - analysis may be limited"));
2209
2511
  }
2210
2512
  } else {
2211
- entryPath = path8.join(targetDir, entry.path);
2212
- process.stdout.write(chalk7.green(`✓ Entry point: ${entry.path} (from ${entry.source})
2513
+ entryPath = path9.join(targetDir, entry.path);
2514
+ process.stdout.write(chalk8.green(`✓ Entry point: ${entry.path} (from ${entry.source})
2213
2515
  `));
2214
2516
  }
2215
2517
  } catch (entryError) {
2216
2518
  const llmEntry = await runLlmFallback("Heuristics failed");
2217
2519
  if (llmEntry) {
2218
- entryPath = path8.join(targetDir, llmEntry);
2219
- process.stdout.write(chalk7.green(`✓ Entry point: ${llmEntry} (from LLM fallback)
2520
+ entryPath = path9.join(targetDir, llmEntry);
2521
+ process.stdout.write(chalk8.green(`✓ Entry point: ${llmEntry} (from LLM fallback)
2220
2522
  `));
2221
2523
  } else {
2222
- process.stdout.write(chalk7.red(`✗ Could not detect entry point (set OPENAI_API_KEY for smart fallback)
2524
+ process.stdout.write(chalk8.red(`✗ Could not detect entry point (set OPENAI_API_KEY for smart fallback)
2223
2525
  `));
2224
2526
  throw entryError;
2225
2527
  }
2226
2528
  }
2227
- process.stdout.write(chalk7.cyan(`> Analyzing documentation coverage...
2529
+ process.stdout.write(chalk8.cyan(`> Analyzing documentation coverage...
2228
2530
  `));
2229
2531
  let result;
2230
2532
  try {
2231
2533
  const resolveExternalTypes = !options.skipResolve;
2232
2534
  const doccov = createDocCov({ resolveExternalTypes });
2233
2535
  result = await doccov.analyzeFileWithDiagnostics(entryPath);
2234
- process.stdout.write(chalk7.green(`✓ Analysis complete
2536
+ process.stdout.write(chalk8.green(`✓ Analysis complete
2235
2537
  `));
2236
2538
  } catch (analysisError) {
2237
- process.stdout.write(chalk7.red(`✗ Analysis failed
2539
+ process.stdout.write(chalk8.red(`✗ Analysis failed
2238
2540
  `));
2239
2541
  throw analysisError;
2240
2542
  }
2241
2543
  const spec = result.spec;
2242
2544
  const coverageScore = spec.docs?.coverageScore ?? 0;
2243
2545
  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}`));
2546
+ const specPath = path9.resolve(process.cwd(), options.saveSpec);
2547
+ fs8.writeFileSync(specPath, JSON.stringify(spec, null, 2));
2548
+ log(chalk8.green(`✓ Saved spec to ${options.saveSpec}`));
2247
2549
  }
2248
2550
  const undocumented = [];
2249
2551
  const driftIssues = [];
@@ -2280,7 +2582,7 @@ function registerScanCommand(program, dependencies = {}) {
2280
2582
  printTextResult(scanResult, log);
2281
2583
  }
2282
2584
  } catch (commandError) {
2283
- error(chalk7.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
2585
+ error(chalk8.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
2284
2586
  process.exitCode = 1;
2285
2587
  } finally {
2286
2588
  if (tempDir && options.cleanup !== false) {
@@ -2290,63 +2592,180 @@ function registerScanCommand(program, dependencies = {}) {
2290
2592
  stdio: "ignore"
2291
2593
  }).unref();
2292
2594
  } else if (tempDir) {
2293
- log(chalk7.gray(`Repo preserved at: ${tempDir}`));
2595
+ log(chalk8.gray(`Repo preserved at: ${tempDir}`));
2294
2596
  }
2295
2597
  }
2296
2598
  });
2297
2599
  }
2298
2600
  function printTextResult(result, log) {
2299
2601
  log("");
2300
- log(chalk7.bold("DocCov Scan Results"));
2602
+ log(chalk8.bold("DocCov Scan Results"));
2301
2603
  log("─".repeat(40));
2302
2604
  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)}`);
2605
+ log(`Repository: ${chalk8.cyan(repoName)}`);
2606
+ log(`Branch: ${chalk8.gray(result.ref)}`);
2305
2607
  log("");
2306
- const coverageColor = result.coverage >= 80 ? chalk7.green : result.coverage >= 50 ? chalk7.yellow : chalk7.red;
2307
- log(chalk7.bold("Coverage"));
2608
+ const coverageColor = result.coverage >= 80 ? chalk8.green : result.coverage >= 50 ? chalk8.yellow : chalk8.red;
2609
+ log(chalk8.bold("Coverage"));
2308
2610
  log(` ${coverageColor(`${result.coverage}%`)}`);
2309
2611
  log("");
2310
- log(chalk7.bold("Stats"));
2612
+ log(chalk8.bold("Stats"));
2311
2613
  log(` ${result.exportCount} exports`);
2312
2614
  log(` ${result.typeCount} types`);
2313
2615
  log(` ${result.undocumented.length} undocumented`);
2314
2616
  log(` ${result.driftCount} drift issues`);
2315
2617
  if (result.undocumented.length > 0) {
2316
2618
  log("");
2317
- log(chalk7.bold("Undocumented Exports"));
2619
+ log(chalk8.bold("Undocumented Exports"));
2318
2620
  for (const name of result.undocumented.slice(0, 10)) {
2319
- log(chalk7.yellow(` ! ${name}`));
2621
+ log(chalk8.yellow(` ! ${name}`));
2320
2622
  }
2321
2623
  if (result.undocumented.length > 10) {
2322
- log(chalk7.gray(` ... and ${result.undocumented.length - 10} more`));
2624
+ log(chalk8.gray(` ... and ${result.undocumented.length - 10} more`));
2323
2625
  }
2324
2626
  }
2325
2627
  if (result.drift.length > 0) {
2326
2628
  log("");
2327
- log(chalk7.bold("Drift Issues"));
2629
+ log(chalk8.bold("Drift Issues"));
2328
2630
  for (const d of result.drift.slice(0, 5)) {
2329
- log(chalk7.red(` • ${d.export}: ${d.issue}`));
2631
+ log(chalk8.red(` • ${d.export}: ${d.issue}`));
2330
2632
  }
2331
2633
  if (result.drift.length > 5) {
2332
- log(chalk7.gray(` ... and ${result.drift.length - 5} more`));
2634
+ log(chalk8.gray(` ... and ${result.drift.length - 5} more`));
2333
2635
  }
2334
2636
  }
2335
2637
  log("");
2336
2638
  }
2337
2639
 
2640
+ // src/commands/typecheck.ts
2641
+ import * as fs9 from "node:fs";
2642
+ import * as path10 from "node:path";
2643
+ import {
2644
+ detectEntryPoint as detectEntryPoint6,
2645
+ detectMonorepo as detectMonorepo6,
2646
+ DocCov as DocCov6,
2647
+ findPackageByName as findPackageByName6,
2648
+ NodeFileSystem as NodeFileSystem6,
2649
+ typecheckExamples as typecheckExamples2
2650
+ } from "@doccov/sdk";
2651
+ import chalk9 from "chalk";
2652
+ var defaultDependencies7 = {
2653
+ createDocCov: (options) => new DocCov6(options),
2654
+ log: console.log,
2655
+ error: console.error
2656
+ };
2657
+ function registerTypecheckCommand(program, dependencies = {}) {
2658
+ const { createDocCov, log, error } = {
2659
+ ...defaultDependencies7,
2660
+ ...dependencies
2661
+ };
2662
+ 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) => {
2663
+ try {
2664
+ let targetDir = options.cwd;
2665
+ let entryFile = entry;
2666
+ const fileSystem = new NodeFileSystem6(options.cwd);
2667
+ if (options.package) {
2668
+ const mono = await detectMonorepo6(fileSystem);
2669
+ if (!mono.isMonorepo) {
2670
+ throw new Error("Not a monorepo. Remove --package flag.");
2671
+ }
2672
+ const pkg = findPackageByName6(mono.packages, options.package);
2673
+ if (!pkg) {
2674
+ const available = mono.packages.map((p) => p.name).join(", ");
2675
+ throw new Error(`Package "${options.package}" not found. Available: ${available}`);
2676
+ }
2677
+ targetDir = path10.join(options.cwd, pkg.path);
2678
+ log(chalk9.gray(`Found package at ${pkg.path}`));
2679
+ }
2680
+ if (!entryFile) {
2681
+ const targetFs = new NodeFileSystem6(targetDir);
2682
+ const detected = await detectEntryPoint6(targetFs);
2683
+ entryFile = path10.join(targetDir, detected.path);
2684
+ log(chalk9.gray(`Auto-detected entry point: ${detected.path}`));
2685
+ } else {
2686
+ entryFile = path10.resolve(targetDir, entryFile);
2687
+ if (fs9.existsSync(entryFile) && fs9.statSync(entryFile).isDirectory()) {
2688
+ targetDir = entryFile;
2689
+ const dirFs = new NodeFileSystem6(entryFile);
2690
+ const detected = await detectEntryPoint6(dirFs);
2691
+ entryFile = path10.join(entryFile, detected.path);
2692
+ log(chalk9.gray(`Auto-detected entry point: ${detected.path}`));
2693
+ }
2694
+ }
2695
+ const resolveExternalTypes = !options.skipResolve;
2696
+ process.stdout.write(chalk9.cyan(`> Analyzing documentation...
2697
+ `));
2698
+ const doccov = createDocCov({ resolveExternalTypes });
2699
+ const specResult = await doccov.analyzeFileWithDiagnostics(entryFile);
2700
+ if (!specResult) {
2701
+ throw new Error("Failed to analyze documentation.");
2702
+ }
2703
+ const allExamples = [];
2704
+ for (const exp of specResult.spec.exports ?? []) {
2705
+ if (exp.examples && exp.examples.length > 0) {
2706
+ allExamples.push({ exportName: exp.name, examples: exp.examples });
2707
+ }
2708
+ }
2709
+ if (allExamples.length === 0) {
2710
+ log(chalk9.gray("No @example blocks found"));
2711
+ return;
2712
+ }
2713
+ const totalExamples = allExamples.reduce((sum, e) => sum + e.examples.length, 0);
2714
+ process.stdout.write(chalk9.cyan(`> Type-checking ${totalExamples} example(s)...
2715
+ `));
2716
+ const allErrors = [];
2717
+ let passed = 0;
2718
+ let failed = 0;
2719
+ for (const { exportName, examples } of allExamples) {
2720
+ const result = typecheckExamples2(examples, targetDir);
2721
+ for (const err of result.errors) {
2722
+ allErrors.push({ exportName, error: err });
2723
+ }
2724
+ passed += result.passed;
2725
+ failed += result.failed;
2726
+ }
2727
+ if (allErrors.length === 0) {
2728
+ log(chalk9.green(`✓ All ${totalExamples} example(s) passed type checking`));
2729
+ return;
2730
+ }
2731
+ log("");
2732
+ const byExport = new Map;
2733
+ for (const { exportName, error: err } of allErrors) {
2734
+ const existing = byExport.get(exportName) ?? [];
2735
+ existing.push(err);
2736
+ byExport.set(exportName, existing);
2737
+ }
2738
+ for (const [exportName, errors] of byExport) {
2739
+ log(chalk9.red(`✗ ${exportName}`));
2740
+ for (const err of errors) {
2741
+ log(chalk9.gray(` @example block ${err.exampleIndex + 1}, line ${err.line}:`));
2742
+ log(chalk9.red(` ${err.message}`));
2743
+ }
2744
+ log("");
2745
+ }
2746
+ log(chalk9.red(`${failed} example(s) failed`) + chalk9.gray(`, ${passed} passed`));
2747
+ process.exit(1);
2748
+ } catch (commandError) {
2749
+ error(chalk9.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
2750
+ process.exit(1);
2751
+ }
2752
+ });
2753
+ }
2754
+
2338
2755
  // src/cli.ts
2339
2756
  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"));
2757
+ var __dirname2 = path11.dirname(__filename2);
2758
+ var packageJson = JSON.parse(readFileSync5(path11.join(__dirname2, "../package.json"), "utf-8"));
2342
2759
  var program = new Command;
2343
2760
  program.name("doccov").description("DocCov - Documentation coverage and drift detection for TypeScript").version(packageJson.version);
2344
2761
  registerGenerateCommand(program);
2345
2762
  registerCheckCommand(program);
2346
2763
  registerDiffCommand(program);
2347
2764
  registerInitCommand(program);
2765
+ registerLintCommand(program);
2348
2766
  registerReportCommand(program);
2349
2767
  registerScanCommand(program);
2768
+ registerTypecheckCommand(program);
2350
2769
  program.command("*", { hidden: true }).action(() => {
2351
2770
  program.outputHelp();
2352
2771
  });
@@ -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.5.8",
4
4
  "description": "DocCov CLI - Documentation coverage and drift detection for TypeScript",
5
5
  "keywords": [
6
6
  "typescript",
@@ -49,7 +49,7 @@
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",
52
+ "@doccov/sdk": "^0.5.8",
53
53
  "@openpkg-ts/spec": "^0.4.0",
54
54
  "ai": "^4.0.0",
55
55
  "chalk": "^5.4.1",