@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.
- package/dist/cli.js +55 -5
- package/dist/commands/check.d.ts +7 -0
- package/dist/commands/check.js +287 -61
- package/dist/commands/demo-display.d.ts +11 -0
- package/dist/commands/demo-display.js +181 -0
- package/dist/commands/demo-helpers.d.ts +9 -0
- package/dist/commands/demo-helpers.js +28 -0
- package/dist/commands/demo-scenarios.d.ts +11 -0
- package/dist/commands/demo-scenarios.js +356 -0
- package/dist/commands/demo.d.ts +2 -15
- package/dist/commands/demo.js +5 -569
- package/dist/commands/init.js +83 -1
- package/dist/commands/scan.js +53 -10
- package/dist/commands/settings.d.ts +13 -0
- package/dist/commands/settings.js +143 -0
- package/dist/commands/setup.js +110 -19
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -12,6 +12,7 @@ import { studioCommand } from './commands/studio.js';
|
|
|
12
12
|
import { exportAuditCommand } from './commands/export-audit.js';
|
|
13
13
|
import { demoCommand } from './commands/demo.js';
|
|
14
14
|
import { hooksInitCommand } from './commands/hooks.js';
|
|
15
|
+
import { settingsShowCommand, settingsSetKeyCommand, settingsRemoveKeyCommand, settingsSetCommand, settingsGetCommand, settingsResetCommand, settingsPathCommand } from './commands/settings.js';
|
|
15
16
|
import { checkForUpdates } from './utils/version.js';
|
|
16
17
|
import chalk from 'chalk';
|
|
17
18
|
const CLI_VERSION = '2.0.0';
|
|
@@ -59,13 +60,24 @@ program
|
|
|
59
60
|
.option('--json', 'Output report in JSON format')
|
|
60
61
|
.option('-i, --interactive', 'Run in interactive mode with rich output')
|
|
61
62
|
.option('-c, --config <path>', 'Path to custom rigour.yml configuration')
|
|
63
|
+
.option('--deep', 'Enable deep LLM-powered analysis (local, 350MB one-time download)')
|
|
64
|
+
.option('--pro', 'Use larger model for deep analysis (900MB, higher quality)')
|
|
65
|
+
.option('-k, --api-key <key>', 'Use cloud API key instead of local model (BYOK)')
|
|
66
|
+
.option('--provider <name>', 'Cloud provider: claude, openai, gemini, groq, mistral, together, deepseek, ollama, or any OpenAI-compatible')
|
|
67
|
+
.option('--api-base-url <url>', 'Custom API base URL (for self-hosted or proxy endpoints)')
|
|
68
|
+
.option('--model-name <name>', 'Override cloud model name')
|
|
69
|
+
.option('--agents <count>', 'Number of parallel agents for deep scan (cloud-only, default: 1)', '1')
|
|
62
70
|
.addHelpText('after', `
|
|
63
71
|
Examples:
|
|
64
|
-
$ rigour check
|
|
65
|
-
$ rigour check
|
|
66
|
-
$ rigour check
|
|
67
|
-
$ rigour check --
|
|
68
|
-
$ rigour check --
|
|
72
|
+
$ rigour check # AST only. Instant. Free.
|
|
73
|
+
$ rigour check --deep # AST + local LLM (350MB one-time)
|
|
74
|
+
$ rigour check --deep --pro # AST + larger local LLM (900MB)
|
|
75
|
+
$ rigour check --deep -k sk-ant-xxx # AST + Claude API (BYOK)
|
|
76
|
+
$ rigour check --deep -k gsk_xxx --provider groq # Use Groq
|
|
77
|
+
$ rigour check --deep -k xxx --provider ollama # Use local Ollama
|
|
78
|
+
$ rigour check --deep -k xxx --provider custom --api-base-url http://my-server/v1 # Any endpoint
|
|
79
|
+
$ rigour check ./src --deep # Deep on specific directory
|
|
80
|
+
$ rigour check --ci # CI environment
|
|
69
81
|
`)
|
|
70
82
|
.action(async (files, options) => {
|
|
71
83
|
await checkCommand(process.cwd(), files, options);
|
|
@@ -184,6 +196,44 @@ Examples:
|
|
|
184
196
|
.action(async (options) => {
|
|
185
197
|
await hooksInitCommand(process.cwd(), options);
|
|
186
198
|
});
|
|
199
|
+
// Settings management (like Claude Code's settings.json)
|
|
200
|
+
const settingsCmd = program
|
|
201
|
+
.command('settings')
|
|
202
|
+
.description('Manage global settings (~/.rigour/settings.json) — API keys, providers, defaults');
|
|
203
|
+
settingsCmd
|
|
204
|
+
.command('show', { isDefault: true })
|
|
205
|
+
.description('Show current settings')
|
|
206
|
+
.action(async () => { await settingsShowCommand(); });
|
|
207
|
+
settingsCmd
|
|
208
|
+
.command('set-key')
|
|
209
|
+
.description('Add or update an API key for a provider')
|
|
210
|
+
.argument('<provider>', 'Provider name: anthropic, openai, groq, deepseek, mistral, together, gemini, ollama')
|
|
211
|
+
.argument('<key>', 'API key')
|
|
212
|
+
.action(async (provider, key) => { await settingsSetKeyCommand(provider, key); });
|
|
213
|
+
settingsCmd
|
|
214
|
+
.command('remove-key')
|
|
215
|
+
.description('Remove an API key for a provider')
|
|
216
|
+
.argument('<provider>', 'Provider name')
|
|
217
|
+
.action(async (provider) => { await settingsRemoveKeyCommand(provider); });
|
|
218
|
+
settingsCmd
|
|
219
|
+
.command('set')
|
|
220
|
+
.description('Set a configuration value (dot-notation)')
|
|
221
|
+
.argument('<key>', 'Setting key (e.g., deep.defaultProvider, cli.verboseOutput)')
|
|
222
|
+
.argument('<value>', 'Setting value')
|
|
223
|
+
.action(async (key, value) => { await settingsSetCommand(key, value); });
|
|
224
|
+
settingsCmd
|
|
225
|
+
.command('get')
|
|
226
|
+
.description('Get a configuration value')
|
|
227
|
+
.argument('<key>', 'Setting key')
|
|
228
|
+
.action(async (key) => { await settingsGetCommand(key); });
|
|
229
|
+
settingsCmd
|
|
230
|
+
.command('reset')
|
|
231
|
+
.description('Reset all settings to defaults')
|
|
232
|
+
.action(async () => { await settingsResetCommand(); });
|
|
233
|
+
settingsCmd
|
|
234
|
+
.command('path')
|
|
235
|
+
.description('Show settings file path')
|
|
236
|
+
.action(async () => { await settingsPathCommand(); });
|
|
187
237
|
// Check for updates before parsing (non-blocking)
|
|
188
238
|
(async () => {
|
|
189
239
|
try {
|
package/dist/commands/check.d.ts
CHANGED
|
@@ -3,5 +3,12 @@ export interface CheckOptions {
|
|
|
3
3
|
json?: boolean;
|
|
4
4
|
interactive?: boolean;
|
|
5
5
|
config?: string;
|
|
6
|
+
deep?: boolean;
|
|
7
|
+
pro?: boolean;
|
|
8
|
+
apiKey?: string;
|
|
9
|
+
provider?: string;
|
|
10
|
+
apiBaseUrl?: string;
|
|
11
|
+
modelName?: string;
|
|
12
|
+
agents?: string;
|
|
6
13
|
}
|
|
7
14
|
export declare function checkCommand(cwd: string, files?: string[], options?: CheckOptions): Promise<void>;
|
package/dist/commands/check.js
CHANGED
|
@@ -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, recordScore, getScoreTrend } from '@rigour-labs/core';
|
|
5
|
+
import { GateRunner, ConfigSchema, recordScore, getScoreTrend, resolveDeepOptions } from '@rigour-labs/core';
|
|
6
6
|
import inquirer from 'inquirer';
|
|
7
7
|
import { randomUUID } from 'crypto';
|
|
8
8
|
// Exit codes per spec
|
|
@@ -42,8 +42,15 @@ export async function checkCommand(cwd, files = [], options = {}) {
|
|
|
42
42
|
const configContent = await fs.readFile(configPath, 'utf-8');
|
|
43
43
|
const rawConfig = yaml.parse(configContent);
|
|
44
44
|
const config = ConfigSchema.parse(rawConfig);
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
const isDeep = !!options.deep || !!options.pro || !!options.apiKey;
|
|
46
|
+
const isSilent = !!options.ci || !!options.json;
|
|
47
|
+
if (!isSilent) {
|
|
48
|
+
if (isDeep) {
|
|
49
|
+
console.log(chalk.blue.bold('Running Rigour checks + deep analysis...\n'));
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.log(chalk.blue('Running Rigour checks...\n'));
|
|
53
|
+
}
|
|
47
54
|
}
|
|
48
55
|
const runner = new GateRunner(config);
|
|
49
56
|
const requestId = randomUUID();
|
|
@@ -51,14 +58,63 @@ export async function checkCommand(cwd, files = [], options = {}) {
|
|
|
51
58
|
type: "tool_call",
|
|
52
59
|
requestId,
|
|
53
60
|
tool: "rigour_check",
|
|
54
|
-
arguments: { files }
|
|
61
|
+
arguments: { files, deep: isDeep }
|
|
55
62
|
});
|
|
56
|
-
|
|
63
|
+
// Build deep options if enabled
|
|
64
|
+
// Merges CLI flags with ~/.rigour/settings.json (CLI flags win)
|
|
65
|
+
let deepOpts;
|
|
66
|
+
if (isDeep) {
|
|
67
|
+
const resolved = resolveDeepOptions({
|
|
68
|
+
apiKey: options.apiKey,
|
|
69
|
+
provider: options.provider,
|
|
70
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
71
|
+
modelName: options.modelName,
|
|
72
|
+
});
|
|
73
|
+
// If settings.json provided an API key but user didn't pass --deep explicitly,
|
|
74
|
+
// treat it as cloud mode
|
|
75
|
+
const hasApiKey = !!resolved.apiKey;
|
|
76
|
+
const agentCount = Math.max(1, parseInt(options.agents || '1', 10) || 1);
|
|
77
|
+
deepOpts = {
|
|
78
|
+
enabled: true,
|
|
79
|
+
pro: !!options.pro,
|
|
80
|
+
apiKey: resolved.apiKey,
|
|
81
|
+
provider: hasApiKey ? (resolved.provider || 'claude') : 'local',
|
|
82
|
+
apiBaseUrl: resolved.apiBaseUrl,
|
|
83
|
+
modelName: resolved.modelName,
|
|
84
|
+
agents: agentCount > 1 ? agentCount : undefined,
|
|
85
|
+
onProgress: isSilent ? undefined : (msg) => {
|
|
86
|
+
process.stderr.write(msg + '\n');
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const report = await runner.run(cwd, files.length > 0 ? files : undefined, deepOpts);
|
|
57
91
|
// Write machine report
|
|
58
92
|
const reportPath = path.join(cwd, config.output.report_path);
|
|
59
93
|
await fs.writeJson(reportPath, report, { spaces: 2 });
|
|
60
94
|
// Record score for trend tracking
|
|
61
95
|
recordScore(cwd, report);
|
|
96
|
+
// Persist to SQLite if deep analysis was used
|
|
97
|
+
if (isDeep) {
|
|
98
|
+
try {
|
|
99
|
+
const { openDatabase, insertScan, insertFindings } = await import('@rigour-labs/core');
|
|
100
|
+
const db = openDatabase();
|
|
101
|
+
if (db) {
|
|
102
|
+
const repoName = path.basename(cwd);
|
|
103
|
+
const scanId = insertScan(db, repoName, report, {
|
|
104
|
+
deepTier: options.pro ? 'pro' : (options.apiKey ? 'cloud' : 'deep'),
|
|
105
|
+
deepModel: report.stats.deep?.model,
|
|
106
|
+
});
|
|
107
|
+
insertFindings(db, scanId, report.failures);
|
|
108
|
+
db.close();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (dbError) {
|
|
112
|
+
// SQLite persistence is best-effort — log but don't fail
|
|
113
|
+
if (process.env.RIGOUR_DEBUG) {
|
|
114
|
+
console.error(`[rigour] SQLite persistence failed: ${dbError.message}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
62
118
|
await logStudioEvent(cwd, {
|
|
63
119
|
type: "tool_response",
|
|
64
120
|
requestId,
|
|
@@ -103,62 +159,14 @@ export async function checkCommand(cwd, files = [], options = {}) {
|
|
|
103
159
|
await interactiveMode(report, config);
|
|
104
160
|
process.exit(EXIT_FAIL);
|
|
105
161
|
}
|
|
106
|
-
//
|
|
107
|
-
if (
|
|
108
|
-
|
|
162
|
+
// ─── HUMAN-READABLE OUTPUT (with deep analysis dopamine engineering) ───
|
|
163
|
+
if (isDeep) {
|
|
164
|
+
// Deep analysis output format (from product bible)
|
|
165
|
+
renderDeepOutput(report, config, options);
|
|
109
166
|
}
|
|
110
167
|
else {
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
};
|
|
147
|
-
for (const failure of report.failures) {
|
|
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}`));
|
|
152
|
-
if (failure.files && failure.files.length > 0) {
|
|
153
|
-
console.log(chalk.dim(' Files:'));
|
|
154
|
-
failure.files.forEach((f) => console.log(chalk.dim(` - ${f}`)));
|
|
155
|
-
}
|
|
156
|
-
if (failure.hint) {
|
|
157
|
-
console.log(chalk.cyan(` Hint: ${failure.hint}`));
|
|
158
|
-
}
|
|
159
|
-
console.log('');
|
|
160
|
-
}
|
|
161
|
-
console.log(chalk.yellow(`See ${config.output.report_path} for full details.`));
|
|
168
|
+
// Standard AST-only output
|
|
169
|
+
renderStandardOutput(report, config);
|
|
162
170
|
}
|
|
163
171
|
// Score trend display
|
|
164
172
|
const trend = getScoreTrend(cwd);
|
|
@@ -171,8 +179,8 @@ export async function checkCommand(cwd, files = [], options = {}) {
|
|
|
171
179
|
console.log(trendColor(`\nScore Trend: ${scoresStr} (${trend.direction} ${arrow})`));
|
|
172
180
|
}
|
|
173
181
|
// Stats footer
|
|
174
|
-
const footerParts = [`Finished in ${report.stats.duration_ms}
|
|
175
|
-
if (report.
|
|
182
|
+
const footerParts = [`Finished in ${(report.stats.duration_ms / 1000).toFixed(1)}s`];
|
|
183
|
+
if (report.stats.score !== undefined) {
|
|
176
184
|
footerParts.push(`Score: ${report.stats.score}/100`);
|
|
177
185
|
}
|
|
178
186
|
console.log(chalk.dim('\n' + footerParts.join(' | ')));
|
|
@@ -200,6 +208,217 @@ export async function checkCommand(cwd, files = [], options = {}) {
|
|
|
200
208
|
process.exit(EXIT_INTERNAL_ERROR);
|
|
201
209
|
}
|
|
202
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Render deep analysis output — enhanced with detailed findings grouped by provenance.
|
|
213
|
+
*
|
|
214
|
+
* Shows:
|
|
215
|
+
* - Score box at top (AI Health, Code Quality, Overall)
|
|
216
|
+
* - Detailed findings table grouped by provenance:
|
|
217
|
+
* - Deep analysis findings (most detailed): severity, category, file:line, description, suggestion
|
|
218
|
+
* - AI drift findings: severity, title, files
|
|
219
|
+
* - Security findings: severity, title, hint
|
|
220
|
+
* - Traditional findings: severity, title
|
|
221
|
+
* - Privacy badge and model info
|
|
222
|
+
* - Summary count at end
|
|
223
|
+
*/
|
|
224
|
+
function renderDeepOutput(report, config, options) {
|
|
225
|
+
const stats = report.stats;
|
|
226
|
+
const isLocal = !options.apiKey;
|
|
227
|
+
console.log('');
|
|
228
|
+
if (report.status === 'PASS') {
|
|
229
|
+
console.log(chalk.green.bold(' ✨ All quality gates passed.\n'));
|
|
230
|
+
}
|
|
231
|
+
// Score breakdown — the screenshottable moment
|
|
232
|
+
const aiHealth = stats.ai_health_score ?? 100;
|
|
233
|
+
const codeQuality = stats.code_quality_score ?? stats.structural_score ?? 100;
|
|
234
|
+
const overall = stats.score ?? 100;
|
|
235
|
+
const scoreColor = (score) => score >= 80 ? chalk.green : score >= 60 ? chalk.yellow : chalk.red;
|
|
236
|
+
console.log(` ${chalk.bold('AI Health:')} ${scoreColor(aiHealth).bold(aiHealth + '/100')}`);
|
|
237
|
+
console.log(` ${chalk.bold('Code Quality:')} ${scoreColor(codeQuality).bold(codeQuality + '/100')}`);
|
|
238
|
+
console.log(` ${chalk.bold('Overall:')} ${scoreColor(overall).bold(overall + '/100')}`);
|
|
239
|
+
console.log('');
|
|
240
|
+
// Privacy badge — this IS the marketing
|
|
241
|
+
if (isLocal) {
|
|
242
|
+
console.log(chalk.green(' 🔒 100% local. Your code never left this machine.'));
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
console.log(chalk.yellow(` ☁️ Code was sent to ${options.provider || 'cloud'} API.`));
|
|
246
|
+
}
|
|
247
|
+
// Deep stats
|
|
248
|
+
if (stats.deep) {
|
|
249
|
+
const tier = stats.deep.tier === 'cloud' ? options.provider || 'cloud' : stats.deep.tier;
|
|
250
|
+
const model = stats.deep.model || 'unknown';
|
|
251
|
+
const inferenceSec = stats.deep.total_ms ? (stats.deep.total_ms / 1000).toFixed(1) + 's' : '';
|
|
252
|
+
console.log(chalk.dim(` Model: ${model} (${tier}) ${inferenceSec}`));
|
|
253
|
+
}
|
|
254
|
+
console.log('');
|
|
255
|
+
// Categorize findings by provenance
|
|
256
|
+
const deepFailures = report.failures.filter((f) => f.provenance === 'deep-analysis');
|
|
257
|
+
const aiDriftFailures = report.failures.filter((f) => f.provenance === 'ai-drift');
|
|
258
|
+
const securityFailures = report.failures.filter((f) => f.provenance === 'security');
|
|
259
|
+
const traditionalFailures = report.failures.filter((f) => f.provenance !== 'deep-analysis' && f.provenance !== 'ai-drift' && f.provenance !== 'security');
|
|
260
|
+
// DEEP ANALYSIS FINDINGS — most detailed
|
|
261
|
+
if (deepFailures.length > 0) {
|
|
262
|
+
console.log(chalk.bold(` ── Deep Analysis Findings (${deepFailures.length} verified) ──\n`));
|
|
263
|
+
for (const failure of deepFailures) {
|
|
264
|
+
const sev = severityIcon(failure.severity);
|
|
265
|
+
const cat = failure.category || failure.id;
|
|
266
|
+
const catLabel = formatCategory(cat);
|
|
267
|
+
// Extract file and line from files array if available
|
|
268
|
+
let fileLocation = '';
|
|
269
|
+
if (failure.files && failure.files.length > 0) {
|
|
270
|
+
fileLocation = failure.files[0];
|
|
271
|
+
}
|
|
272
|
+
// Description: up to 120 chars
|
|
273
|
+
const description = failure.details ? failure.details.substring(0, 120) : failure.title.substring(0, 120);
|
|
274
|
+
const descDisplay = description.length > 120 ? description.substring(0, 117) + '...' : description;
|
|
275
|
+
// Suggestion from hint
|
|
276
|
+
const suggestion = failure.hint || failure.suggestion || '';
|
|
277
|
+
console.log(` ${sev} [${catLabel}] ${fileLocation}`);
|
|
278
|
+
console.log(` ${descDisplay}`);
|
|
279
|
+
if (suggestion) {
|
|
280
|
+
console.log(` → ${suggestion}`);
|
|
281
|
+
}
|
|
282
|
+
// Show confidence and verified status if available
|
|
283
|
+
if (failure.confidence !== undefined || failure.verified !== undefined) {
|
|
284
|
+
const confStr = failure.confidence !== undefined ? ` (${(failure.confidence * 100).toFixed(0)}% conf)` : '';
|
|
285
|
+
const verStr = failure.verified !== undefined ? ` [${failure.verified ? 'verified' : 'unverified'}]` : '';
|
|
286
|
+
console.log(chalk.dim(` ${confStr}${verStr}`));
|
|
287
|
+
}
|
|
288
|
+
console.log('');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// AI DRIFT FINDINGS
|
|
292
|
+
if (aiDriftFailures.length > 0) {
|
|
293
|
+
console.log(chalk.bold(` ── AI Drift Findings (${aiDriftFailures.length}) ──\n`));
|
|
294
|
+
for (const failure of aiDriftFailures) {
|
|
295
|
+
const sev = severityIcon(failure.severity);
|
|
296
|
+
console.log(` ${sev} ${failure.title}`);
|
|
297
|
+
if (failure.files && failure.files.length > 0) {
|
|
298
|
+
console.log(` Files: ${failure.files.slice(0, 3).join(', ')}${failure.files.length > 3 ? ` +${failure.files.length - 3}` : ''}`);
|
|
299
|
+
}
|
|
300
|
+
console.log('');
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// SECURITY FINDINGS
|
|
304
|
+
if (securityFailures.length > 0) {
|
|
305
|
+
console.log(chalk.bold(` ── Security Findings (${securityFailures.length}) ──\n`));
|
|
306
|
+
for (const failure of securityFailures) {
|
|
307
|
+
const sev = severityIcon(failure.severity);
|
|
308
|
+
console.log(` ${sev} ${failure.title}`);
|
|
309
|
+
if (failure.hint) {
|
|
310
|
+
console.log(chalk.cyan(` Hint: ${failure.hint}`));
|
|
311
|
+
}
|
|
312
|
+
console.log('');
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// TRADITIONAL FINDINGS
|
|
316
|
+
if (traditionalFailures.length > 0) {
|
|
317
|
+
console.log(chalk.bold(` ── Traditional Quality Findings (${traditionalFailures.length}) ──\n`));
|
|
318
|
+
for (const failure of traditionalFailures) {
|
|
319
|
+
const sev = severityIcon(failure.severity);
|
|
320
|
+
const prov = failure.provenance ? chalk.dim(`[${failure.provenance}]`) : '';
|
|
321
|
+
console.log(` ${sev} ${prov} ${failure.title}`);
|
|
322
|
+
console.log('');
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// Summary count at the end
|
|
326
|
+
if (deepFailures.length > 0 || aiDriftFailures.length > 0 || securityFailures.length > 0 || traditionalFailures.length > 0) {
|
|
327
|
+
const summary = [
|
|
328
|
+
deepFailures.length > 0 ? `${deepFailures.length} deep` : null,
|
|
329
|
+
aiDriftFailures.length > 0 ? `${aiDriftFailures.length} ai-drift` : null,
|
|
330
|
+
securityFailures.length > 0 ? `${securityFailures.length} security` : null,
|
|
331
|
+
traditionalFailures.length > 0 ? `${traditionalFailures.length} traditional` : null
|
|
332
|
+
].filter(Boolean).join(' | ');
|
|
333
|
+
console.log(chalk.dim(` ${summary}`));
|
|
334
|
+
console.log('');
|
|
335
|
+
}
|
|
336
|
+
console.log(chalk.yellow(` See ${config.output.report_path} for full details.`));
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Render standard AST-only output (existing behavior).
|
|
340
|
+
*/
|
|
341
|
+
function renderStandardOutput(report, config) {
|
|
342
|
+
if (report.status === 'PASS') {
|
|
343
|
+
console.log(chalk.green.bold('✔ PASS - All quality gates satisfied.'));
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
console.log(chalk.red.bold('✘ FAIL - Quality gate violations found.\n'));
|
|
347
|
+
// Score summary line
|
|
348
|
+
const stats = report.stats;
|
|
349
|
+
const scoreParts = [];
|
|
350
|
+
if (stats.score !== undefined)
|
|
351
|
+
scoreParts.push(`Score: ${stats.score}/100`);
|
|
352
|
+
if (stats.ai_health_score !== undefined)
|
|
353
|
+
scoreParts.push(`AI Health: ${stats.ai_health_score}/100`);
|
|
354
|
+
if (stats.structural_score !== undefined)
|
|
355
|
+
scoreParts.push(`Structural: ${stats.structural_score}/100`);
|
|
356
|
+
if (scoreParts.length > 0) {
|
|
357
|
+
console.log(chalk.bold(scoreParts.join(' | ')) + '\n');
|
|
358
|
+
}
|
|
359
|
+
// Severity breakdown
|
|
360
|
+
if (stats.severity_breakdown) {
|
|
361
|
+
const parts = Object.entries(stats.severity_breakdown)
|
|
362
|
+
.filter(([, count]) => count > 0)
|
|
363
|
+
.map(([sev, count]) => {
|
|
364
|
+
const color = sev === 'critical' ? chalk.red.bold : sev === 'high' ? chalk.red : sev === 'medium' ? chalk.yellow : chalk.dim;
|
|
365
|
+
return color(`${sev}: ${count}`);
|
|
366
|
+
});
|
|
367
|
+
if (parts.length > 0) {
|
|
368
|
+
console.log('Severity: ' + parts.join(', ') + '\n');
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
for (const failure of report.failures) {
|
|
372
|
+
const sev = severityIcon(failure.severity);
|
|
373
|
+
const prov = failure.provenance ? chalk.dim(`[${failure.provenance}]`) : '';
|
|
374
|
+
console.log(`${sev} ${prov} ${chalk.red(`[${failure.id}]`)} ${failure.title}`);
|
|
375
|
+
console.log(chalk.dim(` Details: ${failure.details}`));
|
|
376
|
+
if (failure.files && failure.files.length > 0) {
|
|
377
|
+
console.log(chalk.dim(' Files:'));
|
|
378
|
+
failure.files.forEach((f) => console.log(chalk.dim(` - ${f}`)));
|
|
379
|
+
}
|
|
380
|
+
if (failure.hint) {
|
|
381
|
+
console.log(chalk.cyan(` Hint: ${failure.hint}`));
|
|
382
|
+
}
|
|
383
|
+
console.log('');
|
|
384
|
+
}
|
|
385
|
+
console.log(chalk.yellow(`See ${config.output.report_path} for full details.`));
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
function severityIcon(s) {
|
|
389
|
+
switch (s) {
|
|
390
|
+
case 'critical': return chalk.red.bold('CRIT');
|
|
391
|
+
case 'high': return chalk.red('HIGH');
|
|
392
|
+
case 'medium': return chalk.yellow('MED ');
|
|
393
|
+
case 'low': return chalk.dim('LOW ');
|
|
394
|
+
case 'info': return chalk.dim('INFO');
|
|
395
|
+
default: return chalk.yellow('MED ');
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function formatCategory(cat) {
|
|
399
|
+
const labels = {
|
|
400
|
+
srp_violation: 'SOLID: Single Responsibility',
|
|
401
|
+
ocp_violation: 'SOLID: Open/Closed',
|
|
402
|
+
lsp_violation: 'SOLID: Liskov Substitution',
|
|
403
|
+
isp_violation: 'SOLID: Interface Segregation',
|
|
404
|
+
dip_violation: 'SOLID: Dependency Inversion',
|
|
405
|
+
dry_violation: 'DRY',
|
|
406
|
+
god_class: 'Pattern: God class',
|
|
407
|
+
god_function: 'Pattern: God function',
|
|
408
|
+
feature_envy: 'Pattern: Feature envy',
|
|
409
|
+
shotgun_surgery: 'Pattern: Shotgun surgery',
|
|
410
|
+
long_params: 'Params',
|
|
411
|
+
data_clump: 'Data clump',
|
|
412
|
+
inappropriate_intimacy: 'Coupling',
|
|
413
|
+
error_inconsistency: 'Error handling',
|
|
414
|
+
empty_catch: 'Empty catch',
|
|
415
|
+
test_quality: 'Test quality',
|
|
416
|
+
code_smell: 'Code smell',
|
|
417
|
+
architecture: 'Architecture',
|
|
418
|
+
language_idiom: 'Idiom',
|
|
419
|
+
};
|
|
420
|
+
return labels[cat] || cat;
|
|
421
|
+
}
|
|
203
422
|
async function interactiveMode(report, config) {
|
|
204
423
|
console.clear();
|
|
205
424
|
console.log(chalk.bold.blue('══ Rigour Interactive Review ══\n'));
|
|
@@ -237,6 +456,13 @@ async function interactiveMode(report, config) {
|
|
|
237
456
|
if (failure.hint) {
|
|
238
457
|
console.log(`\n${chalk.bold.cyan('Hint:')} ${failure.hint}`);
|
|
239
458
|
}
|
|
459
|
+
// Show deep analysis metadata if present
|
|
460
|
+
if (failure.confidence !== undefined) {
|
|
461
|
+
console.log(`\n${chalk.bold('Confidence:')} ${(failure.confidence * 100).toFixed(0)}%`);
|
|
462
|
+
}
|
|
463
|
+
if (failure.verified !== undefined) {
|
|
464
|
+
console.log(`${chalk.bold('Verified:')} ${failure.verified ? chalk.green('Yes') : chalk.red('No')}`);
|
|
465
|
+
}
|
|
240
466
|
console.log(chalk.dim('\n' + '─'.repeat(40)));
|
|
241
467
|
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to return to list...' }]);
|
|
242
468
|
console.clear();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DemoOptions } from './demo-helpers.js';
|
|
2
|
+
export declare function simulateCodeWrite(filename: string, lines: string[], options: DemoOptions): Promise<void>;
|
|
3
|
+
export declare function simulateHookCatch(gate: string, file: string, message: string, severity: string, options: DemoOptions): Promise<void>;
|
|
4
|
+
export declare function renderScoreBar(score: number, label: string, width?: number): string;
|
|
5
|
+
export declare function renderTrendChart(scores: number[]): string;
|
|
6
|
+
export declare function printBanner(cinematic: boolean): void;
|
|
7
|
+
export declare function printPlantedIssues(): void;
|
|
8
|
+
export declare function displayGateResults(report: any, cinematic: boolean): void;
|
|
9
|
+
export declare function printSeverityBreakdown(stats: any): void;
|
|
10
|
+
export declare function printFailure(failure: any): void;
|
|
11
|
+
export declare function printClosing(cinematic: boolean): void;
|