use crate::core::config::CompressionLevel;
use crate::core::protocol::CrpMode;
use crate::core::rules_canonical::{self as rc, Wrapper};
pub mod solution;
pub use solution::solution_ladder_text;
const INSTRUCTION_CAP_TOKENS: usize = 800;
#[cfg(test)]
const STATIC_INSTRUCTION_BUDGET_TOKENS: usize = 665;
#[cfg(test)]
const STATIC_INSTRUCTION_BUDGET_TDD_TOKENS: usize = 795;
#[cfg(all(test, windows))]
const STATIC_INSTRUCTION_SHELL_HINT_TOKENS: usize = 25;
#[cfg(all(test, not(windows)))]
const STATIC_INSTRUCTION_SHELL_HINT_TOKENS: usize = 0;
#[must_use]
pub fn build_instructions(crp_mode: CrpMode) -> String {
build_instructions_with_client(crp_mode, "")
}
#[must_use]
pub fn build_instructions_with_client(crp_mode: CrpMode, client_name: &str) -> String {
build_instructions_with_client_and_optional_session(crp_mode, client_name, None)
}
#[must_use]
pub fn build_instructions_with_client_and_session(
crp_mode: CrpMode,
client_name: &str,
session: &crate::core::session::SessionState,
) -> String {
build_instructions_with_client_and_optional_session(crp_mode, client_name, Some(session))
}
fn client_instruction_char_cap(client_name: &str) -> Option<usize> {
let id = crate::core::client_capabilities::ClientMcpCapabilities::detect(client_name).client_id;
crate::core::client_constraints::by_client_id(&id).and_then(|c| c.mcp_instructions_max_chars)
}
fn trim_to_char_budget(s: &str, budget: usize) -> String {
if s.chars().count() <= budget {
return s.to_string();
}
let best = s
.char_indices()
.enumerate()
.take_while(|(n, _)| *n <= budget)
.filter(|(_, (_, c))| *c == '\n')
.map(|(_, (i, _))| i)
.last()
.unwrap_or(0);
if best == 0 {
return s.chars().take(budget).collect();
}
s[..best].trim_end().to_string()
}
fn build_instructions_with_client_and_optional_session(
crp_mode: CrpMode,
client_name: &str,
session: Option<&crate::core::session::SessionState>,
) -> String {
let cfg = crate::core::config::Config::load();
let minimal = cfg.minimal_overhead_effective_for_client(client_name);
let shadow = cfg.shadow_mode;
let level = if client_loads_compression_from_file(client_name) {
CompressionLevel::Off
} else {
CompressionLevel::effective(&cfg)
};
let persona_block = crate::core::persona::Persona::resolve(&cfg).prompt_block();
build_full_instructions(
crp_mode,
client_name,
minimal,
level,
shadow,
&persona_block,
session,
)
}
#[must_use]
pub fn claude_code_static_instructions_for_test() -> String {
build_full_instructions(
CrpMode::Off,
"",
true,
CompressionLevel::Off,
false,
"",
None,
)
}
#[must_use]
pub fn build_instructions_for_test(crp_mode: CrpMode) -> String {
let shadow = false;
let level = CompressionLevel::effective(&crate::core::config::Config::load());
let tp =
crate::core::tool_profiles::ToolProfile::from_config(&crate::core::config::Config::load());
let skeleton = rc::render(shadow, Wrapper::Bare, level, &tp);
let shell_hint = build_shell_hint();
let base = format!(
"{skeleton}\n\
{shell_hint}\n\
{decoder_block}\n\
{origin}",
decoder_block =
crate::core::protocol::instruction_decoder_block(matches!(crp_mode, CrpMode::Tdd)),
origin = crate::core::integrity::origin_line(),
);
match crp_mode_suffix(crp_mode) {
"" => base,
crp => format!("{base}\n\n{crp}"),
}
}
#[must_use]
pub fn build_instructions_with_client_for_compiler(
crp_mode: CrpMode,
client_name: &str,
_unified_tool_mode: bool,
) -> String {
let tp = crate::core::tool_visibility::guidance_profile(
&crate::core::config::Config::load(),
client_name,
);
let skeleton = rc::render(true, Wrapper::Bare, CompressionLevel::Off, &tp);
let shell_hint = build_shell_hint();
let base = format!(
"{skeleton}\n\
{shell_hint}\n\
{decoder_block}\n\
{origin}",
decoder_block =
crate::core::protocol::instruction_decoder_block(matches!(crp_mode, CrpMode::Tdd)),
origin = crate::core::integrity::origin_line(),
);
match crp_mode_suffix(crp_mode) {
"" => base,
crp => format!("{base}\n\n{crp}"),
}
}
#[must_use]
pub fn claude_config_dir_display() -> String {
match std::env::var("CLAUDE_CONFIG_DIR") {
Ok(dir) if !dir.trim().is_empty() => {
let dir = dir.trim().to_string();
if dir.starts_with('~') {
dir
} else if let Some(home) = dirs::home_dir() {
let home_str = home.to_string_lossy();
if let Some(rest) = dir.strip_prefix(home_str.as_ref()) {
format!("~{rest}")
} else {
dir
}
} else {
dir
}
}
_ => "~/.claude".to_string(),
}
}
fn build_full_instructions(
crp_mode: CrpMode,
client_name: &str,
minimal: bool,
level: CompressionLevel,
shadow: bool,
persona_block: &str,
session: Option<&crate::core::session::SessionState>,
) -> String {
let profile = crate::core::litm::LitmProfile::from_client_name(client_name);
let loaded_session = (!minimal).then_some(session).flatten();
let (session_block, litm_end_block) = match loaded_session {
Some(ref session) => {
let mut positioned = crate::core::litm::position_optimize(session);
positioned.enforce_token_budget(crate::core::litm::active_session_budget());
let begin = format!(
"\n\n--- ACTIVE SESSION (LITM P1: begin position, profile: {}) ---\n{}\n---\n",
profile.name, positioned.begin_block
);
let end = if positioned.end_block.is_empty() {
String::new()
} else {
format!(
"\n--- SESSION RESUME (post-compaction) ---\n{}\n---\n",
positioned.end_block
)
};
(begin, end)
}
None => (String::new(), String::new()),
};
let project_root_for_blocks = if minimal {
None
} else {
loaded_session
.as_ref()
.and_then(|s| s.project_root.clone())
.or_else(|| {
std::env::current_dir()
.ok()
.map(|p| p.to_string_lossy().to_string())
})
};
let knowledge_block = match &project_root_for_blocks {
Some(root) => {
let knowledge = crate::core::knowledge::ProjectKnowledge::load(root);
match knowledge {
Some(k) if !k.facts.is_empty() || !k.patterns.is_empty() => {
let memory = k.format_aaak();
if memory.is_empty() {
String::new()
} else {
format!("\n--- PROJECT MEMORY (AAAK) ---\n{}\n---\n", memory.trim())
}
}
_ => String::new(),
}
}
None => String::new(),
};
let gotcha_block = match &project_root_for_blocks {
Some(root) => {
let store = crate::core::gotcha_tracker::GotchaStore::load(root);
let files: Vec<String> = loaded_session
.as_ref()
.map(|s| s.files_touched.iter().map(|ft| ft.path.clone()).collect())
.unwrap_or_default();
let block = store.format_injection_block(&files);
if block.is_empty() {
String::new()
} else {
format!("\n{block}\n")
}
}
None => String::new(),
};
let health_block = match &project_root_for_blocks {
Some(root) => {
let block = crate::core::code_health::persist::format_session_block(root);
if block.is_empty() {
String::new()
} else {
format!("\n{block}\n")
}
}
None => String::new(),
};
let shell_hint = build_shell_hint();
let cfg = crate::core::config::Config::load();
let tool_profile = crate::core::tool_visibility::guidance_profile(&cfg, client_name);
let is_shadow_only = is_shadow_surface_active();
let skeleton = if client_loads_rules_from_file(client_name) {
let anchor = if client_is_hook_covered(client_name) {
if is_shadow_only {
shadow_only_anchor(&tool_profile)
} else {
hook_covered_anchor(&tool_profile)
}
} else {
skeleton_anchor(&tool_profile)
};
let compression = rc::compression_text(level);
if compression.is_empty() {
anchor
} else {
format!("{anchor}\n{compression}")
}
} else {
let effective_shadow = shadow && client_is_hook_covered(client_name);
rc::render(effective_shadow, Wrapper::Bare, level, &tool_profile)
};
let config_dir = claude_config_dir_display();
let persona_section = if persona_block.is_empty() {
String::new()
} else {
format!("\n{persona_block}")
};
let solution_ladder = {
let loaded_config = crate::core::config::Config::load();
let sol_cfg = &loaded_config.solution;
let is_subagent = crate::core::conversation::is_subagent_context();
let inject_solution = if is_subagent {
sol_cfg.inject_in_subagents
} else {
sol_cfg.inject_in_instructions
};
if sol_cfg.enabled && inject_solution {
let ladder =
crate::instructions::solution::solution_ladder_text(sol_cfg.effective_intensity());
if ladder.is_empty() {
String::new()
} else {
format!("\n{ladder}\n")
}
} else {
String::new()
}
};
let base = format!(
"{skeleton}\n\
{persona_section}\
{shell_hint}\n\
{decoder_block}\n\
Full instructions at {config_dir}/CLAUDE.md\n\
{session_block}\n\
{knowledge_block}\n\
{gotcha_block}\n\
{health_block}\n\
{origin}\n\
{litm_end_block}",
decoder_block =
crate::core::protocol::instruction_decoder_block(matches!(crp_mode, CrpMode::Tdd)),
origin = crate::core::integrity::origin_line(),
litm_end_block = litm_end_block
);
let guidance_suffix = format!("{}{}", crp_mode_suffix(crp_mode), solution_ladder);
let base = match client_instruction_char_cap(client_name) {
Some(max) => {
let suffix_chars = guidance_suffix.trim_end_matches('\n').chars().count();
trim_to_char_budget(&base, max.saturating_sub(suffix_chars + 2))
}
None => base,
};
assemble_within_cap(&base, &guidance_suffix, INSTRUCTION_CAP_TOKENS)
}
fn crp_mode_suffix(crp_mode: CrpMode) -> &'static str {
match crp_mode {
CrpMode::Off => "",
CrpMode::Compact => {
"CRP MODE: compact — omit filler; abbreviate fn,cfg,impl,deps,req,res; \
diff lines (+/-) only; <=200 tok; trust tool outputs."
}
CrpMode::Tdd => {
"CRP MODE: tdd — max density; Fn refs + diff lines only \
(+F1:42 | -F1:10-15 | ~F1:42 old->new); <=150 tok; zero narration."
}
}
}
fn assemble_within_cap(base: &str, suffix: &str, cap_tokens: usize) -> String {
use crate::core::tokens::count_tokens;
let suffix = suffix.trim_end_matches('\n');
if suffix.is_empty() {
let full = base.to_string();
return if count_tokens(&full) > cap_tokens {
truncate_to_token_cap(&full, cap_tokens)
} else {
full
};
}
let full = format!("{base}\n\n{suffix}");
if count_tokens(&full) <= cap_tokens {
return full;
}
let suffix_tokens = count_tokens(suffix);
let Some(base_budget) = cap_tokens.checked_sub(suffix_tokens + 1) else {
return truncate_to_token_cap(&full, cap_tokens);
};
let trimmed_base = truncate_to_token_cap(base, base_budget);
format!("{trimmed_base}\n\n{suffix}")
}
fn truncate_to_token_cap(s: &str, cap_tokens: usize) -> String {
use crate::core::tokens::count_tokens;
if count_tokens(s) <= cap_tokens {
return s.to_string();
}
let cuts: Vec<usize> = s.match_indices('\n').map(|(i, _)| i).collect();
let (mut lo, mut hi) = (0usize, cuts.len());
let mut best: Option<usize> = None;
while lo < hi {
let mid = lo + (hi - lo) / 2;
let end = cuts[mid];
if end > 0 && count_tokens(&s[..end]) <= cap_tokens {
best = Some(end);
lo = mid + 1;
} else {
hi = mid;
}
}
if let Some(end) = best {
return s[..end].to_string();
}
let byte_approx = cap_tokens * 4;
let safe = s.floor_char_boundary(byte_approx.min(s.len()));
s[..safe].to_string()
}
#[must_use]
pub fn claude_code_instructions() -> String {
build_instructions(CrpMode::Off)
}
fn skeleton_anchor(_tp: &crate::core::tool_profiles::ToolProfile) -> String {
SKELETON_ANCHOR.into()
}
fn hook_covered_anchor(tp: &crate::core::tool_profiles::ToolProfile) -> String {
let mut s =
String::from("lean-ctx active — hooks compress native Shell/Read/Grep transparently");
if tp.is_tool_enabled("ctx_compose") {
s.push_str("; call ctx_compose to orient");
}
if tp.is_tool_enabled("ctx_session") || tp.is_tool_enabled("ctx_knowledge") {
s.push_str(", ctx_search(action=semantic) / ctx_knowledge for meaning & memory");
}
s.push('.');
s
}
fn shadow_only_anchor(tp: &crate::core::tool_profiles::ToolProfile) -> String {
let mut s =
String::from("lean-ctx active — hooks compress native Shell/Read/Grep transparently");
let has_exclusive = tp.is_tool_enabled("ctx_compose")
|| tp.is_tool_enabled("ctx_knowledge")
|| tp.is_tool_enabled("ctx_session");
if has_exclusive {
s.push_str("; use ctx_call for compose/knowledge/session");
}
s.push('.');
s
}
fn is_shadow_surface_active() -> bool {
let cfg = crate::core::config::Config::load();
match cfg.tool_surface.as_deref() {
Some("shadow") => true,
Some("mcp") => false,
_ => cfg.shadow_mode, }
}
const SKELETON_ANCHOR: &str = "lean-ctx active — your auto-loaded lean-ctx rules apply: \
ctx_* tools replace native Read/Grep/Shell/Glob.";
fn client_loads_compression_from_file(client_name: &str) -> bool {
crate::core::home::resolve_home_dir().is_some_and(|home| {
crate::core::rules_channel::client_autoloads_compression(client_name, &home)
})
}
fn client_loads_rules_from_file(client_name: &str) -> bool {
crate::core::home::resolve_home_dir()
.is_some_and(|home| crate::core::rules_channel::client_autoloads_rules(client_name, &home))
}
fn client_is_hook_covered(client_name: &str) -> bool {
crate::core::home::resolve_home_dir()
.is_some_and(|home| crate::core::rules_channel::client_hook_covered(client_name, &home))
}
fn build_shell_hint() -> String {
if !cfg!(windows) {
return String::new();
}
shell_hint_text(&crate::shell::shell_name())
}
pub(crate) fn shell_hint_text(name: &str) -> String {
let is_posix = matches!(name, "bash" | "sh" | "zsh" | "fish");
if is_posix {
format!("\nSHELL: {name} (POSIX) — no PowerShell cmdlets.\n")
} else if name.contains("powershell") || name.contains("pwsh") {
format!("\nSHELL: {name}. Use PowerShell cmdlets.\n")
} else {
format!("\nSHELL: {name}.\n")
}
}
#[cfg(test)]
pub(crate) fn max_shell_hint_tokens() -> usize {
const BUDGETED_SHELL_NAMES: &[&str] = &[
"powershell",
"pwsh",
"cmd",
"bash",
"sh",
"zsh",
"fish",
"git-bash",
];
BUDGETED_SHELL_NAMES
.iter()
.map(|name| crate::core::tokens::count_tokens(&shell_hint_text(name)))
.max()
.unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::tokens::count_tokens;
#[test]
fn default_prefix_is_byte_identical_across_sessions() {
use crate::core::session::SessionState;
fn busy_session() -> SessionState {
let mut s = SessionState::default();
s.set_task("refactor the payment retry loop", Some("edit"));
s.touch_file("src/payments/retry.rs", None, "full", 812);
s.add_finding(
Some("src/payments/retry.rs"),
Some(42),
"backoff ignores jitter",
);
s.add_decision("keep exponential backoff", None);
s
}
fn prefix(session: &SessionState) -> String {
let tools = serde_json::to_string(
&crate::core::tool_visibility::advertised_tool_defs_default(),
)
.expect("tool defs serialize");
let instructions = build_instructions_with_client_and_session(
CrpMode::effective(),
"claude-code",
session,
);
format!(
"{tools}\n{instructions}\n{}",
crate::rules_inject::canonical_rules_block()
)
}
let fresh = {
let _iso = crate::core::data_dir::isolated_data_dir();
prefix(&SessionState::default())
};
let busy = {
let _iso = crate::core::data_dir::isolated_data_dir();
prefix(&busy_session())
};
assert!(fresh == busy, "default prefix depends on session state");
let _iso = crate::core::data_dir::isolated_data_dir();
let render = |s: &SessionState| {
build_full_instructions(
CrpMode::Off,
"",
false,
CompressionLevel::Off,
false,
"",
Some(s),
)
};
assert_ne!(render(&SessionState::default()), render(&busy_session()));
}
#[test]
fn char_budget_cuts_at_line_ends_counting_chars() {
let line = "→".repeat(10); let text = format!("{line}\r\n{line}\r\n{line}");
assert_eq!(
trim_to_char_budget(&text, 25),
format!("{line}\r\n{line}"),
"CRLF-safe, whole lines"
);
assert_eq!(trim_to_char_budget(&text, 15), line);
assert_eq!(
trim_to_char_budget(&text, 100),
text,
"under budget untouched"
);
assert_eq!(
trim_to_char_budget(&"é".repeat(50), 7),
"é".repeat(7),
"one long line"
);
assert_eq!(client_instruction_char_cap("claude-code"), Some(2048));
assert_eq!(client_instruction_char_cap("cursor"), None);
}
#[test]
fn guidance_suffix_survives_oversized_base() {
let base = "SESSION LINE\n".repeat(4000);
let suffix = "OUTPUT STYLE: expert-terse\nFn refs only, diff lines only.";
let out = assemble_within_cap(&base, suffix, INSTRUCTION_CAP_TOKENS);
assert!(out.contains("OUTPUT STYLE: expert-terse"));
assert!(count_tokens(&out) <= INSTRUCTION_CAP_TOKENS);
assert!(out.len() < base.len());
}
#[test]
fn empty_client_never_dedups_compression() {
assert!(!client_loads_compression_from_file(""));
assert!(!client_loads_compression_from_file("totally-unknown-agent"));
}
#[test]
fn live_session_instructions_include_only_the_owned_session() {
let mut current = crate::core::session::SessionState::new();
current.add_finding(None, None, "current-instance-marker");
let mut other = crate::core::session::SessionState::new();
other.add_finding(None, None, "other-instance-marker");
let out = build_full_instructions(
CrpMode::Off,
"",
false,
CompressionLevel::Off,
false,
"",
Some(¤t),
);
assert!(out.contains("current-instance-marker"));
assert!(!out.contains("other-instance-marker"));
assert!(!out.contains("never echo tool output"));
}
#[test]
fn covered_client_gets_anchor_instead_of_skeleton() {
let _guard = crate::core::data_dir::test_env_lock();
let tmp = tempfile::tempdir().unwrap();
let home = tmp.path();
std::fs::create_dir_all(home.join(".cursor/rules")).unwrap();
let cfg = crate::core::config::Config::load();
let shadow = cfg.shadow_mode;
let tp = crate::core::tool_profiles::ToolProfile::Power;
std::fs::write(
home.join(".cursor/rules/lean-ctx.mdc"),
rc::render(
shadow,
Wrapper::Dedicated,
crate::core::config::CompressionLevel::Standard,
&tp,
),
)
.unwrap();
let old_home = std::env::var("HOME").ok();
crate::test_env::set_var("HOME", home);
crate::test_env::set_var("LEAN_CTX_MINIMAL", "1");
let covered = build_instructions_with_client(CrpMode::Off, "cursor");
let uncovered = build_instructions_with_client(CrpMode::Off, "some-other-agent");
if let Some(h) = old_home {
crate::test_env::set_var("HOME", h);
} else {
crate::test_env::remove_var("HOME");
}
crate::test_env::remove_var("LEAN_CTX_MINIMAL");
assert!(
covered.contains(SKELETON_ANCHOR),
"covered client must get the anchor:\n{covered}"
);
if shadow {
assert!(
!covered.contains("auto-route"),
"covered shadow client must not re-pay the shadow nudge:\n{covered}"
);
} else {
assert!(
!covered.contains("lean-ctx routing"),
"covered client must not re-pay the skeleton:\n{covered}"
);
}
assert!(
!covered.contains("OUTPUT STYLE:"),
"covered client must not re-pay the compression prompt:\n{covered}"
);
if shadow {
assert!(
!uncovered.contains("auto-route"),
"#987: uncovered client WITHOUT hooks must NOT get shadow nudge:\n{uncovered}"
);
assert!(
uncovered.contains("lean-ctx routing") || uncovered.contains("ctx_"),
"uncovered client without hooks gets explicit tool mapping:\n{uncovered}"
);
} else {
assert!(
uncovered.contains("lean-ctx routing"),
"uncovered client keeps the full skeleton:\n{uncovered}"
);
}
eprintln!(
"instructions footprint: covered={} tok, uncovered={} tok",
count_tokens(&covered),
count_tokens(&uncovered)
);
assert!(
count_tokens(&covered) < count_tokens(&uncovered),
"anchor path must be strictly cheaper"
);
}
#[test]
fn hook_covered_client_gets_hook_aware_anchor() {
let _guard = crate::core::data_dir::test_env_lock();
let tmp = tempfile::tempdir().unwrap();
let home = tmp.path();
std::fs::create_dir_all(home.join(".cursor/rules")).unwrap();
let tp = crate::core::tool_profiles::ToolProfile::Power;
std::fs::write(
home.join(".cursor/rules/lean-ctx.mdc"),
rc::render(
false,
Wrapper::HookCovered,
crate::core::config::CompressionLevel::Off,
&tp,
),
)
.unwrap();
std::fs::write(
home.join(".cursor/hooks.json"),
r#"{"version":1,"hooks":{"preToolUse":[
{"matcher":"Shell","command":"/usr/local/bin/lean-ctx hook rewrite"},
{"matcher":"Read|Grep","command":"/usr/local/bin/lean-ctx hook redirect"}
]}}"#,
)
.unwrap();
let old_home = std::env::var("HOME").ok();
crate::test_env::set_var("HOME", home);
crate::test_env::set_var("LEAN_CTX_MINIMAL", "1");
let covered = build_instructions_with_client(CrpMode::Off, "cursor");
if let Some(h) = old_home {
crate::test_env::set_var("HOME", h);
} else {
crate::test_env::remove_var("HOME");
}
crate::test_env::remove_var("LEAN_CTX_MINIMAL");
assert!(
covered.contains("hooks compress native Shell/Read/Grep"),
"hook-covered client must get the hook-aware anchor:\n{covered}"
);
assert!(
!covered.contains("ctx_* tools replace native")
&& !covered.contains("lean-ctx routing"),
"hook-covered client must not carry the replace-native wording:\n{covered}"
);
}
#[test]
fn non_coding_persona_block_lands_in_instructions() {
let _guard = crate::core::data_dir::test_env_lock();
crate::test_env::set_var("LEAN_CTX_MINIMAL", "1");
crate::test_env::set_var("LEAN_CTX_PERSONA", "research");
let research = build_instructions_with_client(CrpMode::Off, "");
crate::test_env::set_var("LEAN_CTX_PERSONA", "coding");
let coding = build_instructions_with_client(CrpMode::Off, "");
crate::test_env::remove_var("LEAN_CTX_PERSONA");
crate::test_env::remove_var("LEAN_CTX_MINIMAL");
assert!(
research.contains("PERSONA: research"),
"research persona must announce its domain block:\n{research}"
);
assert!(
research.contains("INTENTS: explore, summarize, compare, cite, synthesize"),
"research persona must carry its intent vocabulary:\n{research}"
);
assert!(
!coding.contains("PERSONA:"),
"the coding default must keep the instructions byte-stable (#498):\n{coding}"
);
}
#[test]
fn under_cap_keeps_everything() {
let base = "tool mapping block";
let suffix = "OUTPUT STYLE: dense";
let out = assemble_within_cap(base, suffix, INSTRUCTION_CAP_TOKENS);
assert!(out.contains(base));
assert!(out.contains(suffix));
}
#[test]
fn empty_suffix_caps_base_only() {
let base = "x\n".repeat(4000);
let out = assemble_within_cap(&base, "", INSTRUCTION_CAP_TOKENS);
assert!(count_tokens(&out) <= INSTRUCTION_CAP_TOKENS);
}
#[cfg(windows)]
#[test]
fn shell_hint_stays_within_its_budget() {
let hint = build_shell_hint();
let tokens = count_tokens(&hint);
assert!(
tokens <= STATIC_INSTRUCTION_SHELL_HINT_TOKENS,
"shell hint = {tokens} tok, budget {STATIC_INSTRUCTION_SHELL_HINT_TOKENS}: {hint}"
);
}
#[test]
fn minimal_overhead_instructions_stay_within_budget() {
const MINIMAL_INSTRUCTION_BUDGET_TOKENS: usize =
STATIC_INSTRUCTION_BUDGET_TDD_TOKENS + STATIC_INSTRUCTION_SHELL_HINT_TOKENS;
let _iso = crate::core::data_dir::isolated_data_dir();
crate::test_env::set_var("LEAN_CTX_MINIMAL", "1");
let out = build_instructions(CrpMode::Compact);
crate::test_env::remove_var("LEAN_CTX_MINIMAL");
let tokens = count_tokens(&out);
assert!(
tokens <= MINIMAL_INSTRUCTION_BUDGET_TOKENS,
"minimal-overhead instructions = {tokens} tok, budget {MINIMAL_INSTRUCTION_BUDGET_TOKENS}\n---\n{out}\n---"
);
}
#[test]
fn static_skeleton_stays_within_budget() {
let _iso = crate::core::data_dir::isolated_data_dir();
crate::test_env::set_var("LEAN_CTX_COMPRESSION", "off");
for (mode, base_budget) in [
(CrpMode::Off, STATIC_INSTRUCTION_BUDGET_TOKENS),
(CrpMode::Compact, STATIC_INSTRUCTION_BUDGET_TOKENS),
(CrpMode::Tdd, STATIC_INSTRUCTION_BUDGET_TDD_TOKENS),
] {
let budget = base_budget + STATIC_INSTRUCTION_SHELL_HINT_TOKENS;
let out = build_instructions_for_test(mode);
let tokens = count_tokens(&out);
assert!(
tokens <= budget,
"static instructions for {mode:?} = {tokens} tok, budget {budget}\n---\n{out}\n---"
);
}
crate::test_env::remove_var("LEAN_CTX_COMPRESSION");
}
}