Skip to content
Draft
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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ Notable changes to JevGate. Versions follow [Semantic Versioning](https://semver

## [Unreleased]

Fingerprints stay the same across merges, stacked branches and renames, so a dismissed finding is not raised again because main moved underneath it or a check selected other files. An upgraded baseline keeps accepting every finding it accepted, and its next write moves each entry to its new fingerprint with its reason and note: expect one large `jevgate-baseline.json` diff.

- A shared-logic finding is identified by its copies, each function that holds one by path and name, and a copy outside a function by its path and statements: no longer by which file a check selected, which pair represents a group or how far the repeated window reaches. A baseline accepts a repeat while every copy is one an accepted repeat names, so a copy that goes leaves it accepted and a new copy that joins raises it again.
- A file-organization finding is identified by its file's path, and a baseline accepts it while at least 80% of its member names are the ones accepted; a file that grew a lot is asked about again.
- A custom `hunk` question asks about each run of changed lines on its own, as a diff without context lines has it, identified by the definition JevGate's parser finds around it and the lines it changes, not Git's hunk header: a change nearby, such as a parent branch's, or a function added above leaves it as it was.
- With `--base` and in an agent's turn, a finding of a file the change renamed is also accepted under its old path.
- Baseline entries of shared-logic and file-organization findings record their `members`. An entry written by 0.35 or earlier still accepts its finding through the fingerprint it had then; `jevgate baseline`, `--merge` and `baseline mark` rewrite it to the new one. The JSON report gives such a finding's earlier fingerprint as `fingerprint_v1`, its fingerprints under a renamed file's old path as `aliases`, and its `members`.
- SARIF results carry `jevgateFingerprint/v2` beside `jevgateFingerprint/v1`, which holds the earlier fingerprint where it changed, so code-scanning alerts stay the same alerts across the upgrade. GitLab Code Quality reports and MCP ids use the new fingerprint.

## [0.35.0] - 2026-10-03

A repeat found by a `--base` check or an agent's turn points at the change's own copy and names the copies it left untouched, so a change fixes its own copy without rewriting code it never touched. Fingerprints, and what fails the gate, are unchanged.
Expand Down
140 changes: 140 additions & 0 deletions jevgate-baseline.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"version": 1,
"created_at": 1790976832,
Expand Down Expand Up @@ -427,6 +427,16 @@
"message": "This file may do several separate kinds of work, such as separate features, layers or integrations.",
"reason": "wrong"
},
{
"fingerprint": "5dc95562dd420d856dd0a09a3e6a44afcd14cef3aabe34d0dd9ca73d8f66e7a3",
"rule": "maintainability/file-organization",
"path": "src/baseline.rs",
"line": 19,
"strength": "review",
"message": "This file may do several separate kinds of work, such as separate features, layers or integrations.",
"reason": "intended",
"note": "the baseline file's reads and writes; this PR moves matching and listing to baseline/entries.rs and baseline/listing.rs"
},
{
"fingerprint": "89c1deedd5fe8507aad0a494fda37caae01d5a80be6d0590ca591092f0592dbe",
"rule": "maintainability/file-organization",
Expand All @@ -437,6 +447,17 @@
"reason": "wrong",
"note": "every function reads or writes jevgate-baseline.json; marked wrong on main before"
},
{
"fingerprint": "d0d9af103ceb80928d4f43721e3c4abb763c76ad1babc37d992e36cbd11df897",
"rule": "maintainability/shared-logic",
"path": "src/baseline.rs",
"line": 358,
"unit": "`last_check` (src/baseline.rs:190) and `mark` (src/baseline.rs:358)",
"strength": "review",
"message": "`mark` (src/baseline.rs:358), which this change touched, may repeat logic that copies it left untouched also hold: `last_check` (src/baseline.rs:190, in a file this change edits).",
"reason": "wrong",
"note": "one read_latest call, optional for a mark and required for a write"
},
{
"fingerprint": "b5974a3e8b0774e52416c53f1003281dc460cd55853041125750b311c3d71376",
"rule": "maintainability/function-simplification",
Expand Down Expand Up @@ -802,6 +823,17 @@
"message": "`question_tables` could likely be made simpler to read or change: it may be long, deeply nested, repetitive or mix separate jobs.",
"reason": "intended"
},
{
"fingerprint": "40804709e7f925e3b46f2579ce4fcd2d9568214edf862e73c2a55caf656ebdac",
"rule": "maintainability/function-simplification",
"path": "src/hook/events.rs",
"line": 311,
"unit": "Hook::told",
"strength": "review",
"message": "`Hook::told` could likely be made simpler to read or change: it may be long, deeply nested, repetitive or mix separate jobs.",
"reason": "intended",
"note": "as on main; this PR changes one partition condition"
},
{
"fingerprint": "417b704fd1318a0cca1d3dea37b13407764d0e5707b579e27ac017b7c880c0ae",
"rule": "maintainability/function-simplification",
Expand Down Expand Up @@ -1235,6 +1267,17 @@
"reason": "intended",
"note": "as on main; this PR only adds the new empty untouched field"
},
{
"fingerprint": "758ba90e8cd677dd4f595cec6954a046c60fb0906f3195f62feb41173cccd4cf",
"rule": "maintainability/function-simplification",
"path": "src/units/compose/comments.rs",
"line": 44,
"unit": "comment_findings",
"strength": "review",
"message": "`comment_findings` could likely be made simpler to read or change: it may be long, deeply nested, repetitive or mix separate jobs.",
"reason": "intended",
"note": "as on main; this PR only names the identity and adds rename aliases"
},
{
"fingerprint": "0426cf29bc82927d5ba0d17e12226e080d2c849266ad2a8909303cd6fc633099",
"rule": "maintainability/function-simplification",
Expand All @@ -1244,6 +1287,28 @@
"message": "`counted_status` could likely be made simpler to read or change: it may be long, deeply nested, repetitive or mix separate jobs.",
"reason": "wrong"
},
{
"fingerprint": "04ff31dddb13b208c6d96a23cf0c263172b25248218f99a754d6a0d105697687",
"rule": "maintainability/shared-logic",
"path": "src/units/compose/mod.rs",
"line": 350,
"unit": "`undecided_unit` (src/units/compose/mod.rs:350) and `finding` (src/units/compose/mod.rs:787)",
"strength": "review",
"message": "`undecided_unit` (src/units/compose/mod.rs:350) and `finding` (src/units/compose/mod.rs:787) may repeat one piece of logic, so a change to it would have to be made in each place.",
"reason": "wrong",
"note": "a two-arm match on the custom question in two different outputs"
},
{
"fingerprint": "64555705f5133a7b8372d67a8333eed3393f9e853c91b19b698db3cca8ddaa36",
"rule": "maintainability/shared-logic",
"path": "src/units/compose/mod.rs",
"line": 374,
"unit": "`finding` (src/units/compose/mod.rs:782), `group_finding` (src/units/compose/redundant.rs:127) and 1 more copy",
"strength": "review",
"message": "`undecided_unit` (src/units/compose/mod.rs:374), which this change touched, may repeat logic that copies it left untouched also hold: `finding` (src/units/compose/mod.rs:782, in a file this change edits); `group_finding` (src/units/compose/redundant.rs:127, in a file this change edits).",
"reason": "wrong",
"note": "one first-line expression in three struct literals"
},
{
"fingerprint": "839519bcf4ed992fc3927e179fb57cb1d5fd5ea1f68d08f85c9d4d1f8f254db9",
"rule": "maintainability/function-simplification",
Expand Down Expand Up @@ -1273,6 +1338,28 @@
"reason": "intended",
"note": "as on main; this PR only adds the new empty untouched field"
},
{
"fingerprint": "e4ab21fc2ce573a1a85c439f886b5e57b8ab8deb8aa200e826f94fd179bcb545",
"rule": "maintainability/function-simplification",
"path": "src/units/compose/mod.rs",
"line": 667,
"unit": "finding",
"strength": "review",
"message": "`finding` could likely be made simpler to read or change: it may be long, deeply nested, repetitive or mix separate jobs.",
"reason": "intended",
"note": "as on main; this PR only takes the fingerprint and identity from known()"
},
{
"fingerprint": "819b49424c2d2fa8601c98ce04e340aba0f82f13576dd277fa02159ca1a5ad22",
"rule": "maintainability/function-simplification",
"path": "src/units/custom/hunks.rs",
"line": 189,
"unit": "split",
"strength": "review",
"message": "`split` could likely be made simpler to read or change: it may be long, deeply nested, repetitive or mix separate jobs.",
"reason": "wrong",
"note": "a short loop over the parts lines_now, runs and in_order compute"
},
{
"fingerprint": "7b9c45a11bf97f35d12cf672422f6bb47560176fa5a5a91cd0618f98fed57922",
"rule": "maintainability/shared-logic",
Expand All @@ -1282,6 +1369,17 @@
"message": "`whole` (src/units/custom/items.rs:170), `plan_unit` (src/units/drift.rs:384) and 6 more copies may repeat one piece of logic, so a change to it would have to be made in each place.",
"reason": "wrong"
},
{
"fingerprint": "9fd482e579cb61d310999ba136e44333c58e7af38d6bcf3893deeec372679199",
"rule": "maintainability/shared-logic",
"path": "src/units/custom/items.rs",
"line": 199,
"unit": "`comments` (src/units/custom/items.rs:116), `sections` (src/units/custom/items.rs:150) and 1 more copy",
"strength": "review",
"message": "`changed` (src/units/custom/items.rs:199), which this change touched, may repeat logic that copies it left untouched also hold: `comments` (src/units/custom/items.rs:116, in a file this change edits); `sections` (src/units/custom/items.rs:150, in a file this change edits).",
"reason": "wrong",
"note": "each kind zips its own items with their ids; the items differ"
},
{
"fingerprint": "63f373d699dc0e64490e490447be4f918b909ade305fbbc5f0e524a216cb144c",
"rule": "maintainability/function-simplification",
Expand Down Expand Up @@ -1428,6 +1526,17 @@
"message": "`rule_outcome` could likely be made simpler to read or change: it may be long, deeply nested, repetitive or mix separate jobs.",
"reason": "intended"
},
{
"fingerprint": "d4621f0ec7cfb7d2c46fa373015502193d63cca30378530ca13848ad34a6c5de",
"rule": "maintainability/function-simplification",
"path": "src/units/outcome/mod.rs",
"line": 258,
"unit": "rule_outcome",
"strength": "review",
"message": "`rule_outcome` could likely be made simpler to read or change: it may be long, deeply nested, repetitive or mix separate jobs.",
"reason": "intended",
"note": "as on main; this PR only adds a field to a pattern"
},
{
"fingerprint": "0d67b1174cf848d492e1869b020379d045170a9ed8cd1a70111ef6573d3274d1",
"rule": "maintainability/function-simplification",
Expand Down Expand Up @@ -1466,6 +1575,17 @@
"message": "`parsed_scope` could likely be made simpler to read or change: it may be long, deeply nested, repetitive or mix separate jobs.",
"reason": "intended"
},
{
"fingerprint": "ed7dae0e4679174d2696ad4d986f61fe7a0c86883018ec54cd2b606308b79a00",
"rule": "maintainability/function-simplification",
"path": "src/units/plan/mod.rs",
"line": 98,
"unit": "plan",
"strength": "review",
"message": "`plan` could likely be made simpler to read or change: it may be long, deeply nested, repetitive or mix separate jobs.",
"reason": "intended",
"note": "as on main; this PR only adds the note_renames call"
},
{
"fingerprint": "fa6491af668dc8dc248218b34a7b9c7e88fd633565412eacf3d670dd6b91f4ab",
"rule": "maintainability/function-simplification",
Expand Down Expand Up @@ -1638,6 +1758,16 @@
"message": "This test file may test several separate subjects.",
"reason": "wrong"
},
{
"fingerprint": "fb70b4fbd170ce7111d30aeedbb9490409aa0df579c613eb63d97e8fae913a26",
"rule": "maintainability/file-organization",
"path": "src/units/tests/custom.rs",
"line": 6,
"strength": "review",
"message": "This test file may test several separate subjects.",
"reason": "intended",
"note": "the custom question tests, one file as for every rule; this PR adds a rename test"
},
{
"fingerprint": "b2e04f6d34e41c1ca8e363f836ca25f0515e2d01a5f17a06d56fdeab751fca7c",
"rule": "maintainability/file-organization",
Expand All @@ -1656,6 +1786,16 @@
"message": "A constant of this file may fix a value worth a look: one that differs between environments or singles out one record.",
"reason": "intended"
},
{
"fingerprint": "0d6b6454a9185e789a8d4165909e449868ca000d3ec7c849157abcaa97b756ce",
"rule": "maintainability/file-organization",
"path": "src/units/tests/mod.rs",
"line": 34,
"strength": "review",
"message": "This test file may test several separate subjects.",
"reason": "intended",
"note": "the unit tests' shared helpers; this PR adds accept_all"
},
{
"fingerprint": "76f152756739bb3b1c167f2ef55d71da86cf3e59f62983af21b177e3986b2231",
"rule": "maintainability/file-organization",
Expand Down
2 changes: 1 addition & 1 deletion site/src/custom-questions.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ function auditOrder(req: Request) {
}
```

A finding keeps its fingerprint through unrelated edits, as the built-in rules' do: a function's by its name and code, a hunk's by what it changes and where. A file's is its path and text, so a baselined finding of a `file` question covers the file as it was: once the file is edited, the question is asked of it again, as it is of an edited function.
A finding keeps its fingerprint through unrelated edits, as the built-in rules' do: a function's by its name and code, a hunk's by the lines it changes and the definition around them. Each run of changed lines is a hunk of its own, shown with up to three unchanged lines on each side, so a change nearby, such as a parent branch's, never joins it. A file's is its path and text, so a baselined finding of a `file` question covers the file as it was: once the file is edited, the question is asked of it again, as it is of an edited function.

## Writing a question

Expand Down
4 changes: 3 additions & 1 deletion site/src/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ PORT = 4222

The report keeps the finding with its reason, it never fails the gate, and `jevgate baseline` leaves it out, so deleting the comment brings it back.

`jevgate baseline` can record why each finding was accepted: `intended` (right, and meant to be so), `later` (right, to fix later) or `wrong` (mistaken), with `--reason` or `jevgate baseline mark`. `baseline mark --note "#192"` keeps a one-line note with the reason, such as the issue a `later` finding will be fixed in. Reasons and notes survive later rewrites of the baseline. `jevgate baseline list --reason later --format md` prints the marks as a checklist for a cleanup issue (`text` and `json` too), and `jevgate baseline stats` reports each rule's share of findings marked wrong: labels from daily use, not the model's own probabilities.
`jevgate baseline` can record why each finding was accepted: `intended` (right, and meant to be so), `later` (right, to fix later) or `wrong` (mistaken), with `--reason` or `jevgate baseline mark`. `baseline mark --note "#192"` keeps a one-line note with the reason, such as the issue a `later` finding will be fixed in. Reasons and notes survive later rewrites of the baseline.

A baseline matches findings by fingerprint, which a finding keeps through edits elsewhere, merges and changes to which files a check selects: a function's is its path, name and code, a [repeat](rules/maintainability/shared-logic.md#fingerprints)'s its copies, and a file outline's its path, with the baseline accepting it while at least 80% of its member names are the ones accepted (the entry's `members`). With `--base` and in an agent's turn, a finding of a file the change renamed is also accepted under its old path (`aliases` in the JSON report). An entry written by JevGate 0.35 or earlier still accepts its finding through the fingerprint it had then (`fingerprint_v1`), until `jevgate baseline`, `--merge` or `baseline mark` rewrites it to the new one with its reason and note. SARIF results carry both as `jevgateFingerprint/v1` and `jevgateFingerprint/v2`. `jevgate baseline list --reason later --format md` prints the marks as a checklist for a cleanup issue (`text` and `json` too), and `jevgate baseline stats` reports each rule's share of findings marked wrong: labels from daily use, not the model's own probabilities.

## Guards

Expand Down
6 changes: 5 additions & 1 deletion site/src/rules/maintainability/shared-logic.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ Copies are compared within a package and across packages linked by a local depen

## Copies a change left untouched

With `--base`, and in an agent's turn, a repeat is asked about when the change touched at least one copy. When it left some copies untouched, the finding points at the change's own copy and names each untouched one, saying whether its file is one the change edits; the JSON report lists them under `untouched`, with `file_changed`. The change fixes its own copy, for example by reusing an untouched one; when sharing the logic would rewrite the untouched copies, it marks the finding `later --note "#issue"` and leaves them. The agent hook holds the end of a turn for the change's copy until it is fixed or marked, never for the untouched ones. The finding keeps the fingerprint its repeat has in a check of whole files, so a baseline accepts it in both.
With `--base`, and in an agent's turn, a repeat is asked about when the change touched at least one copy. When it left some copies untouched, the finding points at the change's own copy and names each untouched one, saying whether its file is one the change edits; the JSON report lists them under `untouched`, with `file_changed`. The change fixes its own copy, for example by reusing an untouched one; when sharing the logic would rewrite the untouched copies, it marks the finding `later --note "#issue"` and leaves them. The agent hook holds the end of a turn for the change's copy until it is fixed or marked, never for the untouched ones. A baseline that accepts the repeat from a check of whole files accepts it here too.

## Fingerprints

A repeat is identified by its copies: each function that holds one, by path and name, and a copy outside a function by its path and statements. Its fingerprint is the same whichever file a check selects or reports it in and whichever window of those functions repeats. A baseline accepts it while every copy is one an accepted repeat names: a copy that goes leaves it accepted, and a new copy that joins raises it again.

## How it is measured

Expand Down
Loading
Loading