omo-memory 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -64
- package/dist/cli.js +6 -89
- package/dist/mcp.js +2 -2
- package/dist/mcpGlobalTools.js +23 -0
- package/dist/memory.js +37 -31
- package/dist/memoryDb.js +43 -113
- package/dist/memoryReport.js +2 -2
- package/docs/adapter-integration.md +19 -91
- package/docs/epic-omo-memory.md +29 -51
- package/package.json +6 -5
- package/scripts/postinstall-cleanup.mjs +91 -0
- package/dist/conceptExtraction.js +0 -188
- package/dist/graphTui.js +0 -234
- package/dist/graphTuiCanvas.js +0 -104
- package/dist/mcpOntologyTools.js +0 -117
- package/dist/ontologyCore.js +0 -142
- package/dist/ontologyGraph.js +0 -136
- package/dist/ontologyGraphEdges.js +0 -86
- package/dist/ontologyQueries.js +0 -30
- package/dist/ontologySupersede.js +0 -49
- package/dist/retentionPolicy.js +0 -76
- package/dist/retentionRecompute.js +0 -175
- package/scripts/omo-memory-user-prompt.mjs +0 -107
package/dist/graphTui.js
DELETED
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { BoxRenderable, createCliRenderer, TextRenderable } from "@opentui/core";
|
|
3
|
-
import { classCode, graphContent } from "./graphTuiCanvas.js";
|
|
4
|
-
const SNAPSHOT_ENV = "OMO_MEMORY_GRAPH_TUI_SNAPSHOT";
|
|
5
|
-
const COLORS = {
|
|
6
|
-
background: "#101419",
|
|
7
|
-
border: "#516071",
|
|
8
|
-
detail: "#17212b",
|
|
9
|
-
text: "#d8dee9",
|
|
10
|
-
muted: "#94a3b8",
|
|
11
|
-
accent: "#7dd3fc",
|
|
12
|
-
};
|
|
13
|
-
export async function runGraphTui(options) {
|
|
14
|
-
const existingSnapshot = parseSnapshot(process.env[SNAPSHOT_ENV]);
|
|
15
|
-
if (process.versions["bun"] === undefined) {
|
|
16
|
-
const graphSnapshot = existingSnapshot ?? (await createGraphSnapshot(options));
|
|
17
|
-
await runWithBun(options, graphSnapshot);
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
if (existingSnapshot === null) {
|
|
21
|
-
throw new Error("graph tui OpenTUI renderer requires a graph snapshot");
|
|
22
|
-
}
|
|
23
|
-
const renderer = await createCliRenderer({
|
|
24
|
-
targetFps: 12,
|
|
25
|
-
maxFps: 12,
|
|
26
|
-
screenMode: "main-screen",
|
|
27
|
-
consoleMode: "disabled",
|
|
28
|
-
clearOnShutdown: false,
|
|
29
|
-
});
|
|
30
|
-
renderer.setTerminalTitle("OMO Ontology Graph");
|
|
31
|
-
let state = { options, selectedId: null, snapshot: existingSnapshot };
|
|
32
|
-
let graph = loadGraph(state);
|
|
33
|
-
state = { ...state, selectedId: graph.detail?.id ?? null };
|
|
34
|
-
const root = new BoxRenderable(renderer, {
|
|
35
|
-
id: "graph-root",
|
|
36
|
-
width: "100%",
|
|
37
|
-
height: "100%",
|
|
38
|
-
flexDirection: "column",
|
|
39
|
-
backgroundColor: COLORS.background,
|
|
40
|
-
padding: 1,
|
|
41
|
-
gap: 1,
|
|
42
|
-
});
|
|
43
|
-
const title = new TextRenderable(renderer, { id: "graph-title", content: "", fg: COLORS.accent, height: 1, truncate: true });
|
|
44
|
-
const body = new BoxRenderable(renderer, { id: "graph-body", flexGrow: 1, flexDirection: "row", gap: 1 });
|
|
45
|
-
const nodesPane = new BoxRenderable(renderer, {
|
|
46
|
-
id: "graph-nodes-pane",
|
|
47
|
-
width: "50%",
|
|
48
|
-
height: "100%",
|
|
49
|
-
border: true,
|
|
50
|
-
borderColor: COLORS.border,
|
|
51
|
-
title: "Nodes",
|
|
52
|
-
padding: 1,
|
|
53
|
-
backgroundColor: COLORS.background,
|
|
54
|
-
});
|
|
55
|
-
const detailPane = new BoxRenderable(renderer, {
|
|
56
|
-
id: "graph-detail-pane",
|
|
57
|
-
flexGrow: 1,
|
|
58
|
-
height: "100%",
|
|
59
|
-
border: true,
|
|
60
|
-
borderColor: COLORS.border,
|
|
61
|
-
title: "Detail Pane",
|
|
62
|
-
padding: 1,
|
|
63
|
-
backgroundColor: COLORS.detail,
|
|
64
|
-
});
|
|
65
|
-
const nodesText = new TextRenderable(renderer, { id: "graph-nodes", content: "", fg: COLORS.text, wrapMode: "word" });
|
|
66
|
-
const detailText = new TextRenderable(renderer, { id: "graph-detail", content: "", fg: COLORS.text, wrapMode: "word" });
|
|
67
|
-
const footer = new TextRenderable(renderer, { id: "graph-footer", content: "", fg: COLORS.muted, height: 1, truncate: true });
|
|
68
|
-
nodesPane.add(nodesText);
|
|
69
|
-
detailPane.add(detailText);
|
|
70
|
-
body.add(nodesPane);
|
|
71
|
-
body.add(detailPane);
|
|
72
|
-
root.add(title);
|
|
73
|
-
root.add(body);
|
|
74
|
-
root.add(footer);
|
|
75
|
-
renderer.root.add(root);
|
|
76
|
-
const render = () => {
|
|
77
|
-
graph = loadGraph(state);
|
|
78
|
-
state = { ...state, selectedId: graph.detail?.id ?? null };
|
|
79
|
-
title.content = titleText(graph, state.options.query);
|
|
80
|
-
nodesText.content = graphContent(graph);
|
|
81
|
-
detailText.content = detailContent(graph);
|
|
82
|
-
footer.content = "q quit | ArrowUp/ArrowDown/Tab select | Legend: D durable, W working, T temporary, E ephemeral";
|
|
83
|
-
renderer.requestRender();
|
|
84
|
-
};
|
|
85
|
-
await new Promise((resolve) => {
|
|
86
|
-
const quit = () => {
|
|
87
|
-
renderer.keyInput.removeAllListeners("keypress");
|
|
88
|
-
renderer.destroy();
|
|
89
|
-
resolve();
|
|
90
|
-
};
|
|
91
|
-
renderer.keyInput.on("keypress", (key) => {
|
|
92
|
-
if (key.name === "q" || (key.name === "c" && key.ctrl)) {
|
|
93
|
-
quit();
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
const nextId = nextSelectedId(graph.nodes, state.selectedId, key.name);
|
|
97
|
-
if (nextId !== state.selectedId) {
|
|
98
|
-
state = { ...state, selectedId: nextId };
|
|
99
|
-
render();
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
writeCaptureFrame(graph, state.options.query);
|
|
103
|
-
renderer.start();
|
|
104
|
-
render();
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
async function runWithBun(options, snapshot) {
|
|
108
|
-
const scriptPath = process.argv[1];
|
|
109
|
-
if (scriptPath === undefined)
|
|
110
|
-
throw new Error("graph tui requires a CLI script path");
|
|
111
|
-
const args = [scriptPath, "graph", "tui", "--db", options.dbPath];
|
|
112
|
-
if (options.query !== undefined)
|
|
113
|
-
args.push("--query", options.query);
|
|
114
|
-
await new Promise((resolve, reject) => {
|
|
115
|
-
const child = spawn("bun", args, {
|
|
116
|
-
stdio: "inherit",
|
|
117
|
-
env: { ...process.env, [SNAPSHOT_ENV]: JSON.stringify(snapshot) },
|
|
118
|
-
});
|
|
119
|
-
child.once("error", (error) => {
|
|
120
|
-
reject(new Error(`graph tui requires Bun for OpenTUI native FFI: ${error.message}`));
|
|
121
|
-
});
|
|
122
|
-
child.once("exit", (code, signal) => {
|
|
123
|
-
if (code === 0) {
|
|
124
|
-
resolve();
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
reject(new Error(`graph tui OpenTUI runtime exited with ${signal ?? code ?? "unknown status"}`));
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
async function createGraphSnapshot(options) {
|
|
132
|
-
const { projectOntologyGraph } = await import("./ontologyGraph.js");
|
|
133
|
-
const graph = projectOntologyGraph({
|
|
134
|
-
dbPath: options.dbPath,
|
|
135
|
-
...(options.query === undefined ? {} : { query: options.query }),
|
|
136
|
-
});
|
|
137
|
-
const details = graph.detail === null ? [] : [graph.detail];
|
|
138
|
-
return { graph, details };
|
|
139
|
-
}
|
|
140
|
-
function parseSnapshot(raw) {
|
|
141
|
-
if (raw === undefined)
|
|
142
|
-
return null;
|
|
143
|
-
const parsed = JSON.parse(raw);
|
|
144
|
-
if (!isGraphSnapshot(parsed))
|
|
145
|
-
throw new Error("invalid graph tui snapshot");
|
|
146
|
-
return parsed;
|
|
147
|
-
}
|
|
148
|
-
function isRecord(value) {
|
|
149
|
-
return value !== null && typeof value === "object";
|
|
150
|
-
}
|
|
151
|
-
function isGraphSnapshot(value) {
|
|
152
|
-
if (!isRecord(value))
|
|
153
|
-
return false;
|
|
154
|
-
return isRecord(value["graph"]) && Array.isArray(value["details"]);
|
|
155
|
-
}
|
|
156
|
-
function loadGraph(state) {
|
|
157
|
-
const selectedId = state.selectedId ?? state.snapshot.graph.detail?.id ?? state.snapshot.graph.nodes[0]?.id ?? null;
|
|
158
|
-
const selectedNode = selectedId === null ? undefined : state.snapshot.graph.nodes.find((node) => node.id === selectedId);
|
|
159
|
-
const detail = selectedId === null ? state.snapshot.graph.detail : (state.snapshot.details.find((item) => item.id === selectedId) ?? nodeDetail(selectedNode) ?? null);
|
|
160
|
-
return {
|
|
161
|
-
...state.snapshot.graph,
|
|
162
|
-
nodes: state.snapshot.graph.nodes.map((node) => ({ ...node, selected: node.id === selectedId })),
|
|
163
|
-
detail,
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
function nodeDetail(node) {
|
|
167
|
-
if (node === undefined)
|
|
168
|
-
return null;
|
|
169
|
-
return {
|
|
170
|
-
id: node.id,
|
|
171
|
-
label: node.label,
|
|
172
|
-
kind: node.kind,
|
|
173
|
-
description: node.description,
|
|
174
|
-
aliases: node.aliases,
|
|
175
|
-
retentionClass: node.retentionClass,
|
|
176
|
-
score: node.score,
|
|
177
|
-
scoreLabel: node.scoreLabel,
|
|
178
|
-
refCount: node.refCount,
|
|
179
|
-
projectSpread: node.projectSpread,
|
|
180
|
-
firstSeen: node.firstSeen,
|
|
181
|
-
lastSeen: node.lastSeen,
|
|
182
|
-
project: node.project,
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
function titleText(graph, query) {
|
|
186
|
-
const queryLabel = query === undefined ? "all concepts" : `query "${query}"`;
|
|
187
|
-
return `OMO Ontology Graph - ${queryLabel} - ${graph.nodes.length} nodes / ${graph.edges.length} edges`;
|
|
188
|
-
}
|
|
189
|
-
function detailContent(graph) {
|
|
190
|
-
if (graph.detail === null)
|
|
191
|
-
return graph.message ?? "No ontology graph data is available yet.";
|
|
192
|
-
const detail = graph.detail;
|
|
193
|
-
return [
|
|
194
|
-
`Label: ${detail.label}`,
|
|
195
|
-
`Kind: ${detail.kind}`,
|
|
196
|
-
`Retention: ${classCode(detail.retentionClass)} ${detail.retentionClass}`,
|
|
197
|
-
`Score: ${detail.scoreLabel}`,
|
|
198
|
-
`Refs: ${detail.refCount}`,
|
|
199
|
-
`Projects: ${detail.projectSpread}`,
|
|
200
|
-
`First seen: ${detail.firstSeen ?? "unknown"}`,
|
|
201
|
-
`Last seen: ${detail.lastSeen ?? "unknown"}`,
|
|
202
|
-
`Project: ${detail.project.repoRoot}`,
|
|
203
|
-
`Remote: ${detail.project.gitRemote ?? "none"}`,
|
|
204
|
-
"",
|
|
205
|
-
"Description:",
|
|
206
|
-
detail.description ?? "none",
|
|
207
|
-
"",
|
|
208
|
-
`Aliases: ${detail.aliases.length === 0 ? "none" : detail.aliases.join(", ")}`,
|
|
209
|
-
].join("\n");
|
|
210
|
-
}
|
|
211
|
-
function writeCaptureFrame(graph, query) {
|
|
212
|
-
process.stdout.write([
|
|
213
|
-
titleText(graph, query),
|
|
214
|
-
"",
|
|
215
|
-
"Graph",
|
|
216
|
-
graphContent(graph),
|
|
217
|
-
"",
|
|
218
|
-
"Detail",
|
|
219
|
-
detailContent(graph),
|
|
220
|
-
"",
|
|
221
|
-
"Legend: D durable, W working, T temporary, E ephemeral",
|
|
222
|
-
"",
|
|
223
|
-
].join("\n"));
|
|
224
|
-
}
|
|
225
|
-
function nextSelectedId(nodes, selectedId, keyName) {
|
|
226
|
-
if (nodes.length === 0)
|
|
227
|
-
return null;
|
|
228
|
-
const currentIndex = Math.max(0, nodes.findIndex((node) => node.id === selectedId));
|
|
229
|
-
const delta = keyName === "up" ? -1 : keyName === "down" || keyName === "tab" ? 1 : 0;
|
|
230
|
-
if (delta === 0)
|
|
231
|
-
return selectedId;
|
|
232
|
-
const nextIndex = (currentIndex + delta + nodes.length) % nodes.length;
|
|
233
|
-
return nodes[nextIndex]?.id ?? selectedId;
|
|
234
|
-
}
|
package/dist/graphTuiCanvas.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
const GRAPH_WIDTH = 66;
|
|
2
|
-
const GRAPH_HEIGHT = 24;
|
|
3
|
-
const NODE_LIMIT = 28;
|
|
4
|
-
export function graphContent(graph) {
|
|
5
|
-
if (graph.nodes.length === 0)
|
|
6
|
-
return graph.message ?? "No ontology graph data is available yet.";
|
|
7
|
-
const drawnNodes = graph.nodes.slice(0, NODE_LIMIT);
|
|
8
|
-
const positions = layoutNodes(drawnNodes);
|
|
9
|
-
const rows = Array.from({ length: GRAPH_HEIGHT }, () => Array.from({ length: GRAPH_WIDTH }, () => " "));
|
|
10
|
-
for (const edge of graph.edges) {
|
|
11
|
-
const source = positions.get(edge.sourceId);
|
|
12
|
-
const target = positions.get(edge.targetId);
|
|
13
|
-
if (source === undefined || target === undefined)
|
|
14
|
-
continue;
|
|
15
|
-
drawEdge(rows, source, target);
|
|
16
|
-
}
|
|
17
|
-
for (const node of drawnNodes) {
|
|
18
|
-
const position = positions.get(node.id);
|
|
19
|
-
if (position === undefined)
|
|
20
|
-
continue;
|
|
21
|
-
drawNode(rows, node, position);
|
|
22
|
-
}
|
|
23
|
-
const graphLines = rows.map((row) => row.join("").trimEnd());
|
|
24
|
-
const legend = drawnNodes.map((node) => nodeLine(node));
|
|
25
|
-
return [...graphLines, "", "Nodes", ...legend, "", "Relations", ...relationLines(graph, positions)].join("\n");
|
|
26
|
-
}
|
|
27
|
-
export function nodeLine(node) {
|
|
28
|
-
const marker = node.selected ? ">" : " ";
|
|
29
|
-
return `${marker} ${classCode(node.retentionClass)} ${node.label} (${node.kind}, ${node.scoreLabel}, refs ${node.refCount})`;
|
|
30
|
-
}
|
|
31
|
-
export function classCode(retentionClass) {
|
|
32
|
-
const normalized = retentionClass.toLowerCase();
|
|
33
|
-
if (normalized === "durable")
|
|
34
|
-
return "D";
|
|
35
|
-
if (normalized === "temporary")
|
|
36
|
-
return "T";
|
|
37
|
-
if (normalized === "ephemeral")
|
|
38
|
-
return "E";
|
|
39
|
-
return "W";
|
|
40
|
-
}
|
|
41
|
-
function layoutNodes(nodes) {
|
|
42
|
-
const centerX = Math.floor(GRAPH_WIDTH / 2);
|
|
43
|
-
const centerY = Math.floor(GRAPH_HEIGHT / 2);
|
|
44
|
-
const radiusX = Math.max(8, Math.floor(GRAPH_WIDTH / 2) - 8);
|
|
45
|
-
const radiusY = Math.max(4, Math.floor(GRAPH_HEIGHT / 2) - 3);
|
|
46
|
-
const positions = new Map();
|
|
47
|
-
nodes.forEach((node, index) => {
|
|
48
|
-
const angle = (2 * Math.PI * index) / Math.max(1, nodes.length);
|
|
49
|
-
const scorePull = Math.max(0.55, 1 - Math.min(90, node.score) / 220);
|
|
50
|
-
positions.set(node.id, {
|
|
51
|
-
x: clamp(Math.round(centerX + Math.cos(angle) * radiusX * scorePull), 2, GRAPH_WIDTH - 3),
|
|
52
|
-
y: clamp(Math.round(centerY + Math.sin(angle) * radiusY * scorePull), 1, GRAPH_HEIGHT - 2),
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
return positions;
|
|
56
|
-
}
|
|
57
|
-
function drawEdge(rows, source, target) {
|
|
58
|
-
const steps = Math.max(Math.abs(target.x - source.x), Math.abs(target.y - source.y), 1);
|
|
59
|
-
for (let index = 1; index < steps; index += 1) {
|
|
60
|
-
const x = Math.round(source.x + ((target.x - source.x) * index) / steps);
|
|
61
|
-
const y = Math.round(source.y + ((target.y - source.y) * index) / steps);
|
|
62
|
-
const row = rows[y];
|
|
63
|
-
const current = row?.[x];
|
|
64
|
-
if (row === undefined || current === undefined || current !== " ")
|
|
65
|
-
continue;
|
|
66
|
-
row[x] = edgeGlyph(source, target);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
function edgeGlyph(source, target) {
|
|
70
|
-
const dx = target.x - source.x;
|
|
71
|
-
const dy = target.y - source.y;
|
|
72
|
-
if (Math.abs(dx) > Math.abs(dy) * 2)
|
|
73
|
-
return "-";
|
|
74
|
-
if (Math.abs(dy) > Math.abs(dx) * 2)
|
|
75
|
-
return "|";
|
|
76
|
-
return dx * dy > 0 ? "\\" : "/";
|
|
77
|
-
}
|
|
78
|
-
function drawNode(rows, node, point) {
|
|
79
|
-
const glyph = node.selected ? "●" : classCode(node.retentionClass);
|
|
80
|
-
const label = `${glyph}${shortLabel(node.label)}`;
|
|
81
|
-
const startX = clamp(point.x - Math.floor(label.length / 2), 0, Math.max(0, GRAPH_WIDTH - label.length));
|
|
82
|
-
for (let index = 0; index < label.length; index += 1) {
|
|
83
|
-
const row = rows[point.y];
|
|
84
|
-
if (row === undefined)
|
|
85
|
-
continue;
|
|
86
|
-
row[startX + index] = label[index] ?? " ";
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
function shortLabel(label) {
|
|
90
|
-
const compact = label.replace(/\s+/g, " ").trim();
|
|
91
|
-
return compact.length <= 11 ? compact : compact.slice(0, 10);
|
|
92
|
-
}
|
|
93
|
-
function relationLines(graph, positions) {
|
|
94
|
-
const visible = graph.edges.filter((edge) => positions.has(edge.sourceId) && positions.has(edge.targetId)).slice(0, 12);
|
|
95
|
-
if (visible.length === 0)
|
|
96
|
-
return [" none in current filter"];
|
|
97
|
-
return visible.map((edge) => ` ${nodeLabel(graph, edge.sourceId)} -> ${nodeLabel(graph, edge.targetId)} [${edge.label}]`);
|
|
98
|
-
}
|
|
99
|
-
function nodeLabel(graph, nodeId) {
|
|
100
|
-
return graph.nodes.find((node) => node.id === nodeId)?.label ?? nodeId;
|
|
101
|
-
}
|
|
102
|
-
function clamp(value, min, max) {
|
|
103
|
-
return Math.min(max, Math.max(min, value));
|
|
104
|
-
}
|
package/dist/mcpOntologyTools.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { applyConceptExtraction, extractConceptCandidates } from "./conceptExtraction.js";
|
|
3
|
-
import { listGlobalMemory, migrateToGlobalMemory, scanForMemoryDbs } from "./globalMemory.js";
|
|
4
|
-
import { memoryPaths } from "./memoryReport.js";
|
|
5
|
-
import { createDurableMemory, listOntologyRows, supersedeDurableMemory, updateDurableRetention } from "./ontologyCore.js";
|
|
6
|
-
import { resolveProjectContext } from "./projectContext.js";
|
|
7
|
-
import { recomputeRetentionScores } from "./retentionRecompute.js";
|
|
8
|
-
const retentionClassSchema = z.enum(["forget", "temporary", "working", "durable", "permanent"]);
|
|
9
|
-
const nonBlankStringSchema = z.string().trim().min(1);
|
|
10
|
-
export function registerGlobalOntologyTools(server) {
|
|
11
|
-
server.registerTool("memory_global_scan", {
|
|
12
|
-
title: "Scan Global OMO Memory Sources",
|
|
13
|
-
description: "Explicitly scan a filesystem root for local OMO memory SQLite databases without importing them.",
|
|
14
|
-
inputSchema: { rootPath: nonBlankStringSchema },
|
|
15
|
-
}, async ({ rootPath }) => jsonResult(scanForMemoryDbs(rootPath)));
|
|
16
|
-
server.registerTool("memory_global_migrate", {
|
|
17
|
-
title: "Migrate OMO Memory To Global SQLite",
|
|
18
|
-
description: "Explicitly create or update a global OMO memory SQLite database from discovered local memory databases.",
|
|
19
|
-
inputSchema: { rootPath: nonBlankStringSchema, globalDbPath: nonBlankStringSchema },
|
|
20
|
-
}, async ({ rootPath, globalDbPath }) => jsonResult(migrateToGlobalMemory({ rootPath, globalDbPath })));
|
|
21
|
-
server.registerTool("memory_global_list", {
|
|
22
|
-
title: "List Global OMO Memory",
|
|
23
|
-
description: "List sources and counts from an explicit global OMO memory SQLite database.",
|
|
24
|
-
inputSchema: { globalDbPath: nonBlankStringSchema },
|
|
25
|
-
}, async ({ globalDbPath }) => jsonResult(listGlobalMemory(globalDbPath)));
|
|
26
|
-
registerOntologyTools(server);
|
|
27
|
-
}
|
|
28
|
-
function registerOntologyTools(server) {
|
|
29
|
-
server.registerTool("memory_ontology_candidates", {
|
|
30
|
-
title: "Extract OMO Ontology Candidates",
|
|
31
|
-
description: "Return deterministic ontology candidate labels from a short event summary without writing rows.",
|
|
32
|
-
inputSchema: { summary: nonBlankStringSchema, eventType: nonBlankStringSchema.optional() },
|
|
33
|
-
}, async ({ summary, eventType }) => jsonResult({ candidates: extractConceptCandidates(summary, eventType) }));
|
|
34
|
-
server.registerTool("memory_ontology_extract", {
|
|
35
|
-
title: "Write OMO Ontology Extraction",
|
|
36
|
-
description: "Explicitly extract concepts from a short event summary and write event-to-concept references.",
|
|
37
|
-
inputSchema: {
|
|
38
|
-
sourceEventId: nonBlankStringSchema,
|
|
39
|
-
summary: nonBlankStringSchema,
|
|
40
|
-
eventType: nonBlankStringSchema.optional(),
|
|
41
|
-
},
|
|
42
|
-
}, async ({ sourceEventId, summary, eventType }) => jsonResult(applyConceptExtraction(memoryPaths().dbPath, resolveProjectContext(), sourceEventId, summary, eventType)));
|
|
43
|
-
server.registerTool("memory_ontology_score", {
|
|
44
|
-
title: "Recompute OMO Ontology Scores",
|
|
45
|
-
description: "Explicitly recompute ontology retention scores for the local OMO memory SQLite database.",
|
|
46
|
-
inputSchema: { nowIso: z.string().datetime().optional() },
|
|
47
|
-
}, async ({ nowIso }) => jsonResult(recomputeRetentionScores({ dbPath: memoryPaths().dbPath, nowIso: nowIso ?? new Date().toISOString() })));
|
|
48
|
-
server.registerTool("memory_ontology_promote", {
|
|
49
|
-
title: "Promote OMO Durable Memory",
|
|
50
|
-
description: "Explicitly promote a summarized memory into durable ontology storage.",
|
|
51
|
-
inputSchema: {
|
|
52
|
-
type: nonBlankStringSchema,
|
|
53
|
-
summary: nonBlankStringSchema,
|
|
54
|
-
body: nonBlankStringSchema.optional(),
|
|
55
|
-
sourceEventId: nonBlankStringSchema.optional(),
|
|
56
|
-
sourceHandoffId: nonBlankStringSchema.optional(),
|
|
57
|
-
confidence: z.number().min(0).max(1).optional(),
|
|
58
|
-
status: nonBlankStringSchema.optional(),
|
|
59
|
-
retentionClass: retentionClassSchema.optional(),
|
|
60
|
-
},
|
|
61
|
-
}, async ({ type, summary, body, sourceEventId, sourceHandoffId, confidence, status, retentionClass }) => jsonResult(createDurableMemory(memoryPaths().dbPath, resolveProjectContext(), {
|
|
62
|
-
type,
|
|
63
|
-
summary,
|
|
64
|
-
...(body === undefined ? {} : { body }),
|
|
65
|
-
...(sourceEventId === undefined ? {} : { sourceEventId }),
|
|
66
|
-
...(sourceHandoffId === undefined ? {} : { sourceHandoffId }),
|
|
67
|
-
...(confidence === undefined ? {} : { confidence }),
|
|
68
|
-
...(status === undefined ? {} : { status }),
|
|
69
|
-
...(retentionClass === undefined ? {} : { retentionClass }),
|
|
70
|
-
})));
|
|
71
|
-
server.registerTool("memory_ontology_demote", {
|
|
72
|
-
title: "Demote OMO Durable Memory",
|
|
73
|
-
description: "Explicitly change the retention class for a durable ontology memory.",
|
|
74
|
-
inputSchema: { durableId: nonBlankStringSchema, retentionClass: retentionClassSchema.default("temporary") },
|
|
75
|
-
}, async ({ durableId, retentionClass }) => jsonResult(updateDurableRetention(memoryPaths().dbPath, resolveProjectContext(), durableId, { retentionClass })));
|
|
76
|
-
server.registerTool("memory_ontology_supersede", {
|
|
77
|
-
title: "Supersede OMO Durable Memory",
|
|
78
|
-
description: "Explicitly mark a durable memory superseded and create a successor memory.",
|
|
79
|
-
inputSchema: {
|
|
80
|
-
durableId: nonBlankStringSchema,
|
|
81
|
-
reason: nonBlankStringSchema.optional(),
|
|
82
|
-
newSummary: nonBlankStringSchema.optional(),
|
|
83
|
-
},
|
|
84
|
-
}, async ({ durableId, reason, newSummary }) => jsonResult(supersedeDurableMemory(memoryPaths().dbPath, resolveProjectContext(), durableId, {
|
|
85
|
-
...(reason === undefined ? {} : { reason }),
|
|
86
|
-
...(newSummary === undefined ? {} : { newSummary }),
|
|
87
|
-
})));
|
|
88
|
-
server.registerTool("memory_ontology_recall", {
|
|
89
|
-
title: "Recall OMO Ontology Rows",
|
|
90
|
-
description: "Explicitly recall ontology concepts and durable memories matching a query.",
|
|
91
|
-
inputSchema: { query: nonBlankStringSchema, limit: z.number().int().positive().max(100).default(10) },
|
|
92
|
-
}, async ({ query, limit }) => jsonResult(recallOntology(query, limit)));
|
|
93
|
-
}
|
|
94
|
-
function jsonResult(value) {
|
|
95
|
-
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
|
|
96
|
-
}
|
|
97
|
-
function recallOntology(query, limit) {
|
|
98
|
-
const rows = listOntologyRows(memoryPaths().dbPath, resolveProjectContext());
|
|
99
|
-
const normalizedQuery = query.toLowerCase();
|
|
100
|
-
const queryTerms = normalizedQuery.split(/[^a-z0-9_-]+/).filter((term) => term.length >= 3);
|
|
101
|
-
return {
|
|
102
|
-
concepts: rows.concepts.filter((concept) => matchesConcept(concept, normalizedQuery, queryTerms)).slice(0, limit),
|
|
103
|
-
durableMemories: rows.durableMemories.filter((memory) => matchesDurableMemory(memory, normalizedQuery, queryTerms)).slice(0, limit),
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
function matchesConcept(concept, normalizedQuery, queryTerms) {
|
|
107
|
-
return matchesText([concept.label, concept.description ?? "", concept.aliasesJson], normalizedQuery, queryTerms);
|
|
108
|
-
}
|
|
109
|
-
function matchesDurableMemory(memory, normalizedQuery, queryTerms) {
|
|
110
|
-
return matchesText([memory.type, memory.summary, memory.body ?? ""], normalizedQuery, queryTerms);
|
|
111
|
-
}
|
|
112
|
-
function matchesText(values, normalizedQuery, queryTerms) {
|
|
113
|
-
const normalizedValues = values.map((value) => value.toLowerCase());
|
|
114
|
-
if (normalizedValues.some((value) => value.includes(normalizedQuery)))
|
|
115
|
-
return true;
|
|
116
|
-
return queryTerms.length > 0 && queryTerms.every((term) => normalizedValues.some((value) => value.includes(term)));
|
|
117
|
-
}
|
package/dist/ontologyCore.js
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { upsertProject } from "./memory.js";
|
|
3
|
-
import { migrate, openMemoryDb } from "./memoryDb.js";
|
|
4
|
-
import { redactSecrets } from "./privacy.js";
|
|
5
|
-
export { listOntologyRows } from "./ontologyQueries.js";
|
|
6
|
-
export { supersedeDurableMemory } from "./ontologySupersede.js";
|
|
7
|
-
function nowIso() {
|
|
8
|
-
return new Date().toISOString();
|
|
9
|
-
}
|
|
10
|
-
function normalizeLabel(label) {
|
|
11
|
-
return label.trim().toLowerCase();
|
|
12
|
-
}
|
|
13
|
-
export function upsertConcept(dbPath, project, input) {
|
|
14
|
-
const db = openMemoryDb(dbPath);
|
|
15
|
-
try {
|
|
16
|
-
migrate(db);
|
|
17
|
-
upsertProject(db, project);
|
|
18
|
-
const label = normalizeLabel(input.label);
|
|
19
|
-
const now = nowIso();
|
|
20
|
-
const existing = db.prepare(`SELECT id FROM concepts WHERE project_id = ? AND label = ? LIMIT 1`).get(project.id, label);
|
|
21
|
-
if (existing?.id) {
|
|
22
|
-
db.prepare("UPDATE concepts SET last_seen = ?, updated_at = ? WHERE id = ? AND project_id = ?").run(now, now, existing.id, project.id);
|
|
23
|
-
const row = db
|
|
24
|
-
.prepare(`SELECT id, kind, label, description, aliases_json AS aliasesJson, payload_json AS payloadJson,
|
|
25
|
-
valid_from AS validFrom, valid_to AS validTo, created_at AS createdAt, updated_at AS updatedAt,
|
|
26
|
-
COALESCE(score, 0) AS score, COALESCE(retention_class, 'working') AS retentionClass,
|
|
27
|
-
COALESCE(manual_pin, 0) AS manualPin, COALESCE(ref_count, 0) AS refCount,
|
|
28
|
-
COALESCE(project_spread, 1) AS projectSpread, first_seen AS firstSeen, last_seen AS lastSeen
|
|
29
|
-
FROM concepts WHERE id = ?`)
|
|
30
|
-
.get(existing.id);
|
|
31
|
-
return row;
|
|
32
|
-
}
|
|
33
|
-
const id = randomUUID();
|
|
34
|
-
const score = input.score ?? 0;
|
|
35
|
-
const retentionClass = input.retentionClass ?? "working";
|
|
36
|
-
const manualPin = input.manualPin ?? 0;
|
|
37
|
-
const created = now;
|
|
38
|
-
db.prepare(`
|
|
39
|
-
INSERT INTO concepts (
|
|
40
|
-
id, project_id, kind, label, description, aliases_json, payload_json,
|
|
41
|
-
valid_from, valid_to, created_at, updated_at,
|
|
42
|
-
score, retention_class, manual_pin, ref_count, project_spread, first_seen, last_seen
|
|
43
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
44
|
-
`).run(id, project.id, input.kind, label, input.description ?? null, "[]", "{}", null, null, created, created, score, retentionClass, manualPin, 0, 1, created, created);
|
|
45
|
-
const row = db
|
|
46
|
-
.prepare(`SELECT id, kind, label, description, aliases_json AS aliasesJson, payload_json AS payloadJson,
|
|
47
|
-
valid_from AS validFrom, valid_to AS validTo, created_at AS createdAt, updated_at AS updatedAt,
|
|
48
|
-
COALESCE(score, 0) AS score, COALESCE(retention_class, 'working') AS retentionClass,
|
|
49
|
-
COALESCE(manual_pin, 0) AS manualPin, COALESCE(ref_count, 0) AS refCount,
|
|
50
|
-
COALESCE(project_spread, 1) AS projectSpread, first_seen AS firstSeen, last_seen AS lastSeen
|
|
51
|
-
FROM concepts WHERE id = ?`)
|
|
52
|
-
.get(id);
|
|
53
|
-
return row;
|
|
54
|
-
}
|
|
55
|
-
finally {
|
|
56
|
-
db.close();
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
export function createDurableMemory(dbPath, project, input) {
|
|
60
|
-
const db = openMemoryDb(dbPath);
|
|
61
|
-
try {
|
|
62
|
-
migrate(db);
|
|
63
|
-
upsertProject(db, project);
|
|
64
|
-
const id = randomUUID();
|
|
65
|
-
const created = nowIso();
|
|
66
|
-
const redactedSummary = redactSecrets(input.summary);
|
|
67
|
-
const redactedBody = input.body == null ? null : redactSecrets(input.body);
|
|
68
|
-
const status = input.status ?? "active";
|
|
69
|
-
const retentionClass = input.retentionClass ?? "durable";
|
|
70
|
-
const confidence = input.confidence ?? 0;
|
|
71
|
-
db.prepare(`
|
|
72
|
-
INSERT INTO durable_memories (
|
|
73
|
-
id, project_id, type, summary, body, source_event_id, source_handoff_id,
|
|
74
|
-
confidence, status, retention_class, valid_from, valid_to, created_at, updated_at
|
|
75
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
76
|
-
`).run(id, project.id, input.type, redactedSummary, redactedBody, input.sourceEventId ?? null, input.sourceHandoffId ?? null, confidence, status, retentionClass, null, null, created, created);
|
|
77
|
-
const row = db
|
|
78
|
-
.prepare(`SELECT id, type, summary, body, source_event_id AS sourceEventId, source_handoff_id AS sourceHandoffId,
|
|
79
|
-
confidence, status, COALESCE(retention_class, 'durable') AS retentionClass,
|
|
80
|
-
valid_from AS validFrom, valid_to AS validTo, created_at AS createdAt, updated_at AS updatedAt
|
|
81
|
-
FROM durable_memories WHERE id = ?`)
|
|
82
|
-
.get(id);
|
|
83
|
-
return row;
|
|
84
|
-
}
|
|
85
|
-
finally {
|
|
86
|
-
db.close();
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
export function recordMemoryReference(dbPath, project, input) {
|
|
90
|
-
const db = openMemoryDb(dbPath);
|
|
91
|
-
try {
|
|
92
|
-
migrate(db);
|
|
93
|
-
upsertProject(db, project);
|
|
94
|
-
const id = randomUUID();
|
|
95
|
-
const created = nowIso();
|
|
96
|
-
const refKind = input.refKind ?? "mentions";
|
|
97
|
-
const weight = input.weight ?? 1;
|
|
98
|
-
const inserted = db
|
|
99
|
-
.prepare(`
|
|
100
|
-
INSERT INTO memory_references (
|
|
101
|
-
id, project_id, source_type, source_id, target_type, target_id, ref_kind, weight, created_at
|
|
102
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
103
|
-
ON CONFLICT(project_id, source_type, source_id, target_type, target_id, ref_kind) DO NOTHING
|
|
104
|
-
`)
|
|
105
|
-
.run(id, project.id, input.sourceType, input.sourceId, input.targetType, input.targetId, refKind, weight, created);
|
|
106
|
-
if (inserted.changes > 0 && input.targetType === "concept") {
|
|
107
|
-
db.prepare("UPDATE concepts SET ref_count = COALESCE(ref_count, 0) + 1, last_seen = ?, updated_at = ? WHERE id = ? AND project_id = ?").run(created, created, input.targetId, project.id);
|
|
108
|
-
}
|
|
109
|
-
const row = db
|
|
110
|
-
.prepare(`SELECT id, source_type AS sourceType, source_id AS sourceId, target_type AS targetType, target_id AS targetId,
|
|
111
|
-
ref_kind AS refKind, weight, created_at AS createdAt
|
|
112
|
-
FROM memory_references
|
|
113
|
-
WHERE project_id = ? AND source_type = ? AND source_id = ? AND target_type = ? AND target_id = ? AND ref_kind = ?`)
|
|
114
|
-
.get(project.id, input.sourceType, input.sourceId, input.targetType, input.targetId, refKind);
|
|
115
|
-
return row;
|
|
116
|
-
}
|
|
117
|
-
finally {
|
|
118
|
-
db.close();
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
export function updateDurableRetention(dbPath, project, durableId, update) {
|
|
122
|
-
const db = openMemoryDb(dbPath);
|
|
123
|
-
try {
|
|
124
|
-
migrate(db);
|
|
125
|
-
const existing = db.prepare("SELECT retention_class FROM durable_memories WHERE id = ? AND project_id = ?").get(durableId, project.id);
|
|
126
|
-
if (!existing) {
|
|
127
|
-
throw new Error("durable memory not found for project");
|
|
128
|
-
}
|
|
129
|
-
const now = nowIso();
|
|
130
|
-
db.prepare("UPDATE durable_memories SET retention_class = COALESCE(?, retention_class), updated_at = ? WHERE id = ? AND project_id = ?").run(update.retentionClass ?? null, now, durableId, project.id);
|
|
131
|
-
const row = db
|
|
132
|
-
.prepare(`SELECT id, type, summary, body, source_event_id AS sourceEventId, source_handoff_id AS sourceHandoffId,
|
|
133
|
-
confidence, status, COALESCE(retention_class, 'durable') AS retentionClass,
|
|
134
|
-
valid_from AS validFrom, valid_to AS validTo, created_at AS createdAt, updated_at AS updatedAt
|
|
135
|
-
FROM durable_memories WHERE id = ?`)
|
|
136
|
-
.get(durableId);
|
|
137
|
-
return row;
|
|
138
|
-
}
|
|
139
|
-
finally {
|
|
140
|
-
db.close();
|
|
141
|
-
}
|
|
142
|
-
}
|