@neurcode-ai/cli 0.16.4 → 0.16.6

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 (76) hide show
  1. package/.telemetry-bundle/dist/index.js +0 -0
  2. package/LICENSE +201 -0
  3. package/dist/api-client.d.ts +75 -0
  4. package/dist/api-client.d.ts.map +1 -1
  5. package/dist/api-client.js +43 -0
  6. package/dist/api-client.js.map +1 -1
  7. package/dist/commands/brain.d.ts.map +1 -1
  8. package/dist/commands/brain.js +151 -0
  9. package/dist/commands/brain.js.map +1 -1
  10. package/dist/commands/cursor.d.ts.map +1 -1
  11. package/dist/commands/cursor.js +72 -0
  12. package/dist/commands/cursor.js.map +1 -1
  13. package/dist/commands/eval.d.ts +19 -0
  14. package/dist/commands/eval.d.ts.map +1 -0
  15. package/dist/commands/eval.js +246 -0
  16. package/dist/commands/eval.js.map +1 -0
  17. package/dist/commands/onboard.d.ts +29 -0
  18. package/dist/commands/onboard.d.ts.map +1 -0
  19. package/dist/commands/onboard.js +247 -0
  20. package/dist/commands/onboard.js.map +1 -0
  21. package/dist/commands/runtime-doctor.d.ts.map +1 -1
  22. package/dist/commands/runtime-doctor.js +80 -9
  23. package/dist/commands/runtime-doctor.js.map +1 -1
  24. package/dist/commands/runtime-sync.d.ts.map +1 -1
  25. package/dist/commands/runtime-sync.js +22 -0
  26. package/dist/commands/runtime-sync.js.map +1 -1
  27. package/dist/commands/runtime.d.ts +18 -0
  28. package/dist/commands/runtime.d.ts.map +1 -1
  29. package/dist/commands/runtime.js +321 -1
  30. package/dist/commands/runtime.js.map +1 -1
  31. package/dist/commands/session-hook.d.ts +10 -2
  32. package/dist/commands/session-hook.d.ts.map +1 -1
  33. package/dist/commands/session-hook.js +533 -122
  34. package/dist/commands/session-hook.js.map +1 -1
  35. package/dist/commands/session.d.ts +34 -0
  36. package/dist/commands/session.d.ts.map +1 -1
  37. package/dist/commands/session.js +243 -2
  38. package/dist/commands/session.js.map +1 -1
  39. package/dist/index.js +84 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/runtime-build.json +5 -5
  42. package/dist/utils/agent-guard-supervisor.d.ts.map +1 -1
  43. package/dist/utils/agent-guard-supervisor.js +0 -1
  44. package/dist/utils/agent-guard-supervisor.js.map +1 -1
  45. package/dist/utils/cursor-gate.d.ts +1 -0
  46. package/dist/utils/cursor-gate.d.ts.map +1 -1
  47. package/dist/utils/cursor-gate.js +34 -7
  48. package/dist/utils/cursor-gate.js.map +1 -1
  49. package/dist/utils/guided-eval.d.ts +251 -0
  50. package/dist/utils/guided-eval.d.ts.map +1 -0
  51. package/dist/utils/guided-eval.js +880 -0
  52. package/dist/utils/guided-eval.js.map +1 -0
  53. package/dist/utils/local-repo-brain.d.ts +158 -0
  54. package/dist/utils/local-repo-brain.d.ts.map +1 -0
  55. package/dist/utils/local-repo-brain.js +854 -0
  56. package/dist/utils/local-repo-brain.js.map +1 -0
  57. package/dist/utils/runtime-live.d.ts +25 -0
  58. package/dist/utils/runtime-live.d.ts.map +1 -1
  59. package/dist/utils/runtime-live.js +103 -4
  60. package/dist/utils/runtime-live.js.map +1 -1
  61. package/dist/utils/runtime-outbox.d.ts +2 -1
  62. package/dist/utils/runtime-outbox.d.ts.map +1 -1
  63. package/dist/utils/runtime-outbox.js +21 -16
  64. package/dist/utils/runtime-outbox.js.map +1 -1
  65. package/dist/utils/session-allowlist-rules.d.ts +12 -0
  66. package/dist/utils/session-allowlist-rules.d.ts.map +1 -1
  67. package/dist/utils/session-allowlist-rules.js +61 -1
  68. package/dist/utils/session-allowlist-rules.js.map +1 -1
  69. package/dist/utils/structural-understanding.d.ts +61 -1
  70. package/dist/utils/structural-understanding.d.ts.map +1 -1
  71. package/dist/utils/structural-understanding.js +534 -1
  72. package/dist/utils/structural-understanding.js.map +1 -1
  73. package/dist/utils/v0-governance.d.ts.map +1 -1
  74. package/dist/utils/v0-governance.js +10 -0
  75. package/dist/utils/v0-governance.js.map +1 -1
  76. package/package.json +7 -8
@@ -0,0 +1,854 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LOCAL_REPO_BRAIN_SCHEMA_VERSION = void 0;
4
+ exports.localRepoBrainPath = localRepoBrainPath;
5
+ exports.localRepoBrainMarkdownPath = localRepoBrainMarkdownPath;
6
+ exports.buildLocalRepoBrain = buildLocalRepoBrain;
7
+ exports.writeLocalRepoBrain = writeLocalRepoBrain;
8
+ exports.readLocalRepoBrain = readLocalRepoBrain;
9
+ exports.renderLocalRepoBrainMarkdown = renderLocalRepoBrainMarkdown;
10
+ exports.getRepoBrainContext = getRepoBrainContext;
11
+ exports.formatRepoBrainFactsForMessage = formatRepoBrainFactsForMessage;
12
+ exports.searchLocalRepoBrain = searchLocalRepoBrain;
13
+ const node_crypto_1 = require("node:crypto");
14
+ const node_child_process_1 = require("node:child_process");
15
+ const node_fs_1 = require("node:fs");
16
+ const node_path_1 = require("node:path");
17
+ exports.LOCAL_REPO_BRAIN_SCHEMA_VERSION = 'neurcode.local-repo-brain.v1';
18
+ const IGNORE_DIRS = new Set([
19
+ '.git',
20
+ 'node_modules',
21
+ 'dist',
22
+ 'build',
23
+ 'coverage',
24
+ '.next',
25
+ '.turbo',
26
+ '.cache',
27
+ '.neurcode/brain',
28
+ '.neurcode/repo-brain',
29
+ '.neurcode/sessions',
30
+ '.neurcode/understanding',
31
+ ]);
32
+ const GENERATED_PATTERNS = [
33
+ /(^|\/)dist\//,
34
+ /(^|\/)build\//,
35
+ /(^|\/)coverage\//,
36
+ /(^|\/)vendor\//,
37
+ /(^|\/)generated\//,
38
+ /\.min\.(js|css)$/i,
39
+ /\.map$/i,
40
+ /(^|\/)pnpm-lock\.yaml$/i,
41
+ /(^|\/)package-lock\.json$/i,
42
+ /(^|\/)yarn\.lock$/i,
43
+ /(^|\/)uv\.lock$/i,
44
+ ];
45
+ const ANALYZABLE_EXTENSIONS = new Set([
46
+ '.ts',
47
+ '.tsx',
48
+ '.js',
49
+ '.jsx',
50
+ '.mjs',
51
+ '.cjs',
52
+ '.py',
53
+ '.go',
54
+ '.java',
55
+ '.rb',
56
+ '.rs',
57
+ '.md',
58
+ '.yml',
59
+ '.yaml',
60
+ '.json',
61
+ '.sh',
62
+ '.bash',
63
+ ]);
64
+ const JS_TS_RESERVED_WORDS = new Set([
65
+ 'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'break', 'continue',
66
+ 'return', 'throw', 'try', 'catch', 'finally', 'yield',
67
+ 'var', 'let', 'const', 'function', 'class', 'import', 'export', 'default',
68
+ 'new', 'delete', 'typeof', 'instanceof', 'in', 'of', 'void',
69
+ 'async', 'await', 'from', 'as', 'with', 'debugger',
70
+ 'this', 'super', 'extends', 'implements',
71
+ 'static', 'abstract', 'override', 'readonly', 'declare',
72
+ 'then',
73
+ 'def', 'pass', 'and', 'or', 'not', 'is', 'lambda', 'assert',
74
+ 'del', 'raise', 'except', 'global', 'nonlocal',
75
+ ]);
76
+ const TEST_PATH_PATTERNS = [
77
+ /\/__tests__\//,
78
+ /\/tests?\//,
79
+ /\.test\.[jt]sx?$/,
80
+ /\.spec\.[jt]sx?$/,
81
+ ];
82
+ function isTestPath(filePath) {
83
+ return TEST_PATH_PATTERNS.some((p) => p.test(filePath));
84
+ }
85
+ function hash(value) {
86
+ return (0, node_crypto_1.createHash)('sha256').update(value).digest('hex');
87
+ }
88
+ function shortHash(value) {
89
+ return hash(value).slice(0, 24);
90
+ }
91
+ function normalizePath(value) {
92
+ return value.replace(/\\/g, '/').replace(/^\.\//, '');
93
+ }
94
+ function moduleKey(filePath) {
95
+ const parts = normalizePath(filePath).split('/').filter(Boolean);
96
+ if (parts.length >= 2 && ['packages', 'services', 'web', 'apps'].includes(parts[0])) {
97
+ return `${parts[0]}/${parts[1]}`;
98
+ }
99
+ return parts[0] || 'root';
100
+ }
101
+ function languageFor(filePath) {
102
+ const ext = (0, node_path_1.extname)(filePath).toLowerCase();
103
+ if (ext === '.ts' || ext === '.tsx')
104
+ return 'typescript';
105
+ if (['.js', '.jsx', '.mjs', '.cjs'].includes(ext))
106
+ return 'javascript';
107
+ if (ext === '.py')
108
+ return 'python';
109
+ if (ext === '.go')
110
+ return 'go';
111
+ if (ext === '.java')
112
+ return 'java';
113
+ if (ext === '.rb')
114
+ return 'ruby';
115
+ if (ext === '.rs')
116
+ return 'rust';
117
+ if (ext === '.md')
118
+ return 'markdown';
119
+ if (ext === '.yml' || ext === '.yaml')
120
+ return 'yaml';
121
+ if (ext === '.json')
122
+ return 'json';
123
+ if (ext === '.sh' || ext === '.bash')
124
+ return 'shell';
125
+ return 'other';
126
+ }
127
+ function isGeneratedPath(filePath) {
128
+ return GENERATED_PATTERNS.some((pattern) => pattern.test(filePath));
129
+ }
130
+ function sensitiveKindsFor(filePath) {
131
+ const lower = filePath.toLowerCase();
132
+ const kinds = new Set();
133
+ if (/(^|\/)(auth|oauth|jwt|session|permission|rbac|sso)(\/|\.|-|_)/.test(lower))
134
+ kinds.add('auth');
135
+ if (/(^|\/)(billing|payment|checkout|invoice|subscription|stripe)(\/|\.|-|_)/.test(lower))
136
+ kinds.add('billing');
137
+ if (/(^|\/)(db|database|schema|prisma)(\/|\.|-|_)/.test(lower))
138
+ kinds.add('database');
139
+ if (/(^|\/)(migrations?|schema)(\/|\.|-|_)/.test(lower))
140
+ kinds.add('migration');
141
+ if (/(^|\/)\.github\/workflows\//.test(lower) || /(^|\/)(ci|workflows?)(\/|\.|-|_)/.test(lower))
142
+ kinds.add('workflow');
143
+ if (/(^|\/)(\.env|secrets?|credentials?|keys?)(\/|\.|-|_)/.test(lower))
144
+ kinds.add('secret');
145
+ if (/(^|\/)(package\.json|pnpm-lock\.yaml|yarn\.lock|package-lock\.json|uv\.lock|requirements\.txt|pyproject\.toml|go\.mod|cargo\.toml)$/.test(lower))
146
+ kinds.add('dependency');
147
+ if (/(^|\/)(config|settings|env\.production|dockerfile|docker-compose|k8s|helm|terraform)(\/|\.|-|_)/.test(lower))
148
+ kinds.add('configuration');
149
+ if (/(^|\/)(packages\/governance-runtime|packages\/cli\/src\/commands\/session-hook|services\/api\/src\/routes\/runtime-evidence|web\/dashboard\/src\/pages\/runtimecontrolplane)/.test(lower))
150
+ kinds.add('runtime_governance');
151
+ return [...kinds].sort();
152
+ }
153
+ function listRepoFiles(projectRoot, maxFiles) {
154
+ try {
155
+ const output = (0, node_child_process_1.execFileSync)('git', ['ls-files', '-co', '--exclude-standard'], {
156
+ cwd: projectRoot,
157
+ encoding: 'utf8',
158
+ maxBuffer: 1024 * 1024 * 20,
159
+ stdio: ['ignore', 'pipe', 'ignore'],
160
+ });
161
+ return output
162
+ .split(/\r?\n/)
163
+ .map((line) => normalizePath(line.trim()))
164
+ .filter(Boolean)
165
+ .filter((file) => ANALYZABLE_EXTENSIONS.has((0, node_path_1.extname)(file).toLowerCase()))
166
+ .slice(0, maxFiles);
167
+ }
168
+ catch {
169
+ const files = [];
170
+ const walk = (dir) => {
171
+ if (files.length >= maxFiles)
172
+ return;
173
+ for (const entry of (0, node_fs_1.readdirSync)(dir)) {
174
+ if (files.length >= maxFiles)
175
+ break;
176
+ const full = (0, node_path_1.join)(dir, entry);
177
+ const rel = normalizePath((0, node_path_1.relative)(projectRoot, full));
178
+ if ([...IGNORE_DIRS].some((ignored) => rel === ignored || rel.startsWith(`${ignored}/`)))
179
+ continue;
180
+ const st = (0, node_fs_1.statSync)(full);
181
+ if (st.isDirectory()) {
182
+ walk(full);
183
+ }
184
+ else if (st.isFile() && ANALYZABLE_EXTENSIONS.has((0, node_path_1.extname)(entry).toLowerCase())) {
185
+ files.push(rel);
186
+ }
187
+ }
188
+ };
189
+ walk(projectRoot);
190
+ return files;
191
+ }
192
+ }
193
+ function normalizeTokenFingerprint(source) {
194
+ const tokens = source
195
+ .replace(/(['"`])(?:\\.|(?!\1).)*\1/g, ' str ')
196
+ .replace(/\b\d+(?:\.\d+)?\b/g, ' num ')
197
+ .replace(/\b[A-Za-z_$][A-Za-z0-9_$]*\b/g, (token) => {
198
+ if (['if', 'for', 'while', 'return', 'await', 'async', 'try', 'catch', 'throw', 'new', 'const', 'let', 'var', 'function', 'class', 'def'].includes(token)) {
199
+ return token;
200
+ }
201
+ return 'id';
202
+ })
203
+ .match(/[A-Za-z_]+|[{}()[\].,;:+\-*/%<>=!&|?]/g);
204
+ if (!tokens || tokens.length < 8)
205
+ return null;
206
+ return shortHash(tokens.slice(0, 160).join(' '));
207
+ }
208
+ function arityFrom(params) {
209
+ if (params == null)
210
+ return null;
211
+ const trimmed = params.trim();
212
+ if (!trimmed)
213
+ return 0;
214
+ return trimmed.split(',').map((part) => part.trim()).filter(Boolean).length;
215
+ }
216
+ function lineNumberAt(source, index) {
217
+ return source.slice(0, index).split(/\r?\n/).length;
218
+ }
219
+ function extractSymbols(filePath, source, language) {
220
+ if (!['typescript', 'javascript', 'python'].includes(language))
221
+ return [];
222
+ const symbols = [];
223
+ const pushMatches = (pattern, kind, nameIndex, paramsIndex, exportedByPattern) => {
224
+ for (const match of source.matchAll(pattern)) {
225
+ const name = match[nameIndex];
226
+ if (!name || name.length > 120)
227
+ continue;
228
+ if (kind === 'method' && JS_TS_RESERVED_WORDS.has(name))
229
+ continue;
230
+ const matchText = match[0] || name;
231
+ const line = lineNumberAt(source, match.index || 0);
232
+ const params = paramsIndex == null ? undefined : match[paramsIndex];
233
+ const exported = exportedByPattern || /^\s*export\b/.test(matchText);
234
+ symbols.push({
235
+ name,
236
+ kind,
237
+ file: filePath,
238
+ line,
239
+ exported,
240
+ arity: arityFrom(params),
241
+ signatureHash: shortHash(`${language}:${kind}:${name}:${params || ''}`),
242
+ tokenFingerprintHash: normalizeTokenFingerprint(matchText),
243
+ language,
244
+ });
245
+ }
246
+ };
247
+ if (language === 'python') {
248
+ pushMatches(/^\s*def\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(([^)]*)\)/gm, 'function', 1, 2, false);
249
+ pushMatches(/^\s*class\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:\(([^)]*)\))?/gm, 'class', 1, 2, false);
250
+ return dedupeSymbols(symbols);
251
+ }
252
+ pushMatches(/^\s*(export\s+)?(?:async\s+)?function\s+([A-Za-z_$][A-Za-z0-9_$]*)(?:<[^>\n]+>)?\s*\(([^)]*)\)/gm, 'function', 2, 3, false);
253
+ pushMatches(/^\s*(export\s+)?class\s+([A-Za-z_$][A-Za-z0-9_$]*)\b/gm, 'class', 2, null, false);
254
+ pushMatches(/^\s*export\s+interface\s+([A-Za-z_$][A-Za-z0-9_$]*)\b/gm, 'interface', 1, null, true);
255
+ pushMatches(/^\s*export\s+type\s+([A-Za-z_$][A-Za-z0-9_$]*)\b/gm, 'type', 1, null, true);
256
+ pushMatches(/^\s*(export\s+)?const\s+([A-Za-z_$][A-Za-z0-9_$]*)(?:\s*:\s*[^=]+)?\s*=\s*(?:async\s*)?\(?([^=;{}]*)\)?\s*=>/gm, 'const', 2, 3, false);
257
+ pushMatches(/^\s*(?:public\s+|private\s+|protected\s+|static\s+|async\s+)*([A-Za-z_$][A-Za-z0-9_$]*)\s*\(([^)]*)\)\s*[:{]/gm, 'method', 1, 2, false);
258
+ return dedupeSymbols(symbols);
259
+ }
260
+ function dedupeSymbols(symbols) {
261
+ const seen = new Set();
262
+ const out = [];
263
+ for (const symbol of symbols) {
264
+ const key = `${symbol.file}:${symbol.line}:${symbol.kind}:${symbol.name}`;
265
+ if (seen.has(key))
266
+ continue;
267
+ seen.add(key);
268
+ out.push(symbol);
269
+ }
270
+ return out;
271
+ }
272
+ function resolveRelativeImport(fromFile, target, fileSet) {
273
+ if (!target.startsWith('.'))
274
+ return null;
275
+ const base = normalizePath((0, node_path_1.join)((0, node_path_1.dirname)(fromFile), target));
276
+ const candidates = [
277
+ base,
278
+ `${base}.ts`,
279
+ `${base}.tsx`,
280
+ `${base}.js`,
281
+ `${base}.jsx`,
282
+ `${base}.py`,
283
+ `${base}/index.ts`,
284
+ `${base}/index.tsx`,
285
+ `${base}/index.js`,
286
+ ].map(normalizePath);
287
+ return candidates.find((candidate) => fileSet.has(candidate)) || null;
288
+ }
289
+ function extractImports(filePath, source, language, fileSet) {
290
+ if (!['typescript', 'javascript', 'python'].includes(language))
291
+ return [];
292
+ const imports = [];
293
+ const add = (target, index, targetKind) => {
294
+ imports.push({
295
+ fromFile: filePath,
296
+ target,
297
+ targetKind,
298
+ resolvedFile: resolveRelativeImport(filePath, target, fileSet),
299
+ line: lineNumberAt(source, index),
300
+ language,
301
+ });
302
+ };
303
+ if (language === 'python') {
304
+ for (const match of source.matchAll(/^\s*(?:from\s+([A-Za-z0-9_.]+)\s+import|import\s+([A-Za-z0-9_.]+))/gm)) {
305
+ add(match[1] || match[2], match.index || 0, 'python_module');
306
+ }
307
+ return imports;
308
+ }
309
+ for (const match of source.matchAll(/\bfrom\s+['"]([^'"]+)['"]/g)) {
310
+ const target = match[1];
311
+ add(target, match.index || 0, target.startsWith('.') ? 'relative' : 'package');
312
+ }
313
+ for (const match of source.matchAll(/\brequire\(\s*['"]([^'"]+)['"]\s*\)/g)) {
314
+ const target = match[1];
315
+ add(target, match.index || 0, target.startsWith('.') ? 'relative' : 'package');
316
+ }
317
+ return imports;
318
+ }
319
+ function discoverCodeownersFiles(projectRoot) {
320
+ try {
321
+ const output = (0, node_child_process_1.execFileSync)('git', ['ls-files', '-co', '--exclude-standard'], {
322
+ cwd: projectRoot,
323
+ encoding: 'utf8',
324
+ maxBuffer: 1024 * 1024 * 20,
325
+ stdio: ['ignore', 'pipe', 'ignore'],
326
+ });
327
+ const found = output
328
+ .split(/\r?\n/)
329
+ .map((line) => normalizePath(line.trim()))
330
+ .filter((p) => p === 'CODEOWNERS' || p.endsWith('/CODEOWNERS'));
331
+ if (found.length > 0)
332
+ return found;
333
+ }
334
+ catch {
335
+ // fall through to filesystem fallback
336
+ }
337
+ return ['CODEOWNERS', '.github/CODEOWNERS', 'docs/CODEOWNERS']
338
+ .filter((candidate) => (0, node_fs_1.existsSync)((0, node_path_1.join)(projectRoot, candidate)));
339
+ }
340
+ function parseCodeowners(projectRoot) {
341
+ const candidatePaths = discoverCodeownersFiles(projectRoot);
342
+ if (candidatePaths.length === 0)
343
+ return { boundaries: [], status: 'not_found' };
344
+ const allBoundaries = [];
345
+ for (const candidatePath of candidatePaths) {
346
+ const full = (0, node_path_1.join)(projectRoot, candidatePath);
347
+ try {
348
+ const parsed = (0, node_fs_1.readFileSync)(full, 'utf8')
349
+ .split(/\r?\n/)
350
+ .map((line) => line.trim())
351
+ .filter((line) => line && !line.startsWith('#'))
352
+ .map((line) => line.split(/\s+/))
353
+ .filter((parts) => parts.length >= 2)
354
+ .map((parts) => ({
355
+ pattern: parts[0],
356
+ owners: parts.slice(1).filter((owner) => owner.startsWith('@')),
357
+ source: 'CODEOWNERS',
358
+ }))
359
+ .filter((entry) => entry.owners.length > 0);
360
+ allBoundaries.push(...parsed);
361
+ }
362
+ catch {
363
+ // skip unreadable CODEOWNERS file
364
+ }
365
+ }
366
+ return { boundaries: allBoundaries, status: allBoundaries.length > 0 ? 'found' : 'not_found' };
367
+ }
368
+ function buildReuseFindings(symbols, options = {}) {
369
+ const { includeFingerprint = false } = options;
370
+ const findings = [];
371
+ const byName = new Map();
372
+ const byFingerprint = includeFingerprint ? new Map() : null;
373
+ for (const symbol of symbols) {
374
+ if (symbol.kind === 'method')
375
+ continue;
376
+ if (isTestPath(symbol.file))
377
+ continue;
378
+ byName.set(symbol.name, [...(byName.get(symbol.name) || []), symbol]);
379
+ if (byFingerprint && symbol.tokenFingerprintHash) {
380
+ byFingerprint.set(symbol.tokenFingerprintHash, [...(byFingerprint.get(symbol.tokenFingerprintHash) || []), symbol]);
381
+ }
382
+ }
383
+ for (const [name, group] of byName.entries()) {
384
+ const files = [...new Set(group.map((symbol) => symbol.file))].sort();
385
+ if (files.length < 2)
386
+ continue;
387
+ findings.push({
388
+ kind: 'symbol_name_reuse',
389
+ severity: group.some((symbol) => symbol.exported) ? 'warn' : 'info',
390
+ confidence: group.some((symbol) => symbol.exported) ? 'high' : 'medium',
391
+ symbolName: name,
392
+ files: files.slice(0, 12),
393
+ symbolCount: group.length,
394
+ reasonCodes: group.some((symbol) => symbol.exported)
395
+ ? ['same_symbol_name', 'exported_symbol_present', 'cross_file']
396
+ : ['same_symbol_name', 'cross_file'],
397
+ evidenceHash: shortHash(`name:${name}:${files.join('|')}`),
398
+ });
399
+ }
400
+ if (byFingerprint) {
401
+ for (const [fingerprint, group] of byFingerprint.entries()) {
402
+ const files = [...new Set(group.map((symbol) => symbol.file))].sort();
403
+ const names = [...new Set(group.map((symbol) => symbol.name))].sort();
404
+ if (files.length < 2 || names.length < 1)
405
+ continue;
406
+ findings.push({
407
+ kind: 'fingerprint_reuse',
408
+ severity: 'info',
409
+ confidence: 'low',
410
+ symbolName: names.length === 1 ? names[0] : null,
411
+ files: files.slice(0, 8),
412
+ symbolCount: group.length,
413
+ reasonCodes: ['same_token_fingerprint', 'cross_file', 'experimental'],
414
+ evidenceHash: shortHash(`fingerprint:${fingerprint}:${files.join('|')}`),
415
+ });
416
+ }
417
+ }
418
+ return findings
419
+ .sort((a, b) => {
420
+ const severityScore = (b.severity === 'warn' ? 1 : 0) - (a.severity === 'warn' ? 1 : 0);
421
+ if (severityScore !== 0)
422
+ return severityScore;
423
+ return b.symbolCount - a.symbolCount || a.evidenceHash.localeCompare(b.evidenceHash);
424
+ })
425
+ .slice(0, 50);
426
+ }
427
+ function buildModules(files) {
428
+ const map = new Map();
429
+ for (const file of files) {
430
+ const current = map.get(file.module) || {
431
+ name: file.module,
432
+ fileCount: 0,
433
+ symbolCount: 0,
434
+ importCount: 0,
435
+ sensitiveKinds: [],
436
+ };
437
+ current.fileCount += 1;
438
+ current.symbolCount += file.symbolCount;
439
+ current.importCount += file.importCount;
440
+ current.sensitiveKinds = [...new Set([...current.sensitiveKinds, ...file.sensitiveKinds])].sort();
441
+ map.set(file.module, current);
442
+ }
443
+ return [...map.values()].sort((a, b) => b.fileCount - a.fileCount || a.name.localeCompare(b.name));
444
+ }
445
+ function buildHotspots(files, imports) {
446
+ const fanIn = new Map();
447
+ const fanOut = new Map();
448
+ for (const edge of imports) {
449
+ fanOut.set(edge.fromFile, (fanOut.get(edge.fromFile) || 0) + 1);
450
+ if (edge.resolvedFile)
451
+ fanIn.set(edge.resolvedFile, (fanIn.get(edge.resolvedFile) || 0) + 1);
452
+ }
453
+ return files.filter((file) => !isTestPath(file.path)).map((file) => {
454
+ const inCount = fanIn.get(file.path) || 0;
455
+ const outCount = fanOut.get(file.path) || 0;
456
+ const reasons = [];
457
+ let score = 0;
458
+ if (inCount > 0) {
459
+ score += inCount * 8;
460
+ reasons.push('referenced_by_other_files');
461
+ }
462
+ if (outCount > 3) {
463
+ score += outCount * 2;
464
+ reasons.push('many_dependencies');
465
+ }
466
+ if (file.sensitiveKinds.length > 0) {
467
+ score += file.sensitiveKinds.length * 18;
468
+ reasons.push('sensitive_surface');
469
+ }
470
+ if (file.symbolCount > 8) {
471
+ score += file.symbolCount;
472
+ reasons.push('large_symbol_surface');
473
+ }
474
+ if (file.lineCount > 400) {
475
+ score += 10;
476
+ reasons.push('large_file');
477
+ }
478
+ return {
479
+ file: file.path,
480
+ score,
481
+ reasons,
482
+ importFanIn: inCount,
483
+ importFanOut: outCount,
484
+ symbolCount: file.symbolCount,
485
+ sensitiveKinds: file.sensitiveKinds,
486
+ };
487
+ }).filter((hotspot) => hotspot.score > 0)
488
+ .sort((a, b) => b.score - a.score || a.file.localeCompare(b.file))
489
+ .slice(0, 40);
490
+ }
491
+ function artifactHashInput(artifact) {
492
+ return JSON.stringify(artifact);
493
+ }
494
+ function localRepoBrainPath(projectRoot) {
495
+ return (0, node_path_1.join)(projectRoot, '.neurcode', 'repo-brain', 'index.json');
496
+ }
497
+ function localRepoBrainMarkdownPath(projectRoot) {
498
+ return (0, node_path_1.join)(projectRoot, '.neurcode', 'repo-brain', 'summary.md');
499
+ }
500
+ function buildLocalRepoBrain(projectRoot, options = {}) {
501
+ const generatedAt = options.generatedAt || new Date().toISOString();
502
+ const maxFiles = Math.max(1, Math.min(50000, options.maxFiles || 8000));
503
+ const maxBytesPerFile = Math.max(2048, Math.min(2_000_000, options.maxBytesPerFile || 350_000));
504
+ const candidates = listRepoFiles(projectRoot, maxFiles);
505
+ const fileSet = new Set(candidates);
506
+ const files = [];
507
+ const symbols = [];
508
+ const imports = [];
509
+ let filesSkipped = 0;
510
+ let generatedFilesSkipped = 0;
511
+ let bytesIndexed = 0;
512
+ for (const filePath of candidates) {
513
+ const full = (0, node_path_1.join)(projectRoot, filePath);
514
+ let stat;
515
+ try {
516
+ stat = (0, node_fs_1.statSync)(full);
517
+ }
518
+ catch {
519
+ filesSkipped += 1;
520
+ continue;
521
+ }
522
+ const generated = isGeneratedPath(filePath);
523
+ if (!stat.isFile() || stat.size > maxBytesPerFile || generated) {
524
+ filesSkipped += 1;
525
+ if (generated)
526
+ generatedFilesSkipped += 1;
527
+ continue;
528
+ }
529
+ let source = '';
530
+ try {
531
+ source = (0, node_fs_1.readFileSync)(full, 'utf8');
532
+ }
533
+ catch {
534
+ filesSkipped += 1;
535
+ continue;
536
+ }
537
+ const language = languageFor(filePath);
538
+ const fileSymbols = extractSymbols(filePath, source, language);
539
+ const fileImports = extractImports(filePath, source, language, fileSet);
540
+ bytesIndexed += stat.size;
541
+ symbols.push(...fileSymbols);
542
+ imports.push(...fileImports);
543
+ files.push({
544
+ path: filePath,
545
+ module: moduleKey(filePath),
546
+ language,
547
+ bytes: stat.size,
548
+ lineCount: source.split(/\r?\n/).length,
549
+ fileHash: shortHash(source),
550
+ symbolCount: fileSymbols.length,
551
+ importCount: fileImports.length,
552
+ sensitiveKinds: sensitiveKindsFor(filePath),
553
+ generated: false,
554
+ });
555
+ }
556
+ const modules = buildModules(files);
557
+ const { boundaries: ownerBoundaries, status: ownerBoundaryStatus } = parseCodeowners(projectRoot);
558
+ const reuseFindings = buildReuseFindings(symbols, { includeFingerprint: options.experimentalFingerprintReuse });
559
+ const hotspots = buildHotspots(files, imports);
560
+ const core = {
561
+ schemaVersion: exports.LOCAL_REPO_BRAIN_SCHEMA_VERSION,
562
+ repoRootHash: hash(projectRoot),
563
+ privacy: {
564
+ sourceUploaded: false,
565
+ sourceStored: false,
566
+ diffStored: false,
567
+ promptStored: false,
568
+ modelUsed: false,
569
+ storedFields: [
570
+ 'relative paths',
571
+ 'symbol names and kinds',
572
+ 'line numbers',
573
+ 'counts',
574
+ 'hashes and token fingerprints',
575
+ 'import targets',
576
+ 'CODEOWNERS owner tokens',
577
+ ],
578
+ },
579
+ summary: {
580
+ filesIndexed: files.length,
581
+ filesSkipped,
582
+ bytesIndexed,
583
+ symbolsIndexed: symbols.length,
584
+ importEdges: imports.length,
585
+ modules: modules.length,
586
+ sensitiveFiles: files.filter((file) => file.sensitiveKinds.length > 0).length,
587
+ ownerBoundaries: ownerBoundaries.length,
588
+ ownerBoundaryStatus,
589
+ reuseFindings: reuseFindings.length,
590
+ generatedFilesSkipped,
591
+ },
592
+ files: files.sort((a, b) => a.path.localeCompare(b.path)),
593
+ symbols: symbols.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.name.localeCompare(b.name)),
594
+ imports: imports.sort((a, b) => a.fromFile.localeCompare(b.fromFile) || a.line - b.line || a.target.localeCompare(b.target)),
595
+ modules,
596
+ ownerBoundaries,
597
+ reuseFindings,
598
+ hotspots,
599
+ limitations: [
600
+ 'V1 is deterministic and source-free; it does not store code bodies, raw diffs, raw prompts, or chat transcripts.',
601
+ 'TS/JS/Python symbols are indexed in V1; other languages are counted at file/module level unless future extractors are added.',
602
+ 'Reuse findings show same-name exported declarations across files; fingerprint-based detection is experimental and off by default.',
603
+ 'Import resolution is conservative and may miss dynamic imports, framework wiring, reflection, and generated code.',
604
+ ],
605
+ };
606
+ const artifactHash = hash(artifactHashInput(core));
607
+ return {
608
+ ...core,
609
+ generatedAt,
610
+ artifactHash,
611
+ };
612
+ }
613
+ function writeLocalRepoBrain(projectRoot, artifact) {
614
+ const jsonPath = localRepoBrainPath(projectRoot);
615
+ const markdownPath = localRepoBrainMarkdownPath(projectRoot);
616
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(jsonPath), { recursive: true });
617
+ (0, node_fs_1.writeFileSync)(jsonPath, JSON.stringify(artifact, null, 2) + '\n', 'utf8');
618
+ (0, node_fs_1.writeFileSync)(markdownPath, renderLocalRepoBrainMarkdown(artifact), 'utf8');
619
+ return { jsonPath, markdownPath };
620
+ }
621
+ function readLocalRepoBrain(projectRoot) {
622
+ const path = localRepoBrainPath(projectRoot);
623
+ if (!(0, node_fs_1.existsSync)(path))
624
+ return null;
625
+ try {
626
+ const parsed = JSON.parse((0, node_fs_1.readFileSync)(path, 'utf8'));
627
+ if (parsed?.schemaVersion !== exports.LOCAL_REPO_BRAIN_SCHEMA_VERSION)
628
+ return null;
629
+ return parsed;
630
+ }
631
+ catch {
632
+ return null;
633
+ }
634
+ }
635
+ function renderLocalRepoBrainMarkdown(artifact) {
636
+ const lines = [];
637
+ lines.push('# Neurcode Local Repo Brain');
638
+ lines.push('');
639
+ lines.push(`Generated: ${artifact.generatedAt}`);
640
+ lines.push(`Artifact hash: ${artifact.artifactHash}`);
641
+ lines.push('');
642
+ lines.push('## Source-free guarantee');
643
+ lines.push('- No source code is uploaded.');
644
+ lines.push('- No source code, raw diffs, raw prompts, or chat transcripts are stored in this artifact.');
645
+ lines.push('- Stored fields are paths, symbols, counts, hashes/fingerprints, import targets, and owner tokens.');
646
+ lines.push('');
647
+ lines.push('## Summary');
648
+ for (const [key, value] of Object.entries(artifact.summary)) {
649
+ lines.push(`- ${key}: ${value}`);
650
+ }
651
+ lines.push('');
652
+ lines.push('## Owner boundaries');
653
+ if (artifact.summary.ownerBoundaryStatus === 'not_found') {
654
+ lines.push('- No CODEOWNERS found; owner boundaries unavailable.');
655
+ }
656
+ else {
657
+ for (const boundary of artifact.ownerBoundaries.slice(0, 20)) {
658
+ lines.push(`- ${boundary.pattern}: ${boundary.owners.join(', ')}`);
659
+ }
660
+ }
661
+ lines.push('');
662
+ lines.push('## Top modules');
663
+ for (const module of artifact.modules.slice(0, 12)) {
664
+ lines.push(`- ${module.name}: ${module.fileCount} files, ${module.symbolCount} declarations, sensitive ${module.sensitiveKinds.join(', ') || 'none'}`);
665
+ }
666
+ lines.push('');
667
+ lines.push('## Hotspots');
668
+ for (const hotspot of artifact.hotspots.slice(0, 12)) {
669
+ lines.push(`- ${hotspot.file}: score ${hotspot.score}, fan-in ${hotspot.importFanIn}, fan-out ${hotspot.importFanOut}, reasons ${hotspot.reasons.join(', ')}`);
670
+ }
671
+ lines.push('');
672
+ lines.push('## Reuse advisories (same-name exported declarations)');
673
+ if (artifact.reuseFindings.length === 0) {
674
+ lines.push('- none found — no exported symbol names appear in more than one non-test file.');
675
+ }
676
+ else {
677
+ for (const finding of artifact.reuseFindings.slice(0, 12)) {
678
+ lines.push(`- ${finding.kind}: ${finding.symbolName || 'fingerprint'} across ${finding.files.join(', ')} (${finding.confidence} confidence)`);
679
+ }
680
+ }
681
+ lines.push('');
682
+ lines.push('## Limitations');
683
+ for (const limitation of artifact.limitations)
684
+ lines.push(`- ${limitation}`);
685
+ lines.push('');
686
+ return lines.join('\n');
687
+ }
688
+ function queryTokens(query) {
689
+ return query.toLowerCase().match(/[a-z0-9_./-]+/g) || [];
690
+ }
691
+ function scoreText(tokens, values) {
692
+ const haystack = values.join(' ').toLowerCase();
693
+ return tokens.reduce((score, token) => score + (haystack.includes(token) ? 1 : 0), 0);
694
+ }
695
+ const SEARCH_KIND_WEIGHT = {
696
+ symbol: 2.0,
697
+ file: 1.8,
698
+ hotspot: 1.4,
699
+ module: 1.2,
700
+ reuse: 0.6,
701
+ };
702
+ function matchesOwnerPattern(filePath, pattern) {
703
+ const norm = pattern.replace(/^\//, '');
704
+ if (norm.endsWith('/'))
705
+ return filePath.startsWith(norm) || filePath === norm.slice(0, -1);
706
+ return filePath === norm || filePath.startsWith(norm + '/');
707
+ }
708
+ function getRepoBrainContext(projectRoot, filePaths) {
709
+ const emptyFileFacts = (fp) => ({
710
+ filePath: fp,
711
+ sensitiveKinds: [],
712
+ module: null,
713
+ hotspot: null,
714
+ ownerBoundary: null,
715
+ reuseAdvisories: [],
716
+ });
717
+ const artifact = readLocalRepoBrain(projectRoot);
718
+ if (!artifact) {
719
+ return {
720
+ status: 'missing',
721
+ artifactHash: null,
722
+ generatedAt: null,
723
+ declarationsIndexed: null,
724
+ sensitiveFilesCount: null,
725
+ ownerBoundaryStatus: null,
726
+ recoveryCommand: 'neurcode brain index',
727
+ files: filePaths.map(emptyFileFacts),
728
+ };
729
+ }
730
+ const fileFacts = filePaths.map((fp) => {
731
+ const file = artifact.files.find((f) => f.path === fp);
732
+ const hotspot = artifact.hotspots.find((h) => h.file === fp) ?? null;
733
+ const ownerBoundary = artifact.ownerBoundaries.find((b) => matchesOwnerPattern(fp, b.pattern)) ?? null;
734
+ const reuseAdvisories = artifact.reuseFindings
735
+ .filter((r) => r.files.includes(fp))
736
+ .slice(0, 3)
737
+ .map((r) => ({ kind: r.kind, symbolName: r.symbolName ?? null, confidence: r.confidence, reasonCodes: r.reasonCodes }));
738
+ return {
739
+ filePath: fp,
740
+ sensitiveKinds: file?.sensitiveKinds ?? [],
741
+ module: file?.module ?? null,
742
+ hotspot: hotspot
743
+ ? { score: hotspot.score, fanIn: hotspot.importFanIn, fanOut: hotspot.importFanOut, reasons: hotspot.reasons }
744
+ : null,
745
+ ownerBoundary: ownerBoundary ? { pattern: ownerBoundary.pattern, owners: ownerBoundary.owners } : null,
746
+ reuseAdvisories,
747
+ };
748
+ });
749
+ return {
750
+ status: 'found',
751
+ artifactHash: artifact.artifactHash,
752
+ generatedAt: artifact.generatedAt,
753
+ declarationsIndexed: artifact.summary.symbolsIndexed,
754
+ sensitiveFilesCount: artifact.summary.sensitiveFiles,
755
+ ownerBoundaryStatus: artifact.summary.ownerBoundaryStatus,
756
+ recoveryCommand: 'neurcode brain index',
757
+ files: fileFacts,
758
+ };
759
+ }
760
+ function formatRepoBrainFactsForMessage(facts) {
761
+ const parts = [];
762
+ if (facts.sensitiveKinds.length > 0)
763
+ parts.push(`Sensitive: ${facts.sensitiveKinds.join(', ')}`);
764
+ if (facts.ownerBoundary)
765
+ parts.push(`CODEOWNERS: ${facts.ownerBoundary.owners.join(' ')}`);
766
+ if (facts.hotspot)
767
+ parts.push(`Hotspot fan-in: ${facts.hotspot.fanIn}`);
768
+ if (facts.reuseAdvisories.length > 0 && facts.reuseAdvisories[0].symbolName) {
769
+ parts.push(`Reuse advisory: ${facts.reuseAdvisories[0].symbolName}`);
770
+ }
771
+ return parts.join(' | ');
772
+ }
773
+ function searchLocalRepoBrain(artifact, query, limit = 12) {
774
+ const tokens = queryTokens(query);
775
+ if (tokens.length === 0)
776
+ return [];
777
+ const seen = new Set();
778
+ const results = [];
779
+ const addResult = (result) => {
780
+ const key = `${result.kind}|${result.title}|${result.file ?? ''}`;
781
+ if (seen.has(key))
782
+ return;
783
+ seen.add(key);
784
+ results.push(result);
785
+ };
786
+ for (const file of artifact.files) {
787
+ const score = scoreText(tokens, [file.path, file.module, file.language, ...file.sensitiveKinds]);
788
+ if (score > 0) {
789
+ addResult({
790
+ kind: 'file',
791
+ score,
792
+ title: file.path,
793
+ file: file.path,
794
+ detail: `${file.language}; ${file.symbolCount} declarations; sensitive ${file.sensitiveKinds.join(', ') || 'none'}`,
795
+ });
796
+ }
797
+ }
798
+ for (const symbol of artifact.symbols) {
799
+ const score = scoreText(tokens, [symbol.name, symbol.kind, symbol.file, symbol.language]);
800
+ if (score > 0) {
801
+ addResult({
802
+ kind: 'symbol',
803
+ score: score + (symbol.exported ? 0.5 : 0),
804
+ title: `${symbol.name} (${symbol.kind})`,
805
+ file: symbol.file,
806
+ detail: `${symbol.exported ? 'exported' : 'local'} ${symbol.language} symbol at line ${symbol.line}`,
807
+ });
808
+ }
809
+ }
810
+ for (const module of artifact.modules) {
811
+ const score = scoreText(tokens, [module.name, ...module.sensitiveKinds]);
812
+ if (score > 0) {
813
+ addResult({
814
+ kind: 'module',
815
+ score,
816
+ title: module.name,
817
+ file: null,
818
+ detail: `${module.fileCount} files; ${module.symbolCount} declarations; sensitive ${module.sensitiveKinds.join(', ') || 'none'}`,
819
+ });
820
+ }
821
+ }
822
+ for (const finding of artifact.reuseFindings) {
823
+ const score = scoreText(tokens, [finding.kind, finding.symbolName || '', ...finding.files, ...finding.reasonCodes]);
824
+ if (score > 0) {
825
+ addResult({
826
+ kind: 'reuse',
827
+ score: score + (finding.severity === 'warn' ? 1 : 0),
828
+ title: `${finding.kind}: ${finding.symbolName || 'fingerprint'}`,
829
+ file: finding.files[0] || null,
830
+ detail: `${finding.confidence} confidence across ${finding.files.length} files`,
831
+ });
832
+ }
833
+ }
834
+ for (const hotspot of artifact.hotspots) {
835
+ const score = scoreText(tokens, [hotspot.file, ...hotspot.reasons, ...hotspot.sensitiveKinds]);
836
+ if (score > 0) {
837
+ addResult({
838
+ kind: 'hotspot',
839
+ score: score + Math.min(2, hotspot.score / 50),
840
+ title: hotspot.file,
841
+ file: hotspot.file,
842
+ detail: `score ${hotspot.score}; fan-in ${hotspot.importFanIn}; reasons ${hotspot.reasons.join(', ')}`,
843
+ });
844
+ }
845
+ }
846
+ return results
847
+ .sort((a, b) => {
848
+ const aW = a.score * (SEARCH_KIND_WEIGHT[a.kind] ?? 1.0);
849
+ const bW = b.score * (SEARCH_KIND_WEIGHT[b.kind] ?? 1.0);
850
+ return bW - aW || a.title.localeCompare(b.title);
851
+ })
852
+ .slice(0, Math.max(1, Math.min(50, limit)));
853
+ }
854
+ //# sourceMappingURL=local-repo-brain.js.map