use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TaskCategory {
Coding,
Debugging,
FeatureDev,
Refactoring,
Testing,
Exploration,
Planning,
Delegation,
GitOps,
BuildDeploy,
Brainstorming,
Conversation,
General,
}
impl fmt::Display for TaskCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl TaskCategory {
pub fn as_str(&self) -> &'static str {
match self {
Self::Coding => "coding",
Self::Debugging => "debugging",
Self::FeatureDev => "feature_dev",
Self::Refactoring => "refactoring",
Self::Testing => "testing",
Self::Exploration => "exploration",
Self::Planning => "planning",
Self::Delegation => "delegation",
Self::GitOps => "git_ops",
Self::BuildDeploy => "build_deploy",
Self::Brainstorming => "brainstorming",
Self::Conversation => "conversation",
Self::General => "general",
}
}
pub fn label(&self) -> &'static str {
match self {
Self::Coding => "Coding",
Self::Debugging => "Debugging",
Self::FeatureDev => "Feature Dev",
Self::Refactoring => "Refactoring",
Self::Testing => "Testing",
Self::Exploration => "Exploration",
Self::Planning => "Planning",
Self::Delegation => "Delegation",
Self::GitOps => "Git Ops",
Self::BuildDeploy => "Build/Deploy",
Self::Brainstorming => "Brainstorming",
Self::Conversation => "Conversation",
Self::General => "General",
}
}
}
pub fn classify(tool_names: &[&str], bash_commands: &[&str]) -> TaskCategory {
if tool_names.is_empty() {
return TaskCategory::Conversation;
}
if tool_names.contains(&"Agent") {
return TaskCategory::Delegation;
}
if tool_names.contains(&"EnterPlanMode") || tool_names.contains(&"TaskCreate") {
return TaskCategory::Planning;
}
let has_edit = tool_names.contains(&"Edit") || tool_names.contains(&"Write");
let has_read_only = tool_names.contains(&"Read")
|| tool_names.contains(&"Grep")
|| tool_names.contains(&"Glob")
|| tool_names.contains(&"WebSearch");
let any_bash = |patterns: &[&str]| -> bool {
bash_commands.iter().any(|cmd| {
let lower = cmd.to_ascii_lowercase();
patterns.iter().any(|p| lower.contains(p))
})
};
if any_bash(&["git "]) {
return TaskCategory::GitOps;
}
if any_bash(&[
"cargo test",
"cargo nextest",
"pytest",
"vitest",
"jest ",
"mocha ",
"npm test",
"npm run test",
"pnpm test",
"pnpm run test",
"go test",
"dotnet test",
"flutter test",
]) {
return TaskCategory::Testing;
}
if any_bash(&[
"cargo build",
"cargo check",
"npm run build",
"pnpm build",
"docker ",
"kubectl ",
"pm2 ",
"tsc",
"next build",
]) {
return TaskCategory::BuildDeploy;
}
if any_bash(&["fix", "debug", "error", "bug", "issue", "stacktrace", "panic"]) && has_edit {
return TaskCategory::Debugging;
}
if any_bash(&["refactor", "rename", "simplify", "extract", "inline"]) {
return TaskCategory::Refactoring;
}
if has_edit {
return TaskCategory::Coding;
}
if has_read_only {
return TaskCategory::Exploration;
}
TaskCategory::General
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_tools_is_conversation() {
assert_eq!(classify(&[], &[]), TaskCategory::Conversation);
}
#[test]
fn test_edit_is_coding() {
assert_eq!(classify(&["Edit"], &[]), TaskCategory::Coding);
}
#[test]
fn test_write_is_coding() {
assert_eq!(classify(&["Write"], &[]), TaskCategory::Coding);
}
#[test]
fn test_agent_is_delegation() {
assert_eq!(
classify(&["Agent", "Edit"], &[]),
TaskCategory::Delegation
);
}
#[test]
fn test_git_bash_is_gitops() {
assert_eq!(
classify(&["Bash"], &["git status"]),
TaskCategory::GitOps
);
}
#[test]
fn test_cargo_test_is_testing() {
assert_eq!(
classify(&["Bash"], &["cargo test --lib"]),
TaskCategory::Testing
);
}
#[test]
fn test_read_only_is_exploration() {
assert_eq!(
classify(&["Read", "Grep"], &[]),
TaskCategory::Exploration
);
}
#[test]
fn test_plan_mode_is_planning() {
assert_eq!(
classify(&["EnterPlanMode"], &[]),
TaskCategory::Planning
);
}
#[test]
fn test_docker_is_build_deploy() {
assert_eq!(
classify(&["Bash"], &["docker build -t myapp ."]),
TaskCategory::BuildDeploy
);
}
#[test]
fn test_fix_with_edit_is_debugging() {
assert_eq!(
classify(&["Bash", "Edit"], &["fix the broken import"]),
TaskCategory::Debugging
);
}
#[test]
fn test_category_display() {
assert_eq!(TaskCategory::GitOps.as_str(), "git_ops");
assert_eq!(TaskCategory::GitOps.label(), "Git Ops");
}
}