#[allow(clippy::wildcard_imports)]
use super::*;
pub(super) fn create_initiative(_config: &Config, store: &Store, args: &Value) -> ToolOutcome {
let name = req_string(args, "name")?;
let outcome = trimmed_optional(opt_string(args, "outcome")?);
let owner = trimmed_optional(opt_string(args, "owner")?);
let status = trimmed_optional(opt_string(args, "status")?);
let priority = initiative_priority(opt_string(args, "priority")?);
let labels = opt_string(args, "labels")?
.map(|text| crate::commands::parse_csv_list(&text))
.unwrap_or_default();
let target_date = trimmed_optional(opt_string(args, "target_date")?);
let projects = opt_string(args, "projects")?
.map(|text| crate::commands::parse_csv_list(&text))
.unwrap_or_default();
let epics = opt_string(args, "epics")?
.map(|text| crate::commands::parse_csv_list(&text))
.unwrap_or_default();
let milestones = opt_string(args, "milestones")?
.map(|text| crate::commands::parse_csv_list(&text))
.unwrap_or_default();
let (mut config, path) = match registry_config(store) {
Ok(loaded) => loaded,
Err(e) => return Ok(error_result(crate::error::render_error(&e))),
};
let new = NewInitiative {
name,
outcome,
owner,
status,
priority,
labels,
target_date,
projects,
epics,
milestones,
extensions: crate::roadmap::registry::InitiativeExtensions::default(),
};
match crate::roadmap::registry::create_initiative(&mut config, &path, &new) {
Ok(id) => {
let record = &config.initiatives[id.as_str()];
Ok(success_result(
format!("Created {} ({id})", new.name),
initiative_record_value(id.as_str(), record),
))
}
Err(e) => Ok(error_result(crate::error::render_error(&e))),
}
}
pub(super) fn delete_initiative(_config: &Config, store: &Store, args: &Value) -> ToolOutcome {
let reference = req_string(args, "initiative")?;
if opt_bool(args, "confirm")? != Some(true) {
return Ok(error_result(
"invalid input: `confirm` must be true to delete an initiative — only the registry \
entry is removed and no project, epic, milestone or issue is deleted"
.to_string(),
));
}
let (mut config, path) = match registry_config(store) {
Ok(loaded) => loaded,
Err(e) => return Ok(error_result(crate::error::render_error(&e))),
};
let id = match crate::roadmap::registry::resolve_initiative_id(&config, &reference) {
Ok(id) => id,
Err(e) => return Ok(error_result(crate::error::render_error(&e))),
};
let Some(record) = crate::roadmap::registry::get(&config, &id).cloned() else {
return Ok(error_result(format!(
"invalid input: unknown initiative `{reference}`"
)));
};
match crate::roadmap::registry::delete_initiative(&mut config, &path, &id) {
Ok(()) => Ok(success_result(
format!(
"Deleted initiative {} ({id})\nRemoved references (nothing cascades): {}",
record.name,
cmd_roadmap::reference_inventory(&record),
),
json!({
"id": id.as_str(),
"name": record.name,
"references": {
"projects": record.projects,
"epics": record.epics,
"milestones": record.milestones,
},
}),
)),
Err(e) => Ok(error_result(crate::error::render_error(&e))),
}
}
pub(super) fn get_initiative(_config: &Config, store: &Store, args: &Value) -> ToolOutcome {
let reference = req_string(args, "initiative")?;
let (config, _path) = match registry_config(store) {
Ok(loaded) => loaded,
Err(e) => return Ok(error_result(crate::error::render_error(&e))),
};
let id = match crate::roadmap::registry::resolve_initiative_id(&config, &reference) {
Ok(id) => id,
Err(e) => return Ok(error_result(crate::error::render_error(&e))),
};
let Some(record) = crate::roadmap::registry::get(&config, &id) else {
return Ok(error_result(format!(
"invalid input: unknown initiative `{reference}`"
)));
};
let text = cmd_roadmap::initiative_record_lines(&id, record).join("\n");
let row = match roadmap_row_by_id(&config, store, id.as_str()) {
Ok(Some(row)) => row,
Ok(None) => {
return Ok(error_result(format!(
"internal error: the roadmap projection returned no row for `{id}`"
)));
}
Err(e) => return Ok(error_result(crate::error::render_error(&e))),
};
let mut structured = initiative_record_value(id.as_str(), record);
structured["references"] = json!(
row.references
.iter()
.map(row_reference_value)
.collect::<Vec<Value>>()
);
structured["summary"] = initiative_summary_value(&row.summary);
structured["diagnostics"] = json!(
row.diagnostics
.iter()
.map(roadmap_diagnostic_value)
.collect::<Vec<Value>>()
);
Ok(success_result(text, structured))
}
pub(super) fn initiative_associate(_config: &Config, store: &Store, args: &Value) -> ToolOutcome {
initiative_association(store, args, true)
}
pub(super) fn initiative_dereference(_config: &Config, store: &Store, args: &Value) -> ToolOutcome {
initiative_association(store, args, false)
}
pub(super) fn list_initiatives(_config: &Config, store: &Store, _args: &Value) -> ToolOutcome {
let (config, _path) = match registry_config(store) {
Ok(loaded) => loaded,
Err(e) => return Ok(error_result(crate::error::render_error(&e))),
};
let lines = cmd_roadmap::initiative_listing_lines(&config);
let initiatives: Vec<Value> = crate::roadmap::registry::ids(&config)
.map(|id| initiative_record_value(id, &config.initiatives[id]))
.collect();
Ok(success_result(
lines.join("\n"),
json!({ "initiatives": initiatives }),
))
}
pub(super) fn update_initiative(_config: &Config, store: &Store, args: &Value) -> ToolOutcome {
let reference = req_string(args, "initiative")?;
let name = opt_string(args, "name")?;
let outcome = opt_string(args, "outcome")?;
let owner = opt_string(args, "owner")?;
let status = opt_string(args, "status")?;
let priority = opt_string(args, "priority")?;
let labels = opt_string(args, "labels")?;
let target_date = opt_string(args, "target_date")?;
let projects = opt_string(args, "projects")?;
let epics = opt_string(args, "epics")?;
let milestones = opt_string(args, "milestones")?;
if name.is_none()
&& outcome.is_none()
&& owner.is_none()
&& status.is_none()
&& priority.is_none()
&& labels.is_none()
&& target_date.is_none()
&& projects.is_none()
&& epics.is_none()
&& milestones.is_none()
{
return Ok(error_result(crate::error::render_error(
&cmd_roadmap::initiative_edit_requires_a_field(),
)));
}
let (mut config, path) = match registry_config(store) {
Ok(loaded) => loaded,
Err(e) => return Ok(error_result(crate::error::render_error(&e))),
};
let id = match crate::roadmap::registry::resolve_initiative_id(&config, &reference) {
Ok(id) => id,
Err(e) => return Ok(error_result(crate::error::render_error(&e))),
};
let edit = InitiativeEdit {
name: name.map_or(
crate::projects::Patch::Unchanged,
crate::projects::Patch::Set,
),
outcome: initiative_text_patch(outcome),
owner: initiative_text_patch(owner),
status: initiative_text_patch(status),
priority: match priority {
None => crate::projects::Patch::Unchanged,
Some(text) if text.trim().is_empty() => crate::projects::Patch::Clear,
Some(text) => crate::projects::Patch::Set(loose_priority(text.trim())),
},
labels: initiative_reference_patch(labels),
target_date: initiative_text_patch(target_date),
projects: initiative_reference_patch(projects),
epics: initiative_reference_patch(epics),
milestones: initiative_reference_patch(milestones),
};
match crate::roadmap::registry::edit_initiative(&mut config, &path, &id, &edit) {
Ok(id) => {
let record = &config.initiatives[id.as_str()];
Ok(success_result(
format!("Updated initiative {id}"),
initiative_record_value(id.as_str(), record),
))
}
Err(e) => Ok(error_result(crate::error::render_error(&e))),
}
}
pub(super) fn dispatch(
config: &Config,
store: &Store,
name: &str,
arguments: &Value,
) -> Option<ToolOutcome> {
match name {
"list_initiatives" => Some(list_initiatives(config, store, arguments)),
"get_initiative" => Some(get_initiative(config, store, arguments)),
"create_initiative" => Some(create_initiative(config, store, arguments)),
"update_initiative" => Some(update_initiative(config, store, arguments)),
"initiative_associate" => Some(initiative_associate(config, store, arguments)),
"initiative_dereference" => Some(initiative_dereference(config, store, arguments)),
"delete_initiative" => Some(delete_initiative(config, store, arguments)),
_ => None,
}
}