@claudetools/tools 0.8.3 → 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.
Files changed (60) hide show
  1. package/dist/cli.js +41 -0
  2. package/dist/context/deduplication.d.ts +72 -0
  3. package/dist/context/deduplication.js +77 -0
  4. package/dist/context/deduplication.test.d.ts +6 -0
  5. package/dist/context/deduplication.test.js +84 -0
  6. package/dist/context/emergency-eviction.d.ts +73 -0
  7. package/dist/context/emergency-eviction.example.d.ts +13 -0
  8. package/dist/context/emergency-eviction.example.js +94 -0
  9. package/dist/context/emergency-eviction.js +226 -0
  10. package/dist/context/eviction-engine.d.ts +76 -0
  11. package/dist/context/eviction-engine.example.d.ts +7 -0
  12. package/dist/context/eviction-engine.example.js +144 -0
  13. package/dist/context/eviction-engine.js +176 -0
  14. package/dist/context/example-usage.d.ts +1 -0
  15. package/dist/context/example-usage.js +128 -0
  16. package/dist/context/exchange-summariser.d.ts +80 -0
  17. package/dist/context/exchange-summariser.js +261 -0
  18. package/dist/context/health-monitor.d.ts +97 -0
  19. package/dist/context/health-monitor.example.d.ts +1 -0
  20. package/dist/context/health-monitor.example.js +164 -0
  21. package/dist/context/health-monitor.js +210 -0
  22. package/dist/context/importance-scorer.d.ts +94 -0
  23. package/dist/context/importance-scorer.example.d.ts +1 -0
  24. package/dist/context/importance-scorer.example.js +140 -0
  25. package/dist/context/importance-scorer.js +187 -0
  26. package/dist/context/index.d.ts +9 -0
  27. package/dist/context/index.js +16 -0
  28. package/dist/context/session-helper.d.ts +10 -0
  29. package/dist/context/session-helper.js +51 -0
  30. package/dist/context/session-store.d.ts +94 -0
  31. package/dist/context/session-store.js +286 -0
  32. package/dist/context/usage-estimator.d.ts +131 -0
  33. package/dist/context/usage-estimator.js +260 -0
  34. package/dist/context/usage-estimator.test.d.ts +1 -0
  35. package/dist/context/usage-estimator.test.js +208 -0
  36. package/dist/context-cli.d.ts +16 -0
  37. package/dist/context-cli.js +309 -0
  38. package/dist/handlers/codedna-handlers.d.ts +1 -1
  39. package/dist/handlers/tool-handlers.js +82 -10
  40. package/dist/helpers/api-client.d.ts +5 -1
  41. package/dist/helpers/api-client.js +3 -1
  42. package/dist/helpers/error-tracking.js +1 -1
  43. package/dist/helpers/usage-analytics.js +1 -1
  44. package/dist/hooks/index.d.ts +4 -0
  45. package/dist/hooks/index.js +6 -0
  46. package/dist/hooks/post-tool-use-hook-cli.d.ts +2 -0
  47. package/dist/hooks/post-tool-use-hook-cli.js +34 -0
  48. package/dist/hooks/post-tool-use.d.ts +67 -0
  49. package/dist/hooks/post-tool-use.js +234 -0
  50. package/dist/hooks/stop-hook-cli.d.ts +2 -0
  51. package/dist/hooks/stop-hook-cli.js +34 -0
  52. package/dist/hooks/stop.d.ts +64 -0
  53. package/dist/hooks/stop.js +192 -0
  54. package/dist/index.d.ts +2 -0
  55. package/dist/index.js +1 -0
  56. package/dist/logger.d.ts +1 -1
  57. package/dist/logger.js +4 -0
  58. package/dist/setup.js +206 -2
  59. package/dist/tools.js +4 -0
  60. package/package.json +3 -1
@@ -0,0 +1,309 @@
1
+ // =============================================================================
2
+ // Context Management CLI Commands
3
+ // =============================================================================
4
+ // CLI interface for context window management: status, evict, summarise, reset
5
+ import chalk from 'chalk';
6
+ import ora from 'ora';
7
+ import prompts from 'prompts';
8
+ import { getSessionStore } from './context/session-store.js';
9
+ import { createEvictionEngine } from './context/eviction-engine.js';
10
+ import { createExchangeSummariser } from './context/exchange-summariser.js';
11
+ // -----------------------------------------------------------------------------
12
+ // Utility Functions
13
+ // -----------------------------------------------------------------------------
14
+ function success(msg) {
15
+ console.log(chalk.green('✓ ') + msg);
16
+ }
17
+ function error(msg) {
18
+ console.log(chalk.red('✗ ') + msg);
19
+ }
20
+ function info(msg) {
21
+ console.log(chalk.blue('ℹ ') + msg);
22
+ }
23
+ function warn(msg) {
24
+ console.log(chalk.yellow('⚠ ') + msg);
25
+ }
26
+ function header(title) {
27
+ console.log('\n' + chalk.cyan('━'.repeat(50)));
28
+ console.log(chalk.cyan.bold(title));
29
+ console.log(chalk.cyan('━'.repeat(50)) + '\n');
30
+ }
31
+ /**
32
+ * Get session ID from args or prompt user to select
33
+ */
34
+ async function getSessionId(args) {
35
+ // Check if session ID provided as argument
36
+ const sessionIdArg = args.find((arg) => arg.startsWith('--session='));
37
+ if (sessionIdArg) {
38
+ return sessionIdArg.split('=')[1];
39
+ }
40
+ // List available sessions
41
+ const store = getSessionStore();
42
+ const sessions = await store.listSessions();
43
+ if (sessions.length === 0) {
44
+ error('No active sessions found');
45
+ return null;
46
+ }
47
+ // If only one session, use it
48
+ if (sessions.length === 1) {
49
+ return sessions[0].session_id;
50
+ }
51
+ // Prompt user to select
52
+ const choices = sessions.map((s) => ({
53
+ title: `${s.session_id} (${s.model}, started ${s.started_at.toLocaleString()})`,
54
+ value: s.session_id,
55
+ }));
56
+ const response = await prompts({
57
+ type: 'select',
58
+ name: 'sessionId',
59
+ message: 'Select a session:',
60
+ choices,
61
+ });
62
+ return response.sessionId || null;
63
+ }
64
+ /**
65
+ * Format fill percentage with colour coding
66
+ */
67
+ function formatFill(fill) {
68
+ const percentage = (fill * 100).toFixed(1);
69
+ if (fill < 0.5) {
70
+ return chalk.green(`${percentage}%`);
71
+ }
72
+ else if (fill < 0.7) {
73
+ return chalk.yellow(`${percentage}%`);
74
+ }
75
+ else {
76
+ return chalk.red(`${percentage}%`);
77
+ }
78
+ }
79
+ /**
80
+ * Format session summary
81
+ */
82
+ function formatSessionSummary(session) {
83
+ console.log(chalk.bold('Session ID:'), session.session_id);
84
+ console.log(chalk.bold('Model:'), session.model);
85
+ console.log(chalk.bold('Started:'), session.started_at.toLocaleString());
86
+ console.log(chalk.bold('Context Limit:'), session.context_limit.toLocaleString(), 'tokens');
87
+ console.log(chalk.bold('Estimated Fill:'), formatFill(session.estimated_fill));
88
+ console.log(chalk.bold('Used Tokens:'), Math.round(session.estimated_fill * session.context_limit).toLocaleString());
89
+ console.log(chalk.bold('Injected Facts:'), session.injected_facts.length);
90
+ console.log(chalk.bold('Exchanges:'), session.exchanges.length);
91
+ // Show exchange summary
92
+ const summarisedCount = session.exchanges.filter((ex) => ex.summarised_at).length;
93
+ if (summarisedCount > 0) {
94
+ console.log(chalk.bold('Summarised Exchanges:'), summarisedCount);
95
+ }
96
+ console.log(chalk.bold('Last Updated:'), session.last_updated.toLocaleString());
97
+ }
98
+ // -----------------------------------------------------------------------------
99
+ // Commands
100
+ // -----------------------------------------------------------------------------
101
+ /**
102
+ * claudetools context status - Show current session context usage
103
+ */
104
+ export async function contextStatus(args) {
105
+ header('Context Status');
106
+ const sessionId = await getSessionId(args);
107
+ if (!sessionId) {
108
+ process.exit(1);
109
+ }
110
+ const spinner = ora('Loading session state...').start();
111
+ try {
112
+ const store = getSessionStore();
113
+ const session = await store.getSession(sessionId);
114
+ if (!session) {
115
+ spinner.fail('Session not found');
116
+ process.exit(1);
117
+ }
118
+ spinner.succeed('Session loaded');
119
+ console.log();
120
+ formatSessionSummary(session);
121
+ // Show warnings
122
+ console.log();
123
+ if (session.estimated_fill > 0.85) {
124
+ warn('Critical: Context window is near full (>85%). Emergency eviction may be triggered.');
125
+ }
126
+ else if (session.estimated_fill > 0.6) {
127
+ warn('Warning: Context window is filling up (>60%). Consider running eviction.');
128
+ }
129
+ else {
130
+ success('Context window is healthy.');
131
+ }
132
+ // Show eviction eligibility
133
+ const engine = createEvictionEngine();
134
+ if (engine.shouldEvict(session)) {
135
+ console.log();
136
+ info('Automatic eviction will be triggered at next opportunity.');
137
+ }
138
+ }
139
+ catch (err) {
140
+ spinner.fail('Failed to load session');
141
+ throw err;
142
+ }
143
+ }
144
+ /**
145
+ * claudetools context evict - Manually trigger eviction cycle
146
+ */
147
+ export async function contextEvict(args) {
148
+ header('Context Eviction');
149
+ const sessionId = await getSessionId(args);
150
+ if (!sessionId) {
151
+ process.exit(1);
152
+ }
153
+ const spinner = ora('Loading session state...').start();
154
+ try {
155
+ const store = getSessionStore();
156
+ const session = await store.getSession(sessionId);
157
+ if (!session) {
158
+ spinner.fail('Session not found');
159
+ process.exit(1);
160
+ }
161
+ spinner.text = 'Analysing session...';
162
+ // Check if eviction is needed
163
+ const engine = createEvictionEngine();
164
+ if (!engine.shouldEvict(session)) {
165
+ spinner.info(`No eviction needed (fill: ${formatFill(session.estimated_fill)})`);
166
+ return;
167
+ }
168
+ // Get eviction plan
169
+ const plan = engine.getEvictionPlan(session);
170
+ spinner.stop();
171
+ console.log();
172
+ console.log(chalk.bold('Eviction Plan:'));
173
+ console.log(chalk.bold('Facts to evict:'), plan.factsToEvict.length);
174
+ console.log(chalk.bold('Current fill:'), formatFill(session.estimated_fill));
175
+ console.log(chalk.bold('Expected fill after:'), formatFill(plan.expectedFillAfter));
176
+ if (plan.includesCritical) {
177
+ warn('Plan includes evicting CRITICAL facts (fill >85%)');
178
+ }
179
+ // Confirm before proceeding
180
+ const confirm = await prompts({
181
+ type: 'confirm',
182
+ name: 'proceed',
183
+ message: 'Proceed with eviction?',
184
+ initial: true,
185
+ });
186
+ if (!confirm.proceed) {
187
+ info('Eviction cancelled');
188
+ return;
189
+ }
190
+ spinner.start('Evicting facts...');
191
+ // Execute eviction
192
+ const result = await engine.runEviction(session);
193
+ spinner.succeed('Eviction complete');
194
+ console.log();
195
+ console.log(chalk.bold('Evicted:'), result.evictedCount, 'facts');
196
+ console.log(chalk.bold('New fill:'), formatFill(result.newEstimatedFill));
197
+ // Update session in store
198
+ await store.updateSession(sessionId, { estimated_fill: result.newEstimatedFill });
199
+ success(`Context window reduced from ${formatFill(session.estimated_fill)} to ${formatFill(result.newEstimatedFill)}`);
200
+ }
201
+ catch (err) {
202
+ spinner.fail('Eviction failed');
203
+ throw err;
204
+ }
205
+ }
206
+ /**
207
+ * claudetools context summarise - Summarise and compress exchanges
208
+ */
209
+ export async function contextSummarise(args) {
210
+ header('Exchange Summarisation');
211
+ const sessionId = await getSessionId(args);
212
+ if (!sessionId) {
213
+ process.exit(1);
214
+ }
215
+ const spinner = ora('Loading session state...').start();
216
+ try {
217
+ const store = getSessionStore();
218
+ const session = await store.getSession(sessionId);
219
+ if (!session) {
220
+ spinner.fail('Session not found');
221
+ process.exit(1);
222
+ }
223
+ spinner.text = 'Analysing exchanges...';
224
+ // Check if summarisation is needed
225
+ const summariser = createExchangeSummariser();
226
+ if (!summariser.shouldSummarise(session)) {
227
+ spinner.info('No exchanges need summarisation (< 10 unsummarised)');
228
+ return;
229
+ }
230
+ spinner.stop();
231
+ console.log();
232
+ const unsummarised = session.exchanges.filter((ex) => !ex.summarised_at);
233
+ console.log(chalk.bold('Unsummarised exchanges:'), unsummarised.length);
234
+ console.log(chalk.bold('Total exchanges:'), session.exchanges.length);
235
+ // Confirm before proceeding
236
+ const confirm = await prompts({
237
+ type: 'confirm',
238
+ name: 'proceed',
239
+ message: 'Proceed with summarisation?',
240
+ initial: true,
241
+ });
242
+ if (!confirm.proceed) {
243
+ info('Summarisation cancelled');
244
+ return;
245
+ }
246
+ spinner.start('Summarising exchanges...');
247
+ // Execute summarisation
248
+ const result = await summariser.summariseOldExchanges(session);
249
+ spinner.succeed('Summarisation complete');
250
+ console.log();
251
+ console.log(chalk.bold('Summarised:'), result.summarisedCount, 'exchanges');
252
+ console.log(chalk.bold('Tokens saved:'), result.tokensSaved.toLocaleString());
253
+ // Mark exchanges as summarised in store
254
+ for (let i = 0; i < result.summarisedCount; i++) {
255
+ await store.markExchangeSummarised(sessionId, i);
256
+ }
257
+ success(`Compressed ${result.summarisedCount} exchanges, saved ~${result.tokensSaved} tokens`);
258
+ }
259
+ catch (err) {
260
+ spinner.fail('Summarisation failed');
261
+ throw err;
262
+ }
263
+ }
264
+ /**
265
+ * claudetools context reset - Clear session state
266
+ */
267
+ export async function contextReset(args) {
268
+ header('Context Reset');
269
+ const sessionId = await getSessionId(args);
270
+ if (!sessionId) {
271
+ process.exit(1);
272
+ }
273
+ const spinner = ora('Loading session state...').start();
274
+ try {
275
+ const store = getSessionStore();
276
+ const session = await store.getSession(sessionId);
277
+ if (!session) {
278
+ spinner.fail('Session not found');
279
+ process.exit(1);
280
+ }
281
+ spinner.stop();
282
+ console.log();
283
+ formatSessionSummary(session);
284
+ console.log();
285
+ warn('This will permanently delete all session data including:');
286
+ console.log(' - Injected facts');
287
+ console.log(' - Exchange history');
288
+ console.log(' - Token usage estimates');
289
+ // Confirm before proceeding
290
+ const confirm = await prompts({
291
+ type: 'confirm',
292
+ name: 'proceed',
293
+ message: chalk.red('Are you sure you want to delete this session?'),
294
+ initial: false,
295
+ });
296
+ if (!confirm.proceed) {
297
+ info('Reset cancelled');
298
+ return;
299
+ }
300
+ spinner.start('Deleting session...');
301
+ await store.deleteSession(sessionId);
302
+ spinner.succeed('Session deleted');
303
+ success('Context state cleared');
304
+ }
305
+ catch (err) {
306
+ spinner.fail('Reset failed');
307
+ throw err;
308
+ }
309
+ }
@@ -369,7 +369,7 @@ export declare function handleInitProject(args: {
369
369
  patterns: {
370
370
  pattern_id: string;
371
371
  name: string;
372
- category: "hooks" | "components" | "forms" | "state" | "validation" | "styling" | "anti-patterns";
372
+ category: "hooks" | "state" | "components" | "forms" | "validation" | "styling" | "anti-patterns";
373
373
  description: string;
374
374
  }[];
375
375
  summary: {
@@ -132,17 +132,89 @@ export function registerToolHandlers(server) {
132
132
  const relationship = args?.relationship;
133
133
  const entity2 = args?.entity2;
134
134
  const context = args?.context;
135
- const result = await storeFact(projectId, entity1, relationship, entity2, context);
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
+ }
136
180
  mcpLogger.memoryStore(entity1, relationship, entity2);
137
- mcpLogger.toolResult(name, true, timer(), `ID: ${result.fact_id}`);
138
- return {
139
- content: [
140
- {
141
- type: 'text',
142
- text: `Stored fact: "${entity1} ${relationship} ${entity2}" (ID: ${result.fact_id})`,
143
- },
144
- ],
145
- };
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
+ }
146
218
  }
147
219
  case 'memory_get_context': {
148
220
  const query = args?.query;
@@ -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, userId?: string): Promise<{
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, userId = DEFAULT_USER_ID) {
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) {
@@ -28,7 +28,7 @@ export class CodeDNAErrorTracker {
28
28
  // Store error in memory system as a fact
29
29
  const userId = DEFAULT_USER_ID;
30
30
  const projectId = error.projectId || resolveProjectId();
31
- await storeFact(projectId, 'CodeDNA', 'ERROR_OCCURRED', error.operation, JSON.stringify(errorRecord), userId);
31
+ await storeFact(projectId, 'CodeDNA', 'ERROR_OCCURRED', error.operation, JSON.stringify(errorRecord), { userId });
32
32
  // Log to console for immediate visibility
33
33
  console.error('[CodeDNA Error]', {
34
34
  operation: error.operation,
@@ -29,7 +29,7 @@ export class UsageAnalytics {
29
29
  const userId = DEFAULT_USER_ID;
30
30
  const projectId = event.projectId || resolveProjectId();
31
31
  // Store as fact in memory system
32
- await storeFact(projectId, 'CodeDNA', 'GENERATION_COMPLETED', event.generator || event.operation, JSON.stringify(usageEvent), userId);
32
+ await storeFact(projectId, 'CodeDNA', 'GENERATION_COMPLETED', event.generator || event.operation, JSON.stringify(usageEvent), { userId });
33
33
  console.log('[CodeDNA Analytics]', {
34
34
  operation: event.operation,
35
35
  generator: event.generator,
@@ -0,0 +1,4 @@
1
+ export { handleStopHook, sessionStore, usageEstimator } from './stop.js';
2
+ export type { StopHookInput, SessionData, ContextHealth } from './stop.js';
3
+ export { handlePostToolUseHook, sessionStore as postToolUseSessionStore, evictionEngine, } from './post-tool-use.js';
4
+ export type { PostToolUseInput, SessionData as PostToolUseSessionData, EvictionTrigger, } from './post-tool-use.js';
@@ -0,0 +1,6 @@
1
+ // =============================================================================
2
+ // Claude Code Hooks - Index
3
+ // =============================================================================
4
+ // Exports hook handlers for Claude Code integration
5
+ export { handleStopHook, sessionStore, usageEstimator } from './stop.js';
6
+ export { handlePostToolUseHook, sessionStore as postToolUseSessionStore, evictionEngine, } from './post-tool-use.js';
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ // =============================================================================
3
+ // Post-Tool-Use Hook CLI Entry Point
4
+ // =============================================================================
5
+ // Called by Claude Code when PostToolUse event occurs
6
+ // Reads hook input from stdin, processes it, and outputs to stdout/stderr
7
+ import { handlePostToolUseHook } from './post-tool-use.js';
8
+ async function main() {
9
+ try {
10
+ // Read JSON input from stdin
11
+ let inputData = '';
12
+ // Set up stdin reading
13
+ process.stdin.setEncoding('utf8');
14
+ for await (const chunk of process.stdin) {
15
+ inputData += chunk;
16
+ }
17
+ // Parse hook input
18
+ const hookInput = JSON.parse(inputData);
19
+ // Validate hook event
20
+ if (hookInput.hook_event_name !== 'PostToolUse') {
21
+ console.error(`Error: Expected PostToolUse event, got ${hookInput.hook_event_name}`);
22
+ process.exit(1);
23
+ }
24
+ // Process the hook
25
+ await handlePostToolUseHook(hookInput);
26
+ // Success - exit code 0
27
+ process.exit(0);
28
+ }
29
+ catch (error) {
30
+ console.error('Error processing PostToolUse hook:', error);
31
+ process.exit(1);
32
+ }
33
+ }
34
+ main();
@@ -0,0 +1,67 @@
1
+ interface PostToolUseInput {
2
+ session_id?: string;
3
+ hook_event_name: 'PostToolUse';
4
+ tool_name: string;
5
+ tool_input?: Record<string, unknown>;
6
+ tool_output?: string;
7
+ post_tool_use_active?: boolean;
8
+ }
9
+ interface EvictionTrigger {
10
+ triggered: boolean;
11
+ level: 'none' | 'standard' | 'emergency';
12
+ estimated_fill: number;
13
+ message: string;
14
+ }
15
+ interface SessionData {
16
+ session_id: string;
17
+ tool_executions: number;
18
+ estimated_tokens: number;
19
+ context_limit: number;
20
+ estimated_fill: number;
21
+ last_updated: Date;
22
+ tool_output_tokens: number;
23
+ }
24
+ declare class SessionStore {
25
+ /**
26
+ * Get or create session data
27
+ */
28
+ getSession(sessionId: string): SessionData;
29
+ /**
30
+ * Update session with tool output tokens
31
+ */
32
+ updateSession(sessionId: string, outputTokens: number): SessionData;
33
+ /**
34
+ * Clear session (for cleanup)
35
+ */
36
+ clearSession(sessionId: string): void;
37
+ /**
38
+ * Get all sessions (for debugging)
39
+ */
40
+ getAllSessions(): SessionData[];
41
+ }
42
+ declare const sessionStore: SessionStore;
43
+ declare class EvictionTriggerEngine {
44
+ /**
45
+ * Check if eviction should be triggered based on fill percentage
46
+ */
47
+ checkEvictionTrigger(session: SessionData): EvictionTrigger;
48
+ /**
49
+ * Run standard eviction cycle
50
+ * NOTE: This is a placeholder - actual implementation would call into
51
+ * the context rotation/eviction system when it's built
52
+ */
53
+ runStandardEviction(session: SessionData): Promise<void>;
54
+ /**
55
+ * Run emergency eviction cycle
56
+ * More aggressive eviction for critical situations
57
+ */
58
+ runEmergencyEviction(session: SessionData): Promise<void>;
59
+ }
60
+ declare const evictionEngine: EvictionTriggerEngine;
61
+ /**
62
+ * Handle PostToolUse hook event
63
+ * Updates session state with tool output and triggers eviction if needed
64
+ */
65
+ export declare function handlePostToolUseHook(input: PostToolUseInput): Promise<void>;
66
+ export { sessionStore, evictionEngine };
67
+ export type { PostToolUseInput, SessionData, EvictionTrigger };