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
14pub 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
24pub fn exit_gate_error(shadow: bool, message: &str) -> ! {
27 eprintln!("{}", message);
28 process::exit(shadow_exit(shadow, 2));
29}
30
31pub 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
40pub 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
71pub 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
311const 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
315fn 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
370fn 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 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}