@psiclawops/hypermem 0.1.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/ARCHITECTURE.md +296 -0
- package/LICENSE +190 -0
- package/README.md +243 -0
- package/dist/background-indexer.d.ts +117 -0
- package/dist/background-indexer.d.ts.map +1 -0
- package/dist/background-indexer.js +732 -0
- package/dist/compaction-fence.d.ts +89 -0
- package/dist/compaction-fence.d.ts.map +1 -0
- package/dist/compaction-fence.js +153 -0
- package/dist/compositor.d.ts +139 -0
- package/dist/compositor.d.ts.map +1 -0
- package/dist/compositor.js +1109 -0
- package/dist/cross-agent.d.ts +57 -0
- package/dist/cross-agent.d.ts.map +1 -0
- package/dist/cross-agent.js +254 -0
- package/dist/db.d.ts +131 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +398 -0
- package/dist/desired-state-store.d.ts +100 -0
- package/dist/desired-state-store.d.ts.map +1 -0
- package/dist/desired-state-store.js +212 -0
- package/dist/doc-chunk-store.d.ts +115 -0
- package/dist/doc-chunk-store.d.ts.map +1 -0
- package/dist/doc-chunk-store.js +278 -0
- package/dist/doc-chunker.d.ts +99 -0
- package/dist/doc-chunker.d.ts.map +1 -0
- package/dist/doc-chunker.js +324 -0
- package/dist/episode-store.d.ts +48 -0
- package/dist/episode-store.d.ts.map +1 -0
- package/dist/episode-store.js +135 -0
- package/dist/fact-store.d.ts +57 -0
- package/dist/fact-store.d.ts.map +1 -0
- package/dist/fact-store.js +175 -0
- package/dist/fleet-store.d.ts +144 -0
- package/dist/fleet-store.d.ts.map +1 -0
- package/dist/fleet-store.js +276 -0
- package/dist/hybrid-retrieval.d.ts +60 -0
- package/dist/hybrid-retrieval.d.ts.map +1 -0
- package/dist/hybrid-retrieval.js +340 -0
- package/dist/index.d.ts +611 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1042 -0
- package/dist/knowledge-graph.d.ts +110 -0
- package/dist/knowledge-graph.d.ts.map +1 -0
- package/dist/knowledge-graph.js +305 -0
- package/dist/knowledge-store.d.ts +72 -0
- package/dist/knowledge-store.d.ts.map +1 -0
- package/dist/knowledge-store.js +241 -0
- package/dist/library-schema.d.ts +22 -0
- package/dist/library-schema.d.ts.map +1 -0
- package/dist/library-schema.js +717 -0
- package/dist/message-store.d.ts +76 -0
- package/dist/message-store.d.ts.map +1 -0
- package/dist/message-store.js +273 -0
- package/dist/preference-store.d.ts +54 -0
- package/dist/preference-store.d.ts.map +1 -0
- package/dist/preference-store.js +109 -0
- package/dist/preservation-gate.d.ts +82 -0
- package/dist/preservation-gate.d.ts.map +1 -0
- package/dist/preservation-gate.js +150 -0
- package/dist/provider-translator.d.ts +40 -0
- package/dist/provider-translator.d.ts.map +1 -0
- package/dist/provider-translator.js +349 -0
- package/dist/rate-limiter.d.ts +76 -0
- package/dist/rate-limiter.d.ts.map +1 -0
- package/dist/rate-limiter.js +179 -0
- package/dist/redis.d.ts +188 -0
- package/dist/redis.d.ts.map +1 -0
- package/dist/redis.js +534 -0
- package/dist/schema.d.ts +15 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +203 -0
- package/dist/secret-scanner.d.ts +51 -0
- package/dist/secret-scanner.d.ts.map +1 -0
- package/dist/secret-scanner.js +248 -0
- package/dist/seed.d.ts +108 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +177 -0
- package/dist/system-store.d.ts +73 -0
- package/dist/system-store.d.ts.map +1 -0
- package/dist/system-store.js +182 -0
- package/dist/topic-store.d.ts +45 -0
- package/dist/topic-store.d.ts.map +1 -0
- package/dist/topic-store.js +136 -0
- package/dist/types.d.ts +329 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/vector-store.d.ts +132 -0
- package/dist/vector-store.d.ts.map +1 -0
- package/dist/vector-store.js +498 -0
- package/dist/work-store.d.ts +112 -0
- package/dist/work-store.d.ts.map +1 -0
- package/dist/work-store.js +273 -0
- package/package.json +57 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HyperMem Knowledge Graph
|
|
3
|
+
*
|
|
4
|
+
* DAG traversal over knowledge_links in library.db.
|
|
5
|
+
* Links connect entities across collections:
|
|
6
|
+
* - fact ↔ fact (supersedes, contradicts, supports)
|
|
7
|
+
* - fact ↔ knowledge (references, derived_from)
|
|
8
|
+
* - knowledge ↔ knowledge (depends_on, extends)
|
|
9
|
+
* - topic ↔ fact (covers)
|
|
10
|
+
* - agent ↔ fact (authored_by)
|
|
11
|
+
*
|
|
12
|
+
* Traversal is bounded (max depth, max results) to prevent runaway queries.
|
|
13
|
+
*/
|
|
14
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
15
|
+
export type EntityType = 'fact' | 'knowledge' | 'topic' | 'episode' | 'agent' | 'preference';
|
|
16
|
+
export interface KnowledgeLink {
|
|
17
|
+
id: number;
|
|
18
|
+
fromType: EntityType;
|
|
19
|
+
fromId: number;
|
|
20
|
+
toType: EntityType;
|
|
21
|
+
toId: number;
|
|
22
|
+
linkType: string;
|
|
23
|
+
createdAt: string;
|
|
24
|
+
}
|
|
25
|
+
export interface GraphNode {
|
|
26
|
+
type: EntityType;
|
|
27
|
+
id: number;
|
|
28
|
+
depth: number;
|
|
29
|
+
linkType: string;
|
|
30
|
+
direction: 'outbound' | 'inbound';
|
|
31
|
+
}
|
|
32
|
+
export interface TraversalResult {
|
|
33
|
+
nodes: GraphNode[];
|
|
34
|
+
edges: KnowledgeLink[];
|
|
35
|
+
truncated: boolean;
|
|
36
|
+
}
|
|
37
|
+
export declare class KnowledgeGraph {
|
|
38
|
+
private readonly db;
|
|
39
|
+
constructor(db: DatabaseSync);
|
|
40
|
+
/**
|
|
41
|
+
* Create a directed link between two entities.
|
|
42
|
+
* Idempotent — unique constraint on (from_type, from_id, to_type, to_id, link_type).
|
|
43
|
+
*/
|
|
44
|
+
addLink(fromType: EntityType, fromId: number, toType: EntityType, toId: number, linkType: string): KnowledgeLink;
|
|
45
|
+
/**
|
|
46
|
+
* Remove a specific link.
|
|
47
|
+
*/
|
|
48
|
+
removeLink(fromType: EntityType, fromId: number, toType: EntityType, toId: number, linkType: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Remove all links involving an entity (both directions).
|
|
51
|
+
*/
|
|
52
|
+
removeEntityLinks(type: EntityType, id: number): number;
|
|
53
|
+
/**
|
|
54
|
+
* Get outbound links from an entity.
|
|
55
|
+
*/
|
|
56
|
+
getOutbound(type: EntityType, id: number, linkType?: string): KnowledgeLink[];
|
|
57
|
+
/**
|
|
58
|
+
* Get inbound links to an entity.
|
|
59
|
+
*/
|
|
60
|
+
getInbound(type: EntityType, id: number, linkType?: string): KnowledgeLink[];
|
|
61
|
+
/**
|
|
62
|
+
* Get all links for an entity (both directions).
|
|
63
|
+
*/
|
|
64
|
+
getLinks(type: EntityType, id: number): KnowledgeLink[];
|
|
65
|
+
/**
|
|
66
|
+
* Breadth-first traversal from a starting entity.
|
|
67
|
+
* Follows links in both directions up to maxDepth.
|
|
68
|
+
*
|
|
69
|
+
* @param startType - Entity type to start from
|
|
70
|
+
* @param startId - Entity ID to start from
|
|
71
|
+
* @param opts - Traversal options
|
|
72
|
+
* @returns Discovered nodes and edges
|
|
73
|
+
*/
|
|
74
|
+
traverse(startType: EntityType, startId: number, opts?: {
|
|
75
|
+
maxDepth?: number;
|
|
76
|
+
maxResults?: number;
|
|
77
|
+
linkTypes?: string[];
|
|
78
|
+
direction?: 'outbound' | 'inbound' | 'both';
|
|
79
|
+
targetTypes?: EntityType[];
|
|
80
|
+
}): TraversalResult;
|
|
81
|
+
/**
|
|
82
|
+
* Find the shortest path between two entities.
|
|
83
|
+
* Uses BFS — returns null if no path exists within maxDepth.
|
|
84
|
+
*/
|
|
85
|
+
findPath(fromType: EntityType, fromId: number, toType: EntityType, toId: number, maxDepth?: number): GraphNode[] | null;
|
|
86
|
+
/**
|
|
87
|
+
* Get the most connected entities (highest degree).
|
|
88
|
+
*/
|
|
89
|
+
getMostConnected(opts?: {
|
|
90
|
+
type?: EntityType;
|
|
91
|
+
limit?: number;
|
|
92
|
+
}): Array<{
|
|
93
|
+
type: EntityType;
|
|
94
|
+
id: number;
|
|
95
|
+
degree: number;
|
|
96
|
+
}>;
|
|
97
|
+
/**
|
|
98
|
+
* Count links by type.
|
|
99
|
+
*/
|
|
100
|
+
getLinkStats(): Array<{
|
|
101
|
+
linkType: string;
|
|
102
|
+
count: number;
|
|
103
|
+
}>;
|
|
104
|
+
/**
|
|
105
|
+
* Total link count.
|
|
106
|
+
*/
|
|
107
|
+
getTotalLinks(): number;
|
|
108
|
+
private rowToLink;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=knowledge-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knowledge-graph.d.ts","sourceRoot":"","sources":["../src/knowledge-graph.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HyperMem Knowledge Graph
|
|
3
|
+
*
|
|
4
|
+
* DAG traversal over knowledge_links in library.db.
|
|
5
|
+
* Links connect entities across collections:
|
|
6
|
+
* - fact ↔ fact (supersedes, contradicts, supports)
|
|
7
|
+
* - fact ↔ knowledge (references, derived_from)
|
|
8
|
+
* - knowledge ↔ knowledge (depends_on, extends)
|
|
9
|
+
* - topic ↔ fact (covers)
|
|
10
|
+
* - agent ↔ fact (authored_by)
|
|
11
|
+
*
|
|
12
|
+
* Traversal is bounded (max depth, max results) to prevent runaway queries.
|
|
13
|
+
*/
|
|
14
|
+
function nowIso() {
|
|
15
|
+
return new Date().toISOString();
|
|
16
|
+
}
|
|
17
|
+
export class KnowledgeGraph {
|
|
18
|
+
db;
|
|
19
|
+
constructor(db) {
|
|
20
|
+
this.db = db;
|
|
21
|
+
}
|
|
22
|
+
// ─── Link Management ───────────────────────────────────────
|
|
23
|
+
/**
|
|
24
|
+
* Create a directed link between two entities.
|
|
25
|
+
* Idempotent — unique constraint on (from_type, from_id, to_type, to_id, link_type).
|
|
26
|
+
*/
|
|
27
|
+
addLink(fromType, fromId, toType, toId, linkType) {
|
|
28
|
+
const now = nowIso();
|
|
29
|
+
this.db.prepare(`
|
|
30
|
+
INSERT OR IGNORE INTO knowledge_links (from_type, from_id, to_type, to_id, link_type, created_at)
|
|
31
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
32
|
+
`).run(fromType, fromId, toType, toId, linkType, now);
|
|
33
|
+
const row = this.db.prepare(`
|
|
34
|
+
SELECT * FROM knowledge_links
|
|
35
|
+
WHERE from_type = ? AND from_id = ? AND to_type = ? AND to_id = ? AND link_type = ?
|
|
36
|
+
`).get(fromType, fromId, toType, toId, linkType);
|
|
37
|
+
return this.rowToLink(row);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Remove a specific link.
|
|
41
|
+
*/
|
|
42
|
+
removeLink(fromType, fromId, toType, toId, linkType) {
|
|
43
|
+
const result = this.db.prepare(`
|
|
44
|
+
DELETE FROM knowledge_links
|
|
45
|
+
WHERE from_type = ? AND from_id = ? AND to_type = ? AND to_id = ? AND link_type = ?
|
|
46
|
+
`).run(fromType, fromId, toType, toId, linkType);
|
|
47
|
+
return result.changes > 0;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Remove all links involving an entity (both directions).
|
|
51
|
+
*/
|
|
52
|
+
removeEntityLinks(type, id) {
|
|
53
|
+
const r1 = this.db.prepare('DELETE FROM knowledge_links WHERE from_type = ? AND from_id = ?').run(type, id);
|
|
54
|
+
const r2 = this.db.prepare('DELETE FROM knowledge_links WHERE to_type = ? AND to_id = ?').run(type, id);
|
|
55
|
+
return r1.changes + r2.changes;
|
|
56
|
+
}
|
|
57
|
+
// ─── Direct Queries ────────────────────────────────────────
|
|
58
|
+
/**
|
|
59
|
+
* Get outbound links from an entity.
|
|
60
|
+
*/
|
|
61
|
+
getOutbound(type, id, linkType) {
|
|
62
|
+
if (linkType) {
|
|
63
|
+
return this.db.prepare('SELECT * FROM knowledge_links WHERE from_type = ? AND from_id = ? AND link_type = ?').all(type, id, linkType).map(r => this.rowToLink(r));
|
|
64
|
+
}
|
|
65
|
+
return this.db.prepare('SELECT * FROM knowledge_links WHERE from_type = ? AND from_id = ?').all(type, id).map(r => this.rowToLink(r));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get inbound links to an entity.
|
|
69
|
+
*/
|
|
70
|
+
getInbound(type, id, linkType) {
|
|
71
|
+
if (linkType) {
|
|
72
|
+
return this.db.prepare('SELECT * FROM knowledge_links WHERE to_type = ? AND to_id = ? AND link_type = ?').all(type, id, linkType).map(r => this.rowToLink(r));
|
|
73
|
+
}
|
|
74
|
+
return this.db.prepare('SELECT * FROM knowledge_links WHERE to_type = ? AND to_id = ?').all(type, id).map(r => this.rowToLink(r));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get all links for an entity (both directions).
|
|
78
|
+
*/
|
|
79
|
+
getLinks(type, id) {
|
|
80
|
+
return this.db.prepare(`
|
|
81
|
+
SELECT * FROM knowledge_links
|
|
82
|
+
WHERE (from_type = ? AND from_id = ?) OR (to_type = ? AND to_id = ?)
|
|
83
|
+
ORDER BY created_at DESC
|
|
84
|
+
`).all(type, id, type, id).map(r => this.rowToLink(r));
|
|
85
|
+
}
|
|
86
|
+
// ─── Traversal ─────────────────────────────────────────────
|
|
87
|
+
/**
|
|
88
|
+
* Breadth-first traversal from a starting entity.
|
|
89
|
+
* Follows links in both directions up to maxDepth.
|
|
90
|
+
*
|
|
91
|
+
* @param startType - Entity type to start from
|
|
92
|
+
* @param startId - Entity ID to start from
|
|
93
|
+
* @param opts - Traversal options
|
|
94
|
+
* @returns Discovered nodes and edges
|
|
95
|
+
*/
|
|
96
|
+
traverse(startType, startId, opts) {
|
|
97
|
+
const maxDepth = opts?.maxDepth ?? 3;
|
|
98
|
+
const maxResults = opts?.maxResults ?? 50;
|
|
99
|
+
const direction = opts?.direction ?? 'both';
|
|
100
|
+
const linkTypes = opts?.linkTypes;
|
|
101
|
+
const targetTypes = opts?.targetTypes;
|
|
102
|
+
const visited = new Set();
|
|
103
|
+
const nodes = [];
|
|
104
|
+
const edges = [];
|
|
105
|
+
let truncated = false;
|
|
106
|
+
// BFS queue: [type, id, depth]
|
|
107
|
+
const queue = [[startType, startId, 0]];
|
|
108
|
+
visited.add(`${startType}:${startId}`);
|
|
109
|
+
while (queue.length > 0 && nodes.length < maxResults) {
|
|
110
|
+
const [currentType, currentId, depth] = queue.shift();
|
|
111
|
+
if (depth >= maxDepth) {
|
|
112
|
+
if (depth > maxDepth)
|
|
113
|
+
truncated = true;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
// Get neighbors
|
|
117
|
+
const neighbors = [];
|
|
118
|
+
if (direction === 'outbound' || direction === 'both') {
|
|
119
|
+
const outbound = linkTypes
|
|
120
|
+
? this.getOutbound(currentType, currentId).filter(l => linkTypes.includes(l.linkType))
|
|
121
|
+
: this.getOutbound(currentType, currentId);
|
|
122
|
+
for (const link of outbound) {
|
|
123
|
+
const nodeType = link.toType;
|
|
124
|
+
if (targetTypes && !targetTypes.includes(nodeType))
|
|
125
|
+
continue;
|
|
126
|
+
neighbors.push({
|
|
127
|
+
node: {
|
|
128
|
+
type: nodeType,
|
|
129
|
+
id: link.toId,
|
|
130
|
+
depth: depth + 1,
|
|
131
|
+
linkType: link.linkType,
|
|
132
|
+
direction: 'outbound',
|
|
133
|
+
},
|
|
134
|
+
link,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (direction === 'inbound' || direction === 'both') {
|
|
139
|
+
const inbound = linkTypes
|
|
140
|
+
? this.getInbound(currentType, currentId).filter(l => linkTypes.includes(l.linkType))
|
|
141
|
+
: this.getInbound(currentType, currentId);
|
|
142
|
+
for (const link of inbound) {
|
|
143
|
+
const nodeType = link.fromType;
|
|
144
|
+
if (targetTypes && !targetTypes.includes(nodeType))
|
|
145
|
+
continue;
|
|
146
|
+
neighbors.push({
|
|
147
|
+
node: {
|
|
148
|
+
type: nodeType,
|
|
149
|
+
id: link.fromId,
|
|
150
|
+
depth: depth + 1,
|
|
151
|
+
linkType: link.linkType,
|
|
152
|
+
direction: 'inbound',
|
|
153
|
+
},
|
|
154
|
+
link,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const { node, link } of neighbors) {
|
|
159
|
+
const key = `${node.type}:${node.id}`;
|
|
160
|
+
if (visited.has(key))
|
|
161
|
+
continue;
|
|
162
|
+
visited.add(key);
|
|
163
|
+
if (nodes.length >= maxResults) {
|
|
164
|
+
truncated = true;
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
nodes.push(node);
|
|
168
|
+
edges.push(link);
|
|
169
|
+
queue.push([node.type, node.id, node.depth]);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (queue.length > 0)
|
|
173
|
+
truncated = true;
|
|
174
|
+
return { nodes, edges, truncated };
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Find the shortest path between two entities.
|
|
178
|
+
* Uses BFS — returns null if no path exists within maxDepth.
|
|
179
|
+
*/
|
|
180
|
+
findPath(fromType, fromId, toType, toId, maxDepth = 5) {
|
|
181
|
+
const visited = new Map();
|
|
182
|
+
const startKey = `${fromType}:${fromId}`;
|
|
183
|
+
const endKey = `${toType}:${toId}`;
|
|
184
|
+
visited.set(startKey, {
|
|
185
|
+
parent: null,
|
|
186
|
+
node: { type: fromType, id: fromId, depth: 0, linkType: 'start', direction: 'outbound' },
|
|
187
|
+
});
|
|
188
|
+
const queue = [[fromType, fromId, 0]];
|
|
189
|
+
while (queue.length > 0) {
|
|
190
|
+
const [currentType, currentId, depth] = queue.shift();
|
|
191
|
+
const currentKey = `${currentType}:${currentId}`;
|
|
192
|
+
if (currentKey === endKey) {
|
|
193
|
+
// Reconstruct path
|
|
194
|
+
const path = [];
|
|
195
|
+
let key = endKey;
|
|
196
|
+
while (key) {
|
|
197
|
+
const entry = visited.get(key);
|
|
198
|
+
if (!entry)
|
|
199
|
+
break;
|
|
200
|
+
path.unshift(entry.node);
|
|
201
|
+
key = entry.parent;
|
|
202
|
+
}
|
|
203
|
+
return path;
|
|
204
|
+
}
|
|
205
|
+
if (depth >= maxDepth)
|
|
206
|
+
continue;
|
|
207
|
+
// Expand in both directions
|
|
208
|
+
const allLinks = this.getLinks(currentType, currentId);
|
|
209
|
+
for (const link of allLinks) {
|
|
210
|
+
let nextType;
|
|
211
|
+
let nextId;
|
|
212
|
+
let dir;
|
|
213
|
+
if (link.fromType === currentType && link.fromId === currentId) {
|
|
214
|
+
nextType = link.toType;
|
|
215
|
+
nextId = link.toId;
|
|
216
|
+
dir = 'outbound';
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
nextType = link.fromType;
|
|
220
|
+
nextId = link.fromId;
|
|
221
|
+
dir = 'inbound';
|
|
222
|
+
}
|
|
223
|
+
const nextKey = `${nextType}:${nextId}`;
|
|
224
|
+
if (visited.has(nextKey))
|
|
225
|
+
continue;
|
|
226
|
+
const node = {
|
|
227
|
+
type: nextType,
|
|
228
|
+
id: nextId,
|
|
229
|
+
depth: depth + 1,
|
|
230
|
+
linkType: link.linkType,
|
|
231
|
+
direction: dir,
|
|
232
|
+
};
|
|
233
|
+
visited.set(nextKey, { parent: currentKey, node });
|
|
234
|
+
queue.push([nextType, nextId, depth + 1]);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return null; // No path found
|
|
238
|
+
}
|
|
239
|
+
// ─── Analytics ─────────────────────────────────────────────
|
|
240
|
+
/**
|
|
241
|
+
* Get the most connected entities (highest degree).
|
|
242
|
+
*/
|
|
243
|
+
getMostConnected(opts) {
|
|
244
|
+
const limit = opts?.limit ?? 10;
|
|
245
|
+
const query = opts?.type
|
|
246
|
+
? `
|
|
247
|
+
SELECT type, id, COUNT(*) as degree FROM (
|
|
248
|
+
SELECT from_type as type, from_id as id FROM knowledge_links WHERE from_type = ?
|
|
249
|
+
UNION ALL
|
|
250
|
+
SELECT to_type as type, to_id as id FROM knowledge_links WHERE to_type = ?
|
|
251
|
+
)
|
|
252
|
+
GROUP BY type, id
|
|
253
|
+
ORDER BY degree DESC
|
|
254
|
+
LIMIT ?
|
|
255
|
+
`
|
|
256
|
+
: `
|
|
257
|
+
SELECT type, id, COUNT(*) as degree FROM (
|
|
258
|
+
SELECT from_type as type, from_id as id FROM knowledge_links
|
|
259
|
+
UNION ALL
|
|
260
|
+
SELECT to_type as type, to_id as id FROM knowledge_links
|
|
261
|
+
)
|
|
262
|
+
GROUP BY type, id
|
|
263
|
+
ORDER BY degree DESC
|
|
264
|
+
LIMIT ?
|
|
265
|
+
`;
|
|
266
|
+
const rows = opts?.type
|
|
267
|
+
? this.db.prepare(query).all(opts.type, opts.type, limit)
|
|
268
|
+
: this.db.prepare(query).all(limit);
|
|
269
|
+
return rows.map(r => ({
|
|
270
|
+
type: r.type,
|
|
271
|
+
id: r.id,
|
|
272
|
+
degree: r.degree,
|
|
273
|
+
}));
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Count links by type.
|
|
277
|
+
*/
|
|
278
|
+
getLinkStats() {
|
|
279
|
+
const rows = this.db.prepare('SELECT link_type, COUNT(*) as count FROM knowledge_links GROUP BY link_type ORDER BY count DESC').all();
|
|
280
|
+
return rows.map(r => ({
|
|
281
|
+
linkType: r.link_type,
|
|
282
|
+
count: r.count,
|
|
283
|
+
}));
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Total link count.
|
|
287
|
+
*/
|
|
288
|
+
getTotalLinks() {
|
|
289
|
+
const row = this.db.prepare('SELECT COUNT(*) as count FROM knowledge_links').get();
|
|
290
|
+
return row.count;
|
|
291
|
+
}
|
|
292
|
+
// ─── Helpers ───────────────────────────────────────────────
|
|
293
|
+
rowToLink(row) {
|
|
294
|
+
return {
|
|
295
|
+
id: row.id,
|
|
296
|
+
fromType: row.from_type,
|
|
297
|
+
fromId: row.from_id,
|
|
298
|
+
toType: row.to_type,
|
|
299
|
+
toId: row.to_id,
|
|
300
|
+
linkType: row.link_type,
|
|
301
|
+
createdAt: row.created_at,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
//# sourceMappingURL=knowledge-graph.js.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HyperMem Knowledge Store
|
|
3
|
+
*
|
|
4
|
+
* Long-term structured knowledge — replaces MEMORY.md.
|
|
5
|
+
* Lives in the central library DB.
|
|
6
|
+
* Knowledge entries are keyed (domain + key), versioned via superseded_by,
|
|
7
|
+
* and linked to each other via knowledge_links.
|
|
8
|
+
*/
|
|
9
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
10
|
+
import type { Knowledge } from './types.js';
|
|
11
|
+
export type LinkType = 'supports' | 'contradicts' | 'depends_on' | 'supersedes' | 'related';
|
|
12
|
+
export declare class KnowledgeStore {
|
|
13
|
+
private readonly db;
|
|
14
|
+
constructor(db: DatabaseSync);
|
|
15
|
+
/**
|
|
16
|
+
* Upsert a knowledge entry.
|
|
17
|
+
*
|
|
18
|
+
* Versioning semantics:
|
|
19
|
+
* - If no active entry exists: insert as version 1
|
|
20
|
+
* - If same content: refresh confidence + timestamp only (no new version)
|
|
21
|
+
* - If different content: insert as new version (max_version + 1), mark
|
|
22
|
+
* previous active row as superseded_by = new_id
|
|
23
|
+
*
|
|
24
|
+
* This guarantees version history is real rows, not in-place overwrites.
|
|
25
|
+
* The unique constraint is (agent_id, domain, key, version) so each
|
|
26
|
+
* version is a distinct row.
|
|
27
|
+
*/
|
|
28
|
+
upsert(agentId: string, domain: string, key: string, content: string, opts?: {
|
|
29
|
+
confidence?: number;
|
|
30
|
+
visibility?: string;
|
|
31
|
+
sourceType?: string;
|
|
32
|
+
sourceRef?: string;
|
|
33
|
+
expiresAt?: string;
|
|
34
|
+
}): Knowledge;
|
|
35
|
+
/**
|
|
36
|
+
* Get current (non-superseded) knowledge for an agent.
|
|
37
|
+
*/
|
|
38
|
+
getActive(agentId: string, opts?: {
|
|
39
|
+
domain?: string;
|
|
40
|
+
limit?: number;
|
|
41
|
+
}): Knowledge[];
|
|
42
|
+
/**
|
|
43
|
+
* Get a specific knowledge entry by domain + key.
|
|
44
|
+
*/
|
|
45
|
+
get(agentId: string, domain: string, key: string): Knowledge | null;
|
|
46
|
+
/**
|
|
47
|
+
* Get the version history of a knowledge entry.
|
|
48
|
+
*/
|
|
49
|
+
getHistory(agentId: string, domain: string, key: string): Knowledge[];
|
|
50
|
+
/**
|
|
51
|
+
* Search knowledge by content.
|
|
52
|
+
*/
|
|
53
|
+
search(agentId: string, query: string, limit?: number): Knowledge[];
|
|
54
|
+
/**
|
|
55
|
+
* List all domains for an agent.
|
|
56
|
+
*/
|
|
57
|
+
getDomains(agentId: string): string[];
|
|
58
|
+
/**
|
|
59
|
+
* Add a link between knowledge entries.
|
|
60
|
+
*/
|
|
61
|
+
addLink(fromId: number, toId: number, linkType: LinkType): void;
|
|
62
|
+
/**
|
|
63
|
+
* Get knowledge count.
|
|
64
|
+
*/
|
|
65
|
+
getCount(agentId: string): number;
|
|
66
|
+
/**
|
|
67
|
+
* Import from MEMORY.md content.
|
|
68
|
+
* Parses markdown sections into domain/key/content entries.
|
|
69
|
+
*/
|
|
70
|
+
importFromMarkdown(agentId: string, markdown: string, sourcePath: string): number;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=knowledge-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knowledge-store.d.ts","sourceRoot":"","sources":["../src/knowledge-store.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAuB5C,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,aAAa,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAC;AAE5F,qBAAa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,YAAY;IAE7C;;;;;;;;;;;;OAYG;IACH,MAAM,CACJ,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;QACL,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GACA,SAAS;IAsEZ;;OAEG;IACH,SAAS,CACP,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;QACL,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GACA,SAAS,EAAE;IAyBd;;OAEG;IACH,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAUnE;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE;IAUrE;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,SAAS,EAAE;IAavE;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE;IAUrC;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAO/D;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAOjC;;;OAGG;IACH,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;CAwDlF"}
|