fastrmemory 0.5.0 → 0.6.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/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -2
- package/.cursor/skills/skills.json +1 -1
- package/README.md +40 -30
- package/dist/bin/fastr.js +122 -14
- package/dist/bin/fastr.js.map +1 -1
- package/dist/src/bench/harness.d.ts.map +1 -1
- package/dist/src/bench/harness.js +4 -0
- package/dist/src/bench/harness.js.map +1 -1
- package/dist/src/bench/marker-corpus.d.ts +98 -0
- package/dist/src/bench/marker-corpus.d.ts.map +1 -0
- package/dist/src/bench/marker-corpus.js +306 -0
- package/dist/src/bench/marker-corpus.js.map +1 -0
- package/dist/src/bench/mcp-stdio.d.ts +55 -0
- package/dist/src/bench/mcp-stdio.d.ts.map +1 -0
- package/dist/src/bench/mcp-stdio.js +146 -0
- package/dist/src/bench/mcp-stdio.js.map +1 -0
- package/dist/src/bench/public-datasets.d.ts +2 -0
- package/dist/src/bench/public-datasets.d.ts.map +1 -1
- package/dist/src/bench/public-datasets.js +2 -1
- package/dist/src/bench/public-datasets.js.map +1 -1
- package/dist/src/bench/public-harness.d.ts +14 -0
- package/dist/src/bench/public-harness.d.ts.map +1 -1
- package/dist/src/bench/public-harness.js +83 -8
- package/dist/src/bench/public-harness.js.map +1 -1
- package/dist/src/bench/public-pins.js +5 -5
- package/dist/src/bench/public-pins.js.map +1 -1
- package/dist/src/bench/split.d.ts +33 -0
- package/dist/src/bench/split.d.ts.map +1 -0
- package/dist/src/bench/split.js +56 -0
- package/dist/src/bench/split.js.map +1 -0
- package/dist/src/bench/stats.d.ts +40 -0
- package/dist/src/bench/stats.d.ts.map +1 -0
- package/dist/src/bench/stats.js +99 -0
- package/dist/src/bench/stats.js.map +1 -0
- package/dist/src/bench/vs-engines.d.ts +51 -0
- package/dist/src/bench/vs-engines.d.ts.map +1 -0
- package/dist/src/bench/vs-engines.js +121 -0
- package/dist/src/bench/vs-engines.js.map +1 -0
- package/dist/src/config.d.ts +33 -0
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +84 -1
- package/dist/src/config.js.map +1 -1
- package/dist/src/db/doctor.d.ts +8 -0
- package/dist/src/db/doctor.d.ts.map +1 -1
- package/dist/src/db/doctor.js +65 -1
- package/dist/src/db/doctor.js.map +1 -1
- package/dist/src/db/fts-queue.d.ts +35 -0
- package/dist/src/db/fts-queue.d.ts.map +1 -0
- package/dist/src/db/fts-queue.js +78 -0
- package/dist/src/db/fts-queue.js.map +1 -0
- package/dist/src/db/repository.d.ts +63 -1
- package/dist/src/db/repository.d.ts.map +1 -1
- package/dist/src/db/repository.js +244 -50
- package/dist/src/db/repository.js.map +1 -1
- package/dist/src/db/schema.d.ts +1 -1
- package/dist/src/db/schema.d.ts.map +1 -1
- package/dist/src/db/schema.js +63 -2
- package/dist/src/db/schema.js.map +1 -1
- package/dist/src/installer/copilot.d.ts +21 -0
- package/dist/src/installer/copilot.d.ts.map +1 -1
- package/dist/src/installer/copilot.js +72 -1
- package/dist/src/installer/copilot.js.map +1 -1
- package/dist/src/installer/gemini.d.ts +13 -0
- package/dist/src/installer/gemini.d.ts.map +1 -1
- package/dist/src/installer/gemini.js +64 -1
- package/dist/src/installer/gemini.js.map +1 -1
- package/dist/src/installer/hooks.d.ts +42 -5
- package/dist/src/installer/hooks.d.ts.map +1 -1
- package/dist/src/installer/hooks.js +270 -62
- package/dist/src/installer/hooks.js.map +1 -1
- package/dist/src/installer/json-config.d.ts +25 -1
- package/dist/src/installer/json-config.d.ts.map +1 -1
- package/dist/src/installer/json-config.js +95 -2
- package/dist/src/installer/json-config.js.map +1 -1
- package/dist/src/installer/mcp-init.d.ts.map +1 -1
- package/dist/src/installer/mcp-init.js +5 -6
- package/dist/src/installer/mcp-init.js.map +1 -1
- package/dist/src/installer/opencode.d.ts +18 -0
- package/dist/src/installer/opencode.d.ts.map +1 -1
- package/dist/src/installer/opencode.js +81 -1
- package/dist/src/installer/opencode.js.map +1 -1
- package/dist/src/installer/setup.d.ts +7 -0
- package/dist/src/installer/setup.d.ts.map +1 -1
- package/dist/src/installer/setup.js +32 -0
- package/dist/src/installer/setup.js.map +1 -1
- package/dist/src/mcp/profiles.d.ts +9 -6
- package/dist/src/mcp/profiles.d.ts.map +1 -1
- package/dist/src/mcp/profiles.js +12 -9
- package/dist/src/mcp/profiles.js.map +1 -1
- package/dist/src/mcp/server.d.ts +1 -1
- package/dist/src/mcp/server.d.ts.map +1 -1
- package/dist/src/mcp/server.js +80 -0
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/search/aggregate.d.ts +24 -0
- package/dist/src/search/aggregate.d.ts.map +1 -0
- package/dist/src/search/aggregate.js +59 -0
- package/dist/src/search/aggregate.js.map +1 -0
- package/dist/src/search/backfill.d.ts.map +1 -1
- package/dist/src/search/backfill.js +7 -2
- package/dist/src/search/backfill.js.map +1 -1
- package/dist/src/search/budget.d.ts +2 -0
- package/dist/src/search/budget.d.ts.map +1 -1
- package/dist/src/search/budget.js.map +1 -1
- package/dist/src/search/embeddings.d.ts +35 -2
- package/dist/src/search/embeddings.d.ts.map +1 -1
- package/dist/src/search/embeddings.js +128 -5
- package/dist/src/search/embeddings.js.map +1 -1
- package/dist/src/search/hybrid-rrf.d.ts +45 -0
- package/dist/src/search/hybrid-rrf.d.ts.map +1 -1
- package/dist/src/search/hybrid-rrf.js +212 -56
- package/dist/src/search/hybrid-rrf.js.map +1 -1
- package/dist/src/search/static-model-install.d.ts +43 -0
- package/dist/src/search/static-model-install.d.ts.map +1 -0
- package/dist/src/search/static-model-install.js +112 -0
- package/dist/src/search/static-model-install.js.map +1 -0
- package/dist/src/search/static-model.d.ts +63 -0
- package/dist/src/search/static-model.d.ts.map +1 -0
- package/dist/src/search/static-model.js +244 -0
- package/dist/src/search/static-model.js.map +1 -0
- package/dist/src/search/temporal.d.ts +32 -0
- package/dist/src/search/temporal.d.ts.map +1 -0
- package/dist/src/search/temporal.js +164 -0
- package/dist/src/search/temporal.js.map +1 -0
- package/llms-install.md +3 -3
- package/package.json +5 -1
- package/server.json +1 -1
- package/skills/skills.json +1 -1
|
@@ -3,6 +3,7 @@ import { RawEvent, Memory, Handoff, Entity, MemoryConflict, Prediction, Action,
|
|
|
3
3
|
import { SpoolManager } from './spool.js';
|
|
4
4
|
import { PredictionBaserate } from '../learn/planning-fallacy.js';
|
|
5
5
|
import { ConfidenceTier } from './confidence.js';
|
|
6
|
+
import { type ConflictMode, type NerMode, type FtsMode } from '../config.js';
|
|
6
7
|
export interface CreateMemoryParams {
|
|
7
8
|
projectId: string;
|
|
8
9
|
scope?: 'project' | 'global';
|
|
@@ -26,11 +27,31 @@ export interface ProjectStatus {
|
|
|
26
27
|
rawEventCount: number;
|
|
27
28
|
pendingSpoolCount: number;
|
|
28
29
|
embeddingCoveragePercent: number;
|
|
30
|
+
pendingConflictCount: number;
|
|
31
|
+
pendingNerCount: number;
|
|
32
|
+
pendingFtsCount: number;
|
|
33
|
+
}
|
|
34
|
+
export interface RepositoryOptions {
|
|
35
|
+
/** Overrides write.conflictMode. Used by tests and the sync/deferred equivalence suite. */
|
|
36
|
+
conflictMode?: ConflictMode;
|
|
37
|
+
/** Overrides write.nerMode. 'sync' restores entity extraction inside insertMemory(). */
|
|
38
|
+
nerMode?: NerMode;
|
|
39
|
+
/** Overrides write.ftsMode. 'sync' restores inline FTS indexing inside insertMemory(). */
|
|
40
|
+
ftsMode?: FtsMode;
|
|
29
41
|
}
|
|
30
42
|
export declare class FastrRepository {
|
|
31
43
|
private db;
|
|
32
44
|
private spool?;
|
|
33
|
-
|
|
45
|
+
private conflictMode;
|
|
46
|
+
private nerMode;
|
|
47
|
+
private ftsMode;
|
|
48
|
+
constructor(db: DatabaseSync, spool?: SpoolManager, options?: RepositoryOptions);
|
|
49
|
+
/** Active conflict-analysis mode for this repository instance. */
|
|
50
|
+
getConflictMode(): ConflictMode;
|
|
51
|
+
/** Active entity-extraction mode for this repository instance. */
|
|
52
|
+
getNerMode(): NerMode;
|
|
53
|
+
/** Active full-text indexing mode for this repository instance. */
|
|
54
|
+
getFtsMode(): FtsMode;
|
|
34
55
|
hasGlobalDb(): boolean;
|
|
35
56
|
attachGlobalDatabase(globalDbPath: string): boolean;
|
|
36
57
|
/** Resolves which schema-qualified memories table holds the given id. */
|
|
@@ -41,6 +62,47 @@ export declare class FastrRepository {
|
|
|
41
62
|
getRawEvent(id: string): RawEvent | null;
|
|
42
63
|
getRawEventsForMemory(memoryId: string): RawEvent[];
|
|
43
64
|
insertMemory(params: CreateMemoryParams): Memory;
|
|
65
|
+
/**
|
|
66
|
+
* The historical conflict window: the 50 most recent active memories in the project that
|
|
67
|
+
* were inserted before the target row. Ordering by rowid makes the window reproducible
|
|
68
|
+
* later: drainConflicts() sees exactly the rows that existed when the memory was written,
|
|
69
|
+
* even if more memories arrived in the meantime.
|
|
70
|
+
*/
|
|
71
|
+
private conflictCandidates;
|
|
72
|
+
/** Runs detectConflict() over the historical window and records new conflict rows. */
|
|
73
|
+
private recordConflicts;
|
|
74
|
+
/** Extracts entities and writes entity + graph rows for one memory. */
|
|
75
|
+
private extractEntitiesForMemory;
|
|
76
|
+
/** Number of memories waiting for entity extraction. */
|
|
77
|
+
pendingNerCount(projectId?: string): number;
|
|
78
|
+
/** Drains the deferred entity-extraction queue in one transaction. */
|
|
79
|
+
drainNer(batchSize?: number): {
|
|
80
|
+
drained: number;
|
|
81
|
+
};
|
|
82
|
+
/** Number of memories waiting for full-text indexing. */
|
|
83
|
+
pendingFtsCount(): number;
|
|
84
|
+
/** Indexes queued memories in one transaction. Idempotent. */
|
|
85
|
+
drainFts(batchSize?: number): {
|
|
86
|
+
drained: number;
|
|
87
|
+
};
|
|
88
|
+
/** Drains both deferred queues. This is what the maintenance loop calls. */
|
|
89
|
+
drainAnalysis(batchSize?: number): {
|
|
90
|
+
conflictsDrained: number;
|
|
91
|
+
conflictsCreated: number;
|
|
92
|
+
entitiesDrained: number;
|
|
93
|
+
ftsDrained: number;
|
|
94
|
+
};
|
|
95
|
+
/** Number of memories waiting for conflict analysis. */
|
|
96
|
+
pendingConflictCount(projectId?: string): number;
|
|
97
|
+
/**
|
|
98
|
+
* Drains the deferred conflict queue in one transaction. Each drained memory is compared
|
|
99
|
+
* against the same 50-row window the synchronous path would have used, so 'sync' and
|
|
100
|
+
* 'deferred' converge on identical memory_conflicts rows.
|
|
101
|
+
*/
|
|
102
|
+
drainConflicts(batchSize?: number): {
|
|
103
|
+
drained: number;
|
|
104
|
+
created: number;
|
|
105
|
+
};
|
|
44
106
|
getMemory(id: string): Memory | null;
|
|
45
107
|
listActiveMemories(projectId: string, module?: string): Memory[];
|
|
46
108
|
listTraps(projectId: string): Memory[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../../src/db/repository.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAY,OAAO,EAAE,MAAM,EAAa,cAAc,EAAE,UAAU,EAAE,MAAM,EAAc,UAAU,EAAiC,MAAM,aAAa,CAAC;AAChL,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG1C,OAAO,EAAqB,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAErF,OAAO,EAAkB,cAAc,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../../src/db/repository.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,12 +10,32 @@ import { computeProvenanceHash, verifyProvenanceHash } from './provenance.js';
|
|
|
10
10
|
import { confidenceTier } from './confidence.js';
|
|
11
11
|
import { scrubSecrets } from '../privacy/scrubber.js';
|
|
12
12
|
import { globalBroadcaster } from '../ui/sse.js';
|
|
13
|
+
import { resolveConflictMode, resolveNerMode, resolveFtsMode } from '../config.js';
|
|
14
|
+
import { drainFtsQueue, indexMemoryFtsNow, pendingFtsCount } from './fts-queue.js';
|
|
13
15
|
export class FastrRepository {
|
|
14
16
|
db;
|
|
15
17
|
spool;
|
|
16
|
-
|
|
18
|
+
conflictMode;
|
|
19
|
+
nerMode;
|
|
20
|
+
ftsMode;
|
|
21
|
+
constructor(db, spool, options = {}) {
|
|
17
22
|
this.db = db;
|
|
18
23
|
this.spool = spool;
|
|
24
|
+
this.conflictMode = options.conflictMode ?? resolveConflictMode();
|
|
25
|
+
this.nerMode = options.nerMode ?? resolveNerMode();
|
|
26
|
+
this.ftsMode = options.ftsMode ?? resolveFtsMode();
|
|
27
|
+
}
|
|
28
|
+
/** Active conflict-analysis mode for this repository instance. */
|
|
29
|
+
getConflictMode() {
|
|
30
|
+
return this.conflictMode;
|
|
31
|
+
}
|
|
32
|
+
/** Active entity-extraction mode for this repository instance. */
|
|
33
|
+
getNerMode() {
|
|
34
|
+
return this.nerMode;
|
|
35
|
+
}
|
|
36
|
+
/** Active full-text indexing mode for this repository instance. */
|
|
37
|
+
getFtsMode() {
|
|
38
|
+
return this.ftsMode;
|
|
19
39
|
}
|
|
20
40
|
hasGlobalDb() {
|
|
21
41
|
try {
|
|
@@ -132,64 +152,69 @@ export class FastrRepository {
|
|
|
132
152
|
content: params.content,
|
|
133
153
|
createdAt: nowIso
|
|
134
154
|
});
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
155
|
+
// Durability: the memory row and its deferred conflict job are committed together.
|
|
156
|
+
// Nothing else (file links, NER, conflict scan) is allowed to delay this write.
|
|
157
|
+
this.db.exec('SAVEPOINT fastr_memory_insert');
|
|
158
|
+
try {
|
|
159
|
+
this.db.prepare(`
|
|
160
|
+
INSERT INTO ${targetTable} (
|
|
161
|
+
id, project_id, agent_id, module, kind, title, content, status, pinned, source_event_ids, created_at, provenance_hash
|
|
162
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
163
|
+
`).run(id, targetProject, agentId, module, kind, params.title, params.content, status, pinned, sourceIds, nowIso, provenanceHash);
|
|
164
|
+
if (this.conflictMode === 'deferred' && !isGlobal) {
|
|
165
|
+
this.db.prepare(`
|
|
166
|
+
INSERT INTO memory_conflict_queue (project_id, memory_id, created_at)
|
|
167
|
+
VALUES (?, ?, ?)
|
|
168
|
+
`).run(targetProject, id, nowIso);
|
|
169
|
+
}
|
|
170
|
+
if (this.nerMode === 'deferred') {
|
|
171
|
+
this.db.prepare(`
|
|
172
|
+
INSERT INTO memory_ner_queue (project_id, memory_id, created_at)
|
|
173
|
+
VALUES (?, ?, ?)
|
|
174
|
+
`).run(params.projectId, id, nowIso);
|
|
175
|
+
}
|
|
176
|
+
this.db.exec('RELEASE fastr_memory_insert');
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
this.db.exec('ROLLBACK TO fastr_memory_insert');
|
|
180
|
+
this.db.exec('RELEASE fastr_memory_insert');
|
|
181
|
+
throw err;
|
|
182
|
+
}
|
|
183
|
+
// Full-text indexing: the insert trigger queued this row. 'sync' pays the tokenization
|
|
184
|
+
// cost before returning; 'deferred' (default) leaves it for the next reader or drain.
|
|
185
|
+
if (this.ftsMode === 'sync') {
|
|
186
|
+
try {
|
|
187
|
+
indexMemoryFtsNow(this.db, id, isGlobal && this.hasGlobalDb() ? 'global_db' : 'main');
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
// Indexing failure must never lose a committed memory.
|
|
191
|
+
}
|
|
192
|
+
}
|
|
140
193
|
// Explicit file links
|
|
141
194
|
if (params.files && params.files.length > 0) {
|
|
142
195
|
this.linkFiles(id, params.files, params.symbols, isGlobal && this.hasGlobalDb());
|
|
143
196
|
}
|
|
144
197
|
// 1. Zero-LLM Fast-Path Named Entity Extraction (NER)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const entId = `ent_${crypto.createHash('sha256').update(`${params.projectId}:${ent.name.toLowerCase()}`).digest('hex').slice(0, 12)}`;
|
|
150
|
-
// Upsert entity
|
|
151
|
-
this.db.prepare(`
|
|
152
|
-
INSERT INTO entities (id, project_id, name, entity_type)
|
|
153
|
-
VALUES (?, ?, ?, ?)
|
|
154
|
-
ON CONFLICT(id) DO NOTHING
|
|
155
|
-
`).run(entId, params.projectId, ent.name, ent.type);
|
|
156
|
-
// Link memory to entity in knowledge graph
|
|
157
|
-
const edgeId = `edge_${crypto.randomBytes(6).toString('hex')}`;
|
|
158
|
-
this.db.prepare(`
|
|
159
|
-
INSERT INTO graph_edges (id, project_id, source_id, target_id, edge_type, weight)
|
|
160
|
-
VALUES (?, ?, ?, ?, 'entity_link', 1.0)
|
|
161
|
-
ON CONFLICT(id) DO NOTHING
|
|
162
|
-
`).run(edgeId, params.projectId, id, entId);
|
|
163
|
-
if (ent.type === 'file' && (!params.files || params.files.length === 0)) {
|
|
164
|
-
autoFiles.push(ent.name);
|
|
165
|
-
}
|
|
198
|
+
// Deferred by default: entity + graph writes are analysis, not durability.
|
|
199
|
+
if (this.nerMode === 'sync') {
|
|
200
|
+
try {
|
|
201
|
+
this.extractEntitiesForMemory(id, params.projectId, params.title, params.content, params.files);
|
|
166
202
|
}
|
|
167
|
-
|
|
168
|
-
|
|
203
|
+
catch {
|
|
204
|
+
// Non-blocking NER failure protection
|
|
169
205
|
}
|
|
170
206
|
}
|
|
171
|
-
catch {
|
|
172
|
-
// Non-blocking NER failure protection
|
|
173
|
-
}
|
|
174
207
|
// 2. Proactive Contradiction & Conflict Detection
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
this.db.prepare(`
|
|
185
|
-
INSERT INTO memory_conflicts (project_id, memory_a_id, memory_b_id, reason, score)
|
|
186
|
-
VALUES (?, ?, ?, ?, ?)
|
|
187
|
-
`).run(params.projectId, id, c.id, check.reason, check.score);
|
|
188
|
-
}
|
|
208
|
+
// 'deferred' (default) queued a job in the insert transaction above; the scan runs in
|
|
209
|
+
// drainConflicts(). 'sync' reproduces the historical inline behaviour for tests and for
|
|
210
|
+
// operators who explicitly want conflicts resolved before the call returns.
|
|
211
|
+
if (this.conflictMode === 'sync') {
|
|
212
|
+
try {
|
|
213
|
+
this.recordConflicts(id, targetProject, params.title, params.content);
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
// Non-blocking conflict check protection
|
|
189
217
|
}
|
|
190
|
-
}
|
|
191
|
-
catch {
|
|
192
|
-
// Non-blocking conflict check protection
|
|
193
218
|
}
|
|
194
219
|
const createdMemory = this.getMemory(id);
|
|
195
220
|
globalBroadcaster.broadcast('memory_created', createdMemory);
|
|
@@ -205,6 +230,172 @@ export class FastrRepository {
|
|
|
205
230
|
}
|
|
206
231
|
return createdMemory;
|
|
207
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* The historical conflict window: the 50 most recent active memories in the project that
|
|
235
|
+
* were inserted before the target row. Ordering by rowid makes the window reproducible
|
|
236
|
+
* later: drainConflicts() sees exactly the rows that existed when the memory was written,
|
|
237
|
+
* even if more memories arrived in the meantime.
|
|
238
|
+
*/
|
|
239
|
+
conflictCandidates(memoryId, projectId) {
|
|
240
|
+
const row = this.db.prepare('SELECT rowid AS rid FROM main.memories WHERE id = ?').get(memoryId);
|
|
241
|
+
if (!row)
|
|
242
|
+
return [];
|
|
243
|
+
return this.db.prepare(`
|
|
244
|
+
SELECT id, title, content FROM main.memories
|
|
245
|
+
WHERE project_id = ? AND valid_to IS NULL AND rowid < ?
|
|
246
|
+
ORDER BY created_at DESC, rowid DESC LIMIT 50
|
|
247
|
+
`).all(projectId, row.rid);
|
|
248
|
+
}
|
|
249
|
+
/** Runs detectConflict() over the historical window and records new conflict rows. */
|
|
250
|
+
recordConflicts(memoryId, projectId, title, content) {
|
|
251
|
+
let created = 0;
|
|
252
|
+
for (const candidate of this.conflictCandidates(memoryId, projectId)) {
|
|
253
|
+
const check = detectConflict(title, content, candidate.title, candidate.content);
|
|
254
|
+
if (!check.hasConflict)
|
|
255
|
+
continue;
|
|
256
|
+
const result = this.db.prepare(`
|
|
257
|
+
INSERT INTO memory_conflicts (project_id, memory_a_id, memory_b_id, reason, score)
|
|
258
|
+
SELECT ?, ?, ?, ?, ?
|
|
259
|
+
WHERE NOT EXISTS (
|
|
260
|
+
SELECT 1 FROM memory_conflicts WHERE memory_a_id = ? AND memory_b_id = ?
|
|
261
|
+
)
|
|
262
|
+
`).run(projectId, memoryId, candidate.id, check.reason, check.score, memoryId, candidate.id);
|
|
263
|
+
created += Number(result.changes ?? 0);
|
|
264
|
+
}
|
|
265
|
+
return created;
|
|
266
|
+
}
|
|
267
|
+
/** Extracts entities and writes entity + graph rows for one memory. */
|
|
268
|
+
extractEntitiesForMemory(memoryId, projectId, title, content, files) {
|
|
269
|
+
const extracted = extractEntities(`${title} ${content}`);
|
|
270
|
+
if (extracted.length === 0)
|
|
271
|
+
return;
|
|
272
|
+
const existingLinks = this.db
|
|
273
|
+
.prepare('SELECT COUNT(*) AS c FROM file_links WHERE memory_id = ?')
|
|
274
|
+
.get(memoryId);
|
|
275
|
+
const hasExplicitFiles = (files && files.length > 0) || Number(existingLinks?.c ?? 0) > 0;
|
|
276
|
+
const autoFiles = [];
|
|
277
|
+
for (const ent of extracted) {
|
|
278
|
+
const entId = `ent_${crypto.createHash('sha256').update(`${projectId}:${ent.name.toLowerCase()}`).digest('hex').slice(0, 12)}`;
|
|
279
|
+
this.db.prepare(`
|
|
280
|
+
INSERT INTO entities (id, project_id, name, entity_type)
|
|
281
|
+
VALUES (?, ?, ?, ?)
|
|
282
|
+
ON CONFLICT(id) DO NOTHING
|
|
283
|
+
`).run(entId, projectId, ent.name, ent.type);
|
|
284
|
+
const edgeId = `edge_${crypto.randomBytes(6).toString('hex')}`;
|
|
285
|
+
this.db.prepare(`
|
|
286
|
+
INSERT INTO graph_edges (id, project_id, source_id, target_id, edge_type, weight)
|
|
287
|
+
VALUES (?, ?, ?, ?, 'entity_link', 1.0)
|
|
288
|
+
ON CONFLICT(id) DO NOTHING
|
|
289
|
+
`).run(edgeId, projectId, memoryId, entId);
|
|
290
|
+
if (ent.type === 'file' && !hasExplicitFiles) {
|
|
291
|
+
autoFiles.push(ent.name);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (autoFiles.length > 0) {
|
|
295
|
+
this.linkFiles(memoryId, autoFiles.slice(0, 5));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/** Number of memories waiting for entity extraction. */
|
|
299
|
+
pendingNerCount(projectId) {
|
|
300
|
+
const row = projectId
|
|
301
|
+
? this.db.prepare('SELECT COUNT(*) AS c FROM memory_ner_queue WHERE project_id = ?').get(projectId)
|
|
302
|
+
: this.db.prepare('SELECT COUNT(*) AS c FROM memory_ner_queue').get();
|
|
303
|
+
return Number(row?.c ?? 0);
|
|
304
|
+
}
|
|
305
|
+
/** Drains the deferred entity-extraction queue in one transaction. */
|
|
306
|
+
drainNer(batchSize = 20) {
|
|
307
|
+
const limit = Math.max(0, Math.floor(batchSize));
|
|
308
|
+
if (limit === 0)
|
|
309
|
+
return { drained: 0 };
|
|
310
|
+
const rows = this.db.prepare(`
|
|
311
|
+
SELECT id, project_id, memory_id FROM memory_ner_queue ORDER BY id ASC LIMIT ?
|
|
312
|
+
`).all(limit);
|
|
313
|
+
if (rows.length === 0)
|
|
314
|
+
return { drained: 0 };
|
|
315
|
+
this.db.exec('SAVEPOINT fastr_drain_ner');
|
|
316
|
+
try {
|
|
317
|
+
for (const row of rows) {
|
|
318
|
+
const memory = this.db.prepare('SELECT title, content FROM main.memories WHERE id = ?').get(row.memory_id);
|
|
319
|
+
if (memory) {
|
|
320
|
+
try {
|
|
321
|
+
this.extractEntitiesForMemory(row.memory_id, row.project_id, memory.title, memory.content);
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
// Non-blocking: a bad row must never stall the queue.
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
this.db.prepare('DELETE FROM memory_ner_queue WHERE id = ?').run(row.id);
|
|
328
|
+
}
|
|
329
|
+
this.db.exec('RELEASE fastr_drain_ner');
|
|
330
|
+
}
|
|
331
|
+
catch (err) {
|
|
332
|
+
this.db.exec('ROLLBACK TO fastr_drain_ner');
|
|
333
|
+
this.db.exec('RELEASE fastr_drain_ner');
|
|
334
|
+
throw err;
|
|
335
|
+
}
|
|
336
|
+
return { drained: rows.length };
|
|
337
|
+
}
|
|
338
|
+
/** Number of memories waiting for full-text indexing. */
|
|
339
|
+
pendingFtsCount() {
|
|
340
|
+
return pendingFtsCount(this.db, 'main');
|
|
341
|
+
}
|
|
342
|
+
/** Indexes queued memories in one transaction. Idempotent. */
|
|
343
|
+
drainFts(batchSize = 500) {
|
|
344
|
+
return drainFtsQueue(this.db, { limit: batchSize, schema: 'main' });
|
|
345
|
+
}
|
|
346
|
+
/** Drains both deferred queues. This is what the maintenance loop calls. */
|
|
347
|
+
drainAnalysis(batchSize = 20) {
|
|
348
|
+
const conflicts = this.drainConflicts(batchSize);
|
|
349
|
+
const entities = this.drainNer(batchSize);
|
|
350
|
+
const fts = this.drainFts(Math.max(batchSize, 500));
|
|
351
|
+
return {
|
|
352
|
+
conflictsDrained: conflicts.drained,
|
|
353
|
+
conflictsCreated: conflicts.created,
|
|
354
|
+
entitiesDrained: entities.drained,
|
|
355
|
+
ftsDrained: fts.drained
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/** Number of memories waiting for conflict analysis. */
|
|
359
|
+
pendingConflictCount(projectId) {
|
|
360
|
+
const row = projectId
|
|
361
|
+
? this.db.prepare('SELECT COUNT(*) AS c FROM memory_conflict_queue WHERE project_id = ?').get(projectId)
|
|
362
|
+
: this.db.prepare('SELECT COUNT(*) AS c FROM memory_conflict_queue').get();
|
|
363
|
+
return Number(row?.c ?? 0);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Drains the deferred conflict queue in one transaction. Each drained memory is compared
|
|
367
|
+
* against the same 50-row window the synchronous path would have used, so 'sync' and
|
|
368
|
+
* 'deferred' converge on identical memory_conflicts rows.
|
|
369
|
+
*/
|
|
370
|
+
drainConflicts(batchSize = 20) {
|
|
371
|
+
const limit = Math.max(0, Math.floor(batchSize));
|
|
372
|
+
if (limit === 0)
|
|
373
|
+
return { drained: 0, created: 0 };
|
|
374
|
+
const rows = this.db.prepare(`
|
|
375
|
+
SELECT id, project_id, memory_id FROM memory_conflict_queue
|
|
376
|
+
ORDER BY id ASC LIMIT ?
|
|
377
|
+
`).all(limit);
|
|
378
|
+
if (rows.length === 0)
|
|
379
|
+
return { drained: 0, created: 0 };
|
|
380
|
+
let created = 0;
|
|
381
|
+
this.db.exec('SAVEPOINT fastr_drain_conflicts');
|
|
382
|
+
try {
|
|
383
|
+
for (const row of rows) {
|
|
384
|
+
const memory = this.db.prepare('SELECT title, content FROM main.memories WHERE id = ?').get(row.memory_id);
|
|
385
|
+
if (memory) {
|
|
386
|
+
created += this.recordConflicts(row.memory_id, row.project_id, memory.title, memory.content);
|
|
387
|
+
}
|
|
388
|
+
this.db.prepare('DELETE FROM memory_conflict_queue WHERE id = ?').run(row.id);
|
|
389
|
+
}
|
|
390
|
+
this.db.exec('RELEASE fastr_drain_conflicts');
|
|
391
|
+
}
|
|
392
|
+
catch (err) {
|
|
393
|
+
this.db.exec('ROLLBACK TO fastr_drain_conflicts');
|
|
394
|
+
this.db.exec('RELEASE fastr_drain_conflicts');
|
|
395
|
+
throw err;
|
|
396
|
+
}
|
|
397
|
+
return { drained: rows.length, created };
|
|
398
|
+
}
|
|
208
399
|
getMemory(id) {
|
|
209
400
|
let row = this.db.prepare('SELECT * FROM memories WHERE id = ?').get(id);
|
|
210
401
|
if (!row && this.hasGlobalDb()) {
|
|
@@ -670,7 +861,10 @@ export class FastrRepository {
|
|
|
670
861
|
dormantCount: counts.dormantCount || 0,
|
|
671
862
|
rawEventCount: rawEvents.count || 0,
|
|
672
863
|
pendingSpoolCount: pendingSpool,
|
|
673
|
-
embeddingCoveragePercent: coverage
|
|
864
|
+
embeddingCoveragePercent: coverage,
|
|
865
|
+
pendingConflictCount: this.pendingConflictCount(projectId),
|
|
866
|
+
pendingNerCount: this.pendingNerCount(projectId),
|
|
867
|
+
pendingFtsCount: this.pendingFtsCount()
|
|
674
868
|
};
|
|
675
869
|
}
|
|
676
870
|
// --- SNAPSHOTS & TIME TRAVEL ---
|