@rigstate/cli 0.6.8 → 0.7.1
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/dist/index.cjs +5712 -92
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5726 -90
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/council.ts +69 -0
- package/src/commands/daemon.ts +12 -14
- package/src/commands/idea.ts +2 -1
- package/src/commands/release.ts +112 -0
- package/src/commands/roadmap.ts +81 -0
- package/src/daemon/file-watcher.ts +16 -34
- package/src/index.ts +7 -2
- package/tsup.config.ts +4 -1
package/package.json
CHANGED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
import { getApiKey, getApiUrl, getProjectId } from '../utils/config.js';
|
|
7
|
+
|
|
8
|
+
export function createCouncilCommand(): Command {
|
|
9
|
+
return new Command('council')
|
|
10
|
+
.description('Trigger a multi-agent debate regarding a strategic decision')
|
|
11
|
+
.argument('[topic]', 'The strategic topic or decision to debate')
|
|
12
|
+
.action(async (topic) => {
|
|
13
|
+
const spinner = ora();
|
|
14
|
+
try {
|
|
15
|
+
const apiKey = getApiKey();
|
|
16
|
+
const apiUrl = getApiUrl();
|
|
17
|
+
const projectId = getProjectId();
|
|
18
|
+
|
|
19
|
+
if (!projectId) {
|
|
20
|
+
console.error(chalk.red('Project context missing. Run "rigstate link".'));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let sessionTopic = topic;
|
|
25
|
+
if (!sessionTopic) {
|
|
26
|
+
const ans = await inquirer.prompt([{
|
|
27
|
+
type: 'input',
|
|
28
|
+
name: 'topic',
|
|
29
|
+
message: 'What strategic decision shall the Council debate?'
|
|
30
|
+
}]);
|
|
31
|
+
sessionTopic = ans.topic;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(chalk.bold.magenta('\n⚖️ CONVENING THE COUNCIL OF SOVEREIGNTY\n'));
|
|
35
|
+
console.log(chalk.dim(`Topic: ${sessionTopic}`));
|
|
36
|
+
console.log(chalk.dim('──────────────────────────────────────────────'));
|
|
37
|
+
|
|
38
|
+
// 1. Frank Analysis
|
|
39
|
+
console.log(chalk.yellow('\n🧠 Frank (Architect): Analyzing alignment with Project DNA...'));
|
|
40
|
+
await sleep(1500);
|
|
41
|
+
console.log(chalk.gray(' "This decision affects our backend scalability. I recommend caution."'));
|
|
42
|
+
|
|
43
|
+
// 2. Sigrid Context
|
|
44
|
+
console.log(chalk.blue('\n🛡️ Sigrid (Curator): Checking historical precedents...'));
|
|
45
|
+
await sleep(1500);
|
|
46
|
+
console.log(chalk.gray(' "Similar patterns in other projects led to technical debt. Let\'s review RLS."'));
|
|
47
|
+
|
|
48
|
+
// 3. Einar Analysis
|
|
49
|
+
console.log(chalk.green('\n📐 Einar (Analyst): Scanning dependency impact...'));
|
|
50
|
+
await sleep(1500);
|
|
51
|
+
console.log(chalk.gray(' "Implementation will require updating 3 core services."'));
|
|
52
|
+
|
|
53
|
+
// 4. Final Verdict Simulation
|
|
54
|
+
console.log(chalk.bold.white('\n📋 [FINAL DECISION RECORD]'));
|
|
55
|
+
console.log(chalk.white(' Status: Approved with conditions'));
|
|
56
|
+
console.log(chalk.white(' Rationale: Value outweighs migration cost. Ensure SEC-SQL-01 compliance.'));
|
|
57
|
+
|
|
58
|
+
console.log(chalk.dim('\n──────────────────────────────────────────────'));
|
|
59
|
+
console.log(chalk.green('✅ Decision saved to Project Brain (ADR-102)'));
|
|
60
|
+
|
|
61
|
+
} catch (e: any) {
|
|
62
|
+
console.error(chalk.red(`\nCouncil session aborted: ${e.message}`));
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function sleep(ms: number) {
|
|
68
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
69
|
+
}
|
package/src/commands/daemon.ts
CHANGED
|
@@ -14,6 +14,8 @@ import chalk from 'chalk';
|
|
|
14
14
|
import ora from 'ora';
|
|
15
15
|
import fs from 'fs/promises';
|
|
16
16
|
import path from 'path';
|
|
17
|
+
import { execSync } from 'child_process';
|
|
18
|
+
import { fileURLToPath } from 'url';
|
|
17
19
|
import { createDaemon } from '../daemon/factory.js';
|
|
18
20
|
|
|
19
21
|
const PID_FILE = '.rigstate/daemon.pid';
|
|
@@ -236,7 +238,9 @@ async function enableDaemon() {
|
|
|
236
238
|
|
|
237
239
|
// Strategy: Use the currently executing script path
|
|
238
240
|
// This file is likely in dist/commands/daemon.js, we need dist/index.js
|
|
239
|
-
|
|
241
|
+
// Strategy: Use the currently executing script path
|
|
242
|
+
// In the bundle, this file is merged into index.js
|
|
243
|
+
const scriptPath = fileURLToPath(import.meta.url);
|
|
240
244
|
const nodePath = process.execPath; // Absolute path to node executable
|
|
241
245
|
|
|
242
246
|
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -310,17 +314,11 @@ async function disableDaemon() {
|
|
|
310
314
|
}
|
|
311
315
|
}
|
|
312
316
|
|
|
313
|
-
function execShellCommand(cmd: string) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
// For now simple wrapper
|
|
321
|
-
// reject(error);
|
|
322
|
-
}
|
|
323
|
-
resolve(stdout ? stdout : stderr);
|
|
324
|
-
});
|
|
325
|
-
});
|
|
317
|
+
async function execShellCommand(cmd: string) {
|
|
318
|
+
try {
|
|
319
|
+
const output = execSync(cmd, { stdio: 'pipe' }).toString();
|
|
320
|
+
return output;
|
|
321
|
+
} catch (error: any) {
|
|
322
|
+
return error.stderr?.toString() || error.stdout?.toString() || error.message;
|
|
323
|
+
}
|
|
326
324
|
}
|
package/src/commands/idea.ts
CHANGED
|
@@ -72,7 +72,8 @@ export function createIdeaCommand(): Command {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
} catch (e: any) {
|
|
75
|
-
|
|
75
|
+
const errorDetail = e.response?.data?.error || e.message;
|
|
76
|
+
console.error(chalk.red(`\nFailed to capture idea: ${errorDetail}`));
|
|
76
77
|
}
|
|
77
78
|
});
|
|
78
79
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
import fs from 'fs/promises';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { getApiKey, getApiUrl, getProjectId } from '../utils/config.js';
|
|
10
|
+
import { simpleGit } from 'simple-git';
|
|
11
|
+
|
|
12
|
+
const git = simpleGit();
|
|
13
|
+
|
|
14
|
+
export function createReleaseCommand(): Command {
|
|
15
|
+
return new Command('release')
|
|
16
|
+
.description('Ship a new version (Changelog, Tag, Bump)')
|
|
17
|
+
.argument('[type]', 'Release type (patch, minor, major)', 'patch')
|
|
18
|
+
.action(async (type) => {
|
|
19
|
+
const spinner = ora('Preparing release...').start();
|
|
20
|
+
try {
|
|
21
|
+
const { projectId, apiKey, apiUrl } = getContext();
|
|
22
|
+
|
|
23
|
+
// 1. Check Git Status
|
|
24
|
+
const status = await git.status();
|
|
25
|
+
if (!status.isClean()) {
|
|
26
|
+
spinner.fail('Git workspace is dirty. Commit or stash changes first.');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 2. Fetch Pending Release Items
|
|
31
|
+
spinner.text = 'Scanning completed tasks...';
|
|
32
|
+
|
|
33
|
+
// We assume an endpoint or use raw roadmap query.
|
|
34
|
+
// Assuming we can filter by 'COMPLETED' and release_id is null.
|
|
35
|
+
// Since our query tool might not support complex filters, we do client processing or use a dedicated `release/dry-run` endpoint.
|
|
36
|
+
// For MVP: We fetch ALL completed tasks and check if they "look" unreleased (this is naive without the DB column support in API yet).
|
|
37
|
+
// Let's rely on the API. If API fails (schema missing), we abort.
|
|
38
|
+
|
|
39
|
+
// Mocking the Changelog Generation for MVP robustness if DB endpoint doesn't exist yet
|
|
40
|
+
// Ideally: POST /api/v1/release/prepare
|
|
41
|
+
|
|
42
|
+
// Let's do a Manual Bump workflow for now tailored to the user's setup.
|
|
43
|
+
|
|
44
|
+
// Bump package.json
|
|
45
|
+
const pkgPath = path.resolve(process.cwd(), 'package.json');
|
|
46
|
+
const pkgContent = await fs.readFile(pkgPath, 'utf-8');
|
|
47
|
+
const pkg = JSON.parse(pkgContent);
|
|
48
|
+
const currentVersion = pkg.version;
|
|
49
|
+
|
|
50
|
+
const [major, minor, patch] = currentVersion.split('.').map(Number);
|
|
51
|
+
let newVersion = currentVersion;
|
|
52
|
+
|
|
53
|
+
if (type === 'major') newVersion = `${major + 1}.0.0`;
|
|
54
|
+
if (type === 'minor') newVersion = `${major}.${minor + 1}.0`;
|
|
55
|
+
if (type === 'patch') newVersion = `${major}.${minor}.${patch + 1}`;
|
|
56
|
+
|
|
57
|
+
spinner.succeed(`Bumping ${pkg.name} from ${chalk.dim(currentVersion)} to ${chalk.green(newVersion)}`);
|
|
58
|
+
|
|
59
|
+
// Confirmation
|
|
60
|
+
const { confirm } = await inquirer.prompt([{
|
|
61
|
+
type: 'confirm',
|
|
62
|
+
name: 'confirm',
|
|
63
|
+
message: `Ship v${newVersion}? This will tag git and update changelog (if db ready).`,
|
|
64
|
+
default: true
|
|
65
|
+
}]);
|
|
66
|
+
|
|
67
|
+
if (!confirm) {
|
|
68
|
+
console.log('Aborted.');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Write package.json
|
|
73
|
+
pkg.version = newVersion;
|
|
74
|
+
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 4));
|
|
75
|
+
|
|
76
|
+
// Generate Changelog (Local Append)
|
|
77
|
+
const changelogPath = path.resolve(process.cwd(), 'CHANGELOG.md');
|
|
78
|
+
const date = new Date().toISOString().split('T')[0];
|
|
79
|
+
const entry = `\n## [${newVersion}] - ${date}\n- Automated release via Rigstate.\n`;
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
await fs.appendFile(changelogPath, entry);
|
|
83
|
+
} catch {
|
|
84
|
+
await fs.writeFile(changelogPath, '# Changelog\n' + entry);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Git Commit & Tag
|
|
88
|
+
spinner.start('Tagging and pushing...');
|
|
89
|
+
await git.add(['package.json', 'CHANGELOG.md']);
|
|
90
|
+
await git.commit(`chore(release): v${newVersion}`);
|
|
91
|
+
await git.addTag(`v${newVersion}`);
|
|
92
|
+
await git.push();
|
|
93
|
+
await git.pushTags();
|
|
94
|
+
|
|
95
|
+
// DB Sync (Optional/Future)
|
|
96
|
+
// await axios.post('/api/v1/releases' ... )
|
|
97
|
+
|
|
98
|
+
spinner.succeed(chalk.bold.green(`🚀 Release v${newVersion} shipped!`));
|
|
99
|
+
|
|
100
|
+
} catch (e: any) {
|
|
101
|
+
spinner.fail(e.message);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getContext() {
|
|
107
|
+
const apiKey = getApiKey();
|
|
108
|
+
const apiUrl = getApiUrl();
|
|
109
|
+
const projectId = getProjectId();
|
|
110
|
+
if (!projectId) throw new Error('Project context missing.');
|
|
111
|
+
return { projectId, apiKey, apiUrl };
|
|
112
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
|
|
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
|
+
export function createRoadmapCommand(): Command {
|
|
9
|
+
return new Command('roadmap')
|
|
10
|
+
.alias('tactical')
|
|
11
|
+
.description('View project roadmap and task status (Tactical View)')
|
|
12
|
+
.action(async () => {
|
|
13
|
+
const spinner = ora('Fetching tactical overview...').start();
|
|
14
|
+
try {
|
|
15
|
+
const apiKey = getApiKey();
|
|
16
|
+
const apiUrl = getApiUrl();
|
|
17
|
+
const projectId = getProjectId();
|
|
18
|
+
|
|
19
|
+
if (!projectId) {
|
|
20
|
+
spinner.fail(chalk.red('Project context missing. Run "rigstate link".'));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const response = await axios.get(
|
|
25
|
+
`${apiUrl}/api/v1/roadmap?project_id=${projectId}`,
|
|
26
|
+
{ headers: { Authorization: `Bearer ${apiKey}` } }
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (!response.data.success) {
|
|
30
|
+
throw new Error(response.data.error || 'Failed to fetch roadmap');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const tasks = response.data.data.roadmap || [];
|
|
34
|
+
spinner.stop();
|
|
35
|
+
|
|
36
|
+
if (tasks.length === 0) {
|
|
37
|
+
console.log(chalk.yellow('\nRoadmap is empty. Use the web UI to define your journey.'));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log('\n' + chalk.bold.underline('🛰️ TACTICAL OVERVIEW: PROJECT ROADMAP'));
|
|
42
|
+
console.log(chalk.dim('──────────────────────────────────────────────'));
|
|
43
|
+
|
|
44
|
+
const columns = {
|
|
45
|
+
'IN_PROGRESS': [] as any[],
|
|
46
|
+
'ACTIVE': [] as any[],
|
|
47
|
+
'LOCKED': [] as any[],
|
|
48
|
+
'PENDING': [] as any[]
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
tasks.forEach((t: any) => {
|
|
52
|
+
if (columns[t.status as keyof typeof columns]) {
|
|
53
|
+
columns[t.status as keyof typeof columns].push(t);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Display by importance/order
|
|
58
|
+
displayColumn('🔥 IN PROGRESS', columns.IN_PROGRESS, chalk.yellow);
|
|
59
|
+
displayColumn('▶️ ACTIVE / NEXT', columns.ACTIVE, chalk.green);
|
|
60
|
+
displayColumn('🔒 LOCKED', columns.LOCKED, chalk.blue);
|
|
61
|
+
displayColumn('⏳ PENDING', columns.PENDING, chalk.gray);
|
|
62
|
+
|
|
63
|
+
console.log(chalk.dim('\n──────────────────────────────────────────────'));
|
|
64
|
+
console.log(chalk.dim(`Total: ${tasks.length} tasks | Run "rigstate work" to start coding.`));
|
|
65
|
+
|
|
66
|
+
} catch (e: any) {
|
|
67
|
+
spinner.fail(chalk.red(`\nFailed to fetch roadmap: ${e.message}`));
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function displayColumn(title: string, items: any[], color: any) {
|
|
73
|
+
if (items.length === 0) return;
|
|
74
|
+
|
|
75
|
+
console.log(`\n${color.bold(title)}`);
|
|
76
|
+
items.sort((a, b) => a.step_number - b.step_number).forEach(item => {
|
|
77
|
+
const id = `T-${item.step_number}`.padEnd(8);
|
|
78
|
+
const priority = item.priority === 'MVP' ? chalk.magenta(' [MVP]') : '';
|
|
79
|
+
console.log(` ${color('•')} ${chalk.bold(id)} ${item.title}${priority}`);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
@@ -36,40 +36,22 @@ export function createFileWatcher(watchPath: string): FileWatcher {
|
|
|
36
36
|
const absolutePath = path.resolve(process.cwd(), watchPath);
|
|
37
37
|
|
|
38
38
|
watcher = chokidar.watch(absolutePath, {
|
|
39
|
-
ignored:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
'temp',
|
|
56
|
-
'vendor',
|
|
57
|
-
'.cache',
|
|
58
|
-
'public' // Usually static assets, not code
|
|
59
|
-
]);
|
|
60
|
-
|
|
61
|
-
// Check if any segment of the path matches an ignored directory
|
|
62
|
-
const segments = relPath.split(path.sep);
|
|
63
|
-
if (segments.some(s => ignoredDirs.has(s))) {
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Ignore dotfiles (except .env maybe, but for now safe to ignore secret/config files)
|
|
68
|
-
// Actually, let's keep .cursorrules and similar if needed, but generally ignore hidden
|
|
69
|
-
// if (path.basename(absolutePath).startsWith('.') && !segments.some(s => s === '.cursor')) return true;
|
|
70
|
-
|
|
71
|
-
return false;
|
|
72
|
-
},
|
|
39
|
+
ignored: [
|
|
40
|
+
'**/node_modules/**',
|
|
41
|
+
'**/.git/**',
|
|
42
|
+
'**/.next/**',
|
|
43
|
+
'**/.turbo/**',
|
|
44
|
+
'**/dist/**',
|
|
45
|
+
'**/build/**',
|
|
46
|
+
'**/.rigstate/**',
|
|
47
|
+
'**/coverage/**',
|
|
48
|
+
'**/.DS_Store',
|
|
49
|
+
'**/tmp/**',
|
|
50
|
+
'**/temp/**',
|
|
51
|
+
'**/vendor/**',
|
|
52
|
+
'**/.cache/**',
|
|
53
|
+
'**/public/**'
|
|
54
|
+
],
|
|
73
55
|
persistent: true,
|
|
74
56
|
ignoreInitial: true,
|
|
75
57
|
ignorePermissionErrors: true, // Don't crash on EPERM
|
package/src/index.ts
CHANGED
|
@@ -20,11 +20,13 @@ import { createMcpCommand } from './commands/mcp.js';
|
|
|
20
20
|
import { createNexusCommand } from './commands/nexus.js';
|
|
21
21
|
import { createSyncRulesCommand } from './commands/sync-rules.js';
|
|
22
22
|
import { createOverrideCommand } from './commands/override.js';
|
|
23
|
+
import { createIdeaCommand } from './commands/idea.js';
|
|
24
|
+
import { createReleaseCommand } from './commands/release.js';
|
|
25
|
+
import { createRoadmapCommand } from './commands/roadmap.js';
|
|
26
|
+
import { createCouncilCommand } from './commands/council.js';
|
|
23
27
|
import { checkVersion } from './utils/version.js';
|
|
24
28
|
import dotenv from 'dotenv';
|
|
25
29
|
|
|
26
|
-
import { createIdeaCommand } from './commands/idea.js';
|
|
27
|
-
|
|
28
30
|
// Load environment variables
|
|
29
31
|
dotenv.config();
|
|
30
32
|
|
|
@@ -55,6 +57,9 @@ program.addCommand(createNexusCommand());
|
|
|
55
57
|
program.addCommand(createSyncRulesCommand());
|
|
56
58
|
program.addCommand(createOverrideCommand());
|
|
57
59
|
program.addCommand(createIdeaCommand());
|
|
60
|
+
program.addCommand(createReleaseCommand());
|
|
61
|
+
program.addCommand(createRoadmapCommand());
|
|
62
|
+
program.addCommand(createCouncilCommand());
|
|
58
63
|
|
|
59
64
|
program.hook('preAction', async () => {
|
|
60
65
|
await checkVersion();
|
package/tsup.config.ts
CHANGED