diff --git a/src/cli.rs b/src/cli.rs index 73b19bd..66abec8 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,4 +1,4 @@ -use crate::{langfuse, log, payload, state}; +use crate::{langfuse, log, payload, state, tags}; use std::io::BufRead; /// SessionStart handler. Records the session, prints a one-line tracing @@ -36,6 +36,8 @@ pub fn on_start() -> i32 { }; let message = if suppressed { "code-trace: tracing PAUSED for this session (private mode).".to_string() + } else if langfuse::require_git_repo() && !tags::cwd_in_git_repo(cwd.as_deref()) { + "code-trace: tracing inactive (not in a git repository).".to_string() } else { format!( "⚠️ code-trace: tracing ENABLED → {}. Use the pause command to make this session private.", @@ -52,7 +54,13 @@ pub fn on_start() -> i32 { pub fn status() -> i32 { match (langfuse::tracing_enabled(), langfuse::config_from_env()) { - (true, Some(config)) => println!("tracing: ENABLED → {}", config.host), + (true, Some(config)) => { + if langfuse::require_git_repo() && !tags::cwd_in_git_repo(None) { + println!("tracing: inactive (not in a git repository)"); + } else { + println!("tracing: ENABLED → {}", config.host); + } + } (true, None) => println!("tracing: not configured (TRACE_TO_LANGFUSE set but keys missing)"), (false, Some(_)) => println!("tracing: disabled (keys configured, TRACE_TO_LANGFUSE not true)"), (false, None) => println!("tracing: not configured"), diff --git a/tests/cli_test.rs b/tests/cli_test.rs index 5206730..da37ab7 100644 --- a/tests/cli_test.rs +++ b/tests/cli_test.rs @@ -101,6 +101,7 @@ impl MockServer { struct TestEnv { home: TempDir, langfuse_url: Option, + extra_env: Vec<(String, String)>, } impl TestEnv { @@ -108,6 +109,7 @@ impl TestEnv { TestEnv { home: TempDir::new().unwrap(), langfuse_url: None, + extra_env: Vec::new(), } } @@ -115,9 +117,17 @@ impl TestEnv { TestEnv { home: TempDir::new().unwrap(), langfuse_url: Some(url.to_string()), + extra_env: Vec::new(), } } + /// Set an extra env var, applied last so it overrides the defaults (e.g. + /// re-enabling the git-repo gate that `command()` turns off). + fn with_env(mut self, key: &str, value: &str) -> Self { + self.extra_env.push((key.to_string(), value.to_string())); + self + } + fn state_file(&self) -> std::path::PathBuf { self.home.path().join("data").join("code-trace").join("state.json") } @@ -149,6 +159,9 @@ impl TestEnv { .env("LANGFUSE_SECRET_KEY", "sk-test") .env("LANGFUSE_BASE_URL", url); } + for (k, v) in &self.extra_env { + cmd.env(k, v); + } cmd } @@ -278,6 +291,58 @@ fn on_start_reports_paused_for_suppressed_session() { assert!(env.read_state().sessions["sess-private"].suppressed); } +#[test] +fn on_start_reports_inactive_outside_git_repo() { + let plain = TempDir::new().unwrap(); // not a git repo + let env = TestEnv::with_langfuse("http://127.0.0.1:9") + .with_env("CODE_TRACE_REQUIRE_GIT_REPO", "true"); + let payload = serde_json::json!({ + "hook_event_name": "SessionStart", + "source": "startup", + "session_id": "sess-nogit-start", + "transcript_path": "/tmp/t.jsonl", + "cwd": plain.path().to_string_lossy(), + }) + .to_string(); + let (code, out, _) = env.run(&["--on-start"], Some(&payload)); + assert_eq!(code, 0); + let v: serde_json::Value = serde_json::from_str(&out).expect("on-start emits JSON"); + let msg = v["systemMessage"].as_str().unwrap_or(""); + assert!(msg.contains("inactive"), "got: {out}"); + assert!(!msg.contains("ENABLED"), "must not claim ENABLED: {out}"); +} + +#[test] +fn on_start_reports_enabled_inside_git_repo() { + let repo = TempDir::new().unwrap(); + let ok = Command::new("git") + .args(["init"]) + .current_dir(repo.path()) + .output() + .expect("git must be installed for this test") + .status + .success(); + assert!(ok, "git init failed"); + + let env = TestEnv::with_langfuse("http://127.0.0.1:9") + .with_env("CODE_TRACE_REQUIRE_GIT_REPO", "true"); + let payload = serde_json::json!({ + "hook_event_name": "SessionStart", + "source": "startup", + "session_id": "sess-git-start", + "transcript_path": "/tmp/t.jsonl", + "cwd": repo.path().to_string_lossy(), + }) + .to_string(); + let (code, out, _) = env.run(&["--on-start"], Some(&payload)); + assert_eq!(code, 0); + let v: serde_json::Value = serde_json::from_str(&out).expect("on-start emits JSON"); + assert!( + v["systemMessage"].as_str().unwrap_or("").contains("ENABLED"), + "got: {out}" + ); +} + #[test] fn pause_targets_most_recent_and_resume_clears() { let env = TestEnv::new();