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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
use clap::{Parser, Subcommand, ValueEnum};
#[derive(Parser)]
#[command(
name = "ritalin",
version,
about = "Executive function for AI coding agents",
long_about = "ritalin is executive function for AI coding agents.\n\
Like Ritalin for ADHD — agents are smart, they just need help focusing their\n\
intelligence on the right things and avoiding avoidable mistakes.\n\n\
It ensures agents research before implementing, ground claims in evidence, reference\n\
real code instead of hallucinating patterns, and actually finish what they start.\n\n\
Workflow:\n \
1. ritalin init --outcome \"...\"\n \
2. ritalin add \"claim\" --proof \"shell command\" (repeat per obligation)\n \
3. Hook ritalin gate --hook-mode into Claude Code's Stop event\n \
4. Agent works, runs ritalin prove <id> as it discharges obligations\n \
5. Stop is blocked until every critical obligation has evidence"
)]
pub struct Cli {
/// Force JSON output even in a terminal
#[arg(long, global = true)]
pub json: bool,
/// Suppress informational human output (errors still print)
#[arg(long, global = true)]
pub quiet: bool,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Clone, Copy, Debug, ValueEnum, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
#[clap(rename_all = "snake_case")]
pub enum ObligationKind {
UserPath,
Integration,
Persistence,
FailurePath,
Performance,
Security,
ResearchGrounded,
CodeReferenced,
ModelCurrent,
LiteralMatch,
LiteralRegex,
Other,
}
impl std::fmt::Display for ObligationKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UserPath => write!(f, "user_path"),
Self::Integration => write!(f, "integration"),
Self::Persistence => write!(f, "persistence"),
Self::FailurePath => write!(f, "failure_path"),
Self::Performance => write!(f, "performance"),
Self::Security => write!(f, "security"),
Self::ResearchGrounded => write!(f, "research_grounded"),
Self::CodeReferenced => write!(f, "code_referenced"),
Self::ModelCurrent => write!(f, "model_current"),
Self::LiteralMatch => write!(f, "literal_match"),
Self::LiteralRegex => write!(f, "literal_regex"),
Self::Other => write!(f, "other"),
}
}
}
#[derive(Subcommand)]
pub enum Commands {
/// Initialize a ritalin scope contract in the current directory
Init {
/// One-line outcome statement (the user-facing thing being built)
#[arg(long, short)]
outcome: Option<String>,
/// Overwrite an existing contract
#[arg(long)]
force: bool,
},
/// Add a new obligation to the ledger
Add {
/// What must be true for this obligation to be discharged
claim: String,
/// Shell command that proves it (e.g. "pnpm test settings.contract.test.ts").
/// Required unless one of --literal / --regex is supplied (with --file).
#[arg(
long,
required_unless_present_any = ["literal", "regex"],
conflicts_with_all = ["literal", "regex"],
)]
proof: Option<String>,
/// Verbatim string that must appear in --file. Pairs with --kind literal_match.
/// Proof command is auto-synthesised as `grep -F -- <literal> <file>` —
/// note that `grep -F` matches anywhere in the file, including comments,
/// so include enough structural context (e.g. `.btn { border-radius: 0`)
/// to avoid matching stripped strings. `allow_hyphen_values` lets literals
/// like `-webkit-font-smoothing` be passed without `=` escaping.
#[arg(long, requires = "file", conflicts_with_all = ["regex"], allow_hyphen_values = true)]
literal: Option<String>,
/// POSIX extended-regex pattern that must match somewhere in --file.
/// Pairs with --kind literal_regex. Proof command is auto-synthesised
/// as `grep -E -- <pattern> <file>`. Use this when the obligation is
/// "the code does X" but the exact spelling may vary — e.g. matching
/// both `if (p.crossover != null)` and `if (p.crossover !== null)`.
/// Document POSIX ERE: `[[:space:]]` rather than `\s`, `[0-9]+`
/// rather than `\d+`. Alternatives via `(A|B)`.
#[arg(long, requires = "file", conflicts_with_all = ["literal"], allow_hyphen_values = true)]
regex: Option<String>,
/// File to search for --literal or --regex. Resolved relative to the
/// current directory at `prove` time; absence is a proof failure
/// (exit 2), not an `add` error.
#[arg(long, allow_hyphen_values = true)]
file: Option<String>,
/// Category of obligation
#[arg(long, value_enum, default_value = "other")]
kind: ObligationKind,
/// Mark as critical (gate blocks stop if open). Default true.
#[arg(long, default_value_t = true, action = clap::ArgAction::Set)]
critical: bool,
/// Comma-separated repo-relative paths the proof depends on. When set,
/// this obligation's evidence freshness is checked against the SHA-256
/// of just these files instead of the whole workspace — so unrelated
/// edits in a parallel session don't churn this obligation. Files
/// must exist at `prove`/`gate` time. When omitted, the global
/// workspace hash is used (v0.3 behavior).
///
/// Example: --depends-on src/foo.rs,src/bar.rs
#[arg(long, value_delimiter = ',')]
depends_on: Vec<String>,
},
/// Run a verification command and record evidence for an obligation.
///
/// With `--all`, re-prove every obligation in order (continues on failure
/// so the summary surfaces every red obligation). With `--stale-only`,
/// skip obligations whose evidence is already fresh — useful as the
/// post-commit "refresh just what changed" call.
Prove {
/// Obligation ID (e.g. O-001). Omit when using `--all`.
#[arg(required_unless_present = "all", conflicts_with = "all")]
id: Option<String>,
/// Override the proof command (single-id mode only; doesn't bypass
/// the gate — proof_hash mismatch keeps the obligation open).
#[arg(long, conflicts_with = "all")]
cmd: Option<String>,
/// Re-prove every obligation in add-order. Continues on failure;
/// the result envelope reports passed / failed / skipped counts.
#[arg(long)]
all: bool,
/// With `--all`, only run obligations whose current evidence is
/// not already passing/fresh (missing, failed, stale, mismatch).
#[arg(long, requires = "all")]
stale_only: bool,
},
/// Stop hook gate. Blocks unless every critical obligation has evidence.
Gate {
/// Emit Claude Code stop hook decision JSON instead of framework envelope
#[arg(long, conflicts_with = "summary")]
hook_mode: bool,
/// Emit a one-line shell-friendly summary instead of human/JSON envelope.
/// Format: `verdict=<pass|fail> critical_open=<n> advisory_open=<n>
/// total=<n>[ blocking=O-NNN]`. Stable schema; safe to grep / awk.
#[arg(long)]
summary: bool,
},
/// Seed a contract from a TOML/YAML manifest file
Seed {
/// Path to the manifest file (TOML or YAML)
manifest: String,
/// Overwrite an existing contract
#[arg(long)]
force: bool,
},
/// Show current scope, obligations, and evidence
Status,
/// Emit a subagent-ready briefing for Task/Agent delegation prompts
ExportContract,
/// Machine-readable capability manifest
#[command(visible_alias = "info")]
AgentInfo,
/// Install skill file to AI agent platforms
Skill {
#[command(subcommand)]
action: SkillAction,
},
/// Self-update from GitHub Releases
Update {
/// Check only, don't install
#[arg(long)]
check: bool,
},
}
#[derive(Subcommand)]
pub enum SkillAction {
/// Write SKILL.md to ~/.claude/skills, ~/.codex/skills, ~/.gemini/skills
Install,
/// Check which platforms have the skill installed
Status,
}