use std::fmt;
use crate::core::session::SessionState;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum TaskIntent {
Debug,
Refactor,
Implement,
Review,
Explore,
#[default]
Unknown,
}
const INTENT_KEYWORDS: &[(TaskIntent, &[&str])] = &[
(
TaskIntent::Debug,
&["fix", "bug", "error", "debug", "crash", "panic"],
),
(
TaskIntent::Refactor,
&[
"refactor",
"rename",
"move",
"extract",
"restructure",
"clean",
],
),
(
TaskIntent::Implement,
&["implement", "add", "create", "build", "feature", "new"],
),
(
TaskIntent::Review,
&["review", "check", "audit", "verify", "inspect"],
),
(
TaskIntent::Explore,
&["understand", "explore", "analyze", "investigate", "find"],
),
];
const ERROR_KEYWORDS: &[&str] = &["error", "panic", "crash", "failed", "failure", "bug"];
pub fn classify_intent(session: &SessionState) -> TaskIntent {
if let Some(task) = &session.task {
if let Some(intent) = classify_text(task.intent.as_deref().unwrap_or_default()) {
return intent;
}
if let Some(intent) = classify_text(&task.description) {
return intent;
}
}
if session
.findings
.iter()
.rev()
.any(|finding| contains_keyword(&finding.summary, ERROR_KEYWORDS))
{
return TaskIntent::Debug;
}
TaskIntent::Unknown
}
fn classify_text(text: &str) -> Option<TaskIntent> {
INTENT_KEYWORDS
.iter()
.find_map(|(intent, keywords)| contains_keyword(text, keywords).then_some(*intent))
}
fn contains_keyword(text: &str, keywords: &[&str]) -> bool {
text.split(|character: char| !character.is_alphanumeric())
.filter(|word| !word.is_empty())
.any(|word| {
keywords
.iter()
.any(|keyword| word.eq_ignore_ascii_case(keyword))
})
}
impl fmt::Display for TaskIntent {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
Self::Debug => "debug",
Self::Refactor => "refactor",
Self::Implement => "implement",
Self::Review => "review",
Self::Explore => "explore",
Self::Unknown => "unknown",
};
formatter.write_str(name)
}
}
#[cfg(test)]
pub mod tests {
use crate::core::session::{SessionState, TaskInfo};
use super::{TaskIntent, classify_intent};
fn session_with_task(description: &str) -> SessionState {
let mut session = SessionState::new();
session.task = Some(TaskInfo {
description: description.to_owned(),
intent: None,
progress_pct: None,
});
session
}
#[test]
fn classify_debug_from_task() {
let session = session_with_task("Fix the parser crash");
assert_eq!(classify_intent(&session), TaskIntent::Debug);
}
#[test]
fn classify_refactor_from_task() {
let session = session_with_task("Refactor the session store");
assert_eq!(classify_intent(&session), TaskIntent::Refactor);
}
#[test]
fn classify_unknown_when_empty() {
assert_eq!(classify_intent(&SessionState::new()), TaskIntent::Unknown);
}
#[test]
fn display_format() {
assert_eq!(TaskIntent::Implement.to_string(), "implement");
}
}