@claudetools/tools 0.1.0
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 +86 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +61 -0
- package/dist/handlers/tool-handlers.d.ts +2 -0
- package/dist/handlers/tool-handlers.js +1108 -0
- package/dist/helpers/api-client.d.ts +50 -0
- package/dist/helpers/api-client.js +60 -0
- package/dist/helpers/config-manager.d.ts +68 -0
- package/dist/helpers/config-manager.js +306 -0
- package/dist/helpers/config.d.ts +55 -0
- package/dist/helpers/config.js +174 -0
- package/dist/helpers/dependencies.d.ts +30 -0
- package/dist/helpers/dependencies.js +87 -0
- package/dist/helpers/formatter.d.ts +2 -0
- package/dist/helpers/formatter.js +24 -0
- package/dist/helpers/patterns.d.ts +15 -0
- package/dist/helpers/patterns.js +118 -0
- package/dist/helpers/project-registration.d.ts +27 -0
- package/dist/helpers/project-registration.js +338 -0
- package/dist/helpers/tasks.d.ts +152 -0
- package/dist/helpers/tasks.js +274 -0
- package/dist/helpers/workers.d.ts +18 -0
- package/dist/helpers/workers.js +146 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +75 -0
- package/dist/logger.d.ts +32 -0
- package/dist/logger.js +401 -0
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.js +64 -0
- package/dist/resources.d.ts +2 -0
- package/dist/resources.js +79 -0
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +206 -0
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +748 -0
- package/package.json +58 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Task Management System
|
|
3
|
+
// =============================================================================
|
|
4
|
+
import { apiRequest } from './api-client.js';
|
|
5
|
+
import { matchTaskToWorker } from './workers.js';
|
|
6
|
+
export function parseJsonArray(value) {
|
|
7
|
+
if (!value)
|
|
8
|
+
return [];
|
|
9
|
+
if (Array.isArray(value))
|
|
10
|
+
return value;
|
|
11
|
+
if (typeof value === 'string') {
|
|
12
|
+
try {
|
|
13
|
+
const parsed = JSON.parse(value);
|
|
14
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
export async function createTask(userId, projectId, taskData) {
|
|
23
|
+
// Filter out undefined values to avoid sending them to API
|
|
24
|
+
const cleanTaskData = {};
|
|
25
|
+
for (const [key, value] of Object.entries(taskData)) {
|
|
26
|
+
if (value !== undefined) {
|
|
27
|
+
cleanTaskData[key] = value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return apiRequest(`/api/v1/tasks/${userId}/${projectId}`, 'POST', cleanTaskData);
|
|
31
|
+
}
|
|
32
|
+
export async function listTasks(userId, projectId, filters) {
|
|
33
|
+
const params = new URLSearchParams();
|
|
34
|
+
if (filters?.type)
|
|
35
|
+
params.set('type', filters.type);
|
|
36
|
+
if (filters?.status)
|
|
37
|
+
params.set('status', filters.status);
|
|
38
|
+
if (filters?.parent_id)
|
|
39
|
+
params.set('parent_id', filters.parent_id);
|
|
40
|
+
if (filters?.assigned_to)
|
|
41
|
+
params.set('assigned_to', filters.assigned_to);
|
|
42
|
+
if (filters?.limit)
|
|
43
|
+
params.set('limit', String(filters.limit));
|
|
44
|
+
if (filters?.offset)
|
|
45
|
+
params.set('offset', String(filters.offset));
|
|
46
|
+
const queryString = params.toString();
|
|
47
|
+
const url = `/api/v1/tasks/${userId}/${projectId}${queryString ? `?${queryString}` : ''}`;
|
|
48
|
+
return apiRequest(url);
|
|
49
|
+
}
|
|
50
|
+
export async function getTask(userId, projectId, taskId, full = false) {
|
|
51
|
+
const endpoint = full ? 'full' : '';
|
|
52
|
+
return apiRequest(`/api/v1/tasks/${userId}/${projectId}/${taskId}${endpoint ? `/${endpoint}` : ''}`);
|
|
53
|
+
}
|
|
54
|
+
export async function claimTask(userId, projectId, taskId, agentId, lockDurationMinutes = 30) {
|
|
55
|
+
return apiRequest(`/api/v1/tasks/${userId}/${projectId}/${taskId}/claim`, 'POST', {
|
|
56
|
+
agent_id: agentId,
|
|
57
|
+
lock_duration_minutes: lockDurationMinutes,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
export async function releaseTask(userId, projectId, taskId, agentId, newStatus, workLog) {
|
|
61
|
+
return apiRequest(`/api/v1/tasks/${userId}/${projectId}/${taskId}/release`, 'POST', {
|
|
62
|
+
agent_id: agentId,
|
|
63
|
+
new_status: newStatus,
|
|
64
|
+
work_log: workLog,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
export async function updateTaskStatus(userId, projectId, taskId, status, agentId) {
|
|
68
|
+
return apiRequest(`/api/v1/tasks/${userId}/${projectId}/${taskId}/status`, 'POST', {
|
|
69
|
+
status,
|
|
70
|
+
agent_id: agentId,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
export async function addTaskContext(userId, projectId, taskId, contextType, content, addedBy, source) {
|
|
74
|
+
return apiRequest(`/api/v1/tasks/${userId}/${projectId}/${taskId}/context`, 'POST', {
|
|
75
|
+
context_type: contextType,
|
|
76
|
+
content,
|
|
77
|
+
added_by: addedBy,
|
|
78
|
+
source,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export async function getTaskSummary(userId, projectId) {
|
|
82
|
+
return apiRequest(`/api/v1/tasks/${userId}/${projectId}/summary`);
|
|
83
|
+
}
|
|
84
|
+
export async function heartbeatTask(userId, projectId, taskId, agentId, extendMinutes = 30) {
|
|
85
|
+
return apiRequest(`/api/v1/tasks/${userId}/${projectId}/${taskId}/heartbeat`, 'POST', {
|
|
86
|
+
agent_id: agentId,
|
|
87
|
+
extend_minutes: extendMinutes,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// -----------------------------------------------------------------------------
|
|
91
|
+
// Orchestration Functions
|
|
92
|
+
// -----------------------------------------------------------------------------
|
|
93
|
+
/**
|
|
94
|
+
* Get all tasks ready for parallel dispatch
|
|
95
|
+
* - Filters for 'ready' status
|
|
96
|
+
* - Excludes already claimed tasks
|
|
97
|
+
* - Resolves dependencies (only returns unblocked tasks)
|
|
98
|
+
* - Matches each to appropriate expert worker
|
|
99
|
+
*/
|
|
100
|
+
export async function getDispatchableTasks(userId, projectId, epicId, maxParallel = 5) {
|
|
101
|
+
// Get all tasks with 'ready' status
|
|
102
|
+
const result = await listTasks(userId, projectId, {
|
|
103
|
+
status: 'ready',
|
|
104
|
+
parent_id: epicId,
|
|
105
|
+
limit: 50,
|
|
106
|
+
});
|
|
107
|
+
if (!result.success || !result.data.length) {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
const dispatchable = [];
|
|
111
|
+
for (const task of result.data) {
|
|
112
|
+
// Skip if already claimed (has lock)
|
|
113
|
+
if (task.assigned_to && task.lock_expires_at) {
|
|
114
|
+
const lockExpires = new Date(task.lock_expires_at);
|
|
115
|
+
if (lockExpires > new Date()) {
|
|
116
|
+
continue; // Still locked
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Check dependencies (blocked_by)
|
|
120
|
+
const blockedBy = parseJsonArray(task.blocked_by);
|
|
121
|
+
if (blockedBy.length > 0) {
|
|
122
|
+
// Check if blocking tasks are done
|
|
123
|
+
let allBlockersComplete = true;
|
|
124
|
+
for (const blockerId of blockedBy) {
|
|
125
|
+
const blockerResult = await getTask(userId, projectId, blockerId);
|
|
126
|
+
if (blockerResult.success) {
|
|
127
|
+
const blocker = blockerResult.data;
|
|
128
|
+
if (blocker.status !== 'done') {
|
|
129
|
+
allBlockersComplete = false;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!allBlockersComplete) {
|
|
135
|
+
continue; // Still blocked
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Match to expert worker
|
|
139
|
+
const worker = matchTaskToWorker({
|
|
140
|
+
title: task.title,
|
|
141
|
+
description: task.description || undefined,
|
|
142
|
+
domain: task.domain || undefined,
|
|
143
|
+
tags: parseJsonArray(task.tags),
|
|
144
|
+
});
|
|
145
|
+
// Get task context
|
|
146
|
+
const fullTask = await getTask(userId, projectId, task.id, true);
|
|
147
|
+
const taskData = fullTask.data;
|
|
148
|
+
dispatchable.push({
|
|
149
|
+
task,
|
|
150
|
+
worker,
|
|
151
|
+
context: taskData.context || [],
|
|
152
|
+
parentContext: taskData.parent,
|
|
153
|
+
dependencies: [],
|
|
154
|
+
});
|
|
155
|
+
if (dispatchable.length >= maxParallel) {
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return dispatchable;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get full execution context for a worker agent
|
|
163
|
+
*/
|
|
164
|
+
export async function getExecutionContext(userId, projectId, taskId) {
|
|
165
|
+
// Get full task data
|
|
166
|
+
const result = await getTask(userId, projectId, taskId, true);
|
|
167
|
+
if (!result.success) {
|
|
168
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
169
|
+
}
|
|
170
|
+
const taskData = result.data;
|
|
171
|
+
const task = taskData.task;
|
|
172
|
+
// Match to worker
|
|
173
|
+
const worker = matchTaskToWorker({
|
|
174
|
+
title: task.title,
|
|
175
|
+
description: task.description || undefined,
|
|
176
|
+
domain: task.domain || undefined,
|
|
177
|
+
tags: parseJsonArray(task.tags),
|
|
178
|
+
});
|
|
179
|
+
// Get sibling tasks for context
|
|
180
|
+
let siblingTasks;
|
|
181
|
+
if (task.parent_id) {
|
|
182
|
+
const siblingResult = await listTasks(userId, projectId, { parent_id: task.parent_id, limit: 20 });
|
|
183
|
+
if (siblingResult.success) {
|
|
184
|
+
siblingTasks = siblingResult.data.filter(t => t.id !== taskId);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// Build system prompt
|
|
188
|
+
let systemPrompt = worker.promptTemplate + '\n\n';
|
|
189
|
+
systemPrompt += `# Task: ${task.title}\n\n`;
|
|
190
|
+
if (task.description) {
|
|
191
|
+
systemPrompt += `## Description\n${task.description}\n\n`;
|
|
192
|
+
}
|
|
193
|
+
// Add acceptance criteria
|
|
194
|
+
const criteria = parseJsonArray(task.acceptance_criteria);
|
|
195
|
+
if (criteria.length > 0) {
|
|
196
|
+
systemPrompt += `## Acceptance Criteria\n`;
|
|
197
|
+
criteria.forEach((c, i) => {
|
|
198
|
+
systemPrompt += `${i + 1}. ${c}\n`;
|
|
199
|
+
});
|
|
200
|
+
systemPrompt += '\n';
|
|
201
|
+
}
|
|
202
|
+
// Add parent context
|
|
203
|
+
if (taskData.parent) {
|
|
204
|
+
systemPrompt += `## Epic Context\n`;
|
|
205
|
+
systemPrompt += `**Epic:** ${taskData.parent.title}\n`;
|
|
206
|
+
if (taskData.parent.description) {
|
|
207
|
+
systemPrompt += `**Goal:** ${taskData.parent.description}\n`;
|
|
208
|
+
}
|
|
209
|
+
systemPrompt += '\n';
|
|
210
|
+
}
|
|
211
|
+
// Add attached context
|
|
212
|
+
if (taskData.context.length > 0) {
|
|
213
|
+
systemPrompt += `## Attached Context\n`;
|
|
214
|
+
for (const ctx of taskData.context) {
|
|
215
|
+
systemPrompt += `### ${ctx.context_type.toUpperCase()}${ctx.source ? ` (${ctx.source})` : ''}\n`;
|
|
216
|
+
systemPrompt += `${ctx.content}\n\n`;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Add sibling task awareness
|
|
220
|
+
if (siblingTasks && siblingTasks.length > 0) {
|
|
221
|
+
systemPrompt += `## Related Tasks (for awareness)\n`;
|
|
222
|
+
for (const sibling of siblingTasks.slice(0, 5)) {
|
|
223
|
+
systemPrompt += `- ${sibling.title} [${sibling.status}]\n`;
|
|
224
|
+
}
|
|
225
|
+
systemPrompt += '\n';
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
task,
|
|
229
|
+
worker,
|
|
230
|
+
systemPrompt,
|
|
231
|
+
context: taskData.context,
|
|
232
|
+
parentTask: taskData.parent,
|
|
233
|
+
siblingTasks,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Find newly unblocked tasks after a completion
|
|
238
|
+
*/
|
|
239
|
+
export async function resolveTaskDependencies(userId, projectId, completedTaskId, epicId) {
|
|
240
|
+
// Get all tasks that might be blocked by the completed task
|
|
241
|
+
const result = await listTasks(userId, projectId, {
|
|
242
|
+
status: 'ready', // or 'blocked'
|
|
243
|
+
parent_id: epicId,
|
|
244
|
+
limit: 50,
|
|
245
|
+
});
|
|
246
|
+
if (!result.success) {
|
|
247
|
+
return [];
|
|
248
|
+
}
|
|
249
|
+
const newlyUnblocked = [];
|
|
250
|
+
for (const task of result.data) {
|
|
251
|
+
const blockedBy = parseJsonArray(task.blocked_by);
|
|
252
|
+
// Check if this task was blocked by the completed task
|
|
253
|
+
if (blockedBy.includes(completedTaskId)) {
|
|
254
|
+
// Check if all other blockers are also done
|
|
255
|
+
let allBlockersComplete = true;
|
|
256
|
+
for (const blockerId of blockedBy) {
|
|
257
|
+
if (blockerId === completedTaskId)
|
|
258
|
+
continue; // Already done
|
|
259
|
+
const blockerResult = await getTask(userId, projectId, blockerId);
|
|
260
|
+
if (blockerResult.success) {
|
|
261
|
+
const blocker = blockerResult.data;
|
|
262
|
+
if (blocker.status !== 'done') {
|
|
263
|
+
allBlockersComplete = false;
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (allBlockersComplete) {
|
|
269
|
+
newlyUnblocked.push(task);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return newlyUnblocked;
|
|
274
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ExpertWorker {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
domains: string[];
|
|
6
|
+
capabilities: string[];
|
|
7
|
+
promptTemplate: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const EXPERT_WORKERS: Record<string, ExpertWorker>;
|
|
10
|
+
/**
|
|
11
|
+
* Match a task to the best expert worker based on domain patterns
|
|
12
|
+
*/
|
|
13
|
+
export declare function matchTaskToWorker(task: {
|
|
14
|
+
title: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
domain?: string;
|
|
17
|
+
tags?: string[];
|
|
18
|
+
}): ExpertWorker;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Expert Worker Definitions (Orchestration System)
|
|
3
|
+
// =============================================================================
|
|
4
|
+
export const EXPERT_WORKERS = {
|
|
5
|
+
'schema-expert': {
|
|
6
|
+
id: 'schema-expert',
|
|
7
|
+
name: 'Database Schema Expert',
|
|
8
|
+
description: 'Specialises in database schema design, migrations, SQL, and data modelling',
|
|
9
|
+
domains: ['**/schema/**', '**/*.sql', '**/migrations/**', '**/models/**'],
|
|
10
|
+
capabilities: ['schema-design', 'sql-queries', 'migrations', 'indexing', 'data-modelling'],
|
|
11
|
+
promptTemplate: `You are a database schema expert. Your domain is limited to:
|
|
12
|
+
- SQL schema files and migrations
|
|
13
|
+
- Database table design and relationships
|
|
14
|
+
- Index optimisation
|
|
15
|
+
- Data modelling decisions
|
|
16
|
+
|
|
17
|
+
Focus only on schema-related changes. Do not modify application code outside your domain.
|
|
18
|
+
When complete, provide a concise summary of changes made.`,
|
|
19
|
+
},
|
|
20
|
+
'api-expert': {
|
|
21
|
+
id: 'api-expert',
|
|
22
|
+
name: 'API & Routes Expert',
|
|
23
|
+
description: 'Specialises in HTTP endpoints, route handlers, request/response handling',
|
|
24
|
+
domains: ['**/routes/**', '**/api/**', '**/handlers/**', '**/controllers/**'],
|
|
25
|
+
capabilities: ['rest-api', 'route-handlers', 'validation', 'error-handling', 'http-methods'],
|
|
26
|
+
promptTemplate: `You are an API and routes expert. Your domain is limited to:
|
|
27
|
+
- HTTP route definitions and handlers
|
|
28
|
+
- Request validation and parsing
|
|
29
|
+
- Response formatting
|
|
30
|
+
- Error handling for endpoints
|
|
31
|
+
- API documentation
|
|
32
|
+
|
|
33
|
+
Focus only on API-related changes. Do not modify schema or extraction code.
|
|
34
|
+
When complete, provide a concise summary of endpoints created or modified.`,
|
|
35
|
+
},
|
|
36
|
+
'mcp-expert': {
|
|
37
|
+
id: 'mcp-expert',
|
|
38
|
+
name: 'MCP Server Expert',
|
|
39
|
+
description: 'Specialises in MCP tool definitions, handlers, and server configuration',
|
|
40
|
+
domains: ['**/mcp-server/**', '**/mcp/**', '**/*mcp*.ts'],
|
|
41
|
+
capabilities: ['mcp-tools', 'mcp-handlers', 'tool-definitions', 'mcp-resources'],
|
|
42
|
+
promptTemplate: `You are an MCP (Model Context Protocol) expert. Your domain is limited to:
|
|
43
|
+
- MCP tool definitions and schemas
|
|
44
|
+
- Tool handler implementations
|
|
45
|
+
- MCP resources and prompts
|
|
46
|
+
- Server configuration
|
|
47
|
+
|
|
48
|
+
Focus only on MCP-related changes. Follow MCP SDK patterns exactly.
|
|
49
|
+
When complete, provide a concise summary of tools added or modified.`,
|
|
50
|
+
},
|
|
51
|
+
'extraction-expert': {
|
|
52
|
+
id: 'extraction-expert',
|
|
53
|
+
name: 'Code Extraction Expert',
|
|
54
|
+
description: 'Specialises in AST parsing, code analysis, and fact extraction',
|
|
55
|
+
domains: ['**/extraction/**', '**/parsers/**', '**/analyzers/**', '**/*extractor*.ts'],
|
|
56
|
+
capabilities: ['ast-parsing', 'code-analysis', 'fact-extraction', 'language-support'],
|
|
57
|
+
promptTemplate: `You are a code extraction expert. Your domain is limited to:
|
|
58
|
+
- AST parsing and traversal
|
|
59
|
+
- Code pattern recognition
|
|
60
|
+
- Fact and relationship extraction
|
|
61
|
+
- Multi-language support
|
|
62
|
+
|
|
63
|
+
Focus only on extraction logic. Follow the BaseExtractor pattern.
|
|
64
|
+
When complete, provide a concise summary of extraction capabilities added.`,
|
|
65
|
+
},
|
|
66
|
+
'integration-expert': {
|
|
67
|
+
id: 'integration-expert',
|
|
68
|
+
name: 'Integration Expert',
|
|
69
|
+
description: 'Specialises in connecting systems, wiring components, and configuration',
|
|
70
|
+
domains: ['**/config/**', '**/index.ts', '**/main.ts', '**/wrangler.toml', '**/*.json'],
|
|
71
|
+
capabilities: ['system-integration', 'configuration', 'wiring', 'dependency-injection'],
|
|
72
|
+
promptTemplate: `You are an integration expert. Your domain is limited to:
|
|
73
|
+
- Connecting components and systems
|
|
74
|
+
- Configuration files and environment setup
|
|
75
|
+
- Import/export wiring
|
|
76
|
+
- Dependency management
|
|
77
|
+
|
|
78
|
+
Focus on integration without modifying core implementation logic.
|
|
79
|
+
When complete, provide a concise summary of integrations configured.`,
|
|
80
|
+
},
|
|
81
|
+
'general-expert': {
|
|
82
|
+
id: 'general-expert',
|
|
83
|
+
name: 'General Purpose Expert',
|
|
84
|
+
description: 'Handles tasks that do not fit other expert domains',
|
|
85
|
+
domains: ['**/*'],
|
|
86
|
+
capabilities: ['general-development', 'refactoring', 'documentation', 'testing'],
|
|
87
|
+
promptTemplate: `You are a general purpose development expert. Handle this task with care:
|
|
88
|
+
- Follow existing code patterns
|
|
89
|
+
- Make minimal necessary changes
|
|
90
|
+
- Document your reasoning
|
|
91
|
+
|
|
92
|
+
When complete, provide a concise summary of work done.`,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Match a task to the best expert worker based on domain patterns
|
|
97
|
+
*/
|
|
98
|
+
export function matchTaskToWorker(task) {
|
|
99
|
+
// If task has explicit domain pattern, match against worker domains
|
|
100
|
+
if (task.domain) {
|
|
101
|
+
for (const [_, worker] of Object.entries(EXPERT_WORKERS)) {
|
|
102
|
+
if (worker.id === 'general-expert')
|
|
103
|
+
continue; // Skip fallback
|
|
104
|
+
for (const pattern of worker.domains) {
|
|
105
|
+
// Simple glob matching - convert ** to regex
|
|
106
|
+
const regex = new RegExp(pattern
|
|
107
|
+
.replace(/\*\*/g, '.*')
|
|
108
|
+
.replace(/\*/g, '[^/]*')
|
|
109
|
+
.replace(/\//g, '\\/'));
|
|
110
|
+
if (regex.test(task.domain)) {
|
|
111
|
+
return worker;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Keyword-based matching from title and description
|
|
117
|
+
const searchText = `${task.title} ${task.description || ''} ${(task.tags || []).join(' ')}`.toLowerCase();
|
|
118
|
+
const keywordMap = {
|
|
119
|
+
'schema': 'schema-expert',
|
|
120
|
+
'database': 'schema-expert',
|
|
121
|
+
'sql': 'schema-expert',
|
|
122
|
+
'migration': 'schema-expert',
|
|
123
|
+
'table': 'schema-expert',
|
|
124
|
+
'api': 'api-expert',
|
|
125
|
+
'route': 'api-expert',
|
|
126
|
+
'endpoint': 'api-expert',
|
|
127
|
+
'handler': 'api-expert',
|
|
128
|
+
'http': 'api-expert',
|
|
129
|
+
'mcp': 'mcp-expert',
|
|
130
|
+
'tool': 'mcp-expert',
|
|
131
|
+
'extraction': 'extraction-expert',
|
|
132
|
+
'extractor': 'extraction-expert',
|
|
133
|
+
'ast': 'extraction-expert',
|
|
134
|
+
'parser': 'extraction-expert',
|
|
135
|
+
'integration': 'integration-expert',
|
|
136
|
+
'config': 'integration-expert',
|
|
137
|
+
'wiring': 'integration-expert',
|
|
138
|
+
};
|
|
139
|
+
for (const [keyword, workerId] of Object.entries(keywordMap)) {
|
|
140
|
+
if (searchText.includes(keyword)) {
|
|
141
|
+
return EXPERT_WORKERS[workerId];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Fallback to general expert
|
|
145
|
+
return EXPERT_WORKERS['general-expert'];
|
|
146
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { ExpertWorker } from './helpers/workers.js';
|
|
2
|
+
export type { Task, TaskContext, DispatchableTask } from './helpers/tasks.js';
|
|
3
|
+
export { EXPERT_WORKERS, matchTaskToWorker } from './helpers/workers.js';
|
|
4
|
+
export { parseJsonArray, getDispatchableTasks, getExecutionContext, resolveTaskDependencies, createTask, listTasks, getTask, claimTask, releaseTask, updateTaskStatus, addTaskContext, getTaskSummary, heartbeatTask } from './helpers/tasks.js';
|
|
5
|
+
export { injectContext } from './helpers/api-client.js';
|
|
6
|
+
export declare function startServer(): Promise<void>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// ClaudeTools Memory MCP Server - Main Entry Point
|
|
3
|
+
// =============================================================================
|
|
4
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
5
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
6
|
+
import { mcpLogger } from './logger.js';
|
|
7
|
+
import { API_BASE_URL, getDefaultProjectIdAsync, AUTO_INJECT_CONTEXT, } from './helpers/config.js';
|
|
8
|
+
// Tool definitions and handlers
|
|
9
|
+
import { registerToolDefinitions } from './tools.js';
|
|
10
|
+
import { registerToolHandlers } from './handlers/tool-handlers.js';
|
|
11
|
+
// Resource and prompt handlers
|
|
12
|
+
import { registerResourceHandlers } from './resources.js';
|
|
13
|
+
import { registerPromptHandlers } from './prompts.js';
|
|
14
|
+
// Re-export functions that are used by handlers
|
|
15
|
+
export { EXPERT_WORKERS, matchTaskToWorker } from './helpers/workers.js';
|
|
16
|
+
export { parseJsonArray, getDispatchableTasks, getExecutionContext, resolveTaskDependencies, createTask, listTasks, getTask, claimTask, releaseTask, updateTaskStatus, addTaskContext, getTaskSummary, heartbeatTask } from './helpers/tasks.js';
|
|
17
|
+
export { injectContext } from './helpers/api-client.js';
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Server Initialization
|
|
20
|
+
// =============================================================================
|
|
21
|
+
const server = new Server({
|
|
22
|
+
name: 'claudetools-memory',
|
|
23
|
+
version: '1.0.0',
|
|
24
|
+
}, {
|
|
25
|
+
capabilities: {
|
|
26
|
+
tools: {},
|
|
27
|
+
resources: {},
|
|
28
|
+
prompts: {},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// Request Handler Registration
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// Tools
|
|
35
|
+
registerToolDefinitions(server);
|
|
36
|
+
registerToolHandlers(server);
|
|
37
|
+
// Resources
|
|
38
|
+
registerResourceHandlers(server);
|
|
39
|
+
// Prompts
|
|
40
|
+
registerPromptHandlers(server);
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// Server Start
|
|
43
|
+
// =============================================================================
|
|
44
|
+
export async function startServer() {
|
|
45
|
+
mcpLogger.separator('MCP Server Starting');
|
|
46
|
+
// Resolve project ID asynchronously (may auto-register)
|
|
47
|
+
let projectId;
|
|
48
|
+
try {
|
|
49
|
+
projectId = await getDefaultProjectIdAsync();
|
|
50
|
+
mcpLogger.info('TOOL', `Project ID resolved: ${projectId}`);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
mcpLogger.error('TOOL', `Failed to resolve project ID: ${error}`);
|
|
54
|
+
mcpLogger.warn('TOOL', 'Continuing startup - tools will fail until project is registered');
|
|
55
|
+
projectId = 'not-configured';
|
|
56
|
+
}
|
|
57
|
+
mcpLogger.info('TOOL', 'ClaudeTools Memory MCP Server v1.0.0', {
|
|
58
|
+
autoInject: AUTO_INJECT_CONTEXT,
|
|
59
|
+
apiUrl: API_BASE_URL,
|
|
60
|
+
projectId,
|
|
61
|
+
});
|
|
62
|
+
const transport = new StdioServerTransport();
|
|
63
|
+
await server.connect(transport);
|
|
64
|
+
mcpLogger.info('TOOL', 'Server connected via stdio');
|
|
65
|
+
console.error(`ClaudeTools Memory MCP server running on stdio (auto-inject: ${AUTO_INJECT_CONTEXT ? 'enabled' : 'disabled'})`);
|
|
66
|
+
}
|
|
67
|
+
// Only run if this file is executed directly (not imported)
|
|
68
|
+
// This check doesn't work reliably in ESM, so we'll rely on cli.ts
|
|
69
|
+
// But we keep this for backward compatibility when running index.ts directly
|
|
70
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
71
|
+
startServer().catch((error) => {
|
|
72
|
+
console.error('Fatal error:', error);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
});
|
|
75
|
+
}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type LogCategory = 'TOOL' | 'API' | 'MEMORY' | 'SEARCH' | 'INJECT' | 'EXTRACT' | 'STORE' | 'QUERY' | 'IMPACT' | 'PATTERN' | 'CONFIG' | 'REGISTRATION' | 'ERROR';
|
|
2
|
+
declare class MCPLogger {
|
|
3
|
+
private logFile;
|
|
4
|
+
private logDir;
|
|
5
|
+
private enabled;
|
|
6
|
+
constructor();
|
|
7
|
+
private formatTimestamp;
|
|
8
|
+
private formatForFile;
|
|
9
|
+
private formatDataAsNaturalLanguage;
|
|
10
|
+
private write;
|
|
11
|
+
debug(category: LogCategory, message: string, data?: Record<string, unknown>): void;
|
|
12
|
+
info(category: LogCategory, message: string, data?: Record<string, unknown>): void;
|
|
13
|
+
warn(category: LogCategory, message: string, data?: Record<string, unknown>): void;
|
|
14
|
+
error(category: LogCategory, message: string, error?: unknown): void;
|
|
15
|
+
toolCall(toolName: string, args: Record<string, unknown>): void;
|
|
16
|
+
toolResult(toolName: string, success: boolean, duration: number, resultSummary?: string): void;
|
|
17
|
+
apiRequest(method: string, endpoint: string): void;
|
|
18
|
+
apiResponse(method: string, endpoint: string, status: number, duration: number): void;
|
|
19
|
+
searchQuery(query: string, method: string): void;
|
|
20
|
+
searchResults(factsFound: number, entitiesFound: number, duration: number): void;
|
|
21
|
+
memoryStore(entity1: string, relation: string, entity2: string): void;
|
|
22
|
+
queryDependencies(functionName: string, direction: string, resultsCount: number, duration: number): void;
|
|
23
|
+
impactAnalysis(functionName: string, analysisType: string, riskLevel: string, totalAffected: number, duration: number): void;
|
|
24
|
+
patternCheck(codeLength: number, warningsFound: number, securityScore: number, perfScore: number, duration: number): void;
|
|
25
|
+
contextInjection(query: string, factsIncluded: number, tokenBudget: number, duration: number): void;
|
|
26
|
+
private sanitizeArgs;
|
|
27
|
+
startTimer(): () => number;
|
|
28
|
+
separator(label?: string): void;
|
|
29
|
+
clear(): void;
|
|
30
|
+
}
|
|
31
|
+
export declare const mcpLogger: MCPLogger;
|
|
32
|
+
export {};
|