@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.
- package/LICENSE +15 -0
- package/README.md +216 -0
- package/dist/api/index.js +473 -0
- package/dist/api/rest/code.js +78 -0
- package/dist/api/rest/docs.js +80 -0
- package/dist/api/rest/embed.js +47 -0
- package/dist/api/rest/files.js +64 -0
- package/dist/api/rest/graph.js +71 -0
- package/dist/api/rest/index.js +371 -0
- package/dist/api/rest/knowledge.js +239 -0
- package/dist/api/rest/skills.js +285 -0
- package/dist/api/rest/tasks.js +273 -0
- package/dist/api/rest/tools.js +157 -0
- package/dist/api/rest/validation.js +196 -0
- package/dist/api/rest/websocket.js +71 -0
- package/dist/api/tools/code/get-file-symbols.js +30 -0
- package/dist/api/tools/code/get-symbol.js +22 -0
- package/dist/api/tools/code/list-files.js +18 -0
- package/dist/api/tools/code/search-code.js +27 -0
- package/dist/api/tools/code/search-files.js +22 -0
- package/dist/api/tools/context/get-context.js +19 -0
- package/dist/api/tools/docs/cross-references.js +76 -0
- package/dist/api/tools/docs/explain-symbol.js +55 -0
- package/dist/api/tools/docs/find-examples.js +52 -0
- package/dist/api/tools/docs/get-node.js +24 -0
- package/dist/api/tools/docs/get-toc.js +22 -0
- package/dist/api/tools/docs/list-snippets.js +46 -0
- package/dist/api/tools/docs/list-topics.js +18 -0
- package/dist/api/tools/docs/search-files.js +22 -0
- package/dist/api/tools/docs/search-snippets.js +43 -0
- package/dist/api/tools/docs/search.js +27 -0
- package/dist/api/tools/file-index/get-file-info.js +21 -0
- package/dist/api/tools/file-index/list-all-files.js +28 -0
- package/dist/api/tools/file-index/search-all-files.js +24 -0
- package/dist/api/tools/knowledge/add-attachment.js +31 -0
- package/dist/api/tools/knowledge/create-note.js +20 -0
- package/dist/api/tools/knowledge/create-relation.js +29 -0
- package/dist/api/tools/knowledge/delete-note.js +19 -0
- package/dist/api/tools/knowledge/delete-relation.js +23 -0
- package/dist/api/tools/knowledge/find-linked-notes.js +25 -0
- package/dist/api/tools/knowledge/get-note.js +20 -0
- package/dist/api/tools/knowledge/list-notes.js +18 -0
- package/dist/api/tools/knowledge/list-relations.js +17 -0
- package/dist/api/tools/knowledge/remove-attachment.js +19 -0
- package/dist/api/tools/knowledge/search-notes.js +25 -0
- package/dist/api/tools/knowledge/update-note.js +34 -0
- package/dist/api/tools/skills/add-attachment.js +31 -0
- package/dist/api/tools/skills/bump-usage.js +19 -0
- package/dist/api/tools/skills/create-skill-link.js +25 -0
- package/dist/api/tools/skills/create-skill.js +26 -0
- package/dist/api/tools/skills/delete-skill-link.js +23 -0
- package/dist/api/tools/skills/delete-skill.js +20 -0
- package/dist/api/tools/skills/find-linked-skills.js +25 -0
- package/dist/api/tools/skills/get-skill.js +21 -0
- package/dist/api/tools/skills/link-skill.js +23 -0
- package/dist/api/tools/skills/list-skills.js +20 -0
- package/dist/api/tools/skills/recall-skills.js +18 -0
- package/dist/api/tools/skills/remove-attachment.js +19 -0
- package/dist/api/tools/skills/search-skills.js +25 -0
- package/dist/api/tools/skills/update-skill.js +58 -0
- package/dist/api/tools/tasks/add-attachment.js +31 -0
- package/dist/api/tools/tasks/create-task-link.js +25 -0
- package/dist/api/tools/tasks/create-task.js +26 -0
- package/dist/api/tools/tasks/delete-task-link.js +23 -0
- package/dist/api/tools/tasks/delete-task.js +20 -0
- package/dist/api/tools/tasks/find-linked-tasks.js +25 -0
- package/dist/api/tools/tasks/get-task.js +20 -0
- package/dist/api/tools/tasks/link-task.js +23 -0
- package/dist/api/tools/tasks/list-tasks.js +25 -0
- package/dist/api/tools/tasks/move-task.js +38 -0
- package/dist/api/tools/tasks/remove-attachment.js +19 -0
- package/dist/api/tools/tasks/search-tasks.js +25 -0
- package/dist/api/tools/tasks/update-task.js +58 -0
- package/dist/cli/index.js +617 -0
- package/dist/cli/indexer.js +275 -0
- package/dist/graphs/attachment-types.js +74 -0
- package/dist/graphs/code-types.js +10 -0
- package/dist/graphs/code.js +204 -0
- package/dist/graphs/docs.js +231 -0
- package/dist/graphs/file-index-types.js +10 -0
- package/dist/graphs/file-index.js +310 -0
- package/dist/graphs/file-lang.js +119 -0
- package/dist/graphs/knowledge-types.js +32 -0
- package/dist/graphs/knowledge.js +768 -0
- package/dist/graphs/manager-types.js +87 -0
- package/dist/graphs/skill-types.js +10 -0
- package/dist/graphs/skill.js +1016 -0
- package/dist/graphs/task-types.js +17 -0
- package/dist/graphs/task.js +972 -0
- package/dist/lib/access.js +67 -0
- package/dist/lib/embedder.js +235 -0
- package/dist/lib/events-log.js +401 -0
- package/dist/lib/file-import.js +328 -0
- package/dist/lib/file-mirror.js +461 -0
- package/dist/lib/frontmatter.js +17 -0
- package/dist/lib/jwt.js +146 -0
- package/dist/lib/mirror-watcher.js +637 -0
- package/dist/lib/multi-config.js +393 -0
- package/dist/lib/parsers/code.js +214 -0
- package/dist/lib/parsers/codeblock.js +33 -0
- package/dist/lib/parsers/docs.js +199 -0
- package/dist/lib/parsers/languages/index.js +15 -0
- package/dist/lib/parsers/languages/registry.js +68 -0
- package/dist/lib/parsers/languages/types.js +2 -0
- package/dist/lib/parsers/languages/typescript.js +306 -0
- package/dist/lib/project-manager.js +458 -0
- package/dist/lib/promise-queue.js +22 -0
- package/dist/lib/search/bm25.js +167 -0
- package/dist/lib/search/code.js +103 -0
- package/dist/lib/search/docs.js +106 -0
- package/dist/lib/search/file-index.js +31 -0
- package/dist/lib/search/files.js +61 -0
- package/dist/lib/search/knowledge.js +101 -0
- package/dist/lib/search/skills.js +104 -0
- package/dist/lib/search/tasks.js +103 -0
- package/dist/lib/team.js +89 -0
- package/dist/lib/watcher.js +67 -0
- package/dist/ui/assets/index-D6oxrVF7.js +1759 -0
- package/dist/ui/assets/index-kKd4mVrh.css +1 -0
- package/dist/ui/favicon.svg +1 -0
- package/dist/ui/icons.svg +24 -0
- package/dist/ui/index.html +14 -0
- 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;
|