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}