@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.
@@ -0,0 +1,221 @@
1
+ // =============================================================================
2
+ // Session-level Tool Call Validation
3
+ // =============================================================================
4
+ // Tracks tool call sequences within a session to enforce best practices:
5
+ // - memory_search before task_start
6
+ // - codebase_map before code modifications
7
+ // - No code in task descriptions (enforced via validateTaskDescription)
8
+ import { mcpLogger } from '../logger.js';
9
+ import { trackEngagementEvent, getSessionId } from './engagement-tracker.js';
10
+ const sessionState = {
11
+ toolCalls: [],
12
+ taskStarts: new Set(),
13
+ memorySearches: new Set(),
14
+ codebaseMapCalled: false,
15
+ engagement: {
16
+ searchCount: 0,
17
+ detailCount: 0,
18
+ injectCount: 0,
19
+ storeFactCount: 0,
20
+ contextReferencedCount: 0,
21
+ },
22
+ };
23
+ /**
24
+ * Record a tool call in the session
25
+ */
26
+ export function recordToolCall(name, args) {
27
+ sessionState.toolCalls.push({
28
+ name,
29
+ timestamp: Date.now(),
30
+ args,
31
+ });
32
+ // Track specific tool types
33
+ if (name === 'task_start' && args?.task_id) {
34
+ sessionState.taskStarts.add(args.task_id);
35
+ }
36
+ if (name === 'memory_search' && args?.query) {
37
+ sessionState.memorySearches.add(args.query);
38
+ }
39
+ if (name === 'codebase_map') {
40
+ sessionState.codebaseMapCalled = true;
41
+ }
42
+ // Track engagement and persist to API
43
+ const projectId = args?.project_id;
44
+ let eventType;
45
+ switch (name) {
46
+ case 'memory_search':
47
+ sessionState.engagement.searchCount++;
48
+ eventType = 'search';
49
+ break;
50
+ case 'memory_detail':
51
+ sessionState.engagement.detailCount++;
52
+ eventType = 'detail';
53
+ break;
54
+ case 'memory_inject':
55
+ sessionState.engagement.injectCount++;
56
+ eventType = 'inject';
57
+ break;
58
+ case 'memory_store_fact':
59
+ sessionState.engagement.storeFactCount++;
60
+ eventType = 'store_fact';
61
+ break;
62
+ }
63
+ // Track to API if this is a memory tool
64
+ if (eventType && projectId) {
65
+ trackEngagementEvent(getSessionId(), projectId, eventType).catch(() => {
66
+ // Silently fail - don't block the tool call
67
+ });
68
+ }
69
+ mcpLogger.debug('TOOL', `Recorded tool call: ${name}`);
70
+ }
71
+ /**
72
+ * Validate that memory_search was called before task_start
73
+ * Returns validation result with warnings if violated
74
+ */
75
+ export function validateTaskStartSequence(taskId) {
76
+ const warnings = [];
77
+ // Check if any memory_search happened in this session
78
+ if (sessionState.memorySearches.size === 0) {
79
+ warnings.push('BEST PRACTICE: Call memory_search() before task_start() to recall relevant context and past decisions.');
80
+ }
81
+ // Check recent tool calls (last 10 calls)
82
+ const recentCalls = sessionState.toolCalls.slice(-10).map(c => c.name);
83
+ const hasRecentMemorySearch = recentCalls.includes('memory_search');
84
+ if (!hasRecentMemorySearch && sessionState.memorySearches.size > 0) {
85
+ warnings.push('RECOMMENDATION: Consider calling memory_search() to check for updated context since your last search.');
86
+ }
87
+ return {
88
+ valid: warnings.length === 0,
89
+ warnings,
90
+ };
91
+ }
92
+ /**
93
+ * Validate that codebase_map was called before code modification tools
94
+ * Code modification tools: codedna_generate_*, or any task involving file changes
95
+ */
96
+ export function validateCodebaseMapSequence() {
97
+ const warnings = [];
98
+ if (!sessionState.codebaseMapCalled) {
99
+ warnings.push('BEST PRACTICE: Call codebase_map() before making code changes to understand project structure and prevent conflicts.');
100
+ }
101
+ return {
102
+ valid: warnings.length === 0,
103
+ warnings,
104
+ };
105
+ }
106
+ /**
107
+ * Check if a tool name indicates code modification
108
+ */
109
+ export function isCodeModificationTool(toolName) {
110
+ const codeTools = [
111
+ 'codedna_generate_api',
112
+ 'codedna_generate_frontend',
113
+ 'codedna_generate_component',
114
+ 'task_start', // Tasks often involve code changes
115
+ ];
116
+ return codeTools.includes(toolName);
117
+ }
118
+ /**
119
+ * Record when auto-injected context is used
120
+ * This tracks the +20 point event when context is automatically provided
121
+ */
122
+ export function recordContextReference(projectId) {
123
+ sessionState.engagement.contextReferencedCount++;
124
+ // Track to API if project ID is available
125
+ if (projectId) {
126
+ trackEngagementEvent(getSessionId(), projectId, 'context_referenced').catch(() => {
127
+ // Silently fail - don't block the resource read
128
+ });
129
+ }
130
+ mcpLogger.debug('TOOL', 'Recorded context reference (auto-inject)');
131
+ }
132
+ /**
133
+ * Calculate engagement score (0-100)
134
+ * - memory_search: +30
135
+ * - memory_detail: +20
136
+ * - memory_inject: +20
137
+ * - memory_store_fact: +10
138
+ * - context_referenced: +20
139
+ */
140
+ export function calculateEngagementScore() {
141
+ const { searchCount, detailCount, injectCount, storeFactCount, contextReferencedCount } = sessionState.engagement;
142
+ const rawScore = searchCount * 30 +
143
+ detailCount * 20 +
144
+ injectCount * 20 +
145
+ storeFactCount * 10 +
146
+ contextReferencedCount * 20;
147
+ // Cap at 100
148
+ return Math.min(rawScore, 100);
149
+ }
150
+ /**
151
+ * Get engagement statistics
152
+ */
153
+ export function getEngagementStats() {
154
+ const { searchCount, detailCount, injectCount, storeFactCount, contextReferencedCount } = sessionState.engagement;
155
+ return {
156
+ score: calculateEngagementScore(),
157
+ breakdown: {
158
+ searchCount,
159
+ searchPoints: searchCount * 30,
160
+ detailCount,
161
+ detailPoints: detailCount * 20,
162
+ injectCount,
163
+ injectPoints: injectCount * 20,
164
+ storeFactCount,
165
+ storeFactPoints: storeFactCount * 10,
166
+ contextReferencedCount,
167
+ contextReferencedPoints: contextReferencedCount * 20,
168
+ },
169
+ };
170
+ }
171
+ /**
172
+ * Get session statistics for debugging
173
+ */
174
+ export function getSessionStats() {
175
+ return {
176
+ totalCalls: sessionState.toolCalls.length,
177
+ uniqueTaskStarts: sessionState.taskStarts.size,
178
+ uniqueSearches: sessionState.memorySearches.size,
179
+ codebaseMapCalled: sessionState.codebaseMapCalled,
180
+ recentCalls: sessionState.toolCalls.slice(-10).map(c => c.name),
181
+ engagement: getEngagementStats(),
182
+ };
183
+ }
184
+ /**
185
+ * Clear session state (for testing or manual reset)
186
+ */
187
+ export function clearSessionState() {
188
+ sessionState.toolCalls = [];
189
+ sessionState.taskStarts.clear();
190
+ sessionState.memorySearches.clear();
191
+ sessionState.codebaseMapCalled = false;
192
+ sessionState.engagement = {
193
+ searchCount: 0,
194
+ detailCount: 0,
195
+ injectCount: 0,
196
+ storeFactCount: 0,
197
+ contextReferencedCount: 0,
198
+ };
199
+ mcpLogger.debug('TOOL', 'Session state cleared');
200
+ }
201
+ /**
202
+ * Get formatted warnings for tool execution
203
+ * Returns null if no warnings
204
+ */
205
+ export function getToolCallWarnings(toolName, args) {
206
+ const warnings = [];
207
+ // Validate task_start sequence
208
+ if (toolName === 'task_start' && args?.task_id) {
209
+ const validation = validateTaskStartSequence(args.task_id);
210
+ warnings.push(...validation.warnings);
211
+ }
212
+ // Validate codebase_map before code changes
213
+ if (isCodeModificationTool(toolName)) {
214
+ const validation = validateCodebaseMapSequence();
215
+ warnings.push(...validation.warnings);
216
+ }
217
+ if (warnings.length === 0) {
218
+ return null;
219
+ }
220
+ return `\n⚠️ WORKFLOW RECOMMENDATIONS:\n${warnings.map(w => ` - ${w}`).join('\n')}\n`;
221
+ }
package/dist/index.d.ts CHANGED
@@ -3,4 +3,5 @@ export type { Task, TaskContext, DispatchableTask } from './helpers/tasks.js';
3
3
  export { EXPERT_WORKERS, matchTaskToWorker } from './helpers/workers.js';
4
4
  export { parseJsonArray, getDispatchableTasks, getExecutionContext, resolveTaskDependencies, createTask, listTasks, getTask, claimTask, releaseTask, updateTaskStatus, addTaskContext, getTaskSummary, heartbeatTask } from './helpers/tasks.js';
5
5
  export { injectContext } from './helpers/api-client.js';
6
+ export { recordToolCall, getToolCallWarnings, getSessionStats, clearSessionState, recordContextReference, calculateEngagementScore, getEngagementStats } from './helpers/session-validation.js';
6
7
  export declare function startServer(): Promise<void>;
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@ import { registerPromptHandlers } from './prompts.js';
15
15
  export { EXPERT_WORKERS, matchTaskToWorker } from './helpers/workers.js';
16
16
  export { parseJsonArray, getDispatchableTasks, getExecutionContext, resolveTaskDependencies, createTask, listTasks, getTask, claimTask, releaseTask, updateTaskStatus, addTaskContext, getTaskSummary, heartbeatTask } from './helpers/tasks.js';
17
17
  export { injectContext } from './helpers/api-client.js';
18
+ export { recordToolCall, getToolCallWarnings, getSessionStats, clearSessionState, recordContextReference, calculateEngagementScore, getEngagementStats } from './helpers/session-validation.js';
18
19
  // =============================================================================
19
20
  // Server Initialization
20
21
  // =============================================================================
package/dist/resources.js CHANGED
@@ -5,6 +5,7 @@ import { ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcon
5
5
  import { getSummary, getEntities, getContext } from './helpers/api-client.js';
6
6
  import { formatContextForClaude } from './helpers/formatter.js';
7
7
  import { getDefaultProjectId } from './helpers/config.js';
8
+ import { recordContextReference } from './helpers/session-validation.js';
8
9
  export function registerResourceHandlers(server) {
9
10
  server.setRequestHandler(ListResourcesRequestSchema, async () => ({
10
11
  resources: [
@@ -59,6 +60,8 @@ export function registerResourceHandlers(server) {
59
60
  }
60
61
  if (uri === 'memory://context') {
61
62
  const context = await getContext(projectId);
63
+ // Track context reference for engagement scoring (+20 points)
64
+ recordContextReference(projectId);
62
65
  return {
63
66
  contents: [
64
67
  {
@@ -5,7 +5,7 @@ export declare const PROJECT_SECTION_END = "<!-- CLAUDETOOLS:PROJECT:END -->";
5
5
  /**
6
6
  * Global CLAUDE.md content - added to ~/.claude/CLAUDE.md
7
7
  */
8
- export declare const GLOBAL_TEMPLATE = "\n<!-- CLAUDETOOLS:START -->\n# ClaudeTools Memory System\n\nYou have access to a persistent memory system via the `claudetools_memory` MCP server. Use it to remember context across sessions.\n\n## Memory Tools\n\n### Searching Memory\n```\nmemory_search(query: \"authentication patterns\")\n```\nSearch for relevant facts, entities, and past context. Use this when:\n- Starting work on a feature to recall past decisions\n- Looking for patterns or conventions used before\n- Finding related code or architectural context\n\n### Storing Facts\n```\nmemory_store_fact(\n entity1: \"UserService\",\n relationship: \"USES\",\n entity2: \"bcrypt\",\n context: \"Password hashing uses bcrypt with 12 rounds\"\n)\n```\nStore important facts as relationships between entities. Use for:\n- Architectural decisions\n- Code patterns and conventions\n- Dependencies and relationships\n- User preferences learned during conversation\n\n### Context Injection\nContext is automatically injected at the start of each session based on the current project. Check `~/.claudetools/session-context.md` for project-specific context.\n\n## Task Management\n\n### Creating Work Plans with Auto-Dispatch\n```\ntask_plan(\n goal: \"Add user authentication\",\n epic_title: \"User Auth System\",\n tasks: [...]\n)\n```\nBreak down complex work into tracked tasks. **Tasks are automatically set to `ready` status.**\n\nAfter `task_plan` completes, you will receive Task tool invocations to spawn parallel agents.\n**Execute ALL Task tool calls in a SINGLE message** to enable parallel execution.\n\n### Parallel Agent Workflow\n1. User describes work needed\n2. Call `task_plan_draft` to present the plan\n3. User says \"go\" to approve\n4. Call `task_plan` - tasks created in `ready` status\n5. Execute the provided Task tool calls in ONE message\n6. Agents work in parallel, each calling `task_complete` when done\n\n### Manual Task Start (Sequential)\n```\ntask_start(task_id: \"task_xxx\")\n```\nClaim a task before working on it. Use for sequential execution.\n\n### Completing Tasks\n```\ntask_complete(task_id: \"task_xxx\", summary: \"Implemented JWT auth with refresh tokens\")\n```\nMark tasks done with a summary of work completed. **Always call this when a task is finished.**\n\n## Codebase Intelligence\n\n### Start with codebase_map() - ALWAYS\n```\ncodebase_map() # FIRST TOOL when exploring unfamiliar code\n```\n**When to use:** Starting a new task, exploring unfamiliar code, understanding project structure, finding entry points.\n\nThe map shows:\n- Project structure and key directories\n- Entry points and their exports\n- Framework detection (React, Express, etc.)\n- Key symbols and their locations\n\n**Use codebase_map BEFORE using Grep/Glob** - it gives you the lay of the land so you know where to look.\n\n### Then use targeted tools\n```\ncodebase_find(\"UserService\") # Find specific symbols/files\ncodebase_context(\"src/auth.ts\") # Get file dependencies\nanalyze_impact(\"validateToken\") # See what changing a function affects\n```\n\n## CodeDNA: Generate Code, Save 99% Tokens\n\n**When creating APIs/CRUD operations:** Call `codedna_generate_api` instead of writing code manually.\n\n```\ncodedna_generate_api({\n spec: \"User(email:string:unique, password:string:hashed, age:integer:min(18))\",\n framework: \"express\",\n options: { auth: true, validation: true, tests: true }\n})\n```\n\n**Generates 6 production files** (models, controllers, routes, validators, auth, tests) in ~5 seconds.\n**Saves:** 30,000 tokens \u2192 200 tokens (99% reduction)\n\n## Best Practices\n\n1. **Search before implementing** - Check memory for existing patterns\n2. **Store decisions** - Save architectural choices as facts\n3. **Use task tracking** - Break complex work into tasks\n4. **Use CodeDNA for APIs** - Generate instead of write (99% token savings)\n<!-- CLAUDETOOLS:END -->\n";
8
+ export declare const GLOBAL_TEMPLATE = "\n<!-- CLAUDETOOLS:START -->\n# ClaudeTools Memory System\n\nYou have access to a persistent memory system. **Context is AUTO-INJECTED via hooks** - you rarely need to call memory tools explicitly.\n\n## \u26A0\uFE0F IMPORTANT: Hooks vs MCP Tools\n\n**AUTOMATIC (via hooks - zero context cost):**\n- Context injection \u2192 `user-prompt-submit` hook runs on every message\n- Fact extraction \u2192 `post-tool-use` hook extracts from your work\n- Session context \u2192 `session-start` hook provides initial context\n\n**EXPLICIT (MCP tools - costs context):**\n- `memory_store_fact` \u2192 Store a specific fact you learned\n- `task_plan` / `task_start` / `task_complete` \u2192 Task management\n\n**DO NOT CALL these tools routinely (context already injected):**\n- `memory_search` - only if you need DIFFERENT search params\n- `memory_inject` - only if you need to refresh for a different query\n- `memory_get_context` - only for debugging\n- `memory_index` - only for debugging\n\n## Storing Facts (DO use this)\n```\nmemory_store_fact(\n entity1: \"UserService\",\n relationship: \"USES\",\n entity2: \"bcrypt\",\n context: \"Password hashing uses bcrypt with 12 rounds\"\n)\n```\nStore important facts when you learn something concrete. The `post-tool-use` hook also extracts facts automatically.\n\n## Task Management\n\n### Creating Work Plans with Auto-Dispatch\n```\ntask_plan(\n goal: \"Add user authentication\",\n epic_title: \"User Auth System\",\n tasks: [...]\n)\n```\nBreak down complex work into tracked tasks. **Tasks are automatically set to `ready` status.**\n\nAfter `task_plan` completes, you will receive Task tool invocations to spawn parallel agents.\n**Execute ALL Task tool calls in a SINGLE message** to enable parallel execution.\n\n### Parallel Agent Workflow\n1. User describes work needed\n2. Call `task_plan_draft` to present the plan\n3. User says \"go\" to approve\n4. Call `task_plan` - tasks created in `ready` status\n5. Execute the provided Task tool calls in ONE message\n6. Agents work in parallel, each calling `task_complete` when done\n\n### Manual Task Start (Sequential)\n```\ntask_start(task_id: \"task_xxx\")\n```\nClaim a task before working on it. Use for sequential execution.\n\n### Completing Tasks\n```\ntask_complete(task_id: \"task_xxx\", summary: \"Implemented JWT auth with refresh tokens\")\n```\nMark tasks done with a summary of work completed. **Always call this when a task is finished.**\n\n## Codebase Intelligence\n\n### Start with codebase_map() - ALWAYS\n```\ncodebase_map() # FIRST TOOL when exploring unfamiliar code\n```\n**When to use:** Starting a new task, exploring unfamiliar code, understanding project structure, finding entry points.\n\nThe map shows:\n- Project structure and key directories\n- Entry points and their exports\n- Framework detection (React, Express, etc.)\n- Key symbols and their locations\n\n**Use codebase_map BEFORE using Grep/Glob** - it gives you the lay of the land so you know where to look.\n\n### Then use targeted tools\n```\ncodebase_find(\"UserService\") # Find specific symbols/files\ncodebase_context(\"src/auth.ts\") # Get file dependencies\nanalyze_impact(\"validateToken\") # See what changing a function affects\n```\n\n## CodeDNA: Generate Code, Save 99% Tokens\n\n**When creating APIs/CRUD operations:** Call `codedna_generate_api` instead of writing code manually.\n\n```\ncodedna_generate_api({\n spec: \"User(email:string:unique, password:string:hashed, age:integer:min(18))\",\n framework: \"express\",\n options: { auth: true, validation: true, tests: true }\n})\n```\n\n**Generates 6 production files** (models, controllers, routes, validators, auth, tests) in ~5 seconds.\n**Saves:** 30,000 tokens \u2192 200 tokens (99% reduction)\n\n## Best Practices\n\n1. **Trust auto-injection** - Context is injected automatically, don't call memory_search\n2. **Store decisions** - Use `memory_store_fact` for architectural choices\n3. **Use task tracking** - Break complex work into tasks\n4. **Use CodeDNA for APIs** - Generate instead of write (99% token savings)\n5. **Minimize tool calls** - Every MCP call costs context tokens\n<!-- CLAUDETOOLS:END -->\n";
9
9
  /**
10
10
  * Project-level CLAUDE.md content - added to .claude/CLAUDE.md
11
11
  */
@@ -14,20 +14,26 @@ export const GLOBAL_TEMPLATE = `
14
14
  ${SECTION_START}
15
15
  # ClaudeTools Memory System
16
16
 
17
- You have access to a persistent memory system via the \`claudetools_memory\` MCP server. Use it to remember context across sessions.
17
+ You have access to a persistent memory system. **Context is AUTO-INJECTED via hooks** - you rarely need to call memory tools explicitly.
18
18
 
19
- ## Memory Tools
19
+ ## ⚠️ IMPORTANT: Hooks vs MCP Tools
20
20
 
21
- ### Searching Memory
22
- \`\`\`
23
- memory_search(query: "authentication patterns")
24
- \`\`\`
25
- Search for relevant facts, entities, and past context. Use this when:
26
- - Starting work on a feature to recall past decisions
27
- - Looking for patterns or conventions used before
28
- - Finding related code or architectural context
21
+ **AUTOMATIC (via hooks - zero context cost):**
22
+ - Context injection → \`user-prompt-submit\` hook runs on every message
23
+ - Fact extraction → \`post-tool-use\` hook extracts from your work
24
+ - Session context → \`session-start\` hook provides initial context
25
+
26
+ **EXPLICIT (MCP tools - costs context):**
27
+ - \`memory_store_fact\` Store a specific fact you learned
28
+ - \`task_plan\` / \`task_start\` / \`task_complete\` → Task management
29
29
 
30
- ### Storing Facts
30
+ **DO NOT CALL these tools routinely (context already injected):**
31
+ - \`memory_search\` - only if you need DIFFERENT search params
32
+ - \`memory_inject\` - only if you need to refresh for a different query
33
+ - \`memory_get_context\` - only for debugging
34
+ - \`memory_index\` - only for debugging
35
+
36
+ ## Storing Facts (DO use this)
31
37
  \`\`\`
32
38
  memory_store_fact(
33
39
  entity1: "UserService",
@@ -36,14 +42,7 @@ memory_store_fact(
36
42
  context: "Password hashing uses bcrypt with 12 rounds"
37
43
  )
38
44
  \`\`\`
39
- Store important facts as relationships between entities. Use for:
40
- - Architectural decisions
41
- - Code patterns and conventions
42
- - Dependencies and relationships
43
- - User preferences learned during conversation
44
-
45
- ### Context Injection
46
- Context is automatically injected at the start of each session based on the current project. Check \`~/.claudetools/session-context.md\` for project-specific context.
45
+ Store important facts when you learn something concrete. The \`post-tool-use\` hook also extracts facts automatically.
47
46
 
48
47
  ## Task Management
49
48
 
@@ -120,10 +119,11 @@ codedna_generate_api({
120
119
 
121
120
  ## Best Practices
122
121
 
123
- 1. **Search before implementing** - Check memory for existing patterns
124
- 2. **Store decisions** - Save architectural choices as facts
122
+ 1. **Trust auto-injection** - Context is injected automatically, don't call memory_search
123
+ 2. **Store decisions** - Use \`memory_store_fact\` for architectural choices
125
124
  3. **Use task tracking** - Break complex work into tasks
126
125
  4. **Use CodeDNA for APIs** - Generate instead of write (99% token savings)
126
+ 5. **Minimize tool calls** - Every MCP call costs context tokens
127
127
  ${SECTION_END}
128
128
  `;
129
129
  /**
@@ -134,31 +134,19 @@ export function getProjectTemplate(projectId, projectName) {
134
134
  ${SECTION_START}
135
135
  # Project: ${projectName}
136
136
 
137
- This project is registered with ClaudeTools Memory.
138
-
139
137
  **Project ID:** \`${projectId}\`
140
138
 
141
- ## Project Memory
142
-
143
- Use memory tools to search and store project-specific context:
139
+ Context is **auto-injected** via hooks. Only use \`memory_store_fact\` to store new facts:
144
140
 
145
141
  \`\`\`
146
- # Search this project's memory
147
- memory_search(query: "your search", project_id: "${projectId}")
148
-
149
- # Store a project fact
150
142
  memory_store_fact(
151
143
  entity1: "ComponentName",
152
144
  relationship: "IMPLEMENTS",
153
145
  entity2: "PatternName",
154
- context: "Description of the relationship",
146
+ context: "Why this decision was made",
155
147
  project_id: "${projectId}"
156
148
  )
157
149
  \`\`\`
158
-
159
- ## Session Context
160
-
161
- Project-specific context is injected automatically. Check \`~/.claudetools/session-context.md\` for current context.
162
150
  ${SECTION_END}
163
151
  `;
164
152
  }