@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.
@@ -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
- let output = `# Starting: ${data.task.title}\n\n`;
762
- output += `**Task ID:** ${taskId}\n`;
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 += `## Description\n${data.task.description}\n\n`;
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 += `## Acceptance Criteria\n`;
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
- let output = `# Completed: ${task.title}\n\n`;
802
- output += `**Task ID:** \`${taskId}\`\n`;
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
- const task = result.data;
847
- let output = `# Task Created\n\n`;
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
- const tasks = result.data;
879
- let output = `# Tasks (${tasks.length})\n\n`;
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
- let output = `# Task Claimed\n\n`;
991
- output += `**Task:** ${data.task.title}\n`;
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
- const data = result.data;
1013
- let output = `# Task Released\n\n`;
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
- const task = result.data;
1030
- let output = `# Status Updated\n\n`;
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
- const ctx = result.data;
1047
- let output = `# Context Added\n\n`;
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
- let output = `# Task Summary\n\n`;
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
- const data = result.data;
1095
- let output = `# Heartbeat\n\n`;
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: JSON.stringify(result, null, 2),
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: JSON.stringify(result, null, 2),
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: JSON.stringify(result, null, 2),
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;