@rigour-labs/cli 2.22.0 → 3.0.1
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 +14 -1
- package/dist/cli.js +68 -5
- package/dist/commands/check.js +68 -11
- package/dist/commands/demo.d.ts +23 -0
- package/dist/commands/demo.js +618 -0
- package/dist/commands/demo.test.d.ts +1 -0
- package/dist/commands/demo.test.js +59 -0
- package/dist/commands/explain.js +46 -1
- package/dist/commands/export-audit.d.ts +16 -0
- package/dist/commands/export-audit.js +245 -0
- package/dist/commands/hooks.d.ts +22 -0
- package/dist/commands/hooks.js +274 -0
- package/dist/commands/hooks.test.d.ts +1 -0
- package/dist/commands/hooks.test.js +77 -0
- package/dist/commands/init.js +25 -1
- package/dist/commands/init.test.d.ts +1 -0
- package/dist/commands/init.test.js +97 -0
- package/dist/init-rules.test.js +2 -1
- package/dist/smoke.test.js +2 -1
- package/package.json +18 -2
- package/studio-dist/assets/index-C0TtM2OR.js +291 -0
- package/studio-dist/index.html +1 -1
- package/studio-dist/assets/index-Ch-q_mnO.js +0 -291
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for hooks init command.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
5
|
+
import { hooksInitCommand } from './hooks.js';
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import * as os from 'os';
|
|
9
|
+
import yaml from 'yaml';
|
|
10
|
+
describe('hooksInitCommand', () => {
|
|
11
|
+
let testDir;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hooks-test-'));
|
|
14
|
+
// Write minimal rigour.yml
|
|
15
|
+
fs.writeFileSync(path.join(testDir, 'rigour.yml'), yaml.stringify({
|
|
16
|
+
version: 1,
|
|
17
|
+
gates: { max_file_lines: 500 },
|
|
18
|
+
}));
|
|
19
|
+
vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
20
|
+
vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
21
|
+
});
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
24
|
+
vi.restoreAllMocks();
|
|
25
|
+
});
|
|
26
|
+
it('should generate Claude hooks', async () => {
|
|
27
|
+
await hooksInitCommand(testDir, { tool: 'claude' });
|
|
28
|
+
const settingsPath = path.join(testDir, '.claude', 'settings.json');
|
|
29
|
+
expect(fs.existsSync(settingsPath)).toBe(true);
|
|
30
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
31
|
+
expect(settings.hooks).toBeDefined();
|
|
32
|
+
expect(settings.hooks.PostToolUse).toBeDefined();
|
|
33
|
+
});
|
|
34
|
+
it('should generate Cursor hooks', async () => {
|
|
35
|
+
await hooksInitCommand(testDir, { tool: 'cursor' });
|
|
36
|
+
const hooksPath = path.join(testDir, '.cursor', 'hooks.json');
|
|
37
|
+
expect(fs.existsSync(hooksPath)).toBe(true);
|
|
38
|
+
const hooks = JSON.parse(fs.readFileSync(hooksPath, 'utf-8'));
|
|
39
|
+
expect(hooks.hooks).toBeDefined();
|
|
40
|
+
});
|
|
41
|
+
it('should generate Cline hooks', async () => {
|
|
42
|
+
await hooksInitCommand(testDir, { tool: 'cline' });
|
|
43
|
+
const hookPath = path.join(testDir, '.clinerules', 'hooks', 'PostToolUse');
|
|
44
|
+
expect(fs.existsSync(hookPath)).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
it('should generate Windsurf hooks', async () => {
|
|
47
|
+
await hooksInitCommand(testDir, { tool: 'windsurf' });
|
|
48
|
+
const hooksPath = path.join(testDir, '.windsurf', 'hooks.json');
|
|
49
|
+
expect(fs.existsSync(hooksPath)).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
it('should support dry-run mode', async () => {
|
|
52
|
+
await hooksInitCommand(testDir, { tool: 'claude', dryRun: true });
|
|
53
|
+
// Dry run should NOT create files
|
|
54
|
+
const settingsPath = path.join(testDir, '.claude', 'settings.json');
|
|
55
|
+
expect(fs.existsSync(settingsPath)).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
it('should not overwrite without --force', async () => {
|
|
58
|
+
// Create existing file
|
|
59
|
+
const claudeDir = path.join(testDir, '.claude');
|
|
60
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
61
|
+
fs.writeFileSync(path.join(claudeDir, 'settings.json'), '{"existing": true}');
|
|
62
|
+
await hooksInitCommand(testDir, { tool: 'claude' });
|
|
63
|
+
// Should keep existing content
|
|
64
|
+
const content = fs.readFileSync(path.join(claudeDir, 'settings.json'), 'utf-8');
|
|
65
|
+
expect(content).toContain('existing');
|
|
66
|
+
});
|
|
67
|
+
it('should overwrite with --force', async () => {
|
|
68
|
+
// Create existing file
|
|
69
|
+
const claudeDir = path.join(testDir, '.claude');
|
|
70
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
71
|
+
fs.writeFileSync(path.join(claudeDir, 'settings.json'), '{"existing": true}');
|
|
72
|
+
await hooksInitCommand(testDir, { tool: 'claude', force: true });
|
|
73
|
+
// Should have new hooks content
|
|
74
|
+
const content = fs.readFileSync(path.join(claudeDir, 'settings.json'), 'utf-8');
|
|
75
|
+
expect(content).toContain('PostToolUse');
|
|
76
|
+
});
|
|
77
|
+
});
|
package/dist/commands/init.js
CHANGED
|
@@ -4,6 +4,7 @@ import chalk from 'chalk';
|
|
|
4
4
|
import yaml from 'yaml';
|
|
5
5
|
import { DiscoveryService } from '@rigour-labs/core';
|
|
6
6
|
import { CODE_QUALITY_RULES, DEBUGGING_RULES, COLLABORATION_RULES, AGNOSTIC_AI_INSTRUCTIONS } from './constants.js';
|
|
7
|
+
import { hooksInitCommand } from './hooks.js';
|
|
7
8
|
import { randomUUID } from 'crypto';
|
|
8
9
|
// Helper to log events for Rigour Studio
|
|
9
10
|
async function logStudioEvent(cwd, event) {
|
|
@@ -322,7 +323,9 @@ ${ruleContent}`;
|
|
|
322
323
|
console.log(chalk.green('✔ Initialized Windsurf Handshake (.windsurfrules)'));
|
|
323
324
|
}
|
|
324
325
|
}
|
|
325
|
-
// 3.
|
|
326
|
+
// 3. Auto-initialize hooks for detected AI coding tools
|
|
327
|
+
await initHooksForDetectedTools(cwd, detectedIDE);
|
|
328
|
+
// 4. Update .gitignore
|
|
326
329
|
const gitignorePath = path.join(cwd, '.gitignore');
|
|
327
330
|
const ignorePatterns = ['rigour-report.json', 'rigour-fix-packet.json', '.rigour/'];
|
|
328
331
|
try {
|
|
@@ -368,3 +371,24 @@ ${ruleContent}`;
|
|
|
368
371
|
content: [{ type: "text", text: `Rigour Governance Initialized` }]
|
|
369
372
|
});
|
|
370
373
|
}
|
|
374
|
+
// Maps detected IDE to hook tool name
|
|
375
|
+
const IDE_TO_HOOK_TOOL = {
|
|
376
|
+
claude: 'claude',
|
|
377
|
+
cursor: 'cursor',
|
|
378
|
+
cline: 'cline',
|
|
379
|
+
windsurf: 'windsurf',
|
|
380
|
+
};
|
|
381
|
+
async function initHooksForDetectedTools(cwd, detectedIDE) {
|
|
382
|
+
const hookTool = IDE_TO_HOOK_TOOL[detectedIDE];
|
|
383
|
+
if (!hookTool) {
|
|
384
|
+
return; // Unknown IDE or no hook support (vscode, gemini, codex)
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
console.log(chalk.dim(`\n Setting up real-time hooks for ${detectedIDE}...`));
|
|
388
|
+
await hooksInitCommand(cwd, { tool: hookTool });
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
// Non-fatal — hooks are a bonus, not a requirement
|
|
392
|
+
console.log(chalk.dim(` (Hooks setup skipped — run 'rigour hooks init' manually)`));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for init command — IDE detection, config generation, auto-hook integration.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
5
|
+
import { initCommand } from './init.js';
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import * as os from 'os';
|
|
9
|
+
describe('initCommand', () => {
|
|
10
|
+
let testDir;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'init-test-'));
|
|
13
|
+
// Minimal package.json for discovery
|
|
14
|
+
fs.writeFileSync(path.join(testDir, 'package.json'), JSON.stringify({
|
|
15
|
+
name: 'test-project',
|
|
16
|
+
dependencies: { express: '^4.0.0' },
|
|
17
|
+
}));
|
|
18
|
+
vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
19
|
+
vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
20
|
+
});
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
23
|
+
vi.restoreAllMocks();
|
|
24
|
+
});
|
|
25
|
+
it('should create rigour.yml', async () => {
|
|
26
|
+
await initCommand(testDir);
|
|
27
|
+
const configPath = path.join(testDir, 'rigour.yml');
|
|
28
|
+
expect(fs.existsSync(configPath)).toBe(true);
|
|
29
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
30
|
+
expect(content).toContain('version');
|
|
31
|
+
});
|
|
32
|
+
it('should create docs/AGENT_INSTRUCTIONS.md', async () => {
|
|
33
|
+
await initCommand(testDir);
|
|
34
|
+
const docsPath = path.join(testDir, 'docs', 'AGENT_INSTRUCTIONS.md');
|
|
35
|
+
expect(fs.existsSync(docsPath)).toBe(true);
|
|
36
|
+
const content = fs.readFileSync(docsPath, 'utf-8');
|
|
37
|
+
expect(content).toContain('Rigour');
|
|
38
|
+
});
|
|
39
|
+
it('should support dry-run mode', async () => {
|
|
40
|
+
await initCommand(testDir, { dryRun: true });
|
|
41
|
+
// Dry run should NOT create rigour.yml
|
|
42
|
+
expect(fs.existsSync(path.join(testDir, 'rigour.yml'))).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
it('should not overwrite without --force', async () => {
|
|
45
|
+
// Create existing rigour.yml
|
|
46
|
+
fs.writeFileSync(path.join(testDir, 'rigour.yml'), 'version: 1\nexisting: true\n');
|
|
47
|
+
await initCommand(testDir);
|
|
48
|
+
const content = fs.readFileSync(path.join(testDir, 'rigour.yml'), 'utf-8');
|
|
49
|
+
expect(content).toContain('existing');
|
|
50
|
+
});
|
|
51
|
+
it('should overwrite with --force', async () => {
|
|
52
|
+
// Create existing rigour.yml
|
|
53
|
+
fs.writeFileSync(path.join(testDir, 'rigour.yml'), 'version: 1\nexisting: true\n');
|
|
54
|
+
await initCommand(testDir, { force: true });
|
|
55
|
+
// Should create backup
|
|
56
|
+
expect(fs.existsSync(path.join(testDir, 'rigour.yml.bak'))).toBe(true);
|
|
57
|
+
// New config should not contain 'existing'
|
|
58
|
+
const content = fs.readFileSync(path.join(testDir, 'rigour.yml'), 'utf-8');
|
|
59
|
+
expect(content).not.toContain('existing: true');
|
|
60
|
+
});
|
|
61
|
+
it('should detect Claude IDE and create hooks', async () => {
|
|
62
|
+
// Create Claude marker
|
|
63
|
+
fs.mkdirSync(path.join(testDir, '.claude'), { recursive: true });
|
|
64
|
+
await initCommand(testDir);
|
|
65
|
+
// Should have created .claude/settings.json (hooks)
|
|
66
|
+
const settingsPath = path.join(testDir, '.claude', 'settings.json');
|
|
67
|
+
expect(fs.existsSync(settingsPath)).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
it('should detect Cursor IDE and create hooks', async () => {
|
|
70
|
+
// Create Cursor marker
|
|
71
|
+
fs.mkdirSync(path.join(testDir, '.cursor'), { recursive: true });
|
|
72
|
+
await initCommand(testDir);
|
|
73
|
+
// Should have created .cursor/hooks.json
|
|
74
|
+
const hooksPath = path.join(testDir, '.cursor', 'hooks.json');
|
|
75
|
+
expect(fs.existsSync(hooksPath)).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
it('should update .gitignore with rigour patterns', async () => {
|
|
78
|
+
await initCommand(testDir);
|
|
79
|
+
const gitignorePath = path.join(testDir, '.gitignore');
|
|
80
|
+
expect(fs.existsSync(gitignorePath)).toBe(true);
|
|
81
|
+
const content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
82
|
+
expect(content).toContain('rigour-report.json');
|
|
83
|
+
expect(content).toContain('.rigour/');
|
|
84
|
+
});
|
|
85
|
+
it('should create .rigour/memory.json for Studio', async () => {
|
|
86
|
+
await initCommand(testDir);
|
|
87
|
+
const memPath = path.join(testDir, '.rigour', 'memory.json');
|
|
88
|
+
expect(fs.existsSync(memPath)).toBe(true);
|
|
89
|
+
const mem = JSON.parse(fs.readFileSync(memPath, 'utf-8'));
|
|
90
|
+
expect(mem.memories.project_boot).toBeDefined();
|
|
91
|
+
});
|
|
92
|
+
it('should support --ide flag to target specific IDE', async () => {
|
|
93
|
+
await initCommand(testDir, { ide: 'windsurf' });
|
|
94
|
+
// Should create windsurf rules
|
|
95
|
+
expect(fs.existsSync(path.join(testDir, '.windsurfrules'))).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
});
|
package/dist/init-rules.test.js
CHANGED
|
@@ -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(
|
|
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
|
});
|
package/dist/smoke.test.js
CHANGED
|
@@ -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(
|
|
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": "
|
|
3
|
+
"version": "3.0.1",
|
|
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": "
|
|
47
|
+
"@rigour-labs/core": "3.0.1"
|
|
32
48
|
},
|
|
33
49
|
"devDependencies": {
|
|
34
50
|
"@types/fs-extra": "^11.0.4",
|