mcp-adr-analysis-server 2.0.8 → 2.0.10
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/dist/src/index.d.ts +8 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +434 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/resources/index.d.ts.map +1 -1
- package/dist/src/resources/index.js +71 -1
- package/dist/src/resources/index.js.map +1 -1
- package/dist/src/tools/human-override-tool.d.ts +42 -0
- package/dist/src/tools/human-override-tool.d.ts.map +1 -0
- package/dist/src/tools/human-override-tool.js +431 -0
- package/dist/src/tools/human-override-tool.js.map +1 -0
- package/dist/src/tools/smart-git-push-tool.d.ts +5 -1
- package/dist/src/tools/smart-git-push-tool.d.ts.map +1 -1
- package/dist/src/tools/smart-git-push-tool.js +75 -30
- package/dist/src/tools/smart-git-push-tool.js.map +1 -1
- package/dist/src/tools/smart-score-tool.d.ts +138 -0
- package/dist/src/tools/smart-score-tool.d.ts.map +1 -0
- package/dist/src/tools/smart-score-tool.js +602 -0
- package/dist/src/tools/smart-score-tool.js.map +1 -0
- package/dist/src/tools/todo-management-tool.d.ts +184 -6
- package/dist/src/tools/todo-management-tool.d.ts.map +1 -1
- package/dist/src/tools/todo-management-tool.js +819 -42
- package/dist/src/tools/todo-management-tool.js.map +1 -1
- package/dist/src/tools/tool-chain-orchestrator.d.ts +164 -0
- package/dist/src/tools/tool-chain-orchestrator.d.ts.map +1 -0
- package/dist/src/tools/tool-chain-orchestrator.js +738 -0
- package/dist/src/tools/tool-chain-orchestrator.js.map +1 -0
- package/dist/src/tools/troubleshoot-guided-workflow-tool.d.ts +150 -0
- package/dist/src/tools/troubleshoot-guided-workflow-tool.d.ts.map +1 -0
- package/dist/src/tools/troubleshoot-guided-workflow-tool.js +520 -0
- package/dist/src/tools/troubleshoot-guided-workflow-tool.js.map +1 -0
- package/dist/src/types/knowledge-graph-schemas.d.ts +481 -0
- package/dist/src/types/knowledge-graph-schemas.d.ts.map +1 -0
- package/dist/src/types/knowledge-graph-schemas.js +59 -0
- package/dist/src/types/knowledge-graph-schemas.js.map +1 -0
- package/dist/src/utils/enhanced-sensitive-detector.js +1 -1
- package/dist/src/utils/enhanced-sensitive-detector.js.map +1 -1
- package/dist/src/utils/json-safe.d.ts +38 -0
- package/dist/src/utils/json-safe.d.ts.map +1 -0
- package/dist/src/utils/json-safe.js +86 -0
- package/dist/src/utils/json-safe.js.map +1 -0
- package/dist/src/utils/knowledge-graph-manager.d.ts +28 -0
- package/dist/src/utils/knowledge-graph-manager.d.ts.map +1 -0
- package/dist/src/utils/knowledge-graph-manager.js +192 -0
- package/dist/src/utils/knowledge-graph-manager.js.map +1 -0
- package/dist/src/utils/mcp-response-validator.d.ts +30 -0
- package/dist/src/utils/mcp-response-validator.d.ts.map +1 -0
- package/dist/src/utils/mcp-response-validator.js +197 -0
- package/dist/src/utils/mcp-response-validator.js.map +1 -0
- package/dist/src/utils/project-health-scoring.d.ts +177 -0
- package/dist/src/utils/project-health-scoring.d.ts.map +1 -0
- package/dist/src/utils/project-health-scoring.js +370 -0
- package/dist/src/utils/project-health-scoring.js.map +1 -0
- package/dist/src/utils/release-readiness-detector.d.ts +2 -0
- package/dist/src/utils/release-readiness-detector.d.ts.map +1 -1
- package/dist/src/utils/release-readiness-detector.js +4 -2
- package/dist/src/utils/release-readiness-detector.js.map +1 -1
- package/dist/src/utils/todo-file-watcher.d.ts +26 -0
- package/dist/src/utils/todo-file-watcher.d.ts.map +1 -0
- package/dist/src/utils/todo-file-watcher.js +163 -0
- package/dist/src/utils/todo-file-watcher.js.map +1 -0
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { McpAdrError } from '../types/index.js';
|
|
7
|
+
import { KnowledgeGraphManager } from '../utils/knowledge-graph-manager.js';
|
|
7
8
|
// Task status enumeration
|
|
8
9
|
const TaskStatus = z.enum(['pending', 'in_progress', 'completed', 'blocked', 'cancelled']);
|
|
9
10
|
// Task priority enumeration
|
|
@@ -43,7 +44,7 @@ const TodoStructure = z.object({
|
|
|
43
44
|
// Operation schemas
|
|
44
45
|
const UpdateTaskStatusSchema = z.object({
|
|
45
46
|
operation: z.literal('update_status'),
|
|
46
|
-
todoPath: z.string().default('
|
|
47
|
+
todoPath: z.string().default('TODO.md').describe('Path to TODO.md file'),
|
|
47
48
|
updates: z.array(z.object({
|
|
48
49
|
taskId: z.string().describe('Task ID to update'),
|
|
49
50
|
status: TaskStatus.describe('New status'),
|
|
@@ -53,44 +54,146 @@ const UpdateTaskStatusSchema = z.object({
|
|
|
53
54
|
});
|
|
54
55
|
const AddTasksSchema = z.object({
|
|
55
56
|
operation: z.literal('add_tasks'),
|
|
56
|
-
todoPath: z.string().default('
|
|
57
|
+
todoPath: z.string().default('TODO.md').describe('Path to TODO.md file'),
|
|
57
58
|
tasks: z.array(TaskSchema.omit({ id: true, createdAt: true, updatedAt: true })).describe('New tasks to add'),
|
|
58
59
|
section: z.string().optional().describe('Section to add tasks to (will create if not exists)')
|
|
59
60
|
});
|
|
60
61
|
const MergeAdrSchema = z.object({
|
|
61
62
|
operation: z.literal('merge_adr_updates'),
|
|
62
|
-
todoPath: z.string().default('
|
|
63
|
+
todoPath: z.string().default('TODO.md').describe('Path to TODO.md file'),
|
|
63
64
|
adrDirectory: z.string().default('docs/adrs').describe('ADR directory path'),
|
|
64
65
|
preserveCompleted: z.boolean().default(true).describe('Keep completed tasks'),
|
|
65
66
|
preserveCustom: z.boolean().default(true).describe('Keep manually added tasks')
|
|
66
67
|
});
|
|
67
68
|
const SyncProgressSchema = z.object({
|
|
68
69
|
operation: z.literal('sync_progress'),
|
|
69
|
-
todoPath: z.string().default('
|
|
70
|
+
todoPath: z.string().default('TODO.md').describe('Path to TODO.md file'),
|
|
70
71
|
adrDirectory: z.string().default('docs/adrs').describe('ADR directory path'),
|
|
71
72
|
updateAdrs: z.boolean().default(false).describe('Update ADR status based on TODO progress')
|
|
72
73
|
});
|
|
73
74
|
const AnalyzeProgressSchema = z.object({
|
|
74
75
|
operation: z.literal('analyze_progress'),
|
|
75
|
-
todoPath: z.string().default('
|
|
76
|
+
todoPath: z.string().default('TODO.md').describe('Path to TODO.md file'),
|
|
76
77
|
timeframe: z.enum(['day', 'week', 'month', 'all']).default('week').describe('Analysis timeframe'),
|
|
77
78
|
includeVelocity: z.boolean().default(true).describe('Include velocity metrics')
|
|
78
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
|
+
});
|
|
79
136
|
// Main operation schema
|
|
80
137
|
const ManageTodoSchema = z.union([
|
|
81
138
|
UpdateTaskStatusSchema,
|
|
82
139
|
AddTasksSchema,
|
|
83
140
|
MergeAdrSchema,
|
|
84
141
|
SyncProgressSchema,
|
|
85
|
-
AnalyzeProgressSchema
|
|
142
|
+
AnalyzeProgressSchema,
|
|
143
|
+
DeleteTaskSchema,
|
|
144
|
+
EditTaskSchema,
|
|
145
|
+
MoveTaskSchema,
|
|
146
|
+
ManageSectionSchema,
|
|
147
|
+
SearchTasksSchema
|
|
86
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
|
+
}
|
|
87
189
|
/**
|
|
88
190
|
* Parse TODO.md file into structured format
|
|
191
|
+
* Supports both TODO.md and todo.md for compatibility
|
|
89
192
|
*/
|
|
90
193
|
async function parseTodoFile(todoPath) {
|
|
91
194
|
try {
|
|
92
|
-
const
|
|
93
|
-
const content = await
|
|
195
|
+
const { readFile } = await import('fs/promises');
|
|
196
|
+
const content = await readFile(todoPath, 'utf-8');
|
|
94
197
|
const lines = content.split('\n');
|
|
95
198
|
const structure = {
|
|
96
199
|
title: 'TODO Management',
|
|
@@ -179,16 +282,45 @@ async function parseTodoFile(todoPath) {
|
|
|
179
282
|
}
|
|
180
283
|
}
|
|
181
284
|
/**
|
|
182
|
-
* Convert structured TODO back to markdown format
|
|
285
|
+
* Convert structured TODO back to markdown format with dynamic health scoring
|
|
183
286
|
*/
|
|
184
|
-
function formatTodoAsMarkdown(structure) {
|
|
287
|
+
async function formatTodoAsMarkdown(structure, projectPath) {
|
|
185
288
|
const lines = [];
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
+
}
|
|
192
324
|
// Progress summary
|
|
193
325
|
lines.push('## 📊 Progress Summary');
|
|
194
326
|
lines.push('');
|
|
@@ -309,24 +441,229 @@ function addTasksToStructure(structure, newTasks, sectionName) {
|
|
|
309
441
|
updatedStructure.lastUpdated = new Date().toISOString();
|
|
310
442
|
return updatedStructure;
|
|
311
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
|
+
}
|
|
312
603
|
/**
|
|
313
604
|
* Main TODO management function
|
|
314
605
|
*/
|
|
315
|
-
export async function manageTodo(args) {
|
|
606
|
+
export async function manageTodo(args, projectPath) {
|
|
316
607
|
try {
|
|
317
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);
|
|
318
645
|
switch (validatedArgs.operation) {
|
|
319
646
|
case 'update_status': {
|
|
320
|
-
const structure = await parseTodoFile(
|
|
647
|
+
const structure = await parseTodoFile(absoluteTodoPath);
|
|
321
648
|
const updatedStructure = updateTaskStatuses(structure, validatedArgs.updates);
|
|
322
|
-
const markdown = formatTodoAsMarkdown(updatedStructure);
|
|
323
|
-
|
|
324
|
-
|
|
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
|
+
}
|
|
325
662
|
return {
|
|
326
663
|
content: [{
|
|
327
664
|
type: 'text',
|
|
328
665
|
text: `# Task Status Updated
|
|
329
|
-
|
|
666
|
+
${needsScoreInitialization ? '\n🎯 **Health scoring header automatically added to TODO.md**\n' : ''}
|
|
330
667
|
## Updates Applied
|
|
331
668
|
${validatedArgs.updates.map(update => `- Task \`${update.taskId}\`: ${update.status}${update.notes ? ` (${update.notes})` : ''}`).join('\n')}
|
|
332
669
|
|
|
@@ -336,16 +673,27 @@ ${validatedArgs.updates.map(update => `- Task \`${update.taskId}\`: ${update.sta
|
|
|
336
673
|
- ⏳ Pending: ${updatedStructure.summary.pending}
|
|
337
674
|
- 🚫 Blocked: ${updatedStructure.summary.blocked}
|
|
338
675
|
|
|
339
|
-
Updated TODO file: \`${
|
|
676
|
+
Updated TODO file: \`${absoluteTodoPath}\``
|
|
340
677
|
}]
|
|
341
678
|
};
|
|
342
679
|
}
|
|
343
680
|
case 'add_tasks': {
|
|
344
|
-
const structure = await parseTodoFile(
|
|
681
|
+
const structure = await parseTodoFile(absoluteTodoPath);
|
|
345
682
|
const updatedStructure = addTasksToStructure(structure, validatedArgs.tasks, validatedArgs.section);
|
|
346
|
-
const markdown = formatTodoAsMarkdown(updatedStructure);
|
|
347
|
-
|
|
348
|
-
|
|
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
|
+
}
|
|
349
697
|
return {
|
|
350
698
|
content: [{
|
|
351
699
|
type: 'text',
|
|
@@ -360,39 +708,78 @@ ${validatedArgs.tasks.map((task) => `- ${task.title} (${task.status}${task.prior
|
|
|
360
708
|
- Total Tasks: ${updatedStructure.summary.total}
|
|
361
709
|
- New Pending: ${validatedArgs.tasks.filter(t => t.status === 'pending').length}
|
|
362
710
|
|
|
363
|
-
Updated TODO file: \`${
|
|
711
|
+
Updated TODO file: \`${absoluteTodoPath}\``
|
|
364
712
|
}]
|
|
365
713
|
};
|
|
366
714
|
}
|
|
367
715
|
case 'merge_adr_updates': {
|
|
368
716
|
// Get current TODO structure
|
|
369
|
-
const currentStructure = await parseTodoFile(
|
|
370
|
-
//
|
|
371
|
-
|
|
372
|
-
|
|
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');
|
|
373
749
|
return {
|
|
374
750
|
content: [{
|
|
375
751
|
type: 'text',
|
|
376
|
-
text: `# ADR Merge
|
|
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'}
|
|
377
758
|
|
|
378
759
|
## Current TODO Status
|
|
379
|
-
- Total Tasks: ${
|
|
380
|
-
- Completed: ${
|
|
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
|
|
381
767
|
- Preserve Completed: ${validatedArgs.preserveCompleted}
|
|
382
768
|
- Preserve Custom: ${validatedArgs.preserveCustom}
|
|
383
769
|
|
|
384
770
|
## Next Steps
|
|
385
|
-
1. Review
|
|
386
|
-
2.
|
|
387
|
-
3.
|
|
388
|
-
4.
|
|
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
|
|
389
775
|
|
|
390
|
-
*
|
|
776
|
+
*Note: Advanced ADR integration requires additional development*
|
|
777
|
+
*Current implementation focuses on preserving existing content*`
|
|
391
778
|
}]
|
|
392
779
|
};
|
|
393
780
|
}
|
|
394
781
|
case 'sync_progress': {
|
|
395
|
-
const structure = await parseTodoFile(
|
|
782
|
+
const structure = await parseTodoFile(absoluteTodoPath);
|
|
396
783
|
return {
|
|
397
784
|
content: [{
|
|
398
785
|
type: 'text',
|
|
@@ -417,7 +804,7 @@ Updated TODO file: \`${validatedArgs.todoPath}\``
|
|
|
417
804
|
};
|
|
418
805
|
}
|
|
419
806
|
case 'analyze_progress': {
|
|
420
|
-
const structure = await parseTodoFile(
|
|
807
|
+
const structure = await parseTodoFile(absoluteTodoPath);
|
|
421
808
|
// Calculate progress metrics
|
|
422
809
|
const totalTasks = structure.summary.total;
|
|
423
810
|
const completionRate = totalTasks > 0 ? (structure.summary.completed / totalTasks) * 100 : 0;
|
|
@@ -458,6 +845,396 @@ ${completionRate > 80 ? '- 🎉 Great progress! Consider adding new tasks' : ''}
|
|
|
458
845
|
}]
|
|
459
846
|
};
|
|
460
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
|
+
}
|
|
461
1238
|
default:
|
|
462
1239
|
throw new McpAdrError(`Unknown operation: ${validatedArgs.operation}`, 'INVALID_INPUT');
|
|
463
1240
|
}
|