@graphmemory/server 1.2.0 → 1.3.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 +84 -12
- package/README.md +7 -1
- package/dist/api/index.js +147 -50
- package/dist/api/rest/index.js +35 -15
- package/dist/api/rest/tools.js +8 -1
- package/dist/api/tools/code/search-code.js +12 -9
- package/dist/api/tools/code/search-files.js +1 -1
- package/dist/api/tools/docs/cross-references.js +3 -2
- package/dist/api/tools/docs/explain-symbol.js +2 -1
- package/dist/api/tools/docs/find-examples.js +2 -1
- package/dist/api/tools/docs/search-files.js +1 -1
- package/dist/api/tools/docs/search-snippets.js +1 -1
- package/dist/api/tools/docs/search.js +5 -4
- package/dist/api/tools/file-index/search-all-files.js +1 -1
- package/dist/api/tools/knowledge/add-attachment.js +14 -3
- package/dist/api/tools/knowledge/remove-attachment.js +5 -1
- package/dist/api/tools/knowledge/search-notes.js +5 -4
- package/dist/api/tools/skills/add-attachment.js +14 -3
- package/dist/api/tools/skills/recall-skills.js +1 -1
- package/dist/api/tools/skills/remove-attachment.js +5 -1
- package/dist/api/tools/skills/search-skills.js +6 -5
- package/dist/api/tools/tasks/add-attachment.js +14 -3
- package/dist/api/tools/tasks/remove-attachment.js +5 -1
- package/dist/api/tools/tasks/search-tasks.js +5 -4
- package/dist/cli/index.js +61 -51
- package/dist/cli/indexer.js +60 -28
- package/dist/graphs/code.js +70 -7
- package/dist/graphs/docs.js +15 -2
- package/dist/graphs/file-index.js +17 -3
- package/dist/graphs/file-lang.js +1 -1
- package/dist/graphs/knowledge.js +20 -3
- package/dist/graphs/skill.js +23 -4
- package/dist/graphs/task.js +23 -4
- package/dist/lib/embedding-codec.js +65 -0
- package/dist/lib/jwt.js +4 -4
- package/dist/lib/multi-config.js +6 -1
- package/dist/lib/parsers/code.js +158 -31
- package/dist/lib/parsers/codeblock.js +11 -6
- package/dist/lib/parsers/docs.js +59 -31
- package/dist/lib/parsers/languages/registry.js +2 -2
- package/dist/lib/parsers/languages/typescript.js +195 -44
- package/dist/lib/project-manager.js +14 -10
- package/dist/lib/search/bm25.js +18 -1
- package/dist/lib/search/code.js +12 -3
- package/dist/ui/assets/NoteForm-aZX9f6-3.js +1 -0
- package/dist/ui/assets/SkillForm-KYa3o92l.js +1 -0
- package/dist/ui/assets/TaskForm-Bl5nkybO.js +1 -0
- package/dist/ui/assets/_articleId_-DjbCByxM.js +1 -0
- package/dist/ui/assets/_docId_-hdCDjclV.js +1 -0
- package/dist/ui/assets/_filePath_-CpG836v4.js +1 -0
- package/dist/ui/assets/_noteId_-C1enaQd1.js +1 -0
- package/dist/ui/assets/_skillId_-hPoCet7J.js +1 -0
- package/dist/ui/assets/_taskId_-DSB3dLVz.js +1 -0
- package/dist/ui/assets/_toolName_-3SmCfxZy.js +2 -0
- package/dist/ui/assets/api-BMnBjMMf.js +1 -0
- package/dist/ui/assets/api-BlFF6gX-.js +1 -0
- package/dist/ui/assets/api-CrGJOcaN.js +1 -0
- package/dist/ui/assets/api-DuX-0a_X.js +1 -0
- package/dist/ui/assets/attachments-CEQ-2nMo.js +1 -0
- package/dist/ui/assets/client-Bq88u7gN.js +1 -0
- package/dist/ui/assets/docs-CrXsRcOG.js +1 -0
- package/dist/ui/assets/edit-BYiy1FZy.js +1 -0
- package/dist/ui/assets/edit-TUIIpUMF.js +1 -0
- package/dist/ui/assets/edit-hc-ZWz3y.js +1 -0
- package/dist/ui/assets/esm-BWiKNcBW.js +1 -0
- package/dist/ui/assets/files-0bPg6NH9.js +1 -0
- package/dist/ui/assets/graph-DXGud_wF.js +1 -0
- package/dist/ui/assets/help-CEMQqZUR.js +891 -0
- package/dist/ui/assets/help-DJ52_fxN.js +1 -0
- package/dist/ui/assets/index-BCZDAYZi.js +2 -0
- package/dist/ui/assets/index-D6zSNtzo.css +1 -0
- package/dist/ui/assets/knowledge-DeygeGGH.js +1 -0
- package/dist/ui/assets/new-CpD7hOBA.js +1 -0
- package/dist/ui/assets/new-DHTg3Dqq.js +1 -0
- package/dist/ui/assets/new-s8c0M75X.js +1 -0
- package/dist/ui/assets/prompts-BgOmdxgM.js +295 -0
- package/dist/ui/assets/rolldown-runtime-Dw2cE7zH.js +1 -0
- package/dist/ui/assets/search-EpJhdP2a.js +1 -0
- package/dist/ui/assets/skill-y9pizyqE.js +1 -0
- package/dist/ui/assets/skills-Cga9iUZN.js +1 -0
- package/dist/ui/assets/tasks-CobouTKV.js +1 -0
- package/dist/ui/assets/tools-JxKH5BDF.js +1 -0
- package/dist/ui/assets/vendor-graph-BWpSgpMe.js +321 -0
- package/dist/ui/assets/vendor-markdown-CT8ZVEPu.js +50 -0
- package/dist/ui/assets/vendor-md-editor-DmWafJvr.js +44 -0
- package/dist/ui/assets/{index-kKd4mVrh.css → vendor-md-editor-HrwGbQou.css} +1 -1
- package/dist/ui/assets/vendor-mui-BPj7d3Sw.js +139 -0
- package/dist/ui/assets/vendor-mui-icons-B196sG3f.js +1 -0
- package/dist/ui/assets/vendor-react-CHUjhoxh.js +11 -0
- package/dist/ui/index.html +11 -3
- package/package.json +2 -2
- package/dist/ui/assets/index-0hRezICt.js +0 -1702
package/dist/graphs/code.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.CodeGraphManager = exports.createCodeGraph = void 0;
|
|
7
7
|
exports.updateCodeFile = updateCodeFile;
|
|
8
8
|
exports.resolvePendingImports = resolvePendingImports;
|
|
9
|
+
exports.resolvePendingEdges = resolvePendingEdges;
|
|
9
10
|
exports.removeCodeFile = removeCodeFile;
|
|
10
11
|
exports.getFileSymbols = getFileSymbols;
|
|
11
12
|
exports.getCodeFileMtime = getCodeFileMtime;
|
|
@@ -20,6 +21,7 @@ const manager_types_1 = require("../graphs/manager-types");
|
|
|
20
21
|
const code_1 = require("../lib/search/code");
|
|
21
22
|
const files_1 = require("../lib/search/files");
|
|
22
23
|
const bm25_1 = require("../lib/search/bm25");
|
|
24
|
+
const embedding_codec_1 = require("../lib/embedding-codec");
|
|
23
25
|
// ---------------------------------------------------------------------------
|
|
24
26
|
// CRUD
|
|
25
27
|
// ---------------------------------------------------------------------------
|
|
@@ -30,19 +32,31 @@ function updateCodeFile(graph, parsed) {
|
|
|
30
32
|
graph.addNode(id, attrs);
|
|
31
33
|
}
|
|
32
34
|
const pendingImports = [];
|
|
35
|
+
const pendingEdges = [];
|
|
33
36
|
for (const { from, to, attrs } of parsed.edges) {
|
|
34
37
|
if (!graph.hasNode(to)) {
|
|
35
|
-
if (attrs.kind === 'imports')
|
|
38
|
+
if (attrs.kind === 'imports') {
|
|
36
39
|
pendingImports.push(to);
|
|
40
|
+
}
|
|
41
|
+
else if (attrs.kind === 'extends' || attrs.kind === 'implements') {
|
|
42
|
+
// Target class/interface may be in another file — defer resolution
|
|
43
|
+
const toName = to.split('::').pop();
|
|
44
|
+
pendingEdges.push({ from, toName, kind: attrs.kind });
|
|
45
|
+
}
|
|
37
46
|
continue;
|
|
38
47
|
}
|
|
39
48
|
if (graph.hasNode(from) && !graph.hasEdge(from, to)) {
|
|
40
49
|
graph.addEdgeWithKey(`${from}→${to}`, from, to, attrs);
|
|
41
50
|
}
|
|
42
51
|
}
|
|
43
|
-
// Store pending
|
|
44
|
-
if (
|
|
45
|
-
|
|
52
|
+
// Store pending data on the file node for post-drain resolution
|
|
53
|
+
if (graph.hasNode(parsed.fileId)) {
|
|
54
|
+
if (pendingImports.length > 0) {
|
|
55
|
+
graph.setNodeAttribute(parsed.fileId, 'pendingImports', pendingImports);
|
|
56
|
+
}
|
|
57
|
+
if (pendingEdges.length > 0) {
|
|
58
|
+
graph.setNodeAttribute(parsed.fileId, 'pendingEdges', pendingEdges);
|
|
59
|
+
}
|
|
46
60
|
}
|
|
47
61
|
}
|
|
48
62
|
/**
|
|
@@ -71,6 +85,43 @@ function resolvePendingImports(graph) {
|
|
|
71
85
|
});
|
|
72
86
|
return created;
|
|
73
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Resolve pending extends/implements edges after all files have been indexed.
|
|
90
|
+
* Searches the entire graph for nodes matching toName (by symbol name).
|
|
91
|
+
*/
|
|
92
|
+
function resolvePendingEdges(graph) {
|
|
93
|
+
// Build name → nodeId index for fast lookup
|
|
94
|
+
const nameIndex = new Map();
|
|
95
|
+
graph.forEachNode((id, attrs) => {
|
|
96
|
+
if (attrs.kind === 'class' || attrs.kind === 'interface') {
|
|
97
|
+
const list = nameIndex.get(attrs.name) ?? [];
|
|
98
|
+
list.push(id);
|
|
99
|
+
nameIndex.set(attrs.name, list);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
let created = 0;
|
|
103
|
+
graph.forEachNode((id, attrs) => {
|
|
104
|
+
if (!attrs.pendingEdges || attrs.pendingEdges.length === 0)
|
|
105
|
+
return;
|
|
106
|
+
const remaining = [];
|
|
107
|
+
for (const edge of attrs.pendingEdges) {
|
|
108
|
+
const candidates = nameIndex.get(edge.toName);
|
|
109
|
+
if (candidates && candidates.length > 0 && graph.hasNode(edge.from)) {
|
|
110
|
+
// Use first match (ambiguity is rare in practice)
|
|
111
|
+
const toId = candidates[0];
|
|
112
|
+
if (toId !== edge.from && !graph.hasEdge(edge.from, toId)) {
|
|
113
|
+
graph.addEdgeWithKey(`${edge.from}→${toId}`, edge.from, toId, { kind: edge.kind });
|
|
114
|
+
created++;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
remaining.push(edge);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
graph.setNodeAttribute(id, 'pendingEdges', remaining.length > 0 ? remaining : undefined);
|
|
122
|
+
});
|
|
123
|
+
return created;
|
|
124
|
+
}
|
|
74
125
|
/** Remove all nodes (and their incident edges) belonging to a file. */
|
|
75
126
|
function removeCodeFile(graph, fileId) {
|
|
76
127
|
const toRemove = graph.filterNodes((_, attrs) => attrs.fileId === fileId);
|
|
@@ -112,8 +163,19 @@ function saveCodeGraph(graph, graphMemory, embeddingFingerprint) {
|
|
|
112
163
|
fs_1.default.mkdirSync(graphMemory, { recursive: true });
|
|
113
164
|
const file = path_1.default.join(graphMemory, 'code.json');
|
|
114
165
|
const tmp = file + '.tmp';
|
|
115
|
-
|
|
116
|
-
|
|
166
|
+
try {
|
|
167
|
+
const exported = graph.export();
|
|
168
|
+
(0, embedding_codec_1.compressEmbeddings)(exported);
|
|
169
|
+
fs_1.default.writeFileSync(tmp, JSON.stringify({ embeddingModel: embeddingFingerprint, graph: exported }));
|
|
170
|
+
fs_1.default.renameSync(tmp, file);
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
try {
|
|
174
|
+
fs_1.default.unlinkSync(tmp);
|
|
175
|
+
}
|
|
176
|
+
catch { /* ignore cleanup error */ }
|
|
177
|
+
throw err;
|
|
178
|
+
}
|
|
117
179
|
}
|
|
118
180
|
function loadCodeGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
119
181
|
const graph = (0, code_types_1.createCodeGraph)();
|
|
@@ -129,6 +191,7 @@ function loadCodeGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
|
129
191
|
process.stderr.write(`[code-graph] Embedding config changed, re-indexing code graph\n`);
|
|
130
192
|
return graph;
|
|
131
193
|
}
|
|
194
|
+
(0, embedding_codec_1.decompressEmbeddings)(data.graph);
|
|
132
195
|
graph.import(data.graph);
|
|
133
196
|
process.stderr.write(`[code-graph] Loaded ${graph.order} nodes, ${graph.size} edges\n`);
|
|
134
197
|
}
|
|
@@ -144,7 +207,7 @@ class CodeGraphManager {
|
|
|
144
207
|
_graph;
|
|
145
208
|
embedFns;
|
|
146
209
|
ext;
|
|
147
|
-
_bm25Index = new bm25_1.BM25Index((attrs) => `${attrs.name} ${attrs.signature} ${attrs.docComment}`);
|
|
210
|
+
_bm25Index = new bm25_1.BM25Index((attrs) => `${attrs.name} ${attrs.signature} ${attrs.docComment} ${attrs.body}`);
|
|
148
211
|
constructor(_graph, embedFns, ext = {}) {
|
|
149
212
|
this._graph = _graph;
|
|
150
213
|
this.embedFns = embedFns;
|
package/dist/graphs/docs.js
CHANGED
|
@@ -20,6 +20,7 @@ const manager_types_1 = require("../graphs/manager-types");
|
|
|
20
20
|
const docs_1 = require("../lib/search/docs");
|
|
21
21
|
const files_1 = require("../lib/search/files");
|
|
22
22
|
const bm25_1 = require("../lib/search/bm25");
|
|
23
|
+
const embedding_codec_1 = require("../lib/embedding-codec");
|
|
23
24
|
function createGraph() {
|
|
24
25
|
return new graphology_1.DirectedGraph({ multi: false, allowSelfLoops: false });
|
|
25
26
|
}
|
|
@@ -133,8 +134,19 @@ function saveGraph(graph, graphMemory, embeddingFingerprint) {
|
|
|
133
134
|
fs_1.default.mkdirSync(graphMemory, { recursive: true });
|
|
134
135
|
const file = path_1.default.join(graphMemory, 'docs.json');
|
|
135
136
|
const tmp = file + '.tmp';
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
try {
|
|
138
|
+
const exported = graph.export();
|
|
139
|
+
(0, embedding_codec_1.compressEmbeddings)(exported);
|
|
140
|
+
fs_1.default.writeFileSync(tmp, JSON.stringify({ embeddingModel: embeddingFingerprint, graph: exported }));
|
|
141
|
+
fs_1.default.renameSync(tmp, file);
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
try {
|
|
145
|
+
fs_1.default.unlinkSync(tmp);
|
|
146
|
+
}
|
|
147
|
+
catch { /* ignore cleanup error */ }
|
|
148
|
+
throw err;
|
|
149
|
+
}
|
|
138
150
|
}
|
|
139
151
|
function loadGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
140
152
|
const graph = createGraph();
|
|
@@ -150,6 +162,7 @@ function loadGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
|
150
162
|
process.stderr.write(`[graph] Embedding config changed, re-indexing docs graph\n`);
|
|
151
163
|
return graph;
|
|
152
164
|
}
|
|
165
|
+
(0, embedding_codec_1.decompressEmbeddings)(data.graph);
|
|
153
166
|
graph.import(data.graph);
|
|
154
167
|
process.stderr.write(`[graph] Loaded ${graph.order} nodes, ${graph.size} edges\n`);
|
|
155
168
|
}
|
|
@@ -19,6 +19,7 @@ const file_index_types_1 = require("../graphs/file-index-types");
|
|
|
19
19
|
const file_lang_1 = require("../graphs/file-lang");
|
|
20
20
|
const manager_types_1 = require("../graphs/manager-types");
|
|
21
21
|
const file_index_1 = require("../lib/search/file-index");
|
|
22
|
+
const embedding_codec_1 = require("../lib/embedding-codec");
|
|
22
23
|
// ---------------------------------------------------------------------------
|
|
23
24
|
// CRUD
|
|
24
25
|
// ---------------------------------------------------------------------------
|
|
@@ -157,7 +158,8 @@ function listAllFiles(graph, options = {}) {
|
|
|
157
158
|
return [];
|
|
158
159
|
graph.forEachOutNeighbor(dirId, (childId) => {
|
|
159
160
|
const attrs = graph.getNodeAttributes(childId);
|
|
160
|
-
|
|
161
|
+
const edgeKey = graph.edge(dirId, childId);
|
|
162
|
+
if (!edgeKey || graph.getEdgeAttribute(edgeKey, 'kind') !== 'contains')
|
|
161
163
|
return;
|
|
162
164
|
if (extension && attrs.extension !== extension)
|
|
163
165
|
return;
|
|
@@ -240,8 +242,19 @@ function saveFileIndexGraph(graph, graphMemory, embeddingFingerprint) {
|
|
|
240
242
|
fs_1.default.mkdirSync(graphMemory, { recursive: true });
|
|
241
243
|
const file = path_1.default.join(graphMemory, 'file-index.json');
|
|
242
244
|
const tmp = file + '.tmp';
|
|
243
|
-
|
|
244
|
-
|
|
245
|
+
try {
|
|
246
|
+
const exported = graph.export();
|
|
247
|
+
(0, embedding_codec_1.compressEmbeddings)(exported);
|
|
248
|
+
fs_1.default.writeFileSync(tmp, JSON.stringify({ embeddingModel: embeddingFingerprint, graph: exported }));
|
|
249
|
+
fs_1.default.renameSync(tmp, file);
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
try {
|
|
253
|
+
fs_1.default.unlinkSync(tmp);
|
|
254
|
+
}
|
|
255
|
+
catch { /* ignore cleanup error */ }
|
|
256
|
+
throw err;
|
|
257
|
+
}
|
|
245
258
|
}
|
|
246
259
|
function loadFileIndexGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
247
260
|
const graph = (0, file_index_types_1.createFileIndexGraph)();
|
|
@@ -257,6 +270,7 @@ function loadFileIndexGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
|
257
270
|
process.stderr.write(`[file-index] Embedding config changed, re-indexing file index\n`);
|
|
258
271
|
return graph;
|
|
259
272
|
}
|
|
273
|
+
(0, embedding_codec_1.decompressEmbeddings)(data.graph);
|
|
260
274
|
graph.import(data.graph);
|
|
261
275
|
process.stderr.write(`[file-index] Loaded ${graph.order} nodes, ${graph.size} edges\n`);
|
|
262
276
|
}
|
package/dist/graphs/file-lang.js
CHANGED
package/dist/graphs/knowledge.js
CHANGED
|
@@ -28,6 +28,7 @@ const manager_types_1 = require("../graphs/manager-types");
|
|
|
28
28
|
const knowledge_1 = require("../lib/search/knowledge");
|
|
29
29
|
const bm25_1 = require("../lib/search/bm25");
|
|
30
30
|
const file_mirror_1 = require("../lib/file-mirror");
|
|
31
|
+
const embedding_codec_1 = require("../lib/embedding-codec");
|
|
31
32
|
const attachment_types_1 = require("../graphs/attachment-types");
|
|
32
33
|
const file_import_1 = require("../lib/file-import");
|
|
33
34
|
// ---------------------------------------------------------------------------
|
|
@@ -329,8 +330,19 @@ function saveKnowledgeGraph(graph, graphMemory, embeddingFingerprint) {
|
|
|
329
330
|
fs_1.default.mkdirSync(graphMemory, { recursive: true });
|
|
330
331
|
const file = path_1.default.join(graphMemory, 'knowledge.json');
|
|
331
332
|
const tmp = file + '.tmp';
|
|
332
|
-
|
|
333
|
-
|
|
333
|
+
try {
|
|
334
|
+
const exported = graph.export();
|
|
335
|
+
(0, embedding_codec_1.compressEmbeddings)(exported);
|
|
336
|
+
fs_1.default.writeFileSync(tmp, JSON.stringify({ embeddingModel: embeddingFingerprint, graph: exported }));
|
|
337
|
+
fs_1.default.renameSync(tmp, file);
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
try {
|
|
341
|
+
fs_1.default.unlinkSync(tmp);
|
|
342
|
+
}
|
|
343
|
+
catch { /* ignore cleanup error */ }
|
|
344
|
+
throw err;
|
|
345
|
+
}
|
|
334
346
|
}
|
|
335
347
|
function loadKnowledgeGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
336
348
|
const graph = (0, knowledge_types_1.createKnowledgeGraph)();
|
|
@@ -346,6 +358,7 @@ function loadKnowledgeGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
|
346
358
|
process.stderr.write(`[knowledge-graph] Embedding config changed, re-indexing knowledge graph\n`);
|
|
347
359
|
return graph;
|
|
348
360
|
}
|
|
361
|
+
(0, embedding_codec_1.decompressEmbeddings)(data.graph);
|
|
349
362
|
graph.import(data.graph);
|
|
350
363
|
process.stderr.write(`[knowledge-graph] Loaded ${graph.order} nodes, ${graph.size} edges\n`);
|
|
351
364
|
}
|
|
@@ -552,7 +565,9 @@ class KnowledgeGraphManager {
|
|
|
552
565
|
try {
|
|
553
566
|
const actualToId = targetGraph ? proxyId(targetGraph, toId, pid) : toId;
|
|
554
567
|
if (this._graph.hasEdge(fromId, actualToId)) {
|
|
555
|
-
|
|
568
|
+
const ek = this._graph.edge(fromId, actualToId);
|
|
569
|
+
if (ek)
|
|
570
|
+
kind = this._graph.getEdgeAttribute(ek, 'kind') ?? '';
|
|
556
571
|
}
|
|
557
572
|
}
|
|
558
573
|
catch { /* ignore */ }
|
|
@@ -628,6 +643,8 @@ class KnowledgeGraphManager {
|
|
|
628
643
|
return;
|
|
629
644
|
const attachments = (0, attachment_types_1.scanAttachments)(path_1.default.join(dir, noteId));
|
|
630
645
|
this._graph.setNodeAttribute(noteId, 'attachments', attachments);
|
|
646
|
+
this._graph.setNodeAttribute(noteId, 'updatedAt', Date.now());
|
|
647
|
+
this._graph.setNodeAttribute(noteId, 'version', (this._graph.getNodeAttribute(noteId, 'version') ?? 0) + 1);
|
|
631
648
|
this.ctx.markDirty();
|
|
632
649
|
}
|
|
633
650
|
listAttachments(noteId) {
|
package/dist/graphs/skill.js
CHANGED
|
@@ -30,6 +30,7 @@ const manager_types_1 = require("../graphs/manager-types");
|
|
|
30
30
|
const skills_1 = require("../lib/search/skills");
|
|
31
31
|
const bm25_1 = require("../lib/search/bm25");
|
|
32
32
|
const file_mirror_1 = require("../lib/file-mirror");
|
|
33
|
+
const embedding_codec_1 = require("../lib/embedding-codec");
|
|
33
34
|
const attachment_types_1 = require("../graphs/attachment-types");
|
|
34
35
|
const file_import_1 = require("../lib/file-import");
|
|
35
36
|
// ---------------------------------------------------------------------------
|
|
@@ -473,8 +474,19 @@ function saveSkillGraph(graph, graphMemory, embeddingFingerprint) {
|
|
|
473
474
|
fs_1.default.mkdirSync(graphMemory, { recursive: true });
|
|
474
475
|
const file = path_1.default.join(graphMemory, 'skills.json');
|
|
475
476
|
const tmp = file + '.tmp';
|
|
476
|
-
|
|
477
|
-
|
|
477
|
+
try {
|
|
478
|
+
const exported = graph.export();
|
|
479
|
+
(0, embedding_codec_1.compressEmbeddings)(exported);
|
|
480
|
+
fs_1.default.writeFileSync(tmp, JSON.stringify({ embeddingModel: embeddingFingerprint, graph: exported }));
|
|
481
|
+
fs_1.default.renameSync(tmp, file);
|
|
482
|
+
}
|
|
483
|
+
catch (err) {
|
|
484
|
+
try {
|
|
485
|
+
fs_1.default.unlinkSync(tmp);
|
|
486
|
+
}
|
|
487
|
+
catch { /* ignore cleanup error */ }
|
|
488
|
+
throw err;
|
|
489
|
+
}
|
|
478
490
|
}
|
|
479
491
|
function loadSkillGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
480
492
|
const graph = (0, skill_types_1.createSkillGraph)();
|
|
@@ -490,6 +502,7 @@ function loadSkillGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
|
490
502
|
process.stderr.write(`[skill-graph] Embedding config changed, re-indexing skill graph\n`);
|
|
491
503
|
return graph;
|
|
492
504
|
}
|
|
505
|
+
(0, embedding_codec_1.decompressEmbeddings)(data.graph);
|
|
493
506
|
graph.import(data.graph);
|
|
494
507
|
process.stderr.write(`[skill-graph] Loaded ${graph.order} nodes, ${graph.size} edges\n`);
|
|
495
508
|
}
|
|
@@ -760,7 +773,9 @@ class SkillGraphManager {
|
|
|
760
773
|
try {
|
|
761
774
|
const proxyNodeId = proxyId(targetGraph, targetId, pid);
|
|
762
775
|
if (this._graph.hasEdge(skillId, proxyNodeId)) {
|
|
763
|
-
|
|
776
|
+
const ek = this._graph.edge(skillId, proxyNodeId);
|
|
777
|
+
if (ek)
|
|
778
|
+
kind = this._graph.getEdgeAttribute(ek, 'kind') ?? '';
|
|
764
779
|
}
|
|
765
780
|
}
|
|
766
781
|
catch { /* ignore */ }
|
|
@@ -788,7 +803,9 @@ class SkillGraphManager {
|
|
|
788
803
|
let kind = '';
|
|
789
804
|
try {
|
|
790
805
|
if (this._graph.hasEdge(fromId, toId)) {
|
|
791
|
-
|
|
806
|
+
const ek = this._graph.edge(fromId, toId);
|
|
807
|
+
if (ek)
|
|
808
|
+
kind = this._graph.getEdgeAttribute(ek, 'kind') ?? '';
|
|
792
809
|
}
|
|
793
810
|
}
|
|
794
811
|
catch { /* ignore */ }
|
|
@@ -856,6 +873,8 @@ class SkillGraphManager {
|
|
|
856
873
|
return;
|
|
857
874
|
const attachments = (0, attachment_types_1.scanAttachments)(path_1.default.join(dir, skillId));
|
|
858
875
|
this._graph.setNodeAttribute(skillId, 'attachments', attachments);
|
|
876
|
+
this._graph.setNodeAttribute(skillId, 'updatedAt', Date.now());
|
|
877
|
+
this._graph.setNodeAttribute(skillId, 'version', (this._graph.getNodeAttribute(skillId, 'version') ?? 0) + 1);
|
|
859
878
|
this.ctx.markDirty();
|
|
860
879
|
}
|
|
861
880
|
listAttachments(skillId) {
|
package/dist/graphs/task.js
CHANGED
|
@@ -30,6 +30,7 @@ const manager_types_1 = require("../graphs/manager-types");
|
|
|
30
30
|
const tasks_1 = require("../lib/search/tasks");
|
|
31
31
|
const bm25_1 = require("../lib/search/bm25");
|
|
32
32
|
const file_mirror_1 = require("../lib/file-mirror");
|
|
33
|
+
const embedding_codec_1 = require("../lib/embedding-codec");
|
|
33
34
|
const attachment_types_1 = require("../graphs/attachment-types");
|
|
34
35
|
const file_import_1 = require("../lib/file-import");
|
|
35
36
|
// ---------------------------------------------------------------------------
|
|
@@ -494,8 +495,19 @@ function saveTaskGraph(graph, graphMemory, embeddingFingerprint) {
|
|
|
494
495
|
fs_1.default.mkdirSync(graphMemory, { recursive: true });
|
|
495
496
|
const file = path_1.default.join(graphMemory, 'tasks.json');
|
|
496
497
|
const tmp = file + '.tmp';
|
|
497
|
-
|
|
498
|
-
|
|
498
|
+
try {
|
|
499
|
+
const exported = graph.export();
|
|
500
|
+
(0, embedding_codec_1.compressEmbeddings)(exported);
|
|
501
|
+
fs_1.default.writeFileSync(tmp, JSON.stringify({ embeddingModel: embeddingFingerprint, graph: exported }));
|
|
502
|
+
fs_1.default.renameSync(tmp, file);
|
|
503
|
+
}
|
|
504
|
+
catch (err) {
|
|
505
|
+
try {
|
|
506
|
+
fs_1.default.unlinkSync(tmp);
|
|
507
|
+
}
|
|
508
|
+
catch { /* ignore cleanup error */ }
|
|
509
|
+
throw err;
|
|
510
|
+
}
|
|
499
511
|
}
|
|
500
512
|
function loadTaskGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
501
513
|
const graph = (0, task_types_1.createTaskGraph)();
|
|
@@ -511,6 +523,7 @@ function loadTaskGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
|
511
523
|
process.stderr.write(`[task-graph] Embedding config changed, re-indexing task graph\n`);
|
|
512
524
|
return graph;
|
|
513
525
|
}
|
|
526
|
+
(0, embedding_codec_1.decompressEmbeddings)(data.graph);
|
|
514
527
|
graph.import(data.graph);
|
|
515
528
|
process.stderr.write(`[task-graph] Loaded ${graph.order} nodes, ${graph.size} edges\n`);
|
|
516
529
|
}
|
|
@@ -733,7 +746,9 @@ class TaskGraphManager {
|
|
|
733
746
|
try {
|
|
734
747
|
const proxyNodeId = proxyId(targetGraph, targetId, pid);
|
|
735
748
|
if (this._graph.hasEdge(taskId, proxyNodeId)) {
|
|
736
|
-
|
|
749
|
+
const ek = this._graph.edge(taskId, proxyNodeId);
|
|
750
|
+
if (ek)
|
|
751
|
+
kind = this._graph.getEdgeAttribute(ek, 'kind') ?? '';
|
|
737
752
|
}
|
|
738
753
|
}
|
|
739
754
|
catch { /* ignore */ }
|
|
@@ -759,7 +774,9 @@ class TaskGraphManager {
|
|
|
759
774
|
let kind = '';
|
|
760
775
|
try {
|
|
761
776
|
if (this._graph.hasEdge(fromId, toId)) {
|
|
762
|
-
|
|
777
|
+
const ek = this._graph.edge(fromId, toId);
|
|
778
|
+
if (ek)
|
|
779
|
+
kind = this._graph.getEdgeAttribute(ek, 'kind') ?? '';
|
|
763
780
|
}
|
|
764
781
|
}
|
|
765
782
|
catch { /* ignore */ }
|
|
@@ -827,6 +844,8 @@ class TaskGraphManager {
|
|
|
827
844
|
return;
|
|
828
845
|
const attachments = (0, attachment_types_1.scanAttachments)(path_1.default.join(dir, taskId));
|
|
829
846
|
this._graph.setNodeAttribute(taskId, 'attachments', attachments);
|
|
847
|
+
this._graph.setNodeAttribute(taskId, 'updatedAt', Date.now());
|
|
848
|
+
this._graph.setNodeAttribute(taskId, 'version', (this._graph.getNodeAttribute(taskId, 'version') ?? 0) + 1);
|
|
830
849
|
this.ctx.markDirty();
|
|
831
850
|
}
|
|
832
851
|
listAttachments(taskId) {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Encode/decode embedding vectors as Base64 for compact JSON serialization.
|
|
4
|
+
* Float32Array → Base64 string saves ~3x vs JSON number arrays.
|
|
5
|
+
* Backwards compatible: detects old format (number[]) on load.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.compressEmbeddings = compressEmbeddings;
|
|
9
|
+
exports.decompressEmbeddings = decompressEmbeddings;
|
|
10
|
+
const EMBEDDING_FIELDS = ['embedding', 'fileEmbedding'];
|
|
11
|
+
/** Convert a number[] to a Base64-encoded Float32Array. */
|
|
12
|
+
function float32ToBase64(arr) {
|
|
13
|
+
const f32 = new Float32Array(arr);
|
|
14
|
+
const bytes = new Uint8Array(f32.buffer);
|
|
15
|
+
let binary = '';
|
|
16
|
+
for (let i = 0; i < bytes.length; i++)
|
|
17
|
+
binary += String.fromCharCode(bytes[i]);
|
|
18
|
+
return Buffer.from(binary, 'binary').toString('base64');
|
|
19
|
+
}
|
|
20
|
+
/** Convert a Base64-encoded Float32Array back to number[]. */
|
|
21
|
+
function base64ToFloat32(b64) {
|
|
22
|
+
const buf = Buffer.from(b64, 'base64');
|
|
23
|
+
// Copy to aligned buffer to guarantee 4-byte alignment for Float32Array
|
|
24
|
+
const aligned = new Uint8Array(buf.byteLength);
|
|
25
|
+
aligned.set(new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
|
|
26
|
+
const f32 = new Float32Array(aligned.buffer, 0, aligned.byteLength / 4);
|
|
27
|
+
return Array.from(f32);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Compress embedding fields in a graphology export object (mutates in place).
|
|
31
|
+
* Converts number[] → Base64 string for fields named 'embedding' or 'fileEmbedding'.
|
|
32
|
+
*/
|
|
33
|
+
function compressEmbeddings(exported) {
|
|
34
|
+
if (!exported?.nodes)
|
|
35
|
+
return;
|
|
36
|
+
for (const node of exported.nodes) {
|
|
37
|
+
const attrs = node.attributes;
|
|
38
|
+
if (!attrs)
|
|
39
|
+
continue;
|
|
40
|
+
for (const field of EMBEDDING_FIELDS) {
|
|
41
|
+
if (Array.isArray(attrs[field]) && attrs[field].length > 0) {
|
|
42
|
+
attrs[field] = float32ToBase64(attrs[field]);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Decompress embedding fields in a graphology export object (mutates in place).
|
|
49
|
+
* Converts Base64 string → number[]. Handles both old format (number[]) and new (string).
|
|
50
|
+
*/
|
|
51
|
+
function decompressEmbeddings(exported) {
|
|
52
|
+
if (!exported?.nodes)
|
|
53
|
+
return;
|
|
54
|
+
for (const node of exported.nodes) {
|
|
55
|
+
const attrs = node.attributes;
|
|
56
|
+
if (!attrs)
|
|
57
|
+
continue;
|
|
58
|
+
for (const field of EMBEDDING_FIELDS) {
|
|
59
|
+
if (typeof attrs[field] === 'string' && attrs[field].length > 0) {
|
|
60
|
+
attrs[field] = base64ToFloat32(attrs[field]);
|
|
61
|
+
}
|
|
62
|
+
// number[] stays as-is (backwards compatible with old format)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
package/dist/lib/jwt.js
CHANGED
|
@@ -85,15 +85,15 @@ function parseTtl(ttl) {
|
|
|
85
85
|
}
|
|
86
86
|
function signAccessToken(userId, secret, ttl) {
|
|
87
87
|
const payload = { userId, type: 'access' };
|
|
88
|
-
return jsonwebtoken_1.default.sign(payload, secret, { expiresIn: parseTtl(ttl) });
|
|
88
|
+
return jsonwebtoken_1.default.sign(payload, secret, { algorithm: 'HS256', expiresIn: parseTtl(ttl) });
|
|
89
89
|
}
|
|
90
90
|
function signRefreshToken(userId, secret, ttl) {
|
|
91
91
|
const payload = { userId, type: 'refresh' };
|
|
92
|
-
return jsonwebtoken_1.default.sign(payload, secret, { expiresIn: parseTtl(ttl) });
|
|
92
|
+
return jsonwebtoken_1.default.sign(payload, secret, { algorithm: 'HS256', expiresIn: parseTtl(ttl) });
|
|
93
93
|
}
|
|
94
94
|
function verifyToken(token, secret) {
|
|
95
95
|
try {
|
|
96
|
-
const decoded = jsonwebtoken_1.default.verify(token, secret);
|
|
96
|
+
const decoded = jsonwebtoken_1.default.verify(token, secret, { algorithms: ['HS256'] });
|
|
97
97
|
if (!decoded.userId || !decoded.type)
|
|
98
98
|
return null;
|
|
99
99
|
return { userId: decoded.userId, type: decoded.type };
|
|
@@ -108,7 +108,7 @@ function verifyToken(token, secret) {
|
|
|
108
108
|
const ACCESS_COOKIE = 'mgm_access';
|
|
109
109
|
const REFRESH_COOKIE = 'mgm_refresh';
|
|
110
110
|
function setAuthCookies(res, accessToken, refreshToken, accessTtl, refreshTtl) {
|
|
111
|
-
const secure = process.env.NODE_ENV
|
|
111
|
+
const secure = process.env.NODE_ENV !== 'development';
|
|
112
112
|
res.cookie(ACCESS_COOKIE, accessToken, {
|
|
113
113
|
httpOnly: true,
|
|
114
114
|
secure,
|
package/dist/lib/multi-config.js
CHANGED
|
@@ -50,6 +50,7 @@ const userSchema = zod_1.z.object({
|
|
|
50
50
|
});
|
|
51
51
|
const graphConfigSchema = zod_1.z.object({
|
|
52
52
|
enabled: zod_1.z.boolean().optional(),
|
|
53
|
+
readonly: zod_1.z.boolean().optional(),
|
|
53
54
|
include: zod_1.z.string().optional(),
|
|
54
55
|
exclude: excludeSchema,
|
|
55
56
|
model: modelConfigSchema.optional(),
|
|
@@ -98,7 +99,7 @@ const serverSchema = zod_1.z.object({
|
|
|
98
99
|
embeddingApi: embeddingApiSchema.optional(),
|
|
99
100
|
defaultAccess: accessLevelSchema.optional(),
|
|
100
101
|
access: accessMapSchema,
|
|
101
|
-
jwtSecret: zod_1.z.string().optional(),
|
|
102
|
+
jwtSecret: zod_1.z.string().min(16).optional(),
|
|
102
103
|
accessTokenTtl: zod_1.z.string().optional(),
|
|
103
104
|
refreshTokenTtl: zod_1.z.string().optional(),
|
|
104
105
|
rateLimit: rateLimitSchema.optional(),
|
|
@@ -107,6 +108,7 @@ const serverSchema = zod_1.z.object({
|
|
|
107
108
|
});
|
|
108
109
|
const wsGraphConfigSchema = zod_1.z.object({
|
|
109
110
|
enabled: zod_1.z.boolean().optional(),
|
|
111
|
+
readonly: zod_1.z.boolean().optional(),
|
|
110
112
|
exclude: excludeSchema,
|
|
111
113
|
model: modelConfigSchema.optional(),
|
|
112
114
|
embedding: embeddingConfigSchema.optional(),
|
|
@@ -301,6 +303,7 @@ function loadMultiConfig(yamlPath) {
|
|
|
301
303
|
const graphExclude = [...projectExclude, ...parseExclude(gc?.exclude)];
|
|
302
304
|
graphConfigs[gn] = {
|
|
303
305
|
enabled: gc?.enabled ?? true,
|
|
306
|
+
readonly: gc?.readonly ?? false,
|
|
304
307
|
include: gc?.include ?? (gn === 'docs' ? PROJECT_DEFAULTS.docsInclude : gn === 'code' ? PROJECT_DEFAULTS.codeInclude : undefined),
|
|
305
308
|
exclude: graphExclude,
|
|
306
309
|
model: resolveModel(gc?.model, projectModel),
|
|
@@ -348,6 +351,7 @@ function loadMultiConfig(yamlPath) {
|
|
|
348
351
|
const gc = rawGraphs[gn];
|
|
349
352
|
graphConfigs[gn] = {
|
|
350
353
|
enabled: gc?.enabled ?? true,
|
|
354
|
+
readonly: gc?.readonly ?? false,
|
|
351
355
|
exclude: [...wsExclude, ...parseExclude(gc?.exclude)],
|
|
352
356
|
model: resolveModel(gc?.model, wsModel),
|
|
353
357
|
embedding: resolveEmbedding(gc?.embedding, wsEmbedding),
|
|
@@ -418,6 +422,7 @@ function defaultConfig(projectDir) {
|
|
|
418
422
|
for (const gn of exports.GRAPH_NAMES) {
|
|
419
423
|
graphConfigs[gn] = {
|
|
420
424
|
enabled: true,
|
|
425
|
+
readonly: false,
|
|
421
426
|
include: gn === 'docs' ? PROJECT_DEFAULTS.docsInclude : gn === 'code' ? PROJECT_DEFAULTS.codeInclude : undefined,
|
|
422
427
|
exclude: [...server.exclude],
|
|
423
428
|
model: MODEL_DEFAULTS,
|