robrain 0.1.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.
- package/dist/commands/explain.d.ts +7 -0
- package/dist/commands/explain.d.ts.map +1 -0
- package/dist/commands/explain.js +145 -0
- package/dist/commands/explain.js.map +1 -0
- package/dist/commands/init-project.d.ts +6 -0
- package/dist/commands/init-project.d.ts.map +1 -0
- package/dist/commands/init-project.js +120 -0
- package/dist/commands/init-project.js.map +1 -0
- package/dist/commands/inject.d.ts +10 -0
- package/dist/commands/inject.d.ts.map +1 -0
- package/dist/commands/inject.js +123 -0
- package/dist/commands/inject.js.map +1 -0
- package/dist/commands/install.d.ts +11 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +305 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/review.d.ts +9 -0
- package/dist/commands/review.d.ts.map +1 -0
- package/dist/commands/review.js +299 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/status.d.ts +9 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +126 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +135 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/auth.d.ts +21 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +73 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/config.d.ts +22 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +49 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/editor.d.ts +25 -0
- package/dist/lib/editor.d.ts.map +1 -0
- package/dist/lib/editor.js +195 -0
- package/dist/lib/editor.js.map +1 -0
- package/dist/lib/load-env.d.ts +3 -0
- package/dist/lib/load-env.d.ts.map +1 -0
- package/dist/lib/load-env.js +29 -0
- package/dist/lib/load-env.js.map +1 -0
- package/dist/lib/mcp-bundle.d.ts +14 -0
- package/dist/lib/mcp-bundle.d.ts.map +1 -0
- package/dist/lib/mcp-bundle.js +42 -0
- package/dist/lib/mcp-bundle.js.map +1 -0
- package/dist/lib/project.d.ts +20 -0
- package/dist/lib/project.d.ts.map +1 -0
- package/dist/lib/project.js +201 -0
- package/dist/lib/project.js.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// src/commands/install.ts
|
|
2
|
+
// ─────────────────────────────────────────────────────────────
|
|
3
|
+
// robrain install [--token TOKEN] [--editor claude-code|cursor|copilot]
|
|
4
|
+
//
|
|
5
|
+
// 1. Authenticate with Rory Plans
|
|
6
|
+
// 2. Fetch provisioned API URLs + keys
|
|
7
|
+
// 3. Prompt for embedding provider if not set
|
|
8
|
+
// 4. Detect editors (or use --editor flag)
|
|
9
|
+
// 5. Write MCP config to each detected editor
|
|
10
|
+
// 6. Save config to ~/.robrain/config.json
|
|
11
|
+
// ─────────────────────────────────────────────────────────────
|
|
12
|
+
import chalk from 'chalk';
|
|
13
|
+
import ora from 'ora';
|
|
14
|
+
import prompts from 'prompts';
|
|
15
|
+
import { readConfig, writeConfig, mergeConfig } from '../lib/config.js';
|
|
16
|
+
import { validateToken, fetchProvisionedConfig } from '../lib/auth.js';
|
|
17
|
+
import { detectEditors, writeMcpConfig } from '../lib/editor.js';
|
|
18
|
+
import { ensureSensingMcpBundle, controlBundleReady, sensingBundleReady, McpBundleError, } from '../lib/mcp-bundle.js';
|
|
19
|
+
import { loadInstallEnv } from '../lib/load-env.js';
|
|
20
|
+
import { join } from 'path';
|
|
21
|
+
import { homedir } from 'os';
|
|
22
|
+
const ROBRAIN_MCP_DIR = join(homedir(), '.robrain', 'mcp');
|
|
23
|
+
function resolveRepoRoot(opts) {
|
|
24
|
+
return opts.repoRoot ?? process.env.ROBRAIN_REPO;
|
|
25
|
+
}
|
|
26
|
+
function prepareMcpBundles(opts) {
|
|
27
|
+
const repoRoot = resolveRepoRoot(opts);
|
|
28
|
+
if (!repoRoot && !sensingBundleReady(ROBRAIN_MCP_DIR)) {
|
|
29
|
+
console.log(chalk.red('\n ✗ Sensing MCP bundle missing.'));
|
|
30
|
+
console.log(chalk.dim(' Expected: ') + join(ROBRAIN_MCP_DIR, 'sensing', 'dist', 'index.js'));
|
|
31
|
+
console.log(chalk.dim('\n Fix: from your robrain clone run ') + chalk.cyan('pnpm install') + chalk.dim(' + ') + chalk.cyan('pnpm build'));
|
|
32
|
+
console.log(chalk.dim(' Then reinstall with: ') + chalk.cyan('robrain install --repo-root /path/to/robrain'));
|
|
33
|
+
console.log(chalk.dim(' (or set ') + chalk.cyan('ROBRAIN_REPO') + chalk.dim(' to that path)\n'));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
if (!repoRoot && sensingBundleReady(ROBRAIN_MCP_DIR))
|
|
37
|
+
return;
|
|
38
|
+
try {
|
|
39
|
+
ensureSensingMcpBundle(repoRoot, ROBRAIN_MCP_DIR);
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
if (e instanceof McpBundleError) {
|
|
43
|
+
console.log(chalk.red(`\n ✗ ${e.message}\n`));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
throw e;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export async function installCommand(opts) {
|
|
50
|
+
loadInstallEnv(resolveRepoRoot(opts));
|
|
51
|
+
console.log();
|
|
52
|
+
console.log(chalk.bold(' RoBrain') + chalk.dim(' — institutional memory for AI coding agents'));
|
|
53
|
+
console.log(chalk.dim(' by roryplans.ai\n'));
|
|
54
|
+
// ── Self-hosted mode — skip Rory Plans auth entirely ──────
|
|
55
|
+
if (opts.selfHosted) {
|
|
56
|
+
prepareMcpBundles(opts);
|
|
57
|
+
return installSelfHosted(opts);
|
|
58
|
+
}
|
|
59
|
+
prepareMcpBundles(opts);
|
|
60
|
+
// ── Cloud mode — authenticate with Rory Plans ─────────────
|
|
61
|
+
let token = opts.token ?? readConfig().token;
|
|
62
|
+
if (!token) {
|
|
63
|
+
console.log(chalk.dim(' Get your token at: ') + chalk.cyan('https://roryplans.ai/settings/api\n'));
|
|
64
|
+
const { inputToken } = await prompts({
|
|
65
|
+
type: 'password',
|
|
66
|
+
name: 'inputToken',
|
|
67
|
+
message: 'Rory Plans API token:',
|
|
68
|
+
});
|
|
69
|
+
if (!inputToken) {
|
|
70
|
+
console.log(chalk.red('\n ✗ No token provided. Exiting.'));
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
token = inputToken;
|
|
74
|
+
}
|
|
75
|
+
// ── Step 2: Validate token ─────────────────────────────────
|
|
76
|
+
const spinner = ora({ text: 'Connecting to roryplans.ai...', color: 'green' }).start();
|
|
77
|
+
const authResult = await validateToken(token);
|
|
78
|
+
if (!authResult.ok) {
|
|
79
|
+
spinner.fail(chalk.red(`Authentication failed: ${authResult.error}`));
|
|
80
|
+
console.log(chalk.dim('\n Create an account at https://roryplans.ai'));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
spinner.succeed(`Authenticated as ${chalk.bold(authResult.email)}`);
|
|
84
|
+
// ── Step 3: Fetch provisioned config ───────────────────────
|
|
85
|
+
spinner.start('Fetching provisioned API config...');
|
|
86
|
+
const provisioned = await fetchProvisionedConfig(token);
|
|
87
|
+
if (!provisioned) {
|
|
88
|
+
spinner.fail('Could not fetch provisioned config. Contact support@roryplans.ai');
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
spinner.succeed('API endpoints configured');
|
|
92
|
+
// ── Step 4: Embedding provider ─────────────────────────────
|
|
93
|
+
let embeddingProvider = provisioned.embeddingProvider;
|
|
94
|
+
let embeddingKey = '';
|
|
95
|
+
if (!embeddingProvider) {
|
|
96
|
+
console.log();
|
|
97
|
+
const { provider } = await prompts({
|
|
98
|
+
type: 'select',
|
|
99
|
+
name: 'provider',
|
|
100
|
+
message: 'Choose embedding provider (for semantic search):',
|
|
101
|
+
choices: [
|
|
102
|
+
{ title: 'OpenAI text-embedding-3-small (~$0.00002/1k tokens)', value: 'openai' },
|
|
103
|
+
{ title: 'Voyage voyage-3-lite (fast, cheap alternative)', value: 'voyage' },
|
|
104
|
+
{ title: 'Cohere embed-english-v3.0 (higher quality)', value: 'cohere' },
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
embeddingProvider = provider;
|
|
108
|
+
const keyName = {
|
|
109
|
+
openai: 'OPENAI_API_KEY',
|
|
110
|
+
voyage: 'VOYAGE_API_KEY',
|
|
111
|
+
cohere: 'COHERE_API_KEY',
|
|
112
|
+
}[embeddingProvider] ?? 'EMBEDDING_API_KEY';
|
|
113
|
+
embeddingKey = process.env[keyName] ?? '';
|
|
114
|
+
if (embeddingKey) {
|
|
115
|
+
console.log(chalk.dim(` Using ${keyName} from environment`));
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
const { key } = await prompts({
|
|
119
|
+
type: 'password',
|
|
120
|
+
name: 'key',
|
|
121
|
+
message: `${keyName}:`,
|
|
122
|
+
});
|
|
123
|
+
embeddingKey = key;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// ── Step 5: Detect / select editors ───────────────────────
|
|
127
|
+
const detected = detectEditors();
|
|
128
|
+
let editorsToConfig = detected;
|
|
129
|
+
if (opts.editor) {
|
|
130
|
+
editorsToConfig = detected.filter(e => e.editor === opts.editor);
|
|
131
|
+
if (editorsToConfig.length === 0) {
|
|
132
|
+
// Force the specified editor even if not detected
|
|
133
|
+
const configPaths = {
|
|
134
|
+
'claude-code': join(homedir(), '.claude.json'),
|
|
135
|
+
'cursor': join(homedir(), '.cursor', 'mcp.json'),
|
|
136
|
+
};
|
|
137
|
+
const configPath = configPaths[opts.editor];
|
|
138
|
+
if (configPath) {
|
|
139
|
+
editorsToConfig = [{
|
|
140
|
+
editor: opts.editor,
|
|
141
|
+
configPath,
|
|
142
|
+
label: opts.editor,
|
|
143
|
+
}];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else if (detected.length === 0) {
|
|
148
|
+
console.log(chalk.yellow('\n No AI editors detected. Configuring Claude Code by default.'));
|
|
149
|
+
editorsToConfig = [{
|
|
150
|
+
editor: 'claude-code',
|
|
151
|
+
configPath: join(homedir(), '.claude.json'),
|
|
152
|
+
label: 'Claude Code',
|
|
153
|
+
}];
|
|
154
|
+
}
|
|
155
|
+
else if (detected.length > 1) {
|
|
156
|
+
console.log();
|
|
157
|
+
const { chosen } = await prompts({
|
|
158
|
+
type: 'multiselect',
|
|
159
|
+
name: 'chosen',
|
|
160
|
+
message: 'Configure RoBrain for which editors?',
|
|
161
|
+
choices: detected.map(e => ({ title: e.label, value: e.editor, selected: true })),
|
|
162
|
+
});
|
|
163
|
+
editorsToConfig = detected.filter(e => chosen.includes(e.editor));
|
|
164
|
+
}
|
|
165
|
+
// ── Step 6: Write MCP configs ──────────────────────────────
|
|
166
|
+
spinner.start('Writing MCP configuration...');
|
|
167
|
+
const includeControl = controlBundleReady(ROBRAIN_MCP_DIR);
|
|
168
|
+
const mcpOpts = {
|
|
169
|
+
sensingMcpPath: join(ROBRAIN_MCP_DIR, 'sensing', 'dist', 'index.js'),
|
|
170
|
+
controlMcpPath: join(ROBRAIN_MCP_DIR, 'control', 'dist', 'index.js'),
|
|
171
|
+
anthropicKey: process.env.ANTHROPIC_API_KEY ?? '',
|
|
172
|
+
perceptionUrl: provisioned.perceptionUrl,
|
|
173
|
+
perceptionKey: provisioned.perceptionKey,
|
|
174
|
+
planningUrl: provisioned.planningUrl,
|
|
175
|
+
planningKey: provisioned.planningKey,
|
|
176
|
+
embeddingProvider: embeddingProvider ?? 'openai',
|
|
177
|
+
embeddingKey,
|
|
178
|
+
includeControl,
|
|
179
|
+
};
|
|
180
|
+
for (const editor of editorsToConfig) {
|
|
181
|
+
writeMcpConfig(editor.configPath, mcpOpts);
|
|
182
|
+
}
|
|
183
|
+
if (!includeControl) {
|
|
184
|
+
console.log(chalk.yellow(' ⚠ robrain-control was not registered — no Control MCP bundle under ~/.robrain/mcp/control.\n' +
|
|
185
|
+
' OSS/manual retrieval: use robrain inject. Rory Plans cloud supplies Control for auto-injection.'));
|
|
186
|
+
console.log();
|
|
187
|
+
}
|
|
188
|
+
// ── Step 7: Save local config ──────────────────────────────
|
|
189
|
+
writeConfig({
|
|
190
|
+
token,
|
|
191
|
+
email: authResult.email,
|
|
192
|
+
perceptionUrl: provisioned.perceptionUrl,
|
|
193
|
+
perceptionKey: provisioned.perceptionKey,
|
|
194
|
+
planningUrl: provisioned.planningUrl,
|
|
195
|
+
planningKey: provisioned.planningKey,
|
|
196
|
+
embeddingProvider: embeddingProvider ?? 'openai',
|
|
197
|
+
installedAt: new Date().toISOString(),
|
|
198
|
+
version: '0.1.0',
|
|
199
|
+
});
|
|
200
|
+
spinner.succeed('MCP servers configured');
|
|
201
|
+
// ── Done ───────────────────────────────────────────────────
|
|
202
|
+
console.log();
|
|
203
|
+
console.log(chalk.green(' ✓ RoBrain installed successfully\n'));
|
|
204
|
+
console.log(chalk.dim(' Configured for: ') + editorsToConfig.map(e => e.label).join(', '));
|
|
205
|
+
console.log();
|
|
206
|
+
console.log(chalk.bold(' Next step:') + chalk.dim(' initialize your project memory'));
|
|
207
|
+
console.log(chalk.dim(' Run in your project root: ') + chalk.cyan('robrain init-project'));
|
|
208
|
+
console.log();
|
|
209
|
+
// Check if ANTHROPIC_API_KEY is set — needed by Sensing
|
|
210
|
+
if (!process.env.ANTHROPIC_API_KEY) {
|
|
211
|
+
console.log(chalk.yellow(' ⚠ ANTHROPIC_API_KEY not found in environment.'));
|
|
212
|
+
console.log(chalk.dim(' Add it to repo-root `.env`, your shell profile, or the MCP config env block, then re-run install if needed.'));
|
|
213
|
+
console.log();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// ── Self-hosted install ────────────────────────────────────────
|
|
217
|
+
// Skips Rory Plans auth. Points Sensing MCP at a local
|
|
218
|
+
// Perception instance started with pnpm docker:up.
|
|
219
|
+
async function installSelfHosted(opts) {
|
|
220
|
+
const perceptionUrl = opts.perceptionUrl ?? 'http://localhost:3001';
|
|
221
|
+
console.log(chalk.bold(' Self-hosted mode\n'));
|
|
222
|
+
console.log(chalk.dim(' Sensing MCP will connect to: ') + chalk.cyan(perceptionUrl));
|
|
223
|
+
console.log(chalk.dim(' Make sure Perception is running: ') + chalk.cyan('pnpm docker:up\n'));
|
|
224
|
+
const envProvider = process.env.EMBEDDING_PROVIDER;
|
|
225
|
+
let provider;
|
|
226
|
+
if (envProvider === 'openai' || envProvider === 'voyage' || envProvider === 'cohere') {
|
|
227
|
+
provider = envProvider;
|
|
228
|
+
console.log(chalk.dim(` Using EMBEDDING_PROVIDER from environment (${provider})`));
|
|
229
|
+
console.log(chalk.dim(' Must match the repo-root `.env` used by `pnpm docker:up`.\n'));
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
const { provider: choice } = await prompts({
|
|
233
|
+
type: 'select',
|
|
234
|
+
name: 'provider',
|
|
235
|
+
message: 'Embedding provider (must match repo-root `.env` for `pnpm docker:up`):',
|
|
236
|
+
choices: [
|
|
237
|
+
{ title: 'OpenAI text-embedding-3-small', value: 'openai' },
|
|
238
|
+
{ title: 'Voyage voyage-3-lite', value: 'voyage' },
|
|
239
|
+
{ title: 'Cohere embed-english-v3.0', value: 'cohere' },
|
|
240
|
+
],
|
|
241
|
+
});
|
|
242
|
+
provider = choice;
|
|
243
|
+
}
|
|
244
|
+
const keyName = { openai: 'OPENAI_API_KEY', voyage: 'VOYAGE_API_KEY', cohere: 'COHERE_API_KEY' }[provider] ?? 'EMBEDDING_API_KEY';
|
|
245
|
+
let embKey = process.env[keyName] ?? '';
|
|
246
|
+
if (embKey) {
|
|
247
|
+
console.log(chalk.dim(` Using ${keyName} from environment`));
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
const answer = await prompts({ type: 'password', name: 'embKey', message: `${keyName}:` });
|
|
251
|
+
embKey = answer.embKey;
|
|
252
|
+
}
|
|
253
|
+
let anthropicKey = process.env.ANTHROPIC_API_KEY ?? '';
|
|
254
|
+
if (anthropicKey) {
|
|
255
|
+
console.log(chalk.dim(' Using ANTHROPIC_API_KEY from environment'));
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
const answer = await prompts({ type: 'password', name: 'anthropicKey', message: 'ANTHROPIC_API_KEY (for Sensing classifiers):' });
|
|
259
|
+
anthropicKey = answer.anthropicKey;
|
|
260
|
+
}
|
|
261
|
+
// Detect editors
|
|
262
|
+
const detected = detectEditors();
|
|
263
|
+
const editorsToConfig = detected.length > 0 ? detected : [{
|
|
264
|
+
editor: 'claude-code',
|
|
265
|
+
configPath: join(homedir(), '.claude.json'),
|
|
266
|
+
label: 'Claude Code',
|
|
267
|
+
}];
|
|
268
|
+
const spinner = ora('Writing MCP configuration...').start();
|
|
269
|
+
const mcpOpts = {
|
|
270
|
+
sensingMcpPath: join(ROBRAIN_MCP_DIR, 'sensing', 'dist', 'index.js'),
|
|
271
|
+
controlMcpPath: join(ROBRAIN_MCP_DIR, 'control', 'dist', 'index.js'),
|
|
272
|
+
anthropicKey,
|
|
273
|
+
perceptionUrl,
|
|
274
|
+
perceptionKey: '',
|
|
275
|
+
planningUrl: '', // no Planning in self-hosted OSS
|
|
276
|
+
planningKey: '',
|
|
277
|
+
embeddingProvider: provider,
|
|
278
|
+
embeddingKey: embKey,
|
|
279
|
+
includeControl: false,
|
|
280
|
+
};
|
|
281
|
+
for (const editor of editorsToConfig) {
|
|
282
|
+
writeMcpConfig(editor.configPath, mcpOpts);
|
|
283
|
+
}
|
|
284
|
+
mergeConfig({
|
|
285
|
+
perceptionUrl,
|
|
286
|
+
embeddingProvider: provider,
|
|
287
|
+
installedAt: new Date().toISOString(),
|
|
288
|
+
version: '0.1.0',
|
|
289
|
+
selfHosted: true,
|
|
290
|
+
});
|
|
291
|
+
spinner.succeed('MCP servers configured for self-hosted mode');
|
|
292
|
+
console.log();
|
|
293
|
+
console.log(chalk.green(' ✓ RoBrain (self-hosted) installed\n'));
|
|
294
|
+
console.log(chalk.dim(' Perception: ') + chalk.cyan(perceptionUrl));
|
|
295
|
+
console.log(chalk.dim(' Planning + Control injection: ') + chalk.yellow('not available in self-hosted OSS'));
|
|
296
|
+
console.log();
|
|
297
|
+
console.log(chalk.dim(' Context retrieval: use ') + chalk.cyan('robrain inject') + chalk.dim(' to get context for manual paste'));
|
|
298
|
+
console.log();
|
|
299
|
+
console.log(chalk.bold(' Next: ') + chalk.dim('run ') + chalk.cyan('robrain init-project') + chalk.dim(' in your repo root'));
|
|
300
|
+
console.log();
|
|
301
|
+
console.log(chalk.dim(' Want automatic injection without pasting?'));
|
|
302
|
+
console.log(chalk.dim(' → ') + chalk.cyan('roryplans.ai') + chalk.dim(' — Control MCP handles it automatically'));
|
|
303
|
+
console.log();
|
|
304
|
+
}
|
|
305
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/commands/review.ts"],"names":[],"mappings":"AAmBA,UAAU,aAAa;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,GAAG,CAAC,EAAM,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,CAAC,EAAI,MAAM,CAAA;CACjB;AAkBD,wBAAsB,aAAa,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA6JtE"}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
// src/commands/review.ts
|
|
2
|
+
// ─────────────────────────────────────────────────────────────
|
|
3
|
+
// robrain review [--session last|SESSION_ID] [--all] [--limit N]
|
|
4
|
+
//
|
|
5
|
+
// Shows decisions captured in recent sessions so developers can
|
|
6
|
+
// review what the system stored, edit wrong decisions, and delete
|
|
7
|
+
// hallucinated ones before they pollute future injections.
|
|
8
|
+
//
|
|
9
|
+
// This is the trust layer — developers should never have to rely
|
|
10
|
+
// on memories they've never seen.
|
|
11
|
+
// ─────────────────────────────────────────────────────────────
|
|
12
|
+
import chalk from 'chalk';
|
|
13
|
+
import ora from 'ora';
|
|
14
|
+
import prompts from 'prompts';
|
|
15
|
+
import { readConfig, isAuthenticated } from '../lib/config.js';
|
|
16
|
+
import { gatherProjectInfo } from '../lib/project.js';
|
|
17
|
+
import { cwd } from 'process';
|
|
18
|
+
export async function reviewCommand(opts) {
|
|
19
|
+
console.log();
|
|
20
|
+
if (!isAuthenticated()) {
|
|
21
|
+
console.log(chalk.red(' ✗ Not authenticated. Run: robrain install'));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
const config = readConfig();
|
|
25
|
+
const info = gatherProjectInfo(cwd());
|
|
26
|
+
const percUrl = config.perceptionUrl;
|
|
27
|
+
const percKey = config.perceptionKey ?? '';
|
|
28
|
+
if (!percUrl) {
|
|
29
|
+
console.log(chalk.red(' ✗ Perception URL not configured. Run: robrain install'));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
// ── Fetch decisions from Perception ───────────────────────
|
|
33
|
+
const spinner = ora({ text: 'Fetching stored decisions...', color: 'green' }).start();
|
|
34
|
+
const limit = opts.limit ?? 20;
|
|
35
|
+
const params = new URLSearchParams({
|
|
36
|
+
project_id: info.id,
|
|
37
|
+
limit: String(limit),
|
|
38
|
+
...(opts.session && opts.session !== 'last' && { session_id: opts.session }),
|
|
39
|
+
...(opts.all ? { all: 'true' } : { recent: 'true' }),
|
|
40
|
+
...(opts.history ? { history: 'true' } : {}),
|
|
41
|
+
});
|
|
42
|
+
let decisions = [];
|
|
43
|
+
try {
|
|
44
|
+
const res = await fetch(`${percUrl}/decisions?${params}`, {
|
|
45
|
+
headers: percKey ? { 'Authorization': `Bearer ${percKey}` } : {},
|
|
46
|
+
});
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
spinner.fail(`Could not fetch decisions (${res.status})`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
const data = await res.json();
|
|
52
|
+
decisions = data.decisions ?? [];
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
spinner.fail('Could not reach Perception API');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
spinner.stop();
|
|
59
|
+
if (decisions.length === 0) {
|
|
60
|
+
console.log(chalk.dim(` No decisions stored yet for ${chalk.bold(info.name)}.`));
|
|
61
|
+
console.log(chalk.dim(' Run a Claude Code session to start capturing memory.\n'));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// ── Display header ─────────────────────────────────────────
|
|
65
|
+
console.log(chalk.bold(` Memory review — ${info.name}`));
|
|
66
|
+
console.log(chalk.dim(` ${decisions.length} decision${decisions.length === 1 ? '' : 's'} · Project ID: ${info.id}`));
|
|
67
|
+
if (opts.history) {
|
|
68
|
+
console.log(chalk.dim(' Showing full history including superseded decisions\n'));
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.log(chalk.dim(' Showing active decisions only · use --history to see full lifecycle\n'));
|
|
72
|
+
}
|
|
73
|
+
// ── Display each decision ──────────────────────────────────
|
|
74
|
+
for (let i = 0; i < decisions.length; i++) {
|
|
75
|
+
const d = decisions[i];
|
|
76
|
+
const index = chalk.dim(`[${i + 1}/${decisions.length}]`);
|
|
77
|
+
const date = new Date(d.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
|
78
|
+
const conf = d.confidence >= 0.9 ? chalk.green('high')
|
|
79
|
+
: d.confidence >= 0.6 ? chalk.yellow('medium')
|
|
80
|
+
: chalk.red('low');
|
|
81
|
+
// Lifecycle state
|
|
82
|
+
const isSuperseded = !!d.invalidated_at;
|
|
83
|
+
const statusLabel = isSuperseded
|
|
84
|
+
? chalk.dim(' ↩ SUPERSEDED')
|
|
85
|
+
: d.conflict_flag
|
|
86
|
+
? chalk.yellow(' ⚠ CONFLICT — needs resolution')
|
|
87
|
+
: null;
|
|
88
|
+
if (statusLabel)
|
|
89
|
+
console.log(statusLabel);
|
|
90
|
+
// Decision text — dim if superseded
|
|
91
|
+
const decisionText = isSuperseded
|
|
92
|
+
? chalk.dim(` ${index} ${d.decision}`)
|
|
93
|
+
: ` ${index} ${chalk.bold(d.decision)}`;
|
|
94
|
+
console.log(decisionText);
|
|
95
|
+
if (d.rationale) {
|
|
96
|
+
console.log(chalk.dim(` because: `) + (isSuperseded ? chalk.dim(d.rationale) : d.rationale));
|
|
97
|
+
}
|
|
98
|
+
if (d.rejected.length > 0) {
|
|
99
|
+
const vetoes = d.rejected.map(r => `${chalk.dim(r.option)} ${chalk.dim(`(${r.reason})`)}`).join(', ');
|
|
100
|
+
console.log(chalk.dim(` rejected: `) + vetoes);
|
|
101
|
+
}
|
|
102
|
+
if (d.files_affected.length > 0) {
|
|
103
|
+
console.log(chalk.dim(` files: `) + d.files_affected.slice(0, 3).join(', ') + (d.files_affected.length > 3 ? ` +${d.files_affected.length - 3} more` : ''));
|
|
104
|
+
}
|
|
105
|
+
// Lifecycle info
|
|
106
|
+
if (d.supersedes_id) {
|
|
107
|
+
console.log(chalk.dim(` ↩ replaces: `) + chalk.dim(`earlier decision (${d.supersedes_id.slice(0, 8)}...)`));
|
|
108
|
+
}
|
|
109
|
+
if (isSuperseded && d.invalidated_at) {
|
|
110
|
+
const supersededDate = new Date(d.invalidated_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
|
111
|
+
console.log(chalk.dim(` ↪ superseded: ${supersededDate}`));
|
|
112
|
+
}
|
|
113
|
+
console.log(chalk.dim(` ${date} · confidence: `) + conf + chalk.dim(` · scope: ${d.scope}`));
|
|
114
|
+
// Skip per-decision prompt for superseded decisions in history mode
|
|
115
|
+
if (isSuperseded) {
|
|
116
|
+
console.log();
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
// ── Per-decision inline action ─────────────────────────
|
|
120
|
+
const { action } = await prompts({
|
|
121
|
+
type: 'select',
|
|
122
|
+
name: 'action',
|
|
123
|
+
message: 'Action:',
|
|
124
|
+
choices: [
|
|
125
|
+
{ title: '✔ Accept — looks correct', value: 'accept' },
|
|
126
|
+
{ title: '✏️ Edit — fix decision text', value: 'edit' },
|
|
127
|
+
{ title: '❌ Reject — delete this', value: 'reject' },
|
|
128
|
+
...(d.conflict_flag
|
|
129
|
+
? [{ title: '⚠️ Resolve conflict', value: 'resolve' }]
|
|
130
|
+
: []),
|
|
131
|
+
{ title: '⏭ Skip remaining', value: 'skip_all' },
|
|
132
|
+
],
|
|
133
|
+
});
|
|
134
|
+
console.log();
|
|
135
|
+
if (!action || action === 'skip_all')
|
|
136
|
+
break;
|
|
137
|
+
if (action === 'edit') {
|
|
138
|
+
await editDecisionInline(d, percUrl, percKey, info.id);
|
|
139
|
+
}
|
|
140
|
+
if (action === 'reject') {
|
|
141
|
+
await rejectDecisionInline(d, percUrl, percKey);
|
|
142
|
+
}
|
|
143
|
+
if (action === 'resolve') {
|
|
144
|
+
await resolveConflictInline(d, percUrl, percKey, info.id);
|
|
145
|
+
}
|
|
146
|
+
// 'accept' — no action needed, move to next
|
|
147
|
+
}
|
|
148
|
+
console.log(chalk.green(' ✓ Review complete\n'));
|
|
149
|
+
}
|
|
150
|
+
// ── Inline: edit a single decision ────────────────────────────
|
|
151
|
+
function normalizedRationale(value) {
|
|
152
|
+
if (value == null)
|
|
153
|
+
return null;
|
|
154
|
+
const t = value.trim();
|
|
155
|
+
return t === '' ? null : t;
|
|
156
|
+
}
|
|
157
|
+
async function editDecisionInline(d, percUrl, percKey, projectId) {
|
|
158
|
+
const { newDecision } = await prompts({
|
|
159
|
+
type: 'text',
|
|
160
|
+
name: 'newDecision',
|
|
161
|
+
message: 'Corrected decision:',
|
|
162
|
+
initial: d.decision,
|
|
163
|
+
});
|
|
164
|
+
if (newDecision == null) {
|
|
165
|
+
console.log(chalk.dim(' Cancelled.'));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const trimmedDecision = newDecision.trim();
|
|
169
|
+
if (trimmedDecision === '') {
|
|
170
|
+
console.log(chalk.dim(' Cancelled.'));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const priorRationale = normalizedRationale(d.rationale);
|
|
174
|
+
const { newRationale } = await prompts({
|
|
175
|
+
type: 'text',
|
|
176
|
+
name: 'newRationale',
|
|
177
|
+
message: 'Corrected rationale (blank removes; unchanged text keeps current):',
|
|
178
|
+
initial: d.rationale ?? '',
|
|
179
|
+
});
|
|
180
|
+
// Cancelled rationale step — keep prior rationale; decision text may still have changed.
|
|
181
|
+
let correctedRationale;
|
|
182
|
+
if (newRationale == null) {
|
|
183
|
+
correctedRationale = priorRationale;
|
|
184
|
+
}
|
|
185
|
+
else if (newRationale.trim() === '') {
|
|
186
|
+
correctedRationale = null;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
correctedRationale = newRationale.trim();
|
|
190
|
+
}
|
|
191
|
+
const decisionChanged = trimmedDecision !== d.decision.trim();
|
|
192
|
+
const rationaleChanged = correctedRationale !== priorRationale;
|
|
193
|
+
if (!decisionChanged && !rationaleChanged) {
|
|
194
|
+
console.log(chalk.dim(' No change made.'));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const spinner = ora('Saving correction...').start();
|
|
198
|
+
try {
|
|
199
|
+
const res = await fetch(`${percUrl}/corrections`, {
|
|
200
|
+
method: 'POST',
|
|
201
|
+
headers: {
|
|
202
|
+
'Content-Type': 'application/json',
|
|
203
|
+
...(percKey ? { 'Authorization': `Bearer ${percKey}` } : {}),
|
|
204
|
+
'X-Project-Id': projectId,
|
|
205
|
+
'X-Session-Id': 'robrain-review-cli',
|
|
206
|
+
},
|
|
207
|
+
body: JSON.stringify({
|
|
208
|
+
decision_id: d.id,
|
|
209
|
+
corrected_decision: trimmedDecision,
|
|
210
|
+
corrected_rationale: correctedRationale,
|
|
211
|
+
invalidate: true,
|
|
212
|
+
source: 'user_correction',
|
|
213
|
+
}),
|
|
214
|
+
});
|
|
215
|
+
res.ok
|
|
216
|
+
? spinner.succeed(chalk.green('✔ Decision updated'))
|
|
217
|
+
: spinner.fail('Could not save correction');
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
spinner.fail('Could not reach Perception API');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// ── Inline: reject (invalidate) a single decision ─────────────
|
|
224
|
+
async function rejectDecisionInline(d, percUrl, percKey) {
|
|
225
|
+
const { confirm } = await prompts({
|
|
226
|
+
type: 'confirm',
|
|
227
|
+
name: 'confirm',
|
|
228
|
+
message: chalk.yellow(`Reject "${d.decision.slice(0, 60)}"? It won't be injected in future sessions.`),
|
|
229
|
+
initial: false,
|
|
230
|
+
});
|
|
231
|
+
if (!confirm) {
|
|
232
|
+
console.log(chalk.dim(' Skipped.'));
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const spinner = ora('Rejecting...').start();
|
|
236
|
+
try {
|
|
237
|
+
const res = await fetch(`${percUrl}/corrections`, {
|
|
238
|
+
method: 'POST',
|
|
239
|
+
headers: {
|
|
240
|
+
'Content-Type': 'application/json',
|
|
241
|
+
...(percKey ? { 'Authorization': `Bearer ${percKey}` } : {}),
|
|
242
|
+
},
|
|
243
|
+
body: JSON.stringify({
|
|
244
|
+
decision_id: d.id,
|
|
245
|
+
invalidate: true,
|
|
246
|
+
source: 'user_correction',
|
|
247
|
+
}),
|
|
248
|
+
});
|
|
249
|
+
res.ok
|
|
250
|
+
? spinner.succeed(chalk.green('❌ Decision rejected — won\'t appear in future sessions'))
|
|
251
|
+
: spinner.fail('Could not reject decision');
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
spinner.fail('Could not reach Perception API');
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// ── Inline: resolve a conflict ─────────────────────────────────
|
|
258
|
+
async function resolveConflictInline(d, percUrl, percKey, projectId) {
|
|
259
|
+
const { resolution } = await prompts({
|
|
260
|
+
type: 'select',
|
|
261
|
+
name: 'resolution',
|
|
262
|
+
message: 'Resolve conflict:',
|
|
263
|
+
choices: [
|
|
264
|
+
{ title: '✔ Keep this — it\'s correct', value: 'keep' },
|
|
265
|
+
{ title: '❌ Reject this — older decision stands', value: 'invalidate' },
|
|
266
|
+
],
|
|
267
|
+
});
|
|
268
|
+
if (!resolution)
|
|
269
|
+
return;
|
|
270
|
+
const spinner = ora('Resolving...').start();
|
|
271
|
+
try {
|
|
272
|
+
const res = await fetch(`${percUrl}/corrections`, {
|
|
273
|
+
method: 'POST',
|
|
274
|
+
headers: {
|
|
275
|
+
'Content-Type': 'application/json',
|
|
276
|
+
...(percKey ? { 'Authorization': `Bearer ${percKey}` } : {}),
|
|
277
|
+
'X-Project-Id': projectId,
|
|
278
|
+
'X-Session-Id': 'robrain-review-cli',
|
|
279
|
+
},
|
|
280
|
+
body: JSON.stringify({
|
|
281
|
+
decision_id: d.id,
|
|
282
|
+
invalidate: resolution === 'invalidate',
|
|
283
|
+
source: 'user_correction',
|
|
284
|
+
}),
|
|
285
|
+
});
|
|
286
|
+
if (res.ok) {
|
|
287
|
+
resolution === 'keep'
|
|
288
|
+
? spinner.succeed(chalk.green('✔ Conflict resolved — decision kept'))
|
|
289
|
+
: spinner.succeed(chalk.green('❌ Conflict resolved — decision rejected'));
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
spinner.fail('Could not resolve conflict');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
spinner.fail('Could not reach Perception API');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=review.js.map
|