@halecraft/verify 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -40,7 +40,7 @@ import { defineConfig } from "@halecraft/verify";
40
40
 
41
41
  export default defineConfig({
42
42
  tasks: [
43
- { key: "format", run: "biome check ." },
43
+ { key: "format", run: "biome check .", fix: "biome check --write ." },
44
44
  { key: "types", run: "tsc --noEmit" },
45
45
  { key: "test", run: "vitest run" },
46
46
  ],
@@ -73,6 +73,12 @@ pnpm exec verify --verbose
73
73
 
74
74
  # Output JSON (for CI)
75
75
  pnpm exec verify --json
76
+
77
+ # Run fix commands where defined
78
+ pnpm exec verify --fix
79
+
80
+ # Fix a specific task
81
+ pnpm exec verify format --fix
76
82
  ```
77
83
 
78
84
  Or you can add `"verify": "verify"` to package.json scripts and run:
@@ -108,6 +114,10 @@ interface VerificationNode {
108
114
  timeout?: number;
109
115
  };
110
116
 
117
+ // Command to run in fix mode (leaf nodes only)
118
+ // Falls back to run when not specified
119
+ fix?: string | { cmd: string; args: string[]; cwd?: string; env?: Record<string, string | null>; timeout?: number };
120
+
111
121
  // Child tasks (for grouping)
112
122
  children?: VerificationNode[];
113
123
 
@@ -135,9 +145,9 @@ interface VerificationNode {
135
145
  }
136
146
  ```
137
147
 
138
- ### Smart Output Suppression with `reportingDependsOn`
148
+ ### Dependency-Aware Failure Reporting with `reportingDependsOn`
139
149
 
140
- When a syntax error occurs, multiple tools often report the same underlying issue (Biome, tsc, esbuild all complaining about the same missing comma). The `reportingDependsOn` option reduces this noise by suppressing redundant failure output.
150
+ When a syntax error occurs, multiple tools often report the same underlying issue (Biome, tsc, esbuild all complaining about the same missing comma). The `reportingDependsOn` option reduces this noise by marking dependent failures as blocked.
141
151
 
142
152
  ```typescript
143
153
  import { defineConfig } from "@halecraft/verify";
@@ -156,17 +166,17 @@ export default defineConfig({
156
166
 
157
167
  - All tasks still execute in parallel (no speed regression)
158
168
  - When a dependency fails (e.g., `format`), dependent tasks are terminated early for faster feedback
159
- - Dependent tasks that also fail are marked as "suppressed"
169
+ - Dependent tasks that also fail are marked as "blocked"
160
170
  - Only the root cause failure shows detailed logs
161
- - Suppressed tasks show `⊘ suppressed` instead of `✗ failed`
171
+ - Blocked tasks show `⊘ blocked` instead of `✗ failed`
162
172
 
163
173
  **Before (noisy):**
164
174
 
165
175
  ```
166
- ✗ format (syntax error at line 14)
167
- ✗ types (syntax error at line 14)
168
- ✗ logic (syntax error at line 14)
169
- ✗ build (syntax error at line 14)
176
+ ✗ format failed (syntax error at line 14)
177
+ ✗ types failed (syntax error at line 14)
178
+ ✗ logic failed (syntax error at line 14)
179
+ ✗ build failed (syntax error at line 14)
170
180
 
171
181
  ==== FORMAT FAIL ====
172
182
  [50 lines of biome output]
@@ -184,10 +194,10 @@ export default defineConfig({
184
194
  **After (clean):**
185
195
 
186
196
  ```
187
- ✗ format (syntax error at line 14)
188
- ⊘ types (suppressed - format failed)
189
- ⊘ logic (suppressed - format failed)
190
- ⊘ build (suppressed - format failed)
197
+ ✗ format failed (syntax error at line 14)
198
+ ⊘ types blocked (by format, 120ms)
199
+ ⊘ logic blocked (by format, 150ms)
200
+ ⊘ build blocked (by format, 130ms)
191
201
 
192
202
  ==== FORMAT FAIL ====
193
203
  [50 lines of biome output]
@@ -277,6 +287,52 @@ When a command exceeds its timeout:
277
287
 
278
288
  **Note:** For object commands, the `timeout` on the command takes precedence over the node-level `timeout`.
279
289
 
290
+ ### Fix Mode
291
+
292
+ Many verification tools have a dual nature: a check mode that reports problems and a fix mode that auto-corrects them. The `fix` property lets you declare the fix variant alongside `run`:
293
+
294
+ ```typescript
295
+ import { defineConfig } from "@halecraft/verify";
296
+
297
+ export default defineConfig({
298
+ tasks: [
299
+ { key: "format", run: "biome check .", fix: "biome check --write ." },
300
+ { key: "lint", run: "eslint .", fix: "eslint . --fix" },
301
+ { key: "types", run: "tsc --noEmit" }, // No fix mode for type-checking
302
+ { key: "test", run: "vitest run" },
303
+ ],
304
+ });
305
+ ```
306
+
307
+ Run fix commands with the `--fix` flag:
308
+
309
+ ```bash
310
+ # Fix all tasks that define a fix command (others run normally)
311
+ verify --fix
312
+
313
+ # Fix a specific task
314
+ verify format --fix
315
+
316
+ # Combine with passthrough args
317
+ verify format --fix -- --extra-arg
318
+ ```
319
+
320
+ When a normal `verify` run fails and at least one failed task has a `fix` command defined, the output includes a hint:
321
+
322
+ ```
323
+ ✗ format failed (2 errors in 24 files)
324
+ ✓ types verified (no errors, 150ms)
325
+
326
+ == verification: Failed ==
327
+
328
+ 💡 Tip: run "verify --fix" to attempt auto-fixes
329
+ ```
330
+
331
+ **Behavior:**
332
+ - Tasks without `fix` run their normal `run` command even with `--fix`
333
+ - Passthrough args, timeouts, env inheritance, and `reportingDependsOn` all work identically with fix commands
334
+ - Fix mode is a simple command swap — there's no automatic re-check after fixing
335
+
280
336
  ### Environment Variables
281
337
 
282
338
  Set environment variables at the config level (applies to all tasks) or at the task level (inherits to children):
@@ -347,6 +403,7 @@ Flags:
347
403
  --init Initialize a new verify.config.ts file
348
404
  --force Overwrite existing config file (with --init)
349
405
  --yes, -y Skip interactive prompts, auto-accept detected tasks
406
+ --fix Run fix commands instead of check commands where available
350
407
  --help, -h Show this help message
351
408
  ```
352
409
 
@@ -459,6 +516,7 @@ interface VerifyResult {
459
516
  finishedAt: string; // ISO timestamp when run finished
460
517
  durationMs: number; // Total duration in milliseconds
461
518
  tasks: TaskResult[]; // Individual task results
519
+ fixAvailable?: boolean; // True when at least one failed task defines a fix command
462
520
  }
463
521
 
464
522
  interface TaskResult {
@@ -469,8 +527,8 @@ interface TaskResult {
469
527
  durationMs: number; // Duration in milliseconds
470
528
  output: string; // Raw output
471
529
  summaryLine: string; // Parsed summary
472
- suppressed?: boolean; // True if output was suppressed
473
- suppressedBy?: string; // Path of dependency that caused suppression
530
+ blocked?: boolean; // True if blocked by a dependency failure
531
+ blockedBy?: string; // Path of dependency that caused the block
474
532
  timedOut?: boolean; // True if task exceeded its timeout
475
533
  children?: TaskResult[]; // Child results (for group nodes)
476
534
  }
package/bin/verify.mjs CHANGED
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // JSON import requires Node 18.20+ (engines permits 18.0+ but this is theoretical;
4
+ // Node 18 is EOL since April 2025 and real-world users are on 18.20+ or newer.)
5
+ import pkg from "../package.json" with { type: "json" }
3
6
  import { cli } from "cleye"
4
7
  import {
5
8
  AmbiguousTaskError,
@@ -11,7 +14,7 @@ import {
11
14
  const argv = cli(
12
15
  {
13
16
  name: "verify",
14
- version: "1.0.0",
17
+ version: pkg.version,
15
18
  description: "Hierarchical verification runner with parallel execution",
16
19
 
17
20
  parameters: [
@@ -63,6 +66,11 @@ const argv = cli(
63
66
  description: "Initialize a new verify.config.ts file",
64
67
  default: false,
65
68
  },
69
+ fix: {
70
+ type: Boolean,
71
+ description: "Run fix commands instead of check commands where available",
72
+ default: false,
73
+ },
66
74
  force: {
67
75
  type: Boolean,
68
76
  description: "Overwrite existing config file (with --init)",
@@ -85,6 +93,8 @@ const argv = cli(
85
93
  "verify --top-level Show only top-level tasks",
86
94
  "verify --json Output JSON for CI",
87
95
  "verify --logs=all Show all output",
96
+ "verify --fix Run auto-fix commands where defined",
97
+ "verify format --fix Fix a specific task",
88
98
  "verify --init Create config interactively",
89
99
  "verify --init -y Create config with all detected tasks",
90
100
  "verify --init --force Overwrite existing config",
@@ -138,6 +148,8 @@ async function main() {
138
148
  cwd: flags.config,
139
149
  topLevelOnly: flags.topLevel,
140
150
  noTty: flags.noTty,
151
+ quiet: flags.quiet,
152
+ fix: flags.fix,
141
153
  passthrough: passthrough && passthrough.length > 0 ? passthrough : undefined,
142
154
  }
143
155
 
package/dist/index.d.ts CHANGED
@@ -26,6 +26,7 @@ interface ParsedResult {
26
26
  metrics?: {
27
27
  passed?: number;
28
28
  failed?: number;
29
+ skipped?: number;
29
30
  total?: number;
30
31
  duration?: string;
31
32
  errors?: number;
@@ -55,6 +56,8 @@ interface VerificationNode {
55
56
  name?: string;
56
57
  /** Command to run (leaf nodes only) - string commands are executed via shell */
57
58
  run?: VerificationCommand | string;
59
+ /** Command to run in fix mode (leaf nodes only). Falls back to run when not specified. */
60
+ fix?: VerificationCommand | string;
58
61
  /** Child verification nodes */
59
62
  children?: VerificationNode[];
60
63
  /** Execution strategy for children (default: parallel) */
@@ -67,7 +70,7 @@ interface VerificationNode {
67
70
  failureLabel?: string;
68
71
  /**
69
72
  * Tasks that must pass for this task's failure to be reported.
70
- * If any dependency fails, this task's failure output is suppressed.
73
+ * If any dependency fails, this task's failure is marked as blocked.
71
74
  * Can specify task keys (e.g., "format") or full paths (e.g., "types:tsc").
72
75
  */
73
76
  reportingDependsOn?: string[];
@@ -104,8 +107,12 @@ interface VerifyOptions {
104
107
  topLevelOnly?: boolean;
105
108
  /** Force sequential output (disable live dashboard) */
106
109
  noTty?: boolean;
110
+ /** Suppress in-progress output, show only final summary */
111
+ quiet?: boolean;
107
112
  /** Arguments to pass through to the underlying command (requires single task filter) */
108
113
  passthrough?: string[];
114
+ /** Run fix commands instead of check commands where available */
115
+ fix?: boolean;
109
116
  }
110
117
  /**
111
118
  * Package discovery options for monorepos
@@ -158,15 +165,15 @@ interface TaskResult {
158
165
  /** Child results (for group nodes) */
159
166
  children?: TaskResult[];
160
167
  /**
161
- * Whether this task's failure output was suppressed due to a dependency failure.
162
- * The task still ran and failed, but its output is hidden to reduce noise.
168
+ * Whether this task's failure was blocked by a dependency failure.
169
+ * The task ran but its failure output is hidden because the root cause is the dependency.
163
170
  */
164
- suppressed?: boolean;
171
+ blocked?: boolean;
165
172
  /**
166
- * The path of the dependency task that caused this task to be suppressed.
167
- * Only set when suppressed is true.
173
+ * The path of the dependency task that caused this task to be blocked.
174
+ * Only set when blocked is true.
168
175
  */
169
- suppressedBy?: string;
176
+ blockedBy?: string;
170
177
  /**
171
178
  * Whether this task was terminated due to timeout.
172
179
  * Only set when the task exceeded its configured timeout.
@@ -187,6 +194,8 @@ interface VerifyResult {
187
194
  durationMs: number;
188
195
  /** Individual task results */
189
196
  tasks: TaskResult[];
197
+ /** True when at least one failed task defines a fix command */
198
+ fixAvailable?: boolean;
190
199
  }
191
200
 
192
201
  /**
@@ -320,6 +329,8 @@ interface DetectedTask {
320
329
  * Auto-populated based on category (types/logic/build depend on format).
321
330
  */
322
331
  reportingDependsOn?: string[];
332
+ /** Command to run in fix mode (auto-detected for known tools) */
333
+ fixCommand?: string;
323
334
  }
324
335
  /**
325
336
  * Detect tasks with proper package manager commands
@@ -468,6 +479,16 @@ interface Reporter {
468
479
  outputLogs(results: TaskResult[], logsMode: "all" | "failed" | "none"): void;
469
480
  /** Called to output final summary */
470
481
  outputSummary(result: VerifyResult): void;
482
+ /** Called to hint that fix commands are available */
483
+ outputFixHint?(result: VerifyResult): void;
484
+ }
485
+ /**
486
+ * Terminal capability context — the single point where globals are read.
487
+ * Passed to pure decision functions for testability.
488
+ */
489
+ interface TerminalContext {
490
+ isTTY: boolean;
491
+ env: Record<string, string | undefined>;
471
492
  }
472
493
  /**
473
494
  * Base Reporter - common functionality for all reporters
@@ -476,7 +497,7 @@ declare abstract class BaseReporter implements Reporter {
476
497
  protected colorEnabled: boolean;
477
498
  protected stream: NodeJS.WriteStream;
478
499
  protected taskDepths: Map<string, number>;
479
- constructor(options?: VerifyOptions);
500
+ constructor(options?: VerifyOptions, ctx?: TerminalContext);
480
501
  /**
481
502
  * Apply ANSI color code to string (if colors enabled)
482
503
  */
@@ -490,9 +511,9 @@ declare abstract class BaseReporter implements Reporter {
490
511
  */
491
512
  protected failMark(): string;
492
513
  /**
493
- * Get suppressed mark (⊘ or SUPPRESSED)
514
+ * Get blocked mark (⊘ or BLOCK)
494
515
  */
495
- protected suppressedMark(): string;
516
+ protected blockedMark(): string;
496
517
  /**
497
518
  * Get arrow symbol (→ or ->)
498
519
  */
@@ -509,6 +530,11 @@ declare abstract class BaseReporter implements Reporter {
509
530
  * Collect task depths from verification tree using walkNodes
510
531
  */
511
532
  protected collectTaskDepths(nodes: VerificationNode[]): void;
533
+ /**
534
+ * Format a completed task result line in name-first format.
535
+ * Shared by LiveDashboardReporter and SequentialReporter.
536
+ */
537
+ protected formatResultLine(name: string, result: TaskResult): string;
512
538
  /**
513
539
  * Extract summary from task result
514
540
  */
@@ -525,6 +551,7 @@ declare abstract class BaseReporter implements Reporter {
525
551
  * Output final summary
526
552
  */
527
553
  outputSummary(result: VerifyResult): void;
554
+ outputFixHint(result: VerifyResult): void;
528
555
  abstract onStart(tasks: VerificationNode[]): void;
529
556
  abstract onTaskStart(path: string, key: string): void;
530
557
  abstract onTaskComplete(result: TaskResult): void;
@@ -539,7 +566,7 @@ declare class LiveDashboardReporter extends BaseReporter {
539
566
  private taskOrder;
540
567
  private spinner;
541
568
  private lineCount;
542
- constructor(options?: VerifyOptions);
569
+ constructor(options?: VerifyOptions, ctx?: TerminalContext);
543
570
  /**
544
571
  * Initialize task list from verification nodes
545
572
  */
@@ -574,7 +601,7 @@ declare class LiveDashboardReporter extends BaseReporter {
574
601
  */
575
602
  declare class SequentialReporter extends BaseReporter {
576
603
  private topLevelOnly;
577
- constructor(options?: VerifyOptions);
604
+ constructor(options?: VerifyOptions, ctx?: TerminalContext);
578
605
  onStart(tasks: VerificationNode[]): void;
579
606
  /**
580
607
  * Check if task should be displayed based on topLevelOnly flag
@@ -606,12 +633,21 @@ declare class QuietReporter extends BaseReporter {
606
633
  onFinish(): void;
607
634
  outputLogs(_results: TaskResult[], _logsMode: "all" | "failed" | "none"): void;
608
635
  outputSummary(result: VerifyResult): void;
636
+ outputFixHint(result: VerifyResult): void;
609
637
  }
610
638
  /**
611
- * Create appropriate reporter based on options
639
+ * Create appropriate reporter based on options.
640
+ *
641
+ * Decision tree: JSON → Quiet → LiveDashboard → Sequential
612
642
  */
613
643
  declare function createReporter(options: VerifyOptions): Reporter;
614
644
 
645
+ /**
646
+ * Resolve which command to execute for a node based on fix mode.
647
+ * Returns `node.fix` when in fix mode and a fix command is defined,
648
+ * otherwise falls back to `node.run`.
649
+ */
650
+ declare function resolveCommand(node: VerificationNode, fix: boolean): VerificationCommand | string | undefined;
615
651
  interface RunnerCallbacks {
616
652
  onTaskStart?: (path: string, key: string) => void;
617
653
  onTaskComplete?: (result: TaskResult) => void;
@@ -665,6 +701,12 @@ declare function walkNodes(nodes: VerificationNode[], visitor: NodeVisitor, pare
665
701
  * Collect all task paths from a verification tree
666
702
  */
667
703
  declare function collectPaths(nodes: VerificationNode[]): string[];
704
+ /**
705
+ * Check whether at least one failed (non-blocked) task defines a fix command.
706
+ * Only considers tasks that actually ran and failed — filtered-out tasks
707
+ * won't appear in results and can't trigger a false positive.
708
+ */
709
+ declare function hasFixAvailable(tasks: VerificationNode[], results: TaskResult[]): boolean;
668
710
 
669
711
  /**
670
712
  * Run verification with the given config and options
@@ -675,4 +717,4 @@ declare function verify(config: VerifyConfig, cliOptions?: Partial<VerifyOptions
675
717
  */
676
718
  declare function verifyFromConfig(cwd?: string, cliOptions?: Partial<VerifyOptions>): Promise<VerifyResult>;
677
719
 
678
- export { AmbiguousTaskError, ConfigError, type DetectedTask, type DiscoveredPackage, type ExecutionStrategy, type InitOptions, type InitResult, JSONReporter, LiveDashboardReporter, type NodeVisitor, type OutputFormat, type OutputParser, PATH_SEPARATOR, type PackageDiscoveryOptions, type ParsedResult, type ParserId, ParserRegistry, QuietReporter, type Reporter, type ResolvedFilter, type RunnerCallbacks, SequentialReporter, SequentialReporter as TTYReporter, TaskNotFoundError, type TaskResult, type VerificationCommand, type VerificationNode, VerificationRunner, type VerifyConfig, type VerifyOptions, type VerifyResult, biomeParser, buildTaskPath, collectPaths, createReporter, defaultRegistry, defineConfig, defineTask, detectTasks, discoverPackages, findBestSuggestion, findConfigFile, generateConfigContent, genericParser, gotestParser, hasPackageChanged, loadConfig, loadConfigFromCwd, mergeOptions, parsers, resolveFilters, runInit, tscParser, validateConfig, verify, verifyFromConfig, vitestParser, walkNodes };
720
+ export { AmbiguousTaskError, ConfigError, type DetectedTask, type DiscoveredPackage, type ExecutionStrategy, type InitOptions, type InitResult, JSONReporter, LiveDashboardReporter, type NodeVisitor, type OutputFormat, type OutputParser, PATH_SEPARATOR, type PackageDiscoveryOptions, type ParsedResult, type ParserId, ParserRegistry, QuietReporter, type Reporter, type ResolvedFilter, type RunnerCallbacks, SequentialReporter, SequentialReporter as TTYReporter, TaskNotFoundError, type TaskResult, type TerminalContext, type VerificationCommand, type VerificationNode, VerificationRunner, type VerifyConfig, type VerifyOptions, type VerifyResult, biomeParser, buildTaskPath, collectPaths, createReporter, defaultRegistry, defineConfig, defineTask, detectTasks, discoverPackages, findBestSuggestion, findConfigFile, generateConfigContent, genericParser, gotestParser, hasFixAvailable, hasPackageChanged, loadConfig, loadConfigFromCwd, mergeOptions, parsers, resolveCommand, resolveFilters, runInit, tscParser, validateConfig, verify, verifyFromConfig, vitestParser, walkNodes };