use crate::*;
use carryctx::adapter::unit_of_work::UnitOfWork;
use carryctx::application;
use carryctx::application::runtime::InvocationContext;
use carryctx::domain::dependency::DependencyKind;
use carryctx::domain::task::TransitionAction;
use carryctx::error::{CarryCtxError, ExitCode};
use clap::Parser;
#[derive(Parser, Debug)]
pub enum TaskCommand {
Create {
#[arg(long)]
title: String,
#[arg(long)]
description: Option<String>,
#[arg(long)]
priority: Option<String>,
#[arg(long)]
owner: Option<String>,
#[arg(long)]
status: Option<String>,
#[arg(long)]
depends_on: Vec<String>,
},
List {
#[arg(long)]
status: Option<String>,
#[arg(long)]
owner: Option<String>,
#[arg(long)]
mine: bool,
},
Show { task_ref: String },
Edit {
task_ref: String,
#[arg(long)]
title: Option<String>,
#[arg(long)]
priority: Option<String>,
},
Claim { task_ref: String },
Release { task_ref: String },
Start { task_ref: String },
Block {
task_ref: String,
#[arg(long)]
reason: String,
},
Unblock { task_ref: String },
Review { task_ref: String },
Complete { task_ref: String },
Cancel {
task_ref: String,
#[arg(long)]
reason: String,
},
Reopen { task_ref: String },
Depend {
task_ref: String,
#[arg(long)]
on: String,
#[arg(long)]
kind: Option<String>,
},
Undepend {
task_ref: String,
#[arg(long)]
on: String,
},
}
#[derive(Parser, Debug)]
pub struct TaskArgs {
#[command(subcommand)]
pub command: TaskCommand,
}
pub fn handle_task(
args: &TaskArgs,
ctx: &InvocationContext,
is_json: bool,
) -> Result<ExitCode, ExitCode> {
let mut runtime = try_open_runtime(ctx)?;
let project_id = &runtime.config.project.id;
let conn = runtime.database.connection_mut();
match &args.command {
TaskCommand::Create {
title,
description: _,
priority,
owner,
status,
depends_on,
} => {
let parsed_status = status
.as_deref()
.map(parse_task_status)
.transpose()
.map_err(|e| e.exit_code)?;
let parsed_priority = priority
.as_deref()
.map(parse_task_priority)
.transpose()
.map_err(|e| e.exit_code)?;
let tx = conn
.transaction()
.map_err(|e| CarryCtxError::database_error(format!("{e}")).exit_code)?;
let uow = UnitOfWork::new(tx);
let result = application::task::create_task(
project_id,
title,
Some(&runtime.config.project.task_prefix),
parsed_status,
parsed_priority,
owner.as_deref(),
depends_on,
ctx.agent.as_deref(),
&uow,
);
let committed = result.and_then(|t| uow.commit().map(|_| t));
render_and_print("task.create", committed, is_json, ctx.quiet)
}
TaskCommand::List {
status,
owner,
mine,
} => {
let parsed_status = status
.as_deref()
.map(parse_task_status)
.transpose()
.map_err(|e| e.exit_code)?;
let filter = TaskFilter {
project_id: project_id.to_string(),
status: parsed_status,
owner_agent_id: owner.clone(),
ready: false,
blocked: false,
mine: if *mine { ctx.agent.clone() } else { None },
};
let tx = conn
.transaction()
.map_err(|e| CarryCtxError::database_error(format!("{e}")).exit_code)?;
let uow = UnitOfWork::new(tx);
let result = application::task::list_tasks(project_id, &filter, &uow);
render_and_print("task.list", result, is_json, ctx.quiet)
}
TaskCommand::Show { task_ref } => {
let tx = conn
.transaction()
.map_err(|e| CarryCtxError::database_error(format!("{e}")).exit_code)?;
let uow = UnitOfWork::new(tx);
let result = application::task::show_task(project_id, task_ref, &uow);
render_and_print("task.show", result, is_json, ctx.quiet)
}
TaskCommand::Edit {
task_ref,
title,
priority,
} => {
let parsed_priority = priority
.as_deref()
.map(parse_task_priority)
.transpose()
.map_err(|e| e.exit_code)?;
let tx = conn
.transaction()
.map_err(|e| CarryCtxError::database_error(format!("{e}")).exit_code)?;
let uow = UnitOfWork::new(tx);
let result = application::task::edit_task(
project_id,
task_ref,
title.as_deref(),
parsed_priority,
ctx.agent.as_deref(),
&uow,
);
let committed = result.and_then(|t| uow.commit().map(|_| t));
render_and_print("task.edit", committed, is_json, ctx.quiet)
}
TaskCommand::Claim { task_ref } => {
let agent_id = ctx.agent.as_deref().unwrap_or("default");
let tx = conn
.transaction()
.map_err(|e| CarryCtxError::database_error(format!("{e}")).exit_code)?;
let uow = UnitOfWork::new(tx);
let result = application::task::claim_task(project_id, task_ref, agent_id, &uow);
let committed = result.and_then(|t| uow.commit().map(|_| t));
render_and_print("task.claim", committed, is_json, ctx.quiet)
}
TaskCommand::Release { task_ref } => {
let tx = conn
.transaction()
.map_err(|e| CarryCtxError::database_error(format!("{e}")).exit_code)?;
let uow = UnitOfWork::new(tx);
let result = application::task::transition_task(
project_id,
task_ref,
TransitionAction::Release,
None,
runtime.config.task.strict_completion,
ctx.agent.as_deref(),
&uow,
);
let committed = result.and_then(|(t, _w)| uow.commit().map(|_| t));
render_and_print("task.release", committed, is_json, ctx.quiet)
}
TaskCommand::Start { task_ref } => {
let tx = conn
.transaction()
.map_err(|e| CarryCtxError::database_error(format!("{e}")).exit_code)?;
let uow = UnitOfWork::new(tx);
let result = application::task::transition_task(
project_id,
task_ref,
TransitionAction::Start,
None,
runtime.config.task.strict_completion,
ctx.agent.as_deref(),
&uow,
);
let committed = result.and_then(|(t, _w)| uow.commit().map(|_| t));
render_and_print("task.start", committed, is_json, ctx.quiet)
}
TaskCommand::Block { task_ref, reason } => {
let tx = conn
.transaction()
.map_err(|e| CarryCtxError::database_error(format!("{e}")).exit_code)?;
let uow = UnitOfWork::new(tx);
let result = application::task::transition_task(
project_id,
task_ref,
TransitionAction::Block,
Some(reason),
runtime.config.task.strict_completion,
ctx.agent.as_deref(),
&uow,
);
let committed = result.and_then(|(t, _w)| uow.commit().map(|_| t));
render_and_print("task.block", committed, is_json, ctx.quiet)
}
TaskCommand::Unblock { task_ref } => {
let tx = conn
.transaction()
.map_err(|e| CarryCtxError::database_error(format!("{e}")).exit_code)?;
let uow = UnitOfWork::new(tx);
let result = application::task::transition_task(
project_id,
task_ref,
TransitionAction::Unblock,
None,
runtime.config.task.strict_completion,
ctx.agent.as_deref(),
&uow,
);
let committed = result.and_then(|(t, _w)| uow.commit().map(|_| t));
render_and_print("task.unblock", committed, is_json, ctx.quiet)
}
TaskCommand::Review { task_ref } => {
let tx = conn
.transaction()
.map_err(|e| CarryCtxError::database_error(format!("{e}")).exit_code)?;
let uow = UnitOfWork::new(tx);
let result = application::task::transition_task(
project_id,
task_ref,
TransitionAction::Review,
None,
runtime.config.task.strict_completion,
ctx.agent.as_deref(),
&uow,
);
let committed = result.and_then(|(t, _w)| uow.commit().map(|_| t));
render_and_print("task.review", committed, is_json, ctx.quiet)
}
TaskCommand::Complete { task_ref } => {
let tx = conn
.transaction()
.map_err(|e| CarryCtxError::database_error(format!("{e}")).exit_code)?;
let uow = UnitOfWork::new(tx);
let result = application::task::transition_task(
project_id,
task_ref,
TransitionAction::Complete,
None,
runtime.config.task.strict_completion,
ctx.agent.as_deref(),
&uow,
);
let committed = result.and_then(|(t, _w)| uow.commit().map(|_| t));
render_and_print("task.complete", committed, is_json, ctx.quiet)
}
TaskCommand::Cancel { task_ref, reason } => {
let tx = conn
.transaction()
.map_err(|e| CarryCtxError::database_error(format!("{e}")).exit_code)?;
let uow = UnitOfWork::new(tx);
let result = application::task::transition_task(
project_id,
task_ref,
TransitionAction::Cancel,
Some(reason),
runtime.config.task.strict_completion,
ctx.agent.as_deref(),
&uow,
);
let committed = result.and_then(|(t, _w)| uow.commit().map(|_| t));
render_and_print("task.cancel", committed, is_json, ctx.quiet)
}
TaskCommand::Reopen { task_ref } => {
let tx = conn
.transaction()
.map_err(|e| CarryCtxError::database_error(format!("{e}")).exit_code)?;
let uow = UnitOfWork::new(tx);
let result = application::task::transition_task(
project_id,
task_ref,
TransitionAction::Reopen,
None,
runtime.config.task.strict_completion,
ctx.agent.as_deref(),
&uow,
);
let committed = result.and_then(|(t, _w)| uow.commit().map(|_| t));
render_and_print("task.reopen", committed, is_json, ctx.quiet)
}
TaskCommand::Depend { task_ref, on, kind } => {
let dep_kind = kind
.as_deref()
.map(parse_dependency_kind)
.transpose()
.map_err(|e| e.exit_code)?
.unwrap_or(DependencyKind::Strong);
let tx = conn
.transaction()
.map_err(|e| CarryCtxError::database_error(format!("{e}")).exit_code)?;
let uow = UnitOfWork::new(tx);
let result = application::task::add_dependency(
project_id,
task_ref,
on,
dep_kind,
ctx.agent.as_deref(),
&uow,
);
let committed = result.and_then(|t| uow.commit().map(|_| t));
render_and_print("task.depend", committed, is_json, ctx.quiet)
}
TaskCommand::Undepend { task_ref, on } => {
let tx = conn
.transaction()
.map_err(|e| CarryCtxError::database_error(format!("{e}")).exit_code)?;
let uow = UnitOfWork::new(tx);
let result = application::task::remove_dependency(
project_id,
task_ref,
on,
ctx.agent.as_deref(),
&uow,
);
let committed = result.and_then(|t| uow.commit().map(|_| t));
render_and_print("task.undepend", committed, is_json, ctx.quiet)
}
}
}