@rigour-labs/cli 2.21.2 → 3.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/README.md CHANGED
@@ -35,6 +35,7 @@ Agent writes code → Rigour checks → FAIL? → Fix Packet → Agent retries
35
35
 
36
36
  ## ⚙️ Quality Gates
37
37
 
38
+ ### Structural & Security Gates
38
39
  | Gate | Description |
39
40
  |:---|:---|
40
41
  | **File Size** | Max lines per file (default: 300-500) |
@@ -42,9 +43,21 @@ Agent writes code → Rigour checks → FAIL? → Fix Packet → Agent retries
42
43
  | **Complexity** | Cyclomatic complexity limits (AST-based) |
43
44
  | **Required Docs** | SPEC.md, ARCH.md, README must exist |
44
45
  | **File Guard** | Protected paths, max files changed |
45
- | **Security Patterns** | XSS, SQL injection, hardcoded secrets, command injection (enabled by default) |
46
+ | **Security Patterns** | XSS, SQL injection, hardcoded secrets, command injection |
46
47
  | **Context Alignment** | Prevents drift by anchoring on project patterns |
47
48
 
49
+ ### AI-Native Drift Detection (v2.16+)
50
+ | Gate | Description |
51
+ |:---|:---|
52
+ | **Duplication Drift** | Near-identical functions across files — AI re-invents what it forgot |
53
+ | **Hallucinated Imports** | Imports referencing modules that don't exist (JS/TS, Python, Go, Ruby, C#) |
54
+ | **Inconsistent Error Handling** | Same error type handled differently across agent sessions |
55
+ | **Context Window Artifacts** | Quality degradation within a file — clean top, messy bottom |
56
+ | **Async & Error Safety** | Unsafe async/promise patterns, unhandled errors across 6 languages |
57
+
58
+ ### Multi-Language Support
59
+ All gates support **TypeScript, JavaScript, Python, Go, Ruby, and C#/.NET**.
60
+
48
61
  ## 🛠️ Commands
49
62
 
50
63
  | Command | Purpose |
package/dist/cli.js CHANGED
@@ -8,6 +8,8 @@ import { guideCommand } from './commands/guide.js';
8
8
  import { setupCommand } from './commands/setup.js';
9
9
  import { indexCommand } from './commands/index.js';
10
10
  import { studioCommand } from './commands/studio.js';
11
+ import { exportAuditCommand } from './commands/export-audit.js';
12
+ import { demoCommand } from './commands/demo.js';
11
13
  import { checkForUpdates } from './utils/version.js';
12
14
  import chalk from 'chalk';
13
15
  const CLI_VERSION = '2.0.0';
@@ -29,7 +31,7 @@ program
29
31
  program
30
32
  .command('init')
31
33
  .description('Initialize Rigour in the current directory')
32
- .option('-p, --preset <name>', 'Project preset (ui, api, infra, data)')
34
+ .option('-p, --preset <name>', 'Project preset (ui, api, infra, data, healthcare, fintech, government)')
33
35
  .option('--paradigm <name>', 'Coding paradigm (oop, functional, minimal)')
34
36
  .option('--ide <name>', 'Target IDE (cursor, vscode, all). Auto-detects if not specified.')
35
37
  .option('--dry-run', 'Show detected configuration without writing files')
@@ -37,10 +39,12 @@ program
37
39
  .option('-f, --force', 'Force re-initialization, overwriting existing rigour.yml')
38
40
  .addHelpText('after', `
39
41
  Examples:
40
- $ rigour init # Auto-discover role & paradigm
41
- $ rigour init --preset api --explain # Force API role and show why
42
- $ rigour init --ide vscode # Only create VS Code compatible files
43
- $ rigour init --ide all # Create files for all IDEs
42
+ $ rigour init # Auto-discover role & paradigm
43
+ $ rigour init --preset api --explain # Force API role and show why
44
+ $ rigour init --preset healthcare # HIPAA-compliant quality gates
45
+ $ rigour init --preset fintech # SOC2/PCI-DSS quality gates
46
+ $ rigour init --preset government # FedRAMP/NIST quality gates
47
+ $ rigour init --ide all # Create files for all IDEs
44
48
  `)
45
49
  .action(async (options) => {
46
50
  await initCommand(process.cwd(), options);
@@ -91,6 +95,33 @@ Examples:
91
95
  failFast: !!options.failFast
92
96
  });
93
97
  });
98
+ program
99
+ .command('export-audit')
100
+ .description('Generate a compliance audit package from the last check')
101
+ .option('-f, --format <type>', 'Output format: json or md', 'json')
102
+ .option('-o, --output <path>', 'Custom output file path')
103
+ .option('--run', 'Run a fresh rigour check before exporting')
104
+ .addHelpText('after', `
105
+ Examples:
106
+ $ rigour export-audit # Export JSON audit package
107
+ $ rigour export-audit --format md # Export Markdown report
108
+ $ rigour export-audit --run # Run check first, then export
109
+ $ rigour export-audit -o audit.json # Custom output path
110
+ `)
111
+ .action(async (options) => {
112
+ await exportAuditCommand(process.cwd(), options);
113
+ });
114
+ program
115
+ .command('demo')
116
+ .description('Run a live demo — see Rigour catch AI drift, security issues, and structural violations')
117
+ .addHelpText('after', `
118
+ Examples:
119
+ $ rigour demo # Run the flagship demo
120
+ $ npx @rigour-labs/cli demo # Try without installing
121
+ `)
122
+ .action(async () => {
123
+ await demoCommand();
124
+ });
94
125
  program
95
126
  .command('guide')
96
127
  .description('Show the interactive engineering guide')
@@ -2,7 +2,7 @@ import fs from 'fs-extra';
2
2
  import path from 'path';
3
3
  import chalk from 'chalk';
4
4
  import yaml from 'yaml';
5
- import { GateRunner, ConfigSchema } from '@rigour-labs/core';
5
+ import { GateRunner, ConfigSchema, recordScore, getScoreTrend } from '@rigour-labs/core';
6
6
  import inquirer from 'inquirer';
7
7
  import { randomUUID } from 'crypto';
8
8
  // Exit codes per spec
@@ -57,6 +57,8 @@ export async function checkCommand(cwd, files = [], options = {}) {
57
57
  // Write machine report
58
58
  const reportPath = path.join(cwd, config.output.report_path);
59
59
  await fs.writeJson(reportPath, report, { spaces: 2 });
60
+ // Record score for trend tracking
61
+ recordScore(cwd, report);
60
62
  await logStudioEvent(cwd, {
61
63
  type: "tool_response",
62
64
  requestId,
@@ -81,15 +83,18 @@ export async function checkCommand(cwd, files = [], options = {}) {
81
83
  });
82
84
  return; // Wait for write callback
83
85
  }
84
- // CI mode: minimal output
86
+ // CI mode: minimal output with score
85
87
  if (options.ci) {
86
88
  if (report.status === 'PASS') {
87
- console.log('PASS');
89
+ const scoreStr = report.stats.score !== undefined ? ` (${report.stats.score}/100)` : '';
90
+ console.log(`PASS${scoreStr}`);
88
91
  }
89
92
  else {
90
- console.log(`FAIL: ${report.failures.length} violation(s)`);
93
+ const scoreStr = report.stats.score !== undefined ? ` Score: ${report.stats.score}/100` : '';
94
+ console.log(`FAIL: ${report.failures.length} violation(s)${scoreStr}`);
91
95
  report.failures.forEach((f) => {
92
- console.log(` - [${f.id}] ${f.title}`);
96
+ const sev = (f.severity || 'medium').toUpperCase();
97
+ console.log(` - [${sev}] [${f.id}] ${f.title}`);
93
98
  });
94
99
  }
95
100
  process.exit(report.status === 'PASS' ? EXIT_PASS : EXIT_FAIL);
@@ -104,21 +109,73 @@ export async function checkCommand(cwd, files = [], options = {}) {
104
109
  }
105
110
  else {
106
111
  console.log(chalk.red.bold('✘ FAIL - Quality gate violations found.\n'));
112
+ // Score summary line
113
+ const stats = report.stats;
114
+ const scoreParts = [];
115
+ if (stats.score !== undefined)
116
+ scoreParts.push(`Score: ${stats.score}/100`);
117
+ if (stats.ai_health_score !== undefined)
118
+ scoreParts.push(`AI Health: ${stats.ai_health_score}/100`);
119
+ if (stats.structural_score !== undefined)
120
+ scoreParts.push(`Structural: ${stats.structural_score}/100`);
121
+ if (scoreParts.length > 0) {
122
+ console.log(chalk.bold(scoreParts.join(' | ')) + '\n');
123
+ }
124
+ // Severity breakdown
125
+ if (stats.severity_breakdown) {
126
+ const parts = Object.entries(stats.severity_breakdown)
127
+ .filter(([, count]) => count > 0)
128
+ .map(([sev, count]) => {
129
+ const color = sev === 'critical' ? chalk.red.bold : sev === 'high' ? chalk.red : sev === 'medium' ? chalk.yellow : chalk.dim;
130
+ return color(`${sev}: ${count}`);
131
+ });
132
+ if (parts.length > 0) {
133
+ console.log('Severity: ' + parts.join(', ') + '\n');
134
+ }
135
+ }
136
+ // Group failures by provenance
137
+ const severityIcon = (s) => {
138
+ switch (s) {
139
+ case 'critical': return chalk.red.bold('CRIT');
140
+ case 'high': return chalk.red('HIGH');
141
+ case 'medium': return chalk.yellow('MED ');
142
+ case 'low': return chalk.dim('LOW ');
143
+ case 'info': return chalk.dim('INFO');
144
+ default: return chalk.yellow('MED ');
145
+ }
146
+ };
107
147
  for (const failure of report.failures) {
108
- console.log(chalk.red(`[${failure.id}] ${failure.title}`));
109
- console.log(chalk.dim(` Details: ${failure.details}`));
148
+ const sev = severityIcon(failure.severity);
149
+ const prov = failure.provenance ? chalk.dim(`[${failure.provenance}]`) : '';
150
+ console.log(`${sev} ${prov} ${chalk.red(`[${failure.id}]`)} ${failure.title}`);
151
+ console.log(chalk.dim(` Details: ${failure.details}`));
110
152
  if (failure.files && failure.files.length > 0) {
111
- console.log(chalk.dim(' Files:'));
112
- failure.files.forEach((f) => console.log(chalk.dim(` - ${f}`)));
153
+ console.log(chalk.dim(' Files:'));
154
+ failure.files.forEach((f) => console.log(chalk.dim(` - ${f}`)));
113
155
  }
114
156
  if (failure.hint) {
115
- console.log(chalk.cyan(` Hint: ${failure.hint}`));
157
+ console.log(chalk.cyan(` Hint: ${failure.hint}`));
116
158
  }
117
159
  console.log('');
118
160
  }
119
161
  console.log(chalk.yellow(`See ${config.output.report_path} for full details.`));
120
162
  }
121
- console.log(chalk.dim(`\nFinished in ${report.stats.duration_ms}ms`));
163
+ // Score trend display
164
+ const trend = getScoreTrend(cwd);
165
+ if (trend && trend.recentScores.length >= 3) {
166
+ const arrow = trend.direction === 'improving' ? chalk.green('↑') :
167
+ trend.direction === 'degrading' ? chalk.red('↓') : chalk.dim('→');
168
+ const trendColor = trend.direction === 'improving' ? chalk.green :
169
+ trend.direction === 'degrading' ? chalk.red : chalk.dim;
170
+ const scoresStr = trend.recentScores.map(s => String(s)).join(' → ');
171
+ console.log(trendColor(`\nScore Trend: ${scoresStr} (${trend.direction} ${arrow})`));
172
+ }
173
+ // Stats footer
174
+ const footerParts = [`Finished in ${report.stats.duration_ms}ms`];
175
+ if (report.status === 'PASS' && report.stats.score !== undefined) {
176
+ footerParts.push(`Score: ${report.stats.score}/100`);
177
+ }
178
+ console.log(chalk.dim('\n' + footerParts.join(' | ')));
122
179
  process.exit(report.status === 'PASS' ? EXIT_PASS : EXIT_FAIL);
123
180
  }
124
181
  catch (error) {
@@ -0,0 +1,16 @@
1
+ /**
2
+ * rigour demo
3
+ *
4
+ * Creates a temp project with intentional AI-generated code issues,
5
+ * runs Rigour against it, and shows the full experience:
6
+ * 1. Scaffolds a broken codebase
7
+ * 2. Runs quality gates → FAIL with violations
8
+ * 3. Shows the fix packet
9
+ * 4. Exports an audit report
10
+ * 5. Cleans up
11
+ *
12
+ * The "flagship demo" — one command to understand Rigour.
13
+ *
14
+ * @since v2.17.0
15
+ */
16
+ export declare function demoCommand(): Promise<void>;
@@ -0,0 +1,306 @@
1
+ /**
2
+ * rigour demo
3
+ *
4
+ * Creates a temp project with intentional AI-generated code issues,
5
+ * runs Rigour against it, and shows the full experience:
6
+ * 1. Scaffolds a broken codebase
7
+ * 2. Runs quality gates → FAIL with violations
8
+ * 3. Shows the fix packet
9
+ * 4. Exports an audit report
10
+ * 5. Cleans up
11
+ *
12
+ * The "flagship demo" — one command to understand Rigour.
13
+ *
14
+ * @since v2.17.0
15
+ */
16
+ import fs from 'fs-extra';
17
+ import path from 'path';
18
+ import chalk from 'chalk';
19
+ import yaml from 'yaml';
20
+ import os from 'os';
21
+ import { GateRunner, ConfigSchema } from '@rigour-labs/core';
22
+ import { recordScore } from '@rigour-labs/core';
23
+ export async function demoCommand() {
24
+ console.log(chalk.bold.cyan(`
25
+ ____ _
26
+ / __ \\(_)____ ___ __ __ _____
27
+ / /_/ // // __ \`/ / / / / // ___/
28
+ / _, _// // /_/ // /_/ / / // /
29
+ /_/ |_|/_/ \\__, / \\__,_/_/ /_/
30
+ /____/
31
+ `));
32
+ console.log(chalk.bold('Rigour Demo — See AI code governance in action.\n'));
33
+ // 1. Create temp project
34
+ const demoDir = path.join(os.tmpdir(), `rigour-demo-${Date.now()}`);
35
+ console.log(chalk.dim(`Creating demo project at ${demoDir}...\n`));
36
+ await fs.ensureDir(demoDir);
37
+ await scaffoldDemoProject(demoDir);
38
+ console.log(chalk.green('✓ Demo project scaffolded with intentional issues.\n'));
39
+ // Show the issues planted
40
+ console.log(chalk.bold.yellow('📋 Planted issues:'));
41
+ console.log(chalk.dim(' 1. src/auth.ts — Hardcoded API key (security)'));
42
+ console.log(chalk.dim(' 2. src/api-handler.ts — Unhandled promise (AI drift)'));
43
+ console.log(chalk.dim(' 3. src/data-loader.ts — Hallucinated import (AI drift)'));
44
+ console.log(chalk.dim(' 4. src/utils.ts — TODO marker left by AI'));
45
+ console.log(chalk.dim(' 5. src/god-file.ts — 350+ lines (structural)'));
46
+ console.log('');
47
+ // 2. Run quality gates
48
+ console.log(chalk.bold.blue('🔍 Running Rigour quality gates...\n'));
49
+ await sleep(500);
50
+ try {
51
+ const configContent = await fs.readFile(path.join(demoDir, 'rigour.yml'), 'utf-8');
52
+ const rawConfig = yaml.parse(configContent);
53
+ const config = ConfigSchema.parse(rawConfig);
54
+ const runner = new GateRunner(config);
55
+ const report = await runner.run(demoDir);
56
+ // Record score
57
+ recordScore(demoDir, report);
58
+ // Write report
59
+ const reportPath = path.join(demoDir, config.output.report_path);
60
+ await fs.writeJson(reportPath, report, { spaces: 2 });
61
+ // Display results
62
+ const stats = report.stats;
63
+ if (report.status === 'FAIL') {
64
+ console.log(chalk.red.bold('✘ FAIL — Quality gate violations found.\n'));
65
+ // Score display
66
+ const scoreParts = [];
67
+ if (stats.score !== undefined)
68
+ scoreParts.push(`Score: ${stats.score}/100`);
69
+ if (stats.ai_health_score !== undefined)
70
+ scoreParts.push(`AI Health: ${stats.ai_health_score}/100`);
71
+ if (stats.structural_score !== undefined)
72
+ scoreParts.push(`Structural: ${stats.structural_score}/100`);
73
+ if (scoreParts.length > 0) {
74
+ console.log(chalk.bold(scoreParts.join(' | ')) + '\n');
75
+ }
76
+ // Severity breakdown
77
+ if (stats.severity_breakdown) {
78
+ const parts = Object.entries(stats.severity_breakdown)
79
+ .filter(([, count]) => count > 0)
80
+ .map(([sev, count]) => {
81
+ const color = sev === 'critical' ? chalk.red.bold : sev === 'high' ? chalk.red : sev === 'medium' ? chalk.yellow : chalk.dim;
82
+ return color(`${sev}: ${count}`);
83
+ });
84
+ if (parts.length > 0) {
85
+ console.log('Severity: ' + parts.join(', ') + '\n');
86
+ }
87
+ }
88
+ // Show violations
89
+ for (const failure of report.failures) {
90
+ const sevLabel = failure.severity === 'critical' ? chalk.red.bold('CRIT') :
91
+ failure.severity === 'high' ? chalk.red('HIGH') :
92
+ failure.severity === 'medium' ? chalk.yellow('MED ') : chalk.dim('LOW ');
93
+ const prov = failure.provenance ? chalk.dim(`[${failure.provenance}]`) : '';
94
+ console.log(` ${sevLabel} ${prov} ${chalk.red(`[${failure.id}]`)} ${failure.title}`);
95
+ if (failure.hint) {
96
+ console.log(chalk.cyan(` 💡 ${failure.hint}`));
97
+ }
98
+ }
99
+ console.log('');
100
+ }
101
+ else {
102
+ console.log(chalk.green.bold('✔ PASS — All quality gates satisfied.\n'));
103
+ }
104
+ console.log(chalk.dim(`Finished in ${stats.duration_ms}ms\n`));
105
+ // 3. Generate fix packet
106
+ if (report.status === 'FAIL') {
107
+ const { FixPacketService } = await import('@rigour-labs/core');
108
+ const fixPacketService = new FixPacketService();
109
+ const fixPacket = fixPacketService.generate(report, config);
110
+ const fixPacketPath = path.join(demoDir, 'rigour-fix-packet.json');
111
+ await fs.writeJson(fixPacketPath, fixPacket, { spaces: 2 });
112
+ console.log(chalk.green(`✓ Fix packet generated: rigour-fix-packet.json`));
113
+ }
114
+ // 4. Generate audit report
115
+ const { exportAuditCommand } = await import('./export-audit.js');
116
+ // We manually build a mini audit export
117
+ const auditPath = path.join(demoDir, 'rigour-audit-report.md');
118
+ await generateDemoAudit(demoDir, report, auditPath);
119
+ console.log(chalk.green(`✓ Audit report exported: rigour-audit-report.md`));
120
+ console.log('');
121
+ console.log(chalk.bold.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
122
+ console.log(chalk.bold('This is what Rigour does:'));
123
+ console.log(chalk.dim(' • Catches AI drift (hallucinated imports, unhandled promises)'));
124
+ console.log(chalk.dim(' • Blocks security issues (hardcoded keys, injection patterns)'));
125
+ console.log(chalk.dim(' • Enforces structure (file size, complexity, documentation)'));
126
+ console.log(chalk.dim(' • Generates audit-ready evidence (scores, trends, reports)'));
127
+ console.log(chalk.bold.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
128
+ console.log(chalk.bold('Get started:'));
129
+ console.log(chalk.white(' $ npx @rigour-labs/cli init'));
130
+ console.log(chalk.white(' $ npx @rigour-labs/cli check'));
131
+ console.log('');
132
+ console.log(chalk.dim(`Demo files: ${demoDir}`));
133
+ console.log(chalk.dim('GitHub: https://github.com/rigour-labs/rigour'));
134
+ console.log(chalk.dim('Docs: https://docs.rigour.run\n'));
135
+ console.log(chalk.dim.italic('If this saved you from a bad commit, star the repo ⭐'));
136
+ }
137
+ catch (error) {
138
+ console.error(chalk.red(`Demo error: ${error.message}`));
139
+ }
140
+ }
141
+ async function scaffoldDemoProject(dir) {
142
+ // rigour.yml
143
+ const config = {
144
+ version: 1,
145
+ preset: 'api',
146
+ gates: {
147
+ max_file_lines: 300,
148
+ forbid_todos: true,
149
+ forbid_fixme: true,
150
+ ast: {
151
+ complexity: 10,
152
+ max_params: 5,
153
+ },
154
+ security: {
155
+ enabled: true,
156
+ block_on_severity: 'high',
157
+ },
158
+ hallucinated_imports: {
159
+ enabled: true,
160
+ severity: 'critical',
161
+ },
162
+ promise_safety: {
163
+ enabled: true,
164
+ severity: 'high',
165
+ },
166
+ },
167
+ ignore: ['.git/**', 'node_modules/**'],
168
+ output: {
169
+ report_path: 'rigour-report.json',
170
+ },
171
+ };
172
+ await fs.writeFile(path.join(dir, 'rigour.yml'), yaml.stringify(config));
173
+ // package.json (for hallucinated import detection)
174
+ await fs.writeJson(path.join(dir, 'package.json'), {
175
+ name: 'rigour-demo',
176
+ version: '1.0.0',
177
+ dependencies: {
178
+ express: '^4.18.0',
179
+ zod: '^3.22.0',
180
+ },
181
+ }, { spaces: 2 });
182
+ // src directory
183
+ await fs.ensureDir(path.join(dir, 'src'));
184
+ // Issue 1: Hardcoded API key (security)
185
+ await fs.writeFile(path.join(dir, 'src', 'auth.ts'), `
186
+ import express from 'express';
187
+
188
+ const API_KEY = "sk-live-4f3c2b1a0987654321abcdef";
189
+ const DB_PASSWORD = "super_secret_p@ssw0rd!";
190
+
191
+ export function authenticate(req: express.Request) {
192
+ const token = req.headers.authorization;
193
+ if (token === API_KEY) {
194
+ return { authenticated: true };
195
+ }
196
+ return { authenticated: false };
197
+ }
198
+
199
+ export function connectDatabase() {
200
+ return { host: 'prod-db.internal', password: DB_PASSWORD };
201
+ }
202
+ `.trim());
203
+ // Issue 2: Unhandled promise (AI drift — promise safety)
204
+ await fs.writeFile(path.join(dir, 'src', 'api-handler.ts'), `
205
+ import express from 'express';
206
+
207
+ export async function fetchUserData(userId: string) {
208
+ const response = await fetch(\`https://api.example.com/users/\${userId}\`);
209
+ return response.json();
210
+ }
211
+
212
+ export function handleRequest(req: express.Request, res: express.Response) {
213
+ // AI generated this without .catch() — floating promise
214
+ fetchUserData(req.params.id);
215
+ res.send('Processing...');
216
+ }
217
+
218
+ export function batchProcess(ids: string[]) {
219
+ // Multiple unhandled promises
220
+ ids.forEach(id => fetchUserData(id));
221
+ }
222
+ `.trim());
223
+ // Issue 3: Hallucinated import (AI drift)
224
+ await fs.writeFile(path.join(dir, 'src', 'data-loader.ts'), `
225
+ import { z } from 'zod';
226
+ import { magicParser } from 'ai-data-magic';
227
+ import { ultraCache } from 'quantum-cache-pro';
228
+
229
+ const schema = z.object({
230
+ name: z.string(),
231
+ email: z.string().email(),
232
+ });
233
+
234
+ export function loadData(raw: unknown) {
235
+ const parsed = schema.parse(raw);
236
+ return parsed;
237
+ }
238
+ `.trim());
239
+ // Issue 4: TODO marker
240
+ await fs.writeFile(path.join(dir, 'src', 'utils.ts'), `
241
+ // TODO: Claude suggested this but I need to review
242
+ // FIXME: This function has edge cases
243
+ export function formatDate(date: Date): string {
244
+ return date.toISOString().split('T')[0];
245
+ }
246
+
247
+ export function sanitizeInput(input: string): string {
248
+ // TODO: Add proper sanitization
249
+ return input.trim();
250
+ }
251
+ `.trim());
252
+ // Issue 5: God file (350+ lines)
253
+ const godFileLines = [
254
+ '// Auto-generated data processing module',
255
+ 'export class DataProcessor {',
256
+ ];
257
+ for (let i = 0; i < 60; i++) {
258
+ godFileLines.push(` process${i}(data: any) {`);
259
+ godFileLines.push(` const result = data.map((x: any) => x * ${i + 1});`);
260
+ godFileLines.push(` if (result.length > ${i * 10}) {`);
261
+ godFileLines.push(` return result.slice(0, ${i * 10});`);
262
+ godFileLines.push(` }`);
263
+ godFileLines.push(` return result;`);
264
+ godFileLines.push(` }`);
265
+ }
266
+ godFileLines.push('}');
267
+ await fs.writeFile(path.join(dir, 'src', 'god-file.ts'), godFileLines.join('\n'));
268
+ // docs directory (missing required docs)
269
+ await fs.ensureDir(path.join(dir, 'docs'));
270
+ await fs.writeFile(path.join(dir, 'README.md'), '# Demo Project\n\nThis is a demo project for Rigour.\n');
271
+ }
272
+ async function generateDemoAudit(dir, report, outputPath) {
273
+ const stats = report.stats || {};
274
+ const failures = report.failures || [];
275
+ const lines = [];
276
+ lines.push('# Rigour Audit Report — Demo');
277
+ lines.push('');
278
+ lines.push(`**Generated:** ${new Date().toISOString()}`);
279
+ lines.push(`**Status:** ${report.status}`);
280
+ lines.push(`**Score:** ${stats.score ?? 100}/100`);
281
+ if (stats.ai_health_score !== undefined)
282
+ lines.push(`**AI Health:** ${stats.ai_health_score}/100`);
283
+ if (stats.structural_score !== undefined)
284
+ lines.push(`**Structural:** ${stats.structural_score}/100`);
285
+ lines.push('');
286
+ lines.push('## Violations');
287
+ lines.push('');
288
+ for (let i = 0; i < failures.length; i++) {
289
+ const f = failures[i];
290
+ lines.push(`### ${i + 1}. [${(f.severity || 'medium').toUpperCase()}] ${f.title}`);
291
+ lines.push(`- **ID:** \`${f.id}\``);
292
+ lines.push(`- **Provenance:** ${f.provenance || 'traditional'}`);
293
+ lines.push(`- **Details:** ${f.details}`);
294
+ if (f.files?.length)
295
+ lines.push(`- **Files:** ${f.files.join(', ')}`);
296
+ if (f.hint)
297
+ lines.push(`- **Hint:** ${f.hint}`);
298
+ lines.push('');
299
+ }
300
+ lines.push('---');
301
+ lines.push('*Generated by Rigour — https://rigour.run*');
302
+ await fs.writeFile(outputPath, lines.join('\n'));
303
+ }
304
+ function sleep(ms) {
305
+ return new Promise(resolve => setTimeout(resolve, ms));
306
+ }
@@ -28,6 +28,38 @@ export async function explainCommand(cwd) {
28
28
  console.log(chalk.bold('Status: ') + (report.status === 'PASS'
29
29
  ? chalk.green.bold('✅ PASS')
30
30
  : chalk.red.bold('🛑 FAIL')));
31
+ // Score summary
32
+ if (report.stats) {
33
+ const stats = report.stats;
34
+ const scoreParts = [];
35
+ if (stats.score !== undefined)
36
+ scoreParts.push(`Score: ${stats.score}/100`);
37
+ if (stats.ai_health_score !== undefined)
38
+ scoreParts.push(`AI Health: ${stats.ai_health_score}/100`);
39
+ if (stats.structural_score !== undefined)
40
+ scoreParts.push(`Structural: ${stats.structural_score}/100`);
41
+ if (scoreParts.length > 0) {
42
+ console.log(chalk.bold('\n' + scoreParts.join(' | ')));
43
+ }
44
+ // Severity breakdown
45
+ if (stats.severity_breakdown) {
46
+ const sevParts = Object.entries(stats.severity_breakdown)
47
+ .filter(([, count]) => count > 0)
48
+ .map(([sev, count]) => `${sev}: ${count}`);
49
+ if (sevParts.length > 0) {
50
+ console.log(chalk.dim('Severity: ' + sevParts.join(', ')));
51
+ }
52
+ }
53
+ // Provenance breakdown
54
+ if (stats.provenance_breakdown) {
55
+ const provParts = Object.entries(stats.provenance_breakdown)
56
+ .filter(([, count]) => count > 0)
57
+ .map(([prov, count]) => `${prov}: ${count}`);
58
+ if (provParts.length > 0) {
59
+ console.log(chalk.dim('Categories: ' + provParts.join(', ')));
60
+ }
61
+ }
62
+ }
31
63
  console.log(chalk.bold('\nGate Summary:'));
32
64
  for (const [gate, status] of Object.entries(report.summary || {})) {
33
65
  const icon = status === 'PASS' ? '✅' : status === 'FAIL' ? '❌' : '⏭️';
@@ -35,8 +67,21 @@ export async function explainCommand(cwd) {
35
67
  }
36
68
  if (report.failures && report.failures.length > 0) {
37
69
  console.log(chalk.bold.red(`\n🔧 ${report.failures.length} Violation(s) to Fix:\n`));
70
+ // Severity color helper
71
+ const sevColor = (s) => {
72
+ switch (s) {
73
+ case 'critical': return chalk.red.bold;
74
+ case 'high': return chalk.red;
75
+ case 'medium': return chalk.yellow;
76
+ case 'low': return chalk.dim;
77
+ default: return chalk.dim;
78
+ }
79
+ };
38
80
  report.failures.forEach((failure, index) => {
39
- console.log(chalk.white(`${index + 1}. `) + chalk.bold.yellow(`[${failure.id.toUpperCase()}]`) + chalk.white(` ${failure.title}`));
81
+ const sev = failure.severity || 'medium';
82
+ const sevLabel = sevColor(sev)(`[${sev.toUpperCase()}]`);
83
+ const provLabel = failure.provenance ? chalk.dim(`(${failure.provenance})`) : '';
84
+ console.log(chalk.white(`${index + 1}. `) + sevLabel + ' ' + chalk.bold.yellow(`[${failure.id.toUpperCase()}]`) + ' ' + provLabel + chalk.white(` ${failure.title}`));
40
85
  console.log(chalk.dim(` └─ ${failure.details}`));
41
86
  if (failure.files && failure.files.length > 0) {
42
87
  console.log(chalk.cyan(` 📁 Files: ${failure.files.join(', ')}`));
@@ -0,0 +1,16 @@
1
+ /**
2
+ * export-audit command
3
+ *
4
+ * Generates a compliance audit package from the last gate check.
5
+ * The artifact compliance officers hand to auditors.
6
+ *
7
+ * Formats: JSON (structured) or Markdown (human-readable)
8
+ *
9
+ * @since v2.17.0
10
+ */
11
+ export interface ExportAuditOptions {
12
+ format?: 'json' | 'md';
13
+ output?: string;
14
+ run?: boolean;
15
+ }
16
+ export declare function exportAuditCommand(cwd: string, options?: ExportAuditOptions): Promise<void>;