@rigour-labs/core 6.12.2-rc.6 → 6.12.3-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,8 @@
8
8
  * - contextFindings: deep findings elsewhere in a changed file; returned so
9
9
  * nothing the model found disappears, never blocking.
10
10
  * - unlocated: no file at all; counted, never shown as a changed-line finding.
11
- * - outside: any other finding on an unchanged line; counted.
11
+ * - outside: any other finding on an unchanged line; counted, and kept in
12
+ * outsideFindings so a check whose findings are all here can say so.
12
13
  *
13
14
  * The CLI, the MCP tool and the GitHub integration all use this, so the same
14
15
  * change gets the same verdict everywhere.
@@ -22,5 +23,6 @@ export interface ChangedLineSplit {
22
23
  contextFindings: Failure[];
23
24
  unlocated: number;
24
25
  outside: number;
26
+ outsideFindings: Failure[];
25
27
  }
26
28
  export declare function splitByChangedLines(failures: Failure[], changedLines: Record<string, Set<number>>, spans?: Record<string, LineSpan[]>, removed?: Record<string, RemovedBlock[]>): ChangedLineSplit;
@@ -1,6 +1,6 @@
1
1
  import { anchorInChangedFunction } from './changed-function-spans.js';
2
2
  export function splitByChangedLines(failures, changedLines, spans = {}, removed = {}) {
3
- const split = { findings: [], fileFindings: [], contextFindings: [], unlocated: 0, outside: 0 };
3
+ const split = { findings: [], fileFindings: [], contextFindings: [], unlocated: 0, outside: 0, outsideFindings: [] };
4
4
  for (const failure of failures) {
5
5
  const files = failure.files ?? [];
6
6
  if (files.length === 0) {
@@ -10,7 +10,7 @@ export function splitByChangedLines(failures, changedLines, spans = {}, removed
10
10
  if (files.some(file => changedLines[file]))
11
11
  split.fileFindings.push(failure);
12
12
  else
13
- split.outside++;
13
+ leftOutside(split, failure);
14
14
  }
15
15
  else if (files.some(file => changedLines[file]?.has(failure.line) || removedInside(failure, removed[file]))) {
16
16
  split.findings.push(failure);
@@ -19,11 +19,15 @@ export function splitByChangedLines(failures, changedLines, spans = {}, removed
19
19
  placeDeepFinding(failure, files, changedLines, spans, split);
20
20
  }
21
21
  else {
22
- split.outside++;
22
+ leftOutside(split, failure);
23
23
  }
24
24
  }
25
25
  return split;
26
26
  }
27
+ function leftOutside(split, failure) {
28
+ split.outside++;
29
+ split.outsideFindings.push(failure);
30
+ }
27
31
  function placeDeepFinding(failure, files, changedLines, spans, split) {
28
32
  for (const file of files) {
29
33
  const anchorLine = anchorInChangedFunction(failure.line, changedLines[file], spans[file]);
@@ -40,6 +40,8 @@ export interface ReviewResult {
40
40
  preexisting: number;
41
41
  /** The same, by check. A check whose findings were all the base's reads PASS in the report's summary. */
42
42
  preexistingByCheck: Record<string, number>;
43
+ /** By check, findings only on lines the change did not touch. A check whose findings were all such reads PASS in the report's summary. */
44
+ outsideChangeByCheck: Record<string, number>;
43
45
  /** A diff given with no base whose change HEAD already holds (committed work): nothing was compared, so nothing was dropped as the base's. */
44
46
  baseUnknown?: boolean;
45
47
  changedLines: Record<string, Set<number>>;
@@ -39,7 +39,7 @@ export async function reviewChange(input) {
39
39
  const changedLines = withoutGenerated(input.cwd, parseDiff(diff));
40
40
  const targets = input.files?.length ? input.files : Object.keys(changedLines);
41
41
  if (targets.length === 0) {
42
- return { status: 'PASS', findings: [], fileFindings: [], contextFindings: [], advisory: [], muted: 0, dismissed: 0, dismissedByGate: {}, unlocated: 0, excludedOutsideChangedLines: 0, preexisting: 0, preexistingByCheck: {}, changedLines, report: null, gateErrors: [], controlFilesChanged: controlFiles(diff), hints: [], covered: [] };
42
+ return { status: 'PASS', findings: [], fileFindings: [], contextFindings: [], advisory: [], muted: 0, dismissed: 0, dismissedByGate: {}, unlocated: 0, excludedOutsideChangedLines: 0, preexisting: 0, preexistingByCheck: {}, outsideChangeByCheck: {}, changedLines, report: null, gateErrors: [], controlFilesChanged: controlFiles(diff), hints: [], covered: [] };
43
43
  }
44
44
  // The team's compiled checks run before the deep review, so it is told what they found and which lessons they covered.
45
45
  const compiled = compiledChecksOn(input.cwd, changedLines, input.config);
@@ -73,6 +73,8 @@ export async function reviewChange(input) {
73
73
  else if (input.typed)
74
74
  reviewCheck('redundancy', 'redundancy', typed.failures);
75
75
  const split = splitByChangedLines(report.failures, changedLines, deep ? changedFunctionSpans(input.cwd, changedLines) : {}, removedByFile(diff));
76
+ const outside = new Set(split.outsideFindings);
77
+ const outsideChangeByCheck = passChecksOnlyLeftOut(report, split.outsideFindings, report.failures.filter(f => !outside.has(f)));
76
78
  const deepError = deepAnalysisError(report);
77
79
  // The goal's findings are about the change as a whole (a file it should not touch, an item it never did), not a line, so they skip the changed-line split.
78
80
  const goal = input.goalDescription !== undefined ? parseGoal(input.goalDescription, fileNames(input.cwd, diff)) : undefined;
@@ -97,6 +99,7 @@ export async function reviewChange(input) {
97
99
  excludedOutsideChangedLines: split.outside,
98
100
  preexisting,
99
101
  preexistingByCheck,
102
+ outsideChangeByCheck,
100
103
  ...(baseUnknown ? { baseUnknown } : {}),
101
104
  changedLines,
102
105
  report,
@@ -110,15 +113,16 @@ export async function reviewChange(input) {
110
113
  };
111
114
  }
112
115
  /**
113
- * A check that failed only on what the base already had passes for this change: its summary reads PASS, and the
114
- * count, by check, says what it left out. A check with a finding the change introduced keeps FAIL.
116
+ * A check whose findings were all left out of this change's verdict (what the base already had, or lines the change
117
+ * did not touch) passes for it: its summary reads PASS, and the count, by check, says what it left out. A check with
118
+ * any finding still in play keeps FAIL.
115
119
  */
116
- function passOnlyPreexisting(report, preexisting) {
120
+ function passChecksOnlyLeftOut(report, leftOut, kept) {
117
121
  const byCheck = {};
118
122
  const check = (f) => gateOf(f) ?? f.id;
119
- for (const f of preexisting)
123
+ for (const f of leftOut)
120
124
  byCheck[check(f)] = (byCheck[check(f)] ?? 0) + 1;
121
- const remaining = new Set(report.failures.map(check));
125
+ const remaining = new Set(kept.map(check));
122
126
  for (const id of Object.keys(byCheck))
123
127
  if (report.summary[id] === 'FAIL' && !remaining.has(id))
124
128
  report.summary[id] = 'PASS';
@@ -143,7 +147,7 @@ async function dropPreexisting(input, report, targets) {
143
147
  const { preexisting } = splitIntroduced(rules, await baseFindings(input.cwd, input.config, commit, targets));
144
148
  const old = new Set(preexisting);
145
149
  report.failures = report.failures.filter(f => !old.has(f));
146
- return { preexisting: old.size, byCheck: passOnlyPreexisting(report, preexisting) };
150
+ return { preexisting: old.size, byCheck: passChecksOnlyLeftOut(report, preexisting, report.failures) };
147
151
  }
148
152
  catch {
149
153
  return { preexisting: 0 }; // the comparison is a courtesy; it never fails a review
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigour-labs/core",
3
- "version": "6.12.2-rc.6",
3
+ "version": "6.12.3-rc.1",
4
4
  "description": "Rigour's review engine: deterministic gates on changed lines, rules and lessons learned from your team's fixes, and per-check precision from what you fix versus dismiss, across TypeScript, JavaScript, Python, Go, Ruby and C#.",
5
5
  "engines": {
6
6
  "node": ">=22.13"
@@ -75,11 +75,11 @@
75
75
  "@anthropic-ai/sdk": "^0.132.1",
76
76
  "pg": "^8.16.3",
77
77
  "openai": "^5.23.2",
78
- "@rigour-labs/brain-darwin-arm64": "6.12.2-rc.6",
79
- "@rigour-labs/brain-darwin-x64": "6.12.2-rc.6",
80
- "@rigour-labs/brain-linux-arm64": "6.12.2-rc.6",
81
- "@rigour-labs/brain-linux-x64": "6.12.2-rc.6",
82
- "@rigour-labs/brain-win-x64": "6.12.2-rc.6"
78
+ "@rigour-labs/brain-darwin-arm64": "6.12.3-rc.1",
79
+ "@rigour-labs/brain-darwin-x64": "6.12.3-rc.1",
80
+ "@rigour-labs/brain-linux-x64": "6.12.3-rc.1",
81
+ "@rigour-labs/brain-win-x64": "6.12.3-rc.1",
82
+ "@rigour-labs/brain-linux-arm64": "6.12.3-rc.1"
83
83
  },
84
84
  "devDependencies": {
85
85
  "@types/fs-extra": "^11.0.4",