@doccov/cli 0.21.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +829 -655
  2. package/package.json +3 -3
package/dist/cli.js CHANGED
@@ -147,36 +147,358 @@ ${formatIssues(issues)}`);
147
147
  var defineConfig = (config) => config;
148
148
  // src/cli.ts
149
149
  import { readFileSync as readFileSync5 } from "node:fs";
150
- import * as path9 from "node:path";
150
+ import * as path10 from "node:path";
151
151
  import { fileURLToPath } from "node:url";
152
152
  import { Command } from "commander";
153
153
 
154
- // src/commands/check.ts
154
+ // src/commands/check/index.ts
155
+ import {
156
+ DocCov,
157
+ enrichSpec,
158
+ NodeFileSystem,
159
+ parseExamplesFlag,
160
+ resolveTarget
161
+ } from "@doccov/sdk";
162
+ import chalk6 from "chalk";
163
+
164
+ // src/utils/filter-options.ts
165
+ import { mergeFilters, parseListFlag } from "@doccov/sdk";
166
+ import chalk from "chalk";
167
+ var parseVisibilityFlag = (value) => {
168
+ if (!value)
169
+ return;
170
+ const validTags = ["public", "beta", "alpha", "internal"];
171
+ const parsed = parseListFlag(value);
172
+ if (!parsed)
173
+ return;
174
+ const result = [];
175
+ for (const tag of parsed) {
176
+ const lower = tag.toLowerCase();
177
+ if (validTags.includes(lower)) {
178
+ result.push(lower);
179
+ }
180
+ }
181
+ return result.length > 0 ? result : undefined;
182
+ };
183
+ var formatList = (label, values) => `${label}: ${values.map((value) => chalk.cyan(value)).join(", ")}`;
184
+ var mergeFilterOptions = (config, cliOptions) => {
185
+ const messages = [];
186
+ if (config?.include) {
187
+ messages.push(formatList("include filters from config", config.include));
188
+ }
189
+ if (config?.exclude) {
190
+ messages.push(formatList("exclude filters from config", config.exclude));
191
+ }
192
+ if (cliOptions.include) {
193
+ messages.push(formatList("apply include filters from CLI", cliOptions.include));
194
+ }
195
+ if (cliOptions.exclude) {
196
+ messages.push(formatList("apply exclude filters from CLI", cliOptions.exclude));
197
+ }
198
+ if (cliOptions.visibility) {
199
+ messages.push(formatList("apply visibility filter from CLI", cliOptions.visibility));
200
+ }
201
+ const resolved = mergeFilters(config, cliOptions);
202
+ if (!resolved.include && !resolved.exclude && !cliOptions.visibility) {
203
+ return { messages };
204
+ }
205
+ const source = resolved.source === "override" ? "cli" : resolved.source;
206
+ return {
207
+ include: resolved.include,
208
+ exclude: resolved.exclude,
209
+ visibility: cliOptions.visibility,
210
+ source,
211
+ messages
212
+ };
213
+ };
214
+
215
+ // src/utils/progress.ts
216
+ import chalk2 from "chalk";
217
+ class StepProgress {
218
+ steps;
219
+ currentStep = 0;
220
+ startTime;
221
+ stepStartTime;
222
+ constructor(steps) {
223
+ this.steps = steps;
224
+ this.startTime = Date.now();
225
+ this.stepStartTime = Date.now();
226
+ }
227
+ start(stepIndex) {
228
+ this.currentStep = stepIndex ?? 0;
229
+ this.stepStartTime = Date.now();
230
+ this.render();
231
+ }
232
+ next() {
233
+ this.completeCurrentStep();
234
+ this.currentStep++;
235
+ if (this.currentStep < this.steps.length) {
236
+ this.stepStartTime = Date.now();
237
+ this.render();
238
+ }
239
+ }
240
+ complete(message) {
241
+ this.completeCurrentStep();
242
+ if (message) {
243
+ const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);
244
+ console.log(`${chalk2.green("✓")} ${message} ${chalk2.dim(`(${elapsed}s)`)}`);
245
+ }
246
+ }
247
+ render() {
248
+ const step = this.steps[this.currentStep];
249
+ if (!step)
250
+ return;
251
+ const label = step.activeLabel ?? step.label;
252
+ const prefix = chalk2.dim(`[${this.currentStep + 1}/${this.steps.length}]`);
253
+ process.stdout.write(`\r${prefix} ${chalk2.cyan(label)}...`);
254
+ }
255
+ completeCurrentStep() {
256
+ const step = this.steps[this.currentStep];
257
+ if (!step)
258
+ return;
259
+ const elapsed = ((Date.now() - this.stepStartTime) / 1000).toFixed(1);
260
+ const prefix = chalk2.dim(`[${this.currentStep + 1}/${this.steps.length}]`);
261
+ process.stdout.write(`\r${" ".repeat(80)}\r`);
262
+ console.log(`${prefix} ${step.label} ${chalk2.green("✓")} ${chalk2.dim(`(${elapsed}s)`)}`);
263
+ }
264
+ }
265
+
266
+ // src/utils/validation.ts
267
+ function clampPercentage(value, fallback = 80) {
268
+ if (Number.isNaN(value))
269
+ return fallback;
270
+ return Math.min(100, Math.max(0, Math.round(value)));
271
+ }
272
+ function resolveThreshold(cliValue, configValue) {
273
+ const raw = cliValue ?? configValue;
274
+ return raw !== undefined ? clampPercentage(Number(raw)) : undefined;
275
+ }
276
+
277
+ // src/commands/check/fix-handler.ts
155
278
  import * as fs2 from "node:fs";
156
- import * as path4 from "node:path";
279
+ import * as path3 from "node:path";
157
280
  import {
158
281
  applyEdits,
159
282
  categorizeDrifts,
160
283
  createSourceFile,
161
- DocCov,
162
- enrichSpec,
163
284
  findJSDocLocation,
164
285
  generateFixesForExport,
165
- generateReport,
166
286
  mergeFixes,
167
- NodeFileSystem,
168
- parseExamplesFlag,
169
287
  parseJSDocToPatch,
170
- parseMarkdownFiles,
171
- resolveTarget,
172
- serializeJSDoc,
173
- validateExamples
288
+ serializeJSDoc
174
289
  } from "@doccov/sdk";
175
- import {
176
- DRIFT_CATEGORIES as DRIFT_CATEGORIES2
177
- } from "@openpkg-ts/spec";
178
- import chalk4 from "chalk";
290
+ import chalk3 from "chalk";
291
+
292
+ // src/commands/check/utils.ts
293
+ import * as fs from "node:fs";
294
+ import * as path2 from "node:path";
295
+ import { parseMarkdownFiles } from "@doccov/sdk";
296
+ import { DRIFT_CATEGORIES } from "@openpkg-ts/spec";
179
297
  import { glob } from "glob";
298
+ function collectDriftsFromExports(exports) {
299
+ const results = [];
300
+ for (const exp of exports) {
301
+ for (const drift of exp.docs?.drift ?? []) {
302
+ results.push({ export: exp, drift });
303
+ }
304
+ }
305
+ return results;
306
+ }
307
+ function groupByExport(drifts) {
308
+ const map = new Map;
309
+ for (const { export: exp, drift } of drifts) {
310
+ const existing = map.get(exp) ?? [];
311
+ existing.push(drift);
312
+ map.set(exp, existing);
313
+ }
314
+ return map;
315
+ }
316
+ function collectDrift(exportsList) {
317
+ const drifts = [];
318
+ for (const entry of exportsList) {
319
+ const drift = entry.docs?.drift;
320
+ if (!drift || drift.length === 0) {
321
+ continue;
322
+ }
323
+ for (const d of drift) {
324
+ drifts.push({
325
+ name: entry.name,
326
+ type: d.type,
327
+ issue: d.issue ?? "Documentation drift detected.",
328
+ suggestion: d.suggestion,
329
+ category: DRIFT_CATEGORIES[d.type]
330
+ });
331
+ }
332
+ }
333
+ return drifts;
334
+ }
335
+ function collect(value, previous) {
336
+ return previous.concat([value]);
337
+ }
338
+ async function loadMarkdownFiles(patterns, cwd) {
339
+ const files = [];
340
+ for (const pattern of patterns) {
341
+ const matches = await glob(pattern, { nodir: true, cwd });
342
+ for (const filePath of matches) {
343
+ try {
344
+ const fullPath = path2.resolve(cwd, filePath);
345
+ const content = fs.readFileSync(fullPath, "utf-8");
346
+ files.push({ path: filePath, content });
347
+ } catch {}
348
+ }
349
+ }
350
+ return parseMarkdownFiles(files);
351
+ }
352
+
353
+ // src/commands/check/fix-handler.ts
354
+ async function handleFixes(spec, options, deps) {
355
+ const { isPreview, targetDir } = options;
356
+ const { log, error } = deps;
357
+ const fixedDriftKeys = new Set;
358
+ const allDrifts = collectDriftsFromExports(spec.exports ?? []);
359
+ if (allDrifts.length === 0) {
360
+ return { fixedDriftKeys, editsApplied: 0, filesModified: 0 };
361
+ }
362
+ const { fixable, nonFixable } = categorizeDrifts(allDrifts.map((d) => d.drift));
363
+ if (fixable.length === 0) {
364
+ log(chalk3.yellow(`Found ${nonFixable.length} drift issue(s), but none are auto-fixable.`));
365
+ return { fixedDriftKeys, editsApplied: 0, filesModified: 0 };
366
+ }
367
+ log("");
368
+ log(chalk3.bold(`Found ${fixable.length} fixable issue(s)`));
369
+ if (nonFixable.length > 0) {
370
+ log(chalk3.gray(`(${nonFixable.length} non-fixable issue(s) skipped)`));
371
+ }
372
+ log("");
373
+ const groupedDrifts = groupByExport(allDrifts.filter((d) => fixable.includes(d.drift)));
374
+ const edits = [];
375
+ const editsByFile = new Map;
376
+ for (const [exp, drifts] of groupedDrifts) {
377
+ const edit = generateEditForExport(exp, drifts, targetDir, log);
378
+ if (!edit)
379
+ continue;
380
+ for (const drift of drifts) {
381
+ fixedDriftKeys.add(`${exp.name}:${drift.issue}`);
382
+ }
383
+ edits.push(edit.edit);
384
+ const fileEdits = editsByFile.get(edit.filePath) ?? [];
385
+ fileEdits.push({
386
+ export: exp,
387
+ edit: edit.edit,
388
+ fixes: edit.fixes,
389
+ existingPatch: edit.existingPatch
390
+ });
391
+ editsByFile.set(edit.filePath, fileEdits);
392
+ }
393
+ if (edits.length === 0) {
394
+ return { fixedDriftKeys, editsApplied: 0, filesModified: 0 };
395
+ }
396
+ if (isPreview) {
397
+ displayPreview(editsByFile, targetDir, log);
398
+ return { fixedDriftKeys, editsApplied: 0, filesModified: 0 };
399
+ }
400
+ const applyResult = await applyEdits(edits);
401
+ if (applyResult.errors.length > 0) {
402
+ for (const err of applyResult.errors) {
403
+ error(chalk3.red(` ${err.file}: ${err.error}`));
404
+ }
405
+ }
406
+ const totalFixes = Array.from(editsByFile.values()).reduce((sum, fileEdits) => sum + fileEdits.reduce((s, e) => s + e.fixes.length, 0), 0);
407
+ log("");
408
+ log(chalk3.green(`✓ Applied ${totalFixes} fix(es) to ${applyResult.filesModified} file(s)`));
409
+ for (const [filePath, fileEdits] of editsByFile) {
410
+ const relativePath = path3.relative(targetDir, filePath);
411
+ const fixCount = fileEdits.reduce((s, e) => s + e.fixes.length, 0);
412
+ log(chalk3.dim(` ${relativePath} (${fixCount} fixes)`));
413
+ }
414
+ return {
415
+ fixedDriftKeys,
416
+ editsApplied: totalFixes,
417
+ filesModified: applyResult.filesModified
418
+ };
419
+ }
420
+ function generateEditForExport(exp, drifts, targetDir, log) {
421
+ if (!exp.source?.file) {
422
+ log(chalk3.gray(` Skipping ${exp.name}: no source location`));
423
+ return null;
424
+ }
425
+ if (exp.source.file.endsWith(".d.ts")) {
426
+ log(chalk3.gray(` Skipping ${exp.name}: declaration file`));
427
+ return null;
428
+ }
429
+ const filePath = path3.resolve(targetDir, exp.source.file);
430
+ if (!fs2.existsSync(filePath)) {
431
+ log(chalk3.gray(` Skipping ${exp.name}: file not found`));
432
+ return null;
433
+ }
434
+ const sourceFile = createSourceFile(filePath);
435
+ const location = findJSDocLocation(sourceFile, exp.name, exp.source.line);
436
+ if (!location) {
437
+ log(chalk3.gray(` Skipping ${exp.name}: could not find declaration`));
438
+ return null;
439
+ }
440
+ let existingPatch = {};
441
+ if (location.hasExisting && location.existingJSDoc) {
442
+ existingPatch = parseJSDocToPatch(location.existingJSDoc);
443
+ }
444
+ const expWithDrift = { ...exp, docs: { ...exp.docs, drift: drifts } };
445
+ const fixes = generateFixesForExport(expWithDrift, existingPatch);
446
+ if (fixes.length === 0)
447
+ return null;
448
+ const mergedPatch = mergeFixes(fixes, existingPatch);
449
+ const newJSDoc = serializeJSDoc(mergedPatch, location.indent);
450
+ const edit = {
451
+ filePath,
452
+ symbolName: exp.name,
453
+ startLine: location.startLine,
454
+ endLine: location.endLine,
455
+ hasExisting: location.hasExisting,
456
+ existingJSDoc: location.existingJSDoc,
457
+ newJSDoc,
458
+ indent: location.indent
459
+ };
460
+ return { filePath, edit, fixes, existingPatch };
461
+ }
462
+ function displayPreview(editsByFile, targetDir, log) {
463
+ log(chalk3.bold("Preview - changes that would be made:"));
464
+ log("");
465
+ for (const [filePath, fileEdits] of editsByFile) {
466
+ const relativePath = path3.relative(targetDir, filePath);
467
+ for (const { export: exp, edit, fixes } of fileEdits) {
468
+ log(chalk3.cyan(`${relativePath}:${edit.startLine + 1}`));
469
+ log(chalk3.bold(` ${exp.name}`));
470
+ log("");
471
+ if (edit.hasExisting && edit.existingJSDoc) {
472
+ const oldLines = edit.existingJSDoc.split(`
473
+ `);
474
+ const newLines = edit.newJSDoc.split(`
475
+ `);
476
+ for (const line of oldLines) {
477
+ log(chalk3.red(` - ${line}`));
478
+ }
479
+ for (const line of newLines) {
480
+ log(chalk3.green(` + ${line}`));
481
+ }
482
+ } else {
483
+ const newLines = edit.newJSDoc.split(`
484
+ `);
485
+ for (const line of newLines) {
486
+ log(chalk3.green(` + ${line}`));
487
+ }
488
+ }
489
+ log("");
490
+ log(chalk3.dim(` Fixes: ${fixes.map((f) => f.description).join(", ")}`));
491
+ log("");
492
+ }
493
+ }
494
+ const totalFixes = Array.from(editsByFile.values()).reduce((sum, fileEdits) => sum + fileEdits.reduce((s, e) => s + e.fixes.length, 0), 0);
495
+ log(chalk3.yellow(`${totalFixes} fix(es) across ${editsByFile.size} file(s) would be applied.`));
496
+ log(chalk3.gray("Run with --fix to apply these changes."));
497
+ }
498
+
499
+ // src/commands/check/output.ts
500
+ import { generateReport } from "@doccov/sdk";
501
+ import chalk5 from "chalk";
180
502
 
181
503
  // src/reports/changelog-renderer.ts
182
504
  function renderChangelog(data, options = {}) {
@@ -246,7 +568,7 @@ function renderChangelog(data, options = {}) {
246
568
  `);
247
569
  }
248
570
  // src/reports/diff-markdown.ts
249
- import * as path2 from "node:path";
571
+ import * as path4 from "node:path";
250
572
  function bar(pct, width = 10) {
251
573
  const filled = Math.round(pct / 100 * width);
252
574
  return "█".repeat(filled) + "░".repeat(width - filled);
@@ -397,7 +719,7 @@ function renderDocsImpactSection(lines, docsImpact, limit) {
397
719
  lines.push("| File | Issues | Details |");
398
720
  lines.push("|------|--------|---------|");
399
721
  for (const file of impactedFiles.slice(0, limit)) {
400
- const filename = path2.basename(file.file);
722
+ const filename = path4.basename(file.file);
401
723
  const issueCount = file.references.length;
402
724
  const firstRef = file.references[0];
403
725
  const detail = firstRef ? `L${firstRef.line}: ${firstRef.memberName ?? firstRef.exportName}` : "-";
@@ -613,6 +935,22 @@ function renderHtml(stats, options = {}) {
613
935
  </html>`;
614
936
  }
615
937
  // src/reports/pr-comment.ts
938
+ function extractReturnType(schema) {
939
+ if (!schema)
940
+ return "void";
941
+ if (typeof schema === "string")
942
+ return schema;
943
+ if (typeof schema === "object") {
944
+ const s = schema;
945
+ if (typeof s.type === "string")
946
+ return s.type;
947
+ if (typeof s.$ref === "string") {
948
+ const ref = s.$ref;
949
+ return ref.startsWith("#/types/") ? ref.slice("#/types/".length) : ref;
950
+ }
951
+ }
952
+ return "unknown";
953
+ }
616
954
  function renderPRComment(data, opts = {}) {
617
955
  const { diff, headSpec } = data;
618
956
  const limit = opts.limit ?? 10;
@@ -768,12 +1106,24 @@ function buildFileLink(file, opts) {
768
1106
  }
769
1107
  return `\`${file}\``;
770
1108
  }
1109
+ function getExportKeyword(kind) {
1110
+ switch (kind) {
1111
+ case "type":
1112
+ return "type";
1113
+ case "interface":
1114
+ return "interface";
1115
+ case "class":
1116
+ return "class";
1117
+ default:
1118
+ return "function";
1119
+ }
1120
+ }
771
1121
  function formatExportSignature(exp) {
772
- const prefix = `export ${exp.kind === "type" ? "type" : exp.kind === "interface" ? "interface" : exp.kind === "class" ? "class" : "function"}`;
1122
+ const prefix = `export ${getExportKeyword(exp.kind)}`;
773
1123
  if (exp.kind === "function" && exp.signatures?.[0]) {
774
1124
  const sig = exp.signatures[0];
775
1125
  const params = sig.parameters?.map((p) => `${p.name}${p.required === false ? "?" : ""}`).join(", ") ?? "";
776
- const ret = sig.returns?.tsType ?? "void";
1126
+ const ret = extractReturnType(sig.returns?.schema);
777
1127
  return `${prefix} ${exp.name}(${params}): ${ret}`;
778
1128
  }
779
1129
  if (exp.kind === "type" || exp.kind === "interface") {
@@ -795,7 +1145,7 @@ function getMissingSignals(exp) {
795
1145
  if (undocParams.length > 0) {
796
1146
  missing.push(`\`@param ${undocParams.map((p) => p.name).join(", ")}\``);
797
1147
  }
798
- if (!sig.returns?.description && sig.returns?.tsType !== "void") {
1148
+ if (!sig.returns?.description && extractReturnType(sig.returns?.schema) !== "void") {
799
1149
  missing.push("`@returns`");
800
1150
  }
801
1151
  }
@@ -837,7 +1187,7 @@ function renderFixGuidance(diff, opts) {
837
1187
 
838
1188
  **Quick fix:** Run \`npx doccov check --fix\` to auto-fix ${opts.fixableDriftCount} issue(s).` : "";
839
1189
  sections.push(`**For doc drift:**
840
- ` + "Update JSDoc to match current code signatures." + fixableNote);
1190
+ Update JSDoc to match current code signatures.${fixableNote}`);
841
1191
  }
842
1192
  if (opts.staleDocsRefs && opts.staleDocsRefs.length > 0) {
843
1193
  sections.push(`**For stale docs:**
@@ -872,7 +1222,7 @@ function renderDetailsTable(lines, diff) {
872
1222
  // src/reports/stats.ts
873
1223
  import { isFixableDrift } from "@doccov/sdk";
874
1224
  import {
875
- DRIFT_CATEGORIES
1225
+ DRIFT_CATEGORIES as DRIFT_CATEGORIES2
876
1226
  } from "@openpkg-ts/spec";
877
1227
  function computeStats(spec) {
878
1228
  const exports = spec.exports ?? [];
@@ -918,7 +1268,7 @@ function computeStats(spec) {
918
1268
  suggestion: d.suggestion
919
1269
  };
920
1270
  driftIssues.push(item);
921
- const category = DRIFT_CATEGORIES[d.type] ?? "semantic";
1271
+ const category = DRIFT_CATEGORIES2[d.type] ?? "semantic";
922
1272
  driftByCategory[category].push(item);
923
1273
  }
924
1274
  }
@@ -967,21 +1317,21 @@ function computeStats(spec) {
967
1317
  };
968
1318
  }
969
1319
  // src/reports/writer.ts
970
- import * as fs from "node:fs";
971
- import * as path3 from "node:path";
1320
+ import * as fs3 from "node:fs";
1321
+ import * as path5 from "node:path";
972
1322
  import { DEFAULT_REPORT_DIR, getReportPath } from "@doccov/sdk";
973
- import chalk from "chalk";
1323
+ import chalk4 from "chalk";
974
1324
  function writeReport(options) {
975
1325
  const { format, content, outputPath, cwd = process.cwd(), silent = false } = options;
976
- const reportPath = outputPath ? path3.resolve(cwd, outputPath) : path3.resolve(cwd, getReportPath(format));
977
- const dir = path3.dirname(reportPath);
978
- if (!fs.existsSync(dir)) {
979
- fs.mkdirSync(dir, { recursive: true });
1326
+ const reportPath = outputPath ? path5.resolve(cwd, outputPath) : path5.resolve(cwd, getReportPath(format));
1327
+ const dir = path5.dirname(reportPath);
1328
+ if (!fs3.existsSync(dir)) {
1329
+ fs3.mkdirSync(dir, { recursive: true });
980
1330
  }
981
- fs.writeFileSync(reportPath, content);
982
- const relativePath = path3.relative(cwd, reportPath);
1331
+ fs3.writeFileSync(reportPath, content);
1332
+ const relativePath = path5.relative(cwd, reportPath);
983
1333
  if (!silent) {
984
- console.log(chalk.green(`✓ Wrote ${format} report to ${relativePath}`));
1334
+ console.log(chalk4.green(`✓ Wrote ${format} report to ${relativePath}`));
985
1335
  }
986
1336
  return { path: reportPath, format, relativePath };
987
1337
  }
@@ -1004,149 +1354,257 @@ function writeReports(options) {
1004
1354
  }));
1005
1355
  return results;
1006
1356
  }
1007
- // src/utils/filter-options.ts
1008
- import { mergeFilters, parseListFlag } from "@doccov/sdk";
1009
- import chalk2 from "chalk";
1010
- var parseVisibilityFlag = (value) => {
1011
- if (!value)
1012
- return;
1013
- const validTags = ["public", "beta", "alpha", "internal"];
1014
- const parsed = parseListFlag(value);
1015
- if (!parsed)
1016
- return;
1017
- const result = [];
1018
- for (const tag of parsed) {
1019
- const lower = tag.toLowerCase();
1020
- if (validTags.includes(lower)) {
1021
- result.push(lower);
1357
+ // src/commands/check/output.ts
1358
+ function displayTextOutput(options, deps) {
1359
+ const {
1360
+ spec,
1361
+ coverageScore,
1362
+ minCoverage,
1363
+ maxDrift,
1364
+ driftExports,
1365
+ typecheckErrors,
1366
+ staleRefs,
1367
+ exampleResult,
1368
+ specWarnings,
1369
+ specInfos
1370
+ } = options;
1371
+ const { log } = deps;
1372
+ const totalExportsForDrift = spec.exports?.length ?? 0;
1373
+ const exportsWithDrift = new Set(driftExports.map((d) => d.name)).size;
1374
+ const driftScore = totalExportsForDrift === 0 ? 0 : Math.round(exportsWithDrift / totalExportsForDrift * 100);
1375
+ const coverageFailed = coverageScore < minCoverage;
1376
+ const driftFailed = maxDrift !== undefined && driftScore > maxDrift;
1377
+ const hasTypecheckErrors = typecheckErrors.length > 0;
1378
+ if (specWarnings.length > 0 || specInfos.length > 0) {
1379
+ log("");
1380
+ for (const diag of specWarnings) {
1381
+ log(chalk5.yellow(`⚠ ${diag.message}`));
1382
+ if (diag.suggestion) {
1383
+ log(chalk5.gray(` ${diag.suggestion}`));
1384
+ }
1385
+ }
1386
+ for (const diag of specInfos) {
1387
+ log(chalk5.cyan(`ℹ ${diag.message}`));
1388
+ if (diag.suggestion) {
1389
+ log(chalk5.gray(` ${diag.suggestion}`));
1390
+ }
1022
1391
  }
1023
1392
  }
1024
- return result.length > 0 ? result : undefined;
1025
- };
1026
- var formatList = (label, values) => `${label}: ${values.map((value) => chalk2.cyan(value)).join(", ")}`;
1027
- var mergeFilterOptions = (config, cliOptions) => {
1028
- const messages = [];
1029
- if (config?.include) {
1030
- messages.push(formatList("include filters from config", config.include));
1393
+ const pkgName = spec.meta?.name ?? "unknown";
1394
+ const pkgVersion = spec.meta?.version ?? "";
1395
+ const totalExports = spec.exports?.length ?? 0;
1396
+ log("");
1397
+ log(chalk5.bold(`${pkgName}${pkgVersion ? `@${pkgVersion}` : ""}`));
1398
+ log("");
1399
+ log(` Exports: ${totalExports}`);
1400
+ if (coverageFailed) {
1401
+ log(chalk5.red(` Coverage: ✗ ${coverageScore}%`) + chalk5.dim(` (min ${minCoverage}%)`));
1402
+ } else {
1403
+ log(chalk5.green(` Coverage: ✓ ${coverageScore}%`) + chalk5.dim(` (min ${minCoverage}%)`));
1031
1404
  }
1032
- if (config?.exclude) {
1033
- messages.push(formatList("exclude filters from config", config.exclude));
1405
+ if (maxDrift !== undefined) {
1406
+ if (driftFailed) {
1407
+ log(chalk5.red(` Drift: ✗ ${driftScore}%`) + chalk5.dim(` (max ${maxDrift}%)`));
1408
+ } else {
1409
+ log(chalk5.green(` Drift: ✓ ${driftScore}%`) + chalk5.dim(` (max ${maxDrift}%)`));
1410
+ }
1411
+ } else {
1412
+ log(` Drift: ${driftScore}%`);
1413
+ }
1414
+ if (exampleResult) {
1415
+ const typecheckCount = exampleResult.typecheck?.errors.length ?? 0;
1416
+ if (typecheckCount > 0) {
1417
+ log(chalk5.yellow(` Examples: ${typecheckCount} type error(s)`));
1418
+ for (const err of typecheckErrors.slice(0, 5)) {
1419
+ const loc = `example[${err.error.exampleIndex}]:${err.error.line}:${err.error.column}`;
1420
+ log(chalk5.dim(` ${err.exportName} ${loc}`));
1421
+ log(chalk5.red(` ${err.error.message}`));
1422
+ }
1423
+ if (typecheckErrors.length > 5) {
1424
+ log(chalk5.dim(` ... and ${typecheckErrors.length - 5} more`));
1425
+ }
1426
+ } else {
1427
+ log(chalk5.green(` Examples: ✓ validated`));
1428
+ }
1034
1429
  }
1035
- if (cliOptions.include) {
1036
- messages.push(formatList("apply include filters from CLI", cliOptions.include));
1430
+ const hasStaleRefs = staleRefs.length > 0;
1431
+ if (hasStaleRefs) {
1432
+ log(chalk5.yellow(` Docs: ${staleRefs.length} stale ref(s)`));
1433
+ for (const ref of staleRefs.slice(0, 5)) {
1434
+ log(chalk5.dim(` ${ref.file}:${ref.line} - "${ref.exportName}"`));
1435
+ }
1436
+ if (staleRefs.length > 5) {
1437
+ log(chalk5.dim(` ... and ${staleRefs.length - 5} more`));
1438
+ }
1037
1439
  }
1038
- if (cliOptions.exclude) {
1039
- messages.push(formatList("apply exclude filters from CLI", cliOptions.exclude));
1440
+ log("");
1441
+ const failed = coverageFailed || driftFailed || hasTypecheckErrors || hasStaleRefs;
1442
+ if (!failed) {
1443
+ const thresholdParts = [];
1444
+ thresholdParts.push(`coverage ${coverageScore}% ≥ ${minCoverage}%`);
1445
+ if (maxDrift !== undefined) {
1446
+ thresholdParts.push(`drift ${driftScore}% ≤ ${maxDrift}%`);
1447
+ }
1448
+ log(chalk5.green(`✓ Check passed (${thresholdParts.join(", ")})`));
1449
+ return true;
1040
1450
  }
1041
- if (cliOptions.visibility) {
1042
- messages.push(formatList("apply visibility filter from CLI", cliOptions.visibility));
1451
+ if (hasTypecheckErrors) {
1452
+ log(chalk5.red(`✗ ${typecheckErrors.length} example type errors`));
1043
1453
  }
1044
- const resolved = mergeFilters(config, cliOptions);
1045
- if (!resolved.include && !resolved.exclude && !cliOptions.visibility) {
1046
- return { messages };
1454
+ if (hasStaleRefs) {
1455
+ log(chalk5.red(`✗ ${staleRefs.length} stale references in docs`));
1047
1456
  }
1048
- const source = resolved.source === "override" ? "cli" : resolved.source;
1049
- return {
1050
- include: resolved.include,
1051
- exclude: resolved.exclude,
1052
- visibility: cliOptions.visibility,
1053
- source,
1054
- messages
1055
- };
1056
- };
1057
-
1058
- // src/utils/progress.ts
1059
- import chalk3 from "chalk";
1060
- class StepProgress {
1061
- steps;
1062
- currentStep = 0;
1063
- startTime;
1064
- stepStartTime;
1065
- constructor(steps) {
1066
- this.steps = steps;
1067
- this.startTime = Date.now();
1068
- this.stepStartTime = Date.now();
1457
+ log("");
1458
+ log(chalk5.dim("Use --format json or --format markdown for detailed reports"));
1459
+ return false;
1460
+ }
1461
+ function handleNonTextOutput(options, deps) {
1462
+ const {
1463
+ format,
1464
+ spec,
1465
+ rawSpec,
1466
+ coverageScore,
1467
+ minCoverage,
1468
+ maxDrift,
1469
+ driftExports,
1470
+ typecheckErrors,
1471
+ limit,
1472
+ stdout,
1473
+ outputPath,
1474
+ cwd
1475
+ } = options;
1476
+ const { log } = deps;
1477
+ const stats = computeStats(spec);
1478
+ const report = generateReport(rawSpec);
1479
+ const jsonContent = JSON.stringify(report, null, 2);
1480
+ let formatContent;
1481
+ switch (format) {
1482
+ case "json":
1483
+ formatContent = jsonContent;
1484
+ break;
1485
+ case "markdown":
1486
+ formatContent = renderMarkdown(stats, { limit });
1487
+ break;
1488
+ case "html":
1489
+ formatContent = renderHtml(stats, { limit });
1490
+ break;
1491
+ case "github":
1492
+ formatContent = renderGithubSummary(stats, {
1493
+ coverageScore,
1494
+ driftCount: driftExports.length
1495
+ });
1496
+ break;
1497
+ default:
1498
+ throw new Error(`Unknown format: ${format}`);
1069
1499
  }
1070
- start(stepIndex) {
1071
- this.currentStep = stepIndex ?? 0;
1072
- this.stepStartTime = Date.now();
1073
- this.render();
1500
+ if (stdout) {
1501
+ log(formatContent);
1502
+ } else {
1503
+ writeReports({
1504
+ format,
1505
+ formatContent,
1506
+ jsonContent,
1507
+ outputPath,
1508
+ cwd
1509
+ });
1074
1510
  }
1075
- next() {
1076
- this.completeCurrentStep();
1077
- this.currentStep++;
1078
- if (this.currentStep < this.steps.length) {
1079
- this.stepStartTime = Date.now();
1080
- this.render();
1511
+ const totalExportsForDrift = spec.exports?.length ?? 0;
1512
+ const exportsWithDrift = new Set(driftExports.map((d) => d.name)).size;
1513
+ const driftScore = totalExportsForDrift === 0 ? 0 : Math.round(exportsWithDrift / totalExportsForDrift * 100);
1514
+ const coverageFailed = coverageScore < minCoverage;
1515
+ const driftFailed = maxDrift !== undefined && driftScore > maxDrift;
1516
+ const hasTypecheckErrors = typecheckErrors.length > 0;
1517
+ return !(coverageFailed || driftFailed || hasTypecheckErrors);
1518
+ }
1519
+
1520
+ // src/commands/check/validation.ts
1521
+ import { validateExamples } from "@doccov/sdk";
1522
+ async function runExampleValidation(spec, options) {
1523
+ const { validations, targetDir, timeout = 5000, installTimeout = 60000 } = options;
1524
+ const typecheckErrors = [];
1525
+ const runtimeDrifts = [];
1526
+ const result = await validateExamples(spec.exports ?? [], {
1527
+ validations,
1528
+ packagePath: targetDir,
1529
+ exportNames: (spec.exports ?? []).map((e) => e.name),
1530
+ timeout,
1531
+ installTimeout
1532
+ });
1533
+ if (result.typecheck) {
1534
+ for (const err of result.typecheck.errors) {
1535
+ typecheckErrors.push({
1536
+ exportName: err.exportName,
1537
+ error: err.error
1538
+ });
1081
1539
  }
1082
1540
  }
1083
- complete(message) {
1084
- this.completeCurrentStep();
1085
- if (message) {
1086
- const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);
1087
- console.log(`${chalk3.green("")} ${message} ${chalk3.dim(`(${elapsed}s)`)}`);
1541
+ if (result.run) {
1542
+ for (const drift of result.run.drifts) {
1543
+ runtimeDrifts.push({
1544
+ name: drift.exportName,
1545
+ type: "example-runtime-error",
1546
+ issue: drift.issue,
1547
+ suggestion: drift.suggestion,
1548
+ category: "example"
1549
+ });
1088
1550
  }
1089
1551
  }
1090
- render() {
1091
- const step = this.steps[this.currentStep];
1092
- if (!step)
1093
- return;
1094
- const label = step.activeLabel ?? step.label;
1095
- const prefix = chalk3.dim(`[${this.currentStep + 1}/${this.steps.length}]`);
1096
- process.stdout.write(`\r${prefix} ${chalk3.cyan(label)}...`);
1097
- }
1098
- completeCurrentStep() {
1099
- const step = this.steps[this.currentStep];
1100
- if (!step)
1101
- return;
1102
- const elapsed = ((Date.now() - this.stepStartTime) / 1000).toFixed(1);
1103
- const prefix = chalk3.dim(`[${this.currentStep + 1}/${this.steps.length}]`);
1104
- process.stdout.write(`\r${" ".repeat(80)}\r`);
1105
- console.log(`${prefix} ${step.label} ${chalk3.green("✓")} ${chalk3.dim(`(${elapsed}s)`)}`);
1106
- }
1552
+ return { result, typecheckErrors, runtimeDrifts };
1107
1553
  }
1108
-
1109
- // src/utils/validation.ts
1110
- function clampPercentage(value, fallback = 80) {
1111
- if (Number.isNaN(value))
1112
- return fallback;
1113
- return Math.min(100, Math.max(0, Math.round(value)));
1114
- }
1115
- function resolveThreshold(cliValue, configValue) {
1116
- const raw = cliValue ?? configValue;
1117
- return raw !== undefined ? clampPercentage(Number(raw)) : undefined;
1554
+ async function validateMarkdownDocs(options) {
1555
+ const { docsPatterns, targetDir, exportNames } = options;
1556
+ const staleRefs = [];
1557
+ if (docsPatterns.length === 0) {
1558
+ return staleRefs;
1559
+ }
1560
+ const markdownFiles = await loadMarkdownFiles(docsPatterns, targetDir);
1561
+ if (markdownFiles.length === 0) {
1562
+ return staleRefs;
1563
+ }
1564
+ const exportSet = new Set(exportNames);
1565
+ for (const mdFile of markdownFiles) {
1566
+ for (const block of mdFile.codeBlocks) {
1567
+ const codeLines = block.code.split(`
1568
+ `);
1569
+ for (let i = 0;i < codeLines.length; i++) {
1570
+ const line = codeLines[i];
1571
+ const importMatch = line.match(/import\s*\{([^}]+)\}\s*from\s*['"][^'"]*['"]/);
1572
+ if (importMatch) {
1573
+ const imports = importMatch[1].split(",").map((s) => s.trim().split(/\s+/)[0]);
1574
+ for (const imp of imports) {
1575
+ if (imp && !exportSet.has(imp)) {
1576
+ staleRefs.push({
1577
+ file: mdFile.path,
1578
+ line: block.lineStart + i,
1579
+ exportName: imp,
1580
+ context: line.trim()
1581
+ });
1582
+ }
1583
+ }
1584
+ }
1585
+ }
1586
+ }
1587
+ }
1588
+ return staleRefs;
1118
1589
  }
1119
1590
 
1120
- // src/commands/check.ts
1591
+ // src/commands/check/index.ts
1121
1592
  var defaultDependencies = {
1122
1593
  createDocCov: (options) => new DocCov(options),
1123
1594
  log: console.log,
1124
1595
  error: console.error
1125
1596
  };
1126
- function collectDriftsFromExports(exports) {
1127
- const results = [];
1128
- for (const exp of exports) {
1129
- for (const drift of exp.docs?.drift ?? []) {
1130
- results.push({ export: exp, drift });
1131
- }
1132
- }
1133
- return results;
1134
- }
1135
- function groupByExport(drifts) {
1136
- const map = new Map;
1137
- for (const { export: exp, drift } of drifts) {
1138
- const existing = map.get(exp) ?? [];
1139
- existing.push(drift);
1140
- map.set(exp, existing);
1141
- }
1142
- return map;
1143
- }
1144
1597
  function registerCheckCommand(program, dependencies = {}) {
1145
1598
  const { createDocCov, log, error } = {
1146
1599
  ...defaultDependencies,
1147
1600
  ...dependencies
1148
1601
  };
1149
- program.command("check [entry]").description("Check documentation coverage and output reports").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--min-coverage <percentage>", "Minimum docs coverage percentage (0-100)", (value) => Number(value)).option("--max-drift <percentage>", "Maximum drift percentage allowed (0-100)", (value) => Number(value)).option("--examples [mode]", "Example validation: presence, typecheck, run (comma-separated). Bare flag runs all.").option("--skip-resolve", "Skip external type resolution from node_modules").option("--docs <glob>", "Glob pattern for markdown docs to check for stale refs", collect, []).option("--fix", "Auto-fix drift issues").option("--write", "Alias for --fix").option("--preview", "Preview fixes with diff output (implies --fix)").option("--dry-run", "Alias for --preview").option("--format <format>", "Output format: text, json, markdown, html, github", "text").option("-o, --output <file>", "Custom output path (overrides default .doccov/ path)").option("--stdout", "Output to stdout instead of writing to .doccov/").option("--update-snapshot", "Force regenerate .doccov/report.json").option("--limit <n>", "Max exports to show in report tables", "20").option("--max-type-depth <number>", "Maximum depth for type conversion (default: 20)").option("--no-cache", "Bypass spec cache and force regeneration").option("--visibility <tags>", "Filter by release stage: public,beta,alpha,internal (comma-separated)").action(async (entry, options) => {
1602
+ program.command("check [entry]").description("Check documentation coverage and output reports").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--min-coverage <percentage>", "Minimum docs coverage percentage (0-100)", (value) => Number(value)).option("--max-drift <percentage>", "Maximum drift percentage allowed (0-100)", (value) => Number(value)).option("--examples [mode]", "Example validation: presence, typecheck, run (comma-separated). Bare flag runs all.").option("--skip-resolve", "Skip external type resolution from node_modules").option("--docs <glob>", "Glob pattern for markdown docs to check for stale refs", collect, []).option("--fix", "Auto-fix drift issues").option("--preview", "Preview fixes with diff output (implies --fix)").option("--format <format>", "Output format: text, json, markdown, html, github", "text").option("-o, --output <file>", "Custom output path (overrides default .doccov/ path)").option("--stdout", "Output to stdout instead of writing to .doccov/").option("--update-snapshot", "Force regenerate .doccov/report.json").option("--limit <n>", "Max exports to show in report tables", "20").option("--max-type-depth <number>", "Maximum depth for type conversion (default: 20)", (value) => {
1603
+ const n = parseInt(value, 10);
1604
+ if (Number.isNaN(n) || n < 1)
1605
+ throw new Error("--max-type-depth must be a positive integer");
1606
+ return n;
1607
+ }).option("--no-cache", "Bypass spec cache and force regeneration").option("--visibility <tags>", "Filter by release stage: public,beta,alpha,internal (comma-separated)").action(async (entry, options) => {
1150
1608
  try {
1151
1609
  let validations = parseExamplesFlag(options.examples);
1152
1610
  let hasExamples = validations.length > 0;
@@ -1190,19 +1648,18 @@ function registerCheckCommand(program, dependencies = {}) {
1190
1648
  };
1191
1649
  const resolvedFilters = mergeFilterOptions(config, cliFilters);
1192
1650
  if (resolvedFilters.visibility) {
1193
- log(chalk4.dim(`Filtering by visibility: ${resolvedFilters.visibility.join(", ")}`));
1651
+ log(chalk6.dim(`Filtering by visibility: ${resolvedFilters.visibility.join(", ")}`));
1194
1652
  }
1195
1653
  steps.next();
1196
1654
  const resolveExternalTypes = !options.skipResolve;
1197
- let specResult;
1198
1655
  const doccov = createDocCov({
1199
1656
  resolveExternalTypes,
1200
- maxDepth: options.maxTypeDepth ? parseInt(options.maxTypeDepth, 10) : undefined,
1657
+ maxDepth: options.maxTypeDepth,
1201
1658
  useCache: options.cache !== false,
1202
1659
  cwd: options.cwd
1203
1660
  });
1204
1661
  const analyzeOptions = resolvedFilters.visibility ? { filters: { visibility: resolvedFilters.visibility } } : {};
1205
- specResult = await doccov.analyzeFileWithDiagnostics(entryFile, analyzeOptions);
1662
+ const specResult = await doccov.analyzeFileWithDiagnostics(entryFile, analyzeOptions);
1206
1663
  if (!specResult) {
1207
1664
  throw new Error("Failed to analyze documentation coverage.");
1208
1665
  }
@@ -1212,384 +1669,84 @@ function registerCheckCommand(program, dependencies = {}) {
1212
1669
  steps.next();
1213
1670
  const specWarnings = specResult.diagnostics.filter((d) => d.severity === "warning");
1214
1671
  const specInfos = specResult.diagnostics.filter((d) => d.severity === "info");
1215
- const isPreview = options.preview || options.dryRun;
1216
- const shouldFix = options.fix || options.write || isPreview;
1672
+ const isPreview = options.preview;
1673
+ const shouldFix = options.fix || isPreview;
1217
1674
  let exampleResult;
1218
- const typecheckErrors = [];
1219
- const runtimeDrifts = [];
1675
+ let typecheckErrors = [];
1676
+ let runtimeDrifts = [];
1220
1677
  if (hasExamples) {
1221
- exampleResult = await validateExamples(spec.exports ?? [], {
1678
+ const validation = await runExampleValidation(spec, {
1222
1679
  validations,
1223
- packagePath: targetDir,
1224
- exportNames: (spec.exports ?? []).map((e) => e.name),
1225
- timeout: 5000,
1226
- installTimeout: 60000
1680
+ targetDir
1227
1681
  });
1228
- if (exampleResult.typecheck) {
1229
- for (const err of exampleResult.typecheck.errors) {
1230
- typecheckErrors.push({
1231
- exportName: err.exportName,
1232
- error: err.error
1233
- });
1234
- }
1235
- }
1236
- if (exampleResult.run) {
1237
- for (const drift of exampleResult.run.drifts) {
1238
- runtimeDrifts.push({
1239
- name: drift.exportName,
1240
- type: "example-runtime-error",
1241
- issue: drift.issue,
1242
- suggestion: drift.suggestion,
1243
- category: "example"
1244
- });
1245
- }
1246
- }
1682
+ exampleResult = validation.result;
1683
+ typecheckErrors = validation.typecheckErrors;
1684
+ runtimeDrifts = validation.runtimeDrifts;
1247
1685
  steps.next();
1248
1686
  }
1249
- const staleRefs = [];
1250
1687
  let docsPatterns = options.docs;
1251
1688
  if (docsPatterns.length === 0 && config?.docs?.include) {
1252
1689
  docsPatterns = config.docs.include;
1253
1690
  }
1254
- if (docsPatterns.length > 0) {
1255
- const markdownFiles = await loadMarkdownFiles(docsPatterns, targetDir);
1256
- if (markdownFiles.length > 0) {
1257
- const exportNames = (spec.exports ?? []).map((e) => e.name);
1258
- const exportSet = new Set(exportNames);
1259
- for (const mdFile of markdownFiles) {
1260
- for (const block of mdFile.codeBlocks) {
1261
- const codeLines = block.code.split(`
1262
- `);
1263
- for (let i = 0;i < codeLines.length; i++) {
1264
- const line = codeLines[i];
1265
- const importMatch = line.match(/import\s*\{([^}]+)\}\s*from\s*['"][^'"]*['"]/);
1266
- if (importMatch) {
1267
- const imports = importMatch[1].split(",").map((s) => s.trim().split(/\s+/)[0]);
1268
- for (const imp of imports) {
1269
- if (imp && !exportSet.has(imp)) {
1270
- staleRefs.push({
1271
- file: mdFile.path,
1272
- line: block.lineStart + i,
1273
- exportName: imp,
1274
- context: line.trim()
1275
- });
1276
- }
1277
- }
1278
- }
1279
- }
1280
- }
1281
- }
1282
- }
1283
- }
1691
+ const staleRefs = await validateMarkdownDocs({
1692
+ docsPatterns,
1693
+ targetDir,
1694
+ exportNames: (spec.exports ?? []).map((e) => e.name)
1695
+ });
1284
1696
  const coverageScore = spec.docs?.coverageScore ?? 0;
1285
1697
  const allDriftExports = [...collectDrift(spec.exports ?? []), ...runtimeDrifts];
1286
1698
  let driftExports = hasExamples ? allDriftExports : allDriftExports.filter((d) => d.category !== "example");
1287
- const fixedDriftKeys = new Set;
1288
1699
  if (shouldFix && driftExports.length > 0) {
1289
- const allDrifts = collectDriftsFromExports(spec.exports ?? []);
1290
- if (allDrifts.length > 0) {
1291
- const { fixable, nonFixable } = categorizeDrifts(allDrifts.map((d) => d.drift));
1292
- if (fixable.length === 0) {
1293
- log(chalk4.yellow(`Found ${nonFixable.length} drift issue(s), but none are auto-fixable.`));
1294
- } else {
1295
- log("");
1296
- log(chalk4.bold(`Found ${fixable.length} fixable issue(s)`));
1297
- if (nonFixable.length > 0) {
1298
- log(chalk4.gray(`(${nonFixable.length} non-fixable issue(s) skipped)`));
1299
- }
1300
- log("");
1301
- const groupedDrifts = groupByExport(allDrifts.filter((d) => fixable.includes(d.drift)));
1302
- const edits = [];
1303
- const editsByFile = new Map;
1304
- for (const [exp, drifts] of groupedDrifts) {
1305
- if (!exp.source?.file) {
1306
- log(chalk4.gray(` Skipping ${exp.name}: no source location`));
1307
- continue;
1308
- }
1309
- if (exp.source.file.endsWith(".d.ts")) {
1310
- log(chalk4.gray(` Skipping ${exp.name}: declaration file`));
1311
- continue;
1312
- }
1313
- const filePath = path4.resolve(targetDir, exp.source.file);
1314
- if (!fs2.existsSync(filePath)) {
1315
- log(chalk4.gray(` Skipping ${exp.name}: file not found`));
1316
- continue;
1317
- }
1318
- const sourceFile = createSourceFile(filePath);
1319
- const location = findJSDocLocation(sourceFile, exp.name, exp.source.line);
1320
- if (!location) {
1321
- log(chalk4.gray(` Skipping ${exp.name}: could not find declaration`));
1322
- continue;
1323
- }
1324
- let existingPatch = {};
1325
- if (location.hasExisting && location.existingJSDoc) {
1326
- existingPatch = parseJSDocToPatch(location.existingJSDoc);
1327
- }
1328
- const expWithDrift = { ...exp, docs: { ...exp.docs, drift: drifts } };
1329
- const fixes = generateFixesForExport(expWithDrift, existingPatch);
1330
- if (fixes.length === 0)
1331
- continue;
1332
- for (const drift of drifts) {
1333
- fixedDriftKeys.add(`${exp.name}:${drift.issue}`);
1334
- }
1335
- const mergedPatch = mergeFixes(fixes, existingPatch);
1336
- const newJSDoc = serializeJSDoc(mergedPatch, location.indent);
1337
- const edit = {
1338
- filePath,
1339
- symbolName: exp.name,
1340
- startLine: location.startLine,
1341
- endLine: location.endLine,
1342
- hasExisting: location.hasExisting,
1343
- existingJSDoc: location.existingJSDoc,
1344
- newJSDoc,
1345
- indent: location.indent
1346
- };
1347
- edits.push(edit);
1348
- const fileEdits = editsByFile.get(filePath) ?? [];
1349
- fileEdits.push({ export: exp, edit, fixes, existingPatch });
1350
- editsByFile.set(filePath, fileEdits);
1351
- }
1352
- if (edits.length > 0) {
1353
- if (isPreview) {
1354
- log(chalk4.bold("Preview - changes that would be made:"));
1355
- log("");
1356
- for (const [filePath, fileEdits] of editsByFile) {
1357
- const relativePath = path4.relative(targetDir, filePath);
1358
- for (const { export: exp, edit, fixes } of fileEdits) {
1359
- log(chalk4.cyan(`${relativePath}:${edit.startLine + 1}`));
1360
- log(chalk4.bold(` ${exp.name}`));
1361
- log("");
1362
- if (edit.hasExisting && edit.existingJSDoc) {
1363
- const oldLines = edit.existingJSDoc.split(`
1364
- `);
1365
- const newLines = edit.newJSDoc.split(`
1366
- `);
1367
- for (const line of oldLines) {
1368
- log(chalk4.red(` - ${line}`));
1369
- }
1370
- for (const line of newLines) {
1371
- log(chalk4.green(` + ${line}`));
1372
- }
1373
- } else {
1374
- const newLines = edit.newJSDoc.split(`
1375
- `);
1376
- for (const line of newLines) {
1377
- log(chalk4.green(` + ${line}`));
1378
- }
1379
- }
1380
- log("");
1381
- log(chalk4.dim(` Fixes: ${fixes.map((f) => f.description).join(", ")}`));
1382
- log("");
1383
- }
1384
- }
1385
- const totalFixes = Array.from(editsByFile.values()).reduce((sum, edits2) => sum + edits2.reduce((s, e) => s + e.fixes.length, 0), 0);
1386
- log(chalk4.yellow(`${totalFixes} fix(es) across ${editsByFile.size} file(s) would be applied.`));
1387
- log(chalk4.gray("Run with --fix to apply these changes."));
1388
- } else {
1389
- const applyResult = await applyEdits(edits);
1390
- if (applyResult.errors.length > 0) {
1391
- for (const err of applyResult.errors) {
1392
- error(chalk4.red(` ${err.file}: ${err.error}`));
1393
- }
1394
- }
1395
- const totalFixes = Array.from(editsByFile.values()).reduce((sum, edits2) => sum + edits2.reduce((s, e) => s + e.fixes.length, 0), 0);
1396
- log("");
1397
- log(chalk4.green(`✓ Applied ${totalFixes} fix(es) to ${applyResult.filesModified} file(s)`));
1398
- for (const [filePath, fileEdits] of editsByFile) {
1399
- const relativePath = path4.relative(targetDir, filePath);
1400
- const fixCount = fileEdits.reduce((s, e) => s + e.fixes.length, 0);
1401
- log(chalk4.dim(` ${relativePath} (${fixCount} fixes)`));
1402
- }
1403
- }
1404
- }
1405
- }
1406
- }
1700
+ const fixResult = await handleFixes(spec, { isPreview, targetDir }, { log, error });
1407
1701
  if (!isPreview) {
1408
- driftExports = driftExports.filter((d) => !fixedDriftKeys.has(`${d.name}:${d.issue}`));
1702
+ driftExports = driftExports.filter((d) => !fixResult.fixedDriftKeys.has(`${d.name}:${d.issue}`));
1409
1703
  }
1410
1704
  }
1411
1705
  steps.complete("Check complete");
1412
1706
  if (format !== "text") {
1413
- const limit = parseInt(options.limit, 10) || 20;
1414
- const stats = computeStats(spec);
1415
- const report = generateReport(specResult.spec);
1416
- const jsonContent = JSON.stringify(report, null, 2);
1417
- let formatContent;
1418
- switch (format) {
1419
- case "json":
1420
- formatContent = jsonContent;
1421
- break;
1422
- case "markdown":
1423
- formatContent = renderMarkdown(stats, { limit });
1424
- break;
1425
- case "html":
1426
- formatContent = renderHtml(stats, { limit });
1427
- break;
1428
- case "github":
1429
- formatContent = renderGithubSummary(stats, {
1430
- coverageScore,
1431
- driftCount: driftExports.length
1432
- });
1433
- break;
1434
- default:
1435
- throw new Error(`Unknown format: ${format}`);
1436
- }
1437
- if (options.stdout) {
1438
- log(formatContent);
1439
- } else {
1440
- writeReports({
1441
- format,
1442
- formatContent,
1443
- jsonContent,
1444
- outputPath: options.output,
1445
- cwd: options.cwd
1446
- });
1447
- }
1448
- const totalExportsForDrift2 = spec.exports?.length ?? 0;
1449
- const exportsWithDrift2 = new Set(driftExports.map((d) => d.name)).size;
1450
- const driftScore2 = totalExportsForDrift2 === 0 ? 0 : Math.round(exportsWithDrift2 / totalExportsForDrift2 * 100);
1451
- const coverageFailed2 = coverageScore < minCoverage;
1452
- const driftFailed2 = maxDrift !== undefined && driftScore2 > maxDrift;
1453
- const hasTypecheckErrors2 = typecheckErrors.length > 0;
1454
- if (coverageFailed2 || driftFailed2 || hasTypecheckErrors2) {
1707
+ const passed2 = handleNonTextOutput({
1708
+ format,
1709
+ spec,
1710
+ rawSpec: specResult.spec,
1711
+ coverageScore,
1712
+ minCoverage,
1713
+ maxDrift,
1714
+ driftExports,
1715
+ typecheckErrors,
1716
+ limit: parseInt(options.limit, 10) || 20,
1717
+ stdout: options.stdout,
1718
+ outputPath: options.output,
1719
+ cwd: options.cwd
1720
+ }, { log });
1721
+ if (!passed2) {
1455
1722
  process.exit(1);
1456
1723
  }
1457
1724
  return;
1458
1725
  }
1459
- const totalExportsForDrift = spec.exports?.length ?? 0;
1460
- const exportsWithDrift = new Set(driftExports.map((d) => d.name)).size;
1461
- const driftScore = totalExportsForDrift === 0 ? 0 : Math.round(exportsWithDrift / totalExportsForDrift * 100);
1462
- const coverageFailed = coverageScore < minCoverage;
1463
- const driftFailed = maxDrift !== undefined && driftScore > maxDrift;
1464
- const hasTypecheckErrors = typecheckErrors.length > 0;
1465
- if (specWarnings.length > 0 || specInfos.length > 0) {
1466
- log("");
1467
- for (const diag of specWarnings) {
1468
- log(chalk4.yellow(`⚠ ${diag.message}`));
1469
- if (diag.suggestion) {
1470
- log(chalk4.gray(` ${diag.suggestion}`));
1471
- }
1472
- }
1473
- for (const diag of specInfos) {
1474
- log(chalk4.cyan(`ℹ ${diag.message}`));
1475
- if (diag.suggestion) {
1476
- log(chalk4.gray(` ${diag.suggestion}`));
1477
- }
1478
- }
1479
- }
1480
- const pkgName = spec.meta?.name ?? "unknown";
1481
- const pkgVersion = spec.meta?.version ?? "";
1482
- const totalExports = spec.exports?.length ?? 0;
1483
- log("");
1484
- log(chalk4.bold(`${pkgName}${pkgVersion ? `@${pkgVersion}` : ""}`));
1485
- log("");
1486
- log(` Exports: ${totalExports}`);
1487
- if (coverageFailed) {
1488
- log(chalk4.red(` Coverage: ✗ ${coverageScore}%`) + chalk4.dim(` (min ${minCoverage}%)`));
1489
- } else {
1490
- log(chalk4.green(` Coverage: ✓ ${coverageScore}%`) + chalk4.dim(` (min ${minCoverage}%)`));
1491
- }
1492
- if (maxDrift !== undefined) {
1493
- if (driftFailed) {
1494
- log(chalk4.red(` Drift: ✗ ${driftScore}%`) + chalk4.dim(` (max ${maxDrift}%)`));
1495
- } else {
1496
- log(chalk4.green(` Drift: ✓ ${driftScore}%`) + chalk4.dim(` (max ${maxDrift}%)`));
1497
- }
1498
- } else {
1499
- log(` Drift: ${driftScore}%`);
1500
- }
1501
- if (exampleResult) {
1502
- const typecheckCount = exampleResult.typecheck?.errors.length ?? 0;
1503
- if (typecheckCount > 0) {
1504
- log(chalk4.yellow(` Examples: ${typecheckCount} type error(s)`));
1505
- for (const err of typecheckErrors.slice(0, 5)) {
1506
- const loc = `example[${err.error.exampleIndex}]:${err.error.line}:${err.error.column}`;
1507
- log(chalk4.dim(` ${err.exportName} ${loc}`));
1508
- log(chalk4.red(` ${err.error.message}`));
1509
- }
1510
- if (typecheckErrors.length > 5) {
1511
- log(chalk4.dim(` ... and ${typecheckErrors.length - 5} more`));
1512
- }
1513
- } else {
1514
- log(chalk4.green(` Examples: ✓ validated`));
1515
- }
1516
- }
1517
- const hasStaleRefs = staleRefs.length > 0;
1518
- if (hasStaleRefs) {
1519
- log(chalk4.yellow(` Docs: ${staleRefs.length} stale ref(s)`));
1520
- for (const ref of staleRefs.slice(0, 5)) {
1521
- log(chalk4.dim(` ${ref.file}:${ref.line} - "${ref.exportName}"`));
1522
- }
1523
- if (staleRefs.length > 5) {
1524
- log(chalk4.dim(` ... and ${staleRefs.length - 5} more`));
1525
- }
1526
- }
1527
- log("");
1528
- const failed = coverageFailed || driftFailed || hasTypecheckErrors || hasStaleRefs;
1529
- if (!failed) {
1530
- const thresholdParts = [];
1531
- thresholdParts.push(`coverage ${coverageScore}% ≥ ${minCoverage}%`);
1532
- if (maxDrift !== undefined) {
1533
- thresholdParts.push(`drift ${driftScore}% ≤ ${maxDrift}%`);
1534
- }
1535
- log(chalk4.green(`✓ Check passed (${thresholdParts.join(", ")})`));
1536
- return;
1537
- }
1538
- if (hasTypecheckErrors) {
1539
- log(chalk4.red(`✗ ${typecheckErrors.length} example type errors`));
1540
- }
1541
- if (hasStaleRefs) {
1542
- log(chalk4.red(`✗ ${staleRefs.length} stale references in docs`));
1726
+ const passed = displayTextOutput({
1727
+ spec,
1728
+ coverageScore,
1729
+ minCoverage,
1730
+ maxDrift,
1731
+ driftExports,
1732
+ typecheckErrors,
1733
+ staleRefs,
1734
+ exampleResult,
1735
+ specWarnings,
1736
+ specInfos
1737
+ }, { log });
1738
+ if (!passed) {
1739
+ process.exit(1);
1543
1740
  }
1544
- log("");
1545
- log(chalk4.dim("Use --format json or --format markdown for detailed reports"));
1546
- process.exit(1);
1547
1741
  } catch (commandError) {
1548
- error(chalk4.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
1742
+ error(chalk6.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
1549
1743
  process.exit(1);
1550
1744
  }
1551
1745
  });
1552
1746
  }
1553
- function collectDrift(exportsList) {
1554
- const drifts = [];
1555
- for (const entry of exportsList) {
1556
- const drift = entry.docs?.drift;
1557
- if (!drift || drift.length === 0) {
1558
- continue;
1559
- }
1560
- for (const d of drift) {
1561
- drifts.push({
1562
- name: entry.name,
1563
- type: d.type,
1564
- issue: d.issue ?? "Documentation drift detected.",
1565
- suggestion: d.suggestion,
1566
- category: DRIFT_CATEGORIES2[d.type]
1567
- });
1568
- }
1569
- }
1570
- return drifts;
1571
- }
1572
- function collect(value, previous) {
1573
- return previous.concat([value]);
1574
- }
1575
- async function loadMarkdownFiles(patterns, cwd) {
1576
- const files = [];
1577
- for (const pattern of patterns) {
1578
- const matches = await glob(pattern, { nodir: true, cwd });
1579
- for (const filePath of matches) {
1580
- try {
1581
- const fullPath = path4.resolve(cwd, filePath);
1582
- const content = fs2.readFileSync(fullPath, "utf-8");
1583
- files.push({ path: filePath, content });
1584
- } catch {}
1585
- }
1586
- }
1587
- return parseMarkdownFiles(files);
1588
- }
1589
-
1590
1747
  // src/commands/diff.ts
1591
- import * as fs3 from "node:fs";
1592
- import * as path5 from "node:path";
1748
+ import * as fs4 from "node:fs";
1749
+ import * as path6 from "node:path";
1593
1750
  import {
1594
1751
  diffSpecWithDocs,
1595
1752
  ensureSpecCoverage,
@@ -1600,10 +1757,10 @@ import {
1600
1757
  parseMarkdownFiles as parseMarkdownFiles2
1601
1758
  } from "@doccov/sdk";
1602
1759
  import { calculateNextVersion, recommendSemverBump } from "@openpkg-ts/spec";
1603
- import chalk5 from "chalk";
1760
+ import chalk7 from "chalk";
1604
1761
  import { glob as glob2 } from "glob";
1605
1762
  var defaultDependencies2 = {
1606
- readFileSync: fs3.readFileSync,
1763
+ readFileSync: fs4.readFileSync,
1607
1764
  log: console.log,
1608
1765
  error: console.error
1609
1766
  };
@@ -1641,12 +1798,12 @@ function registerDiffCommand(program, dependencies = {}) {
1641
1798
  const baseHash = hashString(JSON.stringify(baseSpec));
1642
1799
  const headHash = hashString(JSON.stringify(headSpec));
1643
1800
  const cacheEnabled = options.cache !== false;
1644
- const cachedReportPath = path5.resolve(options.cwd, getDiffReportPath(baseHash, headHash, "json"));
1801
+ const cachedReportPath = path6.resolve(options.cwd, getDiffReportPath(baseHash, headHash, "json"));
1645
1802
  let diff;
1646
1803
  let fromCache = false;
1647
- if (cacheEnabled && fs3.existsSync(cachedReportPath)) {
1804
+ if (cacheEnabled && fs4.existsSync(cachedReportPath)) {
1648
1805
  try {
1649
- const cached = JSON.parse(fs3.readFileSync(cachedReportPath, "utf-8"));
1806
+ const cached = JSON.parse(fs4.readFileSync(cachedReportPath, "utf-8"));
1650
1807
  diff = cached;
1651
1808
  fromCache = true;
1652
1809
  } catch {
@@ -1673,9 +1830,9 @@ function registerDiffCommand(program, dependencies = {}) {
1673
1830
  }, null, 2));
1674
1831
  } else {
1675
1832
  log("");
1676
- log(chalk5.bold("Semver Recommendation"));
1833
+ log(chalk7.bold("Semver Recommendation"));
1677
1834
  log(` Current version: ${currentVersion}`);
1678
- log(` Recommended: ${chalk5.cyan(nextVersion)} (${chalk5.yellow(recommendation.bump.toUpperCase())})`);
1835
+ log(` Recommended: ${chalk7.cyan(nextVersion)} (${chalk7.yellow(recommendation.bump.toUpperCase())})`);
1679
1836
  log(` Reason: ${recommendation.reason}`);
1680
1837
  }
1681
1838
  return;
@@ -1683,8 +1840,8 @@ function registerDiffCommand(program, dependencies = {}) {
1683
1840
  const format = options.format ?? "text";
1684
1841
  const limit = parseInt(options.limit, 10) || 10;
1685
1842
  const checks = getStrictChecks(options.strict);
1686
- const baseName = path5.basename(baseFile);
1687
- const headName = path5.basename(headFile);
1843
+ const baseName = path6.basename(baseFile);
1844
+ const headName = path6.basename(headFile);
1688
1845
  const reportData = {
1689
1846
  baseName,
1690
1847
  headName,
@@ -1704,8 +1861,8 @@ function registerDiffCommand(program, dependencies = {}) {
1704
1861
  silent: true
1705
1862
  });
1706
1863
  }
1707
- const cacheNote = fromCache ? chalk5.cyan(" (cached)") : "";
1708
- log(chalk5.dim(`Report: ${jsonPath}`) + cacheNote);
1864
+ const cacheNote = fromCache ? chalk7.cyan(" (cached)") : "";
1865
+ log(chalk7.dim(`Report: ${jsonPath}`) + cacheNote);
1709
1866
  }
1710
1867
  break;
1711
1868
  case "json": {
@@ -1763,7 +1920,10 @@ function registerDiffCommand(program, dependencies = {}) {
1763
1920
  sha: options.sha,
1764
1921
  minCoverage,
1765
1922
  limit,
1766
- semverBump: { bump: semverRecommendation.bump, reason: semverRecommendation.reason }
1923
+ semverBump: {
1924
+ bump: semverRecommendation.bump,
1925
+ reason: semverRecommendation.reason
1926
+ }
1767
1927
  });
1768
1928
  log(content);
1769
1929
  break;
@@ -1797,18 +1957,18 @@ function registerDiffCommand(program, dependencies = {}) {
1797
1957
  checks
1798
1958
  });
1799
1959
  if (failures.length > 0) {
1800
- log(chalk5.red(`
1960
+ log(chalk7.red(`
1801
1961
  ✗ Check failed`));
1802
1962
  for (const f of failures) {
1803
- log(chalk5.red(` - ${f}`));
1963
+ log(chalk7.red(` - ${f}`));
1804
1964
  }
1805
1965
  process.exitCode = 1;
1806
1966
  } else if (options.strict || minCoverage !== undefined || maxDrift !== undefined) {
1807
- log(chalk5.green(`
1967
+ log(chalk7.green(`
1808
1968
  ✓ All checks passed`));
1809
1969
  }
1810
1970
  } catch (commandError) {
1811
- error(chalk5.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
1971
+ error(chalk7.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
1812
1972
  process.exitCode = 1;
1813
1973
  }
1814
1974
  });
@@ -1822,7 +1982,7 @@ async function loadMarkdownFiles2(patterns) {
1822
1982
  const matches = await glob2(pattern, { nodir: true });
1823
1983
  for (const filePath of matches) {
1824
1984
  try {
1825
- const content = fs3.readFileSync(filePath, "utf-8");
1985
+ const content = fs4.readFileSync(filePath, "utf-8");
1826
1986
  files.push({ path: filePath, content });
1827
1987
  } catch {}
1828
1988
  }
@@ -1835,7 +1995,7 @@ async function generateDiff(baseSpec, headSpec, options, config, log) {
1835
1995
  if (!docsPatterns || docsPatterns.length === 0) {
1836
1996
  if (config?.docs?.include) {
1837
1997
  docsPatterns = config.docs.include;
1838
- log(chalk5.gray(`Using docs patterns from config: ${docsPatterns.join(", ")}`));
1998
+ log(chalk7.gray(`Using docs patterns from config: ${docsPatterns.join(", ")}`));
1839
1999
  }
1840
2000
  }
1841
2001
  if (docsPatterns && docsPatterns.length > 0) {
@@ -1844,8 +2004,8 @@ async function generateDiff(baseSpec, headSpec, options, config, log) {
1844
2004
  return diffSpecWithDocs(baseSpec, headSpec, { markdownFiles });
1845
2005
  }
1846
2006
  function loadSpec(filePath, readFileSync3) {
1847
- const resolvedPath = path5.resolve(filePath);
1848
- if (!fs3.existsSync(resolvedPath)) {
2007
+ const resolvedPath = path6.resolve(filePath);
2008
+ if (!fs4.existsSync(resolvedPath)) {
1849
2009
  throw new Error(`File not found: ${filePath}`);
1850
2010
  }
1851
2011
  try {
@@ -1858,37 +2018,37 @@ function loadSpec(filePath, readFileSync3) {
1858
2018
  }
1859
2019
  function printSummary(diff, baseName, headName, fromCache, log) {
1860
2020
  log("");
1861
- const cacheIndicator = fromCache ? chalk5.cyan(" (cached)") : "";
1862
- log(chalk5.bold(`Comparing: ${baseName} → ${headName}`) + cacheIndicator);
2021
+ const cacheIndicator = fromCache ? chalk7.cyan(" (cached)") : "";
2022
+ log(chalk7.bold(`Comparing: ${baseName} → ${headName}`) + cacheIndicator);
1863
2023
  log("─".repeat(40));
1864
2024
  log("");
1865
- const coverageColor = diff.coverageDelta > 0 ? chalk5.green : diff.coverageDelta < 0 ? chalk5.red : chalk5.gray;
2025
+ const coverageColor = diff.coverageDelta > 0 ? chalk7.green : diff.coverageDelta < 0 ? chalk7.red : chalk7.gray;
1866
2026
  const coverageSign = diff.coverageDelta > 0 ? "+" : "";
1867
2027
  log(` Coverage: ${diff.oldCoverage}% → ${diff.newCoverage}% ${coverageColor(`(${coverageSign}${diff.coverageDelta}%)`)}`);
1868
2028
  const breakingCount = diff.breaking.length;
1869
2029
  const highSeverity = diff.categorizedBreaking?.filter((c) => c.severity === "high").length ?? 0;
1870
2030
  if (breakingCount > 0) {
1871
- const severityNote = highSeverity > 0 ? chalk5.red(` (${highSeverity} high severity)`) : "";
1872
- log(` Breaking: ${chalk5.red(breakingCount)} changes${severityNote}`);
2031
+ const severityNote = highSeverity > 0 ? chalk7.red(` (${highSeverity} high severity)`) : "";
2032
+ log(` Breaking: ${chalk7.red(breakingCount)} changes${severityNote}`);
1873
2033
  } else {
1874
- log(` Breaking: ${chalk5.green("0")} changes`);
2034
+ log(` Breaking: ${chalk7.green("0")} changes`);
1875
2035
  }
1876
2036
  const newCount = diff.nonBreaking.length;
1877
2037
  const undocCount = diff.newUndocumented.length;
1878
2038
  if (newCount > 0) {
1879
- const undocNote = undocCount > 0 ? chalk5.yellow(` (${undocCount} undocumented)`) : "";
1880
- log(` New: ${chalk5.green(newCount)} exports${undocNote}`);
2039
+ const undocNote = undocCount > 0 ? chalk7.yellow(` (${undocCount} undocumented)`) : "";
2040
+ log(` New: ${chalk7.green(newCount)} exports${undocNote}`);
1881
2041
  }
1882
2042
  if (diff.driftIntroduced > 0 || diff.driftResolved > 0) {
1883
2043
  const parts = [];
1884
2044
  if (diff.driftIntroduced > 0)
1885
- parts.push(chalk5.red(`+${diff.driftIntroduced}`));
2045
+ parts.push(chalk7.red(`+${diff.driftIntroduced}`));
1886
2046
  if (diff.driftResolved > 0)
1887
- parts.push(chalk5.green(`-${diff.driftResolved}`));
2047
+ parts.push(chalk7.green(`-${diff.driftResolved}`));
1888
2048
  log(` Drift: ${parts.join(", ")}`);
1889
2049
  }
1890
2050
  const recommendation = recommendSemverBump(diff);
1891
- const bumpColor = recommendation.bump === "major" ? chalk5.red : recommendation.bump === "minor" ? chalk5.yellow : chalk5.green;
2051
+ const bumpColor = recommendation.bump === "major" ? chalk7.red : recommendation.bump === "minor" ? chalk7.yellow : chalk7.green;
1892
2052
  log(` Semver: ${bumpColor(recommendation.bump.toUpperCase())} (${recommendation.reason})`);
1893
2053
  log("");
1894
2054
  }
@@ -1970,7 +2130,7 @@ function printGitHubAnnotations(diff, log) {
1970
2130
 
1971
2131
  // src/commands/info.ts
1972
2132
  import { DocCov as DocCov2, enrichSpec as enrichSpec2, NodeFileSystem as NodeFileSystem2, resolveTarget as resolveTarget2 } from "@doccov/sdk";
1973
- import chalk6 from "chalk";
2133
+ import chalk8 from "chalk";
1974
2134
  function registerInfoCommand(program) {
1975
2135
  program.command("info [entry]").description("Show brief documentation coverage summary").option("--cwd <dir>", "Working directory", process.cwd()).option("--package <name>", "Target package name (for monorepos)").option("--skip-resolve", "Skip external type resolution from node_modules").action(async (entry, options) => {
1976
2136
  try {
@@ -1992,28 +2152,28 @@ function registerInfoCommand(program) {
1992
2152
  const spec = enrichSpec2(specResult.spec);
1993
2153
  const stats = computeStats(spec);
1994
2154
  console.log("");
1995
- console.log(chalk6.bold(`${stats.packageName}@${stats.version}`));
2155
+ console.log(chalk8.bold(`${stats.packageName}@${stats.version}`));
1996
2156
  console.log("");
1997
- console.log(` Exports: ${chalk6.bold(stats.totalExports.toString())}`);
1998
- console.log(` Coverage: ${chalk6.bold(`${stats.coverageScore}%`)}`);
1999
- console.log(` Drift: ${chalk6.bold(`${stats.driftScore}%`)}`);
2157
+ console.log(` Exports: ${chalk8.bold(stats.totalExports.toString())}`);
2158
+ console.log(` Coverage: ${chalk8.bold(`${stats.coverageScore}%`)}`);
2159
+ console.log(` Drift: ${chalk8.bold(`${stats.driftScore}%`)}`);
2000
2160
  console.log("");
2001
2161
  } catch (err) {
2002
- console.error(chalk6.red("Error:"), err instanceof Error ? err.message : err);
2162
+ console.error(chalk8.red("Error:"), err instanceof Error ? err.message : err);
2003
2163
  process.exit(1);
2004
2164
  }
2005
2165
  });
2006
2166
  }
2007
2167
 
2008
2168
  // src/commands/init.ts
2009
- import * as fs4 from "node:fs";
2010
- import * as path6 from "node:path";
2011
- import chalk7 from "chalk";
2169
+ import * as fs5 from "node:fs";
2170
+ import * as path7 from "node:path";
2171
+ import chalk9 from "chalk";
2012
2172
  var defaultDependencies3 = {
2013
- fileExists: fs4.existsSync,
2014
- writeFileSync: fs4.writeFileSync,
2015
- readFileSync: fs4.readFileSync,
2016
- mkdirSync: fs4.mkdirSync,
2173
+ fileExists: fs5.existsSync,
2174
+ writeFileSync: fs5.writeFileSync,
2175
+ readFileSync: fs5.readFileSync,
2176
+ mkdirSync: fs5.mkdirSync,
2017
2177
  log: console.log,
2018
2178
  error: console.error
2019
2179
  };
@@ -2023,56 +2183,56 @@ function registerInitCommand(program, dependencies = {}) {
2023
2183
  ...dependencies
2024
2184
  };
2025
2185
  program.command("init").description("Initialize DocCov: config, GitHub Action, and badge").option("--cwd <dir>", "Working directory", process.cwd()).option("--skip-action", "Skip GitHub Action workflow creation").action((options) => {
2026
- const cwd = path6.resolve(options.cwd);
2186
+ const cwd = path7.resolve(options.cwd);
2027
2187
  const existing = findExistingConfig(cwd, fileExists2);
2028
2188
  if (existing) {
2029
- error(chalk7.red(`A DocCov config already exists at ${path6.relative(cwd, existing) || "./doccov.config.*"}.`));
2189
+ error(chalk9.red(`A DocCov config already exists at ${path7.relative(cwd, existing) || "./doccov.config.*"}.`));
2030
2190
  process.exitCode = 1;
2031
2191
  return;
2032
2192
  }
2033
2193
  const packageType = detectPackageType(cwd, fileExists2, readFileSync4);
2034
2194
  const targetFormat = packageType === "module" ? "ts" : "mts";
2035
2195
  const fileName = `doccov.config.${targetFormat}`;
2036
- const outputPath = path6.join(cwd, fileName);
2196
+ const outputPath = path7.join(cwd, fileName);
2037
2197
  if (fileExists2(outputPath)) {
2038
- error(chalk7.red(`Cannot create ${fileName}; file already exists.`));
2198
+ error(chalk9.red(`Cannot create ${fileName}; file already exists.`));
2039
2199
  process.exitCode = 1;
2040
2200
  return;
2041
2201
  }
2042
2202
  const template = buildConfigTemplate();
2043
2203
  writeFileSync3(outputPath, template, { encoding: "utf8" });
2044
- log(chalk7.green(`✓ Created ${fileName}`));
2204
+ log(chalk9.green(`✓ Created ${fileName}`));
2045
2205
  if (!options.skipAction) {
2046
- const workflowDir = path6.join(cwd, ".github", "workflows");
2047
- const workflowPath = path6.join(workflowDir, "doccov.yml");
2206
+ const workflowDir = path7.join(cwd, ".github", "workflows");
2207
+ const workflowPath = path7.join(workflowDir, "doccov.yml");
2048
2208
  if (!fileExists2(workflowPath)) {
2049
2209
  mkdirSync3(workflowDir, { recursive: true });
2050
2210
  writeFileSync3(workflowPath, buildWorkflowTemplate(), { encoding: "utf8" });
2051
- log(chalk7.green(`✓ Created .github/workflows/doccov.yml`));
2211
+ log(chalk9.green(`✓ Created .github/workflows/doccov.yml`));
2052
2212
  } else {
2053
- log(chalk7.yellow(` Skipped .github/workflows/doccov.yml (already exists)`));
2213
+ log(chalk9.yellow(` Skipped .github/workflows/doccov.yml (already exists)`));
2054
2214
  }
2055
2215
  }
2056
2216
  const repoInfo = detectRepoInfo(cwd, fileExists2, readFileSync4);
2057
2217
  log("");
2058
- log(chalk7.bold("Add this badge to your README:"));
2218
+ log(chalk9.bold("Add this badge to your README:"));
2059
2219
  log("");
2060
2220
  if (repoInfo) {
2061
- log(chalk7.cyan(`[![DocCov](https://doccov.dev/badge/${repoInfo.owner}/${repoInfo.repo})](https://doccov.dev/${repoInfo.owner}/${repoInfo.repo})`));
2221
+ log(chalk9.cyan(`[![DocCov](https://doccov.dev/badge/${repoInfo.owner}/${repoInfo.repo})](https://doccov.dev/${repoInfo.owner}/${repoInfo.repo})`));
2062
2222
  } else {
2063
- log(chalk7.cyan(`[![DocCov](https://doccov.dev/badge/OWNER/REPO)](https://doccov.dev/OWNER/REPO)`));
2064
- log(chalk7.dim(" Replace OWNER/REPO with your GitHub repo"));
2223
+ log(chalk9.cyan(`[![DocCov](https://doccov.dev/badge/OWNER/REPO)](https://doccov.dev/OWNER/REPO)`));
2224
+ log(chalk9.dim(" Replace OWNER/REPO with your GitHub repo"));
2065
2225
  }
2066
2226
  log("");
2067
- log(chalk7.dim("Run `doccov check` to verify your documentation coverage"));
2227
+ log(chalk9.dim("Run `doccov check` to verify your documentation coverage"));
2068
2228
  });
2069
2229
  }
2070
2230
  var findExistingConfig = (cwd, fileExists2) => {
2071
- let current = path6.resolve(cwd);
2072
- const { root } = path6.parse(current);
2231
+ let current = path7.resolve(cwd);
2232
+ const { root } = path7.parse(current);
2073
2233
  while (true) {
2074
2234
  for (const candidate of DOCCOV_CONFIG_FILENAMES) {
2075
- const candidatePath = path6.join(current, candidate);
2235
+ const candidatePath = path7.join(current, candidate);
2076
2236
  if (fileExists2(candidatePath)) {
2077
2237
  return candidatePath;
2078
2238
  }
@@ -2080,7 +2240,7 @@ var findExistingConfig = (cwd, fileExists2) => {
2080
2240
  if (current === root) {
2081
2241
  break;
2082
2242
  }
2083
- current = path6.dirname(current);
2243
+ current = path7.dirname(current);
2084
2244
  }
2085
2245
  return null;
2086
2246
  };
@@ -2102,17 +2262,17 @@ var detectPackageType = (cwd, fileExists2, readFileSync4) => {
2102
2262
  return;
2103
2263
  };
2104
2264
  var findNearestPackageJson = (cwd, fileExists2) => {
2105
- let current = path6.resolve(cwd);
2106
- const { root } = path6.parse(current);
2265
+ let current = path7.resolve(cwd);
2266
+ const { root } = path7.parse(current);
2107
2267
  while (true) {
2108
- const candidate = path6.join(current, "package.json");
2268
+ const candidate = path7.join(current, "package.json");
2109
2269
  if (fileExists2(candidate)) {
2110
2270
  return candidate;
2111
2271
  }
2112
2272
  if (current === root) {
2113
2273
  break;
2114
2274
  }
2115
- current = path6.dirname(current);
2275
+ current = path7.dirname(current);
2116
2276
  }
2117
2277
  return null;
2118
2278
  };
@@ -2174,7 +2334,7 @@ var detectRepoInfo = (cwd, fileExists2, readFileSync4) => {
2174
2334
  }
2175
2335
  } catch {}
2176
2336
  }
2177
- const gitConfigPath = path6.join(cwd, ".git", "config");
2337
+ const gitConfigPath = path7.join(cwd, ".git", "config");
2178
2338
  if (fileExists2(gitConfigPath)) {
2179
2339
  try {
2180
2340
  const config = readFileSync4(gitConfigPath, "utf8");
@@ -2188,18 +2348,24 @@ var detectRepoInfo = (cwd, fileExists2, readFileSync4) => {
2188
2348
  };
2189
2349
 
2190
2350
  // src/commands/spec.ts
2191
- import * as fs5 from "node:fs";
2192
- import * as path7 from "node:path";
2193
- import { DocCov as DocCov3, NodeFileSystem as NodeFileSystem3, renderApiSurface, resolveTarget as resolveTarget3 } from "@doccov/sdk";
2351
+ import * as fs6 from "node:fs";
2352
+ import * as path8 from "node:path";
2353
+ import {
2354
+ DocCov as DocCov3,
2355
+ detectPackageManager,
2356
+ NodeFileSystem as NodeFileSystem3,
2357
+ renderApiSurface,
2358
+ resolveTarget as resolveTarget3
2359
+ } from "@doccov/sdk";
2194
2360
  import { normalize, validateSpec } from "@openpkg-ts/spec";
2195
- import chalk8 from "chalk";
2361
+ import chalk10 from "chalk";
2196
2362
  // package.json
2197
- var version = "0.20.0";
2363
+ var version = "0.22.0";
2198
2364
 
2199
2365
  // src/commands/spec.ts
2200
2366
  var defaultDependencies4 = {
2201
2367
  createDocCov: (options) => new DocCov3(options),
2202
- writeFileSync: fs5.writeFileSync,
2368
+ writeFileSync: fs6.writeFileSync,
2203
2369
  log: console.log,
2204
2370
  error: console.error
2205
2371
  };
@@ -2208,8 +2374,8 @@ function getArrayLength(value) {
2208
2374
  }
2209
2375
  function formatDiagnosticOutput(prefix, diagnostic, baseDir) {
2210
2376
  const location = diagnostic.location;
2211
- const relativePath = location?.file ? path7.relative(baseDir, location.file) || location.file : undefined;
2212
- const locationText = location && relativePath ? chalk8.gray(`${relativePath}:${location.line ?? 1}:${location.column ?? 1}`) : null;
2377
+ const relativePath = location?.file ? path8.relative(baseDir, location.file) || location.file : undefined;
2378
+ const locationText = location && relativePath ? chalk10.gray(`${relativePath}:${location.line ?? 1}:${location.column ?? 1}`) : null;
2213
2379
  const locationPrefix = locationText ? `${locationText} ` : "";
2214
2380
  return `${prefix} ${locationPrefix}${diagnostic.message}`;
2215
2381
  }
@@ -2240,7 +2406,7 @@ function registerSpecCommand(program, dependencies = {}) {
2240
2406
  try {
2241
2407
  config = await loadDocCovConfig(targetDir);
2242
2408
  } catch (configError) {
2243
- error(chalk8.red("Failed to load DocCov config:"), configError instanceof Error ? configError.message : configError);
2409
+ error(chalk10.red("Failed to load DocCov config:"), configError instanceof Error ? configError.message : configError);
2244
2410
  process.exit(1);
2245
2411
  }
2246
2412
  steps.next();
@@ -2259,7 +2425,7 @@ function registerSpecCommand(program, dependencies = {}) {
2259
2425
  schemaExtraction: options.runtime ? "hybrid" : "static"
2260
2426
  });
2261
2427
  const generationInput = {
2262
- entryPoint: path7.relative(targetDir, entryFile),
2428
+ entryPoint: path8.relative(targetDir, entryFile),
2263
2429
  entryPointSource: entryPointInfo.source,
2264
2430
  isDeclarationOnly: entryPointInfo.isDeclarationOnly ?? false,
2265
2431
  generatorName: "@doccov/cli",
@@ -2284,15 +2450,15 @@ function registerSpecCommand(program, dependencies = {}) {
2284
2450
  const normalized = normalize(result.spec);
2285
2451
  const validation = validateSpec(normalized);
2286
2452
  if (!validation.ok) {
2287
- error(chalk8.red("Spec failed schema validation"));
2453
+ error(chalk10.red("Spec failed schema validation"));
2288
2454
  for (const err of validation.errors) {
2289
- error(chalk8.red(`schema: ${err.instancePath || "/"} ${err.message}`));
2455
+ error(chalk10.red(`schema: ${err.instancePath || "/"} ${err.message}`));
2290
2456
  }
2291
2457
  process.exit(1);
2292
2458
  }
2293
2459
  steps.next();
2294
2460
  const format = options.format ?? "json";
2295
- const outputPath = path7.resolve(options.cwd, options.output);
2461
+ const outputPath = path8.resolve(options.cwd, options.output);
2296
2462
  if (format === "api-surface") {
2297
2463
  const apiSurface = renderApiSurface(normalized);
2298
2464
  writeFileSync4(outputPath, apiSurface);
@@ -2301,70 +2467,78 @@ function registerSpecCommand(program, dependencies = {}) {
2301
2467
  writeFileSync4(outputPath, JSON.stringify(normalized, null, 2));
2302
2468
  steps.complete(`Generated ${options.output}`);
2303
2469
  }
2304
- log(chalk8.gray(` ${getArrayLength(normalized.exports)} exports`));
2305
- log(chalk8.gray(` ${getArrayLength(normalized.types)} types`));
2470
+ log(chalk10.gray(` ${getArrayLength(normalized.exports)} exports`));
2471
+ log(chalk10.gray(` ${getArrayLength(normalized.types)} types`));
2472
+ const schemaExtraction = normalized.generation?.analysis?.schemaExtraction;
2473
+ if (options.runtime && (!schemaExtraction?.runtimeCount || schemaExtraction.runtimeCount === 0)) {
2474
+ const pm = await detectPackageManager(fileSystem);
2475
+ const buildCmd = pm.name === "npm" ? "npm run build" : `${pm.name} run build`;
2476
+ log("");
2477
+ log(chalk10.yellow("⚠ Runtime extraction requested but no schemas extracted."));
2478
+ log(chalk10.yellow(` Ensure project is built (${buildCmd}) and dist/ exists.`));
2479
+ }
2306
2480
  if (options.verbose && normalized.generation) {
2307
2481
  const gen = normalized.generation;
2308
2482
  log("");
2309
- log(chalk8.bold("Generation Info"));
2310
- log(chalk8.gray(` Timestamp: ${gen.timestamp}`));
2311
- log(chalk8.gray(` Generator: ${gen.generator.name}@${gen.generator.version}`));
2312
- log(chalk8.gray(` Entry point: ${gen.analysis.entryPoint}`));
2313
- log(chalk8.gray(` Detected via: ${gen.analysis.entryPointSource}`));
2314
- log(chalk8.gray(` Declaration only: ${gen.analysis.isDeclarationOnly ? "yes" : "no"}`));
2315
- log(chalk8.gray(` External types: ${gen.analysis.resolvedExternalTypes ? "resolved" : "skipped"}`));
2483
+ log(chalk10.bold("Generation Info"));
2484
+ log(chalk10.gray(` Timestamp: ${gen.timestamp}`));
2485
+ log(chalk10.gray(` Generator: ${gen.generator.name}@${gen.generator.version}`));
2486
+ log(chalk10.gray(` Entry point: ${gen.analysis.entryPoint}`));
2487
+ log(chalk10.gray(` Detected via: ${gen.analysis.entryPointSource}`));
2488
+ log(chalk10.gray(` Declaration only: ${gen.analysis.isDeclarationOnly ? "yes" : "no"}`));
2489
+ log(chalk10.gray(` External types: ${gen.analysis.resolvedExternalTypes ? "resolved" : "skipped"}`));
2316
2490
  if (gen.analysis.maxTypeDepth) {
2317
- log(chalk8.gray(` Max type depth: ${gen.analysis.maxTypeDepth}`));
2491
+ log(chalk10.gray(` Max type depth: ${gen.analysis.maxTypeDepth}`));
2318
2492
  }
2319
2493
  if (gen.analysis.schemaExtraction) {
2320
2494
  const se = gen.analysis.schemaExtraction;
2321
- log(chalk8.gray(` Schema extraction: ${se.method}`));
2495
+ log(chalk10.gray(` Schema extraction: ${se.method}`));
2322
2496
  if (se.runtimeCount) {
2323
- log(chalk8.gray(` Runtime schemas: ${se.runtimeCount} (${se.vendors?.join(", ")})`));
2497
+ log(chalk10.gray(` Runtime schemas: ${se.runtimeCount} (${se.vendors?.join(", ")})`));
2324
2498
  }
2325
2499
  }
2326
2500
  log("");
2327
- log(chalk8.bold("Environment"));
2328
- log(chalk8.gray(` node_modules: ${gen.environment.hasNodeModules ? "found" : "not found"}`));
2501
+ log(chalk10.bold("Environment"));
2502
+ log(chalk10.gray(` node_modules: ${gen.environment.hasNodeModules ? "found" : "not found"}`));
2329
2503
  if (gen.environment.packageManager) {
2330
- log(chalk8.gray(` Package manager: ${gen.environment.packageManager}`));
2504
+ log(chalk10.gray(` Package manager: ${gen.environment.packageManager}`));
2331
2505
  }
2332
2506
  if (gen.environment.isMonorepo) {
2333
- log(chalk8.gray(` Monorepo: yes`));
2507
+ log(chalk10.gray(` Monorepo: yes`));
2334
2508
  }
2335
2509
  if (gen.environment.targetPackage) {
2336
- log(chalk8.gray(` Target package: ${gen.environment.targetPackage}`));
2510
+ log(chalk10.gray(` Target package: ${gen.environment.targetPackage}`));
2337
2511
  }
2338
2512
  if (gen.issues.length > 0) {
2339
2513
  log("");
2340
- log(chalk8.bold("Issues"));
2514
+ log(chalk10.bold("Issues"));
2341
2515
  for (const issue of gen.issues) {
2342
- const prefix = issue.severity === "error" ? chalk8.red(">") : issue.severity === "warning" ? chalk8.yellow(">") : chalk8.cyan(">");
2516
+ const prefix = issue.severity === "error" ? chalk10.red(">") : issue.severity === "warning" ? chalk10.yellow(">") : chalk10.cyan(">");
2343
2517
  log(`${prefix} [${issue.code}] ${issue.message}`);
2344
2518
  if (issue.suggestion) {
2345
- log(chalk8.gray(` ${issue.suggestion}`));
2519
+ log(chalk10.gray(` ${issue.suggestion}`));
2346
2520
  }
2347
2521
  }
2348
2522
  }
2349
2523
  }
2350
2524
  if (options.showDiagnostics && result.diagnostics.length > 0) {
2351
2525
  log("");
2352
- log(chalk8.bold("Diagnostics"));
2526
+ log(chalk10.bold("Diagnostics"));
2353
2527
  for (const diagnostic of result.diagnostics) {
2354
- const prefix = diagnostic.severity === "error" ? chalk8.red(">") : diagnostic.severity === "warning" ? chalk8.yellow(">") : chalk8.cyan(">");
2528
+ const prefix = diagnostic.severity === "error" ? chalk10.red(">") : diagnostic.severity === "warning" ? chalk10.yellow(">") : chalk10.cyan(">");
2355
2529
  log(formatDiagnosticOutput(prefix, diagnostic, targetDir));
2356
2530
  }
2357
2531
  }
2358
2532
  } catch (commandError) {
2359
- error(chalk8.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
2533
+ error(chalk10.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
2360
2534
  process.exit(1);
2361
2535
  }
2362
2536
  });
2363
2537
  }
2364
2538
 
2365
2539
  // src/commands/trends.ts
2366
- import * as fs6 from "node:fs";
2367
- import * as path8 from "node:path";
2540
+ import * as fs7 from "node:fs";
2541
+ import * as path9 from "node:path";
2368
2542
  import {
2369
2543
  formatDelta as formatDelta2,
2370
2544
  getExtendedTrend,
@@ -2372,11 +2546,11 @@ import {
2372
2546
  loadSnapshots,
2373
2547
  pruneByTier,
2374
2548
  pruneHistory,
2375
- renderSparkline,
2376
2549
  RETENTION_DAYS,
2550
+ renderSparkline,
2377
2551
  saveSnapshot
2378
2552
  } from "@doccov/sdk";
2379
- import chalk9 from "chalk";
2553
+ import chalk11 from "chalk";
2380
2554
  function formatDate(timestamp) {
2381
2555
  const date = new Date(timestamp);
2382
2556
  return date.toLocaleDateString("en-US", {
@@ -2389,19 +2563,19 @@ function formatDate(timestamp) {
2389
2563
  }
2390
2564
  function getColorForScore(score) {
2391
2565
  if (score >= 90)
2392
- return chalk9.green;
2566
+ return chalk11.green;
2393
2567
  if (score >= 70)
2394
- return chalk9.yellow;
2568
+ return chalk11.yellow;
2395
2569
  if (score >= 50)
2396
- return chalk9.hex("#FFA500");
2397
- return chalk9.red;
2570
+ return chalk11.hex("#FFA500");
2571
+ return chalk11.red;
2398
2572
  }
2399
2573
  function formatSnapshot(snapshot) {
2400
2574
  const color = getColorForScore(snapshot.coverageScore);
2401
2575
  const date = formatDate(snapshot.timestamp);
2402
2576
  const version2 = snapshot.version ? ` v${snapshot.version}` : "";
2403
- const commit = snapshot.commit ? chalk9.gray(` (${snapshot.commit.slice(0, 7)})`) : "";
2404
- return `${chalk9.gray(date)} ${color(`${snapshot.coverageScore}%`)} ${snapshot.documentedExports}/${snapshot.totalExports} exports${version2}${commit}`;
2577
+ const commit = snapshot.commit ? chalk11.gray(` (${snapshot.commit.slice(0, 7)})`) : "";
2578
+ return `${chalk11.gray(date)} ${color(`${snapshot.coverageScore}%`)} ${snapshot.documentedExports}/${snapshot.totalExports} exports${version2}${commit}`;
2405
2579
  }
2406
2580
  function formatWeekDate(timestamp) {
2407
2581
  const date = new Date(timestamp);
@@ -2409,55 +2583,55 @@ function formatWeekDate(timestamp) {
2409
2583
  }
2410
2584
  function formatVelocity(velocity) {
2411
2585
  if (velocity > 0)
2412
- return chalk9.green(`+${velocity}%/day`);
2586
+ return chalk11.green(`+${velocity}%/day`);
2413
2587
  if (velocity < 0)
2414
- return chalk9.red(`${velocity}%/day`);
2415
- return chalk9.gray("0%/day");
2588
+ return chalk11.red(`${velocity}%/day`);
2589
+ return chalk11.gray("0%/day");
2416
2590
  }
2417
2591
  function registerTrendsCommand(program) {
2418
2592
  program.command("trends").description("Show coverage trends over time").option("--cwd <dir>", "Working directory", process.cwd()).option("-n, --limit <count>", "Number of snapshots to show", "10").option("--prune <count>", "Prune history to keep only N snapshots").option("--record", "Record current coverage to history").option("--json", "Output as JSON").option("--extended", "Show extended trend analysis (velocity, projections)").option("--tier <tier>", "Retention tier: free (7d), team (30d), pro (90d)", "pro").option("--weekly", "Show weekly summary breakdown").action(async (options) => {
2419
- const cwd = path8.resolve(options.cwd);
2593
+ const cwd = path9.resolve(options.cwd);
2420
2594
  const tier = options.tier ?? "pro";
2421
2595
  if (options.prune) {
2422
2596
  const keepCount = parseInt(options.prune, 10);
2423
- if (!isNaN(keepCount)) {
2597
+ if (!Number.isNaN(keepCount)) {
2424
2598
  const deleted = pruneHistory(cwd, keepCount);
2425
- console.log(chalk9.green(`Pruned ${deleted} old snapshots, kept ${keepCount} most recent`));
2599
+ console.log(chalk11.green(`Pruned ${deleted} old snapshots, kept ${keepCount} most recent`));
2426
2600
  } else {
2427
2601
  const deleted = pruneByTier(cwd, tier);
2428
- console.log(chalk9.green(`Pruned ${deleted} snapshots older than ${RETENTION_DAYS[tier]} days`));
2602
+ console.log(chalk11.green(`Pruned ${deleted} snapshots older than ${RETENTION_DAYS[tier]} days`));
2429
2603
  }
2430
2604
  return;
2431
2605
  }
2432
2606
  if (options.record) {
2433
- const specPath = path8.resolve(cwd, "openpkg.json");
2434
- if (!fs6.existsSync(specPath)) {
2435
- console.error(chalk9.red("No openpkg.json found. Run `doccov spec` first to generate a spec."));
2607
+ const specPath = path9.resolve(cwd, "openpkg.json");
2608
+ if (!fs7.existsSync(specPath)) {
2609
+ console.error(chalk11.red("No openpkg.json found. Run `doccov spec` first to generate a spec."));
2436
2610
  process.exit(1);
2437
2611
  }
2438
2612
  try {
2439
- const specContent = fs6.readFileSync(specPath, "utf-8");
2613
+ const specContent = fs7.readFileSync(specPath, "utf-8");
2440
2614
  const spec = JSON.parse(specContent);
2441
2615
  const trend = getTrend(spec, cwd);
2442
2616
  saveSnapshot(trend.current, cwd);
2443
- console.log(chalk9.green("Recorded coverage snapshot:"));
2617
+ console.log(chalk11.green("Recorded coverage snapshot:"));
2444
2618
  console.log(formatSnapshot(trend.current));
2445
2619
  if (trend.delta !== undefined) {
2446
2620
  const deltaStr = formatDelta2(trend.delta);
2447
- const deltaColor = trend.delta > 0 ? chalk9.green : trend.delta < 0 ? chalk9.red : chalk9.gray;
2448
- console.log(chalk9.gray("Change from previous:"), deltaColor(deltaStr));
2621
+ const deltaColor = trend.delta > 0 ? chalk11.green : trend.delta < 0 ? chalk11.red : chalk11.gray;
2622
+ console.log(chalk11.gray("Change from previous:"), deltaColor(deltaStr));
2449
2623
  }
2450
2624
  return;
2451
2625
  } catch (error) {
2452
- console.error(chalk9.red("Failed to read openpkg.json:"), error instanceof Error ? error.message : error);
2626
+ console.error(chalk11.red("Failed to read openpkg.json:"), error instanceof Error ? error.message : error);
2453
2627
  process.exit(1);
2454
2628
  }
2455
2629
  }
2456
2630
  const snapshots = loadSnapshots(cwd);
2457
2631
  const limit = parseInt(options.limit ?? "10", 10);
2458
2632
  if (snapshots.length === 0) {
2459
- console.log(chalk9.yellow("No coverage history found."));
2460
- console.log(chalk9.gray("Run `doccov trends --record` to save the current coverage."));
2633
+ console.log(chalk11.yellow("No coverage history found."));
2634
+ console.log(chalk11.gray("Run `doccov trends --record` to save the current coverage."));
2461
2635
  return;
2462
2636
  }
2463
2637
  if (options.json) {
@@ -2472,28 +2646,28 @@ function registerTrendsCommand(program) {
2472
2646
  }
2473
2647
  const sparklineData = snapshots.slice(0, 10).map((s) => s.coverageScore).reverse();
2474
2648
  const sparkline = renderSparkline(sparklineData);
2475
- console.log(chalk9.bold("Coverage Trends"));
2476
- console.log(chalk9.gray(`Package: ${snapshots[0].package}`));
2477
- console.log(chalk9.gray(`Sparkline: ${sparkline}`));
2649
+ console.log(chalk11.bold("Coverage Trends"));
2650
+ console.log(chalk11.gray(`Package: ${snapshots[0].package}`));
2651
+ console.log(chalk11.gray(`Sparkline: ${sparkline}`));
2478
2652
  console.log("");
2479
2653
  if (snapshots.length >= 2) {
2480
2654
  const oldest = snapshots[snapshots.length - 1];
2481
2655
  const newest = snapshots[0];
2482
2656
  const overallDelta = newest.coverageScore - oldest.coverageScore;
2483
2657
  const deltaStr = formatDelta2(overallDelta);
2484
- const deltaColor = overallDelta > 0 ? chalk9.green : overallDelta < 0 ? chalk9.red : chalk9.gray;
2485
- console.log(chalk9.gray("Overall trend:"), deltaColor(deltaStr), chalk9.gray(`(${snapshots.length} snapshots)`));
2658
+ const deltaColor = overallDelta > 0 ? chalk11.green : overallDelta < 0 ? chalk11.red : chalk11.gray;
2659
+ console.log(chalk11.gray("Overall trend:"), deltaColor(deltaStr), chalk11.gray(`(${snapshots.length} snapshots)`));
2486
2660
  console.log("");
2487
2661
  }
2488
2662
  if (options.extended) {
2489
- const specPath = path8.resolve(cwd, "openpkg.json");
2490
- if (fs6.existsSync(specPath)) {
2663
+ const specPath = path9.resolve(cwd, "openpkg.json");
2664
+ if (fs7.existsSync(specPath)) {
2491
2665
  try {
2492
- const specContent = fs6.readFileSync(specPath, "utf-8");
2666
+ const specContent = fs7.readFileSync(specPath, "utf-8");
2493
2667
  const spec = JSON.parse(specContent);
2494
2668
  const extended = getExtendedTrend(spec, cwd, { tier });
2495
- console.log(chalk9.bold("Extended Analysis"));
2496
- console.log(chalk9.gray(`Tier: ${tier} (${RETENTION_DAYS[tier]}-day retention)`));
2669
+ console.log(chalk11.bold("Extended Analysis"));
2670
+ console.log(chalk11.gray(`Tier: ${tier} (${RETENTION_DAYS[tier]}-day retention)`));
2497
2671
  console.log("");
2498
2672
  console.log(" Velocity:");
2499
2673
  console.log(` 7-day: ${formatVelocity(extended.velocity7d)}`);
@@ -2502,55 +2676,55 @@ function registerTrendsCommand(program) {
2502
2676
  console.log(` 90-day: ${formatVelocity(extended.velocity90d)}`);
2503
2677
  }
2504
2678
  console.log("");
2505
- const projColor = extended.projected30d >= extended.trend.current.coverageScore ? chalk9.green : chalk9.red;
2679
+ const projColor = extended.projected30d >= extended.trend.current.coverageScore ? chalk11.green : chalk11.red;
2506
2680
  console.log(` Projected (30d): ${projColor(`${extended.projected30d}%`)}`);
2507
- console.log(` All-time high: ${chalk9.green(`${extended.allTimeHigh}%`)}`);
2508
- console.log(` All-time low: ${chalk9.red(`${extended.allTimeLow}%`)}`);
2681
+ console.log(` All-time high: ${chalk11.green(`${extended.allTimeHigh}%`)}`);
2682
+ console.log(` All-time low: ${chalk11.red(`${extended.allTimeLow}%`)}`);
2509
2683
  if (extended.dataRange) {
2510
2684
  const startDate = formatWeekDate(extended.dataRange.start);
2511
2685
  const endDate = formatWeekDate(extended.dataRange.end);
2512
- console.log(chalk9.gray(` Data range: ${startDate} - ${endDate}`));
2686
+ console.log(chalk11.gray(` Data range: ${startDate} - ${endDate}`));
2513
2687
  }
2514
2688
  console.log("");
2515
2689
  if (options.weekly && extended.weeklySummaries.length > 0) {
2516
- console.log(chalk9.bold("Weekly Summary"));
2690
+ console.log(chalk11.bold("Weekly Summary"));
2517
2691
  const weekLimit = Math.min(extended.weeklySummaries.length, 8);
2518
2692
  for (let i = 0;i < weekLimit; i++) {
2519
2693
  const week = extended.weeklySummaries[i];
2520
2694
  const weekStart = formatWeekDate(week.weekStart);
2521
2695
  const weekEnd = formatWeekDate(week.weekEnd);
2522
- const deltaColor = week.delta > 0 ? chalk9.green : week.delta < 0 ? chalk9.red : chalk9.gray;
2696
+ const deltaColor = week.delta > 0 ? chalk11.green : week.delta < 0 ? chalk11.red : chalk11.gray;
2523
2697
  const deltaStr = week.delta > 0 ? `+${week.delta}%` : `${week.delta}%`;
2524
2698
  console.log(` ${weekStart} - ${weekEnd}: ${week.avgCoverage}% avg ${deltaColor(deltaStr)} (${week.snapshotCount} snapshots)`);
2525
2699
  }
2526
2700
  if (extended.weeklySummaries.length > weekLimit) {
2527
- console.log(chalk9.gray(` ... and ${extended.weeklySummaries.length - weekLimit} more weeks`));
2701
+ console.log(chalk11.gray(` ... and ${extended.weeklySummaries.length - weekLimit} more weeks`));
2528
2702
  }
2529
2703
  console.log("");
2530
2704
  }
2531
2705
  } catch {
2532
- console.log(chalk9.yellow("Could not load openpkg.json for extended analysis"));
2706
+ console.log(chalk11.yellow("Could not load openpkg.json for extended analysis"));
2533
2707
  console.log("");
2534
2708
  }
2535
2709
  }
2536
2710
  }
2537
- console.log(chalk9.bold("History"));
2711
+ console.log(chalk11.bold("History"));
2538
2712
  const displaySnapshots = snapshots.slice(0, limit);
2539
2713
  for (let i = 0;i < displaySnapshots.length; i++) {
2540
2714
  const snapshot = displaySnapshots[i];
2541
- const prefix = i === 0 ? chalk9.cyan("→") : " ";
2715
+ const prefix = i === 0 ? chalk11.cyan("→") : " ";
2542
2716
  console.log(`${prefix} ${formatSnapshot(snapshot)}`);
2543
2717
  }
2544
2718
  if (snapshots.length > limit) {
2545
- console.log(chalk9.gray(` ... and ${snapshots.length - limit} more`));
2719
+ console.log(chalk11.gray(` ... and ${snapshots.length - limit} more`));
2546
2720
  }
2547
2721
  });
2548
2722
  }
2549
2723
 
2550
2724
  // src/cli.ts
2551
2725
  var __filename2 = fileURLToPath(import.meta.url);
2552
- var __dirname2 = path9.dirname(__filename2);
2553
- var packageJson = JSON.parse(readFileSync5(path9.join(__dirname2, "../package.json"), "utf-8"));
2726
+ var __dirname2 = path10.dirname(__filename2);
2727
+ var packageJson = JSON.parse(readFileSync5(path10.join(__dirname2, "../package.json"), "utf-8"));
2554
2728
  var program = new Command;
2555
2729
  program.name("doccov").description("DocCov - Documentation coverage and drift detection for TypeScript").version(packageJson.version);
2556
2730
  registerCheckCommand(program);