@neurcode-ai/cli 0.9.7 → 0.9.9

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.
@@ -0,0 +1,853 @@
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
+ const brain_context_1 = require("../utils/brain-context");
54
+ // Import chalk with fallback
55
+ let chalk;
56
+ try {
57
+ chalk = require('chalk');
58
+ }
59
+ catch {
60
+ chalk = {
61
+ green: (str) => str,
62
+ yellow: (str) => str,
63
+ red: (str) => str,
64
+ bold: (str) => str,
65
+ dim: (str) => str,
66
+ cyan: (str) => str,
67
+ white: (str) => str,
68
+ };
69
+ }
70
+ function safeFileSize(path) {
71
+ try {
72
+ if (!(0, fs_1.existsSync)(path))
73
+ return null;
74
+ return (0, fs_1.statSync)(path).size;
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ }
80
+ function countOccurrences(haystack, needle) {
81
+ if (!haystack || !needle)
82
+ return 0;
83
+ let count = 0;
84
+ let idx = 0;
85
+ while (true) {
86
+ const next = haystack.indexOf(needle, idx);
87
+ if (next === -1)
88
+ break;
89
+ count++;
90
+ idx = next + needle.length;
91
+ }
92
+ return count;
93
+ }
94
+ function scanFiles(dir, baseDir, maxFiles = 600) {
95
+ // Light-weight filesystem scan used only as a fallback when git isn't available.
96
+ // Keep it deterministic-ish but fast: only capture relative paths.
97
+ const { readdirSync, statSync } = require('fs');
98
+ const { join, relative } = require('path');
99
+ const files = [];
100
+ const ignoreDirs = new Set(['node_modules', '.git', '.next', 'dist', 'build', '.turbo', '.cache', 'coverage']);
101
+ const ignoreExts = new Set(['map', 'log', 'lock', 'png', 'jpg', 'jpeg', 'gif', 'ico', 'svg', 'woff', 'woff2', 'ttf', 'eot']);
102
+ function walk(current) {
103
+ if (files.length >= maxFiles)
104
+ return;
105
+ let entries = [];
106
+ try {
107
+ entries = readdirSync(current);
108
+ }
109
+ catch {
110
+ return;
111
+ }
112
+ for (const entry of entries) {
113
+ if (files.length >= maxFiles)
114
+ break;
115
+ if (entry.startsWith('.')) {
116
+ // keep common dotfiles, but skip big hidden dirs
117
+ if (ignoreDirs.has(entry))
118
+ continue;
119
+ }
120
+ const full = join(current, entry);
121
+ let st;
122
+ try {
123
+ st = statSync(full);
124
+ }
125
+ catch {
126
+ continue;
127
+ }
128
+ if (st.isDirectory()) {
129
+ if (ignoreDirs.has(entry))
130
+ continue;
131
+ walk(full);
132
+ continue;
133
+ }
134
+ if (!st.isFile())
135
+ continue;
136
+ const ext = entry.split('.').pop()?.toLowerCase();
137
+ if (ext && ignoreExts.has(ext))
138
+ continue;
139
+ files.push(relative(baseDir, full));
140
+ }
141
+ }
142
+ walk(dir);
143
+ return files.slice(0, maxFiles);
144
+ }
145
+ function getBrainScope(projectIdOverride) {
146
+ const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
147
+ const orgId = (0, state_1.getOrgId)();
148
+ const orgName = (0, state_1.getOrgName)();
149
+ const stateProjectId = (0, state_1.getProjectId)();
150
+ const configProjectId = (0, config_1.loadConfig)().projectId || null;
151
+ const projectId = projectIdOverride || stateProjectId || configProjectId;
152
+ return { cwd, orgId, orgName, projectId };
153
+ }
154
+ function formatBytes(bytes) {
155
+ if (!Number.isFinite(bytes) || bytes < 0)
156
+ return 'unknown';
157
+ if (bytes < 1024)
158
+ return `${bytes} B`;
159
+ if (bytes < 1024 * 1024)
160
+ return `${(bytes / 1024).toFixed(1)} KB`;
161
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
162
+ }
163
+ function renderBrainExportMarkdown(input) {
164
+ const lines = [];
165
+ lines.push('# Neurcode Brain Export');
166
+ lines.push('');
167
+ lines.push(`Generated: ${input.generatedAt}`);
168
+ lines.push(`Repo Root: ${input.cwd}`);
169
+ lines.push('');
170
+ lines.push('## Scope');
171
+ lines.push(`- Organization: ${input.scope.orgName || input.scope.orgId || '(not set)'}`);
172
+ if (input.scope.orgId)
173
+ lines.push(`- Org ID: ${input.scope.orgId}`);
174
+ lines.push(`- Project ID: ${input.scope.projectId || '(not set)'}`);
175
+ lines.push('');
176
+ if (input.cacheStats) {
177
+ lines.push('## Plan Cache');
178
+ lines.push(`- Entries (repo): ${input.cacheStats.totalEntries}`);
179
+ if (typeof input.cacheStats.scopedEntries === 'number') {
180
+ lines.push(`- Entries (scope): ${input.cacheStats.scopedEntries}`);
181
+ }
182
+ lines.push('');
183
+ }
184
+ lines.push('## Static Context Sources');
185
+ if (input.staticContext.sources.length === 0) {
186
+ lines.push('- (none)');
187
+ }
188
+ else {
189
+ input.staticContext.sources.forEach((s) => {
190
+ lines.push(`- ${s.label}: ${s.path} (${formatBytes(s.bytes)}${s.truncated ? ', truncated' : ''})`);
191
+ });
192
+ }
193
+ lines.push('');
194
+ lines.push('## Static Context (Combined)');
195
+ if (!input.staticContext.text.trim()) {
196
+ lines.push('_No context files found._');
197
+ }
198
+ else {
199
+ lines.push('```text');
200
+ lines.push(input.staticContext.text.trim());
201
+ lines.push('```');
202
+ }
203
+ lines.push('');
204
+ lines.push('## Architecture Memory (.neurcode/architecture.json)');
205
+ if (!input.architectureJson) {
206
+ lines.push('_Not found._');
207
+ }
208
+ else {
209
+ lines.push('```json');
210
+ lines.push(JSON.stringify(input.architectureJson, null, 2));
211
+ lines.push('```');
212
+ }
213
+ lines.push('');
214
+ lines.push('## Org/Project Memory (Tail)');
215
+ if (!input.memoryTail?.trim()) {
216
+ lines.push('_No memory found for this scope._');
217
+ }
218
+ else {
219
+ lines.push('```text');
220
+ lines.push(input.memoryTail.trim());
221
+ lines.push('```');
222
+ }
223
+ lines.push('');
224
+ return lines.join('\n');
225
+ }
226
+ function brainCommand(program) {
227
+ const brain = program.command('brain').description('Manage Neurcode Brain (local cache, context, and memory)');
228
+ brain
229
+ .command('status')
230
+ .description('Show local Brain status (scope, cache, memory, context sources)')
231
+ .option('--project-id <id>', 'Project ID override')
232
+ .option('--json', 'Output as JSON')
233
+ .action(async (options) => {
234
+ const scope = getBrainScope(options.projectId);
235
+ const allCached = (0, plan_cache_1.listCachedPlans)(scope.cwd);
236
+ const scopedCached = scope.orgId && scope.projectId
237
+ ? allCached.filter((e) => e.input.orgId === scope.orgId && e.input.projectId === scope.projectId)
238
+ : [];
239
+ const staticContext = (0, neurcode_context_1.loadStaticNeurcodeContext)(scope.cwd, scope.orgId && scope.projectId ? { orgId: scope.orgId, projectId: scope.projectId } : undefined);
240
+ const memoryPath = scope.orgId && scope.projectId ? (0, neurcode_context_1.getOrgProjectMemoryPath)(scope.cwd, scope.orgId, scope.projectId) : null;
241
+ const memoryExists = memoryPath ? (0, fs_1.existsSync)(memoryPath) : false;
242
+ const memoryBytes = memoryPath ? safeFileSize(memoryPath) : null;
243
+ let memoryEntries = null;
244
+ if (memoryExists && memoryPath) {
245
+ try {
246
+ const raw = (0, fs_1.readFileSync)(memoryPath, 'utf-8');
247
+ memoryEntries = countOccurrences(raw, '<!-- neurcode-memory-entry -->');
248
+ }
249
+ catch {
250
+ memoryEntries = null;
251
+ }
252
+ }
253
+ const architecturePath = (0, path_1.join)(scope.cwd, '.neurcode', 'architecture.json');
254
+ const architectureBytes = safeFileSize(architecturePath);
255
+ const brainDbPath = (0, plan_cache_1.getBrainDbPath)(scope.cwd);
256
+ const fallbackCachePath = (0, plan_cache_1.getBrainFallbackCachePath)(scope.cwd);
257
+ const brainPointerPath = (0, plan_cache_1.getBrainPointerPath)(scope.cwd);
258
+ const brainDbBytes = (0, plan_cache_1.getBrainDbSizeBytes)(scope.cwd);
259
+ const storageMode = (0, plan_cache_1.getBrainStorageMode)(scope.cwd);
260
+ const backend = (0, plan_cache_1.getBrainStoreBackend)(scope.cwd);
261
+ const activeStorePath = backend === 'sqlite' ? brainDbPath : fallbackCachePath;
262
+ const activeStoreExists = (0, fs_1.existsSync)(activeStorePath);
263
+ const activeStoreBytes = safeFileSize(activeStorePath);
264
+ const contextStats = (0, brain_context_1.getBrainContextStats)(scope.cwd, {
265
+ orgId: scope.orgId,
266
+ projectId: scope.projectId,
267
+ });
268
+ const payload = {
269
+ repoRoot: scope.cwd,
270
+ scope: {
271
+ orgId: scope.orgId,
272
+ orgName: scope.orgName,
273
+ projectId: scope.projectId,
274
+ },
275
+ planCache: {
276
+ totalEntries: allCached.length,
277
+ scopedEntries: scopedCached.length,
278
+ },
279
+ brainStore: {
280
+ backend,
281
+ activeStorePath,
282
+ activeStoreExists,
283
+ activeStoreBytes,
284
+ dbPath: brainDbPath,
285
+ dbExists: (0, fs_1.existsSync)(brainDbPath),
286
+ dbBytes: brainDbBytes,
287
+ fallbackPath: fallbackCachePath,
288
+ fallbackExists: (0, fs_1.existsSync)(fallbackCachePath),
289
+ pointerPath: brainPointerPath,
290
+ pointerExists: (0, fs_1.existsSync)(brainPointerPath),
291
+ noCodeStorage: storageMode.noCodeStorage,
292
+ modeSource: storageMode.source,
293
+ },
294
+ contextIndex: contextStats,
295
+ memory: {
296
+ path: memoryPath,
297
+ exists: memoryExists,
298
+ bytes: memoryBytes,
299
+ entries: memoryEntries,
300
+ },
301
+ staticContext: {
302
+ hash: staticContext.hash,
303
+ sources: staticContext.sources,
304
+ bytes: Buffer.byteLength(staticContext.text || '', 'utf-8'),
305
+ },
306
+ architecture: {
307
+ path: architecturePath,
308
+ exists: (0, fs_1.existsSync)(architecturePath),
309
+ bytes: architectureBytes,
310
+ },
311
+ };
312
+ if (options.json) {
313
+ console.log(JSON.stringify(payload, null, 2));
314
+ return;
315
+ }
316
+ await (0, messages_1.printSuccessBanner)('Neurcode Brain Status');
317
+ (0, messages_1.printSection)('Scope', '🧠');
318
+ console.log(chalk.dim(`Repo Root: ${scope.cwd}`));
319
+ console.log(chalk.dim(`Org: ${scope.orgName || scope.orgId || '(not set)'}`));
320
+ console.log(chalk.dim(`Project: ${scope.projectId || '(not set)'}`));
321
+ if (!scope.orgId || !scope.projectId) {
322
+ (0, messages_1.printWarning)('Brain scope is not fully configured', 'Run: neurcode init (to link this folder to an organization + project)');
323
+ }
324
+ (0, messages_1.printSection)('Brain Store', '🗄️');
325
+ console.log(chalk.dim(`Backend: ${backend}`));
326
+ console.log(chalk.dim(`Active Store: ${activeStorePath}`));
327
+ console.log(chalk.dim(`Store Exists: ${activeStoreExists ? 'yes' : 'no'}`));
328
+ if (activeStoreBytes != null) {
329
+ console.log(chalk.dim(`Store Size: ${formatBytes(activeStoreBytes)}`));
330
+ }
331
+ console.log(chalk.dim(`DB Path: ${brainDbPath}`));
332
+ console.log(chalk.dim(`DB Exists: ${(0, fs_1.existsSync)(brainDbPath) ? 'yes' : 'no'}`));
333
+ if (brainDbBytes != null) {
334
+ console.log(chalk.dim(`DB Size: ${formatBytes(brainDbBytes)}`));
335
+ }
336
+ console.log(chalk.dim(`Fallback Path: ${fallbackCachePath}`));
337
+ console.log(chalk.dim(`Fallback Exists: ${(0, fs_1.existsSync)(fallbackCachePath) ? 'yes' : 'no'}`));
338
+ console.log(chalk.dim(`Pointer Path: ${brainPointerPath}`));
339
+ console.log(chalk.dim(`Pointer Exists: ${(0, fs_1.existsSync)(brainPointerPath) ? 'yes' : 'no'}`));
340
+ console.log(chalk.dim(`No-code-storage: ${storageMode.noCodeStorage ? 'ON' : 'OFF'} (${storageMode.source})`));
341
+ (0, messages_1.printSection)('Context Index', '🧩');
342
+ console.log(chalk.dim(`Path: ${contextStats.path}`));
343
+ console.log(chalk.dim(`Exists: ${contextStats.exists ? 'yes' : 'no'}`));
344
+ console.log(chalk.dim(`Scopes: ${contextStats.totalScopes}`));
345
+ console.log(chalk.dim(`Scope Active: ${contextStats.scopeFound ? 'yes' : 'no'}`));
346
+ console.log(chalk.dim(`Indexed Files: ${contextStats.fileEntries}`));
347
+ console.log(chalk.dim(`Progress Events: ${contextStats.eventEntries}`));
348
+ if (contextStats.lastRefreshAt) {
349
+ console.log(chalk.dim(`Last Refresh: ${new Date(contextStats.lastRefreshAt).toLocaleString()}`));
350
+ }
351
+ if (contextStats.lastUpdatedAt) {
352
+ console.log(chalk.dim(`Last Update: ${new Date(contextStats.lastUpdatedAt).toLocaleString()}`));
353
+ }
354
+ (0, messages_1.printSection)('Plan Cache', '⚡');
355
+ console.log(chalk.dim(`Entries (repo): ${allCached.length}`));
356
+ if (scope.orgId && scope.projectId) {
357
+ console.log(chalk.dim(`Entries (scope): ${scopedCached.length}`));
358
+ }
359
+ (0, messages_1.printSection)('Memory', '📝');
360
+ if (!memoryPath) {
361
+ console.log(chalk.dim('No org/project scope detected, so no memory file is active.'));
362
+ }
363
+ else if (!memoryExists) {
364
+ console.log(chalk.dim(`No memory found yet for this scope.`));
365
+ console.log(chalk.dim(`Path: ${memoryPath}`));
366
+ }
367
+ else {
368
+ console.log(chalk.dim(`Path: ${memoryPath}`));
369
+ if (memoryBytes != null)
370
+ console.log(chalk.dim(`Size: ${formatBytes(memoryBytes)}`));
371
+ if (memoryEntries != null)
372
+ console.log(chalk.dim(`Entries: ${memoryEntries}`));
373
+ }
374
+ (0, messages_1.printSection)('Static Context', '📎');
375
+ if (staticContext.sources.length === 0) {
376
+ console.log(chalk.dim('No context files found. Optional files: neurcode.md, .neurcode/context.md'));
377
+ }
378
+ else {
379
+ staticContext.sources.forEach((s) => {
380
+ console.log(chalk.dim(`- ${s.label}: ${s.path} (${formatBytes(s.bytes)}${s.truncated ? ', truncated' : ''})`));
381
+ });
382
+ }
383
+ (0, messages_1.printSection)('Knowledge', '🏗️');
384
+ if ((0, fs_1.existsSync)(architecturePath)) {
385
+ console.log(chalk.dim(`Architecture memory: ${architecturePath} (${formatBytes(architectureBytes || 0)})`));
386
+ }
387
+ else {
388
+ console.log(chalk.dim('Architecture memory not found yet (it will be created automatically on plan runs).'));
389
+ }
390
+ });
391
+ brain
392
+ .command('mode')
393
+ .description('Show or set Brain storage mode')
394
+ .option('--storage-mode <mode>', 'Set storage mode: full | no-code')
395
+ .option('--enable-no-code-storage', 'Enable no-code-storage mode')
396
+ .option('--disable-no-code-storage', 'Disable no-code-storage mode')
397
+ .option('--json', 'Output as JSON')
398
+ .action(async (options) => {
399
+ const scope = getBrainScope();
400
+ let requestedMode = null;
401
+ if (options.enableNoCodeStorage && options.disableNoCodeStorage) {
402
+ (0, messages_1.printError)('Conflicting flags', 'Use only one of: --enable-no-code-storage or --disable-no-code-storage');
403
+ process.exit(1);
404
+ }
405
+ if (options.enableNoCodeStorage)
406
+ requestedMode = true;
407
+ if (options.disableNoCodeStorage)
408
+ requestedMode = false;
409
+ if (typeof options.storageMode === 'string' && options.storageMode.trim()) {
410
+ const normalized = options.storageMode.trim().toLowerCase();
411
+ if (['no-code', 'no_code', 'no-code-storage', 'nocode', 'hashes'].includes(normalized)) {
412
+ requestedMode = true;
413
+ }
414
+ else if (['full', 'default', 'standard'].includes(normalized)) {
415
+ requestedMode = false;
416
+ }
417
+ else {
418
+ (0, messages_1.printError)('Invalid --storage-mode value', 'Use: full or no-code');
419
+ process.exit(1);
420
+ }
421
+ }
422
+ if (requestedMode !== null) {
423
+ (0, plan_cache_1.setNoCodeStorageMode)(scope.cwd, requestedMode);
424
+ }
425
+ const mode = (0, plan_cache_1.getBrainStorageMode)(scope.cwd);
426
+ const payload = {
427
+ repoRoot: scope.cwd,
428
+ noCodeStorage: mode.noCodeStorage,
429
+ source: mode.source,
430
+ envOverride: process.env.NEURCODE_BRAIN_NO_CODE_STORAGE || null,
431
+ };
432
+ if (options.json) {
433
+ console.log(JSON.stringify(payload, null, 2));
434
+ return;
435
+ }
436
+ await (0, messages_1.printSuccessBanner)('Neurcode Brain Mode');
437
+ console.log(chalk.dim(`Repo Root: ${scope.cwd}`));
438
+ console.log(chalk.dim(`No-code-storage: ${mode.noCodeStorage ? 'ON' : 'OFF'} (${mode.source})`));
439
+ if (process.env.NEURCODE_BRAIN_NO_CODE_STORAGE != null) {
440
+ (0, messages_1.printInfo)('Environment override detected', 'NEURCODE_BRAIN_NO_CODE_STORAGE is currently overriding persisted mode.');
441
+ }
442
+ if (requestedMode !== null) {
443
+ (0, messages_1.printSuccess)('Mode updated', `No-code-storage is now ${mode.noCodeStorage ? 'ON' : 'OFF'}`);
444
+ }
445
+ else {
446
+ (0, messages_1.printInfo)('Usage', [
447
+ 'Set with: neurcode brain mode --enable-no-code-storage',
448
+ 'Or: neurcode brain mode --disable-no-code-storage',
449
+ 'Or: neurcode brain mode --storage-mode no-code|full',
450
+ ].join('\n'));
451
+ }
452
+ });
453
+ brain
454
+ .command('doctor')
455
+ .description('Diagnose Brain scope and explain plan cache hits/misses for an intent')
456
+ .argument('[intent...]', 'Intent to diagnose (if omitted, only prints scope checks)')
457
+ .option('--project-id <id>', 'Project ID override')
458
+ .option('--ticket <id>', 'Ticket reference (e.g., PROJ-123)')
459
+ .option('--issue <id>', 'GitHub issue number (affects cache key)')
460
+ .option('--pr <id>', 'GitHub PR number (affects cache key)')
461
+ .option('--json', 'Output as JSON')
462
+ .action(async (intentParts, options) => {
463
+ const intent = Array.isArray(intentParts) ? intentParts.join(' ').trim() : String(intentParts || '').trim();
464
+ const scope = getBrainScope(options.projectId);
465
+ const ticketRef = options.issue
466
+ ? `github_issue:${options.issue}`
467
+ : options.pr
468
+ ? `github_pr:${options.pr}`
469
+ : options.ticket
470
+ ? `ticket:${options.ticket}`
471
+ : undefined;
472
+ const problems = [];
473
+ if (!scope.orgId)
474
+ problems.push('Missing orgId (run neurcode init)');
475
+ if (!scope.projectId)
476
+ problems.push('Missing projectId (run neurcode init)');
477
+ const staticContext = (0, neurcode_context_1.loadStaticNeurcodeContext)(scope.cwd, scope.orgId && scope.projectId ? { orgId: scope.orgId, projectId: scope.projectId } : undefined);
478
+ const gitFingerprint = (0, plan_cache_1.getGitRepoFingerprint)(scope.cwd);
479
+ const fileTree = !gitFingerprint ? scanFiles(scope.cwd, scope.cwd, 400) : [];
480
+ const fsFingerprint = !gitFingerprint ? (0, plan_cache_1.getFilesystemFingerprintFromTree)(fileTree, scope.cwd) : null;
481
+ const repoFingerprint = gitFingerprint || fsFingerprint;
482
+ const normalized = intent ? (0, plan_cache_1.normalizeIntent)(intent) : '';
483
+ const backend = (0, plan_cache_1.getBrainStoreBackend)(scope.cwd);
484
+ const promptHash = normalized
485
+ ? (0, plan_cache_1.computePromptHash)({
486
+ intent: normalized,
487
+ ticketRef,
488
+ contextHash: staticContext.hash,
489
+ })
490
+ : null;
491
+ const policyVersionHash = (0, plan_cache_1.computePolicyVersionHash)(scope.cwd);
492
+ const neurcodeVersion = (0, plan_cache_1.getNeurcodeVersion)();
493
+ const canComputeKey = Boolean(intent && scope.orgId && scope.projectId && repoFingerprint);
494
+ const keyInput = canComputeKey
495
+ ? {
496
+ schemaVersion: 2,
497
+ orgId: scope.orgId,
498
+ projectId: scope.projectId,
499
+ promptHash: promptHash,
500
+ policyVersionHash,
501
+ neurcodeVersion,
502
+ repo: repoFingerprint,
503
+ }
504
+ : null;
505
+ const key = keyInput ? (0, plan_cache_1.computePlanCacheKey)(keyInput) : null;
506
+ const cached = key ? (0, plan_cache_1.peekCachedPlan)(scope.cwd, key) : null;
507
+ const similar = scope.orgId && scope.projectId && normalized
508
+ ? (0, plan_cache_1.findSimilarCachedPlans)(scope.cwd, {
509
+ orgId: scope.orgId,
510
+ projectId: scope.projectId,
511
+ repoIdentity: repoFingerprint?.repoIdentity,
512
+ }, normalized, 3)
513
+ : [];
514
+ const payload = {
515
+ repoRoot: scope.cwd,
516
+ scope: { orgId: scope.orgId, orgName: scope.orgName, projectId: scope.projectId },
517
+ problems,
518
+ cacheKey: key,
519
+ cacheHit: Boolean(cached),
520
+ keyInput,
521
+ backend,
522
+ repoFingerprint,
523
+ staticContext: { hash: staticContext.hash, sources: staticContext.sources },
524
+ similar: similar.map((s) => ({
525
+ createdAt: s.createdAt,
526
+ intent: s.input.intent,
527
+ planId: s.response.planId,
528
+ summary: s.response.plan.summary,
529
+ })),
530
+ };
531
+ if (options.json) {
532
+ console.log(JSON.stringify(payload, null, 2));
533
+ return;
534
+ }
535
+ await (0, messages_1.printSuccessBanner)('Neurcode Brain Doctor');
536
+ (0, messages_1.printSection)('Scope', '🧭');
537
+ console.log(chalk.dim(`Repo Root: ${scope.cwd}`));
538
+ console.log(chalk.dim(`Org: ${scope.orgName || scope.orgId || '(not set)'}`));
539
+ console.log(chalk.dim(`Project: ${scope.projectId || '(not set)'}`));
540
+ if (problems.length > 0) {
541
+ (0, messages_1.printWarning)('Scope issues detected', problems.join('\n • '));
542
+ }
543
+ (0, messages_1.printSection)('Fingerprint', '🧬');
544
+ console.log(chalk.dim(`Cache backend: ${backend}`));
545
+ if (!repoFingerprint) {
546
+ (0, messages_1.printWarning)('No repo fingerprint available', 'Not a git repo and filesystem scan failed.');
547
+ }
548
+ else if (repoFingerprint.kind === 'git') {
549
+ console.log(chalk.dim(`Kind: git`));
550
+ console.log(chalk.dim(`Repo: ${repoFingerprint.repoIdentity}`));
551
+ console.log(chalk.dim(`HEAD: ${repoFingerprint.headSha.substring(0, 12)}...`));
552
+ console.log(chalk.dim(`Tree: ${repoFingerprint.headTreeSha.substring(0, 12)}...`));
553
+ console.log(chalk.dim(`WorkingHash: ${repoFingerprint.workingTreeHash.substring(0, 12)}...`));
554
+ }
555
+ else {
556
+ console.log(chalk.dim(`Kind: filesystem`));
557
+ console.log(chalk.dim(`Repo: ${repoFingerprint.repoIdentity}`));
558
+ console.log(chalk.dim(`TreeHash: ${repoFingerprint.fileTreeHash.substring(0, 12)}...`));
559
+ console.log(chalk.dim(`Files seen: ${fileTree.length}`));
560
+ }
561
+ (0, messages_1.printSection)('Context', '📎');
562
+ if (staticContext.sources.length === 0) {
563
+ console.log(chalk.dim('No context files found.'));
564
+ }
565
+ else {
566
+ staticContext.sources.forEach((s) => {
567
+ console.log(chalk.dim(`- ${s.label}: ${s.path}${s.truncated ? ' (truncated)' : ''}`));
568
+ });
569
+ }
570
+ console.log(chalk.dim(`ContextHash: ${staticContext.hash.substring(0, 12)}...`));
571
+ (0, messages_1.printSection)('Plan Cache', '⚡');
572
+ if (!intent) {
573
+ (0, messages_1.printInfo)('No intent provided', 'Run: neurcode brain doctor "<intent>" to explain cache hit/miss');
574
+ return;
575
+ }
576
+ if (!scope.orgId || !scope.projectId) {
577
+ (0, messages_1.printError)('Cannot compute plan cache key', 'Missing orgId/projectId. Run: neurcode init');
578
+ return;
579
+ }
580
+ if (!repoFingerprint) {
581
+ (0, messages_1.printError)('Cannot compute plan cache key', 'No repo fingerprint available.');
582
+ return;
583
+ }
584
+ console.log(chalk.dim(`Intent (normalized): ${normalized}`));
585
+ if (ticketRef)
586
+ console.log(chalk.dim(`TicketRef: ${ticketRef}`));
587
+ if (promptHash)
588
+ console.log(chalk.dim(`PromptHash: ${promptHash.substring(0, 12)}...`));
589
+ console.log(chalk.dim(`PolicyHash: ${policyVersionHash.substring(0, 12)}...`));
590
+ console.log(chalk.dim(`NeurcodeVersion: ${neurcodeVersion}`));
591
+ console.log(chalk.dim(`CacheKey: ${key?.substring(0, 16)}...`));
592
+ if (cached) {
593
+ (0, messages_1.printSuccess)('Cache hit', `Created: ${new Date(cached.createdAt).toLocaleString()} | Uses: ${cached.useCount || 1}`);
594
+ }
595
+ else {
596
+ (0, messages_1.printWarning)('Cache miss', 'This intent/repo snapshot has no cached plan yet.');
597
+ }
598
+ if (similar.length > 0) {
599
+ console.log('');
600
+ console.log(chalk.bold.white('Similar cached plans (same org/project):'));
601
+ similar.forEach((s, idx) => {
602
+ const summary = (s.response.plan.summary || '').trim().slice(0, 140);
603
+ console.log(chalk.dim(` ${idx + 1}. intent="${s.input.intent}"`));
604
+ if (summary)
605
+ console.log(chalk.dim(` summary="${summary}${summary.length >= 140 ? '...' : ''}"`));
606
+ });
607
+ }
608
+ });
609
+ brain
610
+ .command('export')
611
+ .description('Export Brain context (static context + architecture + memory tail)')
612
+ .option('--project-id <id>', 'Project ID override')
613
+ .option('--format <format>', 'Output format: md | json | claude | cursor | copilot', 'md')
614
+ .option('--out <path>', 'Write output to a file (defaults to stdout)')
615
+ .option('--write', 'Write to the default file path for the chosen format (instead of stdout)')
616
+ .option('--overwrite', 'Overwrite existing output file when using --out/--write')
617
+ .action(async (options) => {
618
+ const scope = getBrainScope(options.projectId);
619
+ const staticContext = (0, neurcode_context_1.loadStaticNeurcodeContext)(scope.cwd, scope.orgId && scope.projectId ? { orgId: scope.orgId, projectId: scope.projectId } : undefined);
620
+ let architectureJson = null;
621
+ const architecturePath = (0, path_1.join)(scope.cwd, '.neurcode', 'architecture.json');
622
+ if ((0, fs_1.existsSync)(architecturePath)) {
623
+ try {
624
+ architectureJson = JSON.parse((0, fs_1.readFileSync)(architecturePath, 'utf-8'));
625
+ }
626
+ catch {
627
+ architectureJson = null;
628
+ }
629
+ }
630
+ const memoryTail = scope.orgId && scope.projectId ? (0, neurcode_context_1.loadOrgProjectMemoryTail)(scope.cwd, scope.orgId, scope.projectId) : '';
631
+ const allCached = (0, plan_cache_1.listCachedPlans)(scope.cwd);
632
+ const scopedEntries = scope.orgId && scope.projectId
633
+ ? allCached.filter((e) => e.input.orgId === scope.orgId && e.input.projectId === scope.projectId).length
634
+ : undefined;
635
+ const generatedAt = new Date().toISOString();
636
+ const format = String(options.format || 'md').toLowerCase();
637
+ const payload = {
638
+ generatedAt,
639
+ repoRoot: scope.cwd,
640
+ scope: {
641
+ orgId: scope.orgId,
642
+ orgName: scope.orgName,
643
+ projectId: scope.projectId,
644
+ },
645
+ planCache: {
646
+ totalEntries: allCached.length,
647
+ scopedEntries,
648
+ },
649
+ staticContext,
650
+ architecture: architectureJson,
651
+ memoryTail,
652
+ };
653
+ let output = '';
654
+ if (format === 'json') {
655
+ output = JSON.stringify(payload, null, 2) + '\n';
656
+ }
657
+ else if (format === 'claude' || format === 'cursor' || format === 'copilot') {
658
+ // Tool-friendly markdown. Users can redirect it to CLAUDE.md / .cursorrules / .github/copilot-instructions.md
659
+ output =
660
+ renderBrainExportMarkdown({
661
+ generatedAt,
662
+ cwd: scope.cwd,
663
+ scope: payload.scope,
664
+ staticContext,
665
+ architectureJson,
666
+ memoryTail,
667
+ cacheStats: { totalEntries: allCached.length, scopedEntries },
668
+ }) +
669
+ '\n' +
670
+ [
671
+ '## Tool Notes',
672
+ format === 'claude'
673
+ ? '- Save as: CLAUDE.md'
674
+ : format === 'cursor'
675
+ ? '- Save as: .cursorrules'
676
+ : '- Save as: .github/copilot-instructions.md',
677
+ '- Keep this file short. The best results come from clear invariants, boundaries, and conventions.',
678
+ '',
679
+ ].join('\n');
680
+ }
681
+ else {
682
+ output =
683
+ renderBrainExportMarkdown({
684
+ generatedAt,
685
+ cwd: scope.cwd,
686
+ scope: payload.scope,
687
+ staticContext,
688
+ architectureJson,
689
+ memoryTail,
690
+ cacheStats: { totalEntries: allCached.length, scopedEntries },
691
+ }) + '\n';
692
+ }
693
+ const defaultOutPath = format === 'claude'
694
+ ? 'CLAUDE.md'
695
+ : format === 'cursor'
696
+ ? '.cursorrules'
697
+ : format === 'copilot'
698
+ ? (0, path_1.join)('.github', 'copilot-instructions.md')
699
+ : format === 'json'
700
+ ? 'neurcode-brain.json'
701
+ : 'neurcode-brain.md';
702
+ const outArg = options.out;
703
+ const writeFlag = Boolean(options.write);
704
+ const overwrite = Boolean(options.overwrite);
705
+ const outPathRaw = outArg || (writeFlag ? defaultOutPath : null);
706
+ if (outPathRaw) {
707
+ const outPath = (0, path_1.isAbsolute)(outPathRaw) ? outPathRaw : (0, path_1.join)(scope.cwd, outPathRaw);
708
+ const dir = (0, path_1.dirname)(outPath);
709
+ if (!(0, fs_1.existsSync)(dir))
710
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
711
+ if ((0, fs_1.existsSync)(outPath) && !overwrite) {
712
+ (0, messages_1.printError)('Export file already exists', outPath, [
713
+ 'Pass --overwrite to replace it',
714
+ 'Or choose a different path with --out <path>',
715
+ ]);
716
+ process.exit(1);
717
+ }
718
+ (0, fs_1.writeFileSync)(outPath, output, 'utf-8');
719
+ (0, messages_1.printSuccess)('Brain export written', outPath);
720
+ return;
721
+ }
722
+ process.stdout.write(output);
723
+ });
724
+ brain
725
+ .command('clear')
726
+ .description('Clear local Brain data (plan cache + memory) for the selected scope')
727
+ .option('--scope <scope>', 'Scope: project | org | repo', 'project')
728
+ .option('--project-id <id>', 'Project ID override (affects project scope)')
729
+ .option('--yes', 'Skip confirmation prompt')
730
+ .option('--dry-run', 'Show what would be deleted without deleting')
731
+ .action(async (options) => {
732
+ const scopeMode = String(options.scope || 'project').toLowerCase();
733
+ const brainScope = getBrainScope(options.projectId);
734
+ if (scopeMode === 'project' && (!brainScope.orgId || !brainScope.projectId)) {
735
+ (0, messages_1.printError)('Cannot clear project-scoped Brain data', 'Missing orgId/projectId. Run: neurcode init');
736
+ process.exit(1);
737
+ }
738
+ if (scopeMode === 'org' && !brainScope.orgId) {
739
+ (0, messages_1.printError)('Cannot clear org-scoped Brain data', 'Missing orgId. Run: neurcode init');
740
+ process.exit(1);
741
+ }
742
+ if (!['project', 'org', 'repo'].includes(scopeMode)) {
743
+ (0, messages_1.printError)('Invalid --scope', `Expected one of: project, org, repo. Got: ${scopeMode}`);
744
+ process.exit(1);
745
+ }
746
+ const scopeLabel = scopeMode === 'repo'
747
+ ? 'this repo'
748
+ : scopeMode === 'org'
749
+ ? `org ${brainScope.orgName || brainScope.orgId}`
750
+ : `org ${brainScope.orgName || brainScope.orgId} / project ${brainScope.projectId}`;
751
+ const brainDbPath = (0, plan_cache_1.getBrainDbPath)(brainScope.cwd);
752
+ const fallbackCachePath = (0, plan_cache_1.getBrainFallbackCachePath)(brainScope.cwd);
753
+ const brainPointerPath = (0, plan_cache_1.getBrainPointerPath)(brainScope.cwd);
754
+ const contextIndexPath = (0, brain_context_1.getBrainContextPath)(brainScope.cwd);
755
+ const cacheBackend = (0, plan_cache_1.getBrainStoreBackend)(brainScope.cwd);
756
+ const activeCachePath = cacheBackend === 'sqlite' ? brainDbPath : fallbackCachePath;
757
+ const orgsDir = (0, path_1.join)(brainScope.cwd, '.neurcode', 'orgs');
758
+ const orgDir = brainScope.orgId ? (0, path_1.join)(orgsDir, brainScope.orgId) : null;
759
+ const orgProjectDir = brainScope.orgId && brainScope.projectId ? (0, neurcode_context_1.getOrgProjectDir)(brainScope.cwd, brainScope.orgId, brainScope.projectId) : null;
760
+ const plannedDeletes = [];
761
+ // Plan cache deletions
762
+ plannedDeletes.push({ kind: `brain-cache-entries (${cacheBackend})`, path: activeCachePath });
763
+ plannedDeletes.push({ kind: `context-index-entries (${scopeMode})`, path: contextIndexPath });
764
+ if (scopeMode === 'repo') {
765
+ if ((0, fs_1.existsSync)(brainDbPath)) {
766
+ plannedDeletes.push({ kind: 'brain-db-file', path: brainDbPath });
767
+ }
768
+ if ((0, fs_1.existsSync)(fallbackCachePath)) {
769
+ plannedDeletes.push({ kind: 'brain-fallback-file', path: fallbackCachePath });
770
+ }
771
+ if ((0, fs_1.existsSync)(contextIndexPath)) {
772
+ plannedDeletes.push({ kind: 'context-index-file', path: contextIndexPath });
773
+ }
774
+ plannedDeletes.push({ kind: 'brain-pointer', path: brainPointerPath });
775
+ }
776
+ // Memory/context directories
777
+ if (scopeMode === 'repo') {
778
+ plannedDeletes.push({ kind: 'org-memory', path: orgsDir });
779
+ }
780
+ else if (scopeMode === 'org' && orgDir) {
781
+ plannedDeletes.push({ kind: 'org-memory', path: orgDir });
782
+ }
783
+ else if (scopeMode === 'project' && orgProjectDir) {
784
+ plannedDeletes.push({ kind: 'project-memory', path: orgProjectDir });
785
+ }
786
+ if (!options.yes && process.stdout.isTTY && !process.env.CI) {
787
+ await (0, messages_1.printSuccessBanner)('Neurcode Brain Clear');
788
+ (0, messages_1.printWarning)('Destructive action', `This will delete Brain data for ${scopeLabel}.`);
789
+ console.log(chalk.bold.white('\nThis will affect:'));
790
+ plannedDeletes.forEach((d) => console.log(chalk.dim(` - ${d.kind}: ${d.path}`)));
791
+ const { createInterface } = await Promise.resolve().then(() => __importStar(require('readline/promises')));
792
+ const { stdin, stdout } = await Promise.resolve().then(() => __importStar(require('process')));
793
+ const rl = createInterface({ input: stdin, output: stdout });
794
+ const ans = await rl.question(chalk.bold('\nContinue? (y/n): '));
795
+ rl.close();
796
+ if (!['y', 'yes'].includes(ans.trim().toLowerCase())) {
797
+ (0, messages_1.printInfo)('Aborted', 'No changes were made.');
798
+ return;
799
+ }
800
+ }
801
+ if (options.dryRun) {
802
+ await (0, messages_1.printSuccessBanner)('Dry Run');
803
+ plannedDeletes.forEach((d) => console.log(chalk.dim(`Would delete ${d.kind}: ${d.path}`)));
804
+ return;
805
+ }
806
+ // 1) Delete plan cache entries based on scope
807
+ if (scopeMode === 'repo') {
808
+ (0, plan_cache_1.deleteCachedPlans)(brainScope.cwd, () => true);
809
+ }
810
+ else if (scopeMode === 'org' && brainScope.orgId) {
811
+ (0, plan_cache_1.deleteCachedPlans)(brainScope.cwd, (e) => e.input.orgId === brainScope.orgId);
812
+ }
813
+ else if (scopeMode === 'project' && brainScope.orgId && brainScope.projectId) {
814
+ (0, plan_cache_1.deleteCachedPlans)(brainScope.cwd, (e) => e.input.orgId === brainScope.orgId && e.input.projectId === brainScope.projectId);
815
+ }
816
+ (0, brain_context_1.clearBrainContext)(brainScope.cwd, scopeMode, {
817
+ orgId: brainScope.orgId,
818
+ projectId: brainScope.projectId,
819
+ });
820
+ if (scopeMode === 'repo') {
821
+ try {
822
+ (0, plan_cache_1.closeBrainStore)(brainScope.cwd);
823
+ if ((0, fs_1.existsSync)(brainDbPath)) {
824
+ (0, fs_1.rmSync)(brainDbPath, { force: true });
825
+ }
826
+ if ((0, fs_1.existsSync)(fallbackCachePath)) {
827
+ (0, fs_1.rmSync)(fallbackCachePath, { force: true });
828
+ }
829
+ if ((0, fs_1.existsSync)(contextIndexPath)) {
830
+ (0, fs_1.rmSync)(contextIndexPath, { force: true });
831
+ }
832
+ if ((0, fs_1.existsSync)(brainPointerPath)) {
833
+ (0, fs_1.rmSync)(brainPointerPath, { force: true });
834
+ }
835
+ }
836
+ catch {
837
+ // ignore
838
+ }
839
+ }
840
+ // 2) Delete memory directories (local-only)
841
+ try {
842
+ const pathToRemove = scopeMode === 'repo' ? orgsDir : scopeMode === 'org' ? orgDir : orgProjectDir;
843
+ if (pathToRemove && (0, fs_1.existsSync)(pathToRemove)) {
844
+ (0, fs_1.rmSync)(pathToRemove, { recursive: true, force: true });
845
+ }
846
+ }
847
+ catch {
848
+ // ignore
849
+ }
850
+ (0, messages_1.printSuccess)('Brain cleared', `Scope: ${scopeLabel}`);
851
+ });
852
+ }
853
+ //# sourceMappingURL=brain.js.map