Skip to main content

tokmd_config/
lib.rs

1//! # tokmd-config
2//!
3//! **Tier 4 (Configuration)**
4//!
5//! This crate defines the CLI arguments and configuration file structures.
6//! Currently it couples strict configuration schemas with Clap CLI parsing.
7//!
8//! ## What belongs here
9//! * Clap `Parser`, `Args`, `Subcommand` structs
10//! * Configuration file struct definitions (Serde)
11//! * Default values and enums
12//!
13//! ## What does NOT belong here
14//! * Business logic
15//! * I/O operations (except config file parsing)
16//! * Higher-tier crate dependencies
17//!
18//! ## Future Direction
19//! * Split into `tokmd-settings` (pure config) and `tokmd-cli` (Clap parsing)
20
21use std::collections::BTreeMap;
22use std::path::PathBuf;
23
24use clap::{Args, Parser, Subcommand, ValueEnum};
25use serde::{Deserialize, Serialize};
26pub use tokmd_tool_schema::ToolSchemaFormat;
27pub use tokmd_types::{
28    AnalysisFormat, ChildIncludeMode, ChildrenMode, ConfigMode, ExportFormat, RedactMode,
29    TableFormat,
30};
31
32/// `tokmd` — a small, cross-platform, chat-friendly wrapper around `tokei`.
33///
34/// Default mode (no subcommand) prints a language summary.
35#[derive(Parser, Debug)]
36#[command(name = "tokmd", version, about, long_about = None)]
37pub struct Cli {
38    #[command(flatten)]
39    pub global: GlobalArgs,
40
41    /// Default options for the implicit `lang` mode (when no subcommand is provided).
42    #[command(flatten)]
43    pub lang: CliLangArgs,
44
45    #[command(subcommand)]
46    pub command: Option<Commands>,
47
48    /// Configuration profile to use (e.g., "llm_safe", "ci").
49    #[arg(long, visible_alias = "view", global = true)]
50    pub profile: Option<String>,
51}
52
53#[derive(Args, Debug, Clone, Default)]
54pub struct GlobalArgs {
55    /// Exclude pattern(s) using gitignore syntax. Repeatable.
56    ///
57    /// Examples:
58    ///   --exclude target
59    ///   --exclude "**/*.min.js"
60    #[arg(
61        long = "exclude",
62        visible_alias = "ignore",
63        value_name = "PATTERN",
64        global = true
65    )]
66    pub excluded: Vec<String>,
67
68    /// Whether to load `tokei.toml` / `.tokeirc`.
69    #[arg(long, value_enum, default_value_t = ConfigMode::Auto)]
70    pub config: ConfigMode,
71
72    /// Count hidden files and directories.
73    #[arg(long)]
74    pub hidden: bool,
75
76    /// Don't respect ignore files (.gitignore, .ignore, etc.).
77    ///
78    /// Implies --no-ignore-parent, --no-ignore-dot, and --no-ignore-vcs.
79    #[arg(long)]
80    pub no_ignore: bool,
81
82    /// Don't respect ignore files in parent directories.
83    #[arg(long)]
84    pub no_ignore_parent: bool,
85
86    /// Don't respect .ignore and .tokeignore files (including in parent directories).
87    #[arg(long)]
88    pub no_ignore_dot: bool,
89
90    /// Don't respect VCS ignore files (.gitignore, .hgignore, etc.), including in parents.
91    #[arg(long, visible_alias = "no-ignore-git")]
92    pub no_ignore_vcs: bool,
93
94    /// Treat doc strings as comments (language-dependent).
95    #[arg(long)]
96    pub treat_doc_strings_as_comments: bool,
97
98    /// Verbose output (repeat for more detail).
99    #[arg(short = 'v', long = "verbose", action = clap::ArgAction::Count)]
100    pub verbose: u8,
101
102    /// Disable progress spinners.
103    #[arg(long, global = true)]
104    pub no_progress: bool,
105}
106
107#[derive(Subcommand, Debug, Clone)]
108pub enum Commands {
109    /// Language summary (default).
110    Lang(CliLangArgs),
111
112    /// Module summary (group by path prefixes like `crates/<name>` or `packages/<name>`).
113    Module(CliModuleArgs),
114
115    /// Export a file-level dataset (CSV / JSONL / JSON).
116    Export(CliExportArgs),
117
118    /// Analyze receipts or paths to produce derived metrics.
119    Analyze(CliAnalyzeArgs),
120
121    /// Render a simple SVG badge for a metric.
122    Badge(BadgeArgs),
123
124    /// Write a `.tokeignore` template to the target directory.
125    Init(InitArgs),
126
127    /// Generate shell completions.
128    Completions(CompletionsArgs),
129
130    /// Run a full scan and save receipts to a state directory.
131    Run(RunArgs),
132
133    /// Compare two receipts or runs.
134    Diff(DiffArgs),
135
136    /// Pack files into an LLM context window within a token budget.
137    Context(CliContextArgs),
138
139    /// Check why a file is being ignored (for troubleshooting).
140    CheckIgnore(CliCheckIgnoreArgs),
141
142    /// Output CLI schema as JSON for AI agents.
143    Tools(ToolsArgs),
144
145    /// Evaluate policy rules against analysis receipts.
146    Gate(CliGateArgs),
147
148    /// Generate PR cockpit metrics for code review.
149    Cockpit(CockpitArgs),
150
151    /// Generate a complexity baseline for trend tracking.
152    Baseline(BaselineArgs),
153
154    /// Bundle codebase for LLM handoff.
155    Handoff(HandoffArgs),
156
157    /// Run as a conforming sensor, producing a SensorReport.
158    Sensor(SensorArgs),
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize, Default)]
162pub struct UserConfig {
163    pub profiles: BTreeMap<String, Profile>,
164    pub repos: BTreeMap<String, String>, // "owner/repo" -> "profile_name"
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize, Default)]
168pub struct Profile {
169    // Shared
170    pub format: Option<String>, // "json", "md", "tsv", "csv", "jsonl"
171    pub top: Option<usize>,
172
173    // Lang
174    pub files: Option<bool>,
175
176    // Module / Export
177    pub module_roots: Option<Vec<String>>,
178    pub module_depth: Option<usize>,
179    pub min_code: Option<usize>,
180    pub max_rows: Option<usize>,
181    pub redact: Option<RedactMode>,
182    pub meta: Option<bool>,
183
184    // "children" can be ChildrenMode or ChildIncludeMode string
185    pub children: Option<String>,
186}
187
188#[derive(Args, Debug, Clone)]
189pub struct RunArgs {
190    /// Paths to scan.
191    #[arg(value_name = "PATH", default_value = ".")]
192    pub paths: Vec<PathBuf>,
193
194    /// Output directory for artifacts (defaults to `.runs/tokmd` inside the repo, or system temp if not possible).
195    #[arg(long)]
196    pub output_dir: Option<PathBuf>,
197
198    /// Tag or name for this run.
199    #[arg(long)]
200    pub name: Option<String>,
201
202    /// Also emit analysis receipts using this preset.
203    #[arg(long, value_enum)]
204    pub analysis: Option<AnalysisPreset>,
205
206    /// Redact paths (and optionally module names) for safer copy/paste into LLMs.
207    #[arg(long, value_enum)]
208    pub redact: Option<RedactMode>,
209}
210
211#[derive(Args, Debug, Clone)]
212pub struct DiffArgs {
213    /// Base receipt/run or git ref to compare from.
214    #[arg(long)]
215    pub from: Option<String>,
216
217    /// Target receipt/run or git ref to compare to.
218    #[arg(long)]
219    pub to: Option<String>,
220
221    /// Two refs/paths to compare (positional).
222    #[arg(value_name = "REF", num_args = 2)]
223    pub refs: Vec<String>,
224
225    /// Output format.
226    #[arg(long, value_enum, default_value_t = DiffFormat::Md)]
227    pub format: DiffFormat,
228
229    /// Compact output for narrow terminals (summary table only).
230    #[arg(long)]
231    pub compact: bool,
232
233    /// Color policy for terminal output.
234    #[arg(long, value_enum, default_value_t = ColorMode::Auto)]
235    pub color: ColorMode,
236}
237
238#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
239#[serde(rename_all = "kebab-case")]
240pub enum DiffFormat {
241    /// Markdown table output.
242    #[default]
243    Md,
244    /// JSON receipt with envelope metadata.
245    Json,
246}
247
248#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
249#[serde(rename_all = "kebab-case")]
250pub enum ColorMode {
251    /// Enable color when stdout is a TTY and color env vars allow it.
252    #[default]
253    Auto,
254    /// Always emit ANSI color.
255    Always,
256    /// Never emit ANSI color.
257    Never,
258}
259
260#[derive(Args, Debug, Clone)]
261pub struct CompletionsArgs {
262    /// Shell to generate completions for.
263    #[arg(value_enum)]
264    pub shell: Shell,
265}
266
267#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
268#[serde(rename_all = "kebab-case")]
269pub enum Shell {
270    Bash,
271    Elvish,
272    Fish,
273    Powershell,
274    Zsh,
275}
276
277#[derive(Args, Debug, Clone, Default)]
278pub struct CliLangArgs {
279    /// Paths to scan (directories, files, or globs). Defaults to "."
280    #[arg(value_name = "PATH")]
281    pub paths: Option<Vec<PathBuf>>,
282
283    /// Output format [default: md].
284    #[arg(long, value_enum)]
285    pub format: Option<TableFormat>,
286
287    /// Show only the top N rows (by code lines), plus an "Other" row if needed.
288    /// Use 0 to show all rows.
289    #[arg(long)]
290    pub top: Option<usize>,
291
292    /// Include file counts and average lines per file.
293    #[arg(long)]
294    pub files: bool,
295
296    /// How to handle embedded languages (tokei "children" / blobs) [default: collapse].
297    #[arg(long, value_enum)]
298    pub children: Option<ChildrenMode>,
299}
300
301#[derive(Args, Debug, Clone)]
302pub struct CliModuleArgs {
303    /// Paths to scan (directories, files, or globs). Defaults to "."
304    #[arg(value_name = "PATH")]
305    pub paths: Option<Vec<PathBuf>>,
306
307    /// Output format [default: md].
308    #[arg(long, value_enum)]
309    pub format: Option<TableFormat>,
310
311    /// Show only the top N modules (by code lines), plus an "Other" row if needed.
312    /// Use 0 to show all rows.
313    #[arg(long)]
314    pub top: Option<usize>,
315
316    /// Treat these top-level directories as "module roots" [default: crates,packages].
317    ///
318    /// If a file path starts with one of these roots, the module key will include
319    /// `module_depth` segments. Otherwise, the module key is the top-level directory.
320    #[arg(long, value_delimiter = ',')]
321    pub module_roots: Option<Vec<String>>,
322
323    /// How many path segments to include for module roots [default: 2].
324    ///
325    /// Example:
326    ///   crates/foo/src/lib.rs  (depth=2) => crates/foo
327    ///   crates/foo/src/lib.rs  (depth=1) => crates
328    #[arg(long)]
329    pub module_depth: Option<usize>,
330
331    /// Whether to include embedded languages (tokei "children" / blobs) in module totals [default: separate].
332    #[arg(long, value_enum)]
333    pub children: Option<ChildIncludeMode>,
334}
335
336#[derive(Args, Debug, Clone)]
337pub struct CliExportArgs {
338    /// Paths to scan (directories, files, or globs). Defaults to "."
339    #[arg(value_name = "PATH")]
340    pub paths: Option<Vec<PathBuf>>,
341
342    /// Output format [default: jsonl].
343    #[arg(long, value_enum)]
344    pub format: Option<ExportFormat>,
345
346    /// Write output to this file instead of stdout.
347    #[arg(long, value_name = "PATH", visible_alias = "out")]
348    pub output: Option<PathBuf>,
349
350    /// Module roots (see `tokmd module`) [default: crates,packages].
351    #[arg(long, value_delimiter = ',')]
352    pub module_roots: Option<Vec<String>>,
353
354    /// Module depth (see `tokmd module`) [default: 2].
355    #[arg(long)]
356    pub module_depth: Option<usize>,
357
358    /// Whether to include embedded languages (tokei "children" / blobs) [default: separate].
359    #[arg(long, value_enum)]
360    pub children: Option<ChildIncludeMode>,
361
362    /// Drop rows with fewer than N code lines [default: 0].
363    #[arg(long)]
364    pub min_code: Option<usize>,
365
366    /// Stop after emitting N rows (0 = unlimited) [default: 0].
367    #[arg(long)]
368    pub max_rows: Option<usize>,
369
370    /// Include a meta record (JSON / JSONL only). Enabled by default.
371    #[arg(long, action = clap::ArgAction::Set)]
372    pub meta: Option<bool>,
373
374    /// Redact paths (and optionally module names) for safer copy/paste into LLMs [default: none].
375    #[arg(long, value_enum)]
376    pub redact: Option<RedactMode>,
377
378    /// Strip this prefix from paths before output (helps when paths are absolute).
379    #[arg(long, value_name = "PATH")]
380    pub strip_prefix: Option<PathBuf>,
381}
382
383#[derive(Args, Debug, Clone)]
384pub struct CliAnalyzeArgs {
385    /// Inputs to analyze (run dir, receipt.json, export.jsonl, or paths).
386    #[arg(value_name = "INPUT", default_value = ".")]
387    pub inputs: Vec<PathBuf>,
388
389    /// Analysis preset to run [default: receipt].
390    #[arg(long, value_enum)]
391    pub preset: Option<AnalysisPreset>,
392
393    /// Output format [default: md].
394    #[arg(long, value_enum)]
395    pub format: Option<AnalysisFormat>,
396
397    /// Context window size (tokens) for utilization bars.
398    #[arg(long)]
399    pub window: Option<usize>,
400
401    /// Force-enable git-based metrics.
402    #[arg(long, action = clap::ArgAction::SetTrue, conflicts_with = "no_git")]
403    pub git: bool,
404
405    /// Disable git-based metrics.
406    #[arg(long = "no-git", action = clap::ArgAction::SetTrue, conflicts_with = "git")]
407    pub no_git: bool,
408
409    /// Output directory for analysis artifacts.
410    #[arg(long)]
411    pub output_dir: Option<PathBuf>,
412
413    /// Limit how many files are walked for asset/deps/content scans.
414    #[arg(long)]
415    pub max_files: Option<usize>,
416
417    /// Limit total bytes read during content scans.
418    #[arg(long)]
419    pub max_bytes: Option<u64>,
420
421    /// Limit bytes per file during content scans.
422    #[arg(long)]
423    pub max_file_bytes: Option<u64>,
424
425    /// Limit how many commits are scanned for git metrics.
426    #[arg(long)]
427    pub max_commits: Option<usize>,
428
429    /// Limit files per commit when scanning git history.
430    #[arg(long)]
431    pub max_commit_files: Option<usize>,
432
433    /// Import graph granularity [default: module].
434    #[arg(long, value_enum)]
435    pub granularity: Option<ImportGranularity>,
436
437    /// Include function-level complexity details in output.
438    #[arg(long)]
439    pub detail_functions: bool,
440
441    /// Enable near-duplicate file detection (opt-in).
442    #[arg(long)]
443    pub near_dup: bool,
444
445    /// Near-duplicate similarity threshold (0.0–1.0) [default: 0.80].
446    #[arg(long, default_value = "0.80")]
447    pub near_dup_threshold: f64,
448
449    /// Maximum files to analyze for near-duplicates [default: 2000].
450    #[arg(long, default_value = "2000")]
451    pub near_dup_max_files: usize,
452
453    /// Near-duplicate comparison scope [default: module].
454    #[arg(long, value_enum)]
455    pub near_dup_scope: Option<NearDupScope>,
456
457    /// Maximum near-duplicate pairs to emit (truncation guardrail) [default: 10000].
458    #[arg(long, default_value = "10000")]
459    pub near_dup_max_pairs: usize,
460
461    /// Exclude files matching this glob pattern from near-duplicate analysis. Repeatable.
462    #[arg(long, value_name = "GLOB")]
463    pub near_dup_exclude: Vec<String>,
464
465    /// Explain a metric or finding key and exit.
466    #[arg(long, value_name = "KEY")]
467    pub explain: Option<String>,
468}
469
470#[derive(Args, Debug, Clone)]
471pub struct BadgeArgs {
472    /// Inputs to analyze (run dir, receipt.json, export.jsonl, or paths).
473    #[arg(value_name = "INPUT", default_value = ".")]
474    pub inputs: Vec<PathBuf>,
475
476    /// Metric to render.
477    #[arg(long, value_enum)]
478    pub metric: BadgeMetric,
479
480    /// Optional analysis preset to use for the badge.
481    #[arg(long, value_enum)]
482    pub preset: Option<AnalysisPreset>,
483
484    /// Force-enable git-based metrics.
485    #[arg(long, action = clap::ArgAction::SetTrue, conflicts_with = "no_git")]
486    pub git: bool,
487
488    /// Disable git-based metrics.
489    #[arg(long = "no-git", action = clap::ArgAction::SetTrue, conflicts_with = "git")]
490    pub no_git: bool,
491
492    /// Limit how many commits are scanned for git metrics.
493    #[arg(long)]
494    pub max_commits: Option<usize>,
495
496    /// Limit files per commit when scanning git history.
497    #[arg(long)]
498    pub max_commit_files: Option<usize>,
499
500    /// Output file for the badge (defaults to stdout).
501    #[arg(long, visible_alias = "out")]
502    pub output: Option<PathBuf>,
503}
504
505#[derive(Args, Debug, Clone)]
506pub struct InitArgs {
507    /// Target directory (defaults to ".").
508    #[arg(long, value_name = "DIR", default_value = ".")]
509    pub dir: PathBuf,
510
511    /// Overwrite an existing `.tokeignore`.
512    #[arg(long)]
513    pub force: bool,
514
515    /// Print the template to stdout instead of writing a file.
516    #[arg(long)]
517    pub print: bool,
518
519    /// Which template profile to use.
520    #[arg(long, value_enum, default_value_t = InitProfile::Default)]
521    pub template: InitProfile,
522
523    /// Skip interactive wizard and use defaults.
524    #[arg(long)]
525    pub non_interactive: bool,
526}
527
528#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
529#[serde(rename_all = "kebab-case")]
530pub enum AnalysisPreset {
531    Receipt,
532    Health,
533    Risk,
534    Supply,
535    Architecture,
536    Topics,
537    Security,
538    Identity,
539    Git,
540    Deep,
541    Fun,
542}
543
544#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
545#[serde(rename_all = "kebab-case")]
546pub enum ImportGranularity {
547    Module,
548    File,
549}
550
551#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
552#[serde(rename_all = "kebab-case")]
553pub enum BadgeMetric {
554    Lines,
555    Tokens,
556    Bytes,
557    Doc,
558    Blank,
559    Hotspot,
560}
561
562#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
563#[serde(rename_all = "kebab-case")]
564pub enum InitProfile {
565    Default,
566    Rust,
567    Node,
568    Mono,
569    Python,
570    Go,
571    Cpp,
572}
573
574#[derive(Args, Debug, Clone)]
575pub struct CliContextArgs {
576    /// Paths to scan (directories, files, or globs). Defaults to "."
577    #[arg(value_name = "PATH")]
578    pub paths: Option<Vec<PathBuf>>,
579
580    /// Token budget with optional k/m/g suffix, or 'unlimited' (e.g., "128k", "1m", "1g", "unlimited").
581    #[arg(long, default_value = "128k")]
582    pub budget: String,
583
584    /// Packing strategy.
585    #[arg(long, value_enum, default_value_t = ContextStrategy::Greedy)]
586    pub strategy: ContextStrategy,
587
588    /// Metric to rank files by.
589    #[arg(long, value_enum, default_value_t = ValueMetric::Code)]
590    pub rank_by: ValueMetric,
591
592    /// Output mode.
593    #[arg(long = "mode", value_enum, default_value_t = ContextOutput::List)]
594    pub output_mode: ContextOutput,
595
596    /// Strip blank lines from bundle output.
597    #[arg(long)]
598    pub compress: bool,
599
600    /// Disable smart exclusion of lockfiles, minified files, and generated artifacts.
601    #[arg(long)]
602    pub no_smart_exclude: bool,
603
604    /// Module roots (see `tokmd module`).
605    #[arg(long, value_delimiter = ',')]
606    pub module_roots: Option<Vec<String>>,
607
608    /// Module depth (see `tokmd module`).
609    #[arg(long)]
610    pub module_depth: Option<usize>,
611
612    /// Enable git-based ranking (required for churn/hotspot).
613    #[arg(long)]
614    pub git: bool,
615
616    /// Disable git-based ranking.
617    #[arg(long = "no-git")]
618    pub no_git: bool,
619
620    /// Maximum commits to scan for git metrics.
621    #[arg(long, default_value = "1000")]
622    pub max_commits: usize,
623
624    /// Maximum files per commit to process.
625    #[arg(long, default_value = "100")]
626    pub max_commit_files: usize,
627
628    /// Write output to file instead of stdout.
629    #[arg(long, value_name = "PATH", visible_alias = "out")]
630    pub output: Option<PathBuf>,
631
632    /// Overwrite existing output file.
633    #[arg(long)]
634    pub force: bool,
635
636    /// Write bundle to directory with manifest (for large outputs).
637    #[arg(long, value_name = "DIR", conflicts_with = "output")]
638    pub bundle_dir: Option<PathBuf>,
639
640    /// Warn if output exceeds N bytes (default: 10MB, 0=disable).
641    #[arg(long, default_value = "10485760")]
642    pub max_output_bytes: u64,
643
644    /// Append JSONL record to log file (metadata only, not content).
645    #[arg(long, value_name = "PATH")]
646    pub log: Option<PathBuf>,
647
648    /// Maximum fraction of budget a single file may consume (0.0–1.0).
649    #[arg(long, default_value = "0.15")]
650    pub max_file_pct: f64,
651
652    /// Hard cap on tokens per file (overrides percentage-based cap).
653    #[arg(long)]
654    pub max_file_tokens: Option<usize>,
655
656    /// Error if git scores are unavailable when using churn/hotspot ranking.
657    #[arg(long)]
658    pub require_git_scores: bool,
659}
660
661#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
662#[serde(rename_all = "kebab-case")]
663pub enum ContextStrategy {
664    /// Select files by value until budget is exhausted.
665    #[default]
666    Greedy,
667    /// Round-robin across modules/languages for coverage, then greedy fill.
668    Spread,
669}
670
671#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
672#[serde(rename_all = "kebab-case")]
673pub enum ValueMetric {
674    /// Rank by lines of code.
675    #[default]
676    Code,
677    /// Rank by token count.
678    Tokens,
679    /// Rank by git churn (requires git feature).
680    Churn,
681    /// Rank by hotspot score (requires git feature).
682    Hotspot,
683}
684
685#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
686#[serde(rename_all = "kebab-case")]
687pub enum ContextOutput {
688    /// Print list of selected files with stats.
689    #[default]
690    List,
691    /// Concatenate file contents into a single bundle.
692    Bundle,
693    /// Output JSON receipt with selection details.
694    Json,
695}
696
697#[derive(Args, Debug, Clone)]
698pub struct CliCheckIgnoreArgs {
699    /// File path(s) to check.
700    #[arg(value_name = "PATH", required = true)]
701    pub paths: Vec<PathBuf>,
702
703    /// Show verbose output with rule sources.
704    #[arg(long, short = 'v')]
705    pub verbose: bool,
706}
707
708#[derive(Args, Debug, Clone)]
709pub struct ToolsArgs {
710    /// Output format for the tool schema.
711    #[arg(long, value_enum, default_value_t = ToolSchemaFormat::Jsonschema)]
712    pub format: ToolSchemaFormat,
713
714    /// Pretty-print JSON output.
715    #[arg(long)]
716    pub pretty: bool,
717}
718
719#[derive(Args, Debug, Clone)]
720pub struct CliGateArgs {
721    /// Input analysis receipt or path to scan.
722    #[arg(value_name = "INPUT")]
723    pub input: Option<PathBuf>,
724
725    /// Path to policy file (TOML format).
726    #[arg(long)]
727    pub policy: Option<PathBuf>,
728
729    /// Path to baseline receipt for ratchet comparison.
730    ///
731    /// When provided, gate will evaluate ratchet rules comparing current
732    /// metrics against the baseline values.
733    #[arg(long, value_name = "PATH")]
734    pub baseline: Option<PathBuf>,
735
736    /// Path to ratchet config file (TOML format).
737    ///
738    /// Defines rules for comparing current metrics against baseline.
739    /// Can also be specified inline in tokmd.toml under [[gate.ratchet]].
740    #[arg(long, value_name = "PATH")]
741    pub ratchet_config: Option<PathBuf>,
742
743    /// Analysis preset (for compute-then-gate mode).
744    #[arg(long, value_enum)]
745    pub preset: Option<AnalysisPreset>,
746
747    /// Output format.
748    #[arg(long, value_enum, default_value_t = GateFormat::Text)]
749    pub format: GateFormat,
750
751    /// Fail fast on first error.
752    #[arg(long)]
753    pub fail_fast: bool,
754}
755
756#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
757#[serde(rename_all = "kebab-case")]
758pub enum GateFormat {
759    /// Human-readable text output.
760    #[default]
761    Text,
762    /// JSON output.
763    Json,
764}
765
766#[derive(Args, Debug, Clone)]
767pub struct CockpitArgs {
768    /// Base reference to compare from (default: main).
769    #[arg(long, default_value = "main")]
770    pub base: String,
771
772    /// Head reference to compare to (default: HEAD).
773    #[arg(long, default_value = "HEAD")]
774    pub head: String,
775
776    /// Output format.
777    #[arg(long, value_enum, default_value_t = CockpitFormat::Json)]
778    pub format: CockpitFormat,
779
780    /// Output file (stdout if omitted).
781    #[arg(long, value_name = "PATH")]
782    pub output: Option<std::path::PathBuf>,
783
784    /// Write cockpit artifacts (report.json, comment.md) to directory.
785    #[arg(long, value_name = "DIR")]
786    pub artifacts_dir: Option<std::path::PathBuf>,
787
788    /// Path to baseline receipt for trend comparison.
789    ///
790    /// When provided, cockpit will compute delta metrics showing how
791    /// the current state compares to the baseline.
792    #[arg(long, value_name = "PATH")]
793    pub baseline: Option<std::path::PathBuf>,
794
795    /// Diff range syntax: two-dot (default) or three-dot.
796    #[arg(long, value_enum, default_value_t = DiffRangeMode::TwoDot)]
797    pub diff_range: DiffRangeMode,
798
799    /// Run in sensor mode for CI integration.
800    ///
801    /// When enabled:
802    /// - Always writes sensor.report.v1 envelope to artifacts_dir/report.json
803    /// - Exits 0 if receipt written successfully (verdict in envelope instead of exit code)
804    /// - Reports capability availability for "No Green By Omission"
805    #[arg(long)]
806    pub sensor_mode: bool,
807}
808
809#[derive(Args, Debug, Clone)]
810pub struct BaselineArgs {
811    /// Target path to analyze.
812    #[arg(default_value = ".")]
813    pub path: PathBuf,
814
815    /// Output path for baseline file.
816    #[arg(long, default_value = ".tokmd/baseline.json")]
817    pub output: PathBuf,
818
819    /// Include determinism baseline (hash build artifacts).
820    #[arg(long)]
821    pub determinism: bool,
822
823    /// Force overwrite existing baseline.
824    #[arg(long, short)]
825    pub force: bool,
826}
827
828#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
829#[serde(rename_all = "kebab-case")]
830pub enum CockpitFormat {
831    /// JSON output with full metrics.
832    #[default]
833    Json,
834    /// Markdown output for human readability.
835    Md,
836    /// Section-based output for PR template filling.
837    Sections,
838}
839
840#[derive(Args, Debug, Clone)]
841pub struct HandoffArgs {
842    /// Paths to scan (directories, files, or globs). Defaults to ".".
843    #[arg(value_name = "PATH")]
844    pub paths: Option<Vec<PathBuf>>,
845
846    /// Output directory for handoff artifacts.
847    #[arg(long, default_value = ".handoff")]
848    pub out_dir: PathBuf,
849
850    /// Token budget with optional k/m/g suffix, or 'unlimited' (e.g., "128k", "1m", "1g", "unlimited").
851    #[arg(long, default_value = "128k")]
852    pub budget: String,
853
854    /// Packing strategy for code bundle.
855    #[arg(long, value_enum, default_value_t = ContextStrategy::Greedy)]
856    pub strategy: ContextStrategy,
857
858    /// Metric to rank files by for packing.
859    #[arg(long, value_enum, default_value_t = ValueMetric::Hotspot)]
860    pub rank_by: ValueMetric,
861
862    /// Intelligence preset level.
863    #[arg(long, value_enum, default_value_t = HandoffPreset::Risk)]
864    pub preset: HandoffPreset,
865
866    /// Module roots (see `tokmd module`).
867    #[arg(long, value_delimiter = ',')]
868    pub module_roots: Option<Vec<String>>,
869
870    /// Module depth (see `tokmd module`).
871    #[arg(long)]
872    pub module_depth: Option<usize>,
873
874    /// Overwrite existing output directory.
875    #[arg(long)]
876    pub force: bool,
877
878    /// Strip blank lines from code bundle.
879    #[arg(long)]
880    pub compress: bool,
881
882    /// Disable smart exclusion of lockfiles, minified files, and generated artifacts.
883    #[arg(long)]
884    pub no_smart_exclude: bool,
885
886    /// Disable git-based features.
887    #[arg(long = "no-git")]
888    pub no_git: bool,
889
890    /// Maximum commits to scan for git metrics.
891    #[arg(long, default_value = "1000")]
892    pub max_commits: usize,
893
894    /// Maximum files per commit to process.
895    #[arg(long, default_value = "100")]
896    pub max_commit_files: usize,
897
898    /// Maximum fraction of budget a single file may consume (0.0–1.0).
899    #[arg(long, default_value = "0.15")]
900    pub max_file_pct: f64,
901
902    /// Hard cap on tokens per file (overrides percentage-based cap).
903    #[arg(long)]
904    pub max_file_tokens: Option<usize>,
905}
906
907#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
908#[serde(rename_all = "kebab-case")]
909pub enum HandoffPreset {
910    /// Minimal: tree + map only.
911    Minimal,
912    /// Standard: + complexity, derived.
913    Standard,
914    /// Risk: + hotspots, coupling (default).
915    #[default]
916    Risk,
917    /// Deep: everything.
918    Deep,
919}
920
921#[derive(Args, Debug, Clone, Serialize, Deserialize)]
922pub struct SensorArgs {
923    /// Base reference to compare from (default: main).
924    #[arg(long, default_value = "main")]
925    pub base: String,
926
927    /// Head reference to compare to (default: HEAD).
928    #[arg(long, default_value = "HEAD")]
929    pub head: String,
930
931    /// Output file for the sensor report.
932    #[arg(
933        long,
934        value_name = "PATH",
935        default_value = "artifacts/tokmd/report.json"
936    )]
937    pub output: std::path::PathBuf,
938
939    /// Output format.
940    #[arg(long, value_enum, default_value_t = SensorFormat::Json)]
941    pub format: SensorFormat,
942}
943
944#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
945#[serde(rename_all = "kebab-case")]
946pub enum SensorFormat {
947    /// JSON sensor report.
948    #[default]
949    Json,
950    /// Markdown summary.
951    Md,
952}
953
954#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
955#[serde(rename_all = "kebab-case")]
956pub enum NearDupScope {
957    /// Compare files within the same module.
958    #[default]
959    Module,
960    /// Compare files within the same language.
961    Lang,
962    /// Compare all files globally.
963    Global,
964}
965
966#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
967#[serde(rename_all = "kebab-case")]
968pub enum DiffRangeMode {
969    /// Two-dot syntax (A..B) - direct diff between commits.
970    #[default]
971    TwoDot,
972    /// Three-dot syntax (A...B) - diff from merge-base.
973    ThreeDot,
974}
975
976// =============================================================================
977// TOML Configuration File Structures (re-exported from tokmd-settings)
978// =============================================================================
979
980pub use tokmd_settings::{
981    AnalyzeConfig, BadgeConfig, ContextConfig, ExportConfig, GateConfig, GateRule, ModuleConfig,
982    RatchetRuleConfig, ScanConfig, TomlConfig, TomlResult, ViewProfile,
983};
984
985// ============================================================
986// Conversions between CLI GlobalArgs and Tier-0 ScanOptions
987// ============================================================
988
989impl From<&GlobalArgs> for tokmd_settings::ScanOptions {
990    fn from(g: &GlobalArgs) -> Self {
991        Self {
992            excluded: g.excluded.clone(),
993            config: g.config,
994            hidden: g.hidden,
995            no_ignore: g.no_ignore,
996            no_ignore_parent: g.no_ignore_parent,
997            no_ignore_dot: g.no_ignore_dot,
998            no_ignore_vcs: g.no_ignore_vcs,
999            treat_doc_strings_as_comments: g.treat_doc_strings_as_comments,
1000        }
1001    }
1002}
1003
1004impl From<GlobalArgs> for tokmd_settings::ScanOptions {
1005    fn from(g: GlobalArgs) -> Self {
1006        Self::from(&g)
1007    }
1008}