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