use crate::*;
use carryctx::application::runtime::InvocationContext;
use carryctx::domain::collaboration::Decision;
use carryctx::error::{CarryCtxError, ExitCode};
use clap::Parser;
#[derive(Parser, Debug)]
pub enum DecisionCommand {
Add {
#[arg(long)]
title: String,
#[arg(long)]
context: Option<String>,
#[arg(long)]
decision: Option<String>,
#[arg(long)]
consequences: Option<String>,
#[arg(long)]
task: Option<String>,
},
List,
Show { decision_ref: String },
Search { query: String },
Supersede {
decision_ref: String,
#[arg(long)]
by: String,
},
}
#[derive(Parser, Debug)]
pub struct DecisionArgs {
#[command(subcommand)]
pub command: DecisionCommand,
}
pub fn handle_decision(
args: &DecisionArgs,
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();
let decision_repo = SqliteDecisionRepository::new(conn);
let event_repo = SqliteEventRepository::new(conn);
let now = chrono::Utc::now().to_rfc3339();
match &args.command {
DecisionCommand::Add {
title,
context,
decision,
consequences,
task: _,
} => {
let decision_id = ulid::Ulid::generate().to_string();
let display_id = format!("DEC-{}", &decision_id[..8]);
let record = Decision {
id: decision_id,
display_id,
project_id: project_id.to_string(),
title: title.clone(),
context: context.clone(),
decision: decision.clone(),
consequences: consequences.clone(),
related_tasks: vec![],
related_paths: vec![],
created_by_agent: ctx.agent.clone().unwrap_or_else(|| "unknown".to_string()),
created_by_session: ctx.session.clone(),
superseded_by: None,
created_at: now.clone(),
updated_at: now,
};
let result = decision_repo.create(&record);
if let Ok(ref _d) = result {
let _ = event_repo.append(&NewEvent {
id: ulid::Ulid::generate().to_string(),
project_id: project_id.to_string(),
event_type: "decision.created".into(),
actor_agent_id: ctx.agent.clone(),
session_id: ctx.session.clone(),
task_id: None,
payload: serde_json::json!({ "decisionId": record.id }),
occurred_at: chrono::Utc::now().to_rfc3339(),
});
}
render_and_print("decision.add", result, is_json, ctx.quiet)
}
DecisionCommand::List => {
let result = decision_repo.list(project_id);
render_and_print("decision.list", result, is_json, ctx.quiet)
}
DecisionCommand::Show { decision_ref } => {
let result = decision_repo.find_by_id(project_id, decision_ref);
let result = result.and_then(|opt| {
opt.ok_or_else(|| {
CarryCtxError::resource_not_found(format!(
"Decision '{decision_ref}' not found"
))
})
});
render_and_print("decision.show", result, is_json, ctx.quiet)
}
DecisionCommand::Search { query } => {
let result = decision_repo.search(project_id, query);
render_and_print("decision.search", result, is_json, ctx.quiet)
}
DecisionCommand::Supersede { decision_ref, by } => {
let result = decision_repo.supersede(decision_ref, project_id, by, &now);
if result.is_ok() {
let _ = event_repo.append(&NewEvent {
id: ulid::Ulid::generate().to_string(),
project_id: project_id.to_string(),
event_type: "decision.superseded".into(),
actor_agent_id: ctx.agent.clone(),
session_id: ctx.session.clone(),
task_id: None,
payload: serde_json::json!({
"decisionId": decision_ref,
"supersededBy": by
}),
occurred_at: chrono::Utc::now().to_rfc3339(),
});
}
render_and_print("decision.supersede", result, is_json, ctx.quiet)
}
}
}