Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Notable changes to JevGate. Versions follow [Semantic Versioning](https://semver

## [Unreleased]

- `--dry-run` plans the files whose purpose the cache already answers, as a run does, so a warm cache's estimate matches the run: on a Rails project it counted 106 of 1,532 requests as new while the run sent none.

## [0.19.0] - 2026-09-25

- Inline suppressions: a comment `jevgate: allow(RULE) reason` on a finding's line, or in the comments and attributes directly above it, accepts that finding as the baseline does. RULE is a rule ID, key or group, and the reason is required; the report keeps the finding with its reason (`suppressed`, and `gate.suppressed_findings`), and `jevgate baseline` leaves it out.
Expand Down
25 changes: 20 additions & 5 deletions src/evaluate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,50 +123,65 @@
}

/// Planned first-pass requests, without credentials, network or writes; the
/// cache is read so answered requests are not counted as cost. Requests that
/// depend on answers (after file purpose, rechecks, locating blocks) are not
/// known yet.
/// cache is read so answered requests are not counted as cost. A file whose
/// purpose the cache answers is planned as a run plans it; requests that
/// depend on new answers (an unanswered file purpose, rechecks, locating
/// blocks) are not known yet.
fn preview(inputs: &[Input], args: &CheckArgs, root: &std::path::Path, report: &mut Report) {
let budget = &TokenBudget::load(root);
let mut planned = Vec::new();
let mut views = BTreeMap::new();
for (owner, input) in inputs.iter().enumerate() {
if report.files[owner].status != Status::Pending {
continue;
}
match schedule(input, args, budget, &mut report.files[owner]) {
Ok(Scheduled::None) => {}
Ok(Scheduled::Purpose(request)) => planned.push(request),
Ok(Scheduled::Purpose(request)) => {
if let Some(body) = crate::requests::answered(root, args, &request) {
let file = &mut report.files[owner];
let view = crate::file_kind::record_purpose(file, &request, &body)
.and_then(|()| crate::file_kind::decide_after_purpose(input, args, file));
match view {
Ok(Some(view)) => {
views.insert(owner, view);
}
Ok(None) => {}
Err(error) => report.errors.push(error.to_string()),
}
}
planned.push(request);
}
Ok(Scheduled::Ready(view)) => {
views.insert(owner, *view);
}
Err(error) => report.errors.push(error.to_string()),
}
}
let plan = crate::units::plan(inputs, &views, args, budget, root);
for (owner, reason) in &plan.skipped {
skip(&mut report.files[*owner], reason);
}
planned.extend(plan.requests.into_iter().map(|p| p.request));
for request in planned {
let stage = report
.stages
.entry(crate::requests::stage(&request).into())
.or_default();
stage.planned_requests += 1;
stage.planned_evidence_bytes += crate::requests::evidence_bytes(&request);
if crate::requests::answered(root, args, &request) {
if crate::requests::answered(root, args, &request).is_some() {
stage.planned_cached += 1;
} else {
stage.planned_tokens += budget.request_tokens(&request) as u64;
}
if args.show_requests {
report
.initial_requests
.push(crate::requests::provider_request(&request).into_owned());
}
}
}

Check warning on line 184 in src/evaluate.rs

View workflow job for this annotation

GitHub Actions / review

JevGate consider [maintainability/function-simplification]

`preview` has branching that likely hides its main path (0.91). → Consider guard clauses, early returns or a lookup table

impl Session<'_> {
pub fn evaluate(&mut self, inputs: &[Input], report: &mut Report) -> Result<()> {
Expand Down
18 changes: 18 additions & 0 deletions src/file_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,24 @@ mod tests {
}
}

#[test]
fn a_dry_run_plans_the_units_of_a_file_whose_purpose_the_cache_answers() {
let project = Project::new();
project.write("tests/support.rs", SUPPORT);
let mut options = args();
options.rules = vec![crate::catalog::FUNCTION_SIMPLIFICATION.into()];
run(&project, &options, &mut PurposeEval::new("mixed"));
options.dry_run = true;
let stages = crate::tests::snapshot(&project, &options).1.stages;
let planned = |stage: &str| (stages[stage].planned_requests, stages[stage].planned_cached);
assert_eq!(planned("file-purpose"), (1, 1));
assert_eq!(
planned("functions"),
(1, 1),
"the units a run sends are planned"
);
}

struct PurposeEval {
mode: &'static str,
calls: usize,
Expand Down
8 changes: 4 additions & 4 deletions src/requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,17 @@ fn cached_answer(
.filter(|(b, _)| response::validate(b, request).is_ok())
}

/// Whether a dry run's planned request already has a cached answer, read
/// without opening the store.
/// A dry run's cached answer to a planned request, read without opening the
/// store.
pub(super) fn answered(
root: &std::path::Path,
args: &crate::options::CheckArgs,
request: &Value,
) -> bool {
) -> Option<Value> {
cached_answer(args, request, |key, ttl| {
crate::storage::peek(root, key, ttl)
})
.is_some()
.map(|(body, _)| body)
}

impl Session<'_> {
Expand Down
5 changes: 4 additions & 1 deletion src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,10 @@ pub(super) fn session<'a>(
}

/// The selected inputs and the first snapshot of a check, before evaluation.
fn snapshot(project: &Project, options: &CheckArgs) -> (Vec<inventory::Input>, schema::Report) {
pub(super) fn snapshot(
project: &Project,
options: &CheckArgs,
) -> (Vec<inventory::Input>, schema::Report) {
let context = project.context();
let scope = inventory::scope(options, &context).unwrap();
let inputs = inventory::collect(options, &context, &scope).unwrap();
Expand Down
Loading