@rigstate/cli 0.7.35 → 0.7.36

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigstate/cli",
3
- "version": "0.7.35",
3
+ "version": "0.7.36",
4
4
  "description": "Rigstate CLI - Code audit, sync and supervision tool",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,7 +17,6 @@
17
17
  "dependencies": {
18
18
  "@rigstate/rules-engine": "*",
19
19
  "@rigstate/shared": "*",
20
- "uuid": "^9.0.1",
21
20
  "@types/diff": "^7.0.2",
22
21
  "@types/inquirer": "^9.0.9",
23
22
  "axios": "^1.6.5",
@@ -29,7 +28,9 @@
29
28
  "dotenv": "^16.4.1",
30
29
  "glob": "^10.3.10",
31
30
  "inquirer": "^9.3.8",
32
- "ora": "^8.0.1"
31
+ "ora": "^8.0.1",
32
+ "simple-git": "^3.31.1",
33
+ "uuid": "^9.0.1"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@types/node": "^20.11.5",
@@ -0,0 +1,305 @@
1
+ /* eslint-disable no-console */
2
+ import { Command } from 'commander';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+ import axios from 'axios';
6
+ import { getApiKey, getApiUrl, getProjectId } from '../utils/config.js';
7
+
8
+ // ─────────────────────────────────────────────────────────────────────────────
9
+ // Types
10
+ // ─────────────────────────────────────────────────────────────────────────────
11
+ interface GenesisStep {
12
+ id: string;
13
+ title: string;
14
+ step_number: number;
15
+ icon: string;
16
+ verification_path?: string;
17
+ }
18
+
19
+ interface GenesisStatusResponse {
20
+ success: boolean;
21
+ data: {
22
+ genesis_complete: boolean;
23
+ genesis_steps: GenesisStep[];
24
+ genesis_count: number;
25
+ total_roadmap_steps: number;
26
+ detected_template: string;
27
+ detected_stack_key: string;
28
+ project_name: string;
29
+ };
30
+ error?: string;
31
+ }
32
+
33
+ interface GenesisTriggerResponse {
34
+ success: boolean;
35
+ data: {
36
+ project_name: string;
37
+ template: string;
38
+ stack_key: string;
39
+ steps_created: number;
40
+ steps: GenesisStep[];
41
+ existing_steps_shifted: number;
42
+ message: string;
43
+ };
44
+ error?: string;
45
+ }
46
+
47
+ // ─────────────────────────────────────────────────────────────────────────────
48
+ // Command Definition
49
+ // ─────────────────────────────────────────────────────────────────────────────
50
+ export function createGenesisCommand(): Command {
51
+ return new Command('genesis')
52
+ .description('Initialize project foundation (Phase 0). Detects stack and injects foundation steps.')
53
+ .option('--force', 'Re-run genesis even if already initialized (use with caution)')
54
+ .option('--status', 'Check genesis status without triggering')
55
+ .option('--project-id <id>', 'Override project ID (defaults to linked project)')
56
+ .action(async (options) => {
57
+ const apiKey = getApiKey();
58
+ const apiUrl = getApiUrl();
59
+ const projectId = options.projectId || getProjectId();
60
+
61
+ if (!projectId) {
62
+ console.error(chalk.red('❌ No project linked. Run: rigstate link'));
63
+ process.exit(1);
64
+ }
65
+
66
+ if (!apiKey) {
67
+ console.error(chalk.red('❌ Not authenticated. Run: rigstate login'));
68
+ process.exit(1);
69
+ }
70
+
71
+ if (options.status) {
72
+ await checkGenesisStatus(projectId, apiKey, apiUrl);
73
+ } else {
74
+ await triggerGenesis(projectId, apiKey, apiUrl, options.force ?? false);
75
+ }
76
+ });
77
+ }
78
+
79
+ // ─────────────────────────────────────────────────────────────────────────────
80
+ // Status Check
81
+ // ─────────────────────────────────────────────────────────────────────────────
82
+ export async function checkGenesisStatus(
83
+ projectId: string,
84
+ apiKey: string,
85
+ apiUrl: string
86
+ ): Promise<{ complete: boolean; stepCount: number }> {
87
+ // ── Offline-first: check local manifest cache ─────────────────────────────
88
+ try {
89
+ const { loadManifest } = await import('../utils/manifest.js');
90
+ const manifest = await loadManifest();
91
+ if (manifest?.genesis_complete) {
92
+ // Local cache hit — skip API call
93
+ return { complete: true, stepCount: 0 }; // stepCount not cached, but complete is enough
94
+ }
95
+ } catch {
96
+ // Ignore manifest read errors
97
+ }
98
+
99
+ const spinner = ora('Checking Genesis status...').start();
100
+
101
+ try {
102
+ const response = await axios.get<GenesisStatusResponse>(
103
+ `${apiUrl}/api/v1/genesis`,
104
+ {
105
+ params: { project_id: projectId },
106
+ headers: { Authorization: `Bearer ${apiKey}` },
107
+ timeout: 10000
108
+ }
109
+ );
110
+
111
+ spinner.stop();
112
+
113
+ if (!response.data.success) {
114
+ console.log(chalk.yellow(`⚠️ Could not check genesis status: ${response.data.error}`));
115
+ return { complete: false, stepCount: 0 };
116
+ }
117
+
118
+ const { data } = response.data;
119
+
120
+ console.log('');
121
+ console.log(chalk.bold('🏗️ GENESIS STATUS'));
122
+ console.log(chalk.dim('────────────────────────────────────────'));
123
+ console.log(`${chalk.bold('Project:')} ${chalk.cyan(data.project_name)}`);
124
+ console.log(`${chalk.bold('Stack:')} ${chalk.magenta(data.detected_template)}`);
125
+ console.log(`${chalk.bold('Genesis:')} ${data.genesis_complete ? chalk.green('✅ Complete') : chalk.yellow('⚠️ Pending')}`);
126
+ console.log(`${chalk.bold('Foundation:')} ${chalk.white(data.genesis_count)} steps`);
127
+ console.log(`${chalk.bold('Total Roadmap:')} ${chalk.white(data.total_roadmap_steps)} steps`);
128
+
129
+ if (data.genesis_complete && data.genesis_steps.length > 0) {
130
+ console.log('');
131
+ console.log(chalk.dim('Foundation Steps:'));
132
+ data.genesis_steps.forEach(step => {
133
+ console.log(` ${step.icon} ${chalk.bold(`T-${step.step_number}`)}: ${step.title}`);
134
+ });
135
+ } else if (!data.genesis_complete) {
136
+ console.log('');
137
+ console.log(chalk.yellow('⚡ Genesis not yet initialized.'));
138
+ console.log(chalk.dim(' Run: ') + chalk.white('rigstate genesis'));
139
+ }
140
+
141
+ console.log(chalk.dim('────────────────────────────────────────'));
142
+ console.log('');
143
+
144
+ return { complete: data.genesis_complete, stepCount: data.genesis_count };
145
+
146
+ } catch (err: any) {
147
+ spinner.stop();
148
+ if (err.response?.status === 422) {
149
+ console.log(chalk.yellow('⚠️ Genesis pending: Complete onboarding with Frank first.'));
150
+ console.log(chalk.dim(' Then run: rigstate genesis'));
151
+ }
152
+ return { complete: false, stepCount: 0 };
153
+ }
154
+ }
155
+
156
+
157
+ // ─────────────────────────────────────────────────────────────────────────────
158
+ // Trigger Genesis
159
+ // ─────────────────────────────────────────────────────────────────────────────
160
+ export async function triggerGenesis(
161
+ projectId: string,
162
+ apiKey: string,
163
+ apiUrl: string,
164
+ force = false
165
+ ): Promise<boolean> {
166
+ console.log('');
167
+ console.log(chalk.bold.blue('🏗️ GENESIS PROTOCOL'));
168
+ console.log(chalk.dim('Initializing project foundation...'));
169
+ console.log('');
170
+
171
+ const spinner = ora('Detecting tech stack...').start();
172
+
173
+ try {
174
+ // Step 1: Check status first (unless force)
175
+ if (!force) {
176
+ const statusRes = await axios.get<GenesisStatusResponse>(
177
+ `${apiUrl}/api/v1/genesis`,
178
+ {
179
+ params: { project_id: projectId },
180
+ headers: { Authorization: `Bearer ${apiKey}` },
181
+ timeout: 10000
182
+ }
183
+ );
184
+
185
+ if (statusRes.data.success && statusRes.data.data.genesis_complete) {
186
+ spinner.stop();
187
+ console.log(chalk.green('✅ Genesis already complete.'));
188
+ console.log(chalk.dim(` ${statusRes.data.data.genesis_count} foundation steps already in roadmap.`));
189
+ console.log(chalk.dim(' Use --force to re-initialize.'));
190
+ console.log('');
191
+ return true;
192
+ }
193
+
194
+ if (statusRes.data.success) {
195
+ spinner.text = `Stack detected: ${statusRes.data.data.detected_template}. Generating foundation...`;
196
+ }
197
+ } else {
198
+ spinner.text = 'Force mode: Re-generating foundation...';
199
+ }
200
+
201
+ // Step 2: Trigger Genesis
202
+ const response = await axios.post<GenesisTriggerResponse>(
203
+ `${apiUrl}/api/v1/genesis`,
204
+ { project_id: projectId, force },
205
+ {
206
+ headers: { Authorization: `Bearer ${apiKey}` },
207
+ timeout: 60000 // AI enrichment can take time
208
+ }
209
+ );
210
+
211
+ spinner.stop();
212
+
213
+ if (!response.data.success) {
214
+ // Handle specific error cases
215
+ if ((response as any).status === 409) {
216
+ console.log(chalk.yellow('⚠️ Genesis already initialized. Use --force to re-run.'));
217
+ return true;
218
+ }
219
+ if ((response as any).status === 422) {
220
+ console.log('');
221
+ console.log(chalk.yellow('⚠️ Cannot initialize Genesis yet.'));
222
+ console.log(chalk.dim(' Complete onboarding with Frank first to define your tech stack.'));
223
+ console.log(chalk.dim(' Then run: ') + chalk.white('rigstate genesis'));
224
+ console.log('');
225
+ return false;
226
+ }
227
+ console.error(chalk.red(`❌ Genesis failed: ${response.data.error}`));
228
+ return false;
229
+ }
230
+
231
+ const { data } = response.data;
232
+
233
+ // ── Success Output ────────────────────────────────────────────────────
234
+ console.log(chalk.bold.green('✅ GENESIS COMPLETE'));
235
+ console.log(chalk.dim('────────────────────────────────────────'));
236
+ console.log(`${chalk.bold('Project:')} ${chalk.cyan(data.project_name)}`);
237
+ console.log(`${chalk.bold('Stack:')} ${chalk.magenta(data.template)}`);
238
+ console.log(`${chalk.bold('Created:')} ${chalk.white(data.steps_created)} foundation steps`);
239
+
240
+ if (data.existing_steps_shifted > 0) {
241
+ console.log(`${chalk.bold('Shifted:')} ${chalk.dim(`${data.existing_steps_shifted} existing steps moved down`)}`);
242
+ }
243
+
244
+ console.log('');
245
+ console.log(chalk.bold('📋 Foundation Steps:'));
246
+ data.steps.forEach(step => {
247
+ console.log(` ${step.icon} ${chalk.bold(`T-${step.step_number}`)}: ${step.title}`);
248
+ if (step.verification_path) {
249
+ console.log(` ${chalk.dim(`Verify: ${step.verification_path}`)}`);
250
+ }
251
+ });
252
+
253
+ console.log('');
254
+ console.log(chalk.bold.yellow('⚡ NEXT MOVE:'));
255
+ if (data.steps.length > 0) {
256
+ const firstStep = data.steps[0];
257
+ console.log(` ${chalk.white(`> rigstate work start ${firstStep.id}`)} ${chalk.dim(`(Start: ${firstStep.title})`)}`);
258
+ }
259
+ console.log(chalk.dim('────────────────────────────────────────'));
260
+ console.log('');
261
+
262
+ // ── Persist genesis status to local manifest (enables offline checks) ──
263
+ try {
264
+ const { saveManifest } = await import('../utils/manifest.js');
265
+ await saveManifest({
266
+ genesis_complete: true,
267
+ genesis_template: data.template,
268
+ genesis_stack_key: data.stack_key,
269
+ genesis_initialized_at: new Date().toISOString()
270
+ });
271
+ } catch {
272
+ // Non-critical: local cache write failure doesn't break the flow
273
+ }
274
+
275
+ return true;
276
+
277
+
278
+ } catch (err: any) {
279
+ spinner.stop();
280
+
281
+ // Handle axios error responses
282
+ if (err.response?.status === 409) {
283
+ console.log(chalk.green('✅ Genesis already complete.'));
284
+ console.log(chalk.dim(' Use --force to re-initialize.'));
285
+ return true;
286
+ }
287
+
288
+ if (err.response?.status === 422) {
289
+ console.log('');
290
+ console.log(chalk.yellow('⚠️ Genesis pending: Onboarding not complete.'));
291
+ console.log(chalk.dim(' Finish the Frank conversation to define your tech stack.'));
292
+ console.log(chalk.dim(' Then run: ') + chalk.white('rigstate genesis'));
293
+ console.log('');
294
+ return false;
295
+ }
296
+
297
+ if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND') {
298
+ console.log(chalk.dim(' (Genesis skipped: API unreachable)'));
299
+ return false;
300
+ }
301
+
302
+ console.error(chalk.red(`❌ Genesis error: ${err.response?.data?.error || err.message}`));
303
+ return false;
304
+ }
305
+ }
@@ -110,12 +110,28 @@ export function createLinkCommand() {
110
110
  await installHooks(process.cwd());
111
111
  await hardenGitIgnore(process.cwd());
112
112
 
113
+ // 4. Genesis Protocol (Smart Detection)
114
+ console.log(chalk.blue('🏗️ Checking Genesis status...'));
115
+ const { checkGenesisStatus, triggerGenesis } = await import('./genesis.js');
116
+ const genesisStatus = await checkGenesisStatus(projectId, apiKey, apiUrl);
117
+
118
+ if (!genesisStatus.complete) {
119
+ // Try to trigger automatically — will fail gracefully if spec not ready
120
+ const triggered = await triggerGenesis(projectId, apiKey, apiUrl, false);
121
+ if (!triggered) {
122
+ console.log(chalk.dim(' 💡 Run "rigstate genesis" after completing onboarding with Frank.'));
123
+ }
124
+ } else {
125
+ console.log(chalk.green(` ✔ Genesis complete (${genesisStatus.stepCount} foundation steps ready)`));
126
+ }
127
+
113
128
  console.log('');
114
129
  console.log(chalk.bold.green('🚀 Link Complete! Your environment is ready.'));
115
130
 
116
- // 4. Tactical Suggestion
131
+ // 5. Tactical Suggestion
117
132
  const { suggestNextMove } = await import('./suggest.js');
118
133
  await suggestNextMove(projectId, apiKey, apiUrl);
134
+
119
135
  } else {
120
136
  console.log('');
121
137
  console.log(chalk.bold.green('🚀 Link Complete!'));
@@ -43,9 +43,9 @@ export async function executePlan(taskId?: string) {
43
43
  const tasks: any[] = response.data.data.roadmap || [];
44
44
 
45
45
  const choices = tasks
46
- .filter(t => ['ACTIVE', 'IN_PROGRESS', 'PENDING'].includes(t.status))
46
+ .filter(t => ['ACTIVE', 'IN_PROGRESS', 'PENDING', 'LOCKED'].includes(t.status))
47
47
  .map(t => ({
48
- name: `T-${t.step_number}: ${t.title}`,
48
+ name: `${t.status === 'LOCKED' ? '🔒 ' : ''}T-${t.step_number}: ${t.title}${t.origin_id === 'GENESIS' ? chalk.dim(' [Foundation]') : ''}`,
49
49
  value: t
50
50
  }));
51
51
 
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ import { createReleaseCommand } from './commands/release.js';
23
23
  import { createRoadmapCommand } from './commands/roadmap.js';
24
24
  import { createCouncilCommand } from './commands/council.js';
25
25
  import { createPlanCommand } from './commands/plan.js';
26
+ import { createGenesisCommand } from './commands/genesis.js';
26
27
  import { checkVersion } from './utils/version.js';
27
28
  import dotenv from 'dotenv';
28
29
 
@@ -63,6 +64,7 @@ program.addCommand(createReleaseCommand());
63
64
  program.addCommand(createRoadmapCommand());
64
65
  program.addCommand(createCouncilCommand());
65
66
  program.addCommand(createPlanCommand());
67
+ program.addCommand(createGenesisCommand());
66
68
 
67
69
  program.hook('preAction', async () => {
68
70
  await checkVersion();
@@ -79,6 +81,12 @@ program.on('--help', () => {
79
81
  console.log(chalk.cyan(' $ rigstate scan'));
80
82
  console.log(chalk.dim(' Scan the current directory'));
81
83
  console.log('');
84
+ console.log(chalk.cyan(' $ rigstate genesis'));
85
+ console.log(chalk.dim(' Initialize project foundation (auto-detects stack)'));
86
+ console.log('');
87
+ console.log(chalk.cyan(' $ rigstate genesis --status'));
88
+ console.log(chalk.dim(' Check genesis status without triggering'));
89
+ console.log('');
82
90
  console.log(chalk.cyan(' $ rigstate scan ./src --project abc123'));
83
91
  console.log(chalk.dim(' Scan a specific directory with project ID'));
84
92
  console.log('');
@@ -7,8 +7,14 @@ export interface RigstateManifest {
7
7
  api_url?: string;
8
8
  linked_at?: string;
9
9
  api_key?: string;
10
+ // Genesis Protocol status (cached locally to avoid API calls)
11
+ genesis_complete?: boolean;
12
+ genesis_template?: string;
13
+ genesis_stack_key?: string;
14
+ genesis_initialized_at?: string;
10
15
  }
11
16
 
17
+
12
18
  export async function loadManifest(): Promise<RigstateManifest | null> {
13
19
  const cwd = process.cwd();
14
20
  const manifestPaths = [