Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.",
Expand All @@ -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"),
Expand Down
65 changes: 65 additions & 0 deletions tests/cli_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,23 +101,33 @@ impl MockServer {
struct TestEnv {
home: TempDir,
langfuse_url: Option<String>,
extra_env: Vec<(String, String)>,
}

impl TestEnv {
fn new() -> Self {
TestEnv {
home: TempDir::new().unwrap(),
langfuse_url: None,
extra_env: Vec::new(),
}
}

fn with_langfuse(url: &str) -> Self {
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")
}
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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();
Expand Down