@rigour-labs/cli 1.0.0 → 1.4.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Rigour Contributors
3
+ Copyright (c) 2026 Ashutosh (https://github.com/erashu212), Rigour Labs.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/dist/cli.js CHANGED
@@ -1,33 +1,95 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
3
6
  Object.defineProperty(exports, "__esModule", { value: true });
4
7
  const commander_1 = require("commander");
5
8
  const init_js_1 = require("./commands/init.js");
6
9
  const check_js_1 = require("./commands/check.js");
10
+ const explain_js_1 = require("./commands/explain.js");
7
11
  const run_js_1 = require("./commands/run.js");
12
+ const guide_js_1 = require("./commands/guide.js");
13
+ const setup_js_1 = require("./commands/setup.js");
14
+ const chalk_1 = __importDefault(require("chalk"));
8
15
  const program = new commander_1.Command();
9
16
  program
10
17
  .name('rigour')
11
- .description('A quality gate loop controller for AI-assisted coding')
12
- .version('1.0.0');
18
+ .description('šŸ›”ļø Rigour: The Quality Gate Loop for AI-Assisted Engineering')
19
+ .version('1.3.0')
20
+ .addHelpText('before', chalk_1.default.bold.cyan(`
21
+ ____ _
22
+ / __ \\(_)____ ___ __ __ _____
23
+ / /_/ // // __ \`/ / / / / // ___/
24
+ / _, _// // /_/ // /_/ / / // /
25
+ /_/ |_|/_/ \\__, / \\__,_/_/ /_/
26
+ /____/
27
+ `));
13
28
  program
14
29
  .command('init')
15
- .description('Initialize VibeGuard in the current directory')
16
- .action(async () => {
17
- await (0, init_js_1.initCommand)(process.cwd());
30
+ .description('Initialize Rigour in the current directory')
31
+ .option('-p, --preset <name>', 'Project preset (ui, api, infra, data)')
32
+ .option('--paradigm <name>', 'Coding paradigm (oop, functional, minimal)')
33
+ .option('--dry-run', 'Show detected configuration without writing files')
34
+ .option('--explain', 'Show detection markers for roles and paradigms')
35
+ .addHelpText('after', `
36
+ Examples:
37
+ $ rigour init # Auto-discover role & paradigm
38
+ $ rigour init --preset api --explain # Force API role and show why
39
+ `)
40
+ .action(async (options) => {
41
+ await (0, init_js_1.initCommand)(process.cwd(), options);
18
42
  });
19
43
  program
20
44
  .command('check')
21
45
  .description('Run quality gate checks')
46
+ .option('--ci', 'CI mode (minimal output, non-zero exit on fail)')
47
+ .option('--json', 'Output report in JSON format')
48
+ .addHelpText('after', `
49
+ Examples:
50
+ $ rigour check # Run interactive check
51
+ $ rigour check --ci # Run in CI environment
52
+ `)
53
+ .action(async (options) => {
54
+ await (0, check_js_1.checkCommand)(process.cwd(), options);
55
+ });
56
+ program
57
+ .command('explain')
58
+ .description('Explain the last quality gate report with actionable bullets')
59
+ .addHelpText('after', `
60
+ Examples:
61
+ $ rigour explain # Get a human-readable violation summary
62
+ `)
22
63
  .action(async () => {
23
- await (0, check_js_1.checkCommand)(process.cwd());
64
+ await (0, explain_js_1.explainCommand)(process.cwd());
24
65
  });
25
66
  program
26
67
  .command('run')
27
68
  .description('Execute an agent command in a loop until quality gates pass')
28
69
  .argument('[command...]', 'The agent command to run (e.g., cursor-agent ...)')
29
- .option('-i, --iterations <number>', 'Maximum number of loop iterations', '3')
70
+ .option('-c, --max-cycles <number>', 'Maximum number of loop iterations', '3')
71
+ .option('--fail-fast', 'Abort loop immediately on first gate failure')
72
+ .addHelpText('after', `
73
+ Examples:
74
+ $ rigour run -- claude "fix issues" # Loop Claude until PASS
75
+ $ rigour run -c 5 -- cursor-agent # Run Cursor agent for up to 5 cycles
76
+ `)
30
77
  .action(async (args, options) => {
31
- await (0, run_js_1.runLoop)(process.cwd(), args, { iterations: parseInt(options.iterations) });
78
+ await (0, run_js_1.runLoop)(process.cwd(), args, {
79
+ iterations: parseInt(options.maxCycles),
80
+ failFast: !!options.failFast
81
+ });
82
+ });
83
+ program
84
+ .command('guide')
85
+ .description('Show the interactive engineering guide')
86
+ .action(async () => {
87
+ await (0, guide_js_1.guideCommand)();
88
+ });
89
+ program
90
+ .command('setup')
91
+ .description('Show installation and global setup guidance')
92
+ .action(async () => {
93
+ await (0, setup_js_1.setupCommand)();
32
94
  });
33
95
  program.parse();
@@ -1 +1,5 @@
1
- export declare function checkCommand(cwd: string): Promise<void>;
1
+ export interface CheckOptions {
2
+ ci?: boolean;
3
+ json?: boolean;
4
+ }
5
+ export declare function checkCommand(cwd: string, options?: CheckOptions): Promise<void>;
@@ -9,43 +9,90 @@ 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
- async function checkCommand(cwd) {
12
+ // Exit codes per spec
13
+ const EXIT_PASS = 0;
14
+ const EXIT_FAIL = 1;
15
+ const EXIT_CONFIG_ERROR = 2;
16
+ const EXIT_INTERNAL_ERROR = 3;
17
+ async function checkCommand(cwd, options = {}) {
13
18
  const configPath = path_1.default.join(cwd, 'rigour.yml');
14
19
  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.'));
20
+ if (options.json) {
21
+ console.log(JSON.stringify({ error: 'CONFIG_ERROR', message: 'rigour.yml not found' }));
22
+ }
23
+ else if (!options.ci) {
24
+ console.error(chalk_1.default.red('Error: rigour.yml not found. Run `rigour init` first.'));
25
+ }
26
+ process.exit(EXIT_CONFIG_ERROR);
30
27
  }
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}`)));
28
+ try {
29
+ const configContent = await fs_extra_1.default.readFile(configPath, 'utf-8');
30
+ const rawConfig = yaml_1.default.parse(configContent);
31
+ const config = core_1.ConfigSchema.parse(rawConfig);
32
+ if (!options.ci && !options.json) {
33
+ console.log(chalk_1.default.blue('Running Rigour checks...\n'));
34
+ }
35
+ const runner = new core_1.GateRunner(config);
36
+ const report = await runner.run(cwd);
37
+ // Write machine report
38
+ const reportPath = path_1.default.join(cwd, config.output.report_path);
39
+ await fs_extra_1.default.writeJson(reportPath, report, { spaces: 2 });
40
+ // Generate Fix Packet v2 on failure
41
+ if (report.status === 'FAIL') {
42
+ const { FixPacketService } = await import('@rigour-labs/core');
43
+ const fixPacketService = new FixPacketService();
44
+ const fixPacket = fixPacketService.generate(report, config);
45
+ const fixPacketPath = path_1.default.join(cwd, 'rigour-fix-packet.json');
46
+ await fs_extra_1.default.writeJson(fixPacketPath, fixPacket, { spaces: 2 });
47
+ }
48
+ // JSON output mode
49
+ if (options.json) {
50
+ console.log(JSON.stringify(report, null, 2));
51
+ process.exit(report.status === 'PASS' ? EXIT_PASS : EXIT_FAIL);
52
+ }
53
+ // CI mode: minimal output
54
+ if (options.ci) {
55
+ if (report.status === 'PASS') {
56
+ console.log('PASS');
39
57
  }
40
- if (failure.hint) {
41
- console.log(chalk_1.default.cyan(` Hint: ${failure.hint}`));
58
+ else {
59
+ console.log(`FAIL: ${report.failures.length} violation(s)`);
60
+ report.failures.forEach((f) => {
61
+ console.log(` - [${f.id}] ${f.title}`);
62
+ });
42
63
  }
43
- console.log('');
64
+ process.exit(report.status === 'PASS' ? EXIT_PASS : EXIT_FAIL);
44
65
  }
45
- console.log(chalk_1.default.yellow(`See ${config.output.report_path} for full details.`));
66
+ // Normal human-readable output
67
+ if (report.status === 'PASS') {
68
+ console.log(chalk_1.default.green.bold('āœ” PASS - All quality gates satisfied.'));
69
+ }
70
+ else {
71
+ console.log(chalk_1.default.red.bold('✘ FAIL - Quality gate violations found.\n'));
72
+ for (const failure of report.failures) {
73
+ console.log(chalk_1.default.red(`[${failure.id}] ${failure.title}`));
74
+ console.log(chalk_1.default.dim(` Details: ${failure.details}`));
75
+ if (failure.files && failure.files.length > 0) {
76
+ console.log(chalk_1.default.dim(' Files:'));
77
+ failure.files.forEach((f) => console.log(chalk_1.default.dim(` - ${f}`)));
78
+ }
79
+ if (failure.hint) {
80
+ console.log(chalk_1.default.cyan(` Hint: ${failure.hint}`));
81
+ }
82
+ console.log('');
83
+ }
84
+ console.log(chalk_1.default.yellow(`See ${config.output.report_path} for full details.`));
85
+ }
86
+ console.log(chalk_1.default.dim(`\nFinished in ${report.stats.duration_ms}ms`));
87
+ process.exit(report.status === 'PASS' ? EXIT_PASS : EXIT_FAIL);
46
88
  }
47
- console.log(chalk_1.default.dim(`\nFinished in ${report.stats.duration_ms}ms`));
48
- if (report.status !== 'PASS') {
49
- process.exit(1);
89
+ catch (error) {
90
+ if (options.json) {
91
+ console.log(JSON.stringify({ error: 'INTERNAL_ERROR', message: error.message }));
92
+ }
93
+ else if (!options.ci) {
94
+ console.error(chalk_1.default.red(`Internal error: ${error.message}`));
95
+ }
96
+ process.exit(EXIT_INTERNAL_ERROR);
50
97
  }
51
98
  }
@@ -0,0 +1 @@
1
+ export declare function explainCommand(cwd: string): Promise<void>;
@@ -0,0 +1,67 @@
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.explainCommand = explainCommand;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ async function explainCommand(cwd) {
11
+ const configPath = path_1.default.join(cwd, 'rigour.yml');
12
+ let reportPath = path_1.default.join(cwd, 'rigour-report.json');
13
+ // Try to read custom path from config
14
+ if (await fs_extra_1.default.pathExists(configPath)) {
15
+ try {
16
+ const yaml = await import('yaml');
17
+ const configContent = await fs_extra_1.default.readFile(configPath, 'utf-8');
18
+ const config = yaml.parse(configContent);
19
+ if (config?.output?.report_path) {
20
+ reportPath = path_1.default.join(cwd, config.output.report_path);
21
+ }
22
+ }
23
+ catch (e) { }
24
+ }
25
+ if (!(await fs_extra_1.default.pathExists(reportPath))) {
26
+ console.error(chalk_1.default.red(`Error: No report found at ${reportPath}`));
27
+ console.error(chalk_1.default.dim('Run `rigour check` first to generate a report.'));
28
+ process.exit(2);
29
+ }
30
+ try {
31
+ const reportContent = await fs_extra_1.default.readFile(reportPath, 'utf-8');
32
+ const report = JSON.parse(reportContent);
33
+ console.log(chalk_1.default.bold('\nšŸ“‹ Rigour Report Explanation\n'));
34
+ console.log(chalk_1.default.bold('Status: ') + (report.status === 'PASS'
35
+ ? chalk_1.default.green.bold('āœ… PASS')
36
+ : chalk_1.default.red.bold('šŸ›‘ FAIL')));
37
+ console.log(chalk_1.default.bold('\nGate Summary:'));
38
+ for (const [gate, status] of Object.entries(report.summary || {})) {
39
+ const icon = status === 'PASS' ? 'āœ…' : status === 'FAIL' ? 'āŒ' : 'ā­ļø';
40
+ console.log(` ${icon} ${gate}: ${status}`);
41
+ }
42
+ if (report.failures && report.failures.length > 0) {
43
+ console.log(chalk_1.default.bold.red(`\nšŸ”§ ${report.failures.length} Violation(s) to Fix:\n`));
44
+ report.failures.forEach((failure, index) => {
45
+ console.log(chalk_1.default.white(`${index + 1}. `) + chalk_1.default.bold.yellow(`[${failure.id.toUpperCase()}]`) + chalk_1.default.white(` ${failure.title}`));
46
+ console.log(chalk_1.default.dim(` └─ ${failure.details}`));
47
+ if (failure.files && failure.files.length > 0) {
48
+ console.log(chalk_1.default.cyan(` šŸ“ Files: ${failure.files.join(', ')}`));
49
+ }
50
+ if (failure.hint) {
51
+ console.log(chalk_1.default.green(` šŸ’” Hint: ${failure.hint}`));
52
+ }
53
+ console.log('');
54
+ });
55
+ }
56
+ else if (report.status === 'PASS') {
57
+ console.log(chalk_1.default.green('\n✨ All quality gates passed! No violations found.\n'));
58
+ }
59
+ if (report.stats) {
60
+ console.log(chalk_1.default.dim(`Duration: ${report.stats.duration_ms}ms`));
61
+ }
62
+ }
63
+ catch (error) {
64
+ console.error(chalk_1.default.red(`Error reading report: ${error.message}`));
65
+ process.exit(3);
66
+ }
67
+ }
@@ -0,0 +1 @@
1
+ export declare function guideCommand(): Promise<void>;
@@ -0,0 +1,22 @@
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.guideCommand = guideCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ async function guideCommand() {
9
+ console.log(chalk_1.default.bold.cyan('\nšŸ›”ļø Rigour Labs | The Engineering Guide\n'));
10
+ console.log(chalk_1.default.bold('Getting Started:'));
11
+ console.log(chalk_1.default.dim(' 1. Run ') + chalk_1.default.cyan('rigour init') + chalk_1.default.dim(' to detect your project role and apply standards.'));
12
+ console.log(chalk_1.default.dim(' 2. Run ') + chalk_1.default.cyan('rigour check') + chalk_1.default.dim(' to see existing violations.'));
13
+ console.log(chalk_1.default.dim(' 3. Run ') + chalk_1.default.cyan('rigour run -- <your-agent-command>') + chalk_1.default.dim(' to automate the fix loop.\n'));
14
+ console.log(chalk_1.default.bold('Key Concepts:'));
15
+ console.log(chalk_1.default.yellow(' • Fix Packet v2') + chalk_1.default.dim(': Structured diagnostics fed directly into AI agents.'));
16
+ console.log(chalk_1.default.yellow(' • Safety Rails') + chalk_1.default.dim(': Prevents "explosive" refactoring (max files changed).'));
17
+ console.log(chalk_1.default.yellow(' • Strategic Guardians') + chalk_1.default.dim(': Dependency and Architectural boundary enforcement.\n'));
18
+ console.log(chalk_1.default.bold('Workflow Integration:'));
19
+ console.log(chalk_1.default.green(' • Cursor') + chalk_1.default.dim(': Add the MCP server or use the ') + chalk_1.default.cyan('.cursor/rules/rigour.mdc') + chalk_1.default.dim(' handshake.'));
20
+ console.log(chalk_1.default.green(' • CI/CD') + chalk_1.default.dim(': Use ') + chalk_1.default.cyan('rigour check --ci') + chalk_1.default.dim(' to fail PRs that violate quality gates.\n'));
21
+ console.log(chalk_1.default.dim('For more detailed docs, visit: ') + chalk_1.default.underline('https://github.com/erashu212/rigour/docs\n'));
22
+ }
@@ -1 +1,7 @@
1
- export declare function initCommand(cwd: string): Promise<void>;
1
+ export interface InitOptions {
2
+ preset?: string;
3
+ paradigm?: string;
4
+ dryRun?: boolean;
5
+ explain?: boolean;
6
+ }
7
+ export declare function initCommand(cwd: string, options?: InitOptions): Promise<void>;
@@ -9,14 +9,70 @@ 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
- async function initCommand(cwd) {
12
+ async function initCommand(cwd, options = {}) {
13
13
  const discovery = new core_1.DiscoveryService();
14
- const recommendedConfig = await discovery.discover(cwd);
14
+ const result = await discovery.discover(cwd);
15
+ let recommendedConfig = result.config;
16
+ // Override with user options if provided and re-apply template logic if necessary
17
+ if (options.preset || options.paradigm) {
18
+ const core = await import('@rigour-labs/core');
19
+ let customBase = { ...core.UNIVERSAL_CONFIG };
20
+ if (options.preset) {
21
+ const t = core.TEMPLATES.find((t) => t.name === options.preset);
22
+ if (t)
23
+ customBase = discovery.mergeConfig(customBase, t.config);
24
+ }
25
+ else if (recommendedConfig.preset) {
26
+ const t = core.TEMPLATES.find((t) => t.name === recommendedConfig.preset);
27
+ if (t)
28
+ customBase = discovery.mergeConfig(customBase, t.config);
29
+ }
30
+ if (options.paradigm) {
31
+ const t = core.PARADIGM_TEMPLATES.find((t) => t.name === options.paradigm);
32
+ if (t)
33
+ customBase = discovery.mergeConfig(customBase, t.config);
34
+ }
35
+ else if (recommendedConfig.paradigm) {
36
+ const t = core.PARADIGM_TEMPLATES.find((t) => t.name === recommendedConfig.paradigm);
37
+ if (t)
38
+ customBase = discovery.mergeConfig(customBase, t.config);
39
+ }
40
+ recommendedConfig = customBase;
41
+ if (options.preset)
42
+ recommendedConfig.preset = options.preset;
43
+ if (options.paradigm)
44
+ recommendedConfig.paradigm = options.paradigm;
45
+ }
46
+ if (options.dryRun || options.explain) {
47
+ console.log(chalk_1.default.bold.blue('\nšŸ” Rigour Auto-Discovery (Dry Run):'));
48
+ if (recommendedConfig.preset) {
49
+ console.log(chalk_1.default.cyan(` Role: `) + chalk_1.default.bold(recommendedConfig.preset.toUpperCase()));
50
+ if (options.explain && result.matches.preset) {
51
+ console.log(chalk_1.default.dim(` (Marker found: ${result.matches.preset.marker})`));
52
+ }
53
+ }
54
+ if (recommendedConfig.paradigm) {
55
+ console.log(chalk_1.default.cyan(` Paradigm: `) + chalk_1.default.bold(recommendedConfig.paradigm.toUpperCase()));
56
+ if (options.explain && result.matches.paradigm) {
57
+ console.log(chalk_1.default.dim(` (Marker found: ${result.matches.paradigm.marker})`));
58
+ }
59
+ }
60
+ console.log(chalk_1.default.yellow('\n[DRY RUN] No files will be written.'));
61
+ return;
62
+ }
15
63
  const configPath = path_1.default.join(cwd, 'rigour.yml');
16
64
  if (await fs_extra_1.default.pathExists(configPath)) {
17
65
  console.log(chalk_1.default.yellow('rigour.yml already exists. Skipping initialization.'));
18
66
  return;
19
67
  }
68
+ console.log(chalk_1.default.bold.blue('\nšŸ” Rigour Auto-Discovery:'));
69
+ if (recommendedConfig.preset) {
70
+ console.log(chalk_1.default.cyan(` Role: `) + chalk_1.default.bold(recommendedConfig.preset.toUpperCase()));
71
+ }
72
+ if (recommendedConfig.paradigm) {
73
+ console.log(chalk_1.default.cyan(` Paradigm: `) + chalk_1.default.bold(recommendedConfig.paradigm.toUpperCase()));
74
+ }
75
+ console.log('');
20
76
  await fs_extra_1.default.writeFile(configPath, yaml_1.default.stringify(recommendedConfig));
21
77
  console.log(chalk_1.default.green('āœ” Created rigour.yml'));
22
78
  // Create required directories and files
@@ -28,33 +84,51 @@ async function initCommand(cwd) {
28
84
  console.log(chalk_1.default.dim(` - Created ${file}`));
29
85
  }
30
86
  }
31
- // Agent Handshake (Cursor/AntiGravity)
87
+ // Agent Handshake (Universal / AntiGravity / Cursor)
88
+ const rigourDocsDir = path_1.default.join(cwd, 'docs');
89
+ await fs_extra_1.default.ensureDir(rigourDocsDir);
90
+ const instructionsPath = path_1.default.join(rigourDocsDir, 'AGENT_INSTRUCTIONS.md');
91
+ const ruleContent = `# šŸ›”ļø Rigour: Engineering Excellence Protocol
92
+
93
+ You are an Elite Software Engineer. You do not just write code that "works"; you write code that is **modular, maintainable, and rigorously verified.**
94
+
95
+ ## 🚦 The Rigour Loop (Mandatory)
96
+ Before claiming "Done" for any task, you MUST follow this loop:
97
+
98
+ 1. **Check**: Run \`npx @rigour-labs/cli check\` to verify compliance.
99
+ 2. **Analyze**: If it fails, read \`rigour-fix-packet.json\` (V2 High-Fidelity) for exact failure points and constraints.
100
+ 3. **Refactor**: Apply **SOLID** and **DRY** principles to resolve the violations according to constraints.
101
+ 4. **Repeat**: Continue until \`npx @rigour-labs/cli check\` returns **PASS**.
102
+
103
+ ## 🧩 Engineering Standards
104
+ - **Single Responsibility**: Keep files small and focused (max 500 lines).
105
+ - **DRY (Don't Repeat Yourself)**: Extract common logic into utilities.
106
+ - **Done is Done**: No \`TODO\` or \`FIXME\` comments allowed in the final state.
107
+ - **Memory Preservation**: Always update docs/SPEC.md, docs/ARCH.md, docs/DECISIONS.md.
108
+
109
+ ## šŸ› ļø Commands
110
+ \`\`\`bash
111
+ # Verify current state
112
+ npx @rigour-labs/cli check
113
+
114
+ # Self-healing agent loop
115
+ npx @rigour-labs/cli run -- <agent-command>
116
+ \`\`\`
117
+ `;
118
+ // 1. Create Universal Instructions
119
+ await fs_extra_1.default.writeFile(instructionsPath, ruleContent);
120
+ console.log(chalk_1.default.green('āœ” Initialized Universal Agent Handshake (docs/AGENT_INSTRUCTIONS.md)'));
121
+ // 2. Create Cursor Specific Rules (.mdc)
32
122
  const cursorRulesDir = path_1.default.join(cwd, '.cursor', 'rules');
33
123
  await fs_extra_1.default.ensureDir(cursorRulesDir);
34
- const rulePath = path_1.default.join(cursorRulesDir, 'rigour.mdc');
35
- const ruleContent = `---
124
+ const mdcPath = path_1.default.join(cursorRulesDir, 'rigour.mdc');
125
+ const mdcContent = `---
36
126
  description: Enforcement of Rigour quality gates and best practices.
37
127
  globs: **/*
38
128
  ---
39
129
 
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.'));
130
+ ${ruleContent}`;
131
+ await fs_extra_1.default.writeFile(mdcPath, mdcContent);
132
+ console.log(chalk_1.default.green('āœ” Initialized Cursor Handshake (.cursor/rules/rigour.mdc)'));
133
+ console.log(chalk_1.default.blue('\nRigour is ready. Run `npx @rigour-labs/cli check` to verify your project.'));
60
134
  }
@@ -1,3 +1,4 @@
1
1
  export declare function runLoop(cwd: string, agentArgs: string[], options: {
2
2
  iterations: number;
3
+ failFast?: boolean;
3
4
  }): Promise<void>;
@@ -10,11 +10,16 @@ const chalk_1 = __importDefault(require("chalk"));
10
10
  const yaml_1 = __importDefault(require("yaml"));
11
11
  const execa_1 = require("execa");
12
12
  const core_1 = require("@rigour-labs/core");
13
+ // Exit codes per spec
14
+ const EXIT_PASS = 0;
15
+ const EXIT_FAIL = 1;
16
+ const EXIT_CONFIG_ERROR = 2;
17
+ const EXIT_INTERNAL_ERROR = 3;
13
18
  async function runLoop(cwd, agentArgs, options) {
14
19
  const configPath = path_1.default.join(cwd, 'rigour.yml');
15
20
  if (!(await fs_extra_1.default.pathExists(configPath))) {
16
21
  console.error(chalk_1.default.red('Error: rigour.yml not found. Run `rigour init` first.'));
17
- process.exit(1);
22
+ process.exit(EXIT_CONFIG_ERROR);
18
23
  }
19
24
  try {
20
25
  const configContent = await fs_extra_1.default.readFile(configPath, 'utf-8');
@@ -28,42 +33,81 @@ async function runLoop(cwd, agentArgs, options) {
28
33
  console.log(chalk_1.default.bold.blue(`\n══════════════════════════════════════════════════════════════════`));
29
34
  console.log(chalk_1.default.bold.blue(` RIGOUR LOOP: Iteration ${iteration}/${maxIterations}`));
30
35
  console.log(chalk_1.default.bold.blue(`══════════════════════════════════════════════════════════════════`));
31
- // 1. Run the agent command
32
- if (agentArgs.length > 0) {
36
+ // 1. Prepare Command
37
+ let currentArgs = [...agentArgs];
38
+ if (iteration > 1 && agentArgs.length > 0) {
39
+ // Iteration contract: In later cycles, we focus strictly on the fix packet
40
+ console.log(chalk_1.default.yellow(`\nšŸ”„ REFINEMENT CYCLE - Instructing agent to fix specific violations...`));
41
+ // We keep the first part of the command (the agent) but can append or wrap
42
+ // For simplicity, we assume the agent can read the JSON file we generate
43
+ }
44
+ // Snapshot changed files before agent runs
45
+ let beforeFiles = [];
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) { }
51
+ // 2. Run the agent command
52
+ if (currentArgs.length > 0) {
33
53
  console.log(chalk_1.default.cyan(`\nšŸš€ DEPLOYING AGENT:`));
34
- console.log(chalk_1.default.dim(` Command: ${agentArgs.join(' ')}`));
54
+ console.log(chalk_1.default.dim(` Command: ${currentArgs.join(' ')}`));
35
55
  try {
36
- await (0, execa_1.execa)(agentArgs[0], agentArgs.slice(1), { shell: true, stdio: 'inherit', cwd });
56
+ await (0, execa_1.execa)(currentArgs[0], currentArgs.slice(1), { shell: true, stdio: 'inherit', cwd });
37
57
  }
38
58
  catch (error) {
39
59
  console.warn(chalk_1.default.yellow(`\nāš ļø Agent command finished with non-zero exit code. Rigour will now verify state...`));
40
60
  }
41
61
  }
42
- // 2. Run Rigour Check
62
+ // Snapshot changed files after agent runs
63
+ let afterFiles = [];
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) { }
69
+ const changedThisCycle = afterFiles.filter(f => !beforeFiles.includes(f));
70
+ const maxFiles = config.gates.safety?.max_files_changed_per_cycle || 10;
71
+ if (changedThisCycle.length > maxFiles) {
72
+ console.log(chalk_1.default.red.bold(`\nšŸ›‘ SAFETY RAIL ABORT: Agent changed ${changedThisCycle.length} files (max: ${maxFiles}).`));
73
+ console.log(chalk_1.default.red(` This looks like explosive behavior. Check your agent's instructions.`));
74
+ process.exit(EXIT_FAIL);
75
+ }
76
+ // 3. Run Rigour Check
43
77
  console.log(chalk_1.default.magenta('\nšŸ” AUDITING QUALITY GATES...'));
44
78
  const report = await runner.run(cwd);
79
+ // Write report
80
+ const reportPath = path_1.default.join(cwd, config.output.report_path);
81
+ await fs_extra_1.default.writeJson(reportPath, report, { spaces: 2 });
45
82
  if (report.status === 'PASS') {
46
83
  console.log(chalk_1.default.green.bold('\n✨ PASS - All quality gates satisfied.'));
47
84
  console.log(chalk_1.default.green(` Your solution meets the required Engineering Rigour criteria.\n`));
48
85
  return;
49
86
  }
50
- // 3. Generate and print Fix Packet for next iteration
87
+ // 4. Generate Fix Packet v2
88
+ const { FixPacketService } = await import('@rigour-labs/core');
89
+ const fixPacketService = new FixPacketService();
90
+ const fixPacket = fixPacketService.generate(report, config);
91
+ const fixPacketPath = path_1.default.join(cwd, 'rigour-fix-packet.json');
92
+ await fs_extra_1.default.writeJson(fixPacketPath, fixPacket, { spaces: 2 });
51
93
  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...'));
94
+ console.log(chalk_1.default.dim(` Fix Packet generated: rigour-fix-packet.json`));
95
+ if (options.failFast) {
96
+ console.log(chalk_1.default.red.bold(`\nšŸ›‘ FAIL-FAST: Aborting loop as requested.`));
97
+ process.exit(EXIT_FAIL);
98
+ }
99
+ // Print summary
100
+ const summary = report.failures.map((f, i) => {
101
+ return chalk_1.default.white(`${i + 1}. `) + chalk_1.default.bold.red(`[${f.id.toUpperCase()}] `) + chalk_1.default.white(f.title);
102
+ }).join('\n');
103
+ console.log(chalk_1.default.bold.white('\nšŸ“‹ VIOLATIONS SUMMARY:'));
104
+ console.log(summary);
62
105
  if (iteration === maxIterations) {
63
106
  console.log(chalk_1.default.red.bold(`\nāŒ CRITICAL: Reached maximum iterations (${maxIterations}).`));
64
107
  console.log(chalk_1.default.red(` Quality gates remain unfulfilled. Refactor manually or check agent logs.`));
65
- process.exit(1);
108
+ process.exit(EXIT_FAIL);
66
109
  }
110
+ console.log(chalk_1.default.dim('\nReturning control to agent for the next refinement cycle...'));
67
111
  }
68
112
  }
69
113
  catch (error) {
@@ -71,6 +115,6 @@ async function runLoop(cwd, agentArgs, options) {
71
115
  if (error.issues) {
72
116
  console.error(chalk_1.default.dim(JSON.stringify(error.issues, null, 2)));
73
117
  }
74
- process.exit(1);
118
+ process.exit(EXIT_INTERNAL_ERROR);
75
119
  }
76
120
  }
@@ -0,0 +1 @@
1
+ export declare function setupCommand(): Promise<void>;