opencode-fractal-memory 0.2.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/LICENSE +21 -0
- package/README.md +493 -0
- package/agent/memory-hints.md +98 -0
- package/agent/memory-researcher.md +56 -0
- package/commands/memory-auto-test.md +10 -0
- package/commands/memory-cache-status.md +13 -0
- package/commands/memory-check-context.md +4 -0
- package/commands/memory-compress.md +13 -0
- package/commands/memory-dashboard.md +23 -0
- package/commands/memory-delete.md +24 -0
- package/commands/memory-detect-topics.md +28 -0
- package/commands/memory-distill.md +35 -0
- package/commands/memory-drilldown-query.md +28 -0
- package/commands/memory-drilldown.md +11 -0
- package/commands/memory-extract-patterns.md +4 -0
- package/commands/memory-generate-embeddings.md +26 -0
- package/commands/memory-get.md +26 -0
- package/commands/memory-help.md +55 -0
- package/commands/memory-injection-feedback.md +26 -0
- package/commands/memory-injection-stats.md +11 -0
- package/commands/memory-list.md +4 -0
- package/commands/memory-llm-compress.md +34 -0
- package/commands/memory-mcp.md +20 -0
- package/commands/memory-prune.md +4 -0
- package/commands/memory-rate.md +48 -0
- package/commands/memory-reflect.md +37 -0
- package/commands/memory-replace.md +26 -0
- package/commands/memory-retrieve.md +34 -0
- package/commands/memory-search.md +28 -0
- package/commands/memory-session-stats.md +4 -0
- package/commands/memory-set.md +31 -0
- package/commands/memory-stats.md +11 -0
- package/commands/memory-summarize.md +29 -0
- package/commands/memory-tool-stats.md +4 -0
- package/commands/memory-total-tokens.md +10 -0
- package/commands/memory-verify.md +4 -0
- package/commands/memory-version.md +9 -0
- package/dist/cache.js +39 -0
- package/dist/config.js +120 -0
- package/dist/embeddings.js +125 -0
- package/dist/ensure-models.js +70 -0
- package/dist/file-summary.js +143 -0
- package/dist/frontmatter.js +28 -0
- package/dist/hnsw-index.js +138 -0
- package/dist/hooks/auto-discover.js +4 -0
- package/dist/hooks/auto-distill.js +120 -0
- package/dist/hooks/auto-retrieve/content.js +47 -0
- package/dist/hooks/auto-retrieve/detection.js +50 -0
- package/dist/hooks/auto-retrieve/formatting.js +19 -0
- package/dist/hooks/auto-retrieve/index.js +163 -0
- package/dist/hooks/auto-retrieve/scoring.js +56 -0
- package/dist/hooks/auto-retrieve.js +1 -0
- package/dist/hooks/index.js +4 -0
- package/dist/hooks/predictive-rating.js +87 -0
- package/dist/journal.js +279 -0
- package/dist/logging.js +147 -0
- package/dist/management/helpers.js +227 -0
- package/dist/management/router.js +48 -0
- package/dist/management/routes.js +197 -0
- package/dist/management-server.js +4 -0
- package/dist/management-standalone.js +31 -0
- package/dist/mcp/logging.js +57 -0
- package/dist/mcp/server.js +251 -0
- package/dist/mcp/transform.js +48 -0
- package/dist/mcp-server.js +18 -0
- package/dist/memory.js +2 -0
- package/dist/ollama.js +74 -0
- package/dist/plugin/hooks.js +168 -0
- package/dist/plugin/index.js +28 -0
- package/dist/plugin/init.js +109 -0
- package/dist/plugin/state.js +75 -0
- package/dist/plugin/tools.js +45 -0
- package/dist/plugin.js +2 -0
- package/dist/procedural/store.js +1 -0
- package/dist/procedural/types.js +1 -0
- package/dist/seed-nodes.js +804 -0
- package/dist/storage/compress-ops.js +129 -0
- package/dist/storage/compression/formatters.js +243 -0
- package/dist/storage/compression/index.js +107 -0
- package/dist/storage/compression/patterns.js +138 -0
- package/dist/storage/expiration.js +66 -0
- package/dist/storage/index.js +1 -0
- package/dist/storage/injection-events.js +82 -0
- package/dist/storage/lifecycle.js +65 -0
- package/dist/storage/maintenance.js +60 -0
- package/dist/storage/migrations/definitions.js +374 -0
- package/dist/storage/migrations/index.js +21 -0
- package/dist/storage/navigation.js +98 -0
- package/dist/storage/queries/base.js +44 -0
- package/dist/storage/queries/links.js +32 -0
- package/dist/storage/queries/nodes.js +189 -0
- package/dist/storage/queries/search-helpers.js +239 -0
- package/dist/storage/scoring.js +36 -0
- package/dist/storage/search.js +233 -0
- package/dist/storage/session-tracking.js +180 -0
- package/dist/storage/sqlite.js +329 -0
- package/dist/storage/tool-usage.js +56 -0
- package/dist/storage/types.js +1 -0
- package/dist/storage/utils.js +94 -0
- package/dist/tools/auto-test.js +24 -0
- package/dist/tools/cache-status.js +36 -0
- package/dist/tools/compress.js +186 -0
- package/dist/tools/core.js +307 -0
- package/dist/tools/dashboard.js +97 -0
- package/dist/tools/help.js +59 -0
- package/dist/tools/index.js +12 -0
- package/dist/tools/inject.js +91 -0
- package/dist/tools/injection-debug.js +48 -0
- package/dist/tools/journal.js +105 -0
- package/dist/tools/llm-compress.js +41 -0
- package/dist/tools/middle-term.js +68 -0
- package/dist/tools/playbook.js +64 -0
- package/dist/tools/reflect.js +291 -0
- package/dist/tools/search.js +188 -0
- package/dist/tools/session.js +189 -0
- package/dist/tools/shared.js +74 -0
- package/dist/tools/skill.js +37 -0
- package/dist/tools/stats.js +256 -0
- package/dist/tools/version.js +13 -0
- package/dist/tools.js +18 -0
- package/dist/utils/hybridScore.js +67 -0
- package/management/public/app.js +1529 -0
- package/management/public/index.html +486 -0
- package/management/public/three.min.js +6 -0
- package/package.json +65 -0
- package/scripts/download-models.ts +16 -0
- package/scripts/postinstall.cjs +30 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { withRetry } from "./utils";
|
|
3
|
+
export async function insertInjectionMetrics(db, sessionId, data) {
|
|
4
|
+
const id = randomUUID();
|
|
5
|
+
const timestamp = Date.now();
|
|
6
|
+
await withRetry(() => {
|
|
7
|
+
db.run(`INSERT INTO injection_metrics
|
|
8
|
+
(id, session_id, timestamp, injected_node_count, injected_tokens, injection_mode, query_text, tool_calls, memory_tools_used, referenced_nodes)
|
|
9
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 0, '[]', '[]')`, [id, sessionId, timestamp, data.injectedNodeCount, data.injectedTokens, data.injectionMode, data.queryText ?? null]);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export function getPendingInjections(db) {
|
|
13
|
+
try {
|
|
14
|
+
return db.query("SELECT id, node_id as nodeId, scope, source, created_at as createdAt FROM pending_injections WHERE processed = 0 ORDER BY id ASC").all();
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function markInjectionProcessed(db, id) {
|
|
21
|
+
try {
|
|
22
|
+
db.run("UPDATE pending_injections SET processed = 1 WHERE id = ?", [id]);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// silently ignore
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export async function updateMemoryToolCall(db, sessionId, toolName) {
|
|
29
|
+
const metrics = db.query("SELECT id, tool_calls, memory_tools_used FROM injection_metrics WHERE session_id = ? ORDER BY timestamp DESC LIMIT 1").get(sessionId);
|
|
30
|
+
if (!metrics)
|
|
31
|
+
return;
|
|
32
|
+
const tools = JSON.parse(metrics.memory_tools_used || '[]');
|
|
33
|
+
if (!tools.includes(toolName)) {
|
|
34
|
+
tools.push(toolName);
|
|
35
|
+
}
|
|
36
|
+
await withRetry(() => {
|
|
37
|
+
db.run("UPDATE injection_metrics SET tool_calls = ?, memory_tools_used = ? WHERE id = ?", [metrics.tool_calls + 1, JSON.stringify(tools), metrics.id]);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export async function finalizeInjection(db, sessionId, effectivenessScore, taskDescription) {
|
|
41
|
+
await withRetry(() => {
|
|
42
|
+
db.run("UPDATE injection_metrics SET effectiveness_score = ?, task_description = ? WHERE session_id = ?", [effectivenessScore ?? null, taskDescription ?? null, sessionId]);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export async function insertInjectionFeedback(db, sessionId, upvotes, downvotes, taskOutcome, neededNodes) {
|
|
46
|
+
await withRetry(() => {
|
|
47
|
+
db.run(`UPDATE injection_metrics
|
|
48
|
+
SET injection_upvotes = ?, injection_downvotes = ?, task_outcome = ?, needed_nodes = ?
|
|
49
|
+
WHERE session_id = ?`, [upvotes, downvotes, taskOutcome ?? null, neededNodes ? JSON.stringify(neededNodes) : null, sessionId]);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
export function queryInjectionMetrics(db, limit = 100) {
|
|
53
|
+
const rows = db.query(`SELECT session_id, timestamp, injected_node_count, injected_tokens, injection_mode, tool_calls, effectiveness_score
|
|
54
|
+
FROM injection_metrics
|
|
55
|
+
ORDER BY timestamp DESC
|
|
56
|
+
LIMIT ?`).all(limit);
|
|
57
|
+
return rows.map(row => ({
|
|
58
|
+
sessionId: row.session_id,
|
|
59
|
+
timestamp: row.timestamp,
|
|
60
|
+
injectedNodeCount: row.injected_node_count,
|
|
61
|
+
injectedTokens: row.injected_tokens,
|
|
62
|
+
injectionMode: row.injection_mode,
|
|
63
|
+
toolCalls: row.tool_calls,
|
|
64
|
+
effectivenessScore: row.effectiveness_score,
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
export function querySessionMetrics(db, sessionId) {
|
|
68
|
+
const row = db.query(`SELECT
|
|
69
|
+
COUNT(*) as total_injections,
|
|
70
|
+
SUM(tool_calls) as total_tool_calls,
|
|
71
|
+
AVG(effectiveness_score) as avg_effectiveness
|
|
72
|
+
FROM injection_metrics
|
|
73
|
+
WHERE session_id = ?`).get(sessionId);
|
|
74
|
+
const toolsRow = db.query(`SELECT memory_tools_used FROM injection_metrics WHERE session_id = ? LIMIT 1`).get(sessionId);
|
|
75
|
+
const memoryToolsUsed = toolsRow ? JSON.parse(toolsRow.memory_tools_used || '[]') : [];
|
|
76
|
+
return {
|
|
77
|
+
totalInjections: row.total_injections,
|
|
78
|
+
totalToolCalls: row.total_tool_calls ?? 0,
|
|
79
|
+
memoryToolsUsed,
|
|
80
|
+
avgEffectiveness: row.avg_effectiveness,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { rowToNode } from "./queries/base";
|
|
3
|
+
import { queryGetNode } from "./queries/nodes";
|
|
4
|
+
import { withRetry } from "./utils";
|
|
5
|
+
export async function ensureSeed(getDb, seeds) {
|
|
6
|
+
for (const seed of seeds) {
|
|
7
|
+
const db = await getDb(seed.scope);
|
|
8
|
+
const existing = db.query("SELECT id FROM memory_nodes WHERE label = ?").get(seed.label);
|
|
9
|
+
if (existing)
|
|
10
|
+
continue;
|
|
11
|
+
const now = Date.now();
|
|
12
|
+
await withRetry(() => {
|
|
13
|
+
db.run("INSERT INTO memory_nodes (id, scope, label, content, summary, level, parent_ids, embedding, created_at, updated_at, importance, access_count, last_accessed, type, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [randomUUID(), seed.scope, seed.label, "", null, 0, null, null, now, now, 0.5, 0, null, "note", null]);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function resolveNode(getDb, idScopeCache, id) {
|
|
18
|
+
if (idScopeCache.has(id)) {
|
|
19
|
+
const scope = idScopeCache.get(id);
|
|
20
|
+
return { scope, db: await getDb(scope) };
|
|
21
|
+
}
|
|
22
|
+
for (const scope of ["global", "project"]) {
|
|
23
|
+
const db = await getDb(scope);
|
|
24
|
+
const existing = db.query("SELECT id FROM memory_nodes WHERE id = ?").get(id);
|
|
25
|
+
if (existing) {
|
|
26
|
+
idScopeCache.set(id, scope);
|
|
27
|
+
return { scope, db };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
throw new Error(`Memory node not found: ${id}`);
|
|
31
|
+
}
|
|
32
|
+
export async function getNode(getDb, id) {
|
|
33
|
+
let foundNode = null;
|
|
34
|
+
let foundScope = null;
|
|
35
|
+
for (const scope of ["global", "project"]) {
|
|
36
|
+
const node = await queryGetNode(await getDb(scope), id);
|
|
37
|
+
if (node) {
|
|
38
|
+
foundNode = node;
|
|
39
|
+
foundScope = scope;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (!foundNode || !foundScope) {
|
|
44
|
+
throw new Error(`Memory node not found: ${id}`);
|
|
45
|
+
}
|
|
46
|
+
const db = await getDb(foundScope);
|
|
47
|
+
const newAccessCount = foundNode.accessCount + 1;
|
|
48
|
+
const newConfidence = Math.min(1, foundNode.confidence + 0.01);
|
|
49
|
+
db.run("UPDATE memory_nodes SET access_count = ?, last_accessed = ?, confidence = ? WHERE id = ?", [newAccessCount, Date.now(), newConfidence, id]);
|
|
50
|
+
return { ...foundNode, accessCount: newAccessCount, confidence: newConfidence };
|
|
51
|
+
}
|
|
52
|
+
export async function verifyNode(getDb, id) {
|
|
53
|
+
for (const scope of ["global", "project"]) {
|
|
54
|
+
const db = await getDb(scope);
|
|
55
|
+
const existing = db.query("SELECT * FROM memory_nodes WHERE id = ?").get(id);
|
|
56
|
+
if (existing) {
|
|
57
|
+
const newConfidence = Math.min(1, (existing.confidence ?? 0.5) + 0.2);
|
|
58
|
+
await withRetry(() => {
|
|
59
|
+
db.run("UPDATE memory_nodes SET confidence = ?, last_verified = ? WHERE id = ?", [newConfidence, Date.now(), id]);
|
|
60
|
+
});
|
|
61
|
+
return rowToNode({ ...existing, confidence: newConfidence, last_verified: Date.now() });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
throw new Error(`Memory node not found: ${id}`);
|
|
65
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { getHNSWIndex } from "../hnsw-index";
|
|
2
|
+
import { extractLinks, embeddingToBlob, blobToEmbedding, withRetry } from "./utils";
|
|
3
|
+
import { updateBM25Index } from "./queries/search-helpers";
|
|
4
|
+
export async function backfillLinks(db) {
|
|
5
|
+
const nodes = db.query("SELECT id, content FROM memory_nodes").all();
|
|
6
|
+
for (const node of nodes) {
|
|
7
|
+
const links = extractLinks(node.content);
|
|
8
|
+
if (links.length === 0)
|
|
9
|
+
continue;
|
|
10
|
+
await withRetry(() => {
|
|
11
|
+
db.run("DELETE FROM memory_links WHERE source_id = ?", [node.id]);
|
|
12
|
+
});
|
|
13
|
+
for (const label of links) {
|
|
14
|
+
let targetId = null;
|
|
15
|
+
try {
|
|
16
|
+
const target = db.query("SELECT id FROM memory_nodes WHERE label = ?").get(label);
|
|
17
|
+
targetId = target?.id ?? null;
|
|
18
|
+
}
|
|
19
|
+
catch { /* ignore */ }
|
|
20
|
+
await withRetry(() => {
|
|
21
|
+
db.run("INSERT OR REPLACE INTO memory_links (source_id, target_label, target_id) VALUES (?, ?, ?)", [node.id, label, targetId]);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function backfillBinaryEmbeddingsAndBM25(db, scope) {
|
|
27
|
+
const rows = db.query("SELECT id, label, content, embedding, embedding_blob FROM memory_nodes").all();
|
|
28
|
+
for (const row of rows) {
|
|
29
|
+
if (row.embedding && !row.embedding_blob) {
|
|
30
|
+
const embedding = JSON.parse(row.embedding);
|
|
31
|
+
const blob = embeddingToBlob(embedding);
|
|
32
|
+
await withRetry(() => {
|
|
33
|
+
db.run("UPDATE memory_nodes SET embedding_blob = ? WHERE id = ?", [blob, row.id]);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
updateBM25Index(db, row.id, row.content, row.label, scope);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function rebuildHNSWIndex(getDb, scope) {
|
|
40
|
+
const hnsw = getHNSWIndex();
|
|
41
|
+
const nodes = [];
|
|
42
|
+
const scopes = scope === "all" || !scope ? ["global", "project"] : [scope];
|
|
43
|
+
for (const s of scopes) {
|
|
44
|
+
const db = await getDb(s);
|
|
45
|
+
const rows = db.query("SELECT id, embedding, embedding_blob FROM memory_nodes WHERE embedding IS NOT NULL OR embedding_blob IS NOT NULL").all();
|
|
46
|
+
for (const row of rows) {
|
|
47
|
+
let embedding = null;
|
|
48
|
+
if (row.embedding_blob) {
|
|
49
|
+
embedding = blobToEmbedding(row.embedding_blob);
|
|
50
|
+
}
|
|
51
|
+
else if (row.embedding) {
|
|
52
|
+
embedding = JSON.parse(row.embedding);
|
|
53
|
+
}
|
|
54
|
+
if (embedding) {
|
|
55
|
+
nodes.push({ id: row.id, embedding, scope: s });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
await hnsw.rebuild(nodes);
|
|
60
|
+
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
export const MIGRATIONS = [
|
|
2
|
+
{
|
|
3
|
+
version: 1,
|
|
4
|
+
name: "create-table",
|
|
5
|
+
up: (db) => {
|
|
6
|
+
db.run(`
|
|
7
|
+
CREATE TABLE IF NOT EXISTS memory_nodes (
|
|
8
|
+
id TEXT PRIMARY KEY,
|
|
9
|
+
scope TEXT NOT NULL,
|
|
10
|
+
label TEXT NOT NULL,
|
|
11
|
+
content TEXT NOT NULL,
|
|
12
|
+
summary TEXT,
|
|
13
|
+
level INT NOT NULL DEFAULT 0,
|
|
14
|
+
parent_ids TEXT,
|
|
15
|
+
embedding TEXT,
|
|
16
|
+
created_at INT NOT NULL,
|
|
17
|
+
updated_at INT NOT NULL,
|
|
18
|
+
importance REAL DEFAULT 0.5,
|
|
19
|
+
access_count INT DEFAULT 0,
|
|
20
|
+
last_accessed INT,
|
|
21
|
+
type TEXT,
|
|
22
|
+
metadata TEXT
|
|
23
|
+
)
|
|
24
|
+
`);
|
|
25
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_nodes_scope ON memory_nodes(scope)`);
|
|
26
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_nodes_level ON memory_nodes(level)`);
|
|
27
|
+
try {
|
|
28
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_nodes_parent ON memory_nodes(parent_ids)`);
|
|
29
|
+
}
|
|
30
|
+
catch { /* index might already exist */ }
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
version: 2,
|
|
35
|
+
name: "add-parent-ids-embedding",
|
|
36
|
+
up: (db) => {
|
|
37
|
+
try {
|
|
38
|
+
const tableInfo = db.query("PRAGMA table_info(memory_nodes)").all();
|
|
39
|
+
const existingColumns = new Set(tableInfo.map(c => c.name));
|
|
40
|
+
if (existingColumns.size > 0) {
|
|
41
|
+
if (!existingColumns.has("parent_ids")) {
|
|
42
|
+
db.run("ALTER TABLE memory_nodes ADD COLUMN parent_ids TEXT");
|
|
43
|
+
}
|
|
44
|
+
if (!existingColumns.has("embedding")) {
|
|
45
|
+
db.run("ALTER TABLE memory_nodes ADD COLUMN embedding TEXT");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch { /* table may not exist yet — v1 will create it */ }
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
version: 3,
|
|
54
|
+
name: "add-config-table",
|
|
55
|
+
up: (db) => {
|
|
56
|
+
db.run(`
|
|
57
|
+
CREATE TABLE IF NOT EXISTS memory_config (
|
|
58
|
+
key TEXT PRIMARY KEY,
|
|
59
|
+
value TEXT NOT NULL,
|
|
60
|
+
updated_at INT NOT NULL
|
|
61
|
+
)
|
|
62
|
+
`);
|
|
63
|
+
const now = Date.now();
|
|
64
|
+
db.run("INSERT OR IGNORE INTO memory_config (key, value, updated_at) VALUES (?, ?, ?)", ["context_threshold", "0.8", now]);
|
|
65
|
+
db.run("INSERT OR IGNORE INTO memory_config (key, value, updated_at) VALUES (?, ?, ?)", ["context_limit", "128000", now]);
|
|
66
|
+
db.run("INSERT OR IGNORE INTO memory_config (key, value, updated_at) VALUES (?, ?, ?)", ["similarity_threshold", "0.3", now]);
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
version: 5,
|
|
71
|
+
name: "add-usage-log-table",
|
|
72
|
+
up: (db) => {
|
|
73
|
+
db.run(`
|
|
74
|
+
CREATE TABLE IF NOT EXISTS memory_usage_log (
|
|
75
|
+
id TEXT PRIMARY KEY,
|
|
76
|
+
tool_name TEXT NOT NULL,
|
|
77
|
+
timestamp INT NOT NULL,
|
|
78
|
+
result_tokens INT DEFAULT 0,
|
|
79
|
+
context_warning INT DEFAULT 0,
|
|
80
|
+
success INT DEFAULT 1,
|
|
81
|
+
duration_ms INT DEFAULT 0
|
|
82
|
+
)
|
|
83
|
+
`);
|
|
84
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_usage_log_tool ON memory_usage_log(tool_name)`);
|
|
85
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_usage_log_ts ON memory_usage_log(timestamp)`);
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
version: 6,
|
|
90
|
+
name: "add-sticky-column",
|
|
91
|
+
up: (db) => {
|
|
92
|
+
try {
|
|
93
|
+
const tableInfo = db.query("PRAGMA table_info(memory_nodes)").all();
|
|
94
|
+
const existingColumns = new Set(tableInfo.map(c => c.name));
|
|
95
|
+
if (existingColumns.size > 0 && !existingColumns.has("sticky")) {
|
|
96
|
+
db.run("ALTER TABLE memory_nodes ADD COLUMN sticky INT DEFAULT 0");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch { /* table may not exist yet — v1 will create it */ }
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
version: 7,
|
|
104
|
+
name: "add-confidence-tracking",
|
|
105
|
+
up: (db) => {
|
|
106
|
+
try {
|
|
107
|
+
const tableInfo = db.query("PRAGMA table_info(memory_nodes)").all();
|
|
108
|
+
const existingColumns = new Set(tableInfo.map(c => c.name));
|
|
109
|
+
if (existingColumns.size > 0) {
|
|
110
|
+
if (!existingColumns.has("confidence")) {
|
|
111
|
+
db.run("ALTER TABLE memory_nodes ADD COLUMN confidence REAL DEFAULT 0.5");
|
|
112
|
+
}
|
|
113
|
+
if (!existingColumns.has("last_verified")) {
|
|
114
|
+
db.run("ALTER TABLE memory_nodes ADD COLUMN last_verified INT");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch { /* table may not exist yet — v1 will create it */ }
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
version: 8,
|
|
123
|
+
name: "add-memory-links",
|
|
124
|
+
up: (db) => {
|
|
125
|
+
db.run(`
|
|
126
|
+
CREATE TABLE IF NOT EXISTS memory_links (
|
|
127
|
+
source_id TEXT NOT NULL,
|
|
128
|
+
target_label TEXT NOT NULL,
|
|
129
|
+
target_id TEXT,
|
|
130
|
+
PRIMARY KEY (source_id, target_label)
|
|
131
|
+
)
|
|
132
|
+
`);
|
|
133
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_links_source ON memory_links(source_id)`);
|
|
134
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_links_target ON memory_links(target_label)`);
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
version: 9,
|
|
139
|
+
name: "add-binary-embeddings-and-bm25-index",
|
|
140
|
+
up: (db) => {
|
|
141
|
+
try {
|
|
142
|
+
const tableInfo = db.query("PRAGMA table_info(memory_nodes)").all();
|
|
143
|
+
const existingColumns = new Set(tableInfo.map(c => c.name));
|
|
144
|
+
if (existingColumns.size > 0 && !existingColumns.has("embedding_blob")) {
|
|
145
|
+
db.run("ALTER TABLE memory_nodes ADD COLUMN embedding_blob BLOB");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch { /* table may not exist yet */ }
|
|
149
|
+
db.run(`
|
|
150
|
+
CREATE TABLE IF NOT EXISTS bm25_index (
|
|
151
|
+
term TEXT NOT NULL,
|
|
152
|
+
node_id TEXT NOT NULL,
|
|
153
|
+
frequency INT NOT NULL DEFAULT 1,
|
|
154
|
+
scope TEXT NOT NULL,
|
|
155
|
+
PRIMARY KEY (term, node_id)
|
|
156
|
+
)
|
|
157
|
+
`);
|
|
158
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_bm25_term ON bm25_index(term)`);
|
|
159
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_bm25_node ON bm25_index(node_id)`);
|
|
160
|
+
db.run(`
|
|
161
|
+
CREATE TABLE IF NOT EXISTS bm25_doc_stats (
|
|
162
|
+
node_id TEXT PRIMARY KEY,
|
|
163
|
+
token_count INT NOT NULL DEFAULT 0,
|
|
164
|
+
scope TEXT NOT NULL
|
|
165
|
+
)
|
|
166
|
+
`);
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
version: 10,
|
|
171
|
+
name: "add-injection-metrics",
|
|
172
|
+
up: (db) => {
|
|
173
|
+
db.run(`
|
|
174
|
+
CREATE TABLE IF NOT EXISTS injection_metrics (
|
|
175
|
+
id TEXT PRIMARY KEY,
|
|
176
|
+
session_id TEXT NOT NULL,
|
|
177
|
+
timestamp INT NOT NULL,
|
|
178
|
+
injected_node_count INT NOT NULL DEFAULT 0,
|
|
179
|
+
injected_tokens INT NOT NULL DEFAULT 0,
|
|
180
|
+
injection_mode TEXT,
|
|
181
|
+
query_text TEXT,
|
|
182
|
+
tool_calls INT NOT NULL DEFAULT 0,
|
|
183
|
+
memory_tools_used TEXT,
|
|
184
|
+
referenced_nodes TEXT,
|
|
185
|
+
effectiveness_score REAL,
|
|
186
|
+
task_description TEXT
|
|
187
|
+
)
|
|
188
|
+
`);
|
|
189
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_injection_session ON injection_metrics(session_id)`);
|
|
190
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_injection_ts ON injection_metrics(timestamp)`);
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
version: 12,
|
|
195
|
+
name: "add-tool-call-duration",
|
|
196
|
+
up: (db) => {
|
|
197
|
+
try {
|
|
198
|
+
db.run(`ALTER TABLE agent_tool_calls ADD COLUMN duration_ms REAL`);
|
|
199
|
+
}
|
|
200
|
+
catch (e) {
|
|
201
|
+
// Column may already exist
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
version: 13,
|
|
207
|
+
name: "add-injection-feedback",
|
|
208
|
+
up: (db) => {
|
|
209
|
+
try {
|
|
210
|
+
db.run(`ALTER TABLE injection_metrics ADD COLUMN injection_upvotes INT DEFAULT 0`);
|
|
211
|
+
db.run(`ALTER TABLE injection_metrics ADD COLUMN injection_downvotes INT DEFAULT 0`);
|
|
212
|
+
db.run(`ALTER TABLE injection_metrics ADD COLUMN task_outcome TEXT`);
|
|
213
|
+
db.run(`ALTER TABLE injection_metrics ADD COLUMN needed_nodes TEXT`);
|
|
214
|
+
}
|
|
215
|
+
catch (e) {
|
|
216
|
+
// Columns may already exist
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
version: 14,
|
|
222
|
+
name: "add-agent-tool-tracking",
|
|
223
|
+
up: (db) => {
|
|
224
|
+
db.run(`
|
|
225
|
+
CREATE TABLE IF NOT EXISTS agent_tool_calls (
|
|
226
|
+
id TEXT PRIMARY KEY,
|
|
227
|
+
session_id TEXT NOT NULL,
|
|
228
|
+
timestamp INT NOT NULL,
|
|
229
|
+
tool_name TEXT NOT NULL,
|
|
230
|
+
args_json TEXT,
|
|
231
|
+
output_preview TEXT,
|
|
232
|
+
success INTEGER,
|
|
233
|
+
duration_ms INTEGER,
|
|
234
|
+
tool_category TEXT,
|
|
235
|
+
file_path TEXT,
|
|
236
|
+
command TEXT
|
|
237
|
+
)
|
|
238
|
+
`);
|
|
239
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_agent_tool_session ON agent_tool_calls(session_id)`);
|
|
240
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_agent_tool_name ON agent_tool_calls(tool_name)`);
|
|
241
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_agent_tool_ts ON agent_tool_calls(timestamp)`);
|
|
242
|
+
db.run(`
|
|
243
|
+
CREATE TABLE IF NOT EXISTS session_metrics (
|
|
244
|
+
session_id TEXT PRIMARY KEY,
|
|
245
|
+
started_at INT NOT NULL,
|
|
246
|
+
ended_at INT,
|
|
247
|
+
total_tool_calls INT DEFAULT 0,
|
|
248
|
+
file_reads INT DEFAULT 0,
|
|
249
|
+
file_edits INT DEFAULT 0,
|
|
250
|
+
bash_commands INT DEFAULT 0,
|
|
251
|
+
memory_tools INT DEFAULT 0,
|
|
252
|
+
failed_tools INT DEFAULT 0,
|
|
253
|
+
unique_files_touched TEXT,
|
|
254
|
+
injection_count INT DEFAULT 0,
|
|
255
|
+
injected_tokens INT DEFAULT 0,
|
|
256
|
+
task_description TEXT,
|
|
257
|
+
status TEXT DEFAULT 'active'
|
|
258
|
+
)
|
|
259
|
+
`);
|
|
260
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_session_started ON session_metrics(started_at)`);
|
|
261
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_session_status ON session_metrics(status)`);
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
version: 16,
|
|
266
|
+
name: "add-pending-injections",
|
|
267
|
+
up: (db) => {
|
|
268
|
+
db.run(`
|
|
269
|
+
CREATE TABLE IF NOT EXISTS pending_injections (
|
|
270
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
271
|
+
node_id TEXT NOT NULL,
|
|
272
|
+
scope TEXT NOT NULL DEFAULT 'global',
|
|
273
|
+
source TEXT DEFAULT 'management',
|
|
274
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
275
|
+
processed INTEGER NOT NULL DEFAULT 0
|
|
276
|
+
)
|
|
277
|
+
`);
|
|
278
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_pending_injections_processed ON pending_injections(processed)`);
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
version: 15,
|
|
283
|
+
name: "add-usefulness-scores",
|
|
284
|
+
up: (db) => {
|
|
285
|
+
try {
|
|
286
|
+
const tableInfo = db.query("PRAGMA table_info(memory_nodes)").all();
|
|
287
|
+
const existingColumns = new Set(tableInfo.map(c => c.name));
|
|
288
|
+
if (existingColumns.size > 0) {
|
|
289
|
+
if (!existingColumns.has("usefulness_score")) {
|
|
290
|
+
db.run("ALTER TABLE memory_nodes ADD COLUMN usefulness_score REAL DEFAULT 0");
|
|
291
|
+
}
|
|
292
|
+
if (!existingColumns.has("times_used")) {
|
|
293
|
+
db.run("ALTER TABLE memory_nodes ADD COLUMN times_used INT DEFAULT 0");
|
|
294
|
+
}
|
|
295
|
+
if (!existingColumns.has("times_helpful")) {
|
|
296
|
+
db.run("ALTER TABLE memory_nodes ADD COLUMN times_helpful INT DEFAULT 0");
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
catch { /* table may not exist yet */ }
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
version: 17,
|
|
305
|
+
name: "add-composite-index",
|
|
306
|
+
up: (db) => {
|
|
307
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_nodes_scope_level_importance_created ON memory_nodes(scope, level, importance DESC, created_at DESC)");
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
version: 18,
|
|
312
|
+
name: "add-playbooks",
|
|
313
|
+
up: (db) => {
|
|
314
|
+
db.run(`
|
|
315
|
+
CREATE TABLE IF NOT EXISTS playbooks (
|
|
316
|
+
id TEXT PRIMARY KEY,
|
|
317
|
+
name TEXT NOT NULL,
|
|
318
|
+
description TEXT NOT NULL,
|
|
319
|
+
scope TEXT NOT NULL DEFAULT 'project',
|
|
320
|
+
steps TEXT NOT NULL,
|
|
321
|
+
triggers TEXT NOT NULL DEFAULT '[]',
|
|
322
|
+
execution_count INT NOT NULL DEFAULT 0,
|
|
323
|
+
avg_duration_ms REAL,
|
|
324
|
+
last_executed_at INT,
|
|
325
|
+
created_at INT NOT NULL,
|
|
326
|
+
updated_at INT NOT NULL,
|
|
327
|
+
source_session_id TEXT,
|
|
328
|
+
tags TEXT NOT NULL DEFAULT '[]'
|
|
329
|
+
)
|
|
330
|
+
`);
|
|
331
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_playbooks_scope ON playbooks(scope)`);
|
|
332
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_playbooks_name ON playbooks(name)`);
|
|
333
|
+
db.run(`
|
|
334
|
+
CREATE TABLE IF NOT EXISTS playbook_steps (
|
|
335
|
+
id TEXT PRIMARY KEY,
|
|
336
|
+
playbook_id TEXT NOT NULL,
|
|
337
|
+
step_index INT NOT NULL,
|
|
338
|
+
tool_name TEXT NOT NULL,
|
|
339
|
+
description TEXT NOT NULL,
|
|
340
|
+
params TEXT NOT NULL DEFAULT '{}',
|
|
341
|
+
expected_outcome TEXT,
|
|
342
|
+
critical INT NOT NULL DEFAULT 0,
|
|
343
|
+
FOREIGN KEY (playbook_id) REFERENCES playbooks(id) ON DELETE CASCADE
|
|
344
|
+
)
|
|
345
|
+
`);
|
|
346
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_playbook_steps_pb ON playbook_steps(playbook_id)`);
|
|
347
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_playbook_steps_tool ON playbook_steps(tool_name)`);
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
version: 19,
|
|
352
|
+
name: "add-ttl",
|
|
353
|
+
up: (db) => {
|
|
354
|
+
try {
|
|
355
|
+
db.run("ALTER TABLE memory_nodes ADD COLUMN ttl_days INT");
|
|
356
|
+
}
|
|
357
|
+
catch { /* column may already exist */ }
|
|
358
|
+
try {
|
|
359
|
+
db.run("ALTER TABLE memory_nodes ADD COLUMN expires_at INT");
|
|
360
|
+
}
|
|
361
|
+
catch { /* column may already exist */ }
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
version: 20,
|
|
366
|
+
name: "add-duration-ms",
|
|
367
|
+
up: (db) => {
|
|
368
|
+
try {
|
|
369
|
+
db.run("ALTER TABLE memory_usage_log ADD COLUMN duration_ms INT DEFAULT 0");
|
|
370
|
+
}
|
|
371
|
+
catch { /* column may already exist */ }
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { MIGRATIONS } from "./definitions";
|
|
2
|
+
export { MIGRATIONS } from "./definitions";
|
|
3
|
+
export const CURRENT_VERSION = 20;
|
|
4
|
+
export function getCurrentVersion(db) {
|
|
5
|
+
const row = db.query("PRAGMA user_version").get();
|
|
6
|
+
return row?.user_version ?? 0;
|
|
7
|
+
}
|
|
8
|
+
export function runMigrations(db) {
|
|
9
|
+
for (const migration of MIGRATIONS) {
|
|
10
|
+
migration.up(db);
|
|
11
|
+
}
|
|
12
|
+
db.run(`PRAGMA user_version = ${CURRENT_VERSION}`);
|
|
13
|
+
return CURRENT_VERSION;
|
|
14
|
+
}
|
|
15
|
+
export function getConfig(db, key, defaultValue) {
|
|
16
|
+
const row = db.query("SELECT value FROM memory_config WHERE key = ?").get(key);
|
|
17
|
+
return row?.value ?? defaultValue;
|
|
18
|
+
}
|
|
19
|
+
export function setConfig(db, key, value) {
|
|
20
|
+
db.run("INSERT OR REPLACE INTO memory_config (key, value, updated_at) VALUES (?, ?, ?)", [key, value, Date.now()]);
|
|
21
|
+
}
|