fix(context): keep generated project context ephemeral

This commit is contained in:
Hunter B
2026-06-12 05:52:00 -07:00
parent 438104510c
commit b424848f7e
4 changed files with 43 additions and 64 deletions
+3
View File
@@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`kimi-k2.7-code`, recognizes `kimi`/`kimi-k2` aliases for that model, keeps
explicit `kimi-k2.6` selectable, and adds the OpenRouter
`moonshotai/kimi-k2.7-code` registry row.
- **Ephemeral generated project context (#3058).** Opening CodeWhale in a
directory with no instruction files now keeps the bounded generated project
overview in memory instead of creating `.codewhale/instructions.md`.
- **Cursor-style activity metadata rows (#3146).** Dense successful tool-run
summaries now render as a single muted `Explored ...` / `Updated metadata`
row, include short command-family labels for successful generic verifier
+3
View File
@@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`kimi-k2.7-code`, recognizes `kimi`/`kimi-k2` aliases for that model, keeps
explicit `kimi-k2.6` selectable, and adds the OpenRouter
`moonshotai/kimi-k2.7-code` registry row.
- **Ephemeral generated project context (#3058).** Opening CodeWhale in a
directory with no instruction files now keeps the bounded generated project
overview in memory instead of creating `.codewhale/instructions.md`.
- **Cursor-style activity metadata rows (#3146).** Dense successful tool-run
summaries now render as a single muted `Explored ...` / `Updated metadata`
row, include short command-family labels for successful generic verifier
+34 -61
View File
@@ -740,22 +740,14 @@ fn load_project_context_with_parents_and_home(
}
}
// Auto-generate .codewhale/instructions.md when no context file exists anywhere.
// This avoids the per-turn filesystem scan fallback in prompts.rs that
// breaks KV prefix cache stability.
// Generate a bounded in-memory fallback when no context file exists
// anywhere. This keeps prompt shape stable without creating project-local
// `.codewhale/` files merely because CodeWhale was opened in a directory.
if !ctx.has_instructions()
&& let Some(generated) = auto_generate_context(workspace)
&& let Some(generated) = generate_ephemeral_context(workspace)
{
let mut warnings = std::mem::take(&mut ctx.warnings);
ctx = load_project_context(workspace);
warnings.extend(ctx.warnings.iter().cloned());
ctx.warnings = warnings;
if !ctx.has_instructions() {
// Loaded from the file we just wrote — use the generated content
// directly as a last resort (shouldn't normally happen).
ctx.instructions = Some(generated);
ctx.source_path = None;
}
ctx.instructions = Some(generated);
ctx.source_path = None;
}
// Load the CodeWhale-specific repo authority policy
@@ -920,44 +912,17 @@ fn load_global_agents_context(workspace: &Path, home_dir: Option<&Path>) -> Opti
None
}
/// Generate a context file from project tree + summary and write it to
/// `.codewhale/instructions.md` (or `.deepseek/instructions.md` as legacy
/// fallback). Returns the generated content on success.
fn auto_generate_context(workspace: &Path) -> Option<String> {
let codewhale_dir = workspace.join(".codewhale");
let instructions_path = codewhale_dir.join("instructions.md");
let legacy_instructions_path = workspace.join(".deepseek/instructions.md");
// Don't overwrite an existing file (check both locations)
if instructions_path.exists() || legacy_instructions_path.exists() {
return None;
}
/// Generate ephemeral context from the project tree. Returns the generated
/// content on success without writing workspace files.
fn generate_ephemeral_context(workspace: &Path) -> Option<String> {
let overview = generate_bounded_project_overview(workspace)?;
let content = format!(
"# Project Context (Auto-generated)\n\n\
> This file was automatically generated by CodeWhale.\n\
> You can edit or delete it at any time.\n\n\
Some(format!(
"# Project Context (Auto-generated, ephemeral)\n\n\
> This context was generated in memory by CodeWhale.\n\
> No .codewhale/instructions.md file was written.\n\n\
{overview}"
);
// Create .codewhale/ directory
if let Err(e) = std::fs::create_dir_all(&codewhale_dir) {
tracing::warn!("Failed to create .codewhale/ directory: {e}");
return None;
}
match std::fs::write(&instructions_path, &content) {
Ok(()) => {
tracing::info!("Auto-generated {}", instructions_path.display());
Some(content)
}
Err(e) => {
tracing::warn!("Failed to write {}: {e}", instructions_path.display());
None
}
}
))
}
/// Load a context file with size checking
@@ -1488,7 +1453,7 @@ mod tests {
}
#[test]
fn auto_generated_context_is_bounded_for_many_file_workspace() {
fn generated_context_is_bounded_and_ephemeral_for_many_file_workspace() {
let workspace = tempdir().expect("workspace tempdir");
let home = tempdir().expect("home tempdir");
let noisy = workspace.path().join("aaa-many-files");
@@ -1513,9 +1478,17 @@ mod tests {
assert!(ctx.has_instructions());
let generated_path = workspace.path().join(".codewhale").join("instructions.md");
assert_eq!(ctx.source_path.as_deref(), Some(generated_path.as_path()));
let generated = fs::read_to_string(&generated_path).expect("read generated");
assert!(generated.contains("Project Context (Auto-generated)"));
assert_eq!(ctx.source_path, None);
assert!(
!generated_path.exists(),
"generated project context should stay ephemeral"
);
assert!(
!workspace.path().join(".codewhale").exists(),
"loading context should not create a .codewhale directory"
);
let generated = ctx.instructions.as_ref().expect("generated instructions");
assert!(generated.contains("Project Context (Auto-generated, ephemeral)"));
assert!(generated.contains("Bounded Project Overview"));
assert!(!generated.contains("<project_context_pack>"));
assert!(
@@ -1613,7 +1586,7 @@ mod tests {
}
#[test]
fn cached_context_regenerates_after_auto_generated_context_is_deleted() {
fn cached_generated_context_stays_ephemeral() {
crate::project_context_cache::clear();
let workspace = tempdir().expect("workspace tempdir");
let home = tempdir().expect("home tempdir");
@@ -1622,17 +1595,17 @@ mod tests {
load_project_context_with_parents_cached_and_home(workspace.path(), Some(home.path()));
assert!(first.has_instructions());
let generated_path = workspace.path().join(".codewhale").join("instructions.md");
assert!(generated_path.is_file(), "expected generated instructions");
fs::remove_file(&generated_path).expect("remove generated instructions");
assert!(!generated_path.exists());
assert!(
!generated_path.exists(),
"first load should not write generated instructions"
);
let second =
load_project_context_with_parents_cached_and_home(workspace.path(), Some(home.path()));
assert!(second.has_instructions());
assert!(
generated_path.is_file(),
"cache hit under the missing-file signature would skip regeneration"
!generated_path.exists(),
"cached generated context should remain in memory-only state"
);
}
@@ -1937,7 +1910,7 @@ mod tests {
ctx.instructions
.as_ref()
.unwrap()
.contains("Project Context (Auto-generated)")
.contains("Project Context (Auto-generated, ephemeral)")
);
}
}
+3 -3
View File
@@ -1088,9 +1088,9 @@ pub fn system_prompt_for_mode_with_context_skills_session_and_approval(
};
// 12. Mode prompt + project context.
// `load_project_context_with_parents` auto-generates .codewhale/instructions.md
// (or .deepseek/instructions.md as fallback) when no context file exists,
// so the fallback should always be available.
// `load_project_context_with_parents` generates an in-memory bounded
// overview when no context file exists, so the fallback should usually be
// available without writing project-local files.
let mut full_prompt = if let Some(project_block) = project_context.as_system_block() {
format!("{mode_prompt}\n\n{project_block}")
} else {