Skip to main content

kimetsu_brain/
benchmark.rs

1use kimetsu_core::memory::MemoryKind;
2use serde::{Deserialize, Serialize};
3
4use crate::context::{ContextBundle, ContextCapsule};
5
6pub const DEFAULT_BENCHMARK_DATASET: &str = "terminal-bench/terminal-bench-2";
7
8const TERMINAL_BENCH_SLUGS: &[&str] = &[
9    "make-mips-interpreter",
10    "circuit-fibsqrt",
11    "build-pov-ray",
12    "overfull-hbox",
13    "distribution-search",
14    "break-filter-js-from-html",
15    "video-processing",
16    "protein-assembly",
17    "path-tracing",
18    "compile-compcert",
19    "log-summary-date-ranges",
20    "openssl-selfsigned-cert",
21    "dna-assembly",
22    "caffe-cifar-10",
23    "install-windows-3-11",
24    "vulnerable-secret",
25];
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(rename_all = "snake_case")]
29pub enum BenchmarkWarmPolicy {
30    ColdBrain,
31    ReactiveWarm,
32    FullWarm,
33}
34
35impl BenchmarkWarmPolicy {
36    pub fn parse(value: &str) -> Option<Self> {
37        match value.trim().to_ascii_lowercase().replace('-', "_").as_str() {
38            "" | "full" | "full_warm" | "warm" | "brain_on_warm" => Some(Self::FullWarm),
39            "reactive" | "reactive_warm" | "warm_reactive" | "optional_warm" => {
40                Some(Self::ReactiveWarm)
41            }
42            "cold" | "cold_brain" | "brain_on_cold" => Some(Self::ColdBrain),
43            _ => None,
44        }
45    }
46
47    pub const fn as_str(self) -> &'static str {
48        match self {
49            Self::ColdBrain => "cold_brain",
50            Self::ReactiveWarm => "reactive_warm",
51            Self::FullWarm => "full_warm",
52        }
53    }
54
55    pub const fn playbook_note(self) -> &'static str {
56        match self {
57            Self::ColdBrain => {
58                "Cold brain: memory capsules are intentionally excluded. This measures broker/repo/prior-run grounding without accepted memories."
59            }
60            Self::ReactiveWarm => {
61                "Reactive warm: Kimetsu memory is available when the harness or model asks for it, but task-specific benchmark memory is not required."
62            }
63            Self::FullWarm => {
64                "Full warm: the benchmark playbook is fetched before the task starts and may include task-specific benchmark memories."
65            }
66        }
67    }
68}
69
70impl Default for BenchmarkWarmPolicy {
71    fn default() -> Self {
72        Self::FullWarm
73    }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
77#[serde(rename_all = "snake_case")]
78pub enum BenchmarkMemoryRole {
79    /// Exact run/task outcome memory. Useful evidence, but low-priority
80    /// guidance because it often overfits one benchmark instance.
81    Episodic,
82    /// A reusable tactic or operator that can transfer across task slugs.
83    SemanticOperator,
84    /// A reusable warning about a failure mode or misleading tactic.
85    AntiPattern,
86}
87
88impl BenchmarkMemoryRole {
89    pub fn parse(value: &str) -> Option<Self> {
90        match value.trim().to_ascii_lowercase().replace('-', "_").as_str() {
91            "" | "episodic" | "run" | "task_run" | "outcome" => Some(Self::Episodic),
92            "semantic" | "semantic_operator" | "operator" | "tactic" | "recipe" => {
93                Some(Self::SemanticOperator)
94            }
95            "anti" | "anti_pattern" | "antipattern" | "failure_pattern" | "warning" => {
96                Some(Self::AntiPattern)
97            }
98            _ => None,
99        }
100    }
101
102    pub const fn as_str(self) -> &'static str {
103        match self {
104            Self::Episodic => "episodic",
105            Self::SemanticOperator => "semantic_operator",
106            Self::AntiPattern => "anti_pattern",
107        }
108    }
109
110    pub const fn is_generalizable(self) -> bool {
111        matches!(self, Self::SemanticOperator | Self::AntiPattern)
112    }
113}
114
115impl Default for BenchmarkMemoryRole {
116    fn default() -> Self {
117        Self::Episodic
118    }
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct BenchmarkBrainContext {
123    pub dataset: String,
124    pub task: String,
125    pub task_slug: Option<String>,
126    pub warm_policy: BenchmarkWarmPolicy,
127    pub query: String,
128    pub stage: String,
129    pub budget_tokens: u32,
130    pub used_tokens: u32,
131    pub capsule_count: usize,
132    pub memory_capsule_count: usize,
133    pub benchmark_memory_count: usize,
134    pub generalizable_memory_count: usize,
135    pub episodic_memory_count: usize,
136    pub required_ok: bool,
137    pub playbook_markdown: String,
138    pub capsules: Vec<ContextCapsule>,
139    pub excluded: Vec<ContextCapsule>,
140}
141
142#[derive(Debug, Clone)]
143pub struct BenchmarkMemoryProposal {
144    pub role: BenchmarkMemoryRole,
145    pub text: String,
146    pub task_family: Option<String>,
147    pub applies_to: Vec<String>,
148    pub does_not_apply_to: Vec<String>,
149    pub evidence_for: Vec<String>,
150    pub evidence_against: Vec<String>,
151    pub rationale: String,
152    pub confidence: f32,
153}
154
155#[derive(Debug, Clone, Default)]
156pub struct BenchmarkOutcome {
157    pub task: String,
158    pub dataset: String,
159    pub task_slug: Option<String>,
160    pub warm_policy: BenchmarkWarmPolicy,
161    pub mode: String,
162    pub passed: Option<bool>,
163    pub score: Option<f32>,
164    pub error: Option<String>,
165    pub summary: String,
166    pub commands: Vec<String>,
167    pub pitfalls: Vec<String>,
168    pub verify: Vec<String>,
169    pub cost_usd: Option<f32>,
170    pub duration_seconds: Option<f32>,
171    pub generalization: Option<BenchmarkMemoryProposal>,
172}
173
174pub fn normalize_task_slug(input: &str) -> Option<String> {
175    let lower = input.to_ascii_lowercase();
176    for slug in TERMINAL_BENCH_SLUGS {
177        if lower.contains(slug) {
178            return Some((*slug).to_string());
179        }
180    }
181
182    lower
183        .split(|ch: char| !(ch.is_ascii_alphanumeric() || ch == '-' || ch == '_'))
184        .filter_map(|token| {
185            let token = token.split("__").next().unwrap_or(token);
186            let token = token.trim_matches('-').replace('_', "-");
187            if looks_like_slug(&token) {
188                Some(token)
189            } else {
190                None
191            }
192        })
193        .next()
194}
195
196pub fn benchmark_query(
197    task: &str,
198    dataset: &str,
199    task_slug: Option<&str>,
200    warm_policy: BenchmarkWarmPolicy,
201) -> String {
202    let compact_task = compact_text(task, 1400);
203    match task_slug {
204        Some(slug) => format!(
205            "terminal-bench benchmark dataset:{dataset} warm-policy:{} terminal-bench:{slug} benchmark:{slug} task-slug:{slug} slug-words:{} task: {compact_task}",
206            warm_policy.as_str(),
207            slug.replace('-', " ")
208        ),
209        None => format!(
210            "terminal-bench benchmark dataset:{dataset} warm-policy:{} task: {compact_task}",
211            warm_policy.as_str()
212        ),
213    }
214}
215
216pub fn build_benchmark_context(
217    bundle: ContextBundle,
218    task: &str,
219    dataset: &str,
220    query: &str,
221    task_slug: Option<String>,
222    warm_policy: BenchmarkWarmPolicy,
223    require_benchmark_memory: bool,
224    max_capsules: usize,
225) -> BenchmarkBrainContext {
226    let max_capsules = max_capsules.clamp(1, 20);
227    let selected = prioritized_capsules(
228        &bundle.capsules,
229        task_slug.as_deref(),
230        warm_policy,
231        max_capsules,
232    );
233    let memory_capsule_count = selected
234        .iter()
235        .filter(|capsule| capsule.kind == "memory")
236        .count();
237    let benchmark_memory_count = selected
238        .iter()
239        .filter(|capsule| benchmark_memory_matches(capsule, task_slug.as_deref()))
240        .count();
241    let generalizable_memory_count = selected
242        .iter()
243        .filter(|capsule| {
244            benchmark_memory_role(capsule).is_some_and(BenchmarkMemoryRole::is_generalizable)
245        })
246        .count();
247    let episodic_memory_count = selected
248        .iter()
249        .filter(|capsule| benchmark_memory_role(capsule) == Some(BenchmarkMemoryRole::Episodic))
250        .count();
251    let required_ok =
252        !require_benchmark_memory || benchmark_memory_count > 0 || generalizable_memory_count > 0;
253    let playbook_markdown = format_playbook(
254        dataset,
255        task,
256        task_slug.as_deref(),
257        warm_policy,
258        query,
259        required_ok,
260        memory_capsule_count,
261        benchmark_memory_count,
262        generalizable_memory_count,
263        episodic_memory_count,
264        &selected,
265    );
266
267    BenchmarkBrainContext {
268        dataset: dataset.to_string(),
269        task: task.to_string(),
270        task_slug,
271        warm_policy,
272        query: query.to_string(),
273        stage: bundle.stage,
274        budget_tokens: bundle.budget_tokens,
275        used_tokens: bundle.used_tokens,
276        capsule_count: selected.len(),
277        memory_capsule_count,
278        benchmark_memory_count,
279        generalizable_memory_count,
280        episodic_memory_count,
281        required_ok,
282        playbook_markdown,
283        capsules: selected,
284        excluded: bundle.excluded,
285    }
286}
287
288pub fn benchmark_memory_matches(capsule: &ContextCapsule, task_slug: Option<&str>) -> bool {
289    if capsule.kind != "memory" {
290        return false;
291    }
292    let Some(slug) = task_slug else {
293        return false;
294    };
295    let slug = slug.to_ascii_lowercase();
296    let haystack = capsule_text(capsule);
297    haystack.contains(&format!("terminal-bench:{slug}"))
298        || haystack.contains(&format!("benchmark:{slug}"))
299        || haystack.contains(&format!("task-slug:{slug}"))
300        || haystack.contains(&slug)
301}
302
303pub fn benchmark_memory_role(capsule: &ContextCapsule) -> Option<BenchmarkMemoryRole> {
304    if capsule.kind != "memory" {
305        return None;
306    }
307    let haystack = capsule_text(capsule);
308    if has_role_marker(&haystack, BenchmarkMemoryRole::SemanticOperator) {
309        return Some(BenchmarkMemoryRole::SemanticOperator);
310    }
311    if has_role_marker(&haystack, BenchmarkMemoryRole::AntiPattern) {
312        return Some(BenchmarkMemoryRole::AntiPattern);
313    }
314    if has_role_marker(&haystack, BenchmarkMemoryRole::Episodic) {
315        return Some(BenchmarkMemoryRole::Episodic);
316    }
317    if haystack.contains("[terminal-bench:") && haystack.contains("status=") {
318        return Some(BenchmarkMemoryRole::Episodic);
319    }
320    None
321}
322
323fn has_role_marker(haystack: &str, role: BenchmarkMemoryRole) -> bool {
324    let role = role.as_str();
325    haystack.contains(&format!("memory_role={role}"))
326        || haystack.contains(&format!("memory-role={role}"))
327        || haystack.contains(&format!("role={role}"))
328}
329
330pub fn outcome_memory_kind(outcome: &BenchmarkOutcome) -> MemoryKind {
331    if outcome
332        .error
333        .as_ref()
334        .is_some_and(|value| !value.trim().is_empty())
335    {
336        return MemoryKind::FailurePattern;
337    }
338    match outcome.passed {
339        Some(false) => MemoryKind::FailurePattern,
340        Some(true) => MemoryKind::Command,
341        None => MemoryKind::Fact,
342    }
343}
344
345pub fn outcome_memory_text(outcome: &BenchmarkOutcome) -> String {
346    let task_slug = outcome
347        .task_slug
348        .clone()
349        .or_else(|| normalize_task_slug(&outcome.task))
350        .unwrap_or_else(|| "unknown".to_string());
351    let status = match (outcome.passed, outcome.error.as_deref()) {
352        (_, Some(error)) if !error.trim().is_empty() => "error",
353        (Some(true), _) => "pass",
354        (Some(false), _) => "fail",
355        (None, _) => "observed",
356    };
357
358    let mut parts = vec![format!(
359        "[terminal-bench:{task_slug}] dataset={} mode={} status={status}",
360        compact_text(&outcome.dataset, 120),
361        compact_text(&outcome.mode, 80),
362    )];
363    parts.push(format!(
364        "memory_role={}",
365        BenchmarkMemoryRole::Episodic.as_str()
366    ));
367    parts.push(format!("warm_policy={}", outcome.warm_policy.as_str()));
368    if let Some(score) = outcome.score {
369        parts.push(format!("score={score:.3}"));
370    }
371    if let Some(cost_usd) = outcome.cost_usd {
372        parts.push(format!("cost_usd={cost_usd:.4}"));
373    }
374    if let Some(duration) = outcome.duration_seconds {
375        parts.push(format!("duration_seconds={duration:.1}"));
376    }
377    if !outcome.summary.trim().is_empty() {
378        parts.push(format!("Summary: {}", compact_text(&outcome.summary, 500)));
379    }
380    if !outcome.commands.is_empty() {
381        parts.push(format!(
382            "Commands: {}",
383            compact_text(&outcome.commands.join("; "), 350)
384        ));
385    }
386    if !outcome.pitfalls.is_empty() {
387        parts.push(format!(
388            "Pitfalls: {}",
389            compact_text(&outcome.pitfalls.join("; "), 350)
390        ));
391    }
392    if !outcome.verify.is_empty() {
393        parts.push(format!(
394            "Verify: {}",
395            compact_text(&outcome.verify.join("; "), 250)
396        ));
397    }
398    if let Some(error) = outcome
399        .error
400        .as_deref()
401        .filter(|value| !value.trim().is_empty())
402    {
403        parts.push(format!("Error: {}", compact_text(error, 250)));
404    }
405    parts.join(". ")
406}
407
408pub fn proposal_memory_kind(proposal: &BenchmarkMemoryProposal) -> MemoryKind {
409    match proposal.role {
410        BenchmarkMemoryRole::AntiPattern => MemoryKind::FailurePattern,
411        BenchmarkMemoryRole::SemanticOperator | BenchmarkMemoryRole::Episodic => {
412            MemoryKind::Command
413        }
414    }
415}
416
417pub fn proposal_memory_text(
418    outcome: &BenchmarkOutcome,
419    proposal: &BenchmarkMemoryProposal,
420) -> String {
421    let task_slug = outcome
422        .task_slug
423        .clone()
424        .or_else(|| normalize_task_slug(&outcome.task))
425        .unwrap_or_else(|| "unknown".to_string());
426    let mut parts = vec![format!(
427        "[terminal-bench-memory] memory_role={} source_task_slug={} dataset={} mode={}",
428        proposal.role.as_str(),
429        task_slug,
430        compact_text(&outcome.dataset, 120),
431        compact_text(&outcome.mode, 80),
432    )];
433    if let Some(task_family) = proposal
434        .task_family
435        .as_deref()
436        .map(str::trim)
437        .filter(|value| !value.is_empty())
438    {
439        parts.push(format!("task_family={}", compact_text(task_family, 120)));
440    }
441    parts.push(format!("Rule: {}", compact_text(&proposal.text, 700)));
442    if !proposal.applies_to.is_empty() {
443        parts.push(format!(
444            "Applies_to: {}",
445            compact_text(&proposal.applies_to.join("; "), 350)
446        ));
447    }
448    if !proposal.does_not_apply_to.is_empty() {
449        parts.push(format!(
450            "Does_not_apply_to: {}",
451            compact_text(&proposal.does_not_apply_to.join("; "), 350)
452        ));
453    }
454    let evidence_for = if proposal.evidence_for.is_empty() {
455        vec![task_slug]
456    } else {
457        proposal.evidence_for.clone()
458    };
459    parts.push(format!(
460        "Evidence_for: {}",
461        compact_text(&evidence_for.join("; "), 250)
462    ));
463    if !proposal.evidence_against.is_empty() {
464        parts.push(format!(
465            "Evidence_against: {}",
466            compact_text(&proposal.evidence_against.join("; "), 250)
467        ));
468    }
469    if !proposal.rationale.trim().is_empty() {
470        parts.push(format!(
471            "Review_rationale: {}",
472            compact_text(&proposal.rationale, 250)
473        ));
474    }
475    parts.push(
476        "Human_review: pending; accept only if this transfers beyond the source task.".to_string(),
477    );
478    parts.join(". ")
479}
480
481fn prioritized_capsules(
482    capsules: &[ContextCapsule],
483    task_slug: Option<&str>,
484    warm_policy: BenchmarkWarmPolicy,
485    max_capsules: usize,
486) -> Vec<ContextCapsule> {
487    let mut ranked = capsules
488        .iter()
489        .enumerate()
490        .map(|(idx, capsule)| {
491            let role = benchmark_memory_role(capsule);
492            let exact_task = benchmark_memory_matches(capsule, task_slug);
493            let priority =
494                if warm_policy == BenchmarkWarmPolicy::ColdBrain && capsule.kind == "memory" {
495                    9
496                } else if role.is_some_and(BenchmarkMemoryRole::is_generalizable) && exact_task {
497                    0
498                } else if role.is_some_and(BenchmarkMemoryRole::is_generalizable) {
499                    1
500                } else if exact_task {
501                    2
502                } else if capsule.kind == "memory" && role == Some(BenchmarkMemoryRole::Episodic) {
503                    6
504                } else if capsule.kind == "memory" {
505                    3
506                } else {
507                    4
508                };
509            (priority, idx, capsule)
510        })
511        .collect::<Vec<_>>();
512    ranked.sort_by(|left, right| {
513        left.0
514            .cmp(&right.0)
515            .then_with(|| left.1.cmp(&right.1))
516            .then_with(|| {
517                right
518                    .2
519                    .score
520                    .partial_cmp(&left.2.score)
521                    .unwrap_or(std::cmp::Ordering::Equal)
522            })
523    });
524    ranked
525        .into_iter()
526        .filter(|(_, _, capsule)| {
527            warm_policy != BenchmarkWarmPolicy::ColdBrain || capsule.kind != "memory"
528        })
529        .take(max_capsules)
530        .map(|(_, _, capsule)| capsule.clone())
531        .collect()
532}
533
534fn format_playbook(
535    dataset: &str,
536    task: &str,
537    task_slug: Option<&str>,
538    warm_policy: BenchmarkWarmPolicy,
539    query: &str,
540    required_ok: bool,
541    memory_capsule_count: usize,
542    benchmark_memory_count: usize,
543    generalizable_memory_count: usize,
544    episodic_memory_count: usize,
545    capsules: &[ContextCapsule],
546) -> String {
547    let mut out = String::new();
548    out.push_str("# Kimetsu Benchmark Playbook\n\n");
549    out.push_str(&format!("dataset: {dataset}\n"));
550    out.push_str(&format!(
551        "task_slug: {}\n",
552        task_slug.unwrap_or("<not-detected>")
553    ));
554    out.push_str(&format!("warm_policy: {}\n", warm_policy.as_str()));
555    out.push_str(&format!("required_ok: {required_ok}\n"));
556    out.push_str(&format!("memory_capsule_count: {memory_capsule_count}\n"));
557    out.push_str(&format!(
558        "benchmark_memory_count: {benchmark_memory_count}\n"
559    ));
560    out.push_str(&format!(
561        "generalizable_memory_count: {generalizable_memory_count}\n"
562    ));
563    out.push_str(&format!("episodic_memory_count: {episodic_memory_count}\n"));
564    out.push('\n');
565    out.push_str(warm_policy.playbook_note());
566    out.push_str("\nUse these capsules as execution constraints before broad exploration. Prefer accepted semantic_operator and anti_pattern memories first because they are intended to transfer across tasks. Use exact episodic run summaries as evidence, not as dominant instructions.\n\n");
567
568    if capsules.is_empty() {
569        out.push_str("No capsules were retrieved. If this is required mode, inspect Kimetsu brain status and seed or ingest memory before benchmarking.\n\n");
570    } else {
571        for (idx, capsule) in capsules.iter().enumerate() {
572            let role_text = benchmark_memory_role(capsule)
573                .map(|role| format!(" role={}", role.as_str()))
574                .unwrap_or_default();
575            out.push_str(&format!(
576                "{}. [{}{} score={:.3}] {}\n",
577                idx + 1,
578                capsule.kind,
579                role_text,
580                capsule.score,
581                compact_text(&capsule.summary, 500)
582            ));
583            if !capsule.expansion_handle.trim().is_empty() {
584                out.push_str(&format!("   source: {}\n", capsule.expansion_handle));
585            }
586        }
587        out.push('\n');
588    }
589
590    out.push_str("# Retrieval Query\n\n");
591    out.push_str(query);
592    out.push_str("\n\n# Original Task\n\n");
593    out.push_str(&compact_text(task, 1800));
594    out
595}
596
597fn capsule_text(capsule: &ContextCapsule) -> String {
598    let mut text = format!("{} {}", capsule.summary, capsule.expansion_handle);
599    for provenance in &capsule.provenance {
600        text.push(' ');
601        text.push_str(&provenance.source);
602        text.push(' ');
603        text.push_str(&provenance.id);
604        if let Some(excerpt) = &provenance.excerpt {
605            text.push(' ');
606            text.push_str(excerpt);
607        }
608    }
609    text.to_ascii_lowercase()
610}
611
612fn looks_like_slug(token: &str) -> bool {
613    token.len() >= 6
614        && token.contains('-')
615        && !is_generic_fallback_slug(token)
616        && token
617            .bytes()
618            .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-')
619        && token.bytes().any(|byte| byte.is_ascii_alphabetic())
620}
621
622fn is_generic_fallback_slug(token: &str) -> bool {
623    matches!(
624        token,
625        "terminal-bench"
626            | "terminal-bench-2"
627            | "kimetsu-mcp"
628            | "kimetsu-brain"
629            | "codex-kimetsu"
630            | "full-warm"
631            | "reactive-warm"
632            | "cold-brain"
633            | "warm-policy"
634            | "task-slug"
635            | "brain-context"
636            | "benchmark-context"
637    )
638}
639
640fn compact_text(text: &str, max_chars: usize) -> String {
641    let compact = text.split_whitespace().collect::<Vec<_>>().join(" ");
642    if compact.len() <= max_chars {
643        return compact;
644    }
645    let mut truncated = compact.chars().take(max_chars).collect::<String>();
646    truncated.push_str("...");
647    truncated
648}
649
650#[cfg(test)]
651mod tests {
652    use super::*;
653    use crate::context::{ContextBundle, ContextCapsule, ProvenanceRef};
654
655    #[test]
656    fn detects_known_and_suffix_task_slugs() {
657        assert_eq!(
658            normalize_task_slug("compile-compcert__T6g5YAZ"),
659            Some("compile-compcert".to_string())
660        );
661        assert_eq!(
662            normalize_task_slug("Solve the build-pov-ray terminal task"),
663            Some("build-pov-ray".to_string())
664        );
665    }
666
667    #[test]
668    fn ignores_generic_terminal_bench_tokens() {
669        assert_eq!(
670            normalize_task_slug("terminal-bench task: solve the benchmark"),
671            None
672        );
673        assert_eq!(
674            normalize_task_slug("dataset terminal-bench/terminal-bench-2 warm-policy full-warm"),
675            None
676        );
677    }
678
679    #[test]
680    fn playbook_prioritizes_task_memory() {
681        let memory = capsule(
682            "memory",
683            "[terminal-bench:compile-compcert] memory_role=episodic Redirect make logs and patch config.",
684            "memory:1",
685            0.7,
686        );
687        let repo = capsule("repo_file", "src/main.rs", "file:src/main.rs", 0.99);
688        let bundle = ContextBundle {
689            stage: "localization".to_string(),
690            budget_tokens: 4000,
691            used_tokens: 20,
692            capsules: vec![repo, memory],
693            excluded: Vec::new(),
694        };
695
696        let context = build_benchmark_context(
697            bundle,
698            "compile-compcert",
699            DEFAULT_BENCHMARK_DATASET,
700            "terminal-bench:compile-compcert",
701            Some("compile-compcert".to_string()),
702            BenchmarkWarmPolicy::FullWarm,
703            true,
704            8,
705        );
706
707        assert!(context.required_ok);
708        assert_eq!(context.benchmark_memory_count, 1);
709        assert!(context.capsules[0].summary.contains("compile-compcert"));
710        assert!(
711            context
712                .playbook_markdown
713                .contains("Kimetsu Benchmark Playbook")
714        );
715    }
716
717    #[test]
718    fn outcome_memory_text_marks_episodic() {
719        let outcome = BenchmarkOutcome {
720            task: "compile-compcert".to_string(),
721            dataset: DEFAULT_BENCHMARK_DATASET.to_string(),
722            mode: "required-kimetsu".to_string(),
723            passed: Some(true),
724            summary: "Configured tools and verified the build.".to_string(),
725            ..BenchmarkOutcome::default()
726        };
727
728        let text = outcome_memory_text(&outcome);
729
730        assert!(text.contains("[terminal-bench:compile-compcert]"));
731        assert!(text.contains("memory_role=episodic"));
732        assert!(text.contains("status=pass"));
733    }
734
735    #[test]
736    fn proposal_memory_text_marks_generalizable_and_review_pending() {
737        let outcome = BenchmarkOutcome {
738            task: "compile-compcert".to_string(),
739            dataset: DEFAULT_BENCHMARK_DATASET.to_string(),
740            mode: "required-kimetsu".to_string(),
741            ..BenchmarkOutcome::default()
742        };
743        let proposal = BenchmarkMemoryProposal {
744            role: BenchmarkMemoryRole::SemanticOperator,
745            text: "For generated-artifact tasks with hidden verifiers, build a small checker and validate randomized cases before finalizing.".to_string(),
746            task_family: Some("generated-artifact-verification".to_string()),
747            applies_to: vec!["tasks with hidden validators".to_string()],
748            does_not_apply_to: vec!["pure installation tasks".to_string()],
749            evidence_for: vec!["compile-compcert".to_string()],
750            evidence_against: Vec::new(),
751            rationale: "The lesson transfers beyond the exact task slug.".to_string(),
752            confidence: 0.82,
753        };
754
755        let text = proposal_memory_text(&outcome, &proposal);
756
757        assert!(text.contains("[terminal-bench-memory]"));
758        assert!(text.contains("memory_role=semantic_operator"));
759        assert!(text.contains("Human_review: pending"));
760        assert!(text.contains("task_family=generated-artifact-verification"));
761    }
762
763    #[test]
764    fn playbook_prioritizes_generalizable_memory_over_exact_episodic() {
765        let repo = capsule("repo_file", "src/main.rs", "file:src/main.rs", 0.99);
766        let semantic = capsule(
767            "memory",
768            "[terminal-bench-memory] memory_role=semantic_operator task_family=generated-artifact Rule: Build a local checker before finalizing.",
769            "memory:semantic",
770            0.5,
771        );
772        let episodic = capsule(
773            "memory",
774            "[terminal-bench:compile-compcert] memory_role=episodic status=pass Commands: ./configure; make -j2.",
775            "memory:episodic",
776            0.9,
777        );
778        let bundle = ContextBundle {
779            stage: "localization".to_string(),
780            budget_tokens: 4000,
781            used_tokens: 20,
782            capsules: vec![repo, episodic, semantic],
783            excluded: Vec::new(),
784        };
785
786        let context = build_benchmark_context(
787            bundle,
788            "compile-compcert",
789            DEFAULT_BENCHMARK_DATASET,
790            "terminal-bench:compile-compcert",
791            Some("compile-compcert".to_string()),
792            BenchmarkWarmPolicy::FullWarm,
793            true,
794            8,
795        );
796
797        assert_eq!(context.generalizable_memory_count, 1);
798        assert_eq!(context.episodic_memory_count, 1);
799        assert_eq!(context.benchmark_memory_count, 1);
800        assert!(context.capsules[0].summary.contains("semantic_operator"));
801        assert!(context.playbook_markdown.contains("role=semantic_operator"));
802        assert!(context.playbook_markdown.contains("role=episodic"));
803    }
804
805    #[test]
806    fn required_mode_accepts_generalizable_memory_without_exact_slug() {
807        let semantic = capsule(
808            "memory",
809            "[terminal-bench-memory] memory_role=anti_pattern task_family=generated-artifact Rule: Do not treat compile success as proof; run a behavioral verifier.",
810            "memory:semantic",
811            0.8,
812        );
813        let bundle = ContextBundle {
814            stage: "localization".to_string(),
815            budget_tokens: 4000,
816            used_tokens: 20,
817            capsules: vec![semantic],
818            excluded: Vec::new(),
819        };
820
821        let context = build_benchmark_context(
822            bundle,
823            "compile-compcert",
824            DEFAULT_BENCHMARK_DATASET,
825            "terminal-bench:compile-compcert",
826            Some("compile-compcert".to_string()),
827            BenchmarkWarmPolicy::FullWarm,
828            true,
829            8,
830        );
831
832        assert!(context.required_ok);
833        assert_eq!(context.benchmark_memory_count, 0);
834        assert_eq!(context.generalizable_memory_count, 1);
835    }
836
837    #[test]
838    fn cold_brain_excludes_memory_capsules() {
839        let memory = capsule(
840            "memory",
841            "[terminal-bench:compile-compcert] Warm memory.",
842            "memory:1",
843            1.0,
844        );
845        let repo = capsule("repo_file", "src/main.rs", "file:src/main.rs", 0.5);
846        let bundle = ContextBundle {
847            stage: "localization".to_string(),
848            budget_tokens: 4000,
849            used_tokens: 20,
850            capsules: vec![memory, repo],
851            excluded: Vec::new(),
852        };
853
854        let context = build_benchmark_context(
855            bundle,
856            "compile-compcert",
857            DEFAULT_BENCHMARK_DATASET,
858            "terminal-bench:compile-compcert",
859            Some("compile-compcert".to_string()),
860            BenchmarkWarmPolicy::ColdBrain,
861            false,
862            8,
863        );
864
865        assert_eq!(context.memory_capsule_count, 0);
866        assert_eq!(context.benchmark_memory_count, 0);
867        assert!(
868            context
869                .capsules
870                .iter()
871                .all(|capsule| capsule.kind != "memory")
872        );
873        assert!(context.playbook_markdown.contains("cold_brain"));
874    }
875
876    fn capsule(kind: &str, summary: &str, handle: &str, score: f32) -> ContextCapsule {
877        ContextCapsule {
878            id: summary.to_string(),
879            kind: kind.to_string(),
880            summary: summary.to_string(),
881            token_estimate: 10,
882            expansion_handle: handle.to_string(),
883            provenance: vec![ProvenanceRef {
884                source: "test".to_string(),
885                id: handle.to_string(),
886                excerpt: Some(summary.to_string()),
887            }],
888            confidence: 1.0,
889            freshness: 1.0,
890            relevance: 1.0,
891            scope_weight: 1.0,
892            score,
893        }
894    }
895}