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
@@ -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":"AAYA,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,EAAiB,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAY,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGlD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAItD;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;IACxC,YAAY,CAAC,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;CACnD;AAED;;;GAGG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,YAAY,CAAqC;IAEzD;;;;OAIG;gBACS,YAAY,GAAE,sBAA2B;IAK/C,QAAQ,CAAC,KAAK,EAAE;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,qBAAqB,CAAC,EAAE,OAAO,CAAC;QAChC,SAAS,CAAC,EAAE,SAAS,CAAC;QACtB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;QACpC,SAAS,CAAC,EAAE,gBAAgB,CAAC;KAC9B,GAAG,OAAO,CAAC,cAAc,CAAC;IA8MrB,iBAAiB,CAAC,KAAK,EAAE;QAC7B,IAAI,EAAE,MAAM,CAAC;QACb,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,qBAAqB,CAAC,EAAE,OAAO,CAAC;QAChC,SAAS,CAAC,EAAE,SAAS,CAAC;QACtB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;QACpC,SAAS,CAAC,EAAE,gBAAgB,CAAC;KAC9B,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IA2Df,SAAS,CAAC,KAAK,EAAE;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,qBAAqB,CAAC,EAAE,OAAO,CAAC;QAChC,SAAS,CAAC,EAAE,SAAS,CAAC;QACtB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;QACpC,SAAS,CAAC,EAAE,gBAAgB,CAAC;KAC9B,GAAG,OAAO,CAAC,MAAM,CAAC;IAqEb,sBAAsB,CAAC,KAAK,EAAE;QAClC,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjC,iBAAiB,CAAC,EAAE,SAAS,CAAC;QAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,qBAAqB,CAAC,EAAE,OAAO,CAAC;QAChC,SAAS,CAAC,EAAE,SAAS,CAAC;QACtB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;QACpC,SAAS,CAAC,EAAE,gBAAgB,CAAC;KAC9B,GAAG,OAAO,CAAC;QACV,SAAS,EAAE,MAAM,EAAE,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAChC,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IA0HI,WAAW,CAAC,KAAK,EAAE;QACvB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,SAAS,CAAC;QACtB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;QACpC,SAAS,CAAC,EAAE,gBAAgB,CAAC;KAC9B,GAAG,OAAO,CAAC;QACV,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE;YACL,QAAQ,EAAE,MAAM,CAAC;YACjB,UAAU,CAAC,EAAE;gBAAE,MAAM,EAAE,MAAM,CAAC;gBAAC,UAAU,EAAE,MAAM,CAAC;gBAAC,KAAK,EAAE,MAAM,CAAA;aAAE,CAAC;YACnE,gBAAgB,CAAC,EAAE,MAAM,CAAC;YAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;SACf,CAAC;KACH,CAAC;IAmDI,WAAW,CAAC,KAAK,EAAE;QACvB,IAAI,EAAE,MAAM,EAAE,CAAC;QACf,mBAAmB,EAAE,MAAM,CAAC;QAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,SAAS,CAAC;QACtB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;QACpC,SAAS,CAAC,EAAE,gBAAgB,CAAC;KAC9B,GAAG,OAAO,CAAC;QACV,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE;YACL,QAAQ,EAAE,MAAM,CAAC;YACjB,UAAU,CAAC,EAAE;gBAAE,MAAM,EAAE,MAAM,CAAC;gBAAC,UAAU,EAAE,MAAM,CAAC;gBAAC,KAAK,EAAE,MAAM,CAAA;aAAE,CAAC;YACnE,gBAAgB,CAAC,EAAE,MAAM,CAAC;YAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;SACf,CAAC;KACH,CAAC;CAuDH;AAKD,wBAAgB,aAAa,IAAI,UAAU,CAK1C;AAGD,eAAO,MAAM,UAAU,YAIrB,CAAC"}
@@ -34,6 +34,8 @@ 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");
@@ -47,6 +49,18 @@ const file_utils_1 = require("../utils/file-utils");
47
49
  * Handles PRD parsing, task extraction, and PRD improvement
48
50
  */
49
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
+ }
50
64
  async parsePRD(input) {
51
65
  const startTime = Date.now();
52
66
  const steps = [];
@@ -59,7 +73,9 @@ class PRDService {
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
80
  // Set working directory and reload config (DRY fix 1.4)
65
81
  const workingDir = input.workingDirectory || process.cwd();
@@ -94,7 +110,9 @@ class PRDService {
94
110
  // Validate AI provider if specified
95
111
  if (input.aiOptions?.aiProvider &&
96
112
  !(0, validation_1.isValidAIProvider)(input.aiOptions.aiProvider)) {
97
- 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
+ });
98
116
  }
99
117
  const aiConfig = (0, ai_config_builder_1.buildAIConfig)(input.aiOptions);
100
118
  input.callbacks?.onProgress?.({
@@ -104,7 +122,7 @@ class PRDService {
104
122
  const stepStart2 = Date.now();
105
123
  // Use utility to wrap streaming options and capture metrics (DRY fix 1.1)
106
124
  const { options: metricsStreamingOptions, getMetrics } = (0, streaming_utils_1.createMetricsStreamingOptions)(input.streamingOptions, stepStart2);
107
- const result = await (0, ai_service_factory_1.getAIOperations)().parsePRD(prdContent, aiConfig, input.promptOverride, input.messageOverride, metricsStreamingOptions, undefined, // retryConfig
125
+ const result = await this.aiOperations.parsePRD(prdContent, aiConfig, input.promptOverride, input.messageOverride, metricsStreamingOptions, undefined, // retryConfig
108
126
  workingDir, // Pass working directory to AI operations
109
127
  input.enableFilesystemTools);
110
128
  // Extract metrics after AI call
@@ -130,7 +148,7 @@ class PRDService {
130
148
  current: i + 1,
131
149
  total: result.tasks.length,
132
150
  });
133
- const createdTask = await (0, ai_service_factory_1.getStorage)().createTask({
151
+ const createdTask = await this.storage.createTask({
134
152
  id: task.id, // Preserve AI-generated ID for dependencies
135
153
  title: task.title,
136
154
  description: task.description,
@@ -152,7 +170,7 @@ class PRDService {
152
170
  aiModel: input.aiOptions?.aiModel,
153
171
  generatedAt: Date.now(),
154
172
  };
155
- await (0, ai_service_factory_1.getStorage)().saveTaskAIMetadata(aiMetadata);
173
+ await this.storage.saveTaskAIMetadata(aiMetadata);
156
174
  }
157
175
  steps.push({
158
176
  step: "Create Tasks",
@@ -209,14 +227,16 @@ class PRDService {
209
227
  const prdContent = (0, fs_1.readFileSync)(input.file, "utf-8");
210
228
  if (input.aiOptions?.aiProvider &&
211
229
  !(0, validation_1.isValidAIProvider)(input.aiOptions.aiProvider)) {
212
- 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
+ });
213
233
  }
214
234
  const aiConfig = (0, ai_config_builder_1.buildAIConfig)(input.aiOptions);
215
235
  input.callbacks?.onProgress?.({
216
236
  type: "progress",
217
237
  message: "Analyzing PRD with AI...",
218
238
  });
219
- 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);
220
240
  input.callbacks?.onProgress?.({
221
241
  type: "completed",
222
242
  message: `Generated ${questions.length} questions`,
@@ -241,14 +261,16 @@ class PRDService {
241
261
  // Validate AI provider if specified
242
262
  if (input.aiOptions?.aiProvider &&
243
263
  !(0, validation_1.isValidAIProvider)(input.aiOptions.aiProvider)) {
244
- 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
+ });
245
267
  }
246
268
  const aiConfig = (0, ai_config_builder_1.buildAIConfig)(input.aiOptions);
247
269
  input.callbacks?.onProgress?.({
248
270
  type: "progress",
249
271
  message: "Calling AI to improve PRD...",
250
272
  });
251
- 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
252
274
  workingDir, // Pass working directory to AI operations
253
275
  input.enableFilesystemTools);
254
276
  input.callbacks?.onProgress?.({
@@ -311,7 +333,7 @@ class PRDService {
311
333
  // User mode: return questions for CLI to prompt user
312
334
  // Answers should be provided in input.answers
313
335
  if (!input.answers || Object.keys(input.answers).length === 0) {
314
- 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.");
315
337
  }
316
338
  answers = input.answers;
317
339
  }
@@ -324,7 +346,7 @@ class PRDService {
324
346
  const prdContent = (0, fs_1.readFileSync)(input.file, "utf-8");
325
347
  // Use questionAIOptions if provided, otherwise use main aiOptions
326
348
  const answeringAIConfig = (0, ai_config_builder_1.buildAIConfig)(input.questionAIOptions || input.aiOptions);
327
- answers = await (0, ai_service_factory_1.getAIOperations)().answerPRDQuestions(prdContent, questions, answeringAIConfig, {
349
+ answers = await this.aiOperations.answerPRDQuestions(prdContent, questions, answeringAIConfig, {
328
350
  stackInfo,
329
351
  }, input.streamingOptions);
330
352
  }
@@ -359,48 +381,23 @@ class PRDService {
359
381
  }
360
382
  async generatePRD(input) {
361
383
  const startTime = Date.now();
362
- let tokenUsage;
363
- let timeToFirstToken;
364
- let cost;
365
384
  input.callbacks?.onProgress?.({
366
385
  type: "started",
367
386
  message: "Generating PRD...",
368
387
  });
369
- // Capture metrics
370
- const metricsStreamingOptions = {
371
- ...input.streamingOptions,
372
- onFinish: async (result) => {
373
- if (result.usage) {
374
- tokenUsage = {
375
- prompt: result.usage.inputTokens || result.usage.promptTokens || 0,
376
- completion: result.usage.outputTokens || result.usage.completionTokens || 0,
377
- total: result.usage.totalTokens || 0,
378
- };
379
- // Simple cost estimation placeholder
380
- if (tokenUsage.total > 0) {
381
- cost = tokenUsage.total * 0.000001;
382
- }
383
- }
384
- await input.streamingOptions?.onFinish?.(result);
385
- },
386
- onChunk: (chunk) => {
387
- if (chunk && !timeToFirstToken) {
388
- timeToFirstToken = Date.now() - startTime;
389
- }
390
- input.streamingOptions?.onChunk?.(chunk);
391
- },
392
- };
388
+ // Use utility to wrap streaming options and capture metrics
389
+ const { options: metricsStreamingOptions, getMetrics } = (0, streaming_utils_1.createMetricsStreamingOptions)(input.streamingOptions, startTime);
393
390
  const aiConfig = (0, ai_config_builder_1.buildAIConfig)(input.aiOptions);
394
- const content = await (0, ai_service_factory_1.getAIOperations)().generatePRD(input.description, aiConfig, undefined, undefined, metricsStreamingOptions);
395
- // Save file
396
- const taskOMaticDir = config_1.configManager.getTaskOMaticDir();
397
- const prdDir = input.outputDir || (0, path_1.join)(taskOMaticDir, "prd");
398
- if (!(0, fs_1.existsSync)(prdDir)) {
399
- (0, fs_1.mkdirSync)(prdDir, { recursive: true });
391
+ const content = await this.aiOperations.generatePRD(input.description, aiConfig, undefined, undefined, metricsStreamingOptions);
392
+ // Get metrics after AI operation
393
+ const { tokenUsage, timeToFirstToken } = getMetrics();
394
+ // Calculate cost if needed
395
+ let cost;
396
+ if (tokenUsage && tokenUsage.total > 0) {
397
+ cost = tokenUsage.total * 0.000001; // Placeholder cost calculation
400
398
  }
401
- const filename = input.filename || "prd.md";
402
- const path = (0, path_1.join)(prdDir, filename);
403
- (0, fs_1.writeFileSync)(path, content);
399
+ // Save file using utility
400
+ const path = (0, file_utils_1.savePRDFile)(content, input.filename, input.outputDir);
404
401
  input.callbacks?.onProgress?.({
405
402
  type: "completed",
406
403
  message: `PRD generated and saved to ${path}`,
@@ -418,47 +415,23 @@ class PRDService {
418
415
  }
419
416
  async combinePRDs(input) {
420
417
  const startTime = Date.now();
421
- let tokenUsage;
422
- let timeToFirstToken;
423
- let cost;
424
418
  input.callbacks?.onProgress?.({
425
419
  type: "started",
426
420
  message: "Combining PRDs...",
427
421
  });
428
- // Capture metrics
429
- const metricsStreamingOptions = {
430
- ...input.streamingOptions,
431
- onFinish: async (result) => {
432
- if (result.usage) {
433
- tokenUsage = {
434
- prompt: result.usage.inputTokens || result.usage.promptTokens || 0,
435
- completion: result.usage.outputTokens || result.usage.completionTokens || 0,
436
- total: result.usage.totalTokens || 0,
437
- };
438
- if (tokenUsage.total > 0) {
439
- cost = tokenUsage.total * 0.000001;
440
- }
441
- }
442
- await input.streamingOptions?.onFinish?.(result);
443
- },
444
- onChunk: (chunk) => {
445
- if (chunk && !timeToFirstToken) {
446
- timeToFirstToken = Date.now() - startTime;
447
- }
448
- input.streamingOptions?.onChunk?.(chunk);
449
- },
450
- };
422
+ // Use utility to wrap streaming options and capture metrics
423
+ const { options: metricsStreamingOptions, getMetrics } = (0, streaming_utils_1.createMetricsStreamingOptions)(input.streamingOptions, startTime);
451
424
  const aiConfig = (0, ai_config_builder_1.buildAIConfig)(input.aiOptions);
452
- const content = await (0, ai_service_factory_1.getAIOperations)().combinePRDs(input.prds, input.originalDescription, aiConfig, undefined, undefined, metricsStreamingOptions);
453
- // Save file
454
- const taskOMaticDir = config_1.configManager.getTaskOMaticDir();
455
- const prdDir = input.outputDir || (0, path_1.join)(taskOMaticDir, "prd");
456
- if (!(0, fs_1.existsSync)(prdDir)) {
457
- (0, fs_1.mkdirSync)(prdDir, { recursive: true });
425
+ const content = await this.aiOperations.combinePRDs(input.prds, input.originalDescription, aiConfig, undefined, undefined, metricsStreamingOptions);
426
+ // Get metrics after AI operation
427
+ const { tokenUsage, timeToFirstToken } = getMetrics();
428
+ // Calculate cost if needed
429
+ let cost;
430
+ if (tokenUsage && tokenUsage.total > 0) {
431
+ cost = tokenUsage.total * 0.000001;
458
432
  }
459
- const filename = input.filename || "prd-master.md";
460
- const path = (0, path_1.join)(prdDir, filename);
461
- (0, fs_1.writeFileSync)(path, content);
433
+ // Save file using utility (defaults to "prd.md" if no filename, so we provide the default for combinePRDs)
434
+ const path = (0, file_utils_1.savePRDFile)(content, input.filename || "prd-master.md", input.outputDir);
462
435
  input.callbacks?.onProgress?.({
463
436
  type: "completed",
464
437
  message: `Master PRD saved to ${path}`,
@@ -476,5 +449,17 @@ class PRDService {
476
449
  }
477
450
  }
478
451
  exports.PRDService = PRDService;
479
- // Export singleton instance
480
- exports.prdService = new PRDService();
452
+ // Lazy singleton instance - only created when first accessed
453
+ let prdServiceInstance;
454
+ function getPRDService() {
455
+ if (!prdServiceInstance) {
456
+ prdServiceInstance = new PRDService();
457
+ }
458
+ return prdServiceInstance;
459
+ }
460
+ // Backward compatibility: export as const but use getter
461
+ exports.prdService = new Proxy({}, {
462
+ get(target, prop) {
463
+ return getPRDService()[prop];
464
+ },
465
+ });