@rigour-labs/cli 2.21.2 → 3.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.
@@ -0,0 +1,245 @@
1
+ /**
2
+ * export-audit command
3
+ *
4
+ * Generates a compliance audit package from the last gate check.
5
+ * The artifact compliance officers hand to auditors.
6
+ *
7
+ * Formats: JSON (structured) or Markdown (human-readable)
8
+ *
9
+ * @since v2.17.0
10
+ */
11
+ import fs from 'fs-extra';
12
+ import path from 'path';
13
+ import chalk from 'chalk';
14
+ import yaml from 'yaml';
15
+ import { getScoreHistory, getScoreTrend } from '@rigour-labs/core';
16
+ const CLI_VERSION = '2.0.0';
17
+ export async function exportAuditCommand(cwd, options = {}) {
18
+ const format = options.format || 'json';
19
+ const configPath = path.join(cwd, 'rigour.yml');
20
+ let reportPath = path.join(cwd, 'rigour-report.json');
21
+ // If --run, execute a fresh check first
22
+ if (options.run) {
23
+ console.log(chalk.blue('Running fresh rigour check...\n'));
24
+ const { checkCommand } = await import('./check.js');
25
+ try {
26
+ await checkCommand(cwd, [], {});
27
+ }
28
+ catch {
29
+ // checkCommand calls process.exit, so we catch here for the --run flow
30
+ }
31
+ }
32
+ // Read config
33
+ let config = {};
34
+ if (await fs.pathExists(configPath)) {
35
+ try {
36
+ const configContent = await fs.readFile(configPath, 'utf-8');
37
+ config = yaml.parse(configContent);
38
+ if (config?.output?.report_path) {
39
+ reportPath = path.join(cwd, config.output.report_path);
40
+ }
41
+ }
42
+ catch { }
43
+ }
44
+ // Read report
45
+ if (!(await fs.pathExists(reportPath))) {
46
+ console.error(chalk.red(`Error: No report found at ${reportPath}`));
47
+ console.error(chalk.dim('Run `rigour check` first, or use `rigour export-audit --run`.'));
48
+ process.exit(2);
49
+ }
50
+ let report;
51
+ try {
52
+ const reportContent = await fs.readFile(reportPath, 'utf-8');
53
+ report = JSON.parse(reportContent);
54
+ }
55
+ catch (error) {
56
+ console.error(chalk.red(`Error reading report: ${error.message}`));
57
+ process.exit(3);
58
+ }
59
+ // Build audit package
60
+ const auditPackage = buildAuditPackage(cwd, report, config);
61
+ // Determine output path
62
+ const outputPath = options.output
63
+ ? path.resolve(cwd, options.output)
64
+ : path.join(cwd, `rigour-audit-report.${format}`);
65
+ // Write output
66
+ if (format === 'md') {
67
+ const markdown = renderMarkdown(auditPackage);
68
+ await fs.writeFile(outputPath, markdown, 'utf-8');
69
+ }
70
+ else {
71
+ await fs.writeJson(outputPath, auditPackage, { spaces: 2 });
72
+ }
73
+ console.log(chalk.green(`\n✔ Audit report exported: ${path.relative(cwd, outputPath)}`));
74
+ console.log(chalk.dim(` Format: ${format.toUpperCase()} | Status: ${auditPackage.summary.status} | Score: ${auditPackage.summary.score}/100`));
75
+ }
76
+ function buildAuditPackage(cwd, report, config) {
77
+ const stats = report.stats || {};
78
+ const failures = report.failures || [];
79
+ // Score trend
80
+ const trend = getScoreTrend(cwd);
81
+ const history = getScoreHistory(cwd, 5);
82
+ // Severity breakdown
83
+ const severityBreakdown = stats.severity_breakdown || {};
84
+ const provenanceBreakdown = stats.provenance_breakdown || {};
85
+ // Gate results from summary
86
+ const gateResults = Object.entries(report.summary || {}).map(([gate, status]) => ({
87
+ gate,
88
+ status: status,
89
+ }));
90
+ // Top violations
91
+ const violations = failures.map((f) => ({
92
+ id: f.id,
93
+ severity: f.severity || 'medium',
94
+ provenance: f.provenance || 'traditional',
95
+ title: f.title,
96
+ details: f.details,
97
+ files: f.files || [],
98
+ line: f.line,
99
+ hint: f.hint,
100
+ }));
101
+ return {
102
+ schema_version: '1.0.0',
103
+ metadata: {
104
+ project: path.basename(cwd),
105
+ rigour_version: CLI_VERSION,
106
+ timestamp: new Date().toISOString(),
107
+ preset: config.preset || 'custom',
108
+ config_path: 'rigour.yml',
109
+ generated_by: 'rigour export-audit',
110
+ },
111
+ summary: {
112
+ status: report.status,
113
+ score: stats.score ?? 100,
114
+ ai_health_score: stats.ai_health_score,
115
+ structural_score: stats.structural_score,
116
+ duration_ms: stats.duration_ms,
117
+ total_violations: failures.length,
118
+ },
119
+ severity_breakdown: {
120
+ critical: severityBreakdown.critical || 0,
121
+ high: severityBreakdown.high || 0,
122
+ medium: severityBreakdown.medium || 0,
123
+ low: severityBreakdown.low || 0,
124
+ info: severityBreakdown.info || 0,
125
+ },
126
+ provenance_breakdown: {
127
+ 'ai-drift': provenanceBreakdown['ai-drift'] || 0,
128
+ traditional: provenanceBreakdown.traditional || 0,
129
+ security: provenanceBreakdown.security || 0,
130
+ governance: provenanceBreakdown.governance || 0,
131
+ },
132
+ gate_results: gateResults,
133
+ violations,
134
+ score_trend: trend ? {
135
+ direction: trend.direction,
136
+ delta: trend.delta,
137
+ recent_average: trend.recentAvg,
138
+ previous_average: trend.previousAvg,
139
+ last_scores: trend.recentScores,
140
+ } : null,
141
+ recent_history: history.map(h => ({
142
+ timestamp: h.timestamp,
143
+ score: h.score,
144
+ status: h.status,
145
+ })),
146
+ };
147
+ }
148
+ function renderMarkdown(audit) {
149
+ const lines = [];
150
+ lines.push(`# Rigour Audit Report`);
151
+ lines.push('');
152
+ lines.push(`**Project:** ${audit.metadata.project}`);
153
+ lines.push(`**Generated:** ${audit.metadata.timestamp}`);
154
+ lines.push(`**Rigour Version:** ${audit.metadata.rigour_version}`);
155
+ lines.push(`**Preset:** ${audit.metadata.preset}`);
156
+ lines.push('');
157
+ // Summary
158
+ lines.push('## Summary');
159
+ lines.push('');
160
+ const statusEmoji = audit.summary.status === 'PASS' ? '✅' : '🛑';
161
+ lines.push(`| Metric | Value |`);
162
+ lines.push(`|:-------|:------|`);
163
+ lines.push(`| **Status** | ${statusEmoji} ${audit.summary.status} |`);
164
+ lines.push(`| **Overall Score** | ${audit.summary.score}/100 |`);
165
+ if (audit.summary.ai_health_score !== undefined) {
166
+ lines.push(`| **AI Health Score** | ${audit.summary.ai_health_score}/100 |`);
167
+ }
168
+ if (audit.summary.structural_score !== undefined) {
169
+ lines.push(`| **Structural Score** | ${audit.summary.structural_score}/100 |`);
170
+ }
171
+ lines.push(`| **Total Violations** | ${audit.summary.total_violations} |`);
172
+ lines.push(`| **Duration** | ${audit.summary.duration_ms}ms |`);
173
+ lines.push('');
174
+ // Severity Breakdown
175
+ lines.push('## Severity Breakdown');
176
+ lines.push('');
177
+ lines.push('| Severity | Count |');
178
+ lines.push('|:---------|:------|');
179
+ for (const [sev, count] of Object.entries(audit.severity_breakdown)) {
180
+ if (count > 0) {
181
+ lines.push(`| ${sev.charAt(0).toUpperCase() + sev.slice(1)} | ${count} |`);
182
+ }
183
+ }
184
+ lines.push('');
185
+ // Provenance Breakdown
186
+ lines.push('## Provenance Breakdown');
187
+ lines.push('');
188
+ lines.push('| Category | Count |');
189
+ lines.push('|:---------|:------|');
190
+ for (const [prov, count] of Object.entries(audit.provenance_breakdown)) {
191
+ if (count > 0) {
192
+ lines.push(`| ${prov} | ${count} |`);
193
+ }
194
+ }
195
+ lines.push('');
196
+ // Gate Results
197
+ lines.push('## Gate Results');
198
+ lines.push('');
199
+ lines.push('| Gate | Status |');
200
+ lines.push('|:-----|:-------|');
201
+ for (const gate of audit.gate_results) {
202
+ const icon = gate.status === 'PASS' ? '✅' : gate.status === 'FAIL' ? '❌' : '⏭️';
203
+ lines.push(`| ${gate.gate} | ${icon} ${gate.status} |`);
204
+ }
205
+ lines.push('');
206
+ // Violations
207
+ if (audit.violations.length > 0) {
208
+ lines.push('## Violations');
209
+ lines.push('');
210
+ for (let i = 0; i < audit.violations.length; i++) {
211
+ const v = audit.violations[i];
212
+ lines.push(`### ${i + 1}. [${v.severity.toUpperCase()}] ${v.title}`);
213
+ lines.push('');
214
+ lines.push(`- **ID:** \`${v.id}\``);
215
+ lines.push(`- **Severity:** ${v.severity}`);
216
+ lines.push(`- **Provenance:** ${v.provenance}`);
217
+ lines.push(`- **Details:** ${v.details}`);
218
+ if (v.files && v.files.length > 0) {
219
+ lines.push(`- **Files:** ${v.files.join(', ')}`);
220
+ }
221
+ if (v.hint) {
222
+ lines.push(`- **Hint:** ${v.hint}`);
223
+ }
224
+ lines.push('');
225
+ }
226
+ }
227
+ // Score Trend
228
+ if (audit.score_trend) {
229
+ lines.push('## Score Trend');
230
+ lines.push('');
231
+ const arrow = audit.score_trend.direction === 'improving' ? '↑' :
232
+ audit.score_trend.direction === 'degrading' ? '↓' : '→';
233
+ lines.push(`**Direction:** ${audit.score_trend.direction} ${arrow}`);
234
+ lines.push(`**Recent Average:** ${audit.score_trend.recent_average}/100`);
235
+ lines.push(`**Previous Average:** ${audit.score_trend.previous_average}/100`);
236
+ lines.push(`**Delta:** ${audit.score_trend.delta > 0 ? '+' : ''}${audit.score_trend.delta}`);
237
+ lines.push(`**Recent Scores:** ${audit.score_trend.last_scores.join(' → ')}`);
238
+ lines.push('');
239
+ }
240
+ // Footer
241
+ lines.push('---');
242
+ lines.push(`*Generated by Rigour v${audit.metadata.rigour_version} — ${audit.metadata.timestamp}*`);
243
+ lines.push('');
244
+ return lines.join('\n');
245
+ }
@@ -1,12 +1,13 @@
1
1
  import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
2
  import fs from 'fs-extra';
3
3
  import path from 'path';
4
+ import os from 'os';
4
5
  async function getInitCommand() {
5
6
  const { initCommand } = await import('./commands/init.js');
6
7
  return initCommand;
7
8
  }
8
9
  describe('Init Command Rules Verification', () => {
9
- const testDir = path.join(process.cwd(), 'temp-init-rules-test');
10
+ const testDir = path.join(os.tmpdir(), 'rigour-temp-init-rules-test-' + process.pid);
10
11
  beforeEach(async () => {
11
12
  await fs.ensureDir(testDir);
12
13
  });
@@ -1,12 +1,13 @@
1
1
  import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
2
  import fs from 'fs-extra';
3
3
  import path from 'path';
4
+ import os from 'os';
4
5
  async function getCheckCommand() {
5
6
  const { checkCommand } = await import('./commands/check.js');
6
7
  return checkCommand;
7
8
  }
8
9
  describe('CLI Smoke Test', () => {
9
- const testDir = path.join(process.cwd(), 'temp-smoke-test');
10
+ const testDir = path.join(os.tmpdir(), 'rigour-temp-smoke-test-' + process.pid);
10
11
  beforeEach(async () => {
11
12
  await fs.ensureDir(testDir);
12
13
  // @ts-ignore
package/package.json CHANGED
@@ -1,6 +1,22 @@
1
1
  {
2
2
  "name": "@rigour-labs/cli",
3
- "version": "2.21.2",
3
+ "version": "3.0.0",
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
+ "license": "MIT",
6
+ "homepage": "https://rigour.run",
7
+ "keywords": [
8
+ "quality-gates",
9
+ "ai-code-quality",
10
+ "cli",
11
+ "linter",
12
+ "static-analysis",
13
+ "claude",
14
+ "cursor",
15
+ "copilot",
16
+ "mcp",
17
+ "code-review",
18
+ "ci-cd"
19
+ ],
4
20
  "type": "module",
5
21
  "bin": {
6
22
  "rigour": "dist/cli.js"
@@ -28,7 +44,7 @@
28
44
  "inquirer": "9.2.16",
29
45
  "ora": "^8.0.1",
30
46
  "yaml": "^2.8.2",
31
- "@rigour-labs/core": "2.21.2"
47
+ "@rigour-labs/core": "3.0.0"
32
48
  },
33
49
  "devDependencies": {
34
50
  "@types/fs-extra": "^11.0.4",