@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/lib/parsers/docs.js
CHANGED
|
@@ -4,9 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.parseFile = parseFile;
|
|
7
|
+
exports.clearWikiIndexCache = clearWikiIndexCache;
|
|
7
8
|
const path_1 = __importDefault(require("path"));
|
|
8
9
|
const fs_1 = __importDefault(require("fs"));
|
|
9
10
|
const codeblock_1 = require("../../lib/parsers/codeblock");
|
|
11
|
+
const defaults_1 = require("../../lib/defaults");
|
|
10
12
|
// Parse a markdown file into chunks split by headings
|
|
11
13
|
async function parseFile(content, absolutePath, projectDir, chunkDepth) {
|
|
12
14
|
const fileId = path_1.default.relative(projectDir, absolutePath);
|
|
@@ -119,8 +121,10 @@ function extractFileTitle(content, filePath) {
|
|
|
119
121
|
function extractLinks(content, fromFile, projectDir) {
|
|
120
122
|
const results = new Set();
|
|
121
123
|
const fileDir = path_1.default.dirname(fromFile);
|
|
124
|
+
// Strip fenced code blocks before extracting links (links inside code are not real references)
|
|
125
|
+
const textOnly = content.replace(FENCE_RE, '');
|
|
122
126
|
// [text](./path.md)
|
|
123
|
-
const mdLinks =
|
|
127
|
+
const mdLinks = textOnly.matchAll(/\[[^\]]*\]\(([^)#\s]+)/g);
|
|
124
128
|
for (const [, href] of mdLinks) {
|
|
125
129
|
if (isExternal(href))
|
|
126
130
|
continue;
|
|
@@ -130,7 +134,7 @@ function extractLinks(content, fromFile, projectDir) {
|
|
|
130
134
|
results.add(fileId);
|
|
131
135
|
}
|
|
132
136
|
// [[wiki link]] or [[wiki link|alias]]
|
|
133
|
-
const wikiLinks =
|
|
137
|
+
const wikiLinks = textOnly.matchAll(/\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g);
|
|
134
138
|
for (const [, name] of wikiLinks) {
|
|
135
139
|
const resolved = findWikiFile(name.trim(), projectDir);
|
|
136
140
|
if (!resolved)
|
|
@@ -160,40 +164,65 @@ function toFileId(absolutePath, projectDir) {
|
|
|
160
164
|
return path_1.default.relative(projectDir, withMd);
|
|
161
165
|
return null;
|
|
162
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Cache: projectDir → Map<lowercased basename (without .md), absolute path>.
|
|
169
|
+
* Built once per project via a single recursive walk, then reused for all wiki link lookups.
|
|
170
|
+
*/
|
|
171
|
+
const _wikiIndex = new Map();
|
|
172
|
+
/** Clear cached wiki link index (call when files change or between projects). */
|
|
173
|
+
function clearWikiIndexCache(projectDir) {
|
|
174
|
+
if (projectDir)
|
|
175
|
+
_wikiIndex.delete(projectDir);
|
|
176
|
+
else
|
|
177
|
+
_wikiIndex.clear();
|
|
178
|
+
}
|
|
179
|
+
function getWikiIndex(projectDir) {
|
|
180
|
+
if (_wikiIndex.has(projectDir))
|
|
181
|
+
return _wikiIndex.get(projectDir);
|
|
182
|
+
const index = new Map();
|
|
183
|
+
const MAX_DEPTH = defaults_1.WIKI_MAX_DEPTH;
|
|
184
|
+
function walk(dir, depth) {
|
|
185
|
+
if (depth >= MAX_DEPTH)
|
|
186
|
+
return;
|
|
187
|
+
let entries;
|
|
188
|
+
try {
|
|
189
|
+
entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
for (const entry of entries) {
|
|
195
|
+
if (entry.name.startsWith('.') || entry.isSymbolicLink())
|
|
196
|
+
continue;
|
|
197
|
+
const full = path_1.default.join(dir, entry.name);
|
|
198
|
+
if (entry.isDirectory()) {
|
|
199
|
+
walk(full, depth + 1);
|
|
200
|
+
}
|
|
201
|
+
else if (entry.isFile()) {
|
|
202
|
+
// Index by basename without extension (lowercased)
|
|
203
|
+
const key = path_1.default.basename(entry.name, path_1.default.extname(entry.name)).toLowerCase();
|
|
204
|
+
if (!index.has(key))
|
|
205
|
+
index.set(key, full);
|
|
206
|
+
// Also index by full filename (lowercased)
|
|
207
|
+
const fullKey = entry.name.toLowerCase();
|
|
208
|
+
if (!index.has(fullKey))
|
|
209
|
+
index.set(fullKey, full);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
walk(projectDir, 0);
|
|
214
|
+
_wikiIndex.set(projectDir, index);
|
|
215
|
+
return index;
|
|
216
|
+
}
|
|
163
217
|
function findWikiFile(name, projectDir) {
|
|
218
|
+
// Direct path check first (fast path)
|
|
164
219
|
const direct = path_1.default.join(projectDir, name);
|
|
165
220
|
if (fs_1.default.existsSync(direct))
|
|
166
221
|
return direct;
|
|
167
222
|
const withMd = direct + '.md';
|
|
168
223
|
if (fs_1.default.existsSync(withMd))
|
|
169
224
|
return withMd;
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
function searchRecursive(name, dir, depth = 0) {
|
|
174
|
-
if (depth >= MAX_SEARCH_DEPTH)
|
|
175
|
-
return null;
|
|
176
|
-
let entries;
|
|
177
|
-
try {
|
|
178
|
-
entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
179
|
-
}
|
|
180
|
-
catch {
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
for (const entry of entries) {
|
|
184
|
-
if (entry.name.startsWith('.'))
|
|
185
|
-
continue;
|
|
186
|
-
const full = path_1.default.join(dir, entry.name);
|
|
187
|
-
if (entry.isDirectory()) {
|
|
188
|
-
const found = searchRecursive(name, full, depth + 1);
|
|
189
|
-
if (found)
|
|
190
|
-
return found;
|
|
191
|
-
}
|
|
192
|
-
else if (entry.isFile()) {
|
|
193
|
-
if (entry.name === name || entry.name === `${name}.md` || path_1.default.basename(entry.name, '.md') === name) {
|
|
194
|
-
return full;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
return null;
|
|
225
|
+
// Fall back to cached index lookup
|
|
226
|
+
const index = getWikiIndex(projectDir);
|
|
227
|
+
return index.get(name.toLowerCase()) ?? index.get(name.toLowerCase() + '.md') ?? null;
|
|
199
228
|
}
|
|
@@ -48,7 +48,7 @@ function isLanguageSupported(languageName) {
|
|
|
48
48
|
}
|
|
49
49
|
/** Reusable parser per language (avoids WASM memory leak from creating Parser on every call). */
|
|
50
50
|
const parsers = new Map();
|
|
51
|
-
/** Parse source code with the appropriate language grammar. Returns
|
|
51
|
+
/** Parse source code with the appropriate language grammar. Returns tree (caller must call tree.delete() when done) or null. */
|
|
52
52
|
async function parseSource(code, languageName) {
|
|
53
53
|
const entry = languages.get(languageName);
|
|
54
54
|
if (!entry)
|
|
@@ -62,7 +62,7 @@ async function parseSource(code, languageName) {
|
|
|
62
62
|
parsers.set(languageName, parser);
|
|
63
63
|
}
|
|
64
64
|
const tree = parser.parse(code);
|
|
65
|
-
return tree
|
|
65
|
+
return tree ?? null;
|
|
66
66
|
}
|
|
67
67
|
/** Get the mapper for a language. Returns undefined for unsupported languages. */
|
|
68
68
|
function getMapper(languageName) {
|
|
@@ -2,22 +2,74 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerTypescript = registerTypescript;
|
|
4
4
|
const registry_1 = require("./registry");
|
|
5
|
+
const defaults_1 = require("../../../lib/defaults");
|
|
5
6
|
/** Get the previous named sibling that is a JSDoc comment. */
|
|
6
7
|
function getDocComment(node) {
|
|
7
|
-
let prev = node.
|
|
8
|
+
let prev = node.previousNamedSibling;
|
|
8
9
|
while (prev && prev.type === 'comment' && !prev.text.startsWith('/**')) {
|
|
9
|
-
prev = prev.
|
|
10
|
+
prev = prev.previousNamedSibling;
|
|
10
11
|
}
|
|
11
12
|
if (prev && prev.type === 'comment' && prev.text.startsWith('/**')) {
|
|
12
13
|
return prev.text.trim();
|
|
13
14
|
}
|
|
14
15
|
return '';
|
|
15
16
|
}
|
|
16
|
-
/**
|
|
17
|
-
function
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
/** Collapse whitespace and truncate. */
|
|
18
|
+
function truncate(text, maxLen = defaults_1.SIGNATURE_MAX_LEN) {
|
|
19
|
+
const collapsed = text.replace(/\s+/g, ' ').trim();
|
|
20
|
+
return collapsed.length > maxLen ? collapsed.slice(0, maxLen) + '…' : collapsed;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Slice outerNode.text up to where bodyNode begins, using line-based
|
|
24
|
+
* slicing to avoid tree-sitter byte-offset vs JS char-offset mismatch
|
|
25
|
+
* (startIndex is UTF-8 bytes, String.slice uses UTF-16 code units).
|
|
26
|
+
*/
|
|
27
|
+
function sliceBeforeBody(outerNode, bodyNode) {
|
|
28
|
+
const text = outerNode.text ?? '';
|
|
29
|
+
const outerStartRow = outerNode.startPosition.row;
|
|
30
|
+
const bodyStartRow = bodyNode.startPosition.row;
|
|
31
|
+
if (bodyStartRow > outerStartRow) {
|
|
32
|
+
const lines = text.split('\n');
|
|
33
|
+
const relativeRow = bodyStartRow - outerStartRow;
|
|
34
|
+
const beforeBody = lines.slice(0, relativeRow);
|
|
35
|
+
const bodyLine = lines[relativeRow] ?? '';
|
|
36
|
+
const braceIdx = bodyLine.indexOf('{');
|
|
37
|
+
if (braceIdx >= 0)
|
|
38
|
+
beforeBody.push(bodyLine.slice(0, braceIdx));
|
|
39
|
+
return beforeBody.join('\n');
|
|
40
|
+
}
|
|
41
|
+
const braceIdx = text.indexOf('{');
|
|
42
|
+
if (braceIdx > 0)
|
|
43
|
+
return text.slice(0, braceIdx);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Build signature by taking everything before the body node.
|
|
48
|
+
* Falls back to first line if no body found.
|
|
49
|
+
*/
|
|
50
|
+
function buildSignature(outerNode, innerNode) {
|
|
51
|
+
const bodyNode = innerNode.childForFieldName('body');
|
|
52
|
+
const text = outerNode.text ?? '';
|
|
53
|
+
if (!bodyNode)
|
|
54
|
+
return truncate(text);
|
|
55
|
+
const header = sliceBeforeBody(outerNode, bodyNode);
|
|
56
|
+
return truncate(header ?? text.split('\n')[0]);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Build signature for variable declarations.
|
|
60
|
+
* If value is arrow/function, strip the function body.
|
|
61
|
+
*/
|
|
62
|
+
function buildVariableSignature(outerNode, declarator) {
|
|
63
|
+
const value = declarator.childForFieldName('value');
|
|
64
|
+
if (value) {
|
|
65
|
+
const valueBody = value.childForFieldName('body');
|
|
66
|
+
if (valueBody) {
|
|
67
|
+
const header = sliceBeforeBody(outerNode, valueBody);
|
|
68
|
+
if (header)
|
|
69
|
+
return truncate(header);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return truncate(outerNode.text ?? '');
|
|
21
73
|
}
|
|
22
74
|
/** Build the full body text for a symbol. Includes JSDoc + node text. */
|
|
23
75
|
function buildBody(node, docComment) {
|
|
@@ -26,10 +78,6 @@ function buildBody(node, docComment) {
|
|
|
26
78
|
}
|
|
27
79
|
return node.text ?? '';
|
|
28
80
|
}
|
|
29
|
-
/** Build a signature: always use the code declaration, not JSDoc. */
|
|
30
|
-
function buildFullSignature(node, _docComment) {
|
|
31
|
-
return buildSignature(node);
|
|
32
|
-
}
|
|
33
81
|
/** Check if a node is inside an export_statement. */
|
|
34
82
|
function isExported(node) {
|
|
35
83
|
const parent = node.parent;
|
|
@@ -49,76 +97,171 @@ function startLine(node) {
|
|
|
49
97
|
function endLine(node) {
|
|
50
98
|
return (node.endPosition?.row ?? 0) + 1;
|
|
51
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Extract the base type name from a type node, handling generic types.
|
|
102
|
+
* `Foo` → "Foo", `Foo<T>` (generic_type) → "Foo"
|
|
103
|
+
*/
|
|
104
|
+
function extractTypeName(node) {
|
|
105
|
+
if (node.type === 'identifier' || node.type === 'type_identifier') {
|
|
106
|
+
return node.text;
|
|
107
|
+
}
|
|
108
|
+
if (node.type === 'generic_type') {
|
|
109
|
+
const name = node.childForFieldName('name') ?? node.namedChildren?.[0];
|
|
110
|
+
if (name && (name.type === 'identifier' || name.type === 'type_identifier')) {
|
|
111
|
+
return name.text;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
52
116
|
// ---------------------------------------------------------------------------
|
|
53
117
|
// Symbol extraction
|
|
54
118
|
// ---------------------------------------------------------------------------
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const doc = getDocComment(outer);
|
|
58
|
-
const name = node.childForFieldName('name')?.text ?? '';
|
|
59
|
-
const body = node.childForFieldName('body');
|
|
60
|
-
// Extract methods
|
|
119
|
+
/** Extract class members: methods, fields, getters/setters. */
|
|
120
|
+
function extractClassMembers(body) {
|
|
61
121
|
const children = [];
|
|
62
|
-
if (body)
|
|
63
|
-
|
|
64
|
-
|
|
122
|
+
if (!body)
|
|
123
|
+
return children;
|
|
124
|
+
for (const member of body.namedChildren) {
|
|
125
|
+
switch (member.type) {
|
|
126
|
+
case 'method_definition':
|
|
127
|
+
case 'abstract_method_definition':
|
|
128
|
+
case 'abstract_method_signature': {
|
|
65
129
|
const methodName = member.childForFieldName('name')?.text ?? '';
|
|
66
130
|
if (!methodName)
|
|
67
131
|
continue;
|
|
68
132
|
const methodDoc = getDocComment(member);
|
|
69
133
|
children.push({
|
|
70
134
|
name: methodName,
|
|
71
|
-
kind: 'method',
|
|
72
|
-
signature: buildSignature(member),
|
|
135
|
+
kind: methodName === 'constructor' ? 'constructor' : 'method',
|
|
136
|
+
signature: buildSignature(member, member),
|
|
73
137
|
docComment: methodDoc,
|
|
74
138
|
body: buildBody(member, methodDoc),
|
|
75
139
|
startLine: startLine(member),
|
|
76
140
|
endLine: endLine(member),
|
|
77
141
|
isExported: false,
|
|
78
142
|
});
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case 'public_field_definition':
|
|
146
|
+
case 'property_definition': {
|
|
147
|
+
const fieldName = member.childForFieldName('name')?.text ?? '';
|
|
148
|
+
if (!fieldName)
|
|
149
|
+
continue;
|
|
150
|
+
const fieldDoc = getDocComment(member);
|
|
151
|
+
children.push({
|
|
152
|
+
name: fieldName,
|
|
153
|
+
kind: 'variable',
|
|
154
|
+
signature: truncate(member.text ?? ''),
|
|
155
|
+
docComment: fieldDoc,
|
|
156
|
+
body: buildBody(member, fieldDoc),
|
|
157
|
+
startLine: startLine(member),
|
|
158
|
+
endLine: endLine(member),
|
|
159
|
+
isExported: false,
|
|
160
|
+
});
|
|
161
|
+
break;
|
|
79
162
|
}
|
|
80
163
|
}
|
|
81
164
|
}
|
|
165
|
+
return children;
|
|
166
|
+
}
|
|
167
|
+
function extractClassSymbol(node) {
|
|
168
|
+
const outer = getOuterNode(node);
|
|
169
|
+
const doc = getDocComment(outer);
|
|
170
|
+
const name = node.childForFieldName('name')?.text ?? '';
|
|
171
|
+
const body = node.childForFieldName('body');
|
|
172
|
+
const children = extractClassMembers(body);
|
|
82
173
|
return {
|
|
83
174
|
name,
|
|
84
175
|
kind: 'class',
|
|
85
|
-
signature:
|
|
176
|
+
signature: buildSignature(outer, node),
|
|
86
177
|
docComment: doc,
|
|
87
178
|
body: buildBody(outer, doc),
|
|
88
179
|
startLine: startLine(outer),
|
|
89
180
|
endLine: endLine(outer),
|
|
90
181
|
isExported: isExported(node),
|
|
91
|
-
children,
|
|
182
|
+
children: children.length > 0 ? children : undefined,
|
|
92
183
|
};
|
|
93
184
|
}
|
|
185
|
+
/** Extract nested named function declarations from a function body (1 level deep). */
|
|
186
|
+
function extractNestedFunctions(body) {
|
|
187
|
+
const nested = [];
|
|
188
|
+
if (!body || body.type !== 'statement_block')
|
|
189
|
+
return nested;
|
|
190
|
+
for (const stmt of body.namedChildren) {
|
|
191
|
+
if (stmt.type === 'function_declaration') {
|
|
192
|
+
const childName = stmt.childForFieldName('name')?.text;
|
|
193
|
+
if (!childName)
|
|
194
|
+
continue;
|
|
195
|
+
const childDoc = getDocComment(stmt);
|
|
196
|
+
nested.push({
|
|
197
|
+
name: childName,
|
|
198
|
+
kind: 'function',
|
|
199
|
+
signature: buildSignature(stmt, stmt),
|
|
200
|
+
docComment: childDoc,
|
|
201
|
+
body: buildBody(stmt, childDoc),
|
|
202
|
+
startLine: startLine(stmt),
|
|
203
|
+
endLine: endLine(stmt),
|
|
204
|
+
isExported: false,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return nested;
|
|
209
|
+
}
|
|
94
210
|
function extractFunctionSymbol(node) {
|
|
95
211
|
const outer = getOuterNode(node);
|
|
96
212
|
const doc = getDocComment(outer);
|
|
97
213
|
const name = node.childForFieldName('name')?.text ?? '';
|
|
214
|
+
const body = node.childForFieldName('body');
|
|
215
|
+
const children = extractNestedFunctions(body);
|
|
98
216
|
return {
|
|
99
217
|
name,
|
|
100
218
|
kind: 'function',
|
|
101
|
-
signature:
|
|
219
|
+
signature: buildSignature(outer, node),
|
|
102
220
|
docComment: doc,
|
|
103
221
|
body: buildBody(outer, doc),
|
|
104
222
|
startLine: startLine(outer),
|
|
105
223
|
endLine: endLine(outer),
|
|
106
224
|
isExported: isExported(node),
|
|
225
|
+
children: children.length > 0 ? children : undefined,
|
|
107
226
|
};
|
|
108
227
|
}
|
|
109
228
|
function extractInterfaceSymbol(node) {
|
|
110
229
|
const outer = getOuterNode(node);
|
|
111
230
|
const doc = getDocComment(outer);
|
|
112
231
|
const name = node.childForFieldName('name')?.text ?? '';
|
|
232
|
+
// Extract interface members
|
|
233
|
+
const children = [];
|
|
234
|
+
const body = node.childForFieldName('body');
|
|
235
|
+
if (body) {
|
|
236
|
+
for (const member of body.namedChildren) {
|
|
237
|
+
if (member.type === 'property_signature' || member.type === 'method_signature') {
|
|
238
|
+
const memberName = member.childForFieldName('name')?.text ?? '';
|
|
239
|
+
if (!memberName)
|
|
240
|
+
continue;
|
|
241
|
+
const memberDoc = getDocComment(member);
|
|
242
|
+
children.push({
|
|
243
|
+
name: memberName,
|
|
244
|
+
kind: member.type === 'method_signature' ? 'method' : 'variable',
|
|
245
|
+
signature: truncate(member.text ?? ''),
|
|
246
|
+
docComment: memberDoc,
|
|
247
|
+
body: buildBody(member, memberDoc),
|
|
248
|
+
startLine: startLine(member),
|
|
249
|
+
endLine: endLine(member),
|
|
250
|
+
isExported: false,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
113
255
|
return {
|
|
114
256
|
name,
|
|
115
257
|
kind: 'interface',
|
|
116
|
-
signature:
|
|
258
|
+
signature: buildSignature(outer, node),
|
|
117
259
|
docComment: doc,
|
|
118
260
|
body: buildBody(outer, doc),
|
|
119
261
|
startLine: startLine(outer),
|
|
120
262
|
endLine: endLine(outer),
|
|
121
263
|
isExported: isExported(node),
|
|
264
|
+
children: children.length > 0 ? children : undefined,
|
|
122
265
|
};
|
|
123
266
|
}
|
|
124
267
|
function extractTypeAliasSymbol(node) {
|
|
@@ -128,7 +271,7 @@ function extractTypeAliasSymbol(node) {
|
|
|
128
271
|
return {
|
|
129
272
|
name,
|
|
130
273
|
kind: 'type',
|
|
131
|
-
signature:
|
|
274
|
+
signature: truncate(outer.text ?? ''),
|
|
132
275
|
docComment: doc,
|
|
133
276
|
body: buildBody(outer, doc),
|
|
134
277
|
startLine: startLine(outer),
|
|
@@ -143,7 +286,7 @@ function extractEnumSymbol(node) {
|
|
|
143
286
|
return {
|
|
144
287
|
name,
|
|
145
288
|
kind: 'enum',
|
|
146
|
-
signature:
|
|
289
|
+
signature: buildSignature(outer, node),
|
|
147
290
|
docComment: doc,
|
|
148
291
|
body: buildBody(outer, doc),
|
|
149
292
|
startLine: startLine(outer),
|
|
@@ -155,7 +298,6 @@ function extractVariableSymbols(node, exported) {
|
|
|
155
298
|
const outer = exported ? node.parent : node;
|
|
156
299
|
const doc = getDocComment(outer);
|
|
157
300
|
const symbols = [];
|
|
158
|
-
// Find all variable_declarator children
|
|
159
301
|
for (const child of node.namedChildren) {
|
|
160
302
|
if (child.type === 'variable_declarator') {
|
|
161
303
|
const name = child.childForFieldName('name')?.text ?? '';
|
|
@@ -163,15 +305,24 @@ function extractVariableSymbols(node, exported) {
|
|
|
163
305
|
continue;
|
|
164
306
|
const value = child.childForFieldName('value');
|
|
165
307
|
const isArrow = value?.type === 'arrow_function' || value?.type === 'function_expression';
|
|
308
|
+
// Extract nested named functions from arrow/function body
|
|
309
|
+
let children;
|
|
310
|
+
if (isArrow && value) {
|
|
311
|
+
const fnBody = value.childForFieldName('body');
|
|
312
|
+
const nested = extractNestedFunctions(fnBody);
|
|
313
|
+
if (nested.length > 0)
|
|
314
|
+
children = nested;
|
|
315
|
+
}
|
|
166
316
|
symbols.push({
|
|
167
317
|
name,
|
|
168
318
|
kind: isArrow ? 'function' : 'variable',
|
|
169
|
-
signature:
|
|
319
|
+
signature: buildVariableSignature(outer, child),
|
|
170
320
|
docComment: doc,
|
|
171
321
|
body: buildBody(outer, doc),
|
|
172
322
|
startLine: startLine(outer),
|
|
173
323
|
endLine: endLine(outer),
|
|
174
324
|
isExported: exported,
|
|
325
|
+
children,
|
|
175
326
|
});
|
|
176
327
|
}
|
|
177
328
|
}
|
|
@@ -182,13 +333,15 @@ function extractVariableSymbols(node, exported) {
|
|
|
182
333
|
// ---------------------------------------------------------------------------
|
|
183
334
|
function processTopLevel(node) {
|
|
184
335
|
switch (node.type) {
|
|
185
|
-
case 'function_declaration':
|
|
336
|
+
case 'function_declaration':
|
|
337
|
+
case 'function_signature': {
|
|
186
338
|
const name = node.childForFieldName('name')?.text;
|
|
187
339
|
if (!name)
|
|
188
340
|
return [];
|
|
189
341
|
return [extractFunctionSymbol(node)];
|
|
190
342
|
}
|
|
191
|
-
case 'class_declaration':
|
|
343
|
+
case 'class_declaration':
|
|
344
|
+
case 'abstract_class_declaration': {
|
|
192
345
|
const name = node.childForFieldName('name')?.text;
|
|
193
346
|
if (!name)
|
|
194
347
|
return [];
|
|
@@ -218,6 +371,17 @@ function processTopLevel(node) {
|
|
|
218
371
|
}
|
|
219
372
|
return [];
|
|
220
373
|
}
|
|
374
|
+
case 'ambient_declaration': {
|
|
375
|
+
// declare function/class/interface/etc — unwrap and process inner declaration
|
|
376
|
+
for (const child of node.namedChildren) {
|
|
377
|
+
if (child.type === 'comment')
|
|
378
|
+
continue;
|
|
379
|
+
const results = processTopLevel(child);
|
|
380
|
+
if (results.length > 0)
|
|
381
|
+
return results;
|
|
382
|
+
}
|
|
383
|
+
return [];
|
|
384
|
+
}
|
|
221
385
|
default:
|
|
222
386
|
return [];
|
|
223
387
|
}
|
|
@@ -233,27 +397,27 @@ const typescriptMapper = {
|
|
|
233
397
|
extractEdges(rootNode) {
|
|
234
398
|
const edges = [];
|
|
235
399
|
function findClasses(node) {
|
|
236
|
-
if (node.type === 'class_declaration') {
|
|
400
|
+
if (node.type === 'class_declaration' || node.type === 'abstract_class_declaration') {
|
|
237
401
|
const className = node.childForFieldName('name')?.text;
|
|
238
402
|
if (!className)
|
|
239
403
|
return;
|
|
240
|
-
// Look for class_heritage
|
|
241
404
|
for (const child of node.namedChildren) {
|
|
242
405
|
if (child.type === 'class_heritage') {
|
|
243
406
|
for (const clause of child.namedChildren) {
|
|
244
407
|
if (clause.type === 'extends_clause') {
|
|
245
|
-
// First named child is the base class
|
|
246
408
|
for (const c of clause.namedChildren) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
409
|
+
const typeName = extractTypeName(c);
|
|
410
|
+
if (typeName) {
|
|
411
|
+
edges.push({ fromName: className, toName: typeName, kind: 'extends' });
|
|
412
|
+
break; // Only one base class
|
|
250
413
|
}
|
|
251
414
|
}
|
|
252
415
|
}
|
|
253
416
|
if (clause.type === 'implements_clause') {
|
|
254
417
|
for (const c of clause.namedChildren) {
|
|
255
|
-
|
|
256
|
-
|
|
418
|
+
const typeName = extractTypeName(c);
|
|
419
|
+
if (typeName) {
|
|
420
|
+
edges.push({ fromName: className, toName: typeName, kind: 'implements' });
|
|
257
421
|
}
|
|
258
422
|
}
|
|
259
423
|
}
|
|
@@ -271,14 +435,20 @@ const typescriptMapper = {
|
|
|
271
435
|
extractImports(rootNode) {
|
|
272
436
|
const imports = [];
|
|
273
437
|
for (const child of rootNode.children) {
|
|
438
|
+
// import statements
|
|
274
439
|
if (child.type === 'import_statement') {
|
|
275
440
|
const source = child.childForFieldName('source');
|
|
276
441
|
if (source) {
|
|
277
|
-
// Remove quotes from string literal
|
|
278
442
|
const specifier = source.text.replace(/^['"]|['"]$/g, '');
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
443
|
+
imports.push({ specifier });
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
// Re-exports: export { ... } from '...' and export * from '...'
|
|
447
|
+
if (child.type === 'export_statement') {
|
|
448
|
+
const source = child.childForFieldName('source');
|
|
449
|
+
if (source) {
|
|
450
|
+
const specifier = source.text.replace(/^['"]|['"]$/g, '');
|
|
451
|
+
imports.push({ specifier });
|
|
282
452
|
}
|
|
283
453
|
}
|
|
284
454
|
}
|
|
@@ -293,10 +463,8 @@ function registerTypescript() {
|
|
|
293
463
|
if (_registered)
|
|
294
464
|
return;
|
|
295
465
|
_registered = true;
|
|
296
|
-
// TypeScript and TSX share the same mapper
|
|
297
466
|
(0, registry_1.registerLanguage)('typescript', 'tree-sitter-typescript.wasm', typescriptMapper);
|
|
298
467
|
(0, registry_1.registerLanguage)('tsx', 'tree-sitter-tsx.wasm', typescriptMapper);
|
|
299
|
-
// JavaScript and JSX use the same mapper (TS is a superset)
|
|
300
468
|
(0, registry_1.registerLanguage)('javascript', 'tree-sitter-javascript.wasm', typescriptMapper);
|
|
301
469
|
(0, registry_1.registerLanguage)('jsx', 'tree-sitter-javascript.wasm', typescriptMapper);
|
|
302
470
|
}
|
|
@@ -13,11 +13,14 @@ const file_index_1 = require("../graphs/file-index");
|
|
|
13
13
|
const task_1 = require("../graphs/task");
|
|
14
14
|
const skill_1 = require("../graphs/skill");
|
|
15
15
|
const indexer_1 = require("../cli/indexer");
|
|
16
|
+
const code_2 = require("../lib/parsers/code");
|
|
17
|
+
const docs_2 = require("../lib/parsers/docs");
|
|
16
18
|
const promise_queue_1 = require("../lib/promise-queue");
|
|
17
19
|
const multi_config_1 = require("../lib/multi-config");
|
|
18
20
|
const mirror_watcher_1 = require("../lib/mirror-watcher");
|
|
19
21
|
const team_1 = require("../lib/team");
|
|
20
22
|
const path_1 = __importDefault(require("path"));
|
|
23
|
+
const defaults_1 = require("../lib/defaults");
|
|
21
24
|
// ---------------------------------------------------------------------------
|
|
22
25
|
// ProjectManager
|
|
23
26
|
// ---------------------------------------------------------------------------
|
|
@@ -264,6 +267,9 @@ class ProjectManager extends events_1.EventEmitter {
|
|
|
264
267
|
const instance = this.projects.get(id);
|
|
265
268
|
if (!instance)
|
|
266
269
|
throw new Error(`Project "${id}" not found`);
|
|
270
|
+
// Clear parser caches to prevent cross-project leaks in multi-project mode
|
|
271
|
+
(0, code_2.clearPathMappingsCache)();
|
|
272
|
+
(0, docs_2.clearWikiIndexCache)();
|
|
267
273
|
const gc = instance.config.graphConfigs;
|
|
268
274
|
const indexer = (0, indexer_1.createProjectIndexer)(instance.docGraph, instance.codeGraph, {
|
|
269
275
|
projectId: id,
|
|
@@ -287,17 +293,21 @@ class ProjectManager extends events_1.EventEmitter {
|
|
|
287
293
|
this.saveProject(instance);
|
|
288
294
|
instance.dirty = false;
|
|
289
295
|
// Scan and watch .notes/ and .tasks/ for reverse import (skip for workspace projects — handled by workspace)
|
|
296
|
+
// Skip mirror entirely if knowledge or tasks graph is readonly (mirror requires both)
|
|
290
297
|
if (instance.mirrorTracker && !instance.workspaceId && instance.knowledgeManager && instance.taskManager) {
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
298
|
+
const gc = instance.config.graphConfigs;
|
|
299
|
+
if (!gc.knowledge.readonly && !gc.tasks.readonly) {
|
|
300
|
+
const mirrorConfig = {
|
|
301
|
+
projectDir: instance.config.projectDir,
|
|
302
|
+
knowledgeManager: instance.knowledgeManager,
|
|
303
|
+
taskManager: instance.taskManager,
|
|
304
|
+
skillManager: gc.skills.readonly ? undefined : instance.skillManager,
|
|
305
|
+
mutationQueue: instance.mutationQueue,
|
|
306
|
+
tracker: instance.mirrorTracker,
|
|
307
|
+
};
|
|
308
|
+
await (0, mirror_watcher_1.scanMirrorDirs)(mirrorConfig);
|
|
309
|
+
instance.mirrorWatcher = (0, mirror_watcher_1.startMirrorWatcher)(mirrorConfig);
|
|
310
|
+
}
|
|
301
311
|
}
|
|
302
312
|
this.emit('project:indexed', { projectId: id });
|
|
303
313
|
process.stderr.write(`[project-manager] Project "${id}" indexed\n`);
|
|
@@ -354,7 +364,7 @@ class ProjectManager extends events_1.EventEmitter {
|
|
|
354
364
|
/**
|
|
355
365
|
* Start auto-save interval (every intervalMs, save dirty projects).
|
|
356
366
|
*/
|
|
357
|
-
startAutoSave(intervalMs =
|
|
367
|
+
startAutoSave(intervalMs = defaults_1.AUTO_SAVE_INTERVAL_MS) {
|
|
358
368
|
this.autoSaveInterval = setInterval(() => {
|
|
359
369
|
for (const instance of this.projects.values()) {
|
|
360
370
|
if (instance.dirty) {
|