@rigour-labs/cli 7.0.0-rc.4 → 7.0.0-rc.6
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/doctor.js +1 -1
- package/dist/commands/hooks.d.ts +2 -0
- package/dist/commands/hooks.js +5 -0
- package/dist/commands/learn-reviews.d.ts +2 -0
- package/dist/commands/learn-reviews.js +22 -3
- package/dist/commands/repo-setup.js +12 -10
- package/dist/commands/setup.js +75 -14
- 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)')
|
package/dist/commands/doctor.js
CHANGED
|
@@ -151,7 +151,7 @@ export async function printRepoSetup(cwd) {
|
|
|
151
151
|
// Settings an older Rigour wrote that no longer mean what they say: loud, since nothing else reports them.
|
|
152
152
|
for (const migration of configMigrations(cwd)) {
|
|
153
153
|
console.log(` ${chalk.red('✘')} ${chalk.red(migration.problem)}`);
|
|
154
|
-
console.log(chalk.dim(` fix: ${migration.automatic ? 'rigour setup' : 'edit rigour.yml'}`));
|
|
154
|
+
console.log(chalk.dim(` fix: ${migration.automatic ? 'rigour setup --team' : 'edit rigour.yml'}`));
|
|
155
155
|
}
|
|
156
156
|
console.log('');
|
|
157
157
|
}
|
package/dist/commands/hooks.d.ts
CHANGED
|
@@ -34,6 +34,8 @@ export interface HooksCheckOptions {
|
|
|
34
34
|
}
|
|
35
35
|
/** This CLI, pinned to its version, as a hook runs it (`npx --yes @rigour-labs/cli@6.6.6`). */
|
|
36
36
|
export declare function pinnedCliCommand(): string;
|
|
37
|
+
/** Every project file Rigour's hooks are written to, for every agent: what a team commits when it installs Rigour. */
|
|
38
|
+
export declare function projectHookPaths(): string[];
|
|
37
39
|
/**
|
|
38
40
|
* The personal install's agent hooks, once per machine (personal.ts): every agent's user-level
|
|
39
41
|
* config, each command guarded so it runs only in a repository switched on with `rigour setup`.
|
package/dist/commands/hooks.js
CHANGED
|
@@ -382,6 +382,11 @@ const GENERATORS = {
|
|
|
382
382
|
cline: generateClineHooks,
|
|
383
383
|
windsurf: generateWindsurfHooks,
|
|
384
384
|
};
|
|
385
|
+
/** Every project file Rigour's hooks are written to, for every agent: what a team commits when it installs Rigour. */
|
|
386
|
+
export function projectHookPaths() {
|
|
387
|
+
const checker = resolveCheckerCommand();
|
|
388
|
+
return [...new Set(Object.keys(GENERATORS).flatMap(tool => GENERATORS[tool](checker, true).map(file => file.path)))];
|
|
389
|
+
}
|
|
385
390
|
// ── File writing ─────────────────────────────────────────────────────
|
|
386
391
|
function printDryRun(files) {
|
|
387
392
|
console.log(chalk.cyan('\nDry run — files that would be created:\n'));
|
|
@@ -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) {
|
|
@@ -32,11 +32,13 @@ export async function checkRepoSetup(cwd, now = new Date(), toolCalls, installed
|
|
|
32
32
|
{ name: 'Windsurf', config: read('.windsurf/hooks.json') + home('.codeium/windsurf/hooks.json') },
|
|
33
33
|
].filter(a => a.config);
|
|
34
34
|
const mcpConfig = read('.mcp.json') + read('.cursor/mcp.json') + home('.claude.json') + home('.cursor/mcp.json');
|
|
35
|
-
|
|
35
|
+
// The repository's own committed install: only `rigour setup --team` changes it (a plain setup leaves it as committed).
|
|
36
|
+
const setup = !personal && /rigour/i.test(agents.map(a => a.config).join('\n')) ? 'rigour setup --team' : 'rigour setup';
|
|
37
|
+
const version = versionCheck(agents.map(a => a.config).join('\n'), mcpConfig, installed, setup);
|
|
36
38
|
return [
|
|
37
39
|
configCheck(cwd, personal),
|
|
38
|
-
editCheck(agents, now, events),
|
|
39
|
-
stopCheck(agents.map(a => a.config).join('\n'), now, events),
|
|
40
|
+
editCheck(agents, now, events, setup),
|
|
41
|
+
stopCheck(agents.map(a => a.config).join('\n'), now, events, setup),
|
|
40
42
|
mcpCheck(mcpConfig, now, calls),
|
|
41
43
|
...(version ? [version] : []),
|
|
42
44
|
prCheck(cwd),
|
|
@@ -47,7 +49,7 @@ export async function checkRepoSetup(cwd, now = new Date(), toolCalls, installed
|
|
|
47
49
|
* (`@rigour-labs/cli@6.12.4`), so an upgrade reaches them only when `rigour setup` rewrites the pin.
|
|
48
50
|
* Nothing to say when nothing pins a version (a source checkout runs its own build).
|
|
49
51
|
*/
|
|
50
|
-
function versionCheck(hooks, mcp, installed) {
|
|
52
|
+
function versionCheck(hooks, mcp, installed, setup) {
|
|
51
53
|
const pins = (text, pkg) => [...new Set([...text.matchAll(new RegExp(`@rigour-labs/${pkg}@([0-9A-Za-z.-]+)`, 'g'))].map(m => m[1]))];
|
|
52
54
|
const hookPins = pins(hooks, 'cli');
|
|
53
55
|
const mcpPins = pins(mcp, 'mcp');
|
|
@@ -62,7 +64,7 @@ function versionCheck(hooks, mcp, installed) {
|
|
|
62
64
|
...mcpPins.filter(v => v !== installed).map(v => (/^\d+$/.test(v) || v === 'latest' ? `the MCP server floats on @${v}` : `the MCP server runs ${v}`)),
|
|
63
65
|
];
|
|
64
66
|
return stale.length
|
|
65
|
-
? { id: 'version', name, state: 'broken', detail: `${stale.join(', ')}; installed is ${installed}`, fix:
|
|
67
|
+
? { id: 'version', name, state: 'broken', detail: `${stale.join(', ')}; installed is ${installed}`, fix: setup }
|
|
66
68
|
: { id: 'version', name, state: 'working', detail: installed };
|
|
67
69
|
}
|
|
68
70
|
function configCheck(cwd, personal) {
|
|
@@ -73,22 +75,22 @@ function configCheck(cwd, personal) {
|
|
|
73
75
|
: { id: 'config', name: 'Project settings', state: 'missing', detail: 'No rigour.yml: Rigour uses its defaults', fix: 'rigour setup' };
|
|
74
76
|
}
|
|
75
77
|
/** One check for every agent's edit hook: events do not say which agent fired them, so counts are not split. */
|
|
76
|
-
function editCheck(agents, now, events) {
|
|
78
|
+
function editCheck(agents, now, events, setup) {
|
|
77
79
|
const wired = agents.filter(a => a.config.includes('hooks check'));
|
|
78
80
|
const name = wired.length ? `Checks ${listOf(wired.map(a => a.name))} as it writes` : 'Checks your agent as it writes';
|
|
79
81
|
if (wired.length === 0)
|
|
80
|
-
return { id: 'edit', name, state: 'missing', detail: 'No edit hook configured', fix:
|
|
82
|
+
return { id: 'edit', name, state: 'missing', detail: 'No edit hook configured', fix: setup };
|
|
81
83
|
const broken = wired.find(a => isOldEditHook(a.config));
|
|
82
84
|
if (broken) {
|
|
83
85
|
// setup rewrites Rigour's own entries where they live (the project, or the machine for a personal install).
|
|
84
|
-
return { id: 'edit', name, state: 'broken', detail: `The ${broken.name} hook is the old form: it reads a variable the agent never sets, so no edit is checked`, fix:
|
|
86
|
+
return { id: 'edit', name, state: 'broken', detail: `The ${broken.name} hook is the old form: it reads a variable the agent never sets, so no edit is checked`, fix: setup };
|
|
85
87
|
}
|
|
86
88
|
return fired('edit', name, events.filter(e => e.type === 'hook_check'), now, 'edit checks');
|
|
87
89
|
}
|
|
88
|
-
function stopCheck(config, now, events) {
|
|
90
|
+
function stopCheck(config, now, events, setup) {
|
|
89
91
|
const name = 'Stops an agent finishing with a known bug';
|
|
90
92
|
if (!config.includes('hooks stop'))
|
|
91
|
-
return { id: 'stop', name, state: 'missing', detail: 'No stop hook configured', fix:
|
|
93
|
+
return { id: 'stop', name, state: 'missing', detail: 'No stop hook configured', fix: setup };
|
|
92
94
|
return fired('stop', name, events.filter(e => e.type === 'stop_review'), now, 'finish checks');
|
|
93
95
|
}
|
|
94
96
|
function mcpCheck(mcpJson, now, calls) {
|
package/dist/commands/setup.js
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
* at user level, guarded so they run only where Rigour is switched on; this repository is switched
|
|
6
6
|
* on inside its git directory (personal.ts). For trying Rigour, or using it alone.
|
|
7
7
|
*
|
|
8
|
-
* Team (`--team
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Team (`--team`): rigour.yml, the project's agent hooks and its MCP server are written into the
|
|
9
|
+
* repository, so everyone who clones gets the same gate. Agent instruction files only with
|
|
10
|
+
* `--instructions`. Only `--team` ever writes into the working tree: in a repository that commits its
|
|
11
|
+
* own Rigour hooks, a plain `rigour setup` leaves every file as committed (the team's install runs).
|
|
11
12
|
*
|
|
12
13
|
* Both install semantic search once per machine (semantic.ts) unless --no-semantic, end with the
|
|
13
14
|
* same checks as `rigour doctor`, and are taken back out by `rigour uninstall`.
|
|
@@ -18,22 +19,36 @@ import fs from 'fs';
|
|
|
18
19
|
import path from 'path';
|
|
19
20
|
import { printRepoSetup } from './doctor.js';
|
|
20
21
|
import { installGitPushHook } from './hooks-git.js';
|
|
21
|
-
import { hooksInitCommand, installMachineHooks, pinnedCliCommand } from './hooks.js';
|
|
22
|
+
import { hooksInitCommand, installMachineHooks, pinnedCliCommand, projectHookPaths } from './hooks.js';
|
|
22
23
|
import { initCommand, registerProjectMcp, resolveMCPServerConfig } from './init.js';
|
|
23
24
|
import { writeAgentInstructions } from './init-handshake.js';
|
|
24
25
|
import { disableHere, enableHere, registerUserMcp } from './personal.js';
|
|
25
|
-
import { applyConfigMigrations } from './setup-migrations.js';
|
|
26
|
+
import { applyConfigMigrations, isOldEditHook } from './setup-migrations.js';
|
|
27
|
+
import { getCliVersion } from '../utils/cli-version.js';
|
|
26
28
|
import { setupSemantic } from './semantic.js';
|
|
27
29
|
const AGENT_NAME = { claude: 'Claude Code', cursor: 'Cursor', cline: 'Cline', windsurf: 'Windsurf' };
|
|
28
30
|
export async function setupCommand(cwd = process.cwd(), options = {}) {
|
|
29
|
-
const team = options.team
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
const team = !!options.team;
|
|
32
|
+
const committed = team ? [] : committedRigourHooks(cwd);
|
|
33
|
+
const mode = team ? 'team: committed to this repository' : committed.length ? "the team's install: nothing in your working tree" : 'personal: nothing in your working tree';
|
|
34
|
+
console.log(chalk.bold.cyan(`\nRigour setup (${mode})\n`));
|
|
35
|
+
if (team) {
|
|
36
|
+
const touched = trackedFiles(cwd, ['rigour.yml', ...rigourInstallPaths()]);
|
|
37
|
+
if (touched.length)
|
|
38
|
+
console.log(chalk.yellow(`Writes into these committed files (commit the result so everyone gets it): ${touched.join(', ')}\n`));
|
|
32
39
|
await teamSetup(cwd, options);
|
|
33
|
-
|
|
40
|
+
}
|
|
41
|
+
else if (committed.length)
|
|
42
|
+
await leaveTeamInstall(cwd, committed, !!options.instructions);
|
|
43
|
+
else {
|
|
44
|
+
if (trackedFiles(cwd, ['rigour.yml']).length)
|
|
45
|
+
console.log(chalk.dim('This repository commits rigour.yml; `rigour setup --team` commits hooks for everyone. This setup is yours alone.\n'));
|
|
34
46
|
await personalSetup(cwd, options.brief !== false);
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
}
|
|
48
|
+
// rigour.yml is the team's file: only --team changes it (doctor's checks below name what is due).
|
|
49
|
+
if (team)
|
|
50
|
+
for (const change of applyConfigMigrations(cwd))
|
|
51
|
+
console.log(chalk.green(`✔ Migrated ${change}`));
|
|
37
52
|
if (options.semantic !== false)
|
|
38
53
|
await setupSemantic(cwd);
|
|
39
54
|
await printRepoSetup(cwd);
|
|
@@ -75,7 +90,53 @@ async function teamSetup(cwd, options) {
|
|
|
75
90
|
else
|
|
76
91
|
await initCommand(cwd, { instructions: options.instructions, brief: options.brief !== false });
|
|
77
92
|
}
|
|
78
|
-
/**
|
|
79
|
-
|
|
80
|
-
|
|
93
|
+
/**
|
|
94
|
+
* The team's install, as committed here: tracked project hook and MCP files that run Rigour. Their agents already run
|
|
95
|
+
* them, so a person's setup changes none of them, and personal hooks beside them would run every check twice.
|
|
96
|
+
*/
|
|
97
|
+
async function leaveTeamInstall(cwd, committed, instructions) {
|
|
98
|
+
disableHere(cwd, false);
|
|
99
|
+
const text = committed.map(file => fs.readFileSync(path.join(cwd, file), 'utf8')).join('\n');
|
|
100
|
+
const pins = [...new Set([...text.matchAll(/@rigour-labs\/cli@([0-9A-Za-z.-]+)/g)].map(m => m[1]))];
|
|
101
|
+
const installed = getCliVersion();
|
|
102
|
+
const stale = pins.filter(pin => pin !== installed);
|
|
103
|
+
console.log(chalk.green('✔ This repository commits its own Rigour install, and your agents run it. Left as committed:'));
|
|
104
|
+
for (const file of committed)
|
|
105
|
+
console.log(chalk.dim(` ${file}`));
|
|
106
|
+
if (stale.length) {
|
|
107
|
+
// The committed pin is what the agents run: name it, and what it costs, since this setup does not move it.
|
|
108
|
+
const caveat = isOldEditHook(text) ? ' (its edit hook checks nothing)' : '';
|
|
109
|
+
console.log(chalk.yellow(` These hooks pin Rigour ${pins.join(', ')}; they keep running that version${caveat} until someone runs \`rigour setup --team\` and commits the result. Installed here: ${installed}.`));
|
|
110
|
+
}
|
|
111
|
+
else
|
|
112
|
+
console.log(chalk.dim(' To change it for everyone: rigour setup --team, then commit the result.'));
|
|
113
|
+
// This clone's own: git's pre-push hook lives in the git directory, never in the working tree.
|
|
114
|
+
const push = installGitPushHook(cwd, pinnedCliCommand(), { workingTree: false });
|
|
115
|
+
if (push.action === 'managed elsewhere')
|
|
116
|
+
console.log(chalk.yellow(`Git pre-push hooks are managed outside this repository (${push.path}); add: ${pinnedCliCommand()} hooks push --git "$@" || exit $?`));
|
|
117
|
+
else if (push.action !== 'no repository')
|
|
118
|
+
console.log(chalk.green(`✔ Push gate: git's pre-push hook (${push.action}); the only thing installed here`));
|
|
119
|
+
// Asked for by name: written only where the repository has none, so no committed file changes.
|
|
120
|
+
if (instructions)
|
|
121
|
+
await writeAgentInstructions(cwd);
|
|
122
|
+
}
|
|
123
|
+
/** Where a team's Rigour install lives in the repository: the project hook and MCP configs. */
|
|
124
|
+
function rigourInstallPaths() {
|
|
125
|
+
return [...projectHookPaths(), '.mcp.json', '.cursor/mcp.json'];
|
|
126
|
+
}
|
|
127
|
+
/** Which of these paths git tracks here. */
|
|
128
|
+
function trackedFiles(cwd, paths) {
|
|
129
|
+
const tracked = spawnSync('git', ['ls-files', '--', ...paths], { cwd, encoding: 'utf8' });
|
|
130
|
+
return tracked.status === 0 ? tracked.stdout.split('\n').filter(Boolean) : [];
|
|
131
|
+
}
|
|
132
|
+
/** The tracked files of a team's Rigour install: project hook and MCP configs that name Rigour. */
|
|
133
|
+
function committedRigourHooks(cwd) {
|
|
134
|
+
return trackedFiles(cwd, rigourInstallPaths()).filter(file => {
|
|
135
|
+
try {
|
|
136
|
+
return /rigour/i.test(fs.readFileSync(path.join(cwd, file), 'utf8'));
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
81
142
|
}
|
|
@@ -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.6",
|
|
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.6"
|
|
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.6"
|
|
68
68
|
},
|
|
69
69
|
"scripts": {
|
|
70
70
|
"build": "tsc && pnpm bundle-studio",
|