task-o-matic 0.0.13 → 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 (144) 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 +60 -12
  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 +5 -4
  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/list.js +2 -2
  16. package/dist/commands/tasks/next.js +4 -4
  17. package/dist/commands/tasks/plan/set.d.ts.map +1 -1
  18. package/dist/commands/tasks/plan/set.js +11 -3
  19. package/dist/commands/tasks/show.d.ts.map +1 -1
  20. package/dist/commands/tasks/show.js +2 -1
  21. package/dist/commands/tasks/status.d.ts.map +1 -1
  22. package/dist/commands/tasks/status.js +4 -3
  23. package/dist/commands/tasks/update.d.ts.map +1 -1
  24. package/dist/commands/tasks/update.js +7 -1
  25. package/dist/lib/ai-service/ai-operations.d.ts +1 -1
  26. package/dist/lib/ai-service/ai-operations.d.ts.map +1 -1
  27. package/dist/lib/ai-service/base-operations.d.ts +22 -0
  28. package/dist/lib/ai-service/base-operations.d.ts.map +1 -1
  29. package/dist/lib/ai-service/base-operations.js +29 -1
  30. package/dist/lib/ai-service/model-provider.d.ts.map +1 -1
  31. package/dist/lib/ai-service/model-provider.js +37 -6
  32. package/dist/lib/ai-service/task-operations.d.ts +2 -1
  33. package/dist/lib/ai-service/task-operations.d.ts.map +1 -1
  34. package/dist/lib/ai-service/task-operations.js +135 -173
  35. package/dist/lib/benchmark/registry.d.ts.map +1 -1
  36. package/dist/lib/benchmark/registry.js +6 -10
  37. package/dist/lib/better-t-stack-cli.d.ts +36 -21
  38. package/dist/lib/better-t-stack-cli.d.ts.map +1 -1
  39. package/dist/lib/better-t-stack-cli.js +212 -33
  40. package/dist/lib/bootstrap/cli-bootstrap.d.ts +14 -0
  41. package/dist/lib/bootstrap/cli-bootstrap.d.ts.map +1 -0
  42. package/dist/lib/bootstrap/cli-bootstrap.js +325 -0
  43. package/dist/lib/bootstrap/index.d.ts +4 -0
  44. package/dist/lib/bootstrap/index.d.ts.map +1 -0
  45. package/dist/lib/bootstrap/index.js +19 -0
  46. package/dist/lib/bootstrap/medusa-bootstrap.d.ts +14 -0
  47. package/dist/lib/bootstrap/medusa-bootstrap.d.ts.map +1 -0
  48. package/dist/lib/bootstrap/medusa-bootstrap.js +218 -0
  49. package/dist/lib/bootstrap/opentui-bootstrap.d.ts +11 -0
  50. package/dist/lib/bootstrap/opentui-bootstrap.d.ts.map +1 -0
  51. package/dist/lib/bootstrap/opentui-bootstrap.js +342 -0
  52. package/dist/lib/config-validation.d.ts +215 -0
  53. package/dist/lib/config-validation.d.ts.map +1 -0
  54. package/dist/lib/config-validation.js +246 -0
  55. package/dist/lib/config.d.ts +14 -0
  56. package/dist/lib/config.d.ts.map +1 -1
  57. package/dist/lib/config.js +37 -5
  58. package/dist/lib/storage/file-system.d.ts.map +1 -1
  59. package/dist/lib/storage/file-system.js +81 -21
  60. package/dist/lib/task-execution-core.d.ts.map +1 -1
  61. package/dist/lib/task-execution-core.js +3 -2
  62. package/dist/services/prd.d.ts +17 -0
  63. package/dist/services/prd.d.ts.map +1 -1
  64. package/dist/services/prd.js +67 -60
  65. package/dist/services/tasks.d.ts +317 -3
  66. package/dist/services/tasks.d.ts.map +1 -1
  67. package/dist/services/tasks.js +531 -185
  68. package/dist/services/workflow-ai-assistant.d.ts.map +1 -1
  69. package/dist/services/workflow-ai-assistant.js +19 -6
  70. package/dist/test/lib/ai-service/task-operations.test.d.ts +2 -0
  71. package/dist/test/lib/ai-service/task-operations.test.d.ts.map +1 -0
  72. package/dist/test/lib/ai-service/task-operations.test.js +362 -0
  73. package/dist/test/mocks/mock-ai-operations.d.ts +15 -0
  74. package/dist/test/mocks/mock-ai-operations.d.ts.map +1 -0
  75. package/dist/test/mocks/mock-ai-operations.js +107 -0
  76. package/dist/test/mocks/mock-context-builder.d.ts +10 -0
  77. package/dist/test/mocks/mock-context-builder.d.ts.map +1 -0
  78. package/dist/test/mocks/mock-context-builder.js +81 -0
  79. package/dist/test/mocks/mock-model-provider.d.ts +7 -0
  80. package/dist/test/mocks/mock-model-provider.d.ts.map +1 -0
  81. package/dist/test/mocks/mock-model-provider.js +21 -0
  82. package/dist/test/mocks/mock-service-factory.d.ts +11 -0
  83. package/dist/test/mocks/mock-service-factory.d.ts.map +1 -0
  84. package/dist/test/mocks/mock-service-factory.js +61 -0
  85. package/dist/test/mocks/mock-storage.d.ts +50 -0
  86. package/dist/test/mocks/mock-storage.d.ts.map +1 -0
  87. package/dist/test/mocks/mock-storage.js +145 -0
  88. package/dist/test/services/task-service.test.d.ts +2 -0
  89. package/dist/test/services/task-service.test.d.ts.map +1 -0
  90. package/dist/test/services/task-service.test.js +352 -0
  91. package/dist/test/test-mock-setup.d.ts +26 -0
  92. package/dist/test/test-mock-setup.d.ts.map +1 -0
  93. package/dist/test/test-mock-setup.js +41 -0
  94. package/dist/test/test-setup.d.ts +9 -0
  95. package/dist/test/test-setup.d.ts.map +1 -0
  96. package/dist/test/test-setup.js +44 -0
  97. package/dist/test/test-utils.d.ts +22 -0
  98. package/dist/test/test-utils.d.ts.map +1 -0
  99. package/dist/test/test-utils.js +37 -0
  100. package/dist/test/utils/ai-operation-utility.test.d.ts +2 -0
  101. package/dist/test/utils/ai-operation-utility.test.d.ts.map +1 -0
  102. package/dist/test/utils/ai-operation-utility.test.js +290 -0
  103. package/dist/test/utils/error-handling.test.d.ts +2 -0
  104. package/dist/test/utils/error-handling.test.d.ts.map +1 -0
  105. package/dist/test/utils/error-handling.test.js +231 -0
  106. package/dist/types/index.d.ts +36 -1
  107. package/dist/types/index.d.ts.map +1 -1
  108. package/dist/types/results.d.ts +60 -6
  109. package/dist/types/results.d.ts.map +1 -1
  110. package/dist/utils/ai-operation-utility.d.ts +142 -0
  111. package/dist/utils/ai-operation-utility.d.ts.map +1 -0
  112. package/dist/utils/ai-operation-utility.js +288 -0
  113. package/dist/utils/ai-service-factory.d.ts +10 -0
  114. package/dist/utils/ai-service-factory.d.ts.map +1 -1
  115. package/dist/utils/ai-service-factory.js +19 -1
  116. package/dist/utils/cli-validators.d.ts +2 -2
  117. package/dist/utils/cli-validators.d.ts.map +1 -1
  118. package/dist/utils/cli-validators.js +7 -6
  119. package/dist/utils/error-utils.d.ts +70 -0
  120. package/dist/utils/error-utils.d.ts.map +1 -0
  121. package/dist/utils/error-utils.js +104 -0
  122. package/dist/utils/file-utils.d.ts +49 -0
  123. package/dist/utils/file-utils.d.ts.map +1 -0
  124. package/dist/utils/file-utils.js +82 -0
  125. package/dist/utils/id-generator.d.ts +92 -0
  126. package/dist/utils/id-generator.d.ts.map +1 -0
  127. package/dist/utils/id-generator.js +146 -0
  128. package/dist/utils/model-executor-parser.d.ts +1 -1
  129. package/dist/utils/model-executor-parser.d.ts.map +1 -1
  130. package/dist/utils/model-executor-parser.js +3 -2
  131. package/dist/utils/stack-formatter.d.ts +2 -1
  132. package/dist/utils/stack-formatter.d.ts.map +1 -1
  133. package/dist/utils/stack-formatter.js +8 -2
  134. package/dist/utils/storage-utils.d.ts +49 -0
  135. package/dist/utils/storage-utils.d.ts.map +1 -0
  136. package/dist/utils/storage-utils.js +80 -0
  137. package/dist/utils/streaming-utils.d.ts +38 -0
  138. package/dist/utils/streaming-utils.d.ts.map +1 -0
  139. package/dist/utils/streaming-utils.js +56 -0
  140. package/dist/utils/task-o-matic-error.d.ts +206 -0
  141. package/dist/utils/task-o-matic-error.d.ts.map +1 -0
  142. package/dist/utils/task-o-matic-error.js +304 -0
  143. package/docs/agents/cli.md +58 -149
  144. package/package.json +2 -2
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FileSystemStorage = void 0;
4
4
  const config_1 = require("../config");
5
5
  const storage_callbacks_1 = require("./storage-callbacks");
6
+ const task_o_matic_error_1 = require("../../utils/task-o-matic-error");
6
7
  class FileSystemStorage {
7
8
  callbacks;
8
9
  constructor(callbacks) {
@@ -16,30 +17,54 @@ class FileSystemStorage {
16
17
  }
17
18
  validateTaskId(taskId) {
18
19
  if (!taskId || typeof taskId !== "string" || taskId.trim() === "") {
19
- throw new Error("Task ID must be a non-empty string");
20
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.INVALID_INPUT, "Task ID must be a non-empty string", {
21
+ context: "validateTaskId",
22
+ suggestions: ["Provide a valid task ID string"],
23
+ metadata: { taskId },
24
+ });
20
25
  }
21
26
  }
22
27
  validateTaskRequest(task) {
23
28
  if (!task || typeof task !== "object") {
24
- throw new Error("Task request must be a valid object");
29
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.INVALID_INPUT, "Task request must be a valid object", {
30
+ context: "validateTaskRequest",
31
+ suggestions: ["Provide a valid task request object"],
32
+ });
25
33
  }
26
34
  if (!task.title ||
27
35
  typeof task.title !== "string" ||
28
36
  task.title.trim() === "") {
29
- throw new Error("Task title is required and must be a non-empty string");
37
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.INVALID_INPUT, "Task title is required and must be a non-empty string", {
38
+ context: "validateTaskRequest - title validation",
39
+ suggestions: ["Provide a non-empty title for the task"],
40
+ });
30
41
  }
31
42
  if (task.parentId && typeof task.parentId !== "string") {
32
- throw new Error("Parent ID must be a string if provided");
43
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.INVALID_INPUT, "Parent ID must be a string if provided", {
44
+ context: "validateTaskRequest - parentId validation",
45
+ suggestions: ["Provide a valid parent ID string or omit the field"],
46
+ metadata: { parentId: task.parentId },
47
+ });
33
48
  }
34
49
  if (task.estimatedEffort &&
35
50
  !["small", "medium", "large"].includes(task.estimatedEffort)) {
36
- throw new Error("Estimated effort must be 'small', 'medium', or 'large'");
51
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.INVALID_INPUT, "Estimated effort must be 'small', 'medium', or 'large'", {
52
+ context: "validateTaskRequest - estimatedEffort validation",
53
+ suggestions: ["Use 'small', 'medium', or 'large' for estimatedEffort"],
54
+ metadata: { estimatedEffort: task.estimatedEffort },
55
+ });
37
56
  }
38
57
  if (task.dependencies && !Array.isArray(task.dependencies)) {
39
- throw new Error("Dependencies must be an array if provided");
58
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.INVALID_INPUT, "Dependencies must be an array if provided", {
59
+ context: "validateTaskRequest - dependencies validation",
60
+ suggestions: ["Provide dependencies as an array of task IDs"],
61
+ });
40
62
  }
41
63
  if (task.tags && !Array.isArray(task.tags)) {
42
- throw new Error("Tags must be an array if provided");
64
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.INVALID_INPUT, "Tags must be an array if provided", {
65
+ context: "validateTaskRequest - tags validation",
66
+ suggestions: ["Provide tags as an array of strings"],
67
+ });
43
68
  }
44
69
  }
45
70
  async loadTasksData() {
@@ -60,7 +85,7 @@ class FileSystemStorage {
60
85
  await this.callbacks.write("tasks.json", JSON.stringify(data, null, 2));
61
86
  }
62
87
  catch (error) {
63
- throw new Error(`Failed to save tasks data: ${error instanceof Error ? error.message : "Unknown error"}`);
88
+ throw (0, task_o_matic_error_1.formatStorageError)("write tasks.json", error instanceof Error ? error : undefined);
64
89
  }
65
90
  }
66
91
  findTaskInHierarchy(tasks, id) {
@@ -111,7 +136,15 @@ class FileSystemStorage {
111
136
  if (task.parentId) {
112
137
  const parentResult = this.findTaskInHierarchy(data.tasks, task.parentId);
113
138
  if (!parentResult.task) {
114
- throw new Error(`Parent task with ID ${task.parentId} not found`);
139
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.TASK_NOT_FOUND, `Parent task with ID ${task.parentId} not found`, {
140
+ context: "createTask - parent validation",
141
+ suggestions: [
142
+ "Create the parent task first",
143
+ "Check that the parent ID is correct",
144
+ "List all tasks to see available parent IDs",
145
+ ],
146
+ metadata: { parentId: task.parentId },
147
+ });
115
148
  }
116
149
  const siblingCount = (parentResult.task.subtasks?.length || 0) + 1;
117
150
  id = `${task.parentId}.${siblingCount}`;
@@ -129,11 +162,26 @@ class FileSystemStorage {
129
162
  for (const depId of task.dependencies) {
130
163
  const depExists = this.taskExists(data.tasks, depId);
131
164
  if (!depExists) {
132
- throw new Error(`Dependency task not found: ${depId}`);
165
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.TASK_NOT_FOUND, `Dependency task not found: ${depId}`, {
166
+ context: "createTask - dependency validation",
167
+ suggestions: [
168
+ "Create the dependency task first",
169
+ "Check that the dependency ID is correct",
170
+ "Remove the dependency from the task",
171
+ ],
172
+ metadata: { dependencyId: depId, taskDependencies: task.dependencies },
173
+ });
133
174
  }
134
175
  }
135
176
  if (this.wouldCreateCircularDependency(data.tasks, id, task.dependencies)) {
136
- throw new Error(`Circular dependency detected for task ${id}`);
177
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.STORAGE_INTEGRITY_ERROR, `Circular dependency detected for task ${id}`, {
178
+ context: "createTask - circular dependency check",
179
+ suggestions: [
180
+ "Remove circular dependencies from the task",
181
+ "Review the dependency chain",
182
+ ],
183
+ metadata: { taskId: id, dependencies: task.dependencies },
184
+ });
137
185
  }
138
186
  }
139
187
  let contentFile;
@@ -184,7 +232,10 @@ class FileSystemStorage {
184
232
  async updateTask(id, updates) {
185
233
  this.validateTaskId(id);
186
234
  if (!updates || typeof updates !== "object") {
187
- throw new Error("Updates must be a valid object");
235
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.INVALID_INPUT, "Updates must be a valid object", {
236
+ context: "updateTask - updates validation",
237
+ suggestions: ["Provide a valid updates object with task properties"],
238
+ });
188
239
  }
189
240
  const data = await this.loadTasksData();
190
241
  const result = this.findTaskInHierarchy(data.tasks, id);
@@ -314,28 +365,34 @@ class FileSystemStorage {
314
365
  async saveTaskContent(taskId, content) {
315
366
  this.validateTaskId(taskId);
316
367
  if (typeof content !== "string") {
317
- throw new Error("Content must be a string");
368
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.INVALID_INPUT, "Content must be a string", {
369
+ context: "Content validation",
370
+ suggestions: ["Provide content as a string"],
371
+ });
318
372
  }
319
373
  const contentFileName = `tasks/${taskId}.md`;
320
374
  try {
321
375
  await this.callbacks.write(contentFileName, content);
322
376
  }
323
377
  catch (error) {
324
- throw new Error(`Failed to save task content: ${error instanceof Error ? error.message : "Unknown error"}`);
378
+ throw (0, task_o_matic_error_1.formatStorageError)("write task content", error instanceof Error ? error : undefined);
325
379
  }
326
380
  return contentFileName;
327
381
  }
328
382
  async saveEnhancedTaskContent(taskId, content) {
329
383
  this.validateTaskId(taskId);
330
384
  if (typeof content !== "string") {
331
- throw new Error("Content must be a string");
385
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.INVALID_INPUT, "Content must be a string", {
386
+ context: "Content validation",
387
+ suggestions: ["Provide content as a string"],
388
+ });
332
389
  }
333
390
  const contentFileName = `tasks/enhanced/${taskId}.md`;
334
391
  try {
335
392
  await this.callbacks.write(contentFileName, content);
336
393
  }
337
394
  catch (error) {
338
- throw new Error(`Failed to save enhanced task content: ${error instanceof Error ? error.message : "Unknown error"}`);
395
+ throw (0, task_o_matic_error_1.formatStorageError)("write enhanced task content", error instanceof Error ? error : undefined);
339
396
  }
340
397
  return contentFileName;
341
398
  }
@@ -496,7 +553,7 @@ class FileSystemStorage {
496
553
  await this.callbacks.write(planFile, JSON.stringify(planData, null, 2));
497
554
  }
498
555
  catch (error) {
499
- throw new Error(`Failed to save plan for task ${taskId}: ${error instanceof Error ? error.message : "Unknown error"}`);
556
+ throw (0, task_o_matic_error_1.formatStorageError)(`write plan for task ${taskId}`, error instanceof Error ? error : undefined);
500
557
  }
501
558
  }
502
559
  async getPlan(taskId) {
@@ -509,7 +566,7 @@ class FileSystemStorage {
509
566
  return JSON.parse(content);
510
567
  }
511
568
  catch (error) {
512
- throw new Error(`Failed to read plan for task ${taskId}: ${error instanceof Error ? error.message : "Unknown error"}`);
569
+ throw (0, task_o_matic_error_1.formatStorageError)(`read plan for task ${taskId}`, error instanceof Error ? error : undefined);
513
570
  }
514
571
  }
515
572
  async listPlans() {
@@ -534,7 +591,7 @@ class FileSystemStorage {
534
591
  return plans.sort((a, b) => b.updatedAt - a.updatedAt);
535
592
  }
536
593
  catch (error) {
537
- throw new Error(`Failed to list plans: ${error instanceof Error ? error.message : "Unknown error"}`);
594
+ throw (0, task_o_matic_error_1.formatStorageError)("list plans", error instanceof Error ? error : undefined);
538
595
  }
539
596
  }
540
597
  async deletePlan(taskId) {
@@ -551,14 +608,17 @@ class FileSystemStorage {
551
608
  async saveTaskDocumentation(taskId, documentation) {
552
609
  this.validateTaskId(taskId);
553
610
  if (typeof documentation !== "string") {
554
- throw new Error("Documentation must be a string");
611
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.INVALID_INPUT, "Documentation must be a string", {
612
+ context: "Documentation validation",
613
+ suggestions: ["Provide documentation as a string"],
614
+ });
555
615
  }
556
616
  const documentationFileName = `docs/tasks/${taskId}.md`;
557
617
  try {
558
618
  await this.callbacks.write(documentationFileName, documentation);
559
619
  }
560
620
  catch (error) {
561
- throw new Error(`Failed to save task documentation: ${error instanceof Error ? error.message : "Unknown error"}`);
621
+ throw (0, task_o_matic_error_1.formatStorageError)("write task documentation", error instanceof Error ? error : undefined);
562
622
  }
563
623
  return documentationFileName;
564
624
  }
@@ -1 +1 @@
1
- {"version":3,"file":"task-execution-core.d.ts","sourceRoot":"","sources":["../../src/lib/task-execution-core.ts"],"names":[],"mappings":"AACA,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EAKpB,MAAM,UAAU,CAAC;AAWlB;;;GAGG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,mBAAmB,CAAC,CA+D9B"}
1
+ {"version":3,"file":"task-execution-core.d.ts","sourceRoot":"","sources":["../../src/lib/task-execution-core.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EAKpB,MAAM,UAAU,CAAC;AAWlB;;;GAGG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,mBAAmB,CAAC,CA+D9B"}
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.executeTaskCore = executeTaskCore;
7
+ const task_o_matic_error_1 = require("../utils/task-o-matic-error");
7
8
  const tasks_1 = require("../services/tasks");
8
9
  const executor_factory_1 = require("./executors/executor-factory");
9
10
  const validation_1 = require("./validation");
@@ -23,7 +24,7 @@ async function executeTaskCore(taskId, config) {
23
24
  // Load task
24
25
  const task = await tasks_1.taskService.getTask(taskId);
25
26
  if (!task) {
26
- throw new Error(`Task with ID ${taskId} not found`);
27
+ throw (0, task_o_matic_error_1.formatTaskNotFoundError)(taskId);
27
28
  }
28
29
  // Check if task has subtasks and should execute them recursively
29
30
  if (executeSubtasks && !customMessage) {
@@ -254,7 +255,7 @@ async function executeSingleAttempt(task, config, attempts, planContent, attempt
254
255
  retryContext,
255
256
  });
256
257
  if (!promptResult.success) {
257
- throw new Error(`Failed to build execution prompt: ${promptResult.error}`);
258
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.CONFIGURATION_ERROR, `Failed to build execution prompt: ${promptResult.error}`);
258
259
  }
259
260
  executionMessage = promptResult.prompt;
260
261
  }
@@ -1,12 +1,28 @@
1
+ import { getAIOperations, getStorage } from "../utils/ai-service-factory";
1
2
  import { AIOptions } from "../utils/ai-config-builder";
2
3
  import { StreamingOptions } from "../types";
3
4
  import { PRDParseResult } from "../types/results";
4
5
  import { ProgressCallback } from "../types/callbacks";
6
+ /**
7
+ * Dependencies for PRDService
8
+ */
9
+ export interface PRDServiceDependencies {
10
+ storage?: ReturnType<typeof getStorage>;
11
+ aiOperations?: ReturnType<typeof getAIOperations>;
12
+ }
5
13
  /**
6
14
  * PRDService - Business logic for PRD operations
7
15
  * Handles PRD parsing, task extraction, and PRD improvement
8
16
  */
9
17
  export declare class PRDService {
18
+ private storage;
19
+ private aiOperations;
20
+ /**
21
+ * Create a new PRDService
22
+ *
23
+ * @param dependencies - Optional dependencies to inject (for testing)
24
+ */
25
+ constructor(dependencies?: PRDServiceDependencies);
10
26
  parsePRD(input: {
11
27
  file: string;
12
28
  workingDirectory?: string;
@@ -98,5 +114,6 @@ export declare class PRDService {
98
114
  };
99
115
  }>;
100
116
  }
117
+ export declare function getPRDService(): PRDService;
101
118
  export declare const prdService: PRDService;
102
119
  //# sourceMappingURL=prd.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prd.d.ts","sourceRoot":"","sources":["../../src/services/prd.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prd.d.ts","sourceRoot":"","sources":["../../src/services/prd.ts"],"names":[],"mappings":"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"}
@@ -34,17 +34,33 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.prdService = exports.PRDService = void 0;
37
+ exports.getPRDService = getPRDService;
38
+ const task_o_matic_error_1 = require("../utils/task-o-matic-error");
37
39
  const fs_1 = require("fs");
38
40
  const path_1 = require("path");
39
41
  const ai_service_factory_1 = require("../utils/ai-service-factory");
40
42
  const ai_config_builder_1 = require("../utils/ai-config-builder");
41
43
  const config_1 = require("../lib/config");
42
44
  const validation_1 = require("../lib/validation");
45
+ const streaming_utils_1 = require("../utils/streaming-utils");
46
+ const file_utils_1 = require("../utils/file-utils");
43
47
  /**
44
48
  * PRDService - Business logic for PRD operations
45
49
  * Handles PRD parsing, task extraction, and PRD improvement
46
50
  */
47
51
  class PRDService {
52
+ storage;
53
+ aiOperations;
54
+ /**
55
+ * Create a new PRDService
56
+ *
57
+ * @param dependencies - Optional dependencies to inject (for testing)
58
+ */
59
+ constructor(dependencies = {}) {
60
+ // Use injected dependencies or fall back to singletons
61
+ this.storage = dependencies.storage ?? (0, ai_service_factory_1.getStorage)();
62
+ this.aiOperations = dependencies.aiOperations ?? (0, ai_service_factory_1.getAIOperations)();
63
+ }
48
64
  async parsePRD(input) {
49
65
  const startTime = Date.now();
50
66
  const steps = [];
@@ -52,20 +68,18 @@ class PRDService {
52
68
  type: "started",
53
69
  message: "Starting PRD parsing...",
54
70
  });
55
- // Validate file exists
56
- if (!(0, fs_1.existsSync)(input.file)) {
57
- throw new Error(`PRD file not found: ${input.file}`);
58
- }
71
+ // Validate file exists (DRY fix 1.2)
72
+ (0, file_utils_1.validateFileExists)(input.file, `PRD file not found: ${input.file}`);
59
73
  // Ensure we're in a task-o-matic project
60
74
  const taskOMaticDir = config_1.configManager.getTaskOMaticDir();
61
75
  if (!(0, fs_1.existsSync)(taskOMaticDir)) {
62
- throw new Error(`Not a task-o-matic project. Run 'task-o-matic init init' first.`);
76
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.CONFIGURATION_ERROR, "Not a task-o-matic project. Run 'task-o-matic init init' first.", {
77
+ suggestions: ["Run `task-o-matic init init` in your project root."],
78
+ });
63
79
  }
64
- // Set working directory from CLI layer (defaults to process.cwd() for backward compatibility)
80
+ // Set working directory and reload config (DRY fix 1.4)
65
81
  const workingDir = input.workingDirectory || process.cwd();
66
- config_1.configManager.setWorkingDirectory(workingDir);
67
- // Reload config after changing working directory
68
- await config_1.configManager.load();
82
+ await (0, config_1.setupWorkingDirectory)(workingDir);
69
83
  input.callbacks?.onProgress?.({
70
84
  type: "progress",
71
85
  message: "Reading PRD file...",
@@ -96,7 +110,9 @@ class PRDService {
96
110
  // Validate AI provider if specified
97
111
  if (input.aiOptions?.aiProvider &&
98
112
  !(0, validation_1.isValidAIProvider)(input.aiOptions.aiProvider)) {
99
- throw new Error(`Invalid AI provider: ${input.aiOptions.aiProvider}`);
113
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.AI_CONFIGURATION_ERROR, `Invalid AI provider: ${input.aiOptions.aiProvider}`, {
114
+ suggestions: ["Use a valid AI provider, e.g., 'openai', 'anthropic'"],
115
+ });
100
116
  }
101
117
  const aiConfig = (0, ai_config_builder_1.buildAIConfig)(input.aiOptions);
102
118
  input.callbacks?.onProgress?.({
@@ -104,34 +120,13 @@ class PRDService {
104
120
  message: "Parsing PRD with AI...",
105
121
  });
106
122
  const stepStart2 = Date.now();
107
- // Capture metrics
108
- let tokenUsage;
109
- let timeToFirstToken;
110
- // Wrap streaming options to capture metrics
111
- const metricsStreamingOptions = {
112
- ...input.streamingOptions,
113
- onFinish: async (result) => {
114
- if (result.usage) {
115
- tokenUsage = {
116
- prompt: result.usage.inputTokens || result.usage.promptTokens || 0,
117
- completion: result.usage.outputTokens || result.usage.completionTokens || 0,
118
- total: result.usage.totalTokens || 0,
119
- };
120
- }
121
- // Call original onFinish if provided
122
- await input.streamingOptions?.onFinish?.(result);
123
- },
124
- onChunk: (chunk) => {
125
- if (chunk && !timeToFirstToken) {
126
- timeToFirstToken = Date.now() - stepStart2;
127
- }
128
- // Call original onChunk if provided
129
- input.streamingOptions?.onChunk?.(chunk);
130
- },
131
- };
132
- const result = await (0, ai_service_factory_1.getAIOperations)().parsePRD(prdContent, aiConfig, input.promptOverride, input.messageOverride, metricsStreamingOptions, undefined, // retryConfig
123
+ // Use utility to wrap streaming options and capture metrics (DRY fix 1.1)
124
+ const { options: metricsStreamingOptions, getMetrics } = (0, streaming_utils_1.createMetricsStreamingOptions)(input.streamingOptions, stepStart2);
125
+ const result = await this.aiOperations.parsePRD(prdContent, aiConfig, input.promptOverride, input.messageOverride, metricsStreamingOptions, undefined, // retryConfig
133
126
  workingDir, // Pass working directory to AI operations
134
127
  input.enableFilesystemTools);
128
+ // Extract metrics after AI call
129
+ const { tokenUsage, timeToFirstToken } = getMetrics();
135
130
  steps.push({
136
131
  step: "AI Parsing",
137
132
  status: "completed",
@@ -153,7 +148,7 @@ class PRDService {
153
148
  current: i + 1,
154
149
  total: result.tasks.length,
155
150
  });
156
- const createdTask = await (0, ai_service_factory_1.getStorage)().createTask({
151
+ const createdTask = await this.storage.createTask({
157
152
  id: task.id, // Preserve AI-generated ID for dependencies
158
153
  title: task.title,
159
154
  description: task.description,
@@ -175,7 +170,7 @@ class PRDService {
175
170
  aiModel: input.aiOptions?.aiModel,
176
171
  generatedAt: Date.now(),
177
172
  };
178
- await (0, ai_service_factory_1.getStorage)().saveTaskAIMetadata(aiMetadata);
173
+ await this.storage.saveTaskAIMetadata(aiMetadata);
179
174
  }
180
175
  steps.push({
181
176
  step: "Create Tasks",
@@ -220,12 +215,11 @@ class PRDService {
220
215
  type: "started",
221
216
  message: "Generating clarifying questions...",
222
217
  });
223
- if (!(0, fs_1.existsSync)(input.file)) {
224
- throw new Error(`PRD file not found: ${input.file}`);
225
- }
218
+ // Validate file exists (DRY fix 1.2)
219
+ (0, file_utils_1.validateFileExists)(input.file, `PRD file not found: ${input.file}`);
220
+ // Set working directory and reload config (DRY fix 1.4)
226
221
  const workingDir = input.workingDirectory || process.cwd();
227
- config_1.configManager.setWorkingDirectory(workingDir);
228
- await config_1.configManager.load();
222
+ await (0, config_1.setupWorkingDirectory)(workingDir);
229
223
  input.callbacks?.onProgress?.({
230
224
  type: "progress",
231
225
  message: "Reading PRD file...",
@@ -233,14 +227,16 @@ class PRDService {
233
227
  const prdContent = (0, fs_1.readFileSync)(input.file, "utf-8");
234
228
  if (input.aiOptions?.aiProvider &&
235
229
  !(0, validation_1.isValidAIProvider)(input.aiOptions.aiProvider)) {
236
- throw new Error(`Invalid AI provider: ${input.aiOptions.aiProvider}`);
230
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.AI_CONFIGURATION_ERROR, `Invalid AI provider: ${input.aiOptions.aiProvider}`, {
231
+ suggestions: ["Use a valid AI provider, e.g., 'openai', 'anthropic'"],
232
+ });
237
233
  }
238
234
  const aiConfig = (0, ai_config_builder_1.buildAIConfig)(input.aiOptions);
239
235
  input.callbacks?.onProgress?.({
240
236
  type: "progress",
241
237
  message: "Analyzing PRD with AI...",
242
238
  });
243
- const questions = await (0, ai_service_factory_1.getAIOperations)().generatePRDQuestions(prdContent, aiConfig, input.promptOverride, input.messageOverride, input.streamingOptions, undefined, workingDir, input.enableFilesystemTools);
239
+ const questions = await this.aiOperations.generatePRDQuestions(prdContent, aiConfig, input.promptOverride, input.messageOverride, input.streamingOptions, undefined, workingDir, input.enableFilesystemTools);
244
240
  input.callbacks?.onProgress?.({
245
241
  type: "completed",
246
242
  message: `Generated ${questions.length} questions`,
@@ -252,14 +248,11 @@ class PRDService {
252
248
  type: "started",
253
249
  message: "Starting PRD improvement...",
254
250
  });
255
- // Validate file exists
256
- if (!(0, fs_1.existsSync)(input.file)) {
257
- throw new Error(`PRD file not found: ${input.file}`);
258
- }
259
- // Set working directory from CLI layer (defaults to process.cwd() for backward compatibility)
251
+ // Validate file exists (DRY fix 1.2)
252
+ (0, file_utils_1.validateFileExists)(input.file, `PRD file not found: ${input.file}`);
253
+ // Set working directory and reload config (DRY fix 1.4)
260
254
  const workingDir = input.workingDirectory || process.cwd();
261
- config_1.configManager.setWorkingDirectory(workingDir);
262
- await config_1.configManager.load();
255
+ await (0, config_1.setupWorkingDirectory)(workingDir);
263
256
  input.callbacks?.onProgress?.({
264
257
  type: "progress",
265
258
  message: "Reading PRD file...",
@@ -268,14 +261,16 @@ class PRDService {
268
261
  // Validate AI provider if specified
269
262
  if (input.aiOptions?.aiProvider &&
270
263
  !(0, validation_1.isValidAIProvider)(input.aiOptions.aiProvider)) {
271
- throw new Error(`Invalid AI provider: ${input.aiOptions.aiProvider}`);
264
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.AI_CONFIGURATION_ERROR, `Invalid AI provider: ${input.aiOptions.aiProvider}`, {
265
+ suggestions: ["Use a valid AI provider, e.g., 'openai', 'anthropic'"],
266
+ });
272
267
  }
273
268
  const aiConfig = (0, ai_config_builder_1.buildAIConfig)(input.aiOptions);
274
269
  input.callbacks?.onProgress?.({
275
270
  type: "progress",
276
271
  message: "Calling AI to improve PRD...",
277
272
  });
278
- const improvedPRD = await (0, ai_service_factory_1.getAIOperations)().reworkPRD(prdContent, input.feedback, aiConfig, input.promptOverride, input.messageOverride, input.streamingOptions, undefined, // retryConfig
273
+ const improvedPRD = await this.aiOperations.reworkPRD(prdContent, input.feedback, aiConfig, input.promptOverride, input.messageOverride, input.streamingOptions, undefined, // retryConfig
279
274
  workingDir, // Pass working directory to AI operations
280
275
  input.enableFilesystemTools);
281
276
  input.callbacks?.onProgress?.({
@@ -338,7 +333,7 @@ class PRDService {
338
333
  // User mode: return questions for CLI to prompt user
339
334
  // Answers should be provided in input.answers
340
335
  if (!input.answers || Object.keys(input.answers).length === 0) {
341
- throw new Error("User mode selected but no answers provided. CLI layer should collect answers.");
336
+ throw (0, task_o_matic_error_1.createStandardError)(task_o_matic_error_1.TaskOMaticErrorCodes.INVALID_INPUT, "User mode selected but no answers provided. CLI layer should collect answers.");
342
337
  }
343
338
  answers = input.answers;
344
339
  }
@@ -351,7 +346,7 @@ class PRDService {
351
346
  const prdContent = (0, fs_1.readFileSync)(input.file, "utf-8");
352
347
  // Use questionAIOptions if provided, otherwise use main aiOptions
353
348
  const answeringAIConfig = (0, ai_config_builder_1.buildAIConfig)(input.questionAIOptions || input.aiOptions);
354
- answers = await (0, ai_service_factory_1.getAIOperations)().answerPRDQuestions(prdContent, questions, answeringAIConfig, {
349
+ answers = await this.aiOperations.answerPRDQuestions(prdContent, questions, answeringAIConfig, {
355
350
  stackInfo,
356
351
  }, input.streamingOptions);
357
352
  }
@@ -418,7 +413,7 @@ class PRDService {
418
413
  },
419
414
  };
420
415
  const aiConfig = (0, ai_config_builder_1.buildAIConfig)(input.aiOptions);
421
- const content = await (0, ai_service_factory_1.getAIOperations)().generatePRD(input.description, aiConfig, undefined, undefined, metricsStreamingOptions);
416
+ const content = await this.aiOperations.generatePRD(input.description, aiConfig, undefined, undefined, metricsStreamingOptions);
422
417
  // Save file
423
418
  const taskOMaticDir = config_1.configManager.getTaskOMaticDir();
424
419
  const prdDir = input.outputDir || (0, path_1.join)(taskOMaticDir, "prd");
@@ -476,7 +471,7 @@ class PRDService {
476
471
  },
477
472
  };
478
473
  const aiConfig = (0, ai_config_builder_1.buildAIConfig)(input.aiOptions);
479
- const content = await (0, ai_service_factory_1.getAIOperations)().combinePRDs(input.prds, input.originalDescription, aiConfig, undefined, undefined, metricsStreamingOptions);
474
+ const content = await this.aiOperations.combinePRDs(input.prds, input.originalDescription, aiConfig, undefined, undefined, metricsStreamingOptions);
480
475
  // Save file
481
476
  const taskOMaticDir = config_1.configManager.getTaskOMaticDir();
482
477
  const prdDir = input.outputDir || (0, path_1.join)(taskOMaticDir, "prd");
@@ -503,5 +498,17 @@ class PRDService {
503
498
  }
504
499
  }
505
500
  exports.PRDService = PRDService;
506
- // Export singleton instance
507
- exports.prdService = new PRDService();
501
+ // Lazy singleton instance - only created when first accessed
502
+ let prdServiceInstance;
503
+ function getPRDService() {
504
+ if (!prdServiceInstance) {
505
+ prdServiceInstance = new PRDService();
506
+ }
507
+ return prdServiceInstance;
508
+ }
509
+ // Backward compatibility: export as const but use getter
510
+ exports.prdService = new Proxy({}, {
511
+ get(target, prop) {
512
+ return getPRDService()[prop];
513
+ },
514
+ });