use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum QueryIntent {
BugFix,
Architecture,
Implementation,
Configuration,
Navigation,
Operational,
General,
}
#[derive(Debug, Clone)]
pub struct IntentAdjustments {
pub doc_demotion_factor: f32,
pub semantic_weight_factor: f32,
pub recency_weight_factor: f32,
pub gate_boost: f32,
pub coupling_threshold: Option<f32>,
}
impl Default for IntentAdjustments {
fn default() -> Self {
Self {
doc_demotion_factor: 1.0,
semantic_weight_factor: 1.0,
recency_weight_factor: 1.0,
gate_boost: 0.0,
coupling_threshold: None,
}
}
}
pub fn apply_doc_demotion_factor(base: f32, factor: f32, floor: f32) -> f32 {
let effect = (1.0 - base) * factor;
(1.0 - effect).clamp(floor, 1.0)
}
pub fn classify_intent(prompt: &str) -> QueryIntent {
let lower = prompt.to_lowercase();
let words: Vec<&str> = lower.split_whitespace().collect();
let mut scores = [
(QueryIntent::BugFix, 0i32),
(QueryIntent::Architecture, 0),
(QueryIntent::Implementation, 0),
(QueryIntent::Configuration, 0),
(QueryIntent::Navigation, 0),
(QueryIntent::Operational, 0),
];
let has_word = |kw: &str| -> bool { words.iter().any(|w| w.starts_with(kw)) };
let bugfix_stems = [
"fix",
"bug",
"broke",
"error",
"crash",
"fail",
"wrong",
"issue",
"debug",
"traceback",
"panic",
"exception",
"stack",
];
let bugfix_phrases = [
"doesn't work",
"does not work",
"not working",
"is broken",
"stopped working",
];
for kw in &bugfix_stems {
if has_word(kw) {
scores[0].1 += 1;
}
}
for phrase in &bugfix_phrases {
if lower.contains(phrase) {
scores[0].1 += 2;
}
}
if lower.contains("error[") || lower.contains("error:") || lower.contains("exception") {
scores[0].1 += 2;
}
let arch_stems = [
"architect",
"design",
"explain",
"overview",
"understand",
"structur",
"diagram",
"pattern",
];
let arch_phrases = [
"how does",
"how do",
"how is",
"what is the",
"walk me through",
];
for kw in &arch_stems {
if has_word(kw) {
scores[1].1 += 1;
}
}
for phrase in &arch_phrases {
if lower.contains(phrase) {
scores[1].1 += 2;
}
}
let impl_strong = ["implement", "creat", "add"];
let impl_stems = ["build", "write", "make", "feature", "extend"];
let impl_phrases = [
"add a",
"create a",
"build a",
"implement a",
"write a",
"add support",
"add the",
"add new",
"add an",
"build the",
"build new",
"create the",
"create new",
"write the",
"write new",
];
for kw in &impl_strong {
if has_word(kw) {
scores[2].1 += 2;
}
}
for kw in &impl_stems {
if has_word(kw) {
scores[2].1 += 1;
}
}
for phrase in &impl_phrases {
if lower.contains(phrase) {
scores[2].1 += 2;
}
}
let config_stems = [
"config",
"deploy",
"setup",
"install",
"env",
"environment",
"dockerfile",
"yaml",
"toml",
"nginx",
"traefik",
"systemd",
"systemctl",
];
let config_phrases = [
"set up",
"how to configure",
"how to deploy",
"how to install",
"deploy to",
"deploy the",
"deploy this",
"deploy it",
];
for kw in &config_stems {
if has_word(kw) {
scores[3].1 += 1;
}
}
for phrase in &config_phrases {
if lower.contains(phrase) {
scores[3].1 += 2;
}
}
let nav_stems = [
"where", "find", "locate", "which", "file", "path", "defin", "declarat",
];
let nav_phrases = [
"where is",
"where are",
"which file",
"find the",
"locate the",
"defined in",
"look at",
"read the",
"open the",
"show me the file",
"what file",
"search for",
"grep for",
"look up",
];
for kw in &nav_stems {
if has_word(kw) {
scores[4].1 += 1;
}
}
for phrase in &nav_phrases {
if lower.contains(phrase) {
scores[4].1 += 2;
}
}
let op_stems = [
"commit", "push", "pull", "merge", "rebase", "stash", "checkout", "check", "status",
"close", "remove", "delete", "rename", "hook", "sling", "nudge",
];
let op_phrases = [
"run the test",
"run test",
"cargo test",
"cargo build",
"cargo check",
"go test",
"npm test",
"npm run",
"make test",
"make build",
"git push",
"git pull",
"git commit",
"git merge",
"git rebase",
"git stash",
"git checkout",
"git status",
"git diff",
"git log",
"bd close",
"bd ready",
"bd list",
"bd show",
"bd update",
"gt hook",
"gt mail",
"gt handoff",
"gt sling",
"gt nudge",
"check status",
"check the status",
"check if tests pass",
"push the code",
"push this",
"commit this",
"commit the",
"land this",
"ship it",
"merge this",
"what's next",
"what is next",
"next task",
"next bead",
"ready beads",
"ready queue",
"what's on my hook",
"check my hook",
"check mail",
"check inbox",
"read mail",
"read inbox",
"checking in",
"session start",
"hand off",
"handoff",
"pick up work",
"pick next",
"what should i work on",
"assigned to me",
"my beads",
"my issues",
"my tasks",
"beads assigned",
"open beads",
"in progress beads",
"close this bead",
"close the bead",
"update the bead",
"close your beads",
"close beads",
"check the status",
"check status",
"check your",
"check on",
"disk usage",
"disk space",
"memory usage",
"cpu usage",
"cpu load",
"service status",
"container status",
"is it up",
"is it down",
"is it running",
"restart the",
"restart service",
"how much disk",
"how much memory",
"how much space",
"free space",
"uptime",
"health check",
"alert firing",
"alert status",
"prometheus",
"grafana",
"backup status",
"cert expir",
"review the pr",
"review this pr",
"review the diff",
"show the diff",
"what changed",
"what did i change",
"show changes",
"git show",
"git blame",
"git shortlog",
"cycle session",
"new session",
"fresh session",
"context low",
"context is low",
"running low on context",
];
let op_strong_phrases = [
"disk usage",
"disk space",
"memory usage",
"cpu usage",
"cpu load",
"how much disk",
"how much memory",
"how much space",
"service status",
"container status",
"backup status",
"alert firing",
"alert status",
"is it up",
"is it down",
"is it running",
];
for phrase in &op_strong_phrases {
if lower.contains(phrase) {
scores[5].1 += 1;
} }
let mgmt_verbs = [
"remove", "delete", "hook", "unhook", "sling", "show", "claim",
];
if words.len() <= 4 {
for verb in &mgmt_verbs {
if has_word(verb) {
scores[5].1 += 2;
}
}
}
let cmd_prefixes = [
"git ", "cargo ", "go ", "npm ", "make ", "bd ", "gt ", "docker ",
];
for phrase in &op_phrases {
if lower.contains(phrase) {
scores[5].1 += 2;
}
}
for kw in &op_stems {
if has_word(kw) {
scores[5].1 += 1;
}
}
for prefix in &cmd_prefixes {
if lower.starts_with(prefix) && words.len() <= 6 {
scores[5].1 += 3;
}
}
let best = scores.iter().max_by_key(|(_, s)| *s).unwrap();
if best.1 >= 2 {
best.0
} else {
QueryIntent::General
}
}
#[path = "intent_adjustments.rs"]
mod adjustments;
pub use adjustments::intent_adjustments;
#[cfg(test)]
#[path = "intent_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "eval_prompt_intent_tests.rs"]
mod eval_prompt_intent_tests;