@neurcode-ai/cli 0.9.6 → 0.9.8

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 (45) hide show
  1. package/dist/commands/apply.d.ts.map +1 -1
  2. package/dist/commands/apply.js +2 -1
  3. package/dist/commands/apply.js.map +1 -1
  4. package/dist/commands/brain.d.ts +11 -0
  5. package/dist/commands/brain.d.ts.map +1 -0
  6. package/dist/commands/brain.js +669 -0
  7. package/dist/commands/brain.js.map +1 -0
  8. package/dist/commands/map.d.ts.map +1 -1
  9. package/dist/commands/map.js +6 -3
  10. package/dist/commands/map.js.map +1 -1
  11. package/dist/commands/plan.d.ts.map +1 -1
  12. package/dist/commands/plan.js +7 -2
  13. package/dist/commands/plan.js.map +1 -1
  14. package/dist/commands/prompt.d.ts.map +1 -1
  15. package/dist/commands/prompt.js +2 -1
  16. package/dist/commands/prompt.js.map +1 -1
  17. package/dist/commands/revert.d.ts.map +1 -1
  18. package/dist/commands/revert.js +5 -2
  19. package/dist/commands/revert.js.map +1 -1
  20. package/dist/commands/verify.d.ts.map +1 -1
  21. package/dist/commands/verify.js +4 -2
  22. package/dist/commands/verify.js.map +1 -1
  23. package/dist/commands/watch.d.ts.map +1 -1
  24. package/dist/commands/watch.js +2 -1
  25. package/dist/commands/watch.js.map +1 -1
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +5 -3
  28. package/dist/config.js.map +1 -1
  29. package/dist/index.js +2 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/utils/plan-cache.d.ts +6 -0
  32. package/dist/utils/plan-cache.d.ts.map +1 -1
  33. package/dist/utils/plan-cache.js +54 -1
  34. package/dist/utils/plan-cache.js.map +1 -1
  35. package/dist/utils/project-root.d.ts +18 -0
  36. package/dist/utils/project-root.d.ts.map +1 -0
  37. package/dist/utils/project-root.js +44 -0
  38. package/dist/utils/project-root.js.map +1 -0
  39. package/dist/utils/state.d.ts.map +1 -1
  40. package/dist/utils/state.js +6 -5
  41. package/dist/utils/state.js.map +1 -1
  42. package/dist/utils/tier.d.ts.map +1 -1
  43. package/dist/utils/tier.js +7 -3
  44. package/dist/utils/tier.js.map +1 -1
  45. package/package.json +1 -1
@@ -0,0 +1,669 @@
1
+ "use strict";
2
+ /**
3
+ * Neurcode Brain - Local Context, Memory, and Cache Management
4
+ *
5
+ * Goals:
6
+ * - Enterprise-grade observability: users can see what "Brain" knows and why cache hits/misses happen
7
+ * - Multi-tenant safety: everything is scoped by orgId + projectId
8
+ * - Robust ops: export + clear for compliance and troubleshooting
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.brainCommand = brainCommand;
45
+ const fs_1 = require("fs");
46
+ const path_1 = require("path");
47
+ const config_1 = require("../config");
48
+ const project_root_1 = require("../utils/project-root");
49
+ const state_1 = require("../utils/state");
50
+ const neurcode_context_1 = require("../utils/neurcode-context");
51
+ const plan_cache_1 = require("../utils/plan-cache");
52
+ const messages_1 = require("../utils/messages");
53
+ // Import chalk with fallback
54
+ let chalk;
55
+ try {
56
+ chalk = require('chalk');
57
+ }
58
+ catch {
59
+ chalk = {
60
+ green: (str) => str,
61
+ yellow: (str) => str,
62
+ red: (str) => str,
63
+ bold: (str) => str,
64
+ dim: (str) => str,
65
+ cyan: (str) => str,
66
+ white: (str) => str,
67
+ };
68
+ }
69
+ function safeFileSize(path) {
70
+ try {
71
+ if (!(0, fs_1.existsSync)(path))
72
+ return null;
73
+ return (0, fs_1.statSync)(path).size;
74
+ }
75
+ catch {
76
+ return null;
77
+ }
78
+ }
79
+ function countOccurrences(haystack, needle) {
80
+ if (!haystack || !needle)
81
+ return 0;
82
+ let count = 0;
83
+ let idx = 0;
84
+ while (true) {
85
+ const next = haystack.indexOf(needle, idx);
86
+ if (next === -1)
87
+ break;
88
+ count++;
89
+ idx = next + needle.length;
90
+ }
91
+ return count;
92
+ }
93
+ function scanFiles(dir, baseDir, maxFiles = 600) {
94
+ // Light-weight filesystem scan used only as a fallback when git isn't available.
95
+ // Keep it deterministic-ish but fast: only capture relative paths.
96
+ const { readdirSync, statSync } = require('fs');
97
+ const { join, relative } = require('path');
98
+ const files = [];
99
+ const ignoreDirs = new Set(['node_modules', '.git', '.next', 'dist', 'build', '.turbo', '.cache', 'coverage']);
100
+ const ignoreExts = new Set(['map', 'log', 'lock', 'png', 'jpg', 'jpeg', 'gif', 'ico', 'svg', 'woff', 'woff2', 'ttf', 'eot']);
101
+ function walk(current) {
102
+ if (files.length >= maxFiles)
103
+ return;
104
+ let entries = [];
105
+ try {
106
+ entries = readdirSync(current);
107
+ }
108
+ catch {
109
+ return;
110
+ }
111
+ for (const entry of entries) {
112
+ if (files.length >= maxFiles)
113
+ break;
114
+ if (entry.startsWith('.')) {
115
+ // keep common dotfiles, but skip big hidden dirs
116
+ if (ignoreDirs.has(entry))
117
+ continue;
118
+ }
119
+ const full = join(current, entry);
120
+ let st;
121
+ try {
122
+ st = statSync(full);
123
+ }
124
+ catch {
125
+ continue;
126
+ }
127
+ if (st.isDirectory()) {
128
+ if (ignoreDirs.has(entry))
129
+ continue;
130
+ walk(full);
131
+ continue;
132
+ }
133
+ if (!st.isFile())
134
+ continue;
135
+ const ext = entry.split('.').pop()?.toLowerCase();
136
+ if (ext && ignoreExts.has(ext))
137
+ continue;
138
+ files.push(relative(baseDir, full));
139
+ }
140
+ }
141
+ walk(dir);
142
+ return files.slice(0, maxFiles);
143
+ }
144
+ function getBrainScope(projectIdOverride) {
145
+ const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
146
+ const orgId = (0, state_1.getOrgId)();
147
+ const orgName = (0, state_1.getOrgName)();
148
+ const stateProjectId = (0, state_1.getProjectId)();
149
+ const configProjectId = (0, config_1.loadConfig)().projectId || null;
150
+ const projectId = projectIdOverride || stateProjectId || configProjectId;
151
+ return { cwd, orgId, orgName, projectId };
152
+ }
153
+ function formatBytes(bytes) {
154
+ if (!Number.isFinite(bytes) || bytes < 0)
155
+ return 'unknown';
156
+ if (bytes < 1024)
157
+ return `${bytes} B`;
158
+ if (bytes < 1024 * 1024)
159
+ return `${(bytes / 1024).toFixed(1)} KB`;
160
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
161
+ }
162
+ function renderBrainExportMarkdown(input) {
163
+ const lines = [];
164
+ lines.push('# Neurcode Brain Export');
165
+ lines.push('');
166
+ lines.push(`Generated: ${input.generatedAt}`);
167
+ lines.push(`Repo Root: ${input.cwd}`);
168
+ lines.push('');
169
+ lines.push('## Scope');
170
+ lines.push(`- Organization: ${input.scope.orgName || input.scope.orgId || '(not set)'}`);
171
+ if (input.scope.orgId)
172
+ lines.push(`- Org ID: ${input.scope.orgId}`);
173
+ lines.push(`- Project ID: ${input.scope.projectId || '(not set)'}`);
174
+ lines.push('');
175
+ if (input.cacheStats) {
176
+ lines.push('## Plan Cache');
177
+ lines.push(`- Entries (repo): ${input.cacheStats.totalEntries}`);
178
+ if (typeof input.cacheStats.scopedEntries === 'number') {
179
+ lines.push(`- Entries (scope): ${input.cacheStats.scopedEntries}`);
180
+ }
181
+ lines.push('');
182
+ }
183
+ lines.push('## Static Context Sources');
184
+ if (input.staticContext.sources.length === 0) {
185
+ lines.push('- (none)');
186
+ }
187
+ else {
188
+ input.staticContext.sources.forEach((s) => {
189
+ lines.push(`- ${s.label}: ${s.path} (${formatBytes(s.bytes)}${s.truncated ? ', truncated' : ''})`);
190
+ });
191
+ }
192
+ lines.push('');
193
+ lines.push('## Static Context (Combined)');
194
+ if (!input.staticContext.text.trim()) {
195
+ lines.push('_No context files found._');
196
+ }
197
+ else {
198
+ lines.push('```text');
199
+ lines.push(input.staticContext.text.trim());
200
+ lines.push('```');
201
+ }
202
+ lines.push('');
203
+ lines.push('## Architecture Memory (.neurcode/architecture.json)');
204
+ if (!input.architectureJson) {
205
+ lines.push('_Not found._');
206
+ }
207
+ else {
208
+ lines.push('```json');
209
+ lines.push(JSON.stringify(input.architectureJson, null, 2));
210
+ lines.push('```');
211
+ }
212
+ lines.push('');
213
+ lines.push('## Org/Project Memory (Tail)');
214
+ if (!input.memoryTail?.trim()) {
215
+ lines.push('_No memory found for this scope._');
216
+ }
217
+ else {
218
+ lines.push('```text');
219
+ lines.push(input.memoryTail.trim());
220
+ lines.push('```');
221
+ }
222
+ lines.push('');
223
+ return lines.join('\n');
224
+ }
225
+ function brainCommand(program) {
226
+ const brain = program.command('brain').description('Manage Neurcode Brain (local cache, context, and memory)');
227
+ brain
228
+ .command('status')
229
+ .description('Show local Brain status (scope, cache, memory, context sources)')
230
+ .option('--project-id <id>', 'Project ID override')
231
+ .option('--json', 'Output as JSON')
232
+ .action(async (options) => {
233
+ const scope = getBrainScope(options.projectId);
234
+ const allCached = (0, plan_cache_1.listCachedPlans)(scope.cwd);
235
+ const scopedCached = scope.orgId && scope.projectId
236
+ ? allCached.filter((e) => e.input.orgId === scope.orgId && e.input.projectId === scope.projectId)
237
+ : [];
238
+ const staticContext = (0, neurcode_context_1.loadStaticNeurcodeContext)(scope.cwd, scope.orgId && scope.projectId ? { orgId: scope.orgId, projectId: scope.projectId } : undefined);
239
+ const memoryPath = scope.orgId && scope.projectId ? (0, neurcode_context_1.getOrgProjectMemoryPath)(scope.cwd, scope.orgId, scope.projectId) : null;
240
+ const memoryExists = memoryPath ? (0, fs_1.existsSync)(memoryPath) : false;
241
+ const memoryBytes = memoryPath ? safeFileSize(memoryPath) : null;
242
+ let memoryEntries = null;
243
+ if (memoryExists && memoryPath) {
244
+ try {
245
+ const raw = (0, fs_1.readFileSync)(memoryPath, 'utf-8');
246
+ memoryEntries = countOccurrences(raw, '<!-- neurcode-memory-entry -->');
247
+ }
248
+ catch {
249
+ memoryEntries = null;
250
+ }
251
+ }
252
+ const architecturePath = (0, path_1.join)(scope.cwd, '.neurcode', 'architecture.json');
253
+ const architectureBytes = safeFileSize(architecturePath);
254
+ const payload = {
255
+ repoRoot: scope.cwd,
256
+ scope: {
257
+ orgId: scope.orgId,
258
+ orgName: scope.orgName,
259
+ projectId: scope.projectId,
260
+ },
261
+ planCache: {
262
+ totalEntries: allCached.length,
263
+ scopedEntries: scopedCached.length,
264
+ },
265
+ memory: {
266
+ path: memoryPath,
267
+ exists: memoryExists,
268
+ bytes: memoryBytes,
269
+ entries: memoryEntries,
270
+ },
271
+ staticContext: {
272
+ hash: staticContext.hash,
273
+ sources: staticContext.sources,
274
+ bytes: Buffer.byteLength(staticContext.text || '', 'utf-8'),
275
+ },
276
+ architecture: {
277
+ path: architecturePath,
278
+ exists: (0, fs_1.existsSync)(architecturePath),
279
+ bytes: architectureBytes,
280
+ },
281
+ };
282
+ if (options.json) {
283
+ console.log(JSON.stringify(payload, null, 2));
284
+ return;
285
+ }
286
+ await (0, messages_1.printSuccessBanner)('Neurcode Brain Status');
287
+ (0, messages_1.printSection)('Scope', '🧠');
288
+ console.log(chalk.dim(`Repo Root: ${scope.cwd}`));
289
+ console.log(chalk.dim(`Org: ${scope.orgName || scope.orgId || '(not set)'}`));
290
+ console.log(chalk.dim(`Project: ${scope.projectId || '(not set)'}`));
291
+ if (!scope.orgId || !scope.projectId) {
292
+ (0, messages_1.printWarning)('Brain scope is not fully configured', 'Run: neurcode init (to link this folder to an organization + project)');
293
+ }
294
+ (0, messages_1.printSection)('Plan Cache', '⚡');
295
+ console.log(chalk.dim(`Entries (repo): ${allCached.length}`));
296
+ if (scope.orgId && scope.projectId) {
297
+ console.log(chalk.dim(`Entries (scope): ${scopedCached.length}`));
298
+ }
299
+ (0, messages_1.printSection)('Memory', '📝');
300
+ if (!memoryPath) {
301
+ console.log(chalk.dim('No org/project scope detected, so no memory file is active.'));
302
+ }
303
+ else if (!memoryExists) {
304
+ console.log(chalk.dim(`No memory found yet for this scope.`));
305
+ console.log(chalk.dim(`Path: ${memoryPath}`));
306
+ }
307
+ else {
308
+ console.log(chalk.dim(`Path: ${memoryPath}`));
309
+ if (memoryBytes != null)
310
+ console.log(chalk.dim(`Size: ${formatBytes(memoryBytes)}`));
311
+ if (memoryEntries != null)
312
+ console.log(chalk.dim(`Entries: ${memoryEntries}`));
313
+ }
314
+ (0, messages_1.printSection)('Static Context', '📎');
315
+ if (staticContext.sources.length === 0) {
316
+ console.log(chalk.dim('No context files found. Optional files: neurcode.md, .neurcode/context.md'));
317
+ }
318
+ else {
319
+ staticContext.sources.forEach((s) => {
320
+ console.log(chalk.dim(`- ${s.label}: ${s.path} (${formatBytes(s.bytes)}${s.truncated ? ', truncated' : ''})`));
321
+ });
322
+ }
323
+ (0, messages_1.printSection)('Knowledge', '🏗️');
324
+ if ((0, fs_1.existsSync)(architecturePath)) {
325
+ console.log(chalk.dim(`Architecture memory: ${architecturePath} (${formatBytes(architectureBytes || 0)})`));
326
+ }
327
+ else {
328
+ console.log(chalk.dim('Architecture memory not found yet (it will be created automatically on plan runs).'));
329
+ }
330
+ });
331
+ brain
332
+ .command('doctor')
333
+ .description('Diagnose Brain scope and explain plan cache hits/misses for an intent')
334
+ .argument('[intent...]', 'Intent to diagnose (if omitted, only prints scope checks)')
335
+ .option('--project-id <id>', 'Project ID override')
336
+ .option('--ticket <id>', 'Ticket reference (e.g., PROJ-123)')
337
+ .option('--issue <id>', 'GitHub issue number (affects cache key)')
338
+ .option('--pr <id>', 'GitHub PR number (affects cache key)')
339
+ .option('--json', 'Output as JSON')
340
+ .action(async (intentParts, options) => {
341
+ const intent = Array.isArray(intentParts) ? intentParts.join(' ').trim() : String(intentParts || '').trim();
342
+ const scope = getBrainScope(options.projectId);
343
+ const apiUrl = ((0, config_1.loadConfig)().apiUrl || config_1.DEFAULT_API_URL).replace(/\/$/, '');
344
+ const ticketRef = options.issue
345
+ ? `github_issue:${options.issue}`
346
+ : options.pr
347
+ ? `github_pr:${options.pr}`
348
+ : options.ticket
349
+ ? `ticket:${options.ticket}`
350
+ : undefined;
351
+ const problems = [];
352
+ if (!scope.orgId)
353
+ problems.push('Missing orgId (run neurcode init)');
354
+ if (!scope.projectId)
355
+ problems.push('Missing projectId (run neurcode init)');
356
+ const staticContext = (0, neurcode_context_1.loadStaticNeurcodeContext)(scope.cwd, scope.orgId && scope.projectId ? { orgId: scope.orgId, projectId: scope.projectId } : undefined);
357
+ const gitFingerprint = (0, plan_cache_1.getGitRepoFingerprint)(scope.cwd);
358
+ const fileTree = !gitFingerprint ? scanFiles(scope.cwd, scope.cwd, 400) : [];
359
+ const fsFingerprint = !gitFingerprint ? (0, plan_cache_1.getFilesystemFingerprintFromTree)(fileTree) : null;
360
+ const repoFingerprint = gitFingerprint || fsFingerprint;
361
+ const normalized = intent ? (0, plan_cache_1.normalizeIntent)(intent) : '';
362
+ const canComputeKey = Boolean(intent && scope.orgId && scope.projectId && repoFingerprint);
363
+ const keyInput = canComputeKey
364
+ ? {
365
+ schemaVersion: 1,
366
+ apiUrl,
367
+ orgId: scope.orgId,
368
+ projectId: scope.projectId,
369
+ intent: normalized,
370
+ ticketRef,
371
+ contextHash: staticContext.hash,
372
+ repo: repoFingerprint,
373
+ }
374
+ : null;
375
+ const key = keyInput ? (0, plan_cache_1.computePlanCacheKey)(keyInput) : null;
376
+ const cached = key ? (0, plan_cache_1.peekCachedPlan)(scope.cwd, key) : null;
377
+ const similar = scope.orgId && scope.projectId && normalized
378
+ ? (0, plan_cache_1.findSimilarCachedPlans)(scope.cwd, { orgId: scope.orgId, projectId: scope.projectId }, normalized, 3)
379
+ : [];
380
+ const payload = {
381
+ repoRoot: scope.cwd,
382
+ scope: { orgId: scope.orgId, orgName: scope.orgName, projectId: scope.projectId },
383
+ problems,
384
+ cacheKey: key,
385
+ cacheHit: Boolean(cached),
386
+ keyInput,
387
+ repoFingerprint,
388
+ staticContext: { hash: staticContext.hash, sources: staticContext.sources },
389
+ similar: similar.map((s) => ({
390
+ createdAt: s.createdAt,
391
+ intent: s.input.intent,
392
+ planId: s.response.planId,
393
+ summary: s.response.plan.summary,
394
+ })),
395
+ };
396
+ if (options.json) {
397
+ console.log(JSON.stringify(payload, null, 2));
398
+ return;
399
+ }
400
+ await (0, messages_1.printSuccessBanner)('Neurcode Brain Doctor');
401
+ (0, messages_1.printSection)('Scope', '🧭');
402
+ console.log(chalk.dim(`Repo Root: ${scope.cwd}`));
403
+ console.log(chalk.dim(`Org: ${scope.orgName || scope.orgId || '(not set)'}`));
404
+ console.log(chalk.dim(`Project: ${scope.projectId || '(not set)'}`));
405
+ if (problems.length > 0) {
406
+ (0, messages_1.printWarning)('Scope issues detected', problems.join('\n • '));
407
+ }
408
+ (0, messages_1.printSection)('Fingerprint', '🧬');
409
+ if (!repoFingerprint) {
410
+ (0, messages_1.printWarning)('No repo fingerprint available', 'Not a git repo and filesystem scan failed.');
411
+ }
412
+ else if (repoFingerprint.kind === 'git') {
413
+ console.log(chalk.dim(`Kind: git`));
414
+ console.log(chalk.dim(`HEAD: ${repoFingerprint.headSha.substring(0, 12)}...`));
415
+ console.log(chalk.dim(`Tree: ${repoFingerprint.headTreeSha.substring(0, 12)}...`));
416
+ console.log(chalk.dim(`StatusHash: ${repoFingerprint.statusHash.substring(0, 12)}...`));
417
+ }
418
+ else {
419
+ console.log(chalk.dim(`Kind: filesystem`));
420
+ console.log(chalk.dim(`TreeHash: ${repoFingerprint.fileTreeHash.substring(0, 12)}...`));
421
+ console.log(chalk.dim(`Files seen: ${fileTree.length}`));
422
+ }
423
+ (0, messages_1.printSection)('Context', '📎');
424
+ if (staticContext.sources.length === 0) {
425
+ console.log(chalk.dim('No context files found.'));
426
+ }
427
+ else {
428
+ staticContext.sources.forEach((s) => {
429
+ console.log(chalk.dim(`- ${s.label}: ${s.path}${s.truncated ? ' (truncated)' : ''}`));
430
+ });
431
+ }
432
+ console.log(chalk.dim(`ContextHash: ${staticContext.hash.substring(0, 12)}...`));
433
+ (0, messages_1.printSection)('Plan Cache', '⚡');
434
+ if (!intent) {
435
+ (0, messages_1.printInfo)('No intent provided', 'Run: neurcode brain doctor "<intent>" to explain cache hit/miss');
436
+ return;
437
+ }
438
+ if (!scope.orgId || !scope.projectId) {
439
+ (0, messages_1.printError)('Cannot compute plan cache key', 'Missing orgId/projectId. Run: neurcode init');
440
+ return;
441
+ }
442
+ if (!repoFingerprint) {
443
+ (0, messages_1.printError)('Cannot compute plan cache key', 'No repo fingerprint available.');
444
+ return;
445
+ }
446
+ console.log(chalk.dim(`Intent (normalized): ${normalized}`));
447
+ if (ticketRef)
448
+ console.log(chalk.dim(`TicketRef: ${ticketRef}`));
449
+ console.log(chalk.dim(`CacheKey: ${key?.substring(0, 16)}...`));
450
+ if (cached) {
451
+ (0, messages_1.printSuccess)('Cache hit', `Created: ${new Date(cached.createdAt).toLocaleString()} | Uses: ${cached.useCount || 1}`);
452
+ }
453
+ else {
454
+ (0, messages_1.printWarning)('Cache miss', 'This intent/repo snapshot has no cached plan yet.');
455
+ }
456
+ if (similar.length > 0) {
457
+ console.log('');
458
+ console.log(chalk.bold.white('Similar cached plans (same org/project):'));
459
+ similar.forEach((s, idx) => {
460
+ const summary = (s.response.plan.summary || '').trim().slice(0, 140);
461
+ console.log(chalk.dim(` ${idx + 1}. intent="${s.input.intent}"`));
462
+ if (summary)
463
+ console.log(chalk.dim(` summary="${summary}${summary.length >= 140 ? '...' : ''}"`));
464
+ });
465
+ }
466
+ });
467
+ brain
468
+ .command('export')
469
+ .description('Export Brain context (static context + architecture + memory tail)')
470
+ .option('--project-id <id>', 'Project ID override')
471
+ .option('--format <format>', 'Output format: md | json | claude | cursor | copilot', 'md')
472
+ .option('--out <path>', 'Write output to a file (defaults to stdout)')
473
+ .option('--write', 'Write to the default file path for the chosen format (instead of stdout)')
474
+ .option('--overwrite', 'Overwrite existing output file when using --out/--write')
475
+ .action(async (options) => {
476
+ const scope = getBrainScope(options.projectId);
477
+ const staticContext = (0, neurcode_context_1.loadStaticNeurcodeContext)(scope.cwd, scope.orgId && scope.projectId ? { orgId: scope.orgId, projectId: scope.projectId } : undefined);
478
+ let architectureJson = null;
479
+ const architecturePath = (0, path_1.join)(scope.cwd, '.neurcode', 'architecture.json');
480
+ if ((0, fs_1.existsSync)(architecturePath)) {
481
+ try {
482
+ architectureJson = JSON.parse((0, fs_1.readFileSync)(architecturePath, 'utf-8'));
483
+ }
484
+ catch {
485
+ architectureJson = null;
486
+ }
487
+ }
488
+ const memoryTail = scope.orgId && scope.projectId ? (0, neurcode_context_1.loadOrgProjectMemoryTail)(scope.cwd, scope.orgId, scope.projectId) : '';
489
+ const allCached = (0, plan_cache_1.listCachedPlans)(scope.cwd);
490
+ const scopedEntries = scope.orgId && scope.projectId
491
+ ? allCached.filter((e) => e.input.orgId === scope.orgId && e.input.projectId === scope.projectId).length
492
+ : undefined;
493
+ const generatedAt = new Date().toISOString();
494
+ const format = String(options.format || 'md').toLowerCase();
495
+ const payload = {
496
+ generatedAt,
497
+ repoRoot: scope.cwd,
498
+ scope: {
499
+ orgId: scope.orgId,
500
+ orgName: scope.orgName,
501
+ projectId: scope.projectId,
502
+ },
503
+ planCache: {
504
+ totalEntries: allCached.length,
505
+ scopedEntries,
506
+ },
507
+ staticContext,
508
+ architecture: architectureJson,
509
+ memoryTail,
510
+ };
511
+ let output = '';
512
+ if (format === 'json') {
513
+ output = JSON.stringify(payload, null, 2) + '\n';
514
+ }
515
+ else if (format === 'claude' || format === 'cursor' || format === 'copilot') {
516
+ // Tool-friendly markdown. Users can redirect it to CLAUDE.md / .cursorrules / .github/copilot-instructions.md
517
+ output =
518
+ renderBrainExportMarkdown({
519
+ generatedAt,
520
+ cwd: scope.cwd,
521
+ scope: payload.scope,
522
+ staticContext,
523
+ architectureJson,
524
+ memoryTail,
525
+ cacheStats: { totalEntries: allCached.length, scopedEntries },
526
+ }) +
527
+ '\n' +
528
+ [
529
+ '## Tool Notes',
530
+ format === 'claude'
531
+ ? '- Save as: CLAUDE.md'
532
+ : format === 'cursor'
533
+ ? '- Save as: .cursorrules'
534
+ : '- Save as: .github/copilot-instructions.md',
535
+ '- Keep this file short. The best results come from clear invariants, boundaries, and conventions.',
536
+ '',
537
+ ].join('\n');
538
+ }
539
+ else {
540
+ output =
541
+ renderBrainExportMarkdown({
542
+ generatedAt,
543
+ cwd: scope.cwd,
544
+ scope: payload.scope,
545
+ staticContext,
546
+ architectureJson,
547
+ memoryTail,
548
+ cacheStats: { totalEntries: allCached.length, scopedEntries },
549
+ }) + '\n';
550
+ }
551
+ const defaultOutPath = format === 'claude'
552
+ ? 'CLAUDE.md'
553
+ : format === 'cursor'
554
+ ? '.cursorrules'
555
+ : format === 'copilot'
556
+ ? (0, path_1.join)('.github', 'copilot-instructions.md')
557
+ : format === 'json'
558
+ ? 'neurcode-brain.json'
559
+ : 'neurcode-brain.md';
560
+ const outArg = options.out;
561
+ const writeFlag = Boolean(options.write);
562
+ const overwrite = Boolean(options.overwrite);
563
+ const outPathRaw = outArg || (writeFlag ? defaultOutPath : null);
564
+ if (outPathRaw) {
565
+ const outPath = (0, path_1.isAbsolute)(outPathRaw) ? outPathRaw : (0, path_1.join)(scope.cwd, outPathRaw);
566
+ const dir = (0, path_1.dirname)(outPath);
567
+ if (!(0, fs_1.existsSync)(dir))
568
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
569
+ if ((0, fs_1.existsSync)(outPath) && !overwrite) {
570
+ (0, messages_1.printError)('Export file already exists', outPath, [
571
+ 'Pass --overwrite to replace it',
572
+ 'Or choose a different path with --out <path>',
573
+ ]);
574
+ process.exit(1);
575
+ }
576
+ (0, fs_1.writeFileSync)(outPath, output, 'utf-8');
577
+ (0, messages_1.printSuccess)('Brain export written', outPath);
578
+ return;
579
+ }
580
+ process.stdout.write(output);
581
+ });
582
+ brain
583
+ .command('clear')
584
+ .description('Clear local Brain data (plan cache + memory) for the selected scope')
585
+ .option('--scope <scope>', 'Scope: project | org | repo', 'project')
586
+ .option('--project-id <id>', 'Project ID override (affects project scope)')
587
+ .option('--yes', 'Skip confirmation prompt')
588
+ .option('--dry-run', 'Show what would be deleted without deleting')
589
+ .action(async (options) => {
590
+ const scopeMode = String(options.scope || 'project').toLowerCase();
591
+ const brainScope = getBrainScope(options.projectId);
592
+ if (scopeMode === 'project' && (!brainScope.orgId || !brainScope.projectId)) {
593
+ (0, messages_1.printError)('Cannot clear project-scoped Brain data', 'Missing orgId/projectId. Run: neurcode init');
594
+ process.exit(1);
595
+ }
596
+ if (scopeMode === 'org' && !brainScope.orgId) {
597
+ (0, messages_1.printError)('Cannot clear org-scoped Brain data', 'Missing orgId. Run: neurcode init');
598
+ process.exit(1);
599
+ }
600
+ if (!['project', 'org', 'repo'].includes(scopeMode)) {
601
+ (0, messages_1.printError)('Invalid --scope', `Expected one of: project, org, repo. Got: ${scopeMode}`);
602
+ process.exit(1);
603
+ }
604
+ const scopeLabel = scopeMode === 'repo'
605
+ ? 'this repo'
606
+ : scopeMode === 'org'
607
+ ? `org ${brainScope.orgName || brainScope.orgId}`
608
+ : `org ${brainScope.orgName || brainScope.orgId} / project ${brainScope.projectId}`;
609
+ const planCachePath = (0, path_1.join)(brainScope.cwd, '.neurcode', 'plan-cache.json');
610
+ const orgsDir = (0, path_1.join)(brainScope.cwd, '.neurcode', 'orgs');
611
+ const orgDir = brainScope.orgId ? (0, path_1.join)(orgsDir, brainScope.orgId) : null;
612
+ const orgProjectDir = brainScope.orgId && brainScope.projectId ? (0, neurcode_context_1.getOrgProjectDir)(brainScope.cwd, brainScope.orgId, brainScope.projectId) : null;
613
+ const plannedDeletes = [];
614
+ // Plan cache deletions
615
+ plannedDeletes.push({ kind: 'plan-cache-entries', path: planCachePath });
616
+ // Memory/context directories
617
+ if (scopeMode === 'repo') {
618
+ plannedDeletes.push({ kind: 'org-memory', path: orgsDir });
619
+ }
620
+ else if (scopeMode === 'org' && orgDir) {
621
+ plannedDeletes.push({ kind: 'org-memory', path: orgDir });
622
+ }
623
+ else if (scopeMode === 'project' && orgProjectDir) {
624
+ plannedDeletes.push({ kind: 'project-memory', path: orgProjectDir });
625
+ }
626
+ if (!options.yes && process.stdout.isTTY && !process.env.CI) {
627
+ await (0, messages_1.printSuccessBanner)('Neurcode Brain Clear');
628
+ (0, messages_1.printWarning)('Destructive action', `This will delete Brain data for ${scopeLabel}.`);
629
+ console.log(chalk.bold.white('\nThis will affect:'));
630
+ plannedDeletes.forEach((d) => console.log(chalk.dim(` - ${d.kind}: ${d.path}`)));
631
+ const { createInterface } = await Promise.resolve().then(() => __importStar(require('readline/promises')));
632
+ const { stdin, stdout } = await Promise.resolve().then(() => __importStar(require('process')));
633
+ const rl = createInterface({ input: stdin, output: stdout });
634
+ const ans = await rl.question(chalk.bold('\nContinue? (y/n): '));
635
+ rl.close();
636
+ if (!['y', 'yes'].includes(ans.trim().toLowerCase())) {
637
+ (0, messages_1.printInfo)('Aborted', 'No changes were made.');
638
+ return;
639
+ }
640
+ }
641
+ if (options.dryRun) {
642
+ await (0, messages_1.printSuccessBanner)('Dry Run');
643
+ plannedDeletes.forEach((d) => console.log(chalk.dim(`Would delete ${d.kind}: ${d.path}`)));
644
+ return;
645
+ }
646
+ // 1) Delete plan cache entries based on scope
647
+ if (scopeMode === 'repo') {
648
+ (0, plan_cache_1.deleteCachedPlans)(brainScope.cwd, () => true);
649
+ }
650
+ else if (scopeMode === 'org' && brainScope.orgId) {
651
+ (0, plan_cache_1.deleteCachedPlans)(brainScope.cwd, (e) => e.input.orgId === brainScope.orgId);
652
+ }
653
+ else if (scopeMode === 'project' && brainScope.orgId && brainScope.projectId) {
654
+ (0, plan_cache_1.deleteCachedPlans)(brainScope.cwd, (e) => e.input.orgId === brainScope.orgId && e.input.projectId === brainScope.projectId);
655
+ }
656
+ // 2) Delete memory directories (local-only)
657
+ try {
658
+ const pathToRemove = scopeMode === 'repo' ? orgsDir : scopeMode === 'org' ? orgDir : orgProjectDir;
659
+ if (pathToRemove && (0, fs_1.existsSync)(pathToRemove)) {
660
+ (0, fs_1.rmSync)(pathToRemove, { recursive: true, force: true });
661
+ }
662
+ }
663
+ catch {
664
+ // ignore
665
+ }
666
+ (0, messages_1.printSuccess)('Brain cleared', `Scope: ${scopeLabel}`);
667
+ });
668
+ }
669
+ //# sourceMappingURL=brain.js.map