@rigour-labs/cli 1.7.0 → 2.1.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/cli.js +9 -4
- package/dist/commands/check.d.ts +2 -1
- package/dist/commands/check.js +2 -2
- package/dist/smoke.test.js +57 -2
- package/package.json +2 -2
- package/src/cli.ts +9 -4
- package/src/commands/check.ts +3 -2
- package/src/smoke.test.ts +64 -3
package/dist/cli.js
CHANGED
|
@@ -16,7 +16,7 @@ const program = new commander_1.Command();
|
|
|
16
16
|
program
|
|
17
17
|
.name('rigour')
|
|
18
18
|
.description('🛡️ Rigour: The Quality Gate Loop for AI-Assisted Engineering')
|
|
19
|
-
.version('
|
|
19
|
+
.version('2.0.0')
|
|
20
20
|
.addHelpText('before', chalk_1.default.bold.cyan(`
|
|
21
21
|
____ _
|
|
22
22
|
/ __ \\(_)____ ___ __ __ _____
|
|
@@ -43,15 +43,20 @@ Examples:
|
|
|
43
43
|
program
|
|
44
44
|
.command('check')
|
|
45
45
|
.description('Run quality gate checks')
|
|
46
|
+
.argument('[files...]', 'Specific files or directories to check')
|
|
46
47
|
.option('--ci', 'CI mode (minimal output, non-zero exit on fail)')
|
|
47
48
|
.option('--json', 'Output report in JSON format')
|
|
49
|
+
.option('-i, --interactive', 'Run in interactive mode with rich output')
|
|
48
50
|
.addHelpText('after', `
|
|
49
51
|
Examples:
|
|
50
|
-
$ rigour check # Run
|
|
52
|
+
$ rigour check # Run standard check
|
|
53
|
+
$ rigour check ./src # Check only the src directory
|
|
54
|
+
$ rigour check ./src/app.ts # Check only app.ts
|
|
55
|
+
$ rigour check --interactive # Run with rich, interactive output
|
|
51
56
|
$ rigour check --ci # Run in CI environment
|
|
52
57
|
`)
|
|
53
|
-
.action(async (options) => {
|
|
54
|
-
await (0, check_js_1.checkCommand)(process.cwd(), options);
|
|
58
|
+
.action(async (files, options) => {
|
|
59
|
+
await (0, check_js_1.checkCommand)(process.cwd(), files, options);
|
|
55
60
|
});
|
|
56
61
|
program
|
|
57
62
|
.command('explain')
|
package/dist/commands/check.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export interface CheckOptions {
|
|
2
2
|
ci?: boolean;
|
|
3
3
|
json?: boolean;
|
|
4
|
+
interactive?: boolean;
|
|
4
5
|
}
|
|
5
|
-
export declare function checkCommand(cwd: string, options?: CheckOptions): Promise<void>;
|
|
6
|
+
export declare function checkCommand(cwd: string, files?: string[], options?: CheckOptions): Promise<void>;
|
package/dist/commands/check.js
CHANGED
|
@@ -14,7 +14,7 @@ const EXIT_PASS = 0;
|
|
|
14
14
|
const EXIT_FAIL = 1;
|
|
15
15
|
const EXIT_CONFIG_ERROR = 2;
|
|
16
16
|
const EXIT_INTERNAL_ERROR = 3;
|
|
17
|
-
async function checkCommand(cwd, options = {}) {
|
|
17
|
+
async function checkCommand(cwd, files = [], options = {}) {
|
|
18
18
|
const configPath = path_1.default.join(cwd, 'rigour.yml');
|
|
19
19
|
if (!(await fs_extra_1.default.pathExists(configPath))) {
|
|
20
20
|
if (options.json) {
|
|
@@ -33,7 +33,7 @@ async function checkCommand(cwd, options = {}) {
|
|
|
33
33
|
console.log(chalk_1.default.blue('Running Rigour checks...\n'));
|
|
34
34
|
}
|
|
35
35
|
const runner = new core_1.GateRunner(config);
|
|
36
|
-
const report = await runner.run(cwd);
|
|
36
|
+
const report = await runner.run(cwd, files.length > 0 ? files : undefined);
|
|
37
37
|
// Write machine report
|
|
38
38
|
const reportPath = path_1.default.join(cwd, config.output.report_path);
|
|
39
39
|
await fs_extra_1.default.writeJson(reportPath, report, { spaces: 2 });
|
package/dist/smoke.test.js
CHANGED
|
@@ -1,8 +1,63 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
const vitest_1 = require("vitest");
|
|
7
|
+
const check_js_1 = require("./commands/check.js");
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
4
10
|
(0, vitest_1.describe)('CLI Smoke Test', () => {
|
|
5
|
-
|
|
6
|
-
|
|
11
|
+
const testDir = path_1.default.join(process.cwd(), 'temp-smoke-test');
|
|
12
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
13
|
+
await fs_extra_1.default.ensureDir(testDir);
|
|
14
|
+
// @ts-ignore
|
|
15
|
+
vitest_1.vi.spyOn(process, 'exit').mockImplementation(() => { });
|
|
16
|
+
});
|
|
17
|
+
(0, vitest_1.afterEach)(async () => {
|
|
18
|
+
await fs_extra_1.default.remove(testDir);
|
|
19
|
+
vitest_1.vi.restoreAllMocks();
|
|
20
|
+
});
|
|
21
|
+
(0, vitest_1.it)('should respect ignore patterns and avoid EPERM', async () => {
|
|
22
|
+
const restrictedDir = path_1.default.join(testDir, '.restricted');
|
|
23
|
+
await fs_extra_1.default.ensureDir(restrictedDir);
|
|
24
|
+
await fs_extra_1.default.writeFile(path_1.default.join(restrictedDir, 'secret.js'), 'TODO: leak');
|
|
25
|
+
await fs_extra_1.default.writeFile(path_1.default.join(testDir, 'rigour.yml'), `
|
|
26
|
+
version: 1
|
|
27
|
+
ignore:
|
|
28
|
+
- ".restricted/**"
|
|
29
|
+
gates:
|
|
30
|
+
forbid_todos: true
|
|
31
|
+
required_files: []
|
|
32
|
+
`);
|
|
33
|
+
// Simulate EPERM by changing permissions
|
|
34
|
+
await fs_extra_1.default.chmod(restrictedDir, 0o000);
|
|
35
|
+
try {
|
|
36
|
+
// We need to mock process.exit or checkCommand should not exit if we want to test it easily
|
|
37
|
+
// For now, we'll just verify it doesn't throw before it would exit (internal logic)
|
|
38
|
+
// But checkCommand calls process.exit(1) on failure.
|
|
39
|
+
// Re-importing checkCommand to ensure it uses the latest core
|
|
40
|
+
await (0, vitest_1.expect)((0, check_js_1.checkCommand)(testDir, [], { ci: true })).resolves.not.toThrow();
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
await fs_extra_1.default.chmod(restrictedDir, 0o777);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
(0, vitest_1.it)('should check specific files when provided', async () => {
|
|
47
|
+
await fs_extra_1.default.writeFile(path_1.default.join(testDir, 'bad.js'), 'TODO: fixme');
|
|
48
|
+
await fs_extra_1.default.writeFile(path_1.default.join(testDir, 'good.js'), 'console.log("hello")');
|
|
49
|
+
await fs_extra_1.default.writeFile(path_1.default.join(testDir, 'rigour.yml'), `
|
|
50
|
+
version: 1
|
|
51
|
+
gates:
|
|
52
|
+
forbid_todos: true
|
|
53
|
+
required_files: []
|
|
54
|
+
`);
|
|
55
|
+
// If we check ONLY good.js, it should PASS (exit PASS)
|
|
56
|
+
await (0, check_js_1.checkCommand)(testDir, [path_1.default.join(testDir, 'good.js')], { ci: true });
|
|
57
|
+
(0, vitest_1.expect)(process.exit).toHaveBeenCalledWith(0);
|
|
58
|
+
// If we check bad.js, it should FAIL (exit FAIL)
|
|
59
|
+
vitest_1.vi.clearAllMocks();
|
|
60
|
+
await (0, check_js_1.checkCommand)(testDir, [path_1.default.join(testDir, 'bad.js')], { ci: true });
|
|
61
|
+
(0, vitest_1.expect)(process.exit).toHaveBeenCalledWith(1);
|
|
7
62
|
});
|
|
8
63
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigour-labs/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"bin": {
|
|
5
5
|
"rigour": "dist/cli.js"
|
|
6
6
|
},
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"fs-extra": "^11.2.0",
|
|
21
21
|
"globby": "^14.0.1",
|
|
22
22
|
"yaml": "^2.8.2",
|
|
23
|
-
"@rigour-labs/core": "1.
|
|
23
|
+
"@rigour-labs/core": "2.1.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/fs-extra": "^11.0.4",
|
package/src/cli.ts
CHANGED
|
@@ -13,7 +13,7 @@ const program = new Command();
|
|
|
13
13
|
program
|
|
14
14
|
.name('rigour')
|
|
15
15
|
.description('🛡️ Rigour: The Quality Gate Loop for AI-Assisted Engineering')
|
|
16
|
-
.version('
|
|
16
|
+
.version('2.0.0')
|
|
17
17
|
.addHelpText('before', chalk.bold.cyan(`
|
|
18
18
|
____ _
|
|
19
19
|
/ __ \\(_)____ ___ __ __ _____
|
|
@@ -42,15 +42,20 @@ Examples:
|
|
|
42
42
|
program
|
|
43
43
|
.command('check')
|
|
44
44
|
.description('Run quality gate checks')
|
|
45
|
+
.argument('[files...]', 'Specific files or directories to check')
|
|
45
46
|
.option('--ci', 'CI mode (minimal output, non-zero exit on fail)')
|
|
46
47
|
.option('--json', 'Output report in JSON format')
|
|
48
|
+
.option('-i, --interactive', 'Run in interactive mode with rich output')
|
|
47
49
|
.addHelpText('after', `
|
|
48
50
|
Examples:
|
|
49
|
-
$ rigour check # Run
|
|
51
|
+
$ rigour check # Run standard check
|
|
52
|
+
$ rigour check ./src # Check only the src directory
|
|
53
|
+
$ rigour check ./src/app.ts # Check only app.ts
|
|
54
|
+
$ rigour check --interactive # Run with rich, interactive output
|
|
50
55
|
$ rigour check --ci # Run in CI environment
|
|
51
56
|
`)
|
|
52
|
-
.action(async (options: any) => {
|
|
53
|
-
await checkCommand(process.cwd(), options);
|
|
57
|
+
.action(async (files: string[], options: any) => {
|
|
58
|
+
await checkCommand(process.cwd(), files, options);
|
|
54
59
|
});
|
|
55
60
|
|
|
56
61
|
program
|
package/src/commands/check.ts
CHANGED
|
@@ -13,9 +13,10 @@ const EXIT_INTERNAL_ERROR = 3;
|
|
|
13
13
|
export interface CheckOptions {
|
|
14
14
|
ci?: boolean;
|
|
15
15
|
json?: boolean;
|
|
16
|
+
interactive?: boolean;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
export async function checkCommand(cwd: string, options: CheckOptions = {}) {
|
|
19
|
+
export async function checkCommand(cwd: string, files: string[] = [], options: CheckOptions = {}) {
|
|
19
20
|
const configPath = path.join(cwd, 'rigour.yml');
|
|
20
21
|
|
|
21
22
|
if (!(await fs.pathExists(configPath))) {
|
|
@@ -37,7 +38,7 @@ export async function checkCommand(cwd: string, options: CheckOptions = {}) {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
const runner = new GateRunner(config);
|
|
40
|
-
const report = await runner.run(cwd);
|
|
41
|
+
const report = await runner.run(cwd, files.length > 0 ? files : undefined);
|
|
41
42
|
|
|
42
43
|
// Write machine report
|
|
43
44
|
const reportPath = path.join(cwd, config.output.report_path);
|
package/src/smoke.test.ts
CHANGED
|
@@ -1,7 +1,68 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { checkCommand } from './commands/check.js';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import path from 'path';
|
|
2
5
|
|
|
3
6
|
describe('CLI Smoke Test', () => {
|
|
4
|
-
|
|
5
|
-
|
|
7
|
+
const testDir = path.join(process.cwd(), 'temp-smoke-test');
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await fs.ensureDir(testDir);
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
vi.spyOn(process, 'exit').mockImplementation(() => { });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await fs.remove(testDir);
|
|
17
|
+
vi.restoreAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should respect ignore patterns and avoid EPERM', async () => {
|
|
21
|
+
const restrictedDir = path.join(testDir, '.restricted');
|
|
22
|
+
await fs.ensureDir(restrictedDir);
|
|
23
|
+
await fs.writeFile(path.join(restrictedDir, 'secret.js'), 'TODO: leak');
|
|
24
|
+
|
|
25
|
+
await fs.writeFile(path.join(testDir, 'rigour.yml'), `
|
|
26
|
+
version: 1
|
|
27
|
+
ignore:
|
|
28
|
+
- ".restricted/**"
|
|
29
|
+
gates:
|
|
30
|
+
forbid_todos: true
|
|
31
|
+
required_files: []
|
|
32
|
+
`);
|
|
33
|
+
|
|
34
|
+
// Simulate EPERM by changing permissions
|
|
35
|
+
await fs.chmod(restrictedDir, 0o000);
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
// We need to mock process.exit or checkCommand should not exit if we want to test it easily
|
|
39
|
+
// For now, we'll just verify it doesn't throw before it would exit (internal logic)
|
|
40
|
+
// But checkCommand calls process.exit(1) on failure.
|
|
41
|
+
|
|
42
|
+
// Re-importing checkCommand to ensure it uses the latest core
|
|
43
|
+
await expect(checkCommand(testDir, [], { ci: true })).resolves.not.toThrow();
|
|
44
|
+
} finally {
|
|
45
|
+
await fs.chmod(restrictedDir, 0o777);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should check specific files when provided', async () => {
|
|
50
|
+
await fs.writeFile(path.join(testDir, 'bad.js'), 'TODO: fixme');
|
|
51
|
+
await fs.writeFile(path.join(testDir, 'good.js'), 'console.log("hello")');
|
|
52
|
+
await fs.writeFile(path.join(testDir, 'rigour.yml'), `
|
|
53
|
+
version: 1
|
|
54
|
+
gates:
|
|
55
|
+
forbid_todos: true
|
|
56
|
+
required_files: []
|
|
57
|
+
`);
|
|
58
|
+
|
|
59
|
+
// If we check ONLY good.js, it should PASS (exit PASS)
|
|
60
|
+
await checkCommand(testDir, [path.join(testDir, 'good.js')], { ci: true });
|
|
61
|
+
expect(process.exit).toHaveBeenCalledWith(0);
|
|
62
|
+
|
|
63
|
+
// If we check bad.js, it should FAIL (exit FAIL)
|
|
64
|
+
vi.clearAllMocks();
|
|
65
|
+
await checkCommand(testDir, [path.join(testDir, 'bad.js')], { ci: true });
|
|
66
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
6
67
|
});
|
|
7
68
|
});
|