task-o-matic 0.0.2 → 0.0.4

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 (129) hide show
  1. package/README.md +96 -40
  2. package/dist/commands/prd.js +4 -0
  3. package/dist/commands/prompt.d.ts.map +1 -1
  4. package/dist/commands/prompt.js +69 -61
  5. package/dist/commands/tasks/create.d.ts +3 -0
  6. package/dist/commands/tasks/create.d.ts.map +1 -0
  7. package/dist/commands/tasks/create.js +58 -0
  8. package/dist/commands/tasks/delete.d.ts +3 -0
  9. package/dist/commands/tasks/delete.d.ts.map +1 -0
  10. package/dist/commands/tasks/delete.js +40 -0
  11. package/dist/commands/tasks/document.d.ts +5 -0
  12. package/dist/commands/tasks/document.d.ts.map +1 -0
  13. package/dist/commands/tasks/document.js +118 -0
  14. package/dist/commands/tasks/enhance.d.ts +3 -0
  15. package/dist/commands/tasks/enhance.d.ts.map +1 -0
  16. package/dist/commands/tasks/enhance.js +86 -0
  17. package/dist/commands/tasks/execute.d.ts +3 -0
  18. package/dist/commands/tasks/execute.d.ts.map +1 -0
  19. package/dist/commands/tasks/execute.js +33 -0
  20. package/dist/commands/tasks/index.d.ts +16 -0
  21. package/dist/commands/tasks/index.d.ts.map +1 -0
  22. package/dist/commands/tasks/index.js +31 -0
  23. package/dist/commands/tasks/list.d.ts +3 -0
  24. package/dist/commands/tasks/list.d.ts.map +1 -0
  25. package/dist/commands/tasks/list.js +27 -0
  26. package/dist/commands/tasks/next.d.ts +3 -0
  27. package/dist/commands/tasks/next.d.ts.map +1 -0
  28. package/dist/commands/tasks/next.js +44 -0
  29. package/dist/commands/tasks/plan.d.ts +7 -0
  30. package/dist/commands/tasks/plan.d.ts.map +1 -0
  31. package/dist/commands/tasks/plan.js +131 -0
  32. package/dist/commands/tasks/show.d.ts +3 -0
  33. package/dist/commands/tasks/show.d.ts.map +1 -0
  34. package/dist/commands/tasks/show.js +23 -0
  35. package/dist/commands/tasks/split.d.ts +3 -0
  36. package/dist/commands/tasks/split.d.ts.map +1 -0
  37. package/dist/commands/tasks/split.js +95 -0
  38. package/dist/commands/tasks/status.d.ts +3 -0
  39. package/dist/commands/tasks/status.d.ts.map +1 -0
  40. package/dist/commands/tasks/status.js +26 -0
  41. package/dist/commands/tasks/subtasks.d.ts +3 -0
  42. package/dist/commands/tasks/subtasks.d.ts.map +1 -0
  43. package/dist/commands/tasks/subtasks.js +35 -0
  44. package/dist/commands/tasks/tags.d.ts +4 -0
  45. package/dist/commands/tasks/tags.d.ts.map +1 -0
  46. package/dist/commands/tasks/tags.js +37 -0
  47. package/dist/commands/tasks/tree.d.ts +3 -0
  48. package/dist/commands/tasks/tree.d.ts.map +1 -0
  49. package/dist/commands/tasks/tree.js +20 -0
  50. package/dist/commands/tasks/update.d.ts +3 -0
  51. package/dist/commands/tasks/update.d.ts.map +1 -0
  52. package/dist/commands/tasks/update.js +35 -0
  53. package/dist/commands/tasks.d.ts.map +1 -1
  54. package/dist/commands/tasks.js +23 -594
  55. package/dist/commands/workflow.d.ts +4 -0
  56. package/dist/commands/workflow.d.ts.map +1 -0
  57. package/dist/commands/workflow.js +434 -0
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +3 -1
  60. package/dist/lib/ai-service/ai-operations.d.ts +5 -3
  61. package/dist/lib/ai-service/ai-operations.d.ts.map +1 -1
  62. package/dist/lib/ai-service/ai-operations.js +231 -30
  63. package/dist/lib/ai-service/filesystem-tools.d.ts +69 -0
  64. package/dist/lib/ai-service/filesystem-tools.d.ts.map +1 -0
  65. package/dist/lib/ai-service/filesystem-tools.js +70 -0
  66. package/dist/lib/ai-service/research-tools.d.ts.map +1 -1
  67. package/dist/lib/ai-service/research-tools.js +2 -2
  68. package/dist/lib/context-builder.d.ts +2 -1
  69. package/dist/lib/context-builder.d.ts.map +1 -1
  70. package/dist/lib/context-builder.js +3 -8
  71. package/dist/lib/executors/claude-code-executor.d.ts +6 -0
  72. package/dist/lib/executors/claude-code-executor.d.ts.map +1 -0
  73. package/dist/lib/executors/claude-code-executor.js +41 -0
  74. package/dist/lib/executors/codex-executor.d.ts +6 -0
  75. package/dist/lib/executors/codex-executor.d.ts.map +1 -0
  76. package/dist/lib/executors/codex-executor.js +41 -0
  77. package/dist/lib/executors/executor-factory.d.ts.map +1 -1
  78. package/dist/lib/executors/executor-factory.js +6 -3
  79. package/dist/lib/executors/gemini-executor.d.ts +6 -0
  80. package/dist/lib/executors/gemini-executor.d.ts.map +1 -0
  81. package/dist/lib/executors/gemini-executor.js +41 -0
  82. package/dist/lib/executors/opencode-executor.d.ts.map +1 -1
  83. package/dist/lib/executors/opencode-executor.js +2 -3
  84. package/dist/lib/hooks/logger.d.ts +2 -0
  85. package/dist/lib/hooks/logger.d.ts.map +1 -0
  86. package/dist/lib/hooks/logger.js +27 -0
  87. package/dist/lib/hooks.d.ts +64 -0
  88. package/dist/lib/hooks.d.ts.map +1 -0
  89. package/dist/lib/hooks.js +60 -0
  90. package/dist/lib/index.d.ts +18 -17
  91. package/dist/lib/index.d.ts.map +1 -1
  92. package/dist/lib/index.js +3 -3
  93. package/dist/lib/prompt-builder.d.ts +8 -0
  94. package/dist/lib/prompt-builder.d.ts.map +1 -1
  95. package/dist/lib/prompt-builder.js +110 -4
  96. package/dist/lib/{storage.d.ts → storage/file-system.d.ts} +4 -3
  97. package/dist/lib/storage/file-system.d.ts.map +1 -0
  98. package/dist/lib/{storage.js → storage/file-system.js} +141 -152
  99. package/dist/lib/storage/types.d.ts +43 -0
  100. package/dist/lib/storage/types.d.ts.map +1 -0
  101. package/dist/lib/storage/types.js +2 -0
  102. package/dist/lib/task-execution.d.ts.map +1 -1
  103. package/dist/lib/task-execution.js +63 -14
  104. package/dist/prompts/workflow-assistance.d.ts +32 -0
  105. package/dist/prompts/workflow-assistance.d.ts.map +1 -0
  106. package/dist/prompts/workflow-assistance.js +130 -0
  107. package/dist/services/prd.d.ts +2 -0
  108. package/dist/services/prd.d.ts.map +1 -1
  109. package/dist/services/prd.js +4 -4
  110. package/dist/services/tasks.d.ts +13 -6
  111. package/dist/services/tasks.d.ts.map +1 -1
  112. package/dist/services/tasks.js +202 -88
  113. package/dist/services/workflow-ai-assistant.d.ts +74 -0
  114. package/dist/services/workflow-ai-assistant.d.ts.map +1 -0
  115. package/dist/services/workflow-ai-assistant.js +223 -0
  116. package/dist/test/hooks.test.d.ts +2 -0
  117. package/dist/test/hooks.test.d.ts.map +1 -0
  118. package/dist/test/hooks.test.js +58 -0
  119. package/dist/test/storage.test.js +16 -16
  120. package/dist/types/options.d.ts +35 -0
  121. package/dist/types/options.d.ts.map +1 -1
  122. package/dist/utils/ai-service-factory.d.ts +5 -5
  123. package/dist/utils/ai-service-factory.d.ts.map +1 -1
  124. package/dist/utils/ai-service-factory.js +4 -3
  125. package/dist/utils/workflow-prompts.d.ts +17 -0
  126. package/dist/utils/workflow-prompts.d.ts.map +1 -0
  127. package/dist/utils/workflow-prompts.js +88 -0
  128. package/package.json +2 -2
  129. package/dist/lib/storage.d.ts.map +0 -1
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LocalStorage = void 0;
4
- const fs_1 = require("fs");
3
+ exports.FileSystemStorage = void 0;
4
+ const promises_1 = require("fs/promises");
5
5
  const path_1 = require("path");
6
- const config_1 = require("./config");
7
- class LocalStorage {
6
+ const config_1 = require("../config");
7
+ class FileSystemStorage {
8
8
  taskOMatic = null;
9
9
  tasksFile = null;
10
10
  initialized = false;
@@ -12,12 +12,6 @@ class LocalStorage {
12
12
  // Pure constructor - NO side effects
13
13
  }
14
14
  sanitizeForFilename(name) {
15
- // console.log('=== SANITIZE DEBUG ===');
16
- // console.log('name type:', typeof name);
17
- // console.log('name value:', name);
18
- // console.log("name length:", name.length);
19
- // console.log("====================");
20
- // Replace slashes with a safe character and remove other invalid characters
21
15
  return name.replace(/[\/\?%*:|"<>]/g, "-");
22
16
  }
23
17
  validateTaskId(taskId) {
@@ -56,10 +50,10 @@ class LocalStorage {
56
50
  this.tasksFile = (0, path_1.join)(this.taskOMatic, "tasks.json");
57
51
  this.initialized = true;
58
52
  }
59
- ensureDirectories() {
53
+ async ensureDirectories() {
60
54
  this.ensureInitialized();
61
55
  if (!this.taskOMatic) {
62
- throw new Error("LocalStorage not initialized");
56
+ throw new Error("FileSystemStorage not initialized");
63
57
  }
64
58
  const dirs = [
65
59
  "prd",
@@ -70,20 +64,31 @@ class LocalStorage {
70
64
  "docs/tasks",
71
65
  "plans",
72
66
  ];
73
- dirs.forEach((dir) => {
67
+ for (const dir of dirs) {
74
68
  const fullPath = (0, path_1.join)(this.taskOMatic, dir);
75
- if (!(0, fs_1.existsSync)(fullPath)) {
76
- (0, fs_1.mkdirSync)(fullPath, { recursive: true });
69
+ try {
70
+ await (0, promises_1.mkdir)(fullPath, { recursive: true });
71
+ }
72
+ catch (error) {
73
+ if (error.code !== "EEXIST")
74
+ throw error;
77
75
  }
78
- });
76
+ }
79
77
  }
80
- loadTasksData() {
78
+ async loadTasksData() {
81
79
  this.ensureInitialized();
82
- if (!this.tasksFile || !(0, fs_1.existsSync)(this.tasksFile)) {
80
+ if (!this.tasksFile) {
83
81
  return { tasks: [], nextId: 1 };
84
82
  }
85
83
  try {
86
- const content = (0, fs_1.readFileSync)(this.tasksFile, "utf-8");
84
+ // Check if file exists using access or stat, or just try reading
85
+ await (0, promises_1.stat)(this.tasksFile);
86
+ }
87
+ catch {
88
+ return { tasks: [], nextId: 1 };
89
+ }
90
+ try {
91
+ const content = await (0, promises_1.readFile)(this.tasksFile, "utf-8");
87
92
  return JSON.parse(content);
88
93
  }
89
94
  catch (error) {
@@ -91,13 +96,13 @@ class LocalStorage {
91
96
  return { tasks: [], nextId: 1 };
92
97
  }
93
98
  }
94
- saveTasksData(data) {
99
+ async saveTasksData(data) {
95
100
  this.ensureInitialized();
96
101
  if (!this.tasksFile) {
97
- throw new Error("LocalStorage not initialized");
102
+ throw new Error("FileSystemStorage not initialized");
98
103
  }
99
104
  try {
100
- (0, fs_1.writeFileSync)(this.tasksFile, JSON.stringify(data, null, 2));
105
+ await (0, promises_1.writeFile)(this.tasksFile, JSON.stringify(data, null, 2));
101
106
  }
102
107
  catch (error) {
103
108
  throw new Error(`Failed to save tasks data: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -131,26 +136,25 @@ class LocalStorage {
131
136
  }
132
137
  // Tasks
133
138
  async getTasks() {
134
- const data = this.loadTasksData();
139
+ const data = await this.loadTasksData();
135
140
  return this.flattenTasks(data.tasks);
136
141
  }
137
142
  async getTopLevelTasks() {
138
- const data = this.loadTasksData();
143
+ const data = await this.loadTasksData();
139
144
  return data.tasks;
140
145
  }
141
146
  async getTask(id) {
142
147
  this.validateTaskId(id);
143
- const data = this.loadTasksData();
148
+ const data = await this.loadTasksData();
144
149
  const result = this.findTaskInHierarchy(data.tasks, id);
145
150
  return result.task;
146
151
  }
147
152
  async createTask(task, aiMetadata) {
148
153
  this.validateTaskRequest(task);
149
- this.ensureDirectories(); // Ensure directories exist when actually creating tasks
150
- const data = this.loadTasksData();
154
+ await this.ensureDirectories();
155
+ const data = await this.loadTasksData();
151
156
  let id;
152
157
  if (task.parentId) {
153
- // Generate dot notation ID for subtask
154
158
  const parentResult = this.findTaskInHierarchy(data.tasks, task.parentId);
155
159
  if (!parentResult.task) {
156
160
  throw new Error(`Parent task with ID ${task.parentId} not found`);
@@ -159,41 +163,32 @@ class LocalStorage {
159
163
  id = `${task.parentId}.${siblingCount}`;
160
164
  }
161
165
  else {
162
- // Check if task has a predefined ID (from AI generation)
163
166
  if (task.id && typeof task.id === "string") {
164
167
  id = task.id;
165
168
  }
166
169
  else {
167
- // Top-level task - use incremental ID
168
170
  id = data.nextId.toString();
169
171
  data.nextId++;
170
172
  }
171
173
  }
172
- // Validate dependencies if provided
173
174
  if (task.dependencies && task.dependencies.length > 0) {
174
- // Check if all dependency tasks exist
175
175
  for (const depId of task.dependencies) {
176
176
  const depExists = this.taskExists(data.tasks, depId);
177
177
  if (!depExists) {
178
178
  throw new Error(`Dependency task not found: ${depId}`);
179
179
  }
180
180
  }
181
- // Check for circular dependencies
182
181
  if (this.wouldCreateCircularDependency(data.tasks, id, task.dependencies)) {
183
182
  throw new Error(`Circular dependency detected for task ${id}`);
184
183
  }
185
184
  }
186
- // Handle content separation
187
185
  let contentFile;
188
186
  let description = task.description || "";
189
187
  if (task.content && task.content.length > 200) {
190
- // Save long content to MD file
191
188
  contentFile = await this.saveTaskContent(id, task.content);
192
- // Keep first 200 chars as description
193
189
  description = task.description || task.content.substring(0, 200) + "...";
194
190
  }
195
191
  else if (task.content) {
196
- // Short content goes in description
197
192
  description = task.description || task.content;
198
193
  }
199
194
  const newTask = {
@@ -211,7 +206,6 @@ class LocalStorage {
211
206
  prdFile: task.prdFile,
212
207
  };
213
208
  if (task.parentId) {
214
- // Find parent and add as subtask
215
209
  const parentResult = this.findTaskInHierarchy(data.tasks, task.parentId);
216
210
  if (parentResult.task) {
217
211
  if (!parentResult.task.subtasks) {
@@ -221,11 +215,9 @@ class LocalStorage {
221
215
  }
222
216
  }
223
217
  else {
224
- // Top-level task
225
218
  data.tasks.push(newTask);
226
219
  }
227
- this.saveTasksData(data);
228
- // Save AI metadata separately if provided
220
+ await this.saveTasksData(data);
229
221
  if (aiMetadata) {
230
222
  await this.saveTaskAIMetadata({
231
223
  ...aiMetadata,
@@ -240,7 +232,7 @@ class LocalStorage {
240
232
  if (!updates || typeof updates !== "object") {
241
233
  throw new Error("Updates must be a valid object");
242
234
  }
243
- const data = this.loadTasksData();
235
+ const data = await this.loadTasksData();
244
236
  const result = this.findTaskInHierarchy(data.tasks, id);
245
237
  if (!result.task)
246
238
  return null;
@@ -251,56 +243,48 @@ class LocalStorage {
251
243
  updatedAt: Date.now(),
252
244
  };
253
245
  if (result.parent) {
254
- // Update in parent's subtasks
255
246
  const parentIndex = result.parent.subtasks.findIndex((t) => t.id === id);
256
247
  result.parent.subtasks[parentIndex] = updatedTask;
257
248
  }
258
249
  else {
259
- // Update top-level task
260
250
  const taskIndex = data.tasks.findIndex((t) => t.id === id);
261
251
  data.tasks[taskIndex] = updatedTask;
262
252
  }
263
- this.saveTasksData(data);
253
+ await this.saveTasksData(data);
264
254
  return updatedTask;
265
255
  }
266
256
  async deleteTask(id) {
267
257
  this.validateTaskId(id);
268
- const data = this.loadTasksData();
258
+ const data = await this.loadTasksData();
269
259
  const result = this.findTaskInHierarchy(data.tasks, id);
270
260
  if (!result.task)
271
261
  return false;
272
262
  if (result.parent) {
273
- // Remove from parent's subtasks
274
263
  const parentIndex = result.parent.subtasks.findIndex((t) => t.id === id);
275
264
  result.parent.subtasks.splice(parentIndex, 1);
276
265
  }
277
266
  else {
278
- // Remove top-level task
279
267
  const taskIndex = data.tasks.findIndex((t) => t.id === id);
280
268
  data.tasks.splice(taskIndex, 1);
281
269
  }
282
- this.saveTasksData(data);
270
+ await this.saveTasksData(data);
283
271
  return true;
284
272
  }
285
273
  async getSubtasks(parentId) {
286
- const data = this.loadTasksData();
274
+ const data = await this.loadTasksData();
287
275
  const result = this.findTaskInHierarchy(data.tasks, parentId);
288
276
  return result.task?.subtasks || [];
289
277
  }
290
- // Helper methods for dependency validation
291
278
  taskExists(tasks, taskId) {
292
- // Check for exact match first
293
279
  const exactMatch = this.findTaskInHierarchy(tasks, taskId).task;
294
280
  if (exactMatch)
295
281
  return true;
296
- // Check for task- prefix match (AI generates "1" but stored as "task-1")
297
282
  const taskPrefixedId = taskId.startsWith("task-")
298
283
  ? taskId.substring(5)
299
284
  : `task-${taskId}`;
300
285
  const prefixedMatch = this.findTaskInHierarchy(tasks, taskPrefixedId).task;
301
286
  if (prefixedMatch)
302
287
  return true;
303
- // Check reverse (stored as "1" but looking for "task-1")
304
288
  if (taskId.startsWith("task-")) {
305
289
  const numericId = taskId.substring(5);
306
290
  const numericMatch = this.findTaskInHierarchy(tasks, numericId).task;
@@ -313,10 +297,10 @@ class LocalStorage {
313
297
  const visited = new Set();
314
298
  const checkCircular = (taskId) => {
315
299
  if (visited.has(taskId)) {
316
- return true; // Circular dependency detected
300
+ return true;
317
301
  }
318
302
  if (taskId === newTaskId) {
319
- return true; // Found circular reference back to new task
303
+ return true;
320
304
  }
321
305
  visited.add(taskId);
322
306
  const task = this.findTaskInHierarchy(tasks, taskId).task;
@@ -330,7 +314,6 @@ class LocalStorage {
330
314
  visited.delete(taskId);
331
315
  return false;
332
316
  };
333
- // Check each dependency chain
334
317
  for (const depId of dependencies) {
335
318
  visited.clear();
336
319
  if (checkCircular(depId)) {
@@ -339,21 +322,23 @@ class LocalStorage {
339
322
  }
340
323
  return false;
341
324
  }
342
- // AI Metadata
343
325
  getAIMetadataFile() {
344
326
  this.ensureInitialized();
345
327
  if (!this.taskOMatic) {
346
- throw new Error("LocalStorage not initialized");
328
+ throw new Error("FileSystemStorage not initialized");
347
329
  }
348
330
  return (0, path_1.join)(this.taskOMatic, "ai-metadata.json");
349
331
  }
350
- loadAIMetadata() {
332
+ async loadAIMetadata() {
351
333
  const metadataFile = this.getAIMetadataFile();
352
- if (!(0, fs_1.existsSync)(metadataFile)) {
334
+ try {
335
+ await (0, promises_1.stat)(metadataFile);
336
+ }
337
+ catch {
353
338
  return [];
354
339
  }
355
340
  try {
356
- const content = (0, fs_1.readFileSync)(metadataFile, "utf-8");
341
+ const content = await (0, promises_1.readFile)(metadataFile, "utf-8");
357
342
  return JSON.parse(content);
358
343
  }
359
344
  catch (error) {
@@ -361,16 +346,16 @@ class LocalStorage {
361
346
  return [];
362
347
  }
363
348
  }
364
- saveAIMetadata(metadata) {
349
+ async saveAIMetadata(metadata) {
365
350
  const metadataFile = this.getAIMetadataFile();
366
- (0, fs_1.writeFileSync)(metadataFile, JSON.stringify(metadata, null, 2));
351
+ await (0, promises_1.writeFile)(metadataFile, JSON.stringify(metadata, null, 2));
367
352
  }
368
353
  async getTaskAIMetadata(taskId) {
369
- const metadata = this.loadAIMetadata();
354
+ const metadata = await this.loadAIMetadata();
370
355
  return metadata.find((meta) => meta.taskId === taskId) || null;
371
356
  }
372
357
  async saveTaskAIMetadata(metadata) {
373
- const allMetadata = this.loadAIMetadata();
358
+ const allMetadata = await this.loadAIMetadata();
374
359
  const existingIndex = allMetadata.findIndex((meta) => meta.taskId === metadata.taskId);
375
360
  if (existingIndex >= 0) {
376
361
  allMetadata[existingIndex] = metadata;
@@ -378,27 +363,26 @@ class LocalStorage {
378
363
  else {
379
364
  allMetadata.push(metadata);
380
365
  }
381
- this.saveAIMetadata(allMetadata);
366
+ await this.saveAIMetadata(allMetadata);
382
367
  }
383
368
  async deleteTaskAIMetadata(taskId) {
384
- const metadata = this.loadAIMetadata();
369
+ const metadata = await this.loadAIMetadata();
385
370
  const filtered = metadata.filter((meta) => meta.taskId !== taskId);
386
- this.saveAIMetadata(filtered);
371
+ await this.saveAIMetadata(filtered);
387
372
  }
388
- // Task Content Files
389
373
  async saveTaskContent(taskId, content) {
390
374
  this.validateTaskId(taskId);
391
375
  if (typeof content !== "string") {
392
376
  throw new Error("Content must be a string");
393
377
  }
394
- this.ensureDirectories(); // Ensure directories exist when saving content
378
+ await this.ensureDirectories();
395
379
  if (!this.taskOMatic) {
396
- throw new Error("LocalStorage not initialized");
380
+ throw new Error("FileSystemStorage not initialized");
397
381
  }
398
382
  const contentFileName = `tasks/${taskId}.md`;
399
383
  const contentFilePath = (0, path_1.join)(this.taskOMatic, contentFileName);
400
384
  try {
401
- (0, fs_1.writeFileSync)(contentFilePath, content, "utf-8");
385
+ await (0, promises_1.writeFile)(contentFilePath, content, "utf-8");
402
386
  }
403
387
  catch (error) {
404
388
  throw new Error(`Failed to save task content: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -410,14 +394,14 @@ class LocalStorage {
410
394
  if (typeof content !== "string") {
411
395
  throw new Error("Content must be a string");
412
396
  }
413
- this.ensureDirectories(); // Ensure directories exist when saving content
397
+ await this.ensureDirectories();
414
398
  if (!this.taskOMatic) {
415
- throw new Error("LocalStorage not initialized");
399
+ throw new Error("FileSystemStorage not initialized");
416
400
  }
417
401
  const contentFileName = `tasks/enhanced/${taskId}.md`;
418
402
  const contentFilePath = (0, path_1.join)(this.taskOMatic, contentFileName);
419
403
  try {
420
- (0, fs_1.writeFileSync)(contentFilePath, content, "utf-8");
404
+ await (0, promises_1.writeFile)(contentFilePath, content, "utf-8");
421
405
  }
422
406
  catch (error) {
423
407
  throw new Error(`Failed to save enhanced task content: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -427,15 +411,18 @@ class LocalStorage {
427
411
  async getTaskContent(taskId) {
428
412
  this.validateTaskId(taskId);
429
413
  if (!this.taskOMatic) {
430
- throw new Error("LocalStorage not initialized");
414
+ throw new Error("FileSystemStorage not initialized");
431
415
  }
432
416
  const contentFileName = `tasks/${taskId}.md`;
433
417
  const contentFilePath = (0, path_1.join)(this.taskOMatic, contentFileName);
434
- if (!(0, fs_1.existsSync)(contentFilePath)) {
418
+ try {
419
+ await (0, promises_1.stat)(contentFilePath);
420
+ }
421
+ catch {
435
422
  return null;
436
423
  }
437
424
  try {
438
- return (0, fs_1.readFileSync)(contentFilePath, "utf-8");
425
+ return await (0, promises_1.readFile)(contentFilePath, "utf-8");
439
426
  }
440
427
  catch (error) {
441
428
  console.error(`Failed to read task content for ${taskId}:`, error);
@@ -445,74 +432,82 @@ class LocalStorage {
445
432
  async deleteTaskContent(taskId) {
446
433
  this.validateTaskId(taskId);
447
434
  if (!this.taskOMatic) {
448
- throw new Error("LocalStorage not initialized");
435
+ throw new Error("FileSystemStorage not initialized");
449
436
  }
450
437
  const contentFileName = `tasks/${taskId}.md`;
451
438
  const contentFilePath = (0, path_1.join)(this.taskOMatic, contentFileName);
452
- if ((0, fs_1.existsSync)(contentFilePath)) {
453
- try {
454
- (0, fs_1.unlinkSync)(contentFilePath);
455
- }
456
- catch (error) {
457
- console.error(`Failed to delete task content for ${taskId}:`, error);
458
- }
439
+ try {
440
+ await (0, promises_1.stat)(contentFilePath);
441
+ await (0, promises_1.unlink)(contentFilePath);
442
+ }
443
+ catch (error) {
444
+ // Ignore if file doesn't exist
459
445
  }
460
446
  }
461
447
  async saveContext7Documentation(library, query, content) {
462
- this.ensureDirectories(); // Ensure .task-o-matic/docs exists
448
+ await this.ensureDirectories();
463
449
  if (!this.taskOMatic) {
464
- throw new Error("LocalStorage not initialized");
450
+ throw new Error("FileSystemStorage not initialized");
465
451
  }
466
452
  const sanitizedLibrary = this.sanitizeForFilename(library);
467
453
  const sanitizedQuery = this.sanitizeForFilename(query);
468
454
  const libraryDir = (0, path_1.join)(this.taskOMatic, "docs", sanitizedLibrary);
469
- if (!(0, fs_1.existsSync)(libraryDir)) {
470
- (0, fs_1.mkdirSync)(libraryDir, { recursive: true });
455
+ try {
456
+ await (0, promises_1.mkdir)(libraryDir, { recursive: true });
457
+ }
458
+ catch (error) {
459
+ if (error.code !== "EEXIST")
460
+ throw error;
471
461
  }
472
462
  const filePath = (0, path_1.join)(libraryDir, `${sanitizedQuery}.md`);
473
- (0, fs_1.writeFileSync)(filePath, content, "utf-8");
474
- // Return the relative path for storage
463
+ await (0, promises_1.writeFile)(filePath, content, "utf-8");
475
464
  return `docs/${sanitizedLibrary}/${sanitizedQuery}.md`;
476
465
  }
477
466
  async getDocumentationFile(fileName) {
478
467
  if (!this.taskOMatic) {
479
- throw new Error("LocalStorage not initialized");
468
+ throw new Error("FileSystemStorage not initialized");
480
469
  }
481
470
  const filePath = (0, path_1.join)(this.taskOMatic, "docs", fileName);
482
- if (!(0, fs_1.existsSync)(filePath)) {
471
+ try {
472
+ await (0, promises_1.stat)(filePath);
473
+ }
474
+ catch {
483
475
  return null;
484
476
  }
485
477
  try {
486
- return (0, fs_1.readFileSync)(filePath, "utf-8");
478
+ return await (0, promises_1.readFile)(filePath, "utf-8");
487
479
  }
488
480
  catch (error) {
489
481
  console.error(`Failed to read documentation file ${fileName}:`, error);
490
482
  return null;
491
483
  }
492
484
  }
493
- // List all available documentation files
494
485
  async listDocumentationFiles() {
495
486
  this.ensureInitialized();
496
487
  try {
497
488
  const docsDir = (0, path_1.join)(this.taskOMatic, "docs");
498
- if (!(0, fs_1.existsSync)(docsDir)) {
489
+ try {
490
+ await (0, promises_1.stat)(docsDir);
491
+ }
492
+ catch {
499
493
  return [];
500
494
  }
501
495
  const files = [];
502
- const scanDirectory = (dir, basePath = "") => {
503
- const items = (0, fs_1.readdirSync)(dir);
496
+ const scanDirectory = async (dir, basePath = "") => {
497
+ const items = await (0, promises_1.readdir)(dir);
504
498
  for (const item of items) {
505
499
  const fullPath = (0, path_1.join)(dir, item);
506
500
  const relativePath = basePath ? (0, path_1.join)(basePath, item) : item;
507
- if ((0, fs_1.statSync)(fullPath).isDirectory() && item !== "_cache") {
508
- scanDirectory(fullPath, relativePath);
501
+ const stats = await (0, promises_1.stat)(fullPath);
502
+ if (stats.isDirectory() && item !== "_cache") {
503
+ await scanDirectory(fullPath, relativePath);
509
504
  }
510
505
  else if (item.endsWith(".md") || item.endsWith(".txt")) {
511
506
  files.push(relativePath);
512
507
  }
513
508
  }
514
509
  };
515
- scanDirectory(docsDir);
510
+ await scanDirectory(docsDir);
516
511
  return files;
517
512
  }
518
513
  catch (error) {
@@ -520,70 +515,62 @@ class LocalStorage {
520
515
  return [];
521
516
  }
522
517
  }
523
- // Migration: Separate existing task content into MD files
524
518
  async migrateTaskContent() {
525
- const data = this.loadTasksData();
519
+ const data = await this.loadTasksData();
526
520
  let migratedCount = 0;
527
- const migrateTask = (task) => {
521
+ const migrateTask = async (task) => {
528
522
  let updatedTask = { ...task };
529
- // Check if task has old content property and no contentFile
530
523
  if ("content" in task && task.content && !task.contentFile) {
531
524
  const oldContent = task.content;
532
525
  if (oldContent.length > 200) {
533
- // Move long content to MD file
534
526
  if (!this.taskOMatic) {
535
- throw new Error("LocalStorage not initialized");
527
+ throw new Error("FileSystemStorage not initialized");
536
528
  }
537
529
  const contentFile = `tasks/${task.id}.md`;
538
530
  const contentFilePath = (0, path_1.join)(this.taskOMatic, contentFile);
539
- (0, fs_1.writeFileSync)(contentFilePath, oldContent, "utf-8");
531
+ await (0, promises_1.writeFile)(contentFilePath, oldContent, "utf-8");
540
532
  updatedTask.contentFile = contentFile;
541
533
  updatedTask.description =
542
534
  task.description || oldContent.substring(0, 200) + "...";
543
535
  }
544
536
  else {
545
- // Keep short content as description
546
537
  updatedTask.description = task.description || oldContent;
547
538
  }
548
- // Remove old content property
549
539
  const { content: _, ...taskWithoutContent } = updatedTask;
550
540
  updatedTask = taskWithoutContent;
551
541
  migratedCount++;
552
542
  }
553
- // Migrate subtasks
554
543
  if (task.subtasks) {
555
- updatedTask.subtasks = task.subtasks.map(migrateTask);
544
+ updatedTask.subtasks = await Promise.all(task.subtasks.map(migrateTask));
556
545
  }
557
546
  return updatedTask;
558
547
  };
559
- // Process all tasks
560
- data.tasks = data.tasks.map(migrateTask);
548
+ data.tasks = await Promise.all(data.tasks.map(migrateTask));
561
549
  if (migratedCount > 0) {
562
- this.saveTasksData(data);
550
+ await this.saveTasksData(data);
563
551
  }
564
552
  return migratedCount;
565
553
  }
566
- // Cleanup utilities
567
554
  async cleanupOrphanedContent() {
568
- const data = this.loadTasksData();
555
+ const data = await this.loadTasksData();
569
556
  const allTasks = this.flattenTasks(data.tasks);
570
557
  const validContentFiles = new Set(allTasks
571
558
  .filter((task) => task.contentFile)
572
559
  .map((task) => task.contentFile));
573
560
  if (!this.taskOMatic) {
574
- throw new Error("LocalStorage not initialized");
561
+ throw new Error("FileSystemStorage not initialized");
575
562
  }
576
563
  const tasksDir = (0, path_1.join)(this.taskOMatic, "tasks");
577
564
  let cleanedCount = 0;
578
565
  try {
579
- const files = (0, fs_1.readdirSync)(tasksDir);
566
+ const files = await (0, promises_1.readdir)(tasksDir);
580
567
  for (const file of files) {
581
568
  if (file.endsWith(".md")) {
582
569
  const contentFile = `tasks/${file}`;
583
570
  if (!validContentFiles.has(contentFile)) {
584
571
  const filePath = (0, path_1.join)(tasksDir, file);
585
572
  try {
586
- (0, fs_1.unlinkSync)(filePath);
573
+ await (0, promises_1.unlink)(filePath);
587
574
  cleanedCount++;
588
575
  console.log(`Cleaned up orphaned content file: ${file}`);
589
576
  }
@@ -602,24 +589,20 @@ class LocalStorage {
602
589
  async validateStorageIntegrity() {
603
590
  const issues = [];
604
591
  try {
605
- // Check if directories exist
606
- this.ensureDirectories();
607
- // Validate tasks data structure
608
- const data = this.loadTasksData();
592
+ await this.ensureDirectories();
593
+ const data = await this.loadTasksData();
609
594
  if (!Array.isArray(data.tasks)) {
610
595
  issues.push("Tasks data is not an array");
611
596
  }
612
597
  if (typeof data.nextId !== "number" || data.nextId < 1) {
613
598
  issues.push("Invalid nextId in tasks data");
614
599
  }
615
- // Check for duplicate task IDs
616
600
  const allTasks = this.flattenTasks(data.tasks);
617
601
  const taskIds = allTasks.map((task) => task.id);
618
602
  const duplicateIds = taskIds.filter((id, index) => taskIds.indexOf(id) !== index);
619
603
  if (duplicateIds.length > 0) {
620
604
  issues.push(`Duplicate task IDs found: ${duplicateIds.join(", ")}`);
621
605
  }
622
- // Check for invalid dependencies
623
606
  for (const task of allTasks) {
624
607
  if (task.dependencies) {
625
608
  for (const depId of task.dependencies) {
@@ -638,14 +621,13 @@ class LocalStorage {
638
621
  issues,
639
622
  };
640
623
  }
641
- // Plan storage methods
642
624
  async savePlan(taskId, plan) {
643
625
  this.validateTaskId(taskId);
644
626
  this.ensureInitialized();
645
627
  if (!this.taskOMatic) {
646
- throw new Error("LocalStorage not initialized");
628
+ throw new Error("FileSystemStorage not initialized");
647
629
  }
648
- const planFile = (0, path_1.join)(this.taskOMatic, "plans", `${taskId}.md`);
630
+ const planFile = (0, path_1.join)(this.taskOMatic, "plans", `${taskId}.json`);
649
631
  try {
650
632
  const planData = {
651
633
  taskId,
@@ -653,7 +635,7 @@ class LocalStorage {
653
635
  createdAt: Date.now(),
654
636
  updatedAt: Date.now(),
655
637
  };
656
- (0, fs_1.writeFileSync)(planFile, JSON.stringify(planData, null, 2));
638
+ await (0, promises_1.writeFile)(planFile, JSON.stringify(planData, null, 2));
657
639
  }
658
640
  catch (error) {
659
641
  throw new Error(`Failed to save plan for task ${taskId}: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -663,14 +645,17 @@ class LocalStorage {
663
645
  this.validateTaskId(taskId);
664
646
  this.ensureInitialized();
665
647
  if (!this.taskOMatic) {
666
- throw new Error("LocalStorage not initialized");
648
+ throw new Error("FileSystemStorage not initialized");
667
649
  }
668
650
  const planFile = (0, path_1.join)(this.taskOMatic, "plans", `${taskId}.json`);
669
- if (!(0, fs_1.existsSync)(planFile)) {
651
+ try {
652
+ await (0, promises_1.stat)(planFile);
653
+ }
654
+ catch {
670
655
  return null;
671
656
  }
672
657
  try {
673
- const content = (0, fs_1.readFileSync)(planFile, "utf-8");
658
+ const content = await (0, promises_1.readFile)(planFile, "utf-8");
674
659
  return JSON.parse(content);
675
660
  }
676
661
  catch (error) {
@@ -680,15 +665,18 @@ class LocalStorage {
680
665
  async listPlans() {
681
666
  this.ensureInitialized();
682
667
  if (!this.taskOMatic) {
683
- throw new Error("LocalStorage not initialized");
668
+ throw new Error("FileSystemStorage not initialized");
684
669
  }
685
670
  const plansDir = (0, path_1.join)(this.taskOMatic, "plans");
686
- if (!(0, fs_1.existsSync)(plansDir)) {
671
+ try {
672
+ await (0, promises_1.stat)(plansDir);
673
+ }
674
+ catch {
687
675
  return [];
688
676
  }
689
677
  try {
690
678
  const plans = [];
691
- const files = (0, fs_1.readdirSync)(plansDir);
679
+ const files = await (0, promises_1.readdir)(plansDir);
692
680
  for (const file of files) {
693
681
  if (file.endsWith(".json")) {
694
682
  const taskId = file.replace(".json", "");
@@ -703,7 +691,7 @@ class LocalStorage {
703
691
  }
704
692
  }
705
693
  }
706
- return plans.sort((a, b) => b.updatedAt - a.updatedAt); // Most recent first
694
+ return plans.sort((a, b) => b.updatedAt - a.updatedAt);
707
695
  }
708
696
  catch (error) {
709
697
  throw new Error(`Failed to list plans: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -713,18 +701,16 @@ class LocalStorage {
713
701
  this.validateTaskId(taskId);
714
702
  this.ensureInitialized();
715
703
  if (!this.taskOMatic) {
716
- throw new Error("LocalStorage not initialized");
704
+ throw new Error("FileSystemStorage not initialized");
717
705
  }
718
706
  const planFile = (0, path_1.join)(this.taskOMatic, "plans", `${taskId}.json`);
719
- if (!(0, fs_1.existsSync)(planFile)) {
720
- return false;
721
- }
722
707
  try {
723
- (0, fs_1.unlinkSync)(planFile);
708
+ await (0, promises_1.stat)(planFile);
709
+ await (0, promises_1.unlink)(planFile);
724
710
  return true;
725
711
  }
726
712
  catch (error) {
727
- throw new Error(`Failed to delete plan for task ${taskId}: ${error instanceof Error ? error.message : "Unknown error"}`);
713
+ return false;
728
714
  }
729
715
  }
730
716
  async saveTaskDocumentation(taskId, documentation) {
@@ -732,14 +718,14 @@ class LocalStorage {
732
718
  if (typeof documentation !== "string") {
733
719
  throw new Error("Documentation must be a string");
734
720
  }
735
- this.ensureDirectories(); // Ensure docs/tasks directory exists
721
+ await this.ensureDirectories();
736
722
  if (!this.taskOMatic) {
737
- throw new Error("LocalStorage not initialized");
723
+ throw new Error("FileSystemStorage not initialized");
738
724
  }
739
725
  const documentationFileName = `docs/tasks/${taskId}.md`;
740
726
  const documentationFilePath = (0, path_1.join)(this.taskOMatic, documentationFileName);
741
727
  try {
742
- (0, fs_1.writeFileSync)(documentationFilePath, documentation, "utf-8");
728
+ await (0, promises_1.writeFile)(documentationFilePath, documentation, "utf-8");
743
729
  }
744
730
  catch (error) {
745
731
  throw new Error(`Failed to save task documentation: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -749,15 +735,18 @@ class LocalStorage {
749
735
  async getTaskDocumentation(taskId) {
750
736
  this.validateTaskId(taskId);
751
737
  if (!this.taskOMatic) {
752
- throw new Error("LocalStorage not initialized");
738
+ throw new Error("FileSystemStorage not initialized");
753
739
  }
754
740
  const documentationFileName = `docs/tasks/${taskId}.md`;
755
741
  const documentationFilePath = (0, path_1.join)(this.taskOMatic, documentationFileName);
756
- if (!(0, fs_1.existsSync)(documentationFilePath)) {
742
+ try {
743
+ await (0, promises_1.stat)(documentationFilePath);
744
+ }
745
+ catch {
757
746
  return null;
758
747
  }
759
748
  try {
760
- return (0, fs_1.readFileSync)(documentationFilePath, "utf-8");
749
+ return await (0, promises_1.readFile)(documentationFilePath, "utf-8");
761
750
  }
762
751
  catch (error) {
763
752
  console.error(`Failed to read task documentation for ${taskId}:`, error);
@@ -765,4 +754,4 @@ class LocalStorage {
765
754
  }
766
755
  }
767
756
  }
768
- exports.LocalStorage = LocalStorage;
757
+ exports.FileSystemStorage = FileSystemStorage;