ds4-context-core 0.1.2 → 0.2.0-beta.2
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/README.md +20 -4
- package/dist/adapter/conformance.d.ts +35 -0
- package/dist/adapter/conformance.d.ts.map +1 -0
- package/dist/adapter/conformance.js +260 -0
- package/dist/adapter/conformance.js.map +1 -0
- package/dist/adapter/local-kv.d.ts +125 -0
- package/dist/adapter/local-kv.d.ts.map +1 -0
- package/dist/adapter/local-kv.js +276 -0
- package/dist/adapter/local-kv.js.map +1 -0
- package/dist/adapter/runtime-adapter.d.ts +124 -0
- package/dist/adapter/runtime-adapter.d.ts.map +1 -0
- package/dist/adapter/runtime-adapter.js +289 -0
- package/dist/adapter/runtime-adapter.js.map +1 -0
- package/dist/config/config-loader.d.ts +1 -0
- package/dist/config/config-loader.d.ts.map +1 -1
- package/dist/config/config-loader.js +110 -1
- package/dist/config/config-loader.js.map +1 -1
- package/dist/config/config.d.ts +50 -0
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +31 -0
- package/dist/config/config.js.map +1 -1
- package/dist/core/canonical-message.d.ts +2 -1
- package/dist/core/canonical-message.d.ts.map +1 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/manifest/context-manifest.d.ts +12 -0
- package/dist/manifest/context-manifest.d.ts.map +1 -1
- package/dist/memory/memory-manager.d.ts +7 -1
- package/dist/memory/memory-manager.d.ts.map +1 -1
- package/dist/memory/memory-manager.js +100 -3
- package/dist/memory/memory-manager.js.map +1 -1
- package/dist/memory/memory-types.d.ts +43 -0
- package/dist/memory/memory-types.d.ts.map +1 -1
- package/dist/memory/memory-types.js.map +1 -1
- package/dist/persistence/migrations.d.ts +2 -1
- package/dist/persistence/migrations.d.ts.map +1 -1
- package/dist/persistence/migrations.js +155 -25
- package/dist/persistence/migrations.js.map +1 -1
- package/dist/persistence/repositories/artifact-repository.d.ts +3 -1
- package/dist/persistence/repositories/artifact-repository.d.ts.map +1 -1
- package/dist/persistence/repositories/artifact-repository.js +16 -27
- package/dist/persistence/repositories/artifact-repository.js.map +1 -1
- package/dist/persistence/repositories/context-manifest-repository.d.ts +3 -1
- package/dist/persistence/repositories/context-manifest-repository.d.ts.map +1 -1
- package/dist/persistence/repositories/context-manifest-repository.js +10 -18
- package/dist/persistence/repositories/context-manifest-repository.js.map +1 -1
- package/dist/persistence/repositories/context-quality-repository.d.ts +21 -0
- package/dist/persistence/repositories/context-quality-repository.d.ts.map +1 -0
- package/dist/persistence/repositories/context-quality-repository.js +83 -0
- package/dist/persistence/repositories/context-quality-repository.js.map +1 -0
- package/dist/persistence/repositories/embedding-repository.d.ts +37 -0
- package/dist/persistence/repositories/embedding-repository.d.ts.map +1 -0
- package/dist/persistence/repositories/embedding-repository.js +142 -0
- package/dist/persistence/repositories/embedding-repository.js.map +1 -0
- package/dist/persistence/repositories/lease-repository.d.ts +22 -0
- package/dist/persistence/repositories/lease-repository.d.ts.map +1 -0
- package/dist/persistence/repositories/lease-repository.js +113 -0
- package/dist/persistence/repositories/lease-repository.js.map +1 -0
- package/dist/persistence/repositories/memory-repository.d.ts +34 -3
- package/dist/persistence/repositories/memory-repository.d.ts.map +1 -1
- package/dist/persistence/repositories/memory-repository.js +426 -64
- package/dist/persistence/repositories/memory-repository.js.map +1 -1
- package/dist/persistence/repositories/project-knowledge-repository.d.ts +28 -5
- package/dist/persistence/repositories/project-knowledge-repository.d.ts.map +1 -1
- package/dist/persistence/repositories/project-knowledge-repository.js +123 -53
- package/dist/persistence/repositories/project-knowledge-repository.js.map +1 -1
- package/dist/persistence/repositories/session-index-repository.d.ts +9 -1
- package/dist/persistence/repositories/session-index-repository.d.ts.map +1 -1
- package/dist/persistence/repositories/session-index-repository.js +38 -17
- package/dist/persistence/repositories/session-index-repository.js.map +1 -1
- package/dist/persistence/repositories/summary-repository.d.ts +3 -1
- package/dist/persistence/repositories/summary-repository.d.ts.map +1 -1
- package/dist/persistence/repositories/summary-repository.js +35 -31
- package/dist/persistence/repositories/summary-repository.js.map +1 -1
- package/dist/persistence/sqlite.d.ts +10 -0
- package/dist/persistence/sqlite.d.ts.map +1 -1
- package/dist/persistence/sqlite.js +41 -12
- package/dist/persistence/sqlite.js.map +1 -1
- package/dist/persistence/write-coordinator.d.ts +37 -0
- package/dist/persistence/write-coordinator.d.ts.map +1 -0
- package/dist/persistence/write-coordinator.js +120 -0
- package/dist/persistence/write-coordinator.js.map +1 -0
- package/dist/planner/context-planner.d.ts +5 -0
- package/dist/planner/context-planner.d.ts.map +1 -1
- package/dist/planner/context-planner.js +41 -62
- package/dist/planner/context-planner.js.map +1 -1
- package/dist/project/file-indexer.d.ts +5 -3
- package/dist/project/file-indexer.d.ts.map +1 -1
- package/dist/project/file-indexer.js +83 -38
- package/dist/project/file-indexer.js.map +1 -1
- package/dist/project/project-knowledge.d.ts +15 -3
- package/dist/project/project-knowledge.d.ts.map +1 -1
- package/dist/project/project-knowledge.js +168 -42
- package/dist/project/project-knowledge.js.map +1 -1
- package/dist/project/symbol-parser.d.ts +48 -0
- package/dist/project/symbol-parser.d.ts.map +1 -0
- package/dist/project/symbol-parser.js +427 -0
- package/dist/project/symbol-parser.js.map +1 -0
- package/dist/quality/context-quality.d.ts +172 -0
- package/dist/quality/context-quality.d.ts.map +1 -0
- package/dist/quality/context-quality.js +624 -0
- package/dist/quality/context-quality.js.map +1 -0
- package/dist/ranking/learned-ranker.d.ts +174 -0
- package/dist/ranking/learned-ranker.d.ts.map +1 -0
- package/dist/ranking/learned-ranker.js +782 -0
- package/dist/ranking/learned-ranker.js.map +1 -0
- package/dist/retrieval/embedding.d.ts +92 -0
- package/dist/retrieval/embedding.d.ts.map +1 -0
- package/dist/retrieval/embedding.js +56 -0
- package/dist/retrieval/embedding.js.map +1 -0
- package/dist/retrieval/retrieval-engine.d.ts +9 -1
- package/dist/retrieval/retrieval-engine.d.ts.map +1 -1
- package/dist/retrieval/retrieval-engine.js +105 -7
- package/dist/retrieval/retrieval-engine.js.map +1 -1
- package/dist/retrieval/semantic-index.d.ts +30 -0
- package/dist/retrieval/semantic-index.d.ts.map +1 -0
- package/dist/retrieval/semantic-index.js +284 -0
- package/dist/retrieval/semantic-index.js.map +1 -0
- package/dist/retrieval/semantic-quality.d.ts +52 -0
- package/dist/retrieval/semantic-quality.d.ts.map +1 -0
- package/dist/retrieval/semantic-quality.js +117 -0
- package/dist/retrieval/semantic-quality.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,17 +1,5 @@
|
|
|
1
|
+
import { SqliteWriteCoordinator } from "../write-coordinator.js";
|
|
1
2
|
import { parseMemoryMutation, parsePinMutation, } from "../../memory/memory-types.js";
|
|
2
|
-
function runTransaction(database, operation) {
|
|
3
|
-
database.exec("BEGIN IMMEDIATE");
|
|
4
|
-
try {
|
|
5
|
-
const result = operation();
|
|
6
|
-
database.exec("COMMIT");
|
|
7
|
-
return result;
|
|
8
|
-
}
|
|
9
|
-
catch (error) {
|
|
10
|
-
if (database.isTransaction)
|
|
11
|
-
database.exec("ROLLBACK");
|
|
12
|
-
throw error;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
3
|
function parseJson(value) {
|
|
16
4
|
try {
|
|
17
5
|
return JSON.parse(value);
|
|
@@ -20,17 +8,53 @@ function parseJson(value) {
|
|
|
20
8
|
return undefined;
|
|
21
9
|
}
|
|
22
10
|
}
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
|
|
11
|
+
function stringList(value) {
|
|
12
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string")
|
|
13
|
+
? [...new Set(value)]
|
|
14
|
+
: undefined;
|
|
15
|
+
}
|
|
16
|
+
function mutationProvenance(value) {
|
|
17
|
+
if (!value || typeof value !== "object")
|
|
18
|
+
return undefined;
|
|
19
|
+
const record = value;
|
|
20
|
+
const sourceEntryIds = stringList(record.sourceEntryIds);
|
|
21
|
+
const contradicts = stringList(record.contradicts);
|
|
22
|
+
if (typeof record.sourceSessionId !== "string"
|
|
23
|
+
|| typeof record.mutationEntryId !== "string"
|
|
24
|
+
|| !sourceEntryIds
|
|
25
|
+
|| !contradicts) {
|
|
26
26
|
return undefined;
|
|
27
|
-
|
|
28
|
-
return
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
sourceSessionId: record.sourceSessionId,
|
|
30
|
+
...(typeof record.sourceSessionFile === "string"
|
|
31
|
+
? { sourceSessionFile: record.sourceSessionFile }
|
|
32
|
+
: {}),
|
|
33
|
+
mutationEntryId: record.mutationEntryId,
|
|
34
|
+
...(typeof record.sourceBranchEntryId === "string"
|
|
35
|
+
? { sourceBranchEntryId: record.sourceBranchEntryId }
|
|
36
|
+
: {}),
|
|
37
|
+
sourceEntryIds,
|
|
38
|
+
...(typeof record.supersedes === "string" ? { supersedes: record.supersedes } : {}),
|
|
39
|
+
contradicts,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function metadata(value) {
|
|
43
|
+
const parsed = parseJson(value);
|
|
44
|
+
if (!parsed || typeof parsed !== "object")
|
|
45
|
+
return {};
|
|
46
|
+
const record = parsed;
|
|
47
|
+
const classification = record.classification === "normal"
|
|
48
|
+
|| record.classification === "internal"
|
|
49
|
+
|| record.classification === "sensitive"
|
|
50
|
+
|| record.classification === "local-only"
|
|
51
|
+
? record.classification
|
|
33
52
|
: undefined;
|
|
53
|
+
const provenance = mutationProvenance(record.provenance);
|
|
54
|
+
return {
|
|
55
|
+
...(classification ? { classification } : {}),
|
|
56
|
+
...(provenance ? { provenance } : {}),
|
|
57
|
+
};
|
|
34
58
|
}
|
|
35
59
|
function entryIdFromKey(entryKey) {
|
|
36
60
|
const separator = entryKey.indexOf(":");
|
|
@@ -41,36 +65,53 @@ function identityKey(item) {
|
|
|
41
65
|
return undefined;
|
|
42
66
|
return [item.scope, item.sessionId ?? "", item.projectPath ?? "", item.key].join("\0");
|
|
43
67
|
}
|
|
68
|
+
function memoryMutationMatchesSource(item, row) {
|
|
69
|
+
return item.scope === "session"
|
|
70
|
+
? item.sessionId === row.session_id
|
|
71
|
+
: item.projectPath === row.source_project_path;
|
|
72
|
+
}
|
|
73
|
+
function pinMutationMatchesSource(item, row) {
|
|
74
|
+
return item.scope === "project"
|
|
75
|
+
? item.projectPath === row.source_project_path
|
|
76
|
+
: item.sessionId === row.session_id;
|
|
77
|
+
}
|
|
44
78
|
function mapMemory(row, sourceEntryIds) {
|
|
79
|
+
const stored = metadata(row.metadata_json);
|
|
80
|
+
const fallbackSessionId = row.origin_session_id ?? row.session_id ?? "legacy";
|
|
45
81
|
return {
|
|
46
82
|
id: row.memory_id,
|
|
47
83
|
scope: row.scope,
|
|
48
84
|
...(row.session_id ? { sessionId: row.session_id } : {}),
|
|
49
85
|
...(row.project_path ? { projectPath: row.project_path } : {}),
|
|
50
86
|
...(row.memory_key ? { key: row.memory_key } : {}),
|
|
51
|
-
...(
|
|
52
|
-
? { classification: classificationFromMetadata(row.metadata_json) }
|
|
53
|
-
: {}),
|
|
87
|
+
...(stored.classification ? { classification: stored.classification } : {}),
|
|
54
88
|
claim: row.claim,
|
|
55
89
|
status: row.status,
|
|
56
90
|
createdAt: row.created_at,
|
|
57
91
|
updatedAt: row.updated_at,
|
|
58
|
-
originSessionId:
|
|
92
|
+
originSessionId: fallbackSessionId,
|
|
59
93
|
...(row.superseded_by ? { supersededBy: row.superseded_by } : {}),
|
|
60
94
|
...(row.status_reason ? { statusReason: row.status_reason } : {}),
|
|
61
95
|
sourceEntryIds,
|
|
96
|
+
provenance: stored.provenance ?? {
|
|
97
|
+
sourceSessionId: fallbackSessionId,
|
|
98
|
+
mutationEntryId: sourceEntryIds.at(-1) ?? row.memory_id,
|
|
99
|
+
sourceEntryIds,
|
|
100
|
+
contradicts: [],
|
|
101
|
+
},
|
|
62
102
|
};
|
|
63
103
|
}
|
|
64
104
|
function mapPin(row) {
|
|
105
|
+
const stored = metadata(row.metadata_json);
|
|
106
|
+
const fallbackSessionId = row.session_id ?? "legacy";
|
|
107
|
+
const fallbackEntryId = row.source_entry_id ?? row.pin_id;
|
|
65
108
|
return {
|
|
66
109
|
id: row.pin_id,
|
|
67
110
|
scope: row.scope,
|
|
68
111
|
...(row.session_id ? { sessionId: row.session_id } : {}),
|
|
69
112
|
...(row.project_path ? { projectPath: row.project_path } : {}),
|
|
70
113
|
...(row.branch_leaf_id ? { branchLeafId: row.branch_leaf_id } : {}),
|
|
71
|
-
...(
|
|
72
|
-
? { classification: classificationFromMetadata(row.metadata_json) }
|
|
73
|
-
: {}),
|
|
114
|
+
...(stored.classification ? { classification: stored.classification } : {}),
|
|
74
115
|
content: row.content,
|
|
75
116
|
status: row.status,
|
|
76
117
|
createdAt: row.created_at,
|
|
@@ -79,36 +120,145 @@ function mapPin(row) {
|
|
|
79
120
|
...(row.status_reason ? { statusReason: row.status_reason } : {}),
|
|
80
121
|
...(row.source_entry_id ? { sourceEntryId: row.source_entry_id } : {}),
|
|
81
122
|
...(row.source_file ? { sourceFile: row.source_file } : {}),
|
|
123
|
+
provenance: stored.provenance ?? {
|
|
124
|
+
sourceSessionId: fallbackSessionId,
|
|
125
|
+
mutationEntryId: fallbackEntryId,
|
|
126
|
+
sourceEntryIds: [fallbackEntryId],
|
|
127
|
+
contradicts: [],
|
|
128
|
+
},
|
|
82
129
|
};
|
|
83
130
|
}
|
|
84
131
|
export class MemoryRepository {
|
|
85
132
|
database;
|
|
86
|
-
|
|
133
|
+
writes;
|
|
134
|
+
constructor(database, writes = new SqliteWriteCoordinator(database)) {
|
|
87
135
|
this.database = database;
|
|
136
|
+
this.writes = writes;
|
|
88
137
|
}
|
|
89
138
|
reconcileSession(sessionId, memoryMutations, pinMutations) {
|
|
90
|
-
return
|
|
139
|
+
return this.writes.transaction("memory-reconcile-session", () => {
|
|
140
|
+
this.database.prepare(`
|
|
141
|
+
DELETE FROM project_memory_sessions AS source
|
|
142
|
+
WHERE source.session_id = ? AND source.status <> 'ready'
|
|
143
|
+
AND NOT EXISTS (
|
|
144
|
+
SELECT 1 FROM project_memory_source_exclusions AS exclusion
|
|
145
|
+
WHERE exclusion.project_path = source.project_path
|
|
146
|
+
AND exclusion.session_id = source.session_id
|
|
147
|
+
)
|
|
148
|
+
`).run(sessionId);
|
|
91
149
|
this.database.prepare("DELETE FROM memory_mutations WHERE session_id = ?").run(sessionId);
|
|
92
150
|
this.database.prepare("DELETE FROM pin_mutations WHERE session_id = ?").run(sessionId);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
151
|
+
this.insertMemoryMutations(memoryMutations);
|
|
152
|
+
this.insertPinMutations(pinMutations);
|
|
153
|
+
return this.materialize();
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
getProjectSourceState(projectPath, sessionId) {
|
|
157
|
+
const row = this.database.prepare(`
|
|
158
|
+
SELECT project_path, session_id, session_file, header_hash, file_size,
|
|
159
|
+
file_mtime_ms, checkpoint_offset, checkpoint_hash_start, checkpoint_hash,
|
|
160
|
+
indexed_records, indexed_mutations, malformed_lines, status, last_error,
|
|
161
|
+
indexed_at, NULL AS excluded_at, NULL AS exclusion_reason
|
|
162
|
+
FROM project_memory_sessions
|
|
163
|
+
WHERE project_path = ? AND session_id = ?
|
|
164
|
+
`).get(projectPath, sessionId);
|
|
165
|
+
return row ? this.mapProjectSourceCheckpoint(row) : undefined;
|
|
166
|
+
}
|
|
167
|
+
replaceProjectSource(checkpoint, memoryMutations, pinMutations) {
|
|
168
|
+
return this.writes.transaction("project-memory-source-rebuild", () => {
|
|
169
|
+
this.database.prepare("DELETE FROM memory_mutations WHERE session_id = ?").run(checkpoint.sessionId);
|
|
170
|
+
this.database.prepare("DELETE FROM pin_mutations WHERE session_id = ?").run(checkpoint.sessionId);
|
|
171
|
+
this.insertMemoryMutations(memoryMutations);
|
|
172
|
+
this.insertPinMutations(pinMutations);
|
|
173
|
+
this.writeProjectSourceState(checkpoint);
|
|
174
|
+
return this.materialize();
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
appendProjectSource(checkpoint, memoryMutations, pinMutations) {
|
|
178
|
+
return this.writes.transaction("project-memory-source-append", () => {
|
|
179
|
+
this.insertMemoryMutations(memoryMutations, true);
|
|
180
|
+
this.insertPinMutations(pinMutations, true);
|
|
181
|
+
this.writeProjectSourceState(checkpoint);
|
|
182
|
+
return this.materialize();
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
markProjectSourceUnavailable(projectPath, sessionId, status, error, indexedAt) {
|
|
186
|
+
return this.writes.transaction("project-memory-source-unavailable", () => {
|
|
187
|
+
const existing = this.getProjectSourceState(projectPath, sessionId);
|
|
188
|
+
if (!existing)
|
|
189
|
+
return undefined;
|
|
190
|
+
const lastError = error.slice(0, 1_000);
|
|
191
|
+
if (existing.status === status
|
|
192
|
+
&& existing.indexedMutations === 0
|
|
193
|
+
&& existing.lastError === lastError) {
|
|
194
|
+
return undefined;
|
|
105
195
|
}
|
|
106
|
-
|
|
107
|
-
|
|
196
|
+
this.writeProjectSourceState({
|
|
197
|
+
...existing,
|
|
198
|
+
indexedMutations: 0,
|
|
199
|
+
status,
|
|
200
|
+
lastError,
|
|
201
|
+
indexedAt,
|
|
202
|
+
});
|
|
203
|
+
return this.materialize();
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
setProjectSourceExcluded(projectPath, sessionId, excluded, excludedAt, reason) {
|
|
207
|
+
return this.writes.transaction("project-memory-source-exclusion", () => {
|
|
208
|
+
if (excluded) {
|
|
209
|
+
this.database.prepare(`
|
|
210
|
+
INSERT INTO project_memory_source_exclusions(project_path, session_id, excluded_at, reason)
|
|
211
|
+
VALUES (?, ?, ?, ?)
|
|
212
|
+
ON CONFLICT(project_path, session_id) DO UPDATE SET
|
|
213
|
+
excluded_at = excluded.excluded_at,
|
|
214
|
+
reason = excluded.reason
|
|
215
|
+
`).run(projectPath, sessionId, excludedAt, reason?.slice(0, 500) ?? null);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
this.database.prepare(`
|
|
219
|
+
DELETE FROM project_memory_source_exclusions
|
|
220
|
+
WHERE project_path = ? AND session_id = ?
|
|
221
|
+
`).run(projectPath, sessionId);
|
|
108
222
|
}
|
|
109
223
|
return this.materialize();
|
|
110
224
|
});
|
|
111
225
|
}
|
|
226
|
+
listProjectSources(projectPath) {
|
|
227
|
+
const rows = this.database.prepare(`
|
|
228
|
+
SELECT state.project_path, state.session_id, state.session_file, state.header_hash,
|
|
229
|
+
state.file_size, state.file_mtime_ms, state.checkpoint_offset,
|
|
230
|
+
state.checkpoint_hash_start, state.checkpoint_hash, state.indexed_records,
|
|
231
|
+
state.indexed_mutations, state.malformed_lines, state.status, state.last_error,
|
|
232
|
+
state.indexed_at, exclusion.excluded_at, exclusion.reason AS exclusion_reason,
|
|
233
|
+
(SELECT count(*) FROM memory_items AS memory
|
|
234
|
+
WHERE memory.scope = 'project' AND memory.project_path = state.project_path
|
|
235
|
+
AND memory.origin_session_id = state.session_id AND memory.status = 'active')
|
|
236
|
+
AS active_project_memories,
|
|
237
|
+
(SELECT count(*) FROM pins AS pin
|
|
238
|
+
WHERE pin.scope = 'project' AND pin.project_path = state.project_path
|
|
239
|
+
AND pin.session_id = state.session_id AND pin.status = 'active')
|
|
240
|
+
AS active_project_pins
|
|
241
|
+
FROM project_memory_sessions AS state
|
|
242
|
+
LEFT JOIN project_memory_source_exclusions AS exclusion
|
|
243
|
+
ON exclusion.project_path = state.project_path AND exclusion.session_id = state.session_id
|
|
244
|
+
WHERE state.project_path = ?
|
|
245
|
+
ORDER BY state.indexed_at DESC, state.session_id
|
|
246
|
+
`).all(projectPath);
|
|
247
|
+
return rows.map((row) => ({
|
|
248
|
+
projectPath: row.project_path,
|
|
249
|
+
sessionId: row.session_id,
|
|
250
|
+
sessionFile: row.session_file,
|
|
251
|
+
status: row.excluded_at !== null ? "excluded" : row.status,
|
|
252
|
+
indexedRecords: row.indexed_records,
|
|
253
|
+
indexedMutations: row.indexed_mutations,
|
|
254
|
+
malformedLines: row.malformed_lines,
|
|
255
|
+
activeProjectMemories: row.active_project_memories ?? 0,
|
|
256
|
+
activeProjectPins: row.active_project_pins ?? 0,
|
|
257
|
+
indexedAt: row.indexed_at,
|
|
258
|
+
...(row.last_error ? { lastError: row.last_error } : {}),
|
|
259
|
+
...(row.exclusion_reason ? { exclusionReason: row.exclusion_reason } : {}),
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
112
262
|
getMemory(id) {
|
|
113
263
|
const row = this.database.prepare("SELECT * FROM memory_items WHERE memory_id = ?").get(id);
|
|
114
264
|
return row ? mapMemory(row, this.memorySourceIds(id)) : undefined;
|
|
@@ -119,10 +269,11 @@ export class MemoryRepository {
|
|
|
119
269
|
SELECT * FROM memory_items
|
|
120
270
|
WHERE (
|
|
121
271
|
(scope = 'session' AND session_id = ?)
|
|
122
|
-
OR (scope = 'project' AND project_path = ? AND ? = 1
|
|
272
|
+
OR (scope = 'project' AND project_path = ? AND ? = 1
|
|
273
|
+
AND (? = 1 OR origin_session_id = ?))
|
|
123
274
|
) ${status}
|
|
124
275
|
ORDER BY created_at DESC, memory_id
|
|
125
|
-
`).all(options.sessionId, options.projectPath, options.includeProject ? 1 : 0);
|
|
276
|
+
`).all(options.sessionId, options.projectPath, options.includeProject ? 1 : 0, options.includeCrossSessionProject ? 1 : 0, options.sessionId);
|
|
126
277
|
const sources = this.memorySources(new Set(rows.map((row) => row.memory_id)));
|
|
127
278
|
return rows.map((row) => mapMemory(row, sources.get(row.memory_id) ?? []));
|
|
128
279
|
}
|
|
@@ -136,28 +287,30 @@ export class MemoryRepository {
|
|
|
136
287
|
SELECT * FROM pins
|
|
137
288
|
WHERE (
|
|
138
289
|
(scope IN ('session', 'branch') AND session_id = ?)
|
|
139
|
-
OR (scope = 'project' AND project_path = ? AND ? = 1
|
|
290
|
+
OR (scope = 'project' AND project_path = ? AND ? = 1
|
|
291
|
+
AND (? = 1 OR session_id = ?))
|
|
140
292
|
) ${status}
|
|
141
293
|
ORDER BY created_at ASC, pin_id
|
|
142
|
-
`).all(options.sessionId, options.projectPath, options.includeProject ? 1 : 0);
|
|
294
|
+
`).all(options.sessionId, options.projectPath, options.includeProject ? 1 : 0, options.includeCrossSessionProject ? 1 : 0, options.sessionId);
|
|
143
295
|
return rows.map(mapPin);
|
|
144
296
|
}
|
|
145
|
-
stats(sessionId, projectPath) {
|
|
297
|
+
stats(sessionId, projectPath, includeCrossSessionProject = true) {
|
|
146
298
|
const memories = this.database.prepare(`
|
|
147
299
|
SELECT
|
|
148
300
|
COALESCE(sum(status = 'active'), 0) AS active,
|
|
149
301
|
COALESCE(sum(status <> 'active'), 0) AS inactive
|
|
150
302
|
FROM memory_items
|
|
151
|
-
WHERE (scope = 'session' AND session_id = ?)
|
|
152
|
-
|
|
303
|
+
WHERE (scope = 'session' AND session_id = ?)
|
|
304
|
+
OR (scope = 'project' AND project_path = ? AND (? = 1 OR origin_session_id = ?))
|
|
305
|
+
`).get(sessionId, projectPath, includeCrossSessionProject ? 1 : 0, sessionId);
|
|
153
306
|
const pins = this.database.prepare(`
|
|
154
307
|
SELECT
|
|
155
308
|
COALESCE(sum(status = 'active'), 0) AS active,
|
|
156
309
|
COALESCE(sum(status <> 'active'), 0) AS inactive
|
|
157
310
|
FROM pins
|
|
158
311
|
WHERE (scope IN ('session', 'branch') AND session_id = ?)
|
|
159
|
-
OR (scope = 'project' AND project_path = ?)
|
|
160
|
-
`).get(sessionId, projectPath);
|
|
312
|
+
OR (scope = 'project' AND project_path = ? AND (? = 1 OR session_id = ?))
|
|
313
|
+
`).get(sessionId, projectPath, includeCrossSessionProject ? 1 : 0, sessionId);
|
|
161
314
|
return {
|
|
162
315
|
activeMemories: memories.active,
|
|
163
316
|
inactiveMemories: memories.inactive,
|
|
@@ -171,8 +324,25 @@ export class MemoryRepository {
|
|
|
171
324
|
const memories = new Map();
|
|
172
325
|
const pins = new Map();
|
|
173
326
|
const memoryRows = this.database.prepare(`
|
|
174
|
-
SELECT mutation_key, mutation_id, session_id,
|
|
175
|
-
|
|
327
|
+
SELECT mutation.mutation_key, mutation.mutation_id, mutation.session_id,
|
|
328
|
+
session.session_file, session.project_path AS source_project_path,
|
|
329
|
+
mutation.created_at, mutation.entry_order, mutation.source_parent_entry_id,
|
|
330
|
+
CASE
|
|
331
|
+
WHEN exclusion.session_id IS NOT NULL THEN 0
|
|
332
|
+
WHEN source.status IS NOT NULL AND source.status <> 'ready' THEN 0
|
|
333
|
+
ELSE 1
|
|
334
|
+
END AS source_project_enabled,
|
|
335
|
+
mutation.payload_json
|
|
336
|
+
FROM memory_mutations AS mutation
|
|
337
|
+
JOIN sessions AS session ON session.session_id = mutation.session_id
|
|
338
|
+
LEFT JOIN project_memory_sessions AS source
|
|
339
|
+
ON source.project_path = session.project_path
|
|
340
|
+
AND source.session_id = mutation.session_id
|
|
341
|
+
LEFT JOIN project_memory_source_exclusions AS exclusion
|
|
342
|
+
ON exclusion.project_path = session.project_path
|
|
343
|
+
AND exclusion.session_id = mutation.session_id
|
|
344
|
+
ORDER BY mutation.created_at, mutation.session_id, mutation.entry_order,
|
|
345
|
+
mutation.mutation_id, mutation.mutation_key
|
|
176
346
|
`).all();
|
|
177
347
|
const memoryOperations = [];
|
|
178
348
|
for (const row of memoryRows) {
|
|
@@ -189,8 +359,25 @@ export class MemoryRepository {
|
|
|
189
359
|
this.applyMemoryMutation(memories, mutation, row, warnings, true);
|
|
190
360
|
}
|
|
191
361
|
const pinRows = this.database.prepare(`
|
|
192
|
-
SELECT mutation_key, mutation_id, session_id,
|
|
193
|
-
|
|
362
|
+
SELECT mutation.mutation_key, mutation.mutation_id, mutation.session_id,
|
|
363
|
+
session.session_file, session.project_path AS source_project_path,
|
|
364
|
+
mutation.created_at, mutation.entry_order, mutation.source_parent_entry_id,
|
|
365
|
+
CASE
|
|
366
|
+
WHEN exclusion.session_id IS NOT NULL THEN 0
|
|
367
|
+
WHEN source.status IS NOT NULL AND source.status <> 'ready' THEN 0
|
|
368
|
+
ELSE 1
|
|
369
|
+
END AS source_project_enabled,
|
|
370
|
+
mutation.payload_json
|
|
371
|
+
FROM pin_mutations AS mutation
|
|
372
|
+
JOIN sessions AS session ON session.session_id = mutation.session_id
|
|
373
|
+
LEFT JOIN project_memory_sessions AS source
|
|
374
|
+
ON source.project_path = session.project_path
|
|
375
|
+
AND source.session_id = mutation.session_id
|
|
376
|
+
LEFT JOIN project_memory_source_exclusions AS exclusion
|
|
377
|
+
ON exclusion.project_path = session.project_path
|
|
378
|
+
AND exclusion.session_id = mutation.session_id
|
|
379
|
+
ORDER BY mutation.created_at, mutation.session_id, mutation.entry_order,
|
|
380
|
+
mutation.mutation_id, mutation.mutation_key
|
|
194
381
|
`).all();
|
|
195
382
|
const pinOperations = [];
|
|
196
383
|
for (const row of pinRows) {
|
|
@@ -219,6 +406,15 @@ export class MemoryRepository {
|
|
|
219
406
|
memory.item.status = "invalid";
|
|
220
407
|
memory.item.statusReason = `Conflicts with active memory ${previousId}`;
|
|
221
408
|
memory.item.updatedAt = Math.max(memory.item.updatedAt, memory.item.createdAt);
|
|
409
|
+
memory.item.provenance.contradicts = [
|
|
410
|
+
...new Set([...memory.item.provenance.contradicts, previousId]),
|
|
411
|
+
];
|
|
412
|
+
const previous = memories.get(previousId);
|
|
413
|
+
if (previous) {
|
|
414
|
+
previous.item.provenance.contradicts = [
|
|
415
|
+
...new Set([...previous.item.provenance.contradicts, memory.item.id]),
|
|
416
|
+
];
|
|
417
|
+
}
|
|
222
418
|
warnings.push(`Memory ${memory.item.id} invalidated because key conflicts with ${previousId}`);
|
|
223
419
|
}
|
|
224
420
|
this.database.exec(`
|
|
@@ -240,7 +436,10 @@ export class MemoryRepository {
|
|
|
240
436
|
const insertMemorySource = this.database.prepare("INSERT OR IGNORE INTO memory_sources(memory_id, entry_key) VALUES (?, ?)");
|
|
241
437
|
const insertMemoryFts = this.database.prepare("INSERT INTO memory_fts(claim, memory_id) VALUES (?, ?)");
|
|
242
438
|
for (const { item, sourceKeys } of memories.values()) {
|
|
243
|
-
insertMemory.run(item.id, item.scope, item.sessionId ?? null, item.projectPath ?? null, item.claim, item.status, item.createdAt, null, item.key ?? null, item.originSessionId, item.updatedAt, item.statusReason ?? null, JSON.stringify({
|
|
439
|
+
insertMemory.run(item.id, item.scope, item.sessionId ?? null, item.projectPath ?? null, item.claim, item.status, item.createdAt, null, item.key ?? null, item.originSessionId, item.updatedAt, item.statusReason ?? null, JSON.stringify({
|
|
440
|
+
...(item.classification ? { classification: item.classification } : {}),
|
|
441
|
+
provenance: item.provenance,
|
|
442
|
+
}));
|
|
244
443
|
for (const sourceKey of sourceKeys) {
|
|
245
444
|
if (availableEntryKeys.has(sourceKey))
|
|
246
445
|
insertMemorySource.run(item.id, sourceKey);
|
|
@@ -263,7 +462,10 @@ export class MemoryRepository {
|
|
|
263
462
|
const sourceEntryId = item.sourceEntryId ?? item.id;
|
|
264
463
|
const sourceEntryKey = item.sessionId ? `${item.sessionId}:${sourceEntryId}` : null;
|
|
265
464
|
const sourceExists = sourceEntryKey ? availableEntryKeys.has(sourceEntryKey) : false;
|
|
266
|
-
insertPin.run(item.id, item.scope, item.sessionId ?? null, item.projectPath ?? null, item.content, sourceExists ? sourceEntryKey : null, sourceExists ? sourceEntryId : null, item.sourceFile ?? null, item.status, item.createdAt, item.branchLeafId ?? null, null, item.updatedAt, item.statusReason ?? null, JSON.stringify({
|
|
465
|
+
insertPin.run(item.id, item.scope, item.sessionId ?? null, item.projectPath ?? null, item.content, sourceExists ? sourceEntryKey : null, sourceExists ? sourceEntryId : null, item.sourceFile ?? null, item.status, item.createdAt, item.branchLeafId ?? null, null, item.updatedAt, item.statusReason ?? null, JSON.stringify({
|
|
466
|
+
...(item.classification ? { classification: item.classification } : {}),
|
|
467
|
+
provenance: item.provenance,
|
|
468
|
+
}));
|
|
267
469
|
}
|
|
268
470
|
const updateSupersededPin = this.database.prepare("UPDATE pins SET superseded_by = ? WHERE pin_id = ?");
|
|
269
471
|
for (const item of pins.values()) {
|
|
@@ -284,9 +486,17 @@ export class MemoryRepository {
|
|
|
284
486
|
if (mutation.operation === "status") {
|
|
285
487
|
const existing = memories.get(mutation.memoryId);
|
|
286
488
|
if (!existing) {
|
|
489
|
+
if (row.source_project_enabled === 0)
|
|
490
|
+
return;
|
|
287
491
|
warnings.push(`Memory status mutation ${mutation.mutationId} references missing ${mutation.memoryId}`);
|
|
288
492
|
return;
|
|
289
493
|
}
|
|
494
|
+
if (existing.item.scope === "project" && row.source_project_enabled === 0)
|
|
495
|
+
return;
|
|
496
|
+
if (!memoryMutationMatchesSource(existing.item, row)) {
|
|
497
|
+
warnings.push(`Memory status mutation ${mutation.mutationId} crosses its source project/session scope`);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
290
500
|
existing.item.status = mutation.status;
|
|
291
501
|
existing.item.updatedAt = mutation.createdAt;
|
|
292
502
|
existing.item.statusReason = mutation.reason;
|
|
@@ -294,11 +504,25 @@ export class MemoryRepository {
|
|
|
294
504
|
return;
|
|
295
505
|
}
|
|
296
506
|
if (mutation.operation === "supersede") {
|
|
507
|
+
if (mutation.item.scope === "project" && row.source_project_enabled === 0)
|
|
508
|
+
return;
|
|
297
509
|
const previous = memories.get(mutation.previousId);
|
|
298
510
|
if (!previous) {
|
|
299
511
|
warnings.push(`Memory supersession ${mutation.mutationId} references missing ${mutation.previousId}`);
|
|
300
512
|
return;
|
|
301
513
|
}
|
|
514
|
+
const replacement = memories.get(mutation.item.id);
|
|
515
|
+
if (!replacement
|
|
516
|
+
|| replacement.item.provenance.sourceSessionId !== row.session_id
|
|
517
|
+
|| replacement.item.provenance.mutationEntryId !== entryIdFromKey(row.mutation_key)) {
|
|
518
|
+
warnings.push(`Memory supersession ${mutation.mutationId} has no valid replacement ${mutation.item.id}`);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (previous.item.scope !== replacement.item.scope
|
|
522
|
+
|| !memoryMutationMatchesSource(previous.item, row)) {
|
|
523
|
+
warnings.push(`Memory supersession ${mutation.mutationId} crosses its source project/session scope`);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
302
526
|
previous.item.status = "superseded";
|
|
303
527
|
previous.item.supersededBy = mutation.item.id;
|
|
304
528
|
previous.item.updatedAt = mutation.createdAt;
|
|
@@ -309,6 +533,8 @@ export class MemoryRepository {
|
|
|
309
533
|
if (mutation.operation === "status")
|
|
310
534
|
return;
|
|
311
535
|
const value = mutation.item;
|
|
536
|
+
if (value.scope === "project" && row.source_project_enabled === 0)
|
|
537
|
+
return;
|
|
312
538
|
const existing = memories.get(value.id);
|
|
313
539
|
if (existing) {
|
|
314
540
|
if (existing.item.claim !== value.claim || existing.item.scope !== value.scope) {
|
|
@@ -320,10 +546,16 @@ export class MemoryRepository {
|
|
|
320
546
|
warnings.push(`Project memory ${value.id} has no project path`);
|
|
321
547
|
return;
|
|
322
548
|
}
|
|
549
|
+
if (value.scope === "project" && value.projectPath !== row.source_project_path) {
|
|
550
|
+
warnings.push(`Project memory ${value.id} does not match its canonical source project`);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
323
553
|
const sourceKeys = new Set([
|
|
324
554
|
row.mutation_key,
|
|
325
555
|
...value.sourceEntryIds.map((entryId) => `${row.session_id}:${entryId}`),
|
|
326
556
|
]);
|
|
557
|
+
const mutationEntryId = entryIdFromKey(row.mutation_key);
|
|
558
|
+
const sourceEntryIds = [...new Set([mutationEntryId, ...value.sourceEntryIds])];
|
|
327
559
|
const item = {
|
|
328
560
|
id: value.id,
|
|
329
561
|
scope: value.scope,
|
|
@@ -336,7 +568,18 @@ export class MemoryRepository {
|
|
|
336
568
|
createdAt: value.createdAt,
|
|
337
569
|
updatedAt: mutation.createdAt,
|
|
338
570
|
originSessionId: row.session_id,
|
|
339
|
-
sourceEntryIds
|
|
571
|
+
sourceEntryIds,
|
|
572
|
+
provenance: {
|
|
573
|
+
sourceSessionId: row.session_id,
|
|
574
|
+
sourceSessionFile: row.session_file,
|
|
575
|
+
mutationEntryId,
|
|
576
|
+
...(row.source_parent_entry_id
|
|
577
|
+
? { sourceBranchEntryId: row.source_parent_entry_id }
|
|
578
|
+
: {}),
|
|
579
|
+
sourceEntryIds,
|
|
580
|
+
...(mutation.operation === "supersede" ? { supersedes: mutation.previousId } : {}),
|
|
581
|
+
contradicts: [],
|
|
582
|
+
},
|
|
340
583
|
};
|
|
341
584
|
memories.set(item.id, { item, sourceKeys });
|
|
342
585
|
}
|
|
@@ -345,9 +588,17 @@ export class MemoryRepository {
|
|
|
345
588
|
if (mutation.operation === "status") {
|
|
346
589
|
const existing = pins.get(mutation.pinId);
|
|
347
590
|
if (!existing) {
|
|
591
|
+
if (row.source_project_enabled === 0)
|
|
592
|
+
return;
|
|
348
593
|
warnings.push(`Pin status mutation ${mutation.mutationId} references missing ${mutation.pinId}`);
|
|
349
594
|
return;
|
|
350
595
|
}
|
|
596
|
+
if (existing.scope === "project" && row.source_project_enabled === 0)
|
|
597
|
+
return;
|
|
598
|
+
if (!pinMutationMatchesSource(existing, row)) {
|
|
599
|
+
warnings.push(`Pin status mutation ${mutation.mutationId} crosses its source project/session scope`);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
351
602
|
existing.status = mutation.status;
|
|
352
603
|
existing.updatedAt = mutation.createdAt;
|
|
353
604
|
existing.statusReason = mutation.reason;
|
|
@@ -355,11 +606,24 @@ export class MemoryRepository {
|
|
|
355
606
|
return;
|
|
356
607
|
}
|
|
357
608
|
if (mutation.operation === "supersede") {
|
|
609
|
+
if (mutation.item.scope === "project" && row.source_project_enabled === 0)
|
|
610
|
+
return;
|
|
358
611
|
const previous = pins.get(mutation.previousId);
|
|
359
612
|
if (!previous) {
|
|
360
613
|
warnings.push(`Pin supersession ${mutation.mutationId} references missing ${mutation.previousId}`);
|
|
361
614
|
return;
|
|
362
615
|
}
|
|
616
|
+
const replacement = pins.get(mutation.item.id);
|
|
617
|
+
if (!replacement
|
|
618
|
+
|| replacement.provenance.sourceSessionId !== row.session_id
|
|
619
|
+
|| replacement.provenance.mutationEntryId !== entryIdFromKey(row.mutation_key)) {
|
|
620
|
+
warnings.push(`Pin supersession ${mutation.mutationId} has no valid replacement ${mutation.item.id}`);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
if (previous.scope !== replacement.scope || !pinMutationMatchesSource(previous, row)) {
|
|
624
|
+
warnings.push(`Pin supersession ${mutation.mutationId} crosses its source project/session scope`);
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
363
627
|
previous.status = "superseded";
|
|
364
628
|
previous.supersededBy = mutation.item.id;
|
|
365
629
|
previous.updatedAt = mutation.createdAt;
|
|
@@ -370,6 +634,8 @@ export class MemoryRepository {
|
|
|
370
634
|
if (mutation.operation === "status")
|
|
371
635
|
return;
|
|
372
636
|
const value = mutation.item;
|
|
637
|
+
if (value.scope === "project" && row.source_project_enabled === 0)
|
|
638
|
+
return;
|
|
373
639
|
const existing = pins.get(value.id);
|
|
374
640
|
if (existing) {
|
|
375
641
|
if (existing.content !== value.content || existing.scope !== value.scope) {
|
|
@@ -381,10 +647,16 @@ export class MemoryRepository {
|
|
|
381
647
|
warnings.push(`Project pin ${value.id} has no project path`);
|
|
382
648
|
return;
|
|
383
649
|
}
|
|
650
|
+
if (value.scope === "project" && value.projectPath !== row.source_project_path) {
|
|
651
|
+
warnings.push(`Project pin ${value.id} does not match its canonical source project`);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
384
654
|
if (value.scope === "branch" && !value.branchLeafId) {
|
|
385
655
|
warnings.push(`Branch pin ${value.id} has no branch leaf`);
|
|
386
656
|
return;
|
|
387
657
|
}
|
|
658
|
+
const mutationEntryId = entryIdFromKey(row.mutation_key);
|
|
659
|
+
const sourceEntryId = value.sourceEntryId ?? mutationEntryId;
|
|
388
660
|
const item = {
|
|
389
661
|
id: value.id,
|
|
390
662
|
scope: value.scope,
|
|
@@ -396,13 +668,103 @@ export class MemoryRepository {
|
|
|
396
668
|
status: "active",
|
|
397
669
|
createdAt: value.createdAt,
|
|
398
670
|
updatedAt: mutation.createdAt,
|
|
399
|
-
|
|
400
|
-
? { sourceEntryId: value.sourceEntryId }
|
|
401
|
-
: { sourceEntryId: entryIdFromKey(row.mutation_key) }),
|
|
671
|
+
sourceEntryId,
|
|
402
672
|
...(value.sourceFile ? { sourceFile: value.sourceFile } : {}),
|
|
673
|
+
provenance: {
|
|
674
|
+
sourceSessionId: row.session_id,
|
|
675
|
+
sourceSessionFile: row.session_file,
|
|
676
|
+
mutationEntryId,
|
|
677
|
+
...(row.source_parent_entry_id
|
|
678
|
+
? { sourceBranchEntryId: row.source_parent_entry_id }
|
|
679
|
+
: {}),
|
|
680
|
+
sourceEntryIds: [...new Set([sourceEntryId, mutationEntryId])],
|
|
681
|
+
...(mutation.operation === "supersede" ? { supersedes: mutation.previousId } : {}),
|
|
682
|
+
contradicts: [],
|
|
683
|
+
},
|
|
403
684
|
};
|
|
404
685
|
pins.set(item.id, item);
|
|
405
686
|
}
|
|
687
|
+
insertMemoryMutations(mutations, ignoreExisting = false) {
|
|
688
|
+
const insert = this.database.prepare(`
|
|
689
|
+
INSERT ${ignoreExisting ? "OR IGNORE" : ""} INTO memory_mutations(
|
|
690
|
+
mutation_key, mutation_id, session_id, created_at, entry_order,
|
|
691
|
+
source_parent_entry_id, payload_json
|
|
692
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
693
|
+
`);
|
|
694
|
+
const existingIds = ignoreExisting
|
|
695
|
+
? new Set(this.database.prepare("SELECT mutation_id FROM memory_mutations WHERE session_id = ?")
|
|
696
|
+
.all(mutations[0]?.sessionId ?? "")
|
|
697
|
+
.map((row) => row.mutation_id))
|
|
698
|
+
: new Set();
|
|
699
|
+
for (const mutation of mutations) {
|
|
700
|
+
if (existingIds.has(mutation.mutationId))
|
|
701
|
+
continue;
|
|
702
|
+
existingIds.add(mutation.mutationId);
|
|
703
|
+
insert.run(mutation.mutationKey, mutation.mutationId, mutation.sessionId, mutation.createdAt, mutation.entryOrder, mutation.sourceParentEntryId ?? null, JSON.stringify(mutation.payload));
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
insertPinMutations(mutations, ignoreExisting = false) {
|
|
707
|
+
const insert = this.database.prepare(`
|
|
708
|
+
INSERT ${ignoreExisting ? "OR IGNORE" : ""} INTO pin_mutations(
|
|
709
|
+
mutation_key, mutation_id, session_id, created_at, entry_order,
|
|
710
|
+
source_parent_entry_id, payload_json
|
|
711
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
712
|
+
`);
|
|
713
|
+
const existingIds = ignoreExisting
|
|
714
|
+
? new Set(this.database.prepare("SELECT mutation_id FROM pin_mutations WHERE session_id = ?")
|
|
715
|
+
.all(mutations[0]?.sessionId ?? "")
|
|
716
|
+
.map((row) => row.mutation_id))
|
|
717
|
+
: new Set();
|
|
718
|
+
for (const mutation of mutations) {
|
|
719
|
+
if (existingIds.has(mutation.mutationId))
|
|
720
|
+
continue;
|
|
721
|
+
existingIds.add(mutation.mutationId);
|
|
722
|
+
insert.run(mutation.mutationKey, mutation.mutationId, mutation.sessionId, mutation.createdAt, mutation.entryOrder, mutation.sourceParentEntryId ?? null, JSON.stringify(mutation.payload));
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
writeProjectSourceState(checkpoint) {
|
|
726
|
+
this.database.prepare(`
|
|
727
|
+
INSERT INTO project_memory_sessions(
|
|
728
|
+
project_path, session_id, session_file, header_hash, file_size,
|
|
729
|
+
file_mtime_ms, checkpoint_offset, checkpoint_hash_start, checkpoint_hash,
|
|
730
|
+
indexed_records, indexed_mutations, malformed_lines, status, last_error,
|
|
731
|
+
indexed_at
|
|
732
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
733
|
+
ON CONFLICT(project_path, session_id) DO UPDATE SET
|
|
734
|
+
session_file = excluded.session_file,
|
|
735
|
+
header_hash = excluded.header_hash,
|
|
736
|
+
file_size = excluded.file_size,
|
|
737
|
+
file_mtime_ms = excluded.file_mtime_ms,
|
|
738
|
+
checkpoint_offset = excluded.checkpoint_offset,
|
|
739
|
+
checkpoint_hash_start = excluded.checkpoint_hash_start,
|
|
740
|
+
checkpoint_hash = excluded.checkpoint_hash,
|
|
741
|
+
indexed_records = excluded.indexed_records,
|
|
742
|
+
indexed_mutations = excluded.indexed_mutations,
|
|
743
|
+
malformed_lines = excluded.malformed_lines,
|
|
744
|
+
status = excluded.status,
|
|
745
|
+
last_error = excluded.last_error,
|
|
746
|
+
indexed_at = excluded.indexed_at
|
|
747
|
+
`).run(checkpoint.projectPath, checkpoint.sessionId, checkpoint.sessionFile, checkpoint.headerHash, checkpoint.fileSize, checkpoint.fileMtimeMs, checkpoint.checkpointOffset, checkpoint.checkpointHashStart, checkpoint.checkpointHash ?? null, checkpoint.indexedRecords, checkpoint.indexedMutations, checkpoint.malformedLines, checkpoint.status, checkpoint.lastError ?? null, checkpoint.indexedAt);
|
|
748
|
+
}
|
|
749
|
+
mapProjectSourceCheckpoint(row) {
|
|
750
|
+
return {
|
|
751
|
+
projectPath: row.project_path,
|
|
752
|
+
sessionId: row.session_id,
|
|
753
|
+
sessionFile: row.session_file,
|
|
754
|
+
headerHash: row.header_hash,
|
|
755
|
+
fileSize: row.file_size,
|
|
756
|
+
fileMtimeMs: row.file_mtime_ms,
|
|
757
|
+
checkpointOffset: row.checkpoint_offset,
|
|
758
|
+
checkpointHashStart: row.checkpoint_hash_start,
|
|
759
|
+
...(row.checkpoint_hash ? { checkpointHash: row.checkpoint_hash } : {}),
|
|
760
|
+
indexedRecords: row.indexed_records,
|
|
761
|
+
indexedMutations: row.indexed_mutations,
|
|
762
|
+
malformedLines: row.malformed_lines,
|
|
763
|
+
status: row.status,
|
|
764
|
+
...(row.last_error ? { lastError: row.last_error } : {}),
|
|
765
|
+
indexedAt: row.indexed_at,
|
|
766
|
+
};
|
|
767
|
+
}
|
|
406
768
|
memorySourceIds(memoryId) {
|
|
407
769
|
return this.memorySources(new Set([memoryId])).get(memoryId) ?? [];
|
|
408
770
|
}
|