@rigour-labs/cli 5.0.0 β 5.1.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 +96 -36
- package/dist/commands/brain.js +4 -4
- package/dist/commands/check.js +4 -4
- package/dist/commands/hooks.js +22 -0
- package/dist/commands/scan-deep.d.ts +1 -1
- package/dist/commands/scan-deep.js +12 -13
- package/dist/commands/scan.js +101 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@rigour-labs/cli)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
7
|
-
**
|
|
8
|
-
Rigour
|
|
7
|
+
**AI Agent Governance CLI β quality gates, DLP, drift detection, and deep analysis.**
|
|
8
|
+
Rigour governs what goes IN (DLP), what comes OUT (quality gates), and what gets PERSISTED (memory governance).
|
|
9
9
|
|
|
10
10
|
> Core gates run locally. Deep analysis can run local or cloud provider mode.
|
|
11
11
|
|
|
@@ -13,8 +13,8 @@ Rigour forces AI agents to meet strict engineering standards before marking task
|
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
npx @rigour-labs/cli scan # Zero-config scan (auto-detect stack)
|
|
16
|
-
npx @rigour-labs/cli init # Initialize
|
|
17
|
-
npx @rigour-labs/cli check # Verify code quality
|
|
16
|
+
npx @rigour-labs/cli init # Initialize config, hooks, DLP, governance
|
|
17
|
+
npx @rigour-labs/cli check # Verify code quality (27+ gates)
|
|
18
18
|
npx @rigour-labs/cli run -- claude "Build feature X" # Agent loop
|
|
19
19
|
```
|
|
20
20
|
|
|
@@ -27,64 +27,126 @@ brew install rigour
|
|
|
27
27
|
|
|
28
28
|
## π The Problem
|
|
29
29
|
|
|
30
|
-
AI agents
|
|
30
|
+
AI agents are powerful but ungoverned. They claim success based on narrative, not execution. Credentials get cached in agent memory. Imports get hallucinated. Code quality drifts across sessions.
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
2. Agent **claims** "Task 100% complete"
|
|
34
|
-
3. **CI Fails** with type errors, lint failures, or broken tests
|
|
35
|
-
|
|
36
|
-
**Rigour breaks this cycle** by forcing agents to face the same verification tools (ruff, mypy, vitest) that CI runsβlocally and immediately.
|
|
32
|
+
**Rigour breaks this cycle** with deterministic PASS/FAIL gates, credential interception, and memory governance β all local-first.
|
|
37
33
|
|
|
38
34
|
## π How It Works
|
|
39
35
|
|
|
40
36
|
```
|
|
41
37
|
Agent writes code β Rigour checks β FAIL? β Fix Packet β Agent retries β PASS β
|
|
38
|
+
DLP: User input β Credential scan β BLOCK before agent sees it
|
|
39
|
+
Memory: Agent writes CLAUDE.md β Rigour intercepts β Forces rigour_remember (DLP-scanned)
|
|
42
40
|
```
|
|
43
41
|
|
|
44
|
-
## βοΈ Quality Gates
|
|
42
|
+
## βοΈ Quality Gates (27+ Deterministic)
|
|
45
43
|
|
|
46
44
|
### Structural & Security Gates
|
|
47
45
|
| Gate | Description |
|
|
48
46
|
|:---|:---|
|
|
49
47
|
| **File Size** | Max lines per file (default: 300-500) |
|
|
50
|
-
| **Hygiene** | No TODO/FIXME comments allowed |
|
|
51
|
-
| **
|
|
48
|
+
| **Content Hygiene** | No TODO/FIXME comments allowed |
|
|
49
|
+
| **AST Analysis** | Cyclomatic complexity, method count, nesting depth, function length |
|
|
52
50
|
| **Required Docs** | SPEC.md, ARCH.md, README must exist |
|
|
53
51
|
| **File Guard** | Protected paths, max files changed |
|
|
54
|
-
| **Security Patterns** | XSS, SQL injection, hardcoded secrets, command injection |
|
|
55
|
-
| **
|
|
52
|
+
| **Security Patterns** | XSS, SQL injection, hardcoded secrets, command injection, path traversal |
|
|
53
|
+
| **Frontend Secret Exposure** | API keys in client-side bundles |
|
|
54
|
+
| **Deprecated APIs** | Node, Python, Web, Go, C#, Java deprecated usage |
|
|
55
|
+
| **Test Quality** | Empty tests, tautological assertions, mock-heavy, snapshot abuse |
|
|
56
|
+
| **Side-Effect Safety** | Unbounded timers, recursive depth, resource lifecycle, retry loops |
|
|
56
57
|
|
|
57
|
-
### AI-Native Drift Detection
|
|
58
|
+
### AI-Native Drift Detection
|
|
59
|
+
| Gate | Description |
|
|
60
|
+
|:---|:---|
|
|
61
|
+
| **Hallucinated Imports** | Imports referencing non-existent modules (JS/TS, Python, Go, Ruby, C#, Rust, Java, Kotlin) |
|
|
62
|
+
| **Phantom APIs** | Non-existent stdlib/framework methods the LLM invented |
|
|
63
|
+
| **Promise Safety** | Unhandled async, unsafe JSON.parse, floating fetch across 6 languages |
|
|
64
|
+
| **Duplication Drift** | Three-pass: MD5 exact β AST Jaccard (tree-sitter) β semantic embedding (384D cosine) |
|
|
65
|
+
| **Style Drift** | Naming conventions, error handling, import style fingerprinted against project baseline |
|
|
66
|
+
| **Logic Drift** | Comparison operators (>= β >), branch counts, return statements tracked per function |
|
|
67
|
+
| **Context Window Artifacts** | Quality degradation within long files β clean top, messy bottom |
|
|
68
|
+
| **Inconsistent Error Handling** | Same error type handled differently across sessions |
|
|
69
|
+
| **Dependency Bloat** | Unused deps, heavy alternatives (momentβdayjs), duplicate purpose packages |
|
|
70
|
+
|
|
71
|
+
### Agent Governance
|
|
58
72
|
| Gate | Description |
|
|
59
73
|
|:---|:---|
|
|
60
|
-
| **
|
|
61
|
-
| **
|
|
62
|
-
| **
|
|
63
|
-
|
|
64
|
-
|
|
74
|
+
| **Memory Governance** | Blocks agent writes to CLAUDE.md, .clinerules, .windsurf/memories/ |
|
|
75
|
+
| **Skills Governance** | Blocks agent writes to .claude/skills/, .cursor/rules/ |
|
|
76
|
+
| **Governance DLP** | Scans content written to any governed file for credentials |
|
|
77
|
+
|
|
78
|
+
### Two-Score System
|
|
79
|
+
Every failure carries a provenance tag (`ai-drift`, `traditional`, `security`, `governance`) and contributes to two sub-scores: **AI Health Score** (0β100) and **Structural Score** (0β100).
|
|
80
|
+
|
|
81
|
+
## π AI Agent DLP (Data Loss Prevention)
|
|
82
|
+
|
|
83
|
+
Real-time credential interception via PreToolUse hooks β blocks credentials before agents see them.
|
|
65
84
|
|
|
66
|
-
|
|
67
|
-
|
|
85
|
+
- **29 credential patterns**: AWS, GCP, Azure, OpenAI, Anthropic, GitHub, Stripe, private keys, database URLs, JWTs, CI/CD tokens
|
|
86
|
+
- **Anti-evasion**: Unicode normalization, zero-width char removal, bidi control stripping, Shannon entropy detection (>4.5 bits)
|
|
87
|
+
- **Compliance mapped**: SOC2-CC6.1, HIPAA-164.312, PCI-DSS-3.4/3.5/6.5, OWASP-A2, CWE-798
|
|
88
|
+
|
|
89
|
+
## π Real-Time Hooks
|
|
90
|
+
|
|
91
|
+
Two-tier supervision: inline hooks (<200ms per file write) + checkpoint suite (full gates).
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
rigour hooks init # auto-detect tool, install hooks + DLP
|
|
95
|
+
rigour hooks init --tool all # all tools at once
|
|
96
|
+
rigour hooks init --block # exit code 2 on failures (strict mode)
|
|
97
|
+
rigour hooks init --no-dlp # skip DLP hooks
|
|
98
|
+
rigour hooks check --files src/a.ts # manual fast check
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Supported tools:** Claude Code, Cursor, Cline, Windsurf β each with quality (post-write) and DLP (pre-write) hooks.
|
|
102
|
+
|
|
103
|
+
## π§ Deep Analysis (LLM-Powered)
|
|
104
|
+
|
|
105
|
+
Five-signal extraction β LLM interpretation β deterministic verification pipeline.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
rigour check --deep # Local sidecar (Qwen3.5-0.8B, any CPU)
|
|
109
|
+
rigour check --deep --pro # Full model (Qwen2.5-Coder-1.5B)
|
|
110
|
+
rigour check --deep --provider claude -k sk-ant-xxx # Cloud BYOK
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## π Multi-Language Support
|
|
114
|
+
|
|
115
|
+
Hallucinated import detection with stdlib whitelists and dependency manifest parsing:
|
|
116
|
+
|
|
117
|
+
**JS/TS** (Node.js builtins, package.json) Β· **Python** (160+ stdlib, local modules) Β· **Go** (150+ stdlib, go.mod) Β· **Ruby** (80+ stdlib, Gemfile) Β· **C#/.NET** (.NET 8 namespaces, .csproj) Β· **Rust** (std/core/alloc, Cargo.toml) Β· **Java** (java/javax/jakarta, build.gradle/pom.xml) Β· **Kotlin** (kotlin/kotlinx + Java interop, build.gradle.kts)
|
|
68
118
|
|
|
69
119
|
## π οΈ Commands
|
|
70
120
|
|
|
71
121
|
| Command | Purpose |
|
|
72
122
|
|:---|:---|
|
|
73
|
-
| `rigour scan` | Zero-config stack-aware scan
|
|
74
|
-
| `rigour
|
|
75
|
-
| `rigour
|
|
76
|
-
| `rigour check
|
|
77
|
-
| `rigour
|
|
78
|
-
| `rigour
|
|
79
|
-
| `rigour
|
|
80
|
-
| `rigour
|
|
123
|
+
| `rigour scan` | Zero-config stack-aware scan (auto-detect) |
|
|
124
|
+
| `rigour scan --deep` | Zero-config + local LLM deep analysis |
|
|
125
|
+
| `rigour init` | Setup config, hooks, DLP, governance |
|
|
126
|
+
| `rigour check` | Full repository quality gates |
|
|
127
|
+
| `rigour check --ci` | CI mode with minimal output |
|
|
128
|
+
| `rigour check --deep` | + local LLM analysis |
|
|
129
|
+
| `rigour hooks init` | Install real-time hooks for detected tools |
|
|
130
|
+
| `rigour hooks check --files ...` | Fast hook gates on specific files |
|
|
131
|
+
| `rigour explain` | Detailed explanation of failures |
|
|
132
|
+
| `rigour run` | Supervisor loop for agent refinement |
|
|
133
|
+
| `rigour run --supervised` | Full supervisor mode (iterative command + gate loop) |
|
|
81
134
|
| `rigour studio` | Dashboard for monitoring |
|
|
82
|
-
| `rigour
|
|
135
|
+
| `rigour brain` | Local memory status (SQLite) |
|
|
136
|
+
| `rigour brain --compact` | Prune old findings, reclaim disk |
|
|
137
|
+
| `rigour doctor` | Diagnose install + deep readiness |
|
|
138
|
+
| `rigour export-audit` | Export compliance audit report (JSON/Markdown) |
|
|
139
|
+
| `rigour demo` | Live demo on synthetic or real repos |
|
|
140
|
+
| `rigour settings` | Manage API keys and provider config |
|
|
83
141
|
|
|
84
142
|
## π€ Works With
|
|
85
143
|
|
|
86
|
-
- **Claude Code**: `rigour run -- claude "..."`
|
|
87
|
-
- **Cursor
|
|
144
|
+
- **Claude Code**: `rigour run -- claude "..."` + real-time hooks
|
|
145
|
+
- **Cursor**: Via MCP server + `.cursor/hooks.json`
|
|
146
|
+
- **Cline**: Via MCP server + `.clinerules/hooks/` scripts
|
|
147
|
+
- **Windsurf**: Via MCP server + `.windsurf/hooks.json`
|
|
148
|
+
- **Gemini**: Via MCP server (`rigour_check`, `rigour_explain`)
|
|
149
|
+
- **GitHub Actions**: `npx @rigour-labs/cli check --ci`
|
|
88
150
|
|
|
89
151
|
## π Documentation
|
|
90
152
|
|
|
@@ -106,5 +168,3 @@ All gates support **TypeScript, JavaScript, Python, Go, Ruby, and C#/.NET**.
|
|
|
106
168
|
## π License
|
|
107
169
|
|
|
108
170
|
MIT Β© [Rigour Labs](https://github.com/rigour-labs)
|
|
109
|
-
|
|
110
|
-
> *"Rigour adds the engineering."*
|
package/dist/commands/brain.js
CHANGED
|
@@ -40,10 +40,10 @@ async function handleStatus(core) {
|
|
|
40
40
|
console.log(chalk.dim(` Database: ~/.rigour/rigour.db`));
|
|
41
41
|
console.log(chalk.dim(` Size: ${formatBytes(sizeBytes)}\n`));
|
|
42
42
|
const cwd = process.cwd();
|
|
43
|
-
const stats = core.getProjectStats(cwd);
|
|
43
|
+
const stats = await core.getProjectStats(cwd);
|
|
44
44
|
if (!stats) {
|
|
45
|
-
console.log(chalk.yellow(' SQLite not available (
|
|
46
|
-
console.log(chalk.dim(' Run: npm install
|
|
45
|
+
console.log(chalk.yellow(' SQLite not available (sqlite3 not installed).'));
|
|
46
|
+
console.log(chalk.dim(' Run: npm install sqlite3'));
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
49
|
if (stats.totalScans === 0) {
|
|
@@ -66,7 +66,7 @@ async function handleStatus(core) {
|
|
|
66
66
|
/** Compact: prune old findings, weak patterns, VACUUM. */
|
|
67
67
|
async function handleCompact(core, retainDays) {
|
|
68
68
|
console.log(chalk.bold.cyan(`\nπ§ Compacting Rigour Brain (retain ${retainDays} days)...\n`));
|
|
69
|
-
const result = core.compactDatabase(retainDays);
|
|
69
|
+
const result = await core.compactDatabase(retainDays);
|
|
70
70
|
console.log(` Findings pruned: ${chalk.yellow(result.pruned)}`);
|
|
71
71
|
console.log(` Patterns removed: ${chalk.yellow(result.patternsDecayed)}`);
|
|
72
72
|
console.log(` Size before: ${formatBytes(result.sizeBefore)}`);
|
package/dist/commands/check.js
CHANGED
|
@@ -114,15 +114,15 @@ export async function checkCommand(cwd, files = [], options = {}) {
|
|
|
114
114
|
if (isDeep) {
|
|
115
115
|
try {
|
|
116
116
|
const { openDatabase, insertScan, insertFindings } = await import('@rigour-labs/core');
|
|
117
|
-
const db = openDatabase();
|
|
117
|
+
const db = await openDatabase();
|
|
118
118
|
if (db) {
|
|
119
119
|
const repoName = path.basename(cwd);
|
|
120
|
-
const scanId = insertScan(db, repoName, report, {
|
|
120
|
+
const scanId = await insertScan(db, repoName, report, {
|
|
121
121
|
deepTier: report.stats.deep?.tier || (options.pro ? 'deep' : (resolvedDeepMode?.isLocal ? 'lite' : 'cloud')),
|
|
122
122
|
deepModel: report.stats.deep?.model,
|
|
123
123
|
});
|
|
124
|
-
insertFindings(db, scanId, report.failures);
|
|
125
|
-
db.close();
|
|
124
|
+
await insertFindings(db, scanId, report.failures);
|
|
125
|
+
await db.close();
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
catch (dbError) {
|
package/dist/commands/hooks.js
CHANGED
|
@@ -19,6 +19,7 @@ import chalk from 'chalk';
|
|
|
19
19
|
import { randomUUID } from 'crypto';
|
|
20
20
|
import { runHookChecker, scanInputForCredentials, formatDLPAlert, createDLPAuditEntry } from '@rigour-labs/core';
|
|
21
21
|
// ββ Studio event logging βββββββββββββββββββββββββββββββββββββββββββββ
|
|
22
|
+
const MAX_EVENT_LOG_LINES = 2000;
|
|
22
23
|
async function logStudioEvent(cwd, event) {
|
|
23
24
|
try {
|
|
24
25
|
const rigourDir = path.join(cwd, '.rigour');
|
|
@@ -30,11 +31,30 @@ async function logStudioEvent(cwd, event) {
|
|
|
30
31
|
...event,
|
|
31
32
|
}) + '\n';
|
|
32
33
|
await fs.appendFile(eventsPath, logEntry);
|
|
34
|
+
// Rotate: keep last MAX_EVENT_LOG_LINES entries to prevent unbounded growth
|
|
35
|
+
await rotateEventLog(eventsPath);
|
|
33
36
|
}
|
|
34
37
|
catch {
|
|
35
38
|
// Silent fail
|
|
36
39
|
}
|
|
37
40
|
}
|
|
41
|
+
async function rotateEventLog(eventsPath) {
|
|
42
|
+
try {
|
|
43
|
+
const stat = await fs.stat(eventsPath);
|
|
44
|
+
// Only check rotation when file exceeds ~500KB (avoids reading on every append)
|
|
45
|
+
if (stat.size < 512 * 1024)
|
|
46
|
+
return;
|
|
47
|
+
const content = await fs.readFile(eventsPath, 'utf-8');
|
|
48
|
+
const lines = content.trim().split('\n');
|
|
49
|
+
if (lines.length > MAX_EVENT_LOG_LINES) {
|
|
50
|
+
const trimmed = lines.slice(-MAX_EVENT_LOG_LINES).join('\n') + '\n';
|
|
51
|
+
await fs.writeFile(eventsPath, trimmed);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Silent fail β rotation is best-effort
|
|
56
|
+
}
|
|
57
|
+
}
|
|
38
58
|
// ββ Tool detection βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
39
59
|
const TOOL_MARKERS = {
|
|
40
60
|
claude: ['CLAUDE.md', '.claude'],
|
|
@@ -249,6 +269,8 @@ process.stdin.on('end', async () => {
|
|
|
249
269
|
const proc = spawnSync(
|
|
250
270
|
command,
|
|
251
271
|
[...baseArgs, '--mode', 'dlp', '--stdin'],
|
|
272
|
+
// Note: joining with \\n is safe β credential patterns match within single values.
|
|
273
|
+
// A credential split across two toolInput fields would be malformed regardless.
|
|
252
274
|
{ input: textsToScan.join('\\n'), encoding: 'utf-8', timeout: 3000 }
|
|
253
275
|
);
|
|
254
276
|
if (proc.error) throw proc.error;
|
|
@@ -4,5 +4,5 @@ import type { ScanOptions, StackSignals } from './scan.js';
|
|
|
4
4
|
export declare function buildDeepOpts(options: ScanOptions, isSilent: boolean): DeepOptions & {
|
|
5
5
|
onProgress?: (msg: string) => void;
|
|
6
6
|
};
|
|
7
|
-
export declare function persistDeepResults(cwd: string, report: Report, isDeep: boolean, options: ScanOptions): void
|
|
7
|
+
export declare function persistDeepResults(cwd: string, report: Report, isDeep: boolean, options: ScanOptions): Promise<void>;
|
|
8
8
|
export declare function renderDeepScanResults(report: Report, stackSignals: StackSignals, config: Config, cwd: string): void;
|
|
@@ -23,22 +23,21 @@ export function buildDeepOpts(options, isSilent) {
|
|
|
23
23
|
},
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
-
export function persistDeepResults(cwd, report, isDeep, options) {
|
|
26
|
+
export async function persistDeepResults(cwd, report, isDeep, options) {
|
|
27
27
|
if (!isDeep)
|
|
28
28
|
return;
|
|
29
29
|
try {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}).catch(() => { });
|
|
30
|
+
const { openDatabase, insertScan, insertFindings } = await import('@rigour-labs/core');
|
|
31
|
+
const db = await openDatabase();
|
|
32
|
+
if (!db)
|
|
33
|
+
return;
|
|
34
|
+
const repoName = require('path').basename(cwd);
|
|
35
|
+
const scanId = await insertScan(db, repoName, report, {
|
|
36
|
+
deepTier: report.stats.deep?.tier || (options.pro ? 'deep' : 'lite'),
|
|
37
|
+
deepModel: report.stats.deep?.model,
|
|
38
|
+
});
|
|
39
|
+
await insertFindings(db, scanId, report.failures);
|
|
40
|
+
await db.close();
|
|
42
41
|
}
|
|
43
42
|
catch { /* silent */ }
|
|
44
43
|
}
|
package/dist/commands/scan.js
CHANGED
|
@@ -58,6 +58,7 @@ export async function scanCommand(cwd, files = [], options = {}) {
|
|
|
58
58
|
const deepOpts = isDeep ? buildDeepOpts(options, isSilent) : undefined;
|
|
59
59
|
const report = await runner.run(cwd, files.length > 0 ? files : undefined, deepOpts);
|
|
60
60
|
await writeReportArtifacts(cwd, report, scanCtx.config);
|
|
61
|
+
await writeLastScanJson(cwd, scanCtx, stackSignals, report, isDeep);
|
|
61
62
|
persistDeepResults(cwd, report, isDeep, options);
|
|
62
63
|
if (options.json) {
|
|
63
64
|
outputJson(scanCtx, stackSignals, report);
|
|
@@ -73,6 +74,7 @@ export async function scanCommand(cwd, files = [], options = {}) {
|
|
|
73
74
|
else {
|
|
74
75
|
renderScanResults(report, stackSignals, scanCtx.config.output.report_path, cwd);
|
|
75
76
|
}
|
|
77
|
+
renderSummaryTable(report, isDeep);
|
|
76
78
|
process.exit(report.status === 'PASS' ? EXIT_PASS : EXIT_FAIL);
|
|
77
79
|
}
|
|
78
80
|
catch (error) {
|
|
@@ -298,3 +300,102 @@ export function extractHallucinatedImports(failures) {
|
|
|
298
300
|
}
|
|
299
301
|
return fakeImports;
|
|
300
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* Always write a comprehensive last-scan.json to .rigour/ for tooling and MCP consumption.
|
|
305
|
+
* This file is written regardless of --json, --ci, or --deep flags.
|
|
306
|
+
*/
|
|
307
|
+
async function writeLastScanJson(cwd, scanCtx, stackSignals, report, isDeep) {
|
|
308
|
+
const rigourDir = path.join(cwd, '.rigour');
|
|
309
|
+
await fs.ensureDir(rigourDir);
|
|
310
|
+
const lastScanPath = path.join(rigourDir, 'last-scan.json');
|
|
311
|
+
const severity = report.stats.severity_breakdown || {};
|
|
312
|
+
const provenance = report.stats.provenance_breakdown || {};
|
|
313
|
+
const payload = {
|
|
314
|
+
timestamp: new Date().toISOString(),
|
|
315
|
+
mode: scanCtx.mode,
|
|
316
|
+
preset: scanCtx.detectedPreset ?? scanCtx.config.preset,
|
|
317
|
+
paradigm: scanCtx.detectedParadigm ?? scanCtx.config.paradigm,
|
|
318
|
+
stack: stackSignals,
|
|
319
|
+
deep: isDeep,
|
|
320
|
+
status: report.status,
|
|
321
|
+
scores: {
|
|
322
|
+
overall: report.stats.score ?? 0,
|
|
323
|
+
ai_health: report.stats.ai_health_score ?? 0,
|
|
324
|
+
structural: report.stats.structural_score ?? 0,
|
|
325
|
+
code_quality: report.stats.code_quality_score ?? 0,
|
|
326
|
+
},
|
|
327
|
+
severity_breakdown: severity,
|
|
328
|
+
provenance_breakdown: provenance,
|
|
329
|
+
total_findings: report.failures.length,
|
|
330
|
+
duration_ms: report.stats.duration_ms,
|
|
331
|
+
deep_stats: report.stats.deep || null,
|
|
332
|
+
findings: report.failures.map(f => ({
|
|
333
|
+
id: f.id,
|
|
334
|
+
title: f.title,
|
|
335
|
+
severity: f.severity ?? 'medium',
|
|
336
|
+
provenance: f.provenance ?? 'traditional',
|
|
337
|
+
files: f.files || [],
|
|
338
|
+
line: f.line,
|
|
339
|
+
hint: f.hint,
|
|
340
|
+
category: f.category,
|
|
341
|
+
verified: f.verified,
|
|
342
|
+
confidence: f.confidence,
|
|
343
|
+
})),
|
|
344
|
+
};
|
|
345
|
+
await fs.writeJson(lastScanPath, payload, { spaces: 2 });
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Render a compact summary table to the terminal after main scan output.
|
|
349
|
+
* Shows findings grouped by gate and provenance β always printed regardless of deep/standard mode.
|
|
350
|
+
*/
|
|
351
|
+
function renderSummaryTable(report, isDeep) {
|
|
352
|
+
if (report.failures.length === 0)
|
|
353
|
+
return;
|
|
354
|
+
console.log('');
|
|
355
|
+
console.log(chalk.bold.underline('Summary by Gate'));
|
|
356
|
+
console.log('');
|
|
357
|
+
// Group failures by gate ID
|
|
358
|
+
const byGate = new Map();
|
|
359
|
+
for (const f of report.failures) {
|
|
360
|
+
const existing = byGate.get(f.id) || { count: 0, critical: 0, high: 0, medium: 0, low: 0, provenance: f.provenance || 'traditional' };
|
|
361
|
+
existing.count++;
|
|
362
|
+
const sev = f.severity ?? 'medium';
|
|
363
|
+
if (sev === 'critical')
|
|
364
|
+
existing.critical++;
|
|
365
|
+
else if (sev === 'high')
|
|
366
|
+
existing.high++;
|
|
367
|
+
else if (sev === 'medium')
|
|
368
|
+
existing.medium++;
|
|
369
|
+
else
|
|
370
|
+
existing.low++;
|
|
371
|
+
byGate.set(f.id, existing);
|
|
372
|
+
}
|
|
373
|
+
// Sort by total count descending
|
|
374
|
+
const sorted = [...byGate.entries()].sort((a, b) => b[1].count - a[1].count);
|
|
375
|
+
// Print header
|
|
376
|
+
const gateCol = 28;
|
|
377
|
+
const numCol = 6;
|
|
378
|
+
const header = ` ${'Gate'.padEnd(gateCol)} ${'Total'.padStart(numCol)} ${'Crit'.padStart(numCol)} ${'High'.padStart(numCol)} ${'Med'.padStart(numCol)} ${'Low'.padStart(numCol)} Provenance`;
|
|
379
|
+
console.log(chalk.dim(header));
|
|
380
|
+
console.log(chalk.dim(' ' + 'β'.repeat(header.length - 2)));
|
|
381
|
+
for (const [gateId, stats] of sorted) {
|
|
382
|
+
const provColor = stats.provenance === 'ai-drift' ? chalk.magenta
|
|
383
|
+
: stats.provenance === 'security' ? chalk.red
|
|
384
|
+
: stats.provenance === 'deep-analysis' ? chalk.blue
|
|
385
|
+
: chalk.dim;
|
|
386
|
+
const critStr = stats.critical > 0 ? chalk.red.bold(String(stats.critical).padStart(numCol)) : chalk.dim(String(stats.critical).padStart(numCol));
|
|
387
|
+
const highStr = stats.high > 0 ? chalk.yellow(String(stats.high).padStart(numCol)) : chalk.dim(String(stats.high).padStart(numCol));
|
|
388
|
+
console.log(` ${gateId.padEnd(gateCol)} ${String(stats.count).padStart(numCol)} ${critStr} ${highStr} ${chalk.dim(String(stats.medium).padStart(numCol))} ${chalk.dim(String(stats.low).padStart(numCol))} ${provColor(stats.provenance)}`);
|
|
389
|
+
}
|
|
390
|
+
// Totals row
|
|
391
|
+
const totals = sorted.reduce((acc, [, s]) => ({
|
|
392
|
+
count: acc.count + s.count,
|
|
393
|
+
critical: acc.critical + s.critical,
|
|
394
|
+
high: acc.high + s.high,
|
|
395
|
+
medium: acc.medium + s.medium,
|
|
396
|
+
low: acc.low + s.low,
|
|
397
|
+
}), { count: 0, critical: 0, high: 0, medium: 0, low: 0 });
|
|
398
|
+
console.log(chalk.dim(' ' + 'β'.repeat(header.length - 2)));
|
|
399
|
+
console.log(chalk.bold(` ${'TOTAL'.padEnd(gateCol)} ${String(totals.count).padStart(numCol)} ${String(totals.critical).padStart(numCol)} ${String(totals.high).padStart(numCol)} ${String(totals.medium).padStart(numCol)} ${String(totals.low).padStart(numCol)}`));
|
|
400
|
+
console.log(chalk.dim(`\n Details saved to: .rigour/last-scan.json`));
|
|
401
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigour-labs/cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.1.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": "5.
|
|
47
|
+
"@rigour-labs/core": "5.1.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/fs-extra": "^11.0.4",
|