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