1//! Where the daemon keeps its socket, its log and its decisions. The mod
2//! computes the same socket path from the same two variables (`bridge.ts`).
3
4use std::path::PathBuf;
5
6/// `$XDG_STATE_HOME/jevhooks`, or `~/.local/state/jevhooks`.
7pub fn state_dir() -> PathBuf {
8    let base = std::env::var_os("XDG_STATE_HOME")
9        .filter(|v| !v.is_empty())
10        .map(PathBuf::from)
11        .or_else(|| std::env::var_os("HOME").map(|home| PathBuf::from(home).join(".local/state")))
12        .unwrap_or_else(|| PathBuf::from("."));
13    base.join("jevhooks")
14}
15
16/// The one daemon's socket; every session's mod and MCP server talk to it.
17pub fn socket() -> PathBuf {
18    state_dir().join("daemon.sock")
19}
20
21/// Held for as long as a daemon runs, so two cannot serve at once.
22pub fn lock() -> PathBuf {
23    state_dir().join("daemon.lock")
24}
25
26/// The daemon's stderr, since nothing else is attached to it.
27pub fn daemon_log() -> PathBuf {
28    state_dir().join("daemon.log")
29}
30
31/// Every decision and what became of it, one JSON object per line.
32pub fn decisions() -> PathBuf {
33    state_dir().join("decisions.jsonl")
34}
35
36/// Which build this process is: the executable's device, inode, size and
37/// modification time. A rebuilt binary is a different identity, which is how
38/// a new session's MCP server knows the running daemon is stale.
39pub fn identity() -> String {
40    use std::os::unix::fs::MetadataExt;
41    match std::env::current_exe().and_then(std::fs::metadata) {
42        Ok(meta) => format!("{}:{}:{}:{}", meta.dev(), meta.ino(), meta.len(), meta.mtime_nsec() as i128 + meta.mtime() as i128 * 1_000_000_000),
43        Err(_) => "unknown".to_owned(),
44    }
45}