From d89940e85bc4e4aa84dd26f8cc65b908ad107f5e Mon Sep 17 00:00:00 2001 From: Timmy Date: Fri, 15 May 2026 07:48:18 +0100 Subject: [PATCH] fix: drop source-map.json from agent orientation bundle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The orientation bundle was 96 KB per coder spawn with 85 KB of that being source-map.json — a static symbol listing that drowned out the workflow rules in AGENT.md and likely explains why PLAN.md ceremony is being skipped (the instruction is ~5% of the bundle, buried under a wall of symbols). Agents are excellent at grep on demand, so the source map adds little value as a preloaded cheat sheet. File stays on disk for the merge-time source-map-check doc-coverage gate. Co-Authored-By: Claude Opus 4.7 (1M context) --- server/src/agents/local_prompt.rs | 158 +++--------------------------- 1 file changed, 16 insertions(+), 142 deletions(-) diff --git a/server/src/agents/local_prompt.rs b/server/src/agents/local_prompt.rs index e848617e..cbdf4873 100644 --- a/server/src/agents/local_prompt.rs +++ b/server/src/agents/local_prompt.rs @@ -10,10 +10,12 @@ //! - `.huskies/README.md` //! - `.huskies/specs/00_CONTEXT.md` //! - `.huskies/AGENT.md` -//! - `.huskies/source-map.json` (up to 200 KB; truncated with a log if larger) //! -//! `STACK.md` is intentionally excluded — it is large and changes often; agents -//! should grep it on demand. +//! `STACK.md` and `.huskies/source-map.json` are intentionally excluded — they +//! are large and change often; agents should grep on demand instead. Earlier +//! versions of this bundle inlined the source map, which ballooned the orientation +//! to ~96 KB and drowned out the workflow rules in AGENT.md; the file is still +//! kept on disk for the merge-time `source-map-check` doc-coverage gate. //! //! Behaviour contract: //! - Files that are missing or empty are skipped silently (no error, no section). @@ -33,12 +35,6 @@ const ORIENTATION_FILES: &[&str] = &[ ".huskies/AGENT.md", ]; -/// Path to the source map (relative to project root), appended after AGENT.md. -const SOURCE_MAP_REL: &str = ".huskies/source-map.json"; - -/// Maximum bytes of source-map content to embed in the prompt. -const SOURCE_MAP_BYTE_CAP: usize = 200 * 1024; - /// Attempt to load the project-local agent prompt by concatenating orientation /// files from the project root. /// @@ -60,14 +56,11 @@ pub fn read_project_local_prompt(project_root: &Path) -> Option { sections.push((rel_path, trimmed.to_string())); } - // Read source-map.json (after AGENT.md) with a byte cap. - let source_map_content = read_source_map_section(project_root); - - if sections.is_empty() && source_map_content.is_none() { + if sections.is_empty() { return None; } - let mut included_files: Vec<&str> = sections.iter().map(|(name, _)| *name).collect(); + let included_files: Vec<&str> = sections.iter().map(|(name, _)| *name).collect(); let mut bundle = String::new(); for (i, (name, content)) in sections.iter().enumerate() { if i > 0 { @@ -77,15 +70,6 @@ pub fn read_project_local_prompt(project_root: &Path) -> Option { bundle.push_str(content); } - if let Some(sm) = source_map_content { - if !bundle.is_empty() { - bundle.push('\n'); - } - bundle.push_str(&format!("=== {SOURCE_MAP_REL} ===\n")); - bundle.push_str(&sm); - included_files.push(SOURCE_MAP_REL); - } - crate::slog!( "[agents] orientation bundle: {} bytes, files: [{}]", bundle.len(), @@ -95,39 +79,6 @@ pub fn read_project_local_prompt(project_root: &Path) -> Option { Some(bundle) } -/// Read `.huskies/source-map.json` from `project_root`, applying a byte cap. -/// -/// Returns `None` when the file is absent, unreadable, or empty. -/// When the content exceeds [`SOURCE_MAP_BYTE_CAP`], truncates at a char -/// boundary and logs the truncation. -#[allow(clippy::string_slice)] // cap is walked back to a char boundary before slicing -fn read_source_map_section(project_root: &Path) -> Option { - let path = project_root.join(SOURCE_MAP_REL); - let Ok(content) = std::fs::read_to_string(&path) else { - return None; - }; - let trimmed = content.trim(); - if trimmed.is_empty() { - return None; - } - if trimmed.len() > SOURCE_MAP_BYTE_CAP { - let mut cap = SOURCE_MAP_BYTE_CAP; - while cap > 0 && !trimmed.is_char_boundary(cap) { - cap -= 1; - } - crate::slog!( - "[agents] source-map.json truncated: {} bytes > {} byte cap; \ - including first {} bytes", - trimmed.len(), - SOURCE_MAP_BYTE_CAP, - cap - ); - Some(trimmed[..cap].to_string()) - } else { - Some(trimmed.to_string()) - } -} - #[cfg(test)] mod tests { use super::*; @@ -310,10 +261,13 @@ mod tests { ); } - // ── source-map.json tests ──────────────────────────────────────────────── + // ── source-map.json must NOT be inlined into the bundle ────────────────── + // The file is kept on disk for the merge-time source-map-check gate, but + // inlining it into every agent spawn ballooned the orientation past 96 KB + // and drowned out the workflow rules in AGENT.md. #[test] - fn source_map_included_after_agent_md() { + fn source_map_not_included_even_when_present() { let tmp = tempfile::tempdir().unwrap(); write_file(tmp.path(), ".huskies/AGENT.md", "agent content"); write_file( @@ -324,92 +278,12 @@ mod tests { let result = read_project_local_prompt(tmp.path()).unwrap(); assert!( - result.contains("=== .huskies/source-map.json ==="), - "source-map delimiter must be present: {result}" + !result.contains("=== .huskies/source-map.json ==="), + "source-map must not appear as an orientation section: {result}" ); assert!( - result.contains(r#""src/lib.rs""#), - "source-map content must be present: {result}" - ); - // source-map section must appear after AGENT.md section - let agent_pos = result.find("=== .huskies/AGENT.md ===").unwrap(); - let sm_pos = result.find("=== .huskies/source-map.json ===").unwrap(); - assert!( - sm_pos > agent_pos, - "source-map section must come after AGENT.md section" - ); - } - - #[test] - fn source_map_missing_skipped_silently() { - let tmp = tempfile::tempdir().unwrap(); - write_file(tmp.path(), ".huskies/AGENT.md", "agent content"); - // source-map.json intentionally absent - - let result = read_project_local_prompt(tmp.path()).unwrap(); - assert!( - !result.contains("source-map.json"), - "absent source-map must not create a section: {result}" - ); - } - - #[test] - fn source_map_empty_skipped_silently() { - let tmp = tempfile::tempdir().unwrap(); - write_file(tmp.path(), ".huskies/AGENT.md", "agent content"); - write_file(tmp.path(), ".huskies/source-map.json", ""); - - let result = read_project_local_prompt(tmp.path()).unwrap(); - assert!( - !result.contains("source-map.json"), - "empty source-map must not create a section: {result}" - ); - } - - #[test] - fn source_map_only_returns_some() { - let tmp = tempfile::tempdir().unwrap(); - // Only source-map.json present; all orientation files absent. - write_file( - tmp.path(), - ".huskies/source-map.json", - r#"{"src/main.rs": {}}"#, - ); - - let result = read_project_local_prompt(tmp.path()); - assert!( - result.is_some(), - "source-map alone must produce Some bundle" - ); - assert!( - result.unwrap().contains("=== .huskies/source-map.json ==="), - "bundle must contain source-map section" - ); - } - - #[test] - #[allow(clippy::string_slice)] // sm_start is derived from str::find — always a char boundary - fn source_map_truncated_at_byte_cap() { - let tmp = tempfile::tempdir().unwrap(); - write_file(tmp.path(), ".huskies/AGENT.md", "agent"); - // Build content larger than SOURCE_MAP_BYTE_CAP (200 KB). - let big = "x".repeat(SOURCE_MAP_BYTE_CAP + 1024); - write_file(tmp.path(), ".huskies/source-map.json", &big); - - let result = read_project_local_prompt(tmp.path()).unwrap(); - assert!( - result.contains("=== .huskies/source-map.json ==="), - "truncated source-map must still produce a section: {result}" - ); - // The content length of just the source-map section must be <= SOURCE_MAP_BYTE_CAP. - let sm_start = result.find("=== .huskies/source-map.json ===").unwrap() - + "=== .huskies/source-map.json ===\n".len(); - let sm_content = &result[sm_start..]; - assert!( - sm_content.len() <= SOURCE_MAP_BYTE_CAP, - "source-map section content must be <= {} bytes, got {}", - SOURCE_MAP_BYTE_CAP, - sm_content.len() + !result.contains("src/lib.rs"), + "source-map content must not be inlined: {result}" ); } }