tsk-bin 0.13.0

tsk — work with a clear context
use std::path::{Path, PathBuf};

use crate::ledger::location::Repo;
use crate::ledger::store::{self, LedgerStore, RefreshOutcome};
use crate::ledger::{fetch_keeping_local_reporting, push_reporting};

pub const CLOUD_SESSION_VAR: &str = "CLAUDE_CODE_REMOTE_SESSION_ID";
pub const MARKER_FILE: &str = "tsk-thread-id";

pub struct Session {
    pub repo: Repo,
    pub cloud_session: Option<String>,
    pub git_dir: PathBuf,
    pub store: Box<dyn LedgerStore>,
}

impl Session {
    pub fn discover() -> Result<Session, String> {
        let cwd = std::env::current_dir()
            .map_err(|e| format!("error: could not read the current directory: {}", e))?;
        let repo = Repo::discover(&cwd)?;
        let git_dir = PathBuf::from(repo.git.run_line([
            "rev-parse",
            "--path-format=absolute",
            "--git-dir",
        ])?);
        let cloud_session = std::env::var(CLOUD_SESSION_VAR)
            .ok()
            .filter(|v| !v.is_empty());
        let store = store::open(&cwd)?;
        Ok(Session {
            repo,
            cloud_session,
            git_dir,
            store,
        })
    }

    pub fn refresh(&self) -> Result<RefreshOutcome, String> {
        fetch_keeping_local_reporting(self.store.as_ref())
    }

    pub fn push(&self, message: &str) -> Result<(), String> {
        push_reporting(self.store.as_ref(), message).map(|_| ())
    }

    pub fn marker_path(&self) -> PathBuf {
        self.git_dir.join(MARKER_FILE)
    }

    pub fn actor_urn(&self) -> String {
        actor_urn(self.cloud_session.as_deref(), &self.git_dir)
    }
}

pub fn actor_urn(cloud_session: Option<&str>, git_dir: &Path) -> String {
    match cloud_session {
        Some(id) => format!("urn:tsk:cloudsession:{}", id),
        None => format!(
            "urn:tsk:worktree:{}",
            git_dir
                .file_name()
                .map(|n| n.to_string_lossy().into_owned())
                .unwrap_or_default()
        ),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn cloud_session_urn_names_the_session() {
        assert_eq!(
            actor_urn(Some("cse_01abc"), Path::new("/r/.git")),
            "urn:tsk:cloudsession:cse_01abc"
        );
    }

    #[test]
    fn main_worktree_urn_is_dot_git() {
        assert_eq!(
            actor_urn(None, Path::new("/r/.git")),
            "urn:tsk:worktree:.git"
        );
    }

    #[test]
    fn linked_worktree_urn_is_the_worktree_name() {
        assert_eq!(
            actor_urn(None, Path::new("/r/.git/worktrees/feature-x")),
            "urn:tsk:worktree:feature-x"
        );
    }
}