@rigour-labs/cli 5.5.4 → 6.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +25 -0
- package/dist/commands/firewall.d.ts +9 -0
- package/dist/commands/firewall.js +106 -0
- package/dist/commands/hooks.js +22 -1
- package/dist/commands/index.js +1 -1
- package/dist/commands/studio-contracts.d.ts +20 -0
- package/dist/commands/studio-contracts.js +78 -0
- package/dist/commands/studio-contracts.test.d.ts +1 -0
- package/dist/commands/studio-contracts.test.js +33 -0
- package/dist/commands/studio.js +279 -24
- package/dist/commands/team.d.ts +2 -0
- package/dist/commands/team.js +78 -0
- package/package.json +4 -3
- package/studio-dist/assets/KnowledgeGraph-CWGbolsD.js +430 -0
- package/studio-dist/assets/KnowledgeGraph-DEqMBSbC.css +1 -0
- package/studio-dist/assets/index-DR7ehi_i.js +398 -0
- package/studio-dist/assets/index-PA_eqVds.css +1 -0
- package/studio-dist/index.html +2 -2
- package/studio-dist/assets/index-GVDvbvMe.js +0 -362
- package/studio-dist/assets/index-jC3k12bf.css +0 -1
package/dist/cli.js
CHANGED
|
@@ -19,6 +19,8 @@ import { deepStatsCommand } from './commands/deep-stats.js';
|
|
|
19
19
|
import { reviewCommand } from './commands/review.js';
|
|
20
20
|
import { checkPatternCommand } from './commands/check-pattern.js';
|
|
21
21
|
import { securityAuditCommand } from './commands/security-audit.js';
|
|
22
|
+
import { firewallTransactCommand, firewallAdversarialCommand, firewallAdmitCommand, firewallStatusCommand } from './commands/firewall.js';
|
|
23
|
+
import { teamCommand } from './commands/team.js';
|
|
22
24
|
import { checkForUpdates } from './utils/version.js';
|
|
23
25
|
import { getCliVersion } from './utils/cli-version.js';
|
|
24
26
|
import chalk from 'chalk';
|
|
@@ -28,6 +30,7 @@ program.addCommand(indexCommand);
|
|
|
28
30
|
program.addCommand(studioCommand);
|
|
29
31
|
program.addCommand(brainCommand);
|
|
30
32
|
program.addCommand(deepStatsCommand);
|
|
33
|
+
program.addCommand(teamCommand);
|
|
31
34
|
program
|
|
32
35
|
.name('rigour')
|
|
33
36
|
.description('🛡️ Rigour: The Quality Gate Loop for AI-Assisted Engineering')
|
|
@@ -363,6 +366,28 @@ settingsCmd
|
|
|
363
366
|
.command('path')
|
|
364
367
|
.description('Show settings file path')
|
|
365
368
|
.action(async () => { await settingsPathCommand(); });
|
|
369
|
+
const firewallCmd = program
|
|
370
|
+
.command('firewall')
|
|
371
|
+
.description('Agent Transaction Firewall — mediate, attest, and prove damage bounds');
|
|
372
|
+
firewallCmd
|
|
373
|
+
.command('status')
|
|
374
|
+
.description('Show transaction, attestation, and adversarial status')
|
|
375
|
+
.action(async () => { await firewallStatusCommand(process.cwd()); });
|
|
376
|
+
firewallCmd
|
|
377
|
+
.command('transact')
|
|
378
|
+
.description('Start a mediated transaction, verify gates, COMMIT or DISCARD')
|
|
379
|
+
.option('--agent <id>', 'Agent id for scope binding')
|
|
380
|
+
.option('--scope <globs>', 'Comma-separated allowed path globs', '**/*')
|
|
381
|
+
.option('--discard', 'Discard the current transaction worktree')
|
|
382
|
+
.action(async (options) => { await firewallTransactCommand(process.cwd(), options); });
|
|
383
|
+
firewallCmd
|
|
384
|
+
.command('adversarial')
|
|
385
|
+
.description('Replay deterministic adversarial corpus against the firewall kernel')
|
|
386
|
+
.action(async () => { await firewallAdversarialCommand(process.cwd()); });
|
|
387
|
+
firewallCmd
|
|
388
|
+
.command('admit')
|
|
389
|
+
.description('CI admission: require valid signed attestation with PASS gates')
|
|
390
|
+
.action(async () => { await firewallAdmitCommand(process.cwd()); });
|
|
366
391
|
// Check for updates before parsing (non-blocking)
|
|
367
392
|
(async () => {
|
|
368
393
|
try {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function firewallTransactCommand(cwd: string, options: {
|
|
2
|
+
agent?: string;
|
|
3
|
+
scope?: string;
|
|
4
|
+
discard?: boolean;
|
|
5
|
+
commit?: boolean;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
export declare function firewallAdversarialCommand(cwd: string): Promise<void>;
|
|
8
|
+
export declare function firewallAdmitCommand(cwd: string): Promise<void>;
|
|
9
|
+
export declare function firewallStatusCommand(cwd: string): Promise<void>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firewall CLI — transact, adversarial replay, CI admit.
|
|
3
|
+
*/
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import yaml from 'yaml';
|
|
8
|
+
import { TransactionRunner, runAdversarialCorpus, persistAdversarialReport, createAttestation, admitForCi, loadLatestAttestation, verifyAttestation, loadCurrentTransaction, listTransactions, GateRunner, ConfigSchema, loadAgentScopesFromDisk, } from '@rigour-labs/core';
|
|
9
|
+
export async function firewallTransactCommand(cwd, options) {
|
|
10
|
+
if (options.discard) {
|
|
11
|
+
const current = await loadCurrentTransaction(cwd);
|
|
12
|
+
if (!current) {
|
|
13
|
+
console.error(chalk.red('No current transaction to discard'));
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const discarded = await TransactionRunner.fromRecord(cwd, current).discard();
|
|
17
|
+
console.log(chalk.yellow(`DISCARD ${discarded.id}`));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const registered = await loadAgentScopesFromDisk(cwd);
|
|
21
|
+
if (registered.length > 0 && !options.agent) {
|
|
22
|
+
console.error(chalk.red('Agent scopes are registered — pass --agent <id> to bind the writer (fail-closed)'));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const scope = (options.scope || '**/*').split(',').map(s => s.trim()).filter(Boolean);
|
|
26
|
+
const tx = new TransactionRunner(cwd, { agentId: options.agent, scope });
|
|
27
|
+
const started = await tx.start();
|
|
28
|
+
console.log(chalk.cyan(`START transaction ${started.id}`));
|
|
29
|
+
const gateCwd = started.worktreePath || cwd;
|
|
30
|
+
if (started.worktreePath) {
|
|
31
|
+
console.log(chalk.dim(`Worktree: ${started.worktreePath}`));
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
await tx.syncWorktreeChanges();
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
await tx.discard();
|
|
38
|
+
console.error(chalk.red(`DISCARD — scope/budget: ${e.message}`));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
const configPath = path.join(cwd, 'rigour.yml');
|
|
42
|
+
let config = ConfigSchema.parse({ version: 1 });
|
|
43
|
+
if (await fs.pathExists(configPath)) {
|
|
44
|
+
config = ConfigSchema.parse(yaml.parse(await fs.readFile(configPath, 'utf-8')));
|
|
45
|
+
}
|
|
46
|
+
const runner = new GateRunner(config);
|
|
47
|
+
const { ok, gateResults } = await tx.verify(async () => {
|
|
48
|
+
const report = await runner.run(gateCwd);
|
|
49
|
+
const failedGates = [...new Set(report.failures.map(f => f.id))];
|
|
50
|
+
return { status: report.status, failedGates, score: report.stats.score };
|
|
51
|
+
});
|
|
52
|
+
if (!ok) {
|
|
53
|
+
const discarded = await tx.discard();
|
|
54
|
+
console.log(chalk.red(`DISCARD ${discarded.id} — gates ${gateResults.status}`));
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
const committed = await tx.commit();
|
|
58
|
+
const attestation = await createAttestation(cwd, {
|
|
59
|
+
transaction: committed,
|
|
60
|
+
gateResults: {
|
|
61
|
+
status: gateResults.status,
|
|
62
|
+
score: gateResults.score,
|
|
63
|
+
failedGates: gateResults.failedGates,
|
|
64
|
+
},
|
|
65
|
+
verifyRoot: gateCwd,
|
|
66
|
+
});
|
|
67
|
+
console.log(chalk.green(`COMMIT ${committed.id}`));
|
|
68
|
+
console.log(chalk.green(`Attestation ${attestation.signature.slice(0, 12)}… tree=${(attestation.treeDigest || '').slice(0, 12)} policy=${attestation.policyHash}`));
|
|
69
|
+
}
|
|
70
|
+
export async function firewallAdversarialCommand(cwd) {
|
|
71
|
+
const report = runAdversarialCorpus();
|
|
72
|
+
const out = await persistAdversarialReport(cwd, report);
|
|
73
|
+
console.log(chalk.bold(`Adversarial replay: ${report.passed} passed, ${report.failed} failed`));
|
|
74
|
+
for (const r of report.results) {
|
|
75
|
+
const mark = r.passed ? chalk.green('PASS') : chalk.red('FAIL');
|
|
76
|
+
console.log(` ${mark} ${r.caseId}: expected=${r.expected} actual=${r.actual} — ${r.reason}`);
|
|
77
|
+
if (r.suggestedRule) {
|
|
78
|
+
console.log(chalk.yellow(' suggested regression:\n') + chalk.dim(r.suggestedRule));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
console.log(chalk.dim(`Report: ${out}`));
|
|
82
|
+
if (report.failed > 0)
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
export async function firewallAdmitCommand(cwd) {
|
|
86
|
+
const result = await admitForCi(cwd);
|
|
87
|
+
if (!result.admit) {
|
|
88
|
+
console.error(chalk.red(`ADMIT DENIED: ${result.reason}`));
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
const bundle = await loadLatestAttestation(cwd);
|
|
92
|
+
const valid = bundle ? await verifyAttestation(cwd, bundle) : false;
|
|
93
|
+
console.log(chalk.green(`ADMIT OK: ${result.reason} (signature ${valid ? 'valid' : 'n/a'})`));
|
|
94
|
+
}
|
|
95
|
+
export async function firewallStatusCommand(cwd) {
|
|
96
|
+
const current = await loadCurrentTransaction(cwd);
|
|
97
|
+
const txs = await listTransactions(cwd);
|
|
98
|
+
const attestation = await loadLatestAttestation(cwd);
|
|
99
|
+
const advPath = path.join(cwd, '.rigour', 'adversarial-report.json');
|
|
100
|
+
const adv = await fs.pathExists(advPath) ? await fs.readJson(advPath) : null;
|
|
101
|
+
console.log(chalk.bold('Firewall status'));
|
|
102
|
+
console.log(` Current TX: ${current ? `${current.id} (${current.status})` : 'none'}`);
|
|
103
|
+
console.log(` Transactions: ${txs.length}`);
|
|
104
|
+
console.log(` Attestation: ${attestation ? `${attestation.transactionId} gates=${attestation.gateResults.status}` : 'none'}`);
|
|
105
|
+
console.log(` Adversarial: ${adv ? `${adv.passed} pass / ${adv.failed} fail` : 'not run'}`);
|
|
106
|
+
}
|
package/dist/commands/hooks.js
CHANGED
|
@@ -18,7 +18,7 @@ import path from 'path';
|
|
|
18
18
|
import chalk from 'chalk';
|
|
19
19
|
import { randomUUID } from 'crypto';
|
|
20
20
|
import { fileURLToPath } from 'url';
|
|
21
|
-
import {
|
|
21
|
+
import { allowLastDLPBlock, createDLPAuditEntry, formatDLPAlert, recordInteractionEvidence, recordInteractionLesson, runHookChecker, scanInputForCredentials, updateAutomaticIndexForFiles, writeDLPBlockManifest, } from '@rigour-labs/core';
|
|
22
22
|
function getHookCliVersion() {
|
|
23
23
|
const thisDir = path.dirname(fileURLToPath(import.meta.url));
|
|
24
24
|
const packagePath = path.resolve(thisDir, '../../package.json');
|
|
@@ -648,7 +648,28 @@ export async function hooksCheckCommand(cwd, options = {}) {
|
|
|
648
648
|
cwd,
|
|
649
649
|
files,
|
|
650
650
|
timeout_ms: Number.isFinite(timeout) ? timeout : 5000,
|
|
651
|
+
agentId: options.agent || process.env.RIGOUR_AGENT_ID,
|
|
651
652
|
});
|
|
653
|
+
const requestId = randomUUID();
|
|
654
|
+
const outcome = result.status === 'pass' ? 'success' : result.status === 'fail' ? 'rejected' : 'error';
|
|
655
|
+
await Promise.allSettled([
|
|
656
|
+
updateAutomaticIndexForFiles(cwd, files),
|
|
657
|
+
recordInteractionEvidence(cwd, {
|
|
658
|
+
tool: 'rigour_hooks_check', requestId, phase: 'response', outcome,
|
|
659
|
+
deterministic: result.status === 'pass', agentId: options.agent || process.env.RIGOUR_AGENT_ID,
|
|
660
|
+
files, summary: `${result.failures.length} finding(s)`,
|
|
661
|
+
}),
|
|
662
|
+
recordInteractionLesson(cwd, {
|
|
663
|
+
tool: 'rigour_hooks_check', requestId, outcome,
|
|
664
|
+
deterministic: result.status === 'pass', agentId: options.agent || process.env.RIGOUR_AGENT_ID,
|
|
665
|
+
files, summary: `${result.failures.length} finding(s)`,
|
|
666
|
+
}),
|
|
667
|
+
logStudioEvent(cwd, {
|
|
668
|
+
type: 'hook_check', requestId, outcome, status: result.status,
|
|
669
|
+
agentId: options.agent || process.env.RIGOUR_AGENT_ID,
|
|
670
|
+
files, summary: `${files.length} file(s), ${result.failures.length} finding(s)`,
|
|
671
|
+
}),
|
|
672
|
+
]);
|
|
652
673
|
// Return Cursor-compatible format if detected as Cursor hook
|
|
653
674
|
if (cursorMode) {
|
|
654
675
|
if (result.status === 'fail') {
|
package/dist/commands/index.js
CHANGED
|
@@ -30,7 +30,7 @@ async function logStudioEvent(cwd, event) {
|
|
|
30
30
|
// native dependency issues from affecting the rest of the CLI.
|
|
31
31
|
export const indexCommand = new Command('index')
|
|
32
32
|
.description('Build or update the pattern index for the current project')
|
|
33
|
-
.option('-
|
|
33
|
+
.option('--no-semantic', 'Skip local semantic embeddings and build only the structural index')
|
|
34
34
|
.option('-f, --force', 'Force a full rebuild of the index', false)
|
|
35
35
|
.option('-o, --output <path>', 'Custom path for the index file')
|
|
36
36
|
.action(async (options) => {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type AgentStatus = 'active' | 'idle' | 'completed';
|
|
2
|
+
export type AgentSessionStatus = AgentStatus | 'aborted' | 'inactive';
|
|
3
|
+
export interface StudioAgent {
|
|
4
|
+
agentId: string;
|
|
5
|
+
taskScope: string[];
|
|
6
|
+
registeredAt: string;
|
|
7
|
+
lastCheckpoint?: string;
|
|
8
|
+
status: AgentStatus;
|
|
9
|
+
}
|
|
10
|
+
export interface StudioAgentSession {
|
|
11
|
+
schemaVersion: 1;
|
|
12
|
+
sessionId: string;
|
|
13
|
+
agents: StudioAgent[];
|
|
14
|
+
status: AgentSessionStatus;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
derived: boolean;
|
|
17
|
+
dataQuality: 'valid' | 'degraded';
|
|
18
|
+
warnings: string[];
|
|
19
|
+
}
|
|
20
|
+
export declare function normalizeAgentSession(input: unknown, now?: string): StudioAgentSession;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const AGENT_STATUSES = new Set(['active', 'idle', 'completed']);
|
|
2
|
+
const SESSION_STATUSES = new Set([
|
|
3
|
+
'active',
|
|
4
|
+
'idle',
|
|
5
|
+
'completed',
|
|
6
|
+
'aborted',
|
|
7
|
+
'inactive',
|
|
8
|
+
]);
|
|
9
|
+
function record(value) {
|
|
10
|
+
return value && typeof value === 'object' ? value : {};
|
|
11
|
+
}
|
|
12
|
+
function validDate(value, fallback) {
|
|
13
|
+
if (typeof value !== 'string' && typeof value !== 'number')
|
|
14
|
+
return fallback;
|
|
15
|
+
const date = new Date(value);
|
|
16
|
+
return Number.isNaN(date.getTime()) ? fallback : date.toISOString();
|
|
17
|
+
}
|
|
18
|
+
function inferSessionStatus(agents) {
|
|
19
|
+
if (agents.length === 0)
|
|
20
|
+
return 'inactive';
|
|
21
|
+
if (agents.some((agent) => agent.status === 'active'))
|
|
22
|
+
return 'active';
|
|
23
|
+
if (agents.some((agent) => agent.status === 'idle'))
|
|
24
|
+
return 'idle';
|
|
25
|
+
return 'completed';
|
|
26
|
+
}
|
|
27
|
+
export function normalizeAgentSession(input, now = new Date().toISOString()) {
|
|
28
|
+
const source = record(input);
|
|
29
|
+
const warnings = [];
|
|
30
|
+
const rawAgents = Array.isArray(source.agents) ? source.agents : [];
|
|
31
|
+
if (!Array.isArray(source.agents) && source.agents !== undefined) {
|
|
32
|
+
warnings.push('Ignored invalid agents collection.');
|
|
33
|
+
}
|
|
34
|
+
const agents = rawAgents.map((value, index) => {
|
|
35
|
+
const raw = record(value);
|
|
36
|
+
const agentId = typeof raw.agentId === 'string' && raw.agentId.trim()
|
|
37
|
+
? raw.agentId.trim()
|
|
38
|
+
: `unknown-agent-${index + 1}`;
|
|
39
|
+
if (agentId.startsWith('unknown-agent-'))
|
|
40
|
+
warnings.push(`Agent ${index + 1} had no identifier.`);
|
|
41
|
+
const taskScope = Array.isArray(raw.taskScope)
|
|
42
|
+
? raw.taskScope.filter((scope) => typeof scope === 'string' && scope.length > 0)
|
|
43
|
+
: [];
|
|
44
|
+
if (!Array.isArray(raw.taskScope) && raw.taskScope !== undefined) {
|
|
45
|
+
warnings.push(`Agent ${agentId} had an invalid scope.`);
|
|
46
|
+
}
|
|
47
|
+
const status = typeof raw.status === 'string' && AGENT_STATUSES.has(raw.status)
|
|
48
|
+
? raw.status
|
|
49
|
+
: 'idle';
|
|
50
|
+
if (status === 'idle' && raw.status !== 'idle')
|
|
51
|
+
warnings.push(`Agent ${agentId} had no valid status.`);
|
|
52
|
+
const registeredAt = validDate(raw.registeredAt, now);
|
|
53
|
+
const lastCheckpoint = raw.lastCheckpoint === undefined
|
|
54
|
+
? undefined
|
|
55
|
+
: validDate(raw.lastCheckpoint, registeredAt);
|
|
56
|
+
return { agentId, taskScope, registeredAt, lastCheckpoint, status };
|
|
57
|
+
});
|
|
58
|
+
const requestedStatus = typeof source.status === 'string'
|
|
59
|
+
? source.status
|
|
60
|
+
: undefined;
|
|
61
|
+
const status = requestedStatus && SESSION_STATUSES.has(requestedStatus)
|
|
62
|
+
? requestedStatus
|
|
63
|
+
: inferSessionStatus(agents);
|
|
64
|
+
if (requestedStatus !== status)
|
|
65
|
+
warnings.push('Session status was inferred from agent activity.');
|
|
66
|
+
return {
|
|
67
|
+
schemaVersion: 1,
|
|
68
|
+
sessionId: typeof source.sessionId === 'string' && source.sessionId.trim()
|
|
69
|
+
? source.sessionId
|
|
70
|
+
: agents.length > 0 ? 'legacy-session' : 'inactive',
|
|
71
|
+
agents,
|
|
72
|
+
status,
|
|
73
|
+
createdAt: validDate(source.createdAt, agents[0]?.registeredAt ?? now),
|
|
74
|
+
derived: Boolean(source.derived),
|
|
75
|
+
dataQuality: warnings.length > 0 ? 'degraded' : 'valid',
|
|
76
|
+
warnings,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { normalizeAgentSession } from './studio-contracts.js';
|
|
3
|
+
describe('normalizeAgentSession', () => {
|
|
4
|
+
it('repairs a legacy session without status', () => {
|
|
5
|
+
const session = normalizeAgentSession({
|
|
6
|
+
sessionId: 'legacy',
|
|
7
|
+
agents: [{ agentId: 'agent-1', taskScope: ['src/**'], registeredAt: '2026-09-10T00:00:00Z' }],
|
|
8
|
+
}, '2026-09-10T01:00:00.000Z');
|
|
9
|
+
expect(session.status).toBe('idle');
|
|
10
|
+
expect(session.agents[0].status).toBe('idle');
|
|
11
|
+
expect(session.dataQuality).toBe('degraded');
|
|
12
|
+
expect(session.warnings).toContain('Session status was inferred from agent activity.');
|
|
13
|
+
});
|
|
14
|
+
it('returns an inactive contract for malformed input', () => {
|
|
15
|
+
expect(normalizeAgentSession({ agents: 'broken' }).status).toBe('inactive');
|
|
16
|
+
expect(normalizeAgentSession(null).agents).toEqual([]);
|
|
17
|
+
});
|
|
18
|
+
it('preserves valid current sessions', () => {
|
|
19
|
+
const session = normalizeAgentSession({
|
|
20
|
+
sessionId: 'session-1',
|
|
21
|
+
status: 'active',
|
|
22
|
+
createdAt: '2026-09-10T00:00:00Z',
|
|
23
|
+
agents: [{
|
|
24
|
+
agentId: 'agent-1',
|
|
25
|
+
taskScope: ['src/**'],
|
|
26
|
+
registeredAt: '2026-09-10T00:00:00Z',
|
|
27
|
+
status: 'active',
|
|
28
|
+
}],
|
|
29
|
+
});
|
|
30
|
+
expect(session.dataQuality).toBe('valid');
|
|
31
|
+
expect(session.status).toBe('active');
|
|
32
|
+
});
|
|
33
|
+
});
|