@rigour-labs/cli 7.0.0-rc.3 → 7.0.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/doctor.js +1 -1
- package/dist/commands/hooks.d.ts +2 -0
- package/dist/commands/hooks.js +5 -0
- package/dist/commands/repo-setup.js +12 -10
- package/dist/commands/setup.js +75 -14
- package/package.json +3 -3
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'));
|
|
@@ -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
|
}
|
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.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,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.5"
|
|
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.5"
|
|
68
68
|
},
|
|
69
69
|
"scripts": {
|
|
70
70
|
"build": "tsc && pnpm bundle-studio",
|