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