@rigour-labs/cli 2.1.0 → 2.2.0
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 +60 -0
- package/dist/commands/init.js +27 -4
- package/dist/commands/run.js +14 -12
- package/package.json +4 -2
- package/src/commands/check.ts +70 -0
- package/src/commands/init.ts +28 -4
- package/src/commands/run.ts +14 -10
package/dist/commands/check.js
CHANGED
|
@@ -9,6 +9,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
9
9
|
const chalk_1 = __importDefault(require("chalk"));
|
|
10
10
|
const yaml_1 = __importDefault(require("yaml"));
|
|
11
11
|
const core_1 = require("@rigour-labs/core");
|
|
12
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
12
13
|
// Exit codes per spec
|
|
13
14
|
const EXIT_PASS = 0;
|
|
14
15
|
const EXIT_FAIL = 1;
|
|
@@ -63,6 +64,10 @@ async function checkCommand(cwd, files = [], options = {}) {
|
|
|
63
64
|
}
|
|
64
65
|
process.exit(report.status === 'PASS' ? EXIT_PASS : EXIT_FAIL);
|
|
65
66
|
}
|
|
67
|
+
if (options.interactive && report.status === 'FAIL') {
|
|
68
|
+
await interactiveMode(report, config);
|
|
69
|
+
process.exit(EXIT_FAIL);
|
|
70
|
+
}
|
|
66
71
|
// Normal human-readable output
|
|
67
72
|
if (report.status === 'PASS') {
|
|
68
73
|
console.log(chalk_1.default.green.bold('✔ PASS - All quality gates satisfied.'));
|
|
@@ -87,6 +92,18 @@ async function checkCommand(cwd, files = [], options = {}) {
|
|
|
87
92
|
process.exit(report.status === 'PASS' ? EXIT_PASS : EXIT_FAIL);
|
|
88
93
|
}
|
|
89
94
|
catch (error) {
|
|
95
|
+
if (error.name === 'ZodError') {
|
|
96
|
+
if (options.json) {
|
|
97
|
+
console.log(JSON.stringify({ error: 'CONFIG_ERROR', details: error.issues }));
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
console.error(chalk_1.default.red('\nInvalid rigour.yml configuration:'));
|
|
101
|
+
error.issues.forEach((issue) => {
|
|
102
|
+
console.error(chalk_1.default.red(` • ${issue.path.join('.')}: ${issue.message}`));
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
process.exit(EXIT_CONFIG_ERROR);
|
|
106
|
+
}
|
|
90
107
|
if (options.json) {
|
|
91
108
|
console.log(JSON.stringify({ error: 'INTERNAL_ERROR', message: error.message }));
|
|
92
109
|
}
|
|
@@ -96,3 +113,46 @@ async function checkCommand(cwd, files = [], options = {}) {
|
|
|
96
113
|
process.exit(EXIT_INTERNAL_ERROR);
|
|
97
114
|
}
|
|
98
115
|
}
|
|
116
|
+
async function interactiveMode(report, config) {
|
|
117
|
+
console.clear();
|
|
118
|
+
console.log(chalk_1.default.bold.blue('══ Rigour Interactive Review ══\n'));
|
|
119
|
+
console.log(chalk_1.default.yellow(`${report.failures.length} violations found.\n`));
|
|
120
|
+
const choices = report.failures.map((f, i) => ({
|
|
121
|
+
name: `[${f.id}] ${f.title}`,
|
|
122
|
+
value: i
|
|
123
|
+
}));
|
|
124
|
+
choices.push(new inquirer_1.default.Separator());
|
|
125
|
+
choices.push({ name: 'Exit', value: -1 });
|
|
126
|
+
let exit = false;
|
|
127
|
+
while (!exit) {
|
|
128
|
+
const { index } = await inquirer_1.default.prompt([
|
|
129
|
+
{
|
|
130
|
+
type: 'list',
|
|
131
|
+
name: 'index',
|
|
132
|
+
message: 'Select a violation to view details:',
|
|
133
|
+
choices,
|
|
134
|
+
pageSize: 15
|
|
135
|
+
}
|
|
136
|
+
]);
|
|
137
|
+
if (index === -1) {
|
|
138
|
+
exit = true;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const failure = report.failures[index];
|
|
142
|
+
console.clear();
|
|
143
|
+
console.log(chalk_1.default.bold.red(`\nViolation: ${failure.title}`));
|
|
144
|
+
console.log(chalk_1.default.dim(`ID: ${failure.id}`));
|
|
145
|
+
console.log(`\n${chalk_1.default.bold('Details:')}\n${failure.details}`);
|
|
146
|
+
if (failure.files && failure.files.length > 0) {
|
|
147
|
+
console.log(`\n${chalk_1.default.bold('Impacted Files:')}`);
|
|
148
|
+
failure.files.forEach((f) => console.log(chalk_1.default.dim(` - ${f}`)));
|
|
149
|
+
}
|
|
150
|
+
if (failure.hint) {
|
|
151
|
+
console.log(`\n${chalk_1.default.bold.cyan('Hint:')} ${failure.hint}`);
|
|
152
|
+
}
|
|
153
|
+
console.log(chalk_1.default.dim('\n' + '─'.repeat(40)));
|
|
154
|
+
await inquirer_1.default.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to return to list...' }]);
|
|
155
|
+
console.clear();
|
|
156
|
+
console.log(chalk_1.default.bold.blue('══ Rigour Interactive Review ══\n'));
|
|
157
|
+
}
|
|
158
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -116,8 +116,10 @@ npx @rigour-labs/cli run -- <agent-command>
|
|
|
116
116
|
\`\`\`
|
|
117
117
|
`;
|
|
118
118
|
// 1. Create Universal Instructions
|
|
119
|
-
await fs_extra_1.default.
|
|
120
|
-
|
|
119
|
+
if (!(await fs_extra_1.default.pathExists(instructionsPath))) {
|
|
120
|
+
await fs_extra_1.default.writeFile(instructionsPath, ruleContent);
|
|
121
|
+
console.log(chalk_1.default.green('✔ Initialized Universal Agent Handshake (docs/AGENT_INSTRUCTIONS.md)'));
|
|
122
|
+
}
|
|
121
123
|
// 2. Create Cursor Specific Rules (.mdc)
|
|
122
124
|
const cursorRulesDir = path_1.default.join(cwd, '.cursor', 'rules');
|
|
123
125
|
await fs_extra_1.default.ensureDir(cursorRulesDir);
|
|
@@ -128,7 +130,28 @@ globs: **/*
|
|
|
128
130
|
---
|
|
129
131
|
|
|
130
132
|
${ruleContent}`;
|
|
131
|
-
await fs_extra_1.default.
|
|
132
|
-
|
|
133
|
+
if (!(await fs_extra_1.default.pathExists(mdcPath))) {
|
|
134
|
+
await fs_extra_1.default.writeFile(mdcPath, mdcContent);
|
|
135
|
+
console.log(chalk_1.default.green('✔ Initialized Cursor Handshake (.cursor/rules/rigour.mdc)'));
|
|
136
|
+
}
|
|
137
|
+
// 3. Update .gitignore
|
|
138
|
+
const gitignorePath = path_1.default.join(cwd, '.gitignore');
|
|
139
|
+
const ignorePatterns = ['rigour-report.json', 'rigour-fix-packet.json', '.rigour/'];
|
|
140
|
+
try {
|
|
141
|
+
let content = '';
|
|
142
|
+
if (await fs_extra_1.default.pathExists(gitignorePath)) {
|
|
143
|
+
content = await fs_extra_1.default.readFile(gitignorePath, 'utf-8');
|
|
144
|
+
}
|
|
145
|
+
const toAdd = ignorePatterns.filter(p => !content.includes(p));
|
|
146
|
+
if (toAdd.length > 0) {
|
|
147
|
+
const separator = content.endsWith('\n') ? '' : '\n';
|
|
148
|
+
const newContent = `${content}${separator}\n# Rigour Artifacts\n${toAdd.join('\n')}\n`;
|
|
149
|
+
await fs_extra_1.default.writeFile(gitignorePath, newContent);
|
|
150
|
+
console.log(chalk_1.default.green('✔ Updated .gitignore'));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
// Failing to update .gitignore isn't fatal
|
|
155
|
+
}
|
|
133
156
|
console.log(chalk_1.default.blue('\nRigour is ready. Run `npx @rigour-labs/cli check` to verify your project.'));
|
|
134
157
|
}
|
package/dist/commands/run.js
CHANGED
|
@@ -41,13 +41,20 @@ async function runLoop(cwd, agentArgs, options) {
|
|
|
41
41
|
// We keep the first part of the command (the agent) but can append or wrap
|
|
42
42
|
// For simplicity, we assume the agent can read the JSON file we generate
|
|
43
43
|
}
|
|
44
|
+
const getTrackedChanges = async () => {
|
|
45
|
+
try {
|
|
46
|
+
const { stdout } = await (0, execa_1.execa)('git', ['status', '--porcelain'], { cwd });
|
|
47
|
+
return stdout.split('\n')
|
|
48
|
+
.filter(l => l.trim())
|
|
49
|
+
.filter(line => /M|A|D|R/.test(line.slice(0, 2)))
|
|
50
|
+
.map(l => l.slice(3).trim());
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
};
|
|
44
56
|
// Snapshot changed files before agent runs
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const { stdout } = await (0, execa_1.execa)('git', ['status', '--porcelain'], { cwd });
|
|
48
|
-
beforeFiles = stdout.split('\n').filter(l => l.trim()).map(l => l.slice(3).trim());
|
|
49
|
-
}
|
|
50
|
-
catch (e) { }
|
|
57
|
+
const beforeFiles = await getTrackedChanges();
|
|
51
58
|
// 2. Run the agent command
|
|
52
59
|
if (currentArgs.length > 0) {
|
|
53
60
|
console.log(chalk_1.default.cyan(`\n🚀 DEPLOYING AGENT:`));
|
|
@@ -60,12 +67,7 @@ async function runLoop(cwd, agentArgs, options) {
|
|
|
60
67
|
}
|
|
61
68
|
}
|
|
62
69
|
// Snapshot changed files after agent runs
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
const { stdout } = await (0, execa_1.execa)('git', ['status', '--porcelain'], { cwd });
|
|
66
|
-
afterFiles = stdout.split('\n').filter(l => l.trim()).map(l => l.slice(3).trim());
|
|
67
|
-
}
|
|
68
|
-
catch (e) { }
|
|
70
|
+
const afterFiles = await getTrackedChanges();
|
|
69
71
|
const changedThisCycle = afterFiles.filter(f => !beforeFiles.includes(f));
|
|
70
72
|
const maxFiles = config.gates.safety?.max_files_changed_per_cycle || 10;
|
|
71
73
|
if (changedThisCycle.length > maxFiles) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigour-labs/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"bin": {
|
|
5
5
|
"rigour": "dist/cli.js"
|
|
6
6
|
},
|
|
@@ -19,11 +19,13 @@
|
|
|
19
19
|
"execa": "^8.0.1",
|
|
20
20
|
"fs-extra": "^11.2.0",
|
|
21
21
|
"globby": "^14.0.1",
|
|
22
|
+
"inquirer": "9.2.16",
|
|
22
23
|
"yaml": "^2.8.2",
|
|
23
|
-
"@rigour-labs/core": "2.
|
|
24
|
+
"@rigour-labs/core": "2.2.0"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
27
|
"@types/fs-extra": "^11.0.4",
|
|
28
|
+
"@types/inquirer": "9.0.7",
|
|
27
29
|
"@types/node": "^25.0.3"
|
|
28
30
|
},
|
|
29
31
|
"scripts": {
|
package/src/commands/check.ts
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import yaml from 'yaml';
|
|
5
5
|
import { GateRunner, ConfigSchema, Failure } from '@rigour-labs/core';
|
|
6
|
+
import inquirer from 'inquirer';
|
|
6
7
|
|
|
7
8
|
// Exit codes per spec
|
|
8
9
|
const EXIT_PASS = 0;
|
|
@@ -72,6 +73,11 @@ export async function checkCommand(cwd: string, files: string[] = [], options: C
|
|
|
72
73
|
process.exit(report.status === 'PASS' ? EXIT_PASS : EXIT_FAIL);
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
if (options.interactive && report.status === 'FAIL') {
|
|
77
|
+
await interactiveMode(report, config);
|
|
78
|
+
process.exit(EXIT_FAIL);
|
|
79
|
+
}
|
|
80
|
+
|
|
75
81
|
// Normal human-readable output
|
|
76
82
|
if (report.status === 'PASS') {
|
|
77
83
|
console.log(chalk.green.bold('✔ PASS - All quality gates satisfied.'));
|
|
@@ -99,6 +105,18 @@ export async function checkCommand(cwd: string, files: string[] = [], options: C
|
|
|
99
105
|
process.exit(report.status === 'PASS' ? EXIT_PASS : EXIT_FAIL);
|
|
100
106
|
|
|
101
107
|
} catch (error: any) {
|
|
108
|
+
if (error.name === 'ZodError') {
|
|
109
|
+
if (options.json) {
|
|
110
|
+
console.log(JSON.stringify({ error: 'CONFIG_ERROR', details: error.issues }));
|
|
111
|
+
} else {
|
|
112
|
+
console.error(chalk.red('\nInvalid rigour.yml configuration:'));
|
|
113
|
+
error.issues.forEach((issue: any) => {
|
|
114
|
+
console.error(chalk.red(` • ${issue.path.join('.')}: ${issue.message}`));
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
process.exit(EXIT_CONFIG_ERROR);
|
|
118
|
+
}
|
|
119
|
+
|
|
102
120
|
if (options.json) {
|
|
103
121
|
console.log(JSON.stringify({ error: 'INTERNAL_ERROR', message: error.message }));
|
|
104
122
|
} else if (!options.ci) {
|
|
@@ -107,3 +125,55 @@ export async function checkCommand(cwd: string, files: string[] = [], options: C
|
|
|
107
125
|
process.exit(EXIT_INTERNAL_ERROR);
|
|
108
126
|
}
|
|
109
127
|
}
|
|
128
|
+
|
|
129
|
+
async function interactiveMode(report: any, config: any) {
|
|
130
|
+
console.clear();
|
|
131
|
+
console.log(chalk.bold.blue('══ Rigour Interactive Review ══\n'));
|
|
132
|
+
console.log(chalk.yellow(`${report.failures.length} violations found.\n`));
|
|
133
|
+
|
|
134
|
+
const choices = report.failures.map((f: Failure, i: number) => ({
|
|
135
|
+
name: `[${f.id}] ${f.title}`,
|
|
136
|
+
value: i
|
|
137
|
+
}));
|
|
138
|
+
|
|
139
|
+
choices.push(new (inquirer as any).Separator());
|
|
140
|
+
choices.push({ name: 'Exit', value: -1 });
|
|
141
|
+
|
|
142
|
+
let exit = false;
|
|
143
|
+
while (!exit) {
|
|
144
|
+
const { index } = await inquirer.prompt([
|
|
145
|
+
{
|
|
146
|
+
type: 'list',
|
|
147
|
+
name: 'index',
|
|
148
|
+
message: 'Select a violation to view details:',
|
|
149
|
+
choices,
|
|
150
|
+
pageSize: 15
|
|
151
|
+
}
|
|
152
|
+
]);
|
|
153
|
+
|
|
154
|
+
if (index === -1) {
|
|
155
|
+
exit = true;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const failure = report.failures[index];
|
|
160
|
+
console.clear();
|
|
161
|
+
console.log(chalk.bold.red(`\nViolation: ${failure.title}`));
|
|
162
|
+
console.log(chalk.dim(`ID: ${failure.id}`));
|
|
163
|
+
console.log(`\n${chalk.bold('Details:')}\n${failure.details}`);
|
|
164
|
+
|
|
165
|
+
if (failure.files && failure.files.length > 0) {
|
|
166
|
+
console.log(`\n${chalk.bold('Impacted Files:')}`);
|
|
167
|
+
failure.files.forEach((f: string) => console.log(chalk.dim(` - ${f}`)));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (failure.hint) {
|
|
171
|
+
console.log(`\n${chalk.bold.cyan('Hint:')} ${failure.hint}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log(chalk.dim('\n' + '─'.repeat(40)));
|
|
175
|
+
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to return to list...' }]);
|
|
176
|
+
console.clear();
|
|
177
|
+
console.log(chalk.bold.blue('══ Rigour Interactive Review ══\n'));
|
|
178
|
+
}
|
|
179
|
+
}
|
package/src/commands/init.ts
CHANGED
|
@@ -124,8 +124,10 @@ npx @rigour-labs/cli run -- <agent-command>
|
|
|
124
124
|
`;
|
|
125
125
|
|
|
126
126
|
// 1. Create Universal Instructions
|
|
127
|
-
await fs.
|
|
128
|
-
|
|
127
|
+
if (!(await fs.pathExists(instructionsPath))) {
|
|
128
|
+
await fs.writeFile(instructionsPath, ruleContent);
|
|
129
|
+
console.log(chalk.green('✔ Initialized Universal Agent Handshake (docs/AGENT_INSTRUCTIONS.md)'));
|
|
130
|
+
}
|
|
129
131
|
|
|
130
132
|
// 2. Create Cursor Specific Rules (.mdc)
|
|
131
133
|
const cursorRulesDir = path.join(cwd, '.cursor', 'rules');
|
|
@@ -138,8 +140,30 @@ globs: **/*
|
|
|
138
140
|
|
|
139
141
|
${ruleContent}`;
|
|
140
142
|
|
|
141
|
-
await fs.
|
|
142
|
-
|
|
143
|
+
if (!(await fs.pathExists(mdcPath))) {
|
|
144
|
+
await fs.writeFile(mdcPath, mdcContent);
|
|
145
|
+
console.log(chalk.green('✔ Initialized Cursor Handshake (.cursor/rules/rigour.mdc)'));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 3. Update .gitignore
|
|
149
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
150
|
+
const ignorePatterns = ['rigour-report.json', 'rigour-fix-packet.json', '.rigour/'];
|
|
151
|
+
try {
|
|
152
|
+
let content = '';
|
|
153
|
+
if (await fs.pathExists(gitignorePath)) {
|
|
154
|
+
content = await fs.readFile(gitignorePath, 'utf-8');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const toAdd = ignorePatterns.filter(p => !content.includes(p));
|
|
158
|
+
if (toAdd.length > 0) {
|
|
159
|
+
const separator = content.endsWith('\n') ? '' : '\n';
|
|
160
|
+
const newContent = `${content}${separator}\n# Rigour Artifacts\n${toAdd.join('\n')}\n`;
|
|
161
|
+
await fs.writeFile(gitignorePath, newContent);
|
|
162
|
+
console.log(chalk.green('✔ Updated .gitignore'));
|
|
163
|
+
}
|
|
164
|
+
} catch (e) {
|
|
165
|
+
// Failing to update .gitignore isn't fatal
|
|
166
|
+
}
|
|
143
167
|
|
|
144
168
|
console.log(chalk.blue('\nRigour is ready. Run `npx @rigour-labs/cli check` to verify your project.'));
|
|
145
169
|
}
|
package/src/commands/run.ts
CHANGED
|
@@ -43,12 +43,20 @@ export async function runLoop(cwd: string, agentArgs: string[], options: { itera
|
|
|
43
43
|
// For simplicity, we assume the agent can read the JSON file we generate
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
const getTrackedChanges = async () => {
|
|
47
|
+
try {
|
|
48
|
+
const { stdout } = await execa('git', ['status', '--porcelain'], { cwd });
|
|
49
|
+
return stdout.split('\n')
|
|
50
|
+
.filter(l => l.trim())
|
|
51
|
+
.filter(line => /M|A|D|R/.test(line.slice(0, 2)))
|
|
52
|
+
.map(l => l.slice(3).trim());
|
|
53
|
+
} catch (e) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
46
58
|
// Snapshot changed files before agent runs
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
const { stdout } = await execa('git', ['status', '--porcelain'], { cwd });
|
|
50
|
-
beforeFiles = stdout.split('\n').filter(l => l.trim()).map(l => l.slice(3).trim());
|
|
51
|
-
} catch (e) { }
|
|
59
|
+
const beforeFiles = await getTrackedChanges();
|
|
52
60
|
|
|
53
61
|
// 2. Run the agent command
|
|
54
62
|
if (currentArgs.length > 0) {
|
|
@@ -62,11 +70,7 @@ export async function runLoop(cwd: string, agentArgs: string[], options: { itera
|
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
// Snapshot changed files after agent runs
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const { stdout } = await execa('git', ['status', '--porcelain'], { cwd });
|
|
68
|
-
afterFiles = stdout.split('\n').filter(l => l.trim()).map(l => l.slice(3).trim());
|
|
69
|
-
} catch (e) { }
|
|
73
|
+
const afterFiles = await getTrackedChanges();
|
|
70
74
|
|
|
71
75
|
const changedThisCycle = afterFiles.filter(f => !beforeFiles.includes(f));
|
|
72
76
|
const maxFiles = config.gates.safety?.max_files_changed_per_cycle || 10;
|