@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
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { execa } from 'execa';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import http from 'http';
|
|
7
|
+
import { randomUUID } from 'crypto';
|
|
8
|
+
export const studioCommand = new Command('studio')
|
|
9
|
+
.description('Launch Rigour Studio (Local-First Governance UI)')
|
|
10
|
+
.option('-p, --port <number>', 'Port to run the studio on', '3000')
|
|
11
|
+
.option('--dev', 'Run in development mode', true)
|
|
12
|
+
.action(async (options) => {
|
|
13
|
+
const cwd = process.cwd();
|
|
14
|
+
const apiPort = parseInt(options.port) + 1;
|
|
15
|
+
const eventsPath = path.join(cwd, '.rigour/events.jsonl');
|
|
16
|
+
// Calculate the local dist path (where the pre-built Studio UI lives)
|
|
17
|
+
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
|
18
|
+
const localStudioDist = path.join(__dirname, '../studio-dist');
|
|
19
|
+
const workspaceRoot = path.join(__dirname, '../../../../');
|
|
20
|
+
console.log(chalk.bold.cyan('\n🛡️ Launching Rigour Studio...'));
|
|
21
|
+
console.log(chalk.gray(`Project Root: ${cwd}`));
|
|
22
|
+
// Pre-flight check: Is the project initialized?
|
|
23
|
+
const configPath = path.join(cwd, 'rigour.yml');
|
|
24
|
+
if (!(await fs.pathExists(configPath))) {
|
|
25
|
+
console.log(chalk.yellow('\n⚠️ Warning: rigour.yml not found.'));
|
|
26
|
+
console.log(chalk.dim('The Studio will be empty until you initialize the project.'));
|
|
27
|
+
console.log(chalk.cyan('Suggest: ') + chalk.bold('npx @rigour-labs/cli init') + '\n');
|
|
28
|
+
}
|
|
29
|
+
console.log(chalk.gray(`Shadowing interactions in ${path.join(cwd, '.rigour/events.jsonl')}\n`));
|
|
30
|
+
// Check if we are in a monorepo development environment
|
|
31
|
+
const isMonorepo = await fs.pathExists(path.join(workspaceRoot, 'packages/rigour-studio'));
|
|
32
|
+
if (isMonorepo && options.dev) {
|
|
33
|
+
console.log(chalk.yellow('Monorepo detected: Launching Studio in Development Mode...'));
|
|
34
|
+
try {
|
|
35
|
+
// Start the Studio dev server in the workspace root
|
|
36
|
+
const studioProcess = execa('pnpm', ['--filter', '@rigour-labs/studio', 'dev', '--port', options.port], {
|
|
37
|
+
stdio: 'inherit',
|
|
38
|
+
shell: true,
|
|
39
|
+
cwd: workspaceRoot
|
|
40
|
+
});
|
|
41
|
+
await setupApiAndLaunch(apiPort, options.port, eventsPath, cwd, studioProcess);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
console.log(chalk.dim('Development mode failed, falling back to standalone...'));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Standalone Mode: Serve pre-built static files
|
|
49
|
+
console.log(chalk.green('Launching Studio in Standalone Mode...'));
|
|
50
|
+
if (!(await fs.pathExists(localStudioDist))) {
|
|
51
|
+
console.error(chalk.red(`\n❌ Error: Studio UI artifacts not found at ${localStudioDist}`));
|
|
52
|
+
console.log(chalk.yellow('If you are a developer, run "pnpm build" in the monorepo root first.\n'));
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
const staticServer = http.createServer(async (req, res) => {
|
|
56
|
+
const url = new URL(req.url || '', `http://${req.headers.host || 'localhost'}`);
|
|
57
|
+
let filePath = path.join(localStudioDist, url.pathname === '/' ? 'index.html' : url.pathname);
|
|
58
|
+
try {
|
|
59
|
+
if (!(await fs.pathExists(filePath)) || (await fs.stat(filePath)).isDirectory()) {
|
|
60
|
+
filePath = path.join(localStudioDist, 'index.html');
|
|
61
|
+
}
|
|
62
|
+
const content = await fs.readFile(filePath);
|
|
63
|
+
const ext = path.extname(filePath);
|
|
64
|
+
const contentTypes = {
|
|
65
|
+
'.html': 'text/html',
|
|
66
|
+
'.js': 'application/javascript',
|
|
67
|
+
'.css': 'text/css',
|
|
68
|
+
'.json': 'application/json',
|
|
69
|
+
'.png': 'image/png',
|
|
70
|
+
'.jpg': 'image/jpeg',
|
|
71
|
+
'.svg': 'image/svg+xml',
|
|
72
|
+
'.ico': 'image/x-icon'
|
|
73
|
+
};
|
|
74
|
+
res.writeHead(200, { 'Content-Type': contentTypes[ext] || 'application/octet-stream' });
|
|
75
|
+
res.end(content);
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
res.writeHead(404);
|
|
79
|
+
res.end('Not Found');
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
staticServer.listen(options.port, () => {
|
|
83
|
+
setupApiAndLaunch(apiPort, options.port, eventsPath, cwd);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
async function setupApiAndLaunch(apiPort, studioPort, eventsPath, cwd, studioProcess) {
|
|
87
|
+
const apiServer = http.createServer(async (req, res) => {
|
|
88
|
+
const url = new URL(req.url || '', `http://${req.headers.host || 'localhost'}`);
|
|
89
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
90
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS, POST');
|
|
91
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
92
|
+
if (req.method === 'OPTIONS') {
|
|
93
|
+
res.writeHead(204);
|
|
94
|
+
res.end();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (url.pathname === '/api/events') {
|
|
98
|
+
res.writeHead(200, {
|
|
99
|
+
'Content-Type': 'text/event-stream',
|
|
100
|
+
'Cache-Control': 'no-cache',
|
|
101
|
+
'Connection': 'keep-alive'
|
|
102
|
+
});
|
|
103
|
+
if (await fs.pathExists(eventsPath)) {
|
|
104
|
+
const content = await fs.readFile(eventsPath, 'utf8');
|
|
105
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
106
|
+
for (const line of lines) {
|
|
107
|
+
res.write(`data: ${line}\n\n`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
await fs.ensureDir(path.dirname(eventsPath));
|
|
111
|
+
const watcher = fs.watch(path.dirname(eventsPath), (eventType, filename) => {
|
|
112
|
+
if (filename === 'events.jsonl') {
|
|
113
|
+
fs.readFile(eventsPath, 'utf8').then(content => {
|
|
114
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
115
|
+
const lastLine = lines[lines.length - 1];
|
|
116
|
+
if (lastLine) {
|
|
117
|
+
res.write(`data: ${lastLine}\n\n`);
|
|
118
|
+
}
|
|
119
|
+
}).catch(() => { });
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
req.on('close', () => watcher.close());
|
|
123
|
+
}
|
|
124
|
+
else if (url.pathname === '/api/file') {
|
|
125
|
+
const filePath = url.searchParams.get('path');
|
|
126
|
+
if (!filePath) {
|
|
127
|
+
res.writeHead(400);
|
|
128
|
+
res.end('Missing path');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const absolutePath = path.resolve(cwd, filePath);
|
|
132
|
+
if (!absolutePath.startsWith(cwd)) {
|
|
133
|
+
res.writeHead(403);
|
|
134
|
+
res.end('Forbidden');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const content = await fs.readFile(absolutePath, 'utf8');
|
|
139
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
140
|
+
res.end(content);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
res.writeHead(404);
|
|
144
|
+
res.end('Not found');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else if (url.pathname === '/api/tree') {
|
|
148
|
+
try {
|
|
149
|
+
const getTree = async (dir) => {
|
|
150
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
151
|
+
let files = [];
|
|
152
|
+
const exclude = ['node_modules', '.git', '.rigour', '.venv', 'dist', 'build'];
|
|
153
|
+
for (const entry of entries) {
|
|
154
|
+
if (exclude.includes(entry.name) || entry.name.startsWith('.'))
|
|
155
|
+
continue;
|
|
156
|
+
const fullPath = path.join(dir, entry.name);
|
|
157
|
+
if (entry.isDirectory()) {
|
|
158
|
+
files = [...files, ...(await getTree(fullPath))];
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
files.push(path.relative(cwd, fullPath));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return files;
|
|
165
|
+
};
|
|
166
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
167
|
+
res.end(JSON.stringify(await getTree(cwd)));
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
res.writeHead(500);
|
|
171
|
+
res.end(e.message);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else if (url.pathname === '/api/config') {
|
|
175
|
+
try {
|
|
176
|
+
const configPath = path.join(cwd, 'rigour.yml');
|
|
177
|
+
if (await fs.pathExists(configPath)) {
|
|
178
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
179
|
+
res.end(await fs.readFile(configPath, 'utf8'));
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
res.writeHead(404);
|
|
183
|
+
res.end('Not found');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
res.writeHead(500);
|
|
188
|
+
res.end(e.message);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else if (url.pathname === '/api/memory') {
|
|
192
|
+
try {
|
|
193
|
+
const memoryPath = path.join(cwd, '.rigour/memory.json');
|
|
194
|
+
if (await fs.pathExists(memoryPath)) {
|
|
195
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
196
|
+
res.end(await fs.readFile(memoryPath, 'utf8'));
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
res.end(JSON.stringify({}));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
res.writeHead(500);
|
|
204
|
+
res.end(e.message);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else if (url.pathname === '/api/index-stats') {
|
|
208
|
+
try {
|
|
209
|
+
const indexPath = path.join(cwd, '.rigour/patterns.json');
|
|
210
|
+
if (await fs.pathExists(indexPath)) {
|
|
211
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
212
|
+
res.end(JSON.stringify(await fs.readJson(indexPath)));
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
res.end(JSON.stringify({ patterns: [], stats: { totalPatterns: 0, totalFiles: 0, byType: {} } }));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
res.writeHead(500);
|
|
220
|
+
res.end(e.message);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else if (url.pathname === '/api/index-search') {
|
|
224
|
+
const query = url.searchParams.get('q');
|
|
225
|
+
if (!query) {
|
|
226
|
+
res.writeHead(400);
|
|
227
|
+
res.end('Missing query');
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
const { generateEmbedding, semanticSearch } = await import('@rigour-labs/core/pattern-index');
|
|
232
|
+
const indexPath = path.join(cwd, '.rigour/patterns.json');
|
|
233
|
+
const indexData = await fs.readJson(indexPath);
|
|
234
|
+
const queryVector = await generateEmbedding(query);
|
|
235
|
+
const similarities = semanticSearch(queryVector, indexData.patterns);
|
|
236
|
+
const results = indexData.patterns.map((p, i) => ({ ...p, similarity: similarities[i] }))
|
|
237
|
+
.filter((p) => p.similarity > 0.3)
|
|
238
|
+
.sort((a, b) => b.similarity - a.similarity)
|
|
239
|
+
.slice(0, 20);
|
|
240
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
241
|
+
res.end(JSON.stringify(results));
|
|
242
|
+
}
|
|
243
|
+
catch (e) {
|
|
244
|
+
res.writeHead(500);
|
|
245
|
+
res.end(e.message);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else if (url.pathname === '/api/arbitrate' && req.method === 'POST') {
|
|
249
|
+
let body = '';
|
|
250
|
+
req.on('data', chunk => body += chunk);
|
|
251
|
+
req.on('end', async () => {
|
|
252
|
+
try {
|
|
253
|
+
const decision = JSON.parse(body);
|
|
254
|
+
const logEntry = JSON.stringify({
|
|
255
|
+
id: randomUUID(),
|
|
256
|
+
timestamp: new Date().toISOString(),
|
|
257
|
+
tool: 'human_arbitration',
|
|
258
|
+
requestId: decision.requestId,
|
|
259
|
+
decision: decision.decision,
|
|
260
|
+
status: decision.decision === 'approve' ? 'success' : 'error',
|
|
261
|
+
arbitrated: true
|
|
262
|
+
}) + "\n";
|
|
263
|
+
await fs.appendFile(eventsPath, logEntry);
|
|
264
|
+
res.writeHead(200);
|
|
265
|
+
res.end(JSON.stringify({ success: true }));
|
|
266
|
+
}
|
|
267
|
+
catch (e) {
|
|
268
|
+
res.writeHead(500);
|
|
269
|
+
res.end(e.message);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
res.writeHead(404);
|
|
275
|
+
res.end();
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
apiServer.listen(apiPort, () => {
|
|
279
|
+
console.log(chalk.gray(`API Streamer active on port ${apiPort}`));
|
|
280
|
+
});
|
|
281
|
+
setTimeout(async () => {
|
|
282
|
+
const url = `http://localhost:${studioPort}`;
|
|
283
|
+
console.log(chalk.green(`\n✅ Rigour Studio is live at ${chalk.bold(url)}`));
|
|
284
|
+
try {
|
|
285
|
+
await execa('open', [url]);
|
|
286
|
+
}
|
|
287
|
+
catch { }
|
|
288
|
+
}, 1500);
|
|
289
|
+
if (studioProcess) {
|
|
290
|
+
await studioProcess;
|
|
291
|
+
}
|
|
292
|
+
}
|
package/dist/init-rules.test.js
CHANGED
|
@@ -1,48 +1,43 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const vitest_1 = require("vitest");
|
|
7
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
9
4
|
async function getInitCommand() {
|
|
10
5
|
const { initCommand } = await import('./commands/init.js');
|
|
11
6
|
return initCommand;
|
|
12
7
|
}
|
|
13
|
-
|
|
14
|
-
const testDir =
|
|
15
|
-
|
|
16
|
-
await
|
|
8
|
+
describe('Init Command Rules Verification', () => {
|
|
9
|
+
const testDir = path.join(process.cwd(), 'temp-init-rules-test');
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
await fs.ensureDir(testDir);
|
|
17
12
|
});
|
|
18
|
-
|
|
19
|
-
await
|
|
13
|
+
afterEach(async () => {
|
|
14
|
+
await fs.remove(testDir);
|
|
20
15
|
});
|
|
21
|
-
|
|
16
|
+
it('should create instructions with agnostic rules and cursor rules on init', async () => {
|
|
22
17
|
const initCommand = await getInitCommand();
|
|
23
18
|
// Run init in test directory with all IDEs to verify rules in both locations
|
|
24
19
|
await initCommand(testDir, { ide: 'all' });
|
|
25
|
-
const instructionsPath =
|
|
26
|
-
const mdcPath =
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const instructionsContent = await
|
|
30
|
-
const mdcContent = await
|
|
20
|
+
const instructionsPath = path.join(testDir, 'docs', 'AGENT_INSTRUCTIONS.md');
|
|
21
|
+
const mdcPath = path.join(testDir, '.cursor', 'rules', 'rigour.mdc');
|
|
22
|
+
expect(await fs.pathExists(instructionsPath)).toBe(true);
|
|
23
|
+
expect(await fs.pathExists(mdcPath)).toBe(true);
|
|
24
|
+
const instructionsContent = await fs.readFile(instructionsPath, 'utf-8');
|
|
25
|
+
const mdcContent = await fs.readFile(mdcPath, 'utf-8');
|
|
31
26
|
// Check for agnostic instructions
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
expect(instructionsContent).toContain('# 🤖 CRITICAL INSTRUCTION FOR AI');
|
|
28
|
+
expect(instructionsContent).toContain('VERIFICATION PROOF REQUIRED');
|
|
34
29
|
// Check for key sections in universal instructions
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
expect(instructionsContent).toContain('# 🛡️ Rigour: Mandatory Engineering Governance Protocol');
|
|
31
|
+
expect(instructionsContent).toContain('# Code Quality Standards');
|
|
37
32
|
// Check that MDC includes agnostic rules
|
|
38
|
-
|
|
33
|
+
expect(mdcContent).toContain('# 🤖 CRITICAL INSTRUCTION FOR AI');
|
|
39
34
|
});
|
|
40
|
-
|
|
35
|
+
it('should create .clinerules when ide is cline or all', async () => {
|
|
41
36
|
const initCommand = await getInitCommand();
|
|
42
37
|
await initCommand(testDir, { ide: 'cline' });
|
|
43
|
-
const clineRulesPath =
|
|
44
|
-
|
|
45
|
-
const content = await
|
|
46
|
-
|
|
38
|
+
const clineRulesPath = path.join(testDir, '.clinerules');
|
|
39
|
+
expect(await fs.pathExists(clineRulesPath)).toBe(true);
|
|
40
|
+
const content = await fs.readFile(clineRulesPath, 'utf-8');
|
|
41
|
+
expect(content).toContain('# 🤖 CRITICAL INSTRUCTION FOR AI');
|
|
47
42
|
});
|
|
48
43
|
});
|
package/dist/smoke.test.js
CHANGED
|
@@ -1,31 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const vitest_1 = require("vitest");
|
|
7
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
9
4
|
async function getCheckCommand() {
|
|
10
5
|
const { checkCommand } = await import('./commands/check.js');
|
|
11
6
|
return checkCommand;
|
|
12
7
|
}
|
|
13
|
-
|
|
14
|
-
const testDir =
|
|
15
|
-
|
|
16
|
-
await
|
|
8
|
+
describe('CLI Smoke Test', () => {
|
|
9
|
+
const testDir = path.join(process.cwd(), 'temp-smoke-test');
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
await fs.ensureDir(testDir);
|
|
17
12
|
// @ts-ignore
|
|
18
|
-
|
|
13
|
+
vi.spyOn(process, 'exit').mockImplementation(() => { });
|
|
19
14
|
});
|
|
20
|
-
|
|
21
|
-
await
|
|
22
|
-
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await fs.remove(testDir);
|
|
17
|
+
vi.restoreAllMocks();
|
|
23
18
|
});
|
|
24
|
-
|
|
25
|
-
const restrictedDir =
|
|
26
|
-
await
|
|
27
|
-
await
|
|
28
|
-
await
|
|
19
|
+
it('should respect ignore patterns and avoid EPERM', async () => {
|
|
20
|
+
const restrictedDir = path.join(testDir, '.restricted');
|
|
21
|
+
await fs.ensureDir(restrictedDir);
|
|
22
|
+
await fs.writeFile(path.join(restrictedDir, 'secret.js'), 'TODO: leak');
|
|
23
|
+
await fs.writeFile(path.join(testDir, 'rigour.yml'), `
|
|
29
24
|
version: 1
|
|
30
25
|
ignore:
|
|
31
26
|
- ".restricted/**"
|
|
@@ -34,23 +29,23 @@ gates:
|
|
|
34
29
|
required_files: []
|
|
35
30
|
`);
|
|
36
31
|
// Simulate EPERM by changing permissions
|
|
37
|
-
await
|
|
32
|
+
await fs.chmod(restrictedDir, 0o000);
|
|
38
33
|
try {
|
|
39
34
|
// We need to mock process.exit or checkCommand should not exit if we want to test it easily
|
|
40
35
|
// For now, we'll just verify it doesn't throw before it would exit (internal logic)
|
|
41
36
|
// But checkCommand calls process.exit(1) on failure.
|
|
42
37
|
// Re-importing checkCommand to ensure it uses the latest core
|
|
43
38
|
const checkCommand = await getCheckCommand();
|
|
44
|
-
await
|
|
39
|
+
await expect(checkCommand(testDir, [], { ci: true })).resolves.not.toThrow();
|
|
45
40
|
}
|
|
46
41
|
finally {
|
|
47
|
-
await
|
|
42
|
+
await fs.chmod(restrictedDir, 0o777);
|
|
48
43
|
}
|
|
49
44
|
});
|
|
50
|
-
|
|
51
|
-
await
|
|
52
|
-
await
|
|
53
|
-
await
|
|
45
|
+
it('should check specific files when provided', async () => {
|
|
46
|
+
await fs.writeFile(path.join(testDir, 'bad.js'), 'TODO: fixme');
|
|
47
|
+
await fs.writeFile(path.join(testDir, 'good.js'), 'console.log("hello")');
|
|
48
|
+
await fs.writeFile(path.join(testDir, 'rigour.yml'), `
|
|
54
49
|
version: 1
|
|
55
50
|
gates:
|
|
56
51
|
forbid_todos: true
|
|
@@ -58,12 +53,12 @@ gates:
|
|
|
58
53
|
`);
|
|
59
54
|
// If we check ONLY good.js, it should PASS (exit PASS)
|
|
60
55
|
const checkCommand = await getCheckCommand();
|
|
61
|
-
await checkCommand(testDir, [
|
|
62
|
-
|
|
56
|
+
await checkCommand(testDir, [path.join(testDir, 'good.js')], { ci: true });
|
|
57
|
+
expect(process.exit).toHaveBeenCalledWith(0);
|
|
63
58
|
// If we check bad.js, it should FAIL (exit FAIL)
|
|
64
|
-
|
|
59
|
+
vi.clearAllMocks();
|
|
65
60
|
const checkCommandFail = await getCheckCommand();
|
|
66
|
-
await checkCommandFail(testDir, [
|
|
67
|
-
|
|
61
|
+
await checkCommandFail(testDir, [path.join(testDir, 'bad.js')], { ci: true });
|
|
62
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
68
63
|
});
|
|
69
64
|
});
|
package/package.json
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigour-labs/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.0",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"bin": {
|
|
5
6
|
"rigour": "dist/cli.js"
|
|
6
7
|
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"studio-dist",
|
|
11
|
+
"templates"
|
|
12
|
+
],
|
|
7
13
|
"repository": {
|
|
8
14
|
"type": "git",
|
|
9
15
|
"url": "https://github.com/rigour-labs/rigour"
|
|
@@ -22,7 +28,7 @@
|
|
|
22
28
|
"inquirer": "9.2.16",
|
|
23
29
|
"ora": "^8.0.1",
|
|
24
30
|
"yaml": "^2.8.2",
|
|
25
|
-
"@rigour-labs/core": "2.
|
|
31
|
+
"@rigour-labs/core": "2.12.0"
|
|
26
32
|
},
|
|
27
33
|
"devDependencies": {
|
|
28
34
|
"@types/fs-extra": "^11.0.4",
|
|
@@ -30,7 +36,8 @@
|
|
|
30
36
|
"@types/node": "^25.0.3"
|
|
31
37
|
},
|
|
32
38
|
"scripts": {
|
|
33
|
-
"build": "tsc",
|
|
39
|
+
"build": "tsc && pnpm bundle-studio",
|
|
40
|
+
"bundle-studio": "node scripts/bundle-studio.js",
|
|
34
41
|
"test": "vitest run"
|
|
35
42
|
}
|
|
36
43
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--bg-main: #fafbfc;--bg-sidebar: #ffffff;--bg-header: #ffffff;--bg-card: #ffffff;--bg-accent: #f4f6f8;--bg-surface: #fafbfc;--border-dim: #e8eaed;--border-med: #d0d7de;--border-color: #e8eaed;--text-main: #1f2328;--text-dim: #656d76;--text-vibrant: #000000;--text-primary: #1f2328;--accent-primary: #0891b2;--accent-secondary: #0d9488;--accent-tertiary: #7c3aed;--accent-glow: rgba(8, 145, 178, .1);--status-ready: #059669;--status-success: #059669;--status-error: #dc2626;--status-warning: #d97706;--code-bg: #f6f8fa;--gradient-accent: linear-gradient(135deg, #0891b2 0%, #06b6d4 100%)}[data-theme=dark]{--bg-main: #050505;--bg-sidebar: #0a0a0a;--bg-header: #0a0a0a;--bg-card: #0d0d0d;--bg-accent: #151515;--bg-surface: #0d0d0d;--border-dim: #1a1a1a;--border-med: #222222;--border-color: #1a1a1a;--text-main: #eeeeee;--text-dim: #888888;--text-vibrant: #ffffff;--text-primary: #eeeeee;--accent-primary: #22d3ee;--accent-secondary: #2dd4bf;--accent-tertiary: #a78bfa;--accent-glow: rgba(34, 211, 238, .1);--status-ready: #34d399;--status-success: #34d399;--status-error: #f87171;--status-warning: #fbbf24;--code-bg: #090909;--gradient-accent: linear-gradient(135deg, #06b6d4 0%, #22d3ee 100%)}*{box-sizing:border-box;margin:0;padding:0}body{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;background-color:var(--bg-main);color:var(--text-main);-webkit-font-smoothing:antialiased;overflow:hidden}.studio{display:flex;height:100vh;width:100vw}.sidebar{width:240px;background-color:var(--bg-sidebar);border-right:1px solid var(--border-dim);display:flex;flex-direction:column;padding:16px;flex-shrink:0}.brand{display:flex;align-items:center;gap:12px;padding:8px 12px;margin-bottom:32px}.logo-icon{width:28px;height:28px;background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));border-radius:6px;display:flex;align-items:center;justify-content:center;font-weight:800;color:#fff;font-size:16px}.brand span{font-weight:700;font-size:18px;color:var(--text-vibrant);letter-spacing:-.5px}.version-pill{font-size:10px;background-color:var(--bg-accent);color:var(--text-dim);padding:2px 6px;border-radius:4px;font-family:JetBrains Mono,monospace;margin-left:auto}nav{display:flex;flex-direction:column;gap:4px;flex-grow:1}.nav-item{display:flex;align-items:center;gap:12px;padding:10px 12px;border-radius:8px;color:var(--text-dim);text-decoration:none;font-size:14px;font-weight:500;background:transparent;border:none;cursor:pointer;width:100%;text-align:left;transition:all .2s ease;position:relative}.nav-item:hover{background-color:var(--bg-accent);color:var(--text-main)}.nav-item.active{color:var(--text-vibrant)}.nav-glow{position:absolute;left:0;top:10%;bottom:10%;width:3px;background-color:var(--accent-primary);border-radius:0 4px 4px 0;box-shadow:0 0 10px var(--accent-primary)}.sidebar-footer{margin-top:auto;padding-top:16px;border-top:1px solid var(--border-dim);display:flex;align-items:center;gap:8px}.trust-indicator{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--text-dim);padding:6px 10px;background:#10b9810d;border:1px solid rgba(16,185,129,.1);border-radius:100px;margin-right:auto}.footer-item{background:transparent;border:none;color:var(--text-dim);cursor:pointer;padding:4px;border-radius:4px}.footer-item:hover{background-color:var(--bg-accent);color:var(--text-main)}.main-content{flex-grow:1;display:flex;flex-direction:column;overflow:hidden}header{height:56px;background-color:var(--bg-header);border-bottom:1px solid var(--border-dim);display:flex;align-items:center;justify-content:space-between;padding:0 24px;flex-shrink:0}.header-right{display:flex;align-items:center;gap:20px}.breadcrumb{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--text-dim)}.breadcrumb .current{color:var(--text-main);font-weight:500}.connection-status{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--text-dim);background:var(--bg-accent);padding:4px 12px;border-radius:6px;font-family:JetBrains Mono,monospace}.status-indicator{display:flex;align-items:center;gap:8px;font-size:12px;font-weight:600;color:var(--status-ready)}.pulse-emitter{width:8px;height:8px;background-color:var(--status-ready);border-radius:50%;position:relative}.pulse-emitter:after{content:"";position:absolute;width:100%;height:100%;background-color:var(--status-ready);border-radius:50%;animation:pulse 2s infinite}@keyframes pulse{0%{transform:scale(1);opacity:.8}to{transform:scale(3);opacity:0}}.view-container{flex-grow:1;padding:32px;overflow-y:auto}.view-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px}.view-title{display:flex;align-items:center;gap:12px;color:var(--text-vibrant)}.view-title h2{font-size:20px;font-weight:700;letter-spacing:-.5px}.view-actions{display:flex;gap:12px}.btn-primary{background-color:var(--accent-primary);color:#fff;border:none;padding:8px 16px;border-radius:6px;font-size:14px;font-weight:600;cursor:pointer;transition:background .2s}.btn-primary:hover{background-color:#4338ca}.btn-secondary{background-color:var(--bg-accent);color:var(--text-main);border:1px solid var(--border-med);padding:8px 16px;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer}.btn-secondary:hover{background-color:var(--border-dim)}.log-container{min-height:400px;background-color:var(--bg-card);border:1px solid var(--border-dim);border-radius:12px;display:flex;align-items:center;justify-content:center;position:relative}.empty-state{text-align:center;display:flex;flex-direction:column;align-items:center;gap:16px;max-width:320px}.empty-state h3{font-weight:600;color:var(--text-vibrant)}.empty-state p{font-size:14px;color:var(--text-dim);line-height:1.5}.log-list{width:100%;height:100%;padding:16px;display:flex;flex-direction:column;gap:12px;overflow-y:auto}.log-entry{background-color:var(--bg-accent);border:1px solid var(--border-med);border-radius:8px;padding:12px;font-family:JetBrains Mono,monospace;font-size:13px;transition:border-color .2s}.log-entry:hover{border-color:var(--accent-primary)}.log-meta{display:flex;align-items:center;gap:12px;margin-bottom:8px;border-bottom:1px solid var(--border-dim);padding-bottom:8px}.log-time{color:var(--text-dim)}.log-request-id{color:var(--border-med);font-size:11px}.log-badge{padding:2px 6px;border-radius:4px;font-size:10px;font-weight:700;text-transform:uppercase}.log-badge.tool_call{background:#4f46e533;color:var(--accent-primary)}.log-badge.tool_response{background:#06b8d433;color:var(--accent-secondary)}.log-tool{font-weight:700;color:var(--text-vibrant);display:block;margin-bottom:4px}.log-args{background:#000;padding:8px;border-radius:4px;color:#a5d6ff;font-size:12px;margin:8px 0;white-space:pre-wrap;word-break:break-all}.log-status{display:inline-flex;align-items:center;gap:4px;font-weight:600;font-size:11px}.log-status.success{color:var(--status-ready)}.log-status.error{color:var(--status-error)}.governance-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:#000c;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);display:flex;align-items:center;justify-content:center;z-index:1000}.governance-window{width:90vw;height:85vh;background:var(--bg-card);border:1px solid var(--border-med);border-radius:12px;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 32px 64px #0009}.governance-header{height:52px;padding:0 20px;background:var(--bg-header);border-bottom:1px solid var(--border-dim);display:flex;align-items:center;justify-content:space-between}.governance-header .title{display:flex;align-items:center;gap:12px;color:var(--text-main);font-weight:600}.governance-body{flex:1;display:flex;overflow:hidden}.file-tree{width:250px;background:var(--bg-header);border-right:1px solid var(--border-dim);display:flex;flex-direction:column}.tree-header{padding:12px 16px;font-size:11px;color:var(--text-dim);text-transform:uppercase;font-weight:700;letter-spacing:.5px;display:flex;align-items:center;gap:8px}.tree-list{flex:1;overflow-y:auto}.tree-item{padding:8px 16px;font-size:13px;color:var(--text-main);cursor:pointer;display:flex;align-items:center;gap:10px;transition:all .2s}.tree-item:hover{background:#ffffff0d;color:var(--text-main)}.tree-item.active{background:var(--accent-glow);color:var(--accent-primary);border-right:2px solid var(--accent-primary)}.diff-view-area{flex:1;background:var(--code-bg);position:relative}.diff-placeholder{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text-dim);gap:16px}.inspectable{cursor:pointer;border-left:3px solid transparent}.inspectable:hover{background:#4f46e51a;border-left-color:var(--accent-primary)}.inspect-pill{margin-left:auto;font-size:10px;background:var(--accent-glow);color:var(--accent-secondary);padding:2px 8px;border-radius:100px;font-weight:500}.close-btn{background:transparent;border:none;color:var(--text-dim);cursor:pointer;padding:6px;border-radius:6px;transition:all .2s}.tree-item{display:flex;align-items:center;gap:8px;padding:6px 12px;cursor:pointer;border-radius:4px;transition:all .2s;font-size:13px;color:var(--text-dim);-webkit-user-select:none;user-select:none}.tree-item:hover{background:var(--bg-surface);color:var(--text-primary)}.tree-item.active{background:var(--accent-glow);color:var(--accent-secondary)}.tree-item.violated{color:var(--status-error);opacity:.9}.chevron{color:var(--text-dim);opacity:.6}.folder-icon{color:var(--accent-primary);opacity:.8}.file-icon{color:var(--text-dim);opacity:.6}.node-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.violation-icon{margin-left:auto;color:var(--status-error);opacity:.8}.diff-editor-container{flex:1;background:#1e1e1e;border-bottom-left-radius:12px;border-bottom-right-radius:12px;overflow:hidden;position:relative;min-height:0}::-webkit-scrollbar{width:8px}::-webkit-scrollbar-track{background:var(--bg-main)}::-webkit-scrollbar-thumb{background:var(--border-med);border-radius:10px}::-webkit-scrollbar-thumb:hover{background:var(--text-dim)}.theme-toggle{background:var(--bg-accent);border:1px solid var(--border-dim);border-radius:8px;padding:8px;cursor:pointer;color:var(--text-dim);display:flex;align-items:center;transition:all .2s}.theme-toggle:hover{color:var(--text-main);border-color:var(--border-med)}.memory-bank{display:flex;flex-direction:column;height:100%;background:var(--bg-main)}.memory-header{display:flex;justify-content:space-between;align-items:center;padding:20px 24px;border-bottom:1px solid var(--border-dim);background:var(--bg-card)}.memory-title{display:flex;align-items:center;gap:12px}.memory-title h2{font-size:18px;font-weight:600;color:var(--text-main)}.memory-count{background:var(--accent-glow);color:var(--accent-primary);padding:4px 10px;border-radius:12px;font-size:12px;font-weight:500}.memory-actions{display:flex;gap:12px;align-items:center}.search-box{display:flex;align-items:center;gap:8px;background:var(--bg-accent);border:1px solid var(--border-dim);border-radius:8px;padding:8px 12px}.search-box input{background:transparent;border:none;outline:none;color:var(--text-main);font-size:14px;width:200px}.search-box input::placeholder{color:var(--text-dim)}.refresh-btn{background:var(--bg-accent);border:1px solid var(--border-dim);border-radius:8px;padding:8px;cursor:pointer;color:var(--text-dim);transition:all .2s}.refresh-btn:hover{color:var(--accent-primary);border-color:var(--accent-primary)}.refresh-btn .spinning{animation:spin 1s linear infinite}.memory-content{display:grid;grid-template-columns:1fr 1.5fr;flex:1;overflow:hidden}.memory-list{border-right:1px solid var(--border-dim);overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}.memory-loading,.memory-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--text-dim);text-align:center;gap:12px}.memory-empty h3{font-size:16px;color:var(--text-main)}.memory-empty code{background:var(--code-bg);padding:2px 6px;border-radius:4px;font-size:13px}.memory-item{background:var(--bg-card);border:1px solid var(--border-dim);border-radius:10px;padding:14px;cursor:pointer;transition:all .2s}.memory-item:hover{border-color:var(--border-med);background:var(--bg-accent)}.memory-item.active{border-color:var(--accent-primary);background:var(--accent-glow)}.memory-item-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.memory-key{font-weight:600;color:var(--accent-primary);flex:1;font-size:14px}.memory-item-header .chevron{color:var(--text-dim);opacity:0;transition:opacity .2s}.memory-item:hover .chevron,.memory-item.active .chevron{opacity:1}.memory-item-meta{display:flex;align-items:center;gap:6px;color:var(--text-dim);font-size:12px;margin-bottom:8px}.memory-item-preview{font-size:13px;color:var(--text-dim);line-height:1.5;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.memory-detail{padding:24px;overflow-y:auto;background:var(--bg-card)}.detail-header{display:flex;align-items:center;gap:10px;margin-bottom:12px}.detail-header h3{font-size:18px;font-weight:600;color:var(--accent-primary)}.detail-meta{display:flex;align-items:center;gap:8px;color:var(--text-dim);font-size:13px;margin-bottom:20px;padding-bottom:16px;border-bottom:1px solid var(--border-dim)}.detail-content pre{background:var(--code-bg);border:1px solid var(--border-dim);border-radius:8px;padding:16px;font-family:JetBrains Mono,Fira Code,monospace;font-size:13px;line-height:1.6;white-space:pre-wrap;word-break:break-word;color:var(--text-main);overflow-x:auto}.detail-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--text-dim);gap:12px}.pattern-index{display:flex;flex-direction:column;height:100%;background:var(--bg-main)}.pattern-header{display:flex;justify-content:space-between;align-items:center;padding:20px 24px;border-bottom:1px solid var(--border-dim);background:var(--bg-card)}.pattern-title{display:flex;align-items:center;gap:12px}.pattern-title h2{font-size:18px;font-weight:600;color:var(--text-main)}.pattern-count{background:var(--accent-glow);color:var(--accent-primary);padding:4px 10px;border-radius:12px;font-size:12px;font-weight:500}.pattern-actions{display:flex;gap:12px;align-items:center}.filter-box{display:flex;align-items:center;gap:8px;background:var(--bg-accent);border:1px solid var(--border-dim);border-radius:8px;padding:8px 12px}.filter-box select{background:transparent;border:none;outline:none;color:var(--text-main);font-size:14px;cursor:pointer}.pattern-stats{display:flex;gap:16px;padding:16px 24px;border-bottom:1px solid var(--border-dim);background:var(--bg-card)}.pattern-stats .stat-card{display:flex;align-items:center;gap:12px;background:var(--bg-accent);padding:12px 16px;border-radius:8px;border:1px solid var(--border-dim)}.stat-info{display:flex;flex-direction:column}.stat-info .stat-value{font-size:20px;font-weight:700;color:var(--text-main)}.stat-info .stat-label{font-size:12px;color:var(--text-dim)}.pattern-content{display:grid;grid-template-columns:1fr 1.5fr;flex:1;overflow:hidden}.pattern-list{border-right:1px solid var(--border-dim);overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:10px}.pattern-loading,.pattern-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--text-dim);text-align:center;gap:12px}.pattern-empty code{background:var(--code-bg);padding:2px 6px;border-radius:4px;font-size:13px}.pattern-item{background:var(--bg-card);border:1px solid var(--border-dim);border-radius:8px;padding:12px;cursor:pointer;transition:all .2s}.pattern-item:hover{border-color:var(--border-med);background:var(--bg-accent)}.pattern-item.active{border-color:var(--accent-primary);background:var(--accent-glow)}.pattern-item-header{display:flex;align-items:center;gap:8px;margin-bottom:6px}.pattern-type-badge{background:var(--accent-secondary);color:#fff;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:600;text-transform:uppercase}.pattern-type-badge.large{padding:4px 12px;font-size:13px}.pattern-name{font-weight:600;color:var(--text-main);flex:1}.pattern-item-file{display:flex;align-items:center;gap:6px;color:var(--text-dim);font-size:12px;font-family:monospace}.exported-badge{background:var(--status-success);color:#fff;padding:2px 6px;border-radius:4px;font-size:10px;margin-top:6px;display:inline-block}.pattern-detail{padding:24px;overflow-y:auto;background:var(--bg-card)}.detail-section{margin-bottom:20px}.detail-section h4{font-size:12px;text-transform:uppercase;color:var(--text-dim);margin-bottom:8px;letter-spacing:.5px}.detail-section code{background:var(--code-bg);padding:8px 12px;border-radius:6px;font-family:monospace;font-size:13px;display:block;color:var(--accent-primary)}.detail-section pre{background:var(--code-bg);padding:12px;border-radius:6px;font-family:monospace;font-size:13px;overflow-x:auto;white-space:pre-wrap}.keywords-list{display:flex;flex-wrap:wrap;gap:6px}.keyword-tag{background:var(--bg-accent);color:var(--text-dim);padding:4px 10px;border-radius:12px;font-size:12px;border:1px solid var(--border-dim)}.quality-gates{display:flex;flex-direction:column;height:100%;background:var(--bg-main)}.gates-header{display:flex;justify-content:space-between;align-items:center;padding:20px 24px;border-bottom:1px solid var(--border-dim);background:var(--bg-card)}.gates-title{display:flex;align-items:center;gap:12px}.gates-title h2{font-size:18px;font-weight:600;color:var(--text-main)}.preset-badge{background:var(--accent-primary);color:#fff;padding:4px 12px;border-radius:12px;font-size:12px;font-weight:600;text-transform:uppercase}.gates-actions{display:flex;gap:12px;align-items:center}.view-toggle{display:flex;background:var(--bg-accent);border-radius:8px;padding:4px;border:1px solid var(--border-dim)}.view-toggle button{display:flex;align-items:center;gap:6px;padding:6px 12px;border:none;background:transparent;color:var(--text-dim);cursor:pointer;border-radius:6px;font-size:13px;transition:all .2s}.view-toggle button:hover{color:var(--text-main)}.view-toggle button.active{background:var(--bg-card);color:var(--accent-primary);box-shadow:0 1px 3px #0000001a}.gates-loading{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-dim)}.gates-raw{flex:1;overflow:auto;padding:24px}.gates-raw pre{background:var(--code-bg);padding:20px;border-radius:8px;border:1px solid var(--border-dim);font-family:JetBrains Mono,monospace;font-size:13px;line-height:1.6;color:var(--text-main);white-space:pre-wrap}.gates-content{flex:1;overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:24px}.gate-section{background:var(--bg-card);border:1px solid var(--border-dim);border-radius:12px;padding:20px}.section-header{display:flex;align-items:center;gap:10px;margin-bottom:16px;color:var(--text-main)}.section-header h3{font-size:15px;font-weight:600}.gate-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px}.gate-card{background:var(--bg-accent);border:1px solid var(--border-dim);border-radius:8px;padding:14px;display:flex;align-items:center;gap:12px}.gate-icon{color:var(--accent-primary)}.gate-info{display:flex;flex-direction:column}.gate-value{font-size:18px;font-weight:700;color:var(--text-main)}.gate-label{font-size:11px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.5px}.protected-paths,.file-list{margin-top:16px}.protected-paths h4{font-size:12px;color:var(--text-dim);text-transform:uppercase;margin-bottom:10px}.path-list{display:flex;flex-wrap:wrap;gap:8px}.path-tag{background:var(--bg-accent);color:var(--accent-secondary);padding:4px 10px;border-radius:6px;font-family:monospace;font-size:12px;border:1px solid var(--border-dim)}.file-list{display:flex;flex-direction:column;gap:8px}.file-item{display:flex;align-items:center;gap:8px;color:var(--text-dim);font-size:13px;font-family:monospace}.audit-log{display:flex;flex-direction:column;height:100%;background:var(--bg-main)}.audit-header{display:flex;justify-content:space-between;align-items:center;padding:20px 24px;border-bottom:1px solid var(--border-dim);background:var(--bg-card)}.audit-title{display:flex;align-items:center;gap:12px}.audit-title h2{font-size:18px;font-weight:600;color:var(--text-main)}.log-count{background:var(--accent-glow);color:var(--accent-primary);padding:4px 10px;border-radius:12px;font-size:12px}.audit-actions{display:flex;gap:10px}.btn-secondary,.btn-primary{display:flex;align-items:center;gap:6px;padding:8px 14px;border-radius:8px;font-size:13px;cursor:pointer;transition:all .2s;border:none}.btn-secondary{background:var(--bg-accent);color:var(--text-dim);border:1px solid var(--border-dim)}.btn-secondary:hover{color:var(--text-main);border-color:var(--border-med)}.btn-primary{background:var(--accent-primary);color:#fff}.btn-primary:hover{opacity:.9}.audit-stats{display:flex;gap:12px;padding:16px 24px;border-bottom:1px solid var(--border-dim);background:var(--bg-card)}.stat-pill{display:flex;align-items:center;gap:6px;padding:6px 12px;background:var(--bg-accent);border-radius:20px;font-size:13px;color:var(--text-dim);border:1px solid var(--border-dim)}.stat-pill.success{color:var(--status-success)}.stat-pill.error{color:var(--status-error)}.stat-pill.pending{color:var(--status-warning)}.audit-filters{display:flex;gap:12px;padding:12px 24px;border-bottom:1px solid var(--border-dim)}.audit-content{display:grid;grid-template-columns:1fr 1.5fr;flex:1;overflow:hidden}.audit-content .log-list{border-right:1px solid var(--border-dim);overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:8px}.log-entry{background:var(--bg-card);border:1px solid var(--border-dim);border-radius:8px;padding:12px;cursor:pointer;transition:all .2s}.log-entry:hover{border-color:var(--border-med);background:var(--bg-accent)}.log-entry.active{border-color:var(--accent-primary);background:var(--accent-glow)}.log-entry.has-report{border-left:3px solid var(--accent-primary)}.log-header{display:flex;align-items:center;gap:8px}.status-dot{width:8px;height:8px;border-radius:50%;background:var(--text-dim)}.status-dot.success{background:var(--status-success)}.status-dot.error{background:var(--status-error)}.status-dot.pending{background:var(--status-warning)}.log-tool{font-weight:600;color:var(--text-main);flex:1;font-size:13px}.log-time{display:flex;align-items:center;gap:4px;color:var(--text-dim);font-size:11px}.report-badge{display:flex;align-items:center;gap:4px;background:var(--accent-glow);color:var(--accent-primary);padding:2px 8px;border-radius:12px;font-size:11px}.log-entry .chevron{color:var(--text-dim);opacity:0;transition:opacity .2s}.log-entry:hover .chevron,.log-entry.active .chevron{opacity:1}.log-request-id{font-size:11px;color:var(--text-dim);font-family:monospace;margin-top:6px}.log-detail{background:var(--bg-card);overflow-y:auto}.log-detail-view{height:100%;display:flex;flex-direction:column}.detail-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border-dim)}.detail-info{display:flex;align-items:center;gap:12px}.detail-info h3{font-size:16px;font-weight:600;color:var(--text-main)}.status-badge{display:flex;align-items:center;gap:4px;padding:4px 10px;border-radius:6px;font-size:12px;font-weight:500;text-transform:capitalize}.status-badge.success{background:#22c55e26;color:var(--status-success)}.status-badge.error{background:#ef444426;color:var(--status-error)}.status-badge.pending{background:#eab30826;color:var(--status-warning)}.close-btn{background:none;border:none;color:var(--text-dim);cursor:pointer;padding:4px}.close-btn:hover{color:var(--text-main)}.detail-meta{display:flex;gap:24px;padding:12px 20px;border-bottom:1px solid var(--border-dim);background:var(--bg-accent)}.meta-item{display:flex;flex-direction:column;gap:2px}.meta-label{font-size:11px;text-transform:uppercase;color:var(--text-dim)}.meta-value{font-size:13px;color:var(--text-main)}.meta-value.mono{font-family:monospace}.detail-tabs{display:flex;padding:0 20px;border-bottom:1px solid var(--border-dim)}.detail-tabs button{padding:12px 16px;background:none;border:none;color:var(--text-dim);cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px}.detail-tabs button:hover{color:var(--text-main)}.detail-tabs button.active{color:var(--accent-primary);border-bottom-color:var(--accent-primary)}.detail-body{flex:1;overflow-y:auto;padding:20px}.code-section,.error-section{margin-bottom:20px}.code-section h4,.error-section h4{font-size:12px;text-transform:uppercase;color:var(--text-dim);margin-bottom:8px}.code-section pre{background:var(--code-bg);padding:16px;border-radius:8px;font-family:JetBrains Mono,monospace;font-size:12px;line-height:1.6;overflow-x:auto;white-space:pre-wrap;color:var(--text-main)}.error-message{background:#ef44441a;border:1px solid rgba(239,68,68,.3);color:var(--status-error);padding:12px;border-radius:8px;font-size:13px}.diff-viewer-overlay{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;background:var(--bg-card)}.diff-viewer-window{flex:1;display:flex;flex-direction:column;overflow:hidden}.diff-viewer-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:var(--bg-sidebar);border-bottom:1px solid var(--border-dim)}.diff-viewer-header .title{display:flex;align-items:center;gap:10px;font-size:13px;font-weight:500;color:var(--text-main)}.diff-viewer-header .diff-badge{background:var(--accent-glow);color:var(--accent-primary);padding:2px 8px;border-radius:10px;font-size:11px}.diff-viewer-header .close-btn{background:transparent;border:none;color:var(--text-dim);cursor:pointer;padding:4px;border-radius:4px}.diff-viewer-header .close-btn:hover{color:var(--text-main);background:var(--bg-accent)}.diff-editor-container{flex:1;min-height:0;background:var(--code-bg)}.semantic-toggle{display:flex;align-items:center;gap:8px;background:var(--bg-accent);border:1px solid var(--border-dim);border-radius:8px;padding:8px 12px;font-size:13px;font-weight:500;color:var(--text-dim);cursor:pointer;transition:all .2s}.semantic-toggle:hover{border-color:var(--border-med);color:var(--text-main)}.semantic-toggle.active{background:var(--accent-glow);border-color:var(--accent-primary);color:var(--accent-primary);box-shadow:0 0 15px var(--accent-glow)}.semantic-toggle svg{color:var(--accent-tertiary)}.semantic-toggle.active svg{animation:rotate-spark 2s infinite ease-in-out}@keyframes rotate-spark{0%{transform:rotate(0) scale(1)}50%{transform:rotate(180deg) scale(1.2)}to{transform:rotate(360deg) scale(1)}}.similarity-badge{position:absolute;top:12px;right:12px;font-size:10px;font-weight:700;color:var(--accent-secondary);background:#2dd4bf1a;padding:2px 6px;border-radius:4px;border:1px solid rgba(45,212,191,.2)}.searching-indicator{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--accent-tertiary);margin:12px 0;font-weight:500}.spinning{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.hitl-actions{display:flex;align-items:center;gap:12px}.btn-approve,.btn-reject{display:flex;align-items:center;gap:8px;padding:6px 14px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;transition:all .2s;border:1px solid transparent}.btn-approve{background:#10b9811a;color:#10b981;border-color:#10b98133}.btn-approve:hover{background:#10b98133;box-shadow:0 0 15px #10b98133}.btn-reject{background:#ef44441a;color:#ef4444;border-color:#ef444433}.btn-reject:hover{background:#ef444433;box-shadow:0 0 15px #ef444433}.hitl-actions .divider{width:1px;height:24px;background:var(--border-dim);margin:0 8px}.arbitrated-badge{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:800;text-transform:uppercase;padding:2px 8px;border-radius:100px;margin-left:8px}.arbitrated-badge.approve{background:#10b98126;color:#10b981;border:1px solid rgba(16,185,129,.3)}.arbitrated-badge.reject{background:#ef444426;color:#ef4444;border:1px solid rgba(239,68,68,.3)}
|