@aitytech/agentkits-memory 1.0.0

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 (116) hide show
  1. package/README.md +250 -0
  2. package/dist/cache-manager.d.ts +134 -0
  3. package/dist/cache-manager.d.ts.map +1 -0
  4. package/dist/cache-manager.js +407 -0
  5. package/dist/cache-manager.js.map +1 -0
  6. package/dist/cli/save.d.ts +20 -0
  7. package/dist/cli/save.d.ts.map +1 -0
  8. package/dist/cli/save.js +94 -0
  9. package/dist/cli/save.js.map +1 -0
  10. package/dist/cli/setup.d.ts +18 -0
  11. package/dist/cli/setup.d.ts.map +1 -0
  12. package/dist/cli/setup.js +163 -0
  13. package/dist/cli/setup.js.map +1 -0
  14. package/dist/cli/viewer.d.ts +21 -0
  15. package/dist/cli/viewer.d.ts.map +1 -0
  16. package/dist/cli/viewer.js +182 -0
  17. package/dist/cli/viewer.js.map +1 -0
  18. package/dist/hnsw-index.d.ts +111 -0
  19. package/dist/hnsw-index.d.ts.map +1 -0
  20. package/dist/hnsw-index.js +781 -0
  21. package/dist/hnsw-index.js.map +1 -0
  22. package/dist/hooks/cli.d.ts +20 -0
  23. package/dist/hooks/cli.d.ts.map +1 -0
  24. package/dist/hooks/cli.js +102 -0
  25. package/dist/hooks/cli.js.map +1 -0
  26. package/dist/hooks/context.d.ts +31 -0
  27. package/dist/hooks/context.d.ts.map +1 -0
  28. package/dist/hooks/context.js +64 -0
  29. package/dist/hooks/context.js.map +1 -0
  30. package/dist/hooks/index.d.ts +16 -0
  31. package/dist/hooks/index.d.ts.map +1 -0
  32. package/dist/hooks/index.js +20 -0
  33. package/dist/hooks/index.js.map +1 -0
  34. package/dist/hooks/observation.d.ts +30 -0
  35. package/dist/hooks/observation.d.ts.map +1 -0
  36. package/dist/hooks/observation.js +79 -0
  37. package/dist/hooks/observation.js.map +1 -0
  38. package/dist/hooks/service.d.ts +102 -0
  39. package/dist/hooks/service.d.ts.map +1 -0
  40. package/dist/hooks/service.js +454 -0
  41. package/dist/hooks/service.js.map +1 -0
  42. package/dist/hooks/session-init.d.ts +30 -0
  43. package/dist/hooks/session-init.d.ts.map +1 -0
  44. package/dist/hooks/session-init.js +54 -0
  45. package/dist/hooks/session-init.js.map +1 -0
  46. package/dist/hooks/summarize.d.ts +30 -0
  47. package/dist/hooks/summarize.d.ts.map +1 -0
  48. package/dist/hooks/summarize.js +74 -0
  49. package/dist/hooks/summarize.js.map +1 -0
  50. package/dist/hooks/types.d.ts +193 -0
  51. package/dist/hooks/types.d.ts.map +1 -0
  52. package/dist/hooks/types.js +137 -0
  53. package/dist/hooks/types.js.map +1 -0
  54. package/dist/index.d.ts +173 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +564 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/mcp/index.d.ts +9 -0
  59. package/dist/mcp/index.d.ts.map +1 -0
  60. package/dist/mcp/index.js +9 -0
  61. package/dist/mcp/index.js.map +1 -0
  62. package/dist/mcp/server.d.ts +22 -0
  63. package/dist/mcp/server.d.ts.map +1 -0
  64. package/dist/mcp/server.js +368 -0
  65. package/dist/mcp/server.js.map +1 -0
  66. package/dist/mcp/tools.d.ts +14 -0
  67. package/dist/mcp/tools.d.ts.map +1 -0
  68. package/dist/mcp/tools.js +110 -0
  69. package/dist/mcp/tools.js.map +1 -0
  70. package/dist/mcp/types.d.ts +100 -0
  71. package/dist/mcp/types.d.ts.map +1 -0
  72. package/dist/mcp/types.js +9 -0
  73. package/dist/mcp/types.js.map +1 -0
  74. package/dist/migration.d.ts +77 -0
  75. package/dist/migration.d.ts.map +1 -0
  76. package/dist/migration.js +457 -0
  77. package/dist/migration.js.map +1 -0
  78. package/dist/sqljs-backend.d.ts +128 -0
  79. package/dist/sqljs-backend.d.ts.map +1 -0
  80. package/dist/sqljs-backend.js +623 -0
  81. package/dist/sqljs-backend.js.map +1 -0
  82. package/dist/types.d.ts +481 -0
  83. package/dist/types.d.ts.map +1 -0
  84. package/dist/types.js +73 -0
  85. package/dist/types.js.map +1 -0
  86. package/hooks.json +46 -0
  87. package/package.json +67 -0
  88. package/src/__tests__/index.test.ts +407 -0
  89. package/src/__tests__/sqljs-backend.test.ts +410 -0
  90. package/src/cache-manager.ts +515 -0
  91. package/src/cli/save.ts +109 -0
  92. package/src/cli/setup.ts +203 -0
  93. package/src/cli/viewer.ts +218 -0
  94. package/src/hnsw-index.ts +1013 -0
  95. package/src/hooks/__tests__/handlers.test.ts +298 -0
  96. package/src/hooks/__tests__/integration.test.ts +431 -0
  97. package/src/hooks/__tests__/service.test.ts +487 -0
  98. package/src/hooks/__tests__/types.test.ts +341 -0
  99. package/src/hooks/cli.ts +121 -0
  100. package/src/hooks/context.ts +77 -0
  101. package/src/hooks/index.ts +23 -0
  102. package/src/hooks/observation.ts +102 -0
  103. package/src/hooks/service.ts +582 -0
  104. package/src/hooks/session-init.ts +70 -0
  105. package/src/hooks/summarize.ts +89 -0
  106. package/src/hooks/types.ts +365 -0
  107. package/src/index.ts +755 -0
  108. package/src/mcp/__tests__/server.test.ts +181 -0
  109. package/src/mcp/index.ts +9 -0
  110. package/src/mcp/server.ts +441 -0
  111. package/src/mcp/tools.ts +113 -0
  112. package/src/mcp/types.ts +109 -0
  113. package/src/migration.ts +574 -0
  114. package/src/sql.js.d.ts +70 -0
  115. package/src/sqljs-backend.ts +789 -0
  116. package/src/types.ts +715 -0
@@ -0,0 +1,341 @@
1
+ /**
2
+ * Unit Tests for Hook Types and Utilities
3
+ *
4
+ * @module @agentkits/memory/hooks/__tests__/types
5
+ */
6
+
7
+ import { describe, it, expect } from 'vitest';
8
+ import {
9
+ generateObservationId,
10
+ getProjectName,
11
+ getObservationType,
12
+ generateObservationTitle,
13
+ truncate,
14
+ parseHookInput,
15
+ formatResponse,
16
+ STANDARD_RESPONSE,
17
+ } from '../types.js';
18
+
19
+ describe('Hook Types Utilities', () => {
20
+ describe('generateObservationId', () => {
21
+ it('should generate unique IDs', () => {
22
+ const id1 = generateObservationId();
23
+ const id2 = generateObservationId();
24
+
25
+ expect(id1).not.toBe(id2);
26
+ });
27
+
28
+ it('should start with obs_ prefix', () => {
29
+ const id = generateObservationId();
30
+
31
+ expect(id).toMatch(/^obs_/);
32
+ });
33
+
34
+ it('should contain timestamp and random parts', () => {
35
+ const id = generateObservationId();
36
+ const parts = id.split('_');
37
+
38
+ expect(parts.length).toBe(3);
39
+ expect(parts[0]).toBe('obs');
40
+ expect(parts[1].length).toBeGreaterThan(0); // timestamp
41
+ expect(parts[2].length).toBe(4); // random
42
+ });
43
+ });
44
+
45
+ describe('getProjectName', () => {
46
+ it('should extract project name from Unix path', () => {
47
+ expect(getProjectName('/home/user/projects/my-app')).toBe('my-app');
48
+ });
49
+
50
+ it('should extract project name from Windows path', () => {
51
+ expect(getProjectName('C:\\Users\\user\\projects\\my-app')).toBe('my-app');
52
+ });
53
+
54
+ it('should handle trailing slash', () => {
55
+ // Trailing slash results in empty string which gets mapped to 'unknown'
56
+ expect(getProjectName('/home/user/projects/my-app/')).toBe('unknown');
57
+ });
58
+
59
+ it('should return unknown for empty path', () => {
60
+ expect(getProjectName('')).toBe('unknown');
61
+ });
62
+
63
+ it('should handle single directory', () => {
64
+ expect(getProjectName('my-app')).toBe('my-app');
65
+ });
66
+ });
67
+
68
+ describe('getObservationType', () => {
69
+ it('should classify read tools', () => {
70
+ expect(getObservationType('Read')).toBe('read');
71
+ expect(getObservationType('Glob')).toBe('read');
72
+ expect(getObservationType('Grep')).toBe('read');
73
+ expect(getObservationType('LS')).toBe('read');
74
+ });
75
+
76
+ it('should classify write tools', () => {
77
+ expect(getObservationType('Write')).toBe('write');
78
+ expect(getObservationType('Edit')).toBe('write');
79
+ expect(getObservationType('NotebookEdit')).toBe('write');
80
+ });
81
+
82
+ it('should classify execute tools', () => {
83
+ expect(getObservationType('Bash')).toBe('execute');
84
+ expect(getObservationType('Task')).toBe('execute');
85
+ expect(getObservationType('Skill')).toBe('execute');
86
+ });
87
+
88
+ it('should classify search tools', () => {
89
+ expect(getObservationType('WebSearch')).toBe('search');
90
+ expect(getObservationType('WebFetch')).toBe('search');
91
+ });
92
+
93
+ it('should return other for unknown tools', () => {
94
+ expect(getObservationType('UnknownTool')).toBe('other');
95
+ expect(getObservationType('CustomTool')).toBe('other');
96
+ });
97
+ });
98
+
99
+ describe('generateObservationTitle', () => {
100
+ it('should generate title for Read tool', () => {
101
+ const title = generateObservationTitle('Read', { file_path: '/path/to/file.ts' });
102
+ expect(title).toBe('Read /path/to/file.ts');
103
+ });
104
+
105
+ it('should generate title for Write tool', () => {
106
+ const title = generateObservationTitle('Write', { file_path: '/path/to/file.ts' });
107
+ expect(title).toBe('Write /path/to/file.ts');
108
+ });
109
+
110
+ it('should generate title for Edit tool', () => {
111
+ const title = generateObservationTitle('Edit', { file_path: '/path/to/file.ts' });
112
+ expect(title).toBe('Edit /path/to/file.ts');
113
+ });
114
+
115
+ it('should generate title for Bash tool', () => {
116
+ const title = generateObservationTitle('Bash', { command: 'npm install' });
117
+ expect(title).toBe('Run: npm install');
118
+ });
119
+
120
+ it('should truncate long Bash commands', () => {
121
+ const longCommand = 'npm install some-very-long-package-name-that-exceeds-fifty-characters';
122
+ const title = generateObservationTitle('Bash', { command: longCommand });
123
+ expect(title).toBe(`Run: ${longCommand.substring(0, 50)}...`);
124
+ });
125
+
126
+ it('should generate title for Glob tool', () => {
127
+ const title = generateObservationTitle('Glob', { pattern: '**/*.ts' });
128
+ expect(title).toBe('Find **/*.ts');
129
+ });
130
+
131
+ it('should generate title for Grep tool', () => {
132
+ const title = generateObservationTitle('Grep', { pattern: 'function\\s+\\w+' });
133
+ expect(title).toBe('Search "function\\s+\\w+"');
134
+ });
135
+
136
+ it('should generate title for Task tool', () => {
137
+ const title = generateObservationTitle('Task', { description: 'explore codebase' });
138
+ expect(title).toBe('Task: explore codebase');
139
+ });
140
+
141
+ it('should generate title for WebSearch tool', () => {
142
+ const title = generateObservationTitle('WebSearch', { query: 'typescript best practices' });
143
+ expect(title).toBe('Search: typescript best practices');
144
+ });
145
+
146
+ it('should generate title for WebFetch tool', () => {
147
+ const title = generateObservationTitle('WebFetch', { url: 'https://example.com' });
148
+ expect(title).toBe('Fetch: https://example.com');
149
+ });
150
+
151
+ it('should handle unknown tools', () => {
152
+ const title = generateObservationTitle('CustomTool', { foo: 'bar' });
153
+ expect(title).toBe('CustomTool');
154
+ });
155
+
156
+ it('should handle Edit with path fallback', () => {
157
+ const title = generateObservationTitle('Edit', { path: '/path/file.ts' });
158
+ expect(title).toBe('Edit /path/file.ts');
159
+ });
160
+
161
+ it('should handle Edit with no path', () => {
162
+ const title = generateObservationTitle('Edit', {});
163
+ expect(title).toBe('Edit file');
164
+ });
165
+
166
+ it('should handle Bash with empty command', () => {
167
+ const title = generateObservationTitle('Bash', {});
168
+ expect(title).toBe('Run: ');
169
+ });
170
+
171
+ it('should handle Glob with no pattern', () => {
172
+ const title = generateObservationTitle('Glob', {});
173
+ expect(title).toBe('Find files');
174
+ });
175
+
176
+ it('should handle Grep with no pattern', () => {
177
+ const title = generateObservationTitle('Grep', {});
178
+ expect(title).toBe('Search ""');
179
+ });
180
+
181
+ it('should handle Task with no description', () => {
182
+ const title = generateObservationTitle('Task', {});
183
+ expect(title).toBe('Task: agent');
184
+ });
185
+
186
+ it('should handle WebSearch with no query', () => {
187
+ const title = generateObservationTitle('WebSearch', {});
188
+ expect(title).toBe('Search: ');
189
+ });
190
+
191
+ it('should handle WebFetch with no url', () => {
192
+ const title = generateObservationTitle('WebFetch', {});
193
+ expect(title).toBe('Fetch: ');
194
+ });
195
+
196
+ it('should handle string input', () => {
197
+ const title = generateObservationTitle('Read', JSON.stringify({ file_path: '/path/file.ts' }));
198
+ expect(title).toBe('Read /path/file.ts');
199
+ });
200
+
201
+ it('should handle null input', () => {
202
+ const title = generateObservationTitle('Read', null);
203
+ expect(title).toBe('Read file');
204
+ });
205
+
206
+ it('should handle parse errors gracefully', () => {
207
+ const title = generateObservationTitle('Read', 'invalid json {');
208
+ expect(title).toBe('Read');
209
+ });
210
+ });
211
+
212
+ describe('truncate', () => {
213
+ it('should not truncate short strings', () => {
214
+ const str = 'Hello World';
215
+ expect(truncate(str, 100)).toBe(str);
216
+ });
217
+
218
+ it('should truncate long strings', () => {
219
+ const str = 'A'.repeat(200);
220
+ const result = truncate(str, 100);
221
+
222
+ expect(result.length).toBe(100 + '...[truncated]'.length);
223
+ expect(result).toContain('...[truncated]');
224
+ });
225
+
226
+ it('should use default max length of 1000', () => {
227
+ const str = 'A'.repeat(1500);
228
+ const result = truncate(str);
229
+
230
+ expect(result.length).toBe(1000 + '...[truncated]'.length);
231
+ });
232
+
233
+ it('should handle exact length', () => {
234
+ const str = 'A'.repeat(100);
235
+ expect(truncate(str, 100)).toBe(str);
236
+ });
237
+ });
238
+
239
+ describe('parseHookInput', () => {
240
+ it('should parse valid JSON input', () => {
241
+ const input = JSON.stringify({
242
+ session_id: 'test-session-123',
243
+ cwd: '/path/to/project',
244
+ prompt: 'Hello Claude',
245
+ tool_name: 'Read',
246
+ tool_input: { file_path: '/path/to/file.ts' },
247
+ tool_result: { content: 'file contents' },
248
+ });
249
+
250
+ const parsed = parseHookInput(input);
251
+
252
+ expect(parsed.sessionId).toBe('test-session-123');
253
+ expect(parsed.cwd).toBe('/path/to/project');
254
+ expect(parsed.project).toBe('project');
255
+ expect(parsed.prompt).toBe('Hello Claude');
256
+ expect(parsed.toolName).toBe('Read');
257
+ expect(parsed.toolInput).toEqual({ file_path: '/path/to/file.ts' });
258
+ expect(parsed.toolResponse).toEqual({ content: 'file contents' });
259
+ expect(parsed.timestamp).toBeGreaterThan(0);
260
+ });
261
+
262
+ it('should handle missing session_id', () => {
263
+ const input = JSON.stringify({ cwd: '/path/to/project' });
264
+ const parsed = parseHookInput(input);
265
+
266
+ expect(parsed.sessionId).toMatch(/^session_\d+$/);
267
+ });
268
+
269
+ it('should handle missing cwd', () => {
270
+ const input = JSON.stringify({ session_id: 'test' });
271
+ const parsed = parseHookInput(input);
272
+
273
+ expect(parsed.cwd).toBe(process.cwd());
274
+ });
275
+
276
+ it('should handle empty input', () => {
277
+ const parsed = parseHookInput('');
278
+
279
+ expect(parsed.sessionId).toMatch(/^session_\d+$/);
280
+ expect(parsed.cwd).toBe(process.cwd());
281
+ expect(parsed.timestamp).toBeGreaterThan(0);
282
+ });
283
+
284
+ it('should handle invalid JSON', () => {
285
+ const parsed = parseHookInput('not valid json');
286
+
287
+ expect(parsed.sessionId).toMatch(/^session_\d+$/);
288
+ expect(parsed.cwd).toBe(process.cwd());
289
+ });
290
+
291
+ it('should parse transcript_path and stop_reason', () => {
292
+ const input = JSON.stringify({
293
+ session_id: 'test',
294
+ cwd: '/path',
295
+ transcript_path: '/path/to/transcript.json',
296
+ stop_reason: 'user_exit',
297
+ });
298
+
299
+ const parsed = parseHookInput(input);
300
+
301
+ expect(parsed.transcriptPath).toBe('/path/to/transcript.json');
302
+ expect(parsed.stopReason).toBe('user_exit');
303
+ });
304
+ });
305
+
306
+ describe('formatResponse', () => {
307
+ it('should format standard response', () => {
308
+ const result = {
309
+ continue: true,
310
+ suppressOutput: true,
311
+ };
312
+
313
+ const response = formatResponse(result);
314
+ const parsed = JSON.parse(response);
315
+
316
+ expect(parsed).toEqual(STANDARD_RESPONSE);
317
+ });
318
+
319
+ it('should format response with additionalContext', () => {
320
+ const result = {
321
+ continue: true,
322
+ suppressOutput: false,
323
+ additionalContext: '# Memory Context\n\nSome context here',
324
+ };
325
+
326
+ const response = formatResponse(result);
327
+ const parsed = JSON.parse(response);
328
+
329
+ expect(parsed.hookSpecificOutput).toBeDefined();
330
+ expect(parsed.hookSpecificOutput.hookEventName).toBe('SessionStart');
331
+ expect(parsed.hookSpecificOutput.additionalContext).toBe('# Memory Context\n\nSome context here');
332
+ });
333
+ });
334
+
335
+ describe('STANDARD_RESPONSE', () => {
336
+ it('should have correct structure', () => {
337
+ expect(STANDARD_RESPONSE.continue).toBe(true);
338
+ expect(STANDARD_RESPONSE.suppressOutput).toBe(true);
339
+ });
340
+ });
341
+ });
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AgentKits Memory Hook CLI
4
+ *
5
+ * Unified CLI handler for all Claude Code hooks.
6
+ * Reads stdin, executes appropriate hook, outputs response.
7
+ *
8
+ * Usage:
9
+ * echo '{"session_id":"..."}' | npx agentkits-memory-hook <event>
10
+ *
11
+ * Events:
12
+ * context - SessionStart: inject memory context
13
+ * session-init - UserPromptSubmit: initialize session
14
+ * observation - PostToolUse: capture tool usage
15
+ * summarize - Stop: generate session summary
16
+ *
17
+ * @module @agentkits/memory/hooks/cli
18
+ */
19
+
20
+ import { parseHookInput, formatResponse, STANDARD_RESPONSE } from './types.js';
21
+ import { createContextHook } from './context.js';
22
+ import { createSessionInitHook } from './session-init.js';
23
+ import { createObservationHook } from './observation.js';
24
+ import { createSummarizeHook } from './summarize.js';
25
+
26
+ /**
27
+ * Read stdin until EOF
28
+ */
29
+ async function readStdin(): Promise<string> {
30
+ return new Promise((resolve) => {
31
+ let data = '';
32
+
33
+ // Set encoding
34
+ process.stdin.setEncoding('utf8');
35
+
36
+ // Handle data
37
+ process.stdin.on('data', (chunk) => {
38
+ data += chunk;
39
+ });
40
+
41
+ // Handle end
42
+ process.stdin.on('end', () => {
43
+ resolve(data);
44
+ });
45
+
46
+ // Handle error
47
+ process.stdin.on('error', () => {
48
+ resolve('');
49
+ });
50
+
51
+ // If stdin is already closed
52
+ if (process.stdin.isTTY) {
53
+ resolve('');
54
+ }
55
+ });
56
+ }
57
+
58
+ /**
59
+ * Main CLI handler
60
+ */
61
+ async function main(): Promise<void> {
62
+ try {
63
+ // Get event type from args
64
+ const event = process.argv[2];
65
+
66
+ if (!event) {
67
+ console.error('Usage: agentkits-memory-hook <event>');
68
+ console.error('Events: context, session-init, observation, summarize');
69
+ process.exit(1);
70
+ }
71
+
72
+ // Read stdin
73
+ const stdin = await readStdin();
74
+
75
+ // Parse input
76
+ const input = parseHookInput(stdin);
77
+
78
+ // Select and execute handler
79
+ let result;
80
+
81
+ switch (event) {
82
+ case 'context':
83
+ result = await createContextHook(input.cwd).execute(input);
84
+ break;
85
+
86
+ case 'session-init':
87
+ result = await createSessionInitHook(input.cwd).execute(input);
88
+ break;
89
+
90
+ case 'observation':
91
+ result = await createObservationHook(input.cwd).execute(input);
92
+ break;
93
+
94
+ case 'summarize':
95
+ result = await createSummarizeHook(input.cwd).execute(input);
96
+ break;
97
+
98
+ default:
99
+ console.error(`Unknown event: ${event}`);
100
+ console.log(JSON.stringify(STANDARD_RESPONSE));
101
+ process.exit(0);
102
+ }
103
+
104
+ // Output response
105
+ console.log(formatResponse(result));
106
+
107
+ } catch (error) {
108
+ // Log error to stderr
109
+ console.error('[AgentKits Memory] CLI error:', error);
110
+
111
+ // Output standard response (don't block Claude)
112
+ console.log(JSON.stringify(STANDARD_RESPONSE));
113
+ }
114
+ }
115
+
116
+ // Run
117
+ main().catch((error) => {
118
+ console.error('[AgentKits Memory] Fatal error:', error);
119
+ console.log(JSON.stringify(STANDARD_RESPONSE));
120
+ process.exit(0);
121
+ });
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Context Hook Handler (SessionStart)
3
+ *
4
+ * Injects memory context at the start of a Claude Code session.
5
+ * Provides previous session history and relevant observations.
6
+ *
7
+ * @module @agentkits/memory/hooks/context
8
+ */
9
+
10
+ import {
11
+ NormalizedHookInput,
12
+ HookResult,
13
+ EventHandler,
14
+ } from './types.js';
15
+ import { MemoryHookService } from './service.js';
16
+
17
+ /**
18
+ * Context Hook - SessionStart Event
19
+ *
20
+ * Called when a new Claude Code session starts.
21
+ * Retrieves and injects previous context to help Claude
22
+ * understand the project history.
23
+ */
24
+ export class ContextHook implements EventHandler {
25
+ private service: MemoryHookService;
26
+
27
+ constructor(service: MemoryHookService) {
28
+ this.service = service;
29
+ }
30
+
31
+ /**
32
+ * Execute the context hook
33
+ */
34
+ async execute(input: NormalizedHookInput): Promise<HookResult> {
35
+ try {
36
+ // Initialize service
37
+ await this.service.initialize();
38
+
39
+ // Get context for this project
40
+ const context = await this.service.getContext(input.project);
41
+
42
+ // No context to inject
43
+ if (!context.markdown || context.markdown.includes('No previous session context')) {
44
+ return {
45
+ continue: true,
46
+ suppressOutput: true,
47
+ };
48
+ }
49
+
50
+ // Inject context as additional context
51
+ return {
52
+ continue: true,
53
+ suppressOutput: false,
54
+ additionalContext: context.markdown,
55
+ };
56
+ } catch (error) {
57
+ // Log error but don't block session
58
+ console.error('[AgentKits Memory] Context hook error:', error);
59
+
60
+ return {
61
+ continue: true,
62
+ suppressOutput: true,
63
+ error: error instanceof Error ? error.message : 'Unknown error',
64
+ };
65
+ }
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Create context hook handler
71
+ */
72
+ export function createContextHook(cwd: string): ContextHook {
73
+ const service = new MemoryHookService(cwd);
74
+ return new ContextHook(service);
75
+ }
76
+
77
+ export default ContextHook;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * AgentKits Memory Hooks
3
+ *
4
+ * Lightweight hook system for auto-capturing Claude Code sessions.
5
+ * Direct SQLite access without HTTP worker overhead.
6
+ *
7
+ * @module @agentkits/memory/hooks
8
+ */
9
+
10
+ // Types
11
+ export * from './types.js';
12
+
13
+ // Service
14
+ export { MemoryHookService, createHookService } from './service.js';
15
+
16
+ // Handlers
17
+ export { ContextHook, createContextHook } from './context.js';
18
+ export { SessionInitHook, createSessionInitHook } from './session-init.js';
19
+ export { ObservationHook, createObservationHook } from './observation.js';
20
+ export { SummarizeHook, createSummarizeHook } from './summarize.js';
21
+
22
+ // Re-export default service
23
+ export { default } from './service.js';
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Observation Hook Handler (PostToolUse)
3
+ *
4
+ * Captures tool usage observations after Claude executes a tool.
5
+ * Stores file reads, writes, commands, and searches for context.
6
+ *
7
+ * @module @agentkits/memory/hooks/observation
8
+ */
9
+
10
+ import {
11
+ NormalizedHookInput,
12
+ HookResult,
13
+ EventHandler,
14
+ } from './types.js';
15
+ import { MemoryHookService } from './service.js';
16
+
17
+ /**
18
+ * Tools to skip capturing (internal/noisy tools)
19
+ */
20
+ const SKIP_TOOLS = new Set([
21
+ 'TodoWrite',
22
+ 'TodoRead',
23
+ 'AskFollowupQuestion',
24
+ 'AttemptCompletion',
25
+ ]);
26
+
27
+ /**
28
+ * Observation Hook - PostToolUse Event
29
+ *
30
+ * Called after Claude executes a tool.
31
+ * Captures the tool usage for future context.
32
+ */
33
+ export class ObservationHook implements EventHandler {
34
+ private service: MemoryHookService;
35
+
36
+ constructor(service: MemoryHookService) {
37
+ this.service = service;
38
+ }
39
+
40
+ /**
41
+ * Execute the observation hook
42
+ */
43
+ async execute(input: NormalizedHookInput): Promise<HookResult> {
44
+ try {
45
+ // Skip if no tool name
46
+ if (!input.toolName) {
47
+ return {
48
+ continue: true,
49
+ suppressOutput: true,
50
+ };
51
+ }
52
+
53
+ // Skip internal tools
54
+ if (SKIP_TOOLS.has(input.toolName)) {
55
+ return {
56
+ continue: true,
57
+ suppressOutput: true,
58
+ };
59
+ }
60
+
61
+ // Initialize service
62
+ await this.service.initialize();
63
+
64
+ // Ensure session exists (create if not)
65
+ await this.service.initSession(input.sessionId, input.project);
66
+
67
+ // Store the observation
68
+ await this.service.storeObservation(
69
+ input.sessionId,
70
+ input.project,
71
+ input.toolName,
72
+ input.toolInput,
73
+ input.toolResponse,
74
+ input.cwd
75
+ );
76
+
77
+ return {
78
+ continue: true,
79
+ suppressOutput: true,
80
+ };
81
+ } catch (error) {
82
+ // Log error but don't block tool execution
83
+ console.error('[AgentKits Memory] Observation hook error:', error);
84
+
85
+ return {
86
+ continue: true,
87
+ suppressOutput: true,
88
+ error: error instanceof Error ? error.message : 'Unknown error',
89
+ };
90
+ }
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Create observation hook handler
96
+ */
97
+ export function createObservationHook(cwd: string): ObservationHook {
98
+ const service = new MemoryHookService(cwd);
99
+ return new ObservationHook(service);
100
+ }
101
+
102
+ export default ObservationHook;