Skip to content

research: corpus-wide runs of every scheme, SonarQube branch mode, and the paper kit - #59

Merged
mevichitra merged 9 commits into
mainfrom
claude/sonar-branch-mode
Oct 18, 2026
Merged

mevichitra merged 9 commits into
mainfrom
claude/sonar-branch-mode

Conversation

@mevichitra

Copy link
Copy Markdown
Owner

Summary

Turns the finding-identity harness into the material for a 6-page IEEE paper. It re-runs the 35-repository corpus with every identity scheme, adds the shipped tools' analyzers and SonarQube's branch-analysis behaviour, runs the controlled experiment (RQ2) on the corpus, and generates the paper's table, figure and numbers into research/finding_identity/paper/. Every generated file there reproduces byte for byte from the commands in paper/outline.md.

Type of change

  • Bug fix (false positive / false negative correction)
  • New rule (attach rule ID from the tracker)
  • Engine / runner change
  • CLI change
  • Docs only
  • Tooling / CI (research code under research/, not part of the released package)

Checklist

  • git commit -s — DCO sign-off on all commits
  • pytest passes locally
  • ruff check research/ and ruff format --check research/ clean
  • MYPYPATH=src mypy --strict research/finding_identity clean
  • News fragment: not needed for research/

What changed

Harness

  • batch --analyzer bandit|semgrep replays the corpus with that analyzer's findings. Bandit skips the JavaScript repositories. A result directory counts as done only for the analyzer named in its analyzer.json.
  • transform <local repository> --ref <commit> exports that commit's Python files, so RQ2 runs on the same pinned commits as RQ1. Corpus clones have no working tree.
  • sonar_branch: SonarQube's tracker as a branch analysis runs it. A renamed file keeps its findings only if SonarQube's file move detection accepts the pair: line counts within (0.84×, 1.18×) and line-hash similarity ≥ 85. This ports FileMoveDetectionStep and SourceSimilarityImpl from 26.10.0.133078. sonar stays the pull-request mode, which takes renames from the SCM. Replay and drift pass renamed down to the matchers.

Paper kit

  • paper_kit.py writes Table II (table_rq1.tex), numbers.md, rq1.csv, the pooled RQ2 matrix (rq2.csv) and Fig. 1 (fig_rq2.pdf). It uses cluster-bootstrap intervals over repositories, splits the false-new rate by renamed files, and can leave rules out (Bandit's B101).
  • gt_sample.py draws a seeded, stratified sample of 100 ground-truth decisions for a manual check (paper/ground_truth_check/).
  • paper/outline.md (page budget, section contents, reproduction commands) and paper/claims.md (every citable number with its interval and source).

Results

CodeGuard findings in 35 repositories (23,237 persisting / 1,974 introduced); Bandit findings in 25 Python repositories. Rates in %, with cluster-bootstrap 95% intervals.

scheme false new same path renamed hidden Bandit findings without B101: hidden
line 52.6 (48.5–57.6) 52.0 100 1.2 1.9
github 3.1 (2.0–4.9) 1.9 100 0.8 3.3
codeguard 1.8 (0.7–3.4) 0.6 100 1.9 2.5
sonar 0.4 (0.3–0.6) 0.4 2.8 1.5 4.5
sonar_branch 0.6 (0.3–1.0) 0.4 20.2 1.4 4.5
bandit 4.4 (3.1–6.3) 3.2 100 20.0 29.2 (20.1–40.9)
  • Every key scheme includes the path. Renamed files hold 1.2% of persisting findings but 69% of codeguard's false-new reports.
  • Bandit's set-membership baseline hides 29% of new Bandit findings without B101, 36% outside test code, and 51% with B101.
  • RQ2 (Fig. 1): each single-key scheme has a blind spot. github loses 77.5% on edits below the line; codeguard loses 100% on function renames and extraction; sonar survives most edits through its fallback passes.
  • The line, line_text, github and codeguard-family results are identical to the first corpus run (2026-10-14).

Test evidence

$ python -m pytest research/finding_identity/tests -q
129 passed, 1 skipped          # the skip is a live Semgrep test
$ python -m pytest -q
652 passed
$ ruff check research/ && ruff format --check research/
All checks passed!
$ MYPYPATH=src python -m mypy --strict research/finding_identity
Success: no issues found in 38 source files

New tests cover the analyzer resume check and an end-to-end Bandit batch, transform --ref on a bare repository, and SonarQube's similarity score. That last one includes a seeded comparison with the textbook edit distance and the exact line-count bounds (ceil(100 × 1.18) = 119). They also cover rename gating, the rename split, rule exclusion, and RQ2 pooling.

Notes for reviewer

  • sonar_branch models SonarQube's decision only for file pairs that git already detects as renames (git diff -M, 50%). A move SonarQube would find but git would not is not modelled. This is documented in the README.
  • CodeGuard's SQL-injection and taint rules put source line numbers in their messages ("built on line N"). That breaks message-based identity (Bandit, SonarQube's message passes) whenever lines shift; it shows up in RQ2's insert_top row for bandit.
  • Clones and per-commit results stay git-ignored; paper/ holds only aggregates and the 100-finding sample.

Generated by Claude Code

`batch --analyzer bandit|semgrep` passes the analyzer to each replay,
so the shipped tools' identity methods can be measured on their own
tools' findings across the whole corpus, not only on Flask and
DefectDojo. Bandit reads Python only, so its batch leaves out the
JavaScript repositories. Results go to results/corpus-<analyzer>/
unless --out is given.

A result directory now counts as done only for the analyzer named in
its analyzer.json (CodeGuard when the file is missing, as in replays
from before it existed), so runs of different analyzers never satisfy
each other's resume check.

Signed-off-by: Vichitra Attri <mevichitra@gmail.com>
`transform <local repository> --ref <commit>` exports the Python files
of that commit to a temporary directory and records the commit as
`head` in summary.json. Corpus clones made by `batch` have no working
tree, so this is what lets the controlled experiment (RQ2) run on the
same pinned commits as the replay (RQ1).

Signed-off-by: Vichitra Attri <mevichitra@gmail.com>
The `sonar` scheme tracks a renamed file's findings against the old
file, as a SonarQube pull-request analysis does with SCM renames. A
branch analysis, which is what compares a long-lived baseline, first
runs FileMoveDetectionStep: an added file is the removed one only if
their line counts are within (0.84x, 1.18x) and the similarity of
their line hashes (100 x (1 - edit distance / longer length)) is at
least 85. Otherwise every finding in it is new.

Add `sonar_branch`, the same tracker behind a port of that decision
(FileMoveDetectionStep and SourceSimilarityImpl, SonarQube
26.10.0.133078). The harness pairs files by git's rename detection,
so only SonarQube's decision for that pair is ported. Replay and drift
pass `renamed` down to scheme_pairs, where schemes listed in
trackers.RENAME_CHECKS may refuse a rename.

In the corpus run, renamed files hold 287 of 23,237 persisting
findings. Every key scheme includes the path and loses all of them;
`sonar` loses 2.8%. The new scheme shows how much of that depends on
the analysis mode.

Signed-off-by: Vichitra Attri <mevichitra@gmail.com>
paper_kit.py reads one corpus batch per analyzer and writes rq1.csv,
Table II as LaTeX (booktabs) and numbers.md, which lists every RQ1
number with its interval, median and counts. It adds a split of the
false-new rate by whether the file was renamed, read from each
replay's findings.jsonl, and reuses the cluster bootstrap of stats.py
with one set of draws per analyzer.

paper/outline.md plans the six-page paper: page budget, research
questions, what each section and table holds, threats, and candidate
related work to verify.

Signed-off-by: Vichitra Attri <mevichitra@gmail.com>
paper_kit --transform reads each repository's transform-<label>/matrix.csv,
pools the false-new and hidden counts per transformation and scheme with
cluster-bootstrap intervals (rq2.csv), and draws the matrix as one heatmap
(fig_rq2.pdf): false-new rates, then hidden rates for the two duplicating
transformations, on a single-hue sequential ramp with every value printed.

Signed-off-by: Vichitra Attri <mevichitra@gmail.com>
Bandit's B101 (assert used) is 91% of its persisting and 92% of its
introduced findings in the corpus, 98% of them in test code, and its
message never varies. --bandit-exclude leaves such rules out of the
table: false-new, hidden and mispair counts are then taken from
findings.jsonl, and the collision rate, which needs every file version,
is not computed. All rules stay in numbers.md as bandit_all_rules.

Signed-off-by: Vichitra Attri <mevichitra@gmail.com>
gt_sample.py draws a seeded, stratified sample of findings from the
replays (edited lines; persisting findings that SonarQube or CodeGuard
report as new; introduced findings a content scheme matched; and two
control strata where every scheme agrees with the truth) and writes
sample.md, with the code before and after each commit and a link to it,
and sample.csv with an empty verdict column.

paper/claims.md lists every RQ1 number the paper can cite, with its
interval, counts and source file.

Signed-off-by: Vichitra Attri <mevichitra@gmail.com>
Replays of the pinned corpus with every scheme, including both
SonarQube modes: CodeGuard findings in 35 repositories and Bandit
findings in the 25 Python ones, and the transformation experiment on
the 25 Python repositories at their pinned commits.

paper/ now holds Table II (table_rq1.tex), Fig. 1 (fig_rq2.pdf), the
numbers behind them (numbers.md, rq1.csv, rq2.csv), the stats tables of
the CodeGuard replays, claims.md with every citable number and its
source, the outline with the commands that regenerate all of it, and a
seeded sample of 100 ground-truth decisions to check by hand.

Fig. 1 leaves out sonar_branch, which equals sonar when no file is
renamed, and the ground-truth sample clips each diff to the two hunks
nearest the finding.

Signed-off-by: Vichitra Attri <mevichitra@gmail.com>
Quoted source lines can end in spaces, which the repository's
pre-commit hooks strip; write sample.md the way the hooks leave it, so
regenerating it reproduces the committed file.

Signed-off-by: Vichitra Attri <mevichitra@gmail.com>
@mevichitra
mevichitra merged commit 5bb5df7 into main Oct 18, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant