use super::*;
#[test]
fn response_style_preserves_the_configured_round_allowance() {
let explain = PromptIntake::analyze("Explain how prompt receipts survive compaction.");
let research = PromptIntake::analyze("Investigate the current compaction behavior.");
assert_eq!(explain.disposition(), PromptDisposition::Explain);
assert_eq!(research.disposition(), PromptDisposition::Research);
assert_eq!(PromptDisposition::Ask.tool_round_limit(40), 0);
assert_eq!(PromptDisposition::Explain.tool_round_limit(40), 40);
assert_eq!(PromptDisposition::Research.tool_round_limit(40), 40);
assert_eq!(PromptDisposition::Plan.tool_round_limit(40), 40);
}
fn pre_1260_lexicon() -> DispositionLexicon {
DispositionLexicon {
action: [
"implement",
"modify",
"change",
"create",
"write",
"edit",
"delete",
"fix",
"build",
"run ",
"execute",
"commit",
"push",
"open a pr",
"open pr",
"merge",
]
.map(str::to_string)
.to_vec(),
research: [
"research",
"investigate",
"look up",
"find out",
"analyze",
"diagnose",
"audit",
"explore",
"compare",
]
.map(str::to_string)
.to_vec(),
explain: [
"explain",
"summarize",
"describe",
"what is",
"why ",
"how does",
"how do",
]
.map(str::to_string)
.to_vec(),
question_mark_disposition: PromptDisposition::Explain,
..DispositionLexicon::default()
}
}
fn infer(prompt: &str, lexicon: &DispositionLexicon) -> PromptDisposition {
let (asks, _) = super::extract_atomic_asks_with(prompt, lexicon);
super::infer_disposition_with(prompt, &asks, lexicon)
}
#[test]
fn largest_files_question_classified_explain_via_question_mark_fallback_pre_1260() {
let old = pre_1260_lexicon();
assert_eq!(
infer(LARGEST_FILES_PROMPT, &old),
PromptDisposition::Explain,
"under the OLD data the ? fallback alone decided"
);
assert_eq!(
infer(LARGEST_FILES_PROMPT.trim_end_matches('?'), &old),
PromptDisposition::Act,
"…and the same prompt minus its ? fell off the cliff to Act"
);
}
#[test]
fn new_defaults_classify_evidence_questions_by_content_not_the_cliff() {
let with_q = PromptIntake::analyze(LARGEST_FILES_PROMPT);
assert_eq!(with_q.disposition(), PromptDisposition::Research);
let without_q = PromptIntake::analyze("What are the 10 largest Rust files in this workspace");
assert_eq!(
without_q.disposition(),
PromptDisposition::Research,
"content decides; removing the ? no longer flips the disposition"
);
let plural = PromptIntake::analyze("What are the tradeoffs of this design?");
assert_eq!(plural.disposition(), PromptDisposition::Explain);
let act = PromptIntake::analyze("update the release notes for 0.8.0");
assert_eq!(act.disposition(), PromptDisposition::Act);
}
#[test]
fn line_count_questions_classify_research_not_the_cliff() {
let regressed =
PromptIntake::analyze("show me the 10 code files with the highest line counts?");
assert_eq!(
regressed.disposition(),
PromptDisposition::Research,
"line-count question is a Research/evidence turn, not Explain or Act"
);
for prompt in [
"which files have the most lines",
"the longest file in the repo",
"files with the fewest lines",
] {
assert_eq!(
PromptIntake::analyze(prompt).disposition(),
PromptDisposition::Research,
"line-count evidence phrasing → Research: {prompt:?}"
);
}
}
#[test]
fn code_file_prompt_adds_source_scope_without_incident_specific_shape_guessing() {
let intake = PromptIntake::analyze(
"show me the 10 code files with the highest line counts in this repository?",
);
let card = intake.model_card();
assert!(
!card.contains("response_shape:"),
"line-count/ranking keywords must not own presentation policy: {card}"
);
assert!(
card.contains("evidence_scope: source_files"),
"`code files` means language source, not every repository file: {card}"
);
assert!(
card.contains("source_filter: category=source"),
"an unqualified code-file request must use the harness-owned source category: {card}"
);
assert!(
card.contains("exclude documentation, manifests, lockfiles"),
"the steering must name the observed false-positive classes: {card}"
);
assert!(
!card.contains("highest")
&& !card.contains("longest")
&& !card.contains("most lines")
&& !card.contains("line/size rankings")
&& !card.contains("code=true"),
"the model card must carry a general source refinement, not an incident lexicon: {card}"
);
}
#[test]
fn explicit_rust_table_prompt_steers_rs_filter_and_gfm_table() {
let intake = PromptIntake::analyze(
"can you give me a table of the rust files with the longest line counts instead?",
);
let card = intake.model_card();
assert!(card.contains("response_shape: table"), "{card}");
assert!(card.contains("evidence_scope: source_files"), "{card}");
assert!(
card.contains("source_extensions: rs"),
"Rust must resolve through the language-pack data to its source extension: {card}"
);
assert!(
card.contains("source_filter: category=source language=rust"),
"the model needs the concrete harness filter, not just a language label: {card}"
);
}
#[test]
fn ordinary_prompt_gets_no_incident_specific_refinement() {
let card = PromptIntake::analyze("explain ownership briefly").model_card();
assert!(!card.contains("response_format:"), "{card}");
assert!(!card.contains("response_shape:"), "{card}");
assert!(!card.contains("evidence_scope:"), "{card}");
assert!(!card.contains("source_filter:"), "{card}");
let comfortable = PromptIntake::analyze("make this interface more comfortable").model_card();
assert!(
!comfortable.contains("response_shape:"),
"presentation inference must not match `table` inside another word: {comfortable}"
);
}
#[test]
fn lexicon_overrides_drive_inference_table_driven() {
let custom = DispositionLexicon {
explain: vec!["kerfuffle".to_string()],
question_mark_disposition: PromptDisposition::Research,
..DispositionLexicon::default()
};
for (prompt, want) in [
("tell me about the kerfuffle", PromptDisposition::Explain),
("what is a monad?", PromptDisposition::Research),
("fix the kerfuffle", PromptDisposition::Act),
("status report", PromptDisposition::Act),
] {
assert_eq!(infer(prompt, &custom), want, "{prompt:?}");
}
}
#[test]
fn analyze_with_applies_the_lexicon_and_keeps_ask_precedence() {
let lex = DispositionLexicon {
research: vec!["kerfuffle".to_string()],
..DispositionLexicon::default()
};
let intake = PromptIntake::analyze_with("tell me about the kerfuffle", &lex);
assert_eq!(intake.disposition(), PromptDisposition::Research);
let asky = PromptIntake::analyze_with(
"Investigate either the kerfuffle or the brouhaha; compare them.",
&lex,
);
if asky.manifest().pending_decision_count() > 0 {
assert_eq!(asky.disposition(), PromptDisposition::Ask);
}
let empty = PromptIntake::analyze_with(" ", &lex);
assert_eq!(empty.disposition(), PromptDisposition::Ask);
}
#[test]
fn a_request_phrased_as_a_question_routes_like_its_imperative() {
let mut misrouted = Vec::new();
for (question, imperative) in [
(
"Can you install skills for that herdr tool?",
"install skills for that herdr tool",
),
("Can you review PR 42 for me?", "review PR 42 for me"),
(
"Could you fetch the release notes from the tracker?",
"fetch the release notes from the tracker",
),
("try now?", "try now"),
(
"Would you look at the failing job?",
"look at the failing job",
),
(
"Thanks for that. Can you review PR 42?",
"Thanks for that. review PR 42",
),
] {
let lex = DispositionLexicon::default();
assert_eq!(
infer(imperative, &lex),
PromptDisposition::Act,
"{imperative:?}"
);
if infer(question, &lex) != PromptDisposition::Act {
misrouted.push((question, infer(question, &lex)));
}
}
assert!(
misrouted.is_empty(),
"requests routed as answers: {misrouted:?}"
);
}
#[test]
fn a_genuine_question_stays_an_answer() {
let lex = DispositionLexicon::default();
for question in [
"How do I install a skill?",
"What does tool_search do?",
"hello?",
"What can you do?",
"Can you explain how prompt intake works?",
"Could you tell me what the herdr tool does?",
"Thanks. How do I install a skill?",
] {
assert_eq!(
infer(question, &lex),
PromptDisposition::Explain,
"{question:?} is a question, not a request"
);
}
}
#[test]
fn a_capability_question_with_a_request_opener_reaches_act_for_2324() {
assert_eq!(
infer("Can you read Python?", &DispositionLexicon::default()),
PromptDisposition::Act
);
}
#[test]
fn refactor_is_an_action_even_when_the_target_is_named_by_size() {
for prompt in [
"refactor the largest file in this repo",
"Refactor newt-core/src/agentic/mod.rs",
] {
assert_eq!(
PromptIntake::analyze(prompt).disposition(),
PromptDisposition::Act,
"{prompt}"
);
}
assert_eq!(
PromptIntake::analyze("What are the 10 largest Rust files in this workspace").disposition(),
PromptDisposition::Research
);
}
#[test]
fn explain_refactoring_is_not_the_refactor_action_needle() {
assert_eq!(
PromptIntake::analyze("explain refactoring strategies").disposition(),
PromptDisposition::Explain,
"refactoring is a different word from refactor, in an explanatory sentence"
);
}
#[test]
fn explain_test_fixture_is_not_the_fix_action_needle() {
assert_eq!(
PromptIntake::analyze("explain the test fixture setup").disposition(),
PromptDisposition::Explain,
"fixture is a different word from fix"
);
}
#[test]
fn plural_line_counts_still_classifies_research() {
assert_eq!(
PromptIntake::analyze("show me the 10 code files with the highest line counts?")
.disposition(),
PromptDisposition::Research,
"the plural form must match its own lexicon entry, not rely on substring matching"
);
}
#[test]
fn where_authority_actually_changes_matches_the_review_table() {
let cases: &[(&str, PromptDisposition)] = &[
(
"investigate the flaky test and get it fixed",
PromptDisposition::Act,
),
("the largest file needs refactoring", PromptDisposition::Act),
(
"Why does this crash? It needs fixing.",
PromptDisposition::Act,
),
(
"investigate why CI failed and get it committed",
PromptDisposition::Act,
),
("rerun the tests and investigate", PromptDisposition::Act),
(
"The auth module is broken and needs fixing.",
PromptDisposition::Act,
),
("auditing the permission table", PromptDisposition::Research),
(
"researching which crate to use",
PromptDisposition::Research,
),
("explaining the retry loop", PromptDisposition::Explain),
("explain refactoring strategies", PromptDisposition::Explain),
("explain the prefix tree", PromptDisposition::Explain),
("what is the credit limit field", PromptDisposition::Explain),
(
"explain the test fixture layout",
PromptDisposition::Explain,
),
(
"describe the emergency stop path",
PromptDisposition::Explain,
),
("explain the exchange module", PromptDisposition::Explain),
];
for (prompt, expected) in cases {
assert_eq!(
PromptIntake::analyze(prompt).disposition(),
*expected,
"{prompt:?} must classify {expected:?}"
);
}
}
#[test]
fn round_3_noun_and_underscore_false_matches_are_fixed() {
let cases: &[(&str, PromptDisposition)] = &[
(
"update the description field in Cargo.toml",
PromptDisposition::Act,
),
("add a summary line to the README", PromptDisposition::Act),
(
"rename the summary() helper to digest()",
PromptDisposition::Act,
),
("expose summary_stats in the api", PromptDisposition::Act),
(
"replace get_description with describe_item",
PromptDisposition::Act,
),
(
"make the explanation shorter in the error message",
PromptDisposition::Act,
),
("update the diagnosis message", PromptDisposition::Act),
(
"add a comparison test for the two parsers",
PromptDisposition::Act,
),
(
"explain why the rerun flag exists",
PromptDisposition::Explain,
),
("please rerun the failing suite", PromptDisposition::Act),
("explain what fix_path does", PromptDisposition::Explain),
(
"describe the build_graph function",
PromptDisposition::Explain,
),
("rerun the tests and investigate", PromptDisposition::Act),
(
"refactor the largest file in this repo",
PromptDisposition::Act,
),
];
for (prompt, expected) in cases {
assert_eq!(
PromptIntake::analyze(prompt).disposition(),
*expected,
"{prompt:?} must classify {expected:?}"
);
}
}
#[test]
fn a_collaborative_opener_plans_before_the_action_needle_acts() {
let lex = DispositionLexicon::default();
for prompt in [
"let's refactor the largest file",
"lets fix the flaky test",
"We should split prompt_intake.rs",
"how about we drop the cache layer",
"I'm thinking we rewrite intake in wyvern",
"shall we move the lexicon to TOML?",
"Let’s refactor tools.rs",
] {
assert_eq!(infer(prompt, &lex), PromptDisposition::Plan, "{prompt:?}");
}
}
#[test]
fn a_bare_continuation_behind_a_collaborative_opener_still_acts() {
let lex = DispositionLexicon::default();
for prompt in [
"let's go",
"Let's go!",
"let's continue",
"lets proceed",
"let's land it",
"let's ship it",
"let's do it",
] {
assert_eq!(infer(prompt, &lex), PromptDisposition::Act, "{prompt:?}");
}
}
#[test]
fn a_collaborative_phrase_mid_clause_is_not_an_opener() {
let lex = DispositionLexicon::default();
for (prompt, expected) in [
("fix the bug we should have caught", PromptDisposition::Act),
(
"explain why we should pin serde",
PromptDisposition::Explain,
),
] {
assert_eq!(infer(prompt, &lex), expected, "{prompt:?}");
}
}
#[test]
fn a_mixed_prompt_with_one_collaborative_ask_keeps_its_own_reading() {
let prompt = "let's talk about the parser\nthen fix the failing test";
assert_eq!(
infer(prompt, &DispositionLexicon::default()),
PromptDisposition::Act,
"one plain imperative among the asks keeps the ordinary reading"
);
}
#[test]
fn a_direct_verb_behind_a_collaborative_opener_still_acts() {
let lex = DispositionLexicon::default();
for prompt in [
"let's commit this",
"let's push",
"lets run the tests",
"Let's merge it!",
"let's rebase onto main",
"we should build and test first",
] {
assert_eq!(infer(prompt, &lex), PromptDisposition::Act, "{prompt:?}");
}
let dropped = DispositionLexicon {
direct_verbs: vec![],
..DispositionLexicon::default()
};
assert_eq!(
infer("let's commit this", &dropped),
PromptDisposition::Plan
);
}
#[test]
fn collaborative_openers_are_droppable_lexicon_data() {
let dropped = DispositionLexicon {
collaborative: vec![],
..DispositionLexicon::default()
};
assert_eq!(
infer("let's refactor the largest file", &dropped),
PromptDisposition::Act
);
}