@neurcode-ai/cli 0.9.8 → 0.9.10

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,646 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getBrainContextPath = getBrainContextPath;
4
+ exports.upsertBrainFileContextFromContent = upsertBrainFileContextFromContent;
5
+ exports.removeBrainFileContext = removeBrainFileContext;
6
+ exports.refreshBrainContextForFiles = refreshBrainContextForFiles;
7
+ exports.refreshBrainContextFromWorkspace = refreshBrainContextFromWorkspace;
8
+ exports.recordBrainProgressEvent = recordBrainProgressEvent;
9
+ exports.buildBrainContextPack = buildBrainContextPack;
10
+ exports.getBrainContextStats = getBrainContextStats;
11
+ exports.clearBrainContext = clearBrainContext;
12
+ const child_process_1 = require("child_process");
13
+ const crypto_1 = require("crypto");
14
+ const fs_1 = require("fs");
15
+ const path_1 = require("path");
16
+ const secret_masking_1 = require("./secret-masking");
17
+ const CONTEXT_SCHEMA_VERSION = 1;
18
+ const BRAIN_CONTEXT_FILE = 'brain-context.json';
19
+ const MAX_FILE_ENTRIES_PER_SCOPE = 2000;
20
+ const MAX_EVENTS_PER_SCOPE = 500;
21
+ const MAX_SUMMARY_LEN = 560;
22
+ const MAX_FILE_BYTES_TO_SUMMARIZE = 512 * 1024;
23
+ const MAX_REFRESH_FILES = 120;
24
+ const INDEXABLE_EXTENSIONS = new Set([
25
+ 'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs',
26
+ 'py', 'go', 'rs', 'java', 'kt', 'swift', 'rb', 'php', 'cs',
27
+ 'json', 'yaml', 'yml', 'toml', 'md', 'sql', 'graphql', 'gql',
28
+ 'sh', 'bash', 'zsh', 'ps1', 'env',
29
+ 'html', 'css', 'scss', 'less',
30
+ 'prisma',
31
+ ]);
32
+ function nowIso() {
33
+ return new Date().toISOString();
34
+ }
35
+ function sha256Hex(input) {
36
+ return (0, crypto_1.createHash)('sha256').update(input).digest('hex');
37
+ }
38
+ function normalizeFilePath(filePath) {
39
+ return filePath.replace(/\\/g, '/').replace(/^\.\//, '').trim();
40
+ }
41
+ function scopeKey(scope) {
42
+ if (!scope.orgId || !scope.projectId)
43
+ return null;
44
+ return `${scope.orgId}::${scope.projectId}`;
45
+ }
46
+ function getContextPath(cwd) {
47
+ return (0, path_1.join)(cwd, '.neurcode', BRAIN_CONTEXT_FILE);
48
+ }
49
+ function ensureNeurcodeDir(cwd) {
50
+ const dir = (0, path_1.join)(cwd, '.neurcode');
51
+ if (!(0, fs_1.existsSync)(dir)) {
52
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
53
+ }
54
+ }
55
+ function readStore(cwd) {
56
+ const path = getContextPath(cwd);
57
+ if (!(0, fs_1.existsSync)(path)) {
58
+ return { schemaVersion: CONTEXT_SCHEMA_VERSION, scopes: {} };
59
+ }
60
+ try {
61
+ const raw = (0, fs_1.readFileSync)(path, 'utf-8');
62
+ const parsed = JSON.parse(raw);
63
+ if (parsed.schemaVersion !== CONTEXT_SCHEMA_VERSION || !parsed.scopes || typeof parsed.scopes !== 'object') {
64
+ throw new Error('Invalid context schema');
65
+ }
66
+ return parsed;
67
+ }
68
+ catch {
69
+ try {
70
+ (0, fs_1.renameSync)(path, path.replace(/\.json$/, `.corrupt-${Date.now()}.json`));
71
+ }
72
+ catch {
73
+ // ignore
74
+ }
75
+ return { schemaVersion: CONTEXT_SCHEMA_VERSION, scopes: {} };
76
+ }
77
+ }
78
+ function writeStore(cwd, store) {
79
+ ensureNeurcodeDir(cwd);
80
+ const path = getContextPath(cwd);
81
+ const tmp = `${path}.tmp`;
82
+ (0, fs_1.writeFileSync)(tmp, JSON.stringify(store, null, 2) + '\n', 'utf-8');
83
+ (0, fs_1.renameSync)(tmp, path);
84
+ }
85
+ function ensureScopeStore(store, key, scope) {
86
+ const existing = store.scopes[key];
87
+ if (existing)
88
+ return existing;
89
+ const created = {
90
+ orgId: scope.orgId,
91
+ projectId: scope.projectId,
92
+ updatedAt: nowIso(),
93
+ files: {},
94
+ events: [],
95
+ };
96
+ store.scopes[key] = created;
97
+ return created;
98
+ }
99
+ function tokenize(text) {
100
+ return text
101
+ .toLowerCase()
102
+ .replace(/[^\w\s]/g, ' ')
103
+ .split(/\s+/)
104
+ .filter((token) => token.length >= 3);
105
+ }
106
+ function jaccard(a, b) {
107
+ if (a.size === 0 || b.size === 0)
108
+ return 0;
109
+ let inter = 0;
110
+ for (const token of a) {
111
+ if (b.has(token))
112
+ inter++;
113
+ }
114
+ const union = a.size + b.size - inter;
115
+ return union === 0 ? 0 : inter / union;
116
+ }
117
+ function inferLanguage(filePath) {
118
+ const path = normalizeFilePath(filePath);
119
+ const ext = path.includes('.') ? path.split('.').pop()?.toLowerCase() || '' : '';
120
+ const byExt = {
121
+ ts: 'typescript',
122
+ tsx: 'typescript-react',
123
+ js: 'javascript',
124
+ jsx: 'javascript-react',
125
+ mjs: 'javascript',
126
+ cjs: 'javascript',
127
+ py: 'python',
128
+ go: 'go',
129
+ rs: 'rust',
130
+ java: 'java',
131
+ kt: 'kotlin',
132
+ swift: 'swift',
133
+ rb: 'ruby',
134
+ php: 'php',
135
+ cs: 'csharp',
136
+ json: 'json',
137
+ yaml: 'yaml',
138
+ yml: 'yaml',
139
+ toml: 'toml',
140
+ md: 'markdown',
141
+ sql: 'sql',
142
+ sh: 'shell',
143
+ bash: 'shell',
144
+ zsh: 'shell',
145
+ html: 'html',
146
+ css: 'css',
147
+ scss: 'scss',
148
+ less: 'less',
149
+ prisma: 'prisma',
150
+ };
151
+ return byExt[ext] || 'text';
152
+ }
153
+ function shouldIndexFile(filePath, content) {
154
+ const normalized = normalizeFilePath(filePath);
155
+ if (!normalized || normalized.startsWith('.neurcode/'))
156
+ return false;
157
+ if (normalized.startsWith('node_modules/'))
158
+ return false;
159
+ if (normalized.startsWith('.git/'))
160
+ return false;
161
+ if (normalized.endsWith('.lock'))
162
+ return false;
163
+ if (normalized.endsWith('.map'))
164
+ return false;
165
+ const ext = normalized.includes('.') ? normalized.split('.').pop()?.toLowerCase() || '' : '';
166
+ if (ext && !INDEXABLE_EXTENSIONS.has(ext))
167
+ return false;
168
+ if (Buffer.byteLength(content, 'utf-8') > MAX_FILE_BYTES_TO_SUMMARIZE)
169
+ return false;
170
+ if (content.includes('\u0000'))
171
+ return false;
172
+ return true;
173
+ }
174
+ function collectSymbols(content, language) {
175
+ const symbols = new Set();
176
+ const push = (name) => {
177
+ const trimmed = name.trim();
178
+ if (!trimmed)
179
+ return;
180
+ if (trimmed.length > 80)
181
+ return;
182
+ symbols.add(trimmed);
183
+ };
184
+ const applyRegex = (regex, index = 1) => {
185
+ for (const match of content.matchAll(regex)) {
186
+ if (!match[index])
187
+ continue;
188
+ push(match[index]);
189
+ if (symbols.size >= 24)
190
+ break;
191
+ }
192
+ };
193
+ if (language.startsWith('typescript') || language.startsWith('javascript')) {
194
+ applyRegex(/\bexport\s+(?:async\s+)?(?:function|class|const|let|var|interface|type|enum)\s+([A-Za-z_][\w$]*)/g, 1);
195
+ applyRegex(/\b(?:function|class)\s+([A-Za-z_][\w$]*)/g, 1);
196
+ applyRegex(/\bconst\s+([A-Za-z_][\w$]*)\s*=\s*(?:async\s*)?\(/g, 1);
197
+ }
198
+ else if (language === 'python') {
199
+ applyRegex(/^\s*(?:async\s+)?def\s+([A-Za-z_]\w*)\s*\(/gm, 1);
200
+ applyRegex(/^\s*class\s+([A-Za-z_]\w*)\s*(?:\(|:)/gm, 1);
201
+ }
202
+ else if (language === 'go') {
203
+ applyRegex(/^\s*func\s+(?:\([^)]+\)\s*)?([A-Za-z_]\w*)\s*\(/gm, 1);
204
+ applyRegex(/^\s*type\s+([A-Za-z_]\w*)\s+/gm, 1);
205
+ }
206
+ else {
207
+ applyRegex(/^\s*(?:class|interface|type|enum|function)\s+([A-Za-z_]\w*)/gm, 1);
208
+ }
209
+ return Array.from(symbols).slice(0, 12);
210
+ }
211
+ function detectConcerns(content, filePath) {
212
+ const text = `${filePath}\n${content}`.toLowerCase();
213
+ const concerns = [];
214
+ const checks = [
215
+ ['auth', /\bauth|jwt|token|login|oauth|session\b/],
216
+ ['api', /\broute|controller|endpoint|http|fastify|express|fetch\b/],
217
+ ['db', /\bquery|sql|migration|prisma|typeorm|database|model\b/],
218
+ ['billing', /\bbilling|subscription|invoice|payment|stripe\b/],
219
+ ['cli', /\bcommand|argv|commander|cli\b/],
220
+ ['tests', /\bdescribe\(|it\(|test\(|assert|expect\(/],
221
+ ['ui', /\bcomponent|render|jsx|tsx|tailwind|css\b/],
222
+ ['security', /\bsecurity|sanitize|encrypt|decrypt|permission|policy\b/],
223
+ ];
224
+ for (const [label, regex] of checks) {
225
+ if (regex.test(text))
226
+ concerns.push(label);
227
+ if (concerns.length >= 5)
228
+ break;
229
+ }
230
+ return concerns;
231
+ }
232
+ function summarizeContent(filePath, content) {
233
+ const language = inferLanguage(filePath);
234
+ const sizeBytes = Buffer.byteLength(content, 'utf-8');
235
+ const lineCount = content.split(/\r?\n/).filter((line) => line.trim().length > 0).length;
236
+ const symbols = collectSymbols(content, language);
237
+ const concerns = detectConcerns(content, filePath);
238
+ const parts = [];
239
+ parts.push(`${language} file`);
240
+ parts.push(`${lineCount} non-empty lines`);
241
+ if (symbols.length > 0)
242
+ parts.push(`symbols: ${symbols.slice(0, 6).join(', ')}`);
243
+ if (concerns.length > 0)
244
+ parts.push(`concerns: ${concerns.join(', ')}`);
245
+ const summary = (0, secret_masking_1.maskSecretsInText)(parts.join(' | ')).masked.slice(0, MAX_SUMMARY_LEN);
246
+ return { summary, symbols, language, sizeBytes };
247
+ }
248
+ function upsertEntry(scopeStore, filePath, content) {
249
+ const path = normalizeFilePath(filePath);
250
+ const timestamp = nowIso();
251
+ const contentHash = sha256Hex(content);
252
+ const existing = scopeStore.files[path];
253
+ if (existing && existing.contentHash === contentHash) {
254
+ existing.lastSeenAt = timestamp;
255
+ scopeStore.updatedAt = timestamp;
256
+ return { created: false, updated: false };
257
+ }
258
+ const { summary, symbols, language, sizeBytes } = summarizeContent(path, content);
259
+ scopeStore.files[path] = {
260
+ path,
261
+ contentHash,
262
+ language,
263
+ symbols,
264
+ summary,
265
+ sizeBytes,
266
+ updatedAt: timestamp,
267
+ lastSeenAt: timestamp,
268
+ };
269
+ scopeStore.updatedAt = timestamp;
270
+ return { created: !existing, updated: Boolean(existing) };
271
+ }
272
+ function pruneScope(scopeStore) {
273
+ const entries = Object.values(scopeStore.files);
274
+ if (entries.length > MAX_FILE_ENTRIES_PER_SCOPE) {
275
+ entries.sort((a, b) => {
276
+ const aTime = Date.parse(a.lastSeenAt) || 0;
277
+ const bTime = Date.parse(b.lastSeenAt) || 0;
278
+ return aTime - bTime;
279
+ });
280
+ const toDelete = entries.slice(0, entries.length - MAX_FILE_ENTRIES_PER_SCOPE);
281
+ for (const entry of toDelete) {
282
+ delete scopeStore.files[entry.path];
283
+ }
284
+ }
285
+ if (scopeStore.events.length > MAX_EVENTS_PER_SCOPE) {
286
+ scopeStore.events = scopeStore.events.slice(scopeStore.events.length - MAX_EVENTS_PER_SCOPE);
287
+ }
288
+ }
289
+ function addEvent(scopeStore, event) {
290
+ const next = {
291
+ id: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
292
+ timestamp: nowIso(),
293
+ ...event,
294
+ };
295
+ scopeStore.events.push(next);
296
+ scopeStore.updatedAt = next.timestamp;
297
+ pruneScope(scopeStore);
298
+ }
299
+ function parseGitStatusPaths(cwd, limit) {
300
+ try {
301
+ const status = (0, child_process_1.execSync)('git status --porcelain', {
302
+ cwd,
303
+ encoding: 'utf-8',
304
+ stdio: ['ignore', 'pipe', 'ignore'],
305
+ });
306
+ const lines = status.split(/\r?\n/).filter((line) => line.trim().length > 0);
307
+ const out = [];
308
+ for (const line of lines) {
309
+ if (out.length >= limit)
310
+ break;
311
+ if (line.length < 4)
312
+ continue;
313
+ const code = line.slice(0, 2);
314
+ const raw = line.slice(3).trim();
315
+ if (!raw)
316
+ continue;
317
+ const deleted = code.includes('D');
318
+ const normalizedRaw = raw.includes(' -> ')
319
+ ? raw.split(' -> ').pop() || raw
320
+ : raw;
321
+ const unquoted = normalizedRaw.startsWith('"') && normalizedRaw.endsWith('"')
322
+ ? normalizedRaw.slice(1, -1).replace(/\\"/g, '"')
323
+ : normalizedRaw;
324
+ out.push({ path: normalizeFilePath(unquoted), deleted });
325
+ }
326
+ return out;
327
+ }
328
+ catch {
329
+ return [];
330
+ }
331
+ }
332
+ function collectDependencyHints(cwd) {
333
+ const hints = [];
334
+ const packageJsonPath = (0, path_1.join)(cwd, 'package.json');
335
+ if (!(0, fs_1.existsSync)(packageJsonPath))
336
+ return hints;
337
+ try {
338
+ const raw = (0, fs_1.readFileSync)(packageJsonPath, 'utf-8');
339
+ const parsed = JSON.parse(raw);
340
+ const deps = Object.keys(parsed.dependencies || {});
341
+ const devDeps = Object.keys(parsed.devDependencies || {});
342
+ const merged = [...new Set([...deps, ...devDeps])].sort();
343
+ return merged.slice(0, 80);
344
+ }
345
+ catch {
346
+ return hints;
347
+ }
348
+ }
349
+ function getBrainContextPath(cwd) {
350
+ return getContextPath(cwd);
351
+ }
352
+ function upsertBrainFileContextFromContent(cwd, scope, filePath, content) {
353
+ const key = scopeKey(scope);
354
+ if (!key)
355
+ return { indexed: false, created: false, updated: false };
356
+ const normalizedPath = normalizeFilePath(filePath);
357
+ if (!shouldIndexFile(normalizedPath, content)) {
358
+ return { indexed: false, created: false, updated: false };
359
+ }
360
+ const store = readStore(cwd);
361
+ const scopeStore = ensureScopeStore(store, key, scope);
362
+ const result = upsertEntry(scopeStore, normalizedPath, content);
363
+ pruneScope(scopeStore);
364
+ writeStore(cwd, store);
365
+ return { indexed: true, ...result };
366
+ }
367
+ function removeBrainFileContext(cwd, scope, filePath) {
368
+ const key = scopeKey(scope);
369
+ if (!key)
370
+ return { removed: false };
371
+ const store = readStore(cwd);
372
+ const scopeStore = store.scopes[key];
373
+ if (!scopeStore)
374
+ return { removed: false };
375
+ const normalizedPath = normalizeFilePath(filePath);
376
+ if (!scopeStore.files[normalizedPath])
377
+ return { removed: false };
378
+ delete scopeStore.files[normalizedPath];
379
+ scopeStore.updatedAt = nowIso();
380
+ pruneScope(scopeStore);
381
+ writeStore(cwd, store);
382
+ return { removed: true };
383
+ }
384
+ function refreshBrainContextForFiles(cwd, scope, filePaths) {
385
+ const key = scopeKey(scope);
386
+ if (!key)
387
+ return { indexed: 0, removed: 0, skipped: filePaths.length };
388
+ const uniquePaths = [...new Set(filePaths.map(normalizeFilePath).filter(Boolean))].slice(0, MAX_REFRESH_FILES);
389
+ if (uniquePaths.length === 0)
390
+ return { indexed: 0, removed: 0, skipped: 0 };
391
+ const store = readStore(cwd);
392
+ const scopeStore = ensureScopeStore(store, key, scope);
393
+ let indexed = 0;
394
+ let removed = 0;
395
+ let skipped = 0;
396
+ for (const relPath of uniquePaths) {
397
+ const fullPath = (0, path_1.join)(cwd, relPath);
398
+ if (!(0, fs_1.existsSync)(fullPath)) {
399
+ if (scopeStore.files[relPath]) {
400
+ delete scopeStore.files[relPath];
401
+ removed++;
402
+ }
403
+ else {
404
+ skipped++;
405
+ }
406
+ continue;
407
+ }
408
+ try {
409
+ const content = (0, fs_1.readFileSync)(fullPath, 'utf-8');
410
+ if (!shouldIndexFile(relPath, content)) {
411
+ skipped++;
412
+ continue;
413
+ }
414
+ const change = upsertEntry(scopeStore, relPath, content);
415
+ if (change.created || change.updated)
416
+ indexed++;
417
+ }
418
+ catch {
419
+ skipped++;
420
+ }
421
+ }
422
+ scopeStore.lastRefreshAt = nowIso();
423
+ pruneScope(scopeStore);
424
+ writeStore(cwd, store);
425
+ return { indexed, removed, skipped };
426
+ }
427
+ function refreshBrainContextFromWorkspace(cwd, scope, options) {
428
+ const key = scopeKey(scope);
429
+ if (!key) {
430
+ return { indexed: 0, removed: 0, skipped: 0, considered: 0, refreshed: false };
431
+ }
432
+ const store = readStore(cwd);
433
+ const scopeStore = ensureScopeStore(store, key, scope);
434
+ const incomingHash = options?.workingTreeHash;
435
+ if (incomingHash && scopeStore.lastWorkingTreeHash === incomingHash) {
436
+ return { indexed: 0, removed: 0, skipped: 0, considered: 0, refreshed: false };
437
+ }
438
+ const limit = Math.max(1, Math.min(options?.maxFiles || MAX_REFRESH_FILES, MAX_REFRESH_FILES));
439
+ const changed = parseGitStatusPaths(cwd, limit);
440
+ if (changed.length === 0) {
441
+ scopeStore.lastWorkingTreeHash = incomingHash;
442
+ scopeStore.lastRefreshAt = nowIso();
443
+ writeStore(cwd, store);
444
+ return { indexed: 0, removed: 0, skipped: 0, considered: 0, refreshed: true };
445
+ }
446
+ let indexed = 0;
447
+ let removed = 0;
448
+ let skipped = 0;
449
+ for (const item of changed) {
450
+ if (!item.path)
451
+ continue;
452
+ const fullPath = (0, path_1.join)(cwd, item.path);
453
+ if (item.deleted || !(0, fs_1.existsSync)(fullPath)) {
454
+ if (scopeStore.files[item.path]) {
455
+ delete scopeStore.files[item.path];
456
+ removed++;
457
+ }
458
+ else {
459
+ skipped++;
460
+ }
461
+ continue;
462
+ }
463
+ try {
464
+ const content = (0, fs_1.readFileSync)(fullPath, 'utf-8');
465
+ if (!shouldIndexFile(item.path, content)) {
466
+ skipped++;
467
+ continue;
468
+ }
469
+ const change = upsertEntry(scopeStore, item.path, content);
470
+ if (change.created || change.updated)
471
+ indexed++;
472
+ }
473
+ catch {
474
+ skipped++;
475
+ }
476
+ }
477
+ scopeStore.lastWorkingTreeHash = incomingHash;
478
+ scopeStore.lastRefreshAt = nowIso();
479
+ if (options?.recordEvent) {
480
+ addEvent(scopeStore, {
481
+ type: 'refresh',
482
+ note: `indexed=${indexed}, removed=${removed}, considered=${changed.length}`,
483
+ });
484
+ }
485
+ pruneScope(scopeStore);
486
+ writeStore(cwd, store);
487
+ return { indexed, removed, skipped, considered: changed.length, refreshed: true };
488
+ }
489
+ function recordBrainProgressEvent(cwd, scope, event) {
490
+ const key = scopeKey(scope);
491
+ if (!key)
492
+ return;
493
+ const store = readStore(cwd);
494
+ const scopeStore = ensureScopeStore(store, key, scope);
495
+ addEvent(scopeStore, event);
496
+ writeStore(cwd, store);
497
+ }
498
+ function buildBrainContextPack(cwd, scope, intent, options) {
499
+ const key = scopeKey(scope);
500
+ if (!key) {
501
+ return { text: '', selectedFiles: 0, recentEvents: 0, totalIndexedFiles: 0 };
502
+ }
503
+ const store = readStore(cwd);
504
+ const scopeStore = store.scopes[key];
505
+ if (!scopeStore) {
506
+ return { text: '', selectedFiles: 0, recentEvents: 0, totalIndexedFiles: 0 };
507
+ }
508
+ const maxFiles = Math.max(1, Math.min(options?.maxFiles || 8, 12));
509
+ const maxEvents = Math.max(0, Math.min(options?.maxEvents || 6, 12));
510
+ const maxBytes = Math.max(1024, Math.min(options?.maxBytes || 12 * 1024, 24 * 1024));
511
+ const intentTokens = new Set(tokenize(intent));
512
+ const nowMs = Date.now();
513
+ const scored = Object.values(scopeStore.files).map((entry) => {
514
+ const entryText = [entry.path, entry.language, entry.summary, entry.symbols.join(' ')].join(' ');
515
+ const entryTokens = new Set(tokenize(entryText));
516
+ let score = jaccard(intentTokens, entryTokens);
517
+ for (const token of intentTokens) {
518
+ if (entry.path.toLowerCase().includes(token)) {
519
+ score += 0.06;
520
+ }
521
+ }
522
+ const recencyHours = Math.max(0, (nowMs - Date.parse(entry.lastSeenAt || entry.updatedAt || nowIso())) / (1000 * 60 * 60));
523
+ if (Number.isFinite(recencyHours)) {
524
+ score += Math.max(0, 0.12 - recencyHours * 0.005);
525
+ }
526
+ return { entry, score };
527
+ });
528
+ const selected = scored
529
+ .filter((row) => row.score > 0)
530
+ .sort((a, b) => b.score - a.score)
531
+ .slice(0, maxFiles)
532
+ .map((row) => row.entry);
533
+ if (selected.length === 0) {
534
+ return {
535
+ text: '',
536
+ selectedFiles: 0,
537
+ recentEvents: 0,
538
+ totalIndexedFiles: Object.keys(scopeStore.files).length,
539
+ };
540
+ }
541
+ const recentEvents = [...scopeStore.events]
542
+ .sort((a, b) => (Date.parse(b.timestamp) || 0) - (Date.parse(a.timestamp) || 0))
543
+ .slice(0, maxEvents);
544
+ const dependencyHints = collectDependencyHints(cwd);
545
+ const lines = [];
546
+ lines.push('NEURCODE LIVE CONTEXT PACK (repo-grounded, local memory)');
547
+ lines.push('');
548
+ if (recentEvents.length > 0) {
549
+ lines.push('Recent Human Progress:');
550
+ for (const event of recentEvents) {
551
+ const ts = event.timestamp.replace('T', ' ').replace('Z', 'Z');
552
+ const details = [
553
+ event.filePath ? `file=${event.filePath}` : '',
554
+ event.planId ? `planId=${event.planId}` : '',
555
+ event.verdict ? `verdict=${event.verdict}` : '',
556
+ event.note ? event.note : '',
557
+ ].filter(Boolean).join(' | ');
558
+ lines.push(`- [${ts}] ${event.type}${details ? `: ${details}` : ''}`);
559
+ }
560
+ lines.push('');
561
+ }
562
+ lines.push('Relevant File Summaries:');
563
+ selected.forEach((entry, idx) => {
564
+ lines.push(`${idx + 1}) ${entry.path} [${entry.language}] hash=${entry.contentHash.slice(0, 12)}...`);
565
+ lines.push(` summary: ${entry.summary}`);
566
+ if (entry.symbols.length > 0) {
567
+ lines.push(` symbols: ${entry.symbols.join(', ')}`);
568
+ }
569
+ });
570
+ lines.push('');
571
+ if (dependencyHints.length > 0) {
572
+ lines.push(`Known Dependency Hints (${dependencyHints.length}):`);
573
+ lines.push(dependencyHints.join(', '));
574
+ lines.push('');
575
+ }
576
+ lines.push('Grounding Rules:');
577
+ lines.push('- Prioritize files and symbols listed above before proposing unrelated modules.');
578
+ lines.push('- Do not assume new package imports unless they appear in dependency hints or user explicitly requests them.');
579
+ lines.push('- If uncertain, propose reading additional existing files first.');
580
+ lines.push('');
581
+ let text = lines.join('\n');
582
+ if (Buffer.byteLength(text, 'utf-8') > maxBytes) {
583
+ text = text.slice(0, maxBytes);
584
+ }
585
+ return {
586
+ text,
587
+ selectedFiles: selected.length,
588
+ recentEvents: recentEvents.length,
589
+ totalIndexedFiles: Object.keys(scopeStore.files).length,
590
+ };
591
+ }
592
+ function getBrainContextStats(cwd, scope) {
593
+ const path = getContextPath(cwd);
594
+ const exists = (0, fs_1.existsSync)(path);
595
+ const store = readStore(cwd);
596
+ const key = scopeKey(scope);
597
+ const scopeStore = key ? store.scopes[key] : undefined;
598
+ return {
599
+ path,
600
+ exists,
601
+ scopeFound: Boolean(scopeStore),
602
+ totalScopes: Object.keys(store.scopes).length,
603
+ fileEntries: scopeStore ? Object.keys(scopeStore.files).length : 0,
604
+ eventEntries: scopeStore ? scopeStore.events.length : 0,
605
+ lastUpdatedAt: scopeStore?.updatedAt,
606
+ lastRefreshAt: scopeStore?.lastRefreshAt,
607
+ lastWorkingTreeHash: scopeStore?.lastWorkingTreeHash,
608
+ };
609
+ }
610
+ function clearBrainContext(cwd, mode, scope) {
611
+ const path = getContextPath(cwd);
612
+ if (!(0, fs_1.existsSync)(path)) {
613
+ return { removedScopes: 0, removedFiles: 0, removedEvents: 0, removedStoreFile: false };
614
+ }
615
+ if (mode === 'repo') {
616
+ try {
617
+ (0, fs_1.renameSync)(path, path.replace(/\.json$/, `.cleared-${Date.now()}.json`));
618
+ return { removedScopes: 0, removedFiles: 0, removedEvents: 0, removedStoreFile: true };
619
+ }
620
+ catch {
621
+ return { removedScopes: 0, removedFiles: 0, removedEvents: 0, removedStoreFile: false };
622
+ }
623
+ }
624
+ const store = readStore(cwd);
625
+ const keys = Object.keys(store.scopes);
626
+ let removedScopes = 0;
627
+ let removedFiles = 0;
628
+ let removedEvents = 0;
629
+ for (const key of keys) {
630
+ const scopeStore = store.scopes[key];
631
+ if (!scopeStore)
632
+ continue;
633
+ const matchesOrg = scope.orgId && scopeStore.orgId === scope.orgId;
634
+ const matchesProject = scope.projectId && scopeStore.projectId === scope.projectId;
635
+ const shouldRemove = mode === 'org' ? Boolean(matchesOrg) : Boolean(matchesOrg && matchesProject);
636
+ if (!shouldRemove)
637
+ continue;
638
+ removedScopes++;
639
+ removedFiles += Object.keys(scopeStore.files).length;
640
+ removedEvents += scopeStore.events.length;
641
+ delete store.scopes[key];
642
+ }
643
+ writeStore(cwd, store);
644
+ return { removedScopes, removedFiles, removedEvents, removedStoreFile: false };
645
+ }
646
+ //# sourceMappingURL=brain-context.js.map