huskies: merge 1034

This commit is contained in:
dave
2026-05-14 13:57:27 +00:00
parent 8625b9a7fc
commit 8faf19f3ab
11 changed files with 362 additions and 246 deletions
+21 -19
View File
@@ -18,22 +18,14 @@ pub(super) fn handle_depends(ctx: &CommandContext) -> Option<String> {
let args = ctx.args.trim();
if args.is_empty() {
return Some(format!(
"Usage: `{} depends <number> [dep1 dep2 ...]`\n\nExamples:\n\
• `{0} depends 484 477 478` — set depends_on: [477, 478]\n\
• `{0} depends 484` — clear all dependencies",
ctx.services.bot_name
));
return None;
}
let mut parts = args.split_whitespace();
let num_str = parts.next().unwrap_or("");
if !num_str.chars().all(|c| c.is_ascii_digit()) || num_str.is_empty() {
return Some(format!(
"Invalid story number: `{num_str}`. Usage: `{} depends <number> [dep1 dep2 ...]`",
ctx.services.bot_name
));
if num_str.is_empty() || !num_str.chars().all(|c| c.is_ascii_digit()) {
return None;
}
// Parse dependency numbers.
@@ -129,22 +121,32 @@ mod tests {
}
#[test]
fn depends_no_args_returns_usage() {
fn depends_no_args_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let output = depends_cmd_with_root(tmp.path(), "").unwrap();
let result = depends_cmd_with_root(tmp.path(), "");
assert!(
output.contains("Usage"),
"no args should show usage: {output}"
result.is_none(),
"no args should route to LLM (None): {result:?}"
);
}
#[test]
fn depends_non_numeric_number_returns_error() {
fn depends_non_numeric_number_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let output = depends_cmd_with_root(tmp.path(), "abc 1 2").unwrap();
let result = depends_cmd_with_root(tmp.path(), "on 477 and 478");
assert!(
output.contains("Invalid story number"),
"non-numeric story number should error: {output}"
result.is_none(),
"natural-language args should route to LLM (None): {result:?}"
);
}
#[test]
fn depends_well_formed_runs_handler() {
let tmp = tempfile::TempDir::new().unwrap();
let result = depends_cmd_with_root(tmp.path(), "999");
assert!(
result.is_some(),
"well-formed numeric story number should run the command handler: {result:?}"
);
}
+21 -19
View File
@@ -13,17 +13,8 @@ use std::process::Command;
/// Usage: `diff <number>`
pub(super) fn handle_diff(ctx: &CommandContext) -> Option<String> {
let num_str = ctx.args.trim();
if num_str.is_empty() {
return Some(format!(
"Usage: `{} diff <number>`\n\nShows the git diff from the main branch to the story's worktree HEAD.",
ctx.services.bot_name
));
}
if !num_str.chars().all(|c| c.is_ascii_digit()) {
return Some(format!(
"Invalid story number: `{num_str}`. Usage: `{} diff <number>`",
ctx.services.bot_name
));
if num_str.is_empty() || !num_str.chars().all(|c| c.is_ascii_digit()) {
return None;
}
let story_id = match find_story_id(num_str) {
@@ -169,22 +160,33 @@ mod tests {
}
#[test]
fn diff_command_no_args_returns_usage() {
fn diff_command_no_args_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let output = diff_cmd(tmp.path(), "").unwrap();
let result = diff_cmd(tmp.path(), "");
assert!(
output.contains("Usage"),
"no args should show usage: {output}"
result.is_none(),
"no args should route to LLM (None): {result:?}"
);
}
#[test]
fn diff_command_non_numeric_returns_error() {
fn diff_command_non_numeric_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let output = diff_cmd(tmp.path(), "abc").unwrap();
let result = diff_cmd(tmp.path(), "the login feature branch");
assert!(
output.contains("Invalid"),
"non-numeric arg should return error: {output}"
result.is_none(),
"natural-language args should route to LLM (None): {result:?}"
);
}
#[test]
fn diff_command_well_formed_runs_handler() {
crate::db::ensure_content_store();
let tmp = tempfile::TempDir::new().unwrap();
let result = diff_cmd(tmp.path(), "99994");
assert!(
result.is_some(),
"well-formed numeric arg should run the command handler: {result:?}"
);
}
+50 -16
View File
@@ -13,10 +13,7 @@ use std::path::Path;
pub(super) fn handle_freeze(ctx: &CommandContext) -> Option<String> {
let num_str = ctx.args.trim();
if num_str.is_empty() || !num_str.chars().all(|c| c.is_ascii_digit()) {
return Some(format!(
"Usage: `{} freeze <number>` (e.g. `freeze 42`)",
ctx.services.bot_name
));
return None;
}
Some(freeze_by_number(ctx.effective_root(), num_str))
}
@@ -57,10 +54,7 @@ fn freeze_by_story_id(story_id: &str) -> String {
pub(super) fn handle_unfreeze(ctx: &CommandContext) -> Option<String> {
let num_str = ctx.args.trim();
if num_str.is_empty() || !num_str.chars().all(|c| c.is_ascii_digit()) {
return Some(format!(
"Usage: `{} unfreeze <number>` (e.g. `unfreeze 42`)",
ctx.services.bot_name
));
return None;
}
Some(unfreeze_by_number(ctx.effective_root(), num_str))
}
@@ -155,22 +149,62 @@ mod tests {
}
#[test]
fn freeze_command_no_args_returns_usage() {
fn freeze_command_no_args_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let output = freeze_cmd_with_root(tmp.path(), "").unwrap();
let result = freeze_cmd_with_root(tmp.path(), "");
assert!(
output.contains("Usage"),
"no args should show usage: {output}"
result.is_none(),
"no args should route to LLM (None): {result:?}"
);
}
#[test]
fn unfreeze_command_no_args_returns_usage() {
fn freeze_command_non_numeric_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let output = unfreeze_cmd_with_root(tmp.path(), "").unwrap();
let result = freeze_cmd_with_root(tmp.path(), "the login story");
assert!(
output.contains("Usage"),
"no args should show usage: {output}"
result.is_none(),
"natural-language args should route to LLM (None): {result:?}"
);
}
#[test]
fn freeze_command_well_formed_runs_handler() {
let tmp = tempfile::TempDir::new().unwrap();
let result = freeze_cmd_with_root(tmp.path(), "999");
assert!(
result.is_some(),
"well-formed numeric arg should run the command handler: {result:?}"
);
}
#[test]
fn unfreeze_command_no_args_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let result = unfreeze_cmd_with_root(tmp.path(), "");
assert!(
result.is_none(),
"no args should route to LLM (None): {result:?}"
);
}
#[test]
fn unfreeze_command_non_numeric_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let result = unfreeze_cmd_with_root(tmp.path(), "the login story");
assert!(
result.is_none(),
"natural-language args should route to LLM (None): {result:?}"
);
}
#[test]
fn unfreeze_command_well_formed_runs_handler() {
let tmp = tempfile::TempDir::new().unwrap();
let result = unfreeze_cmd_with_root(tmp.path(), "999");
assert!(
result.is_some(),
"well-formed numeric arg should run the command handler: {result:?}"
);
}
+20 -19
View File
@@ -13,17 +13,8 @@ use std::path::{Path, PathBuf};
/// recently modified agent log file for that story.
pub(super) fn handle_logs(ctx: &CommandContext) -> Option<String> {
let num_str = ctx.args.trim();
if num_str.is_empty() {
return Some(format!(
"Usage: `{} logs <number>`\n\nShows the last agent log lines for a story.",
ctx.services.bot_name
));
}
if !num_str.chars().all(|c| c.is_ascii_digit()) {
return Some(format!(
"Invalid story number: `{num_str}`. Usage: `{} logs <number>`",
ctx.services.bot_name
));
if num_str.is_empty() || !num_str.chars().all(|c| c.is_ascii_digit()) {
return None;
}
let story_id = match find_story_id_by_number(num_str) {
@@ -155,22 +146,32 @@ mod tests {
// -- input validation ---------------------------------------------------
#[test]
fn logs_no_args_returns_usage() {
fn logs_no_args_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let output = logs_cmd(tmp.path(), "").unwrap();
let result = logs_cmd(tmp.path(), "");
assert!(
output.contains("Usage"),
"no args should show usage: {output}"
result.is_none(),
"no args should route to LLM (None): {result:?}"
);
}
#[test]
fn logs_non_numeric_returns_error() {
fn logs_non_numeric_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let output = logs_cmd(tmp.path(), "abc").unwrap();
let result = logs_cmd(tmp.path(), "for the login story");
assert!(
output.contains("Invalid"),
"non-numeric arg should return error: {output}"
result.is_none(),
"natural-language args should route to LLM (None): {result:?}"
);
}
#[test]
fn logs_well_formed_runs_handler() {
let tmp = tempfile::TempDir::new().unwrap();
let result = logs_cmd(tmp.path(), "99999");
assert!(
result.is_some(),
"well-formed numeric arg should run the command handler: {result:?}"
);
}
+78
View File
@@ -495,6 +495,84 @@ pub(crate) mod tests {
);
}
// -- malformed-args routing (story 1034) -----------------------------------
#[test]
fn malformed_unblock_args_route_to_timmy() {
// "unblock to fix the blocking issue" — verb recognised, args look like
// natural language rather than a numeric story ID. Must return None so
// the message is forwarded to the LLM instead of showing a usage error.
let result = try_cmd_addressed(
"Timmy",
"@timmy:homeserver.local",
"@timmy unblock to fix the blocking issue",
);
assert!(
result.is_none(),
"unblock with natural-language args must route to LLM (None): {result:?}"
);
}
#[test]
fn malformed_start_args_route_to_timmy_via_registry() {
// "start to get the cheese grater working" — the registry entry for
// start always returns None (handled async), so even bad args produce None.
let result = try_cmd_addressed(
"Timmy",
"@timmy:homeserver.local",
"@timmy start to get the cheese grater working",
);
assert!(
result.is_none(),
"start with natural-language args must route to LLM (None): {result:?}"
);
}
#[test]
fn malformed_show_args_route_to_timmy() {
let result = try_cmd_addressed(
"Timmy",
"@timmy:homeserver.local",
"@timmy show me the login bug",
);
assert!(
result.is_none(),
"show with natural-language args must route to LLM (None): {result:?}"
);
}
#[test]
fn malformed_freeze_args_route_to_timmy() {
let result = try_cmd_addressed(
"Timmy",
"@timmy:homeserver.local",
"@timmy freeze the pipeline until Friday",
);
assert!(
result.is_none(),
"freeze with natural-language args must route to LLM (None): {result:?}"
);
}
#[test]
fn well_formed_unblock_runs_handler() {
// Numeric story ID → command handler runs (story not found, but Some is returned).
let result = try_cmd_addressed("Timmy", "@timmy:homeserver.local", "@timmy unblock 1010");
assert!(
result.is_some(),
"well-formed unblock command should run the handler: {result:?}"
);
}
#[test]
fn well_formed_show_runs_handler() {
let result = try_cmd_addressed("Timmy", "@timmy:homeserver.local", "@timmy show 984");
assert!(
result.is_some(),
"well-formed show command should run the handler: {result:?}"
);
}
// -- commands registry --------------------------------------------------
#[test]
+25 -13
View File
@@ -22,19 +22,21 @@ pub(super) fn handle_move(ctx: &CommandContext) -> Option<String> {
let (num_str, stage_raw) = match args.split_once(char::is_whitespace) {
Some((n, s)) => (n.trim(), s.trim()),
None => {
// No stage argument: if args looks like a number show usage, otherwise
// fall through to the LLM (looks like natural language).
if !args.is_empty() && args.chars().all(|c| c.is_ascii_digit()) {
return Some(format!(
"Usage: `{} move <number> <stage>`\n\nValid stages: {}",
ctx.services.bot_name,
VALID_STAGES.join(", ")
));
}
return None;
}
};
if num_str.is_empty() || !num_str.chars().all(|c| c.is_ascii_digit()) {
return Some(format!(
"Invalid story number: `{num_str}`. Usage: `{} move <number> <stage>`",
ctx.services.bot_name
));
return None;
}
let target_stage = stage_raw.to_ascii_lowercase();
@@ -113,12 +115,22 @@ mod tests {
}
#[test]
fn move_command_no_args_returns_usage() {
fn move_command_no_args_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let output = move_cmd_with_root(tmp.path(), "").unwrap();
let result = move_cmd_with_root(tmp.path(), "");
assert!(
output.contains("Usage"),
"no args should show usage hint: {output}"
result.is_none(),
"no args should route to LLM (None): {result:?}"
);
}
#[test]
fn move_command_natural_language_args_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let result = move_cmd_with_root(tmp.path(), "the auth story to done");
assert!(
result.is_none(),
"natural-language args should route to LLM (None): {result:?}"
);
}
@@ -128,7 +140,7 @@ mod tests {
let output = move_cmd_with_root(tmp.path(), "42").unwrap();
assert!(
output.contains("Usage"),
"missing stage should show usage hint: {output}"
"numeric number without stage should show usage hint: {output}"
);
}
@@ -143,12 +155,12 @@ mod tests {
}
#[test]
fn move_command_non_numeric_number_returns_error() {
fn move_command_non_numeric_number_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let output = move_cmd_with_root(tmp.path(), "abc current").unwrap();
let result = move_cmd_with_root(tmp.path(), "abc current");
assert!(
output.contains("Invalid story number"),
"non-numeric number should return error: {output}"
result.is_none(),
"non-numeric story number should route to LLM (None): {result:?}"
);
}
+22 -19
View File
@@ -11,17 +11,8 @@ use super::CommandContext;
#[allow(clippy::string_slice)] // commit_hash is hex (ASCII), min(8) always within bounds
pub(super) fn handle_overview(ctx: &CommandContext) -> Option<String> {
let num_str = ctx.args.trim();
if num_str.is_empty() {
return Some(format!(
"Usage: `{} overview <number>`\n\nShows the implementation summary for a story.",
ctx.services.bot_name
));
}
if !num_str.chars().all(|c| c.is_ascii_digit()) {
return Some(format!(
"Invalid story number: `{num_str}`. Usage: `{} overview <number>`",
ctx.services.bot_name
));
if num_str.is_empty() || !num_str.chars().all(|c| c.is_ascii_digit()) {
return None;
}
let commit_hash = match find_story_merge_commit(ctx.effective_root(), num_str) {
@@ -232,22 +223,34 @@ mod tests {
}
#[test]
fn overview_command_no_args_returns_usage() {
fn overview_command_no_args_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let output = overview_cmd_with_root(tmp.path(), "").unwrap();
let result = overview_cmd_with_root(tmp.path(), "");
assert!(
output.contains("Usage"),
"no args should show usage hint: {output}"
result.is_none(),
"no args should route to LLM (None): {result:?}"
);
}
#[test]
fn overview_command_non_numeric_arg_returns_error() {
fn overview_command_non_numeric_arg_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let output = overview_cmd_with_root(tmp.path(), "abc").unwrap();
let result = overview_cmd_with_root(tmp.path(), "of the auth refactor");
assert!(
output.contains("Invalid"),
"non-numeric arg should return error: {output}"
result.is_none(),
"natural-language args should route to LLM (None): {result:?}"
);
}
#[test]
fn overview_command_well_formed_runs_handler() {
let repo_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap_or(std::path::Path::new("."));
let result = overview_cmd_with_root(repo_root, "99999");
assert!(
result.is_some(),
"well-formed numeric arg should run the command handler: {result:?}"
);
}
+20 -19
View File
@@ -69,17 +69,8 @@ fn strip_front_matter(text: &str) -> (String, String) {
/// Returns a friendly message when no match is found.
pub(super) fn handle_show(ctx: &CommandContext) -> Option<String> {
let num_str = ctx.args.trim();
if num_str.is_empty() {
return Some(format!(
"Usage: `{} show <number>`\n\nDisplays the full text of a story, bug, or spike.",
ctx.services.bot_name
));
}
if !num_str.chars().all(|c| c.is_ascii_digit()) {
return Some(format!(
"Invalid story number: `{num_str}`. Usage: `{} show <number>`",
ctx.services.bot_name
));
if num_str.is_empty() || !num_str.chars().all(|c| c.is_ascii_digit()) {
return None;
}
// Find the story by numeric prefix: CRDT → content store.
@@ -169,22 +160,32 @@ mod tests {
}
#[test]
fn show_command_no_args_returns_usage() {
fn show_command_no_args_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let output = show_cmd_with_root(tmp.path(), "").unwrap();
let result = show_cmd_with_root(tmp.path(), "");
assert!(
output.contains("Usage"),
"no args should show usage hint: {output}"
result.is_none(),
"no args should route to LLM (None), not return a usage error: {result:?}"
);
}
#[test]
fn show_command_non_numeric_args_returns_error() {
fn show_command_non_numeric_args_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let output = show_cmd_with_root(tmp.path(), "abc").unwrap();
let result = show_cmd_with_root(tmp.path(), "me the story about the login bug");
assert!(
output.contains("Invalid"),
"non-numeric arg should return error message: {output}"
result.is_none(),
"natural-language args should route to LLM (None): {result:?}"
);
}
#[test]
fn show_command_well_formed_runs_handler() {
let tmp = tempfile::TempDir::new().unwrap();
let result = show_cmd_with_root(tmp.path(), "999");
assert!(
result.is_some(),
"well-formed numeric arg should run the command handler: {result:?}"
);
}
+17 -15
View File
@@ -18,17 +18,8 @@ use std::process::Command;
/// Handle `{bot_name} status {number}`.
pub(super) fn handle_triage(ctx: &CommandContext) -> Option<String> {
let num_str = ctx.args.trim();
if num_str.is_empty() {
return Some(format!(
"Usage: `{} status <number>`\n\nShows pipeline info for a story: stage, ACs, git diff, recent commits.",
ctx.services.bot_name
));
}
if !num_str.chars().all(|c| c.is_ascii_digit()) {
return Some(format!(
"Invalid story number: `{num_str}`. Usage: `{} status <number>`",
ctx.services.bot_name
));
if num_str.is_empty() || !num_str.chars().all(|c| c.is_ascii_digit()) {
return None;
}
match find_story_by_number(num_str) {
@@ -276,12 +267,23 @@ mod tests {
}
#[test]
fn whatsup_non_numeric_returns_error() {
fn whatsup_non_numeric_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let output = status_triage_cmd(tmp.path(), "abc").unwrap();
let result = status_triage_cmd(tmp.path(), "what is going on");
assert!(
output.contains("Invalid"),
"non-numeric arg should return error: {output}"
result.is_none(),
"natural-language args should route to LLM (None): {result:?}"
);
}
#[test]
fn whatsup_well_formed_runs_handler() {
crate::db::ensure_content_store();
let tmp = tempfile::TempDir::new().unwrap();
let result = status_triage_cmd(tmp.path(), "99996");
assert!(
result.is_some(),
"well-formed numeric arg should run the command handler: {result:?}"
);
}
+19 -12
View File
@@ -16,10 +16,7 @@ pub(super) fn handle_unblock(ctx: &CommandContext) -> Option<String> {
let num_str = ctx.args.trim();
if num_str.is_empty() || !num_str.chars().all(|c| c.is_ascii_digit()) {
return Some(format!(
"Usage: `{} unblock <number>` (e.g. `unblock 42`)",
ctx.services.bot_name
));
return None;
}
Some(unblock_by_number(ctx.effective_root(), num_str))
@@ -152,22 +149,32 @@ mod tests {
}
#[test]
fn unblock_command_no_args_returns_usage() {
fn unblock_command_no_args_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let output = unblock_cmd_with_root(tmp.path(), "").unwrap();
let result = unblock_cmd_with_root(tmp.path(), "");
assert!(
output.contains("Usage"),
"no args should show usage hint: {output}"
result.is_none(),
"no args should route to LLM (None), not return a usage error: {result:?}"
);
}
#[test]
fn unblock_command_non_numeric_returns_usage() {
fn unblock_command_non_numeric_routes_to_timmy() {
let tmp = tempfile::TempDir::new().unwrap();
let output = unblock_cmd_with_root(tmp.path(), "abc").unwrap();
let result = unblock_cmd_with_root(tmp.path(), "to fix the blocking issue");
assert!(
output.contains("Usage"),
"non-numeric arg should show usage hint: {output}"
result.is_none(),
"natural-language args should route to LLM (None): {result:?}"
);
}
#[test]
fn unblock_command_well_formed_runs_handler() {
let tmp = tempfile::TempDir::new().unwrap();
let result = unblock_cmd_with_root(tmp.path(), "666");
assert!(
result.is_some(),
"well-formed numeric arg should run the command handler: {result:?}"
);
}
@@ -272,35 +272,26 @@ pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
// Check for the assign command, which requires async agent ops (stop +
// start) and cannot be handled by the sync command registry.
if let Some(assign_cmd) = super::super::super::assign::extract_assign_command(
// Only handle the well-formed variant; BadArgs falls through to the LLM.
if let Some(super::super::super::assign::AssignCommand::Assign {
story_number,
model,
}) = super::super::super::assign::extract_assign_command(
&user_message,
&ctx.services.bot_name,
ctx.matrix_user_id.as_str(),
) {
let response = match assign_cmd {
super::super::super::assign::AssignCommand::Assign {
story_number,
model,
} => {
slog!(
"[matrix-bot] Handling assign command from {sender}: story {story_number} model={model}"
);
super::super::super::assign::handle_assign(
let response = super::super::super::assign::handle_assign(
&ctx.services.bot_name,
&story_number,
&model,
&effective_root,
&ctx.services.agents,
)
.await
}
super::super::super::assign::AssignCommand::BadArgs => {
format!(
"Usage: `{} assign <number> <model>` (e.g. `assign 42 opus`)",
ctx.services.bot_name
)
}
};
.await;
let html = markdown_to_html(&response);
if let Ok(msg_id) = ctx
.transport
@@ -346,26 +337,22 @@ pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
// Check for the delete command, which requires async agent/worktree ops
// and cannot be handled by the sync command registry.
if let Some(del_cmd) = super::super::super::delete::extract_delete_command(
// Only handle the well-formed variant; BadArgs falls through to the LLM.
if let Some(super::super::super::delete::DeleteCommand::Delete { story_number }) =
super::super::super::delete::extract_delete_command(
&user_message,
&ctx.services.bot_name,
ctx.matrix_user_id.as_str(),
) {
let response = match del_cmd {
super::super::super::delete::DeleteCommand::Delete { story_number } => {
)
{
slog!("[matrix-bot] Handling delete command from {sender}: story {story_number}");
super::super::super::delete::handle_delete(
let response = super::super::super::delete::handle_delete(
&ctx.services.bot_name,
&story_number,
&effective_root,
&ctx.services.agents,
)
.await
}
super::super::super::delete::DeleteCommand::BadArgs => {
format!("Usage: `{} delete <number>`", ctx.services.bot_name)
}
};
.await;
let html = markdown_to_html(&response);
if let Ok(msg_id) = ctx
.transport
@@ -380,26 +367,22 @@ pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
// Check for the rmtree command, which requires async agent/worktree ops
// and cannot be handled by the sync command registry.
if let Some(rmtree_cmd) = super::super::super::rmtree::extract_rmtree_command(
// Only handle the well-formed variant; BadArgs falls through to the LLM.
if let Some(super::super::super::rmtree::RmtreeCommand::Rmtree { story_number }) =
super::super::super::rmtree::extract_rmtree_command(
&user_message,
&ctx.services.bot_name,
ctx.matrix_user_id.as_str(),
) {
let response = match rmtree_cmd {
super::super::super::rmtree::RmtreeCommand::Rmtree { story_number } => {
)
{
slog!("[matrix-bot] Handling rmtree command from {sender}: story {story_number}");
super::super::super::rmtree::handle_rmtree(
let response = super::super::super::rmtree::handle_rmtree(
&ctx.services.bot_name,
&story_number,
&effective_root,
&ctx.services.agents,
)
.await
}
super::super::super::rmtree::RmtreeCommand::BadArgs => {
format!("Usage: `{} rmtree <number>`", ctx.services.bot_name)
}
};
.await;
let html = markdown_to_html(&response);
if let Ok(msg_id) = ctx
.transport
@@ -414,35 +397,26 @@ pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
// Check for the start command, which requires async agent ops and cannot
// be handled by the sync command registry.
if let Some(start_cmd) = super::super::super::start::extract_start_command(
// Only handle the well-formed variant; BadArgs falls through to the LLM.
if let Some(super::super::super::start::StartCommand::Start {
story_number,
agent_hint,
}) = super::super::super::start::extract_start_command(
&user_message,
&ctx.services.bot_name,
ctx.matrix_user_id.as_str(),
) {
let response = match start_cmd {
super::super::super::start::StartCommand::Start {
story_number,
agent_hint,
} => {
slog!(
"[matrix-bot] Handling start command from {sender}: story {story_number} agent={agent_hint:?}"
);
super::super::super::start::handle_start(
let response = super::super::super::start::handle_start(
&ctx.services.bot_name,
&story_number,
agent_hint.as_deref(),
&effective_root,
&ctx.services.agents,
)
.await
}
super::super::super::start::StartCommand::BadArgs => {
format!(
"Usage: `{} start <number>` or `{} start <number> opus`",
ctx.services.bot_name, ctx.services.bot_name
)
}
};
.await;
let html = markdown_to_html(&response);
if let Ok(msg_id) = ctx
.transport