claude-mycelium 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/.agent-meta/_inhibitors.ndjson +1287 -0
  2. package/.agent-meta/_quarantine.json +45 -0
  3. package/.agent-meta/config.json +9 -0
  4. package/.claude/memory.db +0 -0
  5. package/.claude/settings.local.json +4 -1
  6. package/README.md +81 -235
  7. package/SECURITY.md +145 -0
  8. package/dist/agent/worker.d.ts +8 -0
  9. package/dist/agent/worker.d.ts.map +1 -0
  10. package/dist/agent/worker.js +97 -0
  11. package/dist/agent/worker.js.map +1 -0
  12. package/dist/bin.d.ts +7 -0
  13. package/dist/bin.d.ts.map +1 -0
  14. package/dist/bin.js +11 -0
  15. package/dist/bin.js.map +1 -0
  16. package/dist/cli/cost.d.ts +10 -0
  17. package/dist/cli/cost.d.ts.map +1 -0
  18. package/dist/cli/cost.js +163 -0
  19. package/dist/cli/cost.js.map +1 -0
  20. package/dist/cli/gc.d.ts +10 -0
  21. package/dist/cli/gc.d.ts.map +1 -0
  22. package/dist/cli/gc.js +108 -0
  23. package/dist/cli/gc.js.map +1 -0
  24. package/dist/cli/gradients.d.ts +10 -0
  25. package/dist/cli/gradients.d.ts.map +1 -0
  26. package/dist/cli/gradients.js +69 -0
  27. package/dist/cli/gradients.js.map +1 -0
  28. package/dist/cli/index.d.ts +17 -0
  29. package/dist/cli/index.d.ts.map +1 -0
  30. package/dist/cli/index.js +72 -0
  31. package/dist/cli/index.js.map +1 -0
  32. package/dist/cli/init.d.ts +11 -0
  33. package/dist/cli/init.d.ts.map +1 -0
  34. package/dist/cli/init.js +97 -0
  35. package/dist/cli/init.js.map +1 -0
  36. package/dist/cli/status.d.ts +10 -0
  37. package/dist/cli/status.d.ts.map +1 -0
  38. package/dist/cli/status.js +191 -0
  39. package/dist/cli/status.js.map +1 -0
  40. package/dist/coordination/file-locks.d.ts +42 -0
  41. package/dist/coordination/file-locks.d.ts.map +1 -0
  42. package/dist/coordination/file-locks.js +269 -0
  43. package/dist/coordination/file-locks.js.map +1 -0
  44. package/dist/coordination/index.d.ts +4 -0
  45. package/dist/coordination/index.d.ts.map +1 -1
  46. package/dist/coordination/index.js +4 -0
  47. package/dist/coordination/index.js.map +1 -1
  48. package/dist/coordination/inhibitors.d.ts +84 -0
  49. package/dist/coordination/inhibitors.d.ts.map +1 -0
  50. package/dist/coordination/inhibitors.js +290 -0
  51. package/dist/coordination/inhibitors.js.map +1 -0
  52. package/dist/coordination/process-manager.d.ts +73 -0
  53. package/dist/coordination/process-manager.d.ts.map +1 -0
  54. package/dist/coordination/process-manager.js +144 -0
  55. package/dist/coordination/process-manager.js.map +1 -0
  56. package/dist/core/agent-executor.d.ts.map +1 -1
  57. package/dist/core/agent-executor.js +28 -10
  58. package/dist/core/agent-executor.js.map +1 -1
  59. package/dist/core/change-applier.d.ts +29 -5
  60. package/dist/core/change-applier.d.ts.map +1 -1
  61. package/dist/core/change-applier.js +254 -24
  62. package/dist/core/change-applier.js.map +1 -1
  63. package/dist/core/signals/churn.d.ts.map +1 -1
  64. package/dist/core/signals/churn.js +6 -4
  65. package/dist/core/signals/churn.js.map +1 -1
  66. package/dist/core/signals/debt.d.ts.map +1 -1
  67. package/dist/core/signals/debt.js +4 -3
  68. package/dist/core/signals/debt.js.map +1 -1
  69. package/dist/cost/cost-tracker.d.ts.map +1 -1
  70. package/dist/cost/cost-tracker.js +2 -0
  71. package/dist/cost/cost-tracker.js.map +1 -1
  72. package/dist/gc/index.d.ts +17 -0
  73. package/dist/gc/index.d.ts.map +1 -0
  74. package/dist/gc/index.js +17 -0
  75. package/dist/gc/index.js.map +1 -0
  76. package/dist/gc/runner.d.ts +39 -0
  77. package/dist/gc/runner.d.ts.map +1 -0
  78. package/dist/gc/runner.js +277 -0
  79. package/dist/gc/runner.js.map +1 -0
  80. package/dist/gc/trace-compactor.d.ts +31 -0
  81. package/dist/gc/trace-compactor.d.ts.map +1 -0
  82. package/dist/gc/trace-compactor.js +162 -0
  83. package/dist/gc/trace-compactor.js.map +1 -0
  84. package/dist/index.d.ts +5 -1
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +6 -1
  87. package/dist/index.js.map +1 -1
  88. package/dist/prompts/index.d.ts +2 -1
  89. package/dist/prompts/index.d.ts.map +1 -1
  90. package/dist/prompts/index.js.map +1 -1
  91. package/dist/quarantine/explorer.d.ts +65 -0
  92. package/dist/quarantine/explorer.d.ts.map +1 -0
  93. package/dist/quarantine/explorer.js +175 -0
  94. package/dist/quarantine/explorer.js.map +1 -0
  95. package/dist/quarantine/index.d.ts +7 -0
  96. package/dist/quarantine/index.d.ts.map +1 -0
  97. package/dist/quarantine/index.js +7 -0
  98. package/dist/quarantine/index.js.map +1 -0
  99. package/dist/quarantine/manager.d.ts +75 -0
  100. package/dist/quarantine/manager.d.ts.map +1 -0
  101. package/dist/quarantine/manager.js +275 -0
  102. package/dist/quarantine/manager.js.map +1 -0
  103. package/dist/task/acceptance.d.ts +29 -0
  104. package/dist/task/acceptance.d.ts.map +1 -0
  105. package/dist/task/acceptance.js +228 -0
  106. package/dist/task/acceptance.js.map +1 -0
  107. package/dist/task/executor.d.ts +30 -0
  108. package/dist/task/executor.d.ts.map +1 -0
  109. package/dist/task/executor.js +429 -0
  110. package/dist/task/executor.js.map +1 -0
  111. package/dist/task/index.d.ts +12 -0
  112. package/dist/task/index.d.ts.map +1 -0
  113. package/dist/task/index.js +12 -0
  114. package/dist/task/index.js.map +1 -0
  115. package/dist/task/planner.d.ts +21 -0
  116. package/dist/task/planner.d.ts.map +1 -0
  117. package/dist/task/planner.js +253 -0
  118. package/dist/task/planner.js.map +1 -0
  119. package/dist/task/storage.d.ts +46 -0
  120. package/dist/task/storage.d.ts.map +1 -0
  121. package/dist/task/storage.js +266 -0
  122. package/dist/task/storage.js.map +1 -0
  123. package/dist/trace/trace-event.d.ts +2 -18
  124. package/dist/trace/trace-event.d.ts.map +1 -1
  125. package/dist/trace/trace-event.js +6 -6
  126. package/dist/trace/trace-event.js.map +1 -1
  127. package/dist/utils/file-utils.d.ts.map +1 -1
  128. package/dist/utils/file-utils.js +54 -15
  129. package/dist/utils/file-utils.js.map +1 -1
  130. package/docs/PHASE5_IMPLEMENTATION.md +237 -0
  131. package/docs/PHASES-3-7-COMPLETE.md +177 -0
  132. package/docs/PHASE_4_COMPLETE.md +135 -0
  133. package/docs/PHASE_7_DELIVERABLES.md +295 -0
  134. package/docs/PHASE_7_IMPLEMENTATION.md +306 -0
  135. package/docs/PHASE_7_SUMMARY.txt +195 -0
  136. package/docs/RELEASE-NOTES-v2.1.md +213 -0
  137. package/docs/ROADMAP.md +64 -57
  138. package/docs/SECURITY-AUDIT.md +387 -0
  139. package/docs/SNAPSHOT.md +59 -32
  140. package/docs/implementation/phase3-summary.md +220 -0
  141. package/package.json +19 -11
  142. package/src/agent/worker.ts +111 -0
  143. package/src/bin.ts +13 -0
  144. package/src/cli/cost.ts +210 -0
  145. package/src/cli/gc.ts +138 -0
  146. package/src/cli/gradients.ts +95 -0
  147. package/src/cli/index.ts +79 -0
  148. package/src/cli/init.ts +139 -0
  149. package/src/cli/status.ts +218 -0
  150. package/src/coordination/file-locks.ts +300 -0
  151. package/src/coordination/index.ts +4 -0
  152. package/src/coordination/inhibitors.ts +345 -0
  153. package/src/coordination/process-manager.ts +199 -0
  154. package/src/core/agent-executor.ts +20 -4
  155. package/src/core/signals/churn.ts +8 -5
  156. package/src/core/signals/debt.ts +4 -3
  157. package/src/cost/cost-tracker.ts +2 -0
  158. package/src/gc/index.ts +17 -0
  159. package/src/gc/runner.ts +314 -0
  160. package/src/gc/trace-compactor.ts +187 -0
  161. package/src/index.ts +7 -1
  162. package/src/prompts/index.ts +2 -1
  163. package/src/quarantine/explorer.ts +234 -0
  164. package/src/quarantine/index.ts +7 -0
  165. package/src/quarantine/manager.ts +336 -0
  166. package/src/task/acceptance.ts +267 -0
  167. package/src/task/executor.ts +538 -0
  168. package/src/task/index.ts +38 -0
  169. package/src/task/planner.ts +294 -0
  170. package/src/task/storage.ts +332 -0
  171. package/src/trace/trace-event.ts +7 -26
  172. package/src/utils/file-utils.ts +61 -15
  173. package/tests/cli/gc.test.ts +206 -0
  174. package/tests/cli/init.test.ts +181 -0
  175. package/tests/cli/status.test.ts +282 -0
  176. package/tests/coordination/file-locks.test.ts +196 -0
  177. package/tests/coordination/inhibitors.test.ts +459 -0
  178. package/tests/coordination/integration.test.ts +195 -0
  179. package/tests/coordination/process-manager.test.ts +165 -0
  180. package/tests/gc/trace-compactor.test.ts +245 -0
  181. package/tests/integration/phase-7.test.ts +145 -0
  182. package/tests/quarantine/explorer.test.ts +381 -0
  183. package/tests/quarantine/manager.test.ts +399 -0
  184. package/tests/security/command-injection.test.ts +88 -0
  185. package/tests/security/path-traversal.test.ts +103 -0
  186. package/tests/task/acceptance.test.ts +411 -0
  187. package/tests/task/executor.test.ts +421 -0
  188. package/tests/task/planner.test.ts +359 -0
  189. package/tsconfig.json +2 -2
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Task Planning Implementation
3
+ * Per second-spec §12.1: Task Planning
4
+ *
5
+ * LLM-based task decomposition that generates a TaskPlan with steps,
6
+ * dependency analysis, and risk identification.
7
+ */
8
+
9
+ import { callLLM } from '../llm/anthropic-client.js';
10
+ import { Task, TaskPlan, TaskStep } from '../types/index.js';
11
+ import { logDebug, logError } from '../utils/logger.js';
12
+ import fg from 'fast-glob';
13
+
14
+ /**
15
+ * Plan a task by using LLM to decompose it into steps
16
+ */
17
+ export async function planTask(task: Task): Promise<TaskPlan> {
18
+ logDebug('Planning task', { taskId: task.id, description: task.description });
19
+
20
+ try {
21
+ // Get existing files in target directory
22
+ const existingFiles = await getExistingFiles(task.target ?? 'src');
23
+
24
+ // Build prompts for LLM
25
+ const systemPrompt = buildPlanningSystemPrompt();
26
+ const userPrompt = buildPlanningUserPrompt(task, existingFiles);
27
+
28
+ // Call LLM to generate plan
29
+ const response = await callLLM({
30
+ prompt: userPrompt,
31
+ systemPrompt,
32
+ temperature: 0.3, // Lower temperature for more deterministic planning
33
+ maxTokens: 4000,
34
+ });
35
+
36
+ // Parse JSON plan from response
37
+ const plan = parseTaskPlanFromResponse(response.content);
38
+
39
+ logDebug('Task plan generated', {
40
+ taskId: task.id,
41
+ steps: plan.steps.length,
42
+ complexity: plan.estimated_complexity,
43
+ });
44
+
45
+ return plan;
46
+ } catch (error) {
47
+ const errorObj = error instanceof Error ? error : new Error(String(error));
48
+ logError('Failed to plan task', errorObj, { taskId: task.id });
49
+ throw new Error(`Task planning failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Analyze dependencies between task steps
55
+ */
56
+ export function analyzeDependencies(steps: TaskStep[]): Map<number, number[]> {
57
+ const dependencies = new Map<number, number[]>();
58
+
59
+ for (const step of steps) {
60
+ // Direct dependencies from the step definition
61
+ dependencies.set(step.order, [...step.depends_on]);
62
+
63
+ // Detect implicit file dependencies
64
+ const implicitDeps = detectImplicitDependencies(step, steps);
65
+ const allDeps = new Set([...step.depends_on, ...implicitDeps]);
66
+ dependencies.set(step.order, Array.from(allDeps));
67
+ }
68
+
69
+ return dependencies;
70
+ }
71
+
72
+ /**
73
+ * Identify potential risks in the task plan
74
+ */
75
+ export function identifyRisks(plan: TaskPlan): string[] {
76
+ const risks: string[] = [...plan.risks];
77
+
78
+ // Check for circular dependencies
79
+ if (hasCircularDependencies(plan.steps)) {
80
+ risks.push('Circular dependency detected in task steps');
81
+ }
82
+
83
+ // Check for steps with many dependencies
84
+ const stepsWithManyDeps = plan.steps.filter(s => s.depends_on.length > 3);
85
+ if (stepsWithManyDeps.length > 0) {
86
+ risks.push(`${stepsWithManyDeps.length} step(s) have complex dependencies (>3 deps)`);
87
+ }
88
+
89
+ // Check for create mode on existing file patterns
90
+ const createSteps = plan.steps.filter(s => s.mode === 'create');
91
+ if (createSteps.length > 5) {
92
+ risks.push(`Many new files (${createSteps.length}) - may need more validation`);
93
+ }
94
+
95
+ // Check for high complexity tasks
96
+ if (plan.estimated_complexity === 'large' && plan.steps.length > 10) {
97
+ risks.push('Large task with many steps - consider breaking down further');
98
+ }
99
+
100
+ return risks;
101
+ }
102
+
103
+ /**
104
+ * Get existing files in the target directory
105
+ */
106
+ async function getExistingFiles(target: string): Promise<string[]> {
107
+ try {
108
+ const pattern = `${target}/**/*.{ts,tsx,js,jsx}`;
109
+ const files = await fg(pattern, {
110
+ ignore: ['**/node_modules/**', '**/dist/**', '**/*.test.ts', '**/*.spec.ts'],
111
+ onlyFiles: true,
112
+ });
113
+ return files;
114
+ } catch (error) {
115
+ const errorObj = error instanceof Error ? error : new Error(String(error));
116
+ logError('Failed to list existing files', errorObj, { target });
117
+ return [];
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Build system prompt for task planning
123
+ */
124
+ function buildPlanningSystemPrompt(): string {
125
+ return `You are a task planning agent.
126
+
127
+ Given a task description, create a step-by-step plan to accomplish it.
128
+
129
+ Output your plan as JSON in this exact format:
130
+ {
131
+ "summary": "Brief description of approach",
132
+ "steps": [
133
+ {
134
+ "order": 1,
135
+ "description": "What to do",
136
+ "target_file": "path/to/file.ts",
137
+ "mode": "create|error_reducer|complexity_reducer|debt_payer|stabilizer",
138
+ "depends_on": []
139
+ }
140
+ ],
141
+ "estimated_complexity": "small|medium|large",
142
+ "risks": ["potential risk 1", "potential risk 2"]
143
+ }
144
+
145
+ Rules:
146
+ - Prefer small, incremental steps over large changes
147
+ - Each step should be independently testable
148
+ - New files must include basic tests
149
+ - Follow existing code patterns
150
+ - mode "create" is for new files only
151
+ - Use error_reducer for fixing bugs
152
+ - Use complexity_reducer for simplifying code
153
+ - Use debt_payer for fixing lint/type issues
154
+ - Use stabilizer for adding tests/docs`;
155
+ }
156
+
157
+ /**
158
+ * Build user prompt for task planning
159
+ */
160
+ function buildPlanningUserPrompt(task: Task, existingFiles: string[]): string {
161
+ const fileList = existingFiles.length > 50
162
+ ? `${existingFiles.slice(0, 50).join('\n')}\n... and ${existingFiles.length - 50} more`
163
+ : existingFiles.join('\n') || '(no files found)';
164
+
165
+ const criteriaList = task.acceptance_criteria.length > 0
166
+ ? task.acceptance_criteria.map(c => `- ${c.description}`).join('\n')
167
+ : '(none specified)';
168
+
169
+ return `## Task
170
+ ${task.description}
171
+
172
+ ## Target Directory
173
+ ${task.target ?? 'src/'}
174
+
175
+ ## Existing Files
176
+ ${fileList}
177
+
178
+ ## Acceptance Criteria
179
+ ${criteriaList}
180
+
181
+ Create a plan to accomplish this task.`;
182
+ }
183
+
184
+ /**
185
+ * Parse TaskPlan from LLM response
186
+ */
187
+ function parseTaskPlanFromResponse(content: string): TaskPlan {
188
+ // Try to extract JSON from response
189
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
190
+ if (!jsonMatch) {
191
+ throw new Error('Failed to parse task plan from LLM response - no JSON found');
192
+ }
193
+
194
+ try {
195
+ const parsed = JSON.parse(jsonMatch[0]);
196
+
197
+ // Validate required fields
198
+ if (!parsed.summary || !Array.isArray(parsed.steps) || !parsed.estimated_complexity) {
199
+ throw new Error('Invalid task plan structure - missing required fields');
200
+ }
201
+
202
+ // Validate each step
203
+ for (const step of parsed.steps) {
204
+ if (typeof step.order !== 'number' ||
205
+ !step.description ||
206
+ !step.target_file ||
207
+ !step.mode) {
208
+ throw new Error('Invalid task step structure');
209
+ }
210
+ // Ensure depends_on is an array
211
+ step.depends_on = step.depends_on || [];
212
+ step.completed = false;
213
+ }
214
+
215
+ // Ensure risks is an array
216
+ parsed.risks = parsed.risks || [];
217
+
218
+ return parsed as TaskPlan;
219
+ } catch (error) {
220
+ throw new Error(`Failed to parse task plan JSON: ${error instanceof Error ? error.message : 'Unknown error'}`);
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Detect implicit dependencies between steps based on file relationships
226
+ */
227
+ function detectImplicitDependencies(step: TaskStep, allSteps: TaskStep[]): number[] {
228
+ const implicitDeps: number[] = [];
229
+
230
+ // If this step modifies a file, it depends on the step that creates it
231
+ if (step.mode !== 'create') {
232
+ const createStep = allSteps.find(
233
+ s => s.mode === 'create' &&
234
+ s.target_file === step.target_file &&
235
+ s.order < step.order
236
+ );
237
+ if (createStep && !step.depends_on.includes(createStep.order)) {
238
+ implicitDeps.push(createStep.order);
239
+ }
240
+ }
241
+
242
+ // If this step creates a test file, it depends on the implementation file step
243
+ if (step.mode === 'create' && step.target_file.includes('.test.')) {
244
+ const implPath = step.target_file.replace(/\.test\./, '.');
245
+ const implStep = allSteps.find(
246
+ s => s.target_file === implPath && s.order < step.order
247
+ );
248
+ if (implStep && !step.depends_on.includes(implStep.order)) {
249
+ implicitDeps.push(implStep.order);
250
+ }
251
+ }
252
+
253
+ return implicitDeps;
254
+ }
255
+
256
+ /**
257
+ * Check if task steps have circular dependencies
258
+ */
259
+ function hasCircularDependencies(steps: TaskStep[]): boolean {
260
+ const visited = new Set<number>();
261
+ const recursionStack = new Set<number>();
262
+
263
+ function hasCycle(stepOrder: number): boolean {
264
+ if (recursionStack.has(stepOrder)) {
265
+ return true; // Found a cycle
266
+ }
267
+ if (visited.has(stepOrder)) {
268
+ return false; // Already checked this path
269
+ }
270
+
271
+ visited.add(stepOrder);
272
+ recursionStack.add(stepOrder);
273
+
274
+ const step = steps.find(s => s.order === stepOrder);
275
+ if (step) {
276
+ for (const dep of step.depends_on) {
277
+ if (hasCycle(dep)) {
278
+ return true;
279
+ }
280
+ }
281
+ }
282
+
283
+ recursionStack.delete(stepOrder);
284
+ return false;
285
+ }
286
+
287
+ for (const step of steps) {
288
+ if (hasCycle(step.order)) {
289
+ return true;
290
+ }
291
+ }
292
+
293
+ return false;
294
+ }
@@ -0,0 +1,332 @@
1
+ /**
2
+ * Task Storage Implementation
3
+ * Per ROADMAP §Phase 5 Week 6 Day 7: Task Storage
4
+ *
5
+ * Stores tasks in .agent-meta/tasks/ with one file per task,
6
+ * tracks status transitions, and links to trace IDs.
7
+ */
8
+
9
+ import { Task, TaskStatus } from '../types/index.js';
10
+ import {
11
+ fileExists,
12
+ readJsonFile,
13
+ writeJsonFile,
14
+ ensureDir,
15
+ deleteFile,
16
+ } from '../utils/file-utils.js';
17
+ import { logDebug, logError } from '../utils/logger.js';
18
+ import * as path from 'path';
19
+ import * as fs from 'fs';
20
+
21
+ const TASKS_DIR = '.agent-meta/tasks';
22
+ const ACTIVE_TASKS_FILE = path.join(TASKS_DIR, '_active.json');
23
+
24
+ /**
25
+ * Active tasks index structure
26
+ */
27
+ interface ActiveTasksIndex {
28
+ updated_at: string;
29
+ tasks: string[]; // Array of task IDs
30
+ }
31
+
32
+ /**
33
+ * Store a task to disk
34
+ */
35
+ export async function storeTask(task: Task): Promise<void> {
36
+ try {
37
+ // Ensure tasks directory exists
38
+ ensureDir(TASKS_DIR);
39
+
40
+ // Write task to its own file
41
+ const taskFilePath = getTaskFilePath(task.id);
42
+ writeJsonFile(taskFilePath, task);
43
+
44
+ // Update active tasks index
45
+ if (task.status === 'pending' || task.status === 'planning' || task.status === 'in_progress' || task.status === 'validating') {
46
+ await addToActiveIndex(task.id);
47
+ } else {
48
+ await removeFromActiveIndex(task.id);
49
+ }
50
+
51
+ logDebug('Task stored', {
52
+ taskId: task.id,
53
+ status: task.status,
54
+ filePath: taskFilePath,
55
+ });
56
+ } catch (error) {
57
+ const errorObj = error instanceof Error ? error : new Error(String(error));
58
+ logError('Failed to store task', errorObj, { taskId: task.id });
59
+ throw new Error(`Task storage failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Load a task from disk
65
+ */
66
+ export async function loadTask(taskId: string): Promise<Task | null> {
67
+ try {
68
+ const taskFilePath = getTaskFilePath(taskId);
69
+
70
+ if (!fileExists(taskFilePath)) {
71
+ logDebug('Task file not found', { taskId, filePath: taskFilePath });
72
+ return null;
73
+ }
74
+
75
+ const task = readJsonFile<Task>(taskFilePath);
76
+ logDebug('Task loaded', { taskId, status: task.status });
77
+ return task;
78
+ } catch (error) {
79
+ const errorObj = error instanceof Error ? error : new Error(String(error));
80
+ logError('Failed to load task', errorObj, { taskId });
81
+ return null;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * List all tasks, optionally filtered by status
87
+ */
88
+ export async function listTasks(statusFilter?: TaskStatus): Promise<Task[]> {
89
+ try {
90
+ ensureDir(TASKS_DIR);
91
+
92
+ // Get all task files
93
+ const files = fs.readdirSync(TASKS_DIR).filter(f => f.startsWith('task_') && f.endsWith('.json'));
94
+
95
+ const tasks: Task[] = [];
96
+
97
+ for (const file of files) {
98
+ try {
99
+ const taskFilePath = path.join(TASKS_DIR, file);
100
+ const task = readJsonFile<Task>(taskFilePath);
101
+
102
+ // Apply status filter if provided
103
+ if (!statusFilter || task.status === statusFilter) {
104
+ tasks.push(task);
105
+ }
106
+ } catch (error) {
107
+ const errorObj = error instanceof Error ? error : new Error(String(error));
108
+ logError('Failed to load task file', errorObj, { file });
109
+ // Continue with other files
110
+ }
111
+ }
112
+
113
+ // Sort by creation date (newest first)
114
+ tasks.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
115
+
116
+ logDebug('Tasks listed', {
117
+ total: tasks.length,
118
+ statusFilter,
119
+ });
120
+
121
+ return tasks;
122
+ } catch (error) {
123
+ const errorObj = error instanceof Error ? error : new Error(String(error));
124
+ logError('Failed to list tasks', errorObj);
125
+ return [];
126
+ }
127
+ }
128
+
129
+ /**
130
+ * List active tasks (from index for performance)
131
+ */
132
+ export async function listActiveTasks(): Promise<Task[]> {
133
+ try {
134
+ const index = await getActiveIndex();
135
+ const tasks: Task[] = [];
136
+
137
+ for (const taskId of index.tasks) {
138
+ const task = await loadTask(taskId);
139
+ if (task) {
140
+ tasks.push(task);
141
+ }
142
+ }
143
+
144
+ return tasks;
145
+ } catch (error) {
146
+ const errorObj = error instanceof Error ? error : new Error(String(error));
147
+ logError('Failed to list active tasks', errorObj);
148
+ return [];
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Delete a task from storage
154
+ */
155
+ export async function deleteTask(taskId: string): Promise<void> {
156
+ try {
157
+ const taskFilePath = getTaskFilePath(taskId);
158
+
159
+ if (fileExists(taskFilePath)) {
160
+ deleteFile(taskFilePath);
161
+ logDebug('Task deleted', { taskId });
162
+ }
163
+
164
+ // Remove from active index
165
+ await removeFromActiveIndex(taskId);
166
+ } catch (error) {
167
+ const errorObj = error instanceof Error ? error : new Error(String(error));
168
+ logError('Failed to delete task', errorObj, { taskId });
169
+ throw new Error(`Task deletion failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Update task status and store
175
+ */
176
+ export async function updateTaskStatus(
177
+ taskId: string,
178
+ newStatus: TaskStatus,
179
+ error?: string
180
+ ): Promise<void> {
181
+ const task = await loadTask(taskId);
182
+ if (!task) {
183
+ throw new Error(`Task ${taskId} not found`);
184
+ }
185
+
186
+ const oldStatus = task.status;
187
+ task.status = newStatus;
188
+
189
+ if (error) {
190
+ task.error = error;
191
+ }
192
+
193
+ if (newStatus === 'completed' || newStatus === 'failed') {
194
+ task.completed_at = new Date().toISOString();
195
+ }
196
+
197
+ await storeTask(task);
198
+
199
+ logDebug('Task status updated', {
200
+ taskId,
201
+ oldStatus,
202
+ newStatus,
203
+ hasError: !!error,
204
+ });
205
+ }
206
+
207
+ /**
208
+ * Get task file path
209
+ */
210
+ function getTaskFilePath(taskId: string): string {
211
+ return path.join(TASKS_DIR, `task_${taskId}.json`);
212
+ }
213
+
214
+ /**
215
+ * Get active tasks index
216
+ */
217
+ async function getActiveIndex(): Promise<ActiveTasksIndex> {
218
+ try {
219
+ if (fileExists(ACTIVE_TASKS_FILE)) {
220
+ return readJsonFile<ActiveTasksIndex>(ACTIVE_TASKS_FILE);
221
+ }
222
+ } catch (error) {
223
+ const errorObj = error instanceof Error ? error : new Error(String(error));
224
+ logError('Failed to read active tasks index', errorObj);
225
+ }
226
+
227
+ // Return empty index if file doesn't exist or can't be read
228
+ return {
229
+ updated_at: new Date().toISOString(),
230
+ tasks: [],
231
+ };
232
+ }
233
+
234
+ /**
235
+ * Save active tasks index
236
+ */
237
+ async function saveActiveIndex(index: ActiveTasksIndex): Promise<void> {
238
+ index.updated_at = new Date().toISOString();
239
+ writeJsonFile(ACTIVE_TASKS_FILE, index);
240
+ }
241
+
242
+ /**
243
+ * Add task to active index
244
+ */
245
+ async function addToActiveIndex(taskId: string): Promise<void> {
246
+ const index = await getActiveIndex();
247
+
248
+ if (!index.tasks.includes(taskId)) {
249
+ index.tasks.push(taskId);
250
+ await saveActiveIndex(index);
251
+ logDebug('Task added to active index', { taskId });
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Remove task from active index
257
+ */
258
+ async function removeFromActiveIndex(taskId: string): Promise<void> {
259
+ const index = await getActiveIndex();
260
+
261
+ const initialLength = index.tasks.length;
262
+ index.tasks = index.tasks.filter(id => id !== taskId);
263
+
264
+ if (index.tasks.length < initialLength) {
265
+ await saveActiveIndex(index);
266
+ logDebug('Task removed from active index', { taskId });
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Get task history for a file (all tasks that touched it)
272
+ */
273
+ export async function getTaskHistoryForFile(filePath: string): Promise<Task[]> {
274
+ const allTasks = await listTasks();
275
+
276
+ return allTasks.filter(
277
+ task =>
278
+ task.files_created.includes(filePath) ||
279
+ task.files_modified.includes(filePath)
280
+ );
281
+ }
282
+
283
+ /**
284
+ * Get task statistics
285
+ */
286
+ export async function getTaskStatistics(): Promise<{
287
+ total: number;
288
+ byStatus: Record<TaskStatus, number>;
289
+ avgCompletionTime: number;
290
+ successRate: number;
291
+ }> {
292
+ const allTasks = await listTasks();
293
+
294
+ const byStatus: Record<TaskStatus, number> = {
295
+ pending: 0,
296
+ planning: 0,
297
+ in_progress: 0,
298
+ validating: 0,
299
+ completed: 0,
300
+ failed: 0,
301
+ blocked: 0,
302
+ };
303
+
304
+ let totalCompletionTime = 0;
305
+ let completedOrFailed = 0;
306
+
307
+ for (const task of allTasks) {
308
+ byStatus[task.status]++;
309
+
310
+ if (task.completed_at) {
311
+ const created = new Date(task.created_at).getTime();
312
+ const completed = new Date(task.completed_at).getTime();
313
+ totalCompletionTime += completed - created;
314
+ completedOrFailed++;
315
+ }
316
+ }
317
+
318
+ const avgCompletionTime = completedOrFailed > 0
319
+ ? totalCompletionTime / completedOrFailed
320
+ : 0;
321
+
322
+ const successRate = completedOrFailed > 0
323
+ ? byStatus.completed / completedOrFailed
324
+ : 0;
325
+
326
+ return {
327
+ total: allTasks.length,
328
+ byStatus,
329
+ avgCompletionTime,
330
+ successRate,
331
+ };
332
+ }
@@ -9,25 +9,7 @@ import * as path from 'path';
9
9
  import * as fs from 'fs';
10
10
  import { appendFile, readFile, fileExists, ensureDir } from '../utils/file-utils.js';
11
11
  import { logInfo, logError, logDebug } from '../utils/logger.js';
12
-
13
- export type Mode = 'error_reducer' | 'complexity_reducer' | 'debt_payer' | 'stabilizer' | 'explorer';
14
-
15
- export interface TraceEvent {
16
- id: string;
17
- timestamp: string;
18
- agent_id: string;
19
- file_path: string;
20
- mode: Mode;
21
- gradient_before: number;
22
- gradient_after: number;
23
- gradient_delta: number;
24
- changes_made: string[];
25
- tokens_used: number;
26
- cost_usd: number;
27
- duration_ms: number;
28
- success: boolean;
29
- error?: string;
30
- }
12
+ import type { TraceEvent, Mode } from '../types/index.js';
31
13
 
32
14
  export interface TraceFilters {
33
15
  file?: string;
@@ -64,13 +46,13 @@ export async function recordTrace(event: TraceEvent): Promise<void> {
64
46
  appendFile(TRACES_FILE, line);
65
47
 
66
48
  // Log summary
67
- const costValue = Number(event.cost_usd) || 0;
49
+ const costValue = Number(event.cost.estimated_usd) || 0;
68
50
  logInfo('Trace recorded', {
69
51
  file: event.file_path,
70
52
  mode: event.mode,
71
53
  delta: event.gradient_delta.toFixed(3),
72
54
  cost: `$${costValue.toFixed(4)}`,
73
- success: event.success,
55
+ ciPassed: event.ci_passed,
74
56
  });
75
57
 
76
58
  // Check if cleanup needed (every 100 traces)
@@ -172,7 +154,7 @@ export function calculateEfficiency(traces: TraceEvent[]): number {
172
154
  }
173
155
 
174
156
  const totalDelta = traces.reduce((sum, t) => sum + t.gradient_delta, 0);
175
- const totalCost = traces.reduce((sum, t) => sum + t.cost_usd, 0);
157
+ const totalCost = traces.reduce((sum, t) => sum + (t.cost.estimated_usd || 0), 0);
176
158
 
177
159
  // Prevent division by zero
178
160
  if (totalCost === 0) {
@@ -195,9 +177,9 @@ export function getTraceStats(traces: TraceEvent[]): TraceStats {
195
177
  };
196
178
  }
197
179
 
198
- const totalCost = traces.reduce((sum, t) => sum + t.cost_usd, 0);
180
+ const totalCost = traces.reduce((sum, t) => sum + (t.cost.estimated_usd || 0), 0);
199
181
  const totalDelta = traces.reduce((sum, t) => sum + t.gradient_delta, 0);
200
- const successCount = traces.filter(t => t.success).length;
182
+ const successCount = traces.filter(t => t.ci_passed).length;
201
183
 
202
184
  return {
203
185
  totalCost,
@@ -313,12 +295,11 @@ export function calculateCost(tokensIn: number, tokensOut: number, model: string
313
295
  * Create a trace event with defaults
314
296
  */
315
297
  export function createTraceEvent(
316
- partial: Omit<TraceEvent, 'id' | 'timestamp' | 'gradient_delta'>
298
+ partial: Omit<TraceEvent, 'id' | 'timestamp'>
317
299
  ): TraceEvent {
318
300
  return {
319
301
  id: generateTraceId(),
320
302
  timestamp: new Date().toISOString(),
321
- gradient_delta: partial.gradient_before - partial.gradient_after,
322
303
  ...partial,
323
304
  };
324
305
  }