@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.
Files changed (92) hide show
  1. package/LICENSE +84 -12
  2. package/README.md +7 -1
  3. package/dist/api/index.js +147 -50
  4. package/dist/api/rest/index.js +35 -15
  5. package/dist/api/rest/tools.js +8 -1
  6. package/dist/api/tools/code/search-code.js +12 -9
  7. package/dist/api/tools/code/search-files.js +1 -1
  8. package/dist/api/tools/docs/cross-references.js +3 -2
  9. package/dist/api/tools/docs/explain-symbol.js +2 -1
  10. package/dist/api/tools/docs/find-examples.js +2 -1
  11. package/dist/api/tools/docs/search-files.js +1 -1
  12. package/dist/api/tools/docs/search-snippets.js +1 -1
  13. package/dist/api/tools/docs/search.js +5 -4
  14. package/dist/api/tools/file-index/search-all-files.js +1 -1
  15. package/dist/api/tools/knowledge/add-attachment.js +14 -3
  16. package/dist/api/tools/knowledge/remove-attachment.js +5 -1
  17. package/dist/api/tools/knowledge/search-notes.js +5 -4
  18. package/dist/api/tools/skills/add-attachment.js +14 -3
  19. package/dist/api/tools/skills/recall-skills.js +1 -1
  20. package/dist/api/tools/skills/remove-attachment.js +5 -1
  21. package/dist/api/tools/skills/search-skills.js +6 -5
  22. package/dist/api/tools/tasks/add-attachment.js +14 -3
  23. package/dist/api/tools/tasks/remove-attachment.js +5 -1
  24. package/dist/api/tools/tasks/search-tasks.js +5 -4
  25. package/dist/cli/index.js +61 -51
  26. package/dist/cli/indexer.js +60 -28
  27. package/dist/graphs/code.js +70 -7
  28. package/dist/graphs/docs.js +15 -2
  29. package/dist/graphs/file-index.js +17 -3
  30. package/dist/graphs/file-lang.js +1 -1
  31. package/dist/graphs/knowledge.js +20 -3
  32. package/dist/graphs/skill.js +23 -4
  33. package/dist/graphs/task.js +23 -4
  34. package/dist/lib/embedding-codec.js +65 -0
  35. package/dist/lib/jwt.js +4 -4
  36. package/dist/lib/multi-config.js +6 -1
  37. package/dist/lib/parsers/code.js +158 -31
  38. package/dist/lib/parsers/codeblock.js +11 -6
  39. package/dist/lib/parsers/docs.js +59 -31
  40. package/dist/lib/parsers/languages/registry.js +2 -2
  41. package/dist/lib/parsers/languages/typescript.js +195 -44
  42. package/dist/lib/project-manager.js +14 -10
  43. package/dist/lib/search/bm25.js +18 -1
  44. package/dist/lib/search/code.js +12 -3
  45. package/dist/ui/assets/NoteForm-aZX9f6-3.js +1 -0
  46. package/dist/ui/assets/SkillForm-KYa3o92l.js +1 -0
  47. package/dist/ui/assets/TaskForm-Bl5nkybO.js +1 -0
  48. package/dist/ui/assets/_articleId_-DjbCByxM.js +1 -0
  49. package/dist/ui/assets/_docId_-hdCDjclV.js +1 -0
  50. package/dist/ui/assets/_filePath_-CpG836v4.js +1 -0
  51. package/dist/ui/assets/_noteId_-C1enaQd1.js +1 -0
  52. package/dist/ui/assets/_skillId_-hPoCet7J.js +1 -0
  53. package/dist/ui/assets/_taskId_-DSB3dLVz.js +1 -0
  54. package/dist/ui/assets/_toolName_-3SmCfxZy.js +2 -0
  55. package/dist/ui/assets/api-BMnBjMMf.js +1 -0
  56. package/dist/ui/assets/api-BlFF6gX-.js +1 -0
  57. package/dist/ui/assets/api-CrGJOcaN.js +1 -0
  58. package/dist/ui/assets/api-DuX-0a_X.js +1 -0
  59. package/dist/ui/assets/attachments-CEQ-2nMo.js +1 -0
  60. package/dist/ui/assets/client-Bq88u7gN.js +1 -0
  61. package/dist/ui/assets/docs-CrXsRcOG.js +1 -0
  62. package/dist/ui/assets/edit-BYiy1FZy.js +1 -0
  63. package/dist/ui/assets/edit-TUIIpUMF.js +1 -0
  64. package/dist/ui/assets/edit-hc-ZWz3y.js +1 -0
  65. package/dist/ui/assets/esm-BWiKNcBW.js +1 -0
  66. package/dist/ui/assets/files-0bPg6NH9.js +1 -0
  67. package/dist/ui/assets/graph-DXGud_wF.js +1 -0
  68. package/dist/ui/assets/help-CEMQqZUR.js +891 -0
  69. package/dist/ui/assets/help-DJ52_fxN.js +1 -0
  70. package/dist/ui/assets/index-BCZDAYZi.js +2 -0
  71. package/dist/ui/assets/index-D6zSNtzo.css +1 -0
  72. package/dist/ui/assets/knowledge-DeygeGGH.js +1 -0
  73. package/dist/ui/assets/new-CpD7hOBA.js +1 -0
  74. package/dist/ui/assets/new-DHTg3Dqq.js +1 -0
  75. package/dist/ui/assets/new-s8c0M75X.js +1 -0
  76. package/dist/ui/assets/prompts-BgOmdxgM.js +295 -0
  77. package/dist/ui/assets/rolldown-runtime-Dw2cE7zH.js +1 -0
  78. package/dist/ui/assets/search-EpJhdP2a.js +1 -0
  79. package/dist/ui/assets/skill-y9pizyqE.js +1 -0
  80. package/dist/ui/assets/skills-Cga9iUZN.js +1 -0
  81. package/dist/ui/assets/tasks-CobouTKV.js +1 -0
  82. package/dist/ui/assets/tools-JxKH5BDF.js +1 -0
  83. package/dist/ui/assets/vendor-graph-BWpSgpMe.js +321 -0
  84. package/dist/ui/assets/vendor-markdown-CT8ZVEPu.js +50 -0
  85. package/dist/ui/assets/vendor-md-editor-DmWafJvr.js +44 -0
  86. package/dist/ui/assets/{index-kKd4mVrh.css → vendor-md-editor-HrwGbQou.css} +1 -1
  87. package/dist/ui/assets/vendor-mui-BPj7d3Sw.js +139 -0
  88. package/dist/ui/assets/vendor-mui-icons-B196sG3f.js +1 -0
  89. package/dist/ui/assets/vendor-react-CHUjhoxh.js +11 -0
  90. package/dist/ui/index.html +11 -3
  91. package/package.json +2 -2
  92. package/dist/ui/assets/index-0hRezICt.js +0 -1702
@@ -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 imports on the file node for post-drain resolution
44
- if (pendingImports.length > 0 && graph.hasNode(parsed.fileId)) {
45
- graph.setNodeAttribute(parsed.fileId, 'pendingImports', pendingImports);
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
- fs_1.default.writeFileSync(tmp, JSON.stringify({ embeddingModel: embeddingFingerprint, graph: graph.export() }));
116
- fs_1.default.renameSync(tmp, file);
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;
@@ -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
- fs_1.default.writeFileSync(tmp, JSON.stringify({ embeddingModel: embeddingFingerprint, graph: graph.export() }));
137
- fs_1.default.renameSync(tmp, file);
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
- if (graph.getEdgeAttribute(graph.edge(dirId, childId), 'kind') !== 'contains')
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
- fs_1.default.writeFileSync(tmp, JSON.stringify({ embeddingModel: embeddingFingerprint, graph: graph.export() }));
244
- fs_1.default.renameSync(tmp, file);
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
  }
@@ -15,7 +15,7 @@ exports.EXT_TO_LANGUAGE = {
15
15
  '.mjs': 'javascript',
16
16
  '.cjs': 'javascript',
17
17
  '.ts': 'typescript',
18
- '.tsx': 'typescript',
18
+ '.tsx': 'tsx',
19
19
  '.mts': 'typescript',
20
20
  '.cts': 'typescript',
21
21
  // Web
@@ -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
- fs_1.default.writeFileSync(tmp, JSON.stringify({ embeddingModel: embeddingFingerprint, graph: graph.export() }));
333
- fs_1.default.renameSync(tmp, file);
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
- kind = this._graph.getEdgeAttribute(this._graph.edge(fromId, actualToId), 'kind') ?? '';
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) {
@@ -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
- fs_1.default.writeFileSync(tmp, JSON.stringify({ embeddingModel: embeddingFingerprint, graph: graph.export() }));
477
- fs_1.default.renameSync(tmp, file);
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
- kind = this._graph.getEdgeAttribute(this._graph.edge(skillId, proxyNodeId), 'kind') ?? '';
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
- kind = this._graph.getEdgeAttribute(this._graph.edge(fromId, toId), 'kind') ?? '';
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) {
@@ -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
- fs_1.default.writeFileSync(tmp, JSON.stringify({ embeddingModel: embeddingFingerprint, graph: graph.export() }));
498
- fs_1.default.renameSync(tmp, file);
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
- kind = this._graph.getEdgeAttribute(this._graph.edge(taskId, proxyNodeId), 'kind') ?? '';
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
- kind = this._graph.getEdgeAttribute(this._graph.edge(fromId, toId), 'kind') ?? '';
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 === 'production';
111
+ const secure = process.env.NODE_ENV !== 'development';
112
112
  res.cookie(ACCESS_COOKIE, accessToken, {
113
113
  httpOnly: true,
114
114
  secure,
@@ -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,