Skip to main content

coding_tools/cli/
ct_rules.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Jonathan Shook
3
4//! The `ct-rules` command grammar (see [`crate::cli`]); the `ct-rules` bin is a
5//! thin parse-and-dispatch wrapper over this `Cli`.
6
7use std::path::PathBuf;
8
9use clap::Parser;
10
11use crate::explain::Format;
12
13#[derive(Parser, Debug)]
14#[command(
15    name = "ct-rules",
16    version,
17    about = "Record, promote, remove, and list the project's invariant rules (.ct/rules.jsonc).",
18    long_about = "ct-rules is the writing side of the invariant surface (also reachable as \
19                  `ct rules`): --add verifies a probe and records it as a rule, --pending parks \
20                  an aspiration, --promote enforces it once it holds, --def names shared \
21                  vocabulary, --hook cargo wires `ct check` into `cargo test`. Verification of \
22                  the store is ct-check's job. See `ct-rules --explain` for details."
23)]
24pub struct Cli {
25    /// Rule store. Default: the nearest .ct/rules.jsonc walking upward (created by --init/--add when absent).
26    #[arg(long)]
27    pub file: Option<PathBuf>,
28
29    /// Create .ct/rules.jsonc (commented scaffold) if it does not exist.
30    #[arg(long)]
31    pub init: bool,
32
33    /// Record a rule with this id: the probe (after `--`) is gate-validated and RUN now; it must hold unless --pending.
34    #[arg(long, value_name = "ID")]
35    pub add: Option<String>,
36
37    /// With --add: record an aspiration that does not yet hold; reported as PENDING, never enforced, until --promote.
38    #[arg(long)]
39    pub pending: bool,
40
41    /// With --add: the question this rule answers (required).
42    #[arg(long)]
43    pub question: Option<String>,
44
45    /// With --add: why this invariant exists; printed whenever it fails. Accepts file:PATH / text:VALUE payloads.
46    #[arg(long)]
47    pub why: Option<String>,
48
49    /// With --add: the verbatim human request behind this rule, retained in the store so the intent can be revisited; strip all prompts later with --flatten. Accepts file:PATH / text:VALUE payloads.
50    #[arg(long)]
51    pub prompt: Option<String>,
52
53    /// With --add: tags for selection (comma-separated).
54    #[arg(long, value_delimiter = ',')]
55    pub tag: Vec<String>,
56
57    /// With --add: fail (default) or warn (violations report but never redden the exit).
58    #[arg(long)]
59    pub severity: Option<String>,
60
61    /// With --add: outcome adapter for bridge probes: exit (default) or empty.
62    #[arg(long, value_name = "exit|empty")]
63    pub expect: Option<String>,
64
65    /// With --add: matcher adapter — the rule holds when this pattern appears in the probe's output.
66    #[arg(long, value_name = "PATTERN")]
67    pub expect_ok: Option<String>,
68
69    /// With --add: matcher adapter — a violation when this pattern appears in the probe's output.
70    #[arg(long, value_name = "PATTERN")]
71    pub expect_err: Option<String>,
72
73    /// With --add: permit network access where the bridge entry deems it meaningful (cargo deny).
74    #[arg(long)]
75    pub network: bool,
76
77    /// With --add: per-rule probe bound in seconds (fractional allowed).
78    #[arg(long, value_name = "SECS")]
79    pub timeout: Option<f64>,
80
81    /// Re-run a pending rule's probe; if it now holds, clear the pending flag (enforce it).
82    #[arg(long, value_name = "ID")]
83    pub promote: Option<String>,
84
85    /// Remove the rule with this exact id.
86    #[arg(long, value_name = "ID")]
87    pub remove: Option<String>,
88
89    /// Set a def: NAME=VALUE. VALUE is parsed as JSON (e.g. ["A","B"]) or taken as a string.
90    #[arg(long, value_name = "NAME=VALUE")]
91    pub def: Option<String>,
92
93    /// Print defs and rules without changing anything.
94    #[arg(long)]
95    pub list: bool,
96
97    /// Strip the retained "prompt" prose from every rule, leaving only the mechanical definitions.
98    #[arg(long)]
99    pub flatten: bool,
100
101    /// Write the build hook for an ecosystem (currently: cargo — a tests/ shim that runs `ct check`).
102    #[arg(long, value_name = "ECOSYSTEM")]
103    pub hook: Option<String>,
104
105    /// Suppress informational output.
106    #[arg(long)]
107    pub quiet: bool,
108
109    /// Print agent usage docs (md or json) and exit.
110    #[arg(long, value_enum, num_args = 0..=1, default_missing_value = "md")]
111    pub explain: Option<Format>,
112
113    /// The probe for --add (after `--`): an argv run directly, never through a shell.
114    #[arg(last = true, value_name = "PROBE...")]
115    pub probe: Vec<String>,
116}