@rigstate/cli 0.7.26 → 0.7.30

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.26",
3
+ "version": "0.7.30",
4
4
  "description": "Rigstate CLI - Code audit, sync and supervision tool",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -9,9 +9,9 @@ import { getApiUrl, getApiKey } from '../utils/config.js';
9
9
  export function createLinkCommand() {
10
10
  return new Command('link')
11
11
  .description('Link current directory to a Rigstate project')
12
- .argument('<projectId>', 'Project ID to link')
12
+ .argument('[projectId]', 'Project ID to link')
13
13
  .action(async (projectId) => {
14
- // Check Global Override (Rigstate v2.7)
14
+ // Check Global Override first
15
15
  try {
16
16
  const globalPath = path.join(os.homedir(), '.rigstate', 'config.json');
17
17
  const globalData = await fs.readFile(globalPath, 'utf-8').catch(() => null);
@@ -20,14 +20,60 @@ export function createLinkCommand() {
20
20
  const cwd = process.cwd();
21
21
  if (config.overrides && config.overrides[cwd]) {
22
22
  const overrideId = config.overrides[cwd];
23
- if (overrideId !== projectId) {
24
- console.warn(chalk.yellow(`Global override detected. Enforcing project ID: ${overrideId}`));
23
+ console.warn(chalk.yellow(`Global override detected. Enforcing project ID: ${overrideId}`));
24
+ if (!projectId) projectId = overrideId;
25
+ else if (projectId !== overrideId) {
26
+ console.warn(chalk.red(`Ignoring provided ID ${projectId}. Using override.`));
25
27
  projectId = overrideId;
26
28
  }
27
29
  }
28
30
  }
29
31
  } catch (e) { }
30
32
 
33
+ // Interactive Selection if no ID
34
+ if (!projectId) {
35
+ try {
36
+ const inquirer = (await import('inquirer')).default;
37
+ const { getApiKey: _getApiKey, getApiUrl: _getApiUrl } = await import('../utils/config.js');
38
+ const apiKey = getApiKey();
39
+ const apiUrl = getApiUrl();
40
+
41
+ if (!apiKey) {
42
+ console.error(chalk.red('Not authenticated. Please run "rigstate login" or provide a Project ID.'));
43
+ process.exit(1);
44
+ }
45
+
46
+ console.log(chalk.dim('Fetching your projects...'));
47
+ const axios = (await import('axios')).default;
48
+ const response = await axios.get(`${apiUrl}/api/v1/projects`, {
49
+ headers: { Authorization: `Bearer ${apiKey}` }
50
+ });
51
+
52
+ if (!response.data.success || !response.data.data.projects?.length) {
53
+ console.error(chalk.yellow('No projects found. Create one at https://app.rigstate.com'));
54
+ process.exit(1);
55
+ }
56
+
57
+ const choices = response.data.data.projects.map((p: any) => ({
58
+ name: `${p.name} (${p.id})`,
59
+ value: p.id
60
+ }));
61
+
62
+ const answer = await inquirer.prompt([{
63
+ type: 'list',
64
+ name: 'id',
65
+ message: 'Select project to link:',
66
+ choices
67
+ }]);
68
+ projectId = answer.id;
69
+
70
+ } catch (e: any) {
71
+ console.error(chalk.red(`Failed to fetch projects: ${e.message}`));
72
+ console.error('Please provide project ID manually: rigstate link <id>');
73
+ process.exit(1);
74
+ }
75
+ }
76
+
31
77
  const manifestPath = path.join(process.cwd(), '.rigstate');
32
78
 
33
79
  const content: any = {
@@ -42,6 +88,9 @@ export function createLinkCommand() {
42
88
  }
43
89
 
44
90
  try {
91
+ // Ensure .rigstate dir exists
92
+ await fs.mkdir(path.dirname(manifestPath), { recursive: true });
93
+
45
94
  await fs.writeFile(manifestPath, JSON.stringify(content, null, 2), 'utf-8');
46
95
  console.log(chalk.green(`✔ Linked to project ID: ${projectId}`));
47
96
  console.log(chalk.dim(`Created local context manifest at .rigstate`));
@@ -52,7 +101,7 @@ export function createLinkCommand() {
52
101
  console.log('');
53
102
 
54
103
  const { getApiKey: _getApiKey, getApiUrl: _getApiUrl } = await import('../utils/config.js');
55
- const apiKey = getApiKey(); // Might throw if not logged in
104
+ const apiKey = getApiKey();
56
105
  const apiUrl = getApiUrl();
57
106
 
58
107
  if (apiKey) {
@@ -67,19 +116,14 @@ export function createLinkCommand() {
67
116
  await syncProjectRules(projectId, apiKey, apiUrl);
68
117
 
69
118
  // 3. Git Hooks (Auto-Vaccine)
70
- console.log(chalk.blue('🛡️ Injecting Guardian hooks...'));
71
- const { createHooksCommand } = await import('./hooks.js');
72
- // We can't easily invoke the command action directly without refactoring,
73
- // but we can reuse the logic. For now, let's replicate the simple install logic
74
- // or better yet, make a shared utility.
75
- // Actually, the simplest way for v0.7.25 is to call the install logic directly if possible.
76
- // Let's use the helper function at the bottom of this file which I see exists.
119
+ console.log(chalk.blue('🛡️ Injecting Guardian hooks & Safety nets...'));
77
120
  await installHooks(process.cwd());
121
+ await hardenGitIgnore(process.cwd());
78
122
 
79
123
  console.log('');
80
124
  console.log(chalk.bold.green('🚀 Link Complete! Your environment is ready.'));
81
125
 
82
- // 4. Tactical Suggestion (The First Move)
126
+ // 4. Tactical Suggestion
83
127
  const { suggestNextMove } = await import('./suggest.js');
84
128
  await suggestNextMove(projectId, apiKey, apiUrl);
85
129
  } else {
@@ -88,7 +132,7 @@ export function createLinkCommand() {
88
132
  }
89
133
 
90
134
  } catch (error: any) {
91
- if (error.message.includes('Not authenticated')) {
135
+ if (error.message?.includes('Not authenticated')) {
92
136
  console.warn(chalk.yellow('⚠️ Not authenticated. Run "rigstate login" to enable automation features.'));
93
137
  } else {
94
138
  console.error(chalk.red(`Failed to link project: ${error.message}`));
@@ -97,6 +141,47 @@ export function createLinkCommand() {
97
141
  });
98
142
  }
99
143
 
144
+ async function hardenGitIgnore(cwd: string) {
145
+ const fs = await import('fs/promises');
146
+ const path = await import('path');
147
+ const ignorePath = path.join(cwd, '.gitignore');
148
+
149
+ const REQUIRED_IGNORES = [
150
+ '# Rigstate - Runtime Artifacts (Do not commit)',
151
+ '.rigstate/ACTIVE_VIOLATIONS.md',
152
+ '.rigstate/CURRENT_CONTEXT.md',
153
+ '.rigstate/daemon.pid',
154
+ '.rigstate/daemon.state.json',
155
+ '.rigstate/*.log',
156
+ '.rigstate/*.bak',
157
+ '# Keep identity tracked',
158
+ '!.rigstate/identity.json'
159
+ ];
160
+
161
+ try {
162
+ let content = '';
163
+ try {
164
+ content = await fs.readFile(ignorePath, 'utf-8');
165
+ } catch {
166
+ // No .gitignore, start fresh
167
+ content = '';
168
+ }
169
+
170
+ const missing = REQUIRED_IGNORES.filter(line => !content.includes(line) && !line.startsWith('#'));
171
+
172
+ if (missing.length > 0) {
173
+ console.log(chalk.dim(' Configuring .gitignore for Rigstate safety...'));
174
+ const toAppend = '\n\n' + REQUIRED_IGNORES.join('\n') + '\n';
175
+ await fs.writeFile(ignorePath, content + toAppend, 'utf-8');
176
+ console.log(chalk.green(' ✔ .gitignore updated (Artifacts protected)'));
177
+ } else {
178
+ console.log(chalk.green(' ✔ .gitignore already hardened'));
179
+ }
180
+ } catch (e: any) {
181
+ console.warn(chalk.yellow(` Could not update .gitignore: ${e.message}`));
182
+ }
183
+ }
184
+
100
185
  async function installHooks(cwd: string) {
101
186
  const fs = await import('fs/promises');
102
187
  const path = await import('path');
@@ -0,0 +1,165 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import axios from 'axios';
5
+ import fs from 'fs/promises';
6
+ import path from 'path';
7
+ import inquirer from 'inquirer';
8
+ import { getApiKey, getApiUrl, getProjectId } from '../utils/config.js';
9
+
10
+ export function createPlanCommand(): Command {
11
+ const plan = new Command('plan');
12
+
13
+ plan
14
+ .description('Generate an implementation plan for a roadmap task')
15
+ .argument('[taskId]', 'Task ID (e.g. T-10) or UUID')
16
+ .action(async (taskId) => {
17
+ await executePlan(taskId);
18
+ });
19
+
20
+ return plan;
21
+ }
22
+
23
+ export async function executePlan(taskId?: string) {
24
+ const spinner = ora('Initializing Planning Mode...').start();
25
+
26
+ try {
27
+ const { projectId, apiKey, apiUrl } = getContext();
28
+
29
+ // 1. Resolve Task ID if missing or short
30
+ let realId = taskId;
31
+ let taskTitle = '';
32
+ let taskDescription = '';
33
+
34
+ if (!taskId) {
35
+ spinner.text = 'Fetching actionable tasks...';
36
+ // Interactive selection
37
+ const response = await axios.get(
38
+ `${apiUrl}/api/v1/roadmap?project_id=${projectId}`,
39
+ { headers: { 'Authorization': `Bearer ${apiKey}` } }
40
+ );
41
+
42
+ if (!response.data.success) throw new Error('Failed to fetch roadmap');
43
+ const tasks: any[] = response.data.data.roadmap || [];
44
+
45
+ const choices = tasks
46
+ .filter(t => ['ACTIVE', 'IN_PROGRESS', 'PENDING'].includes(t.status))
47
+ .map(t => ({
48
+ name: `T-${t.step_number}: ${t.title}`,
49
+ value: t
50
+ }));
51
+
52
+ if (choices.length === 0) {
53
+ spinner.fail('No actionable tasks found in roadmap.');
54
+ return;
55
+ }
56
+
57
+ spinner.stop();
58
+ const answer = await inquirer.prompt([{
59
+ type: 'list',
60
+ name: 'task',
61
+ message: 'Select a task to plan:',
62
+ choices
63
+ }]);
64
+
65
+ realId = answer.task.id;
66
+ taskTitle = answer.task.title;
67
+ taskDescription = answer.task.description;
68
+ taskId = `T-${answer.task.step_number}`;
69
+ } else {
70
+ // Fetch specific task details
71
+ spinner.text = `Fetching details for ${taskId}...`;
72
+ const response = await axios.get(
73
+ `${apiUrl}/api/v1/roadmap?project_id=${projectId}`,
74
+ { headers: { 'Authorization': `Bearer ${apiKey}` } }
75
+ );
76
+ const task = response.data.data.roadmap.find((t: any) =>
77
+ t.id === taskId ||
78
+ `T-${t.step_number}` === taskId ||
79
+ t.step_number.toString() === taskId
80
+ );
81
+
82
+ if (!task) throw new Error(`Task ${taskId} not found.`);
83
+ realId = task.id;
84
+ taskTitle = task.title;
85
+ taskDescription = task.description;
86
+ }
87
+
88
+ // 2. Generate Context File
89
+ spinner.start('Generating Context for Frank...');
90
+ const contextPath = path.join(process.cwd(), '.rigstate', 'CURRENT_CONTEXT.md');
91
+ const contextContent = `
92
+ # 🎯 Active Mission: ${taskTitle}
93
+ **ID:** ${taskId}
94
+
95
+ ## 📝 Description
96
+ ${taskDescription}
97
+
98
+ ## 🛡️ Architectural Constraints
99
+ - Follow strictly the rules in .cursor/rules/
100
+ - Ensure zero violations in ACTIVE_VIOLATIONS.md
101
+ - Update IMPLEMENTATION_PLAN.md before writing code.
102
+
103
+ *Generated by Rigstate CLI at ${new Date().toLocaleString()}*
104
+ `;
105
+ await fs.mkdir(path.dirname(contextPath), { recursive: true });
106
+ await fs.writeFile(contextPath, contextContent.trim());
107
+
108
+ // 3. Generate Plan Template
109
+ const planPath = path.join(process.cwd(), 'IMPLEMENTATION_PLAN.md');
110
+ const planExists = await fs.stat(planPath).then(() => true).catch(() => false);
111
+
112
+ if (!planExists) {
113
+ const planTemplate = `
114
+ # 📋 Implementation Plan: ${taskTitle}
115
+
116
+ ## 1. 🔍 Analysis
117
+ - [ ] Understand the requirements in .rigstate/CURRENT_CONTEXT.md
118
+ - [ ] Check for existing architectural patterns
119
+
120
+ ## 2. 🏗️ Proposed Changes
121
+ [Frank: List the files you intend to modify and the nature of the changes]
122
+
123
+ ## 3. ✅ Verification
124
+ - [ ] Run tests
125
+ - [ ] Verification Step 1...
126
+
127
+ ## 4. 🚀 Execution
128
+ [Frank: Log your progress here]
129
+ `;
130
+ await fs.writeFile(planPath, planTemplate.trim());
131
+ spinner.succeed(chalk.green('Created new IMPLEMENTATION_PLAN.md'));
132
+ } else {
133
+ spinner.info(chalk.yellow('IMPLEMENTATION_PLAN.md already exists. Preserving it.'));
134
+ // Optionally append or just leave it
135
+ }
136
+
137
+ // 4. Update Status (Optional - maybe set to IN_PROGRESS?)
138
+ // For now, let's keep it pure planning.
139
+
140
+ console.log('');
141
+ console.log(chalk.bold.blue('🚀 Planning Mode Activated'));
142
+ console.log(chalk.dim('────────────────────────────────────────'));
143
+ console.log(`1. Context loaded into: ${chalk.bold('.rigstate/CURRENT_CONTEXT.md')}`);
144
+ console.log(`2. Plan template ready: ${chalk.bold('IMPLEMENTATION_PLAN.md')}`);
145
+ console.log('');
146
+ console.log(chalk.yellow('👉 NEXT STEP:'));
147
+ console.log(` Open ${chalk.bold('IMPLEMENTATION_PLAN.md')} in your IDE.`);
148
+ console.log(` Tell Frank: ${chalk.italic('"Read the context and draft the plan."')}`);
149
+ console.log('');
150
+
151
+ } catch (e: any) {
152
+ spinner.fail(chalk.red(`Planning failed: ${e.message}`));
153
+ }
154
+ }
155
+
156
+ function getContext() {
157
+ const apiKey = getApiKey();
158
+ const apiUrl = getApiUrl();
159
+ const projectId = getProjectId();
160
+
161
+ if (!projectId) {
162
+ throw new Error('Project ID missing. Run rigstate link.');
163
+ }
164
+ return { projectId, apiKey, apiUrl };
165
+ }
@@ -6,6 +6,7 @@ import inquirer from 'inquirer';
6
6
  import fs from 'fs/promises';
7
7
  import { getApiKey, getApiUrl, getProjectId } from '../utils/config.js';
8
8
  import { suggestNextMove } from './suggest.js';
9
+ import { executePlan } from './plan.js';
9
10
 
10
11
  export function createWorkCommand(): Command {
11
12
  const work = new Command('work');
@@ -92,12 +93,17 @@ async function listInteractive() {
92
93
  name: 'action',
93
94
  message: 'Action:',
94
95
  choices: [
96
+ { name: 'Plan (Draft Blueprint - RECOMMENDED)', value: 'plan' },
95
97
  { name: 'Start (Set IN_PROGRESS)', value: 'start' },
96
98
  { name: 'Finish (Audit & Complete)', value: 'finish' },
97
99
  { name: 'Cancel', value: 'cancel' }
98
100
  ]
99
101
  }]);
100
102
 
103
+ if (action === 'plan') {
104
+ await executePlan(taskId);
105
+ // After planning, maybe ask to start? For now, just exit as plan command does sufficient logging.
106
+ }
101
107
  if (action === 'start') await setTaskStatus(taskId, 'IN_PROGRESS');
102
108
  if (action === 'finish') await finishTask(taskId);
103
109
 
package/src/index.ts CHANGED
@@ -22,6 +22,7 @@ import { createIdeaCommand } from './commands/idea.js';
22
22
  import { createReleaseCommand } from './commands/release.js';
23
23
  import { createRoadmapCommand } from './commands/roadmap.js';
24
24
  import { createCouncilCommand } from './commands/council.js';
25
+ import { createPlanCommand } from './commands/plan.js';
25
26
  import { checkVersion } from './utils/version.js';
26
27
  import dotenv from 'dotenv';
27
28
 
@@ -61,6 +62,7 @@ program.addCommand(createIdeaCommand());
61
62
  program.addCommand(createReleaseCommand());
62
63
  program.addCommand(createRoadmapCommand());
63
64
  program.addCommand(createCouncilCommand());
65
+ program.addCommand(createPlanCommand());
64
66
 
65
67
  program.hook('preAction', async () => {
66
68
  await checkVersion();