@graphmemory/server 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +216 -0
  3. package/dist/api/index.js +473 -0
  4. package/dist/api/rest/code.js +78 -0
  5. package/dist/api/rest/docs.js +80 -0
  6. package/dist/api/rest/embed.js +47 -0
  7. package/dist/api/rest/files.js +64 -0
  8. package/dist/api/rest/graph.js +71 -0
  9. package/dist/api/rest/index.js +371 -0
  10. package/dist/api/rest/knowledge.js +239 -0
  11. package/dist/api/rest/skills.js +285 -0
  12. package/dist/api/rest/tasks.js +273 -0
  13. package/dist/api/rest/tools.js +157 -0
  14. package/dist/api/rest/validation.js +196 -0
  15. package/dist/api/rest/websocket.js +71 -0
  16. package/dist/api/tools/code/get-file-symbols.js +30 -0
  17. package/dist/api/tools/code/get-symbol.js +22 -0
  18. package/dist/api/tools/code/list-files.js +18 -0
  19. package/dist/api/tools/code/search-code.js +27 -0
  20. package/dist/api/tools/code/search-files.js +22 -0
  21. package/dist/api/tools/context/get-context.js +19 -0
  22. package/dist/api/tools/docs/cross-references.js +76 -0
  23. package/dist/api/tools/docs/explain-symbol.js +55 -0
  24. package/dist/api/tools/docs/find-examples.js +52 -0
  25. package/dist/api/tools/docs/get-node.js +24 -0
  26. package/dist/api/tools/docs/get-toc.js +22 -0
  27. package/dist/api/tools/docs/list-snippets.js +46 -0
  28. package/dist/api/tools/docs/list-topics.js +18 -0
  29. package/dist/api/tools/docs/search-files.js +22 -0
  30. package/dist/api/tools/docs/search-snippets.js +43 -0
  31. package/dist/api/tools/docs/search.js +27 -0
  32. package/dist/api/tools/file-index/get-file-info.js +21 -0
  33. package/dist/api/tools/file-index/list-all-files.js +28 -0
  34. package/dist/api/tools/file-index/search-all-files.js +24 -0
  35. package/dist/api/tools/knowledge/add-attachment.js +31 -0
  36. package/dist/api/tools/knowledge/create-note.js +20 -0
  37. package/dist/api/tools/knowledge/create-relation.js +29 -0
  38. package/dist/api/tools/knowledge/delete-note.js +19 -0
  39. package/dist/api/tools/knowledge/delete-relation.js +23 -0
  40. package/dist/api/tools/knowledge/find-linked-notes.js +25 -0
  41. package/dist/api/tools/knowledge/get-note.js +20 -0
  42. package/dist/api/tools/knowledge/list-notes.js +18 -0
  43. package/dist/api/tools/knowledge/list-relations.js +17 -0
  44. package/dist/api/tools/knowledge/remove-attachment.js +19 -0
  45. package/dist/api/tools/knowledge/search-notes.js +25 -0
  46. package/dist/api/tools/knowledge/update-note.js +34 -0
  47. package/dist/api/tools/skills/add-attachment.js +31 -0
  48. package/dist/api/tools/skills/bump-usage.js +19 -0
  49. package/dist/api/tools/skills/create-skill-link.js +25 -0
  50. package/dist/api/tools/skills/create-skill.js +26 -0
  51. package/dist/api/tools/skills/delete-skill-link.js +23 -0
  52. package/dist/api/tools/skills/delete-skill.js +20 -0
  53. package/dist/api/tools/skills/find-linked-skills.js +25 -0
  54. package/dist/api/tools/skills/get-skill.js +21 -0
  55. package/dist/api/tools/skills/link-skill.js +23 -0
  56. package/dist/api/tools/skills/list-skills.js +20 -0
  57. package/dist/api/tools/skills/recall-skills.js +18 -0
  58. package/dist/api/tools/skills/remove-attachment.js +19 -0
  59. package/dist/api/tools/skills/search-skills.js +25 -0
  60. package/dist/api/tools/skills/update-skill.js +58 -0
  61. package/dist/api/tools/tasks/add-attachment.js +31 -0
  62. package/dist/api/tools/tasks/create-task-link.js +25 -0
  63. package/dist/api/tools/tasks/create-task.js +26 -0
  64. package/dist/api/tools/tasks/delete-task-link.js +23 -0
  65. package/dist/api/tools/tasks/delete-task.js +20 -0
  66. package/dist/api/tools/tasks/find-linked-tasks.js +25 -0
  67. package/dist/api/tools/tasks/get-task.js +20 -0
  68. package/dist/api/tools/tasks/link-task.js +23 -0
  69. package/dist/api/tools/tasks/list-tasks.js +25 -0
  70. package/dist/api/tools/tasks/move-task.js +38 -0
  71. package/dist/api/tools/tasks/remove-attachment.js +19 -0
  72. package/dist/api/tools/tasks/search-tasks.js +25 -0
  73. package/dist/api/tools/tasks/update-task.js +58 -0
  74. package/dist/cli/index.js +617 -0
  75. package/dist/cli/indexer.js +275 -0
  76. package/dist/graphs/attachment-types.js +74 -0
  77. package/dist/graphs/code-types.js +10 -0
  78. package/dist/graphs/code.js +204 -0
  79. package/dist/graphs/docs.js +231 -0
  80. package/dist/graphs/file-index-types.js +10 -0
  81. package/dist/graphs/file-index.js +310 -0
  82. package/dist/graphs/file-lang.js +119 -0
  83. package/dist/graphs/knowledge-types.js +32 -0
  84. package/dist/graphs/knowledge.js +768 -0
  85. package/dist/graphs/manager-types.js +87 -0
  86. package/dist/graphs/skill-types.js +10 -0
  87. package/dist/graphs/skill.js +1016 -0
  88. package/dist/graphs/task-types.js +17 -0
  89. package/dist/graphs/task.js +972 -0
  90. package/dist/lib/access.js +67 -0
  91. package/dist/lib/embedder.js +235 -0
  92. package/dist/lib/events-log.js +401 -0
  93. package/dist/lib/file-import.js +328 -0
  94. package/dist/lib/file-mirror.js +461 -0
  95. package/dist/lib/frontmatter.js +17 -0
  96. package/dist/lib/jwt.js +146 -0
  97. package/dist/lib/mirror-watcher.js +637 -0
  98. package/dist/lib/multi-config.js +393 -0
  99. package/dist/lib/parsers/code.js +214 -0
  100. package/dist/lib/parsers/codeblock.js +33 -0
  101. package/dist/lib/parsers/docs.js +199 -0
  102. package/dist/lib/parsers/languages/index.js +15 -0
  103. package/dist/lib/parsers/languages/registry.js +68 -0
  104. package/dist/lib/parsers/languages/types.js +2 -0
  105. package/dist/lib/parsers/languages/typescript.js +306 -0
  106. package/dist/lib/project-manager.js +458 -0
  107. package/dist/lib/promise-queue.js +22 -0
  108. package/dist/lib/search/bm25.js +167 -0
  109. package/dist/lib/search/code.js +103 -0
  110. package/dist/lib/search/docs.js +106 -0
  111. package/dist/lib/search/file-index.js +31 -0
  112. package/dist/lib/search/files.js +61 -0
  113. package/dist/lib/search/knowledge.js +101 -0
  114. package/dist/lib/search/skills.js +104 -0
  115. package/dist/lib/search/tasks.js +103 -0
  116. package/dist/lib/team.js +89 -0
  117. package/dist/lib/watcher.js +67 -0
  118. package/dist/ui/assets/index-D6oxrVF7.js +1759 -0
  119. package/dist/ui/assets/index-kKd4mVrh.css +1 -0
  120. package/dist/ui/favicon.svg +1 -0
  121. package/dist/ui/icons.svg +24 -0
  122. package/dist/ui/index.html +14 -0
  123. package/package.json +89 -0
@@ -0,0 +1,275 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createProjectIndexer = createProjectIndexer;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const micromatch_1 = __importDefault(require("micromatch"));
10
+ const embedder_1 = require("../lib/embedder");
11
+ const docs_1 = require("../lib/parsers/docs");
12
+ const docs_2 = require("../graphs/docs");
13
+ const code_1 = require("../lib/parsers/code");
14
+ const code_2 = require("../graphs/code");
15
+ const watcher_1 = require("../lib/watcher");
16
+ const knowledge_1 = require("../graphs/knowledge");
17
+ const task_1 = require("../graphs/task");
18
+ const skill_1 = require("../graphs/skill");
19
+ const file_index_1 = require("../graphs/file-index");
20
+ function createProjectIndexer(docGraph, codeGraph, config, knowledgeGraph, fileIndexGraph, taskGraph, skillGraph) {
21
+ // Three independent serial queues — docs, code, and file index.
22
+ let docsQueue = Promise.resolve();
23
+ let codeQueue = Promise.resolve();
24
+ let fileQueue = Promise.resolve();
25
+ // Error tracking
26
+ let docErrors = 0;
27
+ let codeErrors = 0;
28
+ let fileErrors = 0;
29
+ function enqueueDoc(fn) {
30
+ docsQueue = docsQueue.then(fn).catch((err) => {
31
+ docErrors++;
32
+ process.stderr.write(`[indexer] Doc error: ${err}\n`);
33
+ });
34
+ }
35
+ function enqueueCode(fn) {
36
+ codeQueue = codeQueue.then(fn).catch((err) => {
37
+ codeErrors++;
38
+ process.stderr.write(`[indexer] Code error: ${err}\n`);
39
+ });
40
+ }
41
+ function enqueueFile(fn) {
42
+ fileQueue = fileQueue.then(fn).catch((err) => {
43
+ fileErrors++;
44
+ process.stderr.write(`[indexer] File index error: ${err}\n`);
45
+ });
46
+ }
47
+ // ---------------------------------------------------------------------------
48
+ // Per-file indexing
49
+ // ---------------------------------------------------------------------------
50
+ async function indexDocFile(absolutePath) {
51
+ if (!docGraph)
52
+ return;
53
+ let stat;
54
+ try {
55
+ stat = fs_1.default.statSync(absolutePath);
56
+ }
57
+ catch {
58
+ // File disappeared — remove stale node from graph if present
59
+ const fileId = path_1.default.relative(config.projectDir, absolutePath);
60
+ if (docGraph.hasNode(fileId)) {
61
+ (0, docs_2.removeFile)(docGraph, fileId);
62
+ if (knowledgeGraph)
63
+ (0, knowledge_1.cleanupProxies)(knowledgeGraph, 'docs', docGraph, config.projectId);
64
+ if (taskGraph)
65
+ (0, task_1.cleanupProxies)(taskGraph, 'docs', docGraph, config.projectId);
66
+ if (skillGraph)
67
+ (0, skill_1.cleanupProxies)(skillGraph, 'docs', docGraph, config.projectId);
68
+ }
69
+ return;
70
+ }
71
+ const mtime = stat.mtimeMs;
72
+ const fileId = path_1.default.relative(config.projectDir, absolutePath);
73
+ if ((0, docs_2.getFileMtime)(docGraph, fileId) === mtime)
74
+ return;
75
+ if (config.maxFileSize && stat.size > config.maxFileSize) {
76
+ process.stderr.write(`[indexer] skip doc ${fileId} (${(stat.size / 1024 / 1024).toFixed(1)} MB exceeds limit)\n`);
77
+ return;
78
+ }
79
+ const content = fs_1.default.readFileSync(absolutePath, 'utf-8');
80
+ const chunks = await (0, docs_1.parseFile)(content, absolutePath, config.projectDir, config.chunkDepth);
81
+ // Batch-embed all chunks + file-level in one forward pass
82
+ const batchInputs = chunks.map(c => ({ title: c.title, content: c.content }));
83
+ const rootChunk = chunks.find(c => c.level === 1);
84
+ const embedText = rootChunk?.title
85
+ ? `${fileId} ${rootChunk.title}`
86
+ : `${fileId} ${rootChunk?.content.slice(0, 200) ?? ''}`;
87
+ batchInputs.push({ title: embedText, content: '' });
88
+ const embeddings = await (0, embedder_1.embedBatch)(batchInputs, config.docsModelName);
89
+ for (let i = 0; i < chunks.length; i++) {
90
+ chunks[i].embedding = embeddings[i];
91
+ }
92
+ (0, docs_2.updateFile)(docGraph, chunks, mtime);
93
+ if (rootChunk && docGraph.hasNode(rootChunk.id)) {
94
+ docGraph.setNodeAttribute(rootChunk.id, 'fileEmbedding', embeddings[chunks.length]);
95
+ }
96
+ process.stderr.write(`[indexer] doc ${fileId} (${chunks.length} chunks)\n`);
97
+ }
98
+ async function indexCodeFile(absolutePath) {
99
+ if (!codeGraph)
100
+ return;
101
+ let stat;
102
+ try {
103
+ stat = fs_1.default.statSync(absolutePath);
104
+ }
105
+ catch {
106
+ const fileId = path_1.default.relative(config.projectDir, absolutePath);
107
+ if (codeGraph.hasNode(fileId)) {
108
+ (0, code_2.removeCodeFile)(codeGraph, fileId);
109
+ if (knowledgeGraph)
110
+ (0, knowledge_1.cleanupProxies)(knowledgeGraph, 'code', codeGraph, config.projectId);
111
+ if (taskGraph)
112
+ (0, task_1.cleanupProxies)(taskGraph, 'code', codeGraph, config.projectId);
113
+ if (skillGraph)
114
+ (0, skill_1.cleanupProxies)(skillGraph, 'code', codeGraph, config.projectId);
115
+ }
116
+ return;
117
+ }
118
+ const mtime = stat.mtimeMs;
119
+ const fileId = path_1.default.relative(config.projectDir, absolutePath);
120
+ if ((0, code_2.getCodeFileMtime)(codeGraph, fileId) === mtime)
121
+ return;
122
+ if (config.maxFileSize && stat.size > config.maxFileSize) {
123
+ process.stderr.write(`[indexer] skip code ${fileId} (${(stat.size / 1024 / 1024).toFixed(1)} MB exceeds limit)\n`);
124
+ return;
125
+ }
126
+ const parsed = await (0, code_1.parseCodeFile)(absolutePath, config.projectDir, mtime);
127
+ // Batch-embed all symbols + file-level in one forward pass
128
+ const batchInputs = parsed.nodes.map(({ attrs }) => ({ title: attrs.signature, content: attrs.docComment }));
129
+ batchInputs.push({ title: fileId, content: '' });
130
+ const embeddings = await (0, embedder_1.embedBatch)(batchInputs, config.codeModelName);
131
+ for (let i = 0; i < parsed.nodes.length; i++) {
132
+ parsed.nodes[i].attrs.embedding = embeddings[i];
133
+ }
134
+ (0, code_2.updateCodeFile)(codeGraph, parsed);
135
+ if (codeGraph.hasNode(fileId)) {
136
+ codeGraph.setNodeAttribute(fileId, 'fileEmbedding', embeddings[parsed.nodes.length]);
137
+ }
138
+ process.stderr.write(`[indexer] code ${fileId} (${parsed.nodes.length} symbols)\n`);
139
+ }
140
+ async function indexFileEntry(absolutePath) {
141
+ if (!fileIndexGraph)
142
+ return;
143
+ let stat;
144
+ try {
145
+ stat = fs_1.default.statSync(absolutePath);
146
+ }
147
+ catch {
148
+ const filePath = path_1.default.relative(config.projectDir, absolutePath);
149
+ if (fileIndexGraph.hasNode(filePath)) {
150
+ (0, file_index_1.removeFileEntry)(fileIndexGraph, filePath);
151
+ if (knowledgeGraph)
152
+ (0, knowledge_1.cleanupProxies)(knowledgeGraph, 'files', fileIndexGraph, config.projectId);
153
+ if (taskGraph)
154
+ (0, task_1.cleanupProxies)(taskGraph, 'files', fileIndexGraph, config.projectId);
155
+ if (skillGraph)
156
+ (0, skill_1.cleanupProxies)(skillGraph, 'files', fileIndexGraph, config.projectId);
157
+ }
158
+ return;
159
+ }
160
+ const mtime = stat.mtimeMs;
161
+ const filePath = path_1.default.relative(config.projectDir, absolutePath);
162
+ if ((0, file_index_1.getFileEntryMtime)(fileIndexGraph, filePath) === mtime)
163
+ return;
164
+ const embedding = await (0, embedder_1.embed)(filePath, '', config.filesModelName);
165
+ (0, file_index_1.updateFileEntry)(fileIndexGraph, filePath, stat.size, mtime, embedding);
166
+ }
167
+ // ---------------------------------------------------------------------------
168
+ // Dispatch: match a file against both patterns, enqueue as needed
169
+ // ---------------------------------------------------------------------------
170
+ // Pre-accumulated exclude arrays (already includes server + workspace + project + graph)
171
+ const docsExclude = config.docsExclude;
172
+ const codeExclude = config.codeExclude;
173
+ const filesExclude = config.filesExclude;
174
+ // Union for directory pruning during scan
175
+ const allExcludePatterns = [...new Set([...docsExclude, ...codeExclude, ...filesExclude])];
176
+ function isExcluded(rel, patterns) {
177
+ return patterns.length > 0 && micromatch_1.default.isMatch(rel, patterns);
178
+ }
179
+ function dispatchAdd(absolutePath) {
180
+ const rel = path_1.default.relative(config.projectDir, absolutePath);
181
+ if (config.docsInclude && !isExcluded(rel, docsExclude) && micromatch_1.default.isMatch(rel, config.docsInclude)) {
182
+ enqueueDoc(() => indexDocFile(absolutePath));
183
+ }
184
+ if (codeGraph && config.codeInclude && !isExcluded(rel, codeExclude) && micromatch_1.default.isMatch(rel, config.codeInclude)) {
185
+ enqueueCode(() => indexCodeFile(absolutePath));
186
+ }
187
+ if (fileIndexGraph && !isExcluded(rel, filesExclude)) {
188
+ enqueueFile(() => indexFileEntry(absolutePath));
189
+ }
190
+ }
191
+ function dispatchRemove(absolutePath) {
192
+ const rel = path_1.default.relative(config.projectDir, absolutePath);
193
+ if (docGraph && config.docsInclude && !isExcluded(rel, docsExclude) && micromatch_1.default.isMatch(rel, config.docsInclude)) {
194
+ (0, docs_2.removeFile)(docGraph, rel);
195
+ if (knowledgeGraph)
196
+ (0, knowledge_1.cleanupProxies)(knowledgeGraph, 'docs', docGraph, config.projectId);
197
+ if (taskGraph)
198
+ (0, task_1.cleanupProxies)(taskGraph, 'docs', docGraph, config.projectId);
199
+ if (skillGraph)
200
+ (0, skill_1.cleanupProxies)(skillGraph, 'docs', docGraph, config.projectId);
201
+ process.stderr.write(`[indexer] removed doc ${rel}\n`);
202
+ }
203
+ if (codeGraph && config.codeInclude && !isExcluded(rel, codeExclude) && micromatch_1.default.isMatch(rel, config.codeInclude)) {
204
+ (0, code_2.removeCodeFile)(codeGraph, rel);
205
+ if (knowledgeGraph)
206
+ (0, knowledge_1.cleanupProxies)(knowledgeGraph, 'code', codeGraph, config.projectId);
207
+ if (taskGraph)
208
+ (0, task_1.cleanupProxies)(taskGraph, 'code', codeGraph, config.projectId);
209
+ if (skillGraph)
210
+ (0, skill_1.cleanupProxies)(skillGraph, 'code', codeGraph, config.projectId);
211
+ process.stderr.write(`[indexer] removed code ${rel}\n`);
212
+ }
213
+ if (fileIndexGraph && !isExcluded(rel, filesExclude)) {
214
+ (0, file_index_1.removeFileEntry)(fileIndexGraph, rel);
215
+ if (knowledgeGraph)
216
+ (0, knowledge_1.cleanupProxies)(knowledgeGraph, 'files', fileIndexGraph, config.projectId);
217
+ if (taskGraph)
218
+ (0, task_1.cleanupProxies)(taskGraph, 'files', fileIndexGraph, config.projectId);
219
+ if (skillGraph)
220
+ (0, skill_1.cleanupProxies)(skillGraph, 'files', fileIndexGraph, config.projectId);
221
+ }
222
+ }
223
+ // ---------------------------------------------------------------------------
224
+ // Public API
225
+ // ---------------------------------------------------------------------------
226
+ function scan() {
227
+ function walk(dir) {
228
+ for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
229
+ if (entry.name.startsWith('.'))
230
+ continue;
231
+ const full = path_1.default.join(dir, entry.name);
232
+ if (entry.isDirectory()) {
233
+ const relDir = path_1.default.relative(config.projectDir, full);
234
+ // prune directory if ALL graphs would exclude it
235
+ if (allExcludePatterns.length > 0 && (micromatch_1.default.isMatch(relDir, allExcludePatterns) ||
236
+ micromatch_1.default.isMatch(relDir + '/x', allExcludePatterns)))
237
+ continue;
238
+ walk(full);
239
+ }
240
+ else if (entry.isFile()) {
241
+ dispatchAdd(full);
242
+ }
243
+ }
244
+ }
245
+ walk(config.projectDir);
246
+ }
247
+ function watch() {
248
+ return (0, watcher_1.startWatcher)(config.projectDir, {
249
+ onAdd: (f) => dispatchAdd(f),
250
+ onChange: (f) => dispatchAdd(f),
251
+ onUnlink: (f) => dispatchRemove(f),
252
+ }, '**/*', allExcludePatterns.length > 0 ? allExcludePatterns : undefined);
253
+ }
254
+ async function drain() {
255
+ await Promise.all([docsQueue, codeQueue, fileQueue]);
256
+ if (fileIndexGraph)
257
+ (0, file_index_1.rebuildDirectoryStats)(fileIndexGraph);
258
+ // Resolve cross-file edges that were deferred during indexing
259
+ if (docGraph) {
260
+ const docLinks = (0, docs_2.resolvePendingLinks)(docGraph);
261
+ if (docLinks > 0)
262
+ process.stderr.write(`[indexer] Resolved ${docLinks} deferred doc cross-file link(s)\n`);
263
+ }
264
+ if (codeGraph) {
265
+ const codeImports = (0, code_2.resolvePendingImports)(codeGraph);
266
+ if (codeImports > 0)
267
+ process.stderr.write(`[indexer] Resolved ${codeImports} deferred code import edge(s)\n`);
268
+ }
269
+ const totalErrors = docErrors + codeErrors + fileErrors;
270
+ if (totalErrors > 0) {
271
+ process.stderr.write(`[indexer] Completed with ${totalErrors} error(s): docs=${docErrors}, code=${codeErrors}, files=${fileErrors}\n`);
272
+ }
273
+ }
274
+ return { scan, watch, drain };
275
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.scanAttachments = scanAttachments;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const mime_1 = __importDefault(require("mime"));
43
+ /**
44
+ * Scan the attachments/ subdirectory of an entity directory.
45
+ * Returns metadata for each file found.
46
+ */
47
+ function scanAttachments(entityDir) {
48
+ try {
49
+ const attachmentsDir = path.join(entityDir, 'attachments');
50
+ if (!fs.existsSync(attachmentsDir))
51
+ return [];
52
+ const entries = fs.readdirSync(attachmentsDir, { withFileTypes: true });
53
+ const attachments = [];
54
+ for (const entry of entries) {
55
+ if (!entry.isFile())
56
+ continue;
57
+ const filePath = path.join(attachmentsDir, entry.name);
58
+ try {
59
+ const stat = fs.statSync(filePath);
60
+ attachments.push({
61
+ filename: entry.name,
62
+ mimeType: mime_1.default.getType(entry.name) ?? 'application/octet-stream',
63
+ size: stat.size,
64
+ addedAt: stat.birthtimeMs || stat.mtimeMs,
65
+ });
66
+ }
67
+ catch { /* skip unreadable files */ }
68
+ }
69
+ return attachments;
70
+ }
71
+ catch {
72
+ return [];
73
+ }
74
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createCodeGraph = createCodeGraph;
4
+ const graphology_1 = require("graphology");
5
+ function createCodeGraph() {
6
+ return new graphology_1.DirectedGraph({
7
+ multi: false,
8
+ allowSelfLoops: false,
9
+ });
10
+ }
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CodeGraphManager = exports.createCodeGraph = void 0;
7
+ exports.updateCodeFile = updateCodeFile;
8
+ exports.resolvePendingImports = resolvePendingImports;
9
+ exports.removeCodeFile = removeCodeFile;
10
+ exports.getFileSymbols = getFileSymbols;
11
+ exports.getCodeFileMtime = getCodeFileMtime;
12
+ exports.listCodeFiles = listCodeFiles;
13
+ exports.saveCodeGraph = saveCodeGraph;
14
+ exports.loadCodeGraph = loadCodeGraph;
15
+ const fs_1 = __importDefault(require("fs"));
16
+ const path_1 = __importDefault(require("path"));
17
+ const code_types_1 = require("../graphs/code-types");
18
+ Object.defineProperty(exports, "createCodeGraph", { enumerable: true, get: function () { return code_types_1.createCodeGraph; } });
19
+ const manager_types_1 = require("../graphs/manager-types");
20
+ const code_1 = require("../lib/search/code");
21
+ const files_1 = require("../lib/search/files");
22
+ const bm25_1 = require("../lib/search/bm25");
23
+ // ---------------------------------------------------------------------------
24
+ // CRUD
25
+ // ---------------------------------------------------------------------------
26
+ /** Replace all nodes/edges for a given file. */
27
+ function updateCodeFile(graph, parsed) {
28
+ removeCodeFile(graph, parsed.fileId);
29
+ for (const { id, attrs } of parsed.nodes) {
30
+ graph.addNode(id, attrs);
31
+ }
32
+ const pendingImports = [];
33
+ for (const { from, to, attrs } of parsed.edges) {
34
+ if (!graph.hasNode(to)) {
35
+ if (attrs.kind === 'imports')
36
+ pendingImports.push(to);
37
+ continue;
38
+ }
39
+ if (graph.hasNode(from) && !graph.hasEdge(from, to)) {
40
+ graph.addEdgeWithKey(`${from}→${to}`, from, to, attrs);
41
+ }
42
+ }
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);
46
+ }
47
+ }
48
+ /**
49
+ * Resolve pending import edges after all files have been indexed.
50
+ * Creates 'imports' edges from file nodes to targets that are now in the graph.
51
+ */
52
+ function resolvePendingImports(graph) {
53
+ let created = 0;
54
+ graph.forEachNode((id, attrs) => {
55
+ if (!attrs.pendingImports || attrs.pendingImports.length === 0)
56
+ return;
57
+ const remaining = [];
58
+ for (const targetId of attrs.pendingImports) {
59
+ if (graph.hasNode(targetId) && id !== targetId) {
60
+ const edgeKey = `${id}→${targetId}`;
61
+ if (!graph.hasEdge(edgeKey)) {
62
+ graph.addEdgeWithKey(edgeKey, id, targetId, { kind: 'imports' });
63
+ created++;
64
+ }
65
+ }
66
+ else {
67
+ remaining.push(targetId);
68
+ }
69
+ }
70
+ graph.setNodeAttribute(id, 'pendingImports', remaining.length > 0 ? remaining : undefined);
71
+ });
72
+ return created;
73
+ }
74
+ /** Remove all nodes (and their incident edges) belonging to a file. */
75
+ function removeCodeFile(graph, fileId) {
76
+ const toRemove = graph.filterNodes((_, attrs) => attrs.fileId === fileId);
77
+ toRemove.forEach(id => graph.dropNode(id));
78
+ }
79
+ /** Return all nodes for a file, sorted by startLine. */
80
+ function getFileSymbols(graph, fileId) {
81
+ return graph
82
+ .filterNodes((_, attrs) => attrs.fileId === fileId)
83
+ .map(id => ({ id, ...graph.getNodeAttributes(id) }))
84
+ .sort((a, b) => a.startLine - b.startLine);
85
+ }
86
+ /** Return mtime for any node in the file (0 if not indexed). */
87
+ function getCodeFileMtime(graph, fileId) {
88
+ const nodes = graph.filterNodes((_, attrs) => attrs.fileId === fileId);
89
+ if (nodes.length === 0)
90
+ return 0;
91
+ return graph.getNodeAttribute(nodes[0], 'mtime');
92
+ }
93
+ /** List all indexed files with symbol counts. */
94
+ function listCodeFiles(graph, filter, limit = 20) {
95
+ const files = new Map();
96
+ const lowerFilter = filter?.toLowerCase();
97
+ graph.forEachNode((_, attrs) => {
98
+ files.set(attrs.fileId, (files.get(attrs.fileId) ?? 0) + 1);
99
+ });
100
+ let result = [...files.entries()]
101
+ .map(([fileId, symbolCount]) => ({ fileId, symbolCount }))
102
+ .sort((a, b) => a.fileId.localeCompare(b.fileId));
103
+ if (lowerFilter) {
104
+ result = result.filter(f => f.fileId.toLowerCase().includes(lowerFilter));
105
+ }
106
+ return result.slice(0, limit);
107
+ }
108
+ // ---------------------------------------------------------------------------
109
+ // Persistence
110
+ // ---------------------------------------------------------------------------
111
+ function saveCodeGraph(graph, graphMemory, embeddingFingerprint) {
112
+ fs_1.default.mkdirSync(graphMemory, { recursive: true });
113
+ const file = path_1.default.join(graphMemory, 'code.json');
114
+ const tmp = file + '.tmp';
115
+ fs_1.default.writeFileSync(tmp, JSON.stringify({ embeddingModel: embeddingFingerprint, graph: graph.export() }));
116
+ fs_1.default.renameSync(tmp, file);
117
+ }
118
+ function loadCodeGraph(graphMemory, fresh = false, embeddingFingerprint) {
119
+ const graph = (0, code_types_1.createCodeGraph)();
120
+ if (fresh)
121
+ return graph;
122
+ const file = path_1.default.join(graphMemory, 'code.json');
123
+ if (!fs_1.default.existsSync(file))
124
+ return graph;
125
+ try {
126
+ const data = JSON.parse(fs_1.default.readFileSync(file, 'utf-8'));
127
+ const stored = data.embeddingModel;
128
+ if (embeddingFingerprint && stored !== embeddingFingerprint) {
129
+ process.stderr.write(`[code-graph] Embedding config changed, re-indexing code graph\n`);
130
+ return graph;
131
+ }
132
+ graph.import(data.graph);
133
+ process.stderr.write(`[code-graph] Loaded ${graph.order} nodes, ${graph.size} edges\n`);
134
+ }
135
+ catch (err) {
136
+ process.stderr.write(`[code-graph] Failed to load graph, starting fresh: ${err}\n`);
137
+ }
138
+ return graph;
139
+ }
140
+ // ---------------------------------------------------------------------------
141
+ // CodeGraphManager — unified API for code graph operations
142
+ // ---------------------------------------------------------------------------
143
+ class CodeGraphManager {
144
+ _graph;
145
+ embedFns;
146
+ ext;
147
+ _bm25Index = new bm25_1.BM25Index((attrs) => `${attrs.name} ${attrs.signature} ${attrs.docComment}`);
148
+ constructor(_graph, embedFns, ext = {}) {
149
+ this._graph = _graph;
150
+ this.embedFns = embedFns;
151
+ this.ext = ext;
152
+ _graph.forEachNode((id, attrs) => {
153
+ this._bm25Index.addDocument(id, attrs);
154
+ });
155
+ }
156
+ get graph() { return this._graph; }
157
+ get bm25Index() { return this._bm25Index; }
158
+ // -- Write (used by indexer) --
159
+ updateFile(parsed) {
160
+ // Remove old nodes from BM25
161
+ this._graph.forEachNode((id, attrs) => {
162
+ if (attrs.fileId === parsed.fileId)
163
+ this._bm25Index.removeDocument(id);
164
+ });
165
+ updateCodeFile(this._graph, parsed);
166
+ // Add new nodes to BM25
167
+ this._graph.forEachNode((id, attrs) => {
168
+ if (attrs.fileId === parsed.fileId)
169
+ this._bm25Index.addDocument(id, attrs);
170
+ });
171
+ }
172
+ removeFile(fileId) {
173
+ this._graph.forEachNode((id, attrs) => {
174
+ if (attrs.fileId === fileId)
175
+ this._bm25Index.removeDocument(id);
176
+ });
177
+ removeCodeFile(this._graph, fileId);
178
+ }
179
+ // -- Read --
180
+ listFiles(filter, limit) {
181
+ return listCodeFiles(this._graph, filter, limit);
182
+ }
183
+ getFileSymbols(fileId) {
184
+ return getFileSymbols(this._graph, fileId);
185
+ }
186
+ getFileMtime(fileId) {
187
+ return getCodeFileMtime(this._graph, fileId);
188
+ }
189
+ getSymbol(nodeId) {
190
+ if (!this._graph.hasNode(nodeId))
191
+ return null;
192
+ const crossLinks = (0, manager_types_1.findIncomingCrossLinks)(this.ext, 'code', nodeId);
193
+ return { id: nodeId, ...this._graph.getNodeAttributes(nodeId), ...(crossLinks.length > 0 ? { crossLinks } : {}) };
194
+ }
195
+ async search(query, opts) {
196
+ const embedding = opts?.searchMode === 'keyword' ? [] : await this.embedFns.query(query);
197
+ return (0, code_1.searchCode)(this._graph, embedding, { ...opts, queryText: query, bm25Index: this._bm25Index });
198
+ }
199
+ async searchFiles(query, opts) {
200
+ const embedding = await this.embedFns.query(query);
201
+ return (0, files_1.searchCodeFiles)(this._graph, embedding, opts);
202
+ }
203
+ }
204
+ exports.CodeGraphManager = CodeGraphManager;