@rigour-labs/cli 2.11.0 → 2.13.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/commands/studio.js +249 -229
- package/package.json +9 -3
- package/studio-dist/assets/index-CSj2lLc7.css +1 -0
- package/studio-dist/assets/index-CmJzYc99.js +259 -0
- package/studio-dist/index.html +17 -0
- package/src/cli.ts +0 -112
- package/src/commands/check.ts +0 -215
- package/src/commands/constants.ts +0 -209
- package/src/commands/explain.ts +0 -75
- package/src/commands/guide.ts +0 -21
- package/src/commands/index.ts +0 -105
- package/src/commands/init.ts +0 -413
- package/src/commands/run.ts +0 -135
- package/src/commands/setup.ts +0 -28
- package/src/commands/studio.ts +0 -273
- package/src/init-rules.test.ts +0 -59
- package/src/smoke.test.ts +0 -76
- package/src/templates/handshake.mdc +0 -36
- package/tsconfig.json +0 -10
- package/vitest.config.ts +0 -10
- package/vitest.setup.ts +0 -30
package/src/commands/index.ts
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
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
|
-
import fs from 'fs-extra';
|
|
12
|
-
import { randomUUID } from 'crypto';
|
|
13
|
-
|
|
14
|
-
// Helper to log events for Rigour Studio
|
|
15
|
-
async function logStudioEvent(cwd: string, event: any) {
|
|
16
|
-
try {
|
|
17
|
-
const rigourDir = path.join(cwd, ".rigour");
|
|
18
|
-
await fs.ensureDir(rigourDir);
|
|
19
|
-
const eventsPath = path.join(rigourDir, "events.jsonl");
|
|
20
|
-
const logEntry = JSON.stringify({
|
|
21
|
-
id: randomUUID(),
|
|
22
|
-
timestamp: new Date().toISOString(),
|
|
23
|
-
...event
|
|
24
|
-
}) + "\n";
|
|
25
|
-
await fs.appendFile(eventsPath, logEntry);
|
|
26
|
-
} catch {
|
|
27
|
-
// Silent fail
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
// Dynamic imports are used inside the action handler below to prevent
|
|
31
|
-
// native dependency issues from affecting the rest of the CLI.
|
|
32
|
-
|
|
33
|
-
export const indexCommand = new Command('index')
|
|
34
|
-
.description('Build or update the pattern index for the current project')
|
|
35
|
-
.option('-s, --semantic', 'Generate semantic embeddings for better matching (requires Transformers.js)', false)
|
|
36
|
-
.option('-f, --force', 'Force a full rebuild of the index', false)
|
|
37
|
-
.option('-o, --output <path>', 'Custom path for the index file')
|
|
38
|
-
.action(async (options) => {
|
|
39
|
-
const cwd = process.cwd();
|
|
40
|
-
|
|
41
|
-
// Dynamic import to isolate native dependencies
|
|
42
|
-
const {
|
|
43
|
-
PatternIndexer,
|
|
44
|
-
savePatternIndex,
|
|
45
|
-
loadPatternIndex,
|
|
46
|
-
getDefaultIndexPath
|
|
47
|
-
} = await import('@rigour-labs/core/pattern-index');
|
|
48
|
-
|
|
49
|
-
const indexPath = options.output || getDefaultIndexPath(cwd);
|
|
50
|
-
const spinner = ora('Initializing pattern indexer...').start();
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
const requestId = randomUUID();
|
|
54
|
-
await logStudioEvent(cwd, {
|
|
55
|
-
type: "tool_call",
|
|
56
|
-
requestId,
|
|
57
|
-
tool: "rigour_index",
|
|
58
|
-
arguments: options
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const indexer = new PatternIndexer(cwd, {
|
|
62
|
-
useEmbeddings: options.semantic
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
let index;
|
|
66
|
-
const existingIndex = await loadPatternIndex(indexPath);
|
|
67
|
-
|
|
68
|
-
if (existingIndex && !options.force) {
|
|
69
|
-
spinner.text = 'Updating existing pattern index...';
|
|
70
|
-
index = await indexer.updateIndex(existingIndex);
|
|
71
|
-
} else {
|
|
72
|
-
spinner.text = 'Building fresh pattern index (this may take a while)...';
|
|
73
|
-
index = await indexer.buildIndex();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
spinner.text = 'Saving index to disk...';
|
|
77
|
-
await savePatternIndex(index, indexPath);
|
|
78
|
-
|
|
79
|
-
spinner.succeed(chalk.green(`Pattern index built successfully!`));
|
|
80
|
-
|
|
81
|
-
await logStudioEvent(cwd, {
|
|
82
|
-
type: "tool_response",
|
|
83
|
-
requestId,
|
|
84
|
-
tool: "rigour_index",
|
|
85
|
-
status: "success",
|
|
86
|
-
content: [{ type: "text", text: `Index built: ${index.stats.totalPatterns} patterns` }]
|
|
87
|
-
});
|
|
88
|
-
console.log(chalk.blue(`- Total Patterns: ${index.stats.totalPatterns}`));
|
|
89
|
-
console.log(chalk.blue(`- Total Files: ${index.stats.totalFiles}`));
|
|
90
|
-
console.log(chalk.blue(`- Index Path: ${indexPath}`));
|
|
91
|
-
|
|
92
|
-
if (options.semantic) {
|
|
93
|
-
console.log(chalk.magenta(`- Semantic Search: Enabled (Local Transformers.js)`));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const byType = Object.entries(index.stats.byType)
|
|
97
|
-
.map(([type, count]) => `${type}: ${count}`)
|
|
98
|
-
.join(', ');
|
|
99
|
-
console.log(chalk.gray(`Types: ${byType}`));
|
|
100
|
-
|
|
101
|
-
} catch (error: any) {
|
|
102
|
-
spinner.fail(chalk.red(`Failed to build pattern index: ${error.message}`));
|
|
103
|
-
process.exit(1);
|
|
104
|
-
}
|
|
105
|
-
});
|
package/src/commands/init.ts
DELETED
|
@@ -1,413 +0,0 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import yaml from 'yaml';
|
|
5
|
-
import { DiscoveryService } from '@rigour-labs/core';
|
|
6
|
-
import { CODE_QUALITY_RULES, DEBUGGING_RULES, COLLABORATION_RULES, AGNOSTIC_AI_INSTRUCTIONS } from './constants.js';
|
|
7
|
-
import { randomUUID } from 'crypto';
|
|
8
|
-
|
|
9
|
-
// Helper to log events for Rigour Studio
|
|
10
|
-
async function logStudioEvent(cwd: string, event: any) {
|
|
11
|
-
try {
|
|
12
|
-
const rigourDir = path.join(cwd, ".rigour");
|
|
13
|
-
await fs.ensureDir(rigourDir);
|
|
14
|
-
const eventsPath = path.join(rigourDir, "events.jsonl");
|
|
15
|
-
const logEntry = JSON.stringify({
|
|
16
|
-
id: randomUUID(),
|
|
17
|
-
timestamp: new Date().toISOString(),
|
|
18
|
-
...event
|
|
19
|
-
}) + "\n";
|
|
20
|
-
await fs.appendFile(eventsPath, logEntry);
|
|
21
|
-
} catch {
|
|
22
|
-
// Silent fail
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface InitOptions {
|
|
27
|
-
preset?: string;
|
|
28
|
-
paradigm?: string;
|
|
29
|
-
ide?: 'cursor' | 'vscode' | 'cline' | 'claude' | 'gemini' | 'codex' | 'windsurf' | 'all';
|
|
30
|
-
dryRun?: boolean;
|
|
31
|
-
explain?: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
type DetectedIDE = 'cursor' | 'vscode' | 'cline' | 'claude' | 'gemini' | 'codex' | 'windsurf' | 'unknown';
|
|
35
|
-
|
|
36
|
-
function detectIDE(cwd: string): DetectedIDE {
|
|
37
|
-
// Check for Claude Code markers
|
|
38
|
-
if (fs.existsSync(path.join(cwd, 'CLAUDE.md')) || fs.existsSync(path.join(cwd, '.claude'))) {
|
|
39
|
-
return 'claude';
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Check for Gemini Code Assist markers
|
|
43
|
-
if (fs.existsSync(path.join(cwd, '.gemini'))) {
|
|
44
|
-
return 'gemini';
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Check for Codex/Aider AGENTS.md (universal standard)
|
|
48
|
-
if (fs.existsSync(path.join(cwd, 'AGENTS.md'))) {
|
|
49
|
-
return 'codex';
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Check for Windsurf markers
|
|
53
|
-
if (fs.existsSync(path.join(cwd, '.windsurfrules')) || fs.existsSync(path.join(cwd, '.windsurf'))) {
|
|
54
|
-
return 'windsurf';
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Check for Cline-specific markers
|
|
58
|
-
if (fs.existsSync(path.join(cwd, '.clinerules'))) {
|
|
59
|
-
return 'cline';
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Check for Cursor-specific markers
|
|
63
|
-
if (fs.existsSync(path.join(cwd, '.cursor'))) {
|
|
64
|
-
return 'cursor';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Check for VS Code markers
|
|
68
|
-
if (fs.existsSync(path.join(cwd, '.vscode'))) {
|
|
69
|
-
return 'vscode';
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Check environment variables that IDEs/Agents set
|
|
73
|
-
const termProgram = process.env.TERM_PROGRAM || '';
|
|
74
|
-
const terminal = process.env.TERMINAL_EMULATOR || '';
|
|
75
|
-
const appName = process.env.APP_NAME || '';
|
|
76
|
-
|
|
77
|
-
if (termProgram.toLowerCase().includes('cursor') || terminal.toLowerCase().includes('cursor')) {
|
|
78
|
-
return 'cursor';
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (termProgram.toLowerCase().includes('cline') || appName.toLowerCase().includes('cline')) {
|
|
82
|
-
return 'cline';
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (termProgram.toLowerCase().includes('vscode') || process.env.VSCODE_INJECTION) {
|
|
86
|
-
return 'vscode';
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Check for Claude Code environment
|
|
90
|
-
if (process.env.CLAUDE_CODE || process.env.ANTHROPIC_API_KEY) {
|
|
91
|
-
return 'claude';
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Check for Gemini environment
|
|
95
|
-
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_CLOUD_PROJECT) {
|
|
96
|
-
return 'gemini';
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return 'unknown';
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
export async function initCommand(cwd: string, options: InitOptions = {}) {
|
|
104
|
-
const discovery = new DiscoveryService();
|
|
105
|
-
const result = await discovery.discover(cwd);
|
|
106
|
-
let recommendedConfig = result.config;
|
|
107
|
-
|
|
108
|
-
// Override with user options if provided and re-apply template logic if necessary
|
|
109
|
-
if (options.preset || options.paradigm) {
|
|
110
|
-
const core = await import('@rigour-labs/core');
|
|
111
|
-
|
|
112
|
-
let customBase = { ...core.UNIVERSAL_CONFIG };
|
|
113
|
-
|
|
114
|
-
if (options.preset) {
|
|
115
|
-
const t = core.TEMPLATES.find((t: any) => t.name === options.preset);
|
|
116
|
-
if (t) customBase = (discovery as any).mergeConfig(customBase, t.config);
|
|
117
|
-
} else if (recommendedConfig.preset) {
|
|
118
|
-
const t = core.TEMPLATES.find((t: any) => t.name === recommendedConfig.preset);
|
|
119
|
-
if (t) customBase = (discovery as any).mergeConfig(customBase, t.config);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (options.paradigm) {
|
|
123
|
-
const t = core.PARADIGM_TEMPLATES.find((t: any) => t.name === options.paradigm);
|
|
124
|
-
if (t) customBase = (discovery as any).mergeConfig(customBase, t.config);
|
|
125
|
-
} else if (recommendedConfig.paradigm) {
|
|
126
|
-
const t = core.PARADIGM_TEMPLATES.find((t: any) => t.name === recommendedConfig.paradigm);
|
|
127
|
-
if (t) customBase = (discovery as any).mergeConfig(customBase, t.config);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
recommendedConfig = customBase;
|
|
131
|
-
if (options.preset) recommendedConfig.preset = options.preset;
|
|
132
|
-
if (options.paradigm) recommendedConfig.paradigm = options.paradigm;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (options.dryRun || options.explain) {
|
|
136
|
-
console.log(chalk.bold.blue('\n🔍 Rigour Auto-Discovery (Dry Run):'));
|
|
137
|
-
if (recommendedConfig.preset) {
|
|
138
|
-
console.log(chalk.cyan(` Role: `) + chalk.bold(recommendedConfig.preset.toUpperCase()));
|
|
139
|
-
if (options.explain && result.matches.preset) {
|
|
140
|
-
console.log(chalk.dim(` (Marker found: ${result.matches.preset.marker})`));
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
if (recommendedConfig.paradigm) {
|
|
144
|
-
console.log(chalk.cyan(` Paradigm: `) + chalk.bold(recommendedConfig.paradigm.toUpperCase()));
|
|
145
|
-
if (options.explain && result.matches.paradigm) {
|
|
146
|
-
console.log(chalk.dim(` (Marker found: ${result.matches.paradigm.marker})`));
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
console.log(chalk.yellow('\n[DRY RUN] No files will be written.'));
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const configPath = path.join(cwd, 'rigour.yml');
|
|
154
|
-
|
|
155
|
-
if (await fs.pathExists(configPath)) {
|
|
156
|
-
console.log(chalk.yellow('rigour.yml already exists. Skipping initialization.'));
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
console.log(chalk.bold.blue('\n🔍 Rigour Auto-Discovery:'));
|
|
161
|
-
|
|
162
|
-
const requestId = randomUUID();
|
|
163
|
-
await logStudioEvent(cwd, {
|
|
164
|
-
type: "tool_call",
|
|
165
|
-
requestId,
|
|
166
|
-
tool: "rigour_init",
|
|
167
|
-
arguments: options
|
|
168
|
-
});
|
|
169
|
-
if (recommendedConfig.preset) {
|
|
170
|
-
console.log(chalk.cyan(` Role: `) + chalk.bold(recommendedConfig.preset.toUpperCase()));
|
|
171
|
-
}
|
|
172
|
-
if (recommendedConfig.paradigm) {
|
|
173
|
-
console.log(chalk.cyan(` Paradigm: `) + chalk.bold(recommendedConfig.paradigm.toUpperCase()));
|
|
174
|
-
}
|
|
175
|
-
console.log('');
|
|
176
|
-
|
|
177
|
-
const yamlHeader = `# ⚠️ TEAM STANDARD - DO NOT MODIFY WITHOUT TEAM APPROVAL
|
|
178
|
-
# AI Assistants: Adjust YOUR code to meet these standards, not the other way around.
|
|
179
|
-
# Modifying thresholds or adding ignores to pass checks defeats the purpose of Rigour.
|
|
180
|
-
# See: docs/AGENT_INSTRUCTIONS.md for the correct workflow.
|
|
181
|
-
|
|
182
|
-
`;
|
|
183
|
-
await fs.writeFile(configPath, yamlHeader + yaml.stringify(recommendedConfig));
|
|
184
|
-
console.log(chalk.green('✔ Created rigour.yml'));
|
|
185
|
-
|
|
186
|
-
// Create required directories and files
|
|
187
|
-
const requireddocs = recommendedConfig.gates.required_files || [];
|
|
188
|
-
for (const file of requireddocs) {
|
|
189
|
-
const filePath = path.join(cwd, file);
|
|
190
|
-
if (!(await fs.pathExists(filePath))) {
|
|
191
|
-
await fs.ensureDir(path.dirname(filePath)); // Ensure parent directory exists
|
|
192
|
-
await fs.ensureFile(filePath);
|
|
193
|
-
console.log(chalk.dim(` - Created ${file}`));
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Agent Handshake (Universal / AntiGravity / Cursor)
|
|
198
|
-
const rigourDocsDir = path.join(cwd, 'docs');
|
|
199
|
-
await fs.ensureDir(rigourDocsDir);
|
|
200
|
-
const instructionsPath = path.join(rigourDocsDir, 'AGENT_INSTRUCTIONS.md');
|
|
201
|
-
|
|
202
|
-
const ruleContent = `# 🛡️ Rigour: Mandatory Engineering Governance Protocol
|
|
203
|
-
|
|
204
|
-
**You are an AI Engineering Agent governed by Rigour.**
|
|
205
|
-
|
|
206
|
-
Your access to this codebase is conditional upon your strict adherence to the **Rigour Governance Protocol**. This is an automated requirement for every engineering task.
|
|
207
|
-
|
|
208
|
-
## 🚦 The Mandatory Governance Loop
|
|
209
|
-
You MUST execute this loop for every task. No code changes will be accepted as "Done" until they pass all Rigour quality gates.
|
|
210
|
-
|
|
211
|
-
1. **Context Loading**: Call \`rigour_recall\` (MCP) to align with project-specific engineering memory.
|
|
212
|
-
2. **Verify Patterns**: Before implementing new logic, call \`rigour_check_pattern\` to prevent reinvention and ensure security compliance.
|
|
213
|
-
3. **Audit Enforcement**: Before finishing, you MUST call \`npx @rigour-labs/cli check\`.
|
|
214
|
-
4. **Failure Remediation**: If the check returns **FAIL**, you MUST implement all required engineering refinements.
|
|
215
|
-
5. **PASS or REJECT**: You may only declare a task complete when the check returns **PASS**.
|
|
216
|
-
|
|
217
|
-
## ⚖️ Transparency & Accountability
|
|
218
|
-
**Every action, tool call, and logic shift you perform is automatically logged to the project's local audit trail.** This data is visible in real-time in the human-monitored **Rigour Studio**.
|
|
219
|
-
|
|
220
|
-
${AGNOSTIC_AI_INSTRUCTIONS}
|
|
221
|
-
${CODE_QUALITY_RULES}
|
|
222
|
-
${DEBUGGING_RULES}
|
|
223
|
-
${COLLABORATION_RULES}
|
|
224
|
-
`;
|
|
225
|
-
|
|
226
|
-
// 1. Create Universal Instructions
|
|
227
|
-
if (!(await fs.pathExists(instructionsPath))) {
|
|
228
|
-
await fs.writeFile(instructionsPath, ruleContent);
|
|
229
|
-
console.log(chalk.green('✔ Initialized Universal Agent Handshake (docs/AGENT_INSTRUCTIONS.md)'));
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// 2. Create IDE-Specific Rules based on detection or user preference
|
|
233
|
-
const detectedIDE = detectIDE(cwd);
|
|
234
|
-
const targetIDE = options.ide || (detectedIDE !== 'unknown' ? detectedIDE : 'all');
|
|
235
|
-
|
|
236
|
-
if (detectedIDE !== 'unknown' && !options.ide) {
|
|
237
|
-
console.log(chalk.dim(` (Auto-detected IDE: ${detectedIDE})`));
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (targetIDE === 'cursor' || targetIDE === 'all') {
|
|
241
|
-
const cursorRulesDir = path.join(cwd, '.cursor', 'rules');
|
|
242
|
-
await fs.ensureDir(cursorRulesDir);
|
|
243
|
-
const mdcPath = path.join(cursorRulesDir, 'rigour.mdc');
|
|
244
|
-
const mdcContent = `---
|
|
245
|
-
description: Enforcement of Rigour quality gates and best practices.
|
|
246
|
-
globs: **/*
|
|
247
|
-
---
|
|
248
|
-
|
|
249
|
-
${ruleContent}`;
|
|
250
|
-
|
|
251
|
-
if (!(await fs.pathExists(mdcPath))) {
|
|
252
|
-
await fs.writeFile(mdcPath, mdcContent);
|
|
253
|
-
console.log(chalk.green('✔ Initialized Cursor Handshake (.cursor/rules/rigour.mdc)'));
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (targetIDE === 'vscode' || targetIDE === 'all') {
|
|
258
|
-
// VS Code users use the universal AGENT_INSTRUCTIONS.md (already created above)
|
|
259
|
-
// We could also add .vscode/settings.json or snippets here if needed
|
|
260
|
-
console.log(chalk.green('✔ VS Code mode - using Universal Handshake (docs/AGENT_INSTRUCTIONS.md)'));
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (targetIDE === 'cline' || targetIDE === 'all') {
|
|
264
|
-
const clineRulesPath = path.join(cwd, '.clinerules');
|
|
265
|
-
if (!(await fs.pathExists(clineRulesPath))) {
|
|
266
|
-
await fs.writeFile(clineRulesPath, ruleContent);
|
|
267
|
-
console.log(chalk.green('✔ Initialized Cline Handshake (.clinerules)'));
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Claude Code (CLAUDE.md)
|
|
272
|
-
if (targetIDE === 'claude' || targetIDE === 'all') {
|
|
273
|
-
const claudePath = path.join(cwd, 'CLAUDE.md');
|
|
274
|
-
const claudeContent = `# CLAUDE.md - Project Instructions for Claude Code
|
|
275
|
-
|
|
276
|
-
This file provides Claude Code with context about this project.
|
|
277
|
-
|
|
278
|
-
## Project Overview
|
|
279
|
-
|
|
280
|
-
This project uses Rigour for quality gates. Always run \`npx @rigour-labs/cli check\` before marking tasks complete.
|
|
281
|
-
|
|
282
|
-
## Commands
|
|
283
|
-
|
|
284
|
-
\`\`\`bash
|
|
285
|
-
# Verify quality gates
|
|
286
|
-
npx @rigour-labs/cli check
|
|
287
|
-
|
|
288
|
-
# Get fix packet for failures
|
|
289
|
-
npx @rigour-labs/cli explain
|
|
290
|
-
|
|
291
|
-
# Self-healing agent loop
|
|
292
|
-
npx @rigour-labs/cli run -- claude "<task>"
|
|
293
|
-
\`\`\`
|
|
294
|
-
|
|
295
|
-
${ruleContent}`;
|
|
296
|
-
|
|
297
|
-
if (!(await fs.pathExists(claudePath))) {
|
|
298
|
-
await fs.writeFile(claudePath, claudeContent);
|
|
299
|
-
console.log(chalk.green('✔ Initialized Claude Code Handshake (CLAUDE.md)'));
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Gemini Code Assist (.gemini/styleguide.md)
|
|
304
|
-
if (targetIDE === 'gemini' || targetIDE === 'all') {
|
|
305
|
-
const geminiDir = path.join(cwd, '.gemini');
|
|
306
|
-
await fs.ensureDir(geminiDir);
|
|
307
|
-
const geminiStylePath = path.join(geminiDir, 'styleguide.md');
|
|
308
|
-
const geminiContent = `# Gemini Code Assist Style Guide
|
|
309
|
-
|
|
310
|
-
This project uses Rigour for quality gates.
|
|
311
|
-
|
|
312
|
-
## Required Before Completion
|
|
313
|
-
|
|
314
|
-
Always run \`npx @rigour-labs/cli check\` before marking any task complete.
|
|
315
|
-
|
|
316
|
-
${ruleContent}`;
|
|
317
|
-
|
|
318
|
-
if (!(await fs.pathExists(geminiStylePath))) {
|
|
319
|
-
await fs.writeFile(geminiStylePath, geminiContent);
|
|
320
|
-
console.log(chalk.green('✔ Initialized Gemini Handshake (.gemini/styleguide.md)'));
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// OpenAI Codex / Aider (AGENTS.md - Universal Standard)
|
|
325
|
-
if (targetIDE === 'codex' || targetIDE === 'all') {
|
|
326
|
-
const agentsPath = path.join(cwd, 'AGENTS.md');
|
|
327
|
-
const agentsContent = `# AGENTS.md - Universal AI Agent Instructions
|
|
328
|
-
|
|
329
|
-
This file provides instructions for AI coding agents (Codex, Aider, and others).
|
|
330
|
-
|
|
331
|
-
## Setup
|
|
332
|
-
|
|
333
|
-
\`\`\`bash
|
|
334
|
-
npm install
|
|
335
|
-
npm run dev
|
|
336
|
-
npm test
|
|
337
|
-
\`\`\`
|
|
338
|
-
|
|
339
|
-
## Quality Gates
|
|
340
|
-
|
|
341
|
-
This project uses Rigour. Before completing any task:
|
|
342
|
-
|
|
343
|
-
\`\`\`bash
|
|
344
|
-
npx @rigour-labs/cli check
|
|
345
|
-
\`\`\`
|
|
346
|
-
|
|
347
|
-
${ruleContent}`;
|
|
348
|
-
|
|
349
|
-
if (!(await fs.pathExists(agentsPath))) {
|
|
350
|
-
await fs.writeFile(agentsPath, agentsContent);
|
|
351
|
-
console.log(chalk.green('✔ Initialized Universal Agent Handshake (AGENTS.md)'));
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Windsurf (.windsurfrules)
|
|
356
|
-
if (targetIDE === 'windsurf' || targetIDE === 'all') {
|
|
357
|
-
const windsurfPath = path.join(cwd, '.windsurfrules');
|
|
358
|
-
if (!(await fs.pathExists(windsurfPath))) {
|
|
359
|
-
await fs.writeFile(windsurfPath, ruleContent);
|
|
360
|
-
console.log(chalk.green('✔ Initialized Windsurf Handshake (.windsurfrules)'));
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// 3. Update .gitignore
|
|
365
|
-
const gitignorePath = path.join(cwd, '.gitignore');
|
|
366
|
-
const ignorePatterns = ['rigour-report.json', 'rigour-fix-packet.json', '.rigour/'];
|
|
367
|
-
try {
|
|
368
|
-
let content = '';
|
|
369
|
-
if (await fs.pathExists(gitignorePath)) {
|
|
370
|
-
content = await fs.readFile(gitignorePath, 'utf-8');
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const toAdd = ignorePatterns.filter(p => !content.includes(p));
|
|
374
|
-
if (toAdd.length > 0) {
|
|
375
|
-
const separator = content.endsWith('\n') ? '' : '\n';
|
|
376
|
-
const newContent = `${content}${separator}\n# Rigour Artifacts\n${toAdd.join('\n')}\n`;
|
|
377
|
-
await fs.writeFile(gitignorePath, newContent);
|
|
378
|
-
console.log(chalk.green('✔ Updated .gitignore'));
|
|
379
|
-
}
|
|
380
|
-
} catch (e) {
|
|
381
|
-
// Failing to update .gitignore isn't fatal
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
console.log(chalk.blue('\nRigour is ready. Run `npx @rigour-labs/cli check` to verify your project.'));
|
|
385
|
-
console.log(chalk.cyan('Next Step: ') + chalk.bold('rigour index') + chalk.dim(' (Populate the Pattern Index)'));
|
|
386
|
-
|
|
387
|
-
// Bootstrap initial memory for the Studio
|
|
388
|
-
const rigourDir = path.join(cwd, ".rigour");
|
|
389
|
-
await fs.ensureDir(rigourDir);
|
|
390
|
-
const memPath = path.join(rigourDir, "memory.json");
|
|
391
|
-
if (!(await fs.pathExists(memPath))) {
|
|
392
|
-
await fs.writeJson(memPath, {
|
|
393
|
-
memories: {
|
|
394
|
-
"project_boot": {
|
|
395
|
-
value: `Governance initiated via '${options.preset || 'api'}' preset. This project is now monitored by Rigour Studio.`,
|
|
396
|
-
timestamp: new Date().toISOString()
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}, { spaces: 2 });
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
console.log(chalk.dim('\n💡 Tip: Planning to use a framework like Next.js?'));
|
|
403
|
-
console.log(chalk.dim(' Run its scaffolding tool (e.g., npx create-next-app) BEFORE rigour init,'));
|
|
404
|
-
console.log(chalk.dim(' or move rigour.yml and docs/ aside temporarily to satisfy empty-directory checks.'));
|
|
405
|
-
|
|
406
|
-
await logStudioEvent(cwd, {
|
|
407
|
-
type: "tool_response",
|
|
408
|
-
requestId,
|
|
409
|
-
tool: "rigour_init",
|
|
410
|
-
status: "success",
|
|
411
|
-
content: [{ type: "text", text: `Rigour Governance Initialized` }]
|
|
412
|
-
});
|
|
413
|
-
}
|
package/src/commands/run.ts
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import yaml from 'yaml';
|
|
5
|
-
import { execa } from 'execa';
|
|
6
|
-
import { GateRunner, ConfigSchema } from '@rigour-labs/core';
|
|
7
|
-
|
|
8
|
-
// Exit codes per spec
|
|
9
|
-
const EXIT_PASS = 0;
|
|
10
|
-
const EXIT_FAIL = 1;
|
|
11
|
-
const EXIT_CONFIG_ERROR = 2;
|
|
12
|
-
const EXIT_INTERNAL_ERROR = 3;
|
|
13
|
-
|
|
14
|
-
export async function runLoop(cwd: string, agentArgs: string[], options: { iterations: number, failFast?: boolean }) {
|
|
15
|
-
const configPath = path.join(cwd, 'rigour.yml');
|
|
16
|
-
|
|
17
|
-
if (!(await fs.pathExists(configPath))) {
|
|
18
|
-
console.error(chalk.red('Error: rigour.yml not found. Run `rigour init` first.'));
|
|
19
|
-
process.exit(EXIT_CONFIG_ERROR);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
const configContent = await fs.readFile(configPath, 'utf-8');
|
|
24
|
-
const rawConfig = yaml.parse(configContent);
|
|
25
|
-
const config = ConfigSchema.parse(rawConfig);
|
|
26
|
-
const runner = new GateRunner(config);
|
|
27
|
-
|
|
28
|
-
let iteration = 0;
|
|
29
|
-
const maxIterations = options.iterations;
|
|
30
|
-
|
|
31
|
-
while (iteration < maxIterations) {
|
|
32
|
-
iteration++;
|
|
33
|
-
console.log(chalk.bold.blue(`\n══════════════════════════════════════════════════════════════════`));
|
|
34
|
-
console.log(chalk.bold.blue(` RIGOUR LOOP: Iteration ${iteration}/${maxIterations}`));
|
|
35
|
-
console.log(chalk.bold.blue(`══════════════════════════════════════════════════════════════════`));
|
|
36
|
-
|
|
37
|
-
// 1. Prepare Command
|
|
38
|
-
let currentArgs = [...agentArgs];
|
|
39
|
-
if (iteration > 1 && agentArgs.length > 0) {
|
|
40
|
-
// Iteration contract: In later cycles, we focus strictly on the fix packet
|
|
41
|
-
console.log(chalk.yellow(`\n🔄 REFINEMENT CYCLE - Instructing agent to fix specific violations...`));
|
|
42
|
-
// We keep the first part of the command (the agent) but can append or wrap
|
|
43
|
-
// For simplicity, we assume the agent can read the JSON file we generate
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const getTrackedChanges = async () => {
|
|
47
|
-
try {
|
|
48
|
-
const { stdout } = await execa('git', ['status', '--porcelain'], { cwd });
|
|
49
|
-
return stdout.split('\n')
|
|
50
|
-
.filter(l => l.trim())
|
|
51
|
-
.filter(line => /M|A|D|R/.test(line.slice(0, 2)))
|
|
52
|
-
.map(l => l.slice(3).trim());
|
|
53
|
-
} catch (e) {
|
|
54
|
-
return [];
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// Snapshot changed files before agent runs
|
|
59
|
-
const beforeFiles = await getTrackedChanges();
|
|
60
|
-
|
|
61
|
-
// 2. Run the agent command
|
|
62
|
-
if (currentArgs.length > 0) {
|
|
63
|
-
console.log(chalk.cyan(`\n🚀 DEPLOYING AGENT:`));
|
|
64
|
-
console.log(chalk.dim(` Command: ${currentArgs.join(' ')}`));
|
|
65
|
-
try {
|
|
66
|
-
await execa(currentArgs[0], currentArgs.slice(1), { shell: true, stdio: 'inherit', cwd });
|
|
67
|
-
} catch (error: any) {
|
|
68
|
-
console.warn(chalk.yellow(`\n⚠️ Agent command finished with non-zero exit code. Rigour will now verify state...`));
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Snapshot changed files after agent runs
|
|
73
|
-
const afterFiles = await getTrackedChanges();
|
|
74
|
-
|
|
75
|
-
const changedThisCycle = afterFiles.filter(f => !beforeFiles.includes(f));
|
|
76
|
-
const maxFiles = config.gates.safety?.max_files_changed_per_cycle || 10;
|
|
77
|
-
|
|
78
|
-
if (changedThisCycle.length > maxFiles) {
|
|
79
|
-
console.log(chalk.red.bold(`\n🛑 SAFETY RAIL ABORT: Agent changed ${changedThisCycle.length} files (max: ${maxFiles}).`));
|
|
80
|
-
console.log(chalk.red(` This looks like explosive behavior. Check your agent's instructions.`));
|
|
81
|
-
process.exit(EXIT_FAIL);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// 3. Run Rigour Check
|
|
85
|
-
console.log(chalk.magenta('\n🔍 AUDITING QUALITY GATES...'));
|
|
86
|
-
const report = await runner.run(cwd);
|
|
87
|
-
|
|
88
|
-
// Write report
|
|
89
|
-
const reportPath = path.join(cwd, config.output.report_path);
|
|
90
|
-
await fs.writeJson(reportPath, report, { spaces: 2 });
|
|
91
|
-
|
|
92
|
-
if (report.status === 'PASS') {
|
|
93
|
-
console.log(chalk.green.bold('\n✨ PASS - All quality gates satisfied.'));
|
|
94
|
-
console.log(chalk.green(` Your solution meets the required Engineering Rigour criteria.\n`));
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// 4. Generate Fix Packet v2
|
|
99
|
-
const { FixPacketService } = await import('@rigour-labs/core');
|
|
100
|
-
const fixPacketService = new FixPacketService();
|
|
101
|
-
const fixPacket = fixPacketService.generate(report, config);
|
|
102
|
-
const fixPacketPath = path.join(cwd, 'rigour-fix-packet.json');
|
|
103
|
-
await fs.writeJson(fixPacketPath, fixPacket, { spaces: 2 });
|
|
104
|
-
|
|
105
|
-
console.log(chalk.red.bold(`\n🛑 FAIL - Found ${report.failures.length} engineering violations.`));
|
|
106
|
-
console.log(chalk.dim(` Fix Packet generated: rigour-fix-packet.json`));
|
|
107
|
-
|
|
108
|
-
if (options.failFast) {
|
|
109
|
-
console.log(chalk.red.bold(`\n🛑 FAIL-FAST: Aborting loop as requested.`));
|
|
110
|
-
process.exit(EXIT_FAIL);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Print summary
|
|
114
|
-
const summary = report.failures.map((f, i) => {
|
|
115
|
-
return chalk.white(`${i + 1}. `) + chalk.bold.red(`[${f.id.toUpperCase()}] `) + chalk.white(f.title);
|
|
116
|
-
}).join('\n');
|
|
117
|
-
console.log(chalk.bold.white('\n📋 VIOLATIONS SUMMARY:'));
|
|
118
|
-
console.log(summary);
|
|
119
|
-
|
|
120
|
-
if (iteration === maxIterations) {
|
|
121
|
-
console.log(chalk.red.bold(`\n❌ CRITICAL: Reached maximum iterations (${maxIterations}).`));
|
|
122
|
-
console.log(chalk.red(` Quality gates remain unfulfilled. Refactor manually or check agent logs.`));
|
|
123
|
-
process.exit(EXIT_FAIL);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
console.log(chalk.dim('\nReturning control to agent for the next refinement cycle...'));
|
|
127
|
-
}
|
|
128
|
-
} catch (error: any) {
|
|
129
|
-
console.error(chalk.red(`\n❌ FATAL ERROR: ${error.message}`));
|
|
130
|
-
if (error.issues) {
|
|
131
|
-
console.error(chalk.dim(JSON.stringify(error.issues, null, 2)));
|
|
132
|
-
}
|
|
133
|
-
process.exit(EXIT_INTERNAL_ERROR);
|
|
134
|
-
}
|
|
135
|
-
}
|
package/src/commands/setup.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
|
|
3
|
-
export async function setupCommand() {
|
|
4
|
-
console.log(chalk.bold.cyan('\n🛠️ Rigour Labs | Setup & Installation\n'));
|
|
5
|
-
|
|
6
|
-
console.log(chalk.bold('1. Global Installation (Recommended)'));
|
|
7
|
-
console.log(chalk.dim(' To use Rigour anywhere in your terminal:'));
|
|
8
|
-
console.log(chalk.green(' $ npm install -g @rigour-labs/cli\n'));
|
|
9
|
-
|
|
10
|
-
console.log(chalk.bold('2. Project-Local installation'));
|
|
11
|
-
console.log(chalk.dim(' To keep Rigour versioned with your project:'));
|
|
12
|
-
console.log(chalk.green(' $ npm install --save-dev @rigour-labs/cli\n'));
|
|
13
|
-
|
|
14
|
-
console.log(chalk.bold('3. Standalone Binaries (Zero-Install)'));
|
|
15
|
-
console.log(chalk.dim(' If you do not want to use Node.js:'));
|
|
16
|
-
console.log(chalk.dim(' • macOS: ') + chalk.cyan('https://github.com/erashu212/rigour/releases/latest/download/rigour-macos'));
|
|
17
|
-
console.log(chalk.dim(' • Linux: ') + chalk.cyan('https://github.com/erashu212/rigour/releases/latest/download/rigour-linux'));
|
|
18
|
-
console.log(chalk.dim(' • Windows: ') + chalk.cyan('https://github.com/erashu212/rigour/releases/latest/download/rigour-windows.exe\n'));
|
|
19
|
-
|
|
20
|
-
console.log(chalk.bold('4. MCP Integration (for AI Agents)'));
|
|
21
|
-
console.log(chalk.dim(' To let Cursor or Claude use Rigour natively:'));
|
|
22
|
-
console.log(chalk.dim(' Path to MCP: ') + chalk.cyan('packages/rigour-mcp/dist/index.js'));
|
|
23
|
-
console.log(chalk.dim(' Add this to your Cursor/Claude settings.\n'));
|
|
24
|
-
|
|
25
|
-
console.log(chalk.bold('Update Guidance:'));
|
|
26
|
-
console.log(chalk.dim(' Keep Rigour sharp by updating regularly:'));
|
|
27
|
-
console.log(chalk.green(' $ npm install -g @rigour-labs/cli@latest\n'));
|
|
28
|
-
}
|