Skip to main content

drep/cli/check/
args.rs

1//! The `drep check` command line.
2//!
3//! Its own file because it is the published surface rather than any of the
4//! orchestration: the flags, their groups and the prose a user reads in `--help`.
5//! The orchestrator in the parent reads them and never redefines what one means.
6
7use std::path::PathBuf;
8
9use clap::{ArgGroup, Args};
10
11use crate::analysis::findings::Severity;
12use crate::cli::{OutputFormat, severity_parser};
13
14#[derive(Debug, Args)]
15// One rule, stated once. Paired `conflicts_with_all` attributes say the same
16// thing from each side and have to be kept in agreement; a fourth input mode
17// would mean editing every existing one, and missing a single edit silently
18// permits an illegal combination.
19// Deliberately NOT `.required(true)`. Bare `drep check` is a supported
20// invocation meaning "the whole tree": `input::resolve` expands `root` through
21// `files::expand_paths`, exactly as an explicit `.` would. Requiring one of the
22// three would turn the plainest invocation into a usage error. Pinned by
23// `bare_check_with_no_paths_expands_the_root_instead_of_reading_a_directory`,
24// which exists because an earlier version passed the root through as a *file*
25// and exited 2 without analyzing anything.
26#[command(
27    group(
28        ArgGroup::new("input")
29            .args(["paths", "staged", "diff", "pre_commit_push"])
30            .multiple(false)
31    ),
32    group(ArgGroup::new("cache_mode").args(["cache_only", "push_gate"]).multiple(false))
33)]
34pub struct CheckArgs {
35    /// Files or directories to check. Duplicates and overlaps are collapsed,
36    /// so `drep check a.rs .` analyzes `a.rs` once.
37    #[arg(value_name = "PATH")]
38    pub paths: Vec<PathBuf>,
39
40    /// Check the files staged for commit. For a pre-commit hook.
41    #[arg(long)]
42    pub staged: bool,
43
44    /// Check the files changed since REF, e.g. `origin/main`. For pre-push.
45    #[arg(long, value_name = "REF")]
46    pub diff: Option<String>,
47
48    /// The commit to diff *to*. Defaults to `HEAD`. Only valid with `--diff`.
49    ///
50    /// A pre-push hook needs this: git can push a ref that is not the
51    /// checked-out one (`git push origin feature:feature` from another branch,
52    /// or `git push --all`), and diffing to `HEAD` there reviews a different
53    /// branch and lets the pushed code through unseen.
54    #[arg(long, value_name = "REF", requires = "diff")]
55    pub tip: Option<String>,
56
57    /// Read the pushed base and tip from pre-commit's hook environment.
58    ///
59    /// Used by the published `drep-check-push` hook. pre-commit otherwise
60    /// passes filenames, which would make drep review whole files instead of
61    /// the hunks between `PRE_COMMIT_FROM_REF` and `PRE_COMMIT_TO_REF`.
62    #[arg(long)]
63    pub pre_commit_push: bool,
64
65    #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
66    pub format: OutputFormat,
67
68    /// Also block on LLM findings at or above this severity.
69    ///
70    /// Deterministic tool findings always block; this opts the LLM's findings
71    /// into gating too. Left unset, they inform without blocking - which is
72    /// the useful default, because the model emits style suggestions on
73    /// nearly every file.
74    #[arg(long, value_name = "SEVERITY", value_parser = severity_parser())]
75    pub fail_on: Option<Severity>,
76
77    /// Use cached LLM reviews only; never contact a provider.
78    ///
79    /// An uncached file exits 3 without warming it; run a normal check to
80    /// populate the missing entry. The generated pre-push hook uses
81    /// `--push-gate` for the full warm-and-reconnect handshake.
82    #[arg(long)]
83    pub cache_only: bool,
84
85    /// Prepare a push without resuming a stale remote connection.
86    ///
87    /// Cached reviews pass immediately. A cold review is completed and cached,
88    /// then exits 3 so Git reconnects; repeating `git push` uses the cache.
89    #[arg(long)]
90    pub push_gate: bool,
91
92    /// Override the repository's maximum fresh LLM review rounds.
93    #[arg(
94        long,
95        value_name = "N",
96        value_parser = clap::value_parser!(u32).range(1..),
97        conflicts_with = "unlimited_reviews"
98    )]
99    pub max_review_rounds: Option<u32>,
100
101    /// Permit fresh LLM review rounds without a limit for this invocation.
102    #[arg(long)]
103    pub unlimited_reviews: bool,
104}