@doccov/cli 0.25.8 → 0.25.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +91 -20
  2. package/dist/cli.js +1200 -270
  3. package/package.json +3 -2
package/dist/cli.js CHANGED
@@ -151,6 +151,996 @@ import * as path10 from "node:path";
151
151
  import { fileURLToPath } from "node:url";
152
152
  import { Command } from "commander";
153
153
 
154
+ // ../cli-utils/dist/index.js
155
+ import chalk from "chalk";
156
+ import chalk2 from "chalk";
157
+ import { Worker } from "node:worker_threads";
158
+ var colors = {
159
+ success: chalk.green,
160
+ error: chalk.red,
161
+ warning: chalk.yellow,
162
+ info: chalk.cyan,
163
+ muted: chalk.gray,
164
+ bold: chalk.bold,
165
+ dim: chalk.dim,
166
+ underline: chalk.underline,
167
+ primary: chalk.cyan,
168
+ secondary: chalk.magenta,
169
+ path: chalk.cyan,
170
+ number: chalk.yellow,
171
+ code: chalk.gray
172
+ };
173
+ var symbols = {
174
+ success: "✓",
175
+ error: "✗",
176
+ warning: "⚠",
177
+ info: "ℹ",
178
+ spinner: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
179
+ bullet: "•",
180
+ arrow: "→",
181
+ arrowRight: "›",
182
+ line: "─",
183
+ corner: "└",
184
+ vertical: "│",
185
+ horizontalLine: "─"
186
+ };
187
+ var asciiSymbols = {
188
+ success: "+",
189
+ error: "x",
190
+ warning: "!",
191
+ info: "i",
192
+ spinner: ["-", "\\", "|", "/"],
193
+ bullet: "*",
194
+ arrow: "->",
195
+ arrowRight: ">",
196
+ line: "-",
197
+ corner: "\\",
198
+ vertical: "|",
199
+ horizontalLine: "-"
200
+ };
201
+ function getSymbols(unicodeSupport = true) {
202
+ return unicodeSupport ? symbols : asciiSymbols;
203
+ }
204
+ var prefix = {
205
+ success: colors.success(symbols.success),
206
+ error: colors.error(symbols.error),
207
+ warning: colors.warning(symbols.warning),
208
+ info: colors.info(symbols.info)
209
+ };
210
+ function isTTY() {
211
+ return Boolean(process.stdout.isTTY);
212
+ }
213
+ function isCI() {
214
+ return Boolean(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.CIRCLECI || process.env.TRAVIS || process.env.JENKINS_URL || process.env.BUILDKITE || process.env.TEAMCITY_VERSION || process.env.TF_BUILD || process.env.CODEBUILD_BUILD_ID || process.env.BITBUCKET_BUILD_NUMBER);
215
+ }
216
+ function isInteractive() {
217
+ return isTTY() && !isCI();
218
+ }
219
+ function supportsUnicode() {
220
+ if (process.platform === "win32") {
221
+ return Boolean(process.env.WT_SESSION) || process.env.TERM_PROGRAM === "vscode";
222
+ }
223
+ return process.env.TERM !== "linux";
224
+ }
225
+ var MIN_TERMINAL_WIDTH = 40;
226
+ var DEFAULT_TERMINAL_WIDTH = 80;
227
+ function getTerminalWidth() {
228
+ const width = process.stdout.columns || DEFAULT_TERMINAL_WIDTH;
229
+ return Math.max(width, MIN_TERMINAL_WIDTH);
230
+ }
231
+ function formatDuration(ms) {
232
+ if (ms < 1000) {
233
+ return `${ms}ms`;
234
+ }
235
+ const seconds = ms / 1000;
236
+ if (seconds < 60) {
237
+ return `${seconds.toFixed(1)}s`;
238
+ }
239
+ const minutes = Math.floor(seconds / 60);
240
+ const remainingSeconds = Math.floor(seconds % 60);
241
+ return `${minutes}m ${remainingSeconds}s`;
242
+ }
243
+ var cursor = {
244
+ hide: "\x1B[?25l",
245
+ show: "\x1B[?25h",
246
+ up: (n = 1) => `\x1B[${n}A`,
247
+ down: (n = 1) => `\x1B[${n}B`,
248
+ forward: (n = 1) => `\x1B[${n}C`,
249
+ back: (n = 1) => `\x1B[${n}D`,
250
+ left: "\x1B[G",
251
+ clearLine: "\x1B[2K",
252
+ clearDown: "\x1B[J",
253
+ save: "\x1B[s",
254
+ restore: "\x1B[u"
255
+ };
256
+ function clearLine() {
257
+ if (isTTY()) {
258
+ process.stdout.write(cursor.clearLine + cursor.left);
259
+ }
260
+ }
261
+ function moveCursorUp(lines = 1) {
262
+ if (isTTY()) {
263
+ process.stdout.write(cursor.up(lines));
264
+ }
265
+ }
266
+ function hideCursor() {
267
+ if (isTTY()) {
268
+ process.stdout.write(cursor.hide);
269
+ }
270
+ }
271
+ function showCursor() {
272
+ if (isTTY()) {
273
+ process.stdout.write(cursor.show);
274
+ }
275
+ }
276
+ function truncate(text, maxLength) {
277
+ if (text.length <= maxLength)
278
+ return text;
279
+ return `${text.slice(0, maxLength - 1)}…`;
280
+ }
281
+ var ANSI_REGEX = /\x1B\[[0-9;]*[a-zA-Z]/g;
282
+ function stripAnsi(text) {
283
+ return text.replace(ANSI_REGEX, "");
284
+ }
285
+
286
+ class MultiProgress {
287
+ bars = new Map;
288
+ barOrder = [];
289
+ options;
290
+ spinnerFrames;
291
+ spinnerIndex = 0;
292
+ timer = null;
293
+ lastRenderedLines = 0;
294
+ symbols = getSymbols(supportsUnicode());
295
+ sigintHandler = null;
296
+ filledChar;
297
+ emptyChar;
298
+ constructor(options = {}) {
299
+ this.options = {
300
+ barWidth: options.barWidth,
301
+ showPercent: options.showPercent ?? true,
302
+ showCount: options.showCount ?? true,
303
+ spinnerInterval: options.spinnerInterval ?? 80
304
+ };
305
+ this.spinnerFrames = supportsUnicode() ? ["◐", "◓", "◑", "◒"] : ["-", "\\", "|", "/"];
306
+ const unicode = supportsUnicode();
307
+ this.filledChar = unicode ? "█" : "#";
308
+ this.emptyChar = unicode ? "░" : "-";
309
+ }
310
+ start() {
311
+ if (!isInteractive())
312
+ return this;
313
+ hideCursor();
314
+ this.setupSignalHandler();
315
+ this.timer = setInterval(() => {
316
+ if (this.bars.size > 0 && [...this.bars.values()].some((b) => b.status === "active")) {
317
+ this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;
318
+ this.render();
319
+ }
320
+ }, this.options.spinnerInterval);
321
+ return this;
322
+ }
323
+ add(config) {
324
+ const state = {
325
+ id: config.id,
326
+ label: config.label,
327
+ total: config.total ?? 100,
328
+ current: config.current ?? 0,
329
+ status: "active",
330
+ startTime: Date.now()
331
+ };
332
+ this.bars.set(config.id, state);
333
+ if (!this.barOrder.includes(config.id)) {
334
+ this.barOrder.push(config.id);
335
+ }
336
+ if (!isInteractive()) {
337
+ console.log(`${this.symbols.bullet} ${config.label}`);
338
+ } else {
339
+ this.render();
340
+ }
341
+ return this;
342
+ }
343
+ update(id, current, label) {
344
+ const bar = this.bars.get(id);
345
+ if (!bar)
346
+ return this;
347
+ bar.current = Math.min(current, bar.total);
348
+ if (label !== undefined)
349
+ bar.label = label;
350
+ if (isInteractive()) {
351
+ this.render();
352
+ }
353
+ return this;
354
+ }
355
+ increment(id, amount = 1) {
356
+ const bar = this.bars.get(id);
357
+ if (!bar)
358
+ return this;
359
+ return this.update(id, bar.current + amount);
360
+ }
361
+ complete(id, label) {
362
+ const bar = this.bars.get(id);
363
+ if (!bar)
364
+ return this;
365
+ bar.status = "completed";
366
+ bar.current = bar.total;
367
+ if (label !== undefined)
368
+ bar.label = label;
369
+ if (!isInteractive()) {
370
+ console.log(`${colors.success(this.symbols.success)} ${bar.label}`);
371
+ } else {
372
+ this.render();
373
+ }
374
+ return this;
375
+ }
376
+ fail(id, label) {
377
+ const bar = this.bars.get(id);
378
+ if (!bar)
379
+ return this;
380
+ bar.status = "failed";
381
+ if (label !== undefined)
382
+ bar.label = label;
383
+ if (!isInteractive()) {
384
+ console.log(`${colors.error(this.symbols.error)} ${bar.label}`);
385
+ } else {
386
+ this.render();
387
+ }
388
+ return this;
389
+ }
390
+ remove(id) {
391
+ this.bars.delete(id);
392
+ this.barOrder = this.barOrder.filter((i) => i !== id);
393
+ if (isInteractive()) {
394
+ this.render();
395
+ }
396
+ return this;
397
+ }
398
+ get(id) {
399
+ return this.bars.get(id);
400
+ }
401
+ get allDone() {
402
+ if (this.bars.size === 0)
403
+ return true;
404
+ return [...this.bars.values()].every((b) => b.status !== "active");
405
+ }
406
+ stop() {
407
+ if (this.timer) {
408
+ clearInterval(this.timer);
409
+ this.timer = null;
410
+ }
411
+ this.cleanup();
412
+ return this;
413
+ }
414
+ render() {
415
+ if (!isTTY())
416
+ return;
417
+ this.clearOutput();
418
+ const width = getTerminalWidth();
419
+ const lines = [];
420
+ for (const id of this.barOrder) {
421
+ const bar = this.bars.get(id);
422
+ if (!bar)
423
+ continue;
424
+ const line = this.renderBar(bar, width);
425
+ lines.push(line);
426
+ }
427
+ if (lines.length > 0) {
428
+ process.stdout.write(lines.join(`
429
+ `));
430
+ this.lastRenderedLines = lines.length;
431
+ }
432
+ }
433
+ renderBar(bar, termWidth) {
434
+ const parts = [];
435
+ let symbol;
436
+ switch (bar.status) {
437
+ case "completed":
438
+ symbol = colors.success(this.symbols.success);
439
+ break;
440
+ case "failed":
441
+ symbol = colors.error(this.symbols.error);
442
+ break;
443
+ default:
444
+ symbol = colors.primary(this.spinnerFrames[this.spinnerIndex]);
445
+ }
446
+ parts.push(symbol);
447
+ parts.push(bar.label);
448
+ const suffixParts = [];
449
+ if (this.options.showPercent) {
450
+ const pct = bar.total === 0 ? 0 : Math.round(bar.current / bar.total * 100);
451
+ suffixParts.push(`${pct}%`);
452
+ }
453
+ if (this.options.showCount) {
454
+ suffixParts.push(`${bar.current}/${bar.total}`);
455
+ }
456
+ const suffix = suffixParts.length > 0 ? ` ${suffixParts.join(" ")}` : "";
457
+ const labelLen = stripAnsi(parts.join(" ")).length + 1;
458
+ const bracketLen = 2;
459
+ const suffixLen = stripAnsi(suffix).length;
460
+ const minBarWidth = 10;
461
+ const availableWidth = termWidth - labelLen - bracketLen - suffixLen - 1;
462
+ const barWidth = this.options.barWidth ?? Math.max(minBarWidth, Math.min(30, availableWidth));
463
+ const filledWidth = Math.round(bar.current / bar.total * barWidth);
464
+ const emptyWidth = barWidth - filledWidth;
465
+ const barViz = `[${this.filledChar.repeat(filledWidth)}${this.emptyChar.repeat(emptyWidth)}]`;
466
+ parts.push(barViz);
467
+ return truncate(parts.join(" ") + suffix, termWidth);
468
+ }
469
+ clearOutput() {
470
+ if (!isTTY() || this.lastRenderedLines === 0)
471
+ return;
472
+ for (let i = 0;i < this.lastRenderedLines; i++) {
473
+ if (i > 0)
474
+ process.stdout.write(cursor.up(1));
475
+ clearLine();
476
+ }
477
+ this.lastRenderedLines = 0;
478
+ }
479
+ setupSignalHandler() {
480
+ this.sigintHandler = () => {
481
+ this.cleanup();
482
+ process.exit(130);
483
+ };
484
+ process.on("SIGINT", this.sigintHandler);
485
+ }
486
+ cleanup() {
487
+ if (this.sigintHandler) {
488
+ process.removeListener("SIGINT", this.sigintHandler);
489
+ this.sigintHandler = null;
490
+ }
491
+ showCursor();
492
+ if (isTTY() && this.lastRenderedLines > 0) {
493
+ process.stdout.write(`
494
+ `);
495
+ }
496
+ }
497
+ }
498
+ class ProgressBar {
499
+ total;
500
+ current;
501
+ label;
502
+ width;
503
+ showPercent;
504
+ showCount;
505
+ showETA;
506
+ filledChar;
507
+ emptyChar;
508
+ startTime = null;
509
+ lastRender = "";
510
+ symbols = getSymbols(supportsUnicode());
511
+ sigintHandler = null;
512
+ isComplete = false;
513
+ constructor(options = {}) {
514
+ this.total = options.total ?? 100;
515
+ this.current = options.current ?? 0;
516
+ this.label = options.label ?? "";
517
+ this.width = options.width;
518
+ this.showPercent = options.showPercent ?? true;
519
+ this.showCount = options.showCount ?? true;
520
+ this.showETA = options.showETA ?? true;
521
+ const unicode = supportsUnicode();
522
+ this.filledChar = options.chars?.filled ?? (unicode ? "█" : "#");
523
+ this.emptyChar = options.chars?.empty ?? (unicode ? "░" : "-");
524
+ }
525
+ start(label) {
526
+ if (label !== undefined)
527
+ this.label = label;
528
+ this.startTime = Date.now();
529
+ this.current = 0;
530
+ this.isComplete = false;
531
+ if (!isInteractive()) {
532
+ console.log(`${this.symbols.bullet} ${this.label}`);
533
+ return this;
534
+ }
535
+ hideCursor();
536
+ this.setupSignalHandler();
537
+ this.render();
538
+ return this;
539
+ }
540
+ update(current) {
541
+ this.current = Math.min(current, this.total);
542
+ if (isInteractive()) {
543
+ this.render();
544
+ }
545
+ return this;
546
+ }
547
+ increment(amount = 1) {
548
+ return this.update(this.current + amount);
549
+ }
550
+ setLabel(label) {
551
+ this.label = label;
552
+ if (isInteractive()) {
553
+ this.render();
554
+ }
555
+ return this;
556
+ }
557
+ setTotal(total) {
558
+ this.total = total;
559
+ if (isInteractive()) {
560
+ this.render();
561
+ }
562
+ return this;
563
+ }
564
+ complete(label) {
565
+ if (label !== undefined)
566
+ this.label = label;
567
+ this.current = this.total;
568
+ this.isComplete = true;
569
+ if (!isInteractive()) {
570
+ console.log(`${colors.success(this.symbols.success)} ${this.label}`);
571
+ } else {
572
+ clearLine();
573
+ process.stdout.write(`${colors.success(this.symbols.success)} ${this.label}
574
+ `);
575
+ }
576
+ this.cleanup();
577
+ return this;
578
+ }
579
+ fail(label) {
580
+ if (label !== undefined)
581
+ this.label = label;
582
+ this.isComplete = true;
583
+ if (!isInteractive()) {
584
+ console.log(`${colors.error(this.symbols.error)} ${this.label}`);
585
+ } else {
586
+ clearLine();
587
+ process.stdout.write(`${colors.error(this.symbols.error)} ${this.label}
588
+ `);
589
+ }
590
+ this.cleanup();
591
+ return this;
592
+ }
593
+ get percentage() {
594
+ return this.total === 0 ? 0 : Math.round(this.current / this.total * 100);
595
+ }
596
+ get isDone() {
597
+ return this.isComplete || this.current >= this.total;
598
+ }
599
+ render() {
600
+ if (!isTTY())
601
+ return;
602
+ const termWidth = getTerminalWidth();
603
+ const parts = [];
604
+ if (this.label) {
605
+ parts.push(this.label);
606
+ }
607
+ const suffixParts = [];
608
+ if (this.showPercent) {
609
+ suffixParts.push(`${this.percentage}%`);
610
+ }
611
+ if (this.showCount) {
612
+ suffixParts.push(`${this.current}/${this.total}`);
613
+ }
614
+ if (this.showETA && this.startTime) {
615
+ const eta = this.calculateETA();
616
+ if (eta)
617
+ suffixParts.push(eta);
618
+ }
619
+ const suffix = suffixParts.length > 0 ? ` ${suffixParts.join(" ")}` : "";
620
+ const labelLen = this.label ? stripAnsi(this.label).length + 1 : 0;
621
+ const bracketLen = 2;
622
+ const suffixLen = stripAnsi(suffix).length;
623
+ const minBarWidth = 10;
624
+ const availableWidth = termWidth - labelLen - bracketLen - suffixLen - 1;
625
+ const barWidth = this.width ?? Math.max(minBarWidth, Math.min(40, availableWidth));
626
+ const filledWidth = Math.round(this.current / this.total * barWidth);
627
+ const emptyWidth = barWidth - filledWidth;
628
+ const bar = `[${this.filledChar.repeat(filledWidth)}${this.emptyChar.repeat(emptyWidth)}]`;
629
+ parts.push(bar);
630
+ const line = truncate(parts.join(" ") + suffix, termWidth);
631
+ if (line !== this.lastRender) {
632
+ clearLine();
633
+ process.stdout.write(line);
634
+ this.lastRender = line;
635
+ }
636
+ }
637
+ calculateETA() {
638
+ if (!this.startTime || this.current === 0)
639
+ return null;
640
+ const elapsed = Date.now() - this.startTime;
641
+ if (elapsed < 1000)
642
+ return null;
643
+ const rate = this.current / elapsed;
644
+ const remaining = this.total - this.current;
645
+ const etaMs = remaining / rate;
646
+ return `ETA ${formatDuration(etaMs)}`;
647
+ }
648
+ setupSignalHandler() {
649
+ this.sigintHandler = () => {
650
+ this.cleanup();
651
+ process.exit(130);
652
+ };
653
+ process.on("SIGINT", this.sigintHandler);
654
+ }
655
+ cleanup() {
656
+ if (this.sigintHandler) {
657
+ process.removeListener("SIGINT", this.sigintHandler);
658
+ this.sigintHandler = null;
659
+ }
660
+ showCursor();
661
+ }
662
+ }
663
+ var spinnerColors = {
664
+ cyan: chalk2.cyan,
665
+ yellow: chalk2.yellow,
666
+ green: chalk2.green,
667
+ red: chalk2.red,
668
+ magenta: chalk2.magenta,
669
+ blue: chalk2.blue,
670
+ white: chalk2.white
671
+ };
672
+ var FRAME_SETS = {
673
+ dots: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
674
+ circle: ["◐", "◓", "◑", "◒"]
675
+ };
676
+ var ASCII_FRAME_SET = ["-", "\\", "|", "/"];
677
+
678
+ class Spinner {
679
+ label;
680
+ detail;
681
+ frames;
682
+ interval;
683
+ colorFn;
684
+ frameIndex = 0;
685
+ timer = null;
686
+ state = "stopped";
687
+ symbols = getSymbols(supportsUnicode());
688
+ lastRenderedLines = 0;
689
+ sigintHandler = null;
690
+ animate;
691
+ constructor(options = {}) {
692
+ this.label = options.label ?? "";
693
+ this.detail = options.detail;
694
+ this.interval = options.interval ?? 80;
695
+ this.colorFn = spinnerColors[options.color ?? "cyan"];
696
+ this.animate = options.animate ?? true;
697
+ const style = options.style ?? "circle";
698
+ this.frames = supportsUnicode() ? FRAME_SETS[style] : ASCII_FRAME_SET;
699
+ }
700
+ start(label) {
701
+ if (label !== undefined)
702
+ this.label = label;
703
+ if (this.state === "spinning")
704
+ return this;
705
+ this.state = "spinning";
706
+ this.frameIndex = 0;
707
+ this.lastRenderedLines = 0;
708
+ if (!isInteractive() || !this.animate) {
709
+ console.log(`${this.symbols.bullet} ${this.label}`);
710
+ return this;
711
+ }
712
+ hideCursor();
713
+ this.setupSignalHandler();
714
+ this.render();
715
+ this.timer = setInterval(() => {
716
+ this.frameIndex = (this.frameIndex + 1) % this.frames.length;
717
+ this.render();
718
+ }, this.interval);
719
+ return this;
720
+ }
721
+ stop() {
722
+ if (this.timer) {
723
+ clearInterval(this.timer);
724
+ this.timer = null;
725
+ }
726
+ this.state = "stopped";
727
+ this.clearOutput();
728
+ this.cleanup();
729
+ return this;
730
+ }
731
+ success(label) {
732
+ if (label !== undefined)
733
+ this.label = label;
734
+ this.finish("success");
735
+ return this;
736
+ }
737
+ fail(label) {
738
+ if (label !== undefined)
739
+ this.label = label;
740
+ this.finish("error");
741
+ return this;
742
+ }
743
+ update(label) {
744
+ this.label = label;
745
+ if (this.state === "spinning" && isInteractive()) {
746
+ this.render();
747
+ }
748
+ return this;
749
+ }
750
+ setDetail(detail) {
751
+ this.detail = detail;
752
+ if (this.state === "spinning" && isInteractive()) {
753
+ this.render();
754
+ }
755
+ return this;
756
+ }
757
+ get isSpinning() {
758
+ return this.state === "spinning";
759
+ }
760
+ finish(state) {
761
+ if (this.timer) {
762
+ clearInterval(this.timer);
763
+ this.timer = null;
764
+ }
765
+ this.state = state;
766
+ const symbol = state === "success" ? this.symbols.success : this.symbols.error;
767
+ const colorFn = state === "success" ? colors.success : colors.error;
768
+ if (!isInteractive() || !this.animate) {
769
+ console.log(`${colorFn(symbol)} ${this.label}`);
770
+ } else {
771
+ this.clearOutput();
772
+ process.stdout.write(`${colorFn(symbol)} ${this.label}
773
+ `);
774
+ }
775
+ this.cleanup();
776
+ }
777
+ render() {
778
+ if (!isTTY())
779
+ return;
780
+ this.clearOutput();
781
+ const frame = this.colorFn(this.frames[this.frameIndex]);
782
+ const width = getTerminalWidth();
783
+ const mainLine = truncate(`${frame} ${this.label}`, width);
784
+ process.stdout.write(mainLine);
785
+ let lines = 1;
786
+ if (this.detail) {
787
+ const detailLine = truncate(` ${colors.muted(this.detail)}`, width);
788
+ process.stdout.write(`
789
+ ${detailLine}`);
790
+ lines = 2;
791
+ }
792
+ this.lastRenderedLines = lines;
793
+ }
794
+ clearOutput() {
795
+ if (!isTTY())
796
+ return;
797
+ for (let i = 0;i < this.lastRenderedLines; i++) {
798
+ if (i > 0)
799
+ process.stdout.write(cursor.up(1));
800
+ clearLine();
801
+ }
802
+ }
803
+ setupSignalHandler() {
804
+ this.sigintHandler = () => {
805
+ this.cleanup();
806
+ process.exit(130);
807
+ };
808
+ process.on("SIGINT", this.sigintHandler);
809
+ }
810
+ cleanup() {
811
+ if (this.sigintHandler) {
812
+ process.removeListener("SIGINT", this.sigintHandler);
813
+ this.sigintHandler = null;
814
+ }
815
+ showCursor();
816
+ }
817
+ }
818
+ function spinner(label, options) {
819
+ return new Spinner({ ...options, label }).start();
820
+ }
821
+
822
+ class StepProgress {
823
+ steps = [];
824
+ showNumbers;
825
+ spinnerInterval;
826
+ spinnerFrames;
827
+ spinnerIndex = 0;
828
+ timer = null;
829
+ symbols = getSymbols(supportsUnicode());
830
+ lastRenderedLines = 0;
831
+ sigintHandler = null;
832
+ constructor(options = {}) {
833
+ this.showNumbers = options.showNumbers ?? true;
834
+ this.spinnerInterval = options.spinnerInterval ?? 80;
835
+ this.spinnerFrames = supportsUnicode() ? ["◐", "◓", "◑", "◒"] : ["-", "\\", "|", "/"];
836
+ if (options.steps) {
837
+ this.steps = options.steps.map((label) => ({ label, status: "pending" }));
838
+ }
839
+ }
840
+ start() {
841
+ if (!isInteractive())
842
+ return this;
843
+ hideCursor();
844
+ this.setupSignalHandler();
845
+ this.render();
846
+ this.timer = setInterval(() => {
847
+ if (this.steps.some((s) => s.status === "active")) {
848
+ this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;
849
+ this.render();
850
+ }
851
+ }, this.spinnerInterval);
852
+ return this;
853
+ }
854
+ addStep(label) {
855
+ this.steps.push({ label, status: "pending" });
856
+ if (isInteractive())
857
+ this.render();
858
+ return this;
859
+ }
860
+ startStep(index) {
861
+ if (index >= 0 && index < this.steps.length) {
862
+ this.steps[index].status = "active";
863
+ this.steps[index].startTime = Date.now();
864
+ if (isInteractive()) {
865
+ this.render();
866
+ } else {
867
+ const step = this.steps[index];
868
+ const prefix2 = this.showNumbers ? `[${index + 1}/${this.steps.length}] ` : "";
869
+ console.log(`${this.symbols.bullet} ${prefix2}${step.label}...`);
870
+ }
871
+ }
872
+ return this;
873
+ }
874
+ completeStep(index) {
875
+ if (index >= 0 && index < this.steps.length) {
876
+ this.steps[index].status = "completed";
877
+ this.steps[index].endTime = Date.now();
878
+ if (isInteractive()) {
879
+ this.render();
880
+ } else {
881
+ const step = this.steps[index];
882
+ const duration = this.getStepDuration(step);
883
+ const prefix2 = this.showNumbers ? `[${index + 1}/${this.steps.length}] ` : "";
884
+ console.log(`${colors.success(this.symbols.success)} ${prefix2}${step.label}${duration}`);
885
+ }
886
+ }
887
+ return this;
888
+ }
889
+ failStep(index) {
890
+ if (index >= 0 && index < this.steps.length) {
891
+ this.steps[index].status = "failed";
892
+ this.steps[index].endTime = Date.now();
893
+ if (isInteractive()) {
894
+ this.render();
895
+ } else {
896
+ const step = this.steps[index];
897
+ const prefix2 = this.showNumbers ? `[${index + 1}/${this.steps.length}] ` : "";
898
+ console.log(`${colors.error(this.symbols.error)} ${prefix2}${step.label}`);
899
+ }
900
+ }
901
+ return this;
902
+ }
903
+ skipStep(index) {
904
+ if (index >= 0 && index < this.steps.length) {
905
+ this.steps[index].status = "skipped";
906
+ if (isInteractive())
907
+ this.render();
908
+ }
909
+ return this;
910
+ }
911
+ async run(tasks) {
912
+ this.steps = tasks.map((t) => ({ label: t.label, status: "pending" }));
913
+ this.start();
914
+ const results = [];
915
+ let failed = false;
916
+ for (let i = 0;i < tasks.length; i++) {
917
+ if (failed) {
918
+ this.skipStep(i);
919
+ continue;
920
+ }
921
+ this.startStep(i);
922
+ try {
923
+ results.push(await tasks[i].task());
924
+ this.completeStep(i);
925
+ } catch {
926
+ this.failStep(i);
927
+ failed = true;
928
+ }
929
+ }
930
+ this.stop();
931
+ return { results, failed };
932
+ }
933
+ stop() {
934
+ if (this.timer) {
935
+ clearInterval(this.timer);
936
+ this.timer = null;
937
+ }
938
+ this.cleanup();
939
+ return this;
940
+ }
941
+ get currentStepIndex() {
942
+ const activeIdx = this.steps.findIndex((s) => s.status === "active");
943
+ if (activeIdx >= 0)
944
+ return activeIdx;
945
+ return this.steps.findIndex((s) => s.status === "pending");
946
+ }
947
+ render() {
948
+ if (!isTTY())
949
+ return;
950
+ if (this.lastRenderedLines > 0) {
951
+ moveCursorUp(this.lastRenderedLines - 1);
952
+ for (let i = 0;i < this.lastRenderedLines; i++) {
953
+ clearLine();
954
+ if (i < this.lastRenderedLines - 1) {
955
+ process.stdout.write(cursor.down(1));
956
+ }
957
+ }
958
+ moveCursorUp(this.lastRenderedLines - 1);
959
+ }
960
+ const width = getTerminalWidth();
961
+ const lines = [];
962
+ for (let i = 0;i < this.steps.length; i++) {
963
+ const step = this.steps[i];
964
+ const prefix2 = this.showNumbers ? `[${i + 1}/${this.steps.length}] ` : "";
965
+ const duration = this.getStepDuration(step);
966
+ let symbol;
967
+ let text;
968
+ switch (step.status) {
969
+ case "completed":
970
+ symbol = colors.success(this.symbols.success);
971
+ text = `${prefix2}${step.label}${duration}`;
972
+ break;
973
+ case "failed":
974
+ symbol = colors.error(this.symbols.error);
975
+ text = `${prefix2}${step.label}`;
976
+ break;
977
+ case "active":
978
+ symbol = colors.primary(this.spinnerFrames[this.spinnerIndex]);
979
+ text = `${prefix2}${step.label}`;
980
+ break;
981
+ case "skipped":
982
+ symbol = colors.muted(this.symbols.bullet);
983
+ text = colors.muted(`${prefix2}${step.label} (skipped)`);
984
+ break;
985
+ default:
986
+ symbol = colors.muted("○");
987
+ text = colors.muted(`${prefix2}${step.label}`);
988
+ }
989
+ lines.push(truncate(`${symbol} ${text}`, width));
990
+ }
991
+ process.stdout.write(lines.join(`
992
+ `));
993
+ this.lastRenderedLines = lines.length;
994
+ }
995
+ getStepDuration(step) {
996
+ if (step.startTime && step.endTime) {
997
+ const ms = step.endTime - step.startTime;
998
+ return colors.muted(` (${formatDuration(ms)})`);
999
+ }
1000
+ return "";
1001
+ }
1002
+ setupSignalHandler() {
1003
+ this.sigintHandler = () => {
1004
+ this.cleanup();
1005
+ process.exit(130);
1006
+ };
1007
+ process.on("SIGINT", this.sigintHandler);
1008
+ }
1009
+ cleanup() {
1010
+ if (this.sigintHandler) {
1011
+ process.removeListener("SIGINT", this.sigintHandler);
1012
+ this.sigintHandler = null;
1013
+ }
1014
+ showCursor();
1015
+ if (isTTY() && this.lastRenderedLines > 0) {
1016
+ process.stdout.write(`
1017
+ `);
1018
+ }
1019
+ }
1020
+ }
1021
+ class Summary {
1022
+ items = [];
1023
+ title;
1024
+ boxed;
1025
+ keyWidth;
1026
+ symbols = getSymbols(supportsUnicode());
1027
+ constructor(options = {}) {
1028
+ this.title = options.title;
1029
+ this.boxed = options.boxed ?? false;
1030
+ this.keyWidth = options.keyWidth;
1031
+ }
1032
+ add(item) {
1033
+ this.items.push(item);
1034
+ return this;
1035
+ }
1036
+ addKeyValue(key, value, status) {
1037
+ return this.add({ key, value, status });
1038
+ }
1039
+ addWithThreshold(key, value, threshold) {
1040
+ const status = this.evaluateThreshold(value, threshold) ? "pass" : "fail";
1041
+ return this.add({ key, value, status, threshold });
1042
+ }
1043
+ print() {
1044
+ const output = this.render();
1045
+ console.log(output);
1046
+ }
1047
+ render() {
1048
+ if (this.items.length === 0)
1049
+ return "";
1050
+ const lines = [];
1051
+ const termWidth = getTerminalWidth();
1052
+ const calculatedKeyWidth = this.keyWidth ?? Math.max(...this.items.map((i) => i.key.length));
1053
+ if (this.boxed) {
1054
+ return this.renderBoxed(calculatedKeyWidth, termWidth);
1055
+ }
1056
+ if (this.title) {
1057
+ lines.push(colors.bold(this.title));
1058
+ lines.push("");
1059
+ }
1060
+ for (const item of this.items) {
1061
+ lines.push(this.formatItem(item, calculatedKeyWidth));
1062
+ }
1063
+ return lines.join(`
1064
+ `);
1065
+ }
1066
+ renderBoxed(keyWidth, termWidth) {
1067
+ const lines = [];
1068
+ const unicode = supportsUnicode();
1069
+ const box = unicode ? { tl: "┌", tr: "┐", bl: "└", br: "┘", h: "─", v: "│" } : { tl: "+", tr: "+", bl: "+", br: "+", h: "-", v: "|" };
1070
+ const contentLines = this.items.map((item) => this.formatItem(item, keyWidth));
1071
+ const maxContentWidth = Math.max(...contentLines.map((l) => stripAnsi(l).length), this.title ? this.title.length : 0);
1072
+ const innerWidth = Math.min(maxContentWidth + 2, termWidth - 4);
1073
+ lines.push(box.tl + box.h.repeat(innerWidth) + box.tr);
1074
+ if (this.title) {
1075
+ const padding = innerWidth - this.title.length;
1076
+ const leftPad = Math.floor(padding / 2);
1077
+ const rightPad = padding - leftPad;
1078
+ lines.push(`${box.v}${" ".repeat(leftPad)}${colors.bold(this.title)}${" ".repeat(rightPad)}${box.v}`);
1079
+ lines.push(`${box.v}${" ".repeat(innerWidth)}${box.v}`);
1080
+ }
1081
+ for (const line of contentLines) {
1082
+ const visibleLen = stripAnsi(line).length;
1083
+ const padding = innerWidth - visibleLen - 1;
1084
+ lines.push(`${box.v} ${line}${" ".repeat(Math.max(0, padding))}${box.v}`);
1085
+ }
1086
+ lines.push(box.bl + box.h.repeat(innerWidth) + box.br);
1087
+ return lines.join(`
1088
+ `);
1089
+ }
1090
+ formatItem(item, keyWidth) {
1091
+ const key = item.key.padEnd(keyWidth);
1092
+ const value = String(item.value);
1093
+ let indicator = "";
1094
+ if (item.status) {
1095
+ switch (item.status) {
1096
+ case "pass":
1097
+ indicator = `${colors.success(this.symbols.success)} `;
1098
+ break;
1099
+ case "fail":
1100
+ indicator = `${colors.error(this.symbols.error)} `;
1101
+ break;
1102
+ case "warn":
1103
+ indicator = `${colors.warning(this.symbols.warning)} `;
1104
+ break;
1105
+ case "info":
1106
+ indicator = `${colors.info(this.symbols.info)} `;
1107
+ break;
1108
+ }
1109
+ }
1110
+ let thresholdStr = "";
1111
+ if (item.threshold) {
1112
+ const { operator, value: thresh } = item.threshold;
1113
+ thresholdStr = colors.muted(` (${operator} ${thresh})`);
1114
+ }
1115
+ let coloredValue = value;
1116
+ if (item.status === "pass")
1117
+ coloredValue = colors.success(value);
1118
+ else if (item.status === "fail")
1119
+ coloredValue = colors.error(value);
1120
+ else if (item.status === "warn")
1121
+ coloredValue = colors.warning(value);
1122
+ return `${indicator}${colors.muted(key)} ${coloredValue}${thresholdStr}`;
1123
+ }
1124
+ evaluateThreshold(value, threshold) {
1125
+ if (!threshold)
1126
+ return true;
1127
+ const { operator, value: thresh } = threshold;
1128
+ switch (operator) {
1129
+ case "<":
1130
+ return value < thresh;
1131
+ case ">":
1132
+ return value > thresh;
1133
+ case "<=":
1134
+ return value <= thresh;
1135
+ case ">=":
1136
+ return value >= thresh;
1137
+ }
1138
+ }
1139
+ }
1140
+ function summary(options) {
1141
+ return new Summary(options);
1142
+ }
1143
+
154
1144
  // src/commands/check/index.ts
155
1145
  import {
156
1146
  buildDocCovSpec,
@@ -159,11 +1149,11 @@ import {
159
1149
  parseExamplesFlag,
160
1150
  resolveTarget
161
1151
  } from "@doccov/sdk";
162
- import chalk6 from "chalk";
1152
+ import chalk7 from "chalk";
163
1153
 
164
1154
  // src/utils/filter-options.ts
165
1155
  import { mergeFilters, parseListFlag } from "@doccov/sdk";
166
- import chalk from "chalk";
1156
+ import chalk3 from "chalk";
167
1157
  var parseVisibilityFlag = (value) => {
168
1158
  if (!value)
169
1159
  return;
@@ -180,7 +1170,7 @@ var parseVisibilityFlag = (value) => {
180
1170
  }
181
1171
  return result.length > 0 ? result : undefined;
182
1172
  };
183
- var formatList = (label, values) => `${label}: ${values.map((value) => chalk.cyan(value)).join(", ")}`;
1173
+ var formatList = (label, values) => `${label}: ${values.map((value) => chalk3.cyan(value)).join(", ")}`;
184
1174
  var mergeFilterOptions = (config, cliOptions) => {
185
1175
  const messages = [];
186
1176
  if (config?.include) {
@@ -212,57 +1202,6 @@ var mergeFilterOptions = (config, cliOptions) => {
212
1202
  };
213
1203
  };
214
1204
 
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
1205
  // src/utils/validation.ts
267
1206
  function clampPercentage(value, fallback = 80) {
268
1207
  if (Number.isNaN(value))
@@ -287,7 +1226,7 @@ import {
287
1226
  parseJSDocToPatch,
288
1227
  serializeJSDoc
289
1228
  } from "@doccov/sdk";
290
- import chalk3 from "chalk";
1229
+ import chalk5 from "chalk";
291
1230
 
292
1231
  // src/commands/check/utils.ts
293
1232
  import * as fs from "node:fs";
@@ -361,13 +1300,13 @@ async function handleFixes(openpkg, doccov, options, deps) {
361
1300
  }
362
1301
  const { fixable, nonFixable } = categorizeDrifts(allDrifts.map((d) => d.drift));
363
1302
  if (fixable.length === 0) {
364
- log(chalk3.yellow(`Found ${nonFixable.length} drift issue(s), but none are auto-fixable.`));
1303
+ log(chalk5.yellow(`Found ${nonFixable.length} drift issue(s), but none are auto-fixable.`));
365
1304
  return { fixedDriftKeys, editsApplied: 0, filesModified: 0 };
366
1305
  }
367
1306
  log("");
368
- log(chalk3.bold(`Found ${fixable.length} fixable issue(s)`));
1307
+ log(chalk5.bold(`Found ${fixable.length} fixable issue(s)`));
369
1308
  if (nonFixable.length > 0) {
370
- log(chalk3.gray(`(${nonFixable.length} non-fixable issue(s) skipped)`));
1309
+ log(chalk5.gray(`(${nonFixable.length} non-fixable issue(s) skipped)`));
371
1310
  }
372
1311
  log("");
373
1312
  const groupedDrifts = groupByExport(allDrifts.filter((d) => fixable.includes(d.drift)));
@@ -400,16 +1339,16 @@ async function handleFixes(openpkg, doccov, options, deps) {
400
1339
  const applyResult = await applyEdits(edits);
401
1340
  if (applyResult.errors.length > 0) {
402
1341
  for (const err of applyResult.errors) {
403
- error(chalk3.red(` ${err.file}: ${err.error}`));
1342
+ error(chalk5.red(` ${err.file}: ${err.error}`));
404
1343
  }
405
1344
  }
406
1345
  const totalFixes = Array.from(editsByFile.values()).reduce((sum, fileEdits) => sum + fileEdits.reduce((s, e) => s + e.fixes.length, 0), 0);
407
1346
  log("");
408
- log(chalk3.green(`✓ Applied ${totalFixes} fix(es) to ${applyResult.filesModified} file(s)`));
1347
+ log(chalk5.green(`✓ Applied ${totalFixes} fix(es) to ${applyResult.filesModified} file(s)`));
409
1348
  for (const [filePath, fileEdits] of editsByFile) {
410
1349
  const relativePath = path3.relative(targetDir, filePath);
411
1350
  const fixCount = fileEdits.reduce((s, e) => s + e.fixes.length, 0);
412
- log(chalk3.dim(` ${relativePath} (${fixCount} fixes)`));
1351
+ log(chalk5.dim(` ${relativePath} (${fixCount} fixes)`));
413
1352
  }
414
1353
  return {
415
1354
  fixedDriftKeys,
@@ -419,22 +1358,22 @@ async function handleFixes(openpkg, doccov, options, deps) {
419
1358
  }
420
1359
  function generateEditForExport(exp, drifts, targetDir, log) {
421
1360
  if (!exp.source?.file) {
422
- log(chalk3.gray(` Skipping ${exp.name}: no source location`));
1361
+ log(chalk5.gray(` Skipping ${exp.name}: no source location`));
423
1362
  return null;
424
1363
  }
425
1364
  if (exp.source.file.endsWith(".d.ts")) {
426
- log(chalk3.gray(` Skipping ${exp.name}: declaration file`));
1365
+ log(chalk5.gray(` Skipping ${exp.name}: declaration file`));
427
1366
  return null;
428
1367
  }
429
1368
  const filePath = path3.resolve(targetDir, exp.source.file);
430
1369
  if (!fs2.existsSync(filePath)) {
431
- log(chalk3.gray(` Skipping ${exp.name}: file not found`));
1370
+ log(chalk5.gray(` Skipping ${exp.name}: file not found`));
432
1371
  return null;
433
1372
  }
434
1373
  const sourceFile = createSourceFile(filePath);
435
1374
  const location = findJSDocLocation(sourceFile, exp.name, exp.source.line);
436
1375
  if (!location) {
437
- log(chalk3.gray(` Skipping ${exp.name}: could not find declaration`));
1376
+ log(chalk5.gray(` Skipping ${exp.name}: could not find declaration`));
438
1377
  return null;
439
1378
  }
440
1379
  let existingPatch = {};
@@ -459,13 +1398,13 @@ function generateEditForExport(exp, drifts, targetDir, log) {
459
1398
  return { filePath, edit, fixes, existingPatch };
460
1399
  }
461
1400
  function displayPreview(editsByFile, targetDir, log) {
462
- log(chalk3.bold("Preview - changes that would be made:"));
1401
+ log(chalk5.bold("Preview - changes that would be made:"));
463
1402
  log("");
464
1403
  for (const [filePath, fileEdits] of editsByFile) {
465
1404
  const relativePath = path3.relative(targetDir, filePath);
466
1405
  for (const { export: exp, edit, fixes } of fileEdits) {
467
- log(chalk3.cyan(`${relativePath}:${edit.startLine + 1}`));
468
- log(chalk3.bold(` ${exp.name}`));
1406
+ log(chalk5.cyan(`${relativePath}:${edit.startLine + 1}`));
1407
+ log(chalk5.bold(` ${exp.name}`));
469
1408
  log("");
470
1409
  if (edit.hasExisting && edit.existingJSDoc) {
471
1410
  const oldLines = edit.existingJSDoc.split(`
@@ -473,31 +1412,30 @@ function displayPreview(editsByFile, targetDir, log) {
473
1412
  const newLines = edit.newJSDoc.split(`
474
1413
  `);
475
1414
  for (const line of oldLines) {
476
- log(chalk3.red(` - ${line}`));
1415
+ log(chalk5.red(` - ${line}`));
477
1416
  }
478
1417
  for (const line of newLines) {
479
- log(chalk3.green(` + ${line}`));
1418
+ log(chalk5.green(` + ${line}`));
480
1419
  }
481
1420
  } else {
482
1421
  const newLines = edit.newJSDoc.split(`
483
1422
  `);
484
1423
  for (const line of newLines) {
485
- log(chalk3.green(` + ${line}`));
1424
+ log(chalk5.green(` + ${line}`));
486
1425
  }
487
1426
  }
488
1427
  log("");
489
- log(chalk3.dim(` Fixes: ${fixes.map((f) => f.description).join(", ")}`));
1428
+ log(chalk5.dim(` Fixes: ${fixes.map((f) => f.description).join(", ")}`));
490
1429
  log("");
491
1430
  }
492
1431
  }
493
1432
  const totalFixes = Array.from(editsByFile.values()).reduce((sum, fileEdits) => sum + fileEdits.reduce((s, e) => s + e.fixes.length, 0), 0);
494
- log(chalk3.yellow(`${totalFixes} fix(es) across ${editsByFile.size} file(s) would be applied.`));
495
- log(chalk3.gray("Run with --fix to apply these changes."));
1433
+ log(chalk5.yellow(`${totalFixes} fix(es) across ${editsByFile.size} file(s) would be applied.`));
1434
+ log(chalk5.gray("Run with --fix to apply these changes."));
496
1435
  }
497
1436
 
498
1437
  // src/commands/check/output.ts
499
1438
  import { generateReportFromDocCov } from "@doccov/sdk";
500
- import chalk5 from "chalk";
501
1439
 
502
1440
  // src/reports/changelog-renderer.ts
503
1441
  function renderChangelog(data, options = {}) {
@@ -1117,18 +2055,18 @@ function getExportKeyword(kind) {
1117
2055
  }
1118
2056
  }
1119
2057
  function formatExportSignature(exp) {
1120
- const prefix = `export ${getExportKeyword(exp.kind)}`;
2058
+ const prefix2 = `export ${getExportKeyword(exp.kind)}`;
1121
2059
  if (exp.kind === "function" && exp.signatures?.[0]) {
1122
2060
  const sig = exp.signatures[0];
1123
2061
  const params = sig.parameters?.map((p) => `${p.name}${p.required === false ? "?" : ""}`).join(", ") ?? "";
1124
2062
  const ret = extractReturnType(sig.returns?.schema);
1125
- return `${prefix} ${exp.name}(${params}): ${ret}`;
2063
+ return `${prefix2} ${exp.name}(${params}): ${ret}`;
1126
2064
  }
1127
2065
  if (exp.kind === "type" || exp.kind === "interface") {
1128
- return `${prefix} ${exp.name}`;
2066
+ return `${prefix2} ${exp.name}`;
1129
2067
  }
1130
2068
  if (exp.kind === "class") {
1131
- return `${prefix} ${exp.name}`;
2069
+ return `${prefix2} ${exp.name}`;
1132
2070
  }
1133
2071
  return `export ${exp.kind} ${exp.name}`;
1134
2072
  }
@@ -1322,7 +2260,7 @@ function computeStats(openpkg, doccov) {
1322
2260
  import * as fs3 from "node:fs";
1323
2261
  import * as path5 from "node:path";
1324
2262
  import { DEFAULT_REPORT_DIR, getReportPath } from "@doccov/sdk";
1325
- import chalk4 from "chalk";
2263
+ import chalk6 from "chalk";
1326
2264
  function writeReport(options) {
1327
2265
  const { format, content, outputPath, cwd = process.cwd(), silent = false } = options;
1328
2266
  const reportPath = outputPath ? path5.resolve(cwd, outputPath) : path5.resolve(cwd, getReportPath(format));
@@ -1333,7 +2271,7 @@ function writeReport(options) {
1333
2271
  fs3.writeFileSync(reportPath, content);
1334
2272
  const relativePath = path5.relative(cwd, reportPath);
1335
2273
  if (!silent) {
1336
- console.log(chalk4.green(`✓ Wrote ${format} report to ${relativePath}`));
2274
+ console.log(chalk6.green(`✓ Wrote ${format} report to ${relativePath}`));
1337
2275
  }
1338
2276
  return { path: reportPath, format, relativePath };
1339
2277
  }
@@ -1371,6 +2309,7 @@ function displayTextOutput(options, deps) {
1371
2309
  specInfos
1372
2310
  } = options;
1373
2311
  const { log } = deps;
2312
+ const sym = getSymbols(supportsUnicode());
1374
2313
  const totalExportsForDrift = openpkg.exports?.length ?? 0;
1375
2314
  const exportsWithDrift = new Set(driftExports.map((d) => d.name)).size;
1376
2315
  const driftScore = totalExportsForDrift === 0 ? 0 : Math.round(exportsWithDrift / totalExportsForDrift * 100);
@@ -1380,15 +2319,15 @@ function displayTextOutput(options, deps) {
1380
2319
  if (specWarnings.length > 0 || specInfos.length > 0) {
1381
2320
  log("");
1382
2321
  for (const diag of specWarnings) {
1383
- log(chalk5.yellow(`⚠ ${diag.message}`));
2322
+ log(colors.warning(`${sym.warning} ${diag.message}`));
1384
2323
  if (diag.suggestion) {
1385
- log(chalk5.gray(` ${diag.suggestion}`));
2324
+ log(colors.muted(` ${diag.suggestion}`));
1386
2325
  }
1387
2326
  }
1388
2327
  for (const diag of specInfos) {
1389
- log(chalk5.cyan(`ℹ ${diag.message}`));
2328
+ log(colors.info(`${sym.info} ${diag.message}`));
1390
2329
  if (diag.suggestion) {
1391
- log(chalk5.gray(` ${diag.suggestion}`));
2330
+ log(colors.muted(` ${diag.suggestion}`));
1392
2331
  }
1393
2332
  }
1394
2333
  }
@@ -1396,47 +2335,47 @@ function displayTextOutput(options, deps) {
1396
2335
  const pkgVersion = openpkg.meta?.version ?? "";
1397
2336
  const totalExports = openpkg.exports?.length ?? 0;
1398
2337
  log("");
1399
- log(chalk5.bold(`${pkgName}${pkgVersion ? `@${pkgVersion}` : ""}`));
2338
+ log(colors.bold(`${pkgName}${pkgVersion ? `@${pkgVersion}` : ""}`));
1400
2339
  log("");
1401
- log(` Exports: ${totalExports}`);
1402
- if (coverageFailed) {
1403
- log(chalk5.red(` Coverage: ✗ ${coverageScore}%`) + chalk5.dim(` (min ${minCoverage}%)`));
1404
- } else {
1405
- log(chalk5.green(` Coverage: ✓ ${coverageScore}%`) + chalk5.dim(` (min ${minCoverage}%)`));
1406
- }
2340
+ const summaryBuilder = summary({ keyWidth: 10 });
2341
+ summaryBuilder.addKeyValue("Exports", totalExports);
2342
+ summaryBuilder.addKeyValue("Coverage", `${coverageScore}%`, coverageFailed ? "fail" : "pass");
1407
2343
  if (maxDrift !== undefined) {
1408
- if (driftFailed) {
1409
- log(chalk5.red(` Drift: ✗ ${driftScore}%`) + chalk5.dim(` (max ${maxDrift}%)`));
1410
- } else {
1411
- log(chalk5.green(` Drift: ✓ ${driftScore}%`) + chalk5.dim(` (max ${maxDrift}%)`));
1412
- }
2344
+ summaryBuilder.addKeyValue("Drift", `${driftScore}%`, driftFailed ? "fail" : "pass");
1413
2345
  } else {
1414
- log(` Drift: ${driftScore}%`);
2346
+ summaryBuilder.addKeyValue("Drift", `${driftScore}%`);
1415
2347
  }
1416
2348
  if (exampleResult) {
1417
2349
  const typecheckCount = exampleResult.typecheck?.errors.length ?? 0;
1418
2350
  if (typecheckCount > 0) {
1419
- log(chalk5.yellow(` Examples: ${typecheckCount} type error(s)`));
1420
- for (const err of typecheckErrors.slice(0, 5)) {
1421
- const loc = `example[${err.error.exampleIndex}]:${err.error.line}:${err.error.column}`;
1422
- log(chalk5.dim(` ${err.exportName} ${loc}`));
1423
- log(chalk5.red(` ${err.error.message}`));
1424
- }
1425
- if (typecheckErrors.length > 5) {
1426
- log(chalk5.dim(` ... and ${typecheckErrors.length - 5} more`));
1427
- }
2351
+ summaryBuilder.addKeyValue("Examples", `${typecheckCount} type error(s)`, "warn");
1428
2352
  } else {
1429
- log(chalk5.green(` Examples: ✓ validated`));
2353
+ summaryBuilder.addKeyValue("Examples", "validated", "pass");
1430
2354
  }
1431
2355
  }
1432
2356
  const hasStaleRefs = staleRefs.length > 0;
1433
2357
  if (hasStaleRefs) {
1434
- log(chalk5.yellow(` Docs: ${staleRefs.length} stale ref(s)`));
2358
+ summaryBuilder.addKeyValue("Docs", `${staleRefs.length} stale ref(s)`, "warn");
2359
+ }
2360
+ summaryBuilder.print();
2361
+ if (hasTypecheckErrors) {
2362
+ log("");
2363
+ for (const err of typecheckErrors.slice(0, 5)) {
2364
+ const loc = `example[${err.error.exampleIndex}]:${err.error.line}:${err.error.column}`;
2365
+ log(colors.muted(` ${err.exportName} ${loc}`));
2366
+ log(colors.error(` ${err.error.message}`));
2367
+ }
2368
+ if (typecheckErrors.length > 5) {
2369
+ log(colors.muted(` ... and ${typecheckErrors.length - 5} more`));
2370
+ }
2371
+ }
2372
+ if (hasStaleRefs) {
2373
+ log("");
1435
2374
  for (const ref of staleRefs.slice(0, 5)) {
1436
- log(chalk5.dim(` ${ref.file}:${ref.line} - "${ref.exportName}"`));
2375
+ log(colors.muted(` ${ref.file}:${ref.line} - "${ref.exportName}"`));
1437
2376
  }
1438
2377
  if (staleRefs.length > 5) {
1439
- log(chalk5.dim(` ... and ${staleRefs.length - 5} more`));
2378
+ log(colors.muted(` ... and ${staleRefs.length - 5} more`));
1440
2379
  }
1441
2380
  }
1442
2381
  log("");
@@ -1447,17 +2386,23 @@ function displayTextOutput(options, deps) {
1447
2386
  if (maxDrift !== undefined) {
1448
2387
  thresholdParts.push(`drift ${driftScore}% ≤ ${maxDrift}%`);
1449
2388
  }
1450
- log(chalk5.green(`✓ Check passed (${thresholdParts.join(", ")})`));
2389
+ log(colors.success(`${sym.success} Check passed (${thresholdParts.join(", ")})`));
1451
2390
  return true;
1452
2391
  }
2392
+ if (coverageFailed) {
2393
+ log(colors.error(`${sym.error} Coverage ${coverageScore}% below minimum ${minCoverage}%`));
2394
+ }
2395
+ if (driftFailed) {
2396
+ log(colors.error(`${sym.error} Drift ${driftScore}% exceeds maximum ${maxDrift}%`));
2397
+ }
1453
2398
  if (hasTypecheckErrors) {
1454
- log(chalk5.red(`✗ ${typecheckErrors.length} example type errors`));
2399
+ log(colors.error(`${sym.error} ${typecheckErrors.length} example type errors`));
1455
2400
  }
1456
2401
  if (hasStaleRefs) {
1457
- log(chalk5.red(`✗ ${staleRefs.length} stale references in docs`));
2402
+ log(colors.error(`${sym.error} ${staleRefs.length} stale references in docs`));
1458
2403
  }
1459
2404
  log("");
1460
- log(chalk5.dim("Use --format json or --format markdown for detailed reports"));
2405
+ log(colors.muted("Use --format json or --format markdown for detailed reports"));
1461
2406
  return false;
1462
2407
  }
1463
2408
  function handleNonTextOutput(options, deps) {
@@ -1608,18 +2553,9 @@ function registerCheckCommand(program, dependencies = {}) {
1608
2553
  return n;
1609
2554
  }).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) => {
1610
2555
  try {
2556
+ const spin = spinner("Analyzing...");
1611
2557
  let validations = parseExamplesFlag(options.examples);
1612
2558
  let hasExamples = validations.length > 0;
1613
- const stepList = [
1614
- { label: "Resolved target", activeLabel: "Resolving target" },
1615
- { label: "Loaded config", activeLabel: "Loading config" },
1616
- { label: "Generated spec", activeLabel: "Generating spec" },
1617
- { label: "Enriched spec", activeLabel: "Enriching spec" },
1618
- ...hasExamples ? [{ label: "Validated examples", activeLabel: "Validating examples" }] : [],
1619
- { label: "Processed results", activeLabel: "Processing results" }
1620
- ];
1621
- const steps = new StepProgress(stepList);
1622
- steps.start();
1623
2559
  const fileSystem = new NodeFileSystem(options.cwd);
1624
2560
  const resolved = await resolveTarget(fileSystem, {
1625
2561
  cwd: options.cwd,
@@ -1627,7 +2563,6 @@ function registerCheckCommand(program, dependencies = {}) {
1627
2563
  entry
1628
2564
  });
1629
2565
  const { targetDir, entryFile } = resolved;
1630
- steps.next();
1631
2566
  const config = await loadDocCovConfig(targetDir);
1632
2567
  if (!hasExamples && config?.check?.examples) {
1633
2568
  const configExamples = config.check.examples;
@@ -1650,9 +2585,8 @@ function registerCheckCommand(program, dependencies = {}) {
1650
2585
  };
1651
2586
  const resolvedFilters = mergeFilterOptions(config, cliFilters);
1652
2587
  if (resolvedFilters.visibility) {
1653
- log(chalk6.dim(`Filtering by visibility: ${resolvedFilters.visibility.join(", ")}`));
2588
+ log(chalk7.dim(`Filtering by visibility: ${resolvedFilters.visibility.join(", ")}`));
1654
2589
  }
1655
- steps.next();
1656
2590
  const resolveExternalTypes = !options.skipResolve;
1657
2591
  const analyzer = createDocCov({
1658
2592
  resolveExternalTypes,
@@ -1663,9 +2597,9 @@ function registerCheckCommand(program, dependencies = {}) {
1663
2597
  const analyzeOptions = resolvedFilters.visibility ? { filters: { visibility: resolvedFilters.visibility } } : {};
1664
2598
  const specResult = await analyzer.analyzeFileWithDiagnostics(entryFile, analyzeOptions);
1665
2599
  if (!specResult) {
2600
+ spin.fail("Analysis failed");
1666
2601
  throw new Error("Failed to analyze documentation coverage.");
1667
2602
  }
1668
- steps.next();
1669
2603
  const openpkg = specResult.spec;
1670
2604
  const doccov = buildDocCovSpec({
1671
2605
  openpkg,
@@ -1673,7 +2607,6 @@ function registerCheckCommand(program, dependencies = {}) {
1673
2607
  packagePath: targetDir
1674
2608
  });
1675
2609
  const format = options.format ?? "text";
1676
- steps.next();
1677
2610
  const specWarnings = specResult.diagnostics.filter((d) => d.severity === "warning");
1678
2611
  const specInfos = specResult.diagnostics.filter((d) => d.severity === "info");
1679
2612
  const isPreview = options.preview;
@@ -1689,7 +2622,6 @@ function registerCheckCommand(program, dependencies = {}) {
1689
2622
  exampleResult = validation.result;
1690
2623
  typecheckErrors = validation.typecheckErrors;
1691
2624
  runtimeDrifts = validation.runtimeDrifts;
1692
- steps.next();
1693
2625
  }
1694
2626
  let docsPatterns = options.docs;
1695
2627
  if (docsPatterns.length === 0 && config?.docs?.include) {
@@ -1709,7 +2641,7 @@ function registerCheckCommand(program, dependencies = {}) {
1709
2641
  driftExports = driftExports.filter((d) => !fixResult.fixedDriftKeys.has(`${d.name}:${d.issue}`));
1710
2642
  }
1711
2643
  }
1712
- steps.complete("Check complete");
2644
+ spin.success("Analysis complete");
1713
2645
  if (format !== "text") {
1714
2646
  const passed2 = handleNonTextOutput({
1715
2647
  format,
@@ -1747,7 +2679,7 @@ function registerCheckCommand(program, dependencies = {}) {
1747
2679
  process.exit(1);
1748
2680
  }
1749
2681
  } catch (commandError) {
1750
- error(chalk6.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
2682
+ error(chalk7.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
1751
2683
  process.exit(1);
1752
2684
  }
1753
2685
  });
@@ -1765,7 +2697,7 @@ import {
1765
2697
  parseMarkdownFiles as parseMarkdownFiles2
1766
2698
  } from "@doccov/sdk";
1767
2699
  import { calculateNextVersion, recommendSemverBump } from "@openpkg-ts/spec";
1768
- import chalk7 from "chalk";
2700
+ import chalk8 from "chalk";
1769
2701
  import { glob as glob2 } from "glob";
1770
2702
  var defaultDependencies2 = {
1771
2703
  readFileSync: fs4.readFileSync,
@@ -1800,6 +2732,7 @@ function registerDiffCommand(program, dependencies = {}) {
1800
2732
  ` + `Usage: doccov diff <base> <head>
1801
2733
  ` + " or: doccov diff --base main.json --head feature.json");
1802
2734
  }
2735
+ const spin = spinner("Comparing specs...");
1803
2736
  const baseSpec = loadSpec(baseFile, readFileSync3);
1804
2737
  const headSpec = loadSpec(headFile, readFileSync3);
1805
2738
  const config = await loadDocCovConfig(options.cwd);
@@ -1823,6 +2756,7 @@ function registerDiffCommand(program, dependencies = {}) {
1823
2756
  const minCoverage = resolveThreshold(options.minCoverage, config?.check?.minCoverage);
1824
2757
  const maxDrift = resolveThreshold(options.maxDrift, config?.check?.maxDrift);
1825
2758
  if (options.recommendVersion) {
2759
+ spin.success("Comparison complete");
1826
2760
  const recommendation = recommendSemverBump(diff);
1827
2761
  const currentVersion = headSpec.meta?.version ?? "0.0.0";
1828
2762
  const nextVersion = calculateNextVersion(currentVersion, recommendation.bump);
@@ -1838,9 +2772,9 @@ function registerDiffCommand(program, dependencies = {}) {
1838
2772
  }, null, 2));
1839
2773
  } else {
1840
2774
  log("");
1841
- log(chalk7.bold("Semver Recommendation"));
2775
+ log(chalk8.bold("Semver Recommendation"));
1842
2776
  log(` Current version: ${currentVersion}`);
1843
- log(` Recommended: ${chalk7.cyan(nextVersion)} (${chalk7.yellow(recommendation.bump.toUpperCase())})`);
2777
+ log(` Recommended: ${chalk8.cyan(nextVersion)} (${chalk8.yellow(recommendation.bump.toUpperCase())})`);
1844
2778
  log(` Reason: ${recommendation.reason}`);
1845
2779
  }
1846
2780
  return;
@@ -1855,6 +2789,7 @@ function registerDiffCommand(program, dependencies = {}) {
1855
2789
  headName,
1856
2790
  ...diff
1857
2791
  };
2792
+ spin.success("Comparison complete");
1858
2793
  switch (format) {
1859
2794
  case "text":
1860
2795
  printSummary(diff, baseName, headName, fromCache, log);
@@ -1869,8 +2804,8 @@ function registerDiffCommand(program, dependencies = {}) {
1869
2804
  silent: true
1870
2805
  });
1871
2806
  }
1872
- const cacheNote = fromCache ? chalk7.cyan(" (cached)") : "";
1873
- log(chalk7.dim(`Report: ${jsonPath}`) + cacheNote);
2807
+ const cacheNote = fromCache ? chalk8.cyan(" (cached)") : "";
2808
+ log(chalk8.dim(`Report: ${jsonPath}`) + cacheNote);
1874
2809
  }
1875
2810
  break;
1876
2811
  case "json": {
@@ -1965,18 +2900,18 @@ function registerDiffCommand(program, dependencies = {}) {
1965
2900
  checks
1966
2901
  });
1967
2902
  if (failures.length > 0) {
1968
- log(chalk7.red(`
2903
+ log(chalk8.red(`
1969
2904
  ✗ Check failed`));
1970
2905
  for (const f of failures) {
1971
- log(chalk7.red(` - ${f}`));
2906
+ log(chalk8.red(` - ${f}`));
1972
2907
  }
1973
2908
  process.exitCode = 1;
1974
2909
  } else if (options.strict || minCoverage !== undefined || maxDrift !== undefined) {
1975
- log(chalk7.green(`
2910
+ log(chalk8.green(`
1976
2911
  ✓ All checks passed`));
1977
2912
  }
1978
2913
  } catch (commandError) {
1979
- error(chalk7.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
2914
+ error(chalk8.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
1980
2915
  process.exitCode = 1;
1981
2916
  }
1982
2917
  });
@@ -2003,7 +2938,7 @@ async function generateDiff(baseSpec, headSpec, options, config, log) {
2003
2938
  if (!docsPatterns || docsPatterns.length === 0) {
2004
2939
  if (config?.docs?.include) {
2005
2940
  docsPatterns = config.docs.include;
2006
- log(chalk7.gray(`Using docs patterns from config: ${docsPatterns.join(", ")}`));
2941
+ log(chalk8.gray(`Using docs patterns from config: ${docsPatterns.join(", ")}`));
2007
2942
  }
2008
2943
  }
2009
2944
  if (docsPatterns && docsPatterns.length > 0) {
@@ -2026,37 +2961,37 @@ function loadSpec(filePath, readFileSync3) {
2026
2961
  }
2027
2962
  function printSummary(diff, baseName, headName, fromCache, log) {
2028
2963
  log("");
2029
- const cacheIndicator = fromCache ? chalk7.cyan(" (cached)") : "";
2030
- log(chalk7.bold(`Comparing: ${baseName} → ${headName}`) + cacheIndicator);
2964
+ const cacheIndicator = fromCache ? chalk8.cyan(" (cached)") : "";
2965
+ log(chalk8.bold(`Comparing: ${baseName} → ${headName}`) + cacheIndicator);
2031
2966
  log("─".repeat(40));
2032
2967
  log("");
2033
- const coverageColor = diff.coverageDelta > 0 ? chalk7.green : diff.coverageDelta < 0 ? chalk7.red : chalk7.gray;
2968
+ const coverageColor = diff.coverageDelta > 0 ? chalk8.green : diff.coverageDelta < 0 ? chalk8.red : chalk8.gray;
2034
2969
  const coverageSign = diff.coverageDelta > 0 ? "+" : "";
2035
2970
  log(` Coverage: ${diff.oldCoverage}% → ${diff.newCoverage}% ${coverageColor(`(${coverageSign}${diff.coverageDelta}%)`)}`);
2036
2971
  const breakingCount = diff.breaking.length;
2037
2972
  const highSeverity = diff.categorizedBreaking?.filter((c) => c.severity === "high").length ?? 0;
2038
2973
  if (breakingCount > 0) {
2039
- const severityNote = highSeverity > 0 ? chalk7.red(` (${highSeverity} high severity)`) : "";
2040
- log(` Breaking: ${chalk7.red(breakingCount)} changes${severityNote}`);
2974
+ const severityNote = highSeverity > 0 ? chalk8.red(` (${highSeverity} high severity)`) : "";
2975
+ log(` Breaking: ${chalk8.red(breakingCount)} changes${severityNote}`);
2041
2976
  } else {
2042
- log(` Breaking: ${chalk7.green("0")} changes`);
2977
+ log(` Breaking: ${chalk8.green("0")} changes`);
2043
2978
  }
2044
2979
  const newCount = diff.nonBreaking.length;
2045
2980
  const undocCount = diff.newUndocumented.length;
2046
2981
  if (newCount > 0) {
2047
- const undocNote = undocCount > 0 ? chalk7.yellow(` (${undocCount} undocumented)`) : "";
2048
- log(` New: ${chalk7.green(newCount)} exports${undocNote}`);
2982
+ const undocNote = undocCount > 0 ? chalk8.yellow(` (${undocCount} undocumented)`) : "";
2983
+ log(` New: ${chalk8.green(newCount)} exports${undocNote}`);
2049
2984
  }
2050
2985
  if (diff.driftIntroduced > 0 || diff.driftResolved > 0) {
2051
2986
  const parts = [];
2052
2987
  if (diff.driftIntroduced > 0)
2053
- parts.push(chalk7.red(`+${diff.driftIntroduced}`));
2988
+ parts.push(chalk8.red(`+${diff.driftIntroduced}`));
2054
2989
  if (diff.driftResolved > 0)
2055
- parts.push(chalk7.green(`-${diff.driftResolved}`));
2990
+ parts.push(chalk8.green(`-${diff.driftResolved}`));
2056
2991
  log(` Drift: ${parts.join(", ")}`);
2057
2992
  }
2058
2993
  const recommendation = recommendSemverBump(diff);
2059
- const bumpColor = recommendation.bump === "major" ? chalk7.red : recommendation.bump === "minor" ? chalk7.yellow : chalk7.green;
2994
+ const bumpColor = recommendation.bump === "major" ? chalk8.red : recommendation.bump === "minor" ? chalk8.yellow : chalk8.green;
2060
2995
  log(` Semver: ${bumpColor(recommendation.bump.toUpperCase())} (${recommendation.reason})`);
2061
2996
  log("");
2062
2997
  }
@@ -2084,8 +3019,8 @@ function validateDiff(diff, headSpec, options) {
2084
3019
  failures.push(`${diff.newUndocumented.length} undocumented export(s)`);
2085
3020
  }
2086
3021
  if (checks.has("docs-impact") && hasDocsImpact(diff)) {
2087
- const summary = getDocsImpactSummary(diff);
2088
- failures.push(`${summary.totalIssues} docs issue(s)`);
3022
+ const summary2 = getDocsImpactSummary(diff);
3023
+ failures.push(`${summary2.totalIssues} docs issue(s)`);
2089
3024
  }
2090
3025
  return failures;
2091
3026
  }
@@ -2138,9 +3073,10 @@ function printGitHubAnnotations(diff, log) {
2138
3073
 
2139
3074
  // src/commands/info.ts
2140
3075
  import { buildDocCovSpec as buildDocCovSpec2, DocCov as DocCov2, NodeFileSystem as NodeFileSystem2, resolveTarget as resolveTarget2 } from "@doccov/sdk";
2141
- import chalk8 from "chalk";
3076
+ import chalk9 from "chalk";
2142
3077
  function registerInfoCommand(program) {
2143
3078
  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) => {
3079
+ const spin = spinner("Analyzing documentation coverage");
2144
3080
  try {
2145
3081
  const fileSystem = new NodeFileSystem2(options.cwd);
2146
3082
  const resolved = await resolveTarget2(fileSystem, {
@@ -2155,6 +3091,7 @@ function registerInfoCommand(program) {
2155
3091
  });
2156
3092
  const specResult = await analyzer.analyzeFileWithDiagnostics(entryFile);
2157
3093
  if (!specResult) {
3094
+ spin.fail("Failed to analyze");
2158
3095
  throw new Error("Failed to analyze documentation coverage.");
2159
3096
  }
2160
3097
  const openpkg = specResult.spec;
@@ -2164,15 +3101,17 @@ function registerInfoCommand(program) {
2164
3101
  packagePath: targetDir
2165
3102
  });
2166
3103
  const stats = computeStats(openpkg, doccov);
3104
+ spin.success("Analysis complete");
2167
3105
  console.log("");
2168
- console.log(chalk8.bold(`${stats.packageName}@${stats.version}`));
3106
+ console.log(chalk9.bold(`${stats.packageName}@${stats.version}`));
2169
3107
  console.log("");
2170
- console.log(` Exports: ${chalk8.bold(stats.totalExports.toString())}`);
2171
- console.log(` Coverage: ${chalk8.bold(`${stats.coverageScore}%`)}`);
2172
- console.log(` Drift: ${chalk8.bold(`${stats.driftScore}%`)}`);
3108
+ console.log(` Exports: ${chalk9.bold(stats.totalExports.toString())}`);
3109
+ console.log(` Coverage: ${chalk9.bold(`${stats.coverageScore}%`)}`);
3110
+ console.log(` Drift: ${chalk9.bold(`${stats.driftScore}%`)}`);
2173
3111
  console.log("");
2174
3112
  } catch (err) {
2175
- console.error(chalk8.red("Error:"), err instanceof Error ? err.message : err);
3113
+ spin.fail("Analysis failed");
3114
+ console.error(chalk9.red("Error:"), err instanceof Error ? err.message : err);
2176
3115
  process.exit(1);
2177
3116
  }
2178
3117
  });
@@ -2181,7 +3120,7 @@ function registerInfoCommand(program) {
2181
3120
  // src/commands/init.ts
2182
3121
  import * as fs5 from "node:fs";
2183
3122
  import * as path7 from "node:path";
2184
- import chalk9 from "chalk";
3123
+ import chalk10 from "chalk";
2185
3124
  var defaultDependencies3 = {
2186
3125
  fileExists: fs5.existsSync,
2187
3126
  writeFileSync: fs5.writeFileSync,
@@ -2199,7 +3138,7 @@ function registerInitCommand(program, dependencies = {}) {
2199
3138
  const cwd = path7.resolve(options.cwd);
2200
3139
  const existing = findExistingConfig(cwd, fileExists2);
2201
3140
  if (existing) {
2202
- error(chalk9.red(`A DocCov config already exists at ${path7.relative(cwd, existing) || "./doccov.config.*"}.`));
3141
+ error(chalk10.red(`A DocCov config already exists at ${path7.relative(cwd, existing) || "./doccov.config.*"}.`));
2203
3142
  process.exitCode = 1;
2204
3143
  return;
2205
3144
  }
@@ -2208,36 +3147,37 @@ function registerInitCommand(program, dependencies = {}) {
2208
3147
  const fileName = `doccov.config.${targetFormat}`;
2209
3148
  const outputPath = path7.join(cwd, fileName);
2210
3149
  if (fileExists2(outputPath)) {
2211
- error(chalk9.red(`Cannot create ${fileName}; file already exists.`));
3150
+ error(chalk10.red(`Cannot create ${fileName}; file already exists.`));
2212
3151
  process.exitCode = 1;
2213
3152
  return;
2214
3153
  }
3154
+ const sym = getSymbols(supportsUnicode());
2215
3155
  const template = buildConfigTemplate();
2216
3156
  writeFileSync3(outputPath, template, { encoding: "utf8" });
2217
- log(chalk9.green(`✓ Created ${fileName}`));
3157
+ log(colors.success(`${sym.success} Created ${fileName}`));
2218
3158
  if (!options.skipAction) {
2219
3159
  const workflowDir = path7.join(cwd, ".github", "workflows");
2220
3160
  const workflowPath = path7.join(workflowDir, "doccov.yml");
2221
3161
  if (!fileExists2(workflowPath)) {
2222
3162
  mkdirSync3(workflowDir, { recursive: true });
2223
3163
  writeFileSync3(workflowPath, buildWorkflowTemplate(), { encoding: "utf8" });
2224
- log(chalk9.green(`✓ Created .github/workflows/doccov.yml`));
3164
+ log(colors.success(`${sym.success} Created .github/workflows/doccov.yml`));
2225
3165
  } else {
2226
- log(chalk9.yellow(` Skipped .github/workflows/doccov.yml (already exists)`));
3166
+ log(colors.warning(`${sym.bullet} Skipped .github/workflows/doccov.yml (already exists)`));
2227
3167
  }
2228
3168
  }
2229
3169
  const repoInfo = detectRepoInfo(cwd, fileExists2, readFileSync4);
2230
3170
  log("");
2231
- log(chalk9.bold("Add this badge to your README:"));
3171
+ log(chalk10.bold("Add this badge to your README:"));
2232
3172
  log("");
2233
3173
  if (repoInfo) {
2234
- log(chalk9.cyan(`[![DocCov](https://doccov.dev/badge/${repoInfo.owner}/${repoInfo.repo})](https://doccov.dev/${repoInfo.owner}/${repoInfo.repo})`));
3174
+ log(chalk10.cyan(`[![DocCov](https://doccov.dev/badge/${repoInfo.owner}/${repoInfo.repo})](https://doccov.dev/${repoInfo.owner}/${repoInfo.repo})`));
2235
3175
  } else {
2236
- log(chalk9.cyan(`[![DocCov](https://doccov.dev/badge/OWNER/REPO)](https://doccov.dev/OWNER/REPO)`));
2237
- log(chalk9.dim(" Replace OWNER/REPO with your GitHub repo"));
3176
+ log(chalk10.cyan(`[![DocCov](https://doccov.dev/badge/OWNER/REPO)](https://doccov.dev/OWNER/REPO)`));
3177
+ log(chalk10.dim(" Replace OWNER/REPO with your GitHub repo"));
2238
3178
  }
2239
3179
  log("");
2240
- log(chalk9.dim("Run `doccov check` to verify your documentation coverage"));
3180
+ log(chalk10.dim("Run `doccov check` to verify your documentation coverage"));
2241
3181
  });
2242
3182
  }
2243
3183
  var findExistingConfig = (cwd, fileExists2) => {
@@ -2373,9 +3313,9 @@ import {
2373
3313
  } from "@doccov/sdk";
2374
3314
  import { validateDocCovSpec } from "@doccov/spec";
2375
3315
  import { normalize, validateSpec } from "@openpkg-ts/spec";
2376
- import chalk10 from "chalk";
3316
+ import chalk11 from "chalk";
2377
3317
  // package.json
2378
- var version = "0.25.7";
3318
+ var version = "0.25.9";
2379
3319
 
2380
3320
  // src/commands/spec.ts
2381
3321
  var defaultDependencies4 = {
@@ -2387,12 +3327,12 @@ var defaultDependencies4 = {
2387
3327
  function getArrayLength(value) {
2388
3328
  return Array.isArray(value) ? value.length : 0;
2389
3329
  }
2390
- function formatDiagnosticOutput(prefix, diagnostic, baseDir) {
3330
+ function formatDiagnosticOutput(prefix2, diagnostic, baseDir) {
2391
3331
  const location = diagnostic.location;
2392
3332
  const relativePath = location?.file ? path8.relative(baseDir, location.file) || location.file : undefined;
2393
- const locationText = location && relativePath ? chalk10.gray(`${relativePath}:${location.line ?? 1}:${location.column ?? 1}`) : null;
3333
+ const locationText = location && relativePath ? chalk11.gray(`${relativePath}:${location.line ?? 1}:${location.column ?? 1}`) : null;
2394
3334
  const locationPrefix = locationText ? `${locationText} ` : "";
2395
- return `${prefix} ${locationPrefix}${diagnostic.message}`;
3335
+ return `${prefix2} ${locationPrefix}${diagnostic.message}`;
2396
3336
  }
2397
3337
  function registerSpecCommand(program, dependencies = {}) {
2398
3338
  const { createDocCov, writeFileSync: writeFileSync4, log, error } = {
@@ -2401,18 +3341,7 @@ function registerSpecCommand(program, dependencies = {}) {
2401
3341
  };
2402
3342
  program.command("spec [entry]").description("Generate OpenPkg + DocCov specifications").option("--cwd <dir>", "Working directory", process.cwd()).option("-p, --package <name>", "Target package name (for monorepos)").option("-o, --output <dir>", "Output directory", ".doccov").option("-f, --format <format>", "Output format: json (default) or api-surface", "json").option("--openpkg-only", "Only generate openpkg.json (skip coverage analysis)").option("--include <patterns>", "Include exports matching pattern (comma-separated)").option("--exclude <patterns>", "Exclude exports matching pattern (comma-separated)").option("--visibility <tags>", "Filter by release stage: public,beta,alpha,internal (comma-separated)").option("--skip-resolve", "Skip external type resolution from node_modules").option("--max-type-depth <n>", "Maximum depth for type conversion", "20").option("--runtime", "Enable Standard Schema runtime extraction (richer output for Zod, Valibot, etc.)").option("--no-cache", "Bypass spec cache and force regeneration").option("--show-diagnostics", "Show TypeScript compiler diagnostics").option("--verbose", "Show detailed generation metadata").action(async (entry, options) => {
2403
3343
  try {
2404
- const stepsList = [
2405
- { label: "Resolved target", activeLabel: "Resolving target" },
2406
- { label: "Loaded config", activeLabel: "Loading config" },
2407
- { label: "Generated spec", activeLabel: "Generating spec" },
2408
- { label: "Validated schema", activeLabel: "Validating schema" }
2409
- ];
2410
- if (!options.openpkgOnly) {
2411
- stepsList.push({ label: "Built coverage analysis", activeLabel: "Building coverage" });
2412
- }
2413
- stepsList.push({ label: "Wrote output", activeLabel: "Writing output" });
2414
- const steps = new StepProgress(stepsList);
2415
- steps.start();
3344
+ const spin = spinner("Generating spec...");
2416
3345
  const fileSystem = new NodeFileSystem3(options.cwd);
2417
3346
  const resolved = await resolveTarget3(fileSystem, {
2418
3347
  cwd: options.cwd,
@@ -2420,15 +3349,14 @@ function registerSpecCommand(program, dependencies = {}) {
2420
3349
  entry
2421
3350
  });
2422
3351
  const { targetDir, entryFile, packageInfo, entryPointInfo } = resolved;
2423
- steps.next();
2424
3352
  let config = null;
2425
3353
  try {
2426
3354
  config = await loadDocCovConfig(targetDir);
2427
3355
  } catch (configError) {
2428
- error(chalk10.red("Failed to load DocCov config:"), configError instanceof Error ? configError.message : configError);
3356
+ spin.fail("Failed to load config");
3357
+ error(chalk11.red("Failed to load DocCov config:"), configError instanceof Error ? configError.message : configError);
2429
3358
  process.exit(1);
2430
3359
  }
2431
- steps.next();
2432
3360
  const cliFilters = {
2433
3361
  include: parseListFlag(options.include),
2434
3362
  exclude: parseListFlag(options.exclude),
@@ -2463,19 +3391,19 @@ function registerSpecCommand(program, dependencies = {}) {
2463
3391
  } : { generationInput };
2464
3392
  const result = await doccov.analyzeFileWithDiagnostics(entryFile, analyzeOptions);
2465
3393
  if (!result) {
3394
+ spin.fail("Generation failed");
2466
3395
  throw new Error("Failed to produce an OpenPkg spec.");
2467
3396
  }
2468
- steps.next();
2469
3397
  const normalized = normalize(result.spec);
2470
3398
  const validation = validateSpec(normalized);
2471
3399
  if (!validation.ok) {
2472
- error(chalk10.red("Spec failed schema validation"));
3400
+ spin.fail("Validation failed");
3401
+ error(chalk11.red("Spec failed schema validation"));
2473
3402
  for (const err of validation.errors) {
2474
- error(chalk10.red(`schema: ${err.instancePath || "/"} ${err.message}`));
3403
+ error(chalk11.red(`schema: ${err.instancePath || "/"} ${err.message}`));
2475
3404
  }
2476
3405
  process.exit(1);
2477
3406
  }
2478
- steps.next();
2479
3407
  let doccovSpec = null;
2480
3408
  if (!options.openpkgOnly) {
2481
3409
  doccovSpec = buildDocCovSpec3({
@@ -2485,13 +3413,13 @@ function registerSpecCommand(program, dependencies = {}) {
2485
3413
  });
2486
3414
  const doccovValidation = validateDocCovSpec(doccovSpec);
2487
3415
  if (!doccovValidation.ok) {
2488
- error(chalk10.red("DocCov spec failed schema validation"));
3416
+ spin.fail("DocCov validation failed");
3417
+ error(chalk11.red("DocCov spec failed schema validation"));
2489
3418
  for (const err of doccovValidation.errors) {
2490
- error(chalk10.red(`doccov: ${err.instancePath || "/"} ${err.message}`));
3419
+ error(chalk11.red(`doccov: ${err.instancePath || "/"} ${err.message}`));
2491
3420
  }
2492
3421
  process.exit(1);
2493
3422
  }
2494
- steps.next();
2495
3423
  }
2496
3424
  const format = options.format ?? "json";
2497
3425
  const outputDir = path8.resolve(options.cwd, options.output);
@@ -2500,20 +3428,20 @@ function registerSpecCommand(program, dependencies = {}) {
2500
3428
  const apiSurface = renderApiSurface(normalized);
2501
3429
  const apiSurfacePath = path8.join(outputDir, "api-surface.txt");
2502
3430
  writeFileSync4(apiSurfacePath, apiSurface);
2503
- steps.complete(`Generated ${options.output}/ (API surface)`);
3431
+ spin.success(`Generated ${options.output}/ (API surface)`);
2504
3432
  } else {
2505
3433
  const openpkgPath = path8.join(outputDir, "openpkg.json");
2506
3434
  writeFileSync4(openpkgPath, JSON.stringify(normalized, null, 2));
2507
3435
  if (doccovSpec) {
2508
3436
  const doccovPath = path8.join(outputDir, "doccov.json");
2509
3437
  writeFileSync4(doccovPath, JSON.stringify(doccovSpec, null, 2));
2510
- steps.complete(`Generated ${options.output}/`);
2511
- log(chalk10.gray(` openpkg.json: ${getArrayLength(normalized.exports)} exports`));
2512
- log(chalk10.gray(` doccov.json: ${doccovSpec.summary.score}% coverage, ${doccovSpec.summary.drift.total} drift issues`));
3438
+ spin.success(`Generated ${options.output}/`);
3439
+ log(chalk11.gray(` openpkg.json: ${getArrayLength(normalized.exports)} exports`));
3440
+ log(chalk11.gray(` doccov.json: ${doccovSpec.summary.score}% coverage, ${doccovSpec.summary.drift.total} drift issues`));
2513
3441
  } else {
2514
- steps.complete(`Generated ${options.output}/openpkg.json`);
2515
- log(chalk10.gray(` ${getArrayLength(normalized.exports)} exports`));
2516
- log(chalk10.gray(` ${getArrayLength(normalized.types)} types`));
3442
+ spin.success(`Generated ${options.output}/openpkg.json`);
3443
+ log(chalk11.gray(` ${getArrayLength(normalized.exports)} exports`));
3444
+ log(chalk11.gray(` ${getArrayLength(normalized.types)} types`));
2517
3445
  }
2518
3446
  }
2519
3447
  const schemaExtraction = normalized.generation?.analysis?.schemaExtraction;
@@ -2521,63 +3449,63 @@ function registerSpecCommand(program, dependencies = {}) {
2521
3449
  const pm = await detectPackageManager(fileSystem);
2522
3450
  const buildCmd = pm.name === "npm" ? "npm run build" : `${pm.name} run build`;
2523
3451
  log("");
2524
- log(chalk10.yellow("⚠ Runtime extraction requested but no schemas extracted."));
2525
- log(chalk10.yellow(` Ensure project is built (${buildCmd}) and dist/ exists.`));
3452
+ log(chalk11.yellow("⚠ Runtime extraction requested but no schemas extracted."));
3453
+ log(chalk11.yellow(` Ensure project is built (${buildCmd}) and dist/ exists.`));
2526
3454
  }
2527
3455
  if (options.verbose && normalized.generation) {
2528
3456
  const gen = normalized.generation;
2529
3457
  log("");
2530
- log(chalk10.bold("Generation Info"));
2531
- log(chalk10.gray(` Timestamp: ${gen.timestamp}`));
2532
- log(chalk10.gray(` Generator: ${gen.generator.name}@${gen.generator.version}`));
2533
- log(chalk10.gray(` Entry point: ${gen.analysis.entryPoint}`));
2534
- log(chalk10.gray(` Detected via: ${gen.analysis.entryPointSource}`));
2535
- log(chalk10.gray(` Declaration only: ${gen.analysis.isDeclarationOnly ? "yes" : "no"}`));
2536
- log(chalk10.gray(` External types: ${gen.analysis.resolvedExternalTypes ? "resolved" : "skipped"}`));
3458
+ log(chalk11.bold("Generation Info"));
3459
+ log(chalk11.gray(` Timestamp: ${gen.timestamp}`));
3460
+ log(chalk11.gray(` Generator: ${gen.generator.name}@${gen.generator.version}`));
3461
+ log(chalk11.gray(` Entry point: ${gen.analysis.entryPoint}`));
3462
+ log(chalk11.gray(` Detected via: ${gen.analysis.entryPointSource}`));
3463
+ log(chalk11.gray(` Declaration only: ${gen.analysis.isDeclarationOnly ? "yes" : "no"}`));
3464
+ log(chalk11.gray(` External types: ${gen.analysis.resolvedExternalTypes ? "resolved" : "skipped"}`));
2537
3465
  if (gen.analysis.maxTypeDepth) {
2538
- log(chalk10.gray(` Max type depth: ${gen.analysis.maxTypeDepth}`));
3466
+ log(chalk11.gray(` Max type depth: ${gen.analysis.maxTypeDepth}`));
2539
3467
  }
2540
3468
  if (gen.analysis.schemaExtraction) {
2541
3469
  const se = gen.analysis.schemaExtraction;
2542
- log(chalk10.gray(` Schema extraction: ${se.method}`));
3470
+ log(chalk11.gray(` Schema extraction: ${se.method}`));
2543
3471
  if (se.runtimeCount) {
2544
- log(chalk10.gray(` Runtime schemas: ${se.runtimeCount} (${se.vendors?.join(", ")})`));
3472
+ log(chalk11.gray(` Runtime schemas: ${se.runtimeCount} (${se.vendors?.join(", ")})`));
2545
3473
  }
2546
3474
  }
2547
3475
  log("");
2548
- log(chalk10.bold("Environment"));
2549
- log(chalk10.gray(` node_modules: ${gen.environment.hasNodeModules ? "found" : "not found"}`));
3476
+ log(chalk11.bold("Environment"));
3477
+ log(chalk11.gray(` node_modules: ${gen.environment.hasNodeModules ? "found" : "not found"}`));
2550
3478
  if (gen.environment.packageManager) {
2551
- log(chalk10.gray(` Package manager: ${gen.environment.packageManager}`));
3479
+ log(chalk11.gray(` Package manager: ${gen.environment.packageManager}`));
2552
3480
  }
2553
3481
  if (gen.environment.isMonorepo) {
2554
- log(chalk10.gray(` Monorepo: yes`));
3482
+ log(chalk11.gray(` Monorepo: yes`));
2555
3483
  }
2556
3484
  if (gen.environment.targetPackage) {
2557
- log(chalk10.gray(` Target package: ${gen.environment.targetPackage}`));
3485
+ log(chalk11.gray(` Target package: ${gen.environment.targetPackage}`));
2558
3486
  }
2559
3487
  if (gen.issues.length > 0) {
2560
3488
  log("");
2561
- log(chalk10.bold("Issues"));
3489
+ log(chalk11.bold("Issues"));
2562
3490
  for (const issue of gen.issues) {
2563
- const prefix = issue.severity === "error" ? chalk10.red(">") : issue.severity === "warning" ? chalk10.yellow(">") : chalk10.cyan(">");
2564
- log(`${prefix} [${issue.code}] ${issue.message}`);
3491
+ const prefix2 = issue.severity === "error" ? chalk11.red(">") : issue.severity === "warning" ? chalk11.yellow(">") : chalk11.cyan(">");
3492
+ log(`${prefix2} [${issue.code}] ${issue.message}`);
2565
3493
  if (issue.suggestion) {
2566
- log(chalk10.gray(` ${issue.suggestion}`));
3494
+ log(chalk11.gray(` ${issue.suggestion}`));
2567
3495
  }
2568
3496
  }
2569
3497
  }
2570
3498
  }
2571
3499
  if (options.showDiagnostics && result.diagnostics.length > 0) {
2572
3500
  log("");
2573
- log(chalk10.bold("Diagnostics"));
3501
+ log(chalk11.bold("Diagnostics"));
2574
3502
  for (const diagnostic of result.diagnostics) {
2575
- const prefix = diagnostic.severity === "error" ? chalk10.red(">") : diagnostic.severity === "warning" ? chalk10.yellow(">") : chalk10.cyan(">");
2576
- log(formatDiagnosticOutput(prefix, diagnostic, targetDir));
3503
+ const prefix2 = diagnostic.severity === "error" ? chalk11.red(">") : diagnostic.severity === "warning" ? chalk11.yellow(">") : chalk11.cyan(">");
3504
+ log(formatDiagnosticOutput(prefix2, diagnostic, targetDir));
2577
3505
  }
2578
3506
  }
2579
3507
  } catch (commandError) {
2580
- error(chalk10.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
3508
+ error(chalk11.red("Error:"), commandError instanceof Error ? commandError.message : commandError);
2581
3509
  process.exit(1);
2582
3510
  }
2583
3511
  });
@@ -2597,7 +3525,7 @@ import {
2597
3525
  renderSparkline,
2598
3526
  saveSnapshot
2599
3527
  } from "@doccov/sdk";
2600
- import chalk11 from "chalk";
3528
+ import chalk12 from "chalk";
2601
3529
  function formatDate(timestamp) {
2602
3530
  const date = new Date(timestamp);
2603
3531
  return date.toLocaleDateString("en-US", {
@@ -2610,19 +3538,19 @@ function formatDate(timestamp) {
2610
3538
  }
2611
3539
  function getColorForScore(score) {
2612
3540
  if (score >= 90)
2613
- return chalk11.green;
3541
+ return chalk12.green;
2614
3542
  if (score >= 70)
2615
- return chalk11.yellow;
3543
+ return chalk12.yellow;
2616
3544
  if (score >= 50)
2617
- return chalk11.hex("#FFA500");
2618
- return chalk11.red;
3545
+ return chalk12.hex("#FFA500");
3546
+ return chalk12.red;
2619
3547
  }
2620
3548
  function formatSnapshot(snapshot) {
2621
3549
  const color = getColorForScore(snapshot.coverageScore);
2622
3550
  const date = formatDate(snapshot.timestamp);
2623
3551
  const version2 = snapshot.version ? ` v${snapshot.version}` : "";
2624
- const commit = snapshot.commit ? chalk11.gray(` (${snapshot.commit.slice(0, 7)})`) : "";
2625
- return `${chalk11.gray(date)} ${color(`${snapshot.coverageScore}%`)} ${snapshot.documentedExports}/${snapshot.totalExports} exports${version2}${commit}`;
3552
+ const commit = snapshot.commit ? chalk12.gray(` (${snapshot.commit.slice(0, 7)})`) : "";
3553
+ return `${chalk12.gray(date)} ${color(`${snapshot.coverageScore}%`)} ${snapshot.documentedExports}/${snapshot.totalExports} exports${version2}${commit}`;
2626
3554
  }
2627
3555
  function formatWeekDate(timestamp) {
2628
3556
  const date = new Date(timestamp);
@@ -2630,10 +3558,10 @@ function formatWeekDate(timestamp) {
2630
3558
  }
2631
3559
  function formatVelocity(velocity) {
2632
3560
  if (velocity > 0)
2633
- return chalk11.green(`+${velocity}%/day`);
3561
+ return chalk12.green(`+${velocity}%/day`);
2634
3562
  if (velocity < 0)
2635
- return chalk11.red(`${velocity}%/day`);
2636
- return chalk11.gray("0%/day");
3563
+ return chalk12.red(`${velocity}%/day`);
3564
+ return chalk12.gray("0%/day");
2637
3565
  }
2638
3566
  function registerTrendsCommand(program) {
2639
3567
  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) => {
@@ -2643,42 +3571,44 @@ function registerTrendsCommand(program) {
2643
3571
  const keepCount = parseInt(options.prune, 10);
2644
3572
  if (!Number.isNaN(keepCount)) {
2645
3573
  const deleted = pruneHistory(cwd, keepCount);
2646
- console.log(chalk11.green(`Pruned ${deleted} old snapshots, kept ${keepCount} most recent`));
3574
+ console.log(chalk12.green(`Pruned ${deleted} old snapshots, kept ${keepCount} most recent`));
2647
3575
  } else {
2648
3576
  const deleted = pruneByTier(cwd, tier);
2649
- console.log(chalk11.green(`Pruned ${deleted} snapshots older than ${RETENTION_DAYS[tier]} days`));
3577
+ console.log(chalk12.green(`Pruned ${deleted} snapshots older than ${RETENTION_DAYS[tier]} days`));
2650
3578
  }
2651
3579
  return;
2652
3580
  }
2653
3581
  if (options.record) {
2654
3582
  const specPath = path9.resolve(cwd, "openpkg.json");
2655
3583
  if (!fs7.existsSync(specPath)) {
2656
- console.error(chalk11.red("No openpkg.json found. Run `doccov spec` first to generate a spec."));
3584
+ console.error(chalk12.red("No openpkg.json found. Run `doccov spec` first to generate a spec."));
2657
3585
  process.exit(1);
2658
3586
  }
3587
+ const spin = spinner("Recording coverage snapshot");
2659
3588
  try {
2660
3589
  const specContent = fs7.readFileSync(specPath, "utf-8");
2661
3590
  const spec = JSON.parse(specContent);
2662
3591
  const trend = getTrend(spec, cwd);
2663
3592
  saveSnapshot(trend.current, cwd);
2664
- console.log(chalk11.green("Recorded coverage snapshot:"));
3593
+ spin.success("Recorded coverage snapshot");
2665
3594
  console.log(formatSnapshot(trend.current));
2666
3595
  if (trend.delta !== undefined) {
2667
3596
  const deltaStr = formatDelta2(trend.delta);
2668
- const deltaColor = trend.delta > 0 ? chalk11.green : trend.delta < 0 ? chalk11.red : chalk11.gray;
2669
- console.log(chalk11.gray("Change from previous:"), deltaColor(deltaStr));
3597
+ const deltaColor = trend.delta > 0 ? chalk12.green : trend.delta < 0 ? chalk12.red : chalk12.gray;
3598
+ console.log(chalk12.gray("Change from previous:"), deltaColor(deltaStr));
2670
3599
  }
2671
3600
  return;
2672
3601
  } catch (error) {
2673
- console.error(chalk11.red("Failed to read openpkg.json:"), error instanceof Error ? error.message : error);
3602
+ spin.fail("Failed to record snapshot");
3603
+ console.error(chalk12.red("Failed to read openpkg.json:"), error instanceof Error ? error.message : error);
2674
3604
  process.exit(1);
2675
3605
  }
2676
3606
  }
2677
3607
  const snapshots = loadSnapshots(cwd);
2678
3608
  const limit = parseInt(options.limit ?? "10", 10);
2679
3609
  if (snapshots.length === 0) {
2680
- console.log(chalk11.yellow("No coverage history found."));
2681
- console.log(chalk11.gray("Run `doccov trends --record` to save the current coverage."));
3610
+ console.log(chalk12.yellow("No coverage history found."));
3611
+ console.log(chalk12.gray("Run `doccov trends --record` to save the current coverage."));
2682
3612
  return;
2683
3613
  }
2684
3614
  if (options.json) {
@@ -2693,17 +3623,17 @@ function registerTrendsCommand(program) {
2693
3623
  }
2694
3624
  const sparklineData = snapshots.slice(0, 10).map((s) => s.coverageScore).reverse();
2695
3625
  const sparkline = renderSparkline(sparklineData);
2696
- console.log(chalk11.bold("Coverage Trends"));
2697
- console.log(chalk11.gray(`Package: ${snapshots[0].package}`));
2698
- console.log(chalk11.gray(`Sparkline: ${sparkline}`));
3626
+ console.log(chalk12.bold("Coverage Trends"));
3627
+ console.log(chalk12.gray(`Package: ${snapshots[0].package}`));
3628
+ console.log(chalk12.gray(`Sparkline: ${sparkline}`));
2699
3629
  console.log("");
2700
3630
  if (snapshots.length >= 2) {
2701
3631
  const oldest = snapshots[snapshots.length - 1];
2702
3632
  const newest = snapshots[0];
2703
3633
  const overallDelta = newest.coverageScore - oldest.coverageScore;
2704
3634
  const deltaStr = formatDelta2(overallDelta);
2705
- const deltaColor = overallDelta > 0 ? chalk11.green : overallDelta < 0 ? chalk11.red : chalk11.gray;
2706
- console.log(chalk11.gray("Overall trend:"), deltaColor(deltaStr), chalk11.gray(`(${snapshots.length} snapshots)`));
3635
+ const deltaColor = overallDelta > 0 ? chalk12.green : overallDelta < 0 ? chalk12.red : chalk12.gray;
3636
+ console.log(chalk12.gray("Overall trend:"), deltaColor(deltaStr), chalk12.gray(`(${snapshots.length} snapshots)`));
2707
3637
  console.log("");
2708
3638
  }
2709
3639
  if (options.extended) {
@@ -2713,8 +3643,8 @@ function registerTrendsCommand(program) {
2713
3643
  const specContent = fs7.readFileSync(specPath, "utf-8");
2714
3644
  const spec = JSON.parse(specContent);
2715
3645
  const extended = getExtendedTrend(spec, cwd, { tier });
2716
- console.log(chalk11.bold("Extended Analysis"));
2717
- console.log(chalk11.gray(`Tier: ${tier} (${RETENTION_DAYS[tier]}-day retention)`));
3646
+ console.log(chalk12.bold("Extended Analysis"));
3647
+ console.log(chalk12.gray(`Tier: ${tier} (${RETENTION_DAYS[tier]}-day retention)`));
2718
3648
  console.log("");
2719
3649
  console.log(" Velocity:");
2720
3650
  console.log(` 7-day: ${formatVelocity(extended.velocity7d)}`);
@@ -2723,47 +3653,47 @@ function registerTrendsCommand(program) {
2723
3653
  console.log(` 90-day: ${formatVelocity(extended.velocity90d)}`);
2724
3654
  }
2725
3655
  console.log("");
2726
- const projColor = extended.projected30d >= extended.trend.current.coverageScore ? chalk11.green : chalk11.red;
3656
+ const projColor = extended.projected30d >= extended.trend.current.coverageScore ? chalk12.green : chalk12.red;
2727
3657
  console.log(` Projected (30d): ${projColor(`${extended.projected30d}%`)}`);
2728
- console.log(` All-time high: ${chalk11.green(`${extended.allTimeHigh}%`)}`);
2729
- console.log(` All-time low: ${chalk11.red(`${extended.allTimeLow}%`)}`);
3658
+ console.log(` All-time high: ${chalk12.green(`${extended.allTimeHigh}%`)}`);
3659
+ console.log(` All-time low: ${chalk12.red(`${extended.allTimeLow}%`)}`);
2730
3660
  if (extended.dataRange) {
2731
3661
  const startDate = formatWeekDate(extended.dataRange.start);
2732
3662
  const endDate = formatWeekDate(extended.dataRange.end);
2733
- console.log(chalk11.gray(` Data range: ${startDate} - ${endDate}`));
3663
+ console.log(chalk12.gray(` Data range: ${startDate} - ${endDate}`));
2734
3664
  }
2735
3665
  console.log("");
2736
3666
  if (options.weekly && extended.weeklySummaries.length > 0) {
2737
- console.log(chalk11.bold("Weekly Summary"));
3667
+ console.log(chalk12.bold("Weekly Summary"));
2738
3668
  const weekLimit = Math.min(extended.weeklySummaries.length, 8);
2739
3669
  for (let i = 0;i < weekLimit; i++) {
2740
3670
  const week = extended.weeklySummaries[i];
2741
3671
  const weekStart = formatWeekDate(week.weekStart);
2742
3672
  const weekEnd = formatWeekDate(week.weekEnd);
2743
- const deltaColor = week.delta > 0 ? chalk11.green : week.delta < 0 ? chalk11.red : chalk11.gray;
3673
+ const deltaColor = week.delta > 0 ? chalk12.green : week.delta < 0 ? chalk12.red : chalk12.gray;
2744
3674
  const deltaStr = week.delta > 0 ? `+${week.delta}%` : `${week.delta}%`;
2745
3675
  console.log(` ${weekStart} - ${weekEnd}: ${week.avgCoverage}% avg ${deltaColor(deltaStr)} (${week.snapshotCount} snapshots)`);
2746
3676
  }
2747
3677
  if (extended.weeklySummaries.length > weekLimit) {
2748
- console.log(chalk11.gray(` ... and ${extended.weeklySummaries.length - weekLimit} more weeks`));
3678
+ console.log(chalk12.gray(` ... and ${extended.weeklySummaries.length - weekLimit} more weeks`));
2749
3679
  }
2750
3680
  console.log("");
2751
3681
  }
2752
3682
  } catch {
2753
- console.log(chalk11.yellow("Could not load openpkg.json for extended analysis"));
3683
+ console.log(chalk12.yellow("Could not load openpkg.json for extended analysis"));
2754
3684
  console.log("");
2755
3685
  }
2756
3686
  }
2757
3687
  }
2758
- console.log(chalk11.bold("History"));
3688
+ console.log(chalk12.bold("History"));
2759
3689
  const displaySnapshots = snapshots.slice(0, limit);
2760
3690
  for (let i = 0;i < displaySnapshots.length; i++) {
2761
3691
  const snapshot = displaySnapshots[i];
2762
- const prefix = i === 0 ? chalk11.cyan("→") : " ";
2763
- console.log(`${prefix} ${formatSnapshot(snapshot)}`);
3692
+ const prefix2 = i === 0 ? chalk12.cyan("→") : " ";
3693
+ console.log(`${prefix2} ${formatSnapshot(snapshot)}`);
2764
3694
  }
2765
3695
  if (snapshots.length > limit) {
2766
- console.log(chalk11.gray(` ... and ${snapshots.length - limit} more`));
3696
+ console.log(chalk12.gray(` ... and ${snapshots.length - limit} more`));
2767
3697
  }
2768
3698
  });
2769
3699
  }