@rigour-labs/cli 1.0.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/LICENSE +21 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +33 -0
- package/dist/commands/check.d.ts +1 -0
- package/dist/commands/check.js +51 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +60 -0
- package/dist/commands/run.d.ts +3 -0
- package/dist/commands/run.js +76 -0
- package/dist/smoke.test.d.ts +1 -0
- package/dist/smoke.test.js +8 -0
- package/package.json +33 -0
- package/src/cli.ts +37 -0
- package/src/commands/check.ts +55 -0
- package/src/commands/init.ts +63 -0
- package/src/commands/run.ts +79 -0
- package/src/smoke.test.ts +7 -0
- package/src/templates/handshake.mdc +39 -0
- package/tsconfig.json +10 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rigour Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const init_js_1 = require("./commands/init.js");
|
|
6
|
+
const check_js_1 = require("./commands/check.js");
|
|
7
|
+
const run_js_1 = require("./commands/run.js");
|
|
8
|
+
const program = new commander_1.Command();
|
|
9
|
+
program
|
|
10
|
+
.name('rigour')
|
|
11
|
+
.description('A quality gate loop controller for AI-assisted coding')
|
|
12
|
+
.version('1.0.0');
|
|
13
|
+
program
|
|
14
|
+
.command('init')
|
|
15
|
+
.description('Initialize VibeGuard in the current directory')
|
|
16
|
+
.action(async () => {
|
|
17
|
+
await (0, init_js_1.initCommand)(process.cwd());
|
|
18
|
+
});
|
|
19
|
+
program
|
|
20
|
+
.command('check')
|
|
21
|
+
.description('Run quality gate checks')
|
|
22
|
+
.action(async () => {
|
|
23
|
+
await (0, check_js_1.checkCommand)(process.cwd());
|
|
24
|
+
});
|
|
25
|
+
program
|
|
26
|
+
.command('run')
|
|
27
|
+
.description('Execute an agent command in a loop until quality gates pass')
|
|
28
|
+
.argument('[command...]', 'The agent command to run (e.g., cursor-agent ...)')
|
|
29
|
+
.option('-i, --iterations <number>', 'Maximum number of loop iterations', '3')
|
|
30
|
+
.action(async (args, options) => {
|
|
31
|
+
await (0, run_js_1.runLoop)(process.cwd(), args, { iterations: parseInt(options.iterations) });
|
|
32
|
+
});
|
|
33
|
+
program.parse();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function checkCommand(cwd: string): Promise<void>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.checkCommand = checkCommand;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const yaml_1 = __importDefault(require("yaml"));
|
|
11
|
+
const core_1 = require("@rigour-labs/core");
|
|
12
|
+
async function checkCommand(cwd) {
|
|
13
|
+
const configPath = path_1.default.join(cwd, 'rigour.yml');
|
|
14
|
+
if (!(await fs_extra_1.default.pathExists(configPath))) {
|
|
15
|
+
console.error(chalk_1.default.red('Error: rigour.yml not found. Run `rigour init` first.'));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const configContent = await fs_extra_1.default.readFile(configPath, 'utf-8');
|
|
19
|
+
const rawConfig = yaml_1.default.parse(configContent);
|
|
20
|
+
const config = core_1.ConfigSchema.parse(rawConfig);
|
|
21
|
+
console.log(chalk_1.default.blue('Running Rigour checks...\n'));
|
|
22
|
+
const runner = new core_1.GateRunner(config);
|
|
23
|
+
const report = await runner.run(cwd);
|
|
24
|
+
// Write machine report
|
|
25
|
+
const reportPath = path_1.default.join(cwd, config.output.report_path);
|
|
26
|
+
await fs_extra_1.default.writeJson(reportPath, report, { spaces: 2 });
|
|
27
|
+
// Print human summary
|
|
28
|
+
if (report.status === 'PASS') {
|
|
29
|
+
console.log(chalk_1.default.green.bold('✔ PASS - All quality gates satisfied.'));
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
console.log(chalk_1.default.red.bold('✘ FAIL - Quality gate violations found.\n'));
|
|
33
|
+
for (const failure of report.failures) {
|
|
34
|
+
console.log(chalk_1.default.red(`[${failure.id}] ${failure.title}`));
|
|
35
|
+
console.log(chalk_1.default.dim(` Details: ${failure.details}`));
|
|
36
|
+
if (failure.files && failure.files.length > 0) {
|
|
37
|
+
console.log(chalk_1.default.dim(' Files:'));
|
|
38
|
+
failure.files.forEach((f) => console.log(chalk_1.default.dim(` - ${f}`)));
|
|
39
|
+
}
|
|
40
|
+
if (failure.hint) {
|
|
41
|
+
console.log(chalk_1.default.cyan(` Hint: ${failure.hint}`));
|
|
42
|
+
}
|
|
43
|
+
console.log('');
|
|
44
|
+
}
|
|
45
|
+
console.log(chalk_1.default.yellow(`See ${config.output.report_path} for full details.`));
|
|
46
|
+
}
|
|
47
|
+
console.log(chalk_1.default.dim(`\nFinished in ${report.stats.duration_ms}ms`));
|
|
48
|
+
if (report.status !== 'PASS') {
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function initCommand(cwd: string): Promise<void>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.initCommand = initCommand;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const yaml_1 = __importDefault(require("yaml"));
|
|
11
|
+
const core_1 = require("@rigour-labs/core");
|
|
12
|
+
async function initCommand(cwd) {
|
|
13
|
+
const discovery = new core_1.DiscoveryService();
|
|
14
|
+
const recommendedConfig = await discovery.discover(cwd);
|
|
15
|
+
const configPath = path_1.default.join(cwd, 'rigour.yml');
|
|
16
|
+
if (await fs_extra_1.default.pathExists(configPath)) {
|
|
17
|
+
console.log(chalk_1.default.yellow('rigour.yml already exists. Skipping initialization.'));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
await fs_extra_1.default.writeFile(configPath, yaml_1.default.stringify(recommendedConfig));
|
|
21
|
+
console.log(chalk_1.default.green('✔ Created rigour.yml'));
|
|
22
|
+
// Create required directories and files
|
|
23
|
+
const requireddocs = recommendedConfig.gates.required_files || [];
|
|
24
|
+
for (const file of requireddocs) {
|
|
25
|
+
const filePath = path_1.default.join(cwd, file);
|
|
26
|
+
if (!(await fs_extra_1.default.pathExists(filePath))) {
|
|
27
|
+
await fs_extra_1.default.ensureFile(filePath);
|
|
28
|
+
console.log(chalk_1.default.dim(` - Created ${file}`));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Agent Handshake (Cursor/AntiGravity)
|
|
32
|
+
const cursorRulesDir = path_1.default.join(cwd, '.cursor', 'rules');
|
|
33
|
+
await fs_extra_1.default.ensureDir(cursorRulesDir);
|
|
34
|
+
const rulePath = path_1.default.join(cursorRulesDir, 'rigour.mdc');
|
|
35
|
+
const ruleContent = `---
|
|
36
|
+
description: Enforcement of Rigour quality gates and best practices.
|
|
37
|
+
globs: **/*
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
# Rigour Enforcement
|
|
41
|
+
|
|
42
|
+
You are operating under Rigour engineering discipline.
|
|
43
|
+
|
|
44
|
+
## Core Rules
|
|
45
|
+
- **Never claim done** until you run \`rigour check\` and it returns PASS.
|
|
46
|
+
- If checks FAIL, fix **only** the listed failures. Do not add new features or refactor unrelated code.
|
|
47
|
+
- Maintain project memory in \`docs/SPEC.md\`, \`docs/ARCH.md\`, and \`docs/DECISIONS.md\`.
|
|
48
|
+
- Keep files modular. If a file exceeds 500 lines, you MUST break it into smaller components.
|
|
49
|
+
- No \`TODO\` or \`FIXME\` comments allowed in the final submission.
|
|
50
|
+
|
|
51
|
+
## Workflow
|
|
52
|
+
1. Write/Modify code.
|
|
53
|
+
2. Run \`rigour check\`.
|
|
54
|
+
3. If FAIL: Read \`rigour-report.json\` for exact failure points and fix them.
|
|
55
|
+
4. If PASS: You may claim task completion.
|
|
56
|
+
`;
|
|
57
|
+
await fs_extra_1.default.writeFile(rulePath, ruleContent);
|
|
58
|
+
console.log(chalk_1.default.green('✔ Initialized Agent Handshake (.cursor/rules/rigour.mdc)'));
|
|
59
|
+
console.log(chalk_1.default.blue('\nRigour is ready. Run `rigour check` to verify your project.'));
|
|
60
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runLoop = runLoop;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const yaml_1 = __importDefault(require("yaml"));
|
|
11
|
+
const execa_1 = require("execa");
|
|
12
|
+
const core_1 = require("@rigour-labs/core");
|
|
13
|
+
async function runLoop(cwd, agentArgs, options) {
|
|
14
|
+
const configPath = path_1.default.join(cwd, 'rigour.yml');
|
|
15
|
+
if (!(await fs_extra_1.default.pathExists(configPath))) {
|
|
16
|
+
console.error(chalk_1.default.red('Error: rigour.yml not found. Run `rigour init` first.'));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const configContent = await fs_extra_1.default.readFile(configPath, 'utf-8');
|
|
21
|
+
const rawConfig = yaml_1.default.parse(configContent);
|
|
22
|
+
const config = core_1.ConfigSchema.parse(rawConfig);
|
|
23
|
+
const runner = new core_1.GateRunner(config);
|
|
24
|
+
let iteration = 0;
|
|
25
|
+
const maxIterations = options.iterations;
|
|
26
|
+
while (iteration < maxIterations) {
|
|
27
|
+
iteration++;
|
|
28
|
+
console.log(chalk_1.default.bold.blue(`\n══════════════════════════════════════════════════════════════════`));
|
|
29
|
+
console.log(chalk_1.default.bold.blue(` RIGOUR LOOP: Iteration ${iteration}/${maxIterations}`));
|
|
30
|
+
console.log(chalk_1.default.bold.blue(`══════════════════════════════════════════════════════════════════`));
|
|
31
|
+
// 1. Run the agent command
|
|
32
|
+
if (agentArgs.length > 0) {
|
|
33
|
+
console.log(chalk_1.default.cyan(`\n🚀 DEPLOYING AGENT:`));
|
|
34
|
+
console.log(chalk_1.default.dim(` Command: ${agentArgs.join(' ')}`));
|
|
35
|
+
try {
|
|
36
|
+
await (0, execa_1.execa)(agentArgs[0], agentArgs.slice(1), { shell: true, stdio: 'inherit', cwd });
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.warn(chalk_1.default.yellow(`\n⚠️ Agent command finished with non-zero exit code. Rigour will now verify state...`));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// 2. Run Rigour Check
|
|
43
|
+
console.log(chalk_1.default.magenta('\n🔍 AUDITING QUALITY GATES...'));
|
|
44
|
+
const report = await runner.run(cwd);
|
|
45
|
+
if (report.status === 'PASS') {
|
|
46
|
+
console.log(chalk_1.default.green.bold('\n✨ PASS - All quality gates satisfied.'));
|
|
47
|
+
console.log(chalk_1.default.green(` Your solution meets the required Engineering Rigour criteria.\n`));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// 3. Generate and print Fix Packet for next iteration
|
|
51
|
+
console.log(chalk_1.default.red.bold(`\n🛑 FAIL - Found ${report.failures.length} engineering violations.`));
|
|
52
|
+
const fixPacket = report.failures.map((f, i) => {
|
|
53
|
+
let msg = chalk_1.default.white(`${i + 1}. `) + chalk_1.default.bold.red(`[${f.id.toUpperCase()}] `) + chalk_1.default.white(f.title);
|
|
54
|
+
msg += `\n ├─ ` + chalk_1.default.dim(`Details: ${f.details}`);
|
|
55
|
+
if (f.hint)
|
|
56
|
+
msg += `\n └─ ` + chalk_1.default.yellow(`FIX: ${f.hint}`);
|
|
57
|
+
return msg;
|
|
58
|
+
}).join('\n\n');
|
|
59
|
+
console.log(chalk_1.default.bold.white('\n📋 ACTIONABLE FIX PACKET:'));
|
|
60
|
+
console.log(fixPacket);
|
|
61
|
+
console.log(chalk_1.default.dim('\nReturning control to agent for the next refinement cycle...'));
|
|
62
|
+
if (iteration === maxIterations) {
|
|
63
|
+
console.log(chalk_1.default.red.bold(`\n❌ CRITICAL: Reached maximum iterations (${maxIterations}).`));
|
|
64
|
+
console.log(chalk_1.default.red(` Quality gates remain unfulfilled. Refactor manually or check agent logs.`));
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
console.error(chalk_1.default.red(`\n❌ FATAL ERROR: ${error.message}`));
|
|
71
|
+
if (error.issues) {
|
|
72
|
+
console.error(chalk_1.default.dim(JSON.stringify(error.issues, null, 2)));
|
|
73
|
+
}
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
(0, vitest_1.describe)('CLI Smoke Test', () => {
|
|
5
|
+
(0, vitest_1.it)('should pass', async () => {
|
|
6
|
+
(0, vitest_1.expect)(true).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rigour-labs/cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"bin": {
|
|
5
|
+
"rigour": "dist/cli.js"
|
|
6
|
+
},
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/erashu212/rigour"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public",
|
|
13
|
+
"provenance": true
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"chalk": "^5.3.0",
|
|
17
|
+
"commander": "^12.0.0",
|
|
18
|
+
"cosmiconfig": "^9.0.0",
|
|
19
|
+
"execa": "^8.0.1",
|
|
20
|
+
"fs-extra": "^11.2.0",
|
|
21
|
+
"globby": "^14.0.1",
|
|
22
|
+
"yaml": "^2.8.2",
|
|
23
|
+
"@rigour-labs/core": "1.0.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/fs-extra": "^11.0.4",
|
|
27
|
+
"@types/node": "^25.0.3"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc",
|
|
31
|
+
"test": "vitest run"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { initCommand } from './commands/init.js';
|
|
4
|
+
import { checkCommand } from './commands/check.js';
|
|
5
|
+
import { runLoop } from './commands/run.js';
|
|
6
|
+
|
|
7
|
+
const program = new Command();
|
|
8
|
+
|
|
9
|
+
program
|
|
10
|
+
.name('rigour')
|
|
11
|
+
.description('A quality gate loop controller for AI-assisted coding')
|
|
12
|
+
.version('1.0.0');
|
|
13
|
+
|
|
14
|
+
program
|
|
15
|
+
.command('init')
|
|
16
|
+
.description('Initialize VibeGuard in the current directory')
|
|
17
|
+
.action(async () => {
|
|
18
|
+
await initCommand(process.cwd());
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
program
|
|
22
|
+
.command('check')
|
|
23
|
+
.description('Run quality gate checks')
|
|
24
|
+
.action(async () => {
|
|
25
|
+
await checkCommand(process.cwd());
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
program
|
|
29
|
+
.command('run')
|
|
30
|
+
.description('Execute an agent command in a loop until quality gates pass')
|
|
31
|
+
.argument('[command...]', 'The agent command to run (e.g., cursor-agent ...)')
|
|
32
|
+
.option('-i, --iterations <number>', 'Maximum number of loop iterations', '3')
|
|
33
|
+
.action(async (args, options) => {
|
|
34
|
+
await runLoop(process.cwd(), args, { iterations: parseInt(options.iterations) });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
program.parse();
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import yaml from 'yaml';
|
|
5
|
+
import { GateRunner, ConfigSchema, Failure } from '@rigour-labs/core';
|
|
6
|
+
|
|
7
|
+
export async function checkCommand(cwd: string) {
|
|
8
|
+
const configPath = path.join(cwd, 'rigour.yml');
|
|
9
|
+
|
|
10
|
+
if (!(await fs.pathExists(configPath))) {
|
|
11
|
+
console.error(chalk.red('Error: rigour.yml not found. Run `rigour init` first.'));
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const configContent = await fs.readFile(configPath, 'utf-8');
|
|
16
|
+
const rawConfig = yaml.parse(configContent);
|
|
17
|
+
const config = ConfigSchema.parse(rawConfig);
|
|
18
|
+
|
|
19
|
+
console.log(chalk.blue('Running Rigour checks...\n'));
|
|
20
|
+
|
|
21
|
+
const runner = new GateRunner(config);
|
|
22
|
+
const report = await runner.run(cwd);
|
|
23
|
+
|
|
24
|
+
// Write machine report
|
|
25
|
+
const reportPath = path.join(cwd, config.output.report_path);
|
|
26
|
+
await fs.writeJson(reportPath, report, { spaces: 2 });
|
|
27
|
+
|
|
28
|
+
// Print human summary
|
|
29
|
+
if (report.status === 'PASS') {
|
|
30
|
+
console.log(chalk.green.bold('✔ PASS - All quality gates satisfied.'));
|
|
31
|
+
} else {
|
|
32
|
+
console.log(chalk.red.bold('✘ FAIL - Quality gate violations found.\n'));
|
|
33
|
+
|
|
34
|
+
for (const failure of report.failures as Failure[]) {
|
|
35
|
+
console.log(chalk.red(`[${failure.id}] ${failure.title}`));
|
|
36
|
+
console.log(chalk.dim(` Details: ${failure.details}`));
|
|
37
|
+
if (failure.files && failure.files.length > 0) {
|
|
38
|
+
console.log(chalk.dim(' Files:'));
|
|
39
|
+
failure.files.forEach((f: string) => console.log(chalk.dim(` - ${f}`)));
|
|
40
|
+
}
|
|
41
|
+
if (failure.hint) {
|
|
42
|
+
console.log(chalk.cyan(` Hint: ${failure.hint}`));
|
|
43
|
+
}
|
|
44
|
+
console.log('');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(chalk.yellow(`See ${config.output.report_path} for full details.`));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(chalk.dim(`\nFinished in ${report.stats.duration_ms}ms`));
|
|
51
|
+
|
|
52
|
+
if (report.status !== 'PASS') {
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import yaml from 'yaml';
|
|
5
|
+
import { DiscoveryService } from '@rigour-labs/core';
|
|
6
|
+
|
|
7
|
+
export async function initCommand(cwd: string) {
|
|
8
|
+
const discovery = new DiscoveryService();
|
|
9
|
+
const recommendedConfig = await discovery.discover(cwd);
|
|
10
|
+
|
|
11
|
+
const configPath = path.join(cwd, 'rigour.yml');
|
|
12
|
+
|
|
13
|
+
if (await fs.pathExists(configPath)) {
|
|
14
|
+
console.log(chalk.yellow('rigour.yml already exists. Skipping initialization.'));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
await fs.writeFile(configPath, yaml.stringify(recommendedConfig));
|
|
19
|
+
console.log(chalk.green('✔ Created rigour.yml'));
|
|
20
|
+
|
|
21
|
+
// Create required directories and files
|
|
22
|
+
const requireddocs = recommendedConfig.gates.required_files || [];
|
|
23
|
+
for (const file of requireddocs) {
|
|
24
|
+
const filePath = path.join(cwd, file);
|
|
25
|
+
if (!(await fs.pathExists(filePath))) {
|
|
26
|
+
await fs.ensureFile(filePath);
|
|
27
|
+
console.log(chalk.dim(` - Created ${file}`));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Agent Handshake (Cursor/AntiGravity)
|
|
32
|
+
const cursorRulesDir = path.join(cwd, '.cursor', 'rules');
|
|
33
|
+
await fs.ensureDir(cursorRulesDir);
|
|
34
|
+
const rulePath = path.join(cursorRulesDir, 'rigour.mdc');
|
|
35
|
+
|
|
36
|
+
const ruleContent = `---
|
|
37
|
+
description: Enforcement of Rigour quality gates and best practices.
|
|
38
|
+
globs: **/*
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
# Rigour Enforcement
|
|
42
|
+
|
|
43
|
+
You are operating under Rigour engineering discipline.
|
|
44
|
+
|
|
45
|
+
## Core Rules
|
|
46
|
+
- **Never claim done** until you run \`rigour check\` and it returns PASS.
|
|
47
|
+
- If checks FAIL, fix **only** the listed failures. Do not add new features or refactor unrelated code.
|
|
48
|
+
- Maintain project memory in \`docs/SPEC.md\`, \`docs/ARCH.md\`, and \`docs/DECISIONS.md\`.
|
|
49
|
+
- Keep files modular. If a file exceeds 500 lines, you MUST break it into smaller components.
|
|
50
|
+
- No \`TODO\` or \`FIXME\` comments allowed in the final submission.
|
|
51
|
+
|
|
52
|
+
## Workflow
|
|
53
|
+
1. Write/Modify code.
|
|
54
|
+
2. Run \`rigour check\`.
|
|
55
|
+
3. If FAIL: Read \`rigour-report.json\` for exact failure points and fix them.
|
|
56
|
+
4. If PASS: You may claim task completion.
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
await fs.writeFile(rulePath, ruleContent);
|
|
60
|
+
console.log(chalk.green('✔ Initialized Agent Handshake (.cursor/rules/rigour.mdc)'));
|
|
61
|
+
|
|
62
|
+
console.log(chalk.blue('\nRigour is ready. Run `rigour check` to verify your project.'));
|
|
63
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import yaml from 'yaml';
|
|
5
|
+
import { execa } from 'execa';
|
|
6
|
+
import { GateRunner, ConfigSchema } from '@rigour-labs/core';
|
|
7
|
+
|
|
8
|
+
export async function runLoop(cwd: string, agentArgs: string[], options: { iterations: number }) {
|
|
9
|
+
const configPath = path.join(cwd, 'rigour.yml');
|
|
10
|
+
|
|
11
|
+
if (!(await fs.pathExists(configPath))) {
|
|
12
|
+
console.error(chalk.red('Error: rigour.yml not found. Run `rigour init` first.'));
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const configContent = await fs.readFile(configPath, 'utf-8');
|
|
18
|
+
const rawConfig = yaml.parse(configContent);
|
|
19
|
+
const config = ConfigSchema.parse(rawConfig);
|
|
20
|
+
const runner = new GateRunner(config);
|
|
21
|
+
|
|
22
|
+
let iteration = 0;
|
|
23
|
+
const maxIterations = options.iterations;
|
|
24
|
+
|
|
25
|
+
while (iteration < maxIterations) {
|
|
26
|
+
iteration++;
|
|
27
|
+
console.log(chalk.bold.blue(`\n══════════════════════════════════════════════════════════════════`));
|
|
28
|
+
console.log(chalk.bold.blue(` RIGOUR LOOP: Iteration ${iteration}/${maxIterations}`));
|
|
29
|
+
console.log(chalk.bold.blue(`══════════════════════════════════════════════════════════════════`));
|
|
30
|
+
|
|
31
|
+
// 1. Run the agent command
|
|
32
|
+
if (agentArgs.length > 0) {
|
|
33
|
+
console.log(chalk.cyan(`\n🚀 DEPLOYING AGENT:`));
|
|
34
|
+
console.log(chalk.dim(` Command: ${agentArgs.join(' ')}`));
|
|
35
|
+
try {
|
|
36
|
+
await execa(agentArgs[0], agentArgs.slice(1), { shell: true, stdio: 'inherit', cwd });
|
|
37
|
+
} catch (error: any) {
|
|
38
|
+
console.warn(chalk.yellow(`\n⚠️ Agent command finished with non-zero exit code. Rigour will now verify state...`));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 2. Run Rigour Check
|
|
43
|
+
console.log(chalk.magenta('\n🔍 AUDITING QUALITY GATES...'));
|
|
44
|
+
const report = await runner.run(cwd);
|
|
45
|
+
|
|
46
|
+
if (report.status === 'PASS') {
|
|
47
|
+
console.log(chalk.green.bold('\n✨ PASS - All quality gates satisfied.'));
|
|
48
|
+
console.log(chalk.green(` Your solution meets the required Engineering Rigour criteria.\n`));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 3. Generate and print Fix Packet for next iteration
|
|
53
|
+
console.log(chalk.red.bold(`\n🛑 FAIL - Found ${report.failures.length} engineering violations.`));
|
|
54
|
+
|
|
55
|
+
const fixPacket = report.failures.map((f, i) => {
|
|
56
|
+
let msg = chalk.white(`${i + 1}. `) + chalk.bold.red(`[${f.id.toUpperCase()}] `) + chalk.white(f.title);
|
|
57
|
+
msg += `\n ├─ ` + chalk.dim(`Details: ${f.details}`);
|
|
58
|
+
if (f.hint) msg += `\n └─ ` + chalk.yellow(`FIX: ${f.hint}`);
|
|
59
|
+
return msg;
|
|
60
|
+
}).join('\n\n');
|
|
61
|
+
|
|
62
|
+
console.log(chalk.bold.white('\n📋 ACTIONABLE FIX PACKET:'));
|
|
63
|
+
console.log(fixPacket);
|
|
64
|
+
console.log(chalk.dim('\nReturning control to agent for the next refinement cycle...'));
|
|
65
|
+
|
|
66
|
+
if (iteration === maxIterations) {
|
|
67
|
+
console.log(chalk.red.bold(`\n❌ CRITICAL: Reached maximum iterations (${maxIterations}).`));
|
|
68
|
+
console.log(chalk.red(` Quality gates remain unfulfilled. Refactor manually or check agent logs.`));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} catch (error: any) {
|
|
73
|
+
console.error(chalk.red(`\n❌ FATAL ERROR: ${error.message}`));
|
|
74
|
+
if (error.issues) {
|
|
75
|
+
console.error(chalk.dim(JSON.stringify(error.issues, null, 2)));
|
|
76
|
+
}
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Engineering Rigour Protocol - Enforces high software standards (SOLID, DRY, Patterns) via automated quality gates.
|
|
3
|
+
globs: **/*
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 🛡️ Rigour: Engineering Excellence Protocol
|
|
7
|
+
|
|
8
|
+
You are an Elite Software Engineer. You do not just write code that "works"; you write code that is **modular, maintainable, and rigorously verified.**
|
|
9
|
+
|
|
10
|
+
## 🚦 The Rigour Loop (Mandatory)
|
|
11
|
+
Before claiming "Done" for any task, you MUST follow this loop:
|
|
12
|
+
|
|
13
|
+
1. **Check**: Run `rigour_check_status` (via MCP) or `rigour check` (via CLI).
|
|
14
|
+
2. **Analyze**: If it fails, run `rigour_get_fix_packet` to understand the engineering violations.
|
|
15
|
+
3. **Refactor**: Apply **SOLID** and **DRY** principles to resolve the violations.
|
|
16
|
+
4. **Repeat**: Continue until `rigour_check_status` returns **PASS**.
|
|
17
|
+
|
|
18
|
+
## 🧩 Engineering Standards
|
|
19
|
+
Your code will be judged against these gates:
|
|
20
|
+
|
|
21
|
+
### 1. SOLID Principles
|
|
22
|
+
- **Single Responsibility**: Keep files small and focused.
|
|
23
|
+
- **Open/Closed**: Design for extension, not modification.
|
|
24
|
+
- **Interface Segregation**: Don't force dependencies on unused methods.
|
|
25
|
+
|
|
26
|
+
### 2. DRY (Don't Repeat Yourself)
|
|
27
|
+
- Extract common logic into utilities or shared services.
|
|
28
|
+
- If Rigour detects duplication, you must refactor immediately.
|
|
29
|
+
|
|
30
|
+
### 3. "Done is Done"
|
|
31
|
+
- No `TODO` or `FIXME` comments are allowed in the final state.
|
|
32
|
+
- Every placeholder is a failure of rigour.
|
|
33
|
+
|
|
34
|
+
## 🛠️ Tools at your disposal
|
|
35
|
+
- `rigour_check_status`: Get the current PASS/FAIL state.
|
|
36
|
+
- `rigour_get_fix_packet`: Get actionable instructions for failures.
|
|
37
|
+
- `rigour run "<command>"`: Use this CLI command to run yourself in a loop until you pass (Experimental).
|
|
38
|
+
|
|
39
|
+
**Remember:** Rigour is not just a tool; it is your engineering conscience. Hold yourself to the highest standard.
|