diff --git a/src/commands/analysis.zig b/src/commands/analysis.zig index 0433900..55eb99a 100644 --- a/src/commands/analysis.zig +++ b/src/commands/analysis.zig @@ -27,10 +27,6 @@ pub const meta: framework.Meta = .{ .uppercase_first_arg = false, }; -comptime { - framework.validateCommandModule(@This()); -} - pub fn parseArgs(ctx: *framework.RunCtx, cmd_args: []const []const u8) !ParsedArgs { if (cmd_args.len > 0) { try cli.stderrPrint(ctx.io, "Error: 'analysis' takes no arguments\n"); diff --git a/src/commands/audit.zig b/src/commands/audit.zig index 176a1f4..7e5ed15 100644 --- a/src/commands/audit.zig +++ b/src/commands/audit.zig @@ -2280,10 +2280,6 @@ pub const meta: framework.Meta = .{ .uppercase_first_arg = false, }; -comptime { - framework.validateCommandModule(@This()); -} - pub fn parseArgs(_: *framework.RunCtx, cmd_args: []const []const u8) !ParsedArgs { var parsed: ParsedArgs = .{}; var i: usize = 0; diff --git a/src/commands/cache.zig b/src/commands/cache.zig index d1e4b21..1335a3d 100644 --- a/src/commands/cache.zig +++ b/src/commands/cache.zig @@ -35,10 +35,6 @@ pub const meta: framework.Meta = .{ .uppercase_first_arg = false, }; -comptime { - framework.validateCommandModule(@This()); -} - /// Data types to show in the stats table (skip candles_meta and meta — internal bookkeeping). const display_types = [_]DataType{ .candles_daily, diff --git a/src/commands/compare.zig b/src/commands/compare.zig index d1245bc..f7aa046 100644 --- a/src/commands/compare.zig +++ b/src/commands/compare.zig @@ -128,10 +128,6 @@ pub const meta: framework.Meta = .{ .uppercase_first_arg = false, }; -comptime { - framework.validateCommandModule(@This()); -} - pub fn parseArgs(ctx: *framework.RunCtx, cmd_args: []const []const u8) !ParsedArgs { const io = ctx.io; const today = ctx.today; diff --git a/src/commands/contributions.zig b/src/commands/contributions.zig index a789450..d84edae 100644 --- a/src/commands/contributions.zig +++ b/src/commands/contributions.zig @@ -228,10 +228,6 @@ pub const meta: framework.Meta = .{ .uppercase_first_arg = false, }; -comptime { - framework.validateCommandModule(@This()); -} - pub fn parseArgs(ctx: *framework.RunCtx, cmd_args: []const []const u8) !ParsedArgs { const io = ctx.io; const today = ctx.today; diff --git a/src/commands/divs.zig b/src/commands/divs.zig index 867e7aa..6fb3a00 100644 --- a/src/commands/divs.zig +++ b/src/commands/divs.zig @@ -27,10 +27,6 @@ pub const meta: framework.Meta = .{ , }; -comptime { - framework.validateCommandModule(@This()); -} - pub fn parseArgs(ctx: *framework.RunCtx, cmd_args: []const []const u8) !ParsedArgs { if (cmd_args.len < 1) { try cli.stderrPrint(ctx.io, "Error: 'divs' requires a symbol argument\n"); diff --git a/src/commands/earnings.zig b/src/commands/earnings.zig index b643e3c..e48c92b 100644 --- a/src/commands/earnings.zig +++ b/src/commands/earnings.zig @@ -33,10 +33,6 @@ pub const meta: framework.Meta = .{ , }; -comptime { - framework.validateCommandModule(@This()); -} - pub fn parseArgs(ctx: *framework.RunCtx, cmd_args: []const []const u8) !ParsedArgs { if (cmd_args.len < 1) { try cli.stderrPrint(ctx.io, "Error: 'earnings' requires a symbol argument\n"); diff --git a/src/commands/enrich.zig b/src/commands/enrich.zig index 94e322a..8e5e147 100644 --- a/src/commands/enrich.zig +++ b/src/commands/enrich.zig @@ -43,10 +43,6 @@ pub const meta: framework.Meta = .{ .uppercase_first_arg = false, }; -comptime { - framework.validateCommandModule(@This()); -} - pub fn parseArgs(ctx: *framework.RunCtx, cmd_args: []const []const u8) !ParsedArgs { if (cmd_args.len < 1) { try cli.stderrPrint(ctx.io, "Error: 'enrich' requires a portfolio file path or symbol\n"); diff --git a/src/commands/etf.zig b/src/commands/etf.zig index 7d29c7b..4906075 100644 --- a/src/commands/etf.zig +++ b/src/commands/etf.zig @@ -28,10 +28,6 @@ pub const meta: framework.Meta = .{ , }; -comptime { - framework.validateCommandModule(@This()); -} - pub fn parseArgs(ctx: *framework.RunCtx, cmd_args: []const []const u8) !ParsedArgs { if (cmd_args.len < 1) { try cli.stderrPrint(ctx.io, "Error: 'etf' requires a symbol argument\n"); diff --git a/src/commands/framework.zig b/src/commands/framework.zig index b80b084..28a3348 100644 --- a/src/commands/framework.zig +++ b/src/commands/framework.zig @@ -24,18 +24,20 @@ //! ``` //! //! The `validateCommandModule(Module)` walker enforces the contract at -//! comptime with copy-paste-ready error messages. +//! comptime with copy-paste-ready error messages. It's invoked from a +//! single registry walk in `src/main.zig`; individual command files +//! do NOT carry their own validation block. See the doc-comment on +//! `validateCommandModule` for the rationale. //! //! ## Global-option promotion criterion //! //! A flag belongs as a global iff it is *orthogonal* to every command's //! semantics (purely an I/O / output / cache-policy concern) AND every -//! command would honor it identically. Today's globals are -//! `--no-color`, `-p`, `-w` (and `--refresh` / `--no-refresh` once -//! commit 18c lands). `--as-of` does NOT pass this test because its -//! meaning varies between view-as-of (`projections`) and write-as-of -//! (`snapshot`), and the two-sided commands need two endpoints, not -//! one — so it stays per-command. +//! command would honor it identically. Today's globals are `--no-color`, +//! `-p`, `-w`, and `--refresh-data=`. `--as-of` does +//! NOT pass this test because its meaning varies between view-as-of +//! (`projections`) and write-as-of (`snapshot`), and the two-sided +//! commands need two endpoints, not one — so it stays per-command. const std = @import("std"); const zfin = @import("../root.zig"); @@ -264,8 +266,20 @@ fn resolveUserPath( /// Validate a command module against the framework contract. Emits /// a `@compileError` with the full expected signature for any -/// missing or wrong-shape decl. Call from the `command_modules` -/// registry walker in `src/main.zig`. +/// missing or wrong-shape decl. +/// +/// **Call site policy: registry-walk only.** This function should +/// be invoked exactly once per command, from the `command_modules` +/// registry walker in `src/main.zig`. Do NOT add in-file +/// `comptime { framework.validateCommandModule(@This()); }` blocks +/// to individual command files — they're redundant with the +/// registry walk under both `zig build` and ZLS build-on-save (the +/// only ZLS mode that evaluates comptime; ZLS's own semantic +/// analyzer doesn't run comptime reliably). The registry walk also +/// produces a better diagnostic, identifying the offending command +/// by its registry name (`commands.cache`) rather than just its +/// module identity. The TUI tab framework follows the same policy +/// in `src/tui.zig`. pub fn validateCommandModule(comptime Module: type) void { comptime { const mod_name = @typeName(Module); diff --git a/src/commands/history.zig b/src/commands/history.zig index b3ab538..30c7681 100644 --- a/src/commands/history.zig +++ b/src/commands/history.zig @@ -85,10 +85,6 @@ pub const meta: framework.Meta = .{ , }; -comptime { - framework.validateCommandModule(@This()); -} - pub const Error = error{ UnexpectedArg, InvalidFlagValue, diff --git a/src/commands/lookup.zig b/src/commands/lookup.zig index 83566b5..288a0c8 100644 --- a/src/commands/lookup.zig +++ b/src/commands/lookup.zig @@ -31,10 +31,6 @@ pub const meta: framework.Meta = .{ , }; -comptime { - framework.validateCommandModule(@This()); -} - pub fn parseArgs(ctx: *framework.RunCtx, cmd_args: []const []const u8) !ParsedArgs { if (cmd_args.len < 1) { try cli.stderrPrint(ctx.io, "Error: 'lookup' requires a CUSIP argument\n"); diff --git a/src/commands/milestones.zig b/src/commands/milestones.zig index a636ca2..10bf7f3 100644 --- a/src/commands/milestones.zig +++ b/src/commands/milestones.zig @@ -64,10 +64,6 @@ pub const meta: framework.Meta = .{ .uppercase_first_arg = false, }; -comptime { - framework.validateCommandModule(@This()); -} - pub const RunError = error{ UnexpectedArg, MissingStep, diff --git a/src/commands/options.zig b/src/commands/options.zig index a1b9a9b..fdd3a8b 100644 --- a/src/commands/options.zig +++ b/src/commands/options.zig @@ -35,10 +35,6 @@ pub const meta: framework.Meta = .{ , }; -comptime { - framework.validateCommandModule(@This()); -} - pub fn parseArgs(ctx: *framework.RunCtx, cmd_args: []const []const u8) !ParsedArgs { if (cmd_args.len < 1) { try cli.stderrPrint(ctx.io, "Error: 'options' requires a symbol argument\n"); diff --git a/src/commands/perf.zig b/src/commands/perf.zig index e15aaeb..bc793d1 100644 --- a/src/commands/perf.zig +++ b/src/commands/perf.zig @@ -35,10 +35,6 @@ pub const meta: framework.Meta = .{ , }; -comptime { - framework.validateCommandModule(@This()); -} - pub fn parseArgs(ctx: *framework.RunCtx, cmd_args: []const []const u8) !ParsedArgs { if (cmd_args.len < 1) { try cli.stderrPrint(ctx.io, "Error: 'perf' requires a symbol argument\n"); diff --git a/src/commands/portfolio.zig b/src/commands/portfolio.zig index 57abff2..1e0108e 100644 --- a/src/commands/portfolio.zig +++ b/src/commands/portfolio.zig @@ -32,10 +32,6 @@ pub const meta: framework.Meta = .{ .uppercase_first_arg = false, }; -comptime { - framework.validateCommandModule(@This()); -} - pub fn parseArgs(ctx: *framework.RunCtx, cmd_args: []const []const u8) !ParsedArgs { if (cmd_args.len > 0) { const a = cmd_args[0]; diff --git a/src/commands/projections.zig b/src/commands/projections.zig index f1b6e6c..640883d 100644 --- a/src/commands/projections.zig +++ b/src/commands/projections.zig @@ -110,10 +110,6 @@ pub const meta: framework.Meta = .{ .uppercase_first_arg = false, }; -comptime { - framework.validateCommandModule(@This()); -} - pub fn parseArgs(ctx: *framework.RunCtx, cmd_args: []const []const u8) !ParsedArgs { const io = ctx.io; const today = ctx.today; diff --git a/src/commands/quote.zig b/src/commands/quote.zig index e6a0546..5bad5db 100644 --- a/src/commands/quote.zig +++ b/src/commands/quote.zig @@ -32,10 +32,6 @@ pub const meta: framework.Meta = .{ , }; -comptime { - framework.validateCommandModule(@This()); -} - /// Quote data extracted from the real-time API (or synthesized from candles). pub const QuoteData = struct { price: f64, diff --git a/src/commands/snapshot.zig b/src/commands/snapshot.zig index 273da15..9da5e32 100644 --- a/src/commands/snapshot.zig +++ b/src/commands/snapshot.zig @@ -98,10 +98,6 @@ pub const meta: framework.Meta = .{ .uppercase_first_arg = false, }; -comptime { - framework.validateCommandModule(@This()); -} - pub fn parseArgs(ctx: *framework.RunCtx, cmd_args: []const []const u8) !ParsedArgs { var parsed: ParsedArgs = .{}; var i: usize = 0; diff --git a/src/commands/splits.zig b/src/commands/splits.zig index e38f7d7..4e572f8 100644 --- a/src/commands/splits.zig +++ b/src/commands/splits.zig @@ -27,10 +27,6 @@ pub const meta: framework.Meta = .{ , }; -comptime { - framework.validateCommandModule(@This()); -} - pub fn parseArgs(ctx: *framework.RunCtx, cmd_args: []const []const u8) !ParsedArgs { if (cmd_args.len < 1) { try cli.stderrPrint(ctx.io, "Error: 'splits' requires a symbol argument\n"); diff --git a/src/commands/version.zig b/src/commands/version.zig index c625d70..f24e689 100644 --- a/src/commands/version.zig +++ b/src/commands/version.zig @@ -35,10 +35,6 @@ pub const meta: framework.Meta = .{ .uppercase_first_arg = false, }; -comptime { - framework.validateCommandModule(@This()); -} - /// Parse `--verbose`/`-v`. Unknown args produce an error on stderr. pub fn parseArgs(ctx: *framework.RunCtx, cmd_args: []const []const u8) !ParsedArgs { var parsed: ParsedArgs = .{}; diff --git a/src/tui/tab_framework.zig b/src/tui/tab_framework.zig index 4e72221..0f5f45a 100644 --- a/src/tui/tab_framework.zig +++ b/src/tui/tab_framework.zig @@ -216,13 +216,13 @@ pub fn alwaysEnabled() fn (*App) bool { // Every error message includes the full expected signature so the // developer knows exactly what to add, copy-paste ready. // -// Call sites: the registry in `src/tui.zig` calls this once per -// entry. Each `_tab.zig` can also opt in via a comptime -// block in the file itself for faster local feedback: -// -// ```zig -// comptime { framework.validateTabModule(@This()); } -// ``` +// Call site policy: registry-walk only. The registry in +// `src/tui.zig` calls this once per entry. Do NOT add in-file +// `comptime { framework.validateTabModule(@This()); }` blocks to +// individual tab files — they're redundant with the registry walk +// under both `zig build` and ZLS build-on-save (the only ZLS mode +// that runs comptime), and the registry walk produces a better +// diagnostic. Mirrors the policy in `src/commands/framework.zig`. /// Validate a tab module against the framework contract. Emits a /// `@compileError` with the full expected signature for any