@rigour-labs/core 5.2.2 → 5.2.4

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.
@@ -359,6 +359,8 @@ export class GateRunner {
359
359
  aiDriftFailures: provenanceCounts['ai-drift'],
360
360
  structuralFailures: provenanceCounts['traditional'],
361
361
  securityFailures: provenanceCounts['security'],
362
+ governanceFailures: provenanceCounts['governance'],
363
+ deepAnalysisFailures: provenanceCounts['deep-analysis'],
362
364
  };
363
365
  recordGateRun(cwd, passedCount, failedCount, failures.length, provenanceData);
364
366
  }
package/dist/index.d.ts CHANGED
@@ -30,3 +30,5 @@ export { getProvenanceTrends, getQualityTrend } from './services/adaptive-thresh
30
30
  export type { ProvenanceTrends, ProvenanceRunData } from './services/adaptive-thresholds.js';
31
31
  export { IncrementalCache } from './services/incremental-cache.js';
32
32
  export type { IncrementalResult } from './services/incremental-cache.js';
33
+ export { renderScoreGauge, renderSeveritySection, renderGateGrid, renderBrainStatus, renderFullReport, renderMcpHeadline, renderFixAttribution } from './services/terminal-renderer.js';
34
+ export type { RenderOptions, GateResult } from './services/terminal-renderer.js';
package/dist/index.js CHANGED
@@ -30,6 +30,8 @@ export { generateTemporalDriftReport, formatDriftSummary } from './services/temp
30
30
  export { getProvenanceTrends, getQualityTrend } from './services/adaptive-thresholds.js';
31
31
  // Incremental Cache (cross-run file change detection)
32
32
  export { IncrementalCache } from './services/incremental-cache.js';
33
+ // Terminal Renderer (rich CLI/MCP output)
34
+ export { renderScoreGauge, renderSeveritySection, renderGateGrid, renderBrainStatus, renderFullReport, renderMcpHeadline, renderFixAttribution } from './services/terminal-renderer.js';
33
35
  // Pattern Index is intentionally NOT exported here to prevent
34
36
  // native dependency issues (sharp/transformers) from leaking into
35
37
  // non-AI parts of the system.
@@ -44,6 +44,8 @@ export interface ProvenanceRunData {
44
44
  aiDriftFailures: number;
45
45
  structuralFailures: number;
46
46
  securityFailures: number;
47
+ governanceFailures?: number;
48
+ deepAnalysisFailures?: number;
47
49
  }
48
50
  export interface ProvenanceTrends {
49
51
  aiDrift: QualityTrend;
@@ -164,9 +164,9 @@ export function getProvenanceTrends(cwd) {
164
164
  const recent = withProvenance.slice(-RECENT_WINDOW);
165
165
  const baseline = withProvenance.slice(0, -RECENT_WINDOW);
166
166
  const computeForField = (field) => {
167
- const baselineValues = baseline.map(r => r.provenance[field]);
167
+ const baselineValues = baseline.map(r => r.provenance[field] ?? 0);
168
168
  const { mean, std } = meanAndStd(baselineValues);
169
- const recentAvg = recent.reduce((sum, r) => sum + r.provenance[field], 0) / recent.length;
169
+ const recentAvg = recent.reduce((sum, r) => sum + (r.provenance[field] ?? 0), 0) / recent.length;
170
170
  const z = zScore(recentAvg, mean, std);
171
171
  return { trend: trendFromZScore(z), z: Math.round(z * 100) / 100 };
172
172
  };
@@ -0,0 +1,26 @@
1
+ import type { Report, Failure, Severity, Status } from '../types/index.js';
2
+ export interface RenderOptions {
3
+ /** Show Brain learning status */
4
+ showBrain?: boolean;
5
+ /** Number of learned patterns (from SQLite) */
6
+ brainPatterns?: number;
7
+ /** Trend direction: improving | degrading | stable */
8
+ brainTrend?: 'improving' | 'degrading' | 'stable';
9
+ /** Recent score history for sparkline */
10
+ recentScores?: number[];
11
+ /** Whether deep analysis was used */
12
+ isDeep?: boolean;
13
+ /** Compact mode — fewer lines */
14
+ compact?: boolean;
15
+ }
16
+ export interface GateResult {
17
+ id: string;
18
+ status: Status;
19
+ }
20
+ export declare function renderScoreGauge(score: number, label: string): string;
21
+ export declare function renderSeveritySection(failures: Failure[], severity: Severity, maxItems?: number): string;
22
+ export declare function renderGateGrid(gates: GateResult[]): string;
23
+ export declare function renderBrainStatus(patterns: number, trend: string): string;
24
+ export declare function renderFullReport(report: Report, options?: RenderOptions): string;
25
+ export declare function renderMcpHeadline(report: Report): string;
26
+ export declare function renderFixAttribution(failureCount: number, worstViolation: string): string;
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Rich terminal renderer for Rigour scan/check output.
3
+ * Designed to work in editor integrated terminals (VS Code, Cursor, etc).
4
+ * All output fits within 80-char width.
5
+ */
6
+ import chalk from 'chalk';
7
+ // ── Constants ──────────────────────────────────────────────────────
8
+ const BAR_WIDTH = 20;
9
+ const BOX_WIDTH = 58;
10
+ const SEVERITY_COLORS = {
11
+ critical: chalk.red.bold,
12
+ high: chalk.yellow.bold,
13
+ medium: chalk.white,
14
+ low: chalk.dim,
15
+ info: chalk.dim,
16
+ };
17
+ const SEVERITY_ICONS = {
18
+ critical: '\u{1F534}',
19
+ high: '\u{1F7E0}',
20
+ medium: '\u{1F7E1}',
21
+ low: '\u{1F535}',
22
+ info: '\u{26AA}',
23
+ };
24
+ // ── Score Gauge ────────────────────────────────────────────────────
25
+ export function renderScoreGauge(score, label) {
26
+ const filled = Math.round((score / 100) * BAR_WIDTH);
27
+ const empty = BAR_WIDTH - filled;
28
+ const bar = '\u{2588}'.repeat(filled) + '\u{2591}'.repeat(empty);
29
+ const color = score >= 80 ? chalk.green : score >= 50 ? chalk.yellow : chalk.red;
30
+ return ` ${label.padEnd(14)} ${color(bar)} ${color.bold(String(score) + '/100')}`;
31
+ }
32
+ // ── Severity Section ───────────────────────────────────────────────
33
+ export function renderSeveritySection(failures, severity, maxItems = 3) {
34
+ const items = failures.filter(f => (f.severity ?? 'medium') === severity);
35
+ if (items.length === 0)
36
+ return '';
37
+ const icon = SEVERITY_ICONS[severity] || '';
38
+ const colorFn = SEVERITY_COLORS[severity] || chalk.white;
39
+ const lines = [];
40
+ lines.push(colorFn(` ${icon} ${severity.toUpperCase()} (${items.length})`));
41
+ const shown = items.slice(0, maxItems);
42
+ for (const item of shown) {
43
+ const loc = item.files?.[0]
44
+ ? item.line ? `${item.files[0]}:${item.line}` : item.files[0]
45
+ : '';
46
+ const locStr = loc ? chalk.dim(loc.padEnd(28)) : ''.padEnd(28);
47
+ lines.push(` ${locStr} ${item.title}`);
48
+ }
49
+ if (items.length > maxItems) {
50
+ lines.push(chalk.dim(` ... +${items.length - maxItems} more`));
51
+ }
52
+ return lines.join('\n');
53
+ }
54
+ // ── Gate Grid ──────────────────────────────────────────────────────
55
+ export function renderGateGrid(gates) {
56
+ if (gates.length === 0)
57
+ return '';
58
+ const lines = [' Gates:'];
59
+ const entries = gates.map(g => {
60
+ const icon = g.status === 'PASS' ? chalk.green('\u{2705}') : g.status === 'FAIL' ? chalk.red('\u{274C}') : chalk.dim('\u{23ED}');
61
+ return `${icon} ${g.id}`;
62
+ });
63
+ // Layout in rows of 3
64
+ for (let i = 0; i < entries.length; i += 3) {
65
+ const row = entries.slice(i, i + 3).map(e => e.padEnd(22)).join('');
66
+ lines.push(` ${row}`);
67
+ }
68
+ return lines.join('\n');
69
+ }
70
+ // ── Brain Status ───────────────────────────────────────────────────
71
+ export function renderBrainStatus(patterns, trend) {
72
+ const arrow = trend === 'improving' ? '\u{2191}' : trend === 'degrading' ? '\u{2193}' : '\u{2192}';
73
+ const color = trend === 'improving' ? chalk.green : trend === 'degrading' ? chalk.red : chalk.dim;
74
+ return chalk.dim(' Brain: ') + chalk.white(`learned ${patterns} patterns`) + ' \u{00B7} ' + color(`trend: ${trend} ${arrow}`);
75
+ }
76
+ // ── Headline (scariest finding) ────────────────────────────────────
77
+ function getScaryHeadline(failures) {
78
+ // Priority order: secrets > hallucinated imports > phantom APIs > silent failures > highest severity
79
+ const secrets = failures.filter(f => f.id === 'security-patterns' && f.severity === 'critical');
80
+ if (secrets.length > 0) {
81
+ const loc = secrets[0].files?.[0] || '';
82
+ return chalk.red.bold(` HARDCODED SECRET DETECTED`) + '\n' +
83
+ chalk.red(` ${secrets[0].title}${loc ? ' in ' + loc : ''}`) + '\n' +
84
+ (secrets.length > 1 ? chalk.dim(` + ${secrets.length - 1} more credential(s) exposed\n`) : '');
85
+ }
86
+ const hallucinated = failures.filter(f => f.id === 'hallucinated-imports');
87
+ if (hallucinated.length > 0) {
88
+ return chalk.red.bold(` HALLUCINATED PACKAGES DETECTED`) + '\n' +
89
+ chalk.red(` ${hallucinated.length} import(s) that don't exist — will crash at runtime\n`);
90
+ }
91
+ const phantom = failures.filter(f => f.id === 'phantom-apis');
92
+ if (phantom.length > 0) {
93
+ return chalk.red.bold(` PHANTOM API CALLS DETECTED`) + '\n' +
94
+ chalk.red(` ${phantom.length} call(s) to methods that don't exist\n`);
95
+ }
96
+ // Fall back to first critical finding
97
+ const critical = failures.filter(f => f.severity === 'critical');
98
+ if (critical.length > 0) {
99
+ const f = critical[0];
100
+ const loc = f.files?.[0] ? ` in ${f.files[0]}${f.line ? ':' + f.line : ''}` : '';
101
+ return chalk.red.bold(` ${f.title.toUpperCase()}`) + '\n' +
102
+ chalk.red(` ${f.details.slice(0, 70)}${loc}\n`);
103
+ }
104
+ return null;
105
+ }
106
+ // ── Box Drawing ────────────────────────────────────────────────────
107
+ function box(content) {
108
+ const lines = content.split('\n');
109
+ const top = '\u{250C}' + '\u{2500}'.repeat(BOX_WIDTH) + '\u{2510}';
110
+ const bottom = '\u{2514}' + '\u{2500}'.repeat(BOX_WIDTH) + '\u{2518}';
111
+ const boxed = lines.map(line => {
112
+ // Strip ANSI for length calculation
113
+ const stripped = line.replace(/\u001b\[[0-9;]*m/g, '');
114
+ const pad = Math.max(0, BOX_WIDTH - stripped.length);
115
+ return '\u{2502}' + line + ' '.repeat(pad) + '\u{2502}';
116
+ });
117
+ return [top, ...boxed, bottom].join('\n');
118
+ }
119
+ // ── Full Report ────────────────────────────────────────────────────
120
+ export function renderFullReport(report, options = {}) {
121
+ const lines = [];
122
+ const totalFindings = report.failures.length;
123
+ const score = report.stats.score ?? 0;
124
+ const aiHealth = report.stats.ai_health_score ?? 0;
125
+ // Headline banner
126
+ if (report.status === 'FAIL') {
127
+ lines.push('');
128
+ const headline = getScaryHeadline(report.failures);
129
+ if (headline) {
130
+ lines.push(headline);
131
+ }
132
+ lines.push(chalk.red.bold(` \u{26A0}\u{FE0F} RIGOUR CAUGHT ${totalFindings} ISSUE${totalFindings !== 1 ? 'S' : ''}`));
133
+ }
134
+ else {
135
+ lines.push('');
136
+ lines.push(chalk.green.bold(` \u{2705} RIGOUR: ALL GATES PASSED`));
137
+ }
138
+ lines.push('');
139
+ // Score gauges inside box
140
+ const boxContent = [];
141
+ boxContent.push(renderScoreGauge(score, 'Score'));
142
+ boxContent.push(renderScoreGauge(aiHealth, 'AI Health'));
143
+ if (report.stats.structural_score != null) {
144
+ boxContent.push(renderScoreGauge(report.stats.structural_score, 'Structural'));
145
+ }
146
+ boxContent.push('');
147
+ // Severity sections
148
+ const severities = ['critical', 'high', 'medium', 'low'];
149
+ for (const sev of severities) {
150
+ const section = renderSeveritySection(report.failures, sev, options.compact ? 2 : 3);
151
+ if (section) {
152
+ boxContent.push(section);
153
+ boxContent.push('');
154
+ }
155
+ }
156
+ // Gate grid from report summary
157
+ if (report.summary) {
158
+ const gates = Object.entries(report.summary).map(([id, status]) => ({ id, status }));
159
+ if (gates.length > 0) {
160
+ boxContent.push(renderGateGrid(gates));
161
+ boxContent.push('');
162
+ }
163
+ }
164
+ // Brain status
165
+ if (options.showBrain && options.brainPatterns != null) {
166
+ boxContent.push(renderBrainStatus(options.brainPatterns, options.brainTrend || 'stable'));
167
+ }
168
+ // Trend sparkline
169
+ if (options.recentScores && options.recentScores.length >= 3) {
170
+ const dir = options.recentScores[options.recentScores.length - 1] > options.recentScores[0]
171
+ ? 'improving' : options.recentScores[options.recentScores.length - 1] < options.recentScores[0]
172
+ ? 'degrading' : 'stable';
173
+ const arrow = dir === 'improving' ? '\u{2191}' : dir === 'degrading' ? '\u{2193}' : '\u{2192}';
174
+ const color = dir === 'improving' ? chalk.green : dir === 'degrading' ? chalk.red : chalk.dim;
175
+ boxContent.push(color(` Trend: ${options.recentScores.join(' \u{2192} ')} ${arrow}`));
176
+ }
177
+ lines.push(box(boxContent.join('\n')));
178
+ lines.push('');
179
+ // Duration
180
+ lines.push(chalk.dim(` Finished in ${report.stats.duration_ms}ms`));
181
+ // Next steps
182
+ if (report.status === 'FAIL') {
183
+ lines.push('');
184
+ lines.push(chalk.bold(' Next:'));
185
+ lines.push(` ${chalk.cyan('rigour explain')} get fix instructions`);
186
+ lines.push(` ${chalk.cyan('MCP: rigour_check')} agent self-heals via Fix Packet`);
187
+ }
188
+ return lines.join('\n');
189
+ }
190
+ // ── MCP Headline (for agent tool responses) ────────────────────────
191
+ export function renderMcpHeadline(report) {
192
+ if (report.status === 'PASS') {
193
+ const score = report.stats.score ?? 100;
194
+ return `\u{2705} RIGOUR: ALL GATES PASSED (Score: ${score}/100)`;
195
+ }
196
+ const sev = report.stats.severity_breakdown || {};
197
+ const critical = sev['critical'] || 0;
198
+ const high = sev['high'] || 0;
199
+ const total = report.failures.length;
200
+ const parts = [];
201
+ // Lead with scariest findings
202
+ const secrets = report.failures.filter(f => f.id === 'security-patterns' && f.severity === 'critical');
203
+ const hallucinated = report.failures.filter(f => f.id === 'hallucinated-imports');
204
+ if (secrets.length > 0) {
205
+ const loc = secrets[0].files?.[0] || '';
206
+ parts.push(`Hardcoded secret${secrets.length > 1 ? 's' : ''} in ${loc || 'source'}`);
207
+ }
208
+ if (hallucinated.length > 0) {
209
+ parts.push(`${hallucinated.length} hallucinated import(s)`);
210
+ }
211
+ // Add top critical/high by title if we haven't filled 3 yet
212
+ const topFindings = report.failures
213
+ .filter(f => (f.severity === 'critical' || f.severity === 'high') && f.id !== 'security-patterns' && f.id !== 'hallucinated-imports')
214
+ .slice(0, 3 - parts.length);
215
+ for (const f of topFindings) {
216
+ parts.push(f.title);
217
+ }
218
+ const headline = critical > 0
219
+ ? `\u{26A8} RIGOUR BLOCKED ${critical} CRITICAL ISSUE${critical !== 1 ? 'S' : ''}`
220
+ : high > 0
221
+ ? `\u{26A0} RIGOUR FOUND ${high} HIGH-SEVERITY ISSUE${high !== 1 ? 'S' : ''}`
222
+ : `\u{26A0} RIGOUR FOUND ${total} ISSUE${total !== 1 ? 'S' : ''}`;
223
+ const bulletList = parts.length > 0
224
+ ? '\n' + parts.map(p => ` \u{2022} ${p}`).join('\n')
225
+ : '';
226
+ const score = report.stats.score ?? 0;
227
+ return `${headline}${bulletList}\n\nScore: ${score}/100 | ${total} violations`;
228
+ }
229
+ // ── MCP Fix Attribution ────────────────────────────────────────────
230
+ export function renderFixAttribution(failureCount, worstViolation) {
231
+ return [
232
+ '',
233
+ '\u{2501}'.repeat(40),
234
+ `AFTER FIXING, TELL THE USER:`,
235
+ `"Rigour caught ${failureCount} issue${failureCount !== 1 ? 's' : ''} including ${worstViolation}.`,
236
+ `All fixes applied. Running verification scan..."`,
237
+ '\u{2501}'.repeat(40),
238
+ ].join('\n');
239
+ }
package/package.json CHANGED
@@ -1,21 +1,28 @@
1
1
  {
2
2
  "name": "@rigour-labs/core",
3
- "version": "5.2.2",
4
- "description": "Deterministic quality gate engine for AI-generated code. AST analysis, drift detection, and Fix Packet generation across TypeScript, JavaScript, Python, Go, Ruby, and C#.",
3
+ "version": "5.2.4",
4
+ "description": "AI-native quality gate engine with local Bayesian learning. AST analysis, drift detection, Fix Packet generation, and agent self-healing across TypeScript, JavaScript, Python, Go, Ruby, and C#.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://rigour.run",
7
7
  "keywords": [
8
- "quality-gates",
8
+ "ai",
9
+ "llm",
9
10
  "ai-code-quality",
11
+ "quality-gates",
10
12
  "static-analysis",
11
13
  "ast",
12
14
  "drift-detection",
13
15
  "code-review",
14
16
  "linter",
17
+ "machine-learning",
18
+ "bayesian-learning",
19
+ "fix-packets",
20
+ "agent-governance",
21
+ "vibe-coding",
22
+ "security",
15
23
  "typescript",
16
24
  "python",
17
- "golang",
18
- "csharp"
25
+ "golang"
19
26
  ],
20
27
  "type": "module",
21
28
  "files": [
@@ -59,11 +66,11 @@
59
66
  "@xenova/transformers": "^2.17.2",
60
67
  "sqlite3": "^5.1.7",
61
68
  "openai": "^4.104.0",
62
- "@rigour-labs/brain-darwin-arm64": "5.2.2",
63
- "@rigour-labs/brain-darwin-x64": "5.2.2",
64
- "@rigour-labs/brain-linux-arm64": "5.2.2",
65
- "@rigour-labs/brain-linux-x64": "5.2.2",
66
- "@rigour-labs/brain-win-x64": "5.2.2"
69
+ "@rigour-labs/brain-darwin-arm64": "5.2.4",
70
+ "@rigour-labs/brain-darwin-x64": "5.2.4",
71
+ "@rigour-labs/brain-linux-arm64": "5.2.4",
72
+ "@rigour-labs/brain-linux-x64": "5.2.4",
73
+ "@rigour-labs/brain-win-x64": "5.2.4"
67
74
  },
68
75
  "devDependencies": {
69
76
  "@types/fs-extra": "^11.0.4",