@claudetools/tools 0.8.2 ā 0.8.4
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/cli.js +41 -0
- package/dist/context/deduplication.d.ts +72 -0
- package/dist/context/deduplication.js +77 -0
- package/dist/context/deduplication.test.d.ts +6 -0
- package/dist/context/deduplication.test.js +84 -0
- package/dist/context/emergency-eviction.d.ts +73 -0
- package/dist/context/emergency-eviction.example.d.ts +13 -0
- package/dist/context/emergency-eviction.example.js +94 -0
- package/dist/context/emergency-eviction.js +226 -0
- package/dist/context/eviction-engine.d.ts +76 -0
- package/dist/context/eviction-engine.example.d.ts +7 -0
- package/dist/context/eviction-engine.example.js +144 -0
- package/dist/context/eviction-engine.js +176 -0
- package/dist/context/example-usage.d.ts +1 -0
- package/dist/context/example-usage.js +128 -0
- package/dist/context/exchange-summariser.d.ts +80 -0
- package/dist/context/exchange-summariser.js +261 -0
- package/dist/context/health-monitor.d.ts +97 -0
- package/dist/context/health-monitor.example.d.ts +1 -0
- package/dist/context/health-monitor.example.js +164 -0
- package/dist/context/health-monitor.js +210 -0
- package/dist/context/importance-scorer.d.ts +94 -0
- package/dist/context/importance-scorer.example.d.ts +1 -0
- package/dist/context/importance-scorer.example.js +140 -0
- package/dist/context/importance-scorer.js +187 -0
- package/dist/context/index.d.ts +9 -0
- package/dist/context/index.js +16 -0
- package/dist/context/session-helper.d.ts +10 -0
- package/dist/context/session-helper.js +51 -0
- package/dist/context/session-store.d.ts +94 -0
- package/dist/context/session-store.js +286 -0
- package/dist/context/usage-estimator.d.ts +131 -0
- package/dist/context/usage-estimator.js +260 -0
- package/dist/context/usage-estimator.test.d.ts +1 -0
- package/dist/context/usage-estimator.test.js +208 -0
- package/dist/context-cli.d.ts +16 -0
- package/dist/context-cli.js +309 -0
- package/dist/evaluation/build-dataset.d.ts +1 -0
- package/dist/evaluation/build-dataset.js +135 -0
- package/dist/evaluation/threshold-eval.d.ts +63 -0
- package/dist/evaluation/threshold-eval.js +250 -0
- package/dist/handlers/codedna-handlers.d.ts +2 -2
- package/dist/handlers/tool-handlers.js +126 -165
- package/dist/helpers/api-client.d.ts +5 -1
- package/dist/helpers/api-client.js +3 -1
- package/dist/helpers/compact-formatter.d.ts +51 -0
- package/dist/helpers/compact-formatter.js +130 -0
- package/dist/helpers/engagement-tracker.d.ts +10 -0
- package/dist/helpers/engagement-tracker.js +61 -0
- package/dist/helpers/error-tracking.js +1 -1
- package/dist/helpers/session-validation.d.ts +76 -0
- package/dist/helpers/session-validation.js +221 -0
- package/dist/helpers/usage-analytics.js +1 -1
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/post-tool-use-hook-cli.d.ts +2 -0
- package/dist/hooks/post-tool-use-hook-cli.js +34 -0
- package/dist/hooks/post-tool-use.d.ts +67 -0
- package/dist/hooks/post-tool-use.js +234 -0
- package/dist/hooks/stop-hook-cli.d.ts +2 -0
- package/dist/hooks/stop-hook-cli.js +34 -0
- package/dist/hooks/stop.d.ts +64 -0
- package/dist/hooks/stop.js +192 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js +4 -0
- package/dist/resources.js +3 -0
- package/dist/setup.js +206 -2
- package/dist/templates/claude-md.d.ts +1 -1
- package/dist/templates/claude-md.js +23 -35
- package/dist/templates/worker-prompt.js +35 -202
- package/dist/tools.js +26 -20
- package/package.json +6 -2
|
@@ -7,9 +7,11 @@ import { getDefaultProjectId, DEFAULT_USER_ID, lastContextUsed, setLastContextUs
|
|
|
7
7
|
import { EXPERT_WORKERS, matchTaskToWorker, buildWorkerPrompt } from '../helpers/workers.js';
|
|
8
8
|
import { validateTaskDescription } from '../templates/orchestrator-prompt.js';
|
|
9
9
|
import { searchMemory, addMemory, storeFact, getContext, getSummary, getEntities, injectContext, apiRequest, listCachedDocs, getCachedDocs, cacheDocs, getMemoryIndex, getMemoryDetail, getDisclosureAnalytics } from '../helpers/api-client.js';
|
|
10
|
+
import { recordToolCall, getToolCallWarnings } from '../helpers/session-validation.js';
|
|
10
11
|
import { queryDependencies, analyzeImpact } from '../helpers/dependencies.js';
|
|
11
12
|
import { checkPatterns } from '../helpers/patterns.js';
|
|
12
13
|
import { formatContextForClaude } from '../helpers/formatter.js';
|
|
14
|
+
import { compactTaskList, compactTaskCreated, compactTaskStart, compactTaskComplete, compactTaskClaim, compactTaskRelease, compactStatusUpdate, compactContextAdded, compactHeartbeat, compactSummary, } from '../helpers/compact-formatter.js';
|
|
13
15
|
import { createTask, listTasks, getTask, claimTask, releaseTask, updateTaskStatus, addTaskContext, getTaskSummary, heartbeatTask, parseJsonArray, getDispatchableTasks, getExecutionContext, resolveTaskDependencies, getEpicStatus, getActiveTaskCount, } from '../helpers/tasks.js';
|
|
14
16
|
import { detectTimedOutTasks, retryTask, failTask, autoRetryTimedOutTasks, } from '../helpers/tasks-retry.js';
|
|
15
17
|
import { detectLibrariesFromPlan } from '../helpers/library-detection.js';
|
|
@@ -38,6 +40,10 @@ export function registerToolHandlers(server) {
|
|
|
38
40
|
const timer = mcpLogger.startTimer();
|
|
39
41
|
// Log tool call
|
|
40
42
|
mcpLogger.toolCall(name, args || {});
|
|
43
|
+
// Record tool call in session for sequence validation
|
|
44
|
+
recordToolCall(name, args);
|
|
45
|
+
// Get workflow warnings (if any) for this tool call
|
|
46
|
+
const workflowWarnings = getToolCallWarnings(name, args);
|
|
41
47
|
try {
|
|
42
48
|
switch (name) {
|
|
43
49
|
case 'memory_explain': {
|
|
@@ -126,17 +132,89 @@ export function registerToolHandlers(server) {
|
|
|
126
132
|
const relationship = args?.relationship;
|
|
127
133
|
const entity2 = args?.entity2;
|
|
128
134
|
const context = args?.context;
|
|
129
|
-
const
|
|
135
|
+
const is_critical = args?.is_critical;
|
|
136
|
+
// Commercial-grade storage with blocking verification
|
|
137
|
+
const MAX_RETRIES = 3;
|
|
138
|
+
const VERIFY_DELAY_MS = 200;
|
|
139
|
+
let lastError = null;
|
|
140
|
+
let storedFactId = null;
|
|
141
|
+
let storedIsCritical = false;
|
|
142
|
+
let verified = false;
|
|
143
|
+
let attempts = 0;
|
|
144
|
+
for (let attempt = 1; attempt <= MAX_RETRIES && !verified; attempt++) {
|
|
145
|
+
attempts = attempt;
|
|
146
|
+
try {
|
|
147
|
+
// Step 1: Store the fact
|
|
148
|
+
mcpLogger.info('STORE', `Attempt ${attempt}/${MAX_RETRIES}: Storing "${entity1} ${relationship} ${entity2}"${is_critical ? ' [CRITICAL]' : ''}`);
|
|
149
|
+
const result = await storeFact(projectId, entity1, relationship, entity2, context, { is_critical });
|
|
150
|
+
storedFactId = result.fact_id;
|
|
151
|
+
storedIsCritical = result.is_critical;
|
|
152
|
+
mcpLogger.info('STORE', `Storage response: ${JSON.stringify(result)}`);
|
|
153
|
+
if (!result.success || !result.fact_id) {
|
|
154
|
+
lastError = new Error(`Storage returned unsuccessful: ${JSON.stringify(result)}`);
|
|
155
|
+
mcpLogger.warn('STORE', `Attempt ${attempt} failed: ${lastError.message}`);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
// Step 2: Wait briefly for eventual consistency
|
|
159
|
+
await new Promise(resolve => setTimeout(resolve, VERIFY_DELAY_MS));
|
|
160
|
+
// Step 3: Verify the fact is retrievable by searching for it
|
|
161
|
+
mcpLogger.info('STORE', `Verifying fact ${storedFactId} is retrievable...`);
|
|
162
|
+
const searchQuery = `${entity1} ${relationship} ${entity2}`;
|
|
163
|
+
const searchResult = await searchMemory(projectId, searchQuery, 5);
|
|
164
|
+
// Check if our fact appears in results
|
|
165
|
+
const factFound = searchResult.relevant_facts?.some(f => f.fact?.includes(entity1) && f.fact?.includes(entity2)) || false;
|
|
166
|
+
if (factFound) {
|
|
167
|
+
verified = true;
|
|
168
|
+
mcpLogger.info('STORE', `ā Fact verified as retrievable`);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
lastError = new Error(`Fact stored but not found in search results`);
|
|
172
|
+
mcpLogger.warn('STORE', `Attempt ${attempt}: Stored but not retrievable. Search returned ${searchResult.relevant_facts?.length || 0} facts.`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
177
|
+
mcpLogger.error('STORE', `Attempt ${attempt} error: ${lastError.message}`, err);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
130
180
|
mcpLogger.memoryStore(entity1, relationship, entity2);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
181
|
+
const criticalTag = storedIsCritical ? ' š“ CRITICAL' : '';
|
|
182
|
+
if (verified && storedFactId) {
|
|
183
|
+
mcpLogger.toolResult(name, true, timer(), `ID: ${storedFactId} (verified)${criticalTag}`);
|
|
184
|
+
return {
|
|
185
|
+
content: [
|
|
186
|
+
{
|
|
187
|
+
type: 'text',
|
|
188
|
+
text: `ā Stored and verified: "${entity1} ${relationship} ${entity2}" (ID: ${storedFactId})${criticalTag}`,
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
else if (storedFactId) {
|
|
194
|
+
// Stored but couldn't verify - warn but don't fail
|
|
195
|
+
mcpLogger.toolResult(name, true, timer(), `ID: ${storedFactId} (unverified)${criticalTag}`);
|
|
196
|
+
return {
|
|
197
|
+
content: [
|
|
198
|
+
{
|
|
199
|
+
type: 'text',
|
|
200
|
+
text: `ā ļø Stored but verification pending: "${entity1} ${relationship} ${entity2}" (ID: ${storedFactId})${criticalTag}\nNote: Fact may take a moment to become searchable.`,
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
// Complete failure after all retries
|
|
207
|
+
mcpLogger.toolResult(name, false, timer(), `Failed after ${attempts} attempts`);
|
|
208
|
+
return {
|
|
209
|
+
content: [
|
|
210
|
+
{
|
|
211
|
+
type: 'text',
|
|
212
|
+
text: `ā Failed to store fact after ${attempts} attempts: "${entity1} ${relationship} ${entity2}"\nError: ${lastError?.message || 'Unknown error'}`,
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
isError: true,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
140
218
|
}
|
|
141
219
|
case 'memory_get_context': {
|
|
142
220
|
const query = args?.query;
|
|
@@ -758,30 +836,16 @@ export function registerToolHandlers(server) {
|
|
|
758
836
|
await updateTaskStatus(DEFAULT_USER_ID, projectId, taskId, 'in_progress', agentId);
|
|
759
837
|
mcpLogger.toolResult(name, true, timer());
|
|
760
838
|
const data = claimResult.data;
|
|
761
|
-
|
|
762
|
-
output
|
|
763
|
-
output += `**Status:** in_progress\n`;
|
|
764
|
-
output += `**Lock expires:** ${data.lock_expires_at}\n\n`;
|
|
839
|
+
// Compact output: just essentials
|
|
840
|
+
let output = compactTaskStart(data.task, data.lock_expires_at);
|
|
765
841
|
if (data.task.description) {
|
|
766
|
-
output +=
|
|
842
|
+
output += `\n${data.task.description}`;
|
|
767
843
|
}
|
|
844
|
+
// Only show criteria if present (compact)
|
|
768
845
|
const criteria = parseJsonArray(data.task.acceptance_criteria);
|
|
769
846
|
if (criteria.length) {
|
|
770
|
-
output +=
|
|
771
|
-
criteria.forEach((c, i) => {
|
|
772
|
-
output += `${i + 1}. ${c}\n`;
|
|
773
|
-
});
|
|
774
|
-
output += '\n';
|
|
775
|
-
}
|
|
776
|
-
if (data.context?.length) {
|
|
777
|
-
output += `## Previous Context\n`;
|
|
778
|
-
data.context.forEach((c) => {
|
|
779
|
-
output += `### ${c.context_type} (${c.added_by})\n`;
|
|
780
|
-
output += `${c.content}\n\n`;
|
|
781
|
-
});
|
|
847
|
+
output += `\nCriteria: ${criteria.join('; ')}`;
|
|
782
848
|
}
|
|
783
|
-
output += `---\n`;
|
|
784
|
-
output += `Use \`task_complete\` when done, or \`task_add_context\` to log progress.\n`;
|
|
785
849
|
return {
|
|
786
850
|
content: [{ type: 'text', text: output }],
|
|
787
851
|
};
|
|
@@ -798,25 +862,8 @@ export function registerToolHandlers(server) {
|
|
|
798
862
|
// Check for newly unblocked tasks (orchestration awareness)
|
|
799
863
|
const newlyUnblocked = await resolveTaskDependencies(DEFAULT_USER_ID, projectId, taskId, task.parent_id || undefined);
|
|
800
864
|
mcpLogger.toolResult(name, true, timer());
|
|
801
|
-
|
|
802
|
-
output
|
|
803
|
-
output += `**Status:** done ā
\n\n`;
|
|
804
|
-
output += `## Summary\n${summary}\n\n`;
|
|
805
|
-
// Show newly unblocked tasks for orchestration
|
|
806
|
-
if (newlyUnblocked.length > 0) {
|
|
807
|
-
output += `---\n\n`;
|
|
808
|
-
output += `## š Newly Unblocked Tasks (${newlyUnblocked.length})\n\n`;
|
|
809
|
-
for (const unblocked of newlyUnblocked) {
|
|
810
|
-
const worker = matchTaskToWorker({
|
|
811
|
-
title: unblocked.title,
|
|
812
|
-
description: unblocked.description || undefined,
|
|
813
|
-
domain: unblocked.domain || undefined,
|
|
814
|
-
});
|
|
815
|
-
output += `- **${unblocked.title}** (\`${unblocked.id}\`)\n`;
|
|
816
|
-
output += ` ā Worker: ${worker.name}\n`;
|
|
817
|
-
}
|
|
818
|
-
output += `\n**Next:** Use \`task_dispatch\` to spawn parallel workers for unblocked tasks.\n`;
|
|
819
|
-
}
|
|
865
|
+
// Compact output
|
|
866
|
+
const output = compactTaskComplete(task, summary, newlyUnblocked);
|
|
820
867
|
return {
|
|
821
868
|
content: [{ type: 'text', text: output }],
|
|
822
869
|
};
|
|
@@ -843,24 +890,8 @@ export function registerToolHandlers(server) {
|
|
|
843
890
|
blocked_by: blockedBy,
|
|
844
891
|
});
|
|
845
892
|
mcpLogger.toolResult(name, true, timer());
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
output += `**ID:** ${task.id}\n`;
|
|
849
|
-
output += `**Type:** ${task.type}\n`;
|
|
850
|
-
output += `**Title:** ${task.title}\n`;
|
|
851
|
-
output += `**Status:** ${task.status}\n`;
|
|
852
|
-
output += `**Priority:** ${task.priority}\n`;
|
|
853
|
-
if (task.parent_id)
|
|
854
|
-
output += `**Parent:** ${task.parent_id}\n`;
|
|
855
|
-
if (task.description)
|
|
856
|
-
output += `\n**Description:**\n${task.description}\n`;
|
|
857
|
-
const taskCriteria = parseJsonArray(task.acceptance_criteria);
|
|
858
|
-
if (taskCriteria.length) {
|
|
859
|
-
output += `\n**Acceptance Criteria:**\n`;
|
|
860
|
-
taskCriteria.forEach((c, i) => {
|
|
861
|
-
output += `${i + 1}. ${c}\n`;
|
|
862
|
-
});
|
|
863
|
-
}
|
|
893
|
+
// Compact output
|
|
894
|
+
const output = compactTaskCreated(result.data);
|
|
864
895
|
return {
|
|
865
896
|
content: [{ type: 'text', text: output }],
|
|
866
897
|
};
|
|
@@ -875,45 +906,8 @@ export function registerToolHandlers(server) {
|
|
|
875
906
|
};
|
|
876
907
|
const result = await listTasks(DEFAULT_USER_ID, projectId, filters);
|
|
877
908
|
mcpLogger.toolResult(name, true, timer());
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
if (tasks.length === 0) {
|
|
881
|
-
output += `No tasks found matching the filters.\n`;
|
|
882
|
-
}
|
|
883
|
-
else {
|
|
884
|
-
// Group by type for better organization
|
|
885
|
-
const epics = tasks.filter(t => t.type === 'epic');
|
|
886
|
-
const regularTasks = tasks.filter(t => t.type === 'task');
|
|
887
|
-
const subtasks = tasks.filter(t => t.type === 'subtask');
|
|
888
|
-
const formatTask = (t) => {
|
|
889
|
-
const statusEmoji = {
|
|
890
|
-
backlog: 'š', ready: 'š¢', in_progress: 'š',
|
|
891
|
-
blocked: 'š«', review: 'š', done: 'ā
', cancelled: 'ā'
|
|
892
|
-
};
|
|
893
|
-
const emoji = statusEmoji[t.status] || 'š';
|
|
894
|
-
let line = `- ${emoji} **${t.title}** (\`${t.id}\`)`;
|
|
895
|
-
line += ` [${t.status}]`;
|
|
896
|
-
if (t.priority !== 'medium')
|
|
897
|
-
line += ` [${t.priority}]`;
|
|
898
|
-
if (t.assigned_to)
|
|
899
|
-
line += ` ā ${t.assigned_to}`;
|
|
900
|
-
return line;
|
|
901
|
-
};
|
|
902
|
-
if (epics.length > 0) {
|
|
903
|
-
output += `## Epics\n`;
|
|
904
|
-
epics.forEach(t => { output += formatTask(t) + '\n'; });
|
|
905
|
-
output += '\n';
|
|
906
|
-
}
|
|
907
|
-
if (regularTasks.length > 0) {
|
|
908
|
-
output += `## Tasks\n`;
|
|
909
|
-
regularTasks.forEach(t => { output += formatTask(t) + '\n'; });
|
|
910
|
-
output += '\n';
|
|
911
|
-
}
|
|
912
|
-
if (subtasks.length > 0) {
|
|
913
|
-
output += `## Subtasks\n`;
|
|
914
|
-
subtasks.forEach(t => { output += formatTask(t) + '\n'; });
|
|
915
|
-
}
|
|
916
|
-
}
|
|
909
|
+
// Compact output
|
|
910
|
+
const output = compactTaskList(result.data);
|
|
917
911
|
return {
|
|
918
912
|
content: [{ type: 'text', text: output }],
|
|
919
913
|
};
|
|
@@ -987,17 +981,8 @@ export function registerToolHandlers(server) {
|
|
|
987
981
|
const result = await claimTask(DEFAULT_USER_ID, projectId, taskId, agentId, lockDuration);
|
|
988
982
|
mcpLogger.toolResult(name, true, timer());
|
|
989
983
|
const data = result.data;
|
|
990
|
-
|
|
991
|
-
output
|
|
992
|
-
output += `**Claimed:** ${data.claimed ? 'ā
Yes' : 'ā No'}\n`;
|
|
993
|
-
output += `**Lock Expires:** ${data.lock_expires_at}\n`;
|
|
994
|
-
output += `**Agent:** ${agentId}\n`;
|
|
995
|
-
if (data.context?.length) {
|
|
996
|
-
output += `\n## Previous Context\n`;
|
|
997
|
-
data.context.forEach(c => {
|
|
998
|
-
output += `### ${c.context_type}\n${c.content}\n\n`;
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
984
|
+
// Compact output
|
|
985
|
+
const output = compactTaskClaim(data.task, data.claimed, data.lock_expires_at);
|
|
1001
986
|
return {
|
|
1002
987
|
content: [{ type: 'text', text: output }],
|
|
1003
988
|
};
|
|
@@ -1009,13 +994,8 @@ export function registerToolHandlers(server) {
|
|
|
1009
994
|
const workLog = args?.work_log;
|
|
1010
995
|
const result = await releaseTask(DEFAULT_USER_ID, projectId, taskId, agentId, newStatus, workLog);
|
|
1011
996
|
mcpLogger.toolResult(name, true, timer());
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
output += `**Task:** ${data.task.title}\n`;
|
|
1015
|
-
output += `**Released:** ${data.released ? 'ā
Yes' : 'ā No'}\n`;
|
|
1016
|
-
output += `**New Status:** ${data.task.status}\n`;
|
|
1017
|
-
if (workLog)
|
|
1018
|
-
output += `**Work Log:** ${workLog}\n`;
|
|
997
|
+
// Compact output
|
|
998
|
+
const output = compactTaskRelease(result.data.task, result.data.released);
|
|
1019
999
|
return {
|
|
1020
1000
|
content: [{ type: 'text', text: output }],
|
|
1021
1001
|
};
|
|
@@ -1026,11 +1006,8 @@ export function registerToolHandlers(server) {
|
|
|
1026
1006
|
const agentId = args?.agent_id;
|
|
1027
1007
|
const result = await updateTaskStatus(DEFAULT_USER_ID, projectId, taskId, status, agentId);
|
|
1028
1008
|
mcpLogger.toolResult(name, true, timer());
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
output += `**Task:** ${task.title}\n`;
|
|
1032
|
-
output += `**New Status:** ${task.status}\n`;
|
|
1033
|
-
output += `**Updated At:** ${task.updated_at}\n`;
|
|
1009
|
+
// Compact output
|
|
1010
|
+
const output = compactStatusUpdate(result.data);
|
|
1034
1011
|
return {
|
|
1035
1012
|
content: [{ type: 'text', text: output }],
|
|
1036
1013
|
};
|
|
@@ -1043,14 +1020,8 @@ export function registerToolHandlers(server) {
|
|
|
1043
1020
|
const source = args?.source;
|
|
1044
1021
|
const result = await addTaskContext(DEFAULT_USER_ID, projectId, taskId, contextType, content, addedBy, source);
|
|
1045
1022
|
mcpLogger.toolResult(name, true, timer());
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
output += `**ID:** ${ctx.id}\n`;
|
|
1049
|
-
output += `**Type:** ${ctx.context_type}\n`;
|
|
1050
|
-
output += `**Added By:** ${ctx.added_by}\n`;
|
|
1051
|
-
if (ctx.source)
|
|
1052
|
-
output += `**Source:** ${ctx.source}\n`;
|
|
1053
|
-
output += `\n**Content:**\n${ctx.content}\n`;
|
|
1023
|
+
// Compact output
|
|
1024
|
+
const output = compactContextAdded(result.data);
|
|
1054
1025
|
return {
|
|
1055
1026
|
content: [{ type: 'text', text: output }],
|
|
1056
1027
|
};
|
|
@@ -1058,29 +1029,9 @@ export function registerToolHandlers(server) {
|
|
|
1058
1029
|
case 'task_summary': {
|
|
1059
1030
|
const result = await getTaskSummary(DEFAULT_USER_ID, projectId);
|
|
1060
1031
|
mcpLogger.toolResult(name, true, timer());
|
|
1032
|
+
// Compact output
|
|
1061
1033
|
const data = result.data;
|
|
1062
|
-
|
|
1063
|
-
output += `## By Status\n`;
|
|
1064
|
-
const statusEmoji = {
|
|
1065
|
-
backlog: 'š', ready: 'š¢', in_progress: 'š',
|
|
1066
|
-
blocked: 'š«', review: 'š', done: 'ā
', cancelled: 'ā'
|
|
1067
|
-
};
|
|
1068
|
-
Object.entries(data.by_status).forEach(([status, count]) => {
|
|
1069
|
-
const emoji = statusEmoji[status] || 'š';
|
|
1070
|
-
output += `- ${emoji} ${status}: **${count}**\n`;
|
|
1071
|
-
});
|
|
1072
|
-
output += `\n## By Type\n`;
|
|
1073
|
-
Object.entries(data.by_type).forEach(([type, count]) => {
|
|
1074
|
-
output += `- ${type}: **${count}**\n`;
|
|
1075
|
-
});
|
|
1076
|
-
output += `\n## Active Agents\n`;
|
|
1077
|
-
output += `**${data.active_agents}** agent(s) currently working on tasks\n`;
|
|
1078
|
-
if (data.recent_events?.length) {
|
|
1079
|
-
output += `\n## Recent Activity\n`;
|
|
1080
|
-
data.recent_events.slice(0, 5).forEach(e => {
|
|
1081
|
-
output += `- ${e.event_type}: ${e.task_title} (${new Date(e.created_at).toLocaleString()})\n`;
|
|
1082
|
-
});
|
|
1083
|
-
}
|
|
1034
|
+
const output = compactSummary(data.by_status, data.by_type, data.active_agents);
|
|
1084
1035
|
return {
|
|
1085
1036
|
content: [{ type: 'text', text: output }],
|
|
1086
1037
|
};
|
|
@@ -1091,10 +1042,8 @@ export function registerToolHandlers(server) {
|
|
|
1091
1042
|
const extendMinutes = args?.extend_minutes || 30;
|
|
1092
1043
|
const result = await heartbeatTask(DEFAULT_USER_ID, projectId, taskId, agentId, extendMinutes);
|
|
1093
1044
|
mcpLogger.toolResult(name, true, timer());
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
output += `**Extended:** ${data.extended ? 'ā
Yes' : 'ā No'}\n`;
|
|
1097
|
-
output += `**New Expiry:** ${data.new_expires_at}\n`;
|
|
1045
|
+
// Compact output
|
|
1046
|
+
const output = compactHeartbeat(result.data.extended, result.data.new_expires_at);
|
|
1098
1047
|
return {
|
|
1099
1048
|
content: [{ type: 'text', text: output }],
|
|
1100
1049
|
};
|
|
@@ -1808,30 +1757,42 @@ export function registerToolHandlers(server) {
|
|
|
1808
1757
|
case 'codedna_generate_api': {
|
|
1809
1758
|
const result = await handleGenerateApi(args);
|
|
1810
1759
|
mcpLogger.toolResult(name, true, timer());
|
|
1760
|
+
let output = JSON.stringify(result, null, 2);
|
|
1761
|
+
if (workflowWarnings) {
|
|
1762
|
+
output += '\n\n' + workflowWarnings;
|
|
1763
|
+
}
|
|
1811
1764
|
return {
|
|
1812
1765
|
content: [{
|
|
1813
1766
|
type: 'text',
|
|
1814
|
-
text:
|
|
1767
|
+
text: output,
|
|
1815
1768
|
}],
|
|
1816
1769
|
};
|
|
1817
1770
|
}
|
|
1818
1771
|
case 'codedna_generate_frontend': {
|
|
1819
1772
|
const result = await handleGenerateFrontend(args);
|
|
1820
1773
|
mcpLogger.toolResult(name, true, timer());
|
|
1774
|
+
let output = JSON.stringify(result, null, 2);
|
|
1775
|
+
if (workflowWarnings) {
|
|
1776
|
+
output += '\n\n' + workflowWarnings;
|
|
1777
|
+
}
|
|
1821
1778
|
return {
|
|
1822
1779
|
content: [{
|
|
1823
1780
|
type: 'text',
|
|
1824
|
-
text:
|
|
1781
|
+
text: output,
|
|
1825
1782
|
}],
|
|
1826
1783
|
};
|
|
1827
1784
|
}
|
|
1828
1785
|
case 'codedna_generate_component': {
|
|
1829
1786
|
const result = await handleGenerateComponent(args);
|
|
1830
1787
|
mcpLogger.toolResult(name, true, timer());
|
|
1788
|
+
let output = JSON.stringify(result, null, 2);
|
|
1789
|
+
if (workflowWarnings) {
|
|
1790
|
+
output += '\n\n' + workflowWarnings;
|
|
1791
|
+
}
|
|
1831
1792
|
return {
|
|
1832
1793
|
content: [{
|
|
1833
1794
|
type: 'text',
|
|
1834
|
-
text:
|
|
1795
|
+
text: output,
|
|
1835
1796
|
}],
|
|
1836
1797
|
};
|
|
1837
1798
|
}
|
|
@@ -26,9 +26,13 @@ export declare function addMemory(projectId: string, sessionId: string, messages
|
|
|
26
26
|
episode_ids: string[];
|
|
27
27
|
}>;
|
|
28
28
|
export declare function searchMemory(projectId: string, query: string, limit?: number, userId?: string): Promise<MemoryContext>;
|
|
29
|
-
export declare function storeFact(projectId: string, entity1: string, relationship: string, entity2: string, context: string,
|
|
29
|
+
export declare function storeFact(projectId: string, entity1: string, relationship: string, entity2: string, context: string, options?: {
|
|
30
|
+
userId?: string;
|
|
31
|
+
is_critical?: boolean;
|
|
32
|
+
}): Promise<{
|
|
30
33
|
success: boolean;
|
|
31
34
|
fact_id: string;
|
|
35
|
+
is_critical: boolean;
|
|
32
36
|
}>;
|
|
33
37
|
export declare function getContext(projectId: string, query?: string, userId?: string): Promise<MemoryContext>;
|
|
34
38
|
export declare function getSummary(projectId: string, userId?: string): Promise<string>;
|
|
@@ -39,12 +39,14 @@ export async function searchMemory(projectId, query, limit = 10, userId = DEFAUL
|
|
|
39
39
|
limit,
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
|
-
export async function storeFact(projectId, entity1, relationship, entity2, context,
|
|
42
|
+
export async function storeFact(projectId, entity1, relationship, entity2, context, options = {}) {
|
|
43
|
+
const userId = options.userId ?? DEFAULT_USER_ID;
|
|
43
44
|
return apiRequest(`/api/v1/memory/${userId}/${projectId}/fact`, 'POST', {
|
|
44
45
|
entity1,
|
|
45
46
|
relationship,
|
|
46
47
|
entity2,
|
|
47
48
|
context,
|
|
49
|
+
...(options.is_critical !== undefined && { is_critical: options.is_critical }),
|
|
48
50
|
});
|
|
49
51
|
}
|
|
50
52
|
export async function getContext(projectId, query, userId = DEFAULT_USER_ID) {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Task, TaskContext } from './tasks.js';
|
|
2
|
+
/** Truncate task ID to first 8 chars */
|
|
3
|
+
export declare function shortId(id: string): string;
|
|
4
|
+
/** Format task status with icon */
|
|
5
|
+
export declare function statusIcon(status: string): string;
|
|
6
|
+
/** Single-line task representation */
|
|
7
|
+
export declare function compactTask(t: Task): string;
|
|
8
|
+
/** Compact task list - one line per task */
|
|
9
|
+
export declare function compactTaskList(tasks: Task[]): string;
|
|
10
|
+
/** Compact task creation result */
|
|
11
|
+
export declare function compactTaskCreated(task: Task): string;
|
|
12
|
+
/** Compact task start result */
|
|
13
|
+
export declare function compactTaskStart(task: Task, lockExpires: string): string;
|
|
14
|
+
/** Compact task complete result */
|
|
15
|
+
export declare function compactTaskComplete(task: Task, summary: string, unblocked?: Task[]): string;
|
|
16
|
+
/** Compact task claim result */
|
|
17
|
+
export declare function compactTaskClaim(task: Task, claimed: boolean, expires: string): string;
|
|
18
|
+
/** Compact task release result */
|
|
19
|
+
export declare function compactTaskRelease(task: Task, released: boolean): string;
|
|
20
|
+
/** Compact status update */
|
|
21
|
+
export declare function compactStatusUpdate(task: Task): string;
|
|
22
|
+
/** Compact context added */
|
|
23
|
+
export declare function compactContextAdded(ctx: TaskContext): string;
|
|
24
|
+
/** Compact heartbeat */
|
|
25
|
+
export declare function compactHeartbeat(extended: boolean, expires: string): string;
|
|
26
|
+
/** Compact memory search result */
|
|
27
|
+
export declare function compactMemorySearch(facts: {
|
|
28
|
+
fact: string;
|
|
29
|
+
}[], entities: {
|
|
30
|
+
name: string;
|
|
31
|
+
}[]): string;
|
|
32
|
+
/** Compact memory store result */
|
|
33
|
+
export declare function compactFactStored(entity1: string, rel: string, entity2: string, factId: string): string;
|
|
34
|
+
/** Compact summary stats */
|
|
35
|
+
export declare function compactSummary(byStatus: Record<string, number>, byType: Record<string, number>, activeAgents: number): string;
|
|
36
|
+
/** Compact epic progress */
|
|
37
|
+
export declare function compactEpicStatus(title: string, progress: number, stats: Record<string, number>): string;
|
|
38
|
+
/** Compact dispatch list */
|
|
39
|
+
export declare function compactDispatch(items: {
|
|
40
|
+
task: Task;
|
|
41
|
+
worker: {
|
|
42
|
+
name: string;
|
|
43
|
+
};
|
|
44
|
+
}[]): string;
|
|
45
|
+
/** Compact memory index */
|
|
46
|
+
export declare function compactMemoryIndex(entries: {
|
|
47
|
+
id: string;
|
|
48
|
+
summary: string;
|
|
49
|
+
relevance: number;
|
|
50
|
+
is_critical?: boolean;
|
|
51
|
+
}[]): string;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Compact Response Formatter
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Minimizes tool output to conserve context window.
|
|
5
|
+
// Every byte counts - prefer single-line responses where possible.
|
|
6
|
+
const STATUS_ICONS = {
|
|
7
|
+
backlog: 'š',
|
|
8
|
+
ready: 'š¢',
|
|
9
|
+
in_progress: 'š',
|
|
10
|
+
blocked: 'š«',
|
|
11
|
+
review: 'š',
|
|
12
|
+
done: 'ā
',
|
|
13
|
+
cancelled: 'ā',
|
|
14
|
+
};
|
|
15
|
+
/** Truncate task ID to first 8 chars */
|
|
16
|
+
export function shortId(id) {
|
|
17
|
+
return id.slice(0, 8);
|
|
18
|
+
}
|
|
19
|
+
/** Format task status with icon */
|
|
20
|
+
export function statusIcon(status) {
|
|
21
|
+
return STATUS_ICONS[status] || 'š';
|
|
22
|
+
}
|
|
23
|
+
/** Single-line task representation */
|
|
24
|
+
export function compactTask(t) {
|
|
25
|
+
return `${statusIcon(t.status)} ${t.title} (${shortId(t.id)}) [${t.status}]`;
|
|
26
|
+
}
|
|
27
|
+
/** Compact task list - one line per task */
|
|
28
|
+
export function compactTaskList(tasks) {
|
|
29
|
+
if (tasks.length === 0)
|
|
30
|
+
return 'No tasks found.';
|
|
31
|
+
return tasks.map(t => compactTask(t)).join('\n');
|
|
32
|
+
}
|
|
33
|
+
/** Compact task creation result */
|
|
34
|
+
export function compactTaskCreated(task) {
|
|
35
|
+
return `Created: ${task.type} "${task.title}" ā ${shortId(task.id)}`;
|
|
36
|
+
}
|
|
37
|
+
/** Compact task start result */
|
|
38
|
+
export function compactTaskStart(task, lockExpires) {
|
|
39
|
+
return `Started: ${task.title} (${shortId(task.id)}) lock until ${new Date(lockExpires).toLocaleTimeString()}`;
|
|
40
|
+
}
|
|
41
|
+
/** Compact task complete result */
|
|
42
|
+
export function compactTaskComplete(task, summary, unblocked) {
|
|
43
|
+
let result = `ā
${task.title} ā done`;
|
|
44
|
+
if (unblocked && unblocked.length > 0) {
|
|
45
|
+
result += `\nš Unblocked: ${unblocked.map(t => t.title).join(', ')}`;
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
/** Compact task claim result */
|
|
50
|
+
export function compactTaskClaim(task, claimed, expires) {
|
|
51
|
+
if (!claimed)
|
|
52
|
+
return `ā Failed to claim: ${task.title}`;
|
|
53
|
+
return `Claimed: ${task.title} (${shortId(task.id)}) until ${new Date(expires).toLocaleTimeString()}`;
|
|
54
|
+
}
|
|
55
|
+
/** Compact task release result */
|
|
56
|
+
export function compactTaskRelease(task, released) {
|
|
57
|
+
if (!released)
|
|
58
|
+
return `ā Failed to release: ${task.title}`;
|
|
59
|
+
return `Released: ${task.title} ā ${task.status}`;
|
|
60
|
+
}
|
|
61
|
+
/** Compact status update */
|
|
62
|
+
export function compactStatusUpdate(task) {
|
|
63
|
+
return `${statusIcon(task.status)} ${task.title} ā ${task.status}`;
|
|
64
|
+
}
|
|
65
|
+
/** Compact context added */
|
|
66
|
+
export function compactContextAdded(ctx) {
|
|
67
|
+
return `+ ${ctx.context_type} context added (${shortId(ctx.id)})`;
|
|
68
|
+
}
|
|
69
|
+
/** Compact heartbeat */
|
|
70
|
+
export function compactHeartbeat(extended, expires) {
|
|
71
|
+
if (!extended)
|
|
72
|
+
return 'ā Heartbeat failed';
|
|
73
|
+
return `ā„ Extended until ${new Date(expires).toLocaleTimeString()}`;
|
|
74
|
+
}
|
|
75
|
+
/** Compact memory search result */
|
|
76
|
+
export function compactMemorySearch(facts, entities) {
|
|
77
|
+
if (facts.length === 0 && entities.length === 0)
|
|
78
|
+
return 'No results.';
|
|
79
|
+
const parts = [];
|
|
80
|
+
if (entities.length > 0) {
|
|
81
|
+
parts.push(`Entities: ${entities.map(e => e.name).join(', ')}`);
|
|
82
|
+
}
|
|
83
|
+
if (facts.length > 0) {
|
|
84
|
+
parts.push('Facts:');
|
|
85
|
+
facts.forEach(f => parts.push(`⢠${f.fact}`));
|
|
86
|
+
}
|
|
87
|
+
return parts.join('\n');
|
|
88
|
+
}
|
|
89
|
+
/** Compact memory store result */
|
|
90
|
+
export function compactFactStored(entity1, rel, entity2, factId) {
|
|
91
|
+
return `Stored: ${entity1} ${rel} ${entity2} (${shortId(factId)})`;
|
|
92
|
+
}
|
|
93
|
+
/** Compact summary stats */
|
|
94
|
+
export function compactSummary(byStatus, byType, activeAgents) {
|
|
95
|
+
const statusParts = Object.entries(byStatus)
|
|
96
|
+
.filter(([, count]) => count > 0)
|
|
97
|
+
.map(([s, c]) => `${statusIcon(s)}${c}`)
|
|
98
|
+
.join(' ');
|
|
99
|
+
const typeParts = Object.entries(byType)
|
|
100
|
+
.filter(([, count]) => count > 0)
|
|
101
|
+
.map(([t, c]) => `${t}:${c}`)
|
|
102
|
+
.join(' ');
|
|
103
|
+
return `Tasks: ${statusParts}\nTypes: ${typeParts}\nAgents: ${activeAgents}`;
|
|
104
|
+
}
|
|
105
|
+
/** Compact epic progress */
|
|
106
|
+
export function compactEpicStatus(title, progress, stats) {
|
|
107
|
+
const pct = Math.round(progress * 100);
|
|
108
|
+
const bar = 'ā'.repeat(Math.round(pct / 10)) + 'ā'.repeat(10 - Math.round(pct / 10));
|
|
109
|
+
const statusLine = Object.entries(stats)
|
|
110
|
+
.filter(([, c]) => c > 0)
|
|
111
|
+
.map(([s, c]) => `${statusIcon(s)}${c}`)
|
|
112
|
+
.join(' ');
|
|
113
|
+
return `${title}: [${bar}] ${pct}%\n${statusLine}`;
|
|
114
|
+
}
|
|
115
|
+
/** Compact dispatch list */
|
|
116
|
+
export function compactDispatch(items) {
|
|
117
|
+
if (items.length === 0)
|
|
118
|
+
return 'No tasks ready for dispatch.';
|
|
119
|
+
return `Ready (${items.length}):\n` + items.map(i => `⢠${i.task.title} ā ${i.worker.name}`).join('\n');
|
|
120
|
+
}
|
|
121
|
+
/** Compact memory index */
|
|
122
|
+
export function compactMemoryIndex(entries) {
|
|
123
|
+
if (entries.length === 0)
|
|
124
|
+
return 'No memories found.';
|
|
125
|
+
return entries.map((e, i) => {
|
|
126
|
+
const critical = e.is_critical ? 'š“' : '';
|
|
127
|
+
const rel = Math.round(e.relevance * 100);
|
|
128
|
+
return `${i + 1}. ${critical}${e.summary.slice(0, 60)} (${rel}%) [${shortId(e.id)}]`;
|
|
129
|
+
}).join('\n');
|
|
130
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Track an engagement event via the API
|
|
3
|
+
* This persists the event to D1 for analytics
|
|
4
|
+
*/
|
|
5
|
+
export declare function trackEngagementEvent(sessionId: string, projectId: string, eventType: 'search' | 'detail' | 'inject' | 'store_fact' | 'context_referenced'): Promise<void>;
|
|
6
|
+
export declare function getSessionId(): string;
|
|
7
|
+
/**
|
|
8
|
+
* Reset session ID (for testing or explicit session boundaries)
|
|
9
|
+
*/
|
|
10
|
+
export declare function resetSessionId(): void;
|