neuro-cli 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +510 -0
  3. package/dist/advisor/advisor.d.ts +50 -0
  4. package/dist/advisor/advisor.js +178 -0
  5. package/dist/agents/base.d.ts +62 -0
  6. package/dist/agents/base.js +215 -0
  7. package/dist/agents/orchestrator.d.ts +46 -0
  8. package/dist/agents/orchestrator.js +192 -0
  9. package/dist/agents/team.d.ts +51 -0
  10. package/dist/agents/team.js +210 -0
  11. package/dist/api/models.d.ts +23 -0
  12. package/dist/api/models.js +514 -0
  13. package/dist/api/ollama.d.ts +153 -0
  14. package/dist/api/ollama.js +751 -0
  15. package/dist/api/openrouter.d.ts +55 -0
  16. package/dist/api/openrouter.js +223 -0
  17. package/dist/commands/commands.d.ts +43 -0
  18. package/dist/commands/commands.js +308 -0
  19. package/dist/config/config.d.ts +8 -0
  20. package/dist/config/config.js +311 -0
  21. package/dist/context/compaction.d.ts +54 -0
  22. package/dist/context/compaction.js +251 -0
  23. package/dist/context/custom-agents.d.ts +107 -0
  24. package/dist/context/custom-agents.js +397 -0
  25. package/dist/context/custom-tools.d.ts +120 -0
  26. package/dist/context/custom-tools.js +564 -0
  27. package/dist/context/git-checkpoint.d.ts +52 -0
  28. package/dist/context/git-checkpoint.js +240 -0
  29. package/dist/context/neuro-md.d.ts +48 -0
  30. package/dist/context/neuro-md.js +202 -0
  31. package/dist/context/neuroignore.d.ts +102 -0
  32. package/dist/context/neuroignore.js +441 -0
  33. package/dist/context/repo-map.d.ts +38 -0
  34. package/dist/context/repo-map.js +220 -0
  35. package/dist/context/skill-standard.d.ts +262 -0
  36. package/dist/context/skill-standard.js +1156 -0
  37. package/dist/context/skill-system.d.ts +75 -0
  38. package/dist/context/skill-system.js +578 -0
  39. package/dist/context/tree-sitter.d.ts +99 -0
  40. package/dist/context/tree-sitter.js +1956 -0
  41. package/dist/core/acp.d.ts +325 -0
  42. package/dist/core/acp.js +1498 -0
  43. package/dist/core/api-server.d.ts +143 -0
  44. package/dist/core/api-server.js +550 -0
  45. package/dist/core/approval.d.ts +81 -0
  46. package/dist/core/approval.js +432 -0
  47. package/dist/core/auto-compact.d.ts +127 -0
  48. package/dist/core/auto-compact.js +436 -0
  49. package/dist/core/auto-mode.d.ts +232 -0
  50. package/dist/core/auto-mode.js +831 -0
  51. package/dist/core/background-session.d.ts +166 -0
  52. package/dist/core/background-session.js +696 -0
  53. package/dist/core/cicd.d.ts +257 -0
  54. package/dist/core/cicd.js +1443 -0
  55. package/dist/core/cloud-sync.d.ts +156 -0
  56. package/dist/core/cloud-sync.js +582 -0
  57. package/dist/core/code-review.d.ts +132 -0
  58. package/dist/core/code-review.js +1191 -0
  59. package/dist/core/completion.d.ts +49 -0
  60. package/dist/core/completion.js +384 -0
  61. package/dist/core/context.d.ts +38 -0
  62. package/dist/core/context.js +144 -0
  63. package/dist/core/diff-preview.d.ts +35 -0
  64. package/dist/core/diff-preview.js +173 -0
  65. package/dist/core/doom-loop.d.ts +51 -0
  66. package/dist/core/doom-loop.js +179 -0
  67. package/dist/core/engine.d.ts +183 -0
  68. package/dist/core/engine.js +942 -0
  69. package/dist/core/extended-thinking.d.ts +103 -0
  70. package/dist/core/extended-thinking.js +269 -0
  71. package/dist/core/fallback.d.ts +54 -0
  72. package/dist/core/fallback.js +104 -0
  73. package/dist/core/git-worktree.d.ts +139 -0
  74. package/dist/core/git-worktree.js +614 -0
  75. package/dist/core/headless.d.ts +30 -0
  76. package/dist/core/headless.js +101 -0
  77. package/dist/core/i18n.d.ts +83 -0
  78. package/dist/core/i18n.js +739 -0
  79. package/dist/core/linting.d.ts +129 -0
  80. package/dist/core/linting.js +699 -0
  81. package/dist/core/model-router.d.ts +109 -0
  82. package/dist/core/model-router.js +581 -0
  83. package/dist/core/multi-model.d.ts +243 -0
  84. package/dist/core/multi-model.js +1099 -0
  85. package/dist/core/multi-session.d.ts +144 -0
  86. package/dist/core/multi-session.js +442 -0
  87. package/dist/core/multimodal.d.ts +125 -0
  88. package/dist/core/multimodal.js +286 -0
  89. package/dist/core/observability.d.ts +93 -0
  90. package/dist/core/observability.js +737 -0
  91. package/dist/core/os-sandbox.d.ts +122 -0
  92. package/dist/core/os-sandbox.js +1193 -0
  93. package/dist/core/outcome-grading.d.ts +228 -0
  94. package/dist/core/outcome-grading.js +1123 -0
  95. package/dist/core/output-styles.d.ts +57 -0
  96. package/dist/core/output-styles.js +382 -0
  97. package/dist/core/parallel-agents.d.ts +183 -0
  98. package/dist/core/parallel-agents.js +563 -0
  99. package/dist/core/plugin-bundle.d.ts +236 -0
  100. package/dist/core/plugin-bundle.js +887 -0
  101. package/dist/core/plugin-sdk.d.ts +139 -0
  102. package/dist/core/plugin-sdk.js +273 -0
  103. package/dist/core/prompt-cache.d.ts +163 -0
  104. package/dist/core/prompt-cache.js +599 -0
  105. package/dist/core/sandbox.d.ts +127 -0
  106. package/dist/core/sandbox.js +369 -0
  107. package/dist/core/scheduled-tasks.d.ts +151 -0
  108. package/dist/core/scheduled-tasks.js +502 -0
  109. package/dist/core/security-scanner.d.ts +160 -0
  110. package/dist/core/security-scanner.js +1494 -0
  111. package/dist/core/session.d.ts +83 -0
  112. package/dist/core/session.js +269 -0
  113. package/dist/core/shell-completion.d.ts +51 -0
  114. package/dist/core/shell-completion.js +674 -0
  115. package/dist/core/smart-monitor.d.ts +146 -0
  116. package/dist/core/smart-monitor.js +1199 -0
  117. package/dist/core/spec-driven.d.ts +233 -0
  118. package/dist/core/spec-driven.js +1485 -0
  119. package/dist/core/spending-warnings.d.ts +123 -0
  120. package/dist/core/spending-warnings.js +456 -0
  121. package/dist/core/sub-agent.d.ts +298 -0
  122. package/dist/core/sub-agent.js +1023 -0
  123. package/dist/core/telemetry.d.ts +157 -0
  124. package/dist/core/telemetry.js +412 -0
  125. package/dist/core/terminal-ux.d.ts +134 -0
  126. package/dist/core/terminal-ux.js +649 -0
  127. package/dist/core/testing.d.ts +146 -0
  128. package/dist/core/testing.js +1200 -0
  129. package/dist/core/types.d.ts +439 -0
  130. package/dist/core/types.js +6 -0
  131. package/dist/core/undo-redo.d.ts +112 -0
  132. package/dist/core/undo-redo.js +290 -0
  133. package/dist/core/updater.d.ts +159 -0
  134. package/dist/core/updater.js +608 -0
  135. package/dist/core/vim-mode.d.ts +151 -0
  136. package/dist/core/vim-mode.js +771 -0
  137. package/dist/core/voice.d.ts +137 -0
  138. package/dist/core/voice.js +538 -0
  139. package/dist/core/web-dashboard.d.ts +109 -0
  140. package/dist/core/web-dashboard.js +484 -0
  141. package/dist/hooks/hooks.d.ts +74 -0
  142. package/dist/hooks/hooks.js +160 -0
  143. package/dist/hooks/llm-evaluator.d.ts +165 -0
  144. package/dist/hooks/llm-evaluator.js +560 -0
  145. package/dist/index.d.ts +3 -0
  146. package/dist/index.js +1186 -0
  147. package/dist/lsp/lsp-manager.d.ts +63 -0
  148. package/dist/lsp/lsp-manager.js +351 -0
  149. package/dist/mcp/client.d.ts +133 -0
  150. package/dist/mcp/client.js +684 -0
  151. package/dist/mcp/mcp-apps.d.ts +70 -0
  152. package/dist/mcp/mcp-apps.js +1007 -0
  153. package/dist/tools/bash.d.ts +5 -0
  154. package/dist/tools/bash.js +195 -0
  155. package/dist/tools/browser.d.ts +92 -0
  156. package/dist/tools/browser.js +1570 -0
  157. package/dist/tools/extended.d.ts +6 -0
  158. package/dist/tools/extended.js +191 -0
  159. package/dist/tools/file.d.ts +10 -0
  160. package/dist/tools/file.js +382 -0
  161. package/dist/tools/github.d.ts +389 -0
  162. package/dist/tools/github.js +759 -0
  163. package/dist/tools/index.d.ts +9 -0
  164. package/dist/tools/index.js +40 -0
  165. package/dist/tools/memory.d.ts +6 -0
  166. package/dist/tools/memory.js +197 -0
  167. package/dist/tools/registry.d.ts +29 -0
  168. package/dist/tools/registry.js +64 -0
  169. package/dist/tools/web.d.ts +6 -0
  170. package/dist/tools/web.js +150 -0
  171. package/dist/ui/renderer.d.ts +97 -0
  172. package/dist/ui/renderer.js +279 -0
  173. package/dist/ui/theme.d.ts +27 -0
  174. package/dist/ui/theme.js +106 -0
  175. package/package.json +83 -0
@@ -0,0 +1,441 @@
1
+ // ============================================================
2
+ // NeuroCLI - .neuroignore System
3
+ // Specifies files the AI should not access, with gitignore-style
4
+ // pattern support, negation, caching, and auto-detection.
5
+ // ============================================================
6
+ import { readFileSync, existsSync, readdirSync } from 'fs';
7
+ import { join, relative, normalize, sep, isAbsolute } from 'path';
8
+ import { homedir } from 'os';
9
+ // -----------------------------------------------------------
10
+ // Default patterns
11
+ // -----------------------------------------------------------
12
+ const DEFAULT_IGNORED_PATTERNS = [
13
+ 'node_modules',
14
+ '.git',
15
+ '__pycache__',
16
+ '.env',
17
+ '.env.*',
18
+ '*.pyc',
19
+ '.DS_Store',
20
+ 'dist',
21
+ 'build',
22
+ '.next',
23
+ 'coverage',
24
+ '.cache',
25
+ '*.log',
26
+ '.turbo',
27
+ 'vendor',
28
+ 'target',
29
+ 'bin',
30
+ 'obj',
31
+ '.venv',
32
+ 'venv',
33
+ '.tox',
34
+ '.mypy_cache',
35
+ '.pytest_cache',
36
+ '*.min.js',
37
+ '*.min.css',
38
+ 'bundle.js',
39
+ 'package-lock.json',
40
+ 'yarn.lock',
41
+ 'pnpm-lock.yaml',
42
+ ];
43
+ // -----------------------------------------------------------
44
+ // NeuroIgnore class
45
+ // -----------------------------------------------------------
46
+ export class NeuroIgnore {
47
+ rules = [];
48
+ cache = new Map();
49
+ projectRoot;
50
+ loaded = false;
51
+ constructor(projectRoot) {
52
+ this.projectRoot = normalize(projectRoot);
53
+ }
54
+ // ---------------------------------------------------------
55
+ // Public API
56
+ // ---------------------------------------------------------
57
+ /**
58
+ * Load ignore rules from all sources:
59
+ * 1. Default patterns
60
+ * 2. ~/.neuro/ignore (global user rules)
61
+ * 3. .neuroignore (project-root rules)
62
+ */
63
+ load() {
64
+ this.rules = [];
65
+ this.cache.clear();
66
+ // 1. Built-in defaults
67
+ this.loadDefaultRules();
68
+ // 2. Global user ignore file: ~/.neuro/ignore
69
+ const globalIgnorePath = join(homedir(), '.neuro', 'ignore');
70
+ this.loadFromFile(globalIgnorePath);
71
+ // 3. Project-root .neuroignore
72
+ const projectIgnorePath = join(this.projectRoot, '.neuroignore');
73
+ this.loadFromFile(projectIgnorePath);
74
+ // 4. Auto-detect common directories that should be ignored
75
+ this.autoDetectIgnorableDirs();
76
+ this.loaded = true;
77
+ }
78
+ /**
79
+ * Check whether a given file path should be ignored.
80
+ * The path may be absolute or relative to projectRoot.
81
+ *
82
+ * Evaluation order matters: later rules override earlier ones.
83
+ * Negated patterns (prefixed with !) un-ignore previously matched paths.
84
+ */
85
+ isIgnored(filePath) {
86
+ this.ensureLoaded();
87
+ // Normalise to a relative posix-style path from project root
88
+ const relativePath = this.toRelativePosix(filePath);
89
+ // Check cache first
90
+ const cached = this.cache.get(relativePath);
91
+ if (cached !== undefined) {
92
+ return cached;
93
+ }
94
+ let ignored = false;
95
+ for (const rule of this.rules) {
96
+ if (rule.negated) {
97
+ // A negation rule can only un-ignore something that was already ignored
98
+ if (ignored && rule.regex.test(relativePath)) {
99
+ ignored = false;
100
+ }
101
+ }
102
+ else {
103
+ if (rule.regex.test(relativePath)) {
104
+ ignored = true;
105
+ }
106
+ }
107
+ }
108
+ this.cache.set(relativePath, ignored);
109
+ return ignored;
110
+ }
111
+ /**
112
+ * Filter an array of paths, removing those that are ignored.
113
+ */
114
+ filterPaths(paths) {
115
+ return paths.filter((p) => !this.isIgnored(p));
116
+ }
117
+ /**
118
+ * Dynamically add a rule at runtime.
119
+ */
120
+ addRule(pattern, source = '<runtime>') {
121
+ const negated = pattern.startsWith('!');
122
+ const cleanPattern = negated ? pattern.slice(1) : pattern;
123
+ const regex = this.patternToRegex(cleanPattern);
124
+ this.rules.push({
125
+ pattern,
126
+ negated,
127
+ regex,
128
+ source,
129
+ });
130
+ // Invalidate cache because rules changed
131
+ this.cache.clear();
132
+ }
133
+ /**
134
+ * Remove a rule by its original pattern string.
135
+ * Returns true if a rule was found and removed.
136
+ */
137
+ removeRule(pattern) {
138
+ const index = this.rules.findIndex((r) => r.pattern === pattern);
139
+ if (index === -1) {
140
+ return false;
141
+ }
142
+ this.rules.splice(index, 1);
143
+ this.cache.clear();
144
+ return true;
145
+ }
146
+ /**
147
+ * Return a shallow copy of the current rules list.
148
+ */
149
+ getRules() {
150
+ return [...this.rules];
151
+ }
152
+ /**
153
+ * Clear the result cache.
154
+ */
155
+ clearCache() {
156
+ this.cache.clear();
157
+ }
158
+ /**
159
+ * Print all active rules to stdout (useful for debugging).
160
+ */
161
+ printRules() {
162
+ const sources = new Set(this.rules.map((r) => r.source));
163
+ for (const source of sources) {
164
+ console.log(`\n[Source: ${source}]`);
165
+ for (const rule of this.rules.filter((r) => r.source === source)) {
166
+ const flag = rule.negated ? ' (NEGATED)' : '';
167
+ console.log(` ${rule.pattern}${flag}`);
168
+ }
169
+ }
170
+ console.log(`\nTotal rules: ${this.rules.length}, Cache entries: ${this.cache.size}`);
171
+ }
172
+ // ---------------------------------------------------------
173
+ // Static defaults accessor
174
+ // ---------------------------------------------------------
175
+ static DEFAULT_IGNORED = [...DEFAULT_IGNORED_PATTERNS];
176
+ // ---------------------------------------------------------
177
+ // Private helpers
178
+ // ---------------------------------------------------------
179
+ /**
180
+ * Load rules from a single file. Lines starting with # are comments.
181
+ * Blank lines are skipped. Trailing whitespace is trimmed.
182
+ */
183
+ loadFromFile(filePath) {
184
+ if (!existsSync(filePath)) {
185
+ return;
186
+ }
187
+ try {
188
+ const content = readFileSync(filePath, 'utf-8');
189
+ const lines = content.split(/\r?\n/);
190
+ for (let raw of lines) {
191
+ // Strip comments
192
+ const commentIdx = raw.indexOf('#');
193
+ if (commentIdx !== -1) {
194
+ raw = raw.slice(0, commentIdx);
195
+ }
196
+ const line = raw.trim();
197
+ if (line.length === 0) {
198
+ continue;
199
+ }
200
+ this.addRule(line, filePath);
201
+ }
202
+ }
203
+ catch {
204
+ // Silently skip unreadable files
205
+ }
206
+ }
207
+ /**
208
+ * Populate the default ignore rules.
209
+ */
210
+ loadDefaultRules() {
211
+ for (const pattern of DEFAULT_IGNORED_PATTERNS) {
212
+ this.addRule(pattern, '<defaults>');
213
+ }
214
+ }
215
+ /**
216
+ * Convert a gitignore-style glob pattern into a RegExp.
217
+ *
218
+ * Supported features:
219
+ * * matches anything except /
220
+ * ** matches anything including /
221
+ * ? matches any single character except /
222
+ * [abc] character class
223
+ * [a-z] character range
224
+ * {a,b} brace expansion (alternation)
225
+ * !prefix negation (handled separately before this method)
226
+ *
227
+ * A trailing / means the pattern only matches directories; we keep
228
+ * the regex flexible enough to match both for simplicity, but strip
229
+ * the trailing slash indicator.
230
+ */
231
+ patternToRegex(pattern) {
232
+ let p = pattern;
233
+ // Strip trailing slash (directory-only indicator) -- we match
234
+ // both files and directories for simplicity.
235
+ const dirOnly = p.endsWith('/');
236
+ if (dirOnly) {
237
+ p = p.slice(0, -1);
238
+ }
239
+ // Leading slash means anchored to project root
240
+ const anchored = p.startsWith('/');
241
+ if (anchored) {
242
+ p = p.slice(1);
243
+ }
244
+ // Escape regex-special characters (except those we handle as globs)
245
+ const ESCAPE_RE = /[.+^${}()|[\]\\]/g;
246
+ // We will build the regex manually
247
+ let regexStr = '';
248
+ let i = 0;
249
+ while (i < p.length) {
250
+ const ch = p[i];
251
+ if (ch === '*') {
252
+ // Check for **
253
+ if (i + 1 < p.length && p[i + 1] === '*') {
254
+ // /**/ or **/ or /**
255
+ if (i + 2 < p.length &&
256
+ p[i + 2] === '/') {
257
+ // /**/ -> match any path segment(s) including none
258
+ regexStr += '(?:/|/.+/)';
259
+ i += 3;
260
+ }
261
+ else if (i === 0 ||
262
+ p[i - 1] === '/') {
263
+ // ** at start or after / -> match any prefix
264
+ regexStr += '(?:.*)';
265
+ i += 2;
266
+ }
267
+ else {
268
+ // Embedded ** (not at segment boundary) -- treat as literal
269
+ regexStr += '\\*\\*';
270
+ i += 2;
271
+ }
272
+ }
273
+ else {
274
+ // Single * -> match anything except /
275
+ regexStr += '[^/]*';
276
+ i += 1;
277
+ }
278
+ }
279
+ else if (ch === '?') {
280
+ regexStr += '[^/]';
281
+ i += 1;
282
+ }
283
+ else if (ch === '[') {
284
+ // Character class -- find closing ]
285
+ const closeIdx = p.indexOf(']', i + 1);
286
+ if (closeIdx === -1) {
287
+ // No closing bracket -- escape
288
+ regexStr += '\\[';
289
+ i += 1;
290
+ }
291
+ else {
292
+ const classContent = p.slice(i + 1, closeIdx);
293
+ regexStr += '[' + classContent + ']';
294
+ i = closeIdx + 1;
295
+ }
296
+ }
297
+ else if (ch === '{') {
298
+ // Brace expansion {a,b,c} -> (?:a|b|c)
299
+ const closeIdx = p.indexOf('}', i + 1);
300
+ if (closeIdx === -1) {
301
+ regexStr += '\\{';
302
+ i += 1;
303
+ }
304
+ else {
305
+ const inner = p.slice(i + 1, closeIdx);
306
+ const alternatives = inner.split(',').map((alt) => this.escapeRegex(alt));
307
+ regexStr += '(?:' + alternatives.join('|') + ')';
308
+ i = closeIdx + 1;
309
+ }
310
+ }
311
+ else if (ESCAPE_RE.test(ch)) {
312
+ regexStr += '\\' + ch;
313
+ i += 1;
314
+ }
315
+ else {
316
+ regexStr += ch;
317
+ i += 1;
318
+ }
319
+ }
320
+ // Build final regex
321
+ // - If anchored, match from start of relative path
322
+ // - If not anchored, the pattern can match anywhere in the path
323
+ // (either as a complete segment or as a filename)
324
+ let finalRegex;
325
+ if (anchored) {
326
+ finalRegex = '^' + regexStr + '$';
327
+ }
328
+ else {
329
+ // Unanchored patterns match if:
330
+ // - the entire relative path matches the pattern directly, OR
331
+ // - the pattern matches a trailing segment (e.g. "dist" matches
332
+ // "foo/bar/dist" and "dist")
333
+ finalRegex =
334
+ '^(?:' +
335
+ '(?:.*/)?' +
336
+ regexStr +
337
+ ')$';
338
+ }
339
+ try {
340
+ return new RegExp(finalRegex);
341
+ }
342
+ catch {
343
+ // If the generated regex is invalid, fall back to a simple
344
+ // substring match so we never crash.
345
+ return new RegExp('^.*' + this.escapeRegex(pattern).replace(/\\\*/g, '.*') + '.*$');
346
+ }
347
+ }
348
+ /**
349
+ * Escape a string for use inside a RegExp.
350
+ */
351
+ escapeRegex(str) {
352
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
353
+ }
354
+ /**
355
+ * Ensure rules have been loaded before answering queries.
356
+ */
357
+ ensureLoaded() {
358
+ if (!this.loaded) {
359
+ this.load();
360
+ }
361
+ }
362
+ /**
363
+ * Convert a file path to a relative posix-style path from projectRoot.
364
+ * - Absolute paths are made relative to projectRoot.
365
+ * - Backslashes (Windows) are converted to forward slashes.
366
+ * - Leading ./ is stripped.
367
+ */
368
+ toRelativePosix(filePath) {
369
+ let rel;
370
+ if (isAbsolute(filePath)) {
371
+ rel = relative(this.projectRoot, filePath);
372
+ }
373
+ else {
374
+ rel = filePath;
375
+ }
376
+ // Normalise separators
377
+ rel = rel.split(sep).join('/');
378
+ // Strip leading ./
379
+ if (rel.startsWith('./')) {
380
+ rel = rel.slice(2);
381
+ }
382
+ return rel;
383
+ }
384
+ /**
385
+ * Auto-detect common directories in the project root that should
386
+ * typically be ignored (e.g. a large "vendor" or "dist" directory
387
+ * that was not already covered by default patterns).
388
+ */
389
+ autoDetectIgnorableDirs() {
390
+ const AUTODETECT_CANDIDATES = [
391
+ 'node_modules',
392
+ '.git',
393
+ '__pycache__',
394
+ '.cache',
395
+ '.turbo',
396
+ 'dist',
397
+ 'build',
398
+ '.next',
399
+ 'coverage',
400
+ 'vendor',
401
+ 'target',
402
+ 'bin',
403
+ 'obj',
404
+ '.venv',
405
+ 'venv',
406
+ '.tox',
407
+ '.mypy_cache',
408
+ '.pytest_cache',
409
+ '.sass-cache',
410
+ '.idea',
411
+ '.vscode',
412
+ 'out',
413
+ '.nuxt',
414
+ '.output',
415
+ '.svelte-kit',
416
+ '.angular',
417
+ 'Pods',
418
+ ];
419
+ // Only scan if the project root exists
420
+ if (!existsSync(this.projectRoot)) {
421
+ return;
422
+ }
423
+ try {
424
+ const entries = readdirSync(this.projectRoot, { withFileTypes: true });
425
+ const existingDirs = new Set(entries.filter((e) => e.isDirectory()).map((e) => e.name));
426
+ for (const candidate of AUTODETECT_CANDIDATES) {
427
+ if (existingDirs.has(candidate)) {
428
+ // Check whether we already have a rule for this directory
429
+ const alreadyCovered = this.rules.some((r) => !r.negated && (r.pattern === candidate || r.pattern === candidate + '/'));
430
+ if (!alreadyCovered) {
431
+ this.addRule(candidate, '<auto-detect>');
432
+ }
433
+ }
434
+ }
435
+ }
436
+ catch {
437
+ // Permission error or similar -- skip auto-detection
438
+ }
439
+ }
440
+ }
441
+ //# sourceMappingURL=neuroignore.js.map
@@ -0,0 +1,38 @@
1
+ export interface RepoMapEntry {
2
+ file: string;
3
+ language: string;
4
+ definitions: string[];
5
+ references: string[];
6
+ lineCount: number;
7
+ }
8
+ export interface RepoMap {
9
+ entries: RepoMapEntry[];
10
+ totalFiles: number;
11
+ totalLines: number;
12
+ languages: Record<string, number>;
13
+ summary: string;
14
+ }
15
+ export declare class RepositoryMapper {
16
+ private workingDirectory;
17
+ private cached;
18
+ constructor(workingDirectory: string);
19
+ /**
20
+ * Build a repository map
21
+ */
22
+ build(maxFiles?: number): RepoMap;
23
+ /**
24
+ * Get the repo map as a compact string for LLM context
25
+ */
26
+ getContextString(maxEntries?: number): string;
27
+ /**
28
+ * Invalidate cache
29
+ */
30
+ invalidate(): void;
31
+ private getSourceFiles;
32
+ private mapFile;
33
+ private extractDefinitions;
34
+ private extractReferences;
35
+ private getLanguage;
36
+ private buildSummary;
37
+ }
38
+ //# sourceMappingURL=repo-map.d.ts.map
@@ -0,0 +1,220 @@
1
+ // ============================================================
2
+ // NeuroCLI - Repository Map
3
+ // Semantic codebase context (like Aider's repo map)
4
+ // Builds a structural map of identifiers for large codebases
5
+ // ============================================================
6
+ import { execSync } from 'child_process';
7
+ import { existsSync, readFileSync } from 'fs';
8
+ import { join, extname, relative } from 'path';
9
+ export class RepositoryMapper {
10
+ workingDirectory;
11
+ cached = null;
12
+ constructor(workingDirectory) {
13
+ this.workingDirectory = workingDirectory;
14
+ }
15
+ /**
16
+ * Build a repository map
17
+ */
18
+ build(maxFiles = 200) {
19
+ if (this.cached)
20
+ return this.cached;
21
+ const entries = [];
22
+ const languages = {};
23
+ let totalLines = 0;
24
+ // Get list of source files using ripgrep or git
25
+ const sourceFiles = this.getSourceFiles(maxFiles);
26
+ for (const file of sourceFiles) {
27
+ try {
28
+ const entry = this.mapFile(file);
29
+ if (entry) {
30
+ entries.push(entry);
31
+ totalLines += entry.lineCount;
32
+ languages[entry.language] = (languages[entry.language] || 0) + 1;
33
+ }
34
+ }
35
+ catch { }
36
+ }
37
+ const summary = this.buildSummary(entries, languages, totalLines);
38
+ this.cached = {
39
+ entries,
40
+ totalFiles: entries.length,
41
+ totalLines,
42
+ languages,
43
+ summary,
44
+ };
45
+ return this.cached;
46
+ }
47
+ /**
48
+ * Get the repo map as a compact string for LLM context
49
+ */
50
+ getContextString(maxEntries = 100) {
51
+ const map = this.build();
52
+ const lines = [];
53
+ lines.push(`Repository Map: ${map.totalFiles} files, ${map.totalLines} lines`);
54
+ lines.push(`Languages: ${Object.entries(map.languages).map(([l, c]) => `${l}(${c})`).join(', ')}`);
55
+ lines.push('');
56
+ const entries = map.entries.slice(0, maxEntries);
57
+ for (const entry of entries) {
58
+ const relPath = relative(this.workingDirectory, entry.file);
59
+ const defs = entry.definitions.slice(0, 10).join(', ');
60
+ lines.push(`${relPath} (${entry.language}, ${entry.lineCount} lines)${defs ? `: ${defs}` : ''}`);
61
+ }
62
+ if (map.entries.length > maxEntries) {
63
+ lines.push(`... and ${map.entries.length - maxEntries} more files`);
64
+ }
65
+ return lines.join('\n');
66
+ }
67
+ /**
68
+ * Invalidate cache
69
+ */
70
+ invalidate() {
71
+ this.cached = null;
72
+ }
73
+ // ---- Private Methods ----
74
+ getSourceFiles(maxFiles) {
75
+ const extensions = new Set([
76
+ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
77
+ '.py', '.pyx', '.pyi',
78
+ '.rs', '.go', '.java', '.kt', '.swift', '.c', '.cpp', '.h', '.hpp',
79
+ '.rb', '.php', '.cs', '.scala', '.clj',
80
+ '.sh', '.bash', '.zsh',
81
+ '.sql', '.graphql', '.prisma',
82
+ '.html', '.css', '.scss', '.less', '.vue', '.svelte',
83
+ '.yaml', '.yml', '.toml', '.json', '.json5',
84
+ '.md', '.txt',
85
+ ]);
86
+ try {
87
+ // Use ripgrep for fast file discovery
88
+ const result = execSync(`rg --files --max-count ${maxFiles} --color never "${this.workingDirectory}"`, { encoding: 'utf-8', timeout: 10000, cwd: this.workingDirectory });
89
+ return result.trim().split('\n').filter(f => {
90
+ const ext = extname(f);
91
+ return extensions.has(ext);
92
+ }).slice(0, maxFiles);
93
+ }
94
+ catch {
95
+ // Fallback: use git ls-files
96
+ try {
97
+ const result = execSync('git ls-files', {
98
+ encoding: 'utf-8',
99
+ cwd: this.workingDirectory,
100
+ timeout: 10000,
101
+ });
102
+ return result.trim().split('\n')
103
+ .filter(f => extensions.has(extname(f)))
104
+ .map(f => join(this.workingDirectory, f))
105
+ .slice(0, maxFiles);
106
+ }
107
+ catch {
108
+ return [];
109
+ }
110
+ }
111
+ }
112
+ mapFile(filePath) {
113
+ if (!existsSync(filePath))
114
+ return null;
115
+ const ext = extname(filePath);
116
+ const language = this.getLanguage(ext);
117
+ const content = readFileSync(filePath, 'utf-8');
118
+ const lineCount = content.split('\n').length;
119
+ // Extract definitions based on language
120
+ const definitions = this.extractDefinitions(content, language);
121
+ const references = this.extractReferences(content, language);
122
+ return {
123
+ file: filePath,
124
+ language,
125
+ definitions,
126
+ references,
127
+ lineCount,
128
+ };
129
+ }
130
+ extractDefinitions(content, language) {
131
+ const defs = [];
132
+ const maxDefs = 15;
133
+ const patterns = {
134
+ 'TypeScript': [
135
+ /^(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+(\w+)/gm,
136
+ /^(?:export\s+)?(?:default\s+)?class\s+(\w+)/gm,
137
+ /^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*[:=]/gm,
138
+ /^(?:export\s+)?interface\s+(\w+)/gm,
139
+ /^(?:export\s+)?type\s+(\w+)\s*=/gm,
140
+ /^(?:export\s+)?enum\s+(\w+)/gm,
141
+ ],
142
+ 'Python': [
143
+ /^(?:async\s+)?def\s+(\w+)/gm,
144
+ /^class\s+(\w+)/gm,
145
+ /^(\w+)\s*=\s*/gm,
146
+ ],
147
+ 'Rust': [
148
+ /^(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/gm,
149
+ /^(?:pub\s+)?struct\s+(\w+)/gm,
150
+ /^(?:pub\s+)?enum\s+(\w+)/gm,
151
+ /^(?:pub\s+)?trait\s+(\w+)/gm,
152
+ /^(?:pub\s+)?impl\s+(\w+)/gm,
153
+ ],
154
+ 'Go': [
155
+ /^func\s+(?:\(\w+\s+\*?\w+\)\s+)?(\w+)/gm,
156
+ /^type\s+(\w+)\s+struct/gm,
157
+ /^type\s+(\w+)\s+interface/gm,
158
+ /^var\s+(\w+)\s*=/gm,
159
+ ],
160
+ 'Java': [
161
+ /^(?:public|private|protected)?\s*(?:static\s+)?(?:final\s+)?(?:abstract\s+)?class\s+(\w+)/gm,
162
+ /^(?:public|private|protected)?\s*(?:static\s+)?(?:final\s+)?(?:abstract\s+)?interface\s+(\w+)/gm,
163
+ /^(?:public|private|protected)?\s*(?:static\s+)?(?:final\s+)?(?:abstract\s+)?(?:synchronized\s+)?(?:\w+(?:<[^>]+>)?\s+)+(\w+)\s*\(/gm,
164
+ ],
165
+ };
166
+ const langPatterns = patterns[language] || patterns['TypeScript'] || [];
167
+ for (const pattern of langPatterns) {
168
+ let match;
169
+ while ((match = pattern.exec(content)) !== null && defs.length < maxDefs) {
170
+ const name = match[1];
171
+ if (name && !defs.includes(name)) {
172
+ defs.push(name);
173
+ }
174
+ }
175
+ }
176
+ return defs;
177
+ }
178
+ extractReferences(content, language) {
179
+ // Simple import/require detection
180
+ const refs = [];
181
+ const importRegex = /(?:import|require|from|use|include)\s+['"]([^'"]+)['"]/g;
182
+ let match;
183
+ while ((match = importRegex.exec(content)) !== null) {
184
+ if (match[1] && refs.length < 20) {
185
+ refs.push(match[1]);
186
+ }
187
+ }
188
+ return refs;
189
+ }
190
+ getLanguage(ext) {
191
+ const map = {
192
+ '.ts': 'TypeScript', '.tsx': 'TypeScript',
193
+ '.js': 'JavaScript', '.jsx': 'JavaScript', '.mjs': 'JavaScript',
194
+ '.py': 'Python', '.pyx': 'Python', '.pyi': 'Python',
195
+ '.rs': 'Rust', '.go': 'Go', '.java': 'Java', '.kt': 'Kotlin',
196
+ '.swift': 'Swift', '.c': 'C', '.cpp': 'C++', '.h': 'C', '.hpp': 'C++',
197
+ '.rb': 'Ruby', '.php': 'PHP', '.cs': 'C#',
198
+ '.sh': 'Shell', '.bash': 'Shell',
199
+ '.sql': 'SQL', '.graphql': 'GraphQL',
200
+ '.html': 'HTML', '.css': 'CSS', '.scss': 'CSS',
201
+ '.yaml': 'YAML', '.yml': 'YAML', '.toml': 'TOML',
202
+ '.json': 'JSON', '.md': 'Markdown',
203
+ };
204
+ return map[ext] || 'Unknown';
205
+ }
206
+ buildSummary(entries, languages, totalLines) {
207
+ const topLangs = Object.entries(languages)
208
+ .sort(([, a], [, b]) => b - a)
209
+ .slice(0, 5)
210
+ .map(([l, c]) => `${l}(${c} files)`)
211
+ .join(', ');
212
+ const topFiles = entries
213
+ .sort((a, b) => b.lineCount - a.lineCount)
214
+ .slice(0, 10)
215
+ .map(e => `${relative(this.workingDirectory, e.file)} (${e.lineCount} lines)`)
216
+ .join(', ');
217
+ return `Repository: ${entries.length} files, ${totalLines} lines. Languages: ${topLangs}. Largest files: ${topFiles}`;
218
+ }
219
+ }
220
+ //# sourceMappingURL=repo-map.js.map