feat(diff): --diff-base flag scopes analysis to git-changed files (closes #157)#161
Merged
Conversation
…oses #157) Add global `--diff-base <ref>` flag that restricts any analysis command to only report findings from files changed relative to a git ref. Designed for CI PR checks where only NEW findings introduced by the PR should appear in the output. New files: - scripts/diff_scope.py — DiffScope utility class (312 lines) - from_ref(workspace, ref) factory: validates ref, computes changed-file set via git_aware.get_changed_files() + get_untracked_files() - allows(path) — path-in-allowlist check (handles abs/rel, both sep) - filter_findings(findings, file_keys) — drops findings from unchanged files; tries multiple key names (file/path/defined_in/file_path); keeps findings with no file key (workspace-level findings) - summary() — dict for embedding in command output - Cross-platform path normalization (handles \ and / on any OS) - tests/test_diff_scope.py — 44 tests (443 lines) - Construction, allows(), filter_findings() with all key variants - from_ref() against real temp git repos (uncommitted, untracked, HEAD~1, branch names — auto-detects default branch main/master) - Error cases: invalid ref, empty ref, non-git dir, missing workspace - CLI integration via subprocess: --help, invalid ref exit code, diff_scope in JSON output, empty diff early-exit, before-subcommand Modified files: - scripts/codelens.py (122 lines added): - Add --diff-base to global parser + every subparser (matches --db-path pattern) so it works both before and after the subcommand - Pre-parse loop recognizes --diff-base and --diff-base=<ref> - Build DiffScope after workspace resolution; attach to args - Empty diff → early exit with clear JSON message + exit 0 - Invalid ref → JSON error + exit 1 - Post-filter: after command runs, filter findings/leaks/hints/issues/ violations/matches/chains/results by changed-file set - Attach diff_scope summary (base_ref, changed_files, before/after counts) to result so CI/agents can see what was filtered - Graph-producing commands (scan, trace, impact, circular) are NOT filtered — their results are structural, filtering would corrupt - SKILL.md — add 'Diff-Scoped Analysis' to v8.2 capability pillars - SKILL-QUICK.md — add --diff-base row to flags table Design decisions: - POST-FILTER layer, not pre-filter. Commands still scan the full workspace, but findings from unchanged files are removed before output. This avoids touching every engine's os.walk() (collision risk with other PRs) and keeps the change to one file (codelens.py). Engine-level pre-filtering can be added incrementally later. - DiffScope is a reusable utility class — PR #141 (issue #57) can adopt it to replace the per-command --diff-vs/--diff-scan/--staged flags. - Graph commands excluded from filtering — filtering node/edge lists by file would silently corrupt the graph structure. - Findings with no file key (workspace-level) are KEPT — they may be legitimate (e.g., 'no .gitignore found'). Verified: - 44 new tests pass (test_diff_scope.py) - 173 passed regression check (test_formatters + test_cli + test_command_count + test_command_registry + test_git_aware — no regressions) - Command count unchanged at 70 (flag, not new command) - End-to-end: invalid ref exits 1, valid ref filters findings, empty diff early-exits, --diff-base works before and after subcommand Findings (per pre-flight SKILL.md — flag to BOS): - PR #141 (issue #57, open) implements similar functionality for the `check` command ONLY (--diff-scan, --staged, --diff-vs flags). This PR's --diff-base is a GLOBAL flag for ALL analysis commands. The two are complementary: PR #141 could adopt DiffScope to replace its per-command flags. No file collision (PR #141 touches check.py + baseline_diff.py; this PR touches codelens.py + new diff_scope.py).
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
7 tasks
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.




Closes #157
Summary
Implements issue #157 —
--diff-base <ref>global flag that restricts any analysis command to only report findings from files changed relative to a git ref (branch/tag/SHA/HEAD~1). Designed for CI PR checks where only NEW findings introduced by the PR should appear in the output.Empty diff → early exit with clear JSON message. Invalid ref → JSON error + exit 1. Works on all analysis commands (
secrets,smell,complexity,dead-code,debug-leak,circular,taint,vuln-scan,check,analyze,missing-refs,side-effect,perf-hint,regex-audit,a11y,css-deep,dataflow,stack-trace,config-drift,ownership,test-map).What changed
New files (2)
scripts/diff_scope.py—DiffScopeutility class (312 lines)from_ref(workspace, ref)factory: validates ref, computes changed-file set viagit_aware.get_changed_files()+get_untracked_files()allows(path)— path-in-allowlist check (handles absolute/relative, both\and/separators)filter_findings(findings, file_keys)— drops findings from unchanged files; tries multiple key names (file/path/defined_in/file_path); keeps findings with no file key (workspace-level findings)summary()— dict for embedding in command output\and/on any OS)tests/test_diff_scope.py— 44 tests (443 lines)Modified files (3)
scripts/codelens.py(122 lines added):--diff-baseto global parser + every subparser (matches--db-pathpattern) so it works both before and after the subcommand--diff-baseand--diff-base=<ref>DiffScopeafter workspace resolution; attach toargsfindings/leaks/hints/issues/violations/matches/chains/resultsby changed-file setdiff_scopesummary (base_ref,changed_files,findings_before_filter,findings_after_filter) to result so CI/agents can see what was filteredscan,trace,impact,circular) are NOT filtered — their results are structural, filtering would corrupt the graphSKILL.md— add "Diff-Scoped Analysis" to v8.2 capability pillars with CI usage exampleSKILL-QUICK.md— add--diff-baserow to flags tableAcceptance criteria (from issue body)
--diff-base <ref>flag accepted by all analysis commands — wired into global parser + every subparsergit diff --name-onlyoutput used as file allowlist — viagit_aware.get_changed_files()(existing utility, issue [FEATURE] Git-diff aware incremental re-index (replace file-watcher polling) #14)No changed files relative to <ref>— verified viatest_empty_diff_early_exittest_invalid_ref_exits_nonzero(exit code 1)--diff-basedocumented in--helpoutput for each affected command — appears in both global--helpand subcommand--helpSKILL.mdupdated: CI usage example with--diff-base— added with 3-line bash examplesync_command_count.py --applyrun (count unchanged — this is a flag, not a new command) — verified count = 70How it works
Architecture
Why post-filter (not pre-filter)?
The issue says "pass this set as an allowlist to the existing file discovery layer". This would require modifying every engine's
os.walk()loop to accept and check an allowlist. That's 10+ engine files touched → high collision risk with other open PRs (PR #141 touchescheck.py, PR #148 touchesdeadcode_engine.py+secrets_engine.py).Instead, this PR uses a post-filter layer: commands still scan the full workspace, but findings from unchanged files are removed before output. This:
codelens.py(one file) — minimal collision riskDiffScopeas a reusable utility that engines can adopt incrementally laterThe trade-off is documented in the PR description and the
diff_scope.pydocstring.DiffScope is reusable
PR #141 (issue #57, open) implements similar functionality for the
checkcommand only (--diff-scan,--staged,--diff-vsflags). This PR'sDiffScopeis a reusable utility class that PR #141 could adopt to replace its per-command flags. No file collision — PR #141 touchescheck.py+baseline_diff.py; this PR touchescodelens.py+ newdiff_scope.py.Tests
tests/test_diff_scope.pyTestConstruction(7) — direct construction, path normalizationTestAllows(6) — path matching, backslash handling, empty/NoneTestFilterFindings(11) — all file key variants, workspace-level findings, custom keys, no mutationTestSummary(3) — structure, sorting, base_refTestFromRefRealGit(8) — real temp git repos: no changes, uncommitted, untracked, HEAD~1, branch name (auto-detects main/master)TestFromRefErrors(4) — invalid ref, empty ref, non-git dir, missing workspaceTestCliIntegration(5) — subprocess: --help, invalid ref exit code, diff_scope in JSON, empty diff early-exit, before-subcommandtest_formatters+test_cli+test_command_count+test_command_registry+test_git_aware→ 129 passed, 0 failed (no regressions)End-to-end verification
Findings (per pre-flight SKILL.md — flag to BOS)
PR feat(ci): baseline diff + strict mode + diff-scan flags (closes #57 phase 1+2) #141 collision alert: PR feat(ci): baseline diff + strict mode + diff-scan flags (closes #57 phase 1+2) #141 (issue [FEATURE] CI/CD integration suite — baseline scan, diff scan, strict mode, manifest tests, golden snapshots #57, open) implements similar functionality for the
checkcommand ONLY via--diff-scan,--staged,--diff-vsflags. This PR's--diff-baseis a GLOBAL flag for ALL analysis commands. The two are complementary, not conflicting — no file collision (PR feat(ci): baseline diff + strict mode + diff-scan flags (closes #57 phase 1+2) #141 touchescheck.py+baseline_diff.py; this PR touchescodelens.py+ newdiff_scope.py). PR feat(ci): baseline diff + strict mode + diff-scan flags (closes #57 phase 1+2) #141 could adoptDiffScopein a future refactor to replace its per-command flags. BOS should decide whether to merge both (they coexist fine) or consolidate.Post-filter vs pre-filter trade-off: This PR uses post-filter (commands scan full workspace, findings filtered after). Engine-level pre-filtering (passing allowlist to
os.walk()) would save processing time but touches 10+ engine files — high collision risk. TheDiffScopeutility is exposed so engines can adopt it incrementally. BOS may want to create a follow-up issue for engine-level pre-filtering if CI processing time becomes a concern.Branch
feat/issue-157-diff-base-flag