task-o-matic 0.0.3 → 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.
- package/README.md +96 -40
- package/dist/commands/tasks/create.d.ts +3 -0
- package/dist/commands/tasks/create.d.ts.map +1 -0
- package/dist/commands/tasks/create.js +58 -0
- package/dist/commands/tasks/delete.d.ts +3 -0
- package/dist/commands/tasks/delete.d.ts.map +1 -0
- package/dist/commands/tasks/delete.js +40 -0
- package/dist/commands/tasks/document.d.ts +5 -0
- package/dist/commands/tasks/document.d.ts.map +1 -0
- package/dist/commands/tasks/document.js +118 -0
- package/dist/commands/tasks/enhance.d.ts +3 -0
- package/dist/commands/tasks/enhance.d.ts.map +1 -0
- package/dist/commands/tasks/enhance.js +86 -0
- package/dist/commands/tasks/execute.d.ts +3 -0
- package/dist/commands/tasks/execute.d.ts.map +1 -0
- package/dist/commands/tasks/execute.js +33 -0
- package/dist/commands/tasks/index.d.ts +16 -0
- package/dist/commands/tasks/index.d.ts.map +1 -0
- package/dist/commands/tasks/index.js +31 -0
- package/dist/commands/tasks/list.d.ts +3 -0
- package/dist/commands/tasks/list.d.ts.map +1 -0
- package/dist/commands/tasks/list.js +27 -0
- package/dist/commands/tasks/next.d.ts +3 -0
- package/dist/commands/tasks/next.d.ts.map +1 -0
- package/dist/commands/tasks/next.js +44 -0
- package/dist/commands/tasks/plan.d.ts +7 -0
- package/dist/commands/tasks/plan.d.ts.map +1 -0
- package/dist/commands/tasks/plan.js +131 -0
- package/dist/commands/tasks/show.d.ts +3 -0
- package/dist/commands/tasks/show.d.ts.map +1 -0
- package/dist/commands/tasks/show.js +23 -0
- package/dist/commands/tasks/split.d.ts +3 -0
- package/dist/commands/tasks/split.d.ts.map +1 -0
- package/dist/commands/tasks/split.js +95 -0
- package/dist/commands/tasks/status.d.ts +3 -0
- package/dist/commands/tasks/status.d.ts.map +1 -0
- package/dist/commands/tasks/status.js +26 -0
- package/dist/commands/tasks/subtasks.d.ts +3 -0
- package/dist/commands/tasks/subtasks.d.ts.map +1 -0
- package/dist/commands/tasks/subtasks.js +35 -0
- package/dist/commands/tasks/tags.d.ts +4 -0
- package/dist/commands/tasks/tags.d.ts.map +1 -0
- package/dist/commands/tasks/tags.js +37 -0
- package/dist/commands/tasks/tree.d.ts +3 -0
- package/dist/commands/tasks/tree.d.ts.map +1 -0
- package/dist/commands/tasks/tree.js +20 -0
- package/dist/commands/tasks/update.d.ts +3 -0
- package/dist/commands/tasks/update.d.ts.map +1 -0
- package/dist/commands/tasks/update.js +35 -0
- package/dist/commands/tasks.d.ts.map +1 -1
- package/dist/commands/tasks.js +23 -686
- package/dist/commands/workflow.d.ts +4 -0
- package/dist/commands/workflow.d.ts.map +1 -0
- package/dist/commands/workflow.js +434 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/lib/ai-service/ai-operations.d.ts.map +1 -1
- package/dist/lib/ai-service/ai-operations.js +54 -22
- package/dist/lib/ai-service/research-tools.d.ts.map +1 -1
- package/dist/lib/ai-service/research-tools.js +2 -2
- package/dist/lib/context-builder.d.ts +2 -1
- package/dist/lib/context-builder.d.ts.map +1 -1
- package/dist/lib/context-builder.js +3 -8
- package/dist/lib/hooks/logger.d.ts +2 -0
- package/dist/lib/hooks/logger.d.ts.map +1 -0
- package/dist/lib/hooks/logger.js +27 -0
- package/dist/lib/hooks.d.ts +64 -0
- package/dist/lib/hooks.d.ts.map +1 -0
- package/dist/lib/hooks.js +60 -0
- package/dist/lib/index.d.ts +18 -17
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +3 -3
- package/dist/lib/prompt-builder.d.ts.map +1 -1
- package/dist/lib/prompt-builder.js +16 -8
- package/dist/lib/{storage.d.ts → storage/file-system.d.ts} +4 -3
- package/dist/lib/storage/file-system.d.ts.map +1 -0
- package/dist/lib/{storage.js → storage/file-system.js} +141 -152
- package/dist/lib/storage/types.d.ts +43 -0
- package/dist/lib/storage/types.d.ts.map +1 -0
- package/dist/lib/storage/types.js +2 -0
- package/dist/lib/task-execution.d.ts.map +1 -1
- package/dist/lib/task-execution.js +22 -3
- package/dist/prompts/workflow-assistance.d.ts +32 -0
- package/dist/prompts/workflow-assistance.d.ts.map +1 -0
- package/dist/prompts/workflow-assistance.js +130 -0
- package/dist/services/tasks.d.ts +4 -6
- package/dist/services/tasks.d.ts.map +1 -1
- package/dist/services/tasks.js +115 -96
- package/dist/services/workflow-ai-assistant.d.ts +74 -0
- package/dist/services/workflow-ai-assistant.d.ts.map +1 -0
- package/dist/services/workflow-ai-assistant.js +223 -0
- package/dist/test/hooks.test.d.ts +2 -0
- package/dist/test/hooks.test.d.ts.map +1 -0
- package/dist/test/hooks.test.js +58 -0
- package/dist/test/storage.test.js +16 -16
- package/dist/types/options.d.ts +35 -0
- package/dist/types/options.d.ts.map +1 -1
- package/dist/utils/ai-service-factory.d.ts +5 -5
- package/dist/utils/ai-service-factory.d.ts.map +1 -1
- package/dist/utils/ai-service-factory.js +4 -3
- package/dist/utils/workflow-prompts.d.ts +17 -0
- package/dist/utils/workflow-prompts.d.ts.map +1 -0
- package/dist/utils/workflow-prompts.js +88 -0
- package/package.json +2 -2
- 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.
|
|
4
|
-
const
|
|
3
|
+
exports.FileSystemStorage = void 0;
|
|
4
|
+
const promises_1 = require("fs/promises");
|
|
5
5
|
const path_1 = require("path");
|
|
6
|
-
const config_1 = require("
|
|
7
|
-
class
|
|
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("
|
|
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
|
-
|
|
67
|
+
for (const dir of dirs) {
|
|
74
68
|
const fullPath = (0, path_1.join)(this.taskOMatic, dir);
|
|
75
|
-
|
|
76
|
-
(0,
|
|
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
|
|
80
|
+
if (!this.tasksFile) {
|
|
83
81
|
return { tasks: [], nextId: 1 };
|
|
84
82
|
}
|
|
85
83
|
try {
|
|
86
|
-
|
|
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("
|
|
102
|
+
throw new Error("FileSystemStorage not initialized");
|
|
98
103
|
}
|
|
99
104
|
try {
|
|
100
|
-
(0,
|
|
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();
|
|
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;
|
|
300
|
+
return true;
|
|
317
301
|
}
|
|
318
302
|
if (taskId === newTaskId) {
|
|
319
|
-
return true;
|
|
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("
|
|
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
|
-
|
|
334
|
+
try {
|
|
335
|
+
await (0, promises_1.stat)(metadataFile);
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
353
338
|
return [];
|
|
354
339
|
}
|
|
355
340
|
try {
|
|
356
|
-
const content = (0,
|
|
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,
|
|
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();
|
|
378
|
+
await this.ensureDirectories();
|
|
395
379
|
if (!this.taskOMatic) {
|
|
396
|
-
throw new Error("
|
|
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,
|
|
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();
|
|
397
|
+
await this.ensureDirectories();
|
|
414
398
|
if (!this.taskOMatic) {
|
|
415
|
-
throw new Error("
|
|
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,
|
|
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("
|
|
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
|
-
|
|
418
|
+
try {
|
|
419
|
+
await (0, promises_1.stat)(contentFilePath);
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
435
422
|
return null;
|
|
436
423
|
}
|
|
437
424
|
try {
|
|
438
|
-
return (0,
|
|
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("
|
|
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
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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();
|
|
448
|
+
await this.ensureDirectories();
|
|
463
449
|
if (!this.taskOMatic) {
|
|
464
|
-
throw new Error("
|
|
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
|
-
|
|
470
|
-
(0,
|
|
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,
|
|
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("
|
|
468
|
+
throw new Error("FileSystemStorage not initialized");
|
|
480
469
|
}
|
|
481
470
|
const filePath = (0, path_1.join)(this.taskOMatic, "docs", fileName);
|
|
482
|
-
|
|
471
|
+
try {
|
|
472
|
+
await (0, promises_1.stat)(filePath);
|
|
473
|
+
}
|
|
474
|
+
catch {
|
|
483
475
|
return null;
|
|
484
476
|
}
|
|
485
477
|
try {
|
|
486
|
-
return (0,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
508
|
-
|
|
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("
|
|
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,
|
|
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
|
-
|
|
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("
|
|
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,
|
|
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,
|
|
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
|
-
|
|
606
|
-
this.
|
|
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("
|
|
628
|
+
throw new Error("FileSystemStorage not initialized");
|
|
647
629
|
}
|
|
648
|
-
const planFile = (0, path_1.join)(this.taskOMatic, "plans", `${taskId}.
|
|
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,
|
|
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("
|
|
648
|
+
throw new Error("FileSystemStorage not initialized");
|
|
667
649
|
}
|
|
668
650
|
const planFile = (0, path_1.join)(this.taskOMatic, "plans", `${taskId}.json`);
|
|
669
|
-
|
|
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,
|
|
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("
|
|
668
|
+
throw new Error("FileSystemStorage not initialized");
|
|
684
669
|
}
|
|
685
670
|
const plansDir = (0, path_1.join)(this.taskOMatic, "plans");
|
|
686
|
-
|
|
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,
|
|
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);
|
|
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("
|
|
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,
|
|
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
|
-
|
|
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();
|
|
721
|
+
await this.ensureDirectories();
|
|
736
722
|
if (!this.taskOMatic) {
|
|
737
|
-
throw new Error("
|
|
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,
|
|
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("
|
|
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
|
-
|
|
742
|
+
try {
|
|
743
|
+
await (0, promises_1.stat)(documentationFilePath);
|
|
744
|
+
}
|
|
745
|
+
catch {
|
|
757
746
|
return null;
|
|
758
747
|
}
|
|
759
748
|
try {
|
|
760
|
-
return (0,
|
|
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.
|
|
757
|
+
exports.FileSystemStorage = FileSystemStorage;
|