fastrmemory 0.3.0 → 0.4.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 +14 -14
- package/.claude-plugin/plugin.json +18 -18
- package/.cursor/mcp.json +3 -1
- package/.cursor/skills/fastr-compress/SKILL.md +33 -33
- package/.cursor/skills/fastr-curate/SKILL.md +62 -62
- package/.cursor/skills/fastr-dag/SKILL.md +47 -47
- package/.cursor/skills/fastr-doctor/SKILL.md +39 -39
- package/.cursor/skills/fastr-memory/SKILL.md +51 -51
- package/.cursor/skills/fastr-plan-estimate/SKILL.md +45 -45
- package/.cursor/skills/fastr-session-commit/SKILL.md +49 -49
- package/.cursor/skills/fastr-setup/SKILL.md +45 -45
- package/.cursor/skills/fastr-trap-log/SKILL.md +39 -39
- package/.cursor/skills/fastr-vision/SKILL.md +35 -35
- package/.cursor/skills/skills.json +65 -65
- package/README.md +642 -729
- package/bin/fastr-guard.mjs +61 -0
- package/dist/bin/fastr.js +68 -10
- package/dist/bin/fastr.js.map +1 -1
- package/dist/src/bench/benchmark.js +13 -13
- package/dist/src/bench/harness.js +17 -17
- package/dist/src/bench/public-harness.js +5 -5
- package/dist/src/branch/manager.js +31 -31
- package/dist/src/ci/guardian.js +9 -9
- package/dist/src/db/doctor.js +25 -25
- package/dist/src/db/hygiene.js +23 -23
- package/dist/src/db/repository.js +283 -283
- package/dist/src/db/schema.js +268 -268
- package/dist/src/db/spool.js +4 -4
- package/dist/src/export/obsidian.js +15 -15
- package/dist/src/import/local.js +4 -4
- package/dist/src/installer/mcp-init.d.ts +65 -0
- package/dist/src/installer/mcp-init.d.ts.map +1 -0
- package/dist/src/installer/mcp-init.js +293 -0
- package/dist/src/installer/mcp-init.js.map +1 -0
- package/dist/src/installer/setup.d.ts +2 -0
- package/dist/src/installer/setup.d.ts.map +1 -1
- package/dist/src/installer/setup.js +24 -18
- package/dist/src/installer/setup.js.map +1 -1
- package/dist/src/mcp/server.js +25 -25
- package/dist/src/mirror/markdown-pages.js +22 -22
- package/dist/src/search/backfill.js +6 -6
- package/dist/src/search/hybrid-rrf.js +60 -60
- package/dist/src/ui/server.js +1259 -1259
- package/dist/src/vision/assets.js +6 -6
- package/dist-ui/assets/ActionsPage-B3quzFNF.js +11 -11
- package/dist-ui/assets/GraphPage-DetiATmC.js +1 -1
- package/dist-ui/assets/ObservatoryPage-QIGMwENf.js +16 -16
- package/dist-ui/assets/TreemapPage-C7uLZd37.js +2 -2
- package/dist-ui/assets/index-BJnPINTu.css +1 -1
- package/dist-ui/assets/index-DWAH--Fu.js +329 -329
- package/dist-ui/assets/maximize-2-BebkGbZA.js +6 -6
- package/dist-ui/build-manifest.json +95 -95
- package/dist-ui/index.html +26 -26
- package/llms-install.md +77 -69
- package/package.json +108 -107
- package/server.json +28 -28
- package/skills/fastr-compress/SKILL.md +33 -33
- package/skills/fastr-curate/SKILL.md +62 -62
- package/skills/fastr-dag/SKILL.md +47 -47
- package/skills/fastr-doctor/SKILL.md +39 -39
- package/skills/fastr-memory/SKILL.md +51 -51
- package/skills/fastr-plan-estimate/SKILL.md +45 -45
- package/skills/fastr-session-commit/SKILL.md +49 -49
- package/skills/fastr-setup/SKILL.md +45 -45
- package/skills/fastr-trap-log/SKILL.md +39 -39
- package/skills/fastr-vision/SKILL.md +35 -35
- package/skills/skills.json +65 -65
|
@@ -72,10 +72,10 @@ export class FastrRepository {
|
|
|
72
72
|
agent_id: event.agent_id || 'default'
|
|
73
73
|
};
|
|
74
74
|
try {
|
|
75
|
-
this.db.prepare(`
|
|
76
|
-
INSERT OR IGNORE INTO raw_events (
|
|
77
|
-
id, project_id, session_id, agent_id, event_type, tool_name, file_path, raw_payload, content_hash
|
|
78
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
75
|
+
this.db.prepare(`
|
|
76
|
+
INSERT OR IGNORE INTO raw_events (
|
|
77
|
+
id, project_id, session_id, agent_id, event_type, tool_name, file_path, raw_payload, content_hash
|
|
78
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
79
79
|
`).run(fullEvent.id, fullEvent.project_id, fullEvent.session_id, fullEvent.agent_id, fullEvent.event_type, fullEvent.tool_name || null, fullEvent.file_path || null, fullEvent.raw_payload, fullEvent.content_hash);
|
|
80
80
|
}
|
|
81
81
|
catch (err) {
|
|
@@ -105,8 +105,8 @@ export class FastrRepository {
|
|
|
105
105
|
if (!Array.isArray(eventIds) || eventIds.length === 0)
|
|
106
106
|
return [];
|
|
107
107
|
const placeholders = eventIds.map(() => '?').join(',');
|
|
108
|
-
const rows = this.db.prepare(`
|
|
109
|
-
SELECT * FROM raw_events WHERE id IN (${placeholders}) ORDER BY created_at ASC
|
|
108
|
+
const rows = this.db.prepare(`
|
|
109
|
+
SELECT * FROM raw_events WHERE id IN (${placeholders}) ORDER BY created_at ASC
|
|
110
110
|
`).all(...eventIds);
|
|
111
111
|
return rows;
|
|
112
112
|
}
|
|
@@ -132,10 +132,10 @@ export class FastrRepository {
|
|
|
132
132
|
content: params.content,
|
|
133
133
|
createdAt: nowIso
|
|
134
134
|
});
|
|
135
|
-
this.db.prepare(`
|
|
136
|
-
INSERT INTO ${targetTable} (
|
|
137
|
-
id, project_id, agent_id, module, kind, title, content, status, pinned, source_event_ids, created_at, provenance_hash
|
|
138
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
135
|
+
this.db.prepare(`
|
|
136
|
+
INSERT INTO ${targetTable} (
|
|
137
|
+
id, project_id, agent_id, module, kind, title, content, status, pinned, source_event_ids, created_at, provenance_hash
|
|
138
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
139
139
|
`).run(id, targetProject, agentId, module, kind, params.title, params.content, status, pinned, sourceIds, nowIso, provenanceHash);
|
|
140
140
|
// Explicit file links
|
|
141
141
|
if (params.files && params.files.length > 0) {
|
|
@@ -148,17 +148,17 @@ export class FastrRepository {
|
|
|
148
148
|
for (const ent of extracted) {
|
|
149
149
|
const entId = `ent_${crypto.createHash('sha256').update(`${params.projectId}:${ent.name.toLowerCase()}`).digest('hex').slice(0, 12)}`;
|
|
150
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
|
|
151
|
+
this.db.prepare(`
|
|
152
|
+
INSERT INTO entities (id, project_id, name, entity_type)
|
|
153
|
+
VALUES (?, ?, ?, ?)
|
|
154
|
+
ON CONFLICT(id) DO NOTHING
|
|
155
155
|
`).run(entId, params.projectId, ent.name, ent.type);
|
|
156
156
|
// Link memory to entity in knowledge graph
|
|
157
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
|
|
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
162
|
`).run(edgeId, params.projectId, id, entId);
|
|
163
163
|
if (ent.type === 'file' && (!params.files || params.files.length === 0)) {
|
|
164
164
|
autoFiles.push(ent.name);
|
|
@@ -173,17 +173,17 @@ export class FastrRepository {
|
|
|
173
173
|
}
|
|
174
174
|
// 2. Proactive Contradiction & Conflict Detection
|
|
175
175
|
try {
|
|
176
|
-
const candidates = this.db.prepare(`
|
|
177
|
-
SELECT id, title, content FROM memories
|
|
178
|
-
WHERE project_id = ? AND valid_to IS NULL AND id != ?
|
|
179
|
-
ORDER BY created_at DESC LIMIT 50
|
|
176
|
+
const candidates = this.db.prepare(`
|
|
177
|
+
SELECT id, title, content FROM memories
|
|
178
|
+
WHERE project_id = ? AND valid_to IS NULL AND id != ?
|
|
179
|
+
ORDER BY created_at DESC LIMIT 50
|
|
180
180
|
`).all(params.projectId, id);
|
|
181
181
|
for (const c of candidates) {
|
|
182
182
|
const check = detectConflict(params.title, params.content, c.title, c.content);
|
|
183
183
|
if (check.hasConflict) {
|
|
184
|
-
this.db.prepare(`
|
|
185
|
-
INSERT INTO memory_conflicts (project_id, memory_a_id, memory_b_id, reason, score)
|
|
186
|
-
VALUES (?, ?, ?, ?, ?)
|
|
184
|
+
this.db.prepare(`
|
|
185
|
+
INSERT INTO memory_conflicts (project_id, memory_a_id, memory_b_id, reason, score)
|
|
186
|
+
VALUES (?, ?, ?, ?, ?)
|
|
187
187
|
`).run(params.projectId, id, c.id, check.reason, check.score);
|
|
188
188
|
}
|
|
189
189
|
}
|
|
@@ -213,12 +213,12 @@ export class FastrRepository {
|
|
|
213
213
|
return row || null;
|
|
214
214
|
}
|
|
215
215
|
listActiveMemories(projectId, module) {
|
|
216
|
-
let query = `
|
|
217
|
-
SELECT * FROM memories
|
|
218
|
-
WHERE (project_id = ? OR project_id = 'global')
|
|
219
|
-
AND valid_to IS NULL
|
|
220
|
-
AND status != 'superseded'
|
|
221
|
-
AND status != 'dormant'
|
|
216
|
+
let query = `
|
|
217
|
+
SELECT * FROM memories
|
|
218
|
+
WHERE (project_id = ? OR project_id = 'global')
|
|
219
|
+
AND valid_to IS NULL
|
|
220
|
+
AND status != 'superseded'
|
|
221
|
+
AND status != 'dormant'
|
|
222
222
|
`;
|
|
223
223
|
const params = [projectId];
|
|
224
224
|
if (module) {
|
|
@@ -226,12 +226,12 @@ export class FastrRepository {
|
|
|
226
226
|
params.push(module.trim().toLowerCase());
|
|
227
227
|
}
|
|
228
228
|
if (this.hasGlobalDb()) {
|
|
229
|
-
let gQuery = `
|
|
230
|
-
UNION ALL
|
|
231
|
-
SELECT * FROM global_db.memories
|
|
232
|
-
WHERE valid_to IS NULL
|
|
233
|
-
AND status != 'superseded'
|
|
234
|
-
AND status != 'dormant'
|
|
229
|
+
let gQuery = `
|
|
230
|
+
UNION ALL
|
|
231
|
+
SELECT * FROM global_db.memories
|
|
232
|
+
WHERE valid_to IS NULL
|
|
233
|
+
AND status != 'superseded'
|
|
234
|
+
AND status != 'dormant'
|
|
235
235
|
`;
|
|
236
236
|
if (module) {
|
|
237
237
|
gQuery += ' AND module = ?';
|
|
@@ -243,22 +243,22 @@ export class FastrRepository {
|
|
|
243
243
|
return this.db.prepare(query).all(...params);
|
|
244
244
|
}
|
|
245
245
|
listTraps(projectId) {
|
|
246
|
-
let query = `
|
|
247
|
-
SELECT * FROM memories
|
|
248
|
-
WHERE (project_id = ? OR project_id = 'global')
|
|
249
|
-
AND kind = 'error_trap'
|
|
250
|
-
AND valid_to IS NULL
|
|
251
|
-
AND status != 'superseded'
|
|
252
|
-
AND status != 'dormant'
|
|
246
|
+
let query = `
|
|
247
|
+
SELECT * FROM memories
|
|
248
|
+
WHERE (project_id = ? OR project_id = 'global')
|
|
249
|
+
AND kind = 'error_trap'
|
|
250
|
+
AND valid_to IS NULL
|
|
251
|
+
AND status != 'superseded'
|
|
252
|
+
AND status != 'dormant'
|
|
253
253
|
`;
|
|
254
254
|
if (this.hasGlobalDb()) {
|
|
255
|
-
query += `
|
|
256
|
-
UNION ALL
|
|
257
|
-
SELECT * FROM global_db.memories
|
|
258
|
-
WHERE kind = 'error_trap'
|
|
259
|
-
AND valid_to IS NULL
|
|
260
|
-
AND status != 'superseded'
|
|
261
|
-
AND status != 'dormant'
|
|
255
|
+
query += `
|
|
256
|
+
UNION ALL
|
|
257
|
+
SELECT * FROM global_db.memories
|
|
258
|
+
WHERE kind = 'error_trap'
|
|
259
|
+
AND valid_to IS NULL
|
|
260
|
+
AND status != 'superseded'
|
|
261
|
+
AND status != 'dormant'
|
|
262
262
|
`;
|
|
263
263
|
}
|
|
264
264
|
query += ' ORDER BY reward_score DESC, created_at DESC';
|
|
@@ -277,13 +277,13 @@ export class FastrRepository {
|
|
|
277
277
|
}
|
|
278
278
|
const inGlobal = table === 'global_db.memories';
|
|
279
279
|
// 1. Mark old memory as superseded (only if it is still the live head of its chain)
|
|
280
|
-
const res = this.db.prepare(`
|
|
281
|
-
UPDATE ${table}
|
|
282
|
-
SET valid_to = ?,
|
|
283
|
-
status = 'superseded',
|
|
284
|
-
superseded_by_id = ?,
|
|
285
|
-
superseded_reason = ?
|
|
286
|
-
WHERE id = ? AND valid_to IS NULL
|
|
280
|
+
const res = this.db.prepare(`
|
|
281
|
+
UPDATE ${table}
|
|
282
|
+
SET valid_to = ?,
|
|
283
|
+
status = 'superseded',
|
|
284
|
+
superseded_by_id = ?,
|
|
285
|
+
superseded_reason = ?
|
|
286
|
+
WHERE id = ? AND valid_to IS NULL
|
|
287
287
|
`).run(nowIso, newId, reason, oldMemoryId);
|
|
288
288
|
if (Number(res.changes) === 0) {
|
|
289
289
|
throw new Error(`Memory ${oldMemoryId} is no longer active (already superseded by ${oldMem.superseded_by_id ?? 'another update'}); supersede the current version instead.`);
|
|
@@ -298,10 +298,10 @@ export class FastrRepository {
|
|
|
298
298
|
content: newContent,
|
|
299
299
|
createdAt: nowIso
|
|
300
300
|
});
|
|
301
|
-
this.db.prepare(`
|
|
302
|
-
INSERT INTO ${table} (
|
|
303
|
-
id, project_id, agent_id, module, kind, title, content, status, pinned, reward_score, source_event_ids, created_at, provenance_hash
|
|
304
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, 'verified', ?, ?, ?, ?, ?)
|
|
301
|
+
this.db.prepare(`
|
|
302
|
+
INSERT INTO ${table} (
|
|
303
|
+
id, project_id, agent_id, module, kind, title, content, status, pinned, reward_score, source_event_ids, created_at, provenance_hash
|
|
304
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, 'verified', ?, ?, ?, ?, ?)
|
|
305
305
|
`).run(newId, oldMem.project_id, oldMem.agent_id, oldMem.module, oldMem.kind, newTitle, newContent, oldMem.pinned, oldMem.reward_score, oldMem.source_event_ids, nowIso, provHash);
|
|
306
306
|
// 3. Link files
|
|
307
307
|
const targetFiles = files && files.length > 0 ? files : this.getFilesForMemory(oldMemoryId);
|
|
@@ -341,11 +341,11 @@ export class FastrRepository {
|
|
|
341
341
|
else {
|
|
342
342
|
newScore = Math.max(0.1, Number((rewardBefore - 0.5).toFixed(2)));
|
|
343
343
|
// Count consecutive bad outcomes from the append-only feedback log.
|
|
344
|
-
const recent = this.db.prepare(`
|
|
345
|
-
SELECT outcome FROM memory_feedback
|
|
346
|
-
WHERE memory_id = ?
|
|
347
|
-
ORDER BY created_at DESC, rowid DESC
|
|
348
|
-
LIMIT 2
|
|
344
|
+
const recent = this.db.prepare(`
|
|
345
|
+
SELECT outcome FROM memory_feedback
|
|
346
|
+
WHERE memory_id = ?
|
|
347
|
+
ORDER BY created_at DESC, rowid DESC
|
|
348
|
+
LIMIT 2
|
|
349
349
|
`).all(memoryId);
|
|
350
350
|
badStreak = 1;
|
|
351
351
|
for (const row of recent) {
|
|
@@ -358,20 +358,20 @@ export class FastrRepository {
|
|
|
358
358
|
if (outcome === 'bad' && badStreak >= 3 && statusBefore !== 'superseded') {
|
|
359
359
|
newStatus = 'dormant';
|
|
360
360
|
}
|
|
361
|
-
const res = this.db.prepare(`
|
|
362
|
-
UPDATE ${table}
|
|
363
|
-
SET reward_score = ?, status = ?
|
|
364
|
-
WHERE id = ?
|
|
361
|
+
const res = this.db.prepare(`
|
|
362
|
+
UPDATE ${table}
|
|
363
|
+
SET reward_score = ?, status = ?
|
|
364
|
+
WHERE id = ?
|
|
365
365
|
`).run(newScore, newStatus, memoryId);
|
|
366
366
|
if (Number(res.changes) === 0) {
|
|
367
367
|
throw new Error(`Memory not found: ${memoryId}`);
|
|
368
368
|
}
|
|
369
369
|
const tier = confidenceTier({ status: newStatus, reward_score: newScore });
|
|
370
|
-
this.db.prepare(`
|
|
371
|
-
INSERT INTO memory_feedback (
|
|
372
|
-
id, project_id, memory_id, outcome, note, reward_before, reward_after,
|
|
373
|
-
status_before, status_after, confidence_tier, bad_streak, created_at
|
|
374
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
370
|
+
this.db.prepare(`
|
|
371
|
+
INSERT INTO memory_feedback (
|
|
372
|
+
id, project_id, memory_id, outcome, note, reward_before, reward_after,
|
|
373
|
+
status_before, status_after, confidence_tier, bad_streak, created_at
|
|
374
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
375
375
|
`).run(`fb_${crypto.randomBytes(4).toString('hex')}`, mem.project_id, memoryId, outcome, note ? scrubSecrets(note) : null, rewardBefore, newScore, statusBefore, newStatus, tier, badStreak, new Date().toISOString());
|
|
376
376
|
this.db.exec('COMMIT');
|
|
377
377
|
return {
|
|
@@ -391,10 +391,10 @@ export class FastrRepository {
|
|
|
391
391
|
const table = this.locateMemoryTable(memoryId);
|
|
392
392
|
if (!table)
|
|
393
393
|
return { success: false, pinned, error: `Memory not found: ${memoryId}` };
|
|
394
|
-
const res = this.db.prepare(`
|
|
395
|
-
UPDATE ${table}
|
|
396
|
-
SET pinned = ?
|
|
397
|
-
WHERE id = ?
|
|
394
|
+
const res = this.db.prepare(`
|
|
395
|
+
UPDATE ${table}
|
|
396
|
+
SET pinned = ?
|
|
397
|
+
WHERE id = ?
|
|
398
398
|
`).run(pinned ? 1 : 0, memoryId);
|
|
399
399
|
if (Number(res.changes) === 0)
|
|
400
400
|
return { success: false, pinned, error: `Memory not found: ${memoryId}` };
|
|
@@ -405,20 +405,20 @@ export class FastrRepository {
|
|
|
405
405
|
if (!table)
|
|
406
406
|
return { success: false, error: `Memory not found: ${memoryId}` };
|
|
407
407
|
const nowIso = new Date().toISOString();
|
|
408
|
-
const res = this.db.prepare(`
|
|
409
|
-
UPDATE ${table}
|
|
410
|
-
SET status = 'superseded', valid_to = ?, superseded_reason = ?
|
|
411
|
-
WHERE id = ? AND valid_to IS NULL
|
|
408
|
+
const res = this.db.prepare(`
|
|
409
|
+
UPDATE ${table}
|
|
410
|
+
SET status = 'superseded', valid_to = ?, superseded_reason = ?
|
|
411
|
+
WHERE id = ? AND valid_to IS NULL
|
|
412
412
|
`).run(nowIso, reason, memoryId);
|
|
413
413
|
if (Number(res.changes) === 0)
|
|
414
414
|
return { success: false, error: `Memory ${memoryId} is already inactive` };
|
|
415
415
|
return { success: true };
|
|
416
416
|
}
|
|
417
417
|
verifyStaging(projectId) {
|
|
418
|
-
const res = this.db.prepare(`
|
|
419
|
-
UPDATE memories
|
|
420
|
-
SET status = 'verified'
|
|
421
|
-
WHERE project_id = ? AND status = 'staging'
|
|
418
|
+
const res = this.db.prepare(`
|
|
419
|
+
UPDATE memories
|
|
420
|
+
SET status = 'verified'
|
|
421
|
+
WHERE project_id = ? AND status = 'staging'
|
|
422
422
|
`).run(projectId);
|
|
423
423
|
return Number(res.changes);
|
|
424
424
|
}
|
|
@@ -427,27 +427,27 @@ export class FastrRepository {
|
|
|
427
427
|
return;
|
|
428
428
|
const nowIso = new Date().toISOString();
|
|
429
429
|
const placeholders = memoryIds.map(() => '?').join(',');
|
|
430
|
-
this.db.prepare(`
|
|
431
|
-
UPDATE memories
|
|
432
|
-
SET retrieval_count = retrieval_count + 1,
|
|
433
|
-
last_recalled_at = ?
|
|
434
|
-
WHERE id IN (${placeholders})
|
|
430
|
+
this.db.prepare(`
|
|
431
|
+
UPDATE memories
|
|
432
|
+
SET retrieval_count = retrieval_count + 1,
|
|
433
|
+
last_recalled_at = ?
|
|
434
|
+
WHERE id IN (${placeholders})
|
|
435
435
|
`).run(nowIso, ...memoryIds);
|
|
436
436
|
}
|
|
437
437
|
updateEmbedding(memoryId, embedding) {
|
|
438
438
|
const buffer = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
|
|
439
|
-
this.db.prepare(`
|
|
440
|
-
UPDATE memories
|
|
441
|
-
SET embedding = ?, embedded = 1
|
|
442
|
-
WHERE id = ?
|
|
439
|
+
this.db.prepare(`
|
|
440
|
+
UPDATE memories
|
|
441
|
+
SET embedding = ?, embedded = 1
|
|
442
|
+
WHERE id = ?
|
|
443
443
|
`).run(buffer, memoryId);
|
|
444
444
|
}
|
|
445
445
|
// --- FILE LINKS ---
|
|
446
446
|
linkFiles(memoryId, files, symbols, inGlobal = false) {
|
|
447
447
|
const table = inGlobal && this.hasGlobalDb() ? 'global_db.file_links' : 'file_links';
|
|
448
|
-
const stmt = this.db.prepare(`
|
|
449
|
-
INSERT OR IGNORE INTO ${table} (memory_id, file_path, symbol_name)
|
|
450
|
-
VALUES (?, ?, ?)
|
|
448
|
+
const stmt = this.db.prepare(`
|
|
449
|
+
INSERT OR IGNORE INTO ${table} (memory_id, file_path, symbol_name)
|
|
450
|
+
VALUES (?, ?, ?)
|
|
451
451
|
`);
|
|
452
452
|
const symbolList = symbols && symbols.length > 0 ? symbols : [''];
|
|
453
453
|
for (const f of files) {
|
|
@@ -460,39 +460,39 @@ export class FastrRepository {
|
|
|
460
460
|
}
|
|
461
461
|
}
|
|
462
462
|
getFilesForMemory(memoryId) {
|
|
463
|
-
let rows = this.db.prepare(`
|
|
464
|
-
SELECT DISTINCT file_path FROM file_links WHERE memory_id = ?
|
|
463
|
+
let rows = this.db.prepare(`
|
|
464
|
+
SELECT DISTINCT file_path FROM file_links WHERE memory_id = ?
|
|
465
465
|
`).all(memoryId);
|
|
466
466
|
if (rows.length === 0 && this.hasGlobalDb()) {
|
|
467
|
-
rows = this.db.prepare(`
|
|
468
|
-
SELECT DISTINCT file_path FROM global_db.file_links WHERE memory_id = ?
|
|
467
|
+
rows = this.db.prepare(`
|
|
468
|
+
SELECT DISTINCT file_path FROM global_db.file_links WHERE memory_id = ?
|
|
469
469
|
`).all(memoryId);
|
|
470
470
|
}
|
|
471
471
|
return rows.map((r) => r.file_path);
|
|
472
472
|
}
|
|
473
473
|
getMemoriesForFile(projectId, filePath) {
|
|
474
474
|
const targetPath = filePath.replace(/\\/g, '/').trim();
|
|
475
|
-
let innerQuery = `
|
|
476
|
-
SELECT m.*
|
|
477
|
-
FROM memories m
|
|
478
|
-
JOIN file_links fl ON fl.memory_id = m.id
|
|
479
|
-
WHERE (m.project_id = ? OR m.project_id = 'global')
|
|
480
|
-
AND fl.file_path = ?
|
|
481
|
-
AND m.valid_to IS NULL
|
|
482
|
-
AND m.status != 'superseded'
|
|
483
|
-
AND m.status != 'dormant'
|
|
475
|
+
let innerQuery = `
|
|
476
|
+
SELECT m.*
|
|
477
|
+
FROM memories m
|
|
478
|
+
JOIN file_links fl ON fl.memory_id = m.id
|
|
479
|
+
WHERE (m.project_id = ? OR m.project_id = 'global')
|
|
480
|
+
AND fl.file_path = ?
|
|
481
|
+
AND m.valid_to IS NULL
|
|
482
|
+
AND m.status != 'superseded'
|
|
483
|
+
AND m.status != 'dormant'
|
|
484
484
|
`;
|
|
485
485
|
const params = [projectId, targetPath];
|
|
486
486
|
if (this.hasGlobalDb()) {
|
|
487
|
-
innerQuery += `
|
|
488
|
-
UNION ALL
|
|
489
|
-
SELECT gm.*
|
|
490
|
-
FROM global_db.memories gm
|
|
491
|
-
JOIN global_db.file_links gfl ON gfl.memory_id = gm.id
|
|
492
|
-
WHERE gfl.file_path = ?
|
|
493
|
-
AND gm.valid_to IS NULL
|
|
494
|
-
AND gm.status != 'superseded'
|
|
495
|
-
AND gm.status != 'dormant'
|
|
487
|
+
innerQuery += `
|
|
488
|
+
UNION ALL
|
|
489
|
+
SELECT gm.*
|
|
490
|
+
FROM global_db.memories gm
|
|
491
|
+
JOIN global_db.file_links gfl ON gfl.memory_id = gm.id
|
|
492
|
+
WHERE gfl.file_path = ?
|
|
493
|
+
AND gm.valid_to IS NULL
|
|
494
|
+
AND gm.status != 'superseded'
|
|
495
|
+
AND gm.status != 'dormant'
|
|
496
496
|
`;
|
|
497
497
|
params.push(targetPath);
|
|
498
498
|
}
|
|
@@ -502,24 +502,24 @@ export class FastrRepository {
|
|
|
502
502
|
}
|
|
503
503
|
listGlobalMemories() {
|
|
504
504
|
if (this.hasGlobalDb()) {
|
|
505
|
-
return this.db.prepare(`
|
|
506
|
-
SELECT * FROM global_db.memories
|
|
507
|
-
WHERE valid_to IS NULL AND status != 'superseded'
|
|
508
|
-
ORDER BY reward_score DESC, created_at DESC
|
|
505
|
+
return this.db.prepare(`
|
|
506
|
+
SELECT * FROM global_db.memories
|
|
507
|
+
WHERE valid_to IS NULL AND status != 'superseded'
|
|
508
|
+
ORDER BY reward_score DESC, created_at DESC
|
|
509
509
|
`).all();
|
|
510
510
|
}
|
|
511
|
-
return this.db.prepare(`
|
|
512
|
-
SELECT * FROM memories
|
|
513
|
-
WHERE project_id = 'global' AND valid_to IS NULL AND status != 'superseded'
|
|
514
|
-
ORDER BY reward_score DESC, created_at DESC
|
|
511
|
+
return this.db.prepare(`
|
|
512
|
+
SELECT * FROM memories
|
|
513
|
+
WHERE project_id = 'global' AND valid_to IS NULL AND status != 'superseded'
|
|
514
|
+
ORDER BY reward_score DESC, created_at DESC
|
|
515
515
|
`).all();
|
|
516
516
|
}
|
|
517
517
|
getTimeline(projectId, options = {}) {
|
|
518
518
|
const limit = options.limit || 50;
|
|
519
|
-
let query = `
|
|
520
|
-
SELECT id, kind, title, module, status, reward_score, created_at, valid_to, superseded_by_id, superseded_reason
|
|
521
|
-
FROM memories
|
|
522
|
-
WHERE project_id = ?
|
|
519
|
+
let query = `
|
|
520
|
+
SELECT id, kind, title, module, status, reward_score, created_at, valid_to, superseded_by_id, superseded_reason
|
|
521
|
+
FROM memories
|
|
522
|
+
WHERE project_id = ?
|
|
523
523
|
`;
|
|
524
524
|
const params = [projectId];
|
|
525
525
|
if (options.module) {
|
|
@@ -587,11 +587,11 @@ export class FastrRepository {
|
|
|
587
587
|
}
|
|
588
588
|
// Complete graph
|
|
589
589
|
const memories = this.listActiveMemories(projectId);
|
|
590
|
-
const fileLinks = this.db.prepare(`
|
|
591
|
-
SELECT fl.memory_id, fl.file_path, m.title, m.kind, m.module, m.reward_score
|
|
592
|
-
FROM file_links fl
|
|
593
|
-
JOIN memories m ON m.id = fl.memory_id
|
|
594
|
-
WHERE m.project_id = ? AND m.valid_to IS NULL
|
|
590
|
+
const fileLinks = this.db.prepare(`
|
|
591
|
+
SELECT fl.memory_id, fl.file_path, m.title, m.kind, m.module, m.reward_score
|
|
592
|
+
FROM file_links fl
|
|
593
|
+
JOIN memories m ON m.id = fl.memory_id
|
|
594
|
+
WHERE m.project_id = ? AND m.valid_to IS NULL
|
|
595
595
|
`).all(projectId);
|
|
596
596
|
const nodesMap = new Map();
|
|
597
597
|
const edges = [];
|
|
@@ -610,8 +610,8 @@ export class FastrRepository {
|
|
|
610
610
|
nodesMap.set(ent.id, { id: ent.id, label: ent.name, type: 'entity', kind: ent.entity_type });
|
|
611
611
|
}
|
|
612
612
|
}
|
|
613
|
-
const graphEdges = this.db.prepare(`
|
|
614
|
-
SELECT source_id, target_id, edge_type FROM graph_edges WHERE project_id = ?
|
|
613
|
+
const graphEdges = this.db.prepare(`
|
|
614
|
+
SELECT source_id, target_id, edge_type FROM graph_edges WHERE project_id = ?
|
|
615
615
|
`).all(projectId);
|
|
616
616
|
for (const ge of graphEdges) {
|
|
617
617
|
edges.push({ source: ge.source_id, target: ge.target_id, relation: ge.edge_type });
|
|
@@ -626,9 +626,9 @@ export class FastrRepository {
|
|
|
626
626
|
const activeFilesJson = typeof handoff.active_files === 'string'
|
|
627
627
|
? handoff.active_files
|
|
628
628
|
: JSON.stringify(handoff.active_files || []);
|
|
629
|
-
const res = this.db.prepare(`
|
|
630
|
-
INSERT INTO handoffs (project_id, session_id, task_title, summary, next_step, active_files)
|
|
631
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
629
|
+
const res = this.db.prepare(`
|
|
630
|
+
INSERT INTO handoffs (project_id, session_id, task_title, summary, next_step, active_files)
|
|
631
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
632
632
|
`).run(handoff.project_id, handoff.session_id, handoff.task_title, handoff.summary, handoff.next_step, activeFilesJson);
|
|
633
633
|
return {
|
|
634
634
|
...handoff,
|
|
@@ -637,26 +637,26 @@ export class FastrRepository {
|
|
|
637
637
|
};
|
|
638
638
|
}
|
|
639
639
|
getLatestHandoff(projectId) {
|
|
640
|
-
const row = this.db.prepare(`
|
|
641
|
-
SELECT * FROM handoffs WHERE project_id = ? ORDER BY id DESC LIMIT 1
|
|
640
|
+
const row = this.db.prepare(`
|
|
641
|
+
SELECT * FROM handoffs WHERE project_id = ? ORDER BY id DESC LIMIT 1
|
|
642
642
|
`).get(projectId);
|
|
643
643
|
return row || null;
|
|
644
644
|
}
|
|
645
645
|
// --- STATUS ---
|
|
646
646
|
getStatus(projectId) {
|
|
647
|
-
const counts = this.db.prepare(`
|
|
648
|
-
SELECT
|
|
649
|
-
COUNT(CASE WHEN valid_to IS NULL AND status = 'verified' THEN 1 END) as activeCount,
|
|
650
|
-
COUNT(CASE WHEN valid_to IS NULL AND status = 'staging' THEN 1 END) as stagingCount,
|
|
651
|
-
COUNT(CASE WHEN status = 'superseded' THEN 1 END) as supersededCount,
|
|
652
|
-
COUNT(CASE WHEN status = 'dormant' THEN 1 END) as dormantCount,
|
|
653
|
-
COUNT(CASE WHEN embedded = 1 THEN 1 END) as embeddedCount,
|
|
654
|
-
COUNT(*) as totalMemories
|
|
655
|
-
FROM memories
|
|
656
|
-
WHERE project_id = ?
|
|
647
|
+
const counts = this.db.prepare(`
|
|
648
|
+
SELECT
|
|
649
|
+
COUNT(CASE WHEN valid_to IS NULL AND status = 'verified' THEN 1 END) as activeCount,
|
|
650
|
+
COUNT(CASE WHEN valid_to IS NULL AND status = 'staging' THEN 1 END) as stagingCount,
|
|
651
|
+
COUNT(CASE WHEN status = 'superseded' THEN 1 END) as supersededCount,
|
|
652
|
+
COUNT(CASE WHEN status = 'dormant' THEN 1 END) as dormantCount,
|
|
653
|
+
COUNT(CASE WHEN embedded = 1 THEN 1 END) as embeddedCount,
|
|
654
|
+
COUNT(*) as totalMemories
|
|
655
|
+
FROM memories
|
|
656
|
+
WHERE project_id = ?
|
|
657
657
|
`).get(projectId);
|
|
658
|
-
const rawEvents = this.db.prepare(`
|
|
659
|
-
SELECT COUNT(*) as count FROM raw_events WHERE project_id = ?
|
|
658
|
+
const rawEvents = this.db.prepare(`
|
|
659
|
+
SELECT COUNT(*) as count FROM raw_events WHERE project_id = ?
|
|
660
660
|
`).get(projectId);
|
|
661
661
|
const total = counts.totalMemories || 0;
|
|
662
662
|
const embedded = counts.embeddedCount || 0;
|
|
@@ -678,17 +678,17 @@ export class FastrRepository {
|
|
|
678
678
|
const id = `snap_${crypto.randomBytes(4).toString('hex')}`;
|
|
679
679
|
// Only this project's local rows: rollback rewrites main.memories, so global/federated rows
|
|
680
680
|
// must never be captured (they would be copied into the local DB on restore).
|
|
681
|
-
const memories = this.db.prepare(`
|
|
682
|
-
SELECT * FROM main.memories
|
|
683
|
-
WHERE project_id = ? AND valid_to IS NULL
|
|
684
|
-
ORDER BY pinned DESC, reward_score DESC, created_at DESC
|
|
681
|
+
const memories = this.db.prepare(`
|
|
682
|
+
SELECT * FROM main.memories
|
|
683
|
+
WHERE project_id = ? AND valid_to IS NULL
|
|
684
|
+
ORDER BY pinned DESC, reward_score DESC, created_at DESC
|
|
685
685
|
`).all(projectId);
|
|
686
686
|
for (const m of memories)
|
|
687
687
|
m.embedding = null;
|
|
688
|
-
const fileLinks = this.db.prepare(`
|
|
689
|
-
SELECT fl.* FROM main.file_links fl
|
|
690
|
-
JOIN main.memories m ON m.id = fl.memory_id
|
|
691
|
-
WHERE m.project_id = ? AND m.valid_to IS NULL
|
|
688
|
+
const fileLinks = this.db.prepare(`
|
|
689
|
+
SELECT fl.* FROM main.file_links fl
|
|
690
|
+
JOIN main.memories m ON m.id = fl.memory_id
|
|
691
|
+
WHERE m.project_id = ? AND m.valid_to IS NULL
|
|
692
692
|
`).all(projectId);
|
|
693
693
|
const state = {
|
|
694
694
|
memories,
|
|
@@ -697,24 +697,24 @@ export class FastrRepository {
|
|
|
697
697
|
};
|
|
698
698
|
const stateJson = JSON.stringify(state);
|
|
699
699
|
const createdAt = new Date().toISOString();
|
|
700
|
-
this.db.prepare(`
|
|
701
|
-
INSERT INTO memory_snapshots (id, project_id, tag, description, memory_count, state_json, created_at)
|
|
702
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
700
|
+
this.db.prepare(`
|
|
701
|
+
INSERT INTO memory_snapshots (id, project_id, tag, description, memory_count, state_json, created_at)
|
|
702
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
703
703
|
`).run(id, projectId, tag, description || null, memories.length, stateJson, createdAt);
|
|
704
704
|
return { id, tag, memoryCount: memories.length, createdAt };
|
|
705
705
|
}
|
|
706
706
|
listSnapshots(projectId) {
|
|
707
|
-
const rows = this.db.prepare(`
|
|
708
|
-
SELECT id, tag, description, memory_count as memoryCount, created_at as createdAt
|
|
709
|
-
FROM memory_snapshots
|
|
710
|
-
WHERE project_id = ?
|
|
711
|
-
ORDER BY created_at DESC
|
|
707
|
+
const rows = this.db.prepare(`
|
|
708
|
+
SELECT id, tag, description, memory_count as memoryCount, created_at as createdAt
|
|
709
|
+
FROM memory_snapshots
|
|
710
|
+
WHERE project_id = ?
|
|
711
|
+
ORDER BY created_at DESC
|
|
712
712
|
`).all(projectId);
|
|
713
713
|
return rows;
|
|
714
714
|
}
|
|
715
715
|
getSnapshot(snapshotId) {
|
|
716
|
-
const row = this.db.prepare(`
|
|
717
|
-
SELECT * FROM memory_snapshots WHERE id = ?
|
|
716
|
+
const row = this.db.prepare(`
|
|
717
|
+
SELECT * FROM memory_snapshots WHERE id = ?
|
|
718
718
|
`).get(snapshotId);
|
|
719
719
|
return row || null;
|
|
720
720
|
}
|
|
@@ -732,34 +732,34 @@ export class FastrRepository {
|
|
|
732
732
|
try {
|
|
733
733
|
// 1. Mark all current active memories valid_to = now (superseded by rollback)
|
|
734
734
|
const nowIso = new Date().toISOString();
|
|
735
|
-
this.db.prepare(`
|
|
736
|
-
UPDATE memories
|
|
737
|
-
SET valid_to = ?, status = 'superseded', superseded_reason = ?
|
|
738
|
-
WHERE project_id = ? AND valid_to IS NULL
|
|
735
|
+
this.db.prepare(`
|
|
736
|
+
UPDATE memories
|
|
737
|
+
SET valid_to = ?, status = 'superseded', superseded_reason = ?
|
|
738
|
+
WHERE project_id = ? AND valid_to IS NULL
|
|
739
739
|
`).run(nowIso, `Rollback to snapshot ${snap.tag} (${snap.id})`, projectId);
|
|
740
740
|
// 2. Re-insert or restore the snapshot memories
|
|
741
741
|
for (const m of targetMemories) {
|
|
742
|
-
this.db.prepare(`
|
|
743
|
-
INSERT INTO memories (
|
|
744
|
-
id, project_id, agent_id, module, kind, title, content, status, pinned, reward_score,
|
|
745
|
-
source_event_ids, valid_from, valid_to, created_at
|
|
746
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?)
|
|
747
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
748
|
-
valid_to = NULL,
|
|
749
|
-
status = excluded.status,
|
|
750
|
-
pinned = excluded.pinned,
|
|
751
|
-
reward_score = excluded.reward_score,
|
|
752
|
-
superseded_by_id = NULL,
|
|
753
|
-
superseded_reason = NULL
|
|
742
|
+
this.db.prepare(`
|
|
743
|
+
INSERT INTO memories (
|
|
744
|
+
id, project_id, agent_id, module, kind, title, content, status, pinned, reward_score,
|
|
745
|
+
source_event_ids, valid_from, valid_to, created_at
|
|
746
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?)
|
|
747
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
748
|
+
valid_to = NULL,
|
|
749
|
+
status = excluded.status,
|
|
750
|
+
pinned = excluded.pinned,
|
|
751
|
+
reward_score = excluded.reward_score,
|
|
752
|
+
superseded_by_id = NULL,
|
|
753
|
+
superseded_reason = NULL
|
|
754
754
|
`).run(m.id, m.project_id, m.agent_id, m.module, m.kind, m.title, m.content, m.status, m.pinned, m.reward_score, m.source_event_ids, nowIso, m.created_at);
|
|
755
755
|
}
|
|
756
756
|
// 3. Restore file links
|
|
757
757
|
for (const fl of targetLinks) {
|
|
758
758
|
if (!restoredIds.has(fl.memory_id))
|
|
759
759
|
continue;
|
|
760
|
-
this.db.prepare(`
|
|
761
|
-
INSERT OR IGNORE INTO file_links (memory_id, file_path, symbol_name)
|
|
762
|
-
VALUES (?, ?, ?)
|
|
760
|
+
this.db.prepare(`
|
|
761
|
+
INSERT OR IGNORE INTO file_links (memory_id, file_path, symbol_name)
|
|
762
|
+
VALUES (?, ?, ?)
|
|
763
763
|
`).run(fl.memory_id, fl.file_path, fl.symbol_name || '');
|
|
764
764
|
}
|
|
765
765
|
this.db.exec('COMMIT');
|
|
@@ -772,10 +772,10 @@ export class FastrRepository {
|
|
|
772
772
|
}
|
|
773
773
|
// --- CONFLICTS & NER ---
|
|
774
774
|
listConflicts(projectId, status = 'open') {
|
|
775
|
-
const rows = this.db.prepare(`
|
|
776
|
-
SELECT * FROM memory_conflicts
|
|
777
|
-
WHERE project_id = ? AND status = ?
|
|
778
|
-
ORDER BY id DESC
|
|
775
|
+
const rows = this.db.prepare(`
|
|
776
|
+
SELECT * FROM memory_conflicts
|
|
777
|
+
WHERE project_id = ? AND status = ?
|
|
778
|
+
ORDER BY id DESC
|
|
779
779
|
`).all(projectId, status);
|
|
780
780
|
return rows;
|
|
781
781
|
}
|
|
@@ -797,26 +797,26 @@ export class FastrRepository {
|
|
|
797
797
|
this.db.exec('BEGIN TRANSACTION');
|
|
798
798
|
try {
|
|
799
799
|
// 1. Mark conflict as resolved
|
|
800
|
-
this.db.prepare(`
|
|
801
|
-
UPDATE memory_conflicts
|
|
802
|
-
SET status = 'resolved', resolved_winner_id = ?, resolved_at = ?
|
|
803
|
-
WHERE id = ?
|
|
800
|
+
this.db.prepare(`
|
|
801
|
+
UPDATE memory_conflicts
|
|
802
|
+
SET status = 'resolved', resolved_winner_id = ?, resolved_at = ?
|
|
803
|
+
WHERE id = ?
|
|
804
804
|
`).run(keepMemoryId, nowIso, conflictId);
|
|
805
805
|
// 2. Handle loser memory
|
|
806
806
|
if (forgetLoser) {
|
|
807
807
|
// Soft-delete / supersede loser
|
|
808
|
-
this.db.prepare(`
|
|
809
|
-
UPDATE ${loserTable}
|
|
810
|
-
SET valid_to = ?, status = 'superseded', superseded_by_id = ?, superseded_reason = ?
|
|
811
|
-
WHERE id = ? AND valid_to IS NULL
|
|
808
|
+
this.db.prepare(`
|
|
809
|
+
UPDATE ${loserTable}
|
|
810
|
+
SET valid_to = ?, status = 'superseded', superseded_by_id = ?, superseded_reason = ?
|
|
811
|
+
WHERE id = ? AND valid_to IS NULL
|
|
812
812
|
`).run(nowIso, keepMemoryId, resolutionReason, loserId);
|
|
813
813
|
}
|
|
814
814
|
else {
|
|
815
815
|
// Weaken loser reward score by -0.4 (same 0.1 floor as updateFeedback)
|
|
816
|
-
this.db.prepare(`
|
|
817
|
-
UPDATE ${loserTable}
|
|
818
|
-
SET reward_score = MAX(0.1, reward_score - 0.4)
|
|
819
|
-
WHERE id = ?
|
|
816
|
+
this.db.prepare(`
|
|
817
|
+
UPDATE ${loserTable}
|
|
818
|
+
SET reward_score = MAX(0.1, reward_score - 0.4)
|
|
819
|
+
WHERE id = ?
|
|
820
820
|
`).run(loserId);
|
|
821
821
|
}
|
|
822
822
|
this.db.exec('COMMIT');
|
|
@@ -828,26 +828,26 @@ export class FastrRepository {
|
|
|
828
828
|
}
|
|
829
829
|
}
|
|
830
830
|
getEntities(projectId) {
|
|
831
|
-
const rows = this.db.prepare(`
|
|
832
|
-
SELECT * FROM entities WHERE project_id = ? ORDER BY name ASC
|
|
831
|
+
const rows = this.db.prepare(`
|
|
832
|
+
SELECT * FROM entities WHERE project_id = ? ORDER BY name ASC
|
|
833
833
|
`).all(projectId);
|
|
834
834
|
return rows;
|
|
835
835
|
}
|
|
836
836
|
// --- PREDICTIONS & PLANNING FALLACY ---
|
|
837
837
|
createPrediction(params) {
|
|
838
|
-
const res = this.db.prepare(`
|
|
839
|
-
INSERT INTO predictions (project_id, class_tag, claim_text, estimate_value, estimate_unit)
|
|
840
|
-
VALUES (?, ?, ?, ?, ?)
|
|
838
|
+
const res = this.db.prepare(`
|
|
839
|
+
INSERT INTO predictions (project_id, class_tag, claim_text, estimate_value, estimate_unit)
|
|
840
|
+
VALUES (?, ?, ?, ?, ?)
|
|
841
841
|
`).run(params.projectId, params.classTag.trim().toLowerCase(), params.claimText, params.estimateValue ?? null, params.estimateUnit ?? null);
|
|
842
842
|
const row = this.db.prepare('SELECT * FROM predictions WHERE id = ?').get(res.lastInsertRowid);
|
|
843
843
|
return row;
|
|
844
844
|
}
|
|
845
845
|
closePrediction(id, actualValue) {
|
|
846
846
|
const nowIso = new Date().toISOString();
|
|
847
|
-
this.db.prepare(`
|
|
848
|
-
UPDATE predictions
|
|
849
|
-
SET actual_value = ?, closure_state = 'closed', closed_at = ?
|
|
850
|
-
WHERE id = ?
|
|
847
|
+
this.db.prepare(`
|
|
848
|
+
UPDATE predictions
|
|
849
|
+
SET actual_value = ?, closure_state = 'closed', closed_at = ?
|
|
850
|
+
WHERE id = ?
|
|
851
851
|
`).run(actualValue, nowIso, id);
|
|
852
852
|
const row = this.db.prepare('SELECT * FROM predictions WHERE id = ?').get(id);
|
|
853
853
|
if (!row)
|
|
@@ -870,10 +870,10 @@ export class FastrRepository {
|
|
|
870
870
|
}
|
|
871
871
|
getPredictionBaserate(projectId, classTag) {
|
|
872
872
|
const normalizedTag = classTag.trim().toLowerCase();
|
|
873
|
-
const rows = this.db.prepare(`
|
|
874
|
-
SELECT estimate_value, actual_value
|
|
875
|
-
FROM predictions
|
|
876
|
-
WHERE project_id = ? AND class_tag = ? AND closure_state = 'closed'
|
|
873
|
+
const rows = this.db.prepare(`
|
|
874
|
+
SELECT estimate_value, actual_value
|
|
875
|
+
FROM predictions
|
|
876
|
+
WHERE project_id = ? AND class_tag = ? AND closure_state = 'closed'
|
|
877
877
|
`).all(projectId, normalizedTag);
|
|
878
878
|
return calculateBaserate(normalizedTag, rows);
|
|
879
879
|
}
|
|
@@ -885,23 +885,23 @@ export class FastrRepository {
|
|
|
885
885
|
const assignedTo = params.assignedTo ?? null;
|
|
886
886
|
this.db.exec('BEGIN TRANSACTION');
|
|
887
887
|
try {
|
|
888
|
-
this.db.prepare(`
|
|
889
|
-
INSERT INTO actions (id, project_id, title, description, status, priority, assigned_to)
|
|
890
|
-
VALUES (?, ?, ?, ?, 'pending', ?, ?)
|
|
888
|
+
this.db.prepare(`
|
|
889
|
+
INSERT INTO actions (id, project_id, title, description, status, priority, assigned_to)
|
|
890
|
+
VALUES (?, ?, ?, ?, 'pending', ?, ?)
|
|
891
891
|
`).run(actionId, params.projectId, params.title, description, priority, assignedTo);
|
|
892
892
|
if (params.requires && params.requires.length > 0) {
|
|
893
|
-
const stmt = this.db.prepare(`
|
|
894
|
-
INSERT OR IGNORE INTO action_edges (source_action_id, target_action_id, edge_type)
|
|
895
|
-
VALUES (?, ?, 'requires')
|
|
893
|
+
const stmt = this.db.prepare(`
|
|
894
|
+
INSERT OR IGNORE INTO action_edges (source_action_id, target_action_id, edge_type)
|
|
895
|
+
VALUES (?, ?, 'requires')
|
|
896
896
|
`);
|
|
897
897
|
for (const req of params.requires) {
|
|
898
898
|
stmt.run(actionId, req);
|
|
899
899
|
}
|
|
900
900
|
}
|
|
901
901
|
if (params.conflictsWith && params.conflictsWith.length > 0) {
|
|
902
|
-
const stmt = this.db.prepare(`
|
|
903
|
-
INSERT OR IGNORE INTO action_edges (source_action_id, target_action_id, edge_type)
|
|
904
|
-
VALUES (?, ?, 'conflicts_with')
|
|
902
|
+
const stmt = this.db.prepare(`
|
|
903
|
+
INSERT OR IGNORE INTO action_edges (source_action_id, target_action_id, edge_type)
|
|
904
|
+
VALUES (?, ?, 'conflicts_with')
|
|
905
905
|
`);
|
|
906
906
|
for (const conf of params.conflictsWith) {
|
|
907
907
|
stmt.run(actionId, conf);
|
|
@@ -921,16 +921,16 @@ export class FastrRepository {
|
|
|
921
921
|
const action = this.db.prepare('SELECT * FROM actions WHERE id = ?').get(id);
|
|
922
922
|
if (!action)
|
|
923
923
|
return null;
|
|
924
|
-
const requiresRows = this.db.prepare(`
|
|
925
|
-
SELECT target_action_id FROM action_edges WHERE source_action_id = ? AND edge_type = 'requires'
|
|
924
|
+
const requiresRows = this.db.prepare(`
|
|
925
|
+
SELECT target_action_id FROM action_edges WHERE source_action_id = ? AND edge_type = 'requires'
|
|
926
926
|
`).all(id);
|
|
927
|
-
const unlocksRows = this.db.prepare(`
|
|
928
|
-
SELECT source_action_id FROM action_edges WHERE target_action_id = ? AND edge_type = 'requires'
|
|
927
|
+
const unlocksRows = this.db.prepare(`
|
|
928
|
+
SELECT source_action_id FROM action_edges WHERE target_action_id = ? AND edge_type = 'requires'
|
|
929
929
|
`).all(id);
|
|
930
|
-
const conflictsRows = this.db.prepare(`
|
|
931
|
-
SELECT target_action_id FROM action_edges WHERE source_action_id = ? AND edge_type = 'conflicts_with'
|
|
932
|
-
UNION
|
|
933
|
-
SELECT source_action_id FROM action_edges WHERE target_action_id = ? AND edge_type = 'conflicts_with'
|
|
930
|
+
const conflictsRows = this.db.prepare(`
|
|
931
|
+
SELECT target_action_id FROM action_edges WHERE source_action_id = ? AND edge_type = 'conflicts_with'
|
|
932
|
+
UNION
|
|
933
|
+
SELECT source_action_id FROM action_edges WHERE target_action_id = ? AND edge_type = 'conflicts_with'
|
|
934
934
|
`).all(id, id);
|
|
935
935
|
return {
|
|
936
936
|
...action,
|
|
@@ -1009,32 +1009,32 @@ export class FastrRepository {
|
|
|
1009
1009
|
return this.db.prepare(sql).all(...params);
|
|
1010
1010
|
}
|
|
1011
1011
|
getFrontier(projectId) {
|
|
1012
|
-
const query = `
|
|
1013
|
-
SELECT a.*
|
|
1014
|
-
FROM actions a
|
|
1015
|
-
WHERE a.project_id = ?
|
|
1016
|
-
AND a.status IN ('pending', 'active')
|
|
1017
|
-
AND NOT EXISTS (
|
|
1018
|
-
SELECT 1
|
|
1019
|
-
FROM action_edges e
|
|
1020
|
-
JOIN actions prereq ON prereq.id = e.target_action_id
|
|
1021
|
-
WHERE e.source_action_id = a.id
|
|
1022
|
-
AND e.edge_type = 'requires'
|
|
1023
|
-
AND prereq.status != 'done'
|
|
1024
|
-
)
|
|
1025
|
-
AND NOT EXISTS (
|
|
1026
|
-
SELECT 1
|
|
1027
|
-
FROM action_edges e
|
|
1028
|
-
JOIN actions conf ON (
|
|
1029
|
-
(e.source_action_id = a.id AND conf.id = e.target_action_id)
|
|
1030
|
-
OR
|
|
1031
|
-
(e.target_action_id = a.id AND conf.id = e.source_action_id)
|
|
1032
|
-
)
|
|
1033
|
-
WHERE e.edge_type = 'conflicts_with'
|
|
1034
|
-
AND conf.status = 'active'
|
|
1035
|
-
AND conf.id != a.id
|
|
1036
|
-
)
|
|
1037
|
-
ORDER BY a.priority DESC, a.created_at ASC
|
|
1012
|
+
const query = `
|
|
1013
|
+
SELECT a.*
|
|
1014
|
+
FROM actions a
|
|
1015
|
+
WHERE a.project_id = ?
|
|
1016
|
+
AND a.status IN ('pending', 'active')
|
|
1017
|
+
AND NOT EXISTS (
|
|
1018
|
+
SELECT 1
|
|
1019
|
+
FROM action_edges e
|
|
1020
|
+
JOIN actions prereq ON prereq.id = e.target_action_id
|
|
1021
|
+
WHERE e.source_action_id = a.id
|
|
1022
|
+
AND e.edge_type = 'requires'
|
|
1023
|
+
AND prereq.status != 'done'
|
|
1024
|
+
)
|
|
1025
|
+
AND NOT EXISTS (
|
|
1026
|
+
SELECT 1
|
|
1027
|
+
FROM action_edges e
|
|
1028
|
+
JOIN actions conf ON (
|
|
1029
|
+
(e.source_action_id = a.id AND conf.id = e.target_action_id)
|
|
1030
|
+
OR
|
|
1031
|
+
(e.target_action_id = a.id AND conf.id = e.source_action_id)
|
|
1032
|
+
)
|
|
1033
|
+
WHERE e.edge_type = 'conflicts_with'
|
|
1034
|
+
AND conf.status = 'active'
|
|
1035
|
+
AND conf.id != a.id
|
|
1036
|
+
)
|
|
1037
|
+
ORDER BY a.priority DESC, a.created_at ASC
|
|
1038
1038
|
`;
|
|
1039
1039
|
return this.db.prepare(query).all(projectId);
|
|
1040
1040
|
}
|
|
@@ -1048,9 +1048,9 @@ export class FastrRepository {
|
|
|
1048
1048
|
// Hold the write lock across read + claim so two agents can never claim the same action.
|
|
1049
1049
|
this.db.exec('BEGIN IMMEDIATE');
|
|
1050
1050
|
try {
|
|
1051
|
-
const claim = this.db.prepare(`
|
|
1052
|
-
UPDATE actions SET status = 'active', assigned_to = ?
|
|
1053
|
-
WHERE id = ? AND status = 'pending'
|
|
1051
|
+
const claim = this.db.prepare(`
|
|
1052
|
+
UPDATE actions SET status = 'active', assigned_to = ?
|
|
1053
|
+
WHERE id = ? AND status = 'pending'
|
|
1054
1054
|
`);
|
|
1055
1055
|
for (const candidate of this.getFrontier(projectId)) {
|
|
1056
1056
|
if (candidate.status === 'active') {
|
|
@@ -1094,32 +1094,32 @@ export class FastrRepository {
|
|
|
1094
1094
|
throw new Error(`Content length (${content.length}) exceeds slot size limit (${sizeLimit} chars). Summarize before storing.`);
|
|
1095
1095
|
}
|
|
1096
1096
|
const nowIso = new Date().toISOString();
|
|
1097
|
-
this.db.prepare(`
|
|
1098
|
-
INSERT INTO memory_slots (project_id, slot_key, content, size_limit, updated_at)
|
|
1099
|
-
VALUES (?, ?, ?, ?, ?)
|
|
1100
|
-
ON CONFLICT(project_id, slot_key) DO UPDATE SET
|
|
1101
|
-
content = excluded.content,
|
|
1102
|
-
size_limit = excluded.size_limit,
|
|
1103
|
-
updated_at = excluded.updated_at
|
|
1097
|
+
this.db.prepare(`
|
|
1098
|
+
INSERT INTO memory_slots (project_id, slot_key, content, size_limit, updated_at)
|
|
1099
|
+
VALUES (?, ?, ?, ?, ?)
|
|
1100
|
+
ON CONFLICT(project_id, slot_key) DO UPDATE SET
|
|
1101
|
+
content = excluded.content,
|
|
1102
|
+
size_limit = excluded.size_limit,
|
|
1103
|
+
updated_at = excluded.updated_at
|
|
1104
1104
|
`).run(projectId, normalizedKey, content, sizeLimit, nowIso);
|
|
1105
1105
|
return this.getSlot(projectId, normalizedKey);
|
|
1106
1106
|
}
|
|
1107
1107
|
getSlot(projectId, slotKey) {
|
|
1108
1108
|
const normalizedKey = slotKey.trim().toLowerCase();
|
|
1109
|
-
const row = this.db.prepare(`
|
|
1110
|
-
SELECT * FROM memory_slots WHERE project_id = ? AND slot_key = ?
|
|
1109
|
+
const row = this.db.prepare(`
|
|
1110
|
+
SELECT * FROM memory_slots WHERE project_id = ? AND slot_key = ?
|
|
1111
1111
|
`).get(projectId, normalizedKey);
|
|
1112
1112
|
return row ?? null;
|
|
1113
1113
|
}
|
|
1114
1114
|
listSlots(projectId) {
|
|
1115
|
-
return this.db.prepare(`
|
|
1116
|
-
SELECT * FROM memory_slots WHERE project_id = ? ORDER BY slot_key ASC
|
|
1115
|
+
return this.db.prepare(`
|
|
1116
|
+
SELECT * FROM memory_slots WHERE project_id = ? ORDER BY slot_key ASC
|
|
1117
1117
|
`).all(projectId);
|
|
1118
1118
|
}
|
|
1119
1119
|
deleteSlot(projectId, slotKey) {
|
|
1120
1120
|
const normalizedKey = slotKey.trim().toLowerCase();
|
|
1121
|
-
const res = this.db.prepare(`
|
|
1122
|
-
DELETE FROM memory_slots WHERE project_id = ? AND slot_key = ?
|
|
1121
|
+
const res = this.db.prepare(`
|
|
1122
|
+
DELETE FROM memory_slots WHERE project_id = ? AND slot_key = ?
|
|
1123
1123
|
`).run(projectId, normalizedKey);
|
|
1124
1124
|
return res.changes > 0;
|
|
1125
1125
|
}
|