@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,199 @@
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.parseFile = parseFile;
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const codeblock_1 = require("../../lib/parsers/codeblock");
10
+ // Parse a markdown file into chunks split by headings
11
+ async function parseFile(content, absolutePath, projectDir, chunkDepth) {
12
+ const fileId = path_1.default.relative(projectDir, absolutePath);
13
+ const lines = content.split('\n');
14
+ const rawChunks = [];
15
+ let current = {
16
+ level: 1,
17
+ title: extractFileTitle(content, absolutePath),
18
+ lines: [],
19
+ };
20
+ for (const line of lines) {
21
+ const heading = matchHeading(line, chunkDepth);
22
+ if (heading) {
23
+ rawChunks.push(current);
24
+ current = { level: heading.level, title: heading.title, lines: [line] };
25
+ }
26
+ else {
27
+ current.lines.push(line);
28
+ }
29
+ }
30
+ rawChunks.push(current);
31
+ // Build Chunk objects
32
+ const seenIds = new Set();
33
+ const textChunks = rawChunks
34
+ .filter(c => c.lines.join('\n').trim() || c.level === 1)
35
+ .map(c => {
36
+ const chunkContent = c.lines.join('\n').trim();
37
+ const baseId = c.level === 1 ? fileId : `${fileId}::${c.title}`;
38
+ let id = baseId;
39
+ let counter = 2;
40
+ while (seenIds.has(id))
41
+ id = `${baseId}::${counter++}`;
42
+ seenIds.add(id);
43
+ return {
44
+ id,
45
+ fileId,
46
+ title: c.title,
47
+ content: chunkContent,
48
+ level: c.level,
49
+ links: extractLinks(chunkContent, absolutePath, projectDir),
50
+ embedding: [], // filled later by embedder
51
+ symbols: [],
52
+ };
53
+ });
54
+ return await spliceCodeBlocks(textChunks, seenIds);
55
+ }
56
+ // --- code block extraction ---
57
+ const FENCE_RE = /^(`{3,}|~{3,})(\S*)\s*\n([\s\S]*?)^\1\s*$/gm;
58
+ async function spliceCodeBlocks(chunks, seenIds) {
59
+ const result = [];
60
+ for (const chunk of chunks) {
61
+ // Skip chunks that are already code blocks (shouldn't happen, but guard)
62
+ if (chunk.language !== undefined) {
63
+ result.push(chunk);
64
+ continue;
65
+ }
66
+ const codeBlocks = [];
67
+ let match;
68
+ FENCE_RE.lastIndex = 0;
69
+ while ((match = FENCE_RE.exec(chunk.content)) !== null) {
70
+ const lang = match[2].toLowerCase();
71
+ const code = match[3].trimEnd();
72
+ if (code)
73
+ codeBlocks.push({ language: lang, code, index: match.index });
74
+ }
75
+ result.push(chunk);
76
+ // Create child chunks for each code block
77
+ let codeIdx = 0;
78
+ for (const cb of codeBlocks) {
79
+ codeIdx++;
80
+ const baseId = `${chunk.id}::code-${codeIdx}`;
81
+ let id = baseId;
82
+ let counter = 2;
83
+ while (seenIds.has(id))
84
+ id = `${baseId}::${counter++}`;
85
+ seenIds.add(id);
86
+ const lang = cb.language || undefined;
87
+ const symbols = lang ? await (0, codeblock_1.extractSymbols)(cb.code, lang) : [];
88
+ result.push({
89
+ id,
90
+ fileId: chunk.fileId,
91
+ title: lang || 'code',
92
+ content: cb.code,
93
+ level: chunk.level + 1,
94
+ links: [],
95
+ embedding: [],
96
+ language: lang,
97
+ symbols,
98
+ });
99
+ }
100
+ }
101
+ return result;
102
+ }
103
+ // --- helpers ---
104
+ function matchHeading(line, maxDepth) {
105
+ const match = line.match(/^(#{1,6})\s+(.+)$/);
106
+ if (!match)
107
+ return null;
108
+ const level = match[1].length;
109
+ if (level < 2 || level > maxDepth)
110
+ return null; // # is file title, not a chunk split
111
+ return { level, title: match[2].trim() };
112
+ }
113
+ function extractFileTitle(content, filePath) {
114
+ const match = content.match(/^#\s+(.+)$/m);
115
+ if (match)
116
+ return match[1].trim();
117
+ return path_1.default.basename(filePath, '.md');
118
+ }
119
+ function extractLinks(content, fromFile, projectDir) {
120
+ const results = new Set();
121
+ const fileDir = path_1.default.dirname(fromFile);
122
+ // [text](./path.md)
123
+ const mdLinks = content.matchAll(/\[[^\]]*\]\(([^)#\s]+)/g);
124
+ for (const [, href] of mdLinks) {
125
+ if (isExternal(href))
126
+ continue;
127
+ const resolved = path_1.default.resolve(fileDir, href);
128
+ const fileId = toFileId(resolved, projectDir);
129
+ if (fileId)
130
+ results.add(fileId);
131
+ }
132
+ // [[wiki link]] or [[wiki link|alias]]
133
+ const wikiLinks = content.matchAll(/\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g);
134
+ for (const [, name] of wikiLinks) {
135
+ const resolved = findWikiFile(name.trim(), projectDir);
136
+ if (!resolved)
137
+ continue;
138
+ const fileId = toFileId(resolved, projectDir); // same guard as md links
139
+ if (fileId)
140
+ results.add(fileId);
141
+ }
142
+ return [...results];
143
+ }
144
+ // Reject anything that looks like an external URL
145
+ function isExternal(href) {
146
+ return /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(href) // http://, ftp://, mailto://, etc.
147
+ || href.startsWith('//') // protocol-relative //cdn.example.com
148
+ || href.startsWith('data:') // data URIs
149
+ || href.startsWith('mailto:');
150
+ }
151
+ function toFileId(absolutePath, projectDir) {
152
+ const rel = path_1.default.relative(projectDir, absolutePath);
153
+ if (rel.startsWith('..') || path_1.default.isAbsolute(rel))
154
+ return null;
155
+ if (fs_1.default.existsSync(absolutePath))
156
+ return rel;
157
+ // try adding .md
158
+ const withMd = absolutePath + '.md';
159
+ if (fs_1.default.existsSync(withMd))
160
+ return path_1.default.relative(projectDir, withMd);
161
+ return null;
162
+ }
163
+ function findWikiFile(name, projectDir) {
164
+ const direct = path_1.default.join(projectDir, name);
165
+ if (fs_1.default.existsSync(direct))
166
+ return direct;
167
+ const withMd = direct + '.md';
168
+ if (fs_1.default.existsSync(withMd))
169
+ return withMd;
170
+ return searchRecursive(name, projectDir);
171
+ }
172
+ const MAX_SEARCH_DEPTH = 10;
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;
199
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerTypescript = exports.initParser = exports.listLanguages = exports.getMapper = exports.parseSource = exports.isLanguageSupported = exports.registerLanguage = void 0;
4
+ var registry_1 = require("./registry");
5
+ Object.defineProperty(exports, "registerLanguage", { enumerable: true, get: function () { return registry_1.registerLanguage; } });
6
+ Object.defineProperty(exports, "isLanguageSupported", { enumerable: true, get: function () { return registry_1.isLanguageSupported; } });
7
+ Object.defineProperty(exports, "parseSource", { enumerable: true, get: function () { return registry_1.parseSource; } });
8
+ Object.defineProperty(exports, "getMapper", { enumerable: true, get: function () { return registry_1.getMapper; } });
9
+ Object.defineProperty(exports, "listLanguages", { enumerable: true, get: function () { return registry_1.listLanguages; } });
10
+ Object.defineProperty(exports, "initParser", { enumerable: true, get: function () { return registry_1.initParser; } });
11
+ var typescript_1 = require("./typescript");
12
+ Object.defineProperty(exports, "registerTypescript", { enumerable: true, get: function () { return typescript_1.registerTypescript; } });
13
+ // Auto-register built-in languages on import
14
+ const typescript_2 = require("./typescript");
15
+ (0, typescript_2.registerTypescript)();
@@ -0,0 +1,68 @@
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.initParser = initParser;
7
+ exports.registerLanguage = registerLanguage;
8
+ exports.isLanguageSupported = isLanguageSupported;
9
+ exports.parseSource = parseSource;
10
+ exports.getMapper = getMapper;
11
+ exports.listLanguages = listLanguages;
12
+ const path_1 = __importDefault(require("path"));
13
+ /** Map from language name (matching file-lang.ts names) to entry. */
14
+ const languages = new Map();
15
+ /** WASM directory containing grammar .wasm files */
16
+ const WASM_DIR = path_1.default.join(path_1.default.dirname(require.resolve('@vscode/tree-sitter-wasm/package.json')), 'wasm');
17
+ let _initPromise = null;
18
+ /** web-tree-sitter module (lazy loaded) */
19
+ let _wts = null;
20
+ /** Initialize the WASM parser runtime. Must be called before parsing. */
21
+ async function initParser() {
22
+ if (_wts)
23
+ return;
24
+ if (_initPromise)
25
+ return _initPromise;
26
+ _initPromise = (async () => {
27
+ _wts = require('web-tree-sitter');
28
+ await _wts.Parser.init();
29
+ })();
30
+ return _initPromise;
31
+ }
32
+ /** Register a language (sync — only stores metadata). */
33
+ function registerLanguage(name, wasmFile, mapper) {
34
+ languages.set(name, { wasmFile, language: null, mapper });
35
+ }
36
+ /** Load a language WASM if not already loaded. */
37
+ async function loadLanguage(entry) {
38
+ if (entry.language)
39
+ return entry.language;
40
+ await initParser();
41
+ const wasmPath = path_1.default.join(WASM_DIR, entry.wasmFile);
42
+ entry.language = await _wts.Language.load(wasmPath);
43
+ return entry.language;
44
+ }
45
+ /** Check if a language is registered. */
46
+ function isLanguageSupported(languageName) {
47
+ return languages.has(languageName);
48
+ }
49
+ /** Parse source code with the appropriate language grammar. Returns root node or null. */
50
+ async function parseSource(code, languageName) {
51
+ const entry = languages.get(languageName);
52
+ if (!entry)
53
+ return null;
54
+ await initParser();
55
+ const lang = await loadLanguage(entry);
56
+ const parser = new _wts.Parser();
57
+ parser.setLanguage(lang);
58
+ const tree = parser.parse(code);
59
+ return tree?.rootNode ?? null;
60
+ }
61
+ /** Get the mapper for a language. Returns undefined for unsupported languages. */
62
+ function getMapper(languageName) {
63
+ return languages.get(languageName)?.mapper;
64
+ }
65
+ /** List all registered language names. */
66
+ function listLanguages() {
67
+ return [...languages.keys()];
68
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,306 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerTypescript = registerTypescript;
4
+ const registry_1 = require("./registry");
5
+ /** Get the previous named sibling that is a JSDoc comment. */
6
+ function getDocComment(node) {
7
+ let prev = node.previousSibling;
8
+ while (prev && prev.type === 'comment' && !prev.text.startsWith('/**')) {
9
+ prev = prev.previousSibling;
10
+ }
11
+ if (prev && prev.type === 'comment' && prev.text.startsWith('/**')) {
12
+ return prev.text.trim();
13
+ }
14
+ return '';
15
+ }
16
+ /** Build a signature: first line of the node text (truncated to 200 chars). */
17
+ function buildSignature(node) {
18
+ const text = node.text ?? '';
19
+ const firstLine = text.split('\n')[0].trim();
20
+ return firstLine.length > 200 ? firstLine.slice(0, 200) + '…' : firstLine;
21
+ }
22
+ /** Build the full body text for a symbol. Includes JSDoc + node text. */
23
+ function buildBody(node, docComment) {
24
+ if (docComment) {
25
+ return docComment + '\n' + node.text;
26
+ }
27
+ return node.text ?? '';
28
+ }
29
+ /** Build a signature that includes the JSDoc first line if present. */
30
+ function buildFullSignature(node, docComment) {
31
+ if (docComment) {
32
+ const firstDocLine = docComment.split('\n')[0].trim();
33
+ return firstDocLine;
34
+ }
35
+ return buildSignature(node);
36
+ }
37
+ /** Check if a node is inside an export_statement. */
38
+ function isExported(node) {
39
+ const parent = node.parent;
40
+ return parent?.type === 'export_statement';
41
+ }
42
+ /** Get the wrapping export_statement if this node is exported, otherwise return the node itself. */
43
+ function getOuterNode(node) {
44
+ const parent = node.parent;
45
+ if (parent?.type === 'export_statement')
46
+ return parent;
47
+ return node;
48
+ }
49
+ /** Convert tree-sitter 0-based row to 1-based line number. */
50
+ function startLine(node) {
51
+ return (node.startPosition?.row ?? 0) + 1;
52
+ }
53
+ function endLine(node) {
54
+ return (node.endPosition?.row ?? 0) + 1;
55
+ }
56
+ // ---------------------------------------------------------------------------
57
+ // Symbol extraction
58
+ // ---------------------------------------------------------------------------
59
+ function extractClassSymbol(node) {
60
+ const outer = getOuterNode(node);
61
+ const doc = getDocComment(outer);
62
+ const name = node.childForFieldName('name')?.text ?? '';
63
+ const body = node.childForFieldName('body');
64
+ // Extract methods
65
+ const children = [];
66
+ if (body) {
67
+ for (const member of body.namedChildren) {
68
+ if (member.type === 'method_definition') {
69
+ const methodName = member.childForFieldName('name')?.text ?? '';
70
+ if (!methodName)
71
+ continue;
72
+ const methodDoc = getDocComment(member);
73
+ children.push({
74
+ name: methodName,
75
+ kind: 'method',
76
+ signature: buildSignature(member),
77
+ docComment: methodDoc,
78
+ body: buildBody(member, methodDoc),
79
+ startLine: startLine(member),
80
+ endLine: endLine(member),
81
+ isExported: false,
82
+ });
83
+ }
84
+ }
85
+ }
86
+ return {
87
+ name,
88
+ kind: 'class',
89
+ signature: buildFullSignature(outer, doc),
90
+ docComment: doc,
91
+ body: buildBody(outer, doc),
92
+ startLine: startLine(outer),
93
+ endLine: endLine(outer),
94
+ isExported: isExported(node),
95
+ children,
96
+ };
97
+ }
98
+ function extractFunctionSymbol(node) {
99
+ const outer = getOuterNode(node);
100
+ const doc = getDocComment(outer);
101
+ const name = node.childForFieldName('name')?.text ?? '';
102
+ return {
103
+ name,
104
+ kind: 'function',
105
+ signature: buildFullSignature(outer, doc),
106
+ docComment: doc,
107
+ body: buildBody(outer, doc),
108
+ startLine: startLine(outer),
109
+ endLine: endLine(outer),
110
+ isExported: isExported(node),
111
+ };
112
+ }
113
+ function extractInterfaceSymbol(node) {
114
+ const outer = getOuterNode(node);
115
+ const doc = getDocComment(outer);
116
+ const name = node.childForFieldName('name')?.text ?? '';
117
+ return {
118
+ name,
119
+ kind: 'interface',
120
+ signature: buildFullSignature(outer, doc),
121
+ docComment: doc,
122
+ body: buildBody(outer, doc),
123
+ startLine: startLine(outer),
124
+ endLine: endLine(outer),
125
+ isExported: isExported(node),
126
+ };
127
+ }
128
+ function extractTypeAliasSymbol(node) {
129
+ const outer = getOuterNode(node);
130
+ const doc = getDocComment(outer);
131
+ const name = node.childForFieldName('name')?.text ?? '';
132
+ return {
133
+ name,
134
+ kind: 'type',
135
+ signature: buildFullSignature(outer, doc),
136
+ docComment: doc,
137
+ body: buildBody(outer, doc),
138
+ startLine: startLine(outer),
139
+ endLine: endLine(outer),
140
+ isExported: isExported(node),
141
+ };
142
+ }
143
+ function extractEnumSymbol(node) {
144
+ const outer = getOuterNode(node);
145
+ const doc = getDocComment(outer);
146
+ const name = node.childForFieldName('name')?.text ?? '';
147
+ return {
148
+ name,
149
+ kind: 'enum',
150
+ signature: buildFullSignature(outer, doc),
151
+ docComment: doc,
152
+ body: buildBody(outer, doc),
153
+ startLine: startLine(outer),
154
+ endLine: endLine(outer),
155
+ isExported: isExported(node),
156
+ };
157
+ }
158
+ function extractVariableSymbols(node, exported) {
159
+ const outer = exported ? node.parent : node;
160
+ const doc = getDocComment(outer);
161
+ const symbols = [];
162
+ // Find all variable_declarator children
163
+ for (const child of node.namedChildren) {
164
+ if (child.type === 'variable_declarator') {
165
+ const name = child.childForFieldName('name')?.text ?? '';
166
+ if (!name)
167
+ continue;
168
+ const value = child.childForFieldName('value');
169
+ const isArrow = value?.type === 'arrow_function' || value?.type === 'function_expression';
170
+ symbols.push({
171
+ name,
172
+ kind: isArrow ? 'function' : 'variable',
173
+ signature: buildFullSignature(outer, doc),
174
+ docComment: doc,
175
+ body: buildBody(outer, doc),
176
+ startLine: startLine(outer),
177
+ endLine: endLine(outer),
178
+ isExported: exported,
179
+ });
180
+ }
181
+ }
182
+ return symbols;
183
+ }
184
+ // ---------------------------------------------------------------------------
185
+ // Main mapper
186
+ // ---------------------------------------------------------------------------
187
+ function processTopLevel(node) {
188
+ switch (node.type) {
189
+ case 'function_declaration': {
190
+ const name = node.childForFieldName('name')?.text;
191
+ if (!name)
192
+ return [];
193
+ return [extractFunctionSymbol(node)];
194
+ }
195
+ case 'class_declaration': {
196
+ const name = node.childForFieldName('name')?.text;
197
+ if (!name)
198
+ return [];
199
+ return [extractClassSymbol(node)];
200
+ }
201
+ case 'interface_declaration': {
202
+ return [extractInterfaceSymbol(node)];
203
+ }
204
+ case 'type_alias_declaration': {
205
+ return [extractTypeAliasSymbol(node)];
206
+ }
207
+ case 'enum_declaration': {
208
+ return [extractEnumSymbol(node)];
209
+ }
210
+ case 'lexical_declaration':
211
+ case 'variable_declaration': {
212
+ return extractVariableSymbols(node, isExported(node));
213
+ }
214
+ case 'export_statement': {
215
+ // Unwrap: export_statement wraps the actual declaration
216
+ for (const child of node.namedChildren) {
217
+ if (child.type === 'comment')
218
+ continue;
219
+ const results = processTopLevel(child);
220
+ if (results.length > 0)
221
+ return results;
222
+ }
223
+ return [];
224
+ }
225
+ default:
226
+ return [];
227
+ }
228
+ }
229
+ const typescriptMapper = {
230
+ extractSymbols(rootNode) {
231
+ const symbols = [];
232
+ for (const child of rootNode.children) {
233
+ symbols.push(...processTopLevel(child));
234
+ }
235
+ return symbols;
236
+ },
237
+ extractEdges(rootNode) {
238
+ const edges = [];
239
+ function findClasses(node) {
240
+ if (node.type === 'class_declaration') {
241
+ const className = node.childForFieldName('name')?.text;
242
+ if (!className)
243
+ return;
244
+ // Look for class_heritage
245
+ for (const child of node.namedChildren) {
246
+ if (child.type === 'class_heritage') {
247
+ for (const clause of child.namedChildren) {
248
+ if (clause.type === 'extends_clause') {
249
+ // First named child is the base class
250
+ for (const c of clause.namedChildren) {
251
+ if (c.type === 'identifier' || c.type === 'type_identifier') {
252
+ edges.push({ fromName: className, toName: c.text, kind: 'extends' });
253
+ break;
254
+ }
255
+ }
256
+ }
257
+ if (clause.type === 'implements_clause') {
258
+ for (const c of clause.namedChildren) {
259
+ if (c.type === 'type_identifier' || c.type === 'identifier') {
260
+ edges.push({ fromName: className, toName: c.text, kind: 'implements' });
261
+ }
262
+ }
263
+ }
264
+ }
265
+ }
266
+ }
267
+ }
268
+ for (const child of node.children) {
269
+ findClasses(child);
270
+ }
271
+ }
272
+ findClasses(rootNode);
273
+ return edges;
274
+ },
275
+ extractImports(rootNode) {
276
+ const imports = [];
277
+ for (const child of rootNode.children) {
278
+ if (child.type === 'import_statement') {
279
+ const source = child.childForFieldName('source');
280
+ if (source) {
281
+ // Remove quotes from string literal
282
+ const specifier = source.text.replace(/^['"]|['"]$/g, '');
283
+ if (specifier.startsWith('./') || specifier.startsWith('../')) {
284
+ imports.push({ specifier });
285
+ }
286
+ }
287
+ }
288
+ }
289
+ return imports;
290
+ },
291
+ };
292
+ // ---------------------------------------------------------------------------
293
+ // Registration
294
+ // ---------------------------------------------------------------------------
295
+ let _registered = false;
296
+ function registerTypescript() {
297
+ if (_registered)
298
+ return;
299
+ _registered = true;
300
+ // TypeScript and TSX share the same mapper
301
+ (0, registry_1.registerLanguage)('typescript', 'tree-sitter-typescript.wasm', typescriptMapper);
302
+ (0, registry_1.registerLanguage)('tsx', 'tree-sitter-tsx.wasm', typescriptMapper);
303
+ // JavaScript and JSX use the same mapper (TS is a superset)
304
+ (0, registry_1.registerLanguage)('javascript', 'tree-sitter-javascript.wasm', typescriptMapper);
305
+ (0, registry_1.registerLanguage)('jsx', 'tree-sitter-javascript.wasm', typescriptMapper);
306
+ }