@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,437 @@
|
|
|
1
|
+
// Enhanced Developer Tasks using Context Engine and V2 Tools
|
|
2
|
+
import { Task, type RunContext } from '@ddse/acm-sdk';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import type { ContextPack } from '../context/types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* FindSymbolDefinitionTask - Locate symbol across codebase
|
|
8
|
+
*/
|
|
9
|
+
export class FindSymbolDefinitionTask extends Task<
|
|
10
|
+
{ symbolName: string; fileHint?: string },
|
|
11
|
+
{ locations: Array<{ path: string; line: number; snippet: string }> }
|
|
12
|
+
> {
|
|
13
|
+
constructor() {
|
|
14
|
+
super('find-symbol-definition', 'find_symbol_definition');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async execute(
|
|
18
|
+
ctx: RunContext,
|
|
19
|
+
input: { symbolName: string; fileHint?: string }
|
|
20
|
+
): Promise<{ locations: Array<{ path: string; line: number; snippet: string }> }> {
|
|
21
|
+
const searchTool = ctx.getTool('code_search');
|
|
22
|
+
if (!searchTool) throw new Error('CodeSearchTool not found');
|
|
23
|
+
|
|
24
|
+
// Search for symbol
|
|
25
|
+
const query = `${input.symbolName} function class interface`;
|
|
26
|
+
const result = await searchTool.call({
|
|
27
|
+
query,
|
|
28
|
+
k: 5,
|
|
29
|
+
preferTypes: ['.ts', '.tsx', '.js', '.jsx'],
|
|
30
|
+
includeContext: true,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
ctx.stream?.emit('task', {
|
|
34
|
+
taskId: this.id,
|
|
35
|
+
step: 'symbol_search_complete',
|
|
36
|
+
resultsFound: result.results.length,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
locations: result.results.map((r: any) => ({
|
|
41
|
+
path: r.path,
|
|
42
|
+
line: r.line,
|
|
43
|
+
snippet: r.snippet,
|
|
44
|
+
})),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
verification(): string[] {
|
|
49
|
+
return ['output.locations !== undefined'];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* ImplementFunctionTask - Create function with AI assistance
|
|
55
|
+
*/
|
|
56
|
+
export class ImplementFunctionTask extends Task<
|
|
57
|
+
{
|
|
58
|
+
path: string;
|
|
59
|
+
functionName: string;
|
|
60
|
+
signature: string;
|
|
61
|
+
intent: string;
|
|
62
|
+
dryRun?: boolean;
|
|
63
|
+
},
|
|
64
|
+
{ implemented: boolean; changes: string; testResults?: any }
|
|
65
|
+
> {
|
|
66
|
+
constructor() {
|
|
67
|
+
super('implement-function', 'implement_function');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async execute(
|
|
71
|
+
ctx: RunContext,
|
|
72
|
+
input: {
|
|
73
|
+
path: string;
|
|
74
|
+
functionName: string;
|
|
75
|
+
signature: string;
|
|
76
|
+
intent: string;
|
|
77
|
+
dryRun?: boolean;
|
|
78
|
+
}
|
|
79
|
+
): Promise<{ implemented: boolean; changes: string; testResults?: any }> {
|
|
80
|
+
const readTool = ctx.getTool('file_read_lines');
|
|
81
|
+
const editTool = ctx.getTool('code_edit_v2');
|
|
82
|
+
|
|
83
|
+
if (!readTool || !editTool) {
|
|
84
|
+
throw new Error('Required tools not found');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const workspaceRoot = getWorkspaceRoot(ctx);
|
|
88
|
+
const targetPath = resolveWorkspacePath(workspaceRoot, input.path);
|
|
89
|
+
|
|
90
|
+
// Read the file to understand context
|
|
91
|
+
ctx.stream?.emit('task', { taskId: this.id, step: 'reading_file' });
|
|
92
|
+
const fileContent = await readTool.call({ path: targetPath });
|
|
93
|
+
|
|
94
|
+
// Generate function implementation (simplified - in real use, call LLM)
|
|
95
|
+
const implementation = `
|
|
96
|
+
export ${input.signature} {
|
|
97
|
+
// TODO: ${input.intent}
|
|
98
|
+
throw new Error('Not implemented');
|
|
99
|
+
}
|
|
100
|
+
`;
|
|
101
|
+
|
|
102
|
+
// Apply changes
|
|
103
|
+
ctx.stream?.emit('task', { taskId: this.id, step: 'applying_changes' });
|
|
104
|
+
const newContent = fileContent.content + '\n' + implementation;
|
|
105
|
+
|
|
106
|
+
const result = await editTool.call({
|
|
107
|
+
path: targetPath,
|
|
108
|
+
content: newContent,
|
|
109
|
+
dryRun: input.dryRun,
|
|
110
|
+
backup: !input.dryRun,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
ctx.stream?.emit('task', { taskId: this.id, step: 'implementation_complete' });
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
implemented: result.success,
|
|
117
|
+
changes: implementation,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
policyInput(ctx: RunContext, input: any): Record<string, unknown> {
|
|
122
|
+
const workspaceRoot = getWorkspaceRoot(ctx);
|
|
123
|
+
return {
|
|
124
|
+
path: resolveWorkspacePath(workspaceRoot, input.path),
|
|
125
|
+
action: 'implement_function',
|
|
126
|
+
dryRun: input.dryRun || false,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
verification(): string[] {
|
|
131
|
+
return ['output.implemented === true'];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* RefactorRenameSymbolTask - Rename symbol across codebase
|
|
137
|
+
*/
|
|
138
|
+
export class RefactorRenameSymbolTask extends Task<
|
|
139
|
+
{ symbol: string; newName: string; scope?: string; dryRun?: boolean },
|
|
140
|
+
{ occurrences: number; filesChanged: string[]; success: boolean }
|
|
141
|
+
> {
|
|
142
|
+
constructor() {
|
|
143
|
+
super('refactor-rename-symbol', 'refactor_rename_symbol');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async execute(
|
|
147
|
+
ctx: RunContext,
|
|
148
|
+
input: { symbol: string; newName: string; scope?: string; dryRun?: boolean }
|
|
149
|
+
): Promise<{ occurrences: number; filesChanged: string[]; success: boolean }> {
|
|
150
|
+
const grepTool = ctx.getTool('grep');
|
|
151
|
+
if (!grepTool) throw new Error('GrepTool not found');
|
|
152
|
+
|
|
153
|
+
// Find all occurrences
|
|
154
|
+
ctx.stream?.emit('task', { taskId: this.id, step: 'searching_occurrences' });
|
|
155
|
+
const matches = await grepTool.call({
|
|
156
|
+
pattern: input.symbol,
|
|
157
|
+
regex: false,
|
|
158
|
+
caseInsensitive: false,
|
|
159
|
+
maxResults: 100,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
ctx.stream?.emit('task', {
|
|
163
|
+
taskId: this.id,
|
|
164
|
+
step: 'occurrences_found',
|
|
165
|
+
count: matches.matches.length,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Get unique files
|
|
169
|
+
const filesSet = new Set<string>(matches.matches.map((m: any) => String(m.path)));
|
|
170
|
+
const filesChanged: string[] = Array.from(filesSet);
|
|
171
|
+
|
|
172
|
+
// In production, would actually perform the rename
|
|
173
|
+
if (!input.dryRun) {
|
|
174
|
+
// TODO: Implement actual rename logic
|
|
175
|
+
ctx.stream?.emit('task', { taskId: this.id, step: 'renaming_symbols' });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
occurrences: matches.matches.length,
|
|
180
|
+
filesChanged,
|
|
181
|
+
success: true,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
policyInput(ctx: RunContext, input: any): Record<string, unknown> {
|
|
186
|
+
return {
|
|
187
|
+
action: 'refactor_rename',
|
|
188
|
+
symbol: input.symbol,
|
|
189
|
+
dryRun: input.dryRun || false,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
verification(): string[] {
|
|
194
|
+
return ['output.occurrences >= 0', 'output.success === true'];
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* FixTypeErrorTask - Resolve TypeScript errors
|
|
200
|
+
*/
|
|
201
|
+
export class FixTypeErrorTask extends Task<
|
|
202
|
+
{ diagnostics: string; context?: string; dryRun?: boolean },
|
|
203
|
+
{ fixed: boolean; changes: string[]; remainingErrors: number }
|
|
204
|
+
> {
|
|
205
|
+
constructor() {
|
|
206
|
+
super('fix-type-error', 'fix_type_error');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async execute(
|
|
210
|
+
ctx: RunContext,
|
|
211
|
+
input: { diagnostics: string; context?: string; dryRun?: boolean }
|
|
212
|
+
): Promise<{ fixed: boolean; changes: string[]; remainingErrors: number }> {
|
|
213
|
+
const buildTool = ctx.getTool('build');
|
|
214
|
+
if (!buildTool) throw new Error('BuildTool not found');
|
|
215
|
+
|
|
216
|
+
// Run build to get current errors
|
|
217
|
+
ctx.stream?.emit('task', { taskId: this.id, step: 'building_project' });
|
|
218
|
+
const buildResult = await buildTool.call({
|
|
219
|
+
command: 'npm run build',
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const errorCount = buildResult.errors.length;
|
|
223
|
+
ctx.stream?.emit('task', {
|
|
224
|
+
taskId: this.id,
|
|
225
|
+
step: 'errors_detected',
|
|
226
|
+
count: errorCount,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// In production, would use LLM to suggest fixes
|
|
230
|
+
return {
|
|
231
|
+
fixed: errorCount === 0,
|
|
232
|
+
changes: [],
|
|
233
|
+
remainingErrors: errorCount,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
policyInput(ctx: RunContext, input: any): Record<string, unknown> {
|
|
238
|
+
return {
|
|
239
|
+
action: 'fix_type_error',
|
|
240
|
+
dryRun: input.dryRun || false,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
verification(): string[] {
|
|
245
|
+
return ['output.remainingErrors !== undefined'];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* GenerateUnitTestsTask - Create tests with AI
|
|
251
|
+
*/
|
|
252
|
+
export class GenerateUnitTestsTask extends Task<
|
|
253
|
+
{ targetPath: string; symbolName?: string; coverage?: string; dryRun?: boolean },
|
|
254
|
+
{ implemented: boolean; testPath: string; testCount: number }
|
|
255
|
+
> {
|
|
256
|
+
constructor() {
|
|
257
|
+
super('generate-unit-tests', 'generate_unit_tests');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async execute(
|
|
261
|
+
ctx: RunContext,
|
|
262
|
+
input: { targetPath: string; symbolName?: string; coverage?: string; dryRun?: boolean }
|
|
263
|
+
): Promise<{ implemented: boolean; testPath: string; testCount: number }> {
|
|
264
|
+
const readTool = ctx.getTool('file_read_lines');
|
|
265
|
+
const editTool = ctx.getTool('code_edit_v2');
|
|
266
|
+
|
|
267
|
+
if (!readTool || !editTool) {
|
|
268
|
+
throw new Error('Required tools not found');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const workspaceRoot = getWorkspaceRoot(ctx);
|
|
272
|
+
const absoluteTargetPath = resolveWorkspacePath(workspaceRoot, input.targetPath);
|
|
273
|
+
|
|
274
|
+
// Read target file
|
|
275
|
+
ctx.stream?.emit('task', { taskId: this.id, step: 'reading_target' });
|
|
276
|
+
await readTool.call({ path: absoluteTargetPath });
|
|
277
|
+
|
|
278
|
+
// Generate test file path
|
|
279
|
+
const testPathAbsolute = absoluteTargetPath.replace(/\.ts$/, '.test.ts');
|
|
280
|
+
const testPath = toWorkspaceRelative(workspaceRoot, testPathAbsolute);
|
|
281
|
+
|
|
282
|
+
// Generate test template (simplified)
|
|
283
|
+
const testContent = `import { describe, it, expect } from 'vitest';
|
|
284
|
+
import { ${input.symbolName || 'module'} } from './${input.targetPath.replace(/\.ts$/, '')}';
|
|
285
|
+
|
|
286
|
+
describe('${input.symbolName || 'Module'} Tests', () => {
|
|
287
|
+
it('should work correctly', () => {
|
|
288
|
+
// TODO: Implement test
|
|
289
|
+
expect(true).toBe(true);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
`;
|
|
293
|
+
|
|
294
|
+
ctx.stream?.emit('task', { taskId: this.id, step: 'generating_tests' });
|
|
295
|
+
const result = await editTool.call({
|
|
296
|
+
path: testPathAbsolute,
|
|
297
|
+
content: testContent,
|
|
298
|
+
dryRun: input.dryRun,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
implemented: result.success,
|
|
303
|
+
testPath,
|
|
304
|
+
testCount: 1,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
policyInput(ctx: RunContext, input: any): Record<string, unknown> {
|
|
309
|
+
const workspaceRoot = getWorkspaceRoot(ctx);
|
|
310
|
+
return {
|
|
311
|
+
action: 'generate_tests',
|
|
312
|
+
targetPath: resolveWorkspacePath(workspaceRoot, input.targetPath),
|
|
313
|
+
dryRun: input.dryRun || false,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
verification(): string[] {
|
|
318
|
+
return ['output.implemented === true', 'output.testCount > 0'];
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function getWorkspaceRoot(ctx: RunContext): string {
|
|
323
|
+
const workspace = (ctx.context?.facts?.workspace as string) ?? process.cwd();
|
|
324
|
+
return path.resolve(workspace);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function resolveWorkspacePath(workspaceRoot: string, filePath?: string): string {
|
|
328
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
329
|
+
throw new Error('Task requires a valid file path');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return path.isAbsolute(filePath)
|
|
333
|
+
? filePath
|
|
334
|
+
: path.resolve(workspaceRoot, filePath);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function toWorkspaceRelative(workspaceRoot: string, targetPath: string): string {
|
|
338
|
+
const relative = path.relative(workspaceRoot, targetPath);
|
|
339
|
+
return relative && !relative.startsWith('..') ? relative : targetPath;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* ReadFileLinesTask - Precise file slice reading
|
|
344
|
+
*/
|
|
345
|
+
export class ReadFileLinesTask extends Task<
|
|
346
|
+
{ path: string; startLine?: number; endLine?: number; maxLines?: number },
|
|
347
|
+
{ content: string; startLine: number; endLine: number; totalLines?: number }
|
|
348
|
+
> {
|
|
349
|
+
constructor() {
|
|
350
|
+
super('read-file-lines', 'read_file_lines');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async execute(
|
|
354
|
+
ctx: RunContext,
|
|
355
|
+
input: { path: string; startLine?: number; endLine?: number; maxLines?: number }
|
|
356
|
+
): Promise<{ content: string; startLine: number; endLine: number; totalLines?: number }> {
|
|
357
|
+
const readLines = ctx.getTool('file_read_lines');
|
|
358
|
+
if (!readLines) throw new Error('FileReadLinesTool not found');
|
|
359
|
+
|
|
360
|
+
// Normalize path aliases from planner (filepath/filePath)
|
|
361
|
+
const anyInput = input as any;
|
|
362
|
+
const normalizedPath: string | undefined = anyInput?.path ?? anyInput?.filepath ?? anyInput?.filePath;
|
|
363
|
+
if (!normalizedPath) {
|
|
364
|
+
throw new Error('ReadFileLinesTask: missing required path. Accepted keys: path | filepath | filePath');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const res = await readLines.call({
|
|
368
|
+
path: normalizedPath,
|
|
369
|
+
startLine: input.startLine,
|
|
370
|
+
endLine: input.endLine,
|
|
371
|
+
maxLines: input.maxLines,
|
|
372
|
+
});
|
|
373
|
+
return res;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
verification(): string[] {
|
|
377
|
+
return ['output.content !== undefined'];
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* DiffFilesTask - Generate unified diff between two files
|
|
383
|
+
*/
|
|
384
|
+
export class DiffFilesTask extends Task<
|
|
385
|
+
{ aPath: string; bPath: string; context?: number },
|
|
386
|
+
{ diff: string; stats: { filesChanged: number; additions: number; deletions: number } }
|
|
387
|
+
> {
|
|
388
|
+
constructor() {
|
|
389
|
+
super('diff-files', 'diff_files');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async execute(
|
|
393
|
+
ctx: RunContext,
|
|
394
|
+
input: { aPath: string; bPath: string; context?: number }
|
|
395
|
+
): Promise<{ diff: string; stats: { filesChanged: number; additions: number; deletions: number } }> {
|
|
396
|
+
const diffTool = ctx.getTool('diff');
|
|
397
|
+
if (!diffTool) throw new Error('DiffTool not found');
|
|
398
|
+
|
|
399
|
+
const res = await diffTool.call({ aPath: input.aPath, bPath: input.bPath, context: input.context });
|
|
400
|
+
return res;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
verification(): string[] {
|
|
404
|
+
return ['output.diff !== undefined'];
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* GrepSearchTask - Fast pattern search across workspace
|
|
410
|
+
*/
|
|
411
|
+
export class GrepSearchTask extends Task<
|
|
412
|
+
{ pattern: string; regex?: boolean; caseInsensitive?: boolean; maxResults?: number },
|
|
413
|
+
{ matches: Array<{ path: string; line: number; column: number; preview: string }> }
|
|
414
|
+
> {
|
|
415
|
+
constructor() {
|
|
416
|
+
super('grep-search', 'grep_search');
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
async execute(
|
|
420
|
+
ctx: RunContext,
|
|
421
|
+
input: { pattern: string; regex?: boolean; caseInsensitive?: boolean; maxResults?: number }
|
|
422
|
+
): Promise<{ matches: Array<{ path: string; line: number; column: number; preview: string }> }> {
|
|
423
|
+
const grep = ctx.getTool('grep');
|
|
424
|
+
if (!grep) throw new Error('GrepTool not found');
|
|
425
|
+
const res = await grep.call({
|
|
426
|
+
pattern: input.pattern,
|
|
427
|
+
regex: input.regex,
|
|
428
|
+
caseInsensitive: input.caseInsensitive,
|
|
429
|
+
maxResults: input.maxResults,
|
|
430
|
+
});
|
|
431
|
+
return res;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
verification(): string[] {
|
|
435
|
+
return ['output.matches !== undefined'];
|
|
436
|
+
}
|
|
437
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Edit Tools - Code modification with safety
|
|
2
|
+
import { Tool } from '@ddse/acm-sdk';
|
|
3
|
+
import * as fs from 'fs/promises';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* PatchApplyTool - Apply unified diffs with conflict handling
|
|
8
|
+
*/
|
|
9
|
+
export class PatchApplyTool extends Tool<
|
|
10
|
+
{ diffs: string | string[]; strategy?: 'fail' | 'best-effort'; dryRun?: boolean },
|
|
11
|
+
{
|
|
12
|
+
applied: Array<{ path: string; hunks: number }>;
|
|
13
|
+
conflicts: Array<{ path: string; context: string }>;
|
|
14
|
+
preview?: boolean;
|
|
15
|
+
}
|
|
16
|
+
> {
|
|
17
|
+
name(): string {
|
|
18
|
+
return 'patch_apply';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async call(input: {
|
|
22
|
+
diffs: string | string[];
|
|
23
|
+
strategy?: 'fail' | 'best-effort';
|
|
24
|
+
dryRun?: boolean;
|
|
25
|
+
}): Promise<{
|
|
26
|
+
applied: Array<{ path: string; hunks: number }>;
|
|
27
|
+
conflicts: Array<{ path: string; context: string }>;
|
|
28
|
+
preview?: boolean;
|
|
29
|
+
}> {
|
|
30
|
+
const diffs = Array.isArray(input.diffs) ? input.diffs : [input.diffs];
|
|
31
|
+
const strategy = input.strategy ?? 'fail';
|
|
32
|
+
const dryRun = input.dryRun ?? false;
|
|
33
|
+
|
|
34
|
+
const applied: Array<{ path: string; hunks: number }> = [];
|
|
35
|
+
const conflicts: Array<{ path: string; context: string }> = [];
|
|
36
|
+
|
|
37
|
+
for (const diff of diffs) {
|
|
38
|
+
// Parse diff to extract file path and changes
|
|
39
|
+
const lines = diff.split('\n');
|
|
40
|
+
let filePath = '';
|
|
41
|
+
let hunks = 0;
|
|
42
|
+
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
if (line.startsWith('+++')) {
|
|
45
|
+
// Extract file path (remove +++ prefix and any a/ or b/ prefix)
|
|
46
|
+
filePath = line.substring(4).trim().replace(/^[ab]\//, '');
|
|
47
|
+
} else if (line.startsWith('@@')) {
|
|
48
|
+
hunks++;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!filePath) {
|
|
53
|
+
conflicts.push({
|
|
54
|
+
path: 'unknown',
|
|
55
|
+
context: 'Could not parse file path from diff',
|
|
56
|
+
});
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Apply changes (simplified - in production, use a proper diff parser)
|
|
61
|
+
if (!dryRun) {
|
|
62
|
+
try {
|
|
63
|
+
// For now, just track that we would apply
|
|
64
|
+
applied.push({ path: filePath, hunks });
|
|
65
|
+
} catch (error: any) {
|
|
66
|
+
if (strategy === 'fail') {
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
conflicts.push({
|
|
70
|
+
path: filePath,
|
|
71
|
+
context: error.message,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
applied.push({ path: filePath, hunks });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
applied,
|
|
81
|
+
conflicts,
|
|
82
|
+
preview: dryRun,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* CodeEditToolV2 - Enhanced code editing with backup
|
|
89
|
+
*/
|
|
90
|
+
export class CodeEditToolV2 extends Tool<
|
|
91
|
+
{ path: string; content: string; dryRun?: boolean; backup?: boolean },
|
|
92
|
+
{ success: boolean; path: string; message: string; backupPath?: string }
|
|
93
|
+
> {
|
|
94
|
+
private rootPath: string;
|
|
95
|
+
|
|
96
|
+
constructor(rootPath: string = process.cwd()) {
|
|
97
|
+
super();
|
|
98
|
+
this.rootPath = path.resolve(rootPath);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
name(): string {
|
|
102
|
+
return 'code_edit_v2';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async call(input: {
|
|
106
|
+
path: string;
|
|
107
|
+
content: string;
|
|
108
|
+
dryRun?: boolean;
|
|
109
|
+
backup?: boolean;
|
|
110
|
+
}): Promise<{ success: boolean; path: string; message: string; backupPath?: string }> {
|
|
111
|
+
const targetPath = this.resolvePath(input.path);
|
|
112
|
+
|
|
113
|
+
if (input.dryRun) {
|
|
114
|
+
return {
|
|
115
|
+
success: true,
|
|
116
|
+
path: targetPath,
|
|
117
|
+
message: `Dry run: would write ${input.content.length} bytes to ${targetPath}`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Create backup if requested
|
|
122
|
+
let backupPath: string | undefined;
|
|
123
|
+
if (input.backup) {
|
|
124
|
+
try {
|
|
125
|
+
const existing = await fs.readFile(targetPath, 'utf-8');
|
|
126
|
+
backupPath = `${targetPath}.backup`;
|
|
127
|
+
await fs.writeFile(backupPath, existing, 'utf-8');
|
|
128
|
+
} catch {
|
|
129
|
+
// File might not exist yet
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Ensure directory exists
|
|
134
|
+
const dir = path.dirname(targetPath);
|
|
135
|
+
await fs.mkdir(dir, { recursive: true });
|
|
136
|
+
|
|
137
|
+
// Write file
|
|
138
|
+
await fs.writeFile(targetPath, input.content, 'utf-8');
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
success: true,
|
|
142
|
+
path: targetPath,
|
|
143
|
+
message: `Successfully wrote ${input.content.length} bytes to ${targetPath}`,
|
|
144
|
+
backupPath,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private resolvePath(filePath: string): string {
|
|
149
|
+
return path.isAbsolute(filePath)
|
|
150
|
+
? filePath
|
|
151
|
+
: path.resolve(this.rootPath, filePath);
|
|
152
|
+
}
|
|
153
|
+
}
|