@rigour-labs/cli 7.0.0-rc.5 → 7.0.0-rc.7
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/cli.js +3 -1
- package/dist/commands/hooks-git.js +4 -2
- package/dist/commands/learn-reviews.d.ts +2 -0
- package/dist/commands/learn-reviews.js +22 -3
- package/dist/commands/studio-learning.d.ts +2 -0
- package/dist/commands/studio-learning.js +5 -3
- package/package.json +3 -3
- package/studio-dist/assets/{index-D5EEZJUE.js → index-CmWtilgA.js} +17 -17
- package/studio-dist/index.html +1 -1
package/dist/cli.js
CHANGED
|
@@ -406,7 +406,9 @@ program
|
|
|
406
406
|
.option('--include-bots', 'With --list: also list candidates only review bots raised (hidden by default)')
|
|
407
407
|
.option('--promote <id>', 'Accept a candidate as a lesson (recorded as your decision)')
|
|
408
408
|
.option('--reject <id>', 'Reject a candidate or lesson: the judges are told this team decided against it')
|
|
409
|
-
.option('--
|
|
409
|
+
.option('--not-a-rule <id>', 'Drop a candidate that is a one-off for its pull request, not a practice either way: never served, the judges are told nothing')
|
|
410
|
+
.option('--take-back <id>', 'Take back the last decision on a lesson (promote, reject or not a rule): a candidate again; the decision stays in its history')
|
|
411
|
+
.option('--why <text>', 'Why, recorded with --promote, --reject, --not-a-rule or --take-back')
|
|
410
412
|
.option('--scope <id>', 'Set how far a lesson reaches, with --to: its file, its folder, or every change (a team standard); recorded as your decision')
|
|
411
413
|
.option('--to <reach>', 'With --scope: file, folder or repo')
|
|
412
414
|
.option('--use-wording <id>', 'Take the corrected wording a newer version suggests for a lesson you decided (the old wording is kept as evidence)')
|
|
@@ -13,6 +13,7 @@ import { execFileSync, spawnSync } from 'child_process';
|
|
|
13
13
|
import fs from 'fs';
|
|
14
14
|
import os from 'os';
|
|
15
15
|
import path from 'path';
|
|
16
|
+
import { pinnedHomeEnv } from '@rigour-labs/core/profile';
|
|
16
17
|
import { pushGate } from './hooks-push.js';
|
|
17
18
|
const MARK = 'rigour hooks push --git';
|
|
18
19
|
/**
|
|
@@ -101,18 +102,19 @@ export async function selfTestGitPushHook(rigourCommand) {
|
|
|
101
102
|
git(clone, ['commit', '-qm', 'base']);
|
|
102
103
|
git(clone, ['push', '-q', '-u', 'origin', 'HEAD:main']);
|
|
103
104
|
git(clone, ['checkout', '-qb', 'feature']);
|
|
105
|
+
// The clone is in the temp folder, where another profile may match: the pushes keep this repository's home.
|
|
104
106
|
const installed = installGitPushHook(clone, rigourCommand);
|
|
105
107
|
steps.push(`hook ${installed.action} at ${installed.path}`);
|
|
106
108
|
fs.writeFileSync(path.join(clone, 'src/util.ts'), 'export const used = 1;\nexport const forgotten = 2;\n');
|
|
107
109
|
git(clone, ['commit', '-qam', 'adds an export nothing uses']);
|
|
108
|
-
const refused = spawnSync('git', ['push', 'origin', 'feature'], { cwd: clone, encoding: 'utf8', env: { ...
|
|
110
|
+
const refused = spawnSync('git', ['push', 'origin', 'feature'], { cwd: clone, encoding: 'utf8', env: { ...pinnedHomeEnv(), RIGOUR_TELEMETRY: '0' } });
|
|
109
111
|
const refusedRef = gitOutput(remote, ['rev-parse', '--verify', '-q', 'refs/heads/feature']);
|
|
110
112
|
if (refused.status === 0 || refusedRef)
|
|
111
113
|
return { ok: false, steps: [...steps, `FAIL: a push with an unused export went through (git exit ${refused.status}; remote has feature: ${!!refusedRef})\n${(refused.stderr || '').trim().slice(-600)}`] };
|
|
112
114
|
steps.push(`a push with an unused export was refused (git exit ${refused.status}); the remote has no feature branch`);
|
|
113
115
|
fs.writeFileSync(path.join(clone, 'src/util.ts'), 'export const used = 1;\n');
|
|
114
116
|
git(clone, ['commit', '-qam', 'fix']);
|
|
115
|
-
const accepted = spawnSync('git', ['push', 'origin', 'feature'], { cwd: clone, encoding: 'utf8', env: { ...
|
|
117
|
+
const accepted = spawnSync('git', ['push', 'origin', 'feature'], { cwd: clone, encoding: 'utf8', env: { ...pinnedHomeEnv(), RIGOUR_TELEMETRY: '0' } });
|
|
116
118
|
const acceptedRef = gitOutput(remote, ['rev-parse', '--verify', '-q', 'refs/heads/feature']);
|
|
117
119
|
if (accepted.status !== 0 || acceptedRef !== gitOutput(clone, ['rev-parse', 'HEAD']))
|
|
118
120
|
return { ok: false, steps: [...steps, `FAIL: the fixed push did not land (git exit ${accepted.status})\n${(accepted.stderr || '').trim().slice(-600)}`] };
|
|
@@ -4,6 +4,8 @@ export interface LearnReviewsOptions {
|
|
|
4
4
|
limit?: string;
|
|
5
5
|
promote?: string;
|
|
6
6
|
reject?: string;
|
|
7
|
+
notARule?: string;
|
|
8
|
+
takeBack?: string;
|
|
7
9
|
why?: string;
|
|
8
10
|
list?: boolean;
|
|
9
11
|
/** With --list: also list candidates only review bots raised (hidden by default, and counted). */
|
|
@@ -18,6 +18,10 @@ export async function learnReviewsCommand(cwd, options) {
|
|
|
18
18
|
return decide(cwd, options.promote, 'accepted', options.why);
|
|
19
19
|
if (options.reject)
|
|
20
20
|
return decide(cwd, options.reject, 'rejected', options.why);
|
|
21
|
+
if (options.notARule)
|
|
22
|
+
return decide(cwd, options.notARule, 'not-a-rule', options.why);
|
|
23
|
+
if (options.takeBack)
|
|
24
|
+
return decide(cwd, options.takeBack, 'took-back', options.why);
|
|
21
25
|
if (options.list)
|
|
22
26
|
return list(cwd, options.json, options.includeBots);
|
|
23
27
|
if (options.useWording)
|
|
@@ -81,7 +85,7 @@ function list(cwd, json, includeBots) {
|
|
|
81
85
|
console.log('No review lessons yet. Run `rigour learn-reviews`.');
|
|
82
86
|
}
|
|
83
87
|
function printLesson(l) {
|
|
84
|
-
const label = { verified: chalk.green('lesson '), candidate: chalk.yellow('candidate'), rejected: chalk.red('rejected ') };
|
|
88
|
+
const label = { verified: chalk.green('lesson '), candidate: chalk.yellow('candidate'), rejected: chalk.red('rejected '), dropped: chalk.dim('not a rule') };
|
|
85
89
|
const prs = [...new Set(l.evidence.map(e => `#${e.pr}`))].join(', ');
|
|
86
90
|
const by = chalk.dim(`${l.promotedBy ? ` [${l.promotedBy}]` : ''}${l.scope ? ` [${l.scope === 'repo' ? 'every change' : 'its folder'}]` : ''}`);
|
|
87
91
|
console.log(`${label[l.state]} ${chalk.dim(l.id)} ${l.file || '(no file)'}: ${l.text}${by} ${chalk.dim(`(${prs})`)}`);
|
|
@@ -199,17 +203,32 @@ function useWording(cwd, id) {
|
|
|
199
203
|
console.log(chalk.green(`✔ Now reads: ${lesson.text}`));
|
|
200
204
|
}
|
|
201
205
|
/** A person's decision, kept as evidence with who made it (their git email) and why. */
|
|
206
|
+
/** What each decision tells the person it did. */
|
|
207
|
+
const DECIDED = {
|
|
208
|
+
accepted: text => chalk.green(`✔ A lesson now: ${text}`),
|
|
209
|
+
rejected: text => chalk.yellow(`✔ Rejected: ${text}. The judges are told this team decided against it.`),
|
|
210
|
+
'not-a-rule': text => chalk.dim(`✔ Not a rule (a one-off for that pull request): ${text}. Never served, and the judges are told nothing.`),
|
|
211
|
+
'took-back': text => chalk.yellow(`✔ Taken back, a candidate again: ${text}. The earlier decision is kept in its history.`),
|
|
212
|
+
};
|
|
202
213
|
function decide(cwd, id, decision, why) {
|
|
203
214
|
const by = deciderOrExit(cwd);
|
|
204
215
|
if (!by)
|
|
205
216
|
return;
|
|
206
|
-
|
|
217
|
+
let lesson;
|
|
218
|
+
try {
|
|
219
|
+
lesson = decideLesson(cwd, id, decision, by, why);
|
|
220
|
+
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
console.error(chalk.red(e.message));
|
|
223
|
+
process.exitCode = 1;
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
207
226
|
if (!lesson) {
|
|
208
227
|
console.error(chalk.red(`No lesson ${id}.`));
|
|
209
228
|
process.exitCode = 1;
|
|
210
229
|
return;
|
|
211
230
|
}
|
|
212
|
-
console.log(decision
|
|
231
|
+
console.log(DECIDED[decision](lesson.text));
|
|
213
232
|
}
|
|
214
233
|
/** The repository's rigour.yml, or the defaults when it cannot be read (no rigour.yml is the defaults already). */
|
|
215
234
|
async function teamConfig(cwd) {
|
|
@@ -50,6 +50,8 @@ export interface LessonJourney {
|
|
|
50
50
|
};
|
|
51
51
|
/** A candidate only review bots raised, nothing waiting on a person (core quietBotCandidate): hidden until a person asks to see bot points. */
|
|
52
52
|
fromBots?: true;
|
|
53
|
+
/** A person's last decision on a review lesson (promoted, rejected, not a rule) can be taken back to a candidate (core takeBackable). */
|
|
54
|
+
canTakeBack?: true;
|
|
53
55
|
/**
|
|
54
56
|
* Teammates' decisions on a review lesson, received from the team database (core team-decisions.ts): each with the
|
|
55
57
|
* teammate's display name and when the team database got it; and, when a later team decision settled the lesson
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* lesson's subject prefix). Counts that Rigour cannot know here are null, never 0: PR catches
|
|
8
8
|
* recorded on another machine (CI) never reach this one.
|
|
9
9
|
*/
|
|
10
|
-
import { acceptSuggestedText, scopeLesson, decideCompiledCheck, decideLesson, fixLessonPrefix, proposeCompiledChecks, readCompiledChecks, suspension, localOutcomeMetrics, pendingDecision, quietBotCandidate, listKnowledgeLessons, readLessons, lastDecision, readTeamDecisionCache } from '@rigour-labs/core';
|
|
10
|
+
import { acceptSuggestedText, scopeLesson, decideCompiledCheck, decideLesson, fixLessonPrefix, proposeCompiledChecks, readCompiledChecks, suspension, localOutcomeMetrics, pendingDecision, quietBotCandidate, listKnowledgeLessons, readLessons, lastDecision, takeBackable, readTeamDecisionCache } from '@rigour-labs/core';
|
|
11
11
|
import { decider } from './git-identity.js';
|
|
12
12
|
import { checkoutRoots, eventsAcross, storiesAcross } from './studio-checkouts.js';
|
|
13
13
|
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
|
@@ -53,6 +53,7 @@ export function buildLearning(input) {
|
|
|
53
53
|
...(l.suggestedText ? { suggestedText: { text: l.suggestedText, why: l.suggestedWhy ?? 'reworded' } } : {}),
|
|
54
54
|
reach: { scope: l.scope ?? 'file', hasFile: !!l.file },
|
|
55
55
|
...(quietBotCandidate(l) ? { fromBots: true } : {}),
|
|
56
|
+
...(takeBackable(l) ? { canTakeBack: true } : {}),
|
|
56
57
|
})),
|
|
57
58
|
// Lessons back to a candidate when outcomes stopped promoting come first: a person decides each once.
|
|
58
59
|
].sort((a, b) => Number(!!b.reclassified) - Number(!!a.reclassified) || b.learnedAt.localeCompare(a.learnedAt))
|
|
@@ -107,6 +108,7 @@ function decisionFor(lesson) {
|
|
|
107
108
|
function studioWhy(why) {
|
|
108
109
|
return typeof why === 'string' && why.trim() ? why.trim() : 'decided in Studio';
|
|
109
110
|
}
|
|
111
|
+
const REVIEW_DECISIONS = new Set(['accepted', 'rejected', 'dismissed', 'not-a-rule', 'took-back']);
|
|
110
112
|
/** A person's decision on a review lesson from Studio, recorded as `rigour learn-reviews --promote / --reject` records it: with their git email, and final. */
|
|
111
113
|
export function decideReviewLesson(cwd, body) {
|
|
112
114
|
const { id, decision, why } = (body ?? {});
|
|
@@ -116,8 +118,8 @@ export function decideReviewLesson(cwd, body) {
|
|
|
116
118
|
return rewordFromStudio(cwd, id);
|
|
117
119
|
if (decision === 'scope')
|
|
118
120
|
return scopeFromStudio(cwd, id, body.to, why);
|
|
119
|
-
if (decision
|
|
120
|
-
throw new Error('decision is accepted, rejected, dismissed, reworded or scope');
|
|
121
|
+
if (!REVIEW_DECISIONS.has(decision))
|
|
122
|
+
throw new Error('decision is accepted, rejected, dismissed, not-a-rule, took-back, reworded or scope');
|
|
121
123
|
const lesson = decideLesson(cwd, id, decision, decider(cwd), studioWhy(why));
|
|
122
124
|
if (!lesson)
|
|
123
125
|
throw new Error(`no review lesson ${id}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigour-labs/cli",
|
|
3
|
-
"version": "7.0.0-rc.
|
|
3
|
+
"version": "7.0.0-rc.7",
|
|
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,12 +59,12 @@
|
|
|
59
59
|
"globby": "^14.0.1",
|
|
60
60
|
"ora": "^8.0.1",
|
|
61
61
|
"yaml": "^2.8.2",
|
|
62
|
-
"@rigour-labs/core": "7.0.0-rc.
|
|
62
|
+
"@rigour-labs/core": "7.0.0-rc.7"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@types/fs-extra": "^11.0.4",
|
|
66
66
|
"@types/node": "^25.0.3",
|
|
67
|
-
"@rigour-labs/studio": "7.0.0-rc.
|
|
67
|
+
"@rigour-labs/studio": "7.0.0-rc.7"
|
|
68
68
|
},
|
|
69
69
|
"scripts": {
|
|
70
70
|
"build": "tsc && pnpm bundle-studio",
|