@thegitai/cli 1.0.0-beta.1
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 +20 -0
- package/README.md +30 -0
- package/dist/bin/ai.js +438 -0
- package/dist/parsers/tree-sitter-c-sharp.wasm +0 -0
- package/dist/parsers/tree-sitter-c.wasm +0 -0
- package/dist/parsers/tree-sitter-cpp.wasm +0 -0
- package/dist/parsers/tree-sitter-css.wasm +0 -0
- package/dist/parsers/tree-sitter-go.wasm +0 -0
- package/dist/parsers/tree-sitter-html.wasm +0 -0
- package/dist/parsers/tree-sitter-java.wasm +0 -0
- package/dist/parsers/tree-sitter-javascript.wasm +0 -0
- package/dist/parsers/tree-sitter-objc.wasm +0 -0
- package/dist/parsers/tree-sitter-php.wasm +0 -0
- package/dist/parsers/tree-sitter-python.wasm +0 -0
- package/dist/parsers/tree-sitter-ruby.wasm +0 -0
- package/dist/parsers/tree-sitter-rust.wasm +0 -0
- package/dist/parsers/tree-sitter-tsx.wasm +0 -0
- package/dist/parsers/tree-sitter-typescript.wasm +0 -0
- package/dist/src/agent-mode.js +142 -0
- package/dist/src/api/auth.js +81 -0
- package/dist/src/api/browser-login.js +184 -0
- package/dist/src/api/chat.js +346 -0
- package/dist/src/api/contracts.js +1 -0
- package/dist/src/api/http.js +44 -0
- package/dist/src/api/index.js +11 -0
- package/dist/src/api/models.js +110 -0
- package/dist/src/api/sessions.js +72 -0
- package/dist/src/artifact-policy.js +207 -0
- package/dist/src/client-state.js +14 -0
- package/dist/src/core/clipboard.js +208 -0
- package/dist/src/core/open-url.js +32 -0
- package/dist/src/edit-journal.js +133 -0
- package/dist/src/executor.js +924 -0
- package/dist/src/extractors/cpp.js +18 -0
- package/dist/src/extractors/csharp.js +16 -0
- package/dist/src/extractors/css.js +12 -0
- package/dist/src/extractors/go.js +27 -0
- package/dist/src/extractors/index.js +52 -0
- package/dist/src/extractors/java.js +14 -0
- package/dist/src/extractors/javascript.js +33 -0
- package/dist/src/extractors/objc.js +14 -0
- package/dist/src/extractors/php.js +20 -0
- package/dist/src/extractors/python.js +11 -0
- package/dist/src/extractors/ruby.js +13 -0
- package/dist/src/extractors/rust.js +17 -0
- package/dist/src/extractors/utils.js +58 -0
- package/dist/src/help-text.js +125 -0
- package/dist/src/markdown-renderer.js +112 -0
- package/dist/src/patcher.js +279 -0
- package/dist/src/project-index.js +221 -0
- package/dist/src/repo-map-languages.js +100 -0
- package/dist/src/runtime-mode.js +35 -0
- package/dist/src/scanner.js +362 -0
- package/dist/src/secret-preview.js +137 -0
- package/dist/src/session-exit.js +17 -0
- package/dist/src/session-safety.js +1012 -0
- package/dist/src/session-store.js +266 -0
- package/dist/src/session.js +93 -0
- package/dist/src/tool-executor.js +188 -0
- package/dist/src/tools/code-intel.js +472 -0
- package/dist/src/tools/delete-file.js +27 -0
- package/dist/src/tools/exec-utils.js +17 -0
- package/dist/src/tools/find-symbol.js +70 -0
- package/dist/src/tools/get-diagnostics.js +22 -0
- package/dist/src/tools/grep-code.js +331 -0
- package/dist/src/tools/hover-symbol.js +95 -0
- package/dist/src/tools/index.js +73 -0
- package/dist/src/tools/list-checkpoints.js +11 -0
- package/dist/src/tools/list-directories.js +16 -0
- package/dist/src/tools/list-files.js +13 -0
- package/dist/src/tools/list-session-edits.js +9 -0
- package/dist/src/tools/list-symbols.js +55 -0
- package/dist/src/tools/patch-file.js +88 -0
- package/dist/src/tools/path-listing.js +83 -0
- package/dist/src/tools/read-document.js +111 -0
- package/dist/src/tools/read-file.js +109 -0
- package/dist/src/tools/restore-checkpoint.js +100 -0
- package/dist/src/tools/ripgrep.js +29 -0
- package/dist/src/tools/run-command.js +94 -0
- package/dist/src/tools/run-node-script.js +210 -0
- package/dist/src/tools/search-code.js +37 -0
- package/dist/src/tools/shell-diagnostics.js +707 -0
- package/dist/src/tools/signature-help.js +118 -0
- package/dist/src/tools/str-replace.js +193 -0
- package/dist/src/tools/types.js +1 -0
- package/dist/src/tools/undo-edit.js +202 -0
- package/dist/src/tools/write-file.js +59 -0
- package/dist/src/tree-sitter-runtime.js +135 -0
- package/dist/src/types.js +1 -0
- package/dist/src/ui/paste-collapse.js +22 -0
- package/dist/src/ui/prompt-history-store.js +96 -0
- package/dist/src/ui/repl.js +2238 -0
- package/dist/src/ui/tui/bridge.js +175 -0
- package/dist/src/ui/tui/build-frame.js +718 -0
- package/dist/src/ui/tui/markdown-render.js +455 -0
- package/dist/src/ui/tui/shell-input.js +488 -0
- package/dist/src/ui/tui/text.js +30 -0
- package/dist/src/ui/tui/types.js +1 -0
- package/dist/src/usage.js +47 -0
- package/dist/src/utils.js +38 -0
- package/package.json +38 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import { ALWAYS_IGNORE_DIRS, listProjectFiles as listScannerProjectFiles, shouldIgnorePath, } from '../scanner.js';
|
|
6
|
+
import { normalizeProjectRelativePath } from '../artifact-policy.js';
|
|
7
|
+
import { getRipgrepPathCandidates, isRipgrepExecutionUnavailable, } from './ripgrep.js';
|
|
8
|
+
import { getCommandExitCode } from './exec-utils.js';
|
|
9
|
+
const MAX_RIPGREP_BUFFER = 2 * 1024 * 1024;
|
|
10
|
+
const MAX_RIPGREP_TIMEOUT_MS = 10000;
|
|
11
|
+
const DEFAULT_PROJECT_FILE_LIMIT = 400;
|
|
12
|
+
const MAX_FALLBACK_FILES_TO_SCAN = 2000;
|
|
13
|
+
const execFileAsync = promisify(execFile);
|
|
14
|
+
const SOURCE_FILE_EXTENSIONS = new Set([
|
|
15
|
+
'.c',
|
|
16
|
+
'.cc',
|
|
17
|
+
'.cpp',
|
|
18
|
+
'.cs',
|
|
19
|
+
'.css',
|
|
20
|
+
'.cts',
|
|
21
|
+
'.cxx',
|
|
22
|
+
'.go',
|
|
23
|
+
'.h',
|
|
24
|
+
'.hpp',
|
|
25
|
+
'.html',
|
|
26
|
+
'.java',
|
|
27
|
+
'.js',
|
|
28
|
+
'.jsx',
|
|
29
|
+
'.kt',
|
|
30
|
+
'.kts',
|
|
31
|
+
'.m',
|
|
32
|
+
'.mjml',
|
|
33
|
+
'.mm',
|
|
34
|
+
'.mts',
|
|
35
|
+
'.php',
|
|
36
|
+
'.py',
|
|
37
|
+
'.rb',
|
|
38
|
+
'.rs',
|
|
39
|
+
'.swift',
|
|
40
|
+
'.ts',
|
|
41
|
+
'.tsx',
|
|
42
|
+
'.vue',
|
|
43
|
+
]);
|
|
44
|
+
const DEF_KEYWORDS = [
|
|
45
|
+
'def ',
|
|
46
|
+
'async def ',
|
|
47
|
+
'function ',
|
|
48
|
+
'async function ',
|
|
49
|
+
'class ',
|
|
50
|
+
'const ',
|
|
51
|
+
'let ',
|
|
52
|
+
'var ',
|
|
53
|
+
'interface ',
|
|
54
|
+
'type ',
|
|
55
|
+
'fn ',
|
|
56
|
+
'func ',
|
|
57
|
+
'pub fn ',
|
|
58
|
+
'pub async fn ',
|
|
59
|
+
'export function ',
|
|
60
|
+
'export async function ',
|
|
61
|
+
'export const ',
|
|
62
|
+
'export class ',
|
|
63
|
+
'export interface ',
|
|
64
|
+
'export type ',
|
|
65
|
+
'export default function ',
|
|
66
|
+
'export default class ',
|
|
67
|
+
'struct ',
|
|
68
|
+
'pub struct ',
|
|
69
|
+
'enum ',
|
|
70
|
+
'pub enum ',
|
|
71
|
+
'trait ',
|
|
72
|
+
'pub trait ',
|
|
73
|
+
'impl ',
|
|
74
|
+
'pub mod ',
|
|
75
|
+
'mod ',
|
|
76
|
+
];
|
|
77
|
+
const SYMBOL_PATTERNS = [
|
|
78
|
+
{
|
|
79
|
+
regex: /^\s*(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/,
|
|
80
|
+
kind: 'function',
|
|
81
|
+
nameGroup: 1,
|
|
82
|
+
},
|
|
83
|
+
{ regex: /^\s*(?:pub\s+)?struct\s+(\w+)/, kind: 'struct', nameGroup: 1 },
|
|
84
|
+
{ regex: /^\s*(?:pub\s+)?enum\s+(\w+)/, kind: 'enum', nameGroup: 1 },
|
|
85
|
+
{ regex: /^\s*(?:pub\s+)?trait\s+(\w+)/, kind: 'interface', nameGroup: 1 },
|
|
86
|
+
{ regex: /^\s*(?:pub\s+)?type\s+(\w+)/, kind: 'type', nameGroup: 1 },
|
|
87
|
+
{
|
|
88
|
+
regex: /^\s*(?:pub\s+)?(?:static|const)\s+(\w+)\s*:/,
|
|
89
|
+
kind: 'constant',
|
|
90
|
+
nameGroup: 1,
|
|
91
|
+
},
|
|
92
|
+
{ regex: /^\s*impl(?:<[^>]*>)?\s+(\w+)/, kind: 'class', nameGroup: 1 },
|
|
93
|
+
{ regex: /^\s*(?:pub\s+)?mod\s+(\w+)/, kind: 'module', nameGroup: 1 },
|
|
94
|
+
{ regex: /^func\s+(?:\([^)]+\)\s+)?(\w+)/, kind: 'function', nameGroup: 1 },
|
|
95
|
+
{ regex: /^type\s+(\w+)\s+struct\b/, kind: 'struct', nameGroup: 1 },
|
|
96
|
+
{ regex: /^type\s+(\w+)\s+interface\b/, kind: 'interface', nameGroup: 1 },
|
|
97
|
+
{ regex: /^type\s+(\w+)\b/, kind: 'type', nameGroup: 1 },
|
|
98
|
+
{ regex: /^var\s+(\w+)\b/, kind: 'variable', nameGroup: 1 },
|
|
99
|
+
{ regex: /^const\s+(\w+)\b/, kind: 'constant', nameGroup: 1 },
|
|
100
|
+
{ regex: /^\s*(?:async\s+)?def\s+(\w+)/, kind: 'function', nameGroup: 1 },
|
|
101
|
+
{ regex: /^\s*class\s+(\w+)/, kind: 'class', nameGroup: 1 },
|
|
102
|
+
{
|
|
103
|
+
regex: /^\s*(?:export\s+)?(?:default\s+)?(?:abstract\s+)?class\s+(\w+)/,
|
|
104
|
+
kind: 'class',
|
|
105
|
+
nameGroup: 1,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
regex: /^\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s*\*?\s+(\w+)/,
|
|
109
|
+
kind: 'function',
|
|
110
|
+
nameGroup: 1,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
regex: /^\s*(?:export\s+)?interface\s+(\w+)/,
|
|
114
|
+
kind: 'interface',
|
|
115
|
+
nameGroup: 1,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
regex: /^\s*(?:export\s+)?type\s+(\w+)\s*[<=]/,
|
|
119
|
+
kind: 'type',
|
|
120
|
+
nameGroup: 1,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
regex: /^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*(?::\s*\S+\s*)?=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>/,
|
|
124
|
+
kind: 'function',
|
|
125
|
+
nameGroup: 1,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
regex: /^\s*(?:export\s+)?const\s+(\w+)\s*[=:]/,
|
|
129
|
+
kind: 'constant',
|
|
130
|
+
nameGroup: 1,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
regex: /^\s*(?:export\s+)?(?:let|var)\s+(\w+)\b/,
|
|
134
|
+
kind: 'variable',
|
|
135
|
+
nameGroup: 1,
|
|
136
|
+
},
|
|
137
|
+
{ regex: /^\s*(?:export\s+)?enum\s+(\w+)/, kind: 'enum', nameGroup: 1 },
|
|
138
|
+
];
|
|
139
|
+
function escapeRegExp(value) {
|
|
140
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
141
|
+
}
|
|
142
|
+
function isLikelySourceFile(filePath) {
|
|
143
|
+
return SOURCE_FILE_EXTENSIONS.has(path.extname(filePath).toLowerCase());
|
|
144
|
+
}
|
|
145
|
+
export function normalizeProjectFilePath(rootDir, filePath) {
|
|
146
|
+
if (!filePath)
|
|
147
|
+
return filePath;
|
|
148
|
+
const relativePath = normalizeProjectRelativePath(rootDir, filePath);
|
|
149
|
+
return relativePath ?? filePath.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
150
|
+
}
|
|
151
|
+
export function readProjectText(rootDir, filePath) {
|
|
152
|
+
const normalizedPath = normalizeProjectRelativePath(rootDir, filePath);
|
|
153
|
+
if (!normalizedPath) {
|
|
154
|
+
throw Error(`Refusing to access path outside the project root: ${filePath}`);
|
|
155
|
+
}
|
|
156
|
+
if (shouldIgnorePath(normalizedPath)) {
|
|
157
|
+
throw Error(`Ignored artifact paths are off-limits: ${normalizedPath}`);
|
|
158
|
+
}
|
|
159
|
+
const fullPath = path.join(rootDir, normalizedPath);
|
|
160
|
+
return readFileSync(fullPath, 'utf8');
|
|
161
|
+
}
|
|
162
|
+
export function extractIdentifierAtPosition(content, line, column) {
|
|
163
|
+
const lines = content.split(/\r?\n/);
|
|
164
|
+
if (line < 1 || line > lines.length)
|
|
165
|
+
return null;
|
|
166
|
+
const text = (lines[line - 1] ?? '').replace(/\r$/, '');
|
|
167
|
+
if (column < 1 || column > text.length + 1)
|
|
168
|
+
return null;
|
|
169
|
+
const index = column - 1;
|
|
170
|
+
const identifierChar = /[\w$]/;
|
|
171
|
+
if (!identifierChar.test(text[index] ?? ''))
|
|
172
|
+
return null;
|
|
173
|
+
let start = index;
|
|
174
|
+
let end = index;
|
|
175
|
+
while (start > 0 && identifierChar.test(text[start - 1] ?? ''))
|
|
176
|
+
start--;
|
|
177
|
+
while (end < text.length - 1 && identifierChar.test(text[end + 1] ?? ''))
|
|
178
|
+
end++;
|
|
179
|
+
const symbol = text.slice(start, end + 1).trim();
|
|
180
|
+
return symbol || null;
|
|
181
|
+
}
|
|
182
|
+
export function isDefinitionLine(line, symbol) {
|
|
183
|
+
const trimmed = line.trim();
|
|
184
|
+
if (/^(\/\/|#|\/\*|\*)/.test(trimmed))
|
|
185
|
+
return false;
|
|
186
|
+
for (const keyword of DEF_KEYWORDS) {
|
|
187
|
+
const index = trimmed.indexOf(keyword);
|
|
188
|
+
if (index === -1)
|
|
189
|
+
continue;
|
|
190
|
+
const before = trimmed.slice(0, index);
|
|
191
|
+
if (before.length > 0 && !/^[a-zA-Z0-9_$\s]*$/.test(before))
|
|
192
|
+
continue;
|
|
193
|
+
const after = trimmed.slice(index + keyword.length).trimStart();
|
|
194
|
+
if (!after.startsWith(symbol))
|
|
195
|
+
continue;
|
|
196
|
+
const nextChar = after[symbol.length];
|
|
197
|
+
if (!nextChar || /[\s(<:{=[]/.test(nextChar)) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
export function buildIgnoreArgs() {
|
|
204
|
+
const args = [];
|
|
205
|
+
for (const dir of ALWAYS_IGNORE_DIRS) {
|
|
206
|
+
args.push('--glob', `!${dir}`, '--glob', `!**/${dir}/**`);
|
|
207
|
+
}
|
|
208
|
+
return args;
|
|
209
|
+
}
|
|
210
|
+
async function runRipgrep(rootDir, args) {
|
|
211
|
+
let unavailableError = null;
|
|
212
|
+
for (const candidate of getRipgrepPathCandidates()) {
|
|
213
|
+
try {
|
|
214
|
+
const { stdout } = await execFileAsync(candidate, args, {
|
|
215
|
+
cwd: rootDir,
|
|
216
|
+
encoding: 'utf-8',
|
|
217
|
+
maxBuffer: MAX_RIPGREP_BUFFER,
|
|
218
|
+
timeout: MAX_RIPGREP_TIMEOUT_MS,
|
|
219
|
+
});
|
|
220
|
+
return String(stdout ?? '');
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
if (getCommandExitCode(error) === 1) {
|
|
224
|
+
return '';
|
|
225
|
+
}
|
|
226
|
+
if (!isRipgrepExecutionUnavailable(error)) {
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
unavailableError = error;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (unavailableError) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
function collectRelativeFilesUnder(rootDir, dirRel, out, maxFiles) {
|
|
238
|
+
if (out.length >= maxFiles)
|
|
239
|
+
return;
|
|
240
|
+
const absDir = path.join(rootDir, dirRel);
|
|
241
|
+
let entries;
|
|
242
|
+
try {
|
|
243
|
+
entries = readdirSync(absDir, { withFileTypes: true });
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
for (const ent of entries) {
|
|
249
|
+
if (out.length >= maxFiles)
|
|
250
|
+
return;
|
|
251
|
+
const name = String(ent.name);
|
|
252
|
+
const rel = dirRel ? path.join(dirRel, name) : name;
|
|
253
|
+
const relPosix = rel.split(path.sep).join('/');
|
|
254
|
+
if (shouldIgnorePath(relPosix))
|
|
255
|
+
continue;
|
|
256
|
+
if (ent.isDirectory()) {
|
|
257
|
+
collectRelativeFilesUnder(rootDir, rel, out, maxFiles);
|
|
258
|
+
}
|
|
259
|
+
else if (ent.isFile()) {
|
|
260
|
+
out.push(relPosix);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function listRelativePathsForTextSearch(rootDir, targetRel) {
|
|
265
|
+
const absTarget = path.join(rootDir, targetRel);
|
|
266
|
+
let stat;
|
|
267
|
+
try {
|
|
268
|
+
stat = statSync(absTarget);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return [];
|
|
272
|
+
}
|
|
273
|
+
const out = [];
|
|
274
|
+
if (stat.isFile()) {
|
|
275
|
+
if (!shouldIgnorePath(normalizeProjectFilePath(rootDir, targetRel))) {
|
|
276
|
+
out.push(normalizeProjectFilePath(rootDir, targetRel));
|
|
277
|
+
}
|
|
278
|
+
return out;
|
|
279
|
+
}
|
|
280
|
+
if (stat.isDirectory()) {
|
|
281
|
+
collectRelativeFilesUnder(rootDir, targetRel === '.' ? '' : targetRel, out, MAX_FALLBACK_FILES_TO_SCAN);
|
|
282
|
+
}
|
|
283
|
+
return out;
|
|
284
|
+
}
|
|
285
|
+
function searchTextMatchesWithoutRipgrep(rootDir, searchTerm, options = {}) {
|
|
286
|
+
const target = options.filePath
|
|
287
|
+
? normalizeProjectFilePath(rootDir, options.filePath)
|
|
288
|
+
: '.';
|
|
289
|
+
const limit = Math.max(1, Math.min(Number(options.limit ?? 80), 200));
|
|
290
|
+
const wordPattern = new RegExp(`\\b${escapeRegExp(searchTerm)}\\b`);
|
|
291
|
+
const files = listRelativePathsForTextSearch(rootDir, target);
|
|
292
|
+
const results = [];
|
|
293
|
+
for (const rel of files) {
|
|
294
|
+
if (results.length >= limit)
|
|
295
|
+
break;
|
|
296
|
+
let content;
|
|
297
|
+
try {
|
|
298
|
+
content = readFileSync(path.join(rootDir, rel), 'utf8');
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const lines = content.split(/\r?\n/);
|
|
304
|
+
for (let i = 0; i < lines.length; i++) {
|
|
305
|
+
if (results.length >= limit)
|
|
306
|
+
break;
|
|
307
|
+
const line = lines[i] ?? '';
|
|
308
|
+
if (wordPattern.test(line)) {
|
|
309
|
+
results.push({
|
|
310
|
+
filePath: normalizeProjectFilePath(rootDir, rel),
|
|
311
|
+
line: i + 1,
|
|
312
|
+
content: line,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return results.slice(0, limit);
|
|
318
|
+
}
|
|
319
|
+
function parseTextSearchMatch(rootDir, line) {
|
|
320
|
+
const match = line.match(/^(.+?):(\d+):(.*)$/);
|
|
321
|
+
if (!match)
|
|
322
|
+
return null;
|
|
323
|
+
return {
|
|
324
|
+
filePath: normalizeProjectFilePath(rootDir, match[1] ?? ''),
|
|
325
|
+
line: parseInt(match[2] ?? '1', 10),
|
|
326
|
+
content: match[3] ?? '',
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
export async function searchTextMatches(rootDir, searchTerm, options = {}) {
|
|
330
|
+
const target = options.filePath
|
|
331
|
+
? normalizeProjectFilePath(rootDir, options.filePath)
|
|
332
|
+
: '.';
|
|
333
|
+
const limit = Math.max(1, Math.min(Number(options.limit ?? 80), 200));
|
|
334
|
+
const raw = await runRipgrep(rootDir, [
|
|
335
|
+
'-n',
|
|
336
|
+
'--no-heading',
|
|
337
|
+
'--color',
|
|
338
|
+
'never',
|
|
339
|
+
'--word-regexp',
|
|
340
|
+
'--fixed-strings',
|
|
341
|
+
...buildIgnoreArgs(),
|
|
342
|
+
'-m',
|
|
343
|
+
String(limit),
|
|
344
|
+
'--',
|
|
345
|
+
searchTerm,
|
|
346
|
+
target,
|
|
347
|
+
]);
|
|
348
|
+
if (raw === null) {
|
|
349
|
+
return searchTextMatchesWithoutRipgrep(rootDir, searchTerm, options);
|
|
350
|
+
}
|
|
351
|
+
return raw
|
|
352
|
+
.trim()
|
|
353
|
+
.split('\n')
|
|
354
|
+
.filter(Boolean)
|
|
355
|
+
.map((line) => parseTextSearchMatch(rootDir, line))
|
|
356
|
+
.filter((match) => match != null)
|
|
357
|
+
.slice(0, limit);
|
|
358
|
+
}
|
|
359
|
+
export async function searchSymbolOccurrences(rootDir, symbol, options = {}) {
|
|
360
|
+
const limit = Math.max(1, Math.min(Number(options.limit ?? 80), 200));
|
|
361
|
+
const matches = await searchTextMatches(rootDir, symbol, options);
|
|
362
|
+
const wordPattern = new RegExp(`\\b${escapeRegExp(symbol)}\\b`);
|
|
363
|
+
return matches
|
|
364
|
+
.map((match) => {
|
|
365
|
+
const filePath = match.filePath;
|
|
366
|
+
const startLine = match.line;
|
|
367
|
+
const content = match.content;
|
|
368
|
+
const columnMatch = content.match(wordPattern);
|
|
369
|
+
const startColumn = columnMatch?.index != null ? columnMatch.index + 1 : 1;
|
|
370
|
+
return {
|
|
371
|
+
filePath,
|
|
372
|
+
content,
|
|
373
|
+
startLine,
|
|
374
|
+
startColumn,
|
|
375
|
+
endLine: startLine,
|
|
376
|
+
endColumn: startColumn + symbol.length,
|
|
377
|
+
};
|
|
378
|
+
})
|
|
379
|
+
.slice(0, limit);
|
|
380
|
+
}
|
|
381
|
+
export function expandContext(rootDir, filePath, startLine, maxLines = 35) {
|
|
382
|
+
try {
|
|
383
|
+
const lines = readProjectText(rootDir, filePath).split('\n');
|
|
384
|
+
const start = Math.max(0, startLine - 1);
|
|
385
|
+
const end = Math.min(lines.length, start + maxLines);
|
|
386
|
+
return lines
|
|
387
|
+
.slice(start, end)
|
|
388
|
+
.map((line, index) => `${start + index + 1}: ${line}`)
|
|
389
|
+
.join('\n');
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
return '';
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
export function extractSymbolsFromFile(rootDir, filePath, query, limit = 40) {
|
|
396
|
+
let content;
|
|
397
|
+
try {
|
|
398
|
+
content = readProjectText(rootDir, filePath);
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
return [];
|
|
402
|
+
}
|
|
403
|
+
const lines = content.split('\n');
|
|
404
|
+
const symbols = [];
|
|
405
|
+
const seen = new Set();
|
|
406
|
+
const normalizedPath = normalizeProjectFilePath(rootDir, filePath);
|
|
407
|
+
for (let index = 0; index < lines.length; index++) {
|
|
408
|
+
const line = lines[index] ?? '';
|
|
409
|
+
const trimmed = line.trim();
|
|
410
|
+
if (trimmed.startsWith('//') ||
|
|
411
|
+
trimmed.startsWith('#') ||
|
|
412
|
+
trimmed.startsWith('/*') ||
|
|
413
|
+
trimmed.startsWith('*')) {
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
for (const pattern of SYMBOL_PATTERNS) {
|
|
417
|
+
const match = line.match(pattern.regex);
|
|
418
|
+
if (!match)
|
|
419
|
+
continue;
|
|
420
|
+
const name = match[pattern.nameGroup];
|
|
421
|
+
if (!name || name === '_' || name === 'self')
|
|
422
|
+
continue;
|
|
423
|
+
if (query && !name.toLowerCase().includes(query.toLowerCase()))
|
|
424
|
+
continue;
|
|
425
|
+
const key = `${pattern.kind}:${name}:${index + 1}`;
|
|
426
|
+
if (seen.has(key))
|
|
427
|
+
continue;
|
|
428
|
+
seen.add(key);
|
|
429
|
+
const startColumn = line.indexOf(name) + 1;
|
|
430
|
+
symbols.push({
|
|
431
|
+
name,
|
|
432
|
+
kind: pattern.kind,
|
|
433
|
+
filePath: normalizedPath,
|
|
434
|
+
startLine: index + 1,
|
|
435
|
+
startColumn,
|
|
436
|
+
endLine: index + 1,
|
|
437
|
+
endColumn: startColumn + name.length,
|
|
438
|
+
selectionLine: index + 1,
|
|
439
|
+
selectionColumn: startColumn,
|
|
440
|
+
searchSource: 'regex',
|
|
441
|
+
});
|
|
442
|
+
if (symbols.length >= limit) {
|
|
443
|
+
return symbols;
|
|
444
|
+
}
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return symbols;
|
|
449
|
+
}
|
|
450
|
+
export async function listProjectFiles(rootDir, options = {}) {
|
|
451
|
+
const limit = Math.max(1, Math.min(Number(options.limit ?? DEFAULT_PROJECT_FILE_LIMIT), 2000));
|
|
452
|
+
try {
|
|
453
|
+
const raw = await runRipgrep(rootDir, ['--files', ...buildIgnoreArgs()]);
|
|
454
|
+
if (raw === null) {
|
|
455
|
+
const scanned = listScannerProjectFiles(rootDir, { limit });
|
|
456
|
+
return scanned
|
|
457
|
+
.map((filePath) => filePath.split(path.sep).join('/'))
|
|
458
|
+
.filter((filePath) => !options.sourceOnly || isLikelySourceFile(filePath))
|
|
459
|
+
.slice(0, limit);
|
|
460
|
+
}
|
|
461
|
+
const files = raw
|
|
462
|
+
.split('\n')
|
|
463
|
+
.map((line) => line.trim())
|
|
464
|
+
.filter(Boolean)
|
|
465
|
+
.filter((filePath) => !options.sourceOnly || isLikelySourceFile(filePath));
|
|
466
|
+
return files.slice(0, limit);
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
console.warn(`[code-intel] Failed to list project files: ${error?.message ?? String(error)}`);
|
|
470
|
+
return [];
|
|
471
|
+
}
|
|
472
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { deleteProjectFile } from '../patcher.js';
|
|
3
|
+
import { isTuiMode } from '../runtime-mode.js';
|
|
4
|
+
import { removeIndexFile } from '../project-index.js';
|
|
5
|
+
import { invalidateShellDiagnosticsCache, runShellDiagnostics, } from './shell-diagnostics.js';
|
|
6
|
+
export async function deleteFile(context, args) {
|
|
7
|
+
const { rootDir, projectIndex } = context;
|
|
8
|
+
const filePath = String(args.filePath ?? '').trim();
|
|
9
|
+
if (!filePath) {
|
|
10
|
+
return { ok: false, error: 'filePath is required' };
|
|
11
|
+
}
|
|
12
|
+
const result = deleteProjectFile(rootDir, filePath);
|
|
13
|
+
if (result.deleted) {
|
|
14
|
+
await removeIndexFile(projectIndex, filePath);
|
|
15
|
+
invalidateShellDiagnosticsCache(rootDir, filePath);
|
|
16
|
+
if (!isTuiMode())
|
|
17
|
+
console.log(chalk.red(` 🗑️ Deleted: ${filePath}`));
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
ok: true,
|
|
21
|
+
filePath,
|
|
22
|
+
changed: result.deleted,
|
|
23
|
+
deleted: result.deleted,
|
|
24
|
+
content: result.content,
|
|
25
|
+
diagnostics: result.deleted ? runShellDiagnostics(rootDir) : undefined,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function getCommandExitCode(error) {
|
|
2
|
+
if (typeof error?.status === 'number')
|
|
3
|
+
return error.status;
|
|
4
|
+
if (typeof error?.code === 'number')
|
|
5
|
+
return error.code;
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
export function getCommandErrorText(error) {
|
|
9
|
+
return [
|
|
10
|
+
error?.message,
|
|
11
|
+
error?.stderr,
|
|
12
|
+
error?.stdout,
|
|
13
|
+
]
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.map(String)
|
|
16
|
+
.join('\n');
|
|
17
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { expandContext, extractIdentifierAtPosition, isDefinitionLine, readProjectText, searchSymbolOccurrences, } from './code-intel.js';
|
|
2
|
+
const MAX_CONTEXT_LINES = 35;
|
|
3
|
+
const MAX_REFS = 30;
|
|
4
|
+
const MAX_OUTPUT_CHARS = 8000;
|
|
5
|
+
async function fallbackFindSymbol(rootDir, args) {
|
|
6
|
+
const symbol = resolveRequestedSymbol(rootDir, args);
|
|
7
|
+
if (!symbol)
|
|
8
|
+
return {
|
|
9
|
+
ok: false,
|
|
10
|
+
error: 'symbol or filePath + line + column is required',
|
|
11
|
+
};
|
|
12
|
+
const all = await searchSymbolOccurrences(rootDir, symbol, { limit: 80 });
|
|
13
|
+
const defs = all.filter((hit) => isDefinitionLine(hit.content, symbol));
|
|
14
|
+
const defKeys = new Set(defs.map((definition) => `${definition.filePath}:${definition.startLine}`));
|
|
15
|
+
const refs = args.includeReferences !== false
|
|
16
|
+
? all.filter((hit) => !defKeys.has(`${hit.filePath}:${hit.startLine}`))
|
|
17
|
+
: [];
|
|
18
|
+
const sections = [];
|
|
19
|
+
if (defs.length === 0) {
|
|
20
|
+
sections.push(`No definition found for "${symbol}". Showing all occurrences as references.`);
|
|
21
|
+
for (const r of all.slice(0, MAX_REFS)) {
|
|
22
|
+
sections.push(`- ${r.filePath}:${r.startLine}: ${r.content.trim()}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
sections.push(`## Definitions (${defs.length})`);
|
|
27
|
+
for (const def of defs) {
|
|
28
|
+
sections.push(`\n### ${def.filePath}:${def.startLine}\n\`\`\`\n${expandContext(rootDir, def.filePath, def.startLine, MAX_CONTEXT_LINES)}\n\`\`\``);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (refs.length > 0) {
|
|
32
|
+
const shown = refs.slice(0, MAX_REFS);
|
|
33
|
+
sections.push(`\n## References (${refs.length} total, showing ${shown.length})`);
|
|
34
|
+
for (const r of shown)
|
|
35
|
+
sections.push(`- ${r.filePath}:${r.startLine}: ${r.content.trim()}`);
|
|
36
|
+
}
|
|
37
|
+
const output = sections.join('\n');
|
|
38
|
+
return {
|
|
39
|
+
ok: true,
|
|
40
|
+
provider: 'text_search',
|
|
41
|
+
symbol,
|
|
42
|
+
definitions: defs,
|
|
43
|
+
references: refs,
|
|
44
|
+
definitionCount: defs.length,
|
|
45
|
+
referenceCount: refs.length,
|
|
46
|
+
output: output.length > MAX_OUTPUT_CHARS
|
|
47
|
+
? `${output.slice(0, MAX_OUTPUT_CHARS)}\n...(truncated)`
|
|
48
|
+
: output,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export async function findSymbol(context, args) {
|
|
52
|
+
return fallbackFindSymbol(context.rootDir, args);
|
|
53
|
+
}
|
|
54
|
+
function resolveRequestedSymbol(rootDir, args) {
|
|
55
|
+
const symbol = String(args.symbol ?? '').trim();
|
|
56
|
+
if (symbol)
|
|
57
|
+
return symbol;
|
|
58
|
+
const filePath = String(args.filePath ?? '').trim();
|
|
59
|
+
const line = Number(args.line ?? 0);
|
|
60
|
+
const column = Number(args.column ?? 0);
|
|
61
|
+
if (!filePath || !line || !column)
|
|
62
|
+
return '';
|
|
63
|
+
try {
|
|
64
|
+
const content = readProjectText(rootDir, filePath);
|
|
65
|
+
return extractIdentifierAtPosition(content, line, column) ?? '';
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return '';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { isSensitiveProjectPath, normalizeProjectRelativePath, shouldIgnoreArtifactPath, } from '../artifact-policy.js';
|
|
2
|
+
import { runShellDiagnostics } from './shell-diagnostics.js';
|
|
3
|
+
export async function getDiagnostics(context, args) {
|
|
4
|
+
const filePath = String(args.filePath ?? '').trim() || undefined;
|
|
5
|
+
const projectPath = filePath
|
|
6
|
+
? normalizeProjectRelativePath(context.rootDir, filePath)
|
|
7
|
+
: undefined;
|
|
8
|
+
if (projectPath && shouldIgnoreArtifactPath(projectPath)) {
|
|
9
|
+
return {
|
|
10
|
+
ok: false,
|
|
11
|
+
error: 'This path is not permitted.',
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
if (projectPath && isSensitiveProjectPath(projectPath)) {
|
|
15
|
+
return {
|
|
16
|
+
ok: false,
|
|
17
|
+
error: 'This path is not permitted.',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const limit = args.limit ?? 50;
|
|
21
|
+
return runShellDiagnostics(context.rootDir, filePath, limit);
|
|
22
|
+
}
|