carryctx 0.7.0

Local-first memory for coding agents — resume tasks, checkpoints, and context across windows, sessions, and worktrees.
use crate::domain::task::{TaskPriority, TaskStatus};

pub struct NewTask {
    pub id: String,
    pub display_id: String,
    pub project_id: String,
    pub title: String,
    pub description: Option<String>,
    pub status: TaskStatus,
    pub priority: TaskPriority,
    pub owner_agent_id: Option<String>,
    pub parent_task_id: Option<String>,
    pub required_role: Option<String>,
    pub team_id: Option<String>,
}

#[derive(Clone, serde::Serialize)]
pub struct TaskRecord {
    pub id: String,
    pub display_id: String,
    pub project_id: String,
    pub title: String,
    pub description: Option<String>,
    pub status: TaskStatus,
    pub priority: TaskPriority,
    pub owner_agent_id: Option<String>,
    pub parent_task_id: Option<String>,
    pub created_at: String,
    pub updated_at: String,
    pub started_at: Option<String>,
    pub completed_at: Option<String>,
    pub required_role: Option<String>,
    pub team_id: Option<String>,
}

pub struct TaskFilter {
    pub project_id: String,
    pub status: Option<TaskStatus>,
    pub owner_agent_id: Option<String>,
    pub ready: bool,
    pub blocked: bool,
    pub mine: Option<String>,
}

#[allow(clippy::too_many_arguments)]
pub trait TaskRepository {
    fn allocate_display_id(
        &self,
        project_id: &str,
        prefix: &str,
    ) -> Result<u32, crate::error::CarryCtxError>;
    fn create(&self, input: &NewTask, now: &str)
    -> Result<TaskRecord, crate::error::CarryCtxError>;
    fn find_by_id(
        &self,
        project_id: &str,
        id: &str,
    ) -> Result<Option<TaskRecord>, crate::error::CarryCtxError>;
    fn find_by_display_id(
        &self,
        project_id: &str,
        display_id: &str,
    ) -> Result<Option<TaskRecord>, crate::error::CarryCtxError>;
    fn list(&self, filter: &TaskFilter) -> Result<Vec<TaskRecord>, crate::error::CarryCtxError>;
    fn update_status(
        &self,
        id: &str,
        project_id: &str,
        status: TaskStatus,
        owner_agent_id: Option<String>,
        now: &str,
    ) -> Result<TaskRecord, crate::error::CarryCtxError>;
    /// Compare-and-set claim: transitions the task to `in_progress` and assigns
    /// `owner_agent_id` only while the row is still `ready` and unowned. Used by
    /// racing claim paths so exactly one concurrent caller wins; a lost race is
    /// reported as a conflict, not silently overwritten.
    fn update_status_if_ready_unowned(
        &self,
        id: &str,
        project_id: &str,
        owner_agent_id: String,
        now: &str,
    ) -> Result<TaskRecord, crate::error::CarryCtxError>;
    fn count_open_progress(
        &self,
        project_id: &str,
        task_id: &str,
    ) -> Result<u64, crate::error::CarryCtxError>;
    fn has_active_session(
        &self,
        project_id: &str,
        task_id: &str,
    ) -> Result<bool, crate::error::CarryCtxError>;
    fn list_incomplete_strong_dependencies(
        &self,
        project_id: &str,
        task_id: &str,
    ) -> Result<Vec<String>, crate::error::CarryCtxError>;
    fn edit(
        &self,
        id: &str,
        project_id: &str,
        title: &str,
        priority: TaskPriority,
        description: Option<&str>,
        required_role: Option<&str>,
        now: &str,
    ) -> Result<TaskRecord, crate::error::CarryCtxError>;
}