@rigour-labs/cli 2.10.0 → 2.12.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 +20 -23
- package/dist/commands/check.js +77 -50
- package/dist/commands/constants.js +4 -7
- package/dist/commands/explain.js +30 -36
- package/dist/commands/guide.js +15 -21
- package/dist/commands/index.js +46 -18
- package/dist/commands/init.js +142 -105
- package/dist/commands/run.js +42 -48
- package/dist/commands/setup.js +21 -27
- package/dist/commands/studio.d.ts +2 -0
- package/dist/commands/studio.js +292 -0
- package/dist/init-rules.test.js +26 -31
- package/dist/smoke.test.js +28 -33
- package/package.json +10 -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 -110
- package/src/commands/check.ts +0 -179
- 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 -70
- package/src/commands/init.ts +0 -366
- package/src/commands/run.ts +0 -135
- package/src/commands/setup.ts +0 -28
- package/src/init-rules.test.ts +0 -59
- package/src/smoke.test.ts +0 -76
- package/src/templates/handshake.mdc +0 -39
- package/tsconfig.json +0 -10
- package/vitest.config.ts +0 -10
- package/vitest.setup.ts +0 -30
package/src/commands/init.ts
DELETED
|
@@ -1,366 +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
|
-
|
|
8
|
-
export interface InitOptions {
|
|
9
|
-
preset?: string;
|
|
10
|
-
paradigm?: string;
|
|
11
|
-
ide?: 'cursor' | 'vscode' | 'cline' | 'claude' | 'gemini' | 'codex' | 'windsurf' | 'all';
|
|
12
|
-
dryRun?: boolean;
|
|
13
|
-
explain?: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
type DetectedIDE = 'cursor' | 'vscode' | 'cline' | 'claude' | 'gemini' | 'codex' | 'windsurf' | 'unknown';
|
|
17
|
-
|
|
18
|
-
function detectIDE(cwd: string): DetectedIDE {
|
|
19
|
-
// Check for Claude Code markers
|
|
20
|
-
if (fs.existsSync(path.join(cwd, 'CLAUDE.md')) || fs.existsSync(path.join(cwd, '.claude'))) {
|
|
21
|
-
return 'claude';
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Check for Gemini Code Assist markers
|
|
25
|
-
if (fs.existsSync(path.join(cwd, '.gemini'))) {
|
|
26
|
-
return 'gemini';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Check for Codex/Aider AGENTS.md (universal standard)
|
|
30
|
-
if (fs.existsSync(path.join(cwd, 'AGENTS.md'))) {
|
|
31
|
-
return 'codex';
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Check for Windsurf markers
|
|
35
|
-
if (fs.existsSync(path.join(cwd, '.windsurfrules')) || fs.existsSync(path.join(cwd, '.windsurf'))) {
|
|
36
|
-
return 'windsurf';
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Check for Cline-specific markers
|
|
40
|
-
if (fs.existsSync(path.join(cwd, '.clinerules'))) {
|
|
41
|
-
return 'cline';
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Check for Cursor-specific markers
|
|
45
|
-
if (fs.existsSync(path.join(cwd, '.cursor'))) {
|
|
46
|
-
return 'cursor';
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Check for VS Code markers
|
|
50
|
-
if (fs.existsSync(path.join(cwd, '.vscode'))) {
|
|
51
|
-
return 'vscode';
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Check environment variables that IDEs/Agents set
|
|
55
|
-
const termProgram = process.env.TERM_PROGRAM || '';
|
|
56
|
-
const terminal = process.env.TERMINAL_EMULATOR || '';
|
|
57
|
-
const appName = process.env.APP_NAME || '';
|
|
58
|
-
|
|
59
|
-
if (termProgram.toLowerCase().includes('cursor') || terminal.toLowerCase().includes('cursor')) {
|
|
60
|
-
return 'cursor';
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (termProgram.toLowerCase().includes('cline') || appName.toLowerCase().includes('cline')) {
|
|
64
|
-
return 'cline';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (termProgram.toLowerCase().includes('vscode') || process.env.VSCODE_INJECTION) {
|
|
68
|
-
return 'vscode';
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Check for Claude Code environment
|
|
72
|
-
if (process.env.CLAUDE_CODE || process.env.ANTHROPIC_API_KEY) {
|
|
73
|
-
return 'claude';
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Check for Gemini environment
|
|
77
|
-
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_CLOUD_PROJECT) {
|
|
78
|
-
return 'gemini';
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return 'unknown';
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
export async function initCommand(cwd: string, options: InitOptions = {}) {
|
|
86
|
-
const discovery = new DiscoveryService();
|
|
87
|
-
const result = await discovery.discover(cwd);
|
|
88
|
-
let recommendedConfig = result.config;
|
|
89
|
-
|
|
90
|
-
// Override with user options if provided and re-apply template logic if necessary
|
|
91
|
-
if (options.preset || options.paradigm) {
|
|
92
|
-
const core = await import('@rigour-labs/core');
|
|
93
|
-
|
|
94
|
-
let customBase = { ...core.UNIVERSAL_CONFIG };
|
|
95
|
-
|
|
96
|
-
if (options.preset) {
|
|
97
|
-
const t = core.TEMPLATES.find((t: any) => t.name === options.preset);
|
|
98
|
-
if (t) customBase = (discovery as any).mergeConfig(customBase, t.config);
|
|
99
|
-
} else if (recommendedConfig.preset) {
|
|
100
|
-
const t = core.TEMPLATES.find((t: any) => t.name === recommendedConfig.preset);
|
|
101
|
-
if (t) customBase = (discovery as any).mergeConfig(customBase, t.config);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (options.paradigm) {
|
|
105
|
-
const t = core.PARADIGM_TEMPLATES.find((t: any) => t.name === options.paradigm);
|
|
106
|
-
if (t) customBase = (discovery as any).mergeConfig(customBase, t.config);
|
|
107
|
-
} else if (recommendedConfig.paradigm) {
|
|
108
|
-
const t = core.PARADIGM_TEMPLATES.find((t: any) => t.name === recommendedConfig.paradigm);
|
|
109
|
-
if (t) customBase = (discovery as any).mergeConfig(customBase, t.config);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
recommendedConfig = customBase;
|
|
113
|
-
if (options.preset) recommendedConfig.preset = options.preset;
|
|
114
|
-
if (options.paradigm) recommendedConfig.paradigm = options.paradigm;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (options.dryRun || options.explain) {
|
|
118
|
-
console.log(chalk.bold.blue('\n🔍 Rigour Auto-Discovery (Dry Run):'));
|
|
119
|
-
if (recommendedConfig.preset) {
|
|
120
|
-
console.log(chalk.cyan(` Role: `) + chalk.bold(recommendedConfig.preset.toUpperCase()));
|
|
121
|
-
if (options.explain && result.matches.preset) {
|
|
122
|
-
console.log(chalk.dim(` (Marker found: ${result.matches.preset.marker})`));
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
if (recommendedConfig.paradigm) {
|
|
126
|
-
console.log(chalk.cyan(` Paradigm: `) + chalk.bold(recommendedConfig.paradigm.toUpperCase()));
|
|
127
|
-
if (options.explain && result.matches.paradigm) {
|
|
128
|
-
console.log(chalk.dim(` (Marker found: ${result.matches.paradigm.marker})`));
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
console.log(chalk.yellow('\n[DRY RUN] No files will be written.'));
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const configPath = path.join(cwd, 'rigour.yml');
|
|
136
|
-
|
|
137
|
-
if (await fs.pathExists(configPath)) {
|
|
138
|
-
console.log(chalk.yellow('rigour.yml already exists. Skipping initialization.'));
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
console.log(chalk.bold.blue('\n🔍 Rigour Auto-Discovery:'));
|
|
143
|
-
if (recommendedConfig.preset) {
|
|
144
|
-
console.log(chalk.cyan(` Role: `) + chalk.bold(recommendedConfig.preset.toUpperCase()));
|
|
145
|
-
}
|
|
146
|
-
if (recommendedConfig.paradigm) {
|
|
147
|
-
console.log(chalk.cyan(` Paradigm: `) + chalk.bold(recommendedConfig.paradigm.toUpperCase()));
|
|
148
|
-
}
|
|
149
|
-
console.log('');
|
|
150
|
-
|
|
151
|
-
const yamlHeader = `# ⚠️ TEAM STANDARD - DO NOT MODIFY WITHOUT TEAM APPROVAL
|
|
152
|
-
# AI Assistants: Adjust YOUR code to meet these standards, not the other way around.
|
|
153
|
-
# Modifying thresholds or adding ignores to pass checks defeats the purpose of Rigour.
|
|
154
|
-
# See: docs/AGENT_INSTRUCTIONS.md for the correct workflow.
|
|
155
|
-
|
|
156
|
-
`;
|
|
157
|
-
await fs.writeFile(configPath, yamlHeader + yaml.stringify(recommendedConfig));
|
|
158
|
-
console.log(chalk.green('✔ Created rigour.yml'));
|
|
159
|
-
|
|
160
|
-
// Create required directories and files
|
|
161
|
-
const requireddocs = recommendedConfig.gates.required_files || [];
|
|
162
|
-
for (const file of requireddocs) {
|
|
163
|
-
const filePath = path.join(cwd, file);
|
|
164
|
-
if (!(await fs.pathExists(filePath))) {
|
|
165
|
-
await fs.ensureFile(filePath);
|
|
166
|
-
console.log(chalk.dim(` - Created ${file}`));
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Agent Handshake (Universal / AntiGravity / Cursor)
|
|
171
|
-
const rigourDocsDir = path.join(cwd, 'docs');
|
|
172
|
-
await fs.ensureDir(rigourDocsDir);
|
|
173
|
-
const instructionsPath = path.join(rigourDocsDir, 'AGENT_INSTRUCTIONS.md');
|
|
174
|
-
|
|
175
|
-
const ruleContent = `# 🛡️ Rigour: Engineering Excellence Protocol
|
|
176
|
-
|
|
177
|
-
You are an Elite Software Engineer. You do not just write code that "works"; you write code that is **modular, maintainable, and rigorously verified.**
|
|
178
|
-
|
|
179
|
-
## 🚦 The Rigour Loop (Mandatory)
|
|
180
|
-
Before claiming "Done" for any task, you MUST follow this loop:
|
|
181
|
-
|
|
182
|
-
1. **Check**: Run \`npx @rigour-labs/cli check\` to verify compliance.
|
|
183
|
-
2. **Analyze**: If it fails, read \`rigour-fix-packet.json\` (V2 High-Fidelity) for exact failure points and constraints.
|
|
184
|
-
3. **Refactor**: Apply **SOLID** and **DRY** principles to resolve the violations according to constraints.
|
|
185
|
-
4. **Repeat**: Continue until \`npx @rigour-labs/cli check\` returns **PASS**.
|
|
186
|
-
|
|
187
|
-
## 🛠️ Commands
|
|
188
|
-
\`\`\`bash
|
|
189
|
-
# Verify current state
|
|
190
|
-
npx @rigour-labs/cli check
|
|
191
|
-
|
|
192
|
-
# Self-healing agent loop
|
|
193
|
-
npx @rigour-labs/cli run -- <agent-command>
|
|
194
|
-
\`\`\`
|
|
195
|
-
|
|
196
|
-
${AGNOSTIC_AI_INSTRUCTIONS}
|
|
197
|
-
${CODE_QUALITY_RULES}
|
|
198
|
-
|
|
199
|
-
${DEBUGGING_RULES}
|
|
200
|
-
|
|
201
|
-
${COLLABORATION_RULES}
|
|
202
|
-
`;
|
|
203
|
-
|
|
204
|
-
// 1. Create Universal Instructions
|
|
205
|
-
if (!(await fs.pathExists(instructionsPath))) {
|
|
206
|
-
await fs.writeFile(instructionsPath, ruleContent);
|
|
207
|
-
console.log(chalk.green('✔ Initialized Universal Agent Handshake (docs/AGENT_INSTRUCTIONS.md)'));
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// 2. Create IDE-Specific Rules based on detection or user preference
|
|
211
|
-
const detectedIDE = detectIDE(cwd);
|
|
212
|
-
const targetIDE = options.ide || (detectedIDE !== 'unknown' ? detectedIDE : 'all');
|
|
213
|
-
|
|
214
|
-
if (detectedIDE !== 'unknown' && !options.ide) {
|
|
215
|
-
console.log(chalk.dim(` (Auto-detected IDE: ${detectedIDE})`));
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (targetIDE === 'cursor' || targetIDE === 'all') {
|
|
219
|
-
const cursorRulesDir = path.join(cwd, '.cursor', 'rules');
|
|
220
|
-
await fs.ensureDir(cursorRulesDir);
|
|
221
|
-
const mdcPath = path.join(cursorRulesDir, 'rigour.mdc');
|
|
222
|
-
const mdcContent = `---
|
|
223
|
-
description: Enforcement of Rigour quality gates and best practices.
|
|
224
|
-
globs: **/*
|
|
225
|
-
---
|
|
226
|
-
|
|
227
|
-
${ruleContent}`;
|
|
228
|
-
|
|
229
|
-
if (!(await fs.pathExists(mdcPath))) {
|
|
230
|
-
await fs.writeFile(mdcPath, mdcContent);
|
|
231
|
-
console.log(chalk.green('✔ Initialized Cursor Handshake (.cursor/rules/rigour.mdc)'));
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (targetIDE === 'vscode' || targetIDE === 'all') {
|
|
236
|
-
// VS Code users use the universal AGENT_INSTRUCTIONS.md (already created above)
|
|
237
|
-
// We could also add .vscode/settings.json or snippets here if needed
|
|
238
|
-
console.log(chalk.green('✔ VS Code mode - using Universal Handshake (docs/AGENT_INSTRUCTIONS.md)'));
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (targetIDE === 'cline' || targetIDE === 'all') {
|
|
242
|
-
const clineRulesPath = path.join(cwd, '.clinerules');
|
|
243
|
-
if (!(await fs.pathExists(clineRulesPath))) {
|
|
244
|
-
await fs.writeFile(clineRulesPath, ruleContent);
|
|
245
|
-
console.log(chalk.green('✔ Initialized Cline Handshake (.clinerules)'));
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Claude Code (CLAUDE.md)
|
|
250
|
-
if (targetIDE === 'claude' || targetIDE === 'all') {
|
|
251
|
-
const claudePath = path.join(cwd, 'CLAUDE.md');
|
|
252
|
-
const claudeContent = `# CLAUDE.md - Project Instructions for Claude Code
|
|
253
|
-
|
|
254
|
-
This file provides Claude Code with context about this project.
|
|
255
|
-
|
|
256
|
-
## Project Overview
|
|
257
|
-
|
|
258
|
-
This project uses Rigour for quality gates. Always run \`npx @rigour-labs/cli check\` before marking tasks complete.
|
|
259
|
-
|
|
260
|
-
## Commands
|
|
261
|
-
|
|
262
|
-
\`\`\`bash
|
|
263
|
-
# Verify quality gates
|
|
264
|
-
npx @rigour-labs/cli check
|
|
265
|
-
|
|
266
|
-
# Get fix packet for failures
|
|
267
|
-
npx @rigour-labs/cli explain
|
|
268
|
-
|
|
269
|
-
# Self-healing agent loop
|
|
270
|
-
npx @rigour-labs/cli run -- claude "<task>"
|
|
271
|
-
\`\`\`
|
|
272
|
-
|
|
273
|
-
${ruleContent}`;
|
|
274
|
-
|
|
275
|
-
if (!(await fs.pathExists(claudePath))) {
|
|
276
|
-
await fs.writeFile(claudePath, claudeContent);
|
|
277
|
-
console.log(chalk.green('✔ Initialized Claude Code Handshake (CLAUDE.md)'));
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Gemini Code Assist (.gemini/styleguide.md)
|
|
282
|
-
if (targetIDE === 'gemini' || targetIDE === 'all') {
|
|
283
|
-
const geminiDir = path.join(cwd, '.gemini');
|
|
284
|
-
await fs.ensureDir(geminiDir);
|
|
285
|
-
const geminiStylePath = path.join(geminiDir, 'styleguide.md');
|
|
286
|
-
const geminiContent = `# Gemini Code Assist Style Guide
|
|
287
|
-
|
|
288
|
-
This project uses Rigour for quality gates.
|
|
289
|
-
|
|
290
|
-
## Required Before Completion
|
|
291
|
-
|
|
292
|
-
Always run \`npx @rigour-labs/cli check\` before marking any task complete.
|
|
293
|
-
|
|
294
|
-
${ruleContent}`;
|
|
295
|
-
|
|
296
|
-
if (!(await fs.pathExists(geminiStylePath))) {
|
|
297
|
-
await fs.writeFile(geminiStylePath, geminiContent);
|
|
298
|
-
console.log(chalk.green('✔ Initialized Gemini Handshake (.gemini/styleguide.md)'));
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// OpenAI Codex / Aider (AGENTS.md - Universal Standard)
|
|
303
|
-
if (targetIDE === 'codex' || targetIDE === 'all') {
|
|
304
|
-
const agentsPath = path.join(cwd, 'AGENTS.md');
|
|
305
|
-
const agentsContent = `# AGENTS.md - Universal AI Agent Instructions
|
|
306
|
-
|
|
307
|
-
This file provides instructions for AI coding agents (Codex, Aider, and others).
|
|
308
|
-
|
|
309
|
-
## Setup
|
|
310
|
-
|
|
311
|
-
\`\`\`bash
|
|
312
|
-
npm install
|
|
313
|
-
npm run dev
|
|
314
|
-
npm test
|
|
315
|
-
\`\`\`
|
|
316
|
-
|
|
317
|
-
## Quality Gates
|
|
318
|
-
|
|
319
|
-
This project uses Rigour. Before completing any task:
|
|
320
|
-
|
|
321
|
-
\`\`\`bash
|
|
322
|
-
npx @rigour-labs/cli check
|
|
323
|
-
\`\`\`
|
|
324
|
-
|
|
325
|
-
${ruleContent}`;
|
|
326
|
-
|
|
327
|
-
if (!(await fs.pathExists(agentsPath))) {
|
|
328
|
-
await fs.writeFile(agentsPath, agentsContent);
|
|
329
|
-
console.log(chalk.green('✔ Initialized Universal Agent Handshake (AGENTS.md)'));
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// Windsurf (.windsurfrules)
|
|
334
|
-
if (targetIDE === 'windsurf' || targetIDE === 'all') {
|
|
335
|
-
const windsurfPath = path.join(cwd, '.windsurfrules');
|
|
336
|
-
if (!(await fs.pathExists(windsurfPath))) {
|
|
337
|
-
await fs.writeFile(windsurfPath, ruleContent);
|
|
338
|
-
console.log(chalk.green('✔ Initialized Windsurf Handshake (.windsurfrules)'));
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// 3. Update .gitignore
|
|
343
|
-
const gitignorePath = path.join(cwd, '.gitignore');
|
|
344
|
-
const ignorePatterns = ['rigour-report.json', 'rigour-fix-packet.json', '.rigour/'];
|
|
345
|
-
try {
|
|
346
|
-
let content = '';
|
|
347
|
-
if (await fs.pathExists(gitignorePath)) {
|
|
348
|
-
content = await fs.readFile(gitignorePath, 'utf-8');
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const toAdd = ignorePatterns.filter(p => !content.includes(p));
|
|
352
|
-
if (toAdd.length > 0) {
|
|
353
|
-
const separator = content.endsWith('\n') ? '' : '\n';
|
|
354
|
-
const newContent = `${content}${separator}\n# Rigour Artifacts\n${toAdd.join('\n')}\n`;
|
|
355
|
-
await fs.writeFile(gitignorePath, newContent);
|
|
356
|
-
console.log(chalk.green('✔ Updated .gitignore'));
|
|
357
|
-
}
|
|
358
|
-
} catch (e) {
|
|
359
|
-
// Failing to update .gitignore isn't fatal
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
console.log(chalk.blue('\nRigour is ready. Run `npx @rigour-labs/cli check` to verify your project.'));
|
|
363
|
-
console.log(chalk.dim('\n💡 Tip: Planning to use a framework like Next.js?'));
|
|
364
|
-
console.log(chalk.dim(' Run its scaffolding tool (e.g., npx create-next-app) BEFORE rigour init,'));
|
|
365
|
-
console.log(chalk.dim(' or move rigour.yml and docs/ aside temporarily to satisfy empty-directory checks.'));
|
|
366
|
-
}
|
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
|
-
}
|
package/src/init-rules.test.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import fs from 'fs-extra';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
|
|
7
|
-
async function getInitCommand() {
|
|
8
|
-
const { initCommand } = await import('./commands/init.js');
|
|
9
|
-
return initCommand;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
describe('Init Command Rules Verification', () => {
|
|
13
|
-
const testDir = path.join(process.cwd(), 'temp-init-rules-test');
|
|
14
|
-
|
|
15
|
-
beforeEach(async () => {
|
|
16
|
-
await fs.ensureDir(testDir);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(async () => {
|
|
20
|
-
await fs.remove(testDir);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('should create instructions with agnostic rules and cursor rules on init', async () => {
|
|
24
|
-
const initCommand = await getInitCommand();
|
|
25
|
-
// Run init in test directory with all IDEs to verify rules in both locations
|
|
26
|
-
await initCommand(testDir, { ide: 'all' });
|
|
27
|
-
|
|
28
|
-
const instructionsPath = path.join(testDir, 'docs', 'AGENT_INSTRUCTIONS.md');
|
|
29
|
-
const mdcPath = path.join(testDir, '.cursor', 'rules', 'rigour.mdc');
|
|
30
|
-
|
|
31
|
-
expect(await fs.pathExists(instructionsPath)).toBe(true);
|
|
32
|
-
expect(await fs.pathExists(mdcPath)).toBe(true);
|
|
33
|
-
|
|
34
|
-
const instructionsContent = await fs.readFile(instructionsPath, 'utf-8');
|
|
35
|
-
const mdcContent = await fs.readFile(mdcPath, 'utf-8');
|
|
36
|
-
|
|
37
|
-
// Check for agnostic instructions
|
|
38
|
-
expect(instructionsContent).toContain('# 🤖 CRITICAL INSTRUCTION FOR AI');
|
|
39
|
-
expect(instructionsContent).toContain('VERIFICATION PROOF REQUIRED');
|
|
40
|
-
|
|
41
|
-
// Check for key sections in universal instructions
|
|
42
|
-
expect(instructionsContent).toContain('# 🛡️ Rigour: Engineering Excellence Protocol');
|
|
43
|
-
expect(instructionsContent).toContain('# Code Quality Standards');
|
|
44
|
-
|
|
45
|
-
// Check that MDC includes agnostic rules
|
|
46
|
-
expect(mdcContent).toContain('# 🤖 CRITICAL INSTRUCTION FOR AI');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should create .clinerules when ide is cline or all', async () => {
|
|
50
|
-
const initCommand = await getInitCommand();
|
|
51
|
-
await initCommand(testDir, { ide: 'cline' });
|
|
52
|
-
const clineRulesPath = path.join(testDir, '.clinerules');
|
|
53
|
-
expect(await fs.pathExists(clineRulesPath)).toBe(true);
|
|
54
|
-
|
|
55
|
-
const content = await fs.readFile(clineRulesPath, 'utf-8');
|
|
56
|
-
expect(content).toContain('# 🤖 CRITICAL INSTRUCTION FOR AI');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
});
|
package/src/smoke.test.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import fs from 'fs-extra';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
async function getCheckCommand() {
|
|
7
|
-
const { checkCommand } = await import('./commands/check.js');
|
|
8
|
-
return checkCommand;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
describe('CLI Smoke Test', () => {
|
|
12
|
-
const testDir = path.join(process.cwd(), 'temp-smoke-test');
|
|
13
|
-
|
|
14
|
-
beforeEach(async () => {
|
|
15
|
-
await fs.ensureDir(testDir);
|
|
16
|
-
// @ts-ignore
|
|
17
|
-
vi.spyOn(process, 'exit').mockImplementation(() => { });
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
afterEach(async () => {
|
|
21
|
-
await fs.remove(testDir);
|
|
22
|
-
vi.restoreAllMocks();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('should respect ignore patterns and avoid EPERM', async () => {
|
|
26
|
-
const restrictedDir = path.join(testDir, '.restricted');
|
|
27
|
-
await fs.ensureDir(restrictedDir);
|
|
28
|
-
await fs.writeFile(path.join(restrictedDir, 'secret.js'), 'TODO: leak');
|
|
29
|
-
|
|
30
|
-
await fs.writeFile(path.join(testDir, 'rigour.yml'), `
|
|
31
|
-
version: 1
|
|
32
|
-
ignore:
|
|
33
|
-
- ".restricted/**"
|
|
34
|
-
gates:
|
|
35
|
-
forbid_todos: true
|
|
36
|
-
required_files: []
|
|
37
|
-
`);
|
|
38
|
-
|
|
39
|
-
// Simulate EPERM by changing permissions
|
|
40
|
-
await fs.chmod(restrictedDir, 0o000);
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
// We need to mock process.exit or checkCommand should not exit if we want to test it easily
|
|
44
|
-
// For now, we'll just verify it doesn't throw before it would exit (internal logic)
|
|
45
|
-
// But checkCommand calls process.exit(1) on failure.
|
|
46
|
-
|
|
47
|
-
// Re-importing checkCommand to ensure it uses the latest core
|
|
48
|
-
const checkCommand = await getCheckCommand();
|
|
49
|
-
await expect(checkCommand(testDir, [], { ci: true })).resolves.not.toThrow();
|
|
50
|
-
} finally {
|
|
51
|
-
await fs.chmod(restrictedDir, 0o777);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('should check specific files when provided', async () => {
|
|
56
|
-
await fs.writeFile(path.join(testDir, 'bad.js'), 'TODO: fixme');
|
|
57
|
-
await fs.writeFile(path.join(testDir, 'good.js'), 'console.log("hello")');
|
|
58
|
-
await fs.writeFile(path.join(testDir, 'rigour.yml'), `
|
|
59
|
-
version: 1
|
|
60
|
-
gates:
|
|
61
|
-
forbid_todos: true
|
|
62
|
-
required_files: []
|
|
63
|
-
`);
|
|
64
|
-
|
|
65
|
-
// If we check ONLY good.js, it should PASS (exit PASS)
|
|
66
|
-
const checkCommand = await getCheckCommand();
|
|
67
|
-
await checkCommand(testDir, [path.join(testDir, 'good.js')], { ci: true });
|
|
68
|
-
expect(process.exit).toHaveBeenCalledWith(0);
|
|
69
|
-
|
|
70
|
-
// If we check bad.js, it should FAIL (exit FAIL)
|
|
71
|
-
vi.clearAllMocks();
|
|
72
|
-
const checkCommandFail = await getCheckCommand();
|
|
73
|
-
await checkCommandFail(testDir, [path.join(testDir, 'bad.js')], { ci: true });
|
|
74
|
-
expect(process.exit).toHaveBeenCalledWith(1);
|
|
75
|
-
});
|
|
76
|
-
});
|