From a49fec420b46caf8f4ca6d4ec9d5d2f09ffc42cc Mon Sep 17 00:00:00 2001 From: Postil Maintainer Date: Sun, 9 Aug 2026 19:12:49 +0000 Subject: [PATCH] Exclude ignored paths before review planning --- docs/configuration.md | 2 ++ src/diff.rs | 12 ++++++++++++ src/review.rs | 2 +- tests/e2e.rs | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index f031101..aff9a5b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -40,6 +40,8 @@ model: Place organization-specific merge rules in `.postil/guardrails.md`. Place additions to the built-in prose policy in `.postil/content-policy.md`. Repository policy extends the built-in content policy unless `contentPolicy.enabled` is false. +Ignored paths are excluded before review planning, so generated artifacts do not consume model context or bounded-review coverage. + ## Environment | Variable | Purpose | diff --git a/src/diff.rs b/src/diff.rs index 3c0106a..7a3d356 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -2429,7 +2429,15 @@ fn read_length_prefixed(file: &mut File, context: &str) -> Result } pub fn prepare_review(snapshot: &DiffSnapshot) -> Result { + prepare_review_with_ignore(snapshot, &[]) +} + +pub fn prepare_review_with_ignore( + snapshot: &DiffSnapshot, + ignore_patterns: &[String], +) -> Result { let text = snapshot.as_str(); + let ignore = crate::filter::build_ignore_set(ignore_patterns)?; let mut windows = tempfile::tempfile().context("creating review-window spool")?; let mut window_lease = WorkspaceLease::new(snapshot.workspace_budget()); let mut index = DiffIndex::default(); @@ -2462,6 +2470,10 @@ pub fn prepare_review(snapshot: &DiffSnapshot) -> Result { let end = next_diff_start(text, cursor + "diff --git ".len()).unwrap_or(text.len()); let section = &text[cursor..end]; let path = section_path(section).context("review section has an invalid path header")?; + if ignore.is_match(&path) { + cursor = end; + continue; + } if is_known_lockfile(&path) && let Some(evidence) = lockfile_evidence(&path, section) { diff --git a/src/review.rs b/src/review.rs index dcfbded..bdaa664 100644 --- a/src/review.rs +++ b/src/review.rs @@ -1125,7 +1125,7 @@ async fn review_diff(cfg: &Config, args: &ReviewArgs, input: ReviewInput<'_>) -> repository_source, } = input; let review_started = std::time::Instant::now(); - let mut prepared = diff::prepare_review(diff_snapshot)?; + let mut prepared = diff::prepare_review_with_ignore(diff_snapshot, &cfg.ignore)?; let input_incomplete = prepared.reserved_anchor; let mut index = std::mem::take(&mut prepared.index); let incremental = matches!(scope, filter::ReconcileScope::Incremental { .. }); diff --git a/tests/e2e.rs b/tests/e2e.rs index 059c83b..afdc315 100644 --- a/tests/e2e.rs +++ b/tests/e2e.rs @@ -2210,6 +2210,44 @@ async fn generated_named_source_is_not_omitted_from_review() { assert!(body.contains("eval(userInput)")); } +#[tokio::test] +async fn ignored_paths_are_removed_before_review_planning() { + let server = MockServer::start().await; + mock_review(&server, json!([])).await; + + let dir = tempfile::tempdir().unwrap(); + let diff = dir.path().join("ignored-source.diff"); + std::fs::write( + &diff, + "diff --git a/generated/snapshot.json b/generated/snapshot.json\n--- a/generated/snapshot.json\n+++ b/generated/snapshot.json\n@@ -0,0 +1 @@\n+generated_snapshot_payload\ndiff --git a/src/live.rs b/src/live.rs\n--- a/src/live.rs\n+++ b/src/live.rs\n@@ -0,0 +1 @@\n+validate_live_path();\n", + ) + .unwrap(); + let config = dir.path().join("postil.yml"); + std::fs::write(&config, "ignore:\n - \"generated/**\"\n").unwrap(); + + let out = postil() + .current_dir(dir.path()) + .env("POSTIL_API_BASE", server.uri()) + .env("POSTIL_DISABLE_SCORER", "1") + .args(["review", "--diff-file"]) + .arg(&diff) + .arg("--config") + .arg(&config) + .args(["--output", "json"]) + .assert() + .success(); + + let envelope: Value = serde_json::from_slice(&out.get_output().stdout).unwrap(); + assert!(envelope["findings"].as_array().unwrap().is_empty()); + let requests = server.received_requests().await.unwrap(); + assert_eq!(requests.len(), 1); + let body = String::from_utf8_lossy(&requests[0].body); + assert!(body.contains("src/live.rs")); + assert!(body.contains("validate_live_path")); + assert!(!body.contains("generated/snapshot.json")); + assert!(!body.contains("generated_snapshot_payload")); +} + #[tokio::test] async fn oversized_security_hunk_fails_before_provider_contact() { use std::fmt::Write as _;