@rigstate/cli 0.6.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.
Files changed (56) hide show
  1. package/.env.example +5 -0
  2. package/IMPLEMENTATION.md +239 -0
  3. package/QUICK_START.md +220 -0
  4. package/README.md +150 -0
  5. package/dist/index.cjs +3987 -0
  6. package/dist/index.cjs.map +1 -0
  7. package/dist/index.d.cts +1 -0
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.js +3964 -0
  10. package/dist/index.js.map +1 -0
  11. package/install.sh +15 -0
  12. package/package.json +53 -0
  13. package/src/commands/check.ts +329 -0
  14. package/src/commands/config.ts +81 -0
  15. package/src/commands/daemon.ts +197 -0
  16. package/src/commands/env.ts +158 -0
  17. package/src/commands/fix.ts +140 -0
  18. package/src/commands/focus.ts +134 -0
  19. package/src/commands/hooks.ts +163 -0
  20. package/src/commands/init.ts +282 -0
  21. package/src/commands/link.ts +45 -0
  22. package/src/commands/login.ts +35 -0
  23. package/src/commands/mcp.ts +73 -0
  24. package/src/commands/nexus.ts +81 -0
  25. package/src/commands/override.ts +65 -0
  26. package/src/commands/scan.ts +242 -0
  27. package/src/commands/sync-rules.ts +191 -0
  28. package/src/commands/sync.ts +339 -0
  29. package/src/commands/watch.ts +283 -0
  30. package/src/commands/work.ts +172 -0
  31. package/src/daemon/bridge-listener.ts +127 -0
  32. package/src/daemon/core.ts +184 -0
  33. package/src/daemon/factory.ts +45 -0
  34. package/src/daemon/file-watcher.ts +97 -0
  35. package/src/daemon/guardian-monitor.ts +133 -0
  36. package/src/daemon/heuristic-engine.ts +203 -0
  37. package/src/daemon/intervention-protocol.ts +128 -0
  38. package/src/daemon/telemetry.ts +23 -0
  39. package/src/daemon/types.ts +18 -0
  40. package/src/hive/gateway.ts +74 -0
  41. package/src/hive/protocol.ts +29 -0
  42. package/src/hive/scrubber.ts +72 -0
  43. package/src/index.ts +85 -0
  44. package/src/nexus/council.ts +103 -0
  45. package/src/nexus/dispatcher.ts +133 -0
  46. package/src/utils/config.ts +83 -0
  47. package/src/utils/files.ts +95 -0
  48. package/src/utils/governance.ts +128 -0
  49. package/src/utils/logger.ts +66 -0
  50. package/src/utils/manifest.ts +18 -0
  51. package/src/utils/rule-engine.ts +292 -0
  52. package/src/utils/skills-provisioner.ts +153 -0
  53. package/src/utils/version.ts +1 -0
  54. package/src/utils/watchdog.ts +215 -0
  55. package/tsconfig.json +29 -0
  56. package/tsup.config.ts +11 -0
@@ -0,0 +1,282 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import fs from 'fs/promises';
4
+ import path from 'path';
5
+ import ora from 'ora';
6
+ import { execSync } from 'child_process';
7
+ import { loadManifest } from '../utils/manifest.js';
8
+ import { getApiUrl, getApiKey, setProjectId } from '../utils/config.js';
9
+ import axios from 'axios';
10
+
11
+ export function createInitCommand() {
12
+ return new Command('init')
13
+ .description('Initialize or link a Rigstate project (interactive mode available)')
14
+ .argument('[project-id]', 'ID of the project to link (optional, prompts if not provided)')
15
+ .option('-f, --force', 'Overwrite existing .cursorrules file')
16
+ .option('--rules-only', 'Only regenerate .cursorrules without interactive setup')
17
+ .action(async (projectIdArg, options) => {
18
+ const spinner = ora('Initializing Rigstate project...').start();
19
+
20
+ let apiKey: string;
21
+ try {
22
+ apiKey = getApiKey();
23
+ } catch (e) {
24
+ spinner.fail(chalk.red('Not authenticated. Run "rigstate login" first.'));
25
+ return;
26
+ }
27
+
28
+ const apiUrl = getApiUrl();
29
+ let projectId = projectIdArg;
30
+
31
+ try {
32
+ // If --rules-only, just regenerate rules from existing manifest
33
+ if (options.rulesOnly) {
34
+ const manifest = await loadManifest();
35
+ if (!manifest) {
36
+ spinner.fail('No .rigstate manifest found. Run "rigstate init" first.');
37
+ return;
38
+ }
39
+ projectId = manifest.project_id;
40
+ await generateRules(apiUrl, apiKey, projectId, options.force, spinner);
41
+ return;
42
+ }
43
+
44
+ // Interactive mode if no project ID provided
45
+ if (!projectId) {
46
+ spinner.stop();
47
+
48
+ // Dynamic import for inquirer
49
+ const inquirer = (await import('inquirer')).default;
50
+
51
+ spinner.start('Fetching your projects...');
52
+
53
+ // Fetch projects via API
54
+ let projects: any[] = [];
55
+ try {
56
+ const projectsResponse = await axios.get(`${apiUrl}/api/v1/projects`, {
57
+ headers: { Authorization: `Bearer ${apiKey}` }
58
+ });
59
+ if (projectsResponse.data.success) {
60
+ projects = projectsResponse.data.data.projects || [];
61
+ }
62
+ } catch (e: any) {
63
+ // API might not exist yet - fallback to manual entry
64
+ spinner.info('Projects API not available. Using manual entry mode.');
65
+ }
66
+
67
+ spinner.stop();
68
+
69
+ if (projects.length === 0) {
70
+ // Fallback: Ask for project ID manually
71
+ const { manualProjectId } = await inquirer.prompt([
72
+ {
73
+ type: 'input',
74
+ name: 'manualProjectId',
75
+ message: 'Enter Project ID (from Rigstate dashboard):',
76
+ validate: (input: string) => input.trim() ? true : 'Project ID is required'
77
+ }
78
+ ]);
79
+ projectId = manualProjectId;
80
+ } else {
81
+ // Build choices from fetched projects
82
+ const choices: any[] = [
83
+ { name: 'āž• Create New Project', value: 'NEW' },
84
+ new inquirer.Separator()
85
+ ];
86
+
87
+ projects.forEach((p: any) => {
88
+ choices.push({
89
+ name: `[${p.organization_name || 'Personal'}] ${p.name} (${p.status})`,
90
+ value: p.id
91
+ });
92
+ });
93
+
94
+ const { selectedId } = await inquirer.prompt([
95
+ {
96
+ type: 'list',
97
+ name: 'selectedId',
98
+ message: 'Select a project to link:',
99
+ choices: choices,
100
+ pageSize: 15
101
+ }
102
+ ]);
103
+
104
+ if (selectedId === 'NEW') {
105
+ // Create new project flow
106
+ const { newName } = await inquirer.prompt([
107
+ {
108
+ type: 'input',
109
+ name: 'newName',
110
+ message: 'Enter project name:',
111
+ validate: (input: string) => input.trim() ? true : 'Name is required'
112
+ }
113
+ ]);
114
+
115
+ // Fetch organizations (with fallback)
116
+ spinner.start('Fetching organizations...');
117
+ let orgs: any[] = [];
118
+ try {
119
+ const orgsResponse = await axios.get(`${apiUrl}/api/v1/organizations`, {
120
+ headers: { Authorization: `Bearer ${apiKey}` }
121
+ });
122
+ orgs = orgsResponse.data.data?.organizations || [];
123
+ } catch (e) {
124
+ // API might not exist - skip org selection
125
+ }
126
+ spinner.stop();
127
+
128
+ let selectedOrgId = orgs[0]?.id;
129
+
130
+ if (orgs.length > 1) {
131
+ const { orgId } = await inquirer.prompt([
132
+ {
133
+ type: 'list',
134
+ name: 'orgId',
135
+ message: 'Which organization does this belong to?',
136
+ choices: orgs.map((org: any) => ({
137
+ name: `${org.name} (${org.role || 'member'})`,
138
+ value: org.id
139
+ }))
140
+ }
141
+ ]);
142
+ selectedOrgId = orgId;
143
+ }
144
+
145
+ if (!selectedOrgId) {
146
+ console.log(chalk.yellow('No organization available. Please create the project via the Rigstate dashboard.'));
147
+ return;
148
+ }
149
+
150
+ // Create project via API
151
+ spinner.start('Creating new project...');
152
+ try {
153
+ const createResponse = await axios.post(`${apiUrl}/api/v1/projects`, {
154
+ name: newName,
155
+ organization_id: selectedOrgId
156
+ }, {
157
+ headers: { Authorization: `Bearer ${apiKey}` }
158
+ });
159
+
160
+ if (!createResponse.data.success) {
161
+ spinner.fail(chalk.red('Failed to create project: ' + createResponse.data.error));
162
+ return;
163
+ }
164
+
165
+ projectId = createResponse.data.data.project.id;
166
+ spinner.succeed(chalk.green(`Created new project: ${newName}`));
167
+ } catch (e: any) {
168
+ spinner.fail(chalk.red('Project creation API not available. Please create via dashboard.'));
169
+ return;
170
+ }
171
+ } else {
172
+ projectId = selectedId;
173
+ }
174
+ }
175
+
176
+ spinner.start(`Linking to project ID: ${projectId}...`);
177
+ }
178
+
179
+ // Core link logic
180
+ // Save project ID to config
181
+ setProjectId(projectId);
182
+
183
+ // Write local manifest
184
+ const manifestPath = path.join(process.cwd(), '.rigstate');
185
+ const manifestContent = {
186
+ project_id: projectId,
187
+ last_linked: new Date().toISOString(),
188
+ api_url: apiUrl
189
+ };
190
+ await fs.writeFile(manifestPath, JSON.stringify(manifestContent, null, 2), 'utf-8');
191
+
192
+ // Initialize git if needed
193
+ try {
194
+ await fs.access('.git');
195
+ } catch {
196
+ spinner.text = 'Initializing git repository...';
197
+ execSync('git init', { stdio: 'ignore' });
198
+ }
199
+
200
+ spinner.succeed(chalk.green(`āœ… Linked to project: ${projectId}`));
201
+
202
+ // Generate rules
203
+ await generateRules(apiUrl, apiKey, projectId, options.force, spinner);
204
+
205
+ console.log('');
206
+ console.log(chalk.blue('Next steps:'));
207
+ console.log(chalk.dim(' rigstate sync - Sync roadmap and context'));
208
+ console.log(chalk.dim(' rigstate watch - Start development loop'));
209
+ console.log(chalk.dim(' rigstate focus - Get current task'));
210
+
211
+ } catch (e: any) {
212
+ spinner.fail(chalk.red('Initialization failed: ' + e.message));
213
+ }
214
+ });
215
+ }
216
+
217
+ async function generateRules(apiUrl: string, apiKey: string, projectId: string, force: boolean, spinner: any) {
218
+ spinner.start('Generating AI rules (MDC + AGENTS.md)...');
219
+
220
+ try {
221
+ const response = await axios.post(`${apiUrl}/api/v1/rules/generate`, {
222
+ project_id: projectId
223
+ }, {
224
+ headers: { Authorization: `Bearer ${apiKey}` }
225
+ });
226
+
227
+ if (response.data.success || response.data.files) {
228
+ const files = response.data.files || [];
229
+
230
+ if (files.length === 0 && response.data.rules) {
231
+ // Fallback to legacy mono-file if no multiple files returned
232
+ const rulesPath = path.join(process.cwd(), '.cursorrules');
233
+ await fs.writeFile(rulesPath, response.data.rules, 'utf-8');
234
+ spinner.succeed(chalk.green('āœ” Generated .cursorrules (legacy mode)'));
235
+ return;
236
+ }
237
+
238
+ for (const file of files) {
239
+ const targetPath = path.join(process.cwd(), file.path);
240
+ const targetDir = path.dirname(targetPath);
241
+
242
+ // Ensure directory exists
243
+ await fs.mkdir(targetDir, { recursive: true });
244
+
245
+ // Check if exists and force
246
+ try {
247
+ await fs.access(targetPath);
248
+ if (!force && !file.path.startsWith('.cursor/rules/')) {
249
+ console.log(chalk.dim(` ${file.path} already exists. Skipping.`));
250
+ continue;
251
+ }
252
+ } catch {
253
+ // File doesn't exist, proceed
254
+ }
255
+
256
+ await fs.writeFile(targetPath, file.content, 'utf-8');
257
+ }
258
+
259
+ // Cleanup legacy .cursorrules if we have new files and aren't inhibited
260
+ if (files.length > 0) {
261
+ const legacyPath = path.join(process.cwd(), '.cursorrules');
262
+ try {
263
+ const stats = await fs.stat(legacyPath);
264
+ if (stats.isFile()) {
265
+ await fs.rename(legacyPath, `${legacyPath}.bak`);
266
+ console.log(chalk.dim(' Moved legacy .cursorrules to .cursorrules.bak'));
267
+ }
268
+ } catch (e) {
269
+ // Ignore if legacy file doesn't exist
270
+ }
271
+ }
272
+
273
+ spinner.succeed(chalk.green(`āœ” Generated ${files.length} rule files (v${response.data.version || '3.0'})`));
274
+ } else {
275
+ spinner.info(chalk.dim(' Rules generation skipped (API response invalid)'));
276
+ }
277
+ } catch (e: any) {
278
+ spinner.info(chalk.dim(` Rules generation failed: ${e.message}`));
279
+ }
280
+ }
281
+
282
+
@@ -0,0 +1,45 @@
1
+ import { Command } from 'commander';
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+ import chalk from 'chalk';
5
+ import os from 'os';
6
+
7
+ export function createLinkCommand() {
8
+ return new Command('link')
9
+ .description('Link current directory to a Rigstate project')
10
+ .argument('<projectId>', 'Project ID to link')
11
+ .action(async (projectId) => {
12
+ // Check Global Override (Rigstate v2.7)
13
+ try {
14
+ const globalPath = path.join(os.homedir(), '.rigstate', 'config.json');
15
+ const globalData = await fs.readFile(globalPath, 'utf-8').catch(() => null);
16
+ if (globalData) {
17
+ const config = JSON.parse(globalData);
18
+ const cwd = process.cwd();
19
+ if (config.overrides && config.overrides[cwd]) {
20
+ const overrideId = config.overrides[cwd];
21
+ if (overrideId !== projectId) {
22
+ console.warn(chalk.yellow(`Global override detected. Enforcing project ID: ${overrideId}`));
23
+ projectId = overrideId;
24
+ }
25
+ }
26
+ }
27
+ } catch (e) { }
28
+
29
+ const manifestPath = path.join(process.cwd(), '.rigstate');
30
+
31
+ const content = {
32
+ project_id: projectId,
33
+ api_url: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000',
34
+ linked_at: new Date().toISOString()
35
+ };
36
+
37
+ try {
38
+ await fs.writeFile(manifestPath, JSON.stringify(content, null, 2), 'utf-8');
39
+ console.log(chalk.green(`āœ” Linked to project ID: ${projectId}`));
40
+ console.log(chalk.dim(`Created local context manifest at .rigstate`));
41
+ } catch (error: any) {
42
+ console.error(chalk.red(`Failed to link project: ${error.message}`));
43
+ }
44
+ });
45
+ }
@@ -0,0 +1,35 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { setApiKey } from '../utils/config.js';
4
+
5
+ export function createLoginCommand(): Command {
6
+ return new Command('login')
7
+ .description('Authenticate with your Rigstate API key')
8
+ .argument('<api-key>', 'Your Rigstate API key (starts with sk_)')
9
+ .action(async (apiKey: string) => {
10
+ try {
11
+ // Basic validation
12
+ if (!apiKey || !apiKey.startsWith('sk_rigstate_')) {
13
+ console.error(chalk.red('āŒ Invalid API key format'));
14
+ console.error(chalk.dim('API keys must start with "sk_rigstate_"'));
15
+ process.exit(1);
16
+ }
17
+
18
+ // Store the API key
19
+ setApiKey(apiKey);
20
+
21
+ console.log(chalk.green('āœ… Successfully logged in!'));
22
+ console.log(
23
+ chalk.dim(
24
+ `\nYour API key has been securely stored. You can now use "rigstate scan" to audit your code.`
25
+ )
26
+ );
27
+ } catch (error) {
28
+ console.error(
29
+ chalk.red('āŒ Login failed:'),
30
+ error instanceof Error ? error.message : 'Unknown error'
31
+ );
32
+ process.exit(1);
33
+ }
34
+ });
35
+ }
@@ -0,0 +1,73 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { spawn } from 'child_process';
4
+ import path from 'path';
5
+ import fs from 'fs';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ // ESM compatibility for __dirname
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ export function createMcpCommand() {
13
+ const mcp = new Command('mcp');
14
+
15
+ mcp
16
+ .description('Run the Rigstate MCP server for AI editors')
17
+ .action(async () => {
18
+ // Determine the path to the MCP server
19
+ const possiblePaths = [
20
+ // From packages/cli -> packages/mcp (sibling package)
21
+ path.resolve(__dirname, '../../mcp/dist/index.js'),
22
+ // If installed globally or via npm
23
+ path.resolve(__dirname, '../../../mcp/dist/index.js'),
24
+ // Development path from packages/cli/dist
25
+ path.resolve(__dirname, '../../../packages/mcp/dist/index.js'),
26
+ ];
27
+
28
+ let serverPath = '';
29
+ for (const p of possiblePaths) {
30
+ if (fs.existsSync(p)) {
31
+ serverPath = p;
32
+ break;
33
+ }
34
+ }
35
+
36
+ if (!serverPath) {
37
+ console.error(chalk.red('āŒ Error: Rigstate MCP Server binary not found.'));
38
+ console.error(chalk.yellow('Please ensure that the mcp package is built:'));
39
+ console.error(chalk.white(' cd packages/mcp && npm run build'));
40
+ console.error('');
41
+ console.error(chalk.dim('Or run directly with:'));
42
+ console.error(chalk.white(' npx @rigstate/mcp'));
43
+ process.exit(1);
44
+ }
45
+
46
+ console.log(chalk.dim(`Starting MCP server from: ${serverPath}`));
47
+
48
+ // Map VIBE_API_KEY to RIGSTATE_API_KEY if needed (backwards compatibility)
49
+ if (process.env.VIBE_API_KEY && !process.env.RIGSTATE_API_KEY) {
50
+ process.env.RIGSTATE_API_KEY = process.env.VIBE_API_KEY;
51
+ }
52
+
53
+ // Spawn the MCP server as a child process
54
+ // MCP uses stdio for communication, so we inherit it
55
+ const worker = spawn('node', [serverPath], {
56
+ env: process.env,
57
+ stdio: ['inherit', 'inherit', 'inherit']
58
+ });
59
+
60
+ worker.on('error', (err) => {
61
+ console.error(chalk.red(`āŒ Failed to start MCP server: ${err.message}`));
62
+ process.exit(1);
63
+ });
64
+
65
+ worker.on('exit', (code) => {
66
+ if (code !== 0 && code !== null) {
67
+ process.exit(code);
68
+ }
69
+ });
70
+ });
71
+
72
+ return mcp;
73
+ }
@@ -0,0 +1,81 @@
1
+
2
+ import { Command } from 'commander';
3
+ import chalk from 'chalk';
4
+ import { NexusDispatcher } from '../nexus/dispatcher.js';
5
+ import { NexusContext } from '@rigstate/shared';
6
+ import inquirer from 'inquirer';
7
+
8
+ export function createNexusCommand() {
9
+ const command = new Command('nexus');
10
+
11
+ command
12
+ .description('Interact with The Multi-Agent Nexus (Phase 8)')
13
+ .argument('<intent>', 'The natural language instruction for the swarm')
14
+ .option('--dry-run', 'Enable Dry-Run mode (Kill-Switch Active)', true)
15
+ .option('--force', 'Disable Dry-Run mode (DANGEROUS)', false)
16
+ .action(async (intent: string, options) => {
17
+ console.log(chalk.bold.magenta('\n🦁 Welcome to The Nexus (Phase 8)\n'));
18
+
19
+ const dryRun = !options.force;
20
+
21
+ if (!dryRun) {
22
+ console.log(chalk.black.bgYellow(' WARNING ') + chalk.yellow(' Dry-Run disabled! Eitri is authorized to write code.'));
23
+ const { confirm } = await inquirer.prompt([{
24
+ type: 'confirm',
25
+ name: 'confirm',
26
+ message: 'Are you absolutely sure you want to bypass the Kill-Switch?',
27
+ default: false
28
+ }]);
29
+ if (!confirm) {
30
+ console.log('Aborting.');
31
+ process.exit(0);
32
+ }
33
+ }
34
+
35
+ // Context setup
36
+ const context: NexusContext = {
37
+ projectId: process.env.RIGSTATE_PROJECT_ID || 'local',
38
+ rootPath: process.cwd(),
39
+ sessionUser: 'cli-user', // Should strictly be pulled from auth
40
+ dryRun
41
+ };
42
+
43
+ const dispatcher = new NexusDispatcher(context);
44
+
45
+ // Setup Listeners for Visualization
46
+ dispatcher.on('order:created', (o) => {
47
+ console.log(chalk.blue(`šŸ†• [${o.id.slice(0, 6)}] Order Created: `) + o.intent);
48
+ });
49
+
50
+ dispatcher.on('order:started', (o) => {
51
+ console.log(chalk.yellow(`ā³ [${o.id.slice(0, 6)}] Processing...`));
52
+ });
53
+
54
+ dispatcher.on('order:blocked', (o) => {
55
+ console.log(chalk.red(`šŸ›‘ [${o.id.slice(0, 6)}] BLOCKED by Kill-Switch`));
56
+ console.log(chalk.dim(` Target: ${o.targetAgent} | Action: ${o.action}`));
57
+ console.log(chalk.dim(' Run with --force to execute automatically (NOT RECOMMENDED).'));
58
+ });
59
+
60
+ dispatcher.on('agent:SINDRE', (o) => console.log(chalk.cyan(`šŸ¤– Sindre (Vault): I'm on it! (${o.action})`)));
61
+ dispatcher.on('agent:EITRI', (o) => console.log(chalk.green(`šŸ‘· Eitri (Smith): Ready to build! (${o.action})`)));
62
+
63
+ // Simulation: Frank Analysis Logic
64
+ // In a real scenario, this would call LLM/MCP to get the decomposition.
65
+ // Here we hardcode a mocked response to demonstrate the CLI flow.
66
+
67
+ console.log(chalk.dim('🧠 Frank is analyzing your intent...'));
68
+ await new Promise(r => setTimeout(r, 800)); // Simulate thinking
69
+
70
+ // MOCK: Dispatch logic
71
+ if (intent.toLowerCase().includes('db') || intent.toLowerCase().includes('database')) {
72
+ await dispatcher.dispatch('FRANK', 'SINDRE', intent, 'db.analyze', { raw: intent });
73
+ } else if (intent.toLowerCase().includes('create') || intent.toLowerCase().includes('code')) {
74
+ await dispatcher.dispatch('FRANK', 'EITRI', intent, 'fs.write', { path: 'src/demo.ts', content: '// demo' });
75
+ } else {
76
+ console.log(chalk.gray("Frank didn't understand. Try 'create file' or 'check database'."));
77
+ }
78
+ });
79
+
80
+ return command;
81
+ }
@@ -0,0 +1,65 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { performOverride, getSessionState } from '../utils/governance.js';
4
+ import { getApiKey, getProjectId, getApiUrl } from '../utils/config.js';
5
+ import axios from 'axios';
6
+
7
+ export function createOverrideCommand() {
8
+ const override = new Command('override');
9
+
10
+ override
11
+ .description('Emergency Override for Governance Soft Locks')
12
+ .argument('<violationId>', 'ID of the violation to override (or "all")')
13
+ .requiredOption('-r, --reason <reason>', 'Description of why this override is necessary')
14
+ .action(async (violationId, options) => {
15
+ const { reason } = options;
16
+
17
+ console.log(chalk.bold(`\nšŸ”“ Initiating Governance Override Protocol...`));
18
+
19
+ const session = await getSessionState(process.cwd());
20
+
21
+ if (session.status !== 'SOFT_LOCK') {
22
+ console.log(chalk.yellow(' Info: Session is not currently locked.'));
23
+ return; // Not locked, but maybe we still want to log the "intent"?
24
+ }
25
+
26
+ console.log(chalk.dim(` Active Violation: ${session.active_violation}`));
27
+ console.log(chalk.dim(` Reason Provided: "${reason}"`));
28
+
29
+ // Perform Local Unlock
30
+ const success = await performOverride(violationId, reason, process.cwd());
31
+
32
+ if (success) {
33
+ console.log(chalk.green(` āœ… Session UNLOCKED.`));
34
+ console.log(chalk.dim(` This event has been logged to the Mission Report.`));
35
+
36
+ // Optional: Notify Cloud about the Override (Audit Trail)
37
+ try {
38
+ const projectId = getProjectId();
39
+ if (projectId) {
40
+ const apiUrl = getApiUrl();
41
+ const apiKey = getApiKey();
42
+
43
+ await axios.post(`${apiUrl}/api/v1/execution-logs`, {
44
+ project_id: projectId,
45
+ task_id: 'OVERRIDE-' + Date.now(),
46
+ task_title: `Governance Override: ${violationId}`,
47
+ status: 'COMPLETED',
48
+ execution_summary: `Manual override executed. Reason: ${reason}`,
49
+ agent_role: 'SUPERVISOR' // Override is a supervisor action
50
+ }, {
51
+ headers: { Authorization: `Bearer ${apiKey}` }
52
+ });
53
+ console.log(chalk.dim(` ☁ Audit log synced to Cloud.`));
54
+ }
55
+ } catch (e: any) {
56
+ console.log(chalk.dim(` (Cloud audit sync failed: ${e.message})`));
57
+ }
58
+
59
+ } else {
60
+ console.log(chalk.red(` šŸ›‘ Override Failed. Check project configuration.`));
61
+ }
62
+ });
63
+
64
+ return override;
65
+ }