Skip to main content

jev_harness/
cli.rs

1use crate::{
2    client::{JevClient, COMMANDCODE_API_URL},
3    gates::{
4        modulate_reasoning_effort_full, route_model_tier, should_abort_trajectory,
5        should_nudge_continuation, triage_test_failure, verify_step_completion,
6    },
7};
8use clap::{Parser, Subcommand};
9use std::fs;
10use std::io::{self, IsTerminal, Read};
11use std::path::{Path, PathBuf};
12use std::process;
13
14/// Shadow mode never changes the caller's pipeline: it reports the would-be exit code.
15pub fn shadow_exit(shadow: bool, code: i32) -> i32 {
16    if shadow {
17        eprintln!("[SHADOW] would exit {} - no action taken.", code);
18        0
19    } else {
20        code
21    }
22}
23
24/// Shadow mode never breaks the caller's pipeline: report the would-be code and exit 0.
25/// CLI misuse (missing input, bad flag) keeps exiting 2 — it is not a gate outcome.
26pub fn exit_gate_error(shadow: bool, message: &str) -> ! {
27    eprintln!("{}", message);
28    process::exit(shadow_exit(shadow, 2));
29}
30
31/// `[SIMULATION/MOCK]`, naming the degradation when a provider failure caused it (E0.2).
32pub fn mock_mode_label(degraded_reason: &str) -> String {
33    if degraded_reason.is_empty() {
34        "[SIMULATION/MOCK]".to_string()
35    } else {
36        format!("[SIMULATION/MOCK - degraded: {}]", degraded_reason)
37    }
38}
39
40/// Ensures `.jev/` is git-ignored in the project (E3.8): local state must never be committed.
41/// Returns the entry when it was added.
42pub fn ensure_state_ignored(cwd: &std::path::Path) -> Option<&'static str> {
43    let gitignore = cwd.join(".gitignore");
44    let entry = ".jev/";
45    let existing = fs::read_to_string(&gitignore).unwrap_or_default();
46    let ignored = [".jev/", ".jev", "/.jev/", "/.jev"];
47    if existing.lines().any(|line| ignored.contains(&line.trim())) {
48        return None;
49    }
50    let separator = if existing.is_empty() || existing.ends_with('\n') {
51        ""
52    } else {
53        "\n"
54    };
55    let header = if existing.is_empty() {
56        "# Added by jev-harness: local decision state (sessions, receipts, cache)\n"
57    } else {
58        ""
59    };
60    if fs::write(
61        &gitignore,
62        format!("{}{}{}{}", existing, separator, header, entry),
63    )
64    .is_err()
65    {
66        return None;
67    }
68    Some(entry)
69}
70
71/// Human label for where the effective model came from (E0.3).
72pub fn model_origin_label(source: &str) -> String {
73    match source {
74        "argument" => "explicit argument".to_string(),
75        "env" => "JEV_MODEL environment variable".to_string(),
76        ".jev.json" => "repository .jev.json".to_string(),
77        other => {
78            if other == "provider_default" {
79                "provider default".to_string()
80            } else {
81                other.to_string()
82            }
83        }
84    }
85}
86
87#[derive(Parser)]
88#[command(
89    name = "jev",
90    author = "ISMAEL HOSNI SOILET DE LIMA <soilet.ismael@gmail.com>",
91    version = env!("CARGO_PKG_VERSION"),
92    about = "Zero-overhead System One decision harness and token optimizer for AI coding agents",
93    long_about = None
94)]
95pub struct Cli {
96    #[arg(long, global = true, help = "Force offline heuristic simulation mode")]
97    pub mock: bool,
98
99    #[arg(long, global = true, help = "Output results in machine-readable JSON")]
100    pub json: bool,
101
102    #[arg(
103        long,
104        global = true,
105        help = "Override backend provider (typesafe, commandcode, opencode, openrouter, vercel)"
106    )]
107    pub provider: Option<String>,
108
109    #[arg(
110        long,
111        global = true,
112        help = "Surface provider errors instead of falling back to the offline engine (default: fail-open)"
113    )]
114    pub fail_closed: bool,
115
116    #[arg(
117        long,
118        global = true,
119        help = "Maximum provider attempts for retryable failures (default: 3)"
120    )]
121    pub retries: Option<u32>,
122
123    #[arg(
124        long,
125        global = true,
126        help = "Decide and report, but never change the exit code (also via .jev.json)"
127    )]
128    pub shadow: bool,
129
130    #[command(subcommand)]
131    pub command: Commands,
132}
133
134#[derive(Subcommand)]
135pub enum Commands {
136    #[command(about = "Display active credentials, provider, and engine mode")]
137    Status,
138
139    #[command(about = "Run stdio MCP server for Cursor, Claude Desktop, Antigravity IDE")]
140    Mcp,
141
142    #[command(about = "Initialize Jev Harness configuration and agent adapters in current repo")]
143    Init {
144        #[arg(long, help = "Configure Cursor MCP server")]
145        cursor: bool,
146        #[arg(
147            long,
148            help = "Override the detected test command for the generated git hook"
149        )]
150        test_cmd: Option<String>,
151        #[arg(long, help = "Configure Antigravity IDE hooks")]
152        antigravity: bool,
153        #[arg(long, help = "Configure all available agent integrations")]
154        all: bool,
155        #[arg(long, help = "Install git pre-commit test-gate hook")]
156        git: bool,
157    },
158
159    #[command(about = "Display ROI, token savings, and telemetry statistics")]
160    Metrics {
161        #[arg(long, help = "Reset session metrics")]
162        reset: bool,
163    },
164
165    #[command(
166        alias = "triage",
167        about = "Triage test traceback & determine if frontier LLM call can be skipped"
168    )]
169    TestGate {
170        #[arg(help = "Direct error string or path to error log file")]
171        log_pos: Option<String>,
172
173        #[arg(short, long, help = "Path to error log or raw string")]
174        log: Option<String>,
175
176        #[arg(long, help = "Sample error string (alias for --log)")]
177        sample: Option<String>,
178    },
179
180    #[command(
181        alias = "abort",
182        about = "Evaluate if current trajectory or refactor direction should be aborted"
183    )]
184    AbortCheck {
185        #[arg(help = "Proposed next step plan")]
186        plan_pos: Option<String>,
187
188        #[arg(short, long, help = "Proposed next step plan")]
189        plan: Option<String>,
190
191        #[arg(
192            short = 'H',
193            long,
194            default_value = "",
195            help = "Recent attempts or error history"
196        )]
197        history: String,
198    },
199
200    #[command(about = "Select minimal sufficient model tier for a given task")]
201    Route {
202        #[arg(help = "Task description")]
203        task_pos: Option<String>,
204
205        #[arg(short, long, help = "Task description")]
206        task: Option<String>,
207    },
208
209    #[command(about = "Verify if actual step output satisfies required criteria")]
210    Verify {
211        #[arg(short, long, help = "Acceptance criteria to satisfy")]
212        criteria: String,
213
214        #[arg(short, long, help = "Actual output to verify")]
215        output: String,
216    },
217
218    #[command(
219        alias = "astra-jev",
220        alias = "effort",
221        about = "Dynamically modulate reasoning effort per-generation (Astra-Jev)"
222    )]
223    ReasoningEffort {
224        #[arg(help = "Immediate step context or prompt description")]
225        context_pos: Option<String>,
226
227        #[arg(short, long, help = "Immediate step context or prompt description")]
228        context: Option<String>,
229
230        #[arg(
231            long = "target-provider",
232            default_value = "openai",
233            help = "Target model provider (openai, deepseek, qwen, anthropic, gemini)"
234        )]
235        target_provider: String,
236
237        #[arg(
238            short,
239            long,
240            help = "Target model name (e.g. gpt-5.6-luna, deepseek-v4.1-flash)"
241        )]
242        model: Option<String>,
243
244        #[arg(
245            long = "session-context-tokens",
246            default_value = "0",
247            help = "Active prompt tokens in session context"
248        )]
249        session_context_tokens: usize,
250
251        #[arg(
252            long = "supported-efforts",
253            help = "Comma-separated list of supported effort levels"
254        )]
255        supported_efforts: Option<String>,
256
257        #[arg(
258            long = "max-lease-steps",
259            default_value = "10",
260            help = "Maximum generations to lease unchanged effort"
261        )]
262        max_lease_steps: u32,
263    },
264
265    #[command(
266        alias = "nudge",
267        about = "Evaluate if agent stopped prematurely with unfinished work or unverified changes (Jev Nudge Gate)"
268    )]
269    NudgeGate {
270        #[arg(help = "Recent agent transcript tail or path to file")]
271        transcript_pos: Option<String>,
272
273        #[arg(short, long, help = "Recent agent transcript tail or path to file")]
274        transcript: Option<String>,
275
276        #[arg(
277            short = 'P',
278            long = "previous-nudge",
279            default_value = "",
280            help = "Summary of the previous nudge to verify progress"
281        )]
282        previous_nudge: String,
283
284        #[arg(
285            long,
286            default_value = "0.5",
287            help = "Probability threshold for nudge/waiting/progress (default: 0.5)"
288        )]
289        threshold: f64,
290    },
291}
292
293fn read_input(arg_pos: Option<String>, arg_flag: Option<String>) -> io::Result<String> {
294    if let Some(target) = arg_flag.or(arg_pos) {
295        let p = Path::new(&target);
296        if p.is_file() {
297            return fs::read_to_string(p);
298        }
299        return Ok(target);
300    }
301
302    if !io::stdin().is_terminal() {
303        let mut buffer = String::new();
304        io::stdin().read_to_string(&mut buffer)?;
305        return Ok(buffer);
306    }
307
308    Ok(String::new())
309}
310
311/// Best-effort detection of the repository test command for the generated git hook.
312/// Mirrors the Python and TypeScript runtimes.
313const ENV_EXAMPLE: &str = "# Jev Harness provider credentials. Offline mode needs NO key (zero network calls).\n# Docs: https://github.com/ismaelsoilet/jev-harness/blob/main/docs/AGENT_INTEGRATION_GUIDE.md\n#\n# OpenCode Zen (free tier) - https://opencode.ai/auth\n# JEV_PROVIDER=opencode\n# OPENCODE_API_KEY=\"your_key_here\"\n#\n# TypeSafe AI (direct) - https://console.typesafe.ai\n# TYPESAFE_API_KEY=\"your_key_here\"\n#\n# Command Code - https://commandcode.ai/signup  (or run: cmd login)\n# CMD_API_KEY=\"your_key_here\"\n#\n# OpenRouter (alpha access only)\n# OPENROUTER_API_KEY=\"your_key_here\"\n#\n# Vercel AI Gateway\n# AI_GATEWAY_API_KEY=\"your_key_here\"\n";
314
315/// Returns the project venv binary (e.g. ./.venv/bin/python) when present, else the fallback.
316/// Hook-safe: paths are relative to the repository root, which is the CWD Git uses for hooks.
317fn project_bin(cwd: &Path, name: &str, fallback: &str) -> String {
318    let candidates = [
319        PathBuf::from(".venv").join("bin").join(name),
320        PathBuf::from("venv").join("bin").join(name),
321        PathBuf::from(".venv")
322            .join("Scripts")
323            .join(format!("{}.exe", name)),
324        PathBuf::from("venv")
325            .join("Scripts")
326            .join(format!("{}.exe", name)),
327    ];
328    for rel in candidates {
329        if cwd.join(&rel).exists() {
330            return format!("./{}", rel.to_string_lossy().replace('\\', "/"));
331        }
332    }
333    fallback.to_string()
334}
335
336fn detect_test_command(cwd: &Path, override_cmd: &str) -> String {
337    if !override_cmd.trim().is_empty() {
338        return override_cmd.trim().to_string();
339    }
340    let python_bin = project_bin(cwd, "python", "python3");
341    let package_json = cwd.join("package.json");
342    if package_json.is_file() {
343        if let Ok(content) = fs::read_to_string(&package_json) {
344            if let Ok(value) = serde_json::from_str::<serde_json::Value>(&content) {
345                if value.get("scripts").and_then(|s| s.get("test")).is_some() {
346                    return "npm test --silent".to_string();
347                }
348            }
349        }
350    }
351    if cwd.join("Cargo.toml").is_file() {
352        return "cargo test --quiet".to_string();
353    }
354    let pyproject = cwd.join("pyproject.toml");
355    let pytest_marker = cwd.join("pytest.ini").is_file()
356        || cwd.join("tox.ini").is_file()
357        || (pyproject.is_file()
358            && fs::read_to_string(&pyproject)
359                .map(|c| c.contains("[tool.pytest"))
360                .unwrap_or(false));
361    if pytest_marker {
362        return format!("{} -m pytest -q", python_bin);
363    }
364    if pyproject.is_file() || cwd.join("setup.py").is_file() || cwd.join("tests").is_dir() {
365        return format!("{} -m unittest", python_bin);
366    }
367    String::new()
368}
369
370/// Generated pre-commit hook: the runner decides, Jev only advises.
371fn build_git_hook(test_cmd: &str, jev_bin: &str) -> String {
372    format!(
373        r#"#!/bin/sh
374# Jev Harness pre-commit gate (generated by `jev-harness init --git`).
375# The test runner decides whether the commit is blocked; Jev only triages a failing
376# run so it can be fixed deterministically when possible.
377# Regenerate with: jev-harness init --git
378
379TEST_CMD="{cmd}"
380JEV_BIN="{jev}"
381
382if [ -z "$TEST_CMD" ]; then
383  echo "[jev] No test command detected. Edit this hook and set TEST_CMD (e.g. npm test)." >&2
384  exit 0
385fi
386
387if ! TEST_OUTPUT=$(sh -c "$TEST_CMD" 2>&1); then
388  printf '%s\n' "$TEST_OUTPUT" | "$JEV_BIN" test-gate
389  exit 1
390fi
391exit 0
392"#,
393        cmd = test_cmd,
394        jev = jev_bin
395    )
396}
397
398pub async fn run_cli() {
399    let cli = Cli::parse();
400    let shadow = cli.shadow || crate::config::load_repo_config().shadow;
401    let mut client = JevClient::new(None, None, None, None, cli.mock).with_failure_policy(
402        !cli.fail_closed,
403        cli.retries.unwrap_or(3),
404        500,
405    );
406    if let Some(ref p) = cli.provider {
407        client.provider = p.clone();
408        if p == "commandcode" {
409            client.base_url = COMMANDCODE_API_URL.to_string();
410            client.model = "typesafe/jev".to_string();
411        } else if p == "opencode" {
412            client.base_url = "https://opencode.ai/zen/v1/systemone".to_string();
413            client.model = "jev-1.13-free".to_string();
414        }
415    }
416
417    match cli.command {
418        Commands::Status => {
419            println!("\n=== JEV HARNESS (RUST) STATUS ===");
420            let is_live =
421                !client.force_mock && (client.provider == "opencode" || client.api_key.is_some());
422            if is_live {
423                if client.provider == "opencode" {
424                    println!("Provider:    OPENCODE ZEN (Free Tier)");
425                    println!("Endpoint:    {}", client.base_url);
426                    println!("Engine Mode: LIVE (OpenCode Zen Free Community Model)");
427                } else if client.provider == "commandcode" {
428                    let masked = if let Some(ref key) = client.api_key {
429                        if key.len() > 10 {
430                            format!("{}...{}", &key[..6], &key[key.len() - 4..])
431                        } else {
432                            "***".to_string()
433                        }
434                    } else {
435                        "***".to_string()
436                    };
437                    println!("API Key:     Configured ({})", masked);
438                    println!("Provider:    COMMAND CODE (Free $0.00/M Deal - typesafe/jev)");
439                    println!("Endpoint:    {}", client.base_url);
440                    println!("Engine Mode: LIVE");
441                } else {
442                    let masked = if let Some(ref key) = client.api_key {
443                        if key.len() > 10 {
444                            format!("{}...{}", &key[..6], &key[key.len() - 4..])
445                        } else {
446                            "***".to_string()
447                        }
448                    } else {
449                        "***".to_string()
450                    };
451                    println!("API Key:     Configured ({})", masked);
452                    println!("Provider:    {}", client.provider.to_uppercase());
453                    println!("Endpoint:    {}", client.base_url);
454                    println!("Engine Mode: LIVE");
455                }
456            } else {
457                println!("API Key:     NOT DETECTED");
458                println!("Engine Mode: SIMULATION / MOCK (Heuristic offline mode active)");
459            }
460            println!("Model:       {}", client.model);
461            println!("Model origin: {}", model_origin_label(&client.model_source));
462            if client.model == "jev-latest" {
463                println!(
464                    "Note:        'jev-latest' is a moving alias - pin a version (e.g. \"model\": \"jev-1.13.0\") when your thresholds are calibrated."
465                );
466            }
467            println!("=================================\n");
468            process::exit(0);
469        }
470
471        Commands::Mcp => {
472            if let Err(e) = crate::mcp::run_mcp_server(Some(client)).await {
473                eprintln!("MCP server error: {}", e);
474                process::exit(1);
475            }
476            process::exit(0);
477        }
478
479        Commands::Init {
480            cursor,
481            antigravity: _,
482            all,
483            git,
484            test_cmd,
485        } => {
486            let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
487            let test_cmd_override = test_cmd.clone().unwrap_or_default();
488            println!("Initializing Jev Harness integration in: {}", cwd.display());
489
490            let skills_dir = cwd.join(".agents").join("skills").join("jev-harness");
491            let _ = fs::create_dir_all(&skills_dir);
492            let skill_file = skills_dir.join("SKILL.md");
493            let skill_content = "---\nname: jev-harness\ndescription: Repository adapter for Jev System One.\nlicense: MIT\n---\n\n# Local Jev Harness Adapter\n\nThis repository is connected to the global **Jev System One Harness**.\n";
494            if skill_file.exists() {
495                println!(
496                    "  [=] Existing agent skill preserved: {}",
497                    skill_file.display()
498                );
499            } else {
500                let _ = fs::write(&skill_file, skill_content);
501                println!("  [+] Created agent skill: {}", skill_file.display());
502            }
503
504            let jev_json = cwd.join(".jev.json");
505            if !jev_json.exists() {
506                let _ = fs::write(&jev_json, "{\n  \"api_key\": \"\",\n  \"model\": \"jev-latest\",\n  \"skip_llm_threshold\": 0.65,\n  \"abort_threshold\": 0.70\n}\n");
507                println!("  [+] Created repo config: {}", jev_json.display());
508            }
509
510            if ensure_state_ignored(&cwd).is_some() {
511                println!("  [+] Added '.jev/' to .gitignore (local sessions, receipts and cache)");
512            }
513
514            let env_example = cwd.join(".env.jev.example");
515            if !env_example.exists() {
516                let _ = fs::write(&env_example, ENV_EXAMPLE);
517                println!("  [+] Created env template: {}", env_example.display());
518            }
519
520            if cursor || all || cwd.join(".cursor").exists() {
521                let cursor_dir = cwd.join(".cursor");
522                let _ = fs::create_dir_all(&cursor_dir);
523                let cursor_mcp = cursor_dir.join("mcp.json");
524                if !cursor_mcp.exists() {
525                    let _ = fs::write(&cursor_mcp, "{\n  \"mcpServers\": {\n    \"jev-harness\": {\n      \"command\": \"jev\",\n      \"args\": [\"mcp\"]\n    }\n  }\n}\n");
526                    println!("  [+] Created Cursor MCP config: {}", cursor_mcp.display());
527                }
528            }
529            let mut git_gate_active = true;
530            if git || all {
531                let git_hooks = cwd.join(".git").join("hooks");
532                if git_hooks.is_dir() {
533                    let marker = "Jev Harness pre-commit gate";
534                    let pre_commit = git_hooks.join("pre-commit");
535                    let test_cmd = detect_test_command(&cwd, &test_cmd_override);
536                    let jev_bin = project_bin(&cwd, "jev-harness", "jev-harness");
537                    let hook_script = build_git_hook(&test_cmd, &jev_bin);
538                    let existing = fs::read_to_string(&pre_commit).unwrap_or_default();
539                    if !existing.is_empty()
540                        && !existing.to_lowercase().contains(&marker.to_lowercase())
541                    {
542                        let sample = git_hooks.join("pre-commit.jev");
543                        let _ = fs::write(&sample, &hook_script);
544                        #[cfg(unix)]
545                        {
546                            use std::os::unix::fs::PermissionsExt;
547                            let _ = fs::set_permissions(&sample, fs::Permissions::from_mode(0o755));
548                        }
549                        git_gate_active = false;
550                        println!("  [!] An existing pre-commit hook was preserved; the Jev gate is NOT active yet. Merge {} into it (or use the pre-commit framework) to enable it.", sample.display());
551                    } else {
552                        let _ = fs::write(&pre_commit, &hook_script);
553                        #[cfg(unix)]
554                        {
555                            use std::os::unix::fs::PermissionsExt;
556                            let _ =
557                                fs::set_permissions(&pre_commit, fs::Permissions::from_mode(0o755));
558                        }
559                        let detected = detect_test_command(&cwd, &test_cmd_override);
560                        let detail = if detected.is_empty() {
561                            " (no test command detected yet)".to_string()
562                        } else {
563                            format!(" (test command: {})", detected)
564                        };
565                        println!(
566                            "  [+] Installed Git pre-commit guardrail: {}{}",
567                            pre_commit.display(),
568                            detail
569                        );
570                    }
571                }
572            }
573            if git_gate_active {
574                println!("\n[OK] Repository configured successfully! You can now run 'jev-harness status'.\n");
575            } else {
576                println!("\n[!] Repository configured, but the Jev commit gate is NOT active (an existing hook was preserved). Merge .git/hooks/pre-commit.jev to enable it.\n");
577            }
578            process::exit(0);
579        }
580
581        Commands::Metrics { reset } => {
582            let home = std::env::var("HOME")
583                .or_else(|_| std::env::var("USERPROFILE"))
584                .unwrap_or_else(|_| ".".to_string());
585            let config_dir = PathBuf::from(&home).join(".config").join("jev");
586            let session_path = config_dir.join("session.json");
587
588            if reset {
589                if session_path.exists() {
590                    let _ = fs::remove_file(&session_path);
591                }
592                println!("\n[OK] Jev Harness metrics reset successfully.\n");
593                process::exit(0);
594            }
595
596            let mut triage_calls = 0u64;
597            let mut skipped_llm = 0u64;
598            let mut abort_guards = 0u64;
599            let mut deterministic_routes = 0u64;
600            let mut effort_modulations = 0u64;
601            let mut nudge_continuations = 0u64;
602            let mut tokens_saved = 0u64;
603            let mut cost_saved = 0.0f64;
604
605            if let Ok(content) = fs::read_to_string(&session_path) {
606                if let Ok(val) = serde_json::from_str::<serde_json::Value>(&content) {
607                    triage_calls = val
608                        .get("total_triage_calls")
609                        .and_then(|v| v.as_u64())
610                        .unwrap_or(0);
611                    skipped_llm = val
612                        .get("skipped_llm_calls")
613                        .and_then(|v| v.as_u64())
614                        .unwrap_or(0);
615                    abort_guards = val
616                        .get("abort_guards_triggered")
617                        .and_then(|v| v.as_u64())
618                        .unwrap_or(0);
619                    deterministic_routes = val
620                        .get("deterministic_routes")
621                        .and_then(|v| v.as_u64())
622                        .unwrap_or(0);
623                    effort_modulations = val
624                        .get("effort_modulations")
625                        .and_then(|v| v.as_u64())
626                        .unwrap_or(0);
627                    nudge_continuations = val
628                        .get("nudge_continuations")
629                        .and_then(|v| v.as_u64())
630                        .unwrap_or(0);
631                    tokens_saved = val
632                        .get("estimated_tokens_saved")
633                        .and_then(|v| v.as_u64())
634                        .unwrap_or(0);
635                    cost_saved = val
636                        .get("estimated_cost_saved_usd")
637                        .and_then(|v| v.as_f64())
638                        .unwrap_or(0.0);
639                }
640            }
641
642            if cli.json {
643                let out = serde_json::json!({
644                    "total_triage_calls": triage_calls,
645                    "skipped_llm_calls": skipped_llm,
646                    "abort_guards_triggered": abort_guards,
647                    "deterministic_routes": deterministic_routes,
648                    "effort_modulations": effort_modulations,
649                    "nudge_continuations": nudge_continuations,
650                    "estimated_tokens_saved": tokens_saved,
651                    "estimated_cost_saved_usd": (cost_saved * 100.0).round() / 100.0,
652                    "estimates_are_heuristic": true,
653                });
654                println!("{}", serde_json::to_string_pretty(&out).unwrap());
655            } else {
656                println!("\n=== JEV HARNESS ROI & TOKEN METRICS (RUST) ===");
657                println!("Total Test Triages:      {}", triage_calls);
658                println!(
659                    "LLM Calls Intercepted:   {} (Fixed deterministically)",
660                    skipped_llm
661                );
662                println!("Doom Loops Aborted:      {}", abort_guards);
663                println!("Deterministic Routes:    {}", deterministic_routes);
664                println!(
665                    "Effort Modulations:      {} (Astra-Jev per-generation)",
666                    effort_modulations
667                );
668                println!(
669                    "Continuation Nudges:     {} (Jev Nudge Gate)",
670                    nudge_continuations
671                );
672                println!(
673                    "Estimated Tokens Saved:  ⚡ {} tokens (heuristic estimate)",
674                    tokens_saved
675                );
676                println!(
677                    "Estimated API Cost Saved: 💸 ${:.2} USD (heuristic estimate)",
678                    cost_saved
679                );
680                println!(
681                    "Assumption Model:        {} tokens/${:.2} per intercepted triage; {} tokens/${:.2} per aborted doom loop",
682                    26200, 0.31, 80000, 1.20
683                );
684                println!("==============================================\n");
685            }
686            process::exit(0);
687        }
688
689        Commands::TestGate {
690            log_pos,
691            log,
692            sample,
693        } => {
694            if let Some(path) = log.as_ref() {
695                if !std::path::Path::new(path).is_file() {
696                    // `--log` is documented as a file: a typo must not be triaged as log text.
697                    eprintln!("Error: log file not found: {}", path);
698                    eprintln!("Hint: pass the log text as a positional argument, use --sample for a literal string, or pipe it via stdin.");
699                    process::exit(2);
700                }
701            }
702            let text = match read_input(log_pos, log.or(sample)) {
703                Ok(t) if !t.trim().is_empty() => t,
704                _ => {
705                    eprintln!("Error: No test failure log provided. Pass log via argument or pipe via stdin.");
706                    process::exit(2);
707                }
708            };
709
710            match triage_test_failure(&text, Some(&client)).await {
711                Ok(res) => {
712                    if cli.json {
713                        let mut value = serde_json::to_value(&res).unwrap_or(serde_json::json!({}));
714                        if shadow {
715                            if let Some(obj) = value.as_object_mut() {
716                                obj.insert("shadow".to_string(), serde_json::json!(true));
717                                obj.insert(
718                                    "would_exit".to_string(),
719                                    serde_json::json!(if res.skip_llm { 0 } else { 1 }),
720                                );
721                            }
722                        }
723                        println!("{}", serde_json::to_string_pretty(&value).unwrap());
724                    } else {
725                        println!("\n--- JEV TEST TRIAGE VERDICT (RUST) ---");
726                        println!("Category:        {}", res.category.to_uppercase());
727                        if res.category == "no_failure" {
728                            println!("No failure detected: the test run appears successful. Nothing to triage.");
729                            println!("Mode:            [DETERMINISTIC]");
730                            println!("--------------------------------\n");
731                            process::exit(0);
732                        }
733                        println!("Confidence:      {:.1}%", res.confidence * 100.0);
734                        println!(
735                            "Skip LLM Call:   {}",
736                            if res.skip_llm {
737                                "YES (Save Tokens!)"
738                            } else {
739                                "NO (Dispatch to System 2)"
740                            }
741                        );
742                        println!("Severity Score:  {:.1} / 4.0", res.severity_score);
743                        println!("Recommendation:  {}", res.action_recommendation);
744                        if res.is_mock {
745                            println!("Mode:            {}", mock_mode_label(&res.degraded_reason));
746                        }
747                        println!("--------------------------------------\n");
748                    }
749                    process::exit(shadow_exit(shadow, if res.skip_llm { 0 } else { 1 }));
750                }
751                Err(e) => {
752                    exit_gate_error(shadow, &format!("Error: triaging test failure: {}", e));
753                }
754            }
755        }
756
757        Commands::AbortCheck {
758            plan_pos,
759            plan,
760            history,
761        } => {
762            let plan_text = match read_input(plan_pos, plan) {
763                Ok(p) if !p.trim().is_empty() => p,
764                _ => {
765                    eprintln!(
766                        "Error: No plan provided. Pass --plan <text> or positional argument."
767                    );
768                    process::exit(2);
769                }
770            };
771
772            match should_abort_trajectory(&plan_text, &history, Some(&client)).await {
773                Ok(res) => {
774                    if cli.json {
775                        println!("{}", serde_json::to_string_pretty(&res).unwrap());
776                    } else {
777                        println!("\n--- JEV ABORT GATE VERDICT (RUST) ---");
778                        println!(
779                            "Should Abort:     {}",
780                            if res.should_abort {
781                                "YES - STOP & RECONSIDER"
782                            } else {
783                                "NO - PROCEED"
784                            }
785                        );
786                        println!("Abort Probability: {:.1}%", res.abort_probability * 100.0);
787                        println!("Viability Score:   {:.1} / 4.0", res.viability_score);
788                        println!("Summary:           {}", res.reasoning_summary);
789                        if res.is_mock {
790                            println!(
791                                "Mode:              {}",
792                                mock_mode_label(&res.degraded_reason)
793                            );
794                        }
795                        println!("-------------------------------------\n");
796                    }
797                    process::exit(shadow_exit(shadow, if res.should_abort { 1 } else { 0 }));
798                }
799                Err(e) => {
800                    exit_gate_error(shadow, &format!("Error: evaluating abort gate: {}", e));
801                }
802            }
803        }
804
805        Commands::Route { task_pos, task } => {
806            let task_text = match read_input(task_pos, task) {
807                Ok(t) if !t.trim().is_empty() => t,
808                _ => {
809                    eprintln!("Error: No task description provided. Pass --task <text>.");
810                    process::exit(2);
811                }
812            };
813
814            match route_model_tier(&task_text, Some(&client)).await {
815                Ok(res) => {
816                    if cli.json {
817                        println!("{}", serde_json::to_string_pretty(&res).unwrap());
818                    } else {
819                        println!("\n--- JEV MODEL ROUTE VERDICT (RUST) ---");
820                        println!("Selected Tier:     {}", res.selected_tier.to_uppercase());
821                        println!("Confidence:        {:.1}%", res.confidence * 100.0);
822                        println!("Recommended Model: {}", res.recommended_model);
823                        println!("Rationale:         {}", res.rationale);
824                        if res.is_mock {
825                            println!(
826                                "Mode:              {}",
827                                mock_mode_label(&res.degraded_reason)
828                            );
829                        }
830                        println!("--------------------------------------\n");
831                    }
832                    process::exit(0);
833                }
834                Err(e) => {
835                    exit_gate_error(shadow, &format!("Error: routing model tier: {}", e));
836                }
837            }
838        }
839
840        Commands::Verify { criteria, output } => {
841            match verify_step_completion(&criteria, &output, Some(&client)).await {
842                Ok(res) => {
843                    if cli.json {
844                        println!("{}", serde_json::to_string_pretty(&res).unwrap());
845                    } else {
846                        println!("\n--- JEV VERIFICATION VERDICT (RUST) ---");
847                        println!(
848                            "Verified:          {}",
849                            if res.is_verified {
850                                "PASS"
851                            } else {
852                                "REWORK NEEDED"
853                            }
854                        );
855                        println!(
856                            "Satisfaction Prob: {:.1}%",
857                            res.satisfaction_probability * 100.0
858                        );
859                        println!("Rigor Score:       {:.1} / 4.0", res.rigor_score);
860                        if res.is_mock {
861                            println!(
862                                "Mode:              {}",
863                                mock_mode_label(&res.degraded_reason)
864                            );
865                        }
866                        println!("---------------------------------------\n");
867                    }
868                    process::exit(shadow_exit(shadow, if res.is_verified { 0 } else { 1 }));
869                }
870                Err(e) => {
871                    exit_gate_error(shadow, &format!("Error: verifying step completion: {}", e));
872                }
873            }
874        }
875
876        Commands::ReasoningEffort {
877            context_pos,
878            context,
879            target_provider,
880            model,
881            session_context_tokens,
882            supported_efforts,
883            max_lease_steps,
884        } => {
885            let ctx = match read_input(context_pos, context) {
886                Ok(t) if !t.trim().is_empty() => t,
887                _ => {
888                    eprintln!(
889                        "Error: Context/step description must be provided via argument or stdin."
890                    );
891                    process::exit(2);
892                }
893            };
894
895            let supported_vec = supported_efforts.map(|s| {
896                s.split(',')
897                    .map(|item| item.trim().to_lowercase())
898                    .filter(|item| !item.is_empty())
899                    .collect::<Vec<String>>()
900            });
901            let supported_refs = supported_vec
902                .as_ref()
903                .map(|v| v.iter().map(|s| s.as_str()).collect::<Vec<&str>>());
904
905            match modulate_reasoning_effort_full(
906                &ctx,
907                &target_provider,
908                model.as_deref(),
909                session_context_tokens,
910                supported_refs.as_deref(),
911                max_lease_steps,
912                Some(&client),
913            )
914            .await
915            {
916                Ok(res) => {
917                    if cli.json {
918                        println!("{}", serde_json::to_string_pretty(&res).unwrap());
919                    } else {
920                        println!("\n--- JEV REASONING EFFORT VERDICT (RUST) ---");
921                        println!("Effort:            {}", res.effort.to_uppercase());
922                        println!("Confidence:        {:.1}%", res.confidence * 100.0);
923                        println!("Complexity Score:  {:.1} / 4.0", res.complexity_score);
924                        println!("Lease Steps:       {}", res.lease_steps);
925                        println!("Provider:          {}", res.provider);
926                        println!(
927                            "Supported:         {}",
928                            if res.is_reasoning_supported {
929                                "YES"
930                            } else {
931                                "NO (Direct model)"
932                            }
933                        );
934                        println!("Rationale:         {}", res.rationale);
935                        println!("Provider Params:   {}", res.provider_params);
936                        if !res.cache_safe_recommendation.is_empty() {
937                            println!("Cache Advisory:    {}", res.cache_safe_recommendation);
938                        }
939                        if res.is_mock {
940                            println!(
941                                "Mode:              {}",
942                                mock_mode_label(&res.degraded_reason)
943                            );
944                        }
945                        println!("------------------------------------------\n");
946                    }
947                    process::exit(0);
948                }
949                Err(e) => {
950                    exit_gate_error(
951                        shadow,
952                        &format!("Error: modulating reasoning effort: {}", e),
953                    );
954                }
955            }
956        }
957
958        Commands::NudgeGate {
959            transcript_pos,
960            transcript,
961            previous_nudge,
962            threshold,
963        } => {
964            let tail = match read_input(transcript_pos, transcript) {
965                Ok(t) if !t.trim().is_empty() => t,
966                _ => {
967                    eprintln!("Error: No transcript tail provided. Pass --transcript <text> or pipe via stdin.");
968                    process::exit(2);
969                }
970            };
971
972            match should_nudge_continuation(&tail, &previous_nudge, threshold, Some(&client)).await
973            {
974                Ok(res) => {
975                    if cli.json {
976                        println!("{}", serde_json::to_string_pretty(&res).unwrap());
977                    } else {
978                        println!("\n=== JEV CONTINUATION NUDGE GATE (RUST) ===");
979                        println!(
980                            "Should Nudge:      {}",
981                            if res.should_nudge {
982                                "YES (Inject Continuation)"
983                            } else {
984                                "NO (Stop & Yield to User)"
985                            }
986                        );
987                        println!("Workflow Phase:    {}", res.workflow_phase.to_uppercase());
988                        println!("Nudge Prob:        {:.1}%", res.nudge_probability * 100.0);
989                        println!("Waiting Prob:      {:.1}%", res.waiting_probability * 100.0);
990                        println!(
991                            "Progress Prob:     {:.1}%",
992                            res.progress_probability * 100.0
993                        );
994                        println!("Rationale:         {}", res.rationale);
995                        if !res.suggested_nudge_prompt.is_empty() {
996                            println!("Suggested Prompt:  {}", res.suggested_nudge_prompt);
997                        }
998                        if res.is_mock {
999                            println!(
1000                                "Engine Mode:       {}",
1001                                mock_mode_label(&res.degraded_reason)
1002                            );
1003                        }
1004                        println!("==========================================\n");
1005                    }
1006                    process::exit(shadow_exit(shadow, if res.should_nudge { 0 } else { 1 }));
1007                }
1008                Err(e) => {
1009                    exit_gate_error(shadow, &format!("Error: evaluating nudge gate: {}", e));
1010                }
1011            }
1012        }
1013    }
1014}