@graphmemory/server 1.2.0 → 1.3.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 (92) hide show
  1. package/LICENSE +84 -12
  2. package/README.md +7 -1
  3. package/dist/api/index.js +147 -50
  4. package/dist/api/rest/index.js +35 -15
  5. package/dist/api/rest/tools.js +8 -1
  6. package/dist/api/tools/code/search-code.js +12 -9
  7. package/dist/api/tools/code/search-files.js +1 -1
  8. package/dist/api/tools/docs/cross-references.js +3 -2
  9. package/dist/api/tools/docs/explain-symbol.js +2 -1
  10. package/dist/api/tools/docs/find-examples.js +2 -1
  11. package/dist/api/tools/docs/search-files.js +1 -1
  12. package/dist/api/tools/docs/search-snippets.js +1 -1
  13. package/dist/api/tools/docs/search.js +5 -4
  14. package/dist/api/tools/file-index/search-all-files.js +1 -1
  15. package/dist/api/tools/knowledge/add-attachment.js +14 -3
  16. package/dist/api/tools/knowledge/remove-attachment.js +5 -1
  17. package/dist/api/tools/knowledge/search-notes.js +5 -4
  18. package/dist/api/tools/skills/add-attachment.js +14 -3
  19. package/dist/api/tools/skills/recall-skills.js +1 -1
  20. package/dist/api/tools/skills/remove-attachment.js +5 -1
  21. package/dist/api/tools/skills/search-skills.js +6 -5
  22. package/dist/api/tools/tasks/add-attachment.js +14 -3
  23. package/dist/api/tools/tasks/remove-attachment.js +5 -1
  24. package/dist/api/tools/tasks/search-tasks.js +5 -4
  25. package/dist/cli/index.js +61 -51
  26. package/dist/cli/indexer.js +60 -28
  27. package/dist/graphs/code.js +70 -7
  28. package/dist/graphs/docs.js +15 -2
  29. package/dist/graphs/file-index.js +17 -3
  30. package/dist/graphs/file-lang.js +1 -1
  31. package/dist/graphs/knowledge.js +20 -3
  32. package/dist/graphs/skill.js +23 -4
  33. package/dist/graphs/task.js +23 -4
  34. package/dist/lib/embedding-codec.js +65 -0
  35. package/dist/lib/jwt.js +4 -4
  36. package/dist/lib/multi-config.js +6 -1
  37. package/dist/lib/parsers/code.js +158 -31
  38. package/dist/lib/parsers/codeblock.js +11 -6
  39. package/dist/lib/parsers/docs.js +59 -31
  40. package/dist/lib/parsers/languages/registry.js +2 -2
  41. package/dist/lib/parsers/languages/typescript.js +195 -44
  42. package/dist/lib/project-manager.js +14 -10
  43. package/dist/lib/search/bm25.js +18 -1
  44. package/dist/lib/search/code.js +12 -3
  45. package/dist/ui/assets/NoteForm-aZX9f6-3.js +1 -0
  46. package/dist/ui/assets/SkillForm-KYa3o92l.js +1 -0
  47. package/dist/ui/assets/TaskForm-Bl5nkybO.js +1 -0
  48. package/dist/ui/assets/_articleId_-DjbCByxM.js +1 -0
  49. package/dist/ui/assets/_docId_-hdCDjclV.js +1 -0
  50. package/dist/ui/assets/_filePath_-CpG836v4.js +1 -0
  51. package/dist/ui/assets/_noteId_-C1enaQd1.js +1 -0
  52. package/dist/ui/assets/_skillId_-hPoCet7J.js +1 -0
  53. package/dist/ui/assets/_taskId_-DSB3dLVz.js +1 -0
  54. package/dist/ui/assets/_toolName_-3SmCfxZy.js +2 -0
  55. package/dist/ui/assets/api-BMnBjMMf.js +1 -0
  56. package/dist/ui/assets/api-BlFF6gX-.js +1 -0
  57. package/dist/ui/assets/api-CrGJOcaN.js +1 -0
  58. package/dist/ui/assets/api-DuX-0a_X.js +1 -0
  59. package/dist/ui/assets/attachments-CEQ-2nMo.js +1 -0
  60. package/dist/ui/assets/client-Bq88u7gN.js +1 -0
  61. package/dist/ui/assets/docs-CrXsRcOG.js +1 -0
  62. package/dist/ui/assets/edit-BYiy1FZy.js +1 -0
  63. package/dist/ui/assets/edit-TUIIpUMF.js +1 -0
  64. package/dist/ui/assets/edit-hc-ZWz3y.js +1 -0
  65. package/dist/ui/assets/esm-BWiKNcBW.js +1 -0
  66. package/dist/ui/assets/files-0bPg6NH9.js +1 -0
  67. package/dist/ui/assets/graph-DXGud_wF.js +1 -0
  68. package/dist/ui/assets/help-CEMQqZUR.js +891 -0
  69. package/dist/ui/assets/help-DJ52_fxN.js +1 -0
  70. package/dist/ui/assets/index-BCZDAYZi.js +2 -0
  71. package/dist/ui/assets/index-D6zSNtzo.css +1 -0
  72. package/dist/ui/assets/knowledge-DeygeGGH.js +1 -0
  73. package/dist/ui/assets/new-CpD7hOBA.js +1 -0
  74. package/dist/ui/assets/new-DHTg3Dqq.js +1 -0
  75. package/dist/ui/assets/new-s8c0M75X.js +1 -0
  76. package/dist/ui/assets/prompts-BgOmdxgM.js +295 -0
  77. package/dist/ui/assets/rolldown-runtime-Dw2cE7zH.js +1 -0
  78. package/dist/ui/assets/search-EpJhdP2a.js +1 -0
  79. package/dist/ui/assets/skill-y9pizyqE.js +1 -0
  80. package/dist/ui/assets/skills-Cga9iUZN.js +1 -0
  81. package/dist/ui/assets/tasks-CobouTKV.js +1 -0
  82. package/dist/ui/assets/tools-JxKH5BDF.js +1 -0
  83. package/dist/ui/assets/vendor-graph-BWpSgpMe.js +321 -0
  84. package/dist/ui/assets/vendor-markdown-CT8ZVEPu.js +50 -0
  85. package/dist/ui/assets/vendor-md-editor-DmWafJvr.js +44 -0
  86. package/dist/ui/assets/{index-kKd4mVrh.css → vendor-md-editor-HrwGbQou.css} +1 -1
  87. package/dist/ui/assets/vendor-mui-BPj7d3Sw.js +139 -0
  88. package/dist/ui/assets/vendor-mui-icons-B196sG3f.js +1 -0
  89. package/dist/ui/assets/vendor-react-CHUjhoxh.js +11 -0
  90. package/dist/ui/index.html +11 -3
  91. package/package.json +2 -2
  92. package/dist/ui/assets/index-0hRezICt.js +0 -1702
@@ -3,43 +3,157 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.clearPathMappingsCache = clearPathMappingsCache;
6
7
  exports.parseCodeFile = parseCodeFile;
7
8
  const fs_1 = __importDefault(require("fs"));
8
9
  const path_1 = __importDefault(require("path"));
9
10
  const languages_1 = require("../../lib/parsers/languages");
10
11
  const file_lang_1 = require("../../graphs/file-lang");
12
+ // Strip line and block comments from JSONC, preserving string contents.
13
+ function stripJsoncComments(text) {
14
+ let result = '';
15
+ let i = 0;
16
+ while (i < text.length) {
17
+ // String literal — copy verbatim
18
+ if (text[i] === '"') {
19
+ const start = i++;
20
+ while (i < text.length && text[i] !== '"') {
21
+ if (text[i] === '\\')
22
+ i++; // skip escaped char
23
+ i++;
24
+ }
25
+ i++; // closing quote
26
+ result += text.slice(start, i);
27
+ // Line comment
28
+ }
29
+ else if (text[i] === '/' && text[i + 1] === '/') {
30
+ while (i < text.length && text[i] !== '\n')
31
+ i++;
32
+ // Block comment
33
+ }
34
+ else if (text[i] === '/' && text[i + 1] === '*') {
35
+ i += 2;
36
+ while (i < text.length && !(text[i] === '*' && text[i + 1] === '/'))
37
+ i++;
38
+ i += 2;
39
+ }
40
+ else {
41
+ result += text[i++];
42
+ }
43
+ }
44
+ return result;
45
+ }
11
46
  // ---------------------------------------------------------------------------
12
47
  // Import resolution — replaces ts-morph's getModuleSpecifierSourceFile()
13
48
  // ---------------------------------------------------------------------------
14
49
  const RESOLVE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.mjs', '.cjs'];
15
- /** Resolve a relative import specifier to an absolute file path, or null. */
16
- function resolveRelativeImport(fromFile, specifier) {
17
- const dir = path_1.default.dirname(fromFile);
18
- const base = path_1.default.resolve(dir, specifier);
19
- // Exact match (e.g. './foo.ts')
50
+ function hasFile(p) {
51
+ try {
52
+ return fs_1.default.statSync(p).isFile();
53
+ }
54
+ catch {
55
+ return false;
56
+ }
57
+ }
58
+ /** Try resolving a base path with extensions and index files. */
59
+ function tryResolve(base) {
20
60
  if (hasFile(base))
21
61
  return base;
22
- // Try adding extensions (e.g. './foo' → './foo.ts')
23
62
  for (const ext of RESOLVE_EXTENSIONS) {
24
- const candidate = base + ext;
25
- if (hasFile(candidate))
26
- return candidate;
63
+ if (hasFile(base + ext))
64
+ return base + ext;
27
65
  }
28
- // Try index files (e.g. './foo' → './foo/index.ts')
29
66
  for (const ext of RESOLVE_EXTENSIONS) {
30
- const candidate = path_1.default.join(base, 'index' + ext);
31
- if (hasFile(candidate))
32
- return candidate;
67
+ const idx = path_1.default.join(base, 'index' + ext);
68
+ if (hasFile(idx))
69
+ return idx;
33
70
  }
34
71
  return null;
35
72
  }
36
- function hasFile(p) {
37
- try {
38
- return fs_1.default.statSync(p).isFile();
73
+ /** Resolve a relative import specifier to an absolute file path, or null. */
74
+ function resolveRelativeImport(fromFile, specifier) {
75
+ const dir = path_1.default.dirname(fromFile);
76
+ return tryResolve(path_1.default.resolve(dir, specifier));
77
+ }
78
+ /** Cache: directory → parsed path mappings (null = no tsconfig found up to root). */
79
+ const _pathMappings = new Map();
80
+ /** Clear cached path mappings (call between projects or on config change). */
81
+ function clearPathMappingsCache() { _pathMappings.clear(); }
82
+ /**
83
+ * Find the nearest tsconfig.json / jsconfig.json walking up from `dir` to `root`.
84
+ * Cached per directory — each directory remembers its resolved mappings.
85
+ */
86
+ function findPathMappings(dir, root) {
87
+ if (_pathMappings.has(dir))
88
+ return _pathMappings.get(dir);
89
+ // Try this directory
90
+ const result = _parseTsConfig(dir);
91
+ if (result) {
92
+ _pathMappings.set(dir, result);
93
+ return result;
39
94
  }
40
- catch {
41
- return false;
95
+ // Walk up unless we've reached the project root
96
+ const parent = path_1.default.dirname(dir);
97
+ if (dir === root || parent === dir) {
98
+ _pathMappings.set(dir, null);
99
+ return null;
100
+ }
101
+ const parentResult = findPathMappings(parent, root);
102
+ _pathMappings.set(dir, parentResult);
103
+ return parentResult;
104
+ }
105
+ function _parseTsConfig(dir) {
106
+ for (const name of ['tsconfig.json', 'jsconfig.json']) {
107
+ const configPath = path_1.default.join(dir, name);
108
+ if (!hasFile(configPath))
109
+ continue;
110
+ try {
111
+ // Strip JSONC comments while preserving string contents
112
+ const raw = stripJsoncComments(fs_1.default.readFileSync(configPath, 'utf-8'));
113
+ const config = JSON.parse(raw);
114
+ const compilerOptions = config.compilerOptions;
115
+ if (!compilerOptions?.paths)
116
+ continue;
117
+ const baseUrl = compilerOptions.baseUrl
118
+ ? path_1.default.resolve(dir, compilerOptions.baseUrl)
119
+ : dir;
120
+ const mappings = [];
121
+ for (const [pattern, targets] of Object.entries(compilerOptions.paths)) {
122
+ // Pattern like "@/*" → prefix "@/", or "utils/*" → prefix "utils/"
123
+ const prefix = pattern.endsWith('/*') ? pattern.slice(0, -1) : pattern;
124
+ const resolvedTargets = targets
125
+ .map(t => {
126
+ const target = t.endsWith('/*') ? t.slice(0, -1) : t;
127
+ return path_1.default.resolve(baseUrl, target);
128
+ });
129
+ mappings.push({ prefix, targets: resolvedTargets });
130
+ }
131
+ if (mappings.length > 0)
132
+ return mappings;
133
+ }
134
+ catch {
135
+ // Malformed config — skip
136
+ }
137
+ }
138
+ return null;
139
+ }
140
+ /** Resolve a path-aliased import (e.g. @/lib/foo) using nearest tsconfig paths. */
141
+ function resolveAliasImport(specifier, fromFile, projectDir) {
142
+ const fileDir = path_1.default.dirname(fromFile);
143
+ const mappings = findPathMappings(fileDir, projectDir);
144
+ if (!mappings)
145
+ return null;
146
+ for (const mapping of mappings) {
147
+ if (specifier.startsWith(mapping.prefix)) {
148
+ const rest = specifier.slice(mapping.prefix.length);
149
+ for (const targetDir of mapping.targets) {
150
+ const resolved = tryResolve(path_1.default.join(targetDir, rest));
151
+ if (resolved)
152
+ return resolved;
153
+ }
154
+ }
42
155
  }
156
+ return null;
43
157
  }
44
158
  // ---------------------------------------------------------------------------
45
159
  // Main parser
@@ -62,8 +176,8 @@ async function parseCodeFile(absolutePath, codeDir, mtime) {
62
176
  };
63
177
  }
64
178
  const source = fs_1.default.readFileSync(absolutePath, 'utf-8');
65
- const rootNode = await (0, languages_1.parseSource)(source, language);
66
- if (!rootNode) {
179
+ const tree = await (0, languages_1.parseSource)(source, language);
180
+ if (!tree) {
67
181
  return {
68
182
  fileId,
69
183
  mtime,
@@ -74,17 +188,23 @@ async function parseCodeFile(absolutePath, codeDir, mtime) {
74
188
  edges: [],
75
189
  };
76
190
  }
191
+ const rootNode = tree.rootNode;
77
192
  const mapper = (0, languages_1.getMapper)(language);
78
- const symbols = mapper.extractSymbols(rootNode);
79
- const edgeInfos = mapper.extractEdges(rootNode);
80
- const imports = mapper.extractImports(rootNode);
193
+ let symbols, edgeInfos, imports, fileDocComment, importSummary, lastLine;
194
+ try {
195
+ symbols = mapper.extractSymbols(rootNode);
196
+ edgeInfos = mapper.extractEdges(rootNode);
197
+ imports = mapper.extractImports(rootNode);
198
+ fileDocComment = extractFileDocComment(rootNode);
199
+ importSummary = buildImportSummary(rootNode);
200
+ lastLine = (rootNode.endPosition?.row ?? 0) + 1;
201
+ }
202
+ finally {
203
+ tree.delete();
204
+ }
81
205
  const nodes = [];
82
206
  const edges = [];
83
207
  const fileNodeId = fileId;
84
- // --- File root node ---
85
- const fileDocComment = extractFileDocComment(rootNode);
86
- const importSummary = buildImportSummary(rootNode);
87
- const lastLine = (rootNode.endPosition?.row ?? 0) + 1;
88
208
  nodes.push({
89
209
  id: fileNodeId,
90
210
  attrs: makeFileAttrs(fileId, fileDocComment, importSummary, lastLine, mtime),
@@ -147,13 +267,20 @@ async function parseCodeFile(absolutePath, codeDir, mtime) {
147
267
  }
148
268
  // --- Import edges: file → imported file ---
149
269
  for (const imp of imports) {
150
- const targetAbsolute = resolveRelativeImport(absolutePath, imp.specifier);
270
+ let targetAbsolute = null;
271
+ if (imp.specifier.startsWith('./') || imp.specifier.startsWith('../')) {
272
+ // Relative import
273
+ targetAbsolute = resolveRelativeImport(absolutePath, imp.specifier);
274
+ }
275
+ else {
276
+ // Try path alias resolution (e.g. @/lib/foo, ~/utils)
277
+ targetAbsolute = resolveAliasImport(imp.specifier, absolutePath, codeDir);
278
+ }
151
279
  if (!targetAbsolute)
152
280
  continue;
153
- const targetRel = path_1.default.relative(codeDir, targetAbsolute);
154
- if (targetRel.startsWith('..') || path_1.default.isAbsolute(targetRel))
155
- continue;
156
281
  const targetFileId = path_1.default.relative(codeDir, targetAbsolute);
282
+ if (targetFileId.startsWith('..') || path_1.default.isAbsolute(targetFileId))
283
+ continue;
157
284
  if (targetFileId !== fileNodeId) {
158
285
  edges.push({
159
286
  from: fileNodeId,
@@ -6,7 +6,7 @@ const languages_1 = require("../../lib/parsers/languages");
6
6
  const TAG_TO_LANGUAGE = {
7
7
  ts: 'typescript',
8
8
  typescript: 'typescript',
9
- tsx: 'typescript',
9
+ tsx: 'tsx',
10
10
  js: 'javascript',
11
11
  javascript: 'javascript',
12
12
  jsx: 'javascript',
@@ -20,12 +20,17 @@ async function extractSymbols(code, language) {
20
20
  if (!lang || !(0, languages_1.isLanguageSupported)(lang))
21
21
  return [];
22
22
  try {
23
- const rootNode = await (0, languages_1.parseSource)(code, lang);
24
- if (!rootNode)
23
+ const tree = await (0, languages_1.parseSource)(code, lang);
24
+ if (!tree)
25
25
  return [];
26
- const mapper = (0, languages_1.getMapper)(lang);
27
- const symbols = mapper.extractSymbols(rootNode);
28
- return symbols.map(s => s.name).filter(Boolean);
26
+ try {
27
+ const mapper = (0, languages_1.getMapper)(lang);
28
+ const symbols = mapper.extractSymbols(tree.rootNode);
29
+ return symbols.map(s => s.name).filter(Boolean);
30
+ }
31
+ finally {
32
+ tree.delete();
33
+ }
29
34
  }
30
35
  catch {
31
36
  return [];
@@ -4,6 +4,7 @@ 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");
@@ -119,8 +120,10 @@ function extractFileTitle(content, filePath) {
119
120
  function extractLinks(content, fromFile, projectDir) {
120
121
  const results = new Set();
121
122
  const fileDir = path_1.default.dirname(fromFile);
123
+ // Strip fenced code blocks before extracting links (links inside code are not real references)
124
+ const textOnly = content.replace(FENCE_RE, '');
122
125
  // [text](./path.md)
123
- const mdLinks = content.matchAll(/\[[^\]]*\]\(([^)#\s]+)/g);
126
+ const mdLinks = textOnly.matchAll(/\[[^\]]*\]\(([^)#\s]+)/g);
124
127
  for (const [, href] of mdLinks) {
125
128
  if (isExternal(href))
126
129
  continue;
@@ -130,7 +133,7 @@ function extractLinks(content, fromFile, projectDir) {
130
133
  results.add(fileId);
131
134
  }
132
135
  // [[wiki link]] or [[wiki link|alias]]
133
- const wikiLinks = content.matchAll(/\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g);
136
+ const wikiLinks = textOnly.matchAll(/\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g);
134
137
  for (const [, name] of wikiLinks) {
135
138
  const resolved = findWikiFile(name.trim(), projectDir);
136
139
  if (!resolved)
@@ -160,40 +163,65 @@ function toFileId(absolutePath, projectDir) {
160
163
  return path_1.default.relative(projectDir, withMd);
161
164
  return null;
162
165
  }
166
+ /**
167
+ * Cache: projectDir → Map<lowercased basename (without .md), absolute path>.
168
+ * Built once per project via a single recursive walk, then reused for all wiki link lookups.
169
+ */
170
+ const _wikiIndex = new Map();
171
+ /** Clear cached wiki link index (call when files change or between projects). */
172
+ function clearWikiIndexCache(projectDir) {
173
+ if (projectDir)
174
+ _wikiIndex.delete(projectDir);
175
+ else
176
+ _wikiIndex.clear();
177
+ }
178
+ function getWikiIndex(projectDir) {
179
+ if (_wikiIndex.has(projectDir))
180
+ return _wikiIndex.get(projectDir);
181
+ const index = new Map();
182
+ const MAX_DEPTH = 10;
183
+ function walk(dir, depth) {
184
+ if (depth >= MAX_DEPTH)
185
+ return;
186
+ let entries;
187
+ try {
188
+ entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
189
+ }
190
+ catch {
191
+ return;
192
+ }
193
+ for (const entry of entries) {
194
+ if (entry.name.startsWith('.') || entry.isSymbolicLink())
195
+ continue;
196
+ const full = path_1.default.join(dir, entry.name);
197
+ if (entry.isDirectory()) {
198
+ walk(full, depth + 1);
199
+ }
200
+ else if (entry.isFile()) {
201
+ // Index by basename without extension (lowercased)
202
+ const key = path_1.default.basename(entry.name, path_1.default.extname(entry.name)).toLowerCase();
203
+ if (!index.has(key))
204
+ index.set(key, full);
205
+ // Also index by full filename (lowercased)
206
+ const fullKey = entry.name.toLowerCase();
207
+ if (!index.has(fullKey))
208
+ index.set(fullKey, full);
209
+ }
210
+ }
211
+ }
212
+ walk(projectDir, 0);
213
+ _wikiIndex.set(projectDir, index);
214
+ return index;
215
+ }
163
216
  function findWikiFile(name, projectDir) {
217
+ // Direct path check first (fast path)
164
218
  const direct = path_1.default.join(projectDir, name);
165
219
  if (fs_1.default.existsSync(direct))
166
220
  return direct;
167
221
  const withMd = direct + '.md';
168
222
  if (fs_1.default.existsSync(withMd))
169
223
  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;
224
+ // Fall back to cached index lookup
225
+ const index = getWikiIndex(projectDir);
226
+ return index.get(name.toLowerCase()) ?? index.get(name.toLowerCase() + '.md') ?? null;
199
227
  }
@@ -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 root node or null. */
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?.rootNode ?? null;
65
+ return tree ?? null;
66
66
  }
67
67
  /** Get the mapper for a language. Returns undefined for unsupported languages. */
68
68
  function getMapper(languageName) {