claude-memory-layer 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 (127) hide show
  1. package/.claude-plugin/commands/memory-forget.md +42 -0
  2. package/.claude-plugin/commands/memory-history.md +34 -0
  3. package/.claude-plugin/commands/memory-import.md +56 -0
  4. package/.claude-plugin/commands/memory-list.md +37 -0
  5. package/.claude-plugin/commands/memory-search.md +36 -0
  6. package/.claude-plugin/commands/memory-stats.md +34 -0
  7. package/.claude-plugin/hooks.json +59 -0
  8. package/.claude-plugin/plugin.json +24 -0
  9. package/.history/package_20260201112328.json +45 -0
  10. package/.history/package_20260201113602.json +45 -0
  11. package/.history/package_20260201113713.json +45 -0
  12. package/.history/package_20260201114110.json +45 -0
  13. package/Memo.txt +558 -0
  14. package/README.md +520 -0
  15. package/context.md +636 -0
  16. package/dist/.claude-plugin/commands/memory-forget.md +42 -0
  17. package/dist/.claude-plugin/commands/memory-history.md +34 -0
  18. package/dist/.claude-plugin/commands/memory-import.md +56 -0
  19. package/dist/.claude-plugin/commands/memory-list.md +37 -0
  20. package/dist/.claude-plugin/commands/memory-search.md +36 -0
  21. package/dist/.claude-plugin/commands/memory-stats.md +34 -0
  22. package/dist/.claude-plugin/hooks.json +59 -0
  23. package/dist/.claude-plugin/plugin.json +24 -0
  24. package/dist/cli/index.js +3539 -0
  25. package/dist/cli/index.js.map +7 -0
  26. package/dist/core/index.js +4408 -0
  27. package/dist/core/index.js.map +7 -0
  28. package/dist/hooks/session-end.js +2971 -0
  29. package/dist/hooks/session-end.js.map +7 -0
  30. package/dist/hooks/session-start.js +2969 -0
  31. package/dist/hooks/session-start.js.map +7 -0
  32. package/dist/hooks/stop.js +3123 -0
  33. package/dist/hooks/stop.js.map +7 -0
  34. package/dist/hooks/user-prompt-submit.js +2960 -0
  35. package/dist/hooks/user-prompt-submit.js.map +7 -0
  36. package/dist/services/memory-service.js +2931 -0
  37. package/dist/services/memory-service.js.map +7 -0
  38. package/package.json +45 -0
  39. package/plan.md +1642 -0
  40. package/scripts/build.ts +102 -0
  41. package/spec.md +624 -0
  42. package/specs/citations-system/context.md +243 -0
  43. package/specs/citations-system/plan.md +495 -0
  44. package/specs/citations-system/spec.md +371 -0
  45. package/specs/endless-mode/context.md +305 -0
  46. package/specs/endless-mode/plan.md +620 -0
  47. package/specs/endless-mode/spec.md +455 -0
  48. package/specs/entity-edge-model/context.md +401 -0
  49. package/specs/entity-edge-model/plan.md +459 -0
  50. package/specs/entity-edge-model/spec.md +391 -0
  51. package/specs/evidence-aligner-v2/context.md +401 -0
  52. package/specs/evidence-aligner-v2/plan.md +303 -0
  53. package/specs/evidence-aligner-v2/spec.md +312 -0
  54. package/specs/mcp-desktop-integration/context.md +278 -0
  55. package/specs/mcp-desktop-integration/plan.md +550 -0
  56. package/specs/mcp-desktop-integration/spec.md +494 -0
  57. package/specs/post-tool-use-hook/context.md +319 -0
  58. package/specs/post-tool-use-hook/plan.md +469 -0
  59. package/specs/post-tool-use-hook/spec.md +364 -0
  60. package/specs/private-tags/context.md +288 -0
  61. package/specs/private-tags/plan.md +412 -0
  62. package/specs/private-tags/spec.md +345 -0
  63. package/specs/progressive-disclosure/context.md +346 -0
  64. package/specs/progressive-disclosure/plan.md +663 -0
  65. package/specs/progressive-disclosure/spec.md +415 -0
  66. package/specs/task-entity-system/context.md +297 -0
  67. package/specs/task-entity-system/plan.md +301 -0
  68. package/specs/task-entity-system/spec.md +314 -0
  69. package/specs/vector-outbox-v2/context.md +470 -0
  70. package/specs/vector-outbox-v2/plan.md +562 -0
  71. package/specs/vector-outbox-v2/spec.md +466 -0
  72. package/specs/web-viewer-ui/context.md +384 -0
  73. package/specs/web-viewer-ui/plan.md +797 -0
  74. package/specs/web-viewer-ui/spec.md +516 -0
  75. package/src/cli/index.ts +570 -0
  76. package/src/core/canonical-key.ts +186 -0
  77. package/src/core/citation-generator.ts +63 -0
  78. package/src/core/consolidated-store.ts +279 -0
  79. package/src/core/consolidation-worker.ts +384 -0
  80. package/src/core/context-formatter.ts +276 -0
  81. package/src/core/continuity-manager.ts +336 -0
  82. package/src/core/edge-repo.ts +324 -0
  83. package/src/core/embedder.ts +124 -0
  84. package/src/core/entity-repo.ts +342 -0
  85. package/src/core/event-store.ts +672 -0
  86. package/src/core/evidence-aligner.ts +635 -0
  87. package/src/core/graduation.ts +365 -0
  88. package/src/core/index.ts +32 -0
  89. package/src/core/matcher.ts +210 -0
  90. package/src/core/metadata-extractor.ts +203 -0
  91. package/src/core/privacy/filter.ts +179 -0
  92. package/src/core/privacy/index.ts +20 -0
  93. package/src/core/privacy/tag-parser.ts +145 -0
  94. package/src/core/progressive-retriever.ts +415 -0
  95. package/src/core/retriever.ts +235 -0
  96. package/src/core/task/blocker-resolver.ts +325 -0
  97. package/src/core/task/index.ts +9 -0
  98. package/src/core/task/task-matcher.ts +238 -0
  99. package/src/core/task/task-projector.ts +345 -0
  100. package/src/core/task/task-resolver.ts +414 -0
  101. package/src/core/types.ts +841 -0
  102. package/src/core/vector-outbox.ts +295 -0
  103. package/src/core/vector-store.ts +182 -0
  104. package/src/core/vector-worker.ts +488 -0
  105. package/src/core/working-set-store.ts +244 -0
  106. package/src/hooks/post-tool-use.ts +127 -0
  107. package/src/hooks/session-end.ts +78 -0
  108. package/src/hooks/session-start.ts +57 -0
  109. package/src/hooks/stop.ts +78 -0
  110. package/src/hooks/user-prompt-submit.ts +54 -0
  111. package/src/mcp/handlers.ts +212 -0
  112. package/src/mcp/index.ts +47 -0
  113. package/src/mcp/tools.ts +78 -0
  114. package/src/server/api/citations.ts +101 -0
  115. package/src/server/api/events.ts +101 -0
  116. package/src/server/api/index.ts +18 -0
  117. package/src/server/api/search.ts +98 -0
  118. package/src/server/api/sessions.ts +111 -0
  119. package/src/server/api/stats.ts +97 -0
  120. package/src/server/index.ts +91 -0
  121. package/src/services/memory-service.ts +626 -0
  122. package/src/services/session-history-importer.ts +367 -0
  123. package/tests/canonical-key.test.ts +101 -0
  124. package/tests/evidence-aligner.test.ts +152 -0
  125. package/tests/matcher.test.ts +112 -0
  126. package/tsconfig.json +24 -0
  127. package/vitest.config.ts +15 -0
@@ -0,0 +1,345 @@
1
+ /**
2
+ * Task Projector - Project task events to entities/edges
3
+ * AXIOMMIND: Incremental processing with offset tracking
4
+ */
5
+
6
+ import { Database } from 'duckdb';
7
+ import { randomUUID } from 'crypto';
8
+ import type { BlockerMode, BlockerRef } from '../types.js';
9
+
10
+ const PROJECTOR_NAME = 'task_projector';
11
+ const TASK_EVENT_TYPES = [
12
+ 'task_created',
13
+ 'task_status_changed',
14
+ 'task_priority_changed',
15
+ 'task_blockers_set',
16
+ 'task_transition_rejected',
17
+ 'condition_resolved_to'
18
+ ];
19
+
20
+ interface ProjectionOffset {
21
+ lastEventId: string | null;
22
+ lastTimestamp: Date | null;
23
+ }
24
+
25
+ interface TaskEvent {
26
+ id: string;
27
+ eventType: string;
28
+ sessionId: string;
29
+ timestamp: Date;
30
+ content: Record<string, unknown>;
31
+ }
32
+
33
+ export class TaskProjector {
34
+ constructor(private db: Database) {}
35
+
36
+ /**
37
+ * Get current projection offset
38
+ */
39
+ async getOffset(): Promise<ProjectionOffset> {
40
+ const rows = await this.db.all<Array<Record<string, unknown>>>(
41
+ `SELECT last_event_id, last_timestamp
42
+ FROM projection_offsets
43
+ WHERE projection_name = ?`,
44
+ [PROJECTOR_NAME]
45
+ );
46
+
47
+ if (rows.length === 0) {
48
+ return { lastEventId: null, lastTimestamp: null };
49
+ }
50
+
51
+ return {
52
+ lastEventId: rows[0].last_event_id as string | null,
53
+ lastTimestamp: rows[0].last_timestamp
54
+ ? new Date(rows[0].last_timestamp as string)
55
+ : null
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Update projection offset
61
+ */
62
+ private async updateOffset(eventId: string, timestamp: Date): Promise<void> {
63
+ await this.db.run(
64
+ `INSERT INTO projection_offsets (projection_name, last_event_id, last_timestamp, updated_at)
65
+ VALUES (?, ?, ?, CURRENT_TIMESTAMP)
66
+ ON CONFLICT (projection_name) DO UPDATE SET
67
+ last_event_id = excluded.last_event_id,
68
+ last_timestamp = excluded.last_timestamp,
69
+ updated_at = CURRENT_TIMESTAMP`,
70
+ [PROJECTOR_NAME, eventId, timestamp.toISOString()]
71
+ );
72
+ }
73
+
74
+ /**
75
+ * Fetch events since last offset
76
+ */
77
+ async fetchEventsSince(
78
+ offset: ProjectionOffset,
79
+ limit: number = 100
80
+ ): Promise<TaskEvent[]> {
81
+ let query = `
82
+ SELECT id, event_type, session_id, timestamp, content
83
+ FROM events
84
+ WHERE event_type IN (${TASK_EVENT_TYPES.map(() => '?').join(', ')})
85
+ `;
86
+ const params: unknown[] = [...TASK_EVENT_TYPES];
87
+
88
+ if (offset.lastTimestamp && offset.lastEventId) {
89
+ query += ` AND (timestamp > ? OR (timestamp = ? AND id > ?))`;
90
+ params.push(
91
+ offset.lastTimestamp.toISOString(),
92
+ offset.lastTimestamp.toISOString(),
93
+ offset.lastEventId
94
+ );
95
+ }
96
+
97
+ query += ` ORDER BY timestamp ASC, id ASC LIMIT ?`;
98
+ params.push(limit);
99
+
100
+ const rows = await this.db.all<Array<Record<string, unknown>>>(query, params);
101
+
102
+ return rows.map(row => ({
103
+ id: row.id as string,
104
+ eventType: row.event_type as string,
105
+ sessionId: row.session_id as string,
106
+ timestamp: new Date(row.timestamp as string),
107
+ content: typeof row.content === 'string'
108
+ ? JSON.parse(row.content)
109
+ : row.content as Record<string, unknown>
110
+ }));
111
+ }
112
+
113
+ /**
114
+ * Process a batch of events
115
+ */
116
+ async processBatch(batchSize: number = 100): Promise<number> {
117
+ const offset = await this.getOffset();
118
+ const events = await this.fetchEventsSince(offset, batchSize);
119
+
120
+ if (events.length === 0) {
121
+ return 0;
122
+ }
123
+
124
+ for (const event of events) {
125
+ await this.processEvent(event);
126
+ await this.updateOffset(event.id, event.timestamp);
127
+ }
128
+
129
+ return events.length;
130
+ }
131
+
132
+ /**
133
+ * Process all pending events
134
+ */
135
+ async processAll(): Promise<number> {
136
+ let totalProcessed = 0;
137
+ let processed: number;
138
+
139
+ do {
140
+ processed = await this.processBatch();
141
+ totalProcessed += processed;
142
+ } while (processed > 0);
143
+
144
+ return totalProcessed;
145
+ }
146
+
147
+ /**
148
+ * Process a single event
149
+ */
150
+ private async processEvent(event: TaskEvent): Promise<void> {
151
+ switch (event.eventType) {
152
+ case 'task_created':
153
+ // Entity already created by TaskResolver, just ensure vector outbox entry
154
+ await this.enqueueForVectorization(event.content.taskId as string, 'task_title');
155
+ break;
156
+
157
+ case 'task_status_changed':
158
+ await this.handleStatusChanged(event);
159
+ break;
160
+
161
+ case 'task_priority_changed':
162
+ // Priority change doesn't affect edges
163
+ break;
164
+
165
+ case 'task_blockers_set':
166
+ await this.handleBlockersSet(event);
167
+ break;
168
+
169
+ case 'condition_resolved_to':
170
+ await this.handleConditionResolved(event);
171
+ break;
172
+
173
+ case 'task_transition_rejected':
174
+ // Log event, no state change
175
+ break;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Handle task_status_changed event
181
+ */
182
+ private async handleStatusChanged(event: TaskEvent): Promise<void> {
183
+ const { taskId, toStatus } = event.content;
184
+
185
+ // If status changed to 'done', remove all blocked_by edges
186
+ if (toStatus === 'done') {
187
+ await this.db.run(
188
+ `DELETE FROM edges
189
+ WHERE src_id = ? AND rel_type IN ('blocked_by', 'blocked_by_suggested')`,
190
+ [taskId]
191
+ );
192
+
193
+ // Clear blockers cache in entity
194
+ await this.db.run(
195
+ `UPDATE entities
196
+ SET current_json = json_remove(json_remove(current_json, '$.blockers'), '$.blockerSuggestions'),
197
+ updated_at = CURRENT_TIMESTAMP
198
+ WHERE entity_id = ?`,
199
+ [taskId]
200
+ );
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Handle task_blockers_set event
206
+ */
207
+ private async handleBlockersSet(event: TaskEvent): Promise<void> {
208
+ const { taskId, mode, blockers } = event.content as {
209
+ taskId: string;
210
+ mode: BlockerMode;
211
+ blockers: BlockerRef[];
212
+ };
213
+
214
+ if (mode === 'replace') {
215
+ // Delete existing blocked_by edges
216
+ await this.db.run(
217
+ `DELETE FROM edges WHERE src_id = ? AND rel_type = 'blocked_by'`,
218
+ [taskId]
219
+ );
220
+
221
+ // Create new edges
222
+ for (const blocker of blockers) {
223
+ await this.createBlockerEdge(taskId, blocker, 'blocked_by');
224
+ }
225
+
226
+ // Update entity cache
227
+ const blockerIds = blockers.map(b => b.entityId);
228
+ await this.db.run(
229
+ `UPDATE entities
230
+ SET current_json = json_set(current_json, '$.blockers', ?),
231
+ updated_at = CURRENT_TIMESTAMP
232
+ WHERE entity_id = ?`,
233
+ [JSON.stringify(blockerIds), taskId]
234
+ );
235
+
236
+ } else {
237
+ // mode === 'suggest'
238
+ // Delete existing suggested edges
239
+ await this.db.run(
240
+ `DELETE FROM edges WHERE src_id = ? AND rel_type = 'blocked_by_suggested'`,
241
+ [taskId]
242
+ );
243
+
244
+ // Create suggested edges
245
+ for (const blocker of blockers) {
246
+ await this.createBlockerEdge(taskId, blocker, 'blocked_by_suggested');
247
+ }
248
+
249
+ // Update entity cache (suggestions)
250
+ const suggestionIds = blockers.map(b => b.entityId);
251
+ await this.db.run(
252
+ `UPDATE entities
253
+ SET current_json = json_set(current_json, '$.blockerSuggestions', ?),
254
+ updated_at = CURRENT_TIMESTAMP
255
+ WHERE entity_id = ?`,
256
+ [JSON.stringify(suggestionIds), taskId]
257
+ );
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Create blocker edge
263
+ */
264
+ private async createBlockerEdge(
265
+ taskId: string,
266
+ blocker: BlockerRef,
267
+ relType: 'blocked_by' | 'blocked_by_suggested'
268
+ ): Promise<void> {
269
+ const edgeId = randomUUID();
270
+
271
+ await this.db.run(
272
+ `INSERT INTO edges (edge_id, src_type, src_id, rel_type, dst_type, dst_id, meta_json, created_at)
273
+ VALUES (?, 'entity', ?, ?, 'entity', ?, ?, CURRENT_TIMESTAMP)
274
+ ON CONFLICT DO NOTHING`,
275
+ [
276
+ edgeId,
277
+ taskId,
278
+ relType,
279
+ blocker.entityId,
280
+ JSON.stringify({
281
+ kind: blocker.kind,
282
+ rawText: blocker.rawText,
283
+ confidence: blocker.confidence,
284
+ candidates: blocker.candidates
285
+ })
286
+ ]
287
+ );
288
+ }
289
+
290
+ /**
291
+ * Handle condition_resolved_to event
292
+ */
293
+ private async handleConditionResolved(event: TaskEvent): Promise<void> {
294
+ const { conditionId, resolvedTo } = event.content as {
295
+ conditionId: string;
296
+ resolvedTo: { kind: string; entityId: string };
297
+ };
298
+
299
+ // Update condition entity
300
+ await this.db.run(
301
+ `UPDATE entities
302
+ SET current_json = json_set(json_set(current_json, '$.resolved', true), '$.resolvedTo', ?),
303
+ updated_at = CURRENT_TIMESTAMP
304
+ WHERE entity_id = ?`,
305
+ [JSON.stringify(resolvedTo), conditionId]
306
+ );
307
+
308
+ // Edge already created by TaskResolver
309
+ }
310
+
311
+ /**
312
+ * Enqueue entity for vectorization
313
+ */
314
+ private async enqueueForVectorization(itemId: string, itemKind: string): Promise<void> {
315
+ const jobId = randomUUID();
316
+ const embeddingVersion = 'v1'; // Should come from config
317
+
318
+ await this.db.run(
319
+ `INSERT INTO vector_outbox (job_id, item_kind, item_id, embedding_version, status, retry_count)
320
+ VALUES (?, ?, ?, ?, 'pending', 0)
321
+ ON CONFLICT (item_kind, item_id, embedding_version) DO NOTHING`,
322
+ [jobId, itemKind, itemId, embeddingVersion]
323
+ );
324
+ }
325
+
326
+ /**
327
+ * Rebuild all projections from scratch
328
+ * WARNING: This clears all edges and rebuilds from events
329
+ */
330
+ async rebuild(): Promise<number> {
331
+ // Clear task-related edges
332
+ await this.db.run(
333
+ `DELETE FROM edges WHERE rel_type IN ('blocked_by', 'blocked_by_suggested', 'resolves_to')`
334
+ );
335
+
336
+ // Reset offset
337
+ await this.db.run(
338
+ `DELETE FROM projection_offsets WHERE projection_name = ?`,
339
+ [PROJECTOR_NAME]
340
+ );
341
+
342
+ // Process all events
343
+ return this.processAll();
344
+ }
345
+ }