use super::util::truncate_at_word;
use crate::storage::LedgerRow;
pub const MAX_TASK_CHARS: usize = 120;
pub const NO_TASK_SENTINEL: &str = "<no task captured>";
pub fn extract_task(rows: &[LedgerRow]) -> String {
for row in rows.iter() {
if row.role != "user" {
continue;
}
let trimmed = row.content.trim();
if trimmed.is_empty() {
continue;
}
if trimmed.starts_with('[') {
continue;
}
if trimmed.len() < 10 {
continue;
}
let first_line = trimmed
.lines()
.map(str::trim)
.find(|l| !l.is_empty())
.unwrap_or(trimmed);
return truncate_at_word(first_line, MAX_TASK_CHARS);
}
NO_TASK_SENTINEL.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
fn make_row(role: &str, content: &str) -> LedgerRow {
LedgerRow {
session_id: "s1".to_string(),
tool: "claude".to_string(),
ts: 0,
role: role.to_string(),
content: content.to_string(),
tool_calls_json: None,
files_touched_json: None,
parent_id: None,
}
}
#[test]
fn extracts_first_substantial_user_prompt() {
let rows = vec![
make_row("user", "first substantial message here"),
make_row("assistant", "some response"),
make_row("user", "ok"),
];
assert_eq!(extract_task(&rows), "first substantial message here");
}
#[test]
fn skips_trivial_follow_ups() {
let rows = vec![
make_row("user", "ok"),
make_row("user", "sure"),
make_row("user", "build me a web app with auth"),
];
assert_eq!(extract_task(&rows), "build me a web app with auth");
}
#[test]
fn truncates_long_prompt_at_word_boundary() {
let long = format!("{} {}", "a".repeat(115), "b".repeat(14));
let rows = vec![make_row("user", &long)];
let result = extract_task(&rows);
assert!(result.ends_with('…'), "expected ellipsis, got: {result}");
assert!(
result.chars().count() <= 121,
"result too long: {} chars",
result.chars().count()
);
}
#[test]
fn condenses_multiline_to_first_line() {
let content = "first line\nsecond line\nthird line";
let rows = vec![make_row("user", content)];
assert_eq!(extract_task(&rows), "first line");
}
#[test]
fn handles_empty_ledger() {
assert_eq!(extract_task(&[]), NO_TASK_SENTINEL);
}
#[test]
fn handles_no_user_rows() {
let rows = vec![
make_row("assistant", "response"),
make_row("system", "system msg"),
];
assert_eq!(extract_task(&rows), NO_TASK_SENTINEL);
}
#[test]
fn skips_empty_user_content() {
let rows = vec![
make_row("user", "earlier good message"),
make_row("user", ""),
make_row("user", " "),
];
assert_eq!(extract_task(&rows), "earlier good message");
}
}