@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
package/install.sh ADDED
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+
3
+ # Build the CLI
4
+ echo "šŸ”Ø Building @rigstate/cli..."
5
+ npm run build
6
+
7
+ # Install globally
8
+ echo "šŸ“¦ Installing globally..."
9
+ echo "Note: This may require sudo/administrator permissions"
10
+ npm install -g .
11
+
12
+ echo "āœ… Installation complete!"
13
+ echo ""
14
+ echo "Try running:"
15
+ echo " rigstate --help"
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@rigstate/cli",
3
+ "version": "0.6.0",
4
+ "description": "Rigstate CLI - Code audit, sync and supervision tool",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "bin": {
8
+ "rigstate": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "dev": "tsup --watch",
12
+ "build": "tsup",
13
+ "lint": "tsc --noEmit",
14
+ "start": "node dist/index.js",
15
+ "test": "node dist/index.js --help"
16
+ },
17
+ "dependencies": {
18
+ "@rigstate/rules-engine": "*",
19
+ "@rigstate/shared": "*",
20
+ "uuid": "^9.0.1",
21
+ "@types/diff": "^7.0.2",
22
+ "@types/inquirer": "^9.0.9",
23
+ "axios": "^1.6.5",
24
+ "chalk": "^5.3.0",
25
+ "chokidar": "^3.6.0",
26
+ "commander": "^12.0.0",
27
+ "conf": "^12.0.0",
28
+ "diff": "^4.0.2",
29
+ "dotenv": "^16.4.1",
30
+ "glob": "^10.3.10",
31
+ "inquirer": "^9.3.8",
32
+ "ora": "^8.0.1"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^20.11.5",
36
+ "@types/uuid": "^10.0.0",
37
+ "tsup": "^8.0.1",
38
+ "typescript": "^5.3.3"
39
+ },
40
+ "engines": {
41
+ "node": ">=18.0.0"
42
+ },
43
+ "keywords": [
44
+ "rigstate",
45
+ "cli",
46
+ "audit",
47
+ "security",
48
+ "code-quality",
49
+ "supervisor"
50
+ ],
51
+ "author": "Rigstate",
52
+ "license": "MIT"
53
+ }
@@ -0,0 +1,329 @@
1
+ /**
2
+ * rigstate check - Validate code against Guardian rules
3
+ *
4
+ * Usage:
5
+ * rigstate check # Check current directory
6
+ * rigstate check ./src # Check specific path
7
+ * rigstate check --strict # Exit 1 on any violation
8
+ * rigstate check --strict=critical # Exit 1 only on critical
9
+ * rigstate check --staged # Only check staged files (for pre-commit)
10
+ * rigstate check --json # Output as JSON
11
+ */
12
+
13
+ import { Command } from 'commander';
14
+ import chalk from 'chalk';
15
+ import ora from 'ora';
16
+ import axios from 'axios';
17
+ import { glob } from 'glob';
18
+ import fs from 'fs/promises';
19
+ import path from 'path';
20
+ import { execSync } from 'child_process';
21
+ import { getApiKey, getApiUrl, getProjectId } from '../utils/config.js';
22
+ import { loadManifest } from '../utils/manifest.js';
23
+ import {
24
+ checkFile,
25
+ formatViolations,
26
+ summarizeResults,
27
+ type EffectiveRule,
28
+ type CheckResult,
29
+ type Violation
30
+ } from '../utils/rule-engine.js';
31
+
32
+ // Cache settings
33
+ const CACHE_FILE = '.rigstate/rules-cache.json';
34
+ const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
35
+ const CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours (offline limit)
36
+
37
+ interface CachedRules {
38
+ timestamp: string;
39
+ projectId: string;
40
+ rules: EffectiveRule[];
41
+ settings: { lmax: number; lmax_warning: number };
42
+ }
43
+
44
+ export function createCheckCommand(): Command {
45
+ return new Command('check')
46
+ .description('Validate code against Guardian architectural rules')
47
+ .argument('[path]', 'Directory or file to check', '.')
48
+ .option('--project <id>', 'Project ID (or use .rigstate manifest)')
49
+ .option('--strict [level]', 'Exit 1 on violations. Level: "all" (default) or "critical"')
50
+ .option('--staged', 'Only check git staged files (for pre-commit hooks)')
51
+ .option('--json', 'Output results as JSON')
52
+ .option('--no-cache', 'Skip rule cache and fetch fresh from API')
53
+ .action(async (targetPath: string, options: {
54
+ project?: string;
55
+ strict?: boolean | string;
56
+ staged?: boolean;
57
+ json?: boolean;
58
+ cache?: boolean;
59
+ }) => {
60
+ const spinner = ora();
61
+
62
+ try {
63
+ // 1. Resolve project context
64
+ let projectId = options.project;
65
+ let apiUrl = getApiUrl();
66
+
67
+ if (!projectId) {
68
+ const manifest = await loadManifest();
69
+ if (manifest) {
70
+ projectId = manifest.project_id;
71
+ if (manifest.api_url) apiUrl = manifest.api_url;
72
+ }
73
+ }
74
+
75
+ if (!projectId) {
76
+ projectId = getProjectId();
77
+ }
78
+
79
+ if (!projectId) {
80
+ console.log(chalk.red('āŒ No project context found.'));
81
+ console.log(chalk.dim(' Run "rigstate link" or pass --project <id>'));
82
+ process.exit(2);
83
+ }
84
+
85
+ // 2. Get API key
86
+ let apiKey: string;
87
+ try {
88
+ apiKey = getApiKey();
89
+ } catch {
90
+ console.log(chalk.red('āŒ Not authenticated. Run "rigstate login" first.'));
91
+ process.exit(2);
92
+ }
93
+
94
+ // 3. Fetch rules (with caching)
95
+ spinner.start('Fetching Guardian rules...');
96
+ let rules: EffectiveRule[];
97
+ let settings: { lmax: number; lmax_warning: number };
98
+
99
+ try {
100
+ const cached = options.cache !== false ? await loadCachedRules(projectId) : null;
101
+
102
+ if (cached && !isStale(cached.timestamp, CACHE_TTL_MS)) {
103
+ rules = cached.rules;
104
+ settings = cached.settings;
105
+ spinner.text = 'Using cached rules...';
106
+ } else {
107
+ // Fetch from API
108
+ const response = await axios.get(`${apiUrl}/api/v1/guardian/rules`, {
109
+ params: { project_id: projectId },
110
+ headers: { Authorization: `Bearer ${apiKey}` },
111
+ timeout: 10000
112
+ });
113
+
114
+ if (!response.data.success) {
115
+ throw new Error(response.data.error || 'Unknown API error');
116
+ }
117
+
118
+ rules = response.data.data.rules;
119
+ settings = response.data.data.settings;
120
+
121
+ // Save to cache
122
+ await saveCachedRules(projectId, rules, settings);
123
+ }
124
+ } catch (apiError: any) {
125
+ // Fallback to cache if API fails
126
+ const cached = await loadCachedRules(projectId);
127
+
128
+ if (cached && !isStale(cached.timestamp, CACHE_MAX_AGE_MS)) {
129
+ spinner.warn(chalk.yellow('Using cached rules (API unavailable)'));
130
+ rules = cached.rules;
131
+ settings = cached.settings;
132
+ } else {
133
+ spinner.fail(chalk.red('Failed to fetch rules and no valid cache'));
134
+ console.log(chalk.dim(` Error: ${apiError.message}`));
135
+ process.exit(2);
136
+ }
137
+ }
138
+
139
+ spinner.succeed(`Loaded ${rules.length} Guardian rules`);
140
+
141
+ // 4. Get files to check
142
+ const scanPath = path.resolve(process.cwd(), targetPath);
143
+ let filesToCheck: string[];
144
+
145
+ if (options.staged) {
146
+ // Only staged files
147
+ spinner.start('Getting staged files...');
148
+ try {
149
+ const stagedOutput = execSync('git diff --cached --name-only --diff-filter=ACMR', {
150
+ encoding: 'utf-8',
151
+ cwd: process.cwd()
152
+ });
153
+ filesToCheck = stagedOutput
154
+ .split('\n')
155
+ .filter(f => f.trim())
156
+ .filter(f => isCodeFile(f))
157
+ .map(f => path.resolve(process.cwd(), f));
158
+ } catch {
159
+ spinner.fail('Not a git repository or no staged files');
160
+ process.exit(2);
161
+ }
162
+ } else {
163
+ // All code files in path
164
+ spinner.start(`Scanning ${chalk.cyan(targetPath)}...`);
165
+ const pattern = path.join(scanPath, '**/*');
166
+ const allFiles = await glob(pattern, {
167
+ nodir: true,
168
+ dot: false,
169
+ ignore: [
170
+ '**/node_modules/**',
171
+ '**/.git/**',
172
+ '**/dist/**',
173
+ '**/build/**',
174
+ '**/.next/**',
175
+ '**/coverage/**'
176
+ ]
177
+ });
178
+ filesToCheck = allFiles.filter(f => isCodeFile(f));
179
+ }
180
+
181
+ if (filesToCheck.length === 0) {
182
+ spinner.warn(chalk.yellow('No code files found to check.'));
183
+ outputResults([], !!options.json);
184
+ process.exit(0);
185
+ }
186
+
187
+ spinner.succeed(`Found ${filesToCheck.length} files to check`);
188
+
189
+ // 5. Run checks
190
+ spinner.start('Running Guardian checks...');
191
+ const results: CheckResult[] = [];
192
+
193
+ for (let i = 0; i < filesToCheck.length; i++) {
194
+ const file = filesToCheck[i];
195
+ spinner.text = `Checking ${i + 1}/${filesToCheck.length}: ${path.basename(file)}`;
196
+ const result = await checkFile(file, rules, process.cwd());
197
+ results.push(result);
198
+ }
199
+
200
+ spinner.stop();
201
+
202
+ // 6. Output results
203
+ const summary = summarizeResults(results);
204
+
205
+ if (options.json) {
206
+ outputResults(results, true);
207
+ } else {
208
+ outputResults(results, false);
209
+
210
+ // Summary
211
+ console.log('\n' + chalk.bold('šŸ“Š Summary'));
212
+ console.log(chalk.dim('─'.repeat(50)));
213
+ console.log(`Files checked: ${chalk.cyan(summary.totalFiles)}`);
214
+ console.log(`Total violations: ${summary.totalViolations > 0 ? chalk.red(summary.totalViolations) : chalk.green(0)}`);
215
+
216
+ if (summary.totalViolations > 0) {
217
+ console.log(` ${chalk.red('Critical:')} ${summary.criticalCount}`);
218
+ console.log(` ${chalk.yellow('Warning:')} ${summary.warningCount}`);
219
+ console.log(` ${chalk.blue('Info:')} ${summary.infoCount}`);
220
+ }
221
+
222
+ console.log(chalk.dim('─'.repeat(50)));
223
+ }
224
+
225
+ // 7. Exit code logic
226
+ if (options.strict !== undefined) {
227
+ const strictLevel = typeof options.strict === 'string' ? options.strict : 'all';
228
+
229
+ if (strictLevel === 'critical' && summary.criticalCount > 0) {
230
+ console.log(chalk.red('\nāŒ Check failed: Critical violations found'));
231
+ process.exit(1);
232
+ } else if (strictLevel === 'all' && summary.totalViolations > 0) {
233
+ console.log(chalk.red('\nāŒ Check failed: Violations found'));
234
+ process.exit(1);
235
+ }
236
+ }
237
+
238
+ if (summary.totalViolations === 0) {
239
+ console.log(chalk.green('\nāœ… All checks passed!'));
240
+ }
241
+
242
+ process.exit(0);
243
+
244
+ } catch (error: any) {
245
+ spinner.fail(chalk.red('Check failed'));
246
+ console.error(chalk.red('Error:'), error.message);
247
+ process.exit(2);
248
+ }
249
+ });
250
+ }
251
+
252
+ // Helper functions
253
+
254
+ function isCodeFile(filePath: string): boolean {
255
+ const codeExtensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
256
+ const ext = path.extname(filePath).toLowerCase();
257
+ return codeExtensions.includes(ext);
258
+ }
259
+
260
+ async function loadCachedRules(projectId: string): Promise<CachedRules | null> {
261
+ try {
262
+ const cachePath = path.join(process.cwd(), CACHE_FILE);
263
+ const content = await fs.readFile(cachePath, 'utf-8');
264
+ const cached: CachedRules = JSON.parse(content);
265
+
266
+ if (cached.projectId !== projectId) {
267
+ return null;
268
+ }
269
+
270
+ return cached;
271
+ } catch {
272
+ return null;
273
+ }
274
+ }
275
+
276
+ async function saveCachedRules(
277
+ projectId: string,
278
+ rules: EffectiveRule[],
279
+ settings: { lmax: number; lmax_warning: number }
280
+ ): Promise<void> {
281
+ try {
282
+ const cacheDir = path.join(process.cwd(), '.rigstate');
283
+ await fs.mkdir(cacheDir, { recursive: true });
284
+
285
+ const cached: CachedRules = {
286
+ timestamp: new Date().toISOString(),
287
+ projectId,
288
+ rules,
289
+ settings
290
+ };
291
+
292
+ await fs.writeFile(
293
+ path.join(cacheDir, 'rules-cache.json'),
294
+ JSON.stringify(cached, null, 2)
295
+ );
296
+ } catch {
297
+ // Silently fail cache write
298
+ }
299
+ }
300
+
301
+ function isStale(timestamp: string, maxAge: number): boolean {
302
+ const age = Date.now() - new Date(timestamp).getTime();
303
+ return age > maxAge;
304
+ }
305
+
306
+ function outputResults(results: CheckResult[], json: boolean): void {
307
+ if (json) {
308
+ console.log(JSON.stringify({
309
+ results,
310
+ summary: summarizeResults(results)
311
+ }, null, 2));
312
+ return;
313
+ }
314
+
315
+ const hasViolations = results.some(r => r.violations.length > 0);
316
+
317
+ if (!hasViolations) {
318
+ return;
319
+ }
320
+
321
+ console.log('\n' + chalk.bold('šŸ” Violations Found'));
322
+ console.log(chalk.dim('─'.repeat(50)));
323
+
324
+ for (const result of results) {
325
+ if (result.violations.length > 0) {
326
+ formatViolations(result.violations);
327
+ }
328
+ }
329
+ }
@@ -0,0 +1,81 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { getApiKey, setApiKey, getProjectId, setProjectId, getApiUrl } from '../utils/config.js';
4
+
5
+ export function createConfigCommand() {
6
+ const config = new Command('config');
7
+
8
+ config
9
+ .description('View or modify Rigstate configuration')
10
+ .argument('[key]', 'Configuration key to view/set (api_key, project_id, api_url)')
11
+ .argument('[value]', 'Value to set')
12
+ .action(async (key?: string, value?: string) => {
13
+ // No arguments - show all config
14
+ if (!key) {
15
+ console.log(chalk.bold('Rigstate Configuration'));
16
+ console.log(chalk.dim('─'.repeat(40)));
17
+
18
+ try {
19
+ const apiKey = getApiKey();
20
+ console.log(`${chalk.cyan('api_key')}: ${apiKey.substring(0, 20)}...`);
21
+ } catch (e) {
22
+ console.log(`${chalk.cyan('api_key')}: ${chalk.dim('(not set)')}`);
23
+ }
24
+
25
+ const projectId = getProjectId();
26
+ console.log(`${chalk.cyan('project_id')}: ${projectId || chalk.dim('(not set)')}`);
27
+
28
+ const apiUrl = getApiUrl();
29
+ console.log(`${chalk.cyan('api_url')}: ${apiUrl}`);
30
+
31
+ console.log('');
32
+ console.log(chalk.dim('Use "rigstate config <key> <value>" to set a value.'));
33
+ return;
34
+ }
35
+
36
+ // Get specific key
37
+ if (!value) {
38
+ switch (key) {
39
+ case 'api_key':
40
+ try {
41
+ const apiKey = getApiKey();
42
+ console.log(apiKey);
43
+ } catch (e) {
44
+ console.log(chalk.dim('(not set)'));
45
+ }
46
+ break;
47
+ case 'project_id':
48
+ console.log(getProjectId() || chalk.dim('(not set)'));
49
+ break;
50
+ case 'api_url':
51
+ console.log(getApiUrl());
52
+ break;
53
+ default:
54
+ console.log(chalk.red(`Unknown config key: ${key}`));
55
+ console.log(chalk.dim('Valid keys: api_key, project_id, api_url'));
56
+ }
57
+ return;
58
+ }
59
+
60
+ // Set value
61
+ switch (key) {
62
+ case 'api_key':
63
+ setApiKey(value);
64
+ console.log(chalk.green(`āœ… api_key updated`));
65
+ break;
66
+ case 'project_id':
67
+ setProjectId(value);
68
+ console.log(chalk.green(`āœ… project_id updated`));
69
+ break;
70
+ case 'api_url':
71
+ // api_url is not settable via this command for now
72
+ console.log(chalk.yellow('api_url is set via RIGSTATE_API_URL environment variable'));
73
+ break;
74
+ default:
75
+ console.log(chalk.red(`Unknown config key: ${key}`));
76
+ console.log(chalk.dim('Valid keys: api_key, project_id'));
77
+ }
78
+ });
79
+
80
+ return config;
81
+ }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * rigstate daemon - Unified Guardian Daemon
3
+ *
4
+ * Usage:
5
+ * rigstate daemon # Start daemon in foreground
6
+ * rigstate daemon status # Check if daemon is running
7
+ * rigstate daemon --no-bridge # Disable Agent Bridge
8
+ * rigstate daemon --verbose # Enable verbose output
9
+ */
10
+
11
+ import { Command } from 'commander';
12
+ import chalk from 'chalk';
13
+ import ora from 'ora';
14
+ import fs from 'fs/promises';
15
+ import path from 'path';
16
+ import { createDaemon } from '../daemon/factory.js';
17
+
18
+ const PID_FILE = '.rigstate/daemon.pid';
19
+ const STATE_FILE = '.rigstate/daemon.state.json';
20
+
21
+ export function createDaemonCommand(): Command {
22
+ const daemon = new Command('daemon')
23
+ .description('Start the Guardian daemon for continuous monitoring');
24
+
25
+ // Main daemon command (start in foreground)
26
+ daemon
27
+ .argument('[action]', 'Action: start (default) or status', 'start')
28
+ .option('--project <id>', 'Project ID (or use .rigstate manifest)')
29
+ .option('--path <path>', 'Path to watch', '.')
30
+ .option('--no-bridge', 'Disable Agent Bridge connection')
31
+ .option('--verbose', 'Enable verbose output')
32
+ .action(async (action: string, options: {
33
+ project?: string;
34
+ path?: string;
35
+ bridge?: boolean;
36
+ verbose?: boolean;
37
+ }) => {
38
+ if (action === 'status') {
39
+ await showStatus();
40
+ return;
41
+ }
42
+
43
+ const spinner = ora();
44
+
45
+ try {
46
+ // Check if already running
47
+ if (await isRunning()) {
48
+ console.log(chalk.yellow('⚠ Another daemon instance may be running.'));
49
+ console.log(chalk.dim(` Check ${PID_FILE} or run "rigstate daemon status"`));
50
+ console.log(chalk.dim(' Use Ctrl+C to stop the running daemon first.\n'));
51
+ }
52
+
53
+ // Create daemon
54
+ spinner.start('Initializing Guardian Daemon...');
55
+ const daemonInstance = await createDaemon({
56
+ project: options.project,
57
+ path: options.path,
58
+ noBridge: options.bridge === false,
59
+ verbose: options.verbose
60
+ });
61
+ spinner.stop();
62
+
63
+ // Write PID file
64
+ await writePidFile();
65
+
66
+ // Handle shutdown gracefully
67
+ process.on('SIGINT', async () => {
68
+ console.log(chalk.dim('\n\nShutting down...'));
69
+ await daemonInstance.stop();
70
+ await cleanupPidFile();
71
+ process.exit(0);
72
+ });
73
+
74
+ process.on('SIGTERM', async () => {
75
+ await daemonInstance.stop();
76
+ await cleanupPidFile();
77
+ process.exit(0);
78
+ });
79
+
80
+ // Update state file periodically
81
+ const stateInterval = setInterval(async () => {
82
+ await writeStateFile(daemonInstance.getState());
83
+ }, 5000);
84
+
85
+ daemonInstance.on('stopped', () => {
86
+ clearInterval(stateInterval);
87
+ });
88
+
89
+ // Start the daemon
90
+ await daemonInstance.start();
91
+
92
+ // Keep the process alive
93
+ await new Promise(() => { });
94
+
95
+ } catch (error: any) {
96
+ spinner.fail(chalk.red('Failed to start daemon'));
97
+ console.error(chalk.red('Error:'), error.message);
98
+ process.exit(1);
99
+ }
100
+ });
101
+
102
+ return daemon;
103
+ }
104
+
105
+ async function isRunning(): Promise<boolean> {
106
+ try {
107
+ const pidPath = path.join(process.cwd(), PID_FILE);
108
+ const content = await fs.readFile(pidPath, 'utf-8');
109
+ const pid = parseInt(content.trim(), 10);
110
+
111
+ // Check if process exists
112
+ try {
113
+ process.kill(pid, 0);
114
+ return true;
115
+ } catch {
116
+ // Process doesn't exist, clean up stale PID file
117
+ await fs.unlink(pidPath);
118
+ return false;
119
+ }
120
+ } catch {
121
+ return false;
122
+ }
123
+ }
124
+
125
+ async function writePidFile(): Promise<void> {
126
+ try {
127
+ const dir = path.join(process.cwd(), '.rigstate');
128
+ await fs.mkdir(dir, { recursive: true });
129
+ await fs.writeFile(path.join(dir, 'daemon.pid'), process.pid.toString());
130
+ } catch {
131
+ // Silently fail
132
+ }
133
+ }
134
+
135
+ async function cleanupPidFile(): Promise<void> {
136
+ try {
137
+ await fs.unlink(path.join(process.cwd(), PID_FILE));
138
+ await fs.unlink(path.join(process.cwd(), STATE_FILE));
139
+ } catch {
140
+ // Silently fail
141
+ }
142
+ }
143
+
144
+ async function writeStateFile(state: any): Promise<void> {
145
+ try {
146
+ const dir = path.join(process.cwd(), '.rigstate');
147
+ await fs.mkdir(dir, { recursive: true });
148
+ await fs.writeFile(
149
+ path.join(dir, 'daemon.state.json'),
150
+ JSON.stringify(state, null, 2)
151
+ );
152
+ } catch {
153
+ // Silently fail
154
+ }
155
+ }
156
+
157
+ async function showStatus(): Promise<void> {
158
+ console.log(chalk.bold('\nšŸ›”ļø Guardian Daemon Status\n'));
159
+
160
+ const running = await isRunning();
161
+
162
+ if (!running) {
163
+ console.log(chalk.yellow('Status: Not running'));
164
+ console.log(chalk.dim('Use "rigstate daemon" to start.\n'));
165
+ return;
166
+ }
167
+
168
+ console.log(chalk.green('Status: Running'));
169
+
170
+ // Read state file
171
+ try {
172
+ const statePath = path.join(process.cwd(), STATE_FILE);
173
+ const content = await fs.readFile(statePath, 'utf-8');
174
+ const state = JSON.parse(content);
175
+
176
+ console.log(chalk.dim('─'.repeat(40)));
177
+ console.log(`Started at: ${state.startedAt || 'Unknown'}`);
178
+ console.log(`Files checked: ${state.filesChecked || 0}`);
179
+ console.log(`Violations: ${state.violationsFound || 0}`);
180
+ console.log(`Tasks processed: ${state.tasksProcessed || 0}`);
181
+ console.log(`Last activity: ${state.lastActivity || 'None'}`);
182
+ console.log(chalk.dim('─'.repeat(40)));
183
+ } catch {
184
+ console.log(chalk.dim('(State file not found)'));
185
+ }
186
+
187
+ // Read PID
188
+ try {
189
+ const pidPath = path.join(process.cwd(), PID_FILE);
190
+ const pid = await fs.readFile(pidPath, 'utf-8');
191
+ console.log(chalk.dim(`PID: ${pid.trim()}`));
192
+ } catch {
193
+ // No PID file
194
+ }
195
+
196
+ console.log('');
197
+ }