@graphmemory/server 1.2.0 → 1.3.1
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 +151 -54
- package/dist/api/rest/code.js +2 -1
- package/dist/api/rest/docs.js +2 -1
- package/dist/api/rest/embed.js +8 -1
- package/dist/api/rest/index.js +39 -18
- package/dist/api/rest/knowledge.js +4 -2
- package/dist/api/rest/skills.js +2 -1
- package/dist/api/rest/tasks.js +2 -1
- package/dist/api/rest/tools.js +8 -1
- package/dist/api/rest/validation.js +41 -40
- package/dist/api/rest/websocket.js +24 -7
- 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 +15 -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 +15 -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 +15 -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 +63 -52
- package/dist/cli/indexer.js +62 -29
- package/dist/graphs/attachment-types.js +5 -0
- package/dist/graphs/code.js +99 -10
- package/dist/graphs/docs.js +20 -5
- package/dist/graphs/file-index.js +22 -6
- package/dist/graphs/file-lang.js +1 -1
- package/dist/graphs/knowledge.js +31 -7
- package/dist/graphs/skill.js +35 -9
- package/dist/graphs/task.js +35 -9
- package/dist/lib/defaults.js +78 -0
- package/dist/lib/embedder.js +11 -12
- package/dist/lib/embedding-codec.js +63 -0
- package/dist/lib/graph-persistence.js +68 -0
- package/dist/lib/jwt.js +4 -4
- package/dist/lib/mirror-watcher.js +4 -3
- 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 +60 -31
- package/dist/lib/parsers/languages/registry.js +2 -2
- package/dist/lib/parsers/languages/typescript.js +214 -46
- package/dist/lib/project-manager.js +21 -11
- package/dist/lib/search/bm25.js +23 -5
- package/dist/lib/search/code.js +13 -3
- package/dist/lib/search/docs.js +2 -1
- package/dist/lib/search/file-index.js +2 -1
- package/dist/lib/search/files.js +3 -2
- package/dist/lib/search/knowledge.js +2 -1
- package/dist/lib/search/skills.js +2 -1
- package/dist/lib/search/tasks.js +2 -1
- 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 +6 -3
- 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,9 @@ 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");
|
|
25
|
+
const graph_persistence_1 = require("../lib/graph-persistence");
|
|
26
|
+
const defaults_1 = require("../lib/defaults");
|
|
23
27
|
// ---------------------------------------------------------------------------
|
|
24
28
|
// CRUD
|
|
25
29
|
// ---------------------------------------------------------------------------
|
|
@@ -30,19 +34,31 @@ function updateCodeFile(graph, parsed) {
|
|
|
30
34
|
graph.addNode(id, attrs);
|
|
31
35
|
}
|
|
32
36
|
const pendingImports = [];
|
|
37
|
+
const pendingEdges = [];
|
|
33
38
|
for (const { from, to, attrs } of parsed.edges) {
|
|
34
39
|
if (!graph.hasNode(to)) {
|
|
35
|
-
if (attrs.kind === 'imports')
|
|
40
|
+
if (attrs.kind === 'imports') {
|
|
36
41
|
pendingImports.push(to);
|
|
42
|
+
}
|
|
43
|
+
else if (attrs.kind === 'extends' || attrs.kind === 'implements') {
|
|
44
|
+
// Target class/interface may be in another file — defer resolution
|
|
45
|
+
const toName = to.split('::').pop();
|
|
46
|
+
pendingEdges.push({ from, toName, kind: attrs.kind });
|
|
47
|
+
}
|
|
37
48
|
continue;
|
|
38
49
|
}
|
|
39
50
|
if (graph.hasNode(from) && !graph.hasEdge(from, to)) {
|
|
40
51
|
graph.addEdgeWithKey(`${from}→${to}`, from, to, attrs);
|
|
41
52
|
}
|
|
42
53
|
}
|
|
43
|
-
// Store pending
|
|
44
|
-
if (
|
|
45
|
-
|
|
54
|
+
// Store pending data on the file node for post-drain resolution
|
|
55
|
+
if (graph.hasNode(parsed.fileId)) {
|
|
56
|
+
if (pendingImports.length > 0) {
|
|
57
|
+
graph.setNodeAttribute(parsed.fileId, 'pendingImports', pendingImports);
|
|
58
|
+
}
|
|
59
|
+
if (pendingEdges.length > 0) {
|
|
60
|
+
graph.setNodeAttribute(parsed.fileId, 'pendingEdges', pendingEdges);
|
|
61
|
+
}
|
|
46
62
|
}
|
|
47
63
|
}
|
|
48
64
|
/**
|
|
@@ -71,6 +87,67 @@ function resolvePendingImports(graph) {
|
|
|
71
87
|
});
|
|
72
88
|
return created;
|
|
73
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Resolve pending extends/implements edges after all files have been indexed.
|
|
92
|
+
* When multiple candidates share the same name, prefers the one whose file
|
|
93
|
+
* is imported by the source file (falls back to first match).
|
|
94
|
+
*/
|
|
95
|
+
function resolvePendingEdges(graph) {
|
|
96
|
+
const nameIndex = new Map();
|
|
97
|
+
graph.forEachNode((id, attrs) => {
|
|
98
|
+
if (attrs.kind === 'class' || attrs.kind === 'interface') {
|
|
99
|
+
const list = nameIndex.get(attrs.name) ?? [];
|
|
100
|
+
list.push(id);
|
|
101
|
+
nameIndex.set(attrs.name, list);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
// Build file → imported file IDs index for disambiguation
|
|
105
|
+
const fileImports = new Map();
|
|
106
|
+
graph.forEachNode((id, attrs) => {
|
|
107
|
+
if (attrs.kind === 'file') {
|
|
108
|
+
const imported = new Set();
|
|
109
|
+
graph.forEachOutEdge(id, (_edge, edgeAttrs, _src, target) => {
|
|
110
|
+
if (edgeAttrs.kind === 'imports')
|
|
111
|
+
imported.add(target);
|
|
112
|
+
});
|
|
113
|
+
fileImports.set(id, imported);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
let created = 0;
|
|
117
|
+
graph.forEachNode((id, attrs) => {
|
|
118
|
+
if (!attrs.pendingEdges || attrs.pendingEdges.length === 0)
|
|
119
|
+
return;
|
|
120
|
+
const remaining = [];
|
|
121
|
+
for (const edge of attrs.pendingEdges) {
|
|
122
|
+
const candidates = nameIndex.get(edge.toName);
|
|
123
|
+
if (candidates && candidates.length > 0 && graph.hasNode(edge.from)) {
|
|
124
|
+
let toId;
|
|
125
|
+
if (candidates.length === 1) {
|
|
126
|
+
toId = candidates[0];
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// Disambiguate: prefer candidate whose file is imported by edge.from's file
|
|
130
|
+
const fromFileId = edge.from.split('::')[0];
|
|
131
|
+
const imports = fileImports.get(fromFileId);
|
|
132
|
+
const match = imports && candidates.find(c => {
|
|
133
|
+
const cFileId = c.split('::')[0];
|
|
134
|
+
return imports.has(cFileId);
|
|
135
|
+
});
|
|
136
|
+
toId = match ?? candidates[0];
|
|
137
|
+
}
|
|
138
|
+
if (toId !== edge.from && !graph.hasEdge(edge.from, toId)) {
|
|
139
|
+
graph.addEdgeWithKey(`${edge.from}→${toId}`, edge.from, toId, { kind: edge.kind });
|
|
140
|
+
created++;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
remaining.push(edge);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
graph.setNodeAttribute(id, 'pendingEdges', remaining.length > 0 ? remaining : undefined);
|
|
148
|
+
});
|
|
149
|
+
return created;
|
|
150
|
+
}
|
|
74
151
|
/** Remove all nodes (and their incident edges) belonging to a file. */
|
|
75
152
|
function removeCodeFile(graph, fileId) {
|
|
76
153
|
const toRemove = graph.filterNodes((_, attrs) => attrs.fileId === fileId);
|
|
@@ -91,7 +168,7 @@ function getCodeFileMtime(graph, fileId) {
|
|
|
91
168
|
return graph.getNodeAttribute(nodes[0], 'mtime');
|
|
92
169
|
}
|
|
93
170
|
/** List all indexed files with symbol counts. */
|
|
94
|
-
function listCodeFiles(graph, filter, limit =
|
|
171
|
+
function listCodeFiles(graph, filter, limit = defaults_1.LIST_LIMIT_SMALL) {
|
|
95
172
|
const files = new Map();
|
|
96
173
|
const lowerFilter = filter?.toLowerCase();
|
|
97
174
|
graph.forEachNode((_, attrs) => {
|
|
@@ -112,23 +189,35 @@ function saveCodeGraph(graph, graphMemory, embeddingFingerprint) {
|
|
|
112
189
|
fs_1.default.mkdirSync(graphMemory, { recursive: true });
|
|
113
190
|
const file = path_1.default.join(graphMemory, 'code.json');
|
|
114
191
|
const tmp = file + '.tmp';
|
|
115
|
-
|
|
116
|
-
|
|
192
|
+
try {
|
|
193
|
+
const exported = graph.export();
|
|
194
|
+
(0, embedding_codec_1.compressEmbeddings)(exported);
|
|
195
|
+
fs_1.default.writeFileSync(tmp, JSON.stringify({ embeddingModel: embeddingFingerprint, graph: exported }));
|
|
196
|
+
fs_1.default.renameSync(tmp, file);
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
try {
|
|
200
|
+
fs_1.default.unlinkSync(tmp);
|
|
201
|
+
}
|
|
202
|
+
catch { /* ignore cleanup error */ }
|
|
203
|
+
throw err;
|
|
204
|
+
}
|
|
117
205
|
}
|
|
118
206
|
function loadCodeGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
119
207
|
const graph = (0, code_types_1.createCodeGraph)();
|
|
120
208
|
if (fresh)
|
|
121
209
|
return graph;
|
|
122
210
|
const file = path_1.default.join(graphMemory, 'code.json');
|
|
123
|
-
|
|
211
|
+
const data = (0, graph_persistence_1.readJsonWithTmpFallback)(file);
|
|
212
|
+
if (!data)
|
|
124
213
|
return graph;
|
|
125
214
|
try {
|
|
126
|
-
const data = JSON.parse(fs_1.default.readFileSync(file, 'utf-8'));
|
|
127
215
|
const stored = data.embeddingModel;
|
|
128
216
|
if (embeddingFingerprint && stored !== embeddingFingerprint) {
|
|
129
217
|
process.stderr.write(`[code-graph] Embedding config changed, re-indexing code graph\n`);
|
|
130
218
|
return graph;
|
|
131
219
|
}
|
|
220
|
+
(0, embedding_codec_1.decompressEmbeddings)(data.graph);
|
|
132
221
|
graph.import(data.graph);
|
|
133
222
|
process.stderr.write(`[code-graph] Loaded ${graph.order} nodes, ${graph.size} edges\n`);
|
|
134
223
|
}
|
|
@@ -144,7 +233,7 @@ class CodeGraphManager {
|
|
|
144
233
|
_graph;
|
|
145
234
|
embedFns;
|
|
146
235
|
ext;
|
|
147
|
-
_bm25Index = new bm25_1.BM25Index((attrs) => `${attrs.name} ${attrs.signature} ${attrs.docComment}`);
|
|
236
|
+
_bm25Index = new bm25_1.BM25Index((attrs) => `${attrs.name} ${attrs.signature} ${attrs.docComment} ${attrs.body.slice(0, defaults_1.BM25_BODY_MAX_CHARS)}`);
|
|
148
237
|
constructor(_graph, embedFns, ext = {}) {
|
|
149
238
|
this._graph = _graph;
|
|
150
239
|
this.embedFns = embedFns;
|
package/dist/graphs/docs.js
CHANGED
|
@@ -20,6 +20,9 @@ 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");
|
|
24
|
+
const graph_persistence_1 = require("../lib/graph-persistence");
|
|
25
|
+
const defaults_1 = require("../lib/defaults");
|
|
23
26
|
function createGraph() {
|
|
24
27
|
return new graphology_1.DirectedGraph({ multi: false, allowSelfLoops: false });
|
|
25
28
|
}
|
|
@@ -107,7 +110,7 @@ function getFileMtime(graph, fileId) {
|
|
|
107
110
|
return 0;
|
|
108
111
|
return graph.getNodeAttribute(nodes[0], 'mtime');
|
|
109
112
|
}
|
|
110
|
-
function listFiles(graph, filter, limit =
|
|
113
|
+
function listFiles(graph, filter, limit = defaults_1.LIST_LIMIT_SMALL) {
|
|
111
114
|
const files = new Map();
|
|
112
115
|
const lowerFilter = filter?.toLowerCase();
|
|
113
116
|
graph.forEachNode((_, attrs) => {
|
|
@@ -133,23 +136,35 @@ function saveGraph(graph, graphMemory, embeddingFingerprint) {
|
|
|
133
136
|
fs_1.default.mkdirSync(graphMemory, { recursive: true });
|
|
134
137
|
const file = path_1.default.join(graphMemory, 'docs.json');
|
|
135
138
|
const tmp = file + '.tmp';
|
|
136
|
-
|
|
137
|
-
|
|
139
|
+
try {
|
|
140
|
+
const exported = graph.export();
|
|
141
|
+
(0, embedding_codec_1.compressEmbeddings)(exported);
|
|
142
|
+
fs_1.default.writeFileSync(tmp, JSON.stringify({ embeddingModel: embeddingFingerprint, graph: exported }));
|
|
143
|
+
fs_1.default.renameSync(tmp, file);
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
try {
|
|
147
|
+
fs_1.default.unlinkSync(tmp);
|
|
148
|
+
}
|
|
149
|
+
catch { /* ignore cleanup error */ }
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
138
152
|
}
|
|
139
153
|
function loadGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
140
154
|
const graph = createGraph();
|
|
141
155
|
if (fresh)
|
|
142
156
|
return graph;
|
|
143
157
|
const file = path_1.default.join(graphMemory, 'docs.json');
|
|
144
|
-
|
|
158
|
+
const data = (0, graph_persistence_1.readJsonWithTmpFallback)(file);
|
|
159
|
+
if (!data)
|
|
145
160
|
return graph;
|
|
146
161
|
try {
|
|
147
|
-
const data = JSON.parse(fs_1.default.readFileSync(file, 'utf-8'));
|
|
148
162
|
const stored = data.embeddingModel;
|
|
149
163
|
if (embeddingFingerprint && stored !== embeddingFingerprint) {
|
|
150
164
|
process.stderr.write(`[graph] Embedding config changed, re-indexing docs graph\n`);
|
|
151
165
|
return graph;
|
|
152
166
|
}
|
|
167
|
+
(0, embedding_codec_1.decompressEmbeddings)(data.graph);
|
|
153
168
|
graph.import(data.graph);
|
|
154
169
|
process.stderr.write(`[graph] Loaded ${graph.order} nodes, ${graph.size} edges\n`);
|
|
155
170
|
}
|
|
@@ -19,6 +19,9 @@ 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");
|
|
23
|
+
const graph_persistence_1 = require("../lib/graph-persistence");
|
|
24
|
+
const defaults_1 = require("../lib/defaults");
|
|
22
25
|
// ---------------------------------------------------------------------------
|
|
23
26
|
// CRUD
|
|
24
27
|
// ---------------------------------------------------------------------------
|
|
@@ -147,7 +150,7 @@ function getFileEntryMtime(graph, filePath) {
|
|
|
147
150
|
* Otherwise returns all file nodes matching the filters.
|
|
148
151
|
*/
|
|
149
152
|
function listAllFiles(graph, options = {}) {
|
|
150
|
-
const { directory, extension, language, filter, limit =
|
|
153
|
+
const { directory, extension, language, filter, limit = defaults_1.LIST_LIMIT_LARGE } = options;
|
|
151
154
|
const lowerFilter = filter?.toLowerCase();
|
|
152
155
|
const results = [];
|
|
153
156
|
if (directory !== undefined) {
|
|
@@ -157,7 +160,8 @@ function listAllFiles(graph, options = {}) {
|
|
|
157
160
|
return [];
|
|
158
161
|
graph.forEachOutNeighbor(dirId, (childId) => {
|
|
159
162
|
const attrs = graph.getNodeAttributes(childId);
|
|
160
|
-
|
|
163
|
+
const edgeKey = graph.edge(dirId, childId);
|
|
164
|
+
if (!edgeKey || graph.getEdgeAttribute(edgeKey, 'kind') !== 'contains')
|
|
161
165
|
return;
|
|
162
166
|
if (extension && attrs.extension !== extension)
|
|
163
167
|
return;
|
|
@@ -240,23 +244,35 @@ function saveFileIndexGraph(graph, graphMemory, embeddingFingerprint) {
|
|
|
240
244
|
fs_1.default.mkdirSync(graphMemory, { recursive: true });
|
|
241
245
|
const file = path_1.default.join(graphMemory, 'file-index.json');
|
|
242
246
|
const tmp = file + '.tmp';
|
|
243
|
-
|
|
244
|
-
|
|
247
|
+
try {
|
|
248
|
+
const exported = graph.export();
|
|
249
|
+
(0, embedding_codec_1.compressEmbeddings)(exported);
|
|
250
|
+
fs_1.default.writeFileSync(tmp, JSON.stringify({ embeddingModel: embeddingFingerprint, graph: exported }));
|
|
251
|
+
fs_1.default.renameSync(tmp, file);
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
try {
|
|
255
|
+
fs_1.default.unlinkSync(tmp);
|
|
256
|
+
}
|
|
257
|
+
catch { /* ignore cleanup error */ }
|
|
258
|
+
throw err;
|
|
259
|
+
}
|
|
245
260
|
}
|
|
246
261
|
function loadFileIndexGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
247
262
|
const graph = (0, file_index_types_1.createFileIndexGraph)();
|
|
248
263
|
if (fresh)
|
|
249
264
|
return graph;
|
|
250
265
|
const file = path_1.default.join(graphMemory, 'file-index.json');
|
|
251
|
-
|
|
266
|
+
const data = (0, graph_persistence_1.readJsonWithTmpFallback)(file);
|
|
267
|
+
if (!data)
|
|
252
268
|
return graph;
|
|
253
269
|
try {
|
|
254
|
-
const data = JSON.parse(fs_1.default.readFileSync(file, 'utf-8'));
|
|
255
270
|
const stored = data.embeddingModel;
|
|
256
271
|
if (embeddingFingerprint && stored !== embeddingFingerprint) {
|
|
257
272
|
process.stderr.write(`[file-index] Embedding config changed, re-indexing file index\n`);
|
|
258
273
|
return graph;
|
|
259
274
|
}
|
|
275
|
+
(0, embedding_codec_1.decompressEmbeddings)(data.graph);
|
|
260
276
|
graph.import(data.graph);
|
|
261
277
|
process.stderr.write(`[file-index] Loaded ${graph.order} nodes, ${graph.size} edges\n`);
|
|
262
278
|
}
|
package/dist/graphs/file-lang.js
CHANGED
package/dist/graphs/knowledge.js
CHANGED
|
@@ -28,6 +28,9 @@ 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");
|
|
32
|
+
const graph_persistence_1 = require("../lib/graph-persistence");
|
|
33
|
+
const defaults_1 = require("../lib/defaults");
|
|
31
34
|
const attachment_types_1 = require("../graphs/attachment-types");
|
|
32
35
|
const file_import_1 = require("../lib/file-import");
|
|
33
36
|
// ---------------------------------------------------------------------------
|
|
@@ -161,7 +164,7 @@ function getNote(graph, noteId) {
|
|
|
161
164
|
return { id: noteId, ...graph.getNodeAttributes(noteId) };
|
|
162
165
|
}
|
|
163
166
|
/** List notes with optional filter (substring in title/id) and tag filter. Excludes proxy nodes. */
|
|
164
|
-
function listNotes(graph, filter, tag, limit =
|
|
167
|
+
function listNotes(graph, filter, tag, limit = defaults_1.LIST_LIMIT_SMALL) {
|
|
165
168
|
const lowerFilter = filter?.toLowerCase();
|
|
166
169
|
const lowerTag = tag?.toLowerCase();
|
|
167
170
|
const results = [];
|
|
@@ -178,7 +181,7 @@ function listNotes(graph, filter, tag, limit = 20) {
|
|
|
178
181
|
if (!attrs.tags.some(t => t.toLowerCase() === lowerTag))
|
|
179
182
|
return;
|
|
180
183
|
}
|
|
181
|
-
results.push({ id, title: attrs.title, content: attrs.content.slice(0,
|
|
184
|
+
results.push({ id, title: attrs.title, content: attrs.content.slice(0, defaults_1.CONTENT_PREVIEW_LEN), tags: attrs.tags, updatedAt: attrs.updatedAt });
|
|
182
185
|
});
|
|
183
186
|
return results
|
|
184
187
|
.sort((a, b) => b.updatedAt - a.updatedAt)
|
|
@@ -329,23 +332,35 @@ function saveKnowledgeGraph(graph, graphMemory, embeddingFingerprint) {
|
|
|
329
332
|
fs_1.default.mkdirSync(graphMemory, { recursive: true });
|
|
330
333
|
const file = path_1.default.join(graphMemory, 'knowledge.json');
|
|
331
334
|
const tmp = file + '.tmp';
|
|
332
|
-
|
|
333
|
-
|
|
335
|
+
try {
|
|
336
|
+
const exported = graph.export();
|
|
337
|
+
(0, embedding_codec_1.compressEmbeddings)(exported);
|
|
338
|
+
fs_1.default.writeFileSync(tmp, JSON.stringify({ embeddingModel: embeddingFingerprint, graph: exported }));
|
|
339
|
+
fs_1.default.renameSync(tmp, file);
|
|
340
|
+
}
|
|
341
|
+
catch (err) {
|
|
342
|
+
try {
|
|
343
|
+
fs_1.default.unlinkSync(tmp);
|
|
344
|
+
}
|
|
345
|
+
catch { /* ignore cleanup error */ }
|
|
346
|
+
throw err;
|
|
347
|
+
}
|
|
334
348
|
}
|
|
335
349
|
function loadKnowledgeGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
336
350
|
const graph = (0, knowledge_types_1.createKnowledgeGraph)();
|
|
337
351
|
if (fresh)
|
|
338
352
|
return graph;
|
|
339
353
|
const file = path_1.default.join(graphMemory, 'knowledge.json');
|
|
340
|
-
|
|
354
|
+
const data = (0, graph_persistence_1.readJsonWithTmpFallback)(file);
|
|
355
|
+
if (!data)
|
|
341
356
|
return graph;
|
|
342
357
|
try {
|
|
343
|
-
const data = JSON.parse(fs_1.default.readFileSync(file, 'utf-8'));
|
|
344
358
|
const stored = data.embeddingModel;
|
|
345
359
|
if (embeddingFingerprint && stored !== embeddingFingerprint) {
|
|
346
360
|
process.stderr.write(`[knowledge-graph] Embedding config changed, re-indexing knowledge graph\n`);
|
|
347
361
|
return graph;
|
|
348
362
|
}
|
|
363
|
+
(0, embedding_codec_1.decompressEmbeddings)(data.graph);
|
|
349
364
|
graph.import(data.graph);
|
|
350
365
|
process.stderr.write(`[knowledge-graph] Loaded ${graph.order} nodes, ${graph.size} edges\n`);
|
|
351
366
|
}
|
|
@@ -552,7 +567,9 @@ class KnowledgeGraphManager {
|
|
|
552
567
|
try {
|
|
553
568
|
const actualToId = targetGraph ? proxyId(targetGraph, toId, pid) : toId;
|
|
554
569
|
if (this._graph.hasEdge(fromId, actualToId)) {
|
|
555
|
-
|
|
570
|
+
const ek = this._graph.edge(fromId, actualToId);
|
|
571
|
+
if (ek)
|
|
572
|
+
kind = this._graph.getEdgeAttribute(ek, 'kind') ?? '';
|
|
556
573
|
}
|
|
557
574
|
}
|
|
558
575
|
catch { /* ignore */ }
|
|
@@ -586,6 +603,11 @@ class KnowledgeGraphManager {
|
|
|
586
603
|
return null;
|
|
587
604
|
if (!this._graph.hasNode(noteId) || isProxy(this._graph, noteId))
|
|
588
605
|
return null;
|
|
606
|
+
if (data.length > attachment_types_1.MAX_ATTACHMENT_SIZE)
|
|
607
|
+
return null;
|
|
608
|
+
const existing = (0, attachment_types_1.scanAttachments)(path_1.default.join(dir, noteId));
|
|
609
|
+
if (existing.length >= attachment_types_1.MAX_ATTACHMENTS_PER_ENTITY)
|
|
610
|
+
return null;
|
|
589
611
|
const safe = (0, file_mirror_1.sanitizeFilename)(filename);
|
|
590
612
|
if (!safe)
|
|
591
613
|
return null;
|
|
@@ -628,6 +650,8 @@ class KnowledgeGraphManager {
|
|
|
628
650
|
return;
|
|
629
651
|
const attachments = (0, attachment_types_1.scanAttachments)(path_1.default.join(dir, noteId));
|
|
630
652
|
this._graph.setNodeAttribute(noteId, 'attachments', attachments);
|
|
653
|
+
this._graph.setNodeAttribute(noteId, 'updatedAt', Date.now());
|
|
654
|
+
this._graph.setNodeAttribute(noteId, 'version', (this._graph.getNodeAttribute(noteId, 'version') ?? 0) + 1);
|
|
631
655
|
this.ctx.markDirty();
|
|
632
656
|
}
|
|
633
657
|
listAttachments(noteId) {
|
package/dist/graphs/skill.js
CHANGED
|
@@ -30,6 +30,9 @@ 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");
|
|
34
|
+
const graph_persistence_1 = require("../lib/graph-persistence");
|
|
35
|
+
const defaults_1 = require("../lib/defaults");
|
|
33
36
|
const attachment_types_1 = require("../graphs/attachment-types");
|
|
34
37
|
const file_import_1 = require("../lib/file-import");
|
|
35
38
|
// ---------------------------------------------------------------------------
|
|
@@ -289,7 +292,7 @@ function getSkill(graph, skillId) {
|
|
|
289
292
|
}
|
|
290
293
|
/** List skills with optional filters. Excludes proxy nodes. */
|
|
291
294
|
function listSkills(graph, opts = {}) {
|
|
292
|
-
const { source, tag, filter, limit =
|
|
295
|
+
const { source, tag, filter, limit = defaults_1.LIST_LIMIT_LARGE } = opts;
|
|
293
296
|
const lowerFilter = filter?.toLowerCase();
|
|
294
297
|
const lowerTag = tag?.toLowerCase();
|
|
295
298
|
const results = [];
|
|
@@ -309,7 +312,7 @@ function listSkills(graph, opts = {}) {
|
|
|
309
312
|
results.push({
|
|
310
313
|
id,
|
|
311
314
|
title: attrs.title,
|
|
312
|
-
description: attrs.description?.slice(0,
|
|
315
|
+
description: attrs.description?.slice(0, defaults_1.CONTENT_PREVIEW_LEN),
|
|
313
316
|
steps: attrs.steps,
|
|
314
317
|
triggers: attrs.triggers,
|
|
315
318
|
inputHints: attrs.inputHints,
|
|
@@ -473,23 +476,35 @@ function saveSkillGraph(graph, graphMemory, embeddingFingerprint) {
|
|
|
473
476
|
fs_1.default.mkdirSync(graphMemory, { recursive: true });
|
|
474
477
|
const file = path_1.default.join(graphMemory, 'skills.json');
|
|
475
478
|
const tmp = file + '.tmp';
|
|
476
|
-
|
|
477
|
-
|
|
479
|
+
try {
|
|
480
|
+
const exported = graph.export();
|
|
481
|
+
(0, embedding_codec_1.compressEmbeddings)(exported);
|
|
482
|
+
fs_1.default.writeFileSync(tmp, JSON.stringify({ embeddingModel: embeddingFingerprint, graph: exported }));
|
|
483
|
+
fs_1.default.renameSync(tmp, file);
|
|
484
|
+
}
|
|
485
|
+
catch (err) {
|
|
486
|
+
try {
|
|
487
|
+
fs_1.default.unlinkSync(tmp);
|
|
488
|
+
}
|
|
489
|
+
catch { /* ignore cleanup error */ }
|
|
490
|
+
throw err;
|
|
491
|
+
}
|
|
478
492
|
}
|
|
479
493
|
function loadSkillGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
480
494
|
const graph = (0, skill_types_1.createSkillGraph)();
|
|
481
495
|
if (fresh)
|
|
482
496
|
return graph;
|
|
483
497
|
const file = path_1.default.join(graphMemory, 'skills.json');
|
|
484
|
-
|
|
498
|
+
const data = (0, graph_persistence_1.readJsonWithTmpFallback)(file);
|
|
499
|
+
if (!data)
|
|
485
500
|
return graph;
|
|
486
501
|
try {
|
|
487
|
-
const data = JSON.parse(fs_1.default.readFileSync(file, 'utf-8'));
|
|
488
502
|
const stored = data.embeddingModel;
|
|
489
503
|
if (embeddingFingerprint && stored !== embeddingFingerprint) {
|
|
490
504
|
process.stderr.write(`[skill-graph] Embedding config changed, re-indexing skill graph\n`);
|
|
491
505
|
return graph;
|
|
492
506
|
}
|
|
507
|
+
(0, embedding_codec_1.decompressEmbeddings)(data.graph);
|
|
493
508
|
graph.import(data.graph);
|
|
494
509
|
process.stderr.write(`[skill-graph] Loaded ${graph.order} nodes, ${graph.size} edges\n`);
|
|
495
510
|
}
|
|
@@ -760,7 +775,9 @@ class SkillGraphManager {
|
|
|
760
775
|
try {
|
|
761
776
|
const proxyNodeId = proxyId(targetGraph, targetId, pid);
|
|
762
777
|
if (this._graph.hasEdge(skillId, proxyNodeId)) {
|
|
763
|
-
|
|
778
|
+
const ek = this._graph.edge(skillId, proxyNodeId);
|
|
779
|
+
if (ek)
|
|
780
|
+
kind = this._graph.getEdgeAttribute(ek, 'kind') ?? '';
|
|
764
781
|
}
|
|
765
782
|
}
|
|
766
783
|
catch { /* ignore */ }
|
|
@@ -788,7 +805,9 @@ class SkillGraphManager {
|
|
|
788
805
|
let kind = '';
|
|
789
806
|
try {
|
|
790
807
|
if (this._graph.hasEdge(fromId, toId)) {
|
|
791
|
-
|
|
808
|
+
const ek = this._graph.edge(fromId, toId);
|
|
809
|
+
if (ek)
|
|
810
|
+
kind = this._graph.getEdgeAttribute(ek, 'kind') ?? '';
|
|
792
811
|
}
|
|
793
812
|
}
|
|
794
813
|
catch { /* ignore */ }
|
|
@@ -812,10 +831,15 @@ class SkillGraphManager {
|
|
|
812
831
|
return null;
|
|
813
832
|
if (!this._graph.hasNode(skillId) || isProxy(this._graph, skillId))
|
|
814
833
|
return null;
|
|
834
|
+
if (data.length > attachment_types_1.MAX_ATTACHMENT_SIZE)
|
|
835
|
+
return null;
|
|
836
|
+
const entityDir = path_1.default.join(dir, skillId);
|
|
837
|
+
const existing = (0, attachment_types_1.scanAttachments)(entityDir);
|
|
838
|
+
if (existing.length >= attachment_types_1.MAX_ATTACHMENTS_PER_ENTITY)
|
|
839
|
+
return null;
|
|
815
840
|
const safe = (0, file_mirror_1.sanitizeFilename)(filename);
|
|
816
841
|
if (!safe)
|
|
817
842
|
return null;
|
|
818
|
-
const entityDir = path_1.default.join(dir, skillId);
|
|
819
843
|
(0, file_mirror_1.writeAttachment)(dir, skillId, safe, data);
|
|
820
844
|
this.mirrorTracker?.recordWrite(path_1.default.join(entityDir, 'attachments', safe));
|
|
821
845
|
(0, file_mirror_1.mirrorAttachmentEvent)(entityDir, 'add', safe);
|
|
@@ -856,6 +880,8 @@ class SkillGraphManager {
|
|
|
856
880
|
return;
|
|
857
881
|
const attachments = (0, attachment_types_1.scanAttachments)(path_1.default.join(dir, skillId));
|
|
858
882
|
this._graph.setNodeAttribute(skillId, 'attachments', attachments);
|
|
883
|
+
this._graph.setNodeAttribute(skillId, 'updatedAt', Date.now());
|
|
884
|
+
this._graph.setNodeAttribute(skillId, 'version', (this._graph.getNodeAttribute(skillId, 'version') ?? 0) + 1);
|
|
859
885
|
this.ctx.markDirty();
|
|
860
886
|
}
|
|
861
887
|
listAttachments(skillId) {
|
package/dist/graphs/task.js
CHANGED
|
@@ -30,6 +30,9 @@ 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");
|
|
34
|
+
const graph_persistence_1 = require("../lib/graph-persistence");
|
|
35
|
+
const defaults_1 = require("../lib/defaults");
|
|
33
36
|
const attachment_types_1 = require("../graphs/attachment-types");
|
|
34
37
|
const file_import_1 = require("../lib/file-import");
|
|
35
38
|
// ---------------------------------------------------------------------------
|
|
@@ -300,7 +303,7 @@ function getTask(graph, taskId) {
|
|
|
300
303
|
}
|
|
301
304
|
/** List tasks with optional filters. Excludes proxy nodes. */
|
|
302
305
|
function listTasks(graph, opts = {}) {
|
|
303
|
-
const { status, priority, tag, filter, assignee, limit =
|
|
306
|
+
const { status, priority, tag, filter, assignee, limit = defaults_1.LIST_LIMIT_LARGE } = opts;
|
|
304
307
|
const lowerFilter = filter?.toLowerCase();
|
|
305
308
|
const lowerTag = tag?.toLowerCase();
|
|
306
309
|
const results = [];
|
|
@@ -324,7 +327,7 @@ function listTasks(graph, opts = {}) {
|
|
|
324
327
|
results.push({
|
|
325
328
|
id,
|
|
326
329
|
title: attrs.title,
|
|
327
|
-
description: attrs.description?.slice(0,
|
|
330
|
+
description: attrs.description?.slice(0, defaults_1.CONTENT_PREVIEW_LEN),
|
|
328
331
|
status: attrs.status,
|
|
329
332
|
priority: attrs.priority,
|
|
330
333
|
tags: attrs.tags,
|
|
@@ -494,23 +497,35 @@ function saveTaskGraph(graph, graphMemory, embeddingFingerprint) {
|
|
|
494
497
|
fs_1.default.mkdirSync(graphMemory, { recursive: true });
|
|
495
498
|
const file = path_1.default.join(graphMemory, 'tasks.json');
|
|
496
499
|
const tmp = file + '.tmp';
|
|
497
|
-
|
|
498
|
-
|
|
500
|
+
try {
|
|
501
|
+
const exported = graph.export();
|
|
502
|
+
(0, embedding_codec_1.compressEmbeddings)(exported);
|
|
503
|
+
fs_1.default.writeFileSync(tmp, JSON.stringify({ embeddingModel: embeddingFingerprint, graph: exported }));
|
|
504
|
+
fs_1.default.renameSync(tmp, file);
|
|
505
|
+
}
|
|
506
|
+
catch (err) {
|
|
507
|
+
try {
|
|
508
|
+
fs_1.default.unlinkSync(tmp);
|
|
509
|
+
}
|
|
510
|
+
catch { /* ignore cleanup error */ }
|
|
511
|
+
throw err;
|
|
512
|
+
}
|
|
499
513
|
}
|
|
500
514
|
function loadTaskGraph(graphMemory, fresh = false, embeddingFingerprint) {
|
|
501
515
|
const graph = (0, task_types_1.createTaskGraph)();
|
|
502
516
|
if (fresh)
|
|
503
517
|
return graph;
|
|
504
518
|
const file = path_1.default.join(graphMemory, 'tasks.json');
|
|
505
|
-
|
|
519
|
+
const data = (0, graph_persistence_1.readJsonWithTmpFallback)(file);
|
|
520
|
+
if (!data)
|
|
506
521
|
return graph;
|
|
507
522
|
try {
|
|
508
|
-
const data = JSON.parse(fs_1.default.readFileSync(file, 'utf-8'));
|
|
509
523
|
const stored = data.embeddingModel;
|
|
510
524
|
if (embeddingFingerprint && stored !== embeddingFingerprint) {
|
|
511
525
|
process.stderr.write(`[task-graph] Embedding config changed, re-indexing task graph\n`);
|
|
512
526
|
return graph;
|
|
513
527
|
}
|
|
528
|
+
(0, embedding_codec_1.decompressEmbeddings)(data.graph);
|
|
514
529
|
graph.import(data.graph);
|
|
515
530
|
process.stderr.write(`[task-graph] Loaded ${graph.order} nodes, ${graph.size} edges\n`);
|
|
516
531
|
}
|
|
@@ -733,7 +748,9 @@ class TaskGraphManager {
|
|
|
733
748
|
try {
|
|
734
749
|
const proxyNodeId = proxyId(targetGraph, targetId, pid);
|
|
735
750
|
if (this._graph.hasEdge(taskId, proxyNodeId)) {
|
|
736
|
-
|
|
751
|
+
const ek = this._graph.edge(taskId, proxyNodeId);
|
|
752
|
+
if (ek)
|
|
753
|
+
kind = this._graph.getEdgeAttribute(ek, 'kind') ?? '';
|
|
737
754
|
}
|
|
738
755
|
}
|
|
739
756
|
catch { /* ignore */ }
|
|
@@ -759,7 +776,9 @@ class TaskGraphManager {
|
|
|
759
776
|
let kind = '';
|
|
760
777
|
try {
|
|
761
778
|
if (this._graph.hasEdge(fromId, toId)) {
|
|
762
|
-
|
|
779
|
+
const ek = this._graph.edge(fromId, toId);
|
|
780
|
+
if (ek)
|
|
781
|
+
kind = this._graph.getEdgeAttribute(ek, 'kind') ?? '';
|
|
763
782
|
}
|
|
764
783
|
}
|
|
765
784
|
catch { /* ignore */ }
|
|
@@ -783,10 +802,15 @@ class TaskGraphManager {
|
|
|
783
802
|
return null;
|
|
784
803
|
if (!this._graph.hasNode(taskId) || isProxy(this._graph, taskId))
|
|
785
804
|
return null;
|
|
805
|
+
if (data.length > attachment_types_1.MAX_ATTACHMENT_SIZE)
|
|
806
|
+
return null;
|
|
807
|
+
const entityDir = path_1.default.join(dir, taskId);
|
|
808
|
+
const existing = (0, attachment_types_1.scanAttachments)(entityDir);
|
|
809
|
+
if (existing.length >= attachment_types_1.MAX_ATTACHMENTS_PER_ENTITY)
|
|
810
|
+
return null;
|
|
786
811
|
const safe = (0, file_mirror_1.sanitizeFilename)(filename);
|
|
787
812
|
if (!safe)
|
|
788
813
|
return null;
|
|
789
|
-
const entityDir = path_1.default.join(dir, taskId);
|
|
790
814
|
(0, file_mirror_1.writeAttachment)(dir, taskId, safe, data);
|
|
791
815
|
this.mirrorTracker?.recordWrite(path_1.default.join(entityDir, 'attachments', safe));
|
|
792
816
|
(0, file_mirror_1.mirrorAttachmentEvent)(entityDir, 'add', safe);
|
|
@@ -827,6 +851,8 @@ class TaskGraphManager {
|
|
|
827
851
|
return;
|
|
828
852
|
const attachments = (0, attachment_types_1.scanAttachments)(path_1.default.join(dir, taskId));
|
|
829
853
|
this._graph.setNodeAttribute(taskId, 'attachments', attachments);
|
|
854
|
+
this._graph.setNodeAttribute(taskId, 'updatedAt', Date.now());
|
|
855
|
+
this._graph.setNodeAttribute(taskId, 'version', (this._graph.getNodeAttribute(taskId, 'version') ?? 0) + 1);
|
|
830
856
|
this.ctx.markDirty();
|
|
831
857
|
}
|
|
832
858
|
listAttachments(taskId) {
|