huskies: merge 1050
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
//! CLI binary that regenerates `.huskies/source-map.json` from scratch.
|
//! CLI binary for manual regeneration of `.huskies/source-map.json`.
|
||||||
//!
|
//!
|
||||||
//! Usage: `source-map-regen [--project-root <path>]`
|
//! Usage: `source-map-regen [--project-root <path>]`
|
||||||
//!
|
//!
|
||||||
@@ -6,8 +6,10 @@
|
|||||||
//! extracts public item signatures, and writes a fresh sorted JSON map. The output
|
//! extracts public item signatures, and writes a fresh sorted JSON map. The output
|
||||||
//! is byte-identical across runs on the same source tree (deterministic).
|
//! is byte-identical across runs on the same source tree (deterministic).
|
||||||
//!
|
//!
|
||||||
//! Intended to be called from the pre-commit quality gate (`script/check`) so that
|
//! The pre-commit gate (`script/check`) no longer calls this binary directly — map
|
||||||
//! every commit captures an accurate, stale-entry-free snapshot of the source map.
|
//! regeneration is now inlined into the coder spawn path (`local_prompt.rs`) so every
|
||||||
|
//! agent session starts with a fresh snapshot. This binary is kept as an escape hatch
|
||||||
|
//! for manual out-of-band regeneration (e.g. after bulk refactors outside the pipeline).
|
||||||
|
|
||||||
use source_map_gen::regenerate_source_map;
|
use source_map_gen::regenerate_source_map;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|||||||
@@ -13,9 +13,5 @@ cargo fmt --manifest-path "$PROJECT_ROOT/Cargo.toml" --all --check
|
|||||||
echo "=== Running cargo clippy ==="
|
echo "=== Running cargo clippy ==="
|
||||||
cargo clippy --manifest-path "$PROJECT_ROOT/Cargo.toml" --workspace --all-targets -- -D warnings
|
cargo clippy --manifest-path "$PROJECT_ROOT/Cargo.toml" --workspace --all-targets -- -D warnings
|
||||||
|
|
||||||
echo "=== Regenerating source map ==="
|
|
||||||
cargo run --manifest-path "$PROJECT_ROOT/Cargo.toml" -p source-map-gen --bin source-map-regen --quiet -- --project-root "$PROJECT_ROOT"
|
|
||||||
git -C "$PROJECT_ROOT" add .huskies/source-map.json
|
|
||||||
|
|
||||||
echo "=== Checking doc coverage on changed files ==="
|
echo "=== Checking doc coverage on changed files ==="
|
||||||
cargo run --manifest-path "$PROJECT_ROOT/Cargo.toml" -p source-map-gen --bin source-map-check --quiet -- --worktree "$PROJECT_ROOT" --base master
|
cargo run --manifest-path "$PROJECT_ROOT/Cargo.toml" -p source-map-gen --bin source-map-check --quiet -- --worktree "$PROJECT_ROOT" --base master
|
||||||
|
|||||||
@@ -60,6 +60,13 @@ pub fn read_project_local_prompt(project_root: &Path) -> Option<String> {
|
|||||||
sections.push((rel_path, trimmed.to_string()));
|
sections.push((rel_path, trimmed.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regenerate the source map so agents always start from a fresh snapshot.
|
||||||
|
// Failure is non-fatal: log it and fall through to whatever is on disk.
|
||||||
|
let map_path = project_root.join(SOURCE_MAP_REL);
|
||||||
|
if let Err(e) = source_map_gen::regenerate_source_map(project_root, &map_path) {
|
||||||
|
crate::slog!("[agents] source-map regen failed (non-fatal): {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
// Read source-map.json (after AGENT.md) with a byte cap.
|
// Read source-map.json (after AGENT.md) with a byte cap.
|
||||||
let source_map_content = read_source_map_section(project_root);
|
let source_map_content = read_source_map_section(project_root);
|
||||||
|
|
||||||
@@ -387,6 +394,86 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Regen-on-spawn tests ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn init_git_repo(dir: &Path) {
|
||||||
|
let run = |args: &[&str]| {
|
||||||
|
std::process::Command::new("git")
|
||||||
|
.args(args)
|
||||||
|
.current_dir(dir)
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
};
|
||||||
|
run(&["init"]);
|
||||||
|
run(&["config", "user.email", "test@test.com"]);
|
||||||
|
run(&["config", "user.name", "Test"]);
|
||||||
|
run(&["commit", "--allow-empty", "-m", "init"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Happy path: regen runs successfully and the fresh map is included in the bundle.
|
||||||
|
#[test]
|
||||||
|
fn regen_creates_map_on_coder_spawn() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
init_git_repo(tmp.path());
|
||||||
|
|
||||||
|
// Write a tracked Rust file so git ls-files has something to index.
|
||||||
|
write_file(
|
||||||
|
tmp.path(),
|
||||||
|
"lib.rs",
|
||||||
|
"//! Module doc.\n\n/// A function.\npub fn hello() {}\n",
|
||||||
|
);
|
||||||
|
std::process::Command::new("git")
|
||||||
|
.args(["add", "lib.rs"])
|
||||||
|
.current_dir(tmp.path())
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
std::process::Command::new("git")
|
||||||
|
.args(["commit", "-m", "add lib.rs"])
|
||||||
|
.current_dir(tmp.path())
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Write an orientation file so we get Some back.
|
||||||
|
write_file(tmp.path(), "CLAUDE.md", "agent hints");
|
||||||
|
|
||||||
|
// Map does not exist yet.
|
||||||
|
let map_path = tmp.path().join(SOURCE_MAP_REL);
|
||||||
|
assert!(!map_path.exists(), "map must not exist before spawn");
|
||||||
|
|
||||||
|
let result = read_project_local_prompt(tmp.path());
|
||||||
|
assert!(
|
||||||
|
result.is_some(),
|
||||||
|
"bundle must be Some when CLAUDE.md present"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Regen should have written the map.
|
||||||
|
assert!(
|
||||||
|
map_path.exists(),
|
||||||
|
"regen must have written source-map.json during spawn"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fallback: regen fails (no git repo) but a stale map on disk is still read.
|
||||||
|
#[test]
|
||||||
|
fn regen_fails_stale_map_still_readable() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
// No git repo — regen will fail with "git ls-files" error.
|
||||||
|
|
||||||
|
write_file(tmp.path(), "CLAUDE.md", "agent hints");
|
||||||
|
// Write a stale map manually.
|
||||||
|
write_file(
|
||||||
|
tmp.path(),
|
||||||
|
SOURCE_MAP_REL,
|
||||||
|
r#"{"stale/entry.rs": ["fn old"]}"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = read_project_local_prompt(tmp.path()).unwrap();
|
||||||
|
assert!(
|
||||||
|
result.contains("stale/entry.rs"),
|
||||||
|
"stale map must still be readable after regen failure: {result}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::string_slice)] // sm_start is derived from str::find — always a char boundary
|
#[allow(clippy::string_slice)] // sm_start is derived from str::find — always a char boundary
|
||||||
fn source_map_truncated_at_byte_cap() {
|
fn source_map_truncated_at_byte_cap() {
|
||||||
|
|||||||
Reference in New Issue
Block a user