@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,339 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { getApiKey, getProjectId, getApiUrl, setProjectId } from '../utils/config.js';
5
+ import axios from 'axios';
6
+ import fs from 'fs/promises';
7
+ import path from 'path';
8
+
9
+ export function createSyncCommand() {
10
+ const sync = new Command('sync');
11
+
12
+ sync
13
+ .description('Synchronize local state with Rigstate Cloud')
14
+ .option('-p, --project <id>', 'Specify Project ID (saves to config automatically)')
15
+ .action(async (options) => {
16
+ const spinner = ora('Synchronizing project state...').start();
17
+
18
+ try {
19
+ // 1. Authentication Check
20
+ let apiKey;
21
+ try {
22
+ apiKey = getApiKey();
23
+ } catch (e) {
24
+ spinner.fail('Not authenticated. Run "rigstate login" first.');
25
+ return;
26
+ }
27
+
28
+ // 2. Project Context Resolution
29
+ let projectId = options.project;
30
+
31
+ // Check local .rigstate manifest
32
+ if (!projectId) {
33
+ try {
34
+ const manifestPath = path.join(process.cwd(), '.rigstate');
35
+ const manifestContent = await fs.readFile(manifestPath, 'utf-8');
36
+ const manifest = JSON.parse(manifestContent);
37
+ if (manifest.project_id) projectId = manifest.project_id;
38
+ } catch (e) { }
39
+ }
40
+
41
+ // Check global config
42
+ if (!projectId) projectId = getProjectId();
43
+
44
+ if (options.project) {
45
+ // Persistence: Save project ID for future use
46
+ setProjectId(options.project);
47
+ }
48
+
49
+ if (!projectId) {
50
+ spinner.fail('No project context found.\n Run with --project <id> once to save context.');
51
+ return;
52
+ }
53
+
54
+ const apiUrl = getApiUrl();
55
+
56
+ // 3. API Execution
57
+ const response = await axios.get(`${apiUrl}/api/v1/roadmap`, {
58
+ params: { project_id: projectId },
59
+ headers: { Authorization: `Bearer ${apiKey}` }
60
+ });
61
+
62
+ // Parse Standardized Response ({ success, data, ... })
63
+ if (!response.data.success) {
64
+ throw new Error(response.data.error || 'Unknown API failure');
65
+ }
66
+
67
+ const { roadmap, project } = response.data.data;
68
+ const timestamp = response.data.timestamp;
69
+
70
+ // 4. Write Artifacts
71
+ const targetPath = path.join(process.cwd(), 'roadmap.json');
72
+ const fileContent = JSON.stringify({
73
+ project,
74
+ last_synced: timestamp,
75
+ roadmap
76
+ }, null, 2);
77
+
78
+ await fs.writeFile(targetPath, fileContent, 'utf-8');
79
+
80
+ // 4b. Write Context Manifest (.rigstate) - CONTEXT GUARD
81
+ try {
82
+ const manifestPath = path.join(process.cwd(), '.rigstate');
83
+ const manifestContent = {
84
+ project_id: projectId,
85
+ project_name: project,
86
+ last_synced: timestamp,
87
+ api_url: apiUrl
88
+ };
89
+ await fs.writeFile(manifestPath, JSON.stringify(manifestContent, null, 2), 'utf-8');
90
+ } catch (e) {
91
+ // Fail silently
92
+ }
93
+
94
+ // 4c. Provision Agent Skills (Skills Dominion)
95
+ console.log(chalk.bold('\n🧠 Agent Skills Provisioning...'));
96
+ try {
97
+ const { provisionSkills, generateSkillsDiscoveryBlock } = await import('../utils/skills-provisioner.js');
98
+ const skills = await provisionSkills(apiUrl, apiKey, projectId, process.cwd());
99
+
100
+ // Update .cursorrules with skills discovery block (if file exists)
101
+ const cursorRulesPath = path.join(process.cwd(), '.cursorrules');
102
+ try {
103
+ let rulesContent = await fs.readFile(cursorRulesPath, 'utf-8');
104
+ const skillsBlock = generateSkillsDiscoveryBlock(skills);
105
+
106
+ // Replace existing skills block or insert after PROJECT CONTEXT
107
+ if (rulesContent.includes('<available_skills>')) {
108
+ rulesContent = rulesContent.replace(
109
+ /<available_skills>[\s\S]*?<\/available_skills>/,
110
+ skillsBlock
111
+ );
112
+ } else if (rulesContent.includes('## 🧠 PROJECT CONTEXT')) {
113
+ // Insert after PROJECT CONTEXT section
114
+ const insertPoint = rulesContent.indexOf('---', rulesContent.indexOf('## 🧠 PROJECT CONTEXT'));
115
+ if (insertPoint !== -1) {
116
+ rulesContent = rulesContent.slice(0, insertPoint + 3) +
117
+ '\n\n' + skillsBlock + '\n' +
118
+ rulesContent.slice(insertPoint + 3);
119
+ }
120
+ }
121
+
122
+ await fs.writeFile(cursorRulesPath, rulesContent, 'utf-8');
123
+ console.log(chalk.dim(` Updated .cursorrules with skills discovery block`));
124
+ } catch (e) {
125
+ // .cursorrules doesn't exist or couldn't be updated
126
+ }
127
+ } catch (e: any) {
128
+ console.log(chalk.yellow(` ⚠ Skills provisioning skipped: ${e.message}`));
129
+ }
130
+
131
+ // 5. Process Execution Logs (MISSION REPORTING)
132
+ try {
133
+ const logPath = path.join(process.cwd(), '.rigstate', 'logs', 'last_execution.json');
134
+ try {
135
+ const logContent = await fs.readFile(logPath, 'utf-8');
136
+ const logData = JSON.parse(logContent);
137
+
138
+ if (logData.task_summary) {
139
+ await axios.post(`${apiUrl}/api/v1/execution-logs`, {
140
+ project_id: projectId,
141
+ ...logData,
142
+ agent_role: process.env.RIGSTATE_MODE === 'SUPERVISOR' ? 'SUPERVISOR' : 'WORKER'
143
+ }, {
144
+ headers: { Authorization: `Bearer ${apiKey}` }
145
+ });
146
+
147
+ await fs.unlink(logPath);
148
+ console.log(chalk.dim(`✔ Mission Report uploaded.`));
149
+ }
150
+ } catch (e: any) {
151
+ // Ignore ENOENT (file not found), log errors if API fails
152
+ if (e.code !== 'ENOENT') {
153
+ // console.log(chalk.yellow('Log upload skipped: ' + e.message));
154
+ }
155
+ }
156
+ } catch (e) { }
157
+
158
+ // 6. User Feedback
159
+ spinner.succeed(chalk.green(`Synced ${roadmap.length} roadmap steps for project "${project}"`));
160
+ console.log(chalk.dim(`Local files updated: roadmap.json`));
161
+
162
+ const { runGuardianWatchdog } = await import('../utils/watchdog.js');
163
+ const settings = response.data.data.settings || {};
164
+ await runGuardianWatchdog(process.cwd(), settings, projectId);
165
+
166
+ // 8. Bridge Heartbeat & Pending Tasks
167
+ console.log(chalk.bold('\n📡 Agent Bridge Heartbeat...'));
168
+ try {
169
+ const bridgeResponse = await axios.get(`${apiUrl}/api/v1/agent/bridge`, {
170
+ params: { project_id: projectId },
171
+ headers: { Authorization: `Bearer ${apiKey}` }
172
+ });
173
+
174
+ if (bridgeResponse.data.success) {
175
+ const tasks = bridgeResponse.data.tasks;
176
+ const pending = tasks.filter((t: any) => t.status === 'PENDING');
177
+ const approved = tasks.filter((t: any) => t.status === 'APPROVED');
178
+
179
+ if (pending.length > 0 || approved.length > 0) {
180
+ console.log(chalk.yellow(`⚠ Bridge Alert: ${pending.length} pending, ${approved.length} approved tasks found.`));
181
+ console.log(chalk.dim('Run "rigstate fix" to process these tasks or ensure your IDE MCP server is active.'));
182
+ } else {
183
+ console.log(chalk.green('✔ Heartbeat healthy. No pending bridge tasks.'));
184
+ }
185
+
186
+ // Acknowledge Pings if any
187
+ const pings = pending.filter((t: any) => t.proposal?.startsWith('ping'));
188
+ for (const ping of pings) {
189
+ await axios.post(`${apiUrl}/api/v1/agent/bridge`, {
190
+ bridge_id: ping.id,
191
+ status: 'COMPLETED',
192
+ summary: 'Pong! CLI Sync Heartbeat confirmed.'
193
+ }, {
194
+ headers: { Authorization: `Bearer ${apiKey}` }
195
+ });
196
+ console.log(chalk.cyan(`🏓 Pong! Acknowledged heartbeat signal [${ping.id}]`));
197
+ }
198
+ }
199
+ } catch (e: any) {
200
+ console.log(chalk.yellow(`⚠ Could not verify Bridge status: ${e.message}`));
201
+ }
202
+
203
+ if (options.project) {
204
+ console.log(chalk.blue(`Project context saved. Future commands will use this project.`));
205
+ }
206
+
207
+ // 9. Migration Guard (The Firewall)
208
+ try {
209
+ const migrationDir = path.join(process.cwd(), 'supabase', 'migrations');
210
+ const files = await fs.readdir(migrationDir);
211
+ const sqlFiles = files.filter(f => f.endsWith('.sql')).sort();
212
+
213
+ if (sqlFiles.length > 0) {
214
+ const latestMigration = sqlFiles[sqlFiles.length - 1];
215
+ console.log(chalk.dim(`\n🛡 Migration Guard:`));
216
+ console.log(chalk.dim(` Latest Local: ${latestMigration}`));
217
+ console.log(chalk.yellow(` ⚠ Ensure DB schema matches this version. CLI cannot verify Remote RLS policies directly.`));
218
+ }
219
+ } catch (e) {
220
+ // No migrations folder, or error reading - ignore
221
+ }
222
+
223
+ // 10. Sovereign Foundation (Vault Sync)
224
+ try {
225
+ const vaultResponse = await axios.post(`${apiUrl}/api/v1/vault/sync`,
226
+ { project_id: projectId },
227
+ { headers: { Authorization: `Bearer ${apiKey}` } }
228
+ );
229
+
230
+ if (vaultResponse.data.success) {
231
+ const vaultContent: string = vaultResponse.data.data.content || '';
232
+ const localEnvPath = path.join(process.cwd(), '.env.local');
233
+ let localContent = '';
234
+
235
+ try {
236
+ localContent = await fs.readFile(localEnvPath, 'utf-8');
237
+ } catch (e) { /* File doesn't exist */ }
238
+
239
+ // Normalize for comparison (trim, ignore comments?) - Simple trim for now
240
+ if (vaultContent.trim() !== localContent.trim()) {
241
+ console.log(chalk.bold('\n🔐 Sovereign Foundation (Vault):'));
242
+ console.log(chalk.yellow(' Status: Drift Detected / Update Available'));
243
+
244
+ const { syncVault } = await import('inquirer').then(m => m.default.prompt([{
245
+ type: 'confirm',
246
+ name: 'syncVault',
247
+ message: 'Synchronize local .env.local with Vault secrets?',
248
+ default: false
249
+ }]));
250
+
251
+ if (syncVault) {
252
+ await fs.writeFile(localEnvPath, vaultContent, 'utf-8');
253
+ console.log(chalk.green(' ✅ .env.local synchronized with Vault.'));
254
+ } else {
255
+ console.log(chalk.dim(' Skipped vault sync.'));
256
+ }
257
+ } else {
258
+ console.log(chalk.dim('\n🔐 Sovereign Foundation: Synced.'));
259
+ }
260
+ }
261
+ } catch (e: any) {
262
+ // Fail silently or warn if vault access denied (expected for some users)
263
+ // console.log(chalk.dim(` (Vault check skipped: ${e.message})`));
264
+ }
265
+
266
+ // 11. System Integrity Checks (The Firewall)
267
+ console.log(chalk.dim('\n🛡️ System Integrity Check...'));
268
+ await checkSystemIntegrity(apiUrl, apiKey, projectId);
269
+
270
+ } catch (error: any) {
271
+ if (axios.isAxiosError(error)) {
272
+ const message = error.response?.data?.error || error.message;
273
+ spinner.fail(chalk.red(`Sync failed: ${message}`));
274
+ } else {
275
+ spinner.fail(chalk.red('Sync failed: ' + (error.message || 'Unknown error')));
276
+ }
277
+ }
278
+ });
279
+
280
+ return sync;
281
+ }
282
+
283
+ /**
284
+ * System Integrity Checks
285
+ * Verifies Migration Sync and RLS Status via API
286
+ */
287
+ async function checkSystemIntegrity(apiUrl: string, apiKey: string, projectId: string) {
288
+ try {
289
+ // Call System Integrity API
290
+ const response = await axios.get(`${apiUrl}/api/v1/system/integrity`, {
291
+ params: { project_id: projectId },
292
+ headers: { Authorization: `Bearer ${apiKey}` }
293
+ });
294
+
295
+ if (response.data.success) {
296
+ const { migrations, rls, guardian_violations } = response.data.data;
297
+
298
+ // Migration Status
299
+ if (migrations) {
300
+ if (migrations.in_sync) {
301
+ console.log(chalk.green(` ✅ Migrations synced (${migrations.count} versions)`));
302
+ } else {
303
+ console.log(chalk.red(` 🛑 CRITICAL: DB Schema out of sync! ${migrations.missing?.length || 0} migrations not applied.`));
304
+ if (migrations.missing?.length > 0) {
305
+ console.log(chalk.dim(` Missing: ${migrations.missing.slice(0, 3).join(', ')}${migrations.missing.length > 3 ? '...' : ''}`));
306
+ }
307
+ console.log(chalk.yellow(` Run 'supabase db push' or apply migrations immediately.`));
308
+ }
309
+ }
310
+
311
+ // RLS Status
312
+ if (rls) {
313
+ if (rls.all_secured) {
314
+ console.log(chalk.green(` ✅ RLS Audit Passed (${rls.table_count} tables secured)`));
315
+ } else {
316
+ console.log(chalk.red(` 🛑 CRITICAL: Security Vulnerability! ${rls.unsecured?.length || 0} tables have RLS disabled.`));
317
+ rls.unsecured?.forEach((table: string) => {
318
+ console.log(chalk.red(` - ${table}`));
319
+ });
320
+ console.log(chalk.yellow(' Enable RLS immediately: ALTER TABLE "table" ENABLE ROW LEVEL SECURITY;'));
321
+ }
322
+ }
323
+
324
+ // Guardian Violations
325
+ if (guardian_violations) {
326
+ if (guardian_violations.count === 0) {
327
+ console.log(chalk.green(' ✅ Guardian: No active violations'));
328
+ } else {
329
+ console.log(chalk.yellow(` ⚠️ Guardian: ${guardian_violations.count} active violations`));
330
+ console.log(chalk.dim(' Run "rigstate check" for details.'));
331
+ }
332
+ }
333
+ }
334
+ } catch (e: any) {
335
+ // API might not have this endpoint yet - fail silently
336
+ console.log(chalk.dim(' (System integrity check skipped - API endpoint not available)'));
337
+ }
338
+ }
339
+
@@ -0,0 +1,283 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import chokidar from 'chokidar';
5
+ import fs from 'fs/promises';
6
+ import path from 'path';
7
+ import { execSync } from 'child_process';
8
+ import { getApiKey, getProjectId, getApiUrl } from '../utils/config.js';
9
+ import axios from 'axios';
10
+
11
+ interface VerificationCriteria {
12
+ type: 'file_exists' | 'file_content' | 'content_match';
13
+ path: string;
14
+ pattern?: string;
15
+ match?: string;
16
+ }
17
+
18
+ export function createWatchCommand() {
19
+ const watch = new Command('watch');
20
+
21
+ watch
22
+ .description('Watch for changes and auto-verify roadmap tasks')
23
+ .option('--no-auto-commit', 'Disable auto-commit on verification')
24
+ .option('--no-auto-push', 'Disable auto-push after commit')
25
+ .option('--run-tests', 'Run tests before committing')
26
+ .option('--test-command <cmd>', 'Custom test command (default: npm test)')
27
+ .action(async (options) => {
28
+ console.log(chalk.bold.blue('🔭 Rigstate Watch Mode'));
29
+ console.log(chalk.dim('Monitoring for task completion...'));
30
+ console.log('');
31
+
32
+ // Get config
33
+ let apiKey: string;
34
+ let projectId: string | undefined;
35
+
36
+ try {
37
+ apiKey = getApiKey();
38
+ } catch (e) {
39
+ console.log(chalk.red('Not authenticated. Run "rigstate login" first.'));
40
+ return;
41
+ }
42
+
43
+ projectId = getProjectId();
44
+ if (!projectId) {
45
+ // Try to read from local manifest
46
+ try {
47
+ const manifestPath = path.join(process.cwd(), '.rigstate');
48
+ const content = await fs.readFile(manifestPath, 'utf-8');
49
+ const manifest = JSON.parse(content);
50
+ projectId = manifest.project_id;
51
+ } catch (e) { }
52
+ }
53
+
54
+ if (!projectId) {
55
+ console.log(chalk.red('No project context. Run "rigstate link" or "rigstate sync --project <id>" first.'));
56
+ return;
57
+ }
58
+
59
+ const apiUrl = getApiUrl();
60
+
61
+ // Settings
62
+ const config = {
63
+ autoCommit: options.autoCommit !== false,
64
+ autoPush: options.autoPush !== false,
65
+ runTests: options.runTests || false,
66
+ testCommand: options.testCommand || 'npm test'
67
+ };
68
+
69
+ console.log(chalk.dim(`Auto-commit: ${config.autoCommit ? 'ON' : 'OFF'}`));
70
+ console.log(chalk.dim(`Auto-push: ${config.autoPush ? 'ON' : 'OFF'}`));
71
+ console.log('');
72
+
73
+ // Fetch active task
74
+ const fetchActiveTask = async () => {
75
+ try {
76
+ const response = await axios.get(`${apiUrl}/api/v1/roadmap`, {
77
+ params: { project_id: projectId },
78
+ headers: { Authorization: `Bearer ${apiKey}` }
79
+ });
80
+
81
+ if (!response.data.success) return null;
82
+
83
+ const roadmap = response.data.data.roadmap || [];
84
+
85
+ // Priority: IN_PROGRESS > ACTIVE > LOCKED
86
+ const statusPriority: Record<string, number> = {
87
+ 'IN_PROGRESS': 0,
88
+ 'ACTIVE': 1,
89
+ 'LOCKED': 2
90
+ };
91
+
92
+ const activeTasks = roadmap
93
+ .filter((t: any) => ['IN_PROGRESS', 'ACTIVE', 'LOCKED'].includes(t.status))
94
+ .sort((a: any, b: any) => {
95
+ const pA = statusPriority[a.status] ?? 99;
96
+ const pB = statusPriority[b.status] ?? 99;
97
+ if (pA !== pB) return pA - pB;
98
+ return (a.step_number || 0) - (b.step_number || 0);
99
+ });
100
+
101
+ return activeTasks[0] || null;
102
+ } catch (e) {
103
+ return null;
104
+ }
105
+ };
106
+
107
+ // Check verification criteria
108
+ const checkCriteria = async (criteria: VerificationCriteria): Promise<boolean> => {
109
+ try {
110
+ const fullPath = path.resolve(process.cwd(), criteria.path);
111
+
112
+ switch (criteria.type) {
113
+ case 'file_exists':
114
+ await fs.access(fullPath);
115
+ return true;
116
+
117
+ case 'file_content':
118
+ const content = await fs.readFile(fullPath, 'utf-8');
119
+ return content.length > 0;
120
+
121
+ case 'content_match':
122
+ if (!criteria.match) return false;
123
+ const fileContent = await fs.readFile(fullPath, 'utf-8');
124
+ return fileContent.includes(criteria.match);
125
+
126
+ default:
127
+ return false;
128
+ }
129
+ } catch (e) {
130
+ return false;
131
+ }
132
+ };
133
+
134
+ // Complete task
135
+ const completeTask = async (taskId: string, task: any) => {
136
+ const spinner = ora('Completing task...').start();
137
+
138
+ try {
139
+ // Run tests if enabled
140
+ if (config.runTests) {
141
+ spinner.text = 'Running tests...';
142
+ try {
143
+ execSync(config.testCommand, { stdio: 'pipe' });
144
+ spinner.text = 'Tests passed!';
145
+ } catch (e) {
146
+ spinner.fail('Tests failed. Task not completed.');
147
+ return;
148
+ }
149
+ }
150
+
151
+ // Update status via API
152
+ await axios.post(`${apiUrl}/api/v1/roadmap/update-status`, {
153
+ project_id: projectId,
154
+ chunk_id: taskId,
155
+ status: 'COMPLETED'
156
+ }, {
157
+ headers: { Authorization: `Bearer ${apiKey}` }
158
+ });
159
+
160
+ spinner.succeed(chalk.green(`✅ Task #${task.step_number} completed: ${task.title}`));
161
+
162
+ // Auto-commit
163
+ if (config.autoCommit) {
164
+ spinner.start('Committing changes...');
165
+ try {
166
+ execSync('git add -A', { stdio: 'pipe' });
167
+ const commitMsg = `feat: Complete task #${task.step_number} - ${task.title}`;
168
+ execSync(`git commit -m "${commitMsg}"`, { stdio: 'pipe' });
169
+ spinner.succeed('Changes committed');
170
+
171
+ // Auto-push
172
+ if (config.autoPush) {
173
+ spinner.start('Pushing to remote...');
174
+ try {
175
+ execSync('git push', { stdio: 'pipe' });
176
+ spinner.succeed('Pushed to remote');
177
+ } catch (e) {
178
+ spinner.warn('Push failed (no remote or conflict)');
179
+ }
180
+ }
181
+ } catch (e: any) {
182
+ spinner.warn('Nothing to commit or commit failed');
183
+ }
184
+ }
185
+
186
+ console.log('');
187
+ console.log(chalk.blue('Watching for next task...'));
188
+
189
+ } catch (e: any) {
190
+ spinner.fail(`Failed to complete task: ${e.message}`);
191
+ }
192
+ };
193
+
194
+ // Main watch loop
195
+ let currentTask: any = null;
196
+ let isProcessing = false;
197
+
198
+ const processActiveTask = async () => {
199
+ if (isProcessing) return;
200
+ isProcessing = true;
201
+
202
+ const task = await fetchActiveTask();
203
+
204
+ if (!task) {
205
+ if (currentTask) {
206
+ console.log(chalk.green('🎉 All tasks completed! Watching for new tasks...'));
207
+ currentTask = null;
208
+ }
209
+ isProcessing = false;
210
+ return;
211
+ }
212
+
213
+ if (!currentTask || currentTask.id !== task.id) {
214
+ currentTask = task;
215
+ console.log('');
216
+ console.log(chalk.bold.yellow(`📌 Active Task #${task.step_number}: ${task.title}`));
217
+ console.log(chalk.dim(`Status: ${task.status}`));
218
+
219
+ if (task.verification_criteria) {
220
+ console.log(chalk.dim('Verification: Auto-checking criteria...'));
221
+ }
222
+ }
223
+
224
+ // Check verification criteria if present
225
+ if (task.verification_criteria && Array.isArray(task.verification_criteria)) {
226
+ let allPassed = true;
227
+ for (const criteria of task.verification_criteria) {
228
+ const passed = await checkCriteria(criteria);
229
+ if (!passed) {
230
+ allPassed = false;
231
+ break;
232
+ }
233
+ }
234
+
235
+ if (allPassed) {
236
+ console.log(chalk.green('✓ All verification criteria passed!'));
237
+ await completeTask(task.id, task);
238
+ currentTask = null;
239
+ }
240
+ }
241
+
242
+ isProcessing = false;
243
+ };
244
+
245
+ // Initial check
246
+ await processActiveTask();
247
+
248
+ // Set up file watcher
249
+ const watcher = chokidar.watch('.', {
250
+ ignored: [
251
+ /(^|[\/\\])\../, // dotfiles
252
+ '**/node_modules/**',
253
+ '**/.git/**',
254
+ '**/.next/**',
255
+ '**/dist/**'
256
+ ],
257
+ persistent: true,
258
+ ignoreInitial: true
259
+ });
260
+
261
+ watcher.on('all', async (event, filePath) => {
262
+ if (['add', 'change', 'unlink'].includes(event)) {
263
+ // Debounce - wait a bit for multiple rapid changes
264
+ setTimeout(() => processActiveTask(), 500);
265
+ }
266
+ });
267
+
268
+ console.log(chalk.dim('Watching for file changes... (Ctrl+C to exit)'));
269
+
270
+ // Periodic check every 30 seconds
271
+ setInterval(() => processActiveTask(), 30000);
272
+
273
+ // Keep process alive
274
+ process.on('SIGINT', () => {
275
+ console.log('');
276
+ console.log(chalk.dim('Watch mode stopped.'));
277
+ watcher.close();
278
+ process.exit(0);
279
+ });
280
+ });
281
+
282
+ return watch;
283
+ }