Skip to content

Commit de84554

Browse files
nhortonclaude
andcommitted
fix: only run .deepreview rules against git-changed output files
The quality gate was treating all file_path outputs as "changed files" for .deepreview matching. This caused reviews to run on unchanged reference files (e.g. key_reference_files in exploration steps). Now intersects output file paths with actual git-changed files before matching .deepreview rules. Step-specific (dynamic) reviews still run against all declared outputs since those are explicitly defined. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4ccb3c4 commit de84554

2 files changed

Lines changed: 73 additions & 7 deletions

File tree

src/deepwork/jobs/mcp/quality_gate.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from deepwork.review.instructions import (
2727
write_instruction_files,
2828
)
29-
from deepwork.review.matcher import match_files_to_rules
29+
from deepwork.review.matcher import get_changed_files, match_files_to_rules
3030
from deepwork.utils.validation import ValidationError, validate_against_schema
3131

3232
logger = logging.getLogger("deepwork.jobs.mcp.quality_gate")
@@ -328,17 +328,27 @@ def run_quality_gate(
328328
schema_rules, _schema_errors = gen_schema_rules(project_root)
329329
deepreview_rules.extend(schema_rules)
330330

331-
# 4. Get the "changed files" list = output file paths
331+
# 4. Collect output file paths
332332
output_files = _collect_output_file_paths(outputs, job)
333333

334-
# 5. Match .deepreview rules against output files
334+
# 5. Match .deepreview rules against output files that are actually changed.
335+
# Output files may include unchanged reference files — .deepreview rules
336+
# should only fire on files that were actually modified (git diff).
335337
deepreview_tasks: list[ReviewTask] = []
336338
if deepreview_rules and output_files:
337-
deepreview_tasks = match_files_to_rules(
338-
output_files, deepreview_rules, project_root, platform
339-
)
339+
try:
340+
git_changed = get_changed_files(project_root)
341+
except Exception:
342+
git_changed = []
343+
output_set = set(output_files)
344+
changed_output_files = [f for f in git_changed if f in output_set]
345+
if changed_output_files:
346+
deepreview_tasks = match_files_to_rules(
347+
changed_output_files, deepreview_rules, project_root, platform
348+
)
340349

341-
# 6. Match dynamic rules against output files
350+
# 6. Match dynamic rules (step-specific reviews) against all output files.
351+
# These are explicitly defined for specific outputs and should always run.
342352
dynamic_tasks: list[ReviewTask] = []
343353
if dynamic_rules and output_files:
344354
dynamic_tasks = match_files_to_rules(output_files, dynamic_rules, project_root, platform)

tests/unit/jobs/mcp/test_quality_gate.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,10 @@ def test_merges_deepreview_and_dynamic_tasks(self, tmp_path: Path) -> None:
664664
"deepwork.jobs.mcp.quality_gate.load_all_rules",
665665
return_value=([deepreview_rule], []),
666666
),
667+
patch(
668+
"deepwork.jobs.mcp.quality_gate.get_changed_files",
669+
return_value=["report.md"],
670+
),
667671
patch(
668672
"deepwork.jobs.mcp.quality_gate.match_files_to_rules",
669673
side_effect=[[deepreview_task], [dynamic_task]],
@@ -697,6 +701,58 @@ def test_merges_deepreview_and_dynamic_tasks(self, tmp_path: Path) -> None:
697701
assert all_tasks[1].rule_name == "external_rule"
698702
assert result is not None
699703

704+
def test_deepreview_rules_skip_unchanged_output_files(self, tmp_path: Path) -> None:
705+
"""Deepreview rules should only match output files that are actually changed in git."""
706+
arg = StepArgument(name="refs", description="Reference files", type="file_path")
707+
output_ref = StepOutputRef(argument_name="refs", required=False)
708+
step = WorkflowStep(name="explore", outputs={"refs": output_ref})
709+
job, workflow = _make_job(tmp_path, [arg], step)
710+
711+
deepreview_rule = ReviewRule(
712+
name="python_lint",
713+
description="Lint Python files",
714+
include_patterns=["**/*.py"],
715+
exclude_patterns=[],
716+
strategy="matches_together",
717+
instructions="Run linting",
718+
agent=None,
719+
all_changed_filenames=False,
720+
unchanged_matching_files=False,
721+
precomputed_info_bash_command=None,
722+
source_dir=tmp_path,
723+
source_file=tmp_path / ".deepreview",
724+
source_line=1,
725+
)
726+
727+
with (
728+
patch(
729+
"deepwork.jobs.mcp.quality_gate.load_all_rules",
730+
return_value=([deepreview_rule], []),
731+
),
732+
# git says no files changed — output files are just references
733+
patch(
734+
"deepwork.jobs.mcp.quality_gate.get_changed_files",
735+
return_value=[],
736+
),
737+
patch(
738+
"deepwork.jobs.mcp.quality_gate.match_files_to_rules",
739+
) as mock_match,
740+
):
741+
result = run_quality_gate(
742+
step=step,
743+
job=job,
744+
workflow=workflow,
745+
outputs={"refs": ["src/foo.py", "src/bar.py"]},
746+
input_values={},
747+
work_summary=None,
748+
project_root=tmp_path,
749+
)
750+
751+
# match_files_to_rules should not be called for deepreview since
752+
# no output files are in the git changed set
753+
assert mock_match.call_count == 0
754+
assert result is None
755+
700756

701757
# ---------------------------------------------------------------------------
702758
# TestValidateJsonSchemas — additional coverage

0 commit comments

Comments
 (0)