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,596 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.taskService = exports.TaskService = void 0;
|
|
4
|
+
const ai_service_factory_1 = require("../utils/ai-service-factory");
|
|
5
|
+
const stack_formatter_1 = require("../utils/stack-formatter");
|
|
6
|
+
const ai_config_builder_1 = require("../utils/ai-config-builder");
|
|
7
|
+
/**
|
|
8
|
+
* TaskService - Centralized business logic for all task operations
|
|
9
|
+
* This service is framework-agnostic and can be used by CLI, TUI, or Web
|
|
10
|
+
*/
|
|
11
|
+
class TaskService {
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// CORE CRUD OPERATIONS
|
|
14
|
+
// ============================================================================
|
|
15
|
+
async createTask(input) {
|
|
16
|
+
const startTime = Date.now();
|
|
17
|
+
let content = input.content;
|
|
18
|
+
let aiMetadata;
|
|
19
|
+
if (input.aiEnhance) {
|
|
20
|
+
input.callbacks?.onProgress?.({
|
|
21
|
+
type: 'progress',
|
|
22
|
+
message: 'Building context for task...',
|
|
23
|
+
});
|
|
24
|
+
const context = await (0, ai_service_factory_1.getContextBuilder)().buildContextForNewTask(input.title, input.content);
|
|
25
|
+
const stackInfo = (0, stack_formatter_1.formatStackInfo)(context.stack);
|
|
26
|
+
const enhancementAIConfig = (0, ai_config_builder_1.buildAIConfig)(input.aiOptions);
|
|
27
|
+
input.callbacks?.onProgress?.({
|
|
28
|
+
type: 'progress',
|
|
29
|
+
message: 'Enhancing task with AI documentation...',
|
|
30
|
+
});
|
|
31
|
+
const taskDescription = input.content ?? "";
|
|
32
|
+
content = await (0, ai_service_factory_1.getAIOperations)().enhanceTaskWithDocumentation(`task-${Date.now()}`, input.title, taskDescription, stackInfo, input.streamingOptions, undefined, enhancementAIConfig, context.existingResearch);
|
|
33
|
+
const aiConfig = (0, ai_service_factory_1.getModelProvider)().getAIConfig();
|
|
34
|
+
aiMetadata = {
|
|
35
|
+
taskId: "",
|
|
36
|
+
aiGenerated: true,
|
|
37
|
+
aiPrompt: "Enhance task with relevant documentation using Context7 tools",
|
|
38
|
+
confidence: 0.9,
|
|
39
|
+
aiProvider: aiConfig.provider,
|
|
40
|
+
aiModel: aiConfig.model,
|
|
41
|
+
generatedAt: Date.now(),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
input.callbacks?.onProgress?.({
|
|
45
|
+
type: 'progress',
|
|
46
|
+
message: 'Saving task...',
|
|
47
|
+
});
|
|
48
|
+
const task = await (0, ai_service_factory_1.getStorage)().createTask({
|
|
49
|
+
title: input.title,
|
|
50
|
+
description: input.content ?? "",
|
|
51
|
+
content,
|
|
52
|
+
parentId: input.parentId,
|
|
53
|
+
estimatedEffort: input.effort,
|
|
54
|
+
}, aiMetadata);
|
|
55
|
+
input.callbacks?.onProgress?.({
|
|
56
|
+
type: 'completed',
|
|
57
|
+
message: 'Task created successfully',
|
|
58
|
+
});
|
|
59
|
+
return { success: true, task, aiMetadata };
|
|
60
|
+
}
|
|
61
|
+
async listTasks(filters) {
|
|
62
|
+
const storage = (0, ai_service_factory_1.getStorage)();
|
|
63
|
+
const topLevelTasks = await storage.getTopLevelTasks();
|
|
64
|
+
let filteredTasks = topLevelTasks;
|
|
65
|
+
if (filters.status) {
|
|
66
|
+
filteredTasks = filteredTasks.filter((task) => task.status === filters.status);
|
|
67
|
+
}
|
|
68
|
+
if (filters.tag) {
|
|
69
|
+
const tagToFilter = filters.tag; // Type narrowing
|
|
70
|
+
filteredTasks = filteredTasks.filter((task) => task.tags && task.tags.includes(tagToFilter));
|
|
71
|
+
}
|
|
72
|
+
return filteredTasks;
|
|
73
|
+
}
|
|
74
|
+
async getTask(id) {
|
|
75
|
+
return await (0, ai_service_factory_1.getStorage)().getTask(id);
|
|
76
|
+
}
|
|
77
|
+
async getTaskContent(id) {
|
|
78
|
+
return await (0, ai_service_factory_1.getStorage)().getTaskContent(id);
|
|
79
|
+
}
|
|
80
|
+
async getTaskAIMetadata(id) {
|
|
81
|
+
return await (0, ai_service_factory_1.getStorage)().getTaskAIMetadata(id);
|
|
82
|
+
}
|
|
83
|
+
async getSubtasks(id) {
|
|
84
|
+
return await (0, ai_service_factory_1.getStorage)().getSubtasks(id);
|
|
85
|
+
}
|
|
86
|
+
async updateTask(id, updates) {
|
|
87
|
+
const storage = (0, ai_service_factory_1.getStorage)();
|
|
88
|
+
const existingTask = await storage.getTask(id);
|
|
89
|
+
if (!existingTask) {
|
|
90
|
+
throw new Error(`Task with ID ${id} not found`);
|
|
91
|
+
}
|
|
92
|
+
// Validate status transitions
|
|
93
|
+
if (updates.status) {
|
|
94
|
+
const validTransitions = {
|
|
95
|
+
todo: ["in-progress", "completed"],
|
|
96
|
+
"in-progress": ["completed", "todo"],
|
|
97
|
+
completed: ["todo", "in-progress"],
|
|
98
|
+
};
|
|
99
|
+
if (!validTransitions[existingTask.status].includes(updates.status)) {
|
|
100
|
+
throw new Error(`Invalid status transition from ${existingTask.status} to ${updates.status}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const finalUpdates = { ...updates };
|
|
104
|
+
// Handle tags: convert comma-separated string to array and merge
|
|
105
|
+
if (typeof updates.tags === "string") {
|
|
106
|
+
const newTags = updates.tags.split(",").map((tag) => tag.trim());
|
|
107
|
+
const existingTags = existingTask.tags || [];
|
|
108
|
+
finalUpdates.tags = [...new Set([...existingTags, ...newTags])];
|
|
109
|
+
}
|
|
110
|
+
const updatedTask = await storage.updateTask(id, finalUpdates);
|
|
111
|
+
if (!updatedTask) {
|
|
112
|
+
throw new Error(`Failed to update task ${id}`);
|
|
113
|
+
}
|
|
114
|
+
return updatedTask;
|
|
115
|
+
}
|
|
116
|
+
async setTaskStatus(id, status) {
|
|
117
|
+
return await this.updateTask(id, { status });
|
|
118
|
+
}
|
|
119
|
+
async deleteTask(id, options = {}) {
|
|
120
|
+
const { cascade, force } = options;
|
|
121
|
+
const storage = (0, ai_service_factory_1.getStorage)();
|
|
122
|
+
const taskToDelete = await storage.getTask(id);
|
|
123
|
+
if (!taskToDelete) {
|
|
124
|
+
throw new Error(`Task with ID ${id} not found`);
|
|
125
|
+
}
|
|
126
|
+
const deleted = [];
|
|
127
|
+
const orphanedSubtasks = [];
|
|
128
|
+
// Get subtasks before deletion
|
|
129
|
+
const subtasks = await storage.getSubtasks(id);
|
|
130
|
+
if (subtasks.length > 0 && !cascade) {
|
|
131
|
+
if (!force) {
|
|
132
|
+
throw new Error(`Task has ${subtasks.length} subtasks. Use cascade to delete them or force to orphan them`);
|
|
133
|
+
}
|
|
134
|
+
// Orphan subtasks by removing parent reference
|
|
135
|
+
for (const subtask of subtasks) {
|
|
136
|
+
await storage.updateTask(subtask.id, { parentId: undefined });
|
|
137
|
+
orphanedSubtasks.push(subtask);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else if (cascade) {
|
|
141
|
+
// Recursively delete subtasks
|
|
142
|
+
for (const subtask of subtasks) {
|
|
143
|
+
const result = await this.deleteTask(subtask.id, { cascade: true, force: true });
|
|
144
|
+
deleted.push(...result.deleted);
|
|
145
|
+
orphanedSubtasks.push(...result.orphanedSubtasks);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Delete the main task
|
|
149
|
+
const success = await storage.deleteTask(id);
|
|
150
|
+
if (success) {
|
|
151
|
+
deleted.push(taskToDelete);
|
|
152
|
+
}
|
|
153
|
+
return { success: true, deleted, orphanedSubtasks };
|
|
154
|
+
}
|
|
155
|
+
// ============================================================================
|
|
156
|
+
// TAG OPERATIONS
|
|
157
|
+
// ============================================================================
|
|
158
|
+
async addTags(id, tags) {
|
|
159
|
+
const storage = (0, ai_service_factory_1.getStorage)();
|
|
160
|
+
const task = await storage.getTask(id);
|
|
161
|
+
if (!task) {
|
|
162
|
+
throw new Error(`Task with ID ${id} not found`);
|
|
163
|
+
}
|
|
164
|
+
const existingTags = task.tags || [];
|
|
165
|
+
const newTags = tags.filter((tag) => !existingTags.includes(tag));
|
|
166
|
+
if (newTags.length === 0) {
|
|
167
|
+
return task; // No new tags to add
|
|
168
|
+
}
|
|
169
|
+
const updatedTags = [...existingTags, ...newTags];
|
|
170
|
+
const updatedTask = await storage.updateTask(id, { tags: updatedTags });
|
|
171
|
+
if (!updatedTask) {
|
|
172
|
+
throw new Error(`Failed to add tags to task ${id}`);
|
|
173
|
+
}
|
|
174
|
+
return updatedTask;
|
|
175
|
+
}
|
|
176
|
+
async removeTags(id, tags) {
|
|
177
|
+
const storage = (0, ai_service_factory_1.getStorage)();
|
|
178
|
+
const task = await storage.getTask(id);
|
|
179
|
+
if (!task) {
|
|
180
|
+
throw new Error(`Task with ID ${id} not found`);
|
|
181
|
+
}
|
|
182
|
+
const existingTags = task.tags || [];
|
|
183
|
+
const updatedTags = existingTags.filter((tag) => !tags.includes(tag));
|
|
184
|
+
if (updatedTags.length === existingTags.length) {
|
|
185
|
+
return task; // No tags removed
|
|
186
|
+
}
|
|
187
|
+
const updatedTask = await storage.updateTask(id, { tags: updatedTags });
|
|
188
|
+
if (!updatedTask) {
|
|
189
|
+
throw new Error(`Failed to remove tags from task ${id}`);
|
|
190
|
+
}
|
|
191
|
+
return updatedTask;
|
|
192
|
+
}
|
|
193
|
+
// ============================================================================
|
|
194
|
+
// TASK NAVIGATION
|
|
195
|
+
// ============================================================================
|
|
196
|
+
async getNextTask(filters) {
|
|
197
|
+
const storage = (0, ai_service_factory_1.getStorage)();
|
|
198
|
+
const allTasks = await storage.getTasks();
|
|
199
|
+
// Filter by status and other criteria
|
|
200
|
+
let filteredTasks = allTasks.filter((task) => {
|
|
201
|
+
if (filters.status && task.status !== filters.status)
|
|
202
|
+
return false;
|
|
203
|
+
if (filters.tag && (!task.tags || !task.tags.includes(filters.tag)))
|
|
204
|
+
return false;
|
|
205
|
+
if (filters.effort && task.estimatedEffort !== filters.effort)
|
|
206
|
+
return false;
|
|
207
|
+
return true;
|
|
208
|
+
});
|
|
209
|
+
if (filteredTasks.length === 0)
|
|
210
|
+
return null;
|
|
211
|
+
// Sort based on priority
|
|
212
|
+
switch (filters.priority) {
|
|
213
|
+
case "newest":
|
|
214
|
+
return filteredTasks.sort((a, b) => b.createdAt - a.createdAt)[0];
|
|
215
|
+
case "oldest":
|
|
216
|
+
return filteredTasks.sort((a, b) => a.createdAt - b.createdAt)[0];
|
|
217
|
+
case "effort":
|
|
218
|
+
const effortOrder = { small: 1, medium: 2, large: 3 };
|
|
219
|
+
return filteredTasks.sort((a, b) => (effortOrder[a.estimatedEffort || "medium"] || 2) -
|
|
220
|
+
(effortOrder[b.estimatedEffort || "medium"] || 2))[0];
|
|
221
|
+
default:
|
|
222
|
+
// Default: task ID order (1, 1.1, 1.2, 2, 2.1, etc.)
|
|
223
|
+
return filteredTasks.sort((a, b) => a.id.localeCompare(b.id))[0];
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
async getTaskTree(rootId) {
|
|
227
|
+
const storage = (0, ai_service_factory_1.getStorage)();
|
|
228
|
+
if (rootId) {
|
|
229
|
+
// Return tree starting from specific task
|
|
230
|
+
const rootTask = await storage.getTask(rootId);
|
|
231
|
+
if (!rootTask) {
|
|
232
|
+
throw new Error(`Task with ID ${rootId} not found`);
|
|
233
|
+
}
|
|
234
|
+
// Get all subtasks recursively
|
|
235
|
+
const getAllSubtasks = async (task) => {
|
|
236
|
+
const subtasks = await storage.getSubtasks(task.id);
|
|
237
|
+
const allSubtasks = [];
|
|
238
|
+
for (const subtask of subtasks) {
|
|
239
|
+
allSubtasks.push(subtask);
|
|
240
|
+
const deeperSubtasks = await getAllSubtasks(subtask);
|
|
241
|
+
allSubtasks.push(...deeperSubtasks);
|
|
242
|
+
}
|
|
243
|
+
return allSubtasks;
|
|
244
|
+
};
|
|
245
|
+
const subtasks = await getAllSubtasks(rootTask);
|
|
246
|
+
return [rootTask, ...subtasks];
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
// Return all top-level tasks and their subtasks
|
|
250
|
+
return await storage.getTasks();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// ============================================================================
|
|
254
|
+
// AI OPERATIONS
|
|
255
|
+
// ============================================================================
|
|
256
|
+
async enhanceTask(taskId, aiOptions, streamingOptions, callbacks) {
|
|
257
|
+
const startTime = Date.now();
|
|
258
|
+
callbacks?.onProgress?.({
|
|
259
|
+
type: 'started',
|
|
260
|
+
message: 'Starting task enhancement...',
|
|
261
|
+
});
|
|
262
|
+
const task = await (0, ai_service_factory_1.getStorage)().getTask(taskId);
|
|
263
|
+
if (!task) {
|
|
264
|
+
throw new Error(`Task with ID ${taskId} not found`);
|
|
265
|
+
}
|
|
266
|
+
callbacks?.onProgress?.({
|
|
267
|
+
type: 'progress',
|
|
268
|
+
message: 'Building context...',
|
|
269
|
+
});
|
|
270
|
+
const context = await (0, ai_service_factory_1.getContextBuilder)().buildContext(taskId);
|
|
271
|
+
const stackInfo = (0, stack_formatter_1.formatStackInfo)(context.stack);
|
|
272
|
+
const enhancementAIConfig = (0, ai_config_builder_1.buildAIConfig)(aiOptions);
|
|
273
|
+
callbacks?.onProgress?.({
|
|
274
|
+
type: 'progress',
|
|
275
|
+
message: 'Calling AI for enhancement...',
|
|
276
|
+
});
|
|
277
|
+
const enhancedContent = await (0, ai_service_factory_1.getAIOperations)().enhanceTaskWithDocumentation(task.id, task.title, task.description ?? "", stackInfo, streamingOptions, undefined, enhancementAIConfig, context.existingResearch);
|
|
278
|
+
callbacks?.onProgress?.({
|
|
279
|
+
type: 'progress',
|
|
280
|
+
message: 'Saving enhanced content...',
|
|
281
|
+
});
|
|
282
|
+
const originalLength = task.description?.length || 0;
|
|
283
|
+
if (enhancedContent.length > 200) {
|
|
284
|
+
const contentFile = await (0, ai_service_factory_1.getStorage)().saveEnhancedTaskContent(task.id, enhancedContent);
|
|
285
|
+
await (0, ai_service_factory_1.getStorage)().updateTask(task.id, {
|
|
286
|
+
contentFile,
|
|
287
|
+
description: task.description +
|
|
288
|
+
"\n\n🤖 AI-enhanced with Context7 documentation available.",
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
await (0, ai_service_factory_1.getStorage)().updateTask(task.id, { description: enhancedContent });
|
|
293
|
+
}
|
|
294
|
+
const aiConfig = (0, ai_service_factory_1.getModelProvider)().getAIConfig();
|
|
295
|
+
const aiMetadata = {
|
|
296
|
+
taskId: task.id,
|
|
297
|
+
aiGenerated: true,
|
|
298
|
+
aiPrompt: "Enhance task with Context7 documentation using MCP tools",
|
|
299
|
+
confidence: 0.9,
|
|
300
|
+
aiProvider: aiConfig.provider,
|
|
301
|
+
aiModel: aiConfig.model,
|
|
302
|
+
enhancedAt: Date.now(),
|
|
303
|
+
};
|
|
304
|
+
await (0, ai_service_factory_1.getStorage)().saveTaskAIMetadata(aiMetadata);
|
|
305
|
+
const duration = Date.now() - startTime;
|
|
306
|
+
callbacks?.onProgress?.({
|
|
307
|
+
type: 'completed',
|
|
308
|
+
message: 'Task enhancement completed',
|
|
309
|
+
});
|
|
310
|
+
return {
|
|
311
|
+
success: true,
|
|
312
|
+
task,
|
|
313
|
+
enhancedContent,
|
|
314
|
+
stats: {
|
|
315
|
+
originalLength,
|
|
316
|
+
enhancedLength: enhancedContent.length,
|
|
317
|
+
duration,
|
|
318
|
+
},
|
|
319
|
+
metadata: {
|
|
320
|
+
aiProvider: aiConfig.provider,
|
|
321
|
+
aiModel: aiConfig.model,
|
|
322
|
+
confidence: 0.9,
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
async splitTask(taskId, aiOptions, promptOverride, messageOverride, streamingOptions, callbacks) {
|
|
327
|
+
const startTime = Date.now();
|
|
328
|
+
callbacks?.onProgress?.({
|
|
329
|
+
type: 'started',
|
|
330
|
+
message: 'Starting task breakdown...',
|
|
331
|
+
});
|
|
332
|
+
const task = await (0, ai_service_factory_1.getStorage)().getTask(taskId);
|
|
333
|
+
if (!task) {
|
|
334
|
+
throw new Error(`Task with ID ${taskId} not found`);
|
|
335
|
+
}
|
|
336
|
+
// Check if task already has subtasks
|
|
337
|
+
const existingSubtasks = await (0, ai_service_factory_1.getStorage)().getSubtasks(taskId);
|
|
338
|
+
if (existingSubtasks.length > 0) {
|
|
339
|
+
throw new Error(`Task ${task.title} already has ${existingSubtasks.length} subtasks. Use existing subtasks or delete them first.`);
|
|
340
|
+
}
|
|
341
|
+
callbacks?.onProgress?.({
|
|
342
|
+
type: 'progress',
|
|
343
|
+
message: 'Building context...',
|
|
344
|
+
});
|
|
345
|
+
// Build comprehensive context
|
|
346
|
+
const context = await (0, ai_service_factory_1.getContextBuilder)().buildContext(taskId);
|
|
347
|
+
const stackInfo = (0, stack_formatter_1.formatStackInfo)(context.stack);
|
|
348
|
+
// Get full task content
|
|
349
|
+
const fullContent = context.task.fullContent || task.description || "";
|
|
350
|
+
const breakdownAIConfig = (0, ai_config_builder_1.buildAIConfig)(aiOptions);
|
|
351
|
+
callbacks?.onProgress?.({
|
|
352
|
+
type: 'progress',
|
|
353
|
+
message: 'Calling AI to break down task...',
|
|
354
|
+
});
|
|
355
|
+
// Use AI service to break down the task with enhanced context
|
|
356
|
+
const subtaskData = await (0, ai_service_factory_1.getAIOperations)().breakdownTask(task, breakdownAIConfig, promptOverride, messageOverride, streamingOptions, undefined, fullContent, stackInfo, existingSubtasks);
|
|
357
|
+
callbacks?.onProgress?.({
|
|
358
|
+
type: 'progress',
|
|
359
|
+
message: `Creating ${subtaskData.length} subtasks...`,
|
|
360
|
+
});
|
|
361
|
+
// Create subtasks
|
|
362
|
+
const createdSubtasks = [];
|
|
363
|
+
for (let i = 0; i < subtaskData.length; i++) {
|
|
364
|
+
const subtask = subtaskData[i];
|
|
365
|
+
callbacks?.onProgress?.({
|
|
366
|
+
type: 'progress',
|
|
367
|
+
message: `Creating subtask ${i + 1}/${subtaskData.length}: ${subtask.title}`,
|
|
368
|
+
current: i + 1,
|
|
369
|
+
total: subtaskData.length,
|
|
370
|
+
});
|
|
371
|
+
const result = await this.createTask({
|
|
372
|
+
title: subtask.title,
|
|
373
|
+
content: subtask.content,
|
|
374
|
+
effort: subtask.estimatedEffort,
|
|
375
|
+
parentId: taskId,
|
|
376
|
+
});
|
|
377
|
+
createdSubtasks.push(result.task);
|
|
378
|
+
}
|
|
379
|
+
// Create AI metadata for tracking using actual AI config
|
|
380
|
+
const aiConfig = (0, ai_service_factory_1.getModelProvider)().getAIConfig();
|
|
381
|
+
const aiMetadata = {
|
|
382
|
+
taskId: task.id,
|
|
383
|
+
aiGenerated: true,
|
|
384
|
+
aiPrompt: promptOverride ||
|
|
385
|
+
"Split task into meaningful subtasks with full context and existing subtask awareness",
|
|
386
|
+
confidence: 0.9,
|
|
387
|
+
aiProvider: aiConfig.provider,
|
|
388
|
+
aiModel: aiConfig.model,
|
|
389
|
+
splitAt: Date.now(),
|
|
390
|
+
};
|
|
391
|
+
await (0, ai_service_factory_1.getStorage)().saveTaskAIMetadata(aiMetadata);
|
|
392
|
+
const duration = Date.now() - startTime;
|
|
393
|
+
callbacks?.onProgress?.({
|
|
394
|
+
type: 'completed',
|
|
395
|
+
message: `Task split into ${createdSubtasks.length} subtasks`,
|
|
396
|
+
});
|
|
397
|
+
return {
|
|
398
|
+
success: true,
|
|
399
|
+
task,
|
|
400
|
+
subtasks: createdSubtasks,
|
|
401
|
+
stats: {
|
|
402
|
+
subtasksCreated: createdSubtasks.length,
|
|
403
|
+
duration,
|
|
404
|
+
},
|
|
405
|
+
metadata: {
|
|
406
|
+
aiProvider: aiConfig.provider,
|
|
407
|
+
aiModel: aiConfig.model,
|
|
408
|
+
confidence: 0.9,
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
async documentTask(taskId, force = false, aiOptions, streamingOptions, callbacks) {
|
|
413
|
+
const startTime = Date.now();
|
|
414
|
+
callbacks?.onProgress?.({
|
|
415
|
+
type: 'started',
|
|
416
|
+
message: 'Analyzing documentation needs...',
|
|
417
|
+
});
|
|
418
|
+
const task = await (0, ai_service_factory_1.getStorage)().getTask(taskId);
|
|
419
|
+
if (!task) {
|
|
420
|
+
throw new Error(`Task with ID ${taskId} not found`);
|
|
421
|
+
}
|
|
422
|
+
if (task.documentation && !force) {
|
|
423
|
+
if ((0, ai_service_factory_1.getContextBuilder)().isDocumentationFresh(task.documentation)) {
|
|
424
|
+
callbacks?.onProgress?.({
|
|
425
|
+
type: 'info',
|
|
426
|
+
message: 'Documentation is fresh, skipping analysis',
|
|
427
|
+
});
|
|
428
|
+
return {
|
|
429
|
+
success: true,
|
|
430
|
+
task,
|
|
431
|
+
documentation: task.documentation,
|
|
432
|
+
stats: {
|
|
433
|
+
duration: Date.now() - startTime,
|
|
434
|
+
},
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
callbacks?.onProgress?.({
|
|
439
|
+
type: 'progress',
|
|
440
|
+
message: 'Building context...',
|
|
441
|
+
});
|
|
442
|
+
const context = await (0, ai_service_factory_1.getContextBuilder)().buildContext(taskId);
|
|
443
|
+
const stackInfo = (0, stack_formatter_1.formatStackInfo)(context.stack);
|
|
444
|
+
const analysisAIConfig = (0, ai_config_builder_1.buildAIConfig)(aiOptions);
|
|
445
|
+
// Get full task content
|
|
446
|
+
const fullContent = context.task.fullContent || task.description;
|
|
447
|
+
if (!fullContent) {
|
|
448
|
+
throw new Error("Task content is empty");
|
|
449
|
+
}
|
|
450
|
+
// Get existing documentations from all tasks
|
|
451
|
+
const tasks = await (0, ai_service_factory_1.getStorage)().getTasks();
|
|
452
|
+
const documentations = tasks.map((task) => task.documentation);
|
|
453
|
+
callbacks?.onProgress?.({
|
|
454
|
+
type: 'progress',
|
|
455
|
+
message: 'Calling AI to analyze documentation needs...',
|
|
456
|
+
});
|
|
457
|
+
// First analyze what documentation is needed
|
|
458
|
+
const analysis = await (0, ai_service_factory_1.getAIOperations)().analyzeDocumentationNeeds(task.id, task.title, fullContent, stackInfo, streamingOptions, undefined, analysisAIConfig, documentations);
|
|
459
|
+
let documentation;
|
|
460
|
+
if (analysis.libraries.length > 0) {
|
|
461
|
+
callbacks?.onProgress?.({
|
|
462
|
+
type: 'progress',
|
|
463
|
+
message: `Fetching documentation for ${analysis.libraries.length} libraries...`,
|
|
464
|
+
});
|
|
465
|
+
// Build research object from actual libraries
|
|
466
|
+
const research = {};
|
|
467
|
+
for (const lib of analysis.libraries) {
|
|
468
|
+
const sanitizedLibrary = (0, ai_service_factory_1.getStorage)().sanitizeForFilename(lib.name);
|
|
469
|
+
const sanitizedQuery = (0, ai_service_factory_1.getStorage)().sanitizeForFilename(lib.searchQuery);
|
|
470
|
+
const docFile = `docs/${sanitizedLibrary}/${sanitizedQuery}.md`;
|
|
471
|
+
if (!research[lib.name]) {
|
|
472
|
+
research[lib.name] = [];
|
|
473
|
+
}
|
|
474
|
+
research[lib.name].push({
|
|
475
|
+
query: lib.searchQuery,
|
|
476
|
+
doc: docFile,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
callbacks?.onProgress?.({
|
|
480
|
+
type: 'progress',
|
|
481
|
+
message: 'Generating documentation recap...',
|
|
482
|
+
});
|
|
483
|
+
const recap = await (0, ai_service_factory_1.getAIOperations)().generateDocumentationRecap(analysis.libraries, analysis.toolResults?.map((tr) => ({
|
|
484
|
+
library: tr.toolName,
|
|
485
|
+
content: JSON.stringify(tr.output),
|
|
486
|
+
})) || [], streamingOptions);
|
|
487
|
+
documentation = {
|
|
488
|
+
lastFetched: Date.now(),
|
|
489
|
+
libraries: analysis.libraries.map((lib) => lib.context7Id),
|
|
490
|
+
recap,
|
|
491
|
+
files: analysis.files || [],
|
|
492
|
+
research,
|
|
493
|
+
};
|
|
494
|
+
callbacks?.onProgress?.({
|
|
495
|
+
type: 'progress',
|
|
496
|
+
message: 'Saving documentation...',
|
|
497
|
+
});
|
|
498
|
+
await (0, ai_service_factory_1.getStorage)().updateTask(taskId, { documentation });
|
|
499
|
+
}
|
|
500
|
+
const duration = Date.now() - startTime;
|
|
501
|
+
callbacks?.onProgress?.({
|
|
502
|
+
type: 'completed',
|
|
503
|
+
message: 'Documentation analysis completed',
|
|
504
|
+
});
|
|
505
|
+
return {
|
|
506
|
+
success: true,
|
|
507
|
+
task,
|
|
508
|
+
analysis,
|
|
509
|
+
documentation,
|
|
510
|
+
stats: {
|
|
511
|
+
duration,
|
|
512
|
+
},
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
async planTask(taskId, aiOptions, streamingOptions, callbacks) {
|
|
516
|
+
const startTime = Date.now();
|
|
517
|
+
callbacks?.onProgress?.({
|
|
518
|
+
type: 'started',
|
|
519
|
+
message: 'Creating implementation plan...',
|
|
520
|
+
});
|
|
521
|
+
const task = await (0, ai_service_factory_1.getStorage)().getTask(taskId);
|
|
522
|
+
if (!task) {
|
|
523
|
+
throw new Error(`Task with ID ${taskId} not found`);
|
|
524
|
+
}
|
|
525
|
+
const aiService = (0, ai_service_factory_1.getAIOperations)();
|
|
526
|
+
const planAIConfig = (0, ai_config_builder_1.buildAIConfig)(aiOptions);
|
|
527
|
+
callbacks?.onProgress?.({
|
|
528
|
+
type: 'progress',
|
|
529
|
+
message: 'Building task context...',
|
|
530
|
+
});
|
|
531
|
+
// Build task context and details
|
|
532
|
+
let taskContext = `Task ID: ${task.id}\nTitle: ${task.title}\n`;
|
|
533
|
+
let taskDetails = `Description: ${task.description || "No description"}\n`;
|
|
534
|
+
// If this is a subtask, include parent task context
|
|
535
|
+
if (task.parentId) {
|
|
536
|
+
const parentTask = await (0, ai_service_factory_1.getStorage)().getTask(task.parentId);
|
|
537
|
+
if (parentTask) {
|
|
538
|
+
taskContext += `Parent Task ID: ${parentTask.id}\nParent Task Title: ${parentTask.title}\n`;
|
|
539
|
+
taskDetails += `Parent Task Description: ${parentTask.description || "No description"}\n`;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
// If this is a task with subtasks, get subtask details
|
|
543
|
+
const subtasks = await (0, ai_service_factory_1.getStorage)().getSubtasks(taskId);
|
|
544
|
+
if (subtasks.length > 0) {
|
|
545
|
+
taskDetails += `\nSubtasks:\n`;
|
|
546
|
+
subtasks.forEach((subtask, index) => {
|
|
547
|
+
taskDetails += `${index + 1}. ${subtask.title} (${subtask.id})\n`;
|
|
548
|
+
taskDetails += ` ${subtask.description || "No description"}\n\n`;
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
callbacks?.onProgress?.({
|
|
552
|
+
type: 'progress',
|
|
553
|
+
message: 'Calling AI to create plan...',
|
|
554
|
+
});
|
|
555
|
+
const plan = await aiService.planTask(taskContext, taskDetails, planAIConfig, undefined, undefined, streamingOptions);
|
|
556
|
+
callbacks?.onProgress?.({
|
|
557
|
+
type: 'progress',
|
|
558
|
+
message: 'Saving plan...',
|
|
559
|
+
});
|
|
560
|
+
// Save the plan to storage
|
|
561
|
+
await (0, ai_service_factory_1.getStorage)().savePlan(taskId, plan);
|
|
562
|
+
const duration = Date.now() - startTime;
|
|
563
|
+
callbacks?.onProgress?.({
|
|
564
|
+
type: 'completed',
|
|
565
|
+
message: 'Implementation plan created',
|
|
566
|
+
});
|
|
567
|
+
const aiConfig = (0, ai_service_factory_1.getModelProvider)().getAIConfig();
|
|
568
|
+
return {
|
|
569
|
+
success: true,
|
|
570
|
+
task,
|
|
571
|
+
plan,
|
|
572
|
+
stats: {
|
|
573
|
+
duration,
|
|
574
|
+
},
|
|
575
|
+
metadata: {
|
|
576
|
+
aiProvider: aiConfig.provider,
|
|
577
|
+
aiModel: aiConfig.model,
|
|
578
|
+
},
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
// ============================================================================
|
|
582
|
+
// PLAN OPERATIONS
|
|
583
|
+
// ============================================================================
|
|
584
|
+
async getTaskPlan(taskId) {
|
|
585
|
+
return (0, ai_service_factory_1.getStorage)().getPlan(taskId);
|
|
586
|
+
}
|
|
587
|
+
async listTaskPlans() {
|
|
588
|
+
return (0, ai_service_factory_1.getStorage)().listPlans();
|
|
589
|
+
}
|
|
590
|
+
async deleteTaskPlan(taskId) {
|
|
591
|
+
return (0, ai_service_factory_1.getStorage)().deletePlan(taskId);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
exports.TaskService = TaskService;
|
|
595
|
+
// Export singleton instance
|
|
596
|
+
exports.taskService = new TaskService();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.test.d.ts","sourceRoot":"","sources":["../../src/test/commands.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const child_process_1 = require("child_process");
|
|
37
|
+
const assert = __importStar(require("assert"));
|
|
38
|
+
describe("CLI Commands", () => {
|
|
39
|
+
it("should show help for the main command", (done) => {
|
|
40
|
+
(0, child_process_1.exec)("npx tsx src/cli/bin.ts --help", (error, stdout, stderr) => {
|
|
41
|
+
assert.strictEqual(error, null);
|
|
42
|
+
assert.ok(stdout.includes("Usage: task-o-matic [options] [command]"));
|
|
43
|
+
done();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
it("should show help for the config command", (done) => {
|
|
47
|
+
(0, child_process_1.exec)("npx tsx src/cli/bin.ts config --help", (error, stdout, stderr) => {
|
|
48
|
+
assert.strictEqual(error, null);
|
|
49
|
+
assert.ok(stdout.includes("Usage: task-o-matic config [options] [command]"));
|
|
50
|
+
done();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
it("should show help for the tasks command", (done) => {
|
|
54
|
+
(0, child_process_1.exec)("npx tsx src/cli/bin.ts tasks --help", (error, stdout, stderr) => {
|
|
55
|
+
assert.strictEqual(error, null);
|
|
56
|
+
assert.ok(stdout.includes("Usage: task-o-matic tasks [options] [command]"));
|
|
57
|
+
done();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
it("should show help for the prd command", (done) => {
|
|
61
|
+
(0, child_process_1.exec)("npx tsx src/cli/bin.ts prd --help", (error, stdout, stderr) => {
|
|
62
|
+
assert.strictEqual(error, null);
|
|
63
|
+
assert.ok(stdout.includes("Usage: task-o-matic prd [options] [command]"));
|
|
64
|
+
done();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
it("should show help for the init command", (done) => {
|
|
68
|
+
(0, child_process_1.exec)("npx tsx src/cli/bin.ts init --help", (error, stdout, stderr) => {
|
|
69
|
+
assert.strictEqual(error, null);
|
|
70
|
+
assert.ok(stdout.includes("Usage: task-o-matic init [options] [command]"));
|
|
71
|
+
done();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.test.d.ts","sourceRoot":"","sources":["../../src/test/storage.test.ts"],"names":[],"mappings":""}
|