mcp-adr-analysis-server 2.0.10 → 2.0.12
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 +1 -1
- package/dist/src/index.d.ts +1 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +340 -492
- package/dist/src/index.js.map +1 -1
- package/dist/src/prompts/adr-suggestion-prompts.d.ts.map +1 -1
- package/dist/src/prompts/adr-suggestion-prompts.js +2 -0
- package/dist/src/prompts/adr-suggestion-prompts.js.map +1 -1
- package/dist/src/tools/adr-suggestion-tool.d.ts +16 -0
- package/dist/src/tools/adr-suggestion-tool.d.ts.map +1 -1
- package/dist/src/tools/adr-suggestion-tool.js +55 -2
- package/dist/src/tools/adr-suggestion-tool.js.map +1 -1
- package/dist/src/tools/smart-git-push-tool-v2.d.ts +55 -0
- package/dist/src/tools/smart-git-push-tool-v2.d.ts.map +1 -0
- package/dist/src/tools/smart-git-push-tool-v2.js +527 -0
- package/dist/src/tools/smart-git-push-tool-v2.js.map +1 -0
- package/dist/src/tools/smart-git-push-tool.d.ts +27 -3
- package/dist/src/tools/smart-git-push-tool.d.ts.map +1 -1
- package/dist/src/tools/smart-git-push-tool.js +490 -10
- package/dist/src/tools/smart-git-push-tool.js.map +1 -1
- package/dist/src/tools/smart-score-tool.d.ts +39 -4
- package/dist/src/tools/smart-score-tool.d.ts.map +1 -1
- package/dist/src/tools/smart-score-tool.js +94 -15
- package/dist/src/tools/smart-score-tool.js.map +1 -1
- package/dist/src/tools/todo-management-tool-v2.d.ts +329 -0
- package/dist/src/tools/todo-management-tool-v2.d.ts.map +1 -0
- package/dist/src/tools/todo-management-tool-v2.js +644 -0
- package/dist/src/tools/todo-management-tool-v2.js.map +1 -0
- package/dist/src/tools/tool-chain-orchestrator.d.ts +3 -41
- package/dist/src/tools/tool-chain-orchestrator.d.ts.map +1 -1
- package/dist/src/tools/tool-chain-orchestrator.js +10 -198
- package/dist/src/tools/tool-chain-orchestrator.js.map +1 -1
- package/dist/src/types/conversation-context.d.ts +6 -0
- package/dist/src/types/conversation-context.d.ts.map +1 -1
- package/dist/src/types/conversation-context.js +9 -1
- package/dist/src/types/conversation-context.js.map +1 -1
- package/dist/src/types/knowledge-graph-schemas.d.ts +298 -6
- package/dist/src/types/knowledge-graph-schemas.d.ts.map +1 -1
- package/dist/src/types/knowledge-graph-schemas.js +31 -3
- package/dist/src/types/knowledge-graph-schemas.js.map +1 -1
- package/dist/src/types/todo-json-schemas.d.ts +663 -0
- package/dist/src/types/todo-json-schemas.d.ts.map +1 -0
- package/dist/src/types/todo-json-schemas.js +113 -0
- package/dist/src/types/todo-json-schemas.js.map +1 -0
- package/dist/src/utils/chain-of-thought-template.d.ts.map +1 -1
- package/dist/src/utils/chain-of-thought-template.js.map +1 -1
- package/dist/src/utils/knowledge-graph-manager.d.ts +50 -0
- package/dist/src/utils/knowledge-graph-manager.d.ts.map +1 -1
- package/dist/src/utils/knowledge-graph-manager.js +209 -3
- package/dist/src/utils/knowledge-graph-manager.js.map +1 -1
- package/dist/src/utils/project-health-scoring.js +5 -5
- package/dist/src/utils/project-health-scoring.js.map +1 -1
- package/dist/src/utils/todo-json-manager.d.ts +71 -0
- package/dist/src/utils/todo-json-manager.d.ts.map +1 -0
- package/dist/src/utils/todo-json-manager.js +394 -0
- package/dist/src/utils/todo-json-manager.js.map +1 -0
- package/dist/src/utils/todo-markdown-converter.d.ts +16 -0
- package/dist/src/utils/todo-markdown-converter.d.ts.map +1 -0
- package/dist/src/utils/todo-markdown-converter.js +362 -0
- package/dist/src/utils/todo-markdown-converter.js.map +1 -0
- package/dist/src/utils/todo-migration.d.ts +47 -0
- package/dist/src/utils/todo-migration.d.ts.map +1 -0
- package/dist/src/utils/todo-migration.js +261 -0
- package/dist/src/utils/todo-migration.js.map +1 -0
- package/package.json +1 -1
- package/dist/src/tools/human-override-tool.d.ts +0 -42
- package/dist/src/tools/human-override-tool.d.ts.map +0 -1
- package/dist/src/tools/human-override-tool.js +0 -431
- package/dist/src/tools/human-override-tool.js.map +0 -1
- package/dist/src/tools/todo-management-tool.d.ts +0 -344
- package/dist/src/tools/todo-management-tool.d.ts.map +0 -1
- package/dist/src/tools/todo-management-tool.js +0 -1249
- package/dist/src/tools/todo-management-tool.js.map +0 -1
|
@@ -1,1249 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP Tool for TODO.md management with incremental updates and task lifecycle
|
|
3
|
-
* Provides smart merging, status tracking, and preservation of custom tasks
|
|
4
|
-
*/
|
|
5
|
-
import { z } from 'zod';
|
|
6
|
-
import { McpAdrError } from '../types/index.js';
|
|
7
|
-
import { KnowledgeGraphManager } from '../utils/knowledge-graph-manager.js';
|
|
8
|
-
// Task status enumeration
|
|
9
|
-
const TaskStatus = z.enum(['pending', 'in_progress', 'completed', 'blocked', 'cancelled']);
|
|
10
|
-
// Task priority enumeration
|
|
11
|
-
const TaskPriority = z.enum(['low', 'medium', 'high', 'critical']);
|
|
12
|
-
// Individual task schema
|
|
13
|
-
const TaskSchema = z.object({
|
|
14
|
-
id: z.string().describe('Unique task identifier'),
|
|
15
|
-
title: z.string().describe('Task title/description'),
|
|
16
|
-
status: TaskStatus.describe('Current task status'),
|
|
17
|
-
priority: TaskPriority.optional().describe('Task priority level'),
|
|
18
|
-
adrSource: z.string().optional().describe('Source ADR filename if task comes from ADR'),
|
|
19
|
-
category: z.string().optional().describe('Task category (e.g., implementation, testing, docs)'),
|
|
20
|
-
dependencies: z.array(z.string()).optional().describe('IDs of dependent tasks'),
|
|
21
|
-
notes: z.string().optional().describe('Additional task notes'),
|
|
22
|
-
createdAt: z.string().optional().describe('Task creation timestamp'),
|
|
23
|
-
updatedAt: z.string().optional().describe('Last update timestamp'),
|
|
24
|
-
estimatedHours: z.number().optional().describe('Estimated effort in hours'),
|
|
25
|
-
assignee: z.string().optional().describe('Person assigned to task')
|
|
26
|
-
});
|
|
27
|
-
// TODO file structure schema
|
|
28
|
-
const TodoStructure = z.object({
|
|
29
|
-
title: z.string().describe('TODO file title'),
|
|
30
|
-
lastUpdated: z.string().describe('Last update timestamp'),
|
|
31
|
-
summary: z.object({
|
|
32
|
-
total: z.number(),
|
|
33
|
-
completed: z.number(),
|
|
34
|
-
inProgress: z.number(),
|
|
35
|
-
pending: z.number(),
|
|
36
|
-
blocked: z.number()
|
|
37
|
-
}).describe('Task count summary'),
|
|
38
|
-
sections: z.array(z.object({
|
|
39
|
-
name: z.string().describe('Section name'),
|
|
40
|
-
tasks: z.array(TaskSchema).describe('Tasks in this section')
|
|
41
|
-
})).describe('Organized task sections'),
|
|
42
|
-
customContent: z.array(z.string()).optional().describe('Custom markdown content to preserve')
|
|
43
|
-
});
|
|
44
|
-
// Operation schemas
|
|
45
|
-
const UpdateTaskStatusSchema = z.object({
|
|
46
|
-
operation: z.literal('update_status'),
|
|
47
|
-
todoPath: z.string().default('TODO.md').describe('Path to TODO.md file'),
|
|
48
|
-
updates: z.array(z.object({
|
|
49
|
-
taskId: z.string().describe('Task ID to update'),
|
|
50
|
-
status: TaskStatus.describe('New status'),
|
|
51
|
-
notes: z.string().optional().describe('Update notes'),
|
|
52
|
-
assignee: z.string().optional().describe('New assignee')
|
|
53
|
-
})).describe('Status updates to apply')
|
|
54
|
-
});
|
|
55
|
-
const AddTasksSchema = z.object({
|
|
56
|
-
operation: z.literal('add_tasks'),
|
|
57
|
-
todoPath: z.string().default('TODO.md').describe('Path to TODO.md file'),
|
|
58
|
-
tasks: z.array(TaskSchema.omit({ id: true, createdAt: true, updatedAt: true })).describe('New tasks to add'),
|
|
59
|
-
section: z.string().optional().describe('Section to add tasks to (will create if not exists)')
|
|
60
|
-
});
|
|
61
|
-
const MergeAdrSchema = z.object({
|
|
62
|
-
operation: z.literal('merge_adr_updates'),
|
|
63
|
-
todoPath: z.string().default('TODO.md').describe('Path to TODO.md file'),
|
|
64
|
-
adrDirectory: z.string().default('docs/adrs').describe('ADR directory path'),
|
|
65
|
-
preserveCompleted: z.boolean().default(true).describe('Keep completed tasks'),
|
|
66
|
-
preserveCustom: z.boolean().default(true).describe('Keep manually added tasks')
|
|
67
|
-
});
|
|
68
|
-
const SyncProgressSchema = z.object({
|
|
69
|
-
operation: z.literal('sync_progress'),
|
|
70
|
-
todoPath: z.string().default('TODO.md').describe('Path to TODO.md file'),
|
|
71
|
-
adrDirectory: z.string().default('docs/adrs').describe('ADR directory path'),
|
|
72
|
-
updateAdrs: z.boolean().default(false).describe('Update ADR status based on TODO progress')
|
|
73
|
-
});
|
|
74
|
-
const AnalyzeProgressSchema = z.object({
|
|
75
|
-
operation: z.literal('analyze_progress'),
|
|
76
|
-
todoPath: z.string().default('TODO.md').describe('Path to TODO.md file'),
|
|
77
|
-
timeframe: z.enum(['day', 'week', 'month', 'all']).default('week').describe('Analysis timeframe'),
|
|
78
|
-
includeVelocity: z.boolean().default(true).describe('Include velocity metrics')
|
|
79
|
-
});
|
|
80
|
-
// New Interactive Operations
|
|
81
|
-
const DeleteTaskSchema = z.object({
|
|
82
|
-
operation: z.literal('delete_task'),
|
|
83
|
-
todoPath: z.string().default('TODO.md').describe('Path to TODO.md file'),
|
|
84
|
-
taskIds: z.array(z.string()).describe('Task IDs to delete'),
|
|
85
|
-
force: z.boolean().default(false).describe('Skip interactive confirmation if true'),
|
|
86
|
-
preserveComments: z.boolean().default(true).describe('Preserve task notes in deletion log')
|
|
87
|
-
});
|
|
88
|
-
const EditTaskSchema = z.object({
|
|
89
|
-
operation: z.literal('edit_task'),
|
|
90
|
-
todoPath: z.string().default('TODO.md').describe('Path to TODO.md file'),
|
|
91
|
-
taskId: z.string().describe('Task ID to edit'),
|
|
92
|
-
updates: z.object({
|
|
93
|
-
title: z.string().optional().describe('New task title'),
|
|
94
|
-
priority: TaskPriority.optional().describe('New priority level'),
|
|
95
|
-
category: z.string().optional().describe('New category'),
|
|
96
|
-
estimatedHours: z.number().optional().describe('New time estimate'),
|
|
97
|
-
dependencies: z.array(z.string()).optional().describe('New dependency list'),
|
|
98
|
-
notes: z.string().optional().describe('New notes (replaces existing)')
|
|
99
|
-
}).describe('Fields to update'),
|
|
100
|
-
interactive: z.boolean().default(true).describe('Enable interactive field selection'),
|
|
101
|
-
confirmChanges: z.boolean().default(true).describe('Require confirmation before applying')
|
|
102
|
-
});
|
|
103
|
-
const MoveTaskSchema = z.object({
|
|
104
|
-
operation: z.literal('move_task'),
|
|
105
|
-
todoPath: z.string().default('TODO.md').describe('Path to TODO.md file'),
|
|
106
|
-
taskId: z.string().describe('Task ID to move'),
|
|
107
|
-
targetSection: z.string().optional().describe('Target section name'),
|
|
108
|
-
position: z.number().optional().describe('Position in target section (0-based)'),
|
|
109
|
-
interactive: z.boolean().default(true).describe('Enable interactive section/position selection'),
|
|
110
|
-
createSection: z.boolean().default(false).describe('Create target section if it does not exist')
|
|
111
|
-
});
|
|
112
|
-
const ManageSectionSchema = z.object({
|
|
113
|
-
operation: z.literal('manage_section'),
|
|
114
|
-
todoPath: z.string().default('TODO.md').describe('Path to TODO.md file'),
|
|
115
|
-
action: z.enum(['create', 'delete', 'rename', 'reorder']).describe('Section management action'),
|
|
116
|
-
sectionName: z.string().describe('Section name to operate on'),
|
|
117
|
-
newName: z.string().optional().describe('New name for rename action'),
|
|
118
|
-
position: z.number().optional().describe('New position for reorder action'),
|
|
119
|
-
force: z.boolean().default(false).describe('Skip safety checks and confirmations'),
|
|
120
|
-
preserveTasks: z.boolean().default(true).describe('For delete: move tasks to another section instead of deleting')
|
|
121
|
-
});
|
|
122
|
-
const SearchTasksSchema = z.object({
|
|
123
|
-
operation: z.literal('search_tasks'),
|
|
124
|
-
todoPath: z.string().default('TODO.md').describe('Path to TODO.md file'),
|
|
125
|
-
query: z.string().optional().describe('Text to search in task titles and notes'),
|
|
126
|
-
filters: z.object({
|
|
127
|
-
status: TaskStatus.optional(),
|
|
128
|
-
priority: TaskPriority.optional(),
|
|
129
|
-
assignee: z.string().optional(),
|
|
130
|
-
section: z.string().optional(),
|
|
131
|
-
hasNotes: z.boolean().optional(),
|
|
132
|
-
hasDependencies: z.boolean().optional()
|
|
133
|
-
}).optional().describe('Additional filtering criteria'),
|
|
134
|
-
includeMetadata: z.boolean().default(true).describe('Include full task metadata in results')
|
|
135
|
-
});
|
|
136
|
-
// Main operation schema
|
|
137
|
-
const ManageTodoSchema = z.union([
|
|
138
|
-
UpdateTaskStatusSchema,
|
|
139
|
-
AddTasksSchema,
|
|
140
|
-
MergeAdrSchema,
|
|
141
|
-
SyncProgressSchema,
|
|
142
|
-
AnalyzeProgressSchema,
|
|
143
|
-
DeleteTaskSchema,
|
|
144
|
-
EditTaskSchema,
|
|
145
|
-
MoveTaskSchema,
|
|
146
|
-
ManageSectionSchema,
|
|
147
|
-
SearchTasksSchema
|
|
148
|
-
]);
|
|
149
|
-
/**
|
|
150
|
-
* Count critical tasks (high priority or blocked)
|
|
151
|
-
*/
|
|
152
|
-
function getCriticalTasksCount(structure) {
|
|
153
|
-
let criticalCount = 0;
|
|
154
|
-
for (const section of structure.sections) {
|
|
155
|
-
for (const task of section.tasks) {
|
|
156
|
-
if (task.status !== 'completed' &&
|
|
157
|
-
(task.priority === 'critical' || task.priority === 'high' || task.status === 'blocked')) {
|
|
158
|
-
criticalCount++;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return criticalCount;
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Calculate priority weighted completion score
|
|
166
|
-
*/
|
|
167
|
-
function calculatePriorityWeightedScore(structure) {
|
|
168
|
-
if (structure.summary.total === 0)
|
|
169
|
-
return 100;
|
|
170
|
-
const priorityWeights = {
|
|
171
|
-
critical: 4,
|
|
172
|
-
high: 3,
|
|
173
|
-
medium: 2,
|
|
174
|
-
low: 1
|
|
175
|
-
};
|
|
176
|
-
let totalWeightedTasks = 0;
|
|
177
|
-
let completedWeightedTasks = 0;
|
|
178
|
-
for (const section of structure.sections) {
|
|
179
|
-
for (const task of section.tasks) {
|
|
180
|
-
const weight = priorityWeights[task.priority] || 1;
|
|
181
|
-
totalWeightedTasks += weight;
|
|
182
|
-
if (task.status === 'completed') {
|
|
183
|
-
completedWeightedTasks += weight;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return totalWeightedTasks > 0 ? (completedWeightedTasks / totalWeightedTasks) * 100 : 100;
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Parse TODO.md file into structured format
|
|
191
|
-
* Supports both TODO.md and todo.md for compatibility
|
|
192
|
-
*/
|
|
193
|
-
async function parseTodoFile(todoPath) {
|
|
194
|
-
try {
|
|
195
|
-
const { readFile } = await import('fs/promises');
|
|
196
|
-
const content = await readFile(todoPath, 'utf-8');
|
|
197
|
-
const lines = content.split('\n');
|
|
198
|
-
const structure = {
|
|
199
|
-
title: 'TODO Management',
|
|
200
|
-
lastUpdated: new Date().toISOString(),
|
|
201
|
-
summary: { total: 0, completed: 0, inProgress: 0, pending: 0, blocked: 0 },
|
|
202
|
-
sections: [],
|
|
203
|
-
customContent: []
|
|
204
|
-
};
|
|
205
|
-
let currentSection = null;
|
|
206
|
-
let taskIdCounter = 1;
|
|
207
|
-
for (const line of lines) {
|
|
208
|
-
const trimmed = line.trim();
|
|
209
|
-
// Extract title
|
|
210
|
-
if (trimmed.startsWith('# ') && !structure.title) {
|
|
211
|
-
structure.title = trimmed.substring(2);
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
// Extract sections
|
|
215
|
-
if (trimmed.startsWith('## ')) {
|
|
216
|
-
if (currentSection) {
|
|
217
|
-
structure.sections.push(currentSection);
|
|
218
|
-
}
|
|
219
|
-
currentSection = {
|
|
220
|
-
name: trimmed.substring(3),
|
|
221
|
-
tasks: []
|
|
222
|
-
};
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
// Extract tasks (support various markdown formats)
|
|
226
|
-
const taskMatch = trimmed.match(/^[-*+]\s*(\[([x\s])\])?\s*(.+)$/i);
|
|
227
|
-
if (taskMatch && currentSection) {
|
|
228
|
-
const [, , checkbox, taskText] = taskMatch;
|
|
229
|
-
const isCompleted = checkbox?.toLowerCase() === 'x';
|
|
230
|
-
// Parse task metadata from text
|
|
231
|
-
const safeTaskText = taskText || '';
|
|
232
|
-
const metadataMatch = safeTaskText.match(/^(.+?)\s*(?:\(([^)]+)\))?\s*(?:\[([^\]]+)\])?\s*(?:@(\w+))?\s*$/);
|
|
233
|
-
const title = metadataMatch?.[1] || safeTaskText;
|
|
234
|
-
const category = metadataMatch?.[2];
|
|
235
|
-
const priority = metadataMatch?.[3];
|
|
236
|
-
const assignee = metadataMatch?.[4];
|
|
237
|
-
const task = {
|
|
238
|
-
id: `task-${taskIdCounter++}`,
|
|
239
|
-
title: (title || 'Untitled Task').trim(),
|
|
240
|
-
status: isCompleted ? 'completed' : 'pending',
|
|
241
|
-
...(category && { category }),
|
|
242
|
-
...(priority && TaskPriority.safeParse(priority).success && { priority: priority }),
|
|
243
|
-
...(assignee && { assignee }),
|
|
244
|
-
createdAt: new Date().toISOString(),
|
|
245
|
-
updatedAt: new Date().toISOString()
|
|
246
|
-
};
|
|
247
|
-
currentSection.tasks.push(task);
|
|
248
|
-
// Update summary
|
|
249
|
-
structure.summary.total++;
|
|
250
|
-
if (task.status === 'completed')
|
|
251
|
-
structure.summary.completed++;
|
|
252
|
-
else if (task.status === 'in_progress')
|
|
253
|
-
structure.summary.inProgress++;
|
|
254
|
-
else if (task.status === 'blocked')
|
|
255
|
-
structure.summary.blocked++;
|
|
256
|
-
else
|
|
257
|
-
structure.summary.pending++;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
// Add final section
|
|
261
|
-
if (currentSection) {
|
|
262
|
-
structure.sections.push(currentSection);
|
|
263
|
-
}
|
|
264
|
-
return structure;
|
|
265
|
-
}
|
|
266
|
-
catch (error) {
|
|
267
|
-
// Handle specific file not found vs other errors
|
|
268
|
-
if (error?.code === 'ENOENT') {
|
|
269
|
-
// If file doesn't exist, return empty structure
|
|
270
|
-
return {
|
|
271
|
-
title: 'TODO Management',
|
|
272
|
-
lastUpdated: new Date().toISOString(),
|
|
273
|
-
summary: { total: 0, completed: 0, inProgress: 0, pending: 0, blocked: 0 },
|
|
274
|
-
sections: [{
|
|
275
|
-
name: 'General Tasks',
|
|
276
|
-
tasks: []
|
|
277
|
-
}]
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
// Re-throw other errors
|
|
281
|
-
throw error;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Convert structured TODO back to markdown format with dynamic health scoring
|
|
286
|
-
*/
|
|
287
|
-
async function formatTodoAsMarkdown(structure, projectPath) {
|
|
288
|
-
const lines = [];
|
|
289
|
-
// Dynamic health scoring header if projectPath available
|
|
290
|
-
if (projectPath) {
|
|
291
|
-
try {
|
|
292
|
-
const { ProjectHealthScoring } = await import('../utils/project-health-scoring.js');
|
|
293
|
-
const healthScoring = new ProjectHealthScoring(projectPath);
|
|
294
|
-
// Update task completion score
|
|
295
|
-
const criticalTasks = getCriticalTasksCount(structure);
|
|
296
|
-
const priorityWeightedScore = calculatePriorityWeightedScore(structure);
|
|
297
|
-
await healthScoring.updateTaskCompletionScore({
|
|
298
|
-
completed: structure.summary.completed,
|
|
299
|
-
total: structure.summary.total,
|
|
300
|
-
criticalTasksRemaining: criticalTasks,
|
|
301
|
-
priorityWeightedScore: priorityWeightedScore
|
|
302
|
-
});
|
|
303
|
-
// Generate dynamic health header
|
|
304
|
-
const healthHeader = await healthScoring.generateScoreDisplay();
|
|
305
|
-
lines.push(healthHeader);
|
|
306
|
-
}
|
|
307
|
-
catch (error) {
|
|
308
|
-
// Fallback to traditional header if health scoring fails
|
|
309
|
-
lines.push(`# ${structure.title}`);
|
|
310
|
-
lines.push('');
|
|
311
|
-
lines.push(`**Last Updated**: ${new Date(structure.lastUpdated).toLocaleDateString()}`);
|
|
312
|
-
lines.push(`**Progress**: ${structure.summary.completed}/${structure.summary.total} tasks completed (${Math.round((structure.summary.completed / structure.summary.total) * 100) || 0}%)`);
|
|
313
|
-
lines.push('');
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
else {
|
|
317
|
-
// Traditional header when no project path
|
|
318
|
-
lines.push(`# ${structure.title}`);
|
|
319
|
-
lines.push('');
|
|
320
|
-
lines.push(`**Last Updated**: ${new Date(structure.lastUpdated).toLocaleDateString()}`);
|
|
321
|
-
lines.push(`**Progress**: ${structure.summary.completed}/${structure.summary.total} tasks completed (${Math.round((structure.summary.completed / structure.summary.total) * 100) || 0}%)`);
|
|
322
|
-
lines.push('');
|
|
323
|
-
}
|
|
324
|
-
// Progress summary
|
|
325
|
-
lines.push('## 📊 Progress Summary');
|
|
326
|
-
lines.push('');
|
|
327
|
-
lines.push(`- ✅ **Completed**: ${structure.summary.completed}`);
|
|
328
|
-
lines.push(`- 🔄 **In Progress**: ${structure.summary.inProgress}`);
|
|
329
|
-
lines.push(`- ⏳ **Pending**: ${structure.summary.pending}`);
|
|
330
|
-
lines.push(`- 🚫 **Blocked**: ${structure.summary.blocked}`);
|
|
331
|
-
lines.push('');
|
|
332
|
-
// Sections and tasks
|
|
333
|
-
for (const section of structure.sections) {
|
|
334
|
-
lines.push(`## ${section.name}`);
|
|
335
|
-
lines.push('');
|
|
336
|
-
if (section.tasks.length === 0) {
|
|
337
|
-
lines.push('*No tasks in this section*');
|
|
338
|
-
lines.push('');
|
|
339
|
-
continue;
|
|
340
|
-
}
|
|
341
|
-
for (const task of section.tasks) {
|
|
342
|
-
const checkbox = task.status === 'completed' ? '[x]' : '[ ]';
|
|
343
|
-
const statusIcon = {
|
|
344
|
-
pending: '⏳',
|
|
345
|
-
in_progress: '🔄',
|
|
346
|
-
completed: '✅',
|
|
347
|
-
blocked: '🚫',
|
|
348
|
-
cancelled: '❌'
|
|
349
|
-
}[task.status] || '⏳';
|
|
350
|
-
let taskLine = `- ${checkbox} ${statusIcon} ${task.title}`;
|
|
351
|
-
// Add metadata
|
|
352
|
-
const metadata = [];
|
|
353
|
-
if (task.category)
|
|
354
|
-
metadata.push(task.category);
|
|
355
|
-
if (task.priority)
|
|
356
|
-
metadata.push(`${task.priority} priority`);
|
|
357
|
-
if (task.assignee)
|
|
358
|
-
metadata.push(`@${task.assignee}`);
|
|
359
|
-
if (task.adrSource)
|
|
360
|
-
metadata.push(`ADR: ${task.adrSource}`);
|
|
361
|
-
if (metadata.length > 0) {
|
|
362
|
-
taskLine += ` *(${metadata.join(', ')})*`;
|
|
363
|
-
}
|
|
364
|
-
if (task.notes) {
|
|
365
|
-
taskLine += `\n > ${task.notes}`;
|
|
366
|
-
}
|
|
367
|
-
lines.push(taskLine);
|
|
368
|
-
}
|
|
369
|
-
lines.push('');
|
|
370
|
-
}
|
|
371
|
-
// Custom content
|
|
372
|
-
if (structure.customContent && structure.customContent.length > 0) {
|
|
373
|
-
lines.push('## Additional Notes');
|
|
374
|
-
lines.push('');
|
|
375
|
-
lines.push(...structure.customContent);
|
|
376
|
-
lines.push('');
|
|
377
|
-
}
|
|
378
|
-
// Footer
|
|
379
|
-
lines.push('---');
|
|
380
|
-
lines.push('');
|
|
381
|
-
lines.push(`*Generated by MCP ADR Analysis Server at ${new Date().toISOString()}*`);
|
|
382
|
-
return lines.join('\n');
|
|
383
|
-
}
|
|
384
|
-
/**
|
|
385
|
-
* Update task statuses in TODO structure
|
|
386
|
-
*/
|
|
387
|
-
function updateTaskStatuses(structure, updates) {
|
|
388
|
-
const updatedStructure = { ...structure };
|
|
389
|
-
for (const update of updates) {
|
|
390
|
-
for (const section of updatedStructure.sections) {
|
|
391
|
-
const taskIndex = section.tasks.findIndex(task => task.id === update.taskId);
|
|
392
|
-
if (taskIndex >= 0) {
|
|
393
|
-
const task = { ...section.tasks[taskIndex] };
|
|
394
|
-
const oldStatus = task.status;
|
|
395
|
-
task.status = update.status;
|
|
396
|
-
task.updatedAt = new Date().toISOString();
|
|
397
|
-
if (update.notes !== undefined) {
|
|
398
|
-
task.notes = update.notes;
|
|
399
|
-
}
|
|
400
|
-
if (update.assignee !== undefined) {
|
|
401
|
-
task.assignee = update.assignee;
|
|
402
|
-
}
|
|
403
|
-
section.tasks[taskIndex] = task;
|
|
404
|
-
// Update summary counts
|
|
405
|
-
if (oldStatus !== update.status) {
|
|
406
|
-
updatedStructure.summary[oldStatus]--;
|
|
407
|
-
updatedStructure.summary[update.status]++;
|
|
408
|
-
}
|
|
409
|
-
break;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
updatedStructure.lastUpdated = new Date().toISOString();
|
|
414
|
-
return updatedStructure;
|
|
415
|
-
}
|
|
416
|
-
/**
|
|
417
|
-
* Add new tasks to TODO structure
|
|
418
|
-
*/
|
|
419
|
-
function addTasksToStructure(structure, newTasks, sectionName) {
|
|
420
|
-
const updatedStructure = { ...structure };
|
|
421
|
-
const targetSectionName = sectionName || 'General Tasks';
|
|
422
|
-
// Find or create section
|
|
423
|
-
let targetSection = updatedStructure.sections.find(s => s.name === targetSectionName);
|
|
424
|
-
if (!targetSection) {
|
|
425
|
-
targetSection = { name: targetSectionName, tasks: [] };
|
|
426
|
-
updatedStructure.sections.push(targetSection);
|
|
427
|
-
}
|
|
428
|
-
// Add tasks with IDs
|
|
429
|
-
const tasksWithIds = newTasks.map((task, index) => ({
|
|
430
|
-
...task,
|
|
431
|
-
id: `task-${Date.now()}-${index}`,
|
|
432
|
-
createdAt: new Date().toISOString(),
|
|
433
|
-
updatedAt: new Date().toISOString()
|
|
434
|
-
}));
|
|
435
|
-
targetSection.tasks.push(...tasksWithIds);
|
|
436
|
-
// Update summary
|
|
437
|
-
updatedStructure.summary.total += tasksWithIds.length;
|
|
438
|
-
for (const task of tasksWithIds) {
|
|
439
|
-
updatedStructure.summary[task.status]++;
|
|
440
|
-
}
|
|
441
|
-
updatedStructure.lastUpdated = new Date().toISOString();
|
|
442
|
-
return updatedStructure;
|
|
443
|
-
}
|
|
444
|
-
/**
|
|
445
|
-
* Interactive confirmation helper for operations that need human approval
|
|
446
|
-
*/
|
|
447
|
-
function generateInteractiveConfirmation(operation, details) {
|
|
448
|
-
switch (operation) {
|
|
449
|
-
case 'delete_task':
|
|
450
|
-
return `# ⚠️ Task Deletion Confirmation Required
|
|
451
|
-
|
|
452
|
-
## Tasks to Delete:
|
|
453
|
-
${details.tasks.map((task) => `- **${task.title}** (${task.status}${task.priority ? `, ${task.priority} priority` : ''})`).join('\n')}
|
|
454
|
-
|
|
455
|
-
${details.hasNotes ? '\n## Notes that will be lost:\n' + details.tasks.filter((t) => t.notes).map((task) => `- **${task.title}**: ${task.notes}`).join('\n') : ''}
|
|
456
|
-
|
|
457
|
-
## ⚠️ This action cannot be undone!
|
|
458
|
-
|
|
459
|
-
**To proceed with deletion, call the operation again with \`force: true\`**
|
|
460
|
-
|
|
461
|
-
**Alternatives:**
|
|
462
|
-
- Change status to 'cancelled' instead of deleting
|
|
463
|
-
- Archive tasks using the archive operation
|
|
464
|
-
- Move tasks to a "Completed" or "Archive" section`;
|
|
465
|
-
case 'edit_task':
|
|
466
|
-
return `# 📝 Task Edit Confirmation
|
|
467
|
-
|
|
468
|
-
## Current Task:
|
|
469
|
-
- **Title**: ${details.current.title}
|
|
470
|
-
- **Status**: ${details.current.status}
|
|
471
|
-
- **Priority**: ${details.current.priority || 'none'}
|
|
472
|
-
- **Category**: ${details.current.category || 'none'}
|
|
473
|
-
- **Assignee**: ${details.current.assignee || 'unassigned'}
|
|
474
|
-
- **Notes**: ${details.current.notes || 'none'}
|
|
475
|
-
|
|
476
|
-
## Proposed Changes:
|
|
477
|
-
${Object.entries(details.changes).map(([key, value]) => `- **${key}**: "${details.current[key] || 'none'}" → "${value}"`).join('\n')}
|
|
478
|
-
|
|
479
|
-
**To proceed with these changes, call the operation again with \`confirmChanges: false\`**
|
|
480
|
-
|
|
481
|
-
**Note**: You can also call with \`interactive: false\` to skip this confirmation for future edits.`;
|
|
482
|
-
case 'move_task':
|
|
483
|
-
return `# 🔄 Task Move Confirmation
|
|
484
|
-
|
|
485
|
-
## Task to Move:
|
|
486
|
-
**${details.task.title}** (${details.task.status})
|
|
487
|
-
|
|
488
|
-
## Move Details:
|
|
489
|
-
- **From**: "${details.currentSection}"
|
|
490
|
-
- **To**: "${details.targetSection}"${details.position !== undefined ? `\n- **Position**: ${details.position}` : ''}
|
|
491
|
-
|
|
492
|
-
${details.sectionExists ? '' : '\n⚠️ **Target section does not exist and will be created**'}
|
|
493
|
-
|
|
494
|
-
**Available Sections:**
|
|
495
|
-
${details.availableSections.map((section, index) => `${index + 1}. ${section}`).join('\n')}
|
|
496
|
-
|
|
497
|
-
**To proceed, call the operation again with \`interactive: false\`**
|
|
498
|
-
|
|
499
|
-
**To choose a different section**, update the \`targetSection\` parameter and try again.`;
|
|
500
|
-
case 'manage_section':
|
|
501
|
-
return `# 🗂️ Section Management Confirmation
|
|
502
|
-
|
|
503
|
-
## Action: ${details.action.toUpperCase()}
|
|
504
|
-
## Section: "${details.sectionName}"
|
|
505
|
-
|
|
506
|
-
${details.action === 'delete' ? `
|
|
507
|
-
## ⚠️ Section Deletion Warning:
|
|
508
|
-
- **Tasks in section**: ${details.taskCount}
|
|
509
|
-
- **Preserve tasks**: ${details.preserveTasks}
|
|
510
|
-
|
|
511
|
-
${details.preserveTasks ?
|
|
512
|
-
`Tasks will be moved to: "${details.targetSection || 'General Tasks'}"` :
|
|
513
|
-
'**ALL TASKS WILL BE DELETED!**'}
|
|
514
|
-
|
|
515
|
-
## Available target sections:
|
|
516
|
-
${details.availableSections.filter((s) => s !== details.sectionName).map((section, index) => `${index + 1}. ${section}`).join('\n')}
|
|
517
|
-
` : ''}
|
|
518
|
-
|
|
519
|
-
${details.action === 'rename' ? `
|
|
520
|
-
## Rename Details:
|
|
521
|
-
- **Current name**: "${details.sectionName}"
|
|
522
|
-
- **New name**: "${details.newName}"
|
|
523
|
-
` : ''}
|
|
524
|
-
|
|
525
|
-
**To proceed, call the operation again with \`force: true\`**`;
|
|
526
|
-
default:
|
|
527
|
-
return `# ❓ Unknown Operation: ${operation}\n\nPlease review the operation details and try again.`;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
/**
|
|
531
|
-
* Find task by ID in TODO structure
|
|
532
|
-
*/
|
|
533
|
-
function findTaskById(structure, taskId) {
|
|
534
|
-
for (const section of structure.sections) {
|
|
535
|
-
const task = section.tasks.find(t => t.id === taskId);
|
|
536
|
-
if (task) {
|
|
537
|
-
return { task, sectionName: section.name };
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
return null;
|
|
541
|
-
}
|
|
542
|
-
/**
|
|
543
|
-
* Search tasks by criteria
|
|
544
|
-
*/
|
|
545
|
-
function searchTasks(structure, query, filters) {
|
|
546
|
-
const results = [];
|
|
547
|
-
for (const section of structure.sections) {
|
|
548
|
-
for (const task of section.tasks) {
|
|
549
|
-
let matches = true;
|
|
550
|
-
// Text query search
|
|
551
|
-
if (query) {
|
|
552
|
-
const searchText = `${task.title} ${task.notes || ''}`.toLowerCase();
|
|
553
|
-
matches = matches && searchText.includes(query.toLowerCase());
|
|
554
|
-
}
|
|
555
|
-
// Apply filters
|
|
556
|
-
if (filters) {
|
|
557
|
-
if (filters.status && task.status !== filters.status)
|
|
558
|
-
matches = false;
|
|
559
|
-
if (filters.priority && task.priority !== filters.priority)
|
|
560
|
-
matches = false;
|
|
561
|
-
if (filters.assignee && task.assignee !== filters.assignee)
|
|
562
|
-
matches = false;
|
|
563
|
-
if (filters.section && section.name !== filters.section)
|
|
564
|
-
matches = false;
|
|
565
|
-
if (filters.hasNotes !== undefined && Boolean(task.notes) !== filters.hasNotes)
|
|
566
|
-
matches = false;
|
|
567
|
-
if (filters.hasDependencies !== undefined && Boolean(task.dependencies?.length) !== filters.hasDependencies)
|
|
568
|
-
matches = false;
|
|
569
|
-
}
|
|
570
|
-
if (matches) {
|
|
571
|
-
results.push({ task, sectionName: section.name });
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
return results;
|
|
576
|
-
}
|
|
577
|
-
/**
|
|
578
|
-
* Check if TODO.md needs health scoring initialization
|
|
579
|
-
*/
|
|
580
|
-
async function checkNeedsScoreInitialization(todoPath, projectPath) {
|
|
581
|
-
if (!projectPath) {
|
|
582
|
-
return false; // Can't initialize scoring without project path
|
|
583
|
-
}
|
|
584
|
-
try {
|
|
585
|
-
const { readFile } = await import('fs/promises');
|
|
586
|
-
const content = await readFile(todoPath, 'utf-8');
|
|
587
|
-
// Check if file has health scoring header
|
|
588
|
-
const lines = content.split('\n');
|
|
589
|
-
const hasHealthHeader = lines.some(line => line.includes('Project Health Dashboard') ||
|
|
590
|
-
line.includes('Overall Project Health') ||
|
|
591
|
-
line.includes('Health Metrics'));
|
|
592
|
-
return !hasHealthHeader;
|
|
593
|
-
}
|
|
594
|
-
catch (error) {
|
|
595
|
-
if (error.code === 'ENOENT') {
|
|
596
|
-
// File doesn't exist, will need scoring when created
|
|
597
|
-
return true;
|
|
598
|
-
}
|
|
599
|
-
// Other errors, assume no scoring needed to avoid failures
|
|
600
|
-
return false;
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
/**
|
|
604
|
-
* Main TODO management function
|
|
605
|
-
*/
|
|
606
|
-
export async function manageTodo(args, projectPath) {
|
|
607
|
-
try {
|
|
608
|
-
const validatedArgs = ManageTodoSchema.parse(args);
|
|
609
|
-
// Initialize knowledge graph manager
|
|
610
|
-
const kgManager = new KnowledgeGraphManager();
|
|
611
|
-
let intentId;
|
|
612
|
-
// Track operation intent in knowledge graph
|
|
613
|
-
if (['update_status', 'add_tasks', 'merge_adr_updates', 'sync_progress'].includes(validatedArgs.operation)) {
|
|
614
|
-
intentId = await kgManager.createIntent(`TODO Management: ${validatedArgs.operation}`, [`Execute ${validatedArgs.operation} operation`, 'Update TODO.md file'], 'medium');
|
|
615
|
-
}
|
|
616
|
-
// Resolve absolute TODO path consistently
|
|
617
|
-
const { resolve } = await import('path');
|
|
618
|
-
const { readFile, access, writeFile } = await import('fs/promises');
|
|
619
|
-
// Support both TODO.md and todo.md for backward compatibility
|
|
620
|
-
let todoPath = validatedArgs.todoPath;
|
|
621
|
-
const basePath = projectPath || process.cwd();
|
|
622
|
-
// If path is just the filename, check both cases
|
|
623
|
-
if (todoPath === 'TODO.md' || todoPath === 'todo.md') {
|
|
624
|
-
const upperPath = resolve(basePath, 'TODO.md');
|
|
625
|
-
const lowerPath = resolve(basePath, 'todo.md');
|
|
626
|
-
// Check which exists
|
|
627
|
-
try {
|
|
628
|
-
await access(upperPath);
|
|
629
|
-
todoPath = 'TODO.md';
|
|
630
|
-
}
|
|
631
|
-
catch {
|
|
632
|
-
try {
|
|
633
|
-
await access(lowerPath);
|
|
634
|
-
todoPath = 'todo.md';
|
|
635
|
-
}
|
|
636
|
-
catch {
|
|
637
|
-
// Neither exists, use standard TODO.md
|
|
638
|
-
todoPath = 'TODO.md';
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
const absoluteTodoPath = resolve(basePath, todoPath);
|
|
643
|
-
// Check if file exists and has health scoring header
|
|
644
|
-
const needsScoreInitialization = await checkNeedsScoreInitialization(absoluteTodoPath, projectPath);
|
|
645
|
-
switch (validatedArgs.operation) {
|
|
646
|
-
case 'update_status': {
|
|
647
|
-
const structure = await parseTodoFile(absoluteTodoPath);
|
|
648
|
-
const updatedStructure = updateTaskStatuses(structure, validatedArgs.updates);
|
|
649
|
-
const markdown = await formatTodoAsMarkdown(updatedStructure, projectPath);
|
|
650
|
-
await writeFile(absoluteTodoPath, markdown, 'utf-8');
|
|
651
|
-
// Track operation in knowledge graph
|
|
652
|
-
if (intentId) {
|
|
653
|
-
const tasksModified = validatedArgs.updates.map(u => u.taskId);
|
|
654
|
-
await kgManager.addToolExecution(intentId, 'manage_todo_update_status', validatedArgs, {
|
|
655
|
-
updatedTasks: validatedArgs.updates.length,
|
|
656
|
-
tasksModified,
|
|
657
|
-
structureSummary: updatedStructure.summary
|
|
658
|
-
}, true, [], tasksModified, undefined);
|
|
659
|
-
// Update TODO snapshot
|
|
660
|
-
await kgManager.updateTodoSnapshot(intentId, markdown);
|
|
661
|
-
}
|
|
662
|
-
return {
|
|
663
|
-
content: [{
|
|
664
|
-
type: 'text',
|
|
665
|
-
text: `# Task Status Updated
|
|
666
|
-
${needsScoreInitialization ? '\n🎯 **Health scoring header automatically added to TODO.md**\n' : ''}
|
|
667
|
-
## Updates Applied
|
|
668
|
-
${validatedArgs.updates.map(update => `- Task \`${update.taskId}\`: ${update.status}${update.notes ? ` (${update.notes})` : ''}`).join('\n')}
|
|
669
|
-
|
|
670
|
-
## Current Progress
|
|
671
|
-
- ✅ Completed: ${updatedStructure.summary.completed}
|
|
672
|
-
- 🔄 In Progress: ${updatedStructure.summary.inProgress}
|
|
673
|
-
- ⏳ Pending: ${updatedStructure.summary.pending}
|
|
674
|
-
- 🚫 Blocked: ${updatedStructure.summary.blocked}
|
|
675
|
-
|
|
676
|
-
Updated TODO file: \`${absoluteTodoPath}\``
|
|
677
|
-
}]
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
case 'add_tasks': {
|
|
681
|
-
const structure = await parseTodoFile(absoluteTodoPath);
|
|
682
|
-
const updatedStructure = addTasksToStructure(structure, validatedArgs.tasks, validatedArgs.section);
|
|
683
|
-
const markdown = await formatTodoAsMarkdown(updatedStructure, projectPath);
|
|
684
|
-
await writeFile(absoluteTodoPath, markdown, 'utf-8');
|
|
685
|
-
// Track operation in knowledge graph
|
|
686
|
-
if (intentId) {
|
|
687
|
-
const tasksCreated = validatedArgs.tasks.map(task => task.title);
|
|
688
|
-
await kgManager.addToolExecution(intentId, 'manage_todo_add_tasks', validatedArgs, {
|
|
689
|
-
addedTasks: validatedArgs.tasks.length,
|
|
690
|
-
section: validatedArgs.section || 'default',
|
|
691
|
-
tasksCreated,
|
|
692
|
-
structureSummary: updatedStructure.summary
|
|
693
|
-
}, true, tasksCreated, [], undefined);
|
|
694
|
-
// Update TODO snapshot
|
|
695
|
-
await kgManager.updateTodoSnapshot(intentId, markdown);
|
|
696
|
-
}
|
|
697
|
-
return {
|
|
698
|
-
content: [{
|
|
699
|
-
type: 'text',
|
|
700
|
-
text: `# Tasks Added Successfully
|
|
701
|
-
|
|
702
|
-
## Added ${validatedArgs.tasks.length} New Tasks
|
|
703
|
-
${validatedArgs.tasks.map((task) => `- ${task.title} (${task.status}${task.priority ? `, ${task.priority} priority` : ''})`).join('\n')}
|
|
704
|
-
|
|
705
|
-
## Section: ${validatedArgs.section || 'General Tasks'}
|
|
706
|
-
|
|
707
|
-
## Updated Progress
|
|
708
|
-
- Total Tasks: ${updatedStructure.summary.total}
|
|
709
|
-
- New Pending: ${validatedArgs.tasks.filter(t => t.status === 'pending').length}
|
|
710
|
-
|
|
711
|
-
Updated TODO file: \`${absoluteTodoPath}\``
|
|
712
|
-
}]
|
|
713
|
-
};
|
|
714
|
-
}
|
|
715
|
-
case 'merge_adr_updates': {
|
|
716
|
-
// Get current TODO structure
|
|
717
|
-
const currentStructure = await parseTodoFile(absoluteTodoPath);
|
|
718
|
-
// Create backup of current TODO
|
|
719
|
-
const backupPath = `${absoluteTodoPath}.backup.${Date.now()}`;
|
|
720
|
-
try {
|
|
721
|
-
const currentContent = await readFile(absoluteTodoPath, 'utf-8');
|
|
722
|
-
await writeFile(backupPath, currentContent, 'utf-8');
|
|
723
|
-
}
|
|
724
|
-
catch (error) {
|
|
725
|
-
// If backup fails, continue but warn
|
|
726
|
-
console.error(`Warning: Could not create backup at ${backupPath}:`, error);
|
|
727
|
-
}
|
|
728
|
-
// For now, return analysis with preservation logic
|
|
729
|
-
// This maintains existing structure while indicating merge capability
|
|
730
|
-
let mergedStructure = { ...currentStructure };
|
|
731
|
-
// Apply preservation logic
|
|
732
|
-
if (validatedArgs.preserveCompleted) {
|
|
733
|
-
// Keep completed tasks as-is
|
|
734
|
-
mergedStructure.sections.forEach(section => {
|
|
735
|
-
section.tasks = section.tasks.filter(task => task.status !== 'completed' || task.adrSource);
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
if (validatedArgs.preserveCustom) {
|
|
739
|
-
// Keep tasks without ADR source
|
|
740
|
-
mergedStructure.sections.forEach(section => {
|
|
741
|
-
section.tasks = section.tasks.filter(task => !task.adrSource || task.status === 'completed');
|
|
742
|
-
});
|
|
743
|
-
}
|
|
744
|
-
// Update last modified timestamp
|
|
745
|
-
mergedStructure.lastUpdated = new Date().toISOString();
|
|
746
|
-
// Write merged structure back
|
|
747
|
-
const markdown = await formatTodoAsMarkdown(mergedStructure, projectPath);
|
|
748
|
-
await writeFile(absoluteTodoPath, markdown, 'utf-8');
|
|
749
|
-
return {
|
|
750
|
-
content: [{
|
|
751
|
-
type: 'text',
|
|
752
|
-
text: `# ADR Merge Completed
|
|
753
|
-
${needsScoreInitialization ? '\n🎯 **Health scoring header automatically added to TODO.md**\n' : ''}
|
|
754
|
-
## Merge Results
|
|
755
|
-
- **Source**: ${absoluteTodoPath}
|
|
756
|
-
- **Backup**: ${backupPath}
|
|
757
|
-
- **Preservation Applied**: ${validatedArgs.preserveCompleted ? 'Completed tasks preserved' : 'All tasks merged'}
|
|
758
|
-
|
|
759
|
-
## Current TODO Status
|
|
760
|
-
- Total Tasks: ${mergedStructure.summary.total}
|
|
761
|
-
- Completed: ${mergedStructure.summary.completed}
|
|
762
|
-
- In Progress: ${mergedStructure.summary.inProgress}
|
|
763
|
-
- Pending: ${mergedStructure.summary.pending}
|
|
764
|
-
- Blocked: ${mergedStructure.summary.blocked}
|
|
765
|
-
|
|
766
|
-
## Preservation Settings
|
|
767
|
-
- Preserve Completed: ${validatedArgs.preserveCompleted}
|
|
768
|
-
- Preserve Custom: ${validatedArgs.preserveCustom}
|
|
769
|
-
|
|
770
|
-
## Next Steps
|
|
771
|
-
1. Review merged TODO.md content
|
|
772
|
-
2. Use \`manage_todo\` to add new ADR-derived tasks if needed
|
|
773
|
-
3. Update task statuses using \`update_status\` operation
|
|
774
|
-
4. Use \`compare_adr_progress\` to validate implementation
|
|
775
|
-
|
|
776
|
-
*Note: Advanced ADR integration requires additional development*
|
|
777
|
-
*Current implementation focuses on preserving existing content*`
|
|
778
|
-
}]
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
|
-
case 'sync_progress': {
|
|
782
|
-
const structure = await parseTodoFile(absoluteTodoPath);
|
|
783
|
-
return {
|
|
784
|
-
content: [{
|
|
785
|
-
type: 'text',
|
|
786
|
-
text: `# TODO Progress Sync
|
|
787
|
-
|
|
788
|
-
## Current Status
|
|
789
|
-
- Total Tasks: ${structure.summary.total}
|
|
790
|
-
- Completed: ${structure.summary.completed}
|
|
791
|
-
- In Progress: ${structure.summary.inProgress}
|
|
792
|
-
- ADR Directory: ${validatedArgs.adrDirectory}
|
|
793
|
-
- Update ADRs: ${validatedArgs.updateAdrs}
|
|
794
|
-
|
|
795
|
-
## Sync Results
|
|
796
|
-
*This operation would sync TODO progress with ADR status*
|
|
797
|
-
*Implementation requires integration with ADR status tracking*
|
|
798
|
-
|
|
799
|
-
## Next Steps
|
|
800
|
-
1. Review TODO completion status
|
|
801
|
-
2. Update corresponding ADR statuses if enabled
|
|
802
|
-
3. Generate sync report`
|
|
803
|
-
}]
|
|
804
|
-
};
|
|
805
|
-
}
|
|
806
|
-
case 'analyze_progress': {
|
|
807
|
-
const structure = await parseTodoFile(absoluteTodoPath);
|
|
808
|
-
// Calculate progress metrics
|
|
809
|
-
const totalTasks = structure.summary.total;
|
|
810
|
-
const completionRate = totalTasks > 0 ? (structure.summary.completed / totalTasks) * 100 : 0;
|
|
811
|
-
// Calculate velocity (placeholder - would need historical data)
|
|
812
|
-
const velocity = validatedArgs.includeVelocity ? {
|
|
813
|
-
tasksPerDay: 2.5,
|
|
814
|
-
estimatedCompletion: '2 weeks',
|
|
815
|
-
trend: 'stable'
|
|
816
|
-
} : null;
|
|
817
|
-
return {
|
|
818
|
-
content: [{
|
|
819
|
-
type: 'text',
|
|
820
|
-
text: `# TODO Progress Analysis
|
|
821
|
-
|
|
822
|
-
## Overall Progress
|
|
823
|
-
- **Completion Rate**: ${completionRate.toFixed(1)}% (${structure.summary.completed}/${totalTasks})
|
|
824
|
-
- **Active Tasks**: ${structure.summary.inProgress}
|
|
825
|
-
- **Blocked Tasks**: ${structure.summary.blocked}
|
|
826
|
-
|
|
827
|
-
## Status Distribution
|
|
828
|
-
- ✅ Completed: ${structure.summary.completed} (${((structure.summary.completed / totalTasks) * 100).toFixed(1)}%)
|
|
829
|
-
- 🔄 In Progress: ${structure.summary.inProgress} (${((structure.summary.inProgress / totalTasks) * 100).toFixed(1)}%)
|
|
830
|
-
- ⏳ Pending: ${structure.summary.pending} (${((structure.summary.pending / totalTasks) * 100).toFixed(1)}%)
|
|
831
|
-
- 🚫 Blocked: ${structure.summary.blocked} (${((structure.summary.blocked / totalTasks) * 100).toFixed(1)}%)
|
|
832
|
-
|
|
833
|
-
${velocity ? `## Velocity Metrics
|
|
834
|
-
- **Tasks per Day**: ${velocity.tasksPerDay}
|
|
835
|
-
- **Estimated Completion**: ${velocity.estimatedCompletion}
|
|
836
|
-
- **Trend**: ${velocity.trend}` : ''}
|
|
837
|
-
|
|
838
|
-
## Recommendations
|
|
839
|
-
${structure.summary.blocked > 0 ? '- 🚨 Address blocked tasks to improve flow' : ''}
|
|
840
|
-
${structure.summary.inProgress > 5 ? '- ⚠️ Consider reducing work in progress' : ''}
|
|
841
|
-
${completionRate < 50 ? '- 📈 Focus on completing existing tasks' : ''}
|
|
842
|
-
${completionRate > 80 ? '- 🎉 Great progress! Consider adding new tasks' : ''}
|
|
843
|
-
|
|
844
|
-
*Analysis timeframe: ${validatedArgs.timeframe}*`
|
|
845
|
-
}]
|
|
846
|
-
};
|
|
847
|
-
}
|
|
848
|
-
case 'delete_task': {
|
|
849
|
-
const structure = await parseTodoFile(absoluteTodoPath);
|
|
850
|
-
// Find tasks to delete
|
|
851
|
-
const tasksToDelete = validatedArgs.taskIds.map(id => findTaskById(structure, id)).filter(Boolean);
|
|
852
|
-
if (tasksToDelete.length === 0) {
|
|
853
|
-
return {
|
|
854
|
-
content: [{
|
|
855
|
-
type: 'text',
|
|
856
|
-
text: `# ❌ No Tasks Found\n\nNone of the specified task IDs were found:\n${validatedArgs.taskIds.map(id => `- ${id}`).join('\n')}\n\nUse \`search_tasks\` to find available task IDs.`
|
|
857
|
-
}]
|
|
858
|
-
};
|
|
859
|
-
}
|
|
860
|
-
if (tasksToDelete.length !== validatedArgs.taskIds.length) {
|
|
861
|
-
const foundIds = tasksToDelete.map(t => t.task.id);
|
|
862
|
-
const missingIds = validatedArgs.taskIds.filter(id => !foundIds.includes(id));
|
|
863
|
-
return {
|
|
864
|
-
content: [{
|
|
865
|
-
type: 'text',
|
|
866
|
-
text: `# ⚠️ Some Tasks Not Found\n\n**Found tasks:**\n${foundIds.map(id => `- ${id}`).join('\n')}\n\n**Missing tasks:**\n${missingIds.map(id => `- ${id}`).join('\n')}\n\nPlease verify task IDs and try again.`
|
|
867
|
-
}]
|
|
868
|
-
};
|
|
869
|
-
}
|
|
870
|
-
// Interactive confirmation unless force is true
|
|
871
|
-
if (!validatedArgs.force) {
|
|
872
|
-
const hasNotes = tasksToDelete.some(t => t.task.notes);
|
|
873
|
-
return {
|
|
874
|
-
content: [{
|
|
875
|
-
type: 'text',
|
|
876
|
-
text: generateInteractiveConfirmation('delete_task', {
|
|
877
|
-
tasks: tasksToDelete.map(t => t.task),
|
|
878
|
-
hasNotes
|
|
879
|
-
})
|
|
880
|
-
}]
|
|
881
|
-
};
|
|
882
|
-
}
|
|
883
|
-
// Perform deletion
|
|
884
|
-
const updatedStructure = { ...structure };
|
|
885
|
-
let deletedCount = 0;
|
|
886
|
-
for (const section of updatedStructure.sections) {
|
|
887
|
-
const initialLength = section.tasks.length;
|
|
888
|
-
section.tasks = section.tasks.filter(task => !validatedArgs.taskIds.includes(task.id));
|
|
889
|
-
deletedCount += initialLength - section.tasks.length;
|
|
890
|
-
}
|
|
891
|
-
// Update summary counts
|
|
892
|
-
for (const { task } of tasksToDelete) {
|
|
893
|
-
updatedStructure.summary.total--;
|
|
894
|
-
updatedStructure.summary[task.status]--;
|
|
895
|
-
}
|
|
896
|
-
updatedStructure.lastUpdated = new Date().toISOString();
|
|
897
|
-
// Write updated structure
|
|
898
|
-
const markdown = await formatTodoAsMarkdown(updatedStructure, projectPath);
|
|
899
|
-
await writeFile(absoluteTodoPath, markdown, 'utf-8');
|
|
900
|
-
return {
|
|
901
|
-
content: [{
|
|
902
|
-
type: 'text',
|
|
903
|
-
text: `# ✅ Tasks Deleted Successfully
|
|
904
|
-
|
|
905
|
-
## Deleted ${deletedCount} Tasks:
|
|
906
|
-
${tasksToDelete.map(({ task, sectionName }) => `- **${task.title}** from "${sectionName}" section`).join('\n')}
|
|
907
|
-
|
|
908
|
-
## Updated Progress:
|
|
909
|
-
- Total Tasks: ${updatedStructure.summary.total}
|
|
910
|
-
- Completed: ${updatedStructure.summary.completed}
|
|
911
|
-
- In Progress: ${updatedStructure.summary.inProgress}
|
|
912
|
-
- Pending: ${updatedStructure.summary.pending}
|
|
913
|
-
|
|
914
|
-
Updated TODO file: \`${absoluteTodoPath}\``
|
|
915
|
-
}]
|
|
916
|
-
};
|
|
917
|
-
}
|
|
918
|
-
case 'edit_task': {
|
|
919
|
-
const structure = await parseTodoFile(absoluteTodoPath);
|
|
920
|
-
const taskResult = findTaskById(structure, validatedArgs.taskId);
|
|
921
|
-
if (!taskResult) {
|
|
922
|
-
return {
|
|
923
|
-
content: [{
|
|
924
|
-
type: 'text',
|
|
925
|
-
text: `# ❌ Task Not Found\n\nTask ID "${validatedArgs.taskId}" was not found.\n\nUse \`search_tasks\` to find available task IDs.`
|
|
926
|
-
}]
|
|
927
|
-
};
|
|
928
|
-
}
|
|
929
|
-
const { task: currentTask } = taskResult;
|
|
930
|
-
// Interactive confirmation if enabled
|
|
931
|
-
if (validatedArgs.interactive && validatedArgs.confirmChanges) {
|
|
932
|
-
return {
|
|
933
|
-
content: [{
|
|
934
|
-
type: 'text',
|
|
935
|
-
text: generateInteractiveConfirmation('edit_task', {
|
|
936
|
-
current: currentTask,
|
|
937
|
-
changes: validatedArgs.updates
|
|
938
|
-
})
|
|
939
|
-
}]
|
|
940
|
-
};
|
|
941
|
-
}
|
|
942
|
-
// Apply updates
|
|
943
|
-
const updatedStructure = { ...structure };
|
|
944
|
-
for (const section of updatedStructure.sections) {
|
|
945
|
-
const taskIndex = section.tasks.findIndex(t => t.id === validatedArgs.taskId);
|
|
946
|
-
if (taskIndex >= 0) {
|
|
947
|
-
const updatedTask = { ...section.tasks[taskIndex] };
|
|
948
|
-
// Apply each update
|
|
949
|
-
Object.entries(validatedArgs.updates).forEach(([key, value]) => {
|
|
950
|
-
if (value !== undefined) {
|
|
951
|
-
updatedTask[key] = value;
|
|
952
|
-
}
|
|
953
|
-
});
|
|
954
|
-
updatedTask.updatedAt = new Date().toISOString();
|
|
955
|
-
section.tasks[taskIndex] = updatedTask;
|
|
956
|
-
break;
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
updatedStructure.lastUpdated = new Date().toISOString();
|
|
960
|
-
// Write updated structure
|
|
961
|
-
const markdown = await formatTodoAsMarkdown(updatedStructure, projectPath);
|
|
962
|
-
await writeFile(absoluteTodoPath, markdown, 'utf-8');
|
|
963
|
-
return {
|
|
964
|
-
content: [{
|
|
965
|
-
type: 'text',
|
|
966
|
-
text: `# ✅ Task Updated Successfully
|
|
967
|
-
|
|
968
|
-
## Updated Task: "${currentTask.title}"
|
|
969
|
-
|
|
970
|
-
## Changes Applied:
|
|
971
|
-
${Object.entries(validatedArgs.updates).filter(([, value]) => value !== undefined).map(([key, value]) => `- **${key}**: "${currentTask[key] || 'none'}" → "${value}"`).join('\n')}
|
|
972
|
-
|
|
973
|
-
Updated TODO file: \`${absoluteTodoPath}\``
|
|
974
|
-
}]
|
|
975
|
-
};
|
|
976
|
-
}
|
|
977
|
-
case 'move_task': {
|
|
978
|
-
const structure = await parseTodoFile(absoluteTodoPath);
|
|
979
|
-
const taskResult = findTaskById(structure, validatedArgs.taskId);
|
|
980
|
-
if (!taskResult) {
|
|
981
|
-
return {
|
|
982
|
-
content: [{
|
|
983
|
-
type: 'text',
|
|
984
|
-
text: `# ❌ Task Not Found\n\nTask ID "${validatedArgs.taskId}" was not found.\n\nUse \`search_tasks\` to find available task IDs.`
|
|
985
|
-
}]
|
|
986
|
-
};
|
|
987
|
-
}
|
|
988
|
-
const { task, sectionName: currentSection } = taskResult;
|
|
989
|
-
const availableSections = structure.sections.map(s => s.name);
|
|
990
|
-
// Interactive section selection if needed
|
|
991
|
-
if (validatedArgs.interactive && !validatedArgs.targetSection) {
|
|
992
|
-
return {
|
|
993
|
-
content: [{
|
|
994
|
-
type: 'text',
|
|
995
|
-
text: `# 🔄 Select Target Section
|
|
996
|
-
|
|
997
|
-
## Task to Move:
|
|
998
|
-
**${task.title}** (currently in "${currentSection}")
|
|
999
|
-
|
|
1000
|
-
## Available Sections:
|
|
1001
|
-
${availableSections.map((section, index) => `${index + 1}. ${section}`).join('\n')}
|
|
1002
|
-
|
|
1003
|
-
**To proceed**, call the operation again with \`targetSection\` parameter set to your chosen section name.
|
|
1004
|
-
|
|
1005
|
-
**To skip interactive mode**, set \`interactive: false\`.`
|
|
1006
|
-
}]
|
|
1007
|
-
};
|
|
1008
|
-
}
|
|
1009
|
-
const targetSection = validatedArgs.targetSection || 'General Tasks';
|
|
1010
|
-
const sectionExists = availableSections.includes(targetSection);
|
|
1011
|
-
// Interactive confirmation if enabled
|
|
1012
|
-
if (validatedArgs.interactive) {
|
|
1013
|
-
return {
|
|
1014
|
-
content: [{
|
|
1015
|
-
type: 'text',
|
|
1016
|
-
text: generateInteractiveConfirmation('move_task', {
|
|
1017
|
-
task,
|
|
1018
|
-
currentSection,
|
|
1019
|
-
targetSection,
|
|
1020
|
-
position: validatedArgs.position,
|
|
1021
|
-
sectionExists,
|
|
1022
|
-
availableSections
|
|
1023
|
-
})
|
|
1024
|
-
}]
|
|
1025
|
-
};
|
|
1026
|
-
}
|
|
1027
|
-
// Perform the move
|
|
1028
|
-
const updatedStructure = { ...structure };
|
|
1029
|
-
// Remove from current section
|
|
1030
|
-
for (const section of updatedStructure.sections) {
|
|
1031
|
-
if (section.name === currentSection) {
|
|
1032
|
-
section.tasks = section.tasks.filter(t => t.id !== validatedArgs.taskId);
|
|
1033
|
-
break;
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
// Add to target section
|
|
1037
|
-
let targetSectionObj = updatedStructure.sections.find(s => s.name === targetSection);
|
|
1038
|
-
if (!targetSectionObj) {
|
|
1039
|
-
if (validatedArgs.createSection) {
|
|
1040
|
-
targetSectionObj = { name: targetSection, tasks: [] };
|
|
1041
|
-
updatedStructure.sections.push(targetSectionObj);
|
|
1042
|
-
}
|
|
1043
|
-
else {
|
|
1044
|
-
return {
|
|
1045
|
-
content: [{
|
|
1046
|
-
type: 'text',
|
|
1047
|
-
text: `# ❌ Target Section Not Found\n\nSection "${targetSection}" does not exist.\n\n**Options:**\n- Choose an existing section\n- Set \`createSection: true\` to create the section\n\nAvailable sections:\n${availableSections.map(s => `- ${s}`).join('\n')}`
|
|
1048
|
-
}]
|
|
1049
|
-
};
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
// Insert at specified position or at the end
|
|
1053
|
-
if (validatedArgs.position !== undefined && validatedArgs.position >= 0) {
|
|
1054
|
-
targetSectionObj.tasks.splice(validatedArgs.position, 0, task);
|
|
1055
|
-
}
|
|
1056
|
-
else {
|
|
1057
|
-
targetSectionObj.tasks.push(task);
|
|
1058
|
-
}
|
|
1059
|
-
updatedStructure.lastUpdated = new Date().toISOString();
|
|
1060
|
-
// Write updated structure
|
|
1061
|
-
const markdown = await formatTodoAsMarkdown(updatedStructure, projectPath);
|
|
1062
|
-
await writeFile(absoluteTodoPath, markdown, 'utf-8');
|
|
1063
|
-
return {
|
|
1064
|
-
content: [{
|
|
1065
|
-
type: 'text',
|
|
1066
|
-
text: `# ✅ Task Moved Successfully
|
|
1067
|
-
|
|
1068
|
-
## Task: "${task.title}"
|
|
1069
|
-
- **From**: "${currentSection}"
|
|
1070
|
-
- **To**: "${targetSection}"${validatedArgs.position !== undefined ? `\n- **Position**: ${validatedArgs.position}` : ''}
|
|
1071
|
-
|
|
1072
|
-
${!sectionExists ? '\n🆕 **Target section was created**' : ''}
|
|
1073
|
-
|
|
1074
|
-
Updated TODO file: \`${absoluteTodoPath}\``
|
|
1075
|
-
}]
|
|
1076
|
-
};
|
|
1077
|
-
}
|
|
1078
|
-
case 'manage_section': {
|
|
1079
|
-
const structure = await parseTodoFile(absoluteTodoPath);
|
|
1080
|
-
const sectionExists = structure.sections.some(s => s.name === validatedArgs.sectionName);
|
|
1081
|
-
const availableSections = structure.sections.map(s => s.name);
|
|
1082
|
-
if (validatedArgs.action === 'create') {
|
|
1083
|
-
if (sectionExists) {
|
|
1084
|
-
return {
|
|
1085
|
-
content: [{
|
|
1086
|
-
type: 'text',
|
|
1087
|
-
text: `# ❌ Section Already Exists\n\nSection "${validatedArgs.sectionName}" already exists.\n\nExisting sections:\n${availableSections.map(s => `- ${s}`).join('\n')}`
|
|
1088
|
-
}]
|
|
1089
|
-
};
|
|
1090
|
-
}
|
|
1091
|
-
// Create new section
|
|
1092
|
-
const updatedStructure = { ...structure };
|
|
1093
|
-
updatedStructure.sections.push({ name: validatedArgs.sectionName, tasks: [] });
|
|
1094
|
-
updatedStructure.lastUpdated = new Date().toISOString();
|
|
1095
|
-
const markdown = await formatTodoAsMarkdown(updatedStructure, projectPath);
|
|
1096
|
-
await writeFile(absoluteTodoPath, markdown, 'utf-8');
|
|
1097
|
-
return {
|
|
1098
|
-
content: [{
|
|
1099
|
-
type: 'text',
|
|
1100
|
-
text: `# ✅ Section Created Successfully\n\n**New section**: "${validatedArgs.sectionName}"\n\nUpdated TODO file: \`${absoluteTodoPath}\``
|
|
1101
|
-
}]
|
|
1102
|
-
};
|
|
1103
|
-
}
|
|
1104
|
-
if (!sectionExists) {
|
|
1105
|
-
return {
|
|
1106
|
-
content: [{
|
|
1107
|
-
type: 'text',
|
|
1108
|
-
text: `# ❌ Section Not Found\n\nSection "${validatedArgs.sectionName}" does not exist.\n\nAvailable sections:\n${availableSections.map(s => `- ${s}`).join('\n')}`
|
|
1109
|
-
}]
|
|
1110
|
-
};
|
|
1111
|
-
}
|
|
1112
|
-
const targetSection = structure.sections.find(s => s.name === validatedArgs.sectionName);
|
|
1113
|
-
// Interactive confirmation unless force is true
|
|
1114
|
-
if (!validatedArgs.force) {
|
|
1115
|
-
const details = {
|
|
1116
|
-
action: validatedArgs.action,
|
|
1117
|
-
sectionName: validatedArgs.sectionName,
|
|
1118
|
-
availableSections
|
|
1119
|
-
};
|
|
1120
|
-
if (validatedArgs.action === 'delete') {
|
|
1121
|
-
details.taskCount = targetSection.tasks.length;
|
|
1122
|
-
details.preserveTasks = validatedArgs.preserveTasks;
|
|
1123
|
-
details.targetSection = availableSections.find(s => s !== validatedArgs.sectionName) || 'General Tasks';
|
|
1124
|
-
}
|
|
1125
|
-
if (validatedArgs.action === 'rename') {
|
|
1126
|
-
details.newName = validatedArgs.newName;
|
|
1127
|
-
}
|
|
1128
|
-
return {
|
|
1129
|
-
content: [{
|
|
1130
|
-
type: 'text',
|
|
1131
|
-
text: generateInteractiveConfirmation('manage_section', details)
|
|
1132
|
-
}]
|
|
1133
|
-
};
|
|
1134
|
-
}
|
|
1135
|
-
// Perform the action
|
|
1136
|
-
const updatedStructure = { ...structure };
|
|
1137
|
-
if (validatedArgs.action === 'delete') {
|
|
1138
|
-
const sectionIndex = updatedStructure.sections.findIndex(s => s.name === validatedArgs.sectionName);
|
|
1139
|
-
const tasksToMove = targetSection.tasks;
|
|
1140
|
-
if (validatedArgs.preserveTasks && tasksToMove.length > 0) {
|
|
1141
|
-
// Move tasks to another section
|
|
1142
|
-
const fallbackSectionName = availableSections.find(s => s !== validatedArgs.sectionName) || 'General Tasks';
|
|
1143
|
-
let fallbackSection = updatedStructure.sections.find(s => s.name === fallbackSectionName);
|
|
1144
|
-
if (!fallbackSection && fallbackSectionName === 'General Tasks') {
|
|
1145
|
-
fallbackSection = { name: 'General Tasks', tasks: [] };
|
|
1146
|
-
updatedStructure.sections.unshift(fallbackSection);
|
|
1147
|
-
}
|
|
1148
|
-
if (fallbackSection) {
|
|
1149
|
-
fallbackSection.tasks.push(...tasksToMove);
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
else {
|
|
1153
|
-
// Update summary counts for deleted tasks
|
|
1154
|
-
for (const task of tasksToMove) {
|
|
1155
|
-
updatedStructure.summary.total--;
|
|
1156
|
-
updatedStructure.summary[task.status]--;
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
updatedStructure.sections.splice(sectionIndex, 1);
|
|
1160
|
-
}
|
|
1161
|
-
if (validatedArgs.action === 'rename') {
|
|
1162
|
-
const section = updatedStructure.sections.find(s => s.name === validatedArgs.sectionName);
|
|
1163
|
-
section.name = validatedArgs.newName;
|
|
1164
|
-
}
|
|
1165
|
-
updatedStructure.lastUpdated = new Date().toISOString();
|
|
1166
|
-
const markdown = await formatTodoAsMarkdown(updatedStructure, projectPath);
|
|
1167
|
-
await writeFile(absoluteTodoPath, markdown, 'utf-8');
|
|
1168
|
-
return {
|
|
1169
|
-
content: [{
|
|
1170
|
-
type: 'text',
|
|
1171
|
-
text: `# ✅ Section ${validatedArgs.action.charAt(0).toUpperCase() + validatedArgs.action.slice(1)} Successful
|
|
1172
|
-
|
|
1173
|
-
${validatedArgs.action === 'delete' ? `
|
|
1174
|
-
## Deleted Section: "${validatedArgs.sectionName}"
|
|
1175
|
-
- **Tasks**: ${targetSection.tasks.length}
|
|
1176
|
-
- **Preserved**: ${validatedArgs.preserveTasks}
|
|
1177
|
-
${validatedArgs.preserveTasks && targetSection.tasks.length > 0 ? `- **Moved to**: "${availableSections.find(s => s !== validatedArgs.sectionName) || 'General Tasks'}"` : ''}
|
|
1178
|
-
` : ''}
|
|
1179
|
-
|
|
1180
|
-
${validatedArgs.action === 'rename' ? `
|
|
1181
|
-
## Renamed Section:
|
|
1182
|
-
- **From**: "${validatedArgs.sectionName}"
|
|
1183
|
-
- **To**: "${validatedArgs.newName}"
|
|
1184
|
-
` : ''}
|
|
1185
|
-
|
|
1186
|
-
Updated TODO file: \`${absoluteTodoPath}\``
|
|
1187
|
-
}]
|
|
1188
|
-
};
|
|
1189
|
-
}
|
|
1190
|
-
case 'search_tasks': {
|
|
1191
|
-
const structure = await parseTodoFile(absoluteTodoPath);
|
|
1192
|
-
const results = searchTasks(structure, validatedArgs.query, validatedArgs.filters);
|
|
1193
|
-
if (results.length === 0) {
|
|
1194
|
-
return {
|
|
1195
|
-
content: [{
|
|
1196
|
-
type: 'text',
|
|
1197
|
-
text: `# 🔍 No Tasks Found
|
|
1198
|
-
|
|
1199
|
-
**Search criteria:**
|
|
1200
|
-
${validatedArgs.query ? `- **Query**: "${validatedArgs.query}"` : ''}
|
|
1201
|
-
${validatedArgs.filters ? Object.entries(validatedArgs.filters).filter(([, value]) => value !== undefined).map(([key, value]) => `- **${key}**: ${value}`).join('\n') : ''}
|
|
1202
|
-
|
|
1203
|
-
**Total tasks in TODO.md**: ${structure.summary.total}
|
|
1204
|
-
|
|
1205
|
-
Try adjusting your search criteria or use \`analyze_progress\` to see all tasks.`
|
|
1206
|
-
}]
|
|
1207
|
-
};
|
|
1208
|
-
}
|
|
1209
|
-
return {
|
|
1210
|
-
content: [{
|
|
1211
|
-
type: 'text',
|
|
1212
|
-
text: `# 🔍 Search Results
|
|
1213
|
-
|
|
1214
|
-
Found **${results.length}** tasks:
|
|
1215
|
-
|
|
1216
|
-
${results.map(({ task, sectionName }) => {
|
|
1217
|
-
let taskLine = `## ${task.title}`;
|
|
1218
|
-
if (validatedArgs.includeMetadata) {
|
|
1219
|
-
taskLine += `\n- **ID**: \`${task.id}\`\n- **Section**: ${sectionName}\n- **Status**: ${task.status}`;
|
|
1220
|
-
if (task.priority)
|
|
1221
|
-
taskLine += `\n- **Priority**: ${task.priority}`;
|
|
1222
|
-
if (task.assignee)
|
|
1223
|
-
taskLine += `\n- **Assignee**: ${task.assignee}`;
|
|
1224
|
-
if (task.category)
|
|
1225
|
-
taskLine += `\n- **Category**: ${task.category}`;
|
|
1226
|
-
if (task.notes)
|
|
1227
|
-
taskLine += `\n- **Notes**: ${task.notes}`;
|
|
1228
|
-
if (task.dependencies?.length)
|
|
1229
|
-
taskLine += `\n- **Dependencies**: ${task.dependencies.join(', ')}`;
|
|
1230
|
-
}
|
|
1231
|
-
return taskLine;
|
|
1232
|
-
}).join('\n\n')}
|
|
1233
|
-
|
|
1234
|
-
**Search completed on**: \`${absoluteTodoPath}\``
|
|
1235
|
-
}]
|
|
1236
|
-
};
|
|
1237
|
-
}
|
|
1238
|
-
default:
|
|
1239
|
-
throw new McpAdrError(`Unknown operation: ${validatedArgs.operation}`, 'INVALID_INPUT');
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
catch (error) {
|
|
1243
|
-
if (error instanceof z.ZodError) {
|
|
1244
|
-
throw new McpAdrError(`Invalid input: ${error.errors.map(e => e.message).join(', ')}`, 'INVALID_INPUT');
|
|
1245
|
-
}
|
|
1246
|
-
throw new McpAdrError(`TODO management failed: ${error instanceof Error ? error.message : String(error)}`, 'TODO_MANAGEMENT_ERROR');
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
//# sourceMappingURL=todo-management-tool.js.map
|