@claudetools/tools 0.8.2 → 0.8.3
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/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 +1 -1
- package/dist/handlers/tool-handlers.js +44 -155
- 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/session-validation.d.ts +76 -0
- package/dist/helpers/session-validation.js +221 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/resources.js +3 -0
- 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 +22 -20
- package/package.json +4 -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': {
|
|
@@ -758,30 +764,16 @@ export function registerToolHandlers(server) {
|
|
|
758
764
|
await updateTaskStatus(DEFAULT_USER_ID, projectId, taskId, 'in_progress', agentId);
|
|
759
765
|
mcpLogger.toolResult(name, true, timer());
|
|
760
766
|
const data = claimResult.data;
|
|
761
|
-
|
|
762
|
-
output
|
|
763
|
-
output += `**Status:** in_progress\n`;
|
|
764
|
-
output += `**Lock expires:** ${data.lock_expires_at}\n\n`;
|
|
767
|
+
// Compact output: just essentials
|
|
768
|
+
let output = compactTaskStart(data.task, data.lock_expires_at);
|
|
765
769
|
if (data.task.description) {
|
|
766
|
-
output +=
|
|
770
|
+
output += `\n${data.task.description}`;
|
|
767
771
|
}
|
|
772
|
+
// Only show criteria if present (compact)
|
|
768
773
|
const criteria = parseJsonArray(data.task.acceptance_criteria);
|
|
769
774
|
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
|
-
});
|
|
775
|
+
output += `\nCriteria: ${criteria.join('; ')}`;
|
|
782
776
|
}
|
|
783
|
-
output += `---\n`;
|
|
784
|
-
output += `Use \`task_complete\` when done, or \`task_add_context\` to log progress.\n`;
|
|
785
777
|
return {
|
|
786
778
|
content: [{ type: 'text', text: output }],
|
|
787
779
|
};
|
|
@@ -798,25 +790,8 @@ export function registerToolHandlers(server) {
|
|
|
798
790
|
// Check for newly unblocked tasks (orchestration awareness)
|
|
799
791
|
const newlyUnblocked = await resolveTaskDependencies(DEFAULT_USER_ID, projectId, taskId, task.parent_id || undefined);
|
|
800
792
|
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
|
-
}
|
|
793
|
+
// Compact output
|
|
794
|
+
const output = compactTaskComplete(task, summary, newlyUnblocked);
|
|
820
795
|
return {
|
|
821
796
|
content: [{ type: 'text', text: output }],
|
|
822
797
|
};
|
|
@@ -843,24 +818,8 @@ export function registerToolHandlers(server) {
|
|
|
843
818
|
blocked_by: blockedBy,
|
|
844
819
|
});
|
|
845
820
|
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
|
-
}
|
|
821
|
+
// Compact output
|
|
822
|
+
const output = compactTaskCreated(result.data);
|
|
864
823
|
return {
|
|
865
824
|
content: [{ type: 'text', text: output }],
|
|
866
825
|
};
|
|
@@ -875,45 +834,8 @@ export function registerToolHandlers(server) {
|
|
|
875
834
|
};
|
|
876
835
|
const result = await listTasks(DEFAULT_USER_ID, projectId, filters);
|
|
877
836
|
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
|
-
}
|
|
837
|
+
// Compact output
|
|
838
|
+
const output = compactTaskList(result.data);
|
|
917
839
|
return {
|
|
918
840
|
content: [{ type: 'text', text: output }],
|
|
919
841
|
};
|
|
@@ -987,17 +909,8 @@ export function registerToolHandlers(server) {
|
|
|
987
909
|
const result = await claimTask(DEFAULT_USER_ID, projectId, taskId, agentId, lockDuration);
|
|
988
910
|
mcpLogger.toolResult(name, true, timer());
|
|
989
911
|
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
|
-
}
|
|
912
|
+
// Compact output
|
|
913
|
+
const output = compactTaskClaim(data.task, data.claimed, data.lock_expires_at);
|
|
1001
914
|
return {
|
|
1002
915
|
content: [{ type: 'text', text: output }],
|
|
1003
916
|
};
|
|
@@ -1009,13 +922,8 @@ export function registerToolHandlers(server) {
|
|
|
1009
922
|
const workLog = args?.work_log;
|
|
1010
923
|
const result = await releaseTask(DEFAULT_USER_ID, projectId, taskId, agentId, newStatus, workLog);
|
|
1011
924
|
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`;
|
|
925
|
+
// Compact output
|
|
926
|
+
const output = compactTaskRelease(result.data.task, result.data.released);
|
|
1019
927
|
return {
|
|
1020
928
|
content: [{ type: 'text', text: output }],
|
|
1021
929
|
};
|
|
@@ -1026,11 +934,8 @@ export function registerToolHandlers(server) {
|
|
|
1026
934
|
const agentId = args?.agent_id;
|
|
1027
935
|
const result = await updateTaskStatus(DEFAULT_USER_ID, projectId, taskId, status, agentId);
|
|
1028
936
|
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`;
|
|
937
|
+
// Compact output
|
|
938
|
+
const output = compactStatusUpdate(result.data);
|
|
1034
939
|
return {
|
|
1035
940
|
content: [{ type: 'text', text: output }],
|
|
1036
941
|
};
|
|
@@ -1043,14 +948,8 @@ export function registerToolHandlers(server) {
|
|
|
1043
948
|
const source = args?.source;
|
|
1044
949
|
const result = await addTaskContext(DEFAULT_USER_ID, projectId, taskId, contextType, content, addedBy, source);
|
|
1045
950
|
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`;
|
|
951
|
+
// Compact output
|
|
952
|
+
const output = compactContextAdded(result.data);
|
|
1054
953
|
return {
|
|
1055
954
|
content: [{ type: 'text', text: output }],
|
|
1056
955
|
};
|
|
@@ -1058,29 +957,9 @@ export function registerToolHandlers(server) {
|
|
|
1058
957
|
case 'task_summary': {
|
|
1059
958
|
const result = await getTaskSummary(DEFAULT_USER_ID, projectId);
|
|
1060
959
|
mcpLogger.toolResult(name, true, timer());
|
|
960
|
+
// Compact output
|
|
1061
961
|
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
|
-
}
|
|
962
|
+
const output = compactSummary(data.by_status, data.by_type, data.active_agents);
|
|
1084
963
|
return {
|
|
1085
964
|
content: [{ type: 'text', text: output }],
|
|
1086
965
|
};
|
|
@@ -1091,10 +970,8 @@ export function registerToolHandlers(server) {
|
|
|
1091
970
|
const extendMinutes = args?.extend_minutes || 30;
|
|
1092
971
|
const result = await heartbeatTask(DEFAULT_USER_ID, projectId, taskId, agentId, extendMinutes);
|
|
1093
972
|
mcpLogger.toolResult(name, true, timer());
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
output += `**Extended:** ${data.extended ? '✅ Yes' : '❌ No'}\n`;
|
|
1097
|
-
output += `**New Expiry:** ${data.new_expires_at}\n`;
|
|
973
|
+
// Compact output
|
|
974
|
+
const output = compactHeartbeat(result.data.extended, result.data.new_expires_at);
|
|
1098
975
|
return {
|
|
1099
976
|
content: [{ type: 'text', text: output }],
|
|
1100
977
|
};
|
|
@@ -1808,30 +1685,42 @@ export function registerToolHandlers(server) {
|
|
|
1808
1685
|
case 'codedna_generate_api': {
|
|
1809
1686
|
const result = await handleGenerateApi(args);
|
|
1810
1687
|
mcpLogger.toolResult(name, true, timer());
|
|
1688
|
+
let output = JSON.stringify(result, null, 2);
|
|
1689
|
+
if (workflowWarnings) {
|
|
1690
|
+
output += '\n\n' + workflowWarnings;
|
|
1691
|
+
}
|
|
1811
1692
|
return {
|
|
1812
1693
|
content: [{
|
|
1813
1694
|
type: 'text',
|
|
1814
|
-
text:
|
|
1695
|
+
text: output,
|
|
1815
1696
|
}],
|
|
1816
1697
|
};
|
|
1817
1698
|
}
|
|
1818
1699
|
case 'codedna_generate_frontend': {
|
|
1819
1700
|
const result = await handleGenerateFrontend(args);
|
|
1820
1701
|
mcpLogger.toolResult(name, true, timer());
|
|
1702
|
+
let output = JSON.stringify(result, null, 2);
|
|
1703
|
+
if (workflowWarnings) {
|
|
1704
|
+
output += '\n\n' + workflowWarnings;
|
|
1705
|
+
}
|
|
1821
1706
|
return {
|
|
1822
1707
|
content: [{
|
|
1823
1708
|
type: 'text',
|
|
1824
|
-
text:
|
|
1709
|
+
text: output,
|
|
1825
1710
|
}],
|
|
1826
1711
|
};
|
|
1827
1712
|
}
|
|
1828
1713
|
case 'codedna_generate_component': {
|
|
1829
1714
|
const result = await handleGenerateComponent(args);
|
|
1830
1715
|
mcpLogger.toolResult(name, true, timer());
|
|
1716
|
+
let output = JSON.stringify(result, null, 2);
|
|
1717
|
+
if (workflowWarnings) {
|
|
1718
|
+
output += '\n\n' + workflowWarnings;
|
|
1719
|
+
}
|
|
1831
1720
|
return {
|
|
1832
1721
|
content: [{
|
|
1833
1722
|
type: 'text',
|
|
1834
|
-
text:
|
|
1723
|
+
text: output,
|
|
1835
1724
|
}],
|
|
1836
1725
|
};
|
|
1837
1726
|
}
|
|
@@ -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;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Engagement Tracking Helper
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Tracks memory engagement events to the API for persistence
|
|
5
|
+
// =============================================================================
|
|
6
|
+
import { mcpLogger } from '../logger.js';
|
|
7
|
+
import { API_BASE_URL } from './config.js';
|
|
8
|
+
/**
|
|
9
|
+
* Track an engagement event via the API
|
|
10
|
+
* This persists the event to D1 for analytics
|
|
11
|
+
*/
|
|
12
|
+
export async function trackEngagementEvent(sessionId, projectId, eventType) {
|
|
13
|
+
try {
|
|
14
|
+
// Get API key from environment
|
|
15
|
+
const apiKey = process.env.CLAUDETOOLS_API_KEY;
|
|
16
|
+
if (!apiKey) {
|
|
17
|
+
mcpLogger.debug('API', 'No API key - skipping engagement tracking');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const response = await fetch(`${API_BASE_URL}/api/v1/engagement/track`, {
|
|
21
|
+
method: 'POST',
|
|
22
|
+
headers: {
|
|
23
|
+
'Content-Type': 'application/json',
|
|
24
|
+
Authorization: `Bearer ${apiKey}`,
|
|
25
|
+
},
|
|
26
|
+
body: JSON.stringify({
|
|
27
|
+
session_id: sessionId,
|
|
28
|
+
project_id: projectId,
|
|
29
|
+
event_type: eventType,
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
mcpLogger.warn('API', `Failed to track engagement event: ${response.status}`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
mcpLogger.debug('API', `Tracked ${eventType} event for session ${sessionId}`);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
// Don't fail the main operation if tracking fails
|
|
40
|
+
mcpLogger.debug('API', `Failed to track engagement event: ${error}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get current session ID
|
|
45
|
+
* This should ideally come from Claude Code's session system
|
|
46
|
+
* For now, we'll use a simple timestamp-based session ID
|
|
47
|
+
*/
|
|
48
|
+
let currentSessionId = null;
|
|
49
|
+
export function getSessionId() {
|
|
50
|
+
if (!currentSessionId) {
|
|
51
|
+
// Generate session ID: session_<timestamp>
|
|
52
|
+
currentSessionId = `session_${Date.now()}`;
|
|
53
|
+
}
|
|
54
|
+
return currentSessionId;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Reset session ID (for testing or explicit session boundaries)
|
|
58
|
+
*/
|
|
59
|
+
export function resetSessionId() {
|
|
60
|
+
currentSessionId = null;
|
|
61
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record a tool call in the session
|
|
3
|
+
*/
|
|
4
|
+
export declare function recordToolCall(name: string, args?: Record<string, unknown>): void;
|
|
5
|
+
/**
|
|
6
|
+
* Validate that memory_search was called before task_start
|
|
7
|
+
* Returns validation result with warnings if violated
|
|
8
|
+
*/
|
|
9
|
+
export declare function validateTaskStartSequence(taskId: string): {
|
|
10
|
+
valid: boolean;
|
|
11
|
+
warnings: string[];
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Validate that codebase_map was called before code modification tools
|
|
15
|
+
* Code modification tools: codedna_generate_*, or any task involving file changes
|
|
16
|
+
*/
|
|
17
|
+
export declare function validateCodebaseMapSequence(): {
|
|
18
|
+
valid: boolean;
|
|
19
|
+
warnings: string[];
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Check if a tool name indicates code modification
|
|
23
|
+
*/
|
|
24
|
+
export declare function isCodeModificationTool(toolName: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Record when auto-injected context is used
|
|
27
|
+
* This tracks the +20 point event when context is automatically provided
|
|
28
|
+
*/
|
|
29
|
+
export declare function recordContextReference(projectId?: string): void;
|
|
30
|
+
/**
|
|
31
|
+
* Calculate engagement score (0-100)
|
|
32
|
+
* - memory_search: +30
|
|
33
|
+
* - memory_detail: +20
|
|
34
|
+
* - memory_inject: +20
|
|
35
|
+
* - memory_store_fact: +10
|
|
36
|
+
* - context_referenced: +20
|
|
37
|
+
*/
|
|
38
|
+
export declare function calculateEngagementScore(): number;
|
|
39
|
+
/**
|
|
40
|
+
* Get engagement statistics
|
|
41
|
+
*/
|
|
42
|
+
export declare function getEngagementStats(): {
|
|
43
|
+
score: number;
|
|
44
|
+
breakdown: {
|
|
45
|
+
searchCount: number;
|
|
46
|
+
searchPoints: number;
|
|
47
|
+
detailCount: number;
|
|
48
|
+
detailPoints: number;
|
|
49
|
+
injectCount: number;
|
|
50
|
+
injectPoints: number;
|
|
51
|
+
storeFactCount: number;
|
|
52
|
+
storeFactPoints: number;
|
|
53
|
+
contextReferencedCount: number;
|
|
54
|
+
contextReferencedPoints: number;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Get session statistics for debugging
|
|
59
|
+
*/
|
|
60
|
+
export declare function getSessionStats(): {
|
|
61
|
+
totalCalls: number;
|
|
62
|
+
uniqueTaskStarts: number;
|
|
63
|
+
uniqueSearches: number;
|
|
64
|
+
codebaseMapCalled: boolean;
|
|
65
|
+
recentCalls: string[];
|
|
66
|
+
engagement: ReturnType<typeof getEngagementStats>;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Clear session state (for testing or manual reset)
|
|
70
|
+
*/
|
|
71
|
+
export declare function clearSessionState(): void;
|
|
72
|
+
/**
|
|
73
|
+
* Get formatted warnings for tool execution
|
|
74
|
+
* Returns null if no warnings
|
|
75
|
+
*/
|
|
76
|
+
export declare function getToolCallWarnings(toolName: string, args?: Record<string, unknown>): string | null;
|