@rigour-labs/cli 2.22.0 → 3.0.1

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,9 @@ 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';
13
+ import { hooksInitCommand } from './commands/hooks.js';
11
14
  import { checkForUpdates } from './utils/version.js';
12
15
  import chalk from 'chalk';
13
16
  const CLI_VERSION = '2.0.0';
@@ -29,7 +32,7 @@ program
29
32
  program
30
33
  .command('init')
31
34
  .description('Initialize Rigour in the current directory')
32
- .option('-p, --preset <name>', 'Project preset (ui, api, infra, data)')
35
+ .option('-p, --preset <name>', 'Project preset (ui, api, infra, data, healthcare, fintech, government)')
33
36
  .option('--paradigm <name>', 'Coding paradigm (oop, functional, minimal)')
34
37
  .option('--ide <name>', 'Target IDE (cursor, vscode, all). Auto-detects if not specified.')
35
38
  .option('--dry-run', 'Show detected configuration without writing files')
@@ -37,10 +40,12 @@ program
37
40
  .option('-f, --force', 'Force re-initialization, overwriting existing rigour.yml')
38
41
  .addHelpText('after', `
39
42
  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
43
+ $ rigour init # Auto-discover role & paradigm
44
+ $ rigour init --preset api --explain # Force API role and show why
45
+ $ rigour init --preset healthcare # HIPAA-compliant quality gates
46
+ $ rigour init --preset fintech # SOC2/PCI-DSS quality gates
47
+ $ rigour init --preset government # FedRAMP/NIST quality gates
48
+ $ rigour init --ide all # Create files for all IDEs
44
49
  `)
45
50
  .action(async (options) => {
46
51
  await initCommand(process.cwd(), options);
@@ -91,6 +96,43 @@ Examples:
91
96
  failFast: !!options.failFast
92
97
  });
93
98
  });
99
+ program
100
+ .command('export-audit')
101
+ .description('Generate a compliance audit package from the last check')
102
+ .option('-f, --format <type>', 'Output format: json or md', 'json')
103
+ .option('-o, --output <path>', 'Custom output file path')
104
+ .option('--run', 'Run a fresh rigour check before exporting')
105
+ .addHelpText('after', `
106
+ Examples:
107
+ $ rigour export-audit # Export JSON audit package
108
+ $ rigour export-audit --format md # Export Markdown report
109
+ $ rigour export-audit --run # Run check first, then export
110
+ $ rigour export-audit -o audit.json # Custom output path
111
+ `)
112
+ .action(async (options) => {
113
+ await exportAuditCommand(process.cwd(), options);
114
+ });
115
+ program
116
+ .command('demo')
117
+ .description('Run a live demo — see Rigour catch AI drift, security issues, and structural violations')
118
+ .option('--cinematic', 'Screen-recording mode: typewriter effects, simulated AI agent, before/after scores')
119
+ .option('--hooks', 'Focus on real-time hooks catching issues as AI writes code')
120
+ .option('--speed <speed>', 'Pacing: fast, normal, slow (default: normal)', 'normal')
121
+ .addHelpText('after', `
122
+ Examples:
123
+ $ rigour demo # Run the flagship demo
124
+ $ rigour demo --cinematic # Screen-recording optimized (great for GIFs)
125
+ $ rigour demo --cinematic --speed slow # Slower pacing for presentations
126
+ $ rigour demo --hooks # Focus on hooks catching issues
127
+ $ npx @rigour-labs/cli demo # Try without installing
128
+ `)
129
+ .action(async (options) => {
130
+ await demoCommand({
131
+ cinematic: !!options.cinematic,
132
+ hooks: !!options.hooks,
133
+ speed: options.speed || 'normal',
134
+ });
135
+ });
94
136
  program
95
137
  .command('guide')
96
138
  .description('Show the interactive engineering guide')
@@ -103,6 +145,27 @@ program
103
145
  .action(async () => {
104
146
  await setupCommand();
105
147
  });
148
+ const hooksCmd = program
149
+ .command('hooks')
150
+ .description('Manage AI coding tool hook integrations');
151
+ hooksCmd
152
+ .command('init')
153
+ .description('Generate hook configs for AI coding tools (Claude, Cursor, Cline, Windsurf)')
154
+ .option('-t, --tool <name>', 'Target tool(s): claude, cursor, cline, windsurf, all. Auto-detects if not specified.')
155
+ .option('--dry-run', 'Show what files would be created without writing them')
156
+ .option('-f, --force', 'Overwrite existing hook files')
157
+ .option('--block', 'Configure hooks to block on failure (exit code 2)')
158
+ .addHelpText('after', `
159
+ Examples:
160
+ $ rigour hooks init # Auto-detect tools, generate hooks
161
+ $ rigour hooks init --tool claude # Generate Claude Code hooks only
162
+ $ rigour hooks init --tool all # Generate hooks for all tools
163
+ $ rigour hooks init --dry-run # Preview without writing files
164
+ $ rigour hooks init --tool cursor -f # Force overwrite Cursor hooks
165
+ `)
166
+ .action(async (options) => {
167
+ await hooksInitCommand(process.cwd(), options);
168
+ });
106
169
  // Check for updates before parsing (non-blocking)
107
170
  (async () => {
108
171
  try {
@@ -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,23 @@
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
+ *
7
+ * Modes:
8
+ * Default: Fast demo — scaffold → check → results
9
+ * --cinematic: Screen-recording optimized — typewriter effects, pauses,
10
+ * simulated AI agent writing code, hooks catching issues
11
+ * --hooks: Focus on the real-time hooks experience
12
+ * --speed: Control pacing (fast / normal / slow)
13
+ *
14
+ * The "flagship demo" — one command to understand Rigour.
15
+ *
16
+ * @since v2.17.0 (extended v3.0.0)
17
+ */
18
+ export interface DemoOptions {
19
+ cinematic?: boolean;
20
+ hooks?: boolean;
21
+ speed?: 'fast' | 'normal' | 'slow';
22
+ }
23
+ export declare function demoCommand(options?: DemoOptions): Promise<void>;