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