@rigour-labs/cli 6.13.0-rc.4 → 6.13.0-rc.5
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.
package/dist/commands/check.js
CHANGED
|
@@ -8,6 +8,7 @@ import { randomUUID } from 'crypto';
|
|
|
8
8
|
import { EXIT_PASS, EXIT_FAIL, EXIT_CONFIG_ERROR, EXIT_INTERNAL_ERROR, exitCodeFor } from './exit-codes.js';
|
|
9
9
|
import { deepProvider } from './deep-provider.js';
|
|
10
10
|
import { UsageError } from './review-config.js';
|
|
11
|
+
import { scoreNote } from './score-note.js';
|
|
11
12
|
// Helper to log events for Rigour Studio
|
|
12
13
|
async function logStudioEvent(cwd, event) {
|
|
13
14
|
try {
|
|
@@ -80,6 +81,9 @@ export async function checkCommand(cwd, files = [], options = {}) {
|
|
|
80
81
|
console.log(trendColor(`\nScore Trend: ${scoresStr} (${trend.direction} ${arrow})`));
|
|
81
82
|
}
|
|
82
83
|
console.log(chalk.dim(`\nFinished in ${result.checkMs}ms (cached) | Score: ${result.report.stats.score ?? '?'}/100`));
|
|
84
|
+
const cachedNote = scoreNote(result.report.stats.score);
|
|
85
|
+
if (cachedNote)
|
|
86
|
+
console.log(chalk.dim(cachedNote));
|
|
83
87
|
process.exit(result.report.status === 'PASS' ? EXIT_PASS : EXIT_FAIL);
|
|
84
88
|
}
|
|
85
89
|
}
|
|
@@ -236,6 +240,9 @@ export async function checkCommand(cwd, files = [], options = {}) {
|
|
|
236
240
|
footerParts.push(`Score: ${report.stats.score}/100`);
|
|
237
241
|
}
|
|
238
242
|
console.log(chalk.dim('\n' + footerParts.join(' | ')));
|
|
243
|
+
const note = scoreNote(report.stats.score);
|
|
244
|
+
if (note)
|
|
245
|
+
console.log(chalk.dim(note));
|
|
239
246
|
process.exit(exitCodeFor(report));
|
|
240
247
|
}
|
|
241
248
|
catch (error) {
|
|
@@ -69,43 +69,51 @@ function fileFindingsByCheck(result) {
|
|
|
69
69
|
}
|
|
70
70
|
return countsByCheck(byCheck);
|
|
71
71
|
}
|
|
72
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* One line each, in plain words, for what was seen and never blocks. With --notes, each list is printed under its own
|
|
74
|
+
* line, so nothing listed sits under a "Not shown" line and reads as the issue it left out.
|
|
75
|
+
*/
|
|
73
76
|
function printQuietLines(result, notes) {
|
|
74
|
-
const
|
|
77
|
+
const groups = [];
|
|
78
|
+
const item = (f) => `${f.files?.[0] || '?'}:${f.line ?? '?'} ${f.title}`;
|
|
75
79
|
const seen = result.advisory.length + result.fileFindings.length;
|
|
76
80
|
if (seen)
|
|
77
|
-
|
|
81
|
+
groups.push({ line: `Also seen, never blocking: ${seen} note${seen === 1 ? '' : 's'}${notes ? '' : ' (rigour review --notes)'}`, items: result.advisory.map(item) });
|
|
78
82
|
const wholeFile = fileFindingsByCheck(result);
|
|
83
|
+
// The count above includes these: with notes on a line too, say they are part of it.
|
|
79
84
|
if (wholeFile)
|
|
80
|
-
|
|
85
|
+
groups.push({ line: `${result.advisory.length ? 'Of these, about' : 'About'} a changed file as a whole: ${wholeFile}${notes ? ' (listed below)' : ', shown with --notes'}.`, items: result.fileFindings.map(item) });
|
|
86
|
+
if (result.hints.length)
|
|
87
|
+
groups.push({ line: `To confirm by hand: ${result.hints.length} hint${result.hints.length === 1 ? '' : 's'}${notes ? '' : ' (rigour review --notes)'}`, items: result.hints });
|
|
81
88
|
// Two kinds, each with its own pointer: show_preexisting lists only the first.
|
|
82
89
|
const before = result.preexisting;
|
|
83
90
|
if (before)
|
|
84
|
-
|
|
91
|
+
groups.push({ line: `Not shown: ${before} issue${before === 1 ? '' : 's'} the code already had before this change${leftOutByCheck(result.preexistingByCheck)} (review.show_preexisting: true lists them).` });
|
|
85
92
|
const outside = result.excludedOutsideChangedLines;
|
|
86
93
|
if (outside)
|
|
87
|
-
|
|
94
|
+
groups.push({ line: `Not shown: ${outside} issue${outside === 1 ? '' : 's'} on lines this change did not touch${leftOutByCheck(result.outsideChangeByCheck)}.` });
|
|
88
95
|
if (result.baseUnknown)
|
|
89
|
-
|
|
90
|
-
if (result.hints.length)
|
|
91
|
-
lines.push(`To confirm by hand: ${result.hints.length} hint${result.hints.length === 1 ? '' : 's'}${notes ? '' : ' (rigour review --notes)'}`);
|
|
96
|
+
groups.push({ line: 'Compared with no base: HEAD already holds this diff, so findings on its lines were not checked against the code before it. Pass --base to compare.' });
|
|
92
97
|
if (result.dismissed)
|
|
93
|
-
|
|
98
|
+
groups.push({ line: `Dismissed earlier as not a bug: ${result.dismissed}.` });
|
|
94
99
|
if (result.muted)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
groups.push({ line: `Muted: ${result.muted} from checks this repository usually dismisses (rigour precision).` });
|
|
101
|
+
let open = false;
|
|
102
|
+
for (const group of groups) {
|
|
103
|
+
console.log(chalk.dim(group.line));
|
|
104
|
+
const listed = notes ? group.items ?? [] : [];
|
|
105
|
+
for (const text of listed)
|
|
106
|
+
console.log(chalk.dim(` ${text}`));
|
|
107
|
+
// A blank line closes a list, so the next line is not read as part of it.
|
|
108
|
+
if (listed.length)
|
|
109
|
+
console.log('');
|
|
110
|
+
open = !listed.length;
|
|
111
|
+
}
|
|
98
112
|
if (result.controlFilesChanged.length)
|
|
99
113
|
console.log(chalk.yellow(`This change edits Rigour's own settings: ${result.controlFilesChanged.join(', ')}`));
|
|
100
114
|
for (const f of result.contextFindings)
|
|
101
115
|
console.log(chalk.yellow(`Nearby, not in your change: ${f.files?.[0] || '?'}:${f.line ?? '?'} ${f.title}`));
|
|
102
|
-
if (
|
|
103
|
-
for (const f of [...result.advisory, ...result.fileFindings])
|
|
104
|
-
console.log(chalk.dim(` ${f.files?.[0] || '?'}:${f.line ?? '?'} ${f.title}`));
|
|
105
|
-
for (const hint of result.hints)
|
|
106
|
-
console.log(chalk.dim(` ${hint}`));
|
|
107
|
-
}
|
|
108
|
-
if (lines.length || result.controlFilesChanged.length || result.contextFindings.length)
|
|
116
|
+
if (open || result.controlFilesChanged.length || result.contextFindings.length)
|
|
109
117
|
console.log('');
|
|
110
118
|
}
|
|
111
119
|
/** Why the review is not a pass although it may have found nothing, and the one step that finishes it. */
|
package/dist/commands/review.js
CHANGED
|
@@ -27,6 +27,7 @@ import { loadConfig, UsageError } from './review-config.js';
|
|
|
27
27
|
import { deepProvider } from './deep-provider.js';
|
|
28
28
|
import { buildCiReviewSummary, renderGithubSummary } from './review-summary.js';
|
|
29
29
|
import { EXIT_PASS, EXIT_FAIL, EXIT_CONFIG_ERROR, EXIT_INTERNAL_ERROR } from './exit-codes.js';
|
|
30
|
+
import { scoreNote } from './score-note.js';
|
|
30
31
|
export async function reviewCommand(cwd, options = {}) {
|
|
31
32
|
const started = Date.now();
|
|
32
33
|
if (options.status)
|
|
@@ -229,14 +230,19 @@ function writeJson(result, summary, receipt, reviewer, checked, goal) {
|
|
|
229
230
|
function printCi(result) {
|
|
230
231
|
const score = result.report?.stats.score;
|
|
231
232
|
const scoreStr = score !== undefined ? ` (${score}/100)` : '';
|
|
233
|
+
const note = scoreNote(score, true);
|
|
232
234
|
if (result.findings.length === 0) {
|
|
233
235
|
console.log(`${result.status === 'ERROR' ? 'ERROR' : 'PASS'}${scoreStr}`);
|
|
236
|
+
if (note)
|
|
237
|
+
console.log(` ${note}`);
|
|
234
238
|
return;
|
|
235
239
|
}
|
|
236
240
|
console.log(`FAIL: ${result.findings.length} violation(s) on changed lines${scoreStr}`);
|
|
237
241
|
for (const f of result.findings) {
|
|
238
242
|
console.log(` - [${(f.severity || 'medium').toUpperCase()}] ${f.files?.[0] || ''}:${f.line ?? '?'} ${f.title}`);
|
|
239
243
|
}
|
|
244
|
+
if (note)
|
|
245
|
+
console.log(` ${note}`);
|
|
240
246
|
}
|
|
241
247
|
/** Without a model, point at the risky changed functions a person or their agent should still check. */
|
|
242
248
|
function hintReviewTask(cwd, diff, router) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a score below 100 means, in one line: a person seeing "PASS (62/100)" should not have to guess why a note that
|
|
3
|
+
* never blocks cost points. The weights are read from the ones the score is computed with (SEVERITY_WEIGHTS).
|
|
4
|
+
*/
|
|
5
|
+
import { SEVERITY_WEIGHTS } from '@rigour-labs/core';
|
|
6
|
+
/** Undefined at 100 or with no score; `review` adds that only the findings to fix decide its verdict. */
|
|
7
|
+
export function scoreNote(score, review = false) {
|
|
8
|
+
if (score === undefined || score >= 100)
|
|
9
|
+
return undefined;
|
|
10
|
+
const weights = ['critical', 'high', 'medium', 'low'].map(s => `${s} ${SEVERITY_WEIGHTS[s]}`).join(', ');
|
|
11
|
+
return `Score ${score}/100 counts every finding in the files checked, notes included (${weights} points each, capped per check)${review ? '; only the findings to fix decide the verdict' : ''}.`;
|
|
12
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigour-labs/cli",
|
|
3
|
-
"version": "6.13.0-rc.
|
|
3
|
+
"version": "6.13.0-rc.5",
|
|
4
4
|
"description": "Code review that happens while your agent writes: deterministic checks on the lines it changed, quiet unless it can prove a defect, and learning from your team's fixes and dismissals. Works with Claude Code, Cursor, Codex, Cline and Windsurf.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.13"
|
|
@@ -59,13 +59,13 @@
|
|
|
59
59
|
"inquirer": "9.2.16",
|
|
60
60
|
"ora": "^8.0.1",
|
|
61
61
|
"yaml": "^2.8.2",
|
|
62
|
-
"@rigour-labs/core": "6.13.0-rc.
|
|
62
|
+
"@rigour-labs/core": "6.13.0-rc.5"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@types/fs-extra": "^11.0.4",
|
|
66
66
|
"@types/inquirer": "9.0.7",
|
|
67
67
|
"@types/node": "^25.0.3",
|
|
68
|
-
"@rigour-labs/studio": "6.13.0-rc.
|
|
68
|
+
"@rigour-labs/studio": "6.13.0-rc.5"
|
|
69
69
|
},
|
|
70
70
|
"scripts": {
|
|
71
71
|
"build": "tsc && pnpm bundle-studio",
|