@rigour-labs/cli 2.9.3 → 2.10.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 CHANGED
@@ -11,8 +11,10 @@ const explain_js_1 = require("./commands/explain.js");
11
11
  const run_js_1 = require("./commands/run.js");
12
12
  const guide_js_1 = require("./commands/guide.js");
13
13
  const setup_js_1 = require("./commands/setup.js");
14
+ const index_js_1 = require("./commands/index.js");
14
15
  const chalk_1 = __importDefault(require("chalk"));
15
16
  const program = new commander_1.Command();
17
+ program.addCommand(index_js_1.indexCommand);
16
18
  program
17
19
  .name('rigour')
18
20
  .description('🛡️ Rigour: The Quality Gate Loop for AI-Assisted Engineering')
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Index Command
3
+ *
4
+ * Builds and updates the Rigour Pattern Index to prevent code reinvention.
5
+ */
6
+ import { Command } from 'commander';
7
+ export declare const indexCommand: Command;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ /**
3
+ * Index Command
4
+ *
5
+ * Builds and updates the Rigour Pattern Index to prevent code reinvention.
6
+ */
7
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8
+ return (mod && mod.__esModule) ? mod : { "default": mod };
9
+ };
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.indexCommand = void 0;
12
+ const commander_1 = require("commander");
13
+ const chalk_1 = __importDefault(require("chalk"));
14
+ const ora_1 = __importDefault(require("ora"));
15
+ // Dynamic imports are used inside the action handler below to prevent
16
+ // native dependency issues from affecting the rest of the CLI.
17
+ exports.indexCommand = new commander_1.Command('index')
18
+ .description('Build or update the pattern index for the current project')
19
+ .option('-s, --semantic', 'Generate semantic embeddings for better matching (requires Transformers.js)', false)
20
+ .option('-f, --force', 'Force a full rebuild of the index', false)
21
+ .option('-o, --output <path>', 'Custom path for the index file')
22
+ .action(async (options) => {
23
+ const cwd = process.cwd();
24
+ // Dynamic import to isolate native dependencies
25
+ const { PatternIndexer, savePatternIndex, loadPatternIndex, getDefaultIndexPath } = await import('@rigour-labs/core/pattern-index');
26
+ const indexPath = options.output || getDefaultIndexPath(cwd);
27
+ const spinner = (0, ora_1.default)('Initializing pattern indexer...').start();
28
+ try {
29
+ const indexer = new PatternIndexer(cwd, {
30
+ useEmbeddings: options.semantic
31
+ });
32
+ let index;
33
+ const existingIndex = await loadPatternIndex(indexPath);
34
+ if (existingIndex && !options.force) {
35
+ spinner.text = 'Updating existing pattern index...';
36
+ index = await indexer.updateIndex(existingIndex);
37
+ }
38
+ else {
39
+ spinner.text = 'Building fresh pattern index (this may take a while)...';
40
+ index = await indexer.buildIndex();
41
+ }
42
+ spinner.text = 'Saving index to disk...';
43
+ await savePatternIndex(index, indexPath);
44
+ spinner.succeed(chalk_1.default.green(`Pattern index built successfully!`));
45
+ console.log(chalk_1.default.blue(`- Total Patterns: ${index.stats.totalPatterns}`));
46
+ console.log(chalk_1.default.blue(`- Total Files: ${index.stats.totalFiles}`));
47
+ console.log(chalk_1.default.blue(`- Index Path: ${indexPath}`));
48
+ if (options.semantic) {
49
+ console.log(chalk_1.default.magenta(`- Semantic Search: Enabled (Local Transformers.js)`));
50
+ }
51
+ const byType = Object.entries(index.stats.byType)
52
+ .map(([type, count]) => `${type}: ${count}`)
53
+ .join(', ');
54
+ console.log(chalk_1.default.gray(`Types: ${byType}`));
55
+ }
56
+ catch (error) {
57
+ spinner.fail(chalk_1.default.red(`Failed to build pattern index: ${error.message}`));
58
+ process.exit(1);
59
+ }
60
+ });
@@ -4,9 +4,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const vitest_1 = require("vitest");
7
- const init_js_1 = require("./commands/init.js");
8
7
  const fs_extra_1 = __importDefault(require("fs-extra"));
9
8
  const path_1 = __importDefault(require("path"));
9
+ async function getInitCommand() {
10
+ const { initCommand } = await import('./commands/init.js');
11
+ return initCommand;
12
+ }
10
13
  (0, vitest_1.describe)('Init Command Rules Verification', () => {
11
14
  const testDir = path_1.default.join(process.cwd(), 'temp-init-rules-test');
12
15
  (0, vitest_1.beforeEach)(async () => {
@@ -16,8 +19,9 @@ const path_1 = __importDefault(require("path"));
16
19
  await fs_extra_1.default.remove(testDir);
17
20
  });
18
21
  (0, vitest_1.it)('should create instructions with agnostic rules and cursor rules on init', async () => {
22
+ const initCommand = await getInitCommand();
19
23
  // Run init in test directory with all IDEs to verify rules in both locations
20
- await (0, init_js_1.initCommand)(testDir, { ide: 'all' });
24
+ await initCommand(testDir, { ide: 'all' });
21
25
  const instructionsPath = path_1.default.join(testDir, 'docs', 'AGENT_INSTRUCTIONS.md');
22
26
  const mdcPath = path_1.default.join(testDir, '.cursor', 'rules', 'rigour.mdc');
23
27
  (0, vitest_1.expect)(await fs_extra_1.default.pathExists(instructionsPath)).toBe(true);
@@ -34,7 +38,8 @@ const path_1 = __importDefault(require("path"));
34
38
  (0, vitest_1.expect)(mdcContent).toContain('# 🤖 CRITICAL INSTRUCTION FOR AI');
35
39
  });
36
40
  (0, vitest_1.it)('should create .clinerules when ide is cline or all', async () => {
37
- await (0, init_js_1.initCommand)(testDir, { ide: 'cline' });
41
+ const initCommand = await getInitCommand();
42
+ await initCommand(testDir, { ide: 'cline' });
38
43
  const clineRulesPath = path_1.default.join(testDir, '.clinerules');
39
44
  (0, vitest_1.expect)(await fs_extra_1.default.pathExists(clineRulesPath)).toBe(true);
40
45
  const content = await fs_extra_1.default.readFile(clineRulesPath, 'utf-8');
@@ -4,9 +4,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const vitest_1 = require("vitest");
7
- const check_js_1 = require("./commands/check.js");
8
7
  const fs_extra_1 = __importDefault(require("fs-extra"));
9
8
  const path_1 = __importDefault(require("path"));
9
+ async function getCheckCommand() {
10
+ const { checkCommand } = await import('./commands/check.js');
11
+ return checkCommand;
12
+ }
10
13
  (0, vitest_1.describe)('CLI Smoke Test', () => {
11
14
  const testDir = path_1.default.join(process.cwd(), 'temp-smoke-test');
12
15
  (0, vitest_1.beforeEach)(async () => {
@@ -37,7 +40,8 @@ gates:
37
40
  // For now, we'll just verify it doesn't throw before it would exit (internal logic)
38
41
  // But checkCommand calls process.exit(1) on failure.
39
42
  // Re-importing checkCommand to ensure it uses the latest core
40
- await (0, vitest_1.expect)((0, check_js_1.checkCommand)(testDir, [], { ci: true })).resolves.not.toThrow();
43
+ const checkCommand = await getCheckCommand();
44
+ await (0, vitest_1.expect)(checkCommand(testDir, [], { ci: true })).resolves.not.toThrow();
41
45
  }
42
46
  finally {
43
47
  await fs_extra_1.default.chmod(restrictedDir, 0o777);
@@ -53,11 +57,13 @@ gates:
53
57
  required_files: []
54
58
  `);
55
59
  // If we check ONLY good.js, it should PASS (exit PASS)
56
- await (0, check_js_1.checkCommand)(testDir, [path_1.default.join(testDir, 'good.js')], { ci: true });
60
+ const checkCommand = await getCheckCommand();
61
+ await checkCommand(testDir, [path_1.default.join(testDir, 'good.js')], { ci: true });
57
62
  (0, vitest_1.expect)(process.exit).toHaveBeenCalledWith(0);
58
63
  // If we check bad.js, it should FAIL (exit FAIL)
59
64
  vitest_1.vi.clearAllMocks();
60
- await (0, check_js_1.checkCommand)(testDir, [path_1.default.join(testDir, 'bad.js')], { ci: true });
65
+ const checkCommandFail = await getCheckCommand();
66
+ await checkCommandFail(testDir, [path_1.default.join(testDir, 'bad.js')], { ci: true });
61
67
  (0, vitest_1.expect)(process.exit).toHaveBeenCalledWith(1);
62
68
  });
63
69
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigour-labs/cli",
3
- "version": "2.9.3",
3
+ "version": "2.10.0",
4
4
  "bin": {
5
5
  "rigour": "dist/cli.js"
6
6
  },
@@ -20,8 +20,9 @@
20
20
  "fs-extra": "^11.2.0",
21
21
  "globby": "^14.0.1",
22
22
  "inquirer": "9.2.16",
23
+ "ora": "^8.0.1",
23
24
  "yaml": "^2.8.2",
24
- "@rigour-labs/core": "2.9.3"
25
+ "@rigour-labs/core": "2.10.0"
25
26
  },
26
27
  "devDependencies": {
27
28
  "@types/fs-extra": "^11.0.4",
package/src/cli.ts CHANGED
@@ -6,10 +6,13 @@ import { explainCommand } from './commands/explain.js';
6
6
  import { runLoop } from './commands/run.js';
7
7
  import { guideCommand } from './commands/guide.js';
8
8
  import { setupCommand } from './commands/setup.js';
9
+ import { indexCommand } from './commands/index.js';
9
10
  import chalk from 'chalk';
10
11
 
11
12
  const program = new Command();
12
13
 
14
+ program.addCommand(indexCommand);
15
+
13
16
  program
14
17
  .name('rigour')
15
18
  .description('🛡️ Rigour: The Quality Gate Loop for AI-Assisted Engineering')
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Index Command
3
+ *
4
+ * Builds and updates the Rigour Pattern Index to prevent code reinvention.
5
+ */
6
+
7
+ import { Command } from 'commander';
8
+ import path from 'path';
9
+ import chalk from 'chalk';
10
+ import ora from 'ora';
11
+ // Dynamic imports are used inside the action handler below to prevent
12
+ // native dependency issues from affecting the rest of the CLI.
13
+
14
+ export const indexCommand = new Command('index')
15
+ .description('Build or update the pattern index for the current project')
16
+ .option('-s, --semantic', 'Generate semantic embeddings for better matching (requires Transformers.js)', false)
17
+ .option('-f, --force', 'Force a full rebuild of the index', false)
18
+ .option('-o, --output <path>', 'Custom path for the index file')
19
+ .action(async (options) => {
20
+ const cwd = process.cwd();
21
+
22
+ // Dynamic import to isolate native dependencies
23
+ const {
24
+ PatternIndexer,
25
+ savePatternIndex,
26
+ loadPatternIndex,
27
+ getDefaultIndexPath
28
+ } = await import('@rigour-labs/core/pattern-index');
29
+
30
+ const indexPath = options.output || getDefaultIndexPath(cwd);
31
+ const spinner = ora('Initializing pattern indexer...').start();
32
+
33
+ try {
34
+ const indexer = new PatternIndexer(cwd, {
35
+ useEmbeddings: options.semantic
36
+ });
37
+
38
+ let index;
39
+ const existingIndex = await loadPatternIndex(indexPath);
40
+
41
+ if (existingIndex && !options.force) {
42
+ spinner.text = 'Updating existing pattern index...';
43
+ index = await indexer.updateIndex(existingIndex);
44
+ } else {
45
+ spinner.text = 'Building fresh pattern index (this may take a while)...';
46
+ index = await indexer.buildIndex();
47
+ }
48
+
49
+ spinner.text = 'Saving index to disk...';
50
+ await savePatternIndex(index, indexPath);
51
+
52
+ spinner.succeed(chalk.green(`Pattern index built successfully!`));
53
+ console.log(chalk.blue(`- Total Patterns: ${index.stats.totalPatterns}`));
54
+ console.log(chalk.blue(`- Total Files: ${index.stats.totalFiles}`));
55
+ console.log(chalk.blue(`- Index Path: ${indexPath}`));
56
+
57
+ if (options.semantic) {
58
+ console.log(chalk.magenta(`- Semantic Search: Enabled (Local Transformers.js)`));
59
+ }
60
+
61
+ const byType = Object.entries(index.stats.byType)
62
+ .map(([type, count]) => `${type}: ${count}`)
63
+ .join(', ');
64
+ console.log(chalk.gray(`Types: ${byType}`));
65
+
66
+ } catch (error: any) {
67
+ spinner.fail(chalk.red(`Failed to build pattern index: ${error.message}`));
68
+ process.exit(1);
69
+ }
70
+ });
@@ -1,8 +1,14 @@
1
1
  import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import { initCommand } from './commands/init.js';
2
+
3
+
3
4
  import fs from 'fs-extra';
4
5
  import path from 'path';
5
6
 
7
+ async function getInitCommand() {
8
+ const { initCommand } = await import('./commands/init.js');
9
+ return initCommand;
10
+ }
11
+
6
12
  describe('Init Command Rules Verification', () => {
7
13
  const testDir = path.join(process.cwd(), 'temp-init-rules-test');
8
14
 
@@ -15,6 +21,7 @@ describe('Init Command Rules Verification', () => {
15
21
  });
16
22
 
17
23
  it('should create instructions with agnostic rules and cursor rules on init', async () => {
24
+ const initCommand = await getInitCommand();
18
25
  // Run init in test directory with all IDEs to verify rules in both locations
19
26
  await initCommand(testDir, { ide: 'all' });
20
27
 
@@ -40,6 +47,7 @@ describe('Init Command Rules Verification', () => {
40
47
  });
41
48
 
42
49
  it('should create .clinerules when ide is cline or all', async () => {
50
+ const initCommand = await getInitCommand();
43
51
  await initCommand(testDir, { ide: 'cline' });
44
52
  const clineRulesPath = path.join(testDir, '.clinerules');
45
53
  expect(await fs.pathExists(clineRulesPath)).toBe(true);
package/src/smoke.test.ts CHANGED
@@ -1,8 +1,13 @@
1
1
  import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import { checkCommand } from './commands/check.js';
2
+
3
3
  import fs from 'fs-extra';
4
4
  import path from 'path';
5
5
 
6
+ async function getCheckCommand() {
7
+ const { checkCommand } = await import('./commands/check.js');
8
+ return checkCommand;
9
+ }
10
+
6
11
  describe('CLI Smoke Test', () => {
7
12
  const testDir = path.join(process.cwd(), 'temp-smoke-test');
8
13
 
@@ -40,6 +45,7 @@ gates:
40
45
  // But checkCommand calls process.exit(1) on failure.
41
46
 
42
47
  // Re-importing checkCommand to ensure it uses the latest core
48
+ const checkCommand = await getCheckCommand();
43
49
  await expect(checkCommand(testDir, [], { ci: true })).resolves.not.toThrow();
44
50
  } finally {
45
51
  await fs.chmod(restrictedDir, 0o777);
@@ -57,12 +63,14 @@ gates:
57
63
  `);
58
64
 
59
65
  // If we check ONLY good.js, it should PASS (exit PASS)
66
+ const checkCommand = await getCheckCommand();
60
67
  await checkCommand(testDir, [path.join(testDir, 'good.js')], { ci: true });
61
68
  expect(process.exit).toHaveBeenCalledWith(0);
62
69
 
63
70
  // If we check bad.js, it should FAIL (exit FAIL)
64
71
  vi.clearAllMocks();
65
- await checkCommand(testDir, [path.join(testDir, 'bad.js')], { ci: true });
72
+ const checkCommandFail = await getCheckCommand();
73
+ await checkCommandFail(testDir, [path.join(testDir, 'bad.js')], { ci: true });
66
74
  expect(process.exit).toHaveBeenCalledWith(1);
67
75
  });
68
76
  });
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ setupFiles: ['./vitest.setup.ts'],
6
+ deps: {
7
+ external: ['@xenova/transformers', 'sharp'],
8
+ },
9
+ },
10
+ });
@@ -0,0 +1,30 @@
1
+ import { vi } from 'vitest';
2
+
3
+ // Mock Transformers.js to avoid native binary dependency issues and speed up tests
4
+ vi.mock('@xenova/transformers', () => ({
5
+ pipeline: async () => {
6
+ // Return a mock extractor that produces deterministic "embeddings"
7
+ return async (text: string) => {
8
+ // Create a fake vector based on the text length or hash
9
+ const vector = new Array(384).fill(0);
10
+ for (let i = 0; i < Math.min(text.length, 384); i++) {
11
+ vector[i] = text.charCodeAt(i) / 255;
12
+ }
13
+ return { data: new Float32Array(vector) };
14
+ };
15
+ },
16
+ env: {
17
+ allowImageProcessors: false,
18
+ },
19
+ }));
20
+
21
+ // Also mock sharp just in case something else pulls it in
22
+ vi.mock('sharp', () => ({
23
+ default: () => ({
24
+ resize: () => ({
25
+ toFormat: () => ({
26
+ toBuffer: async () => Buffer.from([]),
27
+ }),
28
+ }),
29
+ }),
30
+ }));