ritalin 0.4.2

Executive function for AI coding agents. Focus their intelligence, ground their work, stop the avoidable mistakes.
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,
}