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.
- package/.claude-plugin/commands/memory-forget.md +42 -0
- package/.claude-plugin/commands/memory-history.md +34 -0
- package/.claude-plugin/commands/memory-import.md +56 -0
- package/.claude-plugin/commands/memory-list.md +37 -0
- package/.claude-plugin/commands/memory-search.md +36 -0
- package/.claude-plugin/commands/memory-stats.md +34 -0
- package/.claude-plugin/hooks.json +59 -0
- package/.claude-plugin/plugin.json +24 -0
- package/.history/package_20260201112328.json +45 -0
- package/.history/package_20260201113602.json +45 -0
- package/.history/package_20260201113713.json +45 -0
- package/.history/package_20260201114110.json +45 -0
- package/Memo.txt +558 -0
- package/README.md +520 -0
- package/context.md +636 -0
- package/dist/.claude-plugin/commands/memory-forget.md +42 -0
- package/dist/.claude-plugin/commands/memory-history.md +34 -0
- package/dist/.claude-plugin/commands/memory-import.md +56 -0
- package/dist/.claude-plugin/commands/memory-list.md +37 -0
- package/dist/.claude-plugin/commands/memory-search.md +36 -0
- package/dist/.claude-plugin/commands/memory-stats.md +34 -0
- package/dist/.claude-plugin/hooks.json +59 -0
- package/dist/.claude-plugin/plugin.json +24 -0
- package/dist/cli/index.js +3539 -0
- package/dist/cli/index.js.map +7 -0
- package/dist/core/index.js +4408 -0
- package/dist/core/index.js.map +7 -0
- package/dist/hooks/session-end.js +2971 -0
- package/dist/hooks/session-end.js.map +7 -0
- package/dist/hooks/session-start.js +2969 -0
- package/dist/hooks/session-start.js.map +7 -0
- package/dist/hooks/stop.js +3123 -0
- package/dist/hooks/stop.js.map +7 -0
- package/dist/hooks/user-prompt-submit.js +2960 -0
- package/dist/hooks/user-prompt-submit.js.map +7 -0
- package/dist/services/memory-service.js +2931 -0
- package/dist/services/memory-service.js.map +7 -0
- package/package.json +45 -0
- package/plan.md +1642 -0
- package/scripts/build.ts +102 -0
- package/spec.md +624 -0
- package/specs/citations-system/context.md +243 -0
- package/specs/citations-system/plan.md +495 -0
- package/specs/citations-system/spec.md +371 -0
- package/specs/endless-mode/context.md +305 -0
- package/specs/endless-mode/plan.md +620 -0
- package/specs/endless-mode/spec.md +455 -0
- package/specs/entity-edge-model/context.md +401 -0
- package/specs/entity-edge-model/plan.md +459 -0
- package/specs/entity-edge-model/spec.md +391 -0
- package/specs/evidence-aligner-v2/context.md +401 -0
- package/specs/evidence-aligner-v2/plan.md +303 -0
- package/specs/evidence-aligner-v2/spec.md +312 -0
- package/specs/mcp-desktop-integration/context.md +278 -0
- package/specs/mcp-desktop-integration/plan.md +550 -0
- package/specs/mcp-desktop-integration/spec.md +494 -0
- package/specs/post-tool-use-hook/context.md +319 -0
- package/specs/post-tool-use-hook/plan.md +469 -0
- package/specs/post-tool-use-hook/spec.md +364 -0
- package/specs/private-tags/context.md +288 -0
- package/specs/private-tags/plan.md +412 -0
- package/specs/private-tags/spec.md +345 -0
- package/specs/progressive-disclosure/context.md +346 -0
- package/specs/progressive-disclosure/plan.md +663 -0
- package/specs/progressive-disclosure/spec.md +415 -0
- package/specs/task-entity-system/context.md +297 -0
- package/specs/task-entity-system/plan.md +301 -0
- package/specs/task-entity-system/spec.md +314 -0
- package/specs/vector-outbox-v2/context.md +470 -0
- package/specs/vector-outbox-v2/plan.md +562 -0
- package/specs/vector-outbox-v2/spec.md +466 -0
- package/specs/web-viewer-ui/context.md +384 -0
- package/specs/web-viewer-ui/plan.md +797 -0
- package/specs/web-viewer-ui/spec.md +516 -0
- package/src/cli/index.ts +570 -0
- package/src/core/canonical-key.ts +186 -0
- package/src/core/citation-generator.ts +63 -0
- package/src/core/consolidated-store.ts +279 -0
- package/src/core/consolidation-worker.ts +384 -0
- package/src/core/context-formatter.ts +276 -0
- package/src/core/continuity-manager.ts +336 -0
- package/src/core/edge-repo.ts +324 -0
- package/src/core/embedder.ts +124 -0
- package/src/core/entity-repo.ts +342 -0
- package/src/core/event-store.ts +672 -0
- package/src/core/evidence-aligner.ts +635 -0
- package/src/core/graduation.ts +365 -0
- package/src/core/index.ts +32 -0
- package/src/core/matcher.ts +210 -0
- package/src/core/metadata-extractor.ts +203 -0
- package/src/core/privacy/filter.ts +179 -0
- package/src/core/privacy/index.ts +20 -0
- package/src/core/privacy/tag-parser.ts +145 -0
- package/src/core/progressive-retriever.ts +415 -0
- package/src/core/retriever.ts +235 -0
- package/src/core/task/blocker-resolver.ts +325 -0
- package/src/core/task/index.ts +9 -0
- package/src/core/task/task-matcher.ts +238 -0
- package/src/core/task/task-projector.ts +345 -0
- package/src/core/task/task-resolver.ts +414 -0
- package/src/core/types.ts +841 -0
- package/src/core/vector-outbox.ts +295 -0
- package/src/core/vector-store.ts +182 -0
- package/src/core/vector-worker.ts +488 -0
- package/src/core/working-set-store.ts +244 -0
- package/src/hooks/post-tool-use.ts +127 -0
- package/src/hooks/session-end.ts +78 -0
- package/src/hooks/session-start.ts +57 -0
- package/src/hooks/stop.ts +78 -0
- package/src/hooks/user-prompt-submit.ts +54 -0
- package/src/mcp/handlers.ts +212 -0
- package/src/mcp/index.ts +47 -0
- package/src/mcp/tools.ts +78 -0
- package/src/server/api/citations.ts +101 -0
- package/src/server/api/events.ts +101 -0
- package/src/server/api/index.ts +18 -0
- package/src/server/api/search.ts +98 -0
- package/src/server/api/sessions.ts +111 -0
- package/src/server/api/stats.ts +97 -0
- package/src/server/index.ts +91 -0
- package/src/services/memory-service.ts +626 -0
- package/src/services/session-history-importer.ts +367 -0
- package/tests/canonical-key.test.ts +101 -0
- package/tests/evidence-aligner.test.ts +152 -0
- package/tests/matcher.test.ts +112 -0
- package/tsconfig.json +24 -0
- 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
|
+
}
|