@rigour-labs/cli 3.0.5 → 4.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.
@@ -157,12 +157,34 @@ function renderScanHeader(scanCtx, stackSignals) {
157
157
  }
158
158
  function renderScanResults(report, stackSignals, reportPath, cwd) {
159
159
  const fakePackages = extractHallucinatedImports(report.failures);
160
+ const criticalSecrets = report.failures.filter(f => f.id === 'security-patterns' && f.severity === 'critical');
161
+ const phantomApis = report.failures.filter(f => f.id === 'phantom-apis');
162
+ const ignoredErrors = report.failures.filter(f => f.id === 'promise-safety' && (f.severity === 'high' || f.severity === 'critical'));
163
+ // --- Scary headlines for the worst findings ---
164
+ let scaryHeadlines = 0;
165
+ if (criticalSecrets.length > 0) {
166
+ console.log(chalk.red.bold(`šŸ”‘ HARDCODED SECRETS: ${criticalSecrets.length} credential(s) exposed in plain text`));
167
+ const firstFile = criticalSecrets[0].files?.[0];
168
+ if (firstFile)
169
+ console.log(chalk.dim(` First hit: ${firstFile}`));
170
+ scaryHeadlines++;
171
+ }
160
172
  if (fakePackages.length > 0) {
161
173
  const unique = [...new Set(fakePackages)];
162
- console.log(chalk.red.bold(`oh shit: ${unique.length} fake package/path import(s) detected`));
163
- console.log(chalk.dim(`Examples: ${unique.slice(0, 5).join(', ')}${unique.length > 5 ? ', ...' : ''}`));
164
- console.log('');
174
+ console.log(chalk.red.bold(`šŸ“¦ HALLUCINATED PACKAGES: ${unique.length} import(s) don't exist — will crash at runtime`));
175
+ console.log(chalk.dim(` Examples: ${unique.slice(0, 4).join(', ')}${unique.length > 4 ? `, +${unique.length - 4} more` : ''}`));
176
+ scaryHeadlines++;
177
+ }
178
+ if (phantomApis.length > 0) {
179
+ console.log(chalk.red.bold(`šŸ‘» PHANTOM APIs: ${phantomApis.length} call(s) to methods that don't exist in stdlib`));
180
+ scaryHeadlines++;
165
181
  }
182
+ if (ignoredErrors.length > 0) {
183
+ console.log(chalk.yellow.bold(`šŸ”‡ SILENT FAILURES: ${ignoredErrors.length} async error(s) swallowed — failures will vanish without a trace`));
184
+ scaryHeadlines++;
185
+ }
186
+ if (scaryHeadlines > 0)
187
+ console.log('');
166
188
  const statusColor = report.status === 'PASS' ? chalk.green.bold : chalk.red.bold;
167
189
  const statusLabel = report.status === 'PASS' ? 'PASS' : 'FAIL';
168
190
  const score = report.stats.score ?? 0;
@@ -179,27 +201,48 @@ function renderScanResults(report, stackSignals, reportPath, cwd) {
179
201
  renderCoverageWarnings(stackSignals);
180
202
  console.log('');
181
203
  if (report.status === 'FAIL') {
182
- const topFindings = report.failures.slice(0, 8);
204
+ // Sort by severity so critical findings appear first
205
+ const SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
206
+ const sorted = [...report.failures].sort((a, b) => (SEVERITY_ORDER[a.severity ?? 'medium'] ?? 2) - (SEVERITY_ORDER[b.severity ?? 'medium'] ?? 2));
207
+ const topFindings = sorted.slice(0, 8);
183
208
  for (const failure of topFindings) {
184
- const sev = (failure.severity || 'medium').toUpperCase().padEnd(8, ' ');
185
- console.log(`${sev} [${failure.id}] ${failure.title}`);
209
+ const sev = failure.severity ?? 'medium';
210
+ const sevColor = sev === 'critical' ? chalk.red.bold
211
+ : sev === 'high' ? chalk.yellow.bold
212
+ : sev === 'medium' ? chalk.white
213
+ : chalk.dim;
214
+ console.log(sevColor(`${sev.toUpperCase().padEnd(8)} [${failure.id}] ${failure.title}`));
186
215
  if (failure.files && failure.files.length > 0) {
187
- console.log(chalk.dim(` files: ${failure.files.slice(0, 3).join(', ')}`));
216
+ console.log(chalk.dim(` ${failure.files.slice(0, 2).join(', ')}`));
188
217
  }
189
218
  }
190
219
  if (report.failures.length > topFindings.length) {
191
- console.log(chalk.dim(`...and ${report.failures.length - topFindings.length} more findings`));
220
+ console.log(chalk.dim(`\n...and ${report.failures.length - topFindings.length} more. See full report.`));
192
221
  }
193
222
  }
194
223
  const trend = getScoreTrend(cwd);
195
224
  if (trend && trend.recentScores.length >= 3) {
196
- console.log(chalk.dim(`\nTrend: ${trend.recentScores.join(' -> ')} (${trend.direction})`));
225
+ const arrow = trend.direction === 'improving' ? '↑' : trend.direction === 'degrading' ? '↓' : '→';
226
+ const color = trend.direction === 'improving' ? chalk.green : trend.direction === 'degrading' ? chalk.red : chalk.dim;
227
+ console.log(color(`\nTrend: ${trend.recentScores.join(' → ')} ${arrow}`));
197
228
  }
198
229
  console.log(chalk.yellow(`\nFull report: ${reportPath}`));
199
230
  if (report.status === 'FAIL') {
200
- console.log(chalk.yellow('Fix packet: rigour-fix-packet.json'));
231
+ console.log(chalk.yellow('Fix packet: rigour-fix-packet.json'));
201
232
  }
202
233
  console.log(chalk.dim(`Finished in ${report.stats.duration_ms}ms`));
234
+ // --- Next steps ---
235
+ console.log('');
236
+ if (report.status === 'FAIL') {
237
+ console.log(chalk.bold('Next steps:'));
238
+ console.log(` ${chalk.cyan('rigour explain')} — get plain-English fix suggestions`);
239
+ console.log(` ${chalk.cyan('rigour init')} — add quality gates to your project (blocks AI from repeating this)`);
240
+ console.log(` ${chalk.cyan('rigour check --ci')} — enforce in CI/CD pipeline`);
241
+ }
242
+ else {
243
+ console.log(chalk.green.bold('āœ“ This repo is clean. Add it to CI to keep it that way:'));
244
+ console.log(` ${chalk.cyan('rigour init')} — write quality gates to rigour.yml + CI config`);
245
+ }
203
246
  }
204
247
  function renderCoverageWarnings(stackSignals) {
205
248
  const gaps = [];
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `rigour settings` — manage ~/.rigour/settings.json
3
+ *
4
+ * Like Claude Code's settings.json or Gemini CLI's config.
5
+ * Stores API keys, default provider, multi-agent config, CLI preferences.
6
+ */
7
+ export declare function settingsShowCommand(): Promise<void>;
8
+ export declare function settingsSetKeyCommand(provider: string, apiKey: string): Promise<void>;
9
+ export declare function settingsRemoveKeyCommand(provider: string): Promise<void>;
10
+ export declare function settingsSetCommand(key: string, value: string): Promise<void>;
11
+ export declare function settingsGetCommand(key: string): Promise<void>;
12
+ export declare function settingsResetCommand(): Promise<void>;
13
+ export declare function settingsPathCommand(): Promise<void>;
@@ -0,0 +1,143 @@
1
+ import chalk from 'chalk';
2
+ import { loadSettings, saveSettings, getSettingsPath, updateProviderKey, removeProviderKey } from '@rigour-labs/core';
3
+ /**
4
+ * `rigour settings` — manage ~/.rigour/settings.json
5
+ *
6
+ * Like Claude Code's settings.json or Gemini CLI's config.
7
+ * Stores API keys, default provider, multi-agent config, CLI preferences.
8
+ */
9
+ export async function settingsShowCommand() {
10
+ const settingsPath = getSettingsPath();
11
+ const settings = loadSettings();
12
+ console.log(chalk.bold.cyan('\n Rigour Settings'));
13
+ console.log(chalk.dim(` ${settingsPath}\n`));
14
+ if (Object.keys(settings).length === 0) {
15
+ console.log(chalk.dim(' No settings configured yet.\n'));
16
+ console.log(chalk.dim(' Quick start:'));
17
+ console.log(chalk.dim(' rigour settings set-key anthropic sk-ant-xxx'));
18
+ console.log(chalk.dim(' rigour settings set-key openai sk-xxx'));
19
+ console.log(chalk.dim(' rigour settings set provider anthropic'));
20
+ console.log('');
21
+ return;
22
+ }
23
+ // Show providers
24
+ if (settings.providers && Object.keys(settings.providers).length > 0) {
25
+ console.log(chalk.bold(' Providers:'));
26
+ for (const [name, key] of Object.entries(settings.providers)) {
27
+ if (key) {
28
+ const masked = maskKey(key);
29
+ console.log(` ${chalk.green(name)}: ${chalk.dim(masked)}`);
30
+ }
31
+ }
32
+ console.log('');
33
+ }
34
+ // Show deep defaults
35
+ if (settings.deep) {
36
+ console.log(chalk.bold(' Deep Analysis Defaults:'));
37
+ if (settings.deep.defaultProvider)
38
+ console.log(` Provider: ${chalk.cyan(settings.deep.defaultProvider)}`);
39
+ if (settings.deep.defaultModel)
40
+ console.log(` Model: ${chalk.cyan(settings.deep.defaultModel)}`);
41
+ if (settings.deep.apiBaseUrl)
42
+ console.log(` API Base: ${chalk.cyan(settings.deep.apiBaseUrl)}`);
43
+ if (settings.deep.maxTokens)
44
+ console.log(` Max Tokens: ${settings.deep.maxTokens}`);
45
+ if (settings.deep.temperature !== undefined)
46
+ console.log(` Temperature: ${settings.deep.temperature}`);
47
+ console.log('');
48
+ }
49
+ // Show agent configs
50
+ if (settings.agents && Object.keys(settings.agents).length > 0) {
51
+ console.log(chalk.bold(' Agent Configurations:'));
52
+ for (const [name, config] of Object.entries(settings.agents)) {
53
+ const parts = [];
54
+ if (config.model)
55
+ parts.push(`model: ${config.model}`);
56
+ if (config.provider)
57
+ parts.push(`provider: ${config.provider}`);
58
+ if (config.fallback)
59
+ parts.push(`fallback: ${config.fallback}`);
60
+ console.log(` ${chalk.green(name)}: ${chalk.dim(parts.join(', '))}`);
61
+ }
62
+ console.log('');
63
+ }
64
+ // Show CLI prefs
65
+ if (settings.cli) {
66
+ console.log(chalk.bold(' CLI Preferences:'));
67
+ if (settings.cli.defaultPreset)
68
+ console.log(` Default Preset: ${settings.cli.defaultPreset}`);
69
+ if (settings.cli.colorOutput !== undefined)
70
+ console.log(` Color Output: ${settings.cli.colorOutput}`);
71
+ if (settings.cli.verboseOutput !== undefined)
72
+ console.log(` Verbose: ${settings.cli.verboseOutput}`);
73
+ console.log('');
74
+ }
75
+ }
76
+ export async function settingsSetKeyCommand(provider, apiKey) {
77
+ updateProviderKey(provider, apiKey);
78
+ const masked = maskKey(apiKey);
79
+ console.log(chalk.green(` āœ“ ${provider} API key saved: ${masked}`));
80
+ console.log(chalk.dim(` Stored in ${getSettingsPath()}`));
81
+ console.log('');
82
+ console.log(chalk.dim(` Usage: rigour check --deep --provider ${provider}`));
83
+ console.log(chalk.dim(` Or set as default: rigour settings set provider ${provider}`));
84
+ }
85
+ export async function settingsRemoveKeyCommand(provider) {
86
+ removeProviderKey(provider);
87
+ console.log(chalk.green(` āœ“ ${provider} API key removed`));
88
+ }
89
+ export async function settingsSetCommand(key, value) {
90
+ const settings = loadSettings();
91
+ // Parse dot-notation keys: "deep.defaultProvider" -> settings.deep.defaultProvider
92
+ const parts = key.split('.');
93
+ let target = settings;
94
+ for (let i = 0; i < parts.length - 1; i++) {
95
+ if (!target[parts[i]])
96
+ target[parts[i]] = {};
97
+ target = target[parts[i]];
98
+ }
99
+ const lastKey = parts[parts.length - 1];
100
+ // Auto-convert booleans and numbers
101
+ if (value === 'true')
102
+ target[lastKey] = true;
103
+ else if (value === 'false')
104
+ target[lastKey] = false;
105
+ else if (!isNaN(Number(value)) && value.trim() !== '')
106
+ target[lastKey] = Number(value);
107
+ else
108
+ target[lastKey] = value;
109
+ saveSettings(settings);
110
+ console.log(chalk.green(` āœ“ ${key} = ${value}`));
111
+ }
112
+ export async function settingsGetCommand(key) {
113
+ const settings = loadSettings();
114
+ const parts = key.split('.');
115
+ let value = settings;
116
+ for (const part of parts) {
117
+ if (value === undefined || value === null)
118
+ break;
119
+ value = value[part];
120
+ }
121
+ if (value === undefined) {
122
+ console.log(chalk.dim(` ${key} is not set`));
123
+ }
124
+ else if (typeof value === 'object') {
125
+ console.log(` ${key} = ${JSON.stringify(value, null, 2)}`);
126
+ }
127
+ else {
128
+ console.log(` ${key} = ${value}`);
129
+ }
130
+ }
131
+ export async function settingsResetCommand() {
132
+ saveSettings({});
133
+ console.log(chalk.green(' āœ“ Settings reset to defaults'));
134
+ console.log(chalk.dim(` ${getSettingsPath()}`));
135
+ }
136
+ export async function settingsPathCommand() {
137
+ console.log(getSettingsPath());
138
+ }
139
+ function maskKey(key) {
140
+ if (key.length <= 8)
141
+ return '***';
142
+ return key.substring(0, 6) + '...' + key.substring(key.length - 4);
143
+ }
@@ -1,22 +1,113 @@
1
1
  import chalk from 'chalk';
2
+ import path from 'path';
3
+ import fs from 'fs-extra';
4
+ import { loadSettings, getSettingsPath, isModelCached, getModelsDir } from '@rigour-labs/core';
2
5
  export async function setupCommand() {
3
- console.log(chalk.bold.cyan('\nšŸ› ļø Rigour Labs | Setup & Installation\n'));
4
- console.log(chalk.bold('1. Global Installation (Recommended)'));
5
- console.log(chalk.dim(' To use Rigour anywhere in your terminal:'));
6
- console.log(chalk.green(' $ npm install -g @rigour-labs/cli\n'));
7
- console.log(chalk.bold('2. Project-Local installation'));
8
- console.log(chalk.dim(' To keep Rigour versioned with your project:'));
9
- console.log(chalk.green(' $ npm install --save-dev @rigour-labs/cli\n'));
10
- console.log(chalk.bold('3. Standalone Binaries (Zero-Install)'));
11
- console.log(chalk.dim(' If you do not want to use Node.js:'));
12
- console.log(chalk.dim(' • macOS: ') + chalk.cyan('https://github.com/erashu212/rigour/releases/latest/download/rigour-macos'));
13
- console.log(chalk.dim(' • Linux: ') + chalk.cyan('https://github.com/erashu212/rigour/releases/latest/download/rigour-linux'));
14
- console.log(chalk.dim(' • Windows: ') + chalk.cyan('https://github.com/erashu212/rigour/releases/latest/download/rigour-windows.exe\n'));
15
- console.log(chalk.bold('4. MCP Integration (for AI Agents)'));
16
- console.log(chalk.dim(' To let Cursor or Claude use Rigour natively:'));
17
- console.log(chalk.dim(' Path to MCP: ') + chalk.cyan('packages/rigour-mcp/dist/index.js'));
18
- console.log(chalk.dim(' Add this to your Cursor/Claude settings.\n'));
19
- console.log(chalk.bold('Update Guidance:'));
20
- console.log(chalk.dim(' Keep Rigour sharp by updating regularly:'));
21
- console.log(chalk.green(' $ npm install -g @rigour-labs/cli@latest\n'));
6
+ console.log(chalk.bold.cyan('\nšŸ› ļø Rigour Labs | Setup & System Check\n'));
7
+ // ── Section 1: Installation Status ──
8
+ console.log(chalk.bold(' Installation'));
9
+ const cliVersion = getCliVersion();
10
+ if (cliVersion) {
11
+ console.log(chalk.green(` āœ” Rigour CLI ${cliVersion}`));
12
+ }
13
+ // Check if rigour.yml exists in cwd
14
+ const hasConfig = fs.existsSync(path.join(process.cwd(), 'rigour.yml'));
15
+ if (hasConfig) {
16
+ console.log(chalk.green(' āœ” rigour.yml found in current directory'));
17
+ }
18
+ else {
19
+ console.log(chalk.yellow(' ā—‹ No rigour.yml — run `rigour init` to set up'));
20
+ }
21
+ // ── Section 2: Settings & API Keys ──
22
+ console.log(chalk.bold('\n Settings'));
23
+ const settingsPath = getSettingsPath();
24
+ const settings = loadSettings();
25
+ const providers = settings.providers || {};
26
+ const configuredKeys = Object.entries(providers).filter(([_, key]) => !!key);
27
+ if (configuredKeys.length > 0) {
28
+ for (const [name, key] of configuredKeys) {
29
+ if (key) {
30
+ const masked = key.length > 8 ? key.substring(0, 6) + '...' + key.substring(key.length - 4) : '***';
31
+ console.log(chalk.green(` āœ” ${name}: ${chalk.dim(masked)}`));
32
+ }
33
+ }
34
+ }
35
+ else {
36
+ console.log(chalk.yellow(' ā—‹ No API keys configured'));
37
+ console.log(chalk.dim(` ${settingsPath}`));
38
+ }
39
+ if (settings.deep?.defaultProvider) {
40
+ console.log(chalk.green(` āœ” Default provider: ${settings.deep.defaultProvider}`));
41
+ }
42
+ // ── Section 3: Deep Analysis Readiness ──
43
+ console.log(chalk.bold('\n Deep Analysis'));
44
+ // Check local models
45
+ const hasDeep = isModelCached('deep');
46
+ const hasPro = isModelCached('pro');
47
+ if (hasDeep)
48
+ console.log(chalk.green(' āœ” Local model: deep (Qwen2.5-Coder-0.5B, 350MB)'));
49
+ if (hasPro)
50
+ console.log(chalk.green(' āœ” Local model: pro (Qwen2.5-Coder-1.5B, 900MB)'));
51
+ if (!hasDeep && !hasPro) {
52
+ console.log(chalk.yellow(' ā—‹ No local models cached'));
53
+ console.log(chalk.dim(` Models dir: ${getModelsDir()}`));
54
+ }
55
+ // Check sidecar binary
56
+ let hasSidecar = false;
57
+ try {
58
+ const { execSync } = await import('child_process');
59
+ execSync('which llama-cli 2>/dev/null || which rigour-brain 2>/dev/null', { encoding: 'utf-8', timeout: 3000 });
60
+ hasSidecar = true;
61
+ console.log(chalk.green(' āœ” Inference binary found'));
62
+ }
63
+ catch {
64
+ const binDir = path.join(getModelsDir(), '..', 'bin');
65
+ if (fs.existsSync(path.join(binDir, 'rigour-brain')) || fs.existsSync(path.join(binDir, 'llama-cli'))) {
66
+ hasSidecar = true;
67
+ console.log(chalk.green(' āœ” Inference binary found'));
68
+ }
69
+ else if (configuredKeys.length === 0) {
70
+ console.log(chalk.yellow(' ā—‹ No local inference binary'));
71
+ }
72
+ }
73
+ // Cloud readiness
74
+ const hasCloudKey = configuredKeys.length > 0;
75
+ const hasLocalReady = hasSidecar && (hasDeep || hasPro);
76
+ if (hasCloudKey || hasLocalReady) {
77
+ console.log(chalk.green.bold('\n āœ“ Deep analysis is ready'));
78
+ }
79
+ else {
80
+ console.log(chalk.yellow.bold('\n ⚠ Deep analysis not configured'));
81
+ }
82
+ // ── Section 4: Quick Setup Commands ──
83
+ if (!hasCloudKey && !hasLocalReady) {
84
+ console.log(chalk.bold('\n Quick Setup:'));
85
+ console.log(chalk.dim(' # Option A: Cloud (recommended)'));
86
+ console.log(` ${chalk.cyan('rigour settings set-key anthropic')} ${chalk.dim('sk-ant-xxx')}`);
87
+ console.log(` ${chalk.cyan('rigour settings set-key openai')} ${chalk.dim('sk-xxx')}`);
88
+ console.log(` ${chalk.cyan('rigour settings set-key groq')} ${chalk.dim('gsk_xxx')}`);
89
+ console.log('');
90
+ console.log(chalk.dim(' # Option B: 100% Local'));
91
+ console.log(` ${chalk.cyan('rigour check --deep')} ${chalk.dim('# auto-downloads 350MB model')}`);
92
+ }
93
+ // ── Section 5: Installation Methods ──
94
+ console.log(chalk.bold('\n Installation Methods:'));
95
+ console.log(chalk.dim(' Global: ') + chalk.cyan('npm install -g @rigour-labs/cli'));
96
+ console.log(chalk.dim(' Local: ') + chalk.cyan('npm install --save-dev @rigour-labs/cli'));
97
+ console.log(chalk.dim(' No-install: ') + chalk.cyan('npx @rigour-labs/cli check'));
98
+ console.log(chalk.dim(' MCP: ') + chalk.cyan('packages/rigour-mcp/dist/index.js'));
99
+ console.log('');
100
+ }
101
+ function getCliVersion() {
102
+ try {
103
+ const pkgPath = path.resolve(new URL(import.meta.url).pathname, '../../../package.json');
104
+ if (fs.existsSync(pkgPath)) {
105
+ const pkg = fs.readJsonSync(pkgPath);
106
+ return pkg.version || null;
107
+ }
108
+ }
109
+ catch {
110
+ // fallback
111
+ }
112
+ return '2.0.0';
22
113
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigour-labs/cli",
3
- "version": "3.0.5",
3
+ "version": "4.0.0",
4
4
  "description": "CLI quality gates for AI-generated code. Forces AI agents (Claude, Cursor, Copilot) to meet strict engineering standards with PASS/FAIL enforcement.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://rigour.run",
@@ -44,7 +44,7 @@
44
44
  "inquirer": "9.2.16",
45
45
  "ora": "^8.0.1",
46
46
  "yaml": "^2.8.2",
47
- "@rigour-labs/core": "3.0.5"
47
+ "@rigour-labs/core": "4.0.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/fs-extra": "^11.0.4",