task-o-matic 0.0.14 → 0.0.16

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