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.
- package/LICENSE +21 -0
- package/README.md +510 -0
- package/dist/advisor/advisor.d.ts +50 -0
- package/dist/advisor/advisor.js +178 -0
- package/dist/agents/base.d.ts +62 -0
- package/dist/agents/base.js +215 -0
- package/dist/agents/orchestrator.d.ts +46 -0
- package/dist/agents/orchestrator.js +192 -0
- package/dist/agents/team.d.ts +51 -0
- package/dist/agents/team.js +210 -0
- package/dist/api/models.d.ts +23 -0
- package/dist/api/models.js +514 -0
- package/dist/api/ollama.d.ts +153 -0
- package/dist/api/ollama.js +751 -0
- package/dist/api/openrouter.d.ts +55 -0
- package/dist/api/openrouter.js +223 -0
- package/dist/commands/commands.d.ts +43 -0
- package/dist/commands/commands.js +308 -0
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.js +311 -0
- package/dist/context/compaction.d.ts +54 -0
- package/dist/context/compaction.js +251 -0
- package/dist/context/custom-agents.d.ts +107 -0
- package/dist/context/custom-agents.js +397 -0
- package/dist/context/custom-tools.d.ts +120 -0
- package/dist/context/custom-tools.js +564 -0
- package/dist/context/git-checkpoint.d.ts +52 -0
- package/dist/context/git-checkpoint.js +240 -0
- package/dist/context/neuro-md.d.ts +48 -0
- package/dist/context/neuro-md.js +202 -0
- package/dist/context/neuroignore.d.ts +102 -0
- package/dist/context/neuroignore.js +441 -0
- package/dist/context/repo-map.d.ts +38 -0
- package/dist/context/repo-map.js +220 -0
- package/dist/context/skill-standard.d.ts +262 -0
- package/dist/context/skill-standard.js +1156 -0
- package/dist/context/skill-system.d.ts +75 -0
- package/dist/context/skill-system.js +578 -0
- package/dist/context/tree-sitter.d.ts +99 -0
- package/dist/context/tree-sitter.js +1956 -0
- package/dist/core/acp.d.ts +325 -0
- package/dist/core/acp.js +1498 -0
- package/dist/core/api-server.d.ts +143 -0
- package/dist/core/api-server.js +550 -0
- package/dist/core/approval.d.ts +81 -0
- package/dist/core/approval.js +432 -0
- package/dist/core/auto-compact.d.ts +127 -0
- package/dist/core/auto-compact.js +436 -0
- package/dist/core/auto-mode.d.ts +232 -0
- package/dist/core/auto-mode.js +831 -0
- package/dist/core/background-session.d.ts +166 -0
- package/dist/core/background-session.js +696 -0
- package/dist/core/cicd.d.ts +257 -0
- package/dist/core/cicd.js +1443 -0
- package/dist/core/cloud-sync.d.ts +156 -0
- package/dist/core/cloud-sync.js +582 -0
- package/dist/core/code-review.d.ts +132 -0
- package/dist/core/code-review.js +1191 -0
- package/dist/core/completion.d.ts +49 -0
- package/dist/core/completion.js +384 -0
- package/dist/core/context.d.ts +38 -0
- package/dist/core/context.js +144 -0
- package/dist/core/diff-preview.d.ts +35 -0
- package/dist/core/diff-preview.js +173 -0
- package/dist/core/doom-loop.d.ts +51 -0
- package/dist/core/doom-loop.js +179 -0
- package/dist/core/engine.d.ts +183 -0
- package/dist/core/engine.js +942 -0
- package/dist/core/extended-thinking.d.ts +103 -0
- package/dist/core/extended-thinking.js +269 -0
- package/dist/core/fallback.d.ts +54 -0
- package/dist/core/fallback.js +104 -0
- package/dist/core/git-worktree.d.ts +139 -0
- package/dist/core/git-worktree.js +614 -0
- package/dist/core/headless.d.ts +30 -0
- package/dist/core/headless.js +101 -0
- package/dist/core/i18n.d.ts +83 -0
- package/dist/core/i18n.js +739 -0
- package/dist/core/linting.d.ts +129 -0
- package/dist/core/linting.js +699 -0
- package/dist/core/model-router.d.ts +109 -0
- package/dist/core/model-router.js +581 -0
- package/dist/core/multi-model.d.ts +243 -0
- package/dist/core/multi-model.js +1099 -0
- package/dist/core/multi-session.d.ts +144 -0
- package/dist/core/multi-session.js +442 -0
- package/dist/core/multimodal.d.ts +125 -0
- package/dist/core/multimodal.js +286 -0
- package/dist/core/observability.d.ts +93 -0
- package/dist/core/observability.js +737 -0
- package/dist/core/os-sandbox.d.ts +122 -0
- package/dist/core/os-sandbox.js +1193 -0
- package/dist/core/outcome-grading.d.ts +228 -0
- package/dist/core/outcome-grading.js +1123 -0
- package/dist/core/output-styles.d.ts +57 -0
- package/dist/core/output-styles.js +382 -0
- package/dist/core/parallel-agents.d.ts +183 -0
- package/dist/core/parallel-agents.js +563 -0
- package/dist/core/plugin-bundle.d.ts +236 -0
- package/dist/core/plugin-bundle.js +887 -0
- package/dist/core/plugin-sdk.d.ts +139 -0
- package/dist/core/plugin-sdk.js +273 -0
- package/dist/core/prompt-cache.d.ts +163 -0
- package/dist/core/prompt-cache.js +599 -0
- package/dist/core/sandbox.d.ts +127 -0
- package/dist/core/sandbox.js +369 -0
- package/dist/core/scheduled-tasks.d.ts +151 -0
- package/dist/core/scheduled-tasks.js +502 -0
- package/dist/core/security-scanner.d.ts +160 -0
- package/dist/core/security-scanner.js +1494 -0
- package/dist/core/session.d.ts +83 -0
- package/dist/core/session.js +269 -0
- package/dist/core/shell-completion.d.ts +51 -0
- package/dist/core/shell-completion.js +674 -0
- package/dist/core/smart-monitor.d.ts +146 -0
- package/dist/core/smart-monitor.js +1199 -0
- package/dist/core/spec-driven.d.ts +233 -0
- package/dist/core/spec-driven.js +1485 -0
- package/dist/core/spending-warnings.d.ts +123 -0
- package/dist/core/spending-warnings.js +456 -0
- package/dist/core/sub-agent.d.ts +298 -0
- package/dist/core/sub-agent.js +1023 -0
- package/dist/core/telemetry.d.ts +157 -0
- package/dist/core/telemetry.js +412 -0
- package/dist/core/terminal-ux.d.ts +134 -0
- package/dist/core/terminal-ux.js +649 -0
- package/dist/core/testing.d.ts +146 -0
- package/dist/core/testing.js +1200 -0
- package/dist/core/types.d.ts +439 -0
- package/dist/core/types.js +6 -0
- package/dist/core/undo-redo.d.ts +112 -0
- package/dist/core/undo-redo.js +290 -0
- package/dist/core/updater.d.ts +159 -0
- package/dist/core/updater.js +608 -0
- package/dist/core/vim-mode.d.ts +151 -0
- package/dist/core/vim-mode.js +771 -0
- package/dist/core/voice.d.ts +137 -0
- package/dist/core/voice.js +538 -0
- package/dist/core/web-dashboard.d.ts +109 -0
- package/dist/core/web-dashboard.js +484 -0
- package/dist/hooks/hooks.d.ts +74 -0
- package/dist/hooks/hooks.js +160 -0
- package/dist/hooks/llm-evaluator.d.ts +165 -0
- package/dist/hooks/llm-evaluator.js +560 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1186 -0
- package/dist/lsp/lsp-manager.d.ts +63 -0
- package/dist/lsp/lsp-manager.js +351 -0
- package/dist/mcp/client.d.ts +133 -0
- package/dist/mcp/client.js +684 -0
- package/dist/mcp/mcp-apps.d.ts +70 -0
- package/dist/mcp/mcp-apps.js +1007 -0
- package/dist/tools/bash.d.ts +5 -0
- package/dist/tools/bash.js +195 -0
- package/dist/tools/browser.d.ts +92 -0
- package/dist/tools/browser.js +1570 -0
- package/dist/tools/extended.d.ts +6 -0
- package/dist/tools/extended.js +191 -0
- package/dist/tools/file.d.ts +10 -0
- package/dist/tools/file.js +382 -0
- package/dist/tools/github.d.ts +389 -0
- package/dist/tools/github.js +759 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +40 -0
- package/dist/tools/memory.d.ts +6 -0
- package/dist/tools/memory.js +197 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/web.d.ts +6 -0
- package/dist/tools/web.js +150 -0
- package/dist/ui/renderer.d.ts +97 -0
- package/dist/ui/renderer.js +279 -0
- package/dist/ui/theme.d.ts +27 -0
- package/dist/ui/theme.js +106 -0
- 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
|