@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.
Files changed (165) hide show
  1. package/.aicoder/index.json +304 -0
  2. package/AICODER_IMPLEMENTATION_PLAN_PHASE2.md +284 -0
  3. package/LICENSE +21 -0
  4. package/README.md +490 -0
  5. package/bin/interactive.tsx +232 -0
  6. package/dist/bin/interactive.d.ts +3 -0
  7. package/dist/bin/interactive.d.ts.map +1 -0
  8. package/dist/bin/interactive.js +155 -0
  9. package/dist/bin/interactive.js.map +1 -0
  10. package/dist/src/config/providers.d.ts +15 -0
  11. package/dist/src/config/providers.d.ts.map +1 -0
  12. package/dist/src/config/providers.js +142 -0
  13. package/dist/src/config/providers.js.map +1 -0
  14. package/dist/src/config/session.d.ts +25 -0
  15. package/dist/src/config/session.d.ts.map +1 -0
  16. package/dist/src/config/session.js +97 -0
  17. package/dist/src/config/session.js.map +1 -0
  18. package/dist/src/context/bm25.d.ts +68 -0
  19. package/dist/src/context/bm25.d.ts.map +1 -0
  20. package/dist/src/context/bm25.js +131 -0
  21. package/dist/src/context/bm25.js.map +1 -0
  22. package/dist/src/context/code-search.d.ts +30 -0
  23. package/dist/src/context/code-search.d.ts.map +1 -0
  24. package/dist/src/context/code-search.js +150 -0
  25. package/dist/src/context/code-search.js.map +1 -0
  26. package/dist/src/context/context-pack.d.ts +25 -0
  27. package/dist/src/context/context-pack.d.ts.map +1 -0
  28. package/dist/src/context/context-pack.js +92 -0
  29. package/dist/src/context/context-pack.js.map +1 -0
  30. package/dist/src/context/dependency-mapper.d.ts +10 -0
  31. package/dist/src/context/dependency-mapper.d.ts.map +1 -0
  32. package/dist/src/context/dependency-mapper.js +62 -0
  33. package/dist/src/context/dependency-mapper.js.map +1 -0
  34. package/dist/src/context/index.d.ts +8 -0
  35. package/dist/src/context/index.d.ts.map +1 -0
  36. package/dist/src/context/index.js +9 -0
  37. package/dist/src/context/index.js.map +1 -0
  38. package/dist/src/context/symbol-extractor.d.ts +26 -0
  39. package/dist/src/context/symbol-extractor.d.ts.map +1 -0
  40. package/dist/src/context/symbol-extractor.js +129 -0
  41. package/dist/src/context/symbol-extractor.js.map +1 -0
  42. package/dist/src/context/test-mapper.d.ts +16 -0
  43. package/dist/src/context/test-mapper.d.ts.map +1 -0
  44. package/dist/src/context/test-mapper.js +66 -0
  45. package/dist/src/context/test-mapper.js.map +1 -0
  46. package/dist/src/context/types.d.ts +61 -0
  47. package/dist/src/context/types.d.ts.map +1 -0
  48. package/dist/src/context/types.js +3 -0
  49. package/dist/src/context/types.js.map +1 -0
  50. package/dist/src/context/workspace-indexer.d.ts +39 -0
  51. package/dist/src/context/workspace-indexer.d.ts.map +1 -0
  52. package/dist/src/context/workspace-indexer.js +222 -0
  53. package/dist/src/context/workspace-indexer.js.map +1 -0
  54. package/dist/src/index.d.ts +5 -0
  55. package/dist/src/index.d.ts.map +1 -0
  56. package/dist/src/index.js +6 -0
  57. package/dist/src/index.js.map +1 -0
  58. package/dist/src/registries.d.ts +34 -0
  59. package/dist/src/registries.d.ts.map +1 -0
  60. package/dist/src/registries.js +87 -0
  61. package/dist/src/registries.js.map +1 -0
  62. package/dist/src/runtime/budget-manager.d.ts +42 -0
  63. package/dist/src/runtime/budget-manager.d.ts.map +1 -0
  64. package/dist/src/runtime/budget-manager.js +82 -0
  65. package/dist/src/runtime/budget-manager.js.map +1 -0
  66. package/dist/src/runtime/interactive-runtime.d.ts +39 -0
  67. package/dist/src/runtime/interactive-runtime.d.ts.map +1 -0
  68. package/dist/src/runtime/interactive-runtime.js +321 -0
  69. package/dist/src/runtime/interactive-runtime.js.map +1 -0
  70. package/dist/src/tasks-v2/analysis-tasks.d.ts +117 -0
  71. package/dist/src/tasks-v2/analysis-tasks.d.ts.map +1 -0
  72. package/dist/src/tasks-v2/analysis-tasks.js +209 -0
  73. package/dist/src/tasks-v2/analysis-tasks.js.map +1 -0
  74. package/dist/src/tasks-v2/developer-tasks.d.ts +226 -0
  75. package/dist/src/tasks-v2/developer-tasks.d.ts.map +1 -0
  76. package/dist/src/tasks-v2/developer-tasks.js +322 -0
  77. package/dist/src/tasks-v2/developer-tasks.js.map +1 -0
  78. package/dist/src/tasks-v2/index.d.ts +3 -0
  79. package/dist/src/tasks-v2/index.d.ts.map +1 -0
  80. package/dist/src/tasks-v2/index.js +4 -0
  81. package/dist/src/tasks-v2/index.js.map +1 -0
  82. package/dist/src/tools-v2/edit-tools.d.ts +67 -0
  83. package/dist/src/tools-v2/edit-tools.d.ts.map +1 -0
  84. package/dist/src/tools-v2/edit-tools.js +117 -0
  85. package/dist/src/tools-v2/edit-tools.js.map +1 -0
  86. package/dist/src/tools-v2/index.d.ts +6 -0
  87. package/dist/src/tools-v2/index.d.ts.map +1 -0
  88. package/dist/src/tools-v2/index.js +7 -0
  89. package/dist/src/tools-v2/index.js.map +1 -0
  90. package/dist/src/tools-v2/read-tools.d.ts +129 -0
  91. package/dist/src/tools-v2/read-tools.d.ts.map +1 -0
  92. package/dist/src/tools-v2/read-tools.js +216 -0
  93. package/dist/src/tools-v2/read-tools.js.map +1 -0
  94. package/dist/src/tools-v2/search-tools.d.ts +73 -0
  95. package/dist/src/tools-v2/search-tools.d.ts.map +1 -0
  96. package/dist/src/tools-v2/search-tools.js +132 -0
  97. package/dist/src/tools-v2/search-tools.js.map +1 -0
  98. package/dist/src/tools-v2/test-tools.d.ts +59 -0
  99. package/dist/src/tools-v2/test-tools.d.ts.map +1 -0
  100. package/dist/src/tools-v2/test-tools.js +111 -0
  101. package/dist/src/tools-v2/test-tools.js.map +1 -0
  102. package/dist/src/tools-v2/workspace-context.d.ts +65 -0
  103. package/dist/src/tools-v2/workspace-context.d.ts.map +1 -0
  104. package/dist/src/tools-v2/workspace-context.js +336 -0
  105. package/dist/src/tools-v2/workspace-context.js.map +1 -0
  106. package/dist/src/ui/App.d.ts +9 -0
  107. package/dist/src/ui/App.d.ts.map +1 -0
  108. package/dist/src/ui/App.js +257 -0
  109. package/dist/src/ui/App.js.map +1 -0
  110. package/dist/src/ui/components/ChatPane.d.ts +12 -0
  111. package/dist/src/ui/components/ChatPane.d.ts.map +1 -0
  112. package/dist/src/ui/components/ChatPane.js +41 -0
  113. package/dist/src/ui/components/ChatPane.js.map +1 -0
  114. package/dist/src/ui/components/EventsPane.d.ts +12 -0
  115. package/dist/src/ui/components/EventsPane.d.ts.map +1 -0
  116. package/dist/src/ui/components/EventsPane.js +48 -0
  117. package/dist/src/ui/components/EventsPane.js.map +1 -0
  118. package/dist/src/ui/components/GoalsTasksPane.d.ts +18 -0
  119. package/dist/src/ui/components/GoalsTasksPane.d.ts.map +1 -0
  120. package/dist/src/ui/components/GoalsTasksPane.js +83 -0
  121. package/dist/src/ui/components/GoalsTasksPane.js.map +1 -0
  122. package/dist/src/ui/store.d.ts +74 -0
  123. package/dist/src/ui/store.d.ts.map +1 -0
  124. package/dist/src/ui/store.js +260 -0
  125. package/dist/src/ui/store.js.map +1 -0
  126. package/dist/tests/integration.test.d.ts +2 -0
  127. package/dist/tests/integration.test.d.ts.map +1 -0
  128. package/dist/tests/integration.test.js +415 -0
  129. package/dist/tests/integration.test.js.map +1 -0
  130. package/dist/tsconfig.tsbuildinfo +1 -0
  131. package/docs/AICODER.png +0 -0
  132. package/docs/INTERACTIVE_CLI_GUIDE.md +201 -0
  133. package/docs/TUI_MOCKUP.md +180 -0
  134. package/package.json +52 -0
  135. package/src/config/providers.ts +174 -0
  136. package/src/config/session.ts +143 -0
  137. package/src/context/bm25.ts +173 -0
  138. package/src/context/code-search.ts +188 -0
  139. package/src/context/context-pack.ts +133 -0
  140. package/src/context/dependency-mapper.ts +72 -0
  141. package/src/context/index.ts +8 -0
  142. package/src/context/symbol-extractor.ts +149 -0
  143. package/src/context/test-mapper.ts +77 -0
  144. package/src/context/types.ts +69 -0
  145. package/src/context/workspace-indexer.ts +249 -0
  146. package/src/index.ts +5 -0
  147. package/src/registries.ts +118 -0
  148. package/src/runtime/budget-manager.ts +118 -0
  149. package/src/runtime/interactive-runtime.ts +423 -0
  150. package/src/tasks-v2/analysis-tasks.ts +311 -0
  151. package/src/tasks-v2/developer-tasks.ts +437 -0
  152. package/src/tasks-v2/index.ts +3 -0
  153. package/src/tools-v2/edit-tools.ts +153 -0
  154. package/src/tools-v2/index.ts +6 -0
  155. package/src/tools-v2/read-tools.ts +286 -0
  156. package/src/tools-v2/search-tools.ts +175 -0
  157. package/src/tools-v2/test-tools.ts +147 -0
  158. package/src/tools-v2/workspace-context.ts +428 -0
  159. package/src/ui/App.tsx +392 -0
  160. package/src/ui/components/ChatPane.tsx +84 -0
  161. package/src/ui/components/EventsPane.tsx +81 -0
  162. package/src/ui/components/GoalsTasksPane.tsx +149 -0
  163. package/src/ui/store.ts +362 -0
  164. package/tests/integration.test.ts +537 -0
  165. 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,3 @@
1
+ // Enhanced Developer Tasks
2
+ export * from './developer-tasks.js';
3
+ export * from './analysis-tasks.js';
@@ -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
+ }
@@ -0,0 +1,6 @@
1
+ // Enhanced Code Tools - Main export
2
+ export * from './read-tools.js';
3
+ export * from './search-tools.js';
4
+ export * from './edit-tools.js';
5
+ export * from './test-tools.js';
6
+ export * from './workspace-context.js';