mcp-adr-analysis-server 2.0.9 → 2.0.11
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 +8 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +573 -348
- 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/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/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.d.ts +21 -1
- package/dist/src/tools/smart-git-push-tool.d.ts.map +1 -1
- package/dist/src/tools/smart-git-push-tool.js +181 -28
- package/dist/src/tools/smart-git-push-tool.js.map +1 -1
- package/dist/src/tools/smart-score-tool.d.ts +173 -0
- package/dist/src/tools/smart-score-tool.d.ts.map +1 -0
- package/dist/src/tools/smart-score-tool.js +681 -0
- package/dist/src/tools/smart-score-tool.js.map +1 -0
- 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 +126 -0
- package/dist/src/tools/tool-chain-orchestrator.d.ts.map +1 -0
- package/dist/src/tools/tool-chain-orchestrator.js +550 -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/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 +773 -0
- package/dist/src/types/knowledge-graph-schemas.d.ts.map +1 -0
- package/dist/src/types/knowledge-graph-schemas.js +87 -0
- package/dist/src/types/knowledge-graph-schemas.js.map +1 -0
- 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/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 +65 -0
- package/dist/src/utils/knowledge-graph-manager.d.ts.map +1 -0
- package/dist/src/utils/knowledge-graph-manager.js +358 -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 +2 -0
- 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/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/todo-management-tool.d.ts +0 -166
- package/dist/src/tools/todo-management-tool.d.ts.map +0 -1
- package/dist/src/tools/todo-management-tool.js +0 -472
- package/dist/src/tools/todo-management-tool.js.map +0 -1
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-First TODO Management Tool
|
|
3
|
+
*
|
|
4
|
+
* Replaces markdown-based TODO management with structured JSON backend
|
|
5
|
+
* for consistent LLM interactions and automatic scoring integration.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT FOR AI ASSISTANTS: This tool requires the .mcp-adr-cache infrastructure
|
|
8
|
+
* to be initialized first. Run `discover_existing_adrs` before using this tool.
|
|
9
|
+
*
|
|
10
|
+
* Cache Dependencies:
|
|
11
|
+
* - Requires: .mcp-adr-cache/todo-data.json (main TODO storage)
|
|
12
|
+
* - Requires: .mcp-adr-cache/knowledge-graph-snapshots.json (for intent tracking)
|
|
13
|
+
* - Updates: .mcp-adr-cache/project-health-scores.json (task completion metrics)
|
|
14
|
+
*
|
|
15
|
+
* Key Features:
|
|
16
|
+
* - JSON-first storage with automatic Markdown synchronization
|
|
17
|
+
* - Automatic project health scoring integration
|
|
18
|
+
* - Knowledge graph intent tracking
|
|
19
|
+
* - Bidirectional TODO.md compatibility
|
|
20
|
+
*/
|
|
21
|
+
import { z } from 'zod';
|
|
22
|
+
import { McpAdrError } from '../types/index.js';
|
|
23
|
+
import { TodoJsonManager } from '../utils/todo-json-manager.js';
|
|
24
|
+
import { KnowledgeGraphManager } from '../utils/knowledge-graph-manager.js';
|
|
25
|
+
// Remove unused import
|
|
26
|
+
// Task operations schema
|
|
27
|
+
const CreateTaskSchema = z.object({
|
|
28
|
+
operation: z.literal('create_task'),
|
|
29
|
+
projectPath: z.string().describe('Project root path'),
|
|
30
|
+
title: z.string().describe('Task title'),
|
|
31
|
+
description: z.string().optional().describe('Task description'),
|
|
32
|
+
priority: z.enum(['low', 'medium', 'high', 'critical']).default('medium'),
|
|
33
|
+
assignee: z.string().optional().describe('Task assignee'),
|
|
34
|
+
dueDate: z.string().optional().describe('Due date (ISO string)'),
|
|
35
|
+
category: z.string().optional().describe('Task category'),
|
|
36
|
+
tags: z.array(z.string()).default([]).describe('Task tags'),
|
|
37
|
+
dependencies: z.array(z.string()).default([]).describe('Task dependencies (IDs)'),
|
|
38
|
+
intentId: z.string().optional().describe('Link to knowledge graph intent'),
|
|
39
|
+
linkedAdrs: z.array(z.string()).default([]).describe('Related ADR files'),
|
|
40
|
+
autoComplete: z.boolean().default(false).describe('Auto-complete when criteria met'),
|
|
41
|
+
completionCriteria: z.string().optional().describe('Auto-completion rules')
|
|
42
|
+
});
|
|
43
|
+
const UpdateTaskSchema = z.object({
|
|
44
|
+
operation: z.literal('update_task'),
|
|
45
|
+
projectPath: z.string().describe('Project root path'),
|
|
46
|
+
taskId: z.string().describe('Task ID to update'),
|
|
47
|
+
updates: z.object({
|
|
48
|
+
title: z.string().optional(),
|
|
49
|
+
description: z.string().optional(),
|
|
50
|
+
status: z.enum(['pending', 'in_progress', 'completed', 'blocked', 'cancelled']).optional(),
|
|
51
|
+
priority: z.enum(['low', 'medium', 'high', 'critical']).optional(),
|
|
52
|
+
assignee: z.string().optional(),
|
|
53
|
+
dueDate: z.string().optional(),
|
|
54
|
+
progressPercentage: z.number().min(0).max(100).optional(),
|
|
55
|
+
notes: z.string().optional(),
|
|
56
|
+
tags: z.array(z.string()).optional()
|
|
57
|
+
}).describe('Fields to update'),
|
|
58
|
+
reason: z.string().describe('Reason for update (for changelog)')
|
|
59
|
+
});
|
|
60
|
+
const BulkUpdateSchema = z.object({
|
|
61
|
+
operation: z.literal('bulk_update'),
|
|
62
|
+
projectPath: z.string().describe('Project root path'),
|
|
63
|
+
updates: z.array(z.object({
|
|
64
|
+
taskId: z.string(),
|
|
65
|
+
status: z.enum(['pending', 'in_progress', 'completed', 'blocked', 'cancelled']).optional(),
|
|
66
|
+
priority: z.enum(['low', 'medium', 'high', 'critical']).optional(),
|
|
67
|
+
assignee: z.string().optional(),
|
|
68
|
+
notes: z.string().optional()
|
|
69
|
+
})).describe('Bulk status updates'),
|
|
70
|
+
reason: z.string().describe('Reason for bulk update')
|
|
71
|
+
});
|
|
72
|
+
const GetTasksSchema = z.object({
|
|
73
|
+
operation: z.literal('get_tasks'),
|
|
74
|
+
projectPath: z.string().describe('Project root path'),
|
|
75
|
+
filters: z.object({
|
|
76
|
+
status: z.enum(['pending', 'in_progress', 'completed', 'blocked', 'cancelled']).optional(),
|
|
77
|
+
priority: z.enum(['low', 'medium', 'high', 'critical']).optional(),
|
|
78
|
+
assignee: z.string().optional(),
|
|
79
|
+
category: z.string().optional(),
|
|
80
|
+
hasDeadline: z.boolean().optional(),
|
|
81
|
+
overdue: z.boolean().optional(),
|
|
82
|
+
tags: z.array(z.string()).optional()
|
|
83
|
+
}).optional().describe('Filter criteria'),
|
|
84
|
+
sortBy: z.enum(['priority', 'dueDate', 'createdAt', 'updatedAt']).default('priority'),
|
|
85
|
+
sortOrder: z.enum(['asc', 'desc']).default('desc'),
|
|
86
|
+
limit: z.number().optional().describe('Maximum number of tasks to return')
|
|
87
|
+
});
|
|
88
|
+
const GetAnalyticsSchema = z.object({
|
|
89
|
+
operation: z.literal('get_analytics'),
|
|
90
|
+
projectPath: z.string().describe('Project root path'),
|
|
91
|
+
timeframe: z.enum(['day', 'week', 'month', 'all']).default('week').describe('Analysis timeframe'),
|
|
92
|
+
includeVelocity: z.boolean().default(true).describe('Include velocity metrics'),
|
|
93
|
+
includeScoring: z.boolean().default(true).describe('Include scoring metrics')
|
|
94
|
+
});
|
|
95
|
+
const ImportAdrTasksSchema = z.object({
|
|
96
|
+
operation: z.literal('import_adr_tasks'),
|
|
97
|
+
projectPath: z.string().describe('Project root path'),
|
|
98
|
+
adrDirectory: z.string().default('docs/adrs').describe('ADR directory path'),
|
|
99
|
+
intentId: z.string().optional().describe('Link imported tasks to intent'),
|
|
100
|
+
preserveExisting: z.boolean().default(true).describe('Keep existing tasks'),
|
|
101
|
+
autoLinkDependencies: z.boolean().default(true).describe('Auto-detect task dependencies')
|
|
102
|
+
});
|
|
103
|
+
const SyncWithKnowledgeGraphSchema = z.object({
|
|
104
|
+
operation: z.literal('sync_knowledge_graph'),
|
|
105
|
+
projectPath: z.string().describe('Project root path'),
|
|
106
|
+
direction: z.enum(['to_kg', 'from_kg', 'bidirectional']).default('bidirectional'),
|
|
107
|
+
intentId: z.string().optional().describe('Specific intent to sync with')
|
|
108
|
+
});
|
|
109
|
+
const SyncToMarkdownSchema = z.object({
|
|
110
|
+
operation: z.literal('sync_to_markdown'),
|
|
111
|
+
projectPath: z.string().describe('Project root path'),
|
|
112
|
+
force: z.boolean().default(false).describe('Force sync even if markdown is newer')
|
|
113
|
+
});
|
|
114
|
+
const ImportFromMarkdownSchema = z.object({
|
|
115
|
+
operation: z.literal('import_from_markdown'),
|
|
116
|
+
projectPath: z.string().describe('Project root path'),
|
|
117
|
+
mergeStrategy: z.enum(['overwrite', 'merge', 'preserve_json']).default('merge'),
|
|
118
|
+
backupExisting: z.boolean().default(true).describe('Create backup before import')
|
|
119
|
+
});
|
|
120
|
+
// Main operation schema
|
|
121
|
+
const TodoManagementV2Schema = z.union([
|
|
122
|
+
CreateTaskSchema,
|
|
123
|
+
UpdateTaskSchema,
|
|
124
|
+
BulkUpdateSchema,
|
|
125
|
+
GetTasksSchema,
|
|
126
|
+
GetAnalyticsSchema,
|
|
127
|
+
ImportAdrTasksSchema,
|
|
128
|
+
SyncWithKnowledgeGraphSchema,
|
|
129
|
+
SyncToMarkdownSchema,
|
|
130
|
+
ImportFromMarkdownSchema
|
|
131
|
+
]);
|
|
132
|
+
/**
|
|
133
|
+
* Main TODO management function with JSON backend
|
|
134
|
+
*/
|
|
135
|
+
export async function manageTodoV2(args) {
|
|
136
|
+
try {
|
|
137
|
+
const validatedArgs = TodoManagementV2Schema.parse(args);
|
|
138
|
+
const todoManager = new TodoJsonManager(validatedArgs.projectPath);
|
|
139
|
+
const kgManager = new KnowledgeGraphManager();
|
|
140
|
+
switch (validatedArgs.operation) {
|
|
141
|
+
case 'create_task': {
|
|
142
|
+
const taskId = await todoManager.createTask({
|
|
143
|
+
title: validatedArgs.title,
|
|
144
|
+
description: validatedArgs.description,
|
|
145
|
+
priority: validatedArgs.priority,
|
|
146
|
+
assignee: validatedArgs.assignee,
|
|
147
|
+
dueDate: validatedArgs.dueDate,
|
|
148
|
+
category: validatedArgs.category,
|
|
149
|
+
tags: validatedArgs.tags,
|
|
150
|
+
dependencies: validatedArgs.dependencies,
|
|
151
|
+
intentId: validatedArgs.intentId,
|
|
152
|
+
linkedAdrs: validatedArgs.linkedAdrs,
|
|
153
|
+
autoComplete: validatedArgs.autoComplete,
|
|
154
|
+
completionCriteria: validatedArgs.completionCriteria
|
|
155
|
+
});
|
|
156
|
+
return {
|
|
157
|
+
content: [{
|
|
158
|
+
type: 'text',
|
|
159
|
+
text: `✅ Task created successfully!\n\n**Task ID**: ${taskId}\n**Title**: ${validatedArgs.title}\n**Priority**: ${validatedArgs.priority}\n**Status**: pending\n\n*Task has been added to JSON backend and will sync to TODO.md automatically.*`
|
|
160
|
+
}]
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
case 'update_task': {
|
|
164
|
+
// Filter out undefined values
|
|
165
|
+
const cleanUpdates = {};
|
|
166
|
+
if (validatedArgs.updates.title !== undefined)
|
|
167
|
+
cleanUpdates.title = validatedArgs.updates.title;
|
|
168
|
+
if (validatedArgs.updates.description !== undefined)
|
|
169
|
+
cleanUpdates.description = validatedArgs.updates.description;
|
|
170
|
+
if (validatedArgs.updates.status !== undefined)
|
|
171
|
+
cleanUpdates.status = validatedArgs.updates.status;
|
|
172
|
+
if (validatedArgs.updates.priority !== undefined)
|
|
173
|
+
cleanUpdates.priority = validatedArgs.updates.priority;
|
|
174
|
+
if (validatedArgs.updates.assignee !== undefined)
|
|
175
|
+
cleanUpdates.assignee = validatedArgs.updates.assignee;
|
|
176
|
+
if (validatedArgs.updates.dueDate !== undefined)
|
|
177
|
+
cleanUpdates.dueDate = validatedArgs.updates.dueDate;
|
|
178
|
+
if (validatedArgs.updates.progressPercentage !== undefined)
|
|
179
|
+
cleanUpdates.progressPercentage = validatedArgs.updates.progressPercentage;
|
|
180
|
+
if (validatedArgs.updates.notes !== undefined)
|
|
181
|
+
cleanUpdates.notes = validatedArgs.updates.notes;
|
|
182
|
+
if (validatedArgs.updates.tags !== undefined)
|
|
183
|
+
cleanUpdates.tags = validatedArgs.updates.tags;
|
|
184
|
+
await todoManager.updateTask({
|
|
185
|
+
taskId: validatedArgs.taskId,
|
|
186
|
+
updates: cleanUpdates,
|
|
187
|
+
reason: validatedArgs.reason,
|
|
188
|
+
triggeredBy: 'tool'
|
|
189
|
+
});
|
|
190
|
+
return {
|
|
191
|
+
content: [{
|
|
192
|
+
type: 'text',
|
|
193
|
+
text: `✅ Task updated successfully!\n\n**Task ID**: ${validatedArgs.taskId}\n**Updates**: ${Object.keys(validatedArgs.updates).join(', ')}\n**Reason**: ${validatedArgs.reason}\n\n*Changes synced to JSON backend and TODO.md.*`
|
|
194
|
+
}]
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
case 'bulk_update': {
|
|
198
|
+
let updateCount = 0;
|
|
199
|
+
for (const update of validatedArgs.updates) {
|
|
200
|
+
// Filter out undefined values
|
|
201
|
+
const cleanUpdates = {};
|
|
202
|
+
if (update.status !== undefined)
|
|
203
|
+
cleanUpdates.status = update.status;
|
|
204
|
+
if (update.priority !== undefined)
|
|
205
|
+
cleanUpdates.priority = update.priority;
|
|
206
|
+
if (update.assignee !== undefined)
|
|
207
|
+
cleanUpdates.assignee = update.assignee;
|
|
208
|
+
if (update.notes !== undefined)
|
|
209
|
+
cleanUpdates.notes = update.notes;
|
|
210
|
+
await todoManager.updateTask({
|
|
211
|
+
taskId: update.taskId,
|
|
212
|
+
updates: cleanUpdates,
|
|
213
|
+
reason: validatedArgs.reason,
|
|
214
|
+
triggeredBy: 'tool'
|
|
215
|
+
});
|
|
216
|
+
updateCount++;
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
content: [{
|
|
220
|
+
type: 'text',
|
|
221
|
+
text: `✅ Bulk update completed!\n\n**Tasks Updated**: ${updateCount}\n**Reason**: ${validatedArgs.reason}\n\n*All changes synced to JSON backend and TODO.md.*`
|
|
222
|
+
}]
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
case 'get_tasks': {
|
|
226
|
+
const data = await todoManager.loadTodoData();
|
|
227
|
+
let tasks = Object.values(data.tasks);
|
|
228
|
+
// Apply filters
|
|
229
|
+
if (validatedArgs.filters) {
|
|
230
|
+
const filters = validatedArgs.filters;
|
|
231
|
+
if (filters.status) {
|
|
232
|
+
tasks = tasks.filter(t => t.status === filters.status);
|
|
233
|
+
}
|
|
234
|
+
if (filters.priority) {
|
|
235
|
+
tasks = tasks.filter(t => t.priority === filters.priority);
|
|
236
|
+
}
|
|
237
|
+
if (filters.assignee) {
|
|
238
|
+
tasks = tasks.filter(t => t.assignee === filters.assignee);
|
|
239
|
+
}
|
|
240
|
+
if (filters.category) {
|
|
241
|
+
tasks = tasks.filter(t => t.category === filters.category);
|
|
242
|
+
}
|
|
243
|
+
if (filters.hasDeadline) {
|
|
244
|
+
tasks = tasks.filter(t => Boolean(t.dueDate));
|
|
245
|
+
}
|
|
246
|
+
if (filters.overdue) {
|
|
247
|
+
const now = new Date();
|
|
248
|
+
tasks = tasks.filter(t => t.dueDate && new Date(t.dueDate) < now && t.status !== 'completed');
|
|
249
|
+
}
|
|
250
|
+
if (filters.tags && filters.tags.length > 0) {
|
|
251
|
+
tasks = tasks.filter(t => filters.tags.some(tag => t.tags.includes(tag)));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Sort tasks
|
|
255
|
+
tasks.sort((a, b) => {
|
|
256
|
+
const priorityOrder = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
257
|
+
switch (validatedArgs.sortBy) {
|
|
258
|
+
case 'priority':
|
|
259
|
+
return validatedArgs.sortOrder === 'desc'
|
|
260
|
+
? priorityOrder[b.priority] - priorityOrder[a.priority]
|
|
261
|
+
: priorityOrder[a.priority] - priorityOrder[b.priority];
|
|
262
|
+
case 'dueDate':
|
|
263
|
+
if (!a.dueDate && !b.dueDate)
|
|
264
|
+
return 0;
|
|
265
|
+
if (!a.dueDate)
|
|
266
|
+
return 1;
|
|
267
|
+
if (!b.dueDate)
|
|
268
|
+
return -1;
|
|
269
|
+
return validatedArgs.sortOrder === 'desc'
|
|
270
|
+
? new Date(b.dueDate).getTime() - new Date(a.dueDate).getTime()
|
|
271
|
+
: new Date(a.dueDate).getTime() - new Date(b.dueDate).getTime();
|
|
272
|
+
case 'createdAt':
|
|
273
|
+
return validatedArgs.sortOrder === 'desc'
|
|
274
|
+
? new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
275
|
+
: new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
|
276
|
+
case 'updatedAt':
|
|
277
|
+
return validatedArgs.sortOrder === 'desc'
|
|
278
|
+
? new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
|
279
|
+
: new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime();
|
|
280
|
+
default:
|
|
281
|
+
return 0;
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
// Apply limit
|
|
285
|
+
if (validatedArgs.limit) {
|
|
286
|
+
tasks = tasks.slice(0, validatedArgs.limit);
|
|
287
|
+
}
|
|
288
|
+
const taskList = tasks.map(task => {
|
|
289
|
+
const dueDateStr = task.dueDate ? new Date(task.dueDate).toLocaleDateString() : 'No due date';
|
|
290
|
+
const isOverdue = task.dueDate && new Date(task.dueDate) < new Date() && task.status !== 'completed';
|
|
291
|
+
return `**${task.title}** (${task.id.substring(0, 8)})\n` +
|
|
292
|
+
` Status: ${task.status} | Priority: ${task.priority}\n` +
|
|
293
|
+
` ${task.assignee ? `Assignee: ${task.assignee} | ` : ''}Due: ${dueDateStr} ${isOverdue ? '🔴 OVERDUE' : ''}\n` +
|
|
294
|
+
` ${task.tags.length > 0 ? `Tags: ${task.tags.join(', ')} | ` : ''}Progress: ${task.progressPercentage}%\n` +
|
|
295
|
+
` ${task.description || 'No description'}\n`;
|
|
296
|
+
}).join('\n');
|
|
297
|
+
return {
|
|
298
|
+
content: [{
|
|
299
|
+
type: 'text',
|
|
300
|
+
text: `# 📋 Task List (${tasks.length} tasks)\n\n${taskList || 'No tasks match the criteria.'}`
|
|
301
|
+
}]
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
case 'get_analytics': {
|
|
305
|
+
const analytics = await todoManager.getAnalytics();
|
|
306
|
+
const { metrics, trends, recommendations } = analytics;
|
|
307
|
+
let analyticsText = `# 📊 TODO Analytics\n\n`;
|
|
308
|
+
analyticsText += `## Task Metrics\n`;
|
|
309
|
+
analyticsText += `- **Total Tasks**: ${metrics.totalTasks}\n`;
|
|
310
|
+
analyticsText += `- **Completed**: ${metrics.completedTasks} (${metrics.completionPercentage.toFixed(1)}%)\n`;
|
|
311
|
+
analyticsText += `- **Priority Score**: ${metrics.priorityWeightedScore.toFixed(1)}%\n`;
|
|
312
|
+
analyticsText += `- **Critical Remaining**: ${metrics.criticalTasksRemaining}\n`;
|
|
313
|
+
analyticsText += `- **Blocked Tasks**: ${metrics.blockedTasksCount}\n`;
|
|
314
|
+
analyticsText += `- **Average Task Age**: ${metrics.averageTaskAge.toFixed(1)} days\n\n`;
|
|
315
|
+
if (validatedArgs.includeVelocity) {
|
|
316
|
+
analyticsText += `## Velocity Metrics\n`;
|
|
317
|
+
analyticsText += `- **Tasks/Week**: ${metrics.velocityMetrics.tasksCompletedLastWeek}\n`;
|
|
318
|
+
analyticsText += `- **Avg Completion Time**: ${metrics.velocityMetrics.averageCompletionTime.toFixed(1)} hours\n\n`;
|
|
319
|
+
}
|
|
320
|
+
if (recommendations.length > 0) {
|
|
321
|
+
analyticsText += `## Recommendations\n`;
|
|
322
|
+
analyticsText += recommendations.map(rec => `- ${rec}`).join('\n');
|
|
323
|
+
analyticsText += '\n\n';
|
|
324
|
+
}
|
|
325
|
+
if (trends.length > 0) {
|
|
326
|
+
analyticsText += `## Recent Score History\n`;
|
|
327
|
+
analyticsText += trends.slice(-5).map(trend => `- ${new Date(trend.timestamp).toLocaleDateString()}: ${trend.score.toFixed(1)}% (${trend.trigger})`).join('\n');
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
content: [{
|
|
331
|
+
type: 'text',
|
|
332
|
+
text: analyticsText
|
|
333
|
+
}]
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
case 'import_adr_tasks': {
|
|
337
|
+
const adrDirectory = validatedArgs.adrDirectory;
|
|
338
|
+
const intentId = validatedArgs.intentId;
|
|
339
|
+
const preserveExisting = validatedArgs.preserveExisting;
|
|
340
|
+
const autoLinkDependencies = validatedArgs.autoLinkDependencies;
|
|
341
|
+
// Discover ADRs in the directory
|
|
342
|
+
const { discoverAdrsInDirectory } = await import('../utils/adr-discovery.js');
|
|
343
|
+
const discoveryResult = await discoverAdrsInDirectory(adrDirectory, true, // include content for task extraction
|
|
344
|
+
validatedArgs.projectPath);
|
|
345
|
+
if (discoveryResult.totalAdrs === 0) {
|
|
346
|
+
return {
|
|
347
|
+
content: [{
|
|
348
|
+
type: 'text',
|
|
349
|
+
text: `⚠️ No ADRs found in directory: ${adrDirectory}\n\n**Recommendations:**\n${discoveryResult.recommendations.join('\n')}`
|
|
350
|
+
}]
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
// Extract tasks from each ADR
|
|
354
|
+
const extractedTasks = await extractTasksFromAdrs(discoveryResult.adrs, adrDirectory, intentId, autoLinkDependencies);
|
|
355
|
+
// Import tasks into JSON backend
|
|
356
|
+
let importedCount = 0;
|
|
357
|
+
let skippedCount = 0;
|
|
358
|
+
const importResults = [];
|
|
359
|
+
for (const task of extractedTasks) {
|
|
360
|
+
try {
|
|
361
|
+
// Check if task already exists (by title and ADR reference)
|
|
362
|
+
const existingData = await todoManager.loadTodoData();
|
|
363
|
+
const existingTask = Object.values(existingData.tasks).find(t => t.title === task.title &&
|
|
364
|
+
t.linkedAdrs.some(adr => task.linkedAdrs.includes(adr)));
|
|
365
|
+
if (existingTask && preserveExisting) {
|
|
366
|
+
skippedCount++;
|
|
367
|
+
importResults.push(`⏭️ Skipped: "${task.title}" (already exists)`);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
// Create the task
|
|
371
|
+
await todoManager.createTask({
|
|
372
|
+
title: task.title,
|
|
373
|
+
description: task.description,
|
|
374
|
+
priority: task.priority,
|
|
375
|
+
category: task.category,
|
|
376
|
+
tags: task.tags,
|
|
377
|
+
dependencies: task.dependencies,
|
|
378
|
+
linkedAdrs: task.linkedAdrs,
|
|
379
|
+
assignee: task.assignee,
|
|
380
|
+
...(intentId && { intentId })
|
|
381
|
+
});
|
|
382
|
+
importedCount++;
|
|
383
|
+
importResults.push(`✅ Imported: "${task.title}" (${task.priority} priority)`);
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
importResults.push(`❌ Failed: "${task.title}" - ${error instanceof Error ? error.message : String(error)}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
content: [{
|
|
391
|
+
type: 'text',
|
|
392
|
+
text: `# ✅ ADR Task Import Completed
|
|
393
|
+
|
|
394
|
+
## Import Summary
|
|
395
|
+
- **ADRs Scanned**: ${discoveryResult.totalAdrs}
|
|
396
|
+
- **Tasks Extracted**: ${extractedTasks.length}
|
|
397
|
+
- **Tasks Imported**: ${importedCount}
|
|
398
|
+
- **Tasks Skipped**: ${skippedCount}
|
|
399
|
+
- **Preserve Existing**: ${preserveExisting}
|
|
400
|
+
- **Auto-Link Dependencies**: ${autoLinkDependencies}
|
|
401
|
+
|
|
402
|
+
## ADR Status Overview
|
|
403
|
+
${Object.entries(discoveryResult.summary.byStatus)
|
|
404
|
+
.map(([status, count]) => `- **${status}**: ${count} ADRs`)
|
|
405
|
+
.join('\n')}
|
|
406
|
+
|
|
407
|
+
## Import Results
|
|
408
|
+
${importResults.join('\n')}
|
|
409
|
+
|
|
410
|
+
## Next Steps
|
|
411
|
+
1. **View Tasks**: Use \`get_tasks\` operation to see imported tasks
|
|
412
|
+
2. **Sync to Markdown**: Tasks will be automatically synced to TODO.md
|
|
413
|
+
3. **Update Status**: Use \`update_task\` operation to track progress
|
|
414
|
+
4. **Link Knowledge Graph**: Tasks are ${intentId ? `linked to intent ${intentId}` : 'ready for intent linking'}
|
|
415
|
+
|
|
416
|
+
*All tasks have been imported into the JSON backend and are ready for the complete TODO management workflow.*`
|
|
417
|
+
}]
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
case 'sync_knowledge_graph': {
|
|
421
|
+
const data = await todoManager.loadTodoData();
|
|
422
|
+
// Sync task completion status to knowledge graph
|
|
423
|
+
for (const task of Object.values(data.tasks)) {
|
|
424
|
+
if (task.intentId) {
|
|
425
|
+
await kgManager.updateTodoSnapshot(task.intentId, `Task ${task.status}: ${task.title} (${task.progressPercentage}%)`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
content: [{
|
|
430
|
+
type: 'text',
|
|
431
|
+
text: `✅ Knowledge graph sync completed!\n\n**Direction**: ${validatedArgs.direction}\n**Tasks with Intent Links**: ${Object.values(data.tasks).filter(t => t.intentId).length}\n\n*Task progress has been synced to the knowledge graph.*`
|
|
432
|
+
}]
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
case 'sync_to_markdown': {
|
|
436
|
+
await todoManager.convertToMarkdown();
|
|
437
|
+
return {
|
|
438
|
+
content: [{
|
|
439
|
+
type: 'text',
|
|
440
|
+
text: `✅ Markdown sync completed!\n\n*TODO.md has been updated with the latest JSON data including health dashboard and formatted task lists.*`
|
|
441
|
+
}]
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
case 'import_from_markdown': {
|
|
445
|
+
if (validatedArgs.backupExisting) {
|
|
446
|
+
// Create backup of existing JSON
|
|
447
|
+
const data = await todoManager.loadTodoData();
|
|
448
|
+
const backupPath = `${validatedArgs.projectPath}/.mcp-adr-cache/todo-data-backup-${Date.now()}.json`;
|
|
449
|
+
const fs = await import('fs/promises');
|
|
450
|
+
await fs.writeFile(backupPath, JSON.stringify(data, null, 2));
|
|
451
|
+
}
|
|
452
|
+
await todoManager.importFromMarkdown();
|
|
453
|
+
return {
|
|
454
|
+
content: [{
|
|
455
|
+
type: 'text',
|
|
456
|
+
text: `✅ Markdown import completed!\n\n**Strategy**: ${validatedArgs.mergeStrategy}\n**Backup Created**: ${validatedArgs.backupExisting}\n\n*TODO.md has been parsed and imported into the JSON backend.*`
|
|
457
|
+
}]
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
default:
|
|
461
|
+
throw new McpAdrError(`Unknown operation: ${validatedArgs.operation}`, 'INVALID_INPUT');
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
if (error instanceof z.ZodError) {
|
|
466
|
+
throw new McpAdrError(`Invalid input: ${error.errors.map(e => e.message).join(', ')}`, 'INVALID_INPUT');
|
|
467
|
+
}
|
|
468
|
+
throw new McpAdrError(`TODO management failed: ${error instanceof Error ? error.message : String(error)}`, 'TODO_MANAGEMENT_ERROR');
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Extract implementation tasks from ADR content
|
|
473
|
+
*/
|
|
474
|
+
async function extractTasksFromAdrs(adrs, _adrDirectory, _intentId, autoLinkDependencies = true) {
|
|
475
|
+
const extractedTasks = [];
|
|
476
|
+
for (const adr of adrs) {
|
|
477
|
+
if (!adr.content)
|
|
478
|
+
continue;
|
|
479
|
+
// Extract tasks from Decision and Consequences sections
|
|
480
|
+
const tasks = extractTasksFromAdrContent(adr);
|
|
481
|
+
// Add ADR metadata to each task
|
|
482
|
+
for (const task of tasks) {
|
|
483
|
+
task.linkedAdrs = [adr.path];
|
|
484
|
+
task.category = task.category || 'ADR Implementation';
|
|
485
|
+
task.tags = task.tags || [];
|
|
486
|
+
// Add ADR-specific tags
|
|
487
|
+
if (adr.metadata?.number) {
|
|
488
|
+
task.tags.push(`adr-${adr.metadata.number}`);
|
|
489
|
+
}
|
|
490
|
+
if (adr.status) {
|
|
491
|
+
task.tags.push(`status-${adr.status}`);
|
|
492
|
+
}
|
|
493
|
+
// Set priority based on ADR status and content
|
|
494
|
+
if (!task.priority) {
|
|
495
|
+
task.priority = determinePriorityFromAdr(adr);
|
|
496
|
+
}
|
|
497
|
+
extractedTasks.push(task);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
// Link dependencies between ADRs if requested
|
|
501
|
+
if (autoLinkDependencies) {
|
|
502
|
+
linkAdrDependencies(extractedTasks, adrs);
|
|
503
|
+
}
|
|
504
|
+
return extractedTasks;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Extract tasks from ADR content sections
|
|
508
|
+
*/
|
|
509
|
+
function extractTasksFromAdrContent(adr) {
|
|
510
|
+
const tasks = [];
|
|
511
|
+
const content = adr.content || '';
|
|
512
|
+
// Look for implementation tasks in Decision section
|
|
513
|
+
if (adr.decision && typeof adr.decision === 'string') {
|
|
514
|
+
const decisionTasks = extractTasksFromText(adr.decision, 'Implementation', 'high');
|
|
515
|
+
tasks.push(...decisionTasks);
|
|
516
|
+
}
|
|
517
|
+
// Look for tasks in Consequences section
|
|
518
|
+
if (adr.consequences && typeof adr.consequences === 'string') {
|
|
519
|
+
const consequenceTasks = extractTasksFromText(adr.consequences, 'Validation', 'medium');
|
|
520
|
+
tasks.push(...consequenceTasks);
|
|
521
|
+
}
|
|
522
|
+
// Look for explicit task lists in content
|
|
523
|
+
const taskListRegex = /(?:##?\s*(?:tasks?|todo|implementation|action items?)\s*\n)([\s\S]*?)(?=\n##|\n#|$)/gi;
|
|
524
|
+
let match;
|
|
525
|
+
while ((match = taskListRegex.exec(content)) !== null) {
|
|
526
|
+
const taskSection = match[1];
|
|
527
|
+
if (taskSection) {
|
|
528
|
+
const sectionTasks = extractTasksFromText(taskSection, 'Task', 'medium');
|
|
529
|
+
tasks.push(...sectionTasks);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
// If no explicit tasks found, create a general implementation task
|
|
533
|
+
if (tasks.length === 0) {
|
|
534
|
+
tasks.push({
|
|
535
|
+
title: `Implement ${adr.title}`,
|
|
536
|
+
description: `Implement the architectural decision: ${adr.title}`,
|
|
537
|
+
priority: 'medium',
|
|
538
|
+
category: 'ADR Implementation',
|
|
539
|
+
tags: ['implementation'],
|
|
540
|
+
dependencies: [],
|
|
541
|
+
linkedAdrs: []
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
return tasks;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Extract tasks from text content using patterns
|
|
548
|
+
*/
|
|
549
|
+
function extractTasksFromText(text, defaultCategory, defaultPriority) {
|
|
550
|
+
const tasks = [];
|
|
551
|
+
// Look for bullet points that look like tasks
|
|
552
|
+
const taskPatterns = [
|
|
553
|
+
/[-*+]\s+(.+?)(?:\n|$)/g,
|
|
554
|
+
/\d+\.\s+(.+?)(?:\n|$)/g,
|
|
555
|
+
/(?:must|should|will|need to|implement|create|build|develop|configure|setup|establish)\s+(.+?)(?:\.|$)/gi
|
|
556
|
+
];
|
|
557
|
+
for (const pattern of taskPatterns) {
|
|
558
|
+
let match;
|
|
559
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
560
|
+
const taskText = match[1]?.trim();
|
|
561
|
+
if (!taskText || taskText.length < 10 || taskText.length > 200)
|
|
562
|
+
continue; // Filter out very short/long matches
|
|
563
|
+
// Determine priority from keywords
|
|
564
|
+
let priority = defaultPriority;
|
|
565
|
+
if (/critical|urgent|must|security|breaking/.test(taskText.toLowerCase())) {
|
|
566
|
+
priority = 'critical';
|
|
567
|
+
}
|
|
568
|
+
else if (/important|should|key|major/.test(taskText.toLowerCase())) {
|
|
569
|
+
priority = 'high';
|
|
570
|
+
}
|
|
571
|
+
else if (/nice|could|minor|optional/.test(taskText.toLowerCase())) {
|
|
572
|
+
priority = 'low';
|
|
573
|
+
}
|
|
574
|
+
// Extract assignee if mentioned
|
|
575
|
+
let assignee;
|
|
576
|
+
const assigneeMatch = taskText.match(/@(\w+)|assigned to (\w+)|by (\w+)/i);
|
|
577
|
+
if (assigneeMatch) {
|
|
578
|
+
assignee = assigneeMatch[1] || assigneeMatch[2] || assigneeMatch[3];
|
|
579
|
+
}
|
|
580
|
+
tasks.push({
|
|
581
|
+
title: taskText.charAt(0).toUpperCase() + taskText.slice(1), // Capitalize first letter
|
|
582
|
+
description: `Task extracted from ADR: ${taskText}`,
|
|
583
|
+
priority,
|
|
584
|
+
category: defaultCategory,
|
|
585
|
+
tags: [],
|
|
586
|
+
dependencies: [],
|
|
587
|
+
linkedAdrs: [],
|
|
588
|
+
...(assignee && { assignee })
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return tasks;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Determine priority based on ADR status and content
|
|
596
|
+
*/
|
|
597
|
+
function determinePriorityFromAdr(adr) {
|
|
598
|
+
const status = adr.status?.toLowerCase() || '';
|
|
599
|
+
const content = (adr.content || '').toLowerCase();
|
|
600
|
+
// High priority for accepted/active ADRs
|
|
601
|
+
if (['accepted', 'active', 'approved'].includes(status)) {
|
|
602
|
+
return 'high';
|
|
603
|
+
}
|
|
604
|
+
// Critical for security/breaking changes
|
|
605
|
+
if (/security|breaking|critical|urgent/.test(content)) {
|
|
606
|
+
return 'critical';
|
|
607
|
+
}
|
|
608
|
+
// Low priority for deprecated/superseded
|
|
609
|
+
if (['deprecated', 'superseded', 'rejected'].includes(status)) {
|
|
610
|
+
return 'low';
|
|
611
|
+
}
|
|
612
|
+
// Default to medium
|
|
613
|
+
return 'medium';
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Link dependencies between ADR tasks
|
|
617
|
+
*/
|
|
618
|
+
function linkAdrDependencies(tasks, adrs) {
|
|
619
|
+
// Simple dependency linking based on ADR numbers and references
|
|
620
|
+
for (const task of tasks) {
|
|
621
|
+
const taskAdr = adrs.find(adr => task.linkedAdrs.includes(adr.path));
|
|
622
|
+
if (!taskAdr)
|
|
623
|
+
continue;
|
|
624
|
+
// Look for references to other ADRs in the content
|
|
625
|
+
const content = taskAdr.content || '';
|
|
626
|
+
const adrReferences = content.match(/adr[-_]?(\d+)/gi) || [];
|
|
627
|
+
for (const ref of adrReferences) {
|
|
628
|
+
const refNumber = ref.match(/(\d+)/)?.[1];
|
|
629
|
+
if (!refNumber || refNumber === taskAdr.metadata?.number)
|
|
630
|
+
continue;
|
|
631
|
+
// Find tasks from the referenced ADR
|
|
632
|
+
const referencedAdr = adrs.find(adr => adr.metadata?.number === refNumber);
|
|
633
|
+
if (!referencedAdr)
|
|
634
|
+
continue;
|
|
635
|
+
const referencedTasks = tasks.filter(t => t.linkedAdrs.includes(referencedAdr.path));
|
|
636
|
+
for (const refTask of referencedTasks) {
|
|
637
|
+
if (!task.dependencies.includes(refTask.title)) {
|
|
638
|
+
task.dependencies.push(refTask.title);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
//# sourceMappingURL=todo-management-tool-v2.js.map
|