task-o-matic 0.0.14 → 0.0.15

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 (110) hide show
  1. package/dist/cli/display/progress.d.ts +15 -2
  2. package/dist/cli/display/progress.d.ts.map +1 -1
  3. package/dist/cli/display/progress.js +72 -4
  4. package/dist/commands/benchmark.d.ts.map +1 -1
  5. package/dist/commands/benchmark.js +11 -3
  6. package/dist/commands/init.d.ts.map +1 -1
  7. package/dist/commands/init.js +19 -4
  8. package/dist/commands/prd.js +7 -1
  9. package/dist/commands/tasks/delete.d.ts.map +1 -1
  10. package/dist/commands/tasks/delete.js +2 -1
  11. package/dist/commands/tasks/document/add.d.ts.map +1 -1
  12. package/dist/commands/tasks/document/add.js +2 -1
  13. package/dist/commands/tasks/document/get.d.ts.map +1 -1
  14. package/dist/commands/tasks/document/get.js +2 -1
  15. package/dist/commands/tasks/plan/set.d.ts.map +1 -1
  16. package/dist/commands/tasks/plan/set.js +11 -3
  17. package/dist/commands/tasks/show.d.ts.map +1 -1
  18. package/dist/commands/tasks/show.js +2 -1
  19. package/dist/commands/tasks/status.d.ts.map +1 -1
  20. package/dist/commands/tasks/status.js +2 -1
  21. package/dist/commands/tasks/update.d.ts.map +1 -1
  22. package/dist/commands/tasks/update.js +7 -1
  23. package/dist/lib/ai-service/model-provider.d.ts.map +1 -1
  24. package/dist/lib/ai-service/model-provider.js +37 -6
  25. package/dist/lib/ai-service/task-operations.d.ts +1 -0
  26. package/dist/lib/ai-service/task-operations.d.ts.map +1 -1
  27. package/dist/lib/ai-service/task-operations.js +135 -173
  28. package/dist/lib/benchmark/registry.d.ts.map +1 -1
  29. package/dist/lib/benchmark/registry.js +6 -10
  30. package/dist/lib/config-validation.d.ts +215 -0
  31. package/dist/lib/config-validation.d.ts.map +1 -0
  32. package/dist/lib/config-validation.js +246 -0
  33. package/dist/lib/config.d.ts.map +1 -1
  34. package/dist/lib/config.js +19 -5
  35. package/dist/lib/storage/file-system.d.ts.map +1 -1
  36. package/dist/lib/storage/file-system.js +81 -21
  37. package/dist/lib/task-execution-core.d.ts.map +1 -1
  38. package/dist/lib/task-execution-core.js +3 -2
  39. package/dist/services/prd.d.ts +17 -0
  40. package/dist/services/prd.d.ts.map +1 -1
  41. package/dist/services/prd.js +49 -15
  42. package/dist/services/tasks.d.ts +315 -1
  43. package/dist/services/tasks.d.ts.map +1 -1
  44. package/dist/services/tasks.js +483 -107
  45. package/dist/services/workflow-ai-assistant.d.ts.map +1 -1
  46. package/dist/services/workflow-ai-assistant.js +19 -6
  47. package/dist/test/lib/ai-service/task-operations.test.d.ts +2 -0
  48. package/dist/test/lib/ai-service/task-operations.test.d.ts.map +1 -0
  49. package/dist/test/lib/ai-service/task-operations.test.js +362 -0
  50. package/dist/test/mocks/mock-ai-operations.d.ts +15 -0
  51. package/dist/test/mocks/mock-ai-operations.d.ts.map +1 -0
  52. package/dist/test/mocks/mock-ai-operations.js +107 -0
  53. package/dist/test/mocks/mock-context-builder.d.ts +10 -0
  54. package/dist/test/mocks/mock-context-builder.d.ts.map +1 -0
  55. package/dist/test/mocks/mock-context-builder.js +81 -0
  56. package/dist/test/mocks/mock-model-provider.d.ts +7 -0
  57. package/dist/test/mocks/mock-model-provider.d.ts.map +1 -0
  58. package/dist/test/mocks/mock-model-provider.js +21 -0
  59. package/dist/test/mocks/mock-service-factory.d.ts +11 -0
  60. package/dist/test/mocks/mock-service-factory.d.ts.map +1 -0
  61. package/dist/test/mocks/mock-service-factory.js +61 -0
  62. package/dist/test/mocks/mock-storage.d.ts +50 -0
  63. package/dist/test/mocks/mock-storage.d.ts.map +1 -0
  64. package/dist/test/mocks/mock-storage.js +145 -0
  65. package/dist/test/services/task-service.test.d.ts +2 -0
  66. package/dist/test/services/task-service.test.d.ts.map +1 -0
  67. package/dist/test/services/task-service.test.js +352 -0
  68. package/dist/test/test-mock-setup.d.ts +26 -0
  69. package/dist/test/test-mock-setup.d.ts.map +1 -0
  70. package/dist/test/test-mock-setup.js +41 -0
  71. package/dist/test/test-setup.d.ts +9 -0
  72. package/dist/test/test-setup.d.ts.map +1 -0
  73. package/dist/test/test-setup.js +44 -0
  74. package/dist/test/test-utils.d.ts +22 -0
  75. package/dist/test/test-utils.d.ts.map +1 -0
  76. package/dist/test/test-utils.js +37 -0
  77. package/dist/test/utils/ai-operation-utility.test.d.ts +2 -0
  78. package/dist/test/utils/ai-operation-utility.test.d.ts.map +1 -0
  79. package/dist/test/utils/ai-operation-utility.test.js +290 -0
  80. package/dist/test/utils/error-handling.test.d.ts +2 -0
  81. package/dist/test/utils/error-handling.test.d.ts.map +1 -0
  82. package/dist/test/utils/error-handling.test.js +231 -0
  83. package/dist/utils/ai-operation-utility.d.ts +142 -0
  84. package/dist/utils/ai-operation-utility.d.ts.map +1 -0
  85. package/dist/utils/ai-operation-utility.js +288 -0
  86. package/dist/utils/ai-service-factory.d.ts +10 -0
  87. package/dist/utils/ai-service-factory.d.ts.map +1 -1
  88. package/dist/utils/ai-service-factory.js +19 -1
  89. package/dist/utils/cli-validators.d.ts +2 -2
  90. package/dist/utils/cli-validators.d.ts.map +1 -1
  91. package/dist/utils/cli-validators.js +7 -6
  92. package/dist/utils/error-utils.d.ts +3 -3
  93. package/dist/utils/error-utils.d.ts.map +1 -1
  94. package/dist/utils/error-utils.js +5 -4
  95. package/dist/utils/file-utils.d.ts +4 -4
  96. package/dist/utils/file-utils.d.ts.map +1 -1
  97. package/dist/utils/file-utils.js +11 -6
  98. package/dist/utils/id-generator.d.ts +1 -1
  99. package/dist/utils/id-generator.d.ts.map +1 -1
  100. package/dist/utils/id-generator.js +8 -2
  101. package/dist/utils/model-executor-parser.d.ts +1 -1
  102. package/dist/utils/model-executor-parser.d.ts.map +1 -1
  103. package/dist/utils/model-executor-parser.js +3 -2
  104. package/dist/utils/storage-utils.d.ts +3 -3
  105. package/dist/utils/storage-utils.d.ts.map +1 -1
  106. package/dist/utils/storage-utils.js +7 -6
  107. package/dist/utils/task-o-matic-error.d.ts +206 -0
  108. package/dist/utils/task-o-matic-error.d.ts.map +1 -0
  109. package/dist/utils/task-o-matic-error.js +304 -0
  110. package/package.json +2 -2
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.taskService = exports.TaskService = void 0;
37
+ exports.getTaskService = getTaskService;
37
38
  const ai_service_factory_1 = require("../utils/ai-service-factory");
38
39
  const stack_formatter_1 = require("../utils/stack-formatter");
39
40
  const ai_config_builder_1 = require("../utils/ai-config-builder");
@@ -41,27 +42,127 @@ const hooks_1 = require("../lib/hooks");
41
42
  const streaming_utils_1 = require("../utils/streaming-utils");
42
43
  const error_utils_1 = require("../utils/error-utils");
43
44
  const id_generator_1 = require("../utils/id-generator");
45
+ const task_o_matic_error_1 = require("../utils/task-o-matic-error");
44
46
  /**
45
47
  * TaskService - Centralized business logic for all task operations
46
- * This service is framework-agnostic and can be used by CLI, TUI, or Web
48
+ *
49
+ * This service provides a comprehensive API for task management with AI-powered features.
50
+ * It's framework-agnostic and can be used by CLI, TUI, or Web applications.
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * import { TaskService } from "task-o-matic";
55
+ *
56
+ * // Initialize with default configuration
57
+ * const taskService = new TaskService();
58
+ *
59
+ * // Create a task with AI enhancement
60
+ * const result = await taskService.createTask({
61
+ * title: "Implement feature",
62
+ * content: "Feature description",
63
+ * aiEnhance: true,
64
+ * aiOptions: {
65
+ * provider: "anthropic",
66
+ * model: "claude-3-5-sonnet"
67
+ * }
68
+ * });
69
+ *
70
+ * // Or inject dependencies for testing
71
+ * const taskService = new TaskService({
72
+ * storage: mockStorage,
73
+ * aiOperations: mockAI,
74
+ * });
75
+ * ```
47
76
  */
48
77
  class TaskService {
78
+ storage;
79
+ aiOperations;
80
+ modelProvider;
81
+ contextBuilder;
82
+ hooks;
83
+ /**
84
+ * Create a new TaskService
85
+ *
86
+ * @param dependencies - Optional dependencies to inject (for testing)
87
+ */
88
+ constructor(dependencies = {}) {
89
+ // Use injected dependencies or fall back to singletons
90
+ this.storage = dependencies.storage ?? (0, ai_service_factory_1.getStorage)();
91
+ this.aiOperations = dependencies.aiOperations ?? (0, ai_service_factory_1.getAIOperations)();
92
+ this.modelProvider = dependencies.modelProvider ?? (0, ai_service_factory_1.getModelProvider)();
93
+ this.contextBuilder = dependencies.contextBuilder ?? (0, ai_service_factory_1.getContextBuilder)();
94
+ this.hooks = dependencies.hooks ?? hooks_1.hooks;
95
+ }
49
96
  // ============================================================================
50
97
  // CORE CRUD OPERATIONS
51
98
  // ============================================================================
99
+ /**
100
+ * Creates a new task with optional AI enhancement
101
+ *
102
+ * @param input - Task creation parameters
103
+ * @param input.title - Task title (required, 1-255 characters)
104
+ * @param input.content - Task content/description (optional)
105
+ * @param input.parentId - Parent task ID for creating subtasks
106
+ * @param input.effort - Estimated effort ("small" | "medium" | "large")
107
+ * @param input.aiEnhance - Enable AI enhancement with Context7 documentation
108
+ * @param input.aiOptions - AI configuration override
109
+ * @param input.streamingOptions - Real-time streaming options
110
+ *
111
+ * @returns Promise resolving to task creation result
112
+ *
113
+ * @throws {TaskOMaticError} If task creation fails (e.g., AI operation errors, storage errors)
114
+ * @throws {Error} If input validation fails
115
+ *
116
+ * @example Basic task creation
117
+ * ```typescript
118
+ * const task = await taskService.createTask({
119
+ * title: "Fix authentication bug",
120
+ * content: "Users cannot login with valid credentials",
121
+ * aiEnhance: false
122
+ * });
123
+ * ```
124
+ *
125
+ * @example Task with AI enhancement
126
+ * ```typescript
127
+ * try {
128
+ * const enhancedTask = await taskService.createTask({
129
+ * title: "Design authentication system",
130
+ * content: "Implement OAuth2 + JWT authentication",
131
+ * aiEnhance: true,
132
+ * streamingOptions: {
133
+ * onChunk: (chunk) => console.log("AI:", chunk)
134
+ * }
135
+ * });
136
+ * console.log("Enhanced content:", enhancedTask.task.content);
137
+ * } catch (error) {
138
+ * if (error instanceof TaskOMaticError) {
139
+ * console.error("AI enhancement failed:", error.getDetails());
140
+ * }
141
+ * }
142
+ * ```
143
+ *
144
+ * @example Creating subtasks
145
+ * ```typescript
146
+ * const subtask = await taskService.createTask({
147
+ * title: "Implement OAuth2 flow",
148
+ * parentId: "1",
149
+ * effort: "medium"
150
+ * });
151
+ * ```
152
+ */
52
153
  async createTask(input) {
53
154
  const startTime = Date.now();
54
155
  let content = input.content;
55
156
  let aiMetadata;
56
157
  if (input.aiEnhance) {
57
- hooks_1.hooks.emit("task:progress", {
158
+ this.hooks.emit("task:progress", {
58
159
  message: "Building context for task...",
59
160
  type: "progress",
60
161
  });
61
162
  // Build context with error handling (Bug fix 2.2)
62
163
  let context;
63
164
  try {
64
- context = await (0, ai_service_factory_1.getContextBuilder)().buildContextForNewTask(input.title, input.content);
165
+ context = await this.contextBuilder.buildContextForNewTask(input.title, input.content);
65
166
  }
66
167
  catch (error) {
67
168
  // Log warning but don't fail task creation
@@ -71,15 +172,15 @@ class TaskService {
71
172
  }
72
173
  const stackInfo = (0, stack_formatter_1.formatStackInfo)(context.stack);
73
174
  const enhancementAIConfig = (0, ai_config_builder_1.buildAIConfig)(input.aiOptions);
74
- hooks_1.hooks.emit("task:progress", {
175
+ this.hooks.emit("task:progress", {
75
176
  message: "Enhancing task with AI documentation...",
76
177
  type: "progress",
77
178
  });
78
179
  const taskDescription = input.content ?? "";
79
180
  // Generate temporary ID for AI operations (Bug fix 2.6)
80
181
  const tempTaskId = id_generator_1.TaskIDGenerator.generate();
81
- content = await (0, ai_service_factory_1.getAIOperations)().enhanceTaskWithDocumentation(tempTaskId, input.title, taskDescription, stackInfo, input.streamingOptions, undefined, enhancementAIConfig, context.existingResearch);
82
- const aiConfig = (0, ai_service_factory_1.getModelProvider)().getAIConfig();
182
+ content = await this.aiOperations.enhanceTaskWithDocumentation(tempTaskId, input.title, taskDescription, stackInfo, input.streamingOptions, undefined, enhancementAIConfig, context.existingResearch);
183
+ const aiConfig = this.modelProvider.getAIConfig();
83
184
  aiMetadata = {
84
185
  taskId: "",
85
186
  aiGenerated: true,
@@ -90,27 +191,48 @@ class TaskService {
90
191
  generatedAt: Date.now(),
91
192
  };
92
193
  }
93
- hooks_1.hooks.emit("task:progress", {
194
+ this.hooks.emit("task:progress", {
94
195
  message: "Saving task...",
95
196
  type: "progress",
96
197
  });
97
- const task = await (0, ai_service_factory_1.getStorage)().createTask({
198
+ const task = await this.storage.createTask({
98
199
  title: input.title,
99
200
  description: input.content ?? "",
100
201
  content,
101
202
  parentId: input.parentId,
102
203
  estimatedEffort: input.effort,
103
204
  }, aiMetadata);
104
- hooks_1.hooks.emit("task:progress", {
205
+ this.hooks.emit("task:progress", {
105
206
  type: "completed",
106
207
  message: "Task created successfully",
107
208
  });
108
209
  // Emit task:created event
109
- await hooks_1.hooks.emit("task:created", { task });
210
+ await this.hooks.emit("task:created", { task });
110
211
  return { success: true, task, aiMetadata };
111
212
  }
213
+ /**
214
+ * List tasks with optional filtering
215
+ *
216
+ * @param filters - Filter criteria
217
+ * @param filters.status - Filter by task status ("todo", "in-progress", "completed")
218
+ * @param filters.tag - Filter by task tag
219
+ *
220
+ * @returns Promise resolving to array of matching tasks
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * // List all tasks
225
+ * const allTasks = await taskService.listTasks({});
226
+ *
227
+ * // List only completed tasks
228
+ * const completedTasks = await taskService.listTasks({ status: "completed" });
229
+ *
230
+ * // List tasks with specific tag
231
+ * const frontendTasks = await taskService.listTasks({ tag: "frontend" });
232
+ * ```
233
+ */
112
234
  async listTasks(filters) {
113
- const storage = (0, ai_service_factory_1.getStorage)();
235
+ const storage = this.storage;
114
236
  const topLevelTasks = await storage.getTopLevelTasks();
115
237
  let filteredTasks = topLevelTasks;
116
238
  if (filters.status) {
@@ -123,22 +245,22 @@ class TaskService {
123
245
  return filteredTasks;
124
246
  }
125
247
  async getTask(id) {
126
- return await (0, ai_service_factory_1.getStorage)().getTask(id);
248
+ return await this.storage.getTask(id);
127
249
  }
128
250
  async getTaskContent(id) {
129
- return await (0, ai_service_factory_1.getStorage)().getTaskContent(id);
251
+ return await this.storage.getTaskContent(id);
130
252
  }
131
253
  async getTaskAIMetadata(id) {
132
- return await (0, ai_service_factory_1.getStorage)().getTaskAIMetadata(id);
254
+ return await this.storage.getTaskAIMetadata(id);
133
255
  }
134
256
  async getSubtasks(id) {
135
- return await (0, ai_service_factory_1.getStorage)().getSubtasks(id);
257
+ return await this.storage.getSubtasks(id);
136
258
  }
137
259
  async updateTask(id, updates) {
138
- const storage = (0, ai_service_factory_1.getStorage)();
260
+ const storage = this.storage;
139
261
  const existingTask = await storage.getTask(id);
140
262
  if (!existingTask) {
141
- throw new Error(`Task with ID ${id} not found`);
263
+ throw (0, task_o_matic_error_1.formatTaskNotFoundError)(id);
142
264
  }
143
265
  // Validate status transitions
144
266
  if (updates.status) {
@@ -148,7 +270,7 @@ class TaskService {
148
270
  completed: ["todo", "in-progress"],
149
271
  };
150
272
  if (!validTransitions[existingTask.status].includes(updates.status)) {
151
- throw new Error(`Invalid status transition from ${existingTask.status} to ${updates.status}`);
273
+ throw (0, task_o_matic_error_1.formatInvalidStatusTransitionError)(existingTask.status, updates.status);
152
274
  }
153
275
  }
154
276
  const finalUpdates = { ...updates };
@@ -160,16 +282,16 @@ class TaskService {
160
282
  }
161
283
  const updatedTask = await storage.updateTask(id, finalUpdates);
162
284
  if (!updatedTask) {
163
- throw new Error(`Failed to update task ${id}`);
285
+ throw (0, task_o_matic_error_1.formatStorageError)(`updateTask ${id}`);
164
286
  }
165
287
  // Emit task:updated event
166
- await hooks_1.hooks.emit("task:updated", {
288
+ await this.hooks.emit("task:updated", {
167
289
  task: updatedTask,
168
290
  changes: finalUpdates,
169
291
  });
170
292
  // Emit task:status-changed event if status changed
171
293
  if (updates.status && updates.status !== existingTask.status) {
172
- await hooks_1.hooks.emit("task:status-changed", {
294
+ await this.hooks.emit("task:status-changed", {
173
295
  task: updatedTask,
174
296
  oldStatus: existingTask.status,
175
297
  newStatus: updates.status,
@@ -182,10 +304,10 @@ class TaskService {
182
304
  }
183
305
  async deleteTask(id, options = {}) {
184
306
  const { cascade, force } = options;
185
- const storage = (0, ai_service_factory_1.getStorage)();
307
+ const storage = this.storage;
186
308
  const taskToDelete = await storage.getTask(id);
187
309
  if (!taskToDelete) {
188
- throw new Error(`Task with ID ${id} not found`);
310
+ throw (0, task_o_matic_error_1.formatTaskNotFoundError)(id);
189
311
  }
190
312
  const deleted = [];
191
313
  const orphanedSubtasks = [];
@@ -193,7 +315,15 @@ class TaskService {
193
315
  const subtasks = await storage.getSubtasks(id);
194
316
  if (subtasks.length > 0 && !cascade) {
195
317
  if (!force) {
196
- throw new Error(`Task has ${subtasks.length} subtasks. Use cascade to delete them or force to orphan them`);
318
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.TASK_OPERATION_FAILED, `Cannot delete task with ${subtasks.length} subtasks`, {
319
+ context: `Task ${id} has ${subtasks.length} subtasks`,
320
+ suggestions: [
321
+ "Use --cascade flag to delete task and all subtasks",
322
+ "Use --force flag to delete task and orphan subtasks",
323
+ "Delete subtasks first, then delete parent task",
324
+ ],
325
+ metadata: { taskId: id, subtaskCount: subtasks.length },
326
+ });
197
327
  }
198
328
  // Orphan subtasks by removing parent reference
199
329
  for (const subtask of subtasks) {
@@ -217,7 +347,7 @@ class TaskService {
217
347
  if (success) {
218
348
  deleted.push(taskToDelete);
219
349
  // Emit task:deleted event
220
- await hooks_1.hooks.emit("task:deleted", { taskId: id });
350
+ await this.hooks.emit("task:deleted", { taskId: id });
221
351
  }
222
352
  return { success: true, deleted, orphanedSubtasks };
223
353
  }
@@ -225,10 +355,10 @@ class TaskService {
225
355
  // TAG OPERATIONS
226
356
  // ============================================================================
227
357
  async addTags(id, tags) {
228
- const storage = (0, ai_service_factory_1.getStorage)();
358
+ const storage = this.storage;
229
359
  const task = await storage.getTask(id);
230
360
  if (!task) {
231
- throw new Error(`Task with ID ${id} not found`);
361
+ throw (0, task_o_matic_error_1.formatTaskNotFoundError)(id);
232
362
  }
233
363
  const existingTags = task.tags || [];
234
364
  const newTags = tags.filter((tag) => !existingTags.includes(tag));
@@ -238,15 +368,15 @@ class TaskService {
238
368
  const updatedTags = [...existingTags, ...newTags];
239
369
  const updatedTask = await storage.updateTask(id, { tags: updatedTags });
240
370
  if (!updatedTask) {
241
- throw new Error(`Failed to add tags to task ${id}`);
371
+ throw (0, task_o_matic_error_1.formatStorageError)(`add tags to task ${id}`);
242
372
  }
243
373
  return updatedTask;
244
374
  }
245
375
  async removeTags(id, tags) {
246
- const storage = (0, ai_service_factory_1.getStorage)();
376
+ const storage = this.storage;
247
377
  const task = await storage.getTask(id);
248
378
  if (!task) {
249
- throw new Error(`Task with ID ${id} not found`);
379
+ throw (0, task_o_matic_error_1.formatTaskNotFoundError)(id);
250
380
  }
251
381
  const existingTags = task.tags || [];
252
382
  const updatedTags = existingTags.filter((tag) => !tags.includes(tag));
@@ -255,15 +385,44 @@ class TaskService {
255
385
  }
256
386
  const updatedTask = await storage.updateTask(id, { tags: updatedTags });
257
387
  if (!updatedTask) {
258
- throw new Error(`Failed to remove tags from task ${id}`);
388
+ throw (0, task_o_matic_error_1.formatStorageError)(`remove tags from task ${id}`);
259
389
  }
260
390
  return updatedTask;
261
391
  }
262
392
  // ============================================================================
263
393
  // TASK NAVIGATION
264
394
  // ============================================================================
395
+ /**
396
+ * Get the next task based on priority and filtering criteria
397
+ *
398
+ * @param filters - Filter and priority criteria
399
+ * @param filters.status - Filter by task status
400
+ * @param filters.tag - Filter by task tag
401
+ * @param filters.effort - Filter by estimated effort
402
+ * @param filters.priority - Priority strategy ("newest", "oldest", "effort", or default)
403
+ *
404
+ * @returns Promise resolving to the highest priority task or null if none found
405
+ *
406
+ * @example
407
+ * ```typescript
408
+ * // Get the next task by default priority (task ID order)
409
+ * const nextTask = await taskService.getNextTask({});
410
+ *
411
+ * // Get the newest task with "todo" status
412
+ * const newestTodo = await taskService.getNextTask({
413
+ * status: "todo",
414
+ * priority: "newest"
415
+ * });
416
+ *
417
+ * // Get the highest effort task with specific tag
418
+ * const highEffortTask = await taskService.getNextTask({
419
+ * tag: "backend",
420
+ * priority: "effort"
421
+ * });
422
+ * ```
423
+ */
265
424
  async getNextTask(filters) {
266
- const storage = (0, ai_service_factory_1.getStorage)();
425
+ const storage = this.storage;
267
426
  const allTasks = await storage.getTasks();
268
427
  // Filter by status and other criteria
269
428
  let filteredTasks = allTasks.filter((task) => {
@@ -293,12 +452,12 @@ class TaskService {
293
452
  }
294
453
  }
295
454
  async getTaskTree(rootId) {
296
- const storage = (0, ai_service_factory_1.getStorage)();
455
+ const storage = this.storage;
297
456
  if (rootId) {
298
457
  // Return tree starting from specific task
299
458
  const rootTask = await storage.getTask(rootId);
300
459
  if (!rootTask) {
301
- throw new Error(`Task with ID ${rootId} not found`);
460
+ throw (0, task_o_matic_error_1.formatTaskNotFoundError)(rootId);
302
461
  }
303
462
  // Get all subtasks recursively
304
463
  const getAllSubtasks = async (task) => {
@@ -322,50 +481,85 @@ class TaskService {
322
481
  // ============================================================================
323
482
  // AI OPERATIONS
324
483
  // ============================================================================
484
+ /**
485
+ * Enhance a task with AI-generated documentation using Context7
486
+ *
487
+ * Uses AI to enrich the task description with relevant documentation,
488
+ * code examples, and best practices from Context7 documentation sources.
489
+ *
490
+ * @param taskId - ID of the task to enhance
491
+ * @param aiOptions - Optional AI configuration overrides
492
+ * @param streamingOptions - Optional streaming callbacks for real-time feedback
493
+ * @returns Promise resolving to enhancement result with metrics
494
+ *
495
+ * @throws {Error} If task not found
496
+ * @throws {TaskOMaticError} If AI enhancement fails
497
+ *
498
+ * @example Basic enhancement
499
+ * ```typescript
500
+ * const result = await taskService.enhanceTask("1");
501
+ * console.log("Enhanced content:", result.enhancedContent);
502
+ * console.log("Took:", result.stats.duration, "ms");
503
+ * ```
504
+ *
505
+ * @example With streaming
506
+ * ```typescript
507
+ * try {
508
+ * const result = await taskService.enhanceTask("1", undefined, {
509
+ * onChunk: (chunk) => process.stdout.write(chunk)
510
+ * });
511
+ * console.log("\nEnhancement complete!");
512
+ * } catch (error) {
513
+ * if (error instanceof TaskOMaticError) {
514
+ * console.error("Enhancement failed:", error.getDetails());
515
+ * }
516
+ * }
517
+ * ```
518
+ */
325
519
  async enhanceTask(taskId, aiOptions, streamingOptions) {
326
520
  const startTime = Date.now();
327
- hooks_1.hooks.emit("task:progress", {
521
+ this.hooks.emit("task:progress", {
328
522
  message: "Starting task enhancement...",
329
523
  type: "started",
330
524
  });
331
- const task = await (0, ai_service_factory_1.getStorage)().getTask(taskId);
525
+ const task = await this.storage.getTask(taskId);
332
526
  if (!task) {
333
- throw new Error(`Task with ID ${taskId} not found`);
527
+ throw (0, task_o_matic_error_1.formatTaskNotFoundError)(taskId);
334
528
  }
335
- hooks_1.hooks.emit("task:progress", {
529
+ this.hooks.emit("task:progress", {
336
530
  message: "Building context...",
337
531
  type: "progress",
338
532
  });
339
- const context = await (0, ai_service_factory_1.getContextBuilder)().buildContext(taskId);
533
+ const context = await this.contextBuilder.buildContext(taskId);
340
534
  const stackInfo = (0, stack_formatter_1.formatStackInfo)(context.stack);
341
535
  const enhancementAIConfig = (0, ai_config_builder_1.buildAIConfig)(aiOptions);
342
- hooks_1.hooks.emit("task:progress", {
536
+ this.hooks.emit("task:progress", {
343
537
  message: "Calling AI for enhancement...",
344
538
  type: "progress",
345
539
  });
346
540
  // Use utility to wrap streaming options and capture metrics (DRY fix 1.1)
347
541
  const aiStartTime = Date.now();
348
542
  const { options: metricsStreamingOptions, getMetrics } = (0, streaming_utils_1.createMetricsStreamingOptions)(streamingOptions, aiStartTime);
349
- const enhancedContent = await (0, ai_service_factory_1.getAIOperations)().enhanceTaskWithDocumentation(task.id, task.title, task.description ?? "", stackInfo, metricsStreamingOptions, undefined, enhancementAIConfig, context.existingResearch);
543
+ const enhancedContent = await this.aiOperations.enhanceTaskWithDocumentation(task.id, task.title, task.description ?? "", stackInfo, metricsStreamingOptions, undefined, enhancementAIConfig, context.existingResearch);
350
544
  // Extract metrics after AI call
351
545
  const { tokenUsage, timeToFirstToken } = getMetrics();
352
- hooks_1.hooks.emit("task:progress", {
546
+ this.hooks.emit("task:progress", {
353
547
  message: "Saving enhanced content...",
354
548
  type: "progress",
355
549
  });
356
550
  const originalLength = task.description?.length || 0;
357
551
  if (enhancedContent.length > 200) {
358
- const contentFile = await (0, ai_service_factory_1.getStorage)().saveEnhancedTaskContent(task.id, enhancedContent);
359
- await (0, ai_service_factory_1.getStorage)().updateTask(task.id, {
552
+ const contentFile = await this.storage.saveEnhancedTaskContent(task.id, enhancedContent);
553
+ await this.storage.updateTask(task.id, {
360
554
  contentFile,
361
555
  description: task.description +
362
556
  "\n\n🤖 AI-enhanced with Context7 documentation available.",
363
557
  });
364
558
  }
365
559
  else {
366
- await (0, ai_service_factory_1.getStorage)().updateTask(task.id, { description: enhancedContent });
560
+ await this.storage.updateTask(task.id, { description: enhancedContent });
367
561
  }
368
- const aiConfig = (0, ai_service_factory_1.getModelProvider)().getAIConfig();
562
+ const aiConfig = this.modelProvider.getAIConfig();
369
563
  const aiMetadata = {
370
564
  taskId: task.id,
371
565
  aiGenerated: true,
@@ -375,9 +569,9 @@ class TaskService {
375
569
  aiModel: aiConfig.model,
376
570
  enhancedAt: Date.now(),
377
571
  };
378
- await (0, ai_service_factory_1.getStorage)().saveTaskAIMetadata(aiMetadata);
572
+ await this.storage.saveTaskAIMetadata(aiMetadata);
379
573
  const duration = Date.now() - startTime;
380
- hooks_1.hooks.emit("task:progress", {
574
+ this.hooks.emit("task:progress", {
381
575
  message: "Task enhancement completed",
382
576
  type: "completed",
383
577
  });
@@ -400,32 +594,86 @@ class TaskService {
400
594
  },
401
595
  };
402
596
  }
597
+ /**
598
+ * Split a task into subtasks using AI
599
+ *
600
+ * Analyzes the task and breaks it down into smaller, actionable subtasks
601
+ * with estimated effort. Can optionally use filesystem tools to understand
602
+ * project structure when creating subtasks.
603
+ *
604
+ * @param taskId - ID of the task to split
605
+ * @param aiOptions - Optional AI configuration overrides
606
+ * @param promptOverride - Optional custom prompt
607
+ * @param messageOverride - Optional custom message
608
+ * @param streamingOptions - Optional streaming callbacks
609
+ * @param enableFilesystemTools - Enable filesystem analysis for context
610
+ * @returns Promise resolving to split result with created subtasks
611
+ *
612
+ * @throws {Error} If task not found or already has subtasks
613
+ * @throws {TaskOMaticError} If AI operation fails
614
+ *
615
+ * @example Basic task splitting
616
+ * ```typescript
617
+ * const result = await taskService.splitTask("1");
618
+ * console.log(`Created ${result.subtasks.length} subtasks`);
619
+ * result.subtasks.forEach(subtask => {
620
+ * console.log(`- ${subtask.title} (${subtask.estimatedEffort})`);
621
+ * });
622
+ * ```
623
+ *
624
+ * @example With filesystem tools for code analysis
625
+ * ```typescript
626
+ * try {
627
+ * const result = await taskService.splitTask(
628
+ * "1",
629
+ * undefined,
630
+ * undefined,
631
+ * undefined,
632
+ * { onChunk: (chunk) => console.log(chunk) },
633
+ * true // Enable filesystem tools
634
+ * );
635
+ * console.log("AI analyzed codebase to create subtasks");
636
+ * } catch (error) {
637
+ * if (error instanceof TaskOMaticError) {
638
+ * console.error("Split failed:", error.suggestions);
639
+ * }
640
+ * }
641
+ * ```
642
+ */
403
643
  async splitTask(taskId, aiOptions, promptOverride, messageOverride, streamingOptions, enableFilesystemTools) {
404
644
  const startTime = Date.now();
405
- hooks_1.hooks.emit("task:progress", {
645
+ this.hooks.emit("task:progress", {
406
646
  message: "Starting task breakdown...",
407
647
  type: "started",
408
648
  });
409
- const task = await (0, ai_service_factory_1.getStorage)().getTask(taskId);
649
+ const task = await this.storage.getTask(taskId);
410
650
  if (!task) {
411
- throw new Error(`Task with ID ${taskId} not found`);
651
+ throw (0, task_o_matic_error_1.formatTaskNotFoundError)(taskId);
412
652
  }
413
653
  // Check if task already has subtasks
414
- const existingSubtasks = await (0, ai_service_factory_1.getStorage)().getSubtasks(taskId);
654
+ const existingSubtasks = await this.storage.getSubtasks(taskId);
415
655
  if (existingSubtasks.length > 0) {
416
- throw new Error(`Task ${task.title} already has ${existingSubtasks.length} subtasks. Use existing subtasks or delete them first.`);
656
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.TASK_OPERATION_FAILED, `Task already has ${existingSubtasks.length} subtasks`, {
657
+ context: `Task "${task.title}" (${taskId}) already has subtasks`,
658
+ suggestions: [
659
+ "Use existing subtasks instead of splitting again",
660
+ "Delete existing subtasks first if you want to re-split",
661
+ "Consider editing existing subtasks instead",
662
+ ],
663
+ metadata: { taskId, subtaskCount: existingSubtasks.length },
664
+ });
417
665
  }
418
- hooks_1.hooks.emit("task:progress", {
666
+ this.hooks.emit("task:progress", {
419
667
  message: "Building context...",
420
668
  type: "progress",
421
669
  });
422
670
  // Build comprehensive context
423
- const context = await (0, ai_service_factory_1.getContextBuilder)().buildContext(taskId);
671
+ const context = await this.contextBuilder.buildContext(taskId);
424
672
  const stackInfo = (0, stack_formatter_1.formatStackInfo)(context.stack);
425
673
  // Get full task content
426
674
  const fullContent = context.task.fullContent || task.description || "";
427
675
  const breakdownAIConfig = (0, ai_config_builder_1.buildAIConfig)(aiOptions);
428
- hooks_1.hooks.emit("task:progress", {
676
+ this.hooks.emit("task:progress", {
429
677
  message: "Calling AI to break down task...",
430
678
  type: "progress",
431
679
  });
@@ -433,20 +681,20 @@ class TaskService {
433
681
  const aiStartTime = Date.now();
434
682
  const { options: metricsStreamingOptions, getMetrics } = (0, streaming_utils_1.createMetricsStreamingOptions)(streamingOptions, aiStartTime);
435
683
  // Use AI service to break down the task with enhanced context
436
- const subtaskData = await (0, ai_service_factory_1.getAIOperations)().breakdownTask(task, breakdownAIConfig, promptOverride, messageOverride, metricsStreamingOptions, undefined, fullContent, stackInfo, existingSubtasks, enableFilesystemTools);
684
+ const subtaskData = await this.aiOperations.breakdownTask(task, breakdownAIConfig, promptOverride, messageOverride, metricsStreamingOptions, undefined, fullContent, stackInfo, existingSubtasks, enableFilesystemTools);
437
685
  // Extract metrics after AI call
438
686
  const { tokenUsage, timeToFirstToken } = getMetrics();
439
- hooks_1.hooks.emit("task:progress", {
687
+ this.hooks.emit("task:progress", {
440
688
  message: `Creating ${subtaskData.length} subtasks...`,
441
689
  type: "progress",
442
690
  });
443
691
  // Create subtasks and save AI metadata for each (Bug fix 2.3)
444
692
  const createdSubtasks = [];
445
- const aiConfig = (0, ai_service_factory_1.getModelProvider)().getAIConfig();
693
+ const aiConfig = this.modelProvider.getAIConfig();
446
694
  const splitTimestamp = Date.now();
447
695
  for (let i = 0; i < subtaskData.length; i++) {
448
696
  const subtask = subtaskData[i];
449
- hooks_1.hooks.emit("task:progress", {
697
+ this.hooks.emit("task:progress", {
450
698
  message: `Creating subtask ${i + 1}/${subtaskData.length}: ${subtask.title}`,
451
699
  type: "progress",
452
700
  });
@@ -486,7 +734,7 @@ class TaskService {
486
734
  };
487
735
  await (0, ai_service_factory_1.getStorage)().saveTaskAIMetadata(parentMetadata);
488
736
  const duration = Date.now() - startTime;
489
- hooks_1.hooks.emit("task:progress", {
737
+ this.hooks.emit("task:progress", {
490
738
  message: `Task split into ${createdSubtasks.length} subtasks`,
491
739
  type: "completed",
492
740
  });
@@ -508,19 +756,57 @@ class TaskService {
508
756
  },
509
757
  };
510
758
  }
759
+ /**
760
+ * Analyze and fetch documentation for a task using Context7
761
+ *
762
+ * Analyzes the task content to identify required libraries and documentation,
763
+ * then fetches relevant documentation from Context7. Caches documentation
764
+ * for future use.
765
+ *
766
+ * @param taskId - ID of the task to document
767
+ * @param force - Force re-fetch even if documentation exists
768
+ * @param aiOptions - Optional AI configuration overrides
769
+ * @param streamingOptions - Optional streaming callbacks
770
+ * @returns Promise resolving to documentation analysis result
771
+ *
772
+ * @throws {Error} If task not found or content is empty
773
+ * @throws {TaskOMaticError} If AI operation fails
774
+ *
775
+ * @example Analyze documentation needs
776
+ * ```typescript
777
+ * const result = await taskService.documentTask("1");
778
+ * if (result.documentation) {
779
+ * console.log("Documentation fetched:");
780
+ * console.log(result.documentation.recap);
781
+ * console.log("Libraries:", result.documentation.libraries);
782
+ * }
783
+ * ```
784
+ *
785
+ * @example Force refresh documentation
786
+ * ```typescript
787
+ * try {
788
+ * const result = await taskService.documentTask("1", true);
789
+ * console.log(`Analyzed ${result.analysis.libraries.length} libraries`);
790
+ * } catch (error) {
791
+ * if (error instanceof TaskOMaticError) {
792
+ * console.error("Documentation fetch failed:", error.getDetails());
793
+ * }
794
+ * }
795
+ * ```
796
+ */
511
797
  async documentTask(taskId, force = false, aiOptions, streamingOptions) {
512
798
  const startTime = Date.now();
513
- hooks_1.hooks.emit("task:progress", {
799
+ this.hooks.emit("task:progress", {
514
800
  message: "Analyzing documentation needs...",
515
801
  type: "started",
516
802
  });
517
- const task = await (0, ai_service_factory_1.getStorage)().getTask(taskId);
803
+ const task = await this.storage.getTask(taskId);
518
804
  if (!task) {
519
- throw new Error(`Task with ID ${taskId} not found`);
805
+ throw (0, task_o_matic_error_1.formatTaskNotFoundError)(taskId);
520
806
  }
521
807
  if (task.documentation && !force) {
522
- if ((0, ai_service_factory_1.getContextBuilder)().isDocumentationFresh(task.documentation)) {
523
- hooks_1.hooks.emit("task:progress", {
808
+ if (this.contextBuilder.isDocumentationFresh(task.documentation)) {
809
+ this.hooks.emit("task:progress", {
524
810
  message: "Documentation is fresh, skipping analysis",
525
811
  type: "info",
526
812
  });
@@ -534,38 +820,45 @@ class TaskService {
534
820
  };
535
821
  }
536
822
  }
537
- hooks_1.hooks.emit("task:progress", {
823
+ this.hooks.emit("task:progress", {
538
824
  message: "Building context...",
539
825
  type: "progress",
540
826
  });
541
- const context = await (0, ai_service_factory_1.getContextBuilder)().buildContext(taskId);
827
+ const context = await this.contextBuilder.buildContext(taskId);
542
828
  const stackInfo = (0, stack_formatter_1.formatStackInfo)(context.stack);
543
829
  const analysisAIConfig = (0, ai_config_builder_1.buildAIConfig)(aiOptions);
544
830
  // Get full task content
545
831
  const fullContent = context.task.fullContent || task.description;
546
832
  if (!fullContent) {
547
- throw new Error("Task content is empty");
833
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.INVALID_INPUT, "Task content is empty", {
834
+ context: `Task ${taskId} has no content to enhance`,
835
+ suggestions: [
836
+ "Add content to the task before enhancing",
837
+ "Provide task description or details",
838
+ ],
839
+ metadata: { taskId },
840
+ });
548
841
  }
549
842
  // Get existing documentations from all tasks
550
- const tasks = await (0, ai_service_factory_1.getStorage)().getTasks();
843
+ const tasks = await this.storage.getTasks();
551
844
  const documentations = tasks.map((task) => task.documentation);
552
- hooks_1.hooks.emit("task:progress", {
845
+ this.hooks.emit("task:progress", {
553
846
  message: "Calling AI to analyze documentation needs...",
554
847
  type: "progress",
555
848
  });
556
849
  // First analyze what documentation is needed
557
- const analysis = await (0, ai_service_factory_1.getAIOperations)().analyzeDocumentationNeeds(task.id, task.title, fullContent, stackInfo, streamingOptions, undefined, analysisAIConfig, documentations);
850
+ const analysis = await this.aiOperations.analyzeDocumentationNeeds(task.id, task.title, fullContent, stackInfo, streamingOptions, undefined, analysisAIConfig, documentations);
558
851
  let documentation;
559
852
  if (analysis.libraries.length > 0) {
560
- hooks_1.hooks.emit("task:progress", {
853
+ this.hooks.emit("task:progress", {
561
854
  message: `Fetching documentation for ${analysis.libraries.length} libraries...`,
562
855
  type: "progress",
563
856
  });
564
857
  // Build research object from actual libraries
565
858
  const research = {};
566
859
  for (const lib of analysis.libraries) {
567
- const sanitizedLibrary = (0, ai_service_factory_1.getStorage)().sanitizeForFilename(lib.name);
568
- const sanitizedQuery = (0, ai_service_factory_1.getStorage)().sanitizeForFilename(lib.searchQuery);
860
+ const sanitizedLibrary = this.storage.sanitizeForFilename(lib.name);
861
+ const sanitizedQuery = this.storage.sanitizeForFilename(lib.searchQuery);
569
862
  const docFile = `docs/${sanitizedLibrary}/${sanitizedQuery}.md`;
570
863
  if (!research[lib.name]) {
571
864
  research[lib.name] = [];
@@ -575,11 +868,11 @@ class TaskService {
575
868
  doc: docFile,
576
869
  });
577
870
  }
578
- hooks_1.hooks.emit("task:progress", {
871
+ this.hooks.emit("task:progress", {
579
872
  message: "Generating documentation recap...",
580
873
  type: "progress",
581
874
  });
582
- const recap = await (0, ai_service_factory_1.getAIOperations)().generateDocumentationRecap(analysis.libraries, analysis.toolResults?.map((tr) => ({
875
+ const recap = await this.aiOperations.generateDocumentationRecap(analysis.libraries, analysis.toolResults?.map((tr) => ({
583
876
  library: tr.toolName,
584
877
  content: JSON.stringify(tr.output),
585
878
  })) || [], streamingOptions);
@@ -590,14 +883,14 @@ class TaskService {
590
883
  files: analysis.files || [],
591
884
  research,
592
885
  };
593
- hooks_1.hooks.emit("task:progress", {
886
+ this.hooks.emit("task:progress", {
594
887
  message: "Saving documentation...",
595
888
  type: "progress",
596
889
  });
597
- await (0, ai_service_factory_1.getStorage)().updateTask(taskId, { documentation });
890
+ await this.storage.updateTask(taskId, { documentation });
598
891
  }
599
892
  const duration = Date.now() - startTime;
600
- hooks_1.hooks.emit("task:progress", {
893
+ this.hooks.emit("task:progress", {
601
894
  message: "Documentation analysis completed",
602
895
  type: "completed",
603
896
  });
@@ -611,19 +904,59 @@ class TaskService {
611
904
  },
612
905
  };
613
906
  }
907
+ /**
908
+ * Generate an implementation plan for a task using AI
909
+ *
910
+ * Creates a detailed implementation plan with steps, considerations,
911
+ * and technical approach. Uses filesystem and Context7 tools to understand
912
+ * the project context and provide relevant suggestions.
913
+ *
914
+ * @param taskId - ID of the task to plan
915
+ * @param aiOptions - Optional AI configuration overrides
916
+ * @param streamingOptions - Optional streaming callbacks
917
+ * @returns Promise resolving to plan result with generated plan text
918
+ *
919
+ * @throws {Error} If task not found
920
+ * @throws {TaskOMaticError} If AI operation fails
921
+ *
922
+ * @example Basic implementation planning
923
+ * ```typescript
924
+ * const result = await taskService.planTask("1");
925
+ * console.log("Implementation Plan:");
926
+ * console.log(result.plan);
927
+ * console.log(`Generated in ${result.stats.duration}ms`);
928
+ * ```
929
+ *
930
+ * @example With streaming for real-time plan generation
931
+ * ```typescript
932
+ * try {
933
+ * const result = await taskService.planTask("1", undefined, {
934
+ * onChunk: (chunk) => {
935
+ * // Display plan as it's generated
936
+ * process.stdout.write(chunk);
937
+ * }
938
+ * });
939
+ * console.log("\n\nPlan saved to:", `plans/${result.task.id}.md`);
940
+ * } catch (error) {
941
+ * if (error instanceof TaskOMaticError) {
942
+ * console.error("Planning failed:", error.getDetails());
943
+ * }
944
+ * }
945
+ * ```
946
+ */
614
947
  async planTask(taskId, aiOptions, streamingOptions) {
615
948
  const startTime = Date.now();
616
- hooks_1.hooks.emit("task:progress", {
949
+ this.hooks.emit("task:progress", {
617
950
  message: "Creating implementation plan...",
618
951
  type: "started",
619
952
  });
620
- const task = await (0, ai_service_factory_1.getStorage)().getTask(taskId);
953
+ const task = await this.storage.getTask(taskId);
621
954
  if (!task) {
622
- throw new Error(`Task with ID ${taskId} not found`);
955
+ throw (0, task_o_matic_error_1.formatTaskNotFoundError)(taskId);
623
956
  }
624
- const aiService = (0, ai_service_factory_1.getAIOperations)();
957
+ const aiService = this.aiOperations;
625
958
  const planAIConfig = (0, ai_config_builder_1.buildAIConfig)(aiOptions);
626
- hooks_1.hooks.emit("task:progress", {
959
+ this.hooks.emit("task:progress", {
627
960
  message: "Building task context...",
628
961
  type: "progress",
629
962
  });
@@ -647,7 +980,7 @@ class TaskService {
647
980
  taskDetails += ` ${subtask.description || "No description"}\n\n`;
648
981
  });
649
982
  }
650
- hooks_1.hooks.emit("task:progress", {
983
+ this.hooks.emit("task:progress", {
651
984
  message: "Calling AI to create plan...",
652
985
  type: "progress",
653
986
  });
@@ -657,18 +990,18 @@ class TaskService {
657
990
  const plan = await aiService.planTask(taskContext, taskDetails, planAIConfig, undefined, undefined, metricsStreamingOptions);
658
991
  // Extract metrics after AI call
659
992
  const { tokenUsage, timeToFirstToken } = getMetrics();
660
- hooks_1.hooks.emit("task:progress", {
993
+ this.hooks.emit("task:progress", {
661
994
  message: "Saving plan...",
662
995
  type: "progress",
663
996
  });
664
997
  // Save the plan to storage
665
- await (0, ai_service_factory_1.getStorage)().savePlan(taskId, plan);
998
+ await this.storage.savePlan(taskId, plan);
666
999
  const duration = Date.now() - startTime;
667
- hooks_1.hooks.emit("task:progress", {
1000
+ this.hooks.emit("task:progress", {
668
1001
  message: "Implementation plan created",
669
1002
  type: "completed",
670
1003
  });
671
- const aiConfig = (0, ai_service_factory_1.getModelProvider)().getAIConfig();
1004
+ const aiConfig = this.modelProvider.getAIConfig();
672
1005
  return {
673
1006
  success: true,
674
1007
  task,
@@ -689,36 +1022,48 @@ class TaskService {
689
1022
  // DOCUMENTATION OPERATIONS
690
1023
  // ============================================================================
691
1024
  async getTaskDocumentation(taskId) {
692
- return (0, ai_service_factory_1.getStorage)().getTaskDocumentation(taskId);
1025
+ return this.storage.getTaskDocumentation(taskId);
693
1026
  }
694
1027
  async addTaskDocumentationFromFile(taskId, filePath) {
695
1028
  const task = await this.getTask(taskId);
696
1029
  if (!task) {
697
- throw new Error(`Task with ID ${taskId} not found`);
1030
+ throw (0, task_o_matic_error_1.formatTaskNotFoundError)(taskId);
698
1031
  }
699
1032
  try {
700
1033
  const { readFileSync, existsSync } = await Promise.resolve().then(() => __importStar(require("fs")));
701
1034
  const { resolve } = await Promise.resolve().then(() => __importStar(require("path")));
702
1035
  const resolvedPath = resolve(filePath);
703
1036
  if (!existsSync(resolvedPath)) {
704
- throw new Error(`Documentation file not found: ${filePath}`);
1037
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.STORAGE_ERROR, `Documentation file not found: ${filePath}`, {
1038
+ context: `Tried to load documentation from ${resolvedPath}`,
1039
+ suggestions: [
1040
+ "Check that the file path is correct",
1041
+ "Ensure the file exists",
1042
+ "Use an absolute path or path relative to current directory",
1043
+ ],
1044
+ metadata: { filePath, resolvedPath },
1045
+ });
705
1046
  }
706
1047
  const content = readFileSync(resolvedPath, "utf-8");
707
- const savedPath = await (0, ai_service_factory_1.getStorage)().saveTaskDocumentation(taskId, content);
1048
+ const savedPath = await this.storage.saveTaskDocumentation(taskId, content);
708
1049
  return {
709
1050
  filePath: savedPath,
710
1051
  task,
711
1052
  };
712
1053
  }
713
1054
  catch (error) {
714
- // Use utility for error message extraction (DRY fix 1.3)
715
- throw new Error(`Failed to add documentation from file: ${(0, error_utils_1.getErrorMessage)(error)}`);
1055
+ // Re-throw if already a TaskOMaticError
1056
+ if (error instanceof task_o_matic_error_1.TaskOMaticError) {
1057
+ throw error;
1058
+ }
1059
+ // Wrap other errors
1060
+ throw (0, task_o_matic_error_1.formatStorageError)("saveTaskDocumentation", error instanceof Error ? error : undefined);
716
1061
  }
717
1062
  }
718
1063
  async setTaskPlan(taskId, planText, planFilePath) {
719
1064
  const task = await this.getTask(taskId);
720
1065
  if (!task) {
721
- throw new Error(`Task with ID ${taskId} not found`);
1066
+ throw (0, task_o_matic_error_1.formatTaskNotFoundError)(taskId);
722
1067
  }
723
1068
  let plan;
724
1069
  if (planFilePath) {
@@ -727,22 +1072,41 @@ class TaskService {
727
1072
  const { resolve } = await Promise.resolve().then(() => __importStar(require("path")));
728
1073
  const resolvedPath = resolve(planFilePath);
729
1074
  if (!existsSync(resolvedPath)) {
730
- throw new Error(`Plan file not found: ${planFilePath}`);
1075
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.STORAGE_ERROR, `Plan file not found: ${planFilePath}`, {
1076
+ context: `Tried to load plan from ${resolvedPath}`,
1077
+ suggestions: [
1078
+ "Check that the file path is correct",
1079
+ "Ensure the file exists",
1080
+ "Use an absolute path or path relative to current directory",
1081
+ ],
1082
+ metadata: { planFilePath, resolvedPath },
1083
+ });
731
1084
  }
732
1085
  plan = readFileSync(resolvedPath, "utf-8");
733
1086
  }
734
1087
  catch (error) {
735
- // Use utility for error message extraction (DRY fix 1.3)
736
- throw new Error(`Failed to read plan file: ${(0, error_utils_1.getErrorMessage)(error)}`);
1088
+ // Re-throw if already a TaskOMaticError
1089
+ if (error instanceof task_o_matic_error_1.TaskOMaticError) {
1090
+ throw error;
1091
+ }
1092
+ // Wrap other errors
1093
+ throw (0, task_o_matic_error_1.formatStorageError)("readFileSync", error instanceof Error ? error : undefined);
737
1094
  }
738
1095
  }
739
1096
  else if (planText) {
740
1097
  plan = planText;
741
1098
  }
742
1099
  else {
743
- throw new Error("Either planText or planFilePath must be provided");
1100
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.INVALID_INPUT, "Either planText or planFilePath must be provided", {
1101
+ context: "setTaskPlan requires either planText or planFilePath parameter",
1102
+ suggestions: [
1103
+ "Provide planText parameter with the plan content",
1104
+ "Provide planFilePath parameter with path to plan file",
1105
+ ],
1106
+ metadata: { taskId },
1107
+ });
744
1108
  }
745
- await (0, ai_service_factory_1.getStorage)().savePlan(taskId, plan);
1109
+ await this.storage.savePlan(taskId, plan);
746
1110
  const planFile = `plans/${taskId}.md`;
747
1111
  return {
748
1112
  planFile,
@@ -753,15 +1117,27 @@ class TaskService {
753
1117
  // PLAN OPERATIONS
754
1118
  // ============================================================================
755
1119
  async getTaskPlan(taskId) {
756
- return (0, ai_service_factory_1.getStorage)().getPlan(taskId);
1120
+ return this.storage.getPlan(taskId);
757
1121
  }
758
1122
  async listTaskPlans() {
759
- return (0, ai_service_factory_1.getStorage)().listPlans();
1123
+ return this.storage.listPlans();
760
1124
  }
761
1125
  async deleteTaskPlan(taskId) {
762
- return (0, ai_service_factory_1.getStorage)().deletePlan(taskId);
1126
+ return this.storage.deletePlan(taskId);
763
1127
  }
764
1128
  }
765
1129
  exports.TaskService = TaskService;
766
- // Export singleton instance
767
- exports.taskService = new TaskService();
1130
+ // Lazy singleton instance - only created when first accessed
1131
+ let taskServiceInstance;
1132
+ function getTaskService() {
1133
+ if (!taskServiceInstance) {
1134
+ taskServiceInstance = new TaskService();
1135
+ }
1136
+ return taskServiceInstance;
1137
+ }
1138
+ // Backward compatibility: export as const but use getter
1139
+ exports.taskService = new Proxy({}, {
1140
+ get(target, prop) {
1141
+ return getTaskService()[prop];
1142
+ },
1143
+ });