@ddse/acm-aicoder 0.5.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/.aicoder/index.json +304 -0
- package/AICODER_IMPLEMENTATION_PLAN_PHASE2.md +284 -0
- package/LICENSE +21 -0
- package/README.md +490 -0
- package/bin/interactive.tsx +232 -0
- package/dist/bin/interactive.d.ts +3 -0
- package/dist/bin/interactive.d.ts.map +1 -0
- package/dist/bin/interactive.js +155 -0
- package/dist/bin/interactive.js.map +1 -0
- package/dist/src/config/providers.d.ts +15 -0
- package/dist/src/config/providers.d.ts.map +1 -0
- package/dist/src/config/providers.js +142 -0
- package/dist/src/config/providers.js.map +1 -0
- package/dist/src/config/session.d.ts +25 -0
- package/dist/src/config/session.d.ts.map +1 -0
- package/dist/src/config/session.js +97 -0
- package/dist/src/config/session.js.map +1 -0
- package/dist/src/context/bm25.d.ts +68 -0
- package/dist/src/context/bm25.d.ts.map +1 -0
- package/dist/src/context/bm25.js +131 -0
- package/dist/src/context/bm25.js.map +1 -0
- package/dist/src/context/code-search.d.ts +30 -0
- package/dist/src/context/code-search.d.ts.map +1 -0
- package/dist/src/context/code-search.js +150 -0
- package/dist/src/context/code-search.js.map +1 -0
- package/dist/src/context/context-pack.d.ts +25 -0
- package/dist/src/context/context-pack.d.ts.map +1 -0
- package/dist/src/context/context-pack.js +92 -0
- package/dist/src/context/context-pack.js.map +1 -0
- package/dist/src/context/dependency-mapper.d.ts +10 -0
- package/dist/src/context/dependency-mapper.d.ts.map +1 -0
- package/dist/src/context/dependency-mapper.js +62 -0
- package/dist/src/context/dependency-mapper.js.map +1 -0
- package/dist/src/context/index.d.ts +8 -0
- package/dist/src/context/index.d.ts.map +1 -0
- package/dist/src/context/index.js +9 -0
- package/dist/src/context/index.js.map +1 -0
- package/dist/src/context/symbol-extractor.d.ts +26 -0
- package/dist/src/context/symbol-extractor.d.ts.map +1 -0
- package/dist/src/context/symbol-extractor.js +129 -0
- package/dist/src/context/symbol-extractor.js.map +1 -0
- package/dist/src/context/test-mapper.d.ts +16 -0
- package/dist/src/context/test-mapper.d.ts.map +1 -0
- package/dist/src/context/test-mapper.js +66 -0
- package/dist/src/context/test-mapper.js.map +1 -0
- package/dist/src/context/types.d.ts +61 -0
- package/dist/src/context/types.d.ts.map +1 -0
- package/dist/src/context/types.js +3 -0
- package/dist/src/context/types.js.map +1 -0
- package/dist/src/context/workspace-indexer.d.ts +39 -0
- package/dist/src/context/workspace-indexer.d.ts.map +1 -0
- package/dist/src/context/workspace-indexer.js +222 -0
- package/dist/src/context/workspace-indexer.js.map +1 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/registries.d.ts +34 -0
- package/dist/src/registries.d.ts.map +1 -0
- package/dist/src/registries.js +87 -0
- package/dist/src/registries.js.map +1 -0
- package/dist/src/runtime/budget-manager.d.ts +42 -0
- package/dist/src/runtime/budget-manager.d.ts.map +1 -0
- package/dist/src/runtime/budget-manager.js +82 -0
- package/dist/src/runtime/budget-manager.js.map +1 -0
- package/dist/src/runtime/interactive-runtime.d.ts +39 -0
- package/dist/src/runtime/interactive-runtime.d.ts.map +1 -0
- package/dist/src/runtime/interactive-runtime.js +321 -0
- package/dist/src/runtime/interactive-runtime.js.map +1 -0
- package/dist/src/tasks-v2/analysis-tasks.d.ts +117 -0
- package/dist/src/tasks-v2/analysis-tasks.d.ts.map +1 -0
- package/dist/src/tasks-v2/analysis-tasks.js +209 -0
- package/dist/src/tasks-v2/analysis-tasks.js.map +1 -0
- package/dist/src/tasks-v2/developer-tasks.d.ts +226 -0
- package/dist/src/tasks-v2/developer-tasks.d.ts.map +1 -0
- package/dist/src/tasks-v2/developer-tasks.js +322 -0
- package/dist/src/tasks-v2/developer-tasks.js.map +1 -0
- package/dist/src/tasks-v2/index.d.ts +3 -0
- package/dist/src/tasks-v2/index.d.ts.map +1 -0
- package/dist/src/tasks-v2/index.js +4 -0
- package/dist/src/tasks-v2/index.js.map +1 -0
- package/dist/src/tools-v2/edit-tools.d.ts +67 -0
- package/dist/src/tools-v2/edit-tools.d.ts.map +1 -0
- package/dist/src/tools-v2/edit-tools.js +117 -0
- package/dist/src/tools-v2/edit-tools.js.map +1 -0
- package/dist/src/tools-v2/index.d.ts +6 -0
- package/dist/src/tools-v2/index.d.ts.map +1 -0
- package/dist/src/tools-v2/index.js +7 -0
- package/dist/src/tools-v2/index.js.map +1 -0
- package/dist/src/tools-v2/read-tools.d.ts +129 -0
- package/dist/src/tools-v2/read-tools.d.ts.map +1 -0
- package/dist/src/tools-v2/read-tools.js +216 -0
- package/dist/src/tools-v2/read-tools.js.map +1 -0
- package/dist/src/tools-v2/search-tools.d.ts +73 -0
- package/dist/src/tools-v2/search-tools.d.ts.map +1 -0
- package/dist/src/tools-v2/search-tools.js +132 -0
- package/dist/src/tools-v2/search-tools.js.map +1 -0
- package/dist/src/tools-v2/test-tools.d.ts +59 -0
- package/dist/src/tools-v2/test-tools.d.ts.map +1 -0
- package/dist/src/tools-v2/test-tools.js +111 -0
- package/dist/src/tools-v2/test-tools.js.map +1 -0
- package/dist/src/tools-v2/workspace-context.d.ts +65 -0
- package/dist/src/tools-v2/workspace-context.d.ts.map +1 -0
- package/dist/src/tools-v2/workspace-context.js +336 -0
- package/dist/src/tools-v2/workspace-context.js.map +1 -0
- package/dist/src/ui/App.d.ts +9 -0
- package/dist/src/ui/App.d.ts.map +1 -0
- package/dist/src/ui/App.js +257 -0
- package/dist/src/ui/App.js.map +1 -0
- package/dist/src/ui/components/ChatPane.d.ts +12 -0
- package/dist/src/ui/components/ChatPane.d.ts.map +1 -0
- package/dist/src/ui/components/ChatPane.js +41 -0
- package/dist/src/ui/components/ChatPane.js.map +1 -0
- package/dist/src/ui/components/EventsPane.d.ts +12 -0
- package/dist/src/ui/components/EventsPane.d.ts.map +1 -0
- package/dist/src/ui/components/EventsPane.js +48 -0
- package/dist/src/ui/components/EventsPane.js.map +1 -0
- package/dist/src/ui/components/GoalsTasksPane.d.ts +18 -0
- package/dist/src/ui/components/GoalsTasksPane.d.ts.map +1 -0
- package/dist/src/ui/components/GoalsTasksPane.js +83 -0
- package/dist/src/ui/components/GoalsTasksPane.js.map +1 -0
- package/dist/src/ui/store.d.ts +74 -0
- package/dist/src/ui/store.d.ts.map +1 -0
- package/dist/src/ui/store.js +260 -0
- package/dist/src/ui/store.js.map +1 -0
- package/dist/tests/integration.test.d.ts +2 -0
- package/dist/tests/integration.test.d.ts.map +1 -0
- package/dist/tests/integration.test.js +415 -0
- package/dist/tests/integration.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/AICODER.png +0 -0
- package/docs/INTERACTIVE_CLI_GUIDE.md +201 -0
- package/docs/TUI_MOCKUP.md +180 -0
- package/package.json +52 -0
- package/src/config/providers.ts +174 -0
- package/src/config/session.ts +143 -0
- package/src/context/bm25.ts +173 -0
- package/src/context/code-search.ts +188 -0
- package/src/context/context-pack.ts +133 -0
- package/src/context/dependency-mapper.ts +72 -0
- package/src/context/index.ts +8 -0
- package/src/context/symbol-extractor.ts +149 -0
- package/src/context/test-mapper.ts +77 -0
- package/src/context/types.ts +69 -0
- package/src/context/workspace-indexer.ts +249 -0
- package/src/index.ts +5 -0
- package/src/registries.ts +118 -0
- package/src/runtime/budget-manager.ts +118 -0
- package/src/runtime/interactive-runtime.ts +423 -0
- package/src/tasks-v2/analysis-tasks.ts +311 -0
- package/src/tasks-v2/developer-tasks.ts +437 -0
- package/src/tasks-v2/index.ts +3 -0
- package/src/tools-v2/edit-tools.ts +153 -0
- package/src/tools-v2/index.ts +6 -0
- package/src/tools-v2/read-tools.ts +286 -0
- package/src/tools-v2/search-tools.ts +175 -0
- package/src/tools-v2/test-tools.ts +147 -0
- package/src/tools-v2/workspace-context.ts +428 -0
- package/src/ui/App.tsx +392 -0
- package/src/ui/components/ChatPane.tsx +84 -0
- package/src/ui/components/EventsPane.tsx +81 -0
- package/src/ui/components/GoalsTasksPane.tsx +149 -0
- package/src/ui/store.ts +362 -0
- package/tests/integration.test.ts +537 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
// Enhanced Code Tools - Reading, Searching, and Comparison
|
|
2
|
+
import { Tool } from '@ddse/acm-sdk';
|
|
3
|
+
import * as fs from 'fs/promises';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* FileStatTool - Check file existence, size, type
|
|
8
|
+
*/
|
|
9
|
+
export class FileStatTool extends Tool<
|
|
10
|
+
{ path: string },
|
|
11
|
+
{ exists: boolean; size: number; mtime: number; isBinary: boolean }
|
|
12
|
+
> {
|
|
13
|
+
private rootPath: string;
|
|
14
|
+
|
|
15
|
+
constructor(rootPath: string = process.cwd()) {
|
|
16
|
+
super();
|
|
17
|
+
this.rootPath = path.resolve(rootPath);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
name(): string {
|
|
21
|
+
return 'file_stat';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async call(input: { path: string }): Promise<{
|
|
25
|
+
exists: boolean;
|
|
26
|
+
size: number;
|
|
27
|
+
mtime: number;
|
|
28
|
+
isBinary: boolean;
|
|
29
|
+
}> {
|
|
30
|
+
const targetPath = this.resolvePath(input.path);
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const stats = await fs.stat(targetPath);
|
|
34
|
+
const ext = path.extname(targetPath);
|
|
35
|
+
const binaryExts = new Set(['.png', '.jpg', '.pdf', '.zip', '.exe', '.dll']);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
exists: true,
|
|
39
|
+
size: stats.size,
|
|
40
|
+
mtime: stats.mtimeMs,
|
|
41
|
+
isBinary: binaryExts.has(ext),
|
|
42
|
+
};
|
|
43
|
+
} catch {
|
|
44
|
+
return {
|
|
45
|
+
exists: false,
|
|
46
|
+
size: 0,
|
|
47
|
+
mtime: 0,
|
|
48
|
+
isBinary: false,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private resolvePath(filePath: string): string {
|
|
54
|
+
return path.isAbsolute(filePath)
|
|
55
|
+
? filePath
|
|
56
|
+
: path.resolve(this.rootPath, filePath);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* FileReadTool - Read files with offset/limit for large files
|
|
62
|
+
*/
|
|
63
|
+
export class FileReadToolV2 extends Tool<
|
|
64
|
+
{ path: string; offset?: number; limit?: number; ranges?: Array<{ start: number; end: number }> },
|
|
65
|
+
{ content: string; bytesRead: number; eof: boolean; lang?: string }
|
|
66
|
+
> {
|
|
67
|
+
private rootPath: string;
|
|
68
|
+
|
|
69
|
+
constructor(rootPath: string = process.cwd()) {
|
|
70
|
+
super();
|
|
71
|
+
this.rootPath = path.resolve(rootPath);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
name(): string {
|
|
75
|
+
return 'file_read_v2';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async call(input: {
|
|
79
|
+
path: string;
|
|
80
|
+
offset?: number;
|
|
81
|
+
limit?: number;
|
|
82
|
+
ranges?: Array<{ start: number; end: number }>;
|
|
83
|
+
}): Promise<{ content: string; bytesRead: number; eof: boolean; lang?: string }> {
|
|
84
|
+
// Normalize common aliases for file path (planner may emit `filepath` or `filePath`)
|
|
85
|
+
const anyInput = input as any;
|
|
86
|
+
const normalizedPath: string | undefined = anyInput?.path ?? anyInput?.filepath ?? anyInput?.filePath;
|
|
87
|
+
if (!normalizedPath) {
|
|
88
|
+
throw new Error('FileReadToolV2: missing required path. Accepted keys: path | filepath | filePath');
|
|
89
|
+
}
|
|
90
|
+
const fullPath = this.resolvePath(normalizedPath);
|
|
91
|
+
const stats = await fs.stat(fullPath);
|
|
92
|
+
|
|
93
|
+
// Check if binary
|
|
94
|
+
const ext = path.extname(normalizedPath);
|
|
95
|
+
const binaryExts = new Set(['.png', '.jpg', '.pdf', '.zip', '.exe', '.dll']);
|
|
96
|
+
if (binaryExts.has(ext)) {
|
|
97
|
+
throw new Error(`File ${normalizedPath} is binary, cannot read as text`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Handle ranged reads
|
|
101
|
+
if (input.ranges && input.ranges.length > 0) {
|
|
102
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
103
|
+
const rangeContents = input.ranges.map(range =>
|
|
104
|
+
content.slice(range.start, range.end)
|
|
105
|
+
);
|
|
106
|
+
return {
|
|
107
|
+
content: rangeContents.join('\n---\n'),
|
|
108
|
+
bytesRead: rangeContents.reduce((sum, c) => sum + c.length, 0),
|
|
109
|
+
eof: true,
|
|
110
|
+
lang: this.detectLanguage(ext),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Handle offset/limit reads
|
|
115
|
+
const offset = input.offset ?? 0;
|
|
116
|
+
const limit = input.limit ?? stats.size;
|
|
117
|
+
|
|
118
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
119
|
+
const slice = content.slice(offset, offset + limit);
|
|
120
|
+
const eof = offset + slice.length >= content.length;
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
content: slice,
|
|
124
|
+
bytesRead: slice.length,
|
|
125
|
+
eof,
|
|
126
|
+
lang: this.detectLanguage(ext),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private detectLanguage(ext: string): string {
|
|
131
|
+
const langMap: Record<string, string> = {
|
|
132
|
+
'.ts': 'typescript',
|
|
133
|
+
'.tsx': 'typescript',
|
|
134
|
+
'.js': 'javascript',
|
|
135
|
+
'.jsx': 'javascript',
|
|
136
|
+
'.py': 'python',
|
|
137
|
+
'.go': 'go',
|
|
138
|
+
'.rs': 'rust',
|
|
139
|
+
};
|
|
140
|
+
return langMap[ext] || 'text';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private resolvePath(filePath: string): string {
|
|
144
|
+
return path.isAbsolute(filePath)
|
|
145
|
+
? filePath
|
|
146
|
+
: path.resolve(this.rootPath, filePath);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* FileReadLinesTool - Line-ranged reading for precision
|
|
152
|
+
*/
|
|
153
|
+
export class FileReadLinesTool extends Tool<
|
|
154
|
+
{ path: string; startLine?: number; endLine?: number; maxLines?: number },
|
|
155
|
+
{ content: string; startLine: number; endLine: number; totalLines?: number }
|
|
156
|
+
> {
|
|
157
|
+
private rootPath: string;
|
|
158
|
+
|
|
159
|
+
constructor(rootPath: string = process.cwd()) {
|
|
160
|
+
super();
|
|
161
|
+
this.rootPath = path.resolve(rootPath);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
name(): string {
|
|
165
|
+
return 'file_read_lines';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async call(input: {
|
|
169
|
+
path: string;
|
|
170
|
+
startLine?: number;
|
|
171
|
+
endLine?: number;
|
|
172
|
+
maxLines?: number;
|
|
173
|
+
}): Promise<{ content: string; startLine: number; endLine: number; totalLines?: number }> {
|
|
174
|
+
// Normalize common aliases for file path (planner may emit `filepath` or `filePath`)
|
|
175
|
+
const anyInput = input as any;
|
|
176
|
+
const normalizedPath: string | undefined = anyInput?.path ?? anyInput?.filepath ?? anyInput?.filePath;
|
|
177
|
+
if (!normalizedPath) {
|
|
178
|
+
throw new Error('FileReadLinesTool: missing required path. Accepted keys: path | filepath | filePath');
|
|
179
|
+
}
|
|
180
|
+
const fullPath = this.resolvePath(normalizedPath);
|
|
181
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
182
|
+
const lines = content.split('\n');
|
|
183
|
+
|
|
184
|
+
const startLine = Math.max(1, input.startLine ?? 1);
|
|
185
|
+
const maxLines = input.maxLines ?? 100;
|
|
186
|
+
const endLine = input.endLine
|
|
187
|
+
? Math.min(lines.length, input.endLine)
|
|
188
|
+
: Math.min(lines.length, startLine + maxLines - 1);
|
|
189
|
+
|
|
190
|
+
// Extract lines (convert from 1-based to 0-based indexing)
|
|
191
|
+
const selectedLines = lines.slice(startLine - 1, endLine);
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
content: selectedLines.join('\n'),
|
|
195
|
+
startLine,
|
|
196
|
+
endLine,
|
|
197
|
+
totalLines: lines.length,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private resolvePath(filePath: string): string {
|
|
202
|
+
return path.isAbsolute(filePath)
|
|
203
|
+
? filePath
|
|
204
|
+
: path.resolve(this.rootPath, filePath);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* DiffTool - Generate unified diffs
|
|
210
|
+
*/
|
|
211
|
+
export class DiffTool extends Tool<
|
|
212
|
+
{ aPath?: string; aContent?: string; bPath?: string; bContent?: string; context?: number },
|
|
213
|
+
{ diff: string; stats: { filesChanged: number; additions: number; deletions: number } }
|
|
214
|
+
> {
|
|
215
|
+
private rootPath: string;
|
|
216
|
+
|
|
217
|
+
constructor(rootPath: string = process.cwd()) {
|
|
218
|
+
super();
|
|
219
|
+
this.rootPath = path.resolve(rootPath);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
name(): string {
|
|
223
|
+
return 'diff';
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async call(input: {
|
|
227
|
+
aPath?: string;
|
|
228
|
+
aContent?: string;
|
|
229
|
+
bPath?: string;
|
|
230
|
+
bContent?: string;
|
|
231
|
+
context?: number;
|
|
232
|
+
}): Promise<{ diff: string; stats: { filesChanged: number; additions: number; deletions: number } }> {
|
|
233
|
+
// Get content from paths or direct content
|
|
234
|
+
const resolvedAPath = input.aPath ? this.resolvePath(input.aPath) : undefined;
|
|
235
|
+
const resolvedBPath = input.bPath ? this.resolvePath(input.bPath) : undefined;
|
|
236
|
+
|
|
237
|
+
const aContent = input.aContent ?? (resolvedAPath ? await fs.readFile(resolvedAPath, 'utf-8') : '');
|
|
238
|
+
const bContent = input.bContent ?? (resolvedBPath ? await fs.readFile(resolvedBPath, 'utf-8') : '');
|
|
239
|
+
|
|
240
|
+
// Simple line-by-line diff
|
|
241
|
+
const aLines = aContent.split('\n');
|
|
242
|
+
const bLines = bContent.split('\n');
|
|
243
|
+
|
|
244
|
+
const diff: string[] = [];
|
|
245
|
+
diff.push(`--- ${resolvedAPath || 'a'}`);
|
|
246
|
+
diff.push(`+++ ${resolvedBPath || 'b'}`);
|
|
247
|
+
|
|
248
|
+
let additions = 0;
|
|
249
|
+
let deletions = 0;
|
|
250
|
+
|
|
251
|
+
// Very simple diff (not optimal, but functional)
|
|
252
|
+
const maxLen = Math.max(aLines.length, bLines.length);
|
|
253
|
+
for (let i = 0; i < maxLen; i++) {
|
|
254
|
+
const aLine = aLines[i];
|
|
255
|
+
const bLine = bLines[i];
|
|
256
|
+
|
|
257
|
+
if (aLine !== bLine) {
|
|
258
|
+
if (aLine !== undefined) {
|
|
259
|
+
diff.push(`-${aLine}`);
|
|
260
|
+
deletions++;
|
|
261
|
+
}
|
|
262
|
+
if (bLine !== undefined) {
|
|
263
|
+
diff.push(`+${bLine}`);
|
|
264
|
+
additions++;
|
|
265
|
+
}
|
|
266
|
+
} else if (aLine !== undefined) {
|
|
267
|
+
diff.push(` ${aLine}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
diff: diff.join('\n'),
|
|
273
|
+
stats: {
|
|
274
|
+
filesChanged: 1,
|
|
275
|
+
additions,
|
|
276
|
+
deletions,
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private resolvePath(filePath: string): string {
|
|
282
|
+
return path.isAbsolute(filePath)
|
|
283
|
+
? filePath
|
|
284
|
+
: path.resolve(this.rootPath, filePath);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// Search Tools - Code search and pattern matching
|
|
2
|
+
import { Tool } from '@ddse/acm-sdk';
|
|
3
|
+
import * as fs from 'fs/promises';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { CodeSearch, type SearchResult as ContextSearchResult } from '../context/index.js';
|
|
6
|
+
import { WorkspaceIndexer } from '../context/index.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* CodeSearchTool - BM25-based code search
|
|
10
|
+
*/
|
|
11
|
+
export class CodeSearchTool extends Tool<
|
|
12
|
+
{ query: string; k?: number; preferTypes?: string[]; includeContext?: boolean },
|
|
13
|
+
{ results: Array<{ path: string; score: number; snippet: string; line: number }> }
|
|
14
|
+
> {
|
|
15
|
+
private search: CodeSearch | null = null;
|
|
16
|
+
private rootPath: string;
|
|
17
|
+
|
|
18
|
+
constructor(rootPath: string = process.cwd()) {
|
|
19
|
+
super();
|
|
20
|
+
this.rootPath = path.resolve(rootPath);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
name(): string {
|
|
24
|
+
return 'code_search';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async call(input: {
|
|
28
|
+
query: string;
|
|
29
|
+
k?: number;
|
|
30
|
+
preferTypes?: string[];
|
|
31
|
+
includeContext?: boolean;
|
|
32
|
+
}): Promise<{ results: Array<{ path: string; score: number; snippet: string; line: number }> }> {
|
|
33
|
+
// Lazy initialize search
|
|
34
|
+
if (!this.search) {
|
|
35
|
+
this.search = new CodeSearch(this.rootPath);
|
|
36
|
+
const indexer = new WorkspaceIndexer(this.rootPath);
|
|
37
|
+
const index = await indexer.buildIndex({ useCache: true });
|
|
38
|
+
await this.search.indexFiles(index);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const results = await this.search.search(input.query, {
|
|
42
|
+
k: input.k ?? 10,
|
|
43
|
+
preferTypes: input.preferTypes,
|
|
44
|
+
includeContext: input.includeContext ?? true,
|
|
45
|
+
contextLines: 2,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
results: results.map(r => ({
|
|
50
|
+
path: r.path,
|
|
51
|
+
score: r.score,
|
|
52
|
+
snippet: r.snippet,
|
|
53
|
+
line: r.line,
|
|
54
|
+
})),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* GrepTool - Pattern-based multi-file search
|
|
61
|
+
*/
|
|
62
|
+
export class GrepTool extends Tool<
|
|
63
|
+
{ pattern: string; globs?: string[]; regex?: boolean; caseInsensitive?: boolean; maxResults?: number },
|
|
64
|
+
{ matches: Array<{ path: string; line: number; column: number; preview: string }> }
|
|
65
|
+
> {
|
|
66
|
+
private rootPath: string;
|
|
67
|
+
|
|
68
|
+
constructor(rootPath: string = process.cwd()) {
|
|
69
|
+
super();
|
|
70
|
+
this.rootPath = path.resolve(rootPath);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
name(): string {
|
|
74
|
+
return 'grep';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async call(input: {
|
|
78
|
+
pattern: string;
|
|
79
|
+
globs?: string[];
|
|
80
|
+
regex?: boolean;
|
|
81
|
+
caseInsensitive?: boolean;
|
|
82
|
+
maxResults?: number;
|
|
83
|
+
}): Promise<{ matches: Array<{ path: string; line: number; column: number; preview: string }> }> {
|
|
84
|
+
const maxResults = input.maxResults ?? 100;
|
|
85
|
+
const matches: Array<{ path: string; line: number; column: number; preview: string }> = [];
|
|
86
|
+
|
|
87
|
+
// Build pattern
|
|
88
|
+
let searchPattern: RegExp;
|
|
89
|
+
if (input.regex) {
|
|
90
|
+
const flags = input.caseInsensitive ? 'gi' : 'g';
|
|
91
|
+
searchPattern = new RegExp(input.pattern, flags);
|
|
92
|
+
} else {
|
|
93
|
+
const escaped = input.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
94
|
+
const flags = input.caseInsensitive ? 'gi' : 'g';
|
|
95
|
+
searchPattern = new RegExp(escaped, flags);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Scan files
|
|
99
|
+
await this.scanDirectory(this.rootPath, searchPattern, matches, maxResults);
|
|
100
|
+
|
|
101
|
+
return { matches };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private async scanDirectory(
|
|
105
|
+
dir: string,
|
|
106
|
+
pattern: RegExp,
|
|
107
|
+
matches: Array<{ path: string; line: number; column: number; preview: string }>,
|
|
108
|
+
maxResults: number
|
|
109
|
+
): Promise<void> {
|
|
110
|
+
if (matches.length >= maxResults) return;
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
114
|
+
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
if (matches.length >= maxResults) break;
|
|
117
|
+
|
|
118
|
+
// Skip common directories
|
|
119
|
+
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'dist') {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const fullPath = path.join(dir, entry.name);
|
|
124
|
+
|
|
125
|
+
if (entry.isDirectory()) {
|
|
126
|
+
await this.scanDirectory(fullPath, pattern, matches, maxResults);
|
|
127
|
+
} else if (entry.isFile()) {
|
|
128
|
+
// Only search text files
|
|
129
|
+
const ext = path.extname(entry.name);
|
|
130
|
+
if (['.ts', '.tsx', '.js', '.jsx', '.md', '.json'].includes(ext)) {
|
|
131
|
+
await this.searchFile(fullPath, pattern, matches, maxResults);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} catch {
|
|
136
|
+
// Skip directories we can't read
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private async searchFile(
|
|
141
|
+
filePath: string,
|
|
142
|
+
pattern: RegExp,
|
|
143
|
+
matches: Array<{ path: string; line: number; column: number; preview: string }>,
|
|
144
|
+
maxResults: number
|
|
145
|
+
): Promise<void> {
|
|
146
|
+
if (matches.length >= maxResults) return;
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
150
|
+
const lines = content.split('\n');
|
|
151
|
+
const relativePath = path.relative(this.rootPath, filePath);
|
|
152
|
+
|
|
153
|
+
for (let i = 0; i < lines.length; i++) {
|
|
154
|
+
if (matches.length >= maxResults) break;
|
|
155
|
+
|
|
156
|
+
const line = lines[i];
|
|
157
|
+
const match = pattern.exec(line);
|
|
158
|
+
|
|
159
|
+
if (match) {
|
|
160
|
+
matches.push({
|
|
161
|
+
path: relativePath,
|
|
162
|
+
line: i + 1,
|
|
163
|
+
column: match.index,
|
|
164
|
+
preview: line.substring(0, 100), // First 100 chars
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Reset lastIndex for global regex
|
|
169
|
+
pattern.lastIndex = 0;
|
|
170
|
+
}
|
|
171
|
+
} catch {
|
|
172
|
+
// Skip files we can't read
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Test and Build Tools
|
|
2
|
+
import { Tool } from '@ddse/acm-sdk';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* RunTestsToolV2 - Enhanced test runner with better output
|
|
7
|
+
*/
|
|
8
|
+
export class RunTestsToolV2 extends Tool<
|
|
9
|
+
{ command?: string; cwd?: string; timeout?: number },
|
|
10
|
+
{ success: boolean; output: string; exitCode: number; duration: number }
|
|
11
|
+
> {
|
|
12
|
+
private defaultCwd: string;
|
|
13
|
+
|
|
14
|
+
constructor(defaultCwd: string = process.cwd()) {
|
|
15
|
+
super();
|
|
16
|
+
this.defaultCwd = path.resolve(defaultCwd);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
name(): string {
|
|
20
|
+
return 'run_tests_v2';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async call(input: {
|
|
24
|
+
command?: string;
|
|
25
|
+
cwd?: string;
|
|
26
|
+
timeout?: number;
|
|
27
|
+
}): Promise<{ success: boolean; output: string; exitCode: number; duration: number }> {
|
|
28
|
+
const { execa } = await import('execa');
|
|
29
|
+
const command = input.command || 'npm test';
|
|
30
|
+
const timeout = input.timeout ?? 300000; // 5 minutes
|
|
31
|
+
|
|
32
|
+
const startTime = Date.now();
|
|
33
|
+
|
|
34
|
+
const resolvedCwd = this.resolveCwd(input.cwd);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const result = await execa(command, {
|
|
38
|
+
shell: true,
|
|
39
|
+
cwd: resolvedCwd,
|
|
40
|
+
timeout,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
success: result.exitCode === 0,
|
|
45
|
+
output: result.stdout + '\n' + result.stderr,
|
|
46
|
+
exitCode: result.exitCode || 0,
|
|
47
|
+
duration: Date.now() - startTime,
|
|
48
|
+
};
|
|
49
|
+
} catch (error: any) {
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
output: (error.stdout || '') + '\n' + (error.stderr || ''),
|
|
53
|
+
exitCode: error.exitCode || 1,
|
|
54
|
+
duration: Date.now() - startTime,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private resolveCwd(cwd?: string): string {
|
|
60
|
+
if (!cwd) {
|
|
61
|
+
return this.defaultCwd;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return path.isAbsolute(cwd) ? cwd : path.resolve(this.defaultCwd, cwd);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* BuildTool - Run build commands with error extraction
|
|
70
|
+
*/
|
|
71
|
+
export class BuildTool extends Tool<
|
|
72
|
+
{ command?: string; cwd?: string; timeout?: number },
|
|
73
|
+
{ success: boolean; output: string; errors: string[]; duration: number }
|
|
74
|
+
> {
|
|
75
|
+
private defaultCwd: string;
|
|
76
|
+
|
|
77
|
+
constructor(defaultCwd: string = process.cwd()) {
|
|
78
|
+
super();
|
|
79
|
+
this.defaultCwd = path.resolve(defaultCwd);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
name(): string {
|
|
83
|
+
return 'build';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async call(input: {
|
|
87
|
+
command?: string;
|
|
88
|
+
cwd?: string;
|
|
89
|
+
timeout?: number;
|
|
90
|
+
}): Promise<{ success: boolean; output: string; errors: string[]; duration: number }> {
|
|
91
|
+
const { execa } = await import('execa');
|
|
92
|
+
const command = input.command || 'npm run build';
|
|
93
|
+
const timeout = input.timeout ?? 600000; // 10 minutes
|
|
94
|
+
|
|
95
|
+
const startTime = Date.now();
|
|
96
|
+
|
|
97
|
+
const resolvedCwd = this.resolveCwd(input.cwd);
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const result = await execa(command, {
|
|
101
|
+
shell: true,
|
|
102
|
+
cwd: resolvedCwd,
|
|
103
|
+
timeout,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
success: result.exitCode === 0,
|
|
108
|
+
output: result.stdout + '\n' + result.stderr,
|
|
109
|
+
errors: [],
|
|
110
|
+
duration: Date.now() - startTime,
|
|
111
|
+
};
|
|
112
|
+
} catch (error: any) {
|
|
113
|
+
const output = (error.stdout || '') + '\n' + (error.stderr || '');
|
|
114
|
+
|
|
115
|
+
// Extract errors (simple pattern matching)
|
|
116
|
+
const errors = this.extractErrors(output);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
success: false,
|
|
120
|
+
output,
|
|
121
|
+
errors,
|
|
122
|
+
duration: Date.now() - startTime,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private extractErrors(output: string): string[] {
|
|
128
|
+
const errors: string[] = [];
|
|
129
|
+
const lines = output.split('\n');
|
|
130
|
+
|
|
131
|
+
for (const line of lines) {
|
|
132
|
+
if (line.includes('error') || line.includes('Error:') || line.includes('ERROR')) {
|
|
133
|
+
errors.push(line.trim());
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return errors.slice(0, 20); // Limit to first 20 errors
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private resolveCwd(cwd?: string): string {
|
|
141
|
+
if (!cwd) {
|
|
142
|
+
return this.defaultCwd;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return path.isAbsolute(cwd) ? cwd : path.resolve(this.defaultCwd, cwd);
|
|
146
|
+
}
|
|
147
|
+
}
|