@prih/mcp-graph-memory 1.0.3

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 (111) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +512 -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/files.js +64 -0
  7. package/dist/api/rest/graph.js +56 -0
  8. package/dist/api/rest/index.js +117 -0
  9. package/dist/api/rest/knowledge.js +238 -0
  10. package/dist/api/rest/skills.js +284 -0
  11. package/dist/api/rest/tasks.js +272 -0
  12. package/dist/api/rest/tools.js +126 -0
  13. package/dist/api/rest/validation.js +191 -0
  14. package/dist/api/rest/websocket.js +65 -0
  15. package/dist/api/tools/code/get-file-symbols.js +30 -0
  16. package/dist/api/tools/code/get-symbol.js +22 -0
  17. package/dist/api/tools/code/list-files.js +18 -0
  18. package/dist/api/tools/code/search-code.js +27 -0
  19. package/dist/api/tools/code/search-files.js +22 -0
  20. package/dist/api/tools/context/get-context.js +19 -0
  21. package/dist/api/tools/docs/cross-references.js +76 -0
  22. package/dist/api/tools/docs/explain-symbol.js +55 -0
  23. package/dist/api/tools/docs/find-examples.js +52 -0
  24. package/dist/api/tools/docs/get-node.js +24 -0
  25. package/dist/api/tools/docs/get-toc.js +22 -0
  26. package/dist/api/tools/docs/list-snippets.js +46 -0
  27. package/dist/api/tools/docs/list-topics.js +18 -0
  28. package/dist/api/tools/docs/search-files.js +22 -0
  29. package/dist/api/tools/docs/search-snippets.js +43 -0
  30. package/dist/api/tools/docs/search.js +27 -0
  31. package/dist/api/tools/file-index/get-file-info.js +21 -0
  32. package/dist/api/tools/file-index/list-all-files.js +28 -0
  33. package/dist/api/tools/file-index/search-all-files.js +24 -0
  34. package/dist/api/tools/knowledge/add-attachment.js +31 -0
  35. package/dist/api/tools/knowledge/create-note.js +20 -0
  36. package/dist/api/tools/knowledge/create-relation.js +29 -0
  37. package/dist/api/tools/knowledge/delete-note.js +19 -0
  38. package/dist/api/tools/knowledge/delete-relation.js +23 -0
  39. package/dist/api/tools/knowledge/find-linked-notes.js +25 -0
  40. package/dist/api/tools/knowledge/get-note.js +20 -0
  41. package/dist/api/tools/knowledge/list-notes.js +18 -0
  42. package/dist/api/tools/knowledge/list-relations.js +17 -0
  43. package/dist/api/tools/knowledge/remove-attachment.js +19 -0
  44. package/dist/api/tools/knowledge/search-notes.js +25 -0
  45. package/dist/api/tools/knowledge/update-note.js +34 -0
  46. package/dist/api/tools/skills/add-attachment.js +31 -0
  47. package/dist/api/tools/skills/bump-usage.js +19 -0
  48. package/dist/api/tools/skills/create-skill-link.js +25 -0
  49. package/dist/api/tools/skills/create-skill.js +26 -0
  50. package/dist/api/tools/skills/delete-skill-link.js +23 -0
  51. package/dist/api/tools/skills/delete-skill.js +20 -0
  52. package/dist/api/tools/skills/find-linked-skills.js +25 -0
  53. package/dist/api/tools/skills/get-skill.js +21 -0
  54. package/dist/api/tools/skills/link-skill.js +23 -0
  55. package/dist/api/tools/skills/list-skills.js +20 -0
  56. package/dist/api/tools/skills/recall-skills.js +18 -0
  57. package/dist/api/tools/skills/remove-attachment.js +19 -0
  58. package/dist/api/tools/skills/search-skills.js +25 -0
  59. package/dist/api/tools/skills/update-skill.js +58 -0
  60. package/dist/api/tools/tasks/add-attachment.js +31 -0
  61. package/dist/api/tools/tasks/create-task-link.js +25 -0
  62. package/dist/api/tools/tasks/create-task.js +25 -0
  63. package/dist/api/tools/tasks/delete-task-link.js +23 -0
  64. package/dist/api/tools/tasks/delete-task.js +20 -0
  65. package/dist/api/tools/tasks/find-linked-tasks.js +25 -0
  66. package/dist/api/tools/tasks/get-task.js +20 -0
  67. package/dist/api/tools/tasks/link-task.js +23 -0
  68. package/dist/api/tools/tasks/list-tasks.js +24 -0
  69. package/dist/api/tools/tasks/move-task.js +38 -0
  70. package/dist/api/tools/tasks/remove-attachment.js +19 -0
  71. package/dist/api/tools/tasks/search-tasks.js +25 -0
  72. package/dist/api/tools/tasks/update-task.js +55 -0
  73. package/dist/cli/index.js +451 -0
  74. package/dist/cli/indexer.js +277 -0
  75. package/dist/graphs/attachment-types.js +74 -0
  76. package/dist/graphs/code-types.js +10 -0
  77. package/dist/graphs/code.js +172 -0
  78. package/dist/graphs/docs.js +198 -0
  79. package/dist/graphs/file-index-types.js +10 -0
  80. package/dist/graphs/file-index.js +310 -0
  81. package/dist/graphs/file-lang.js +119 -0
  82. package/dist/graphs/knowledge-types.js +32 -0
  83. package/dist/graphs/knowledge.js +764 -0
  84. package/dist/graphs/manager-types.js +87 -0
  85. package/dist/graphs/skill-types.js +10 -0
  86. package/dist/graphs/skill.js +1013 -0
  87. package/dist/graphs/task-types.js +17 -0
  88. package/dist/graphs/task.js +960 -0
  89. package/dist/lib/embedder.js +101 -0
  90. package/dist/lib/events-log.js +400 -0
  91. package/dist/lib/file-import.js +327 -0
  92. package/dist/lib/file-mirror.js +446 -0
  93. package/dist/lib/frontmatter.js +17 -0
  94. package/dist/lib/mirror-watcher.js +637 -0
  95. package/dist/lib/multi-config.js +254 -0
  96. package/dist/lib/parsers/code.js +246 -0
  97. package/dist/lib/parsers/codeblock.js +66 -0
  98. package/dist/lib/parsers/docs.js +196 -0
  99. package/dist/lib/project-manager.js +418 -0
  100. package/dist/lib/promise-queue.js +22 -0
  101. package/dist/lib/search/bm25.js +167 -0
  102. package/dist/lib/search/code.js +103 -0
  103. package/dist/lib/search/docs.js +108 -0
  104. package/dist/lib/search/file-index.js +31 -0
  105. package/dist/lib/search/files.js +61 -0
  106. package/dist/lib/search/knowledge.js +101 -0
  107. package/dist/lib/search/skills.js +104 -0
  108. package/dist/lib/search/tasks.js +103 -0
  109. package/dist/lib/watcher.js +67 -0
  110. package/package.json +83 -0
  111. package/ui/README.md +54 -0
@@ -0,0 +1,254 @@
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.GRAPH_NAMES = void 0;
7
+ exports.embeddingFingerprint = embeddingFingerprint;
8
+ exports.formatAuthor = formatAuthor;
9
+ exports.loadMultiConfig = loadMultiConfig;
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const os_1 = __importDefault(require("os"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const yaml_1 = require("yaml");
14
+ const zod_1 = require("zod");
15
+ const HOME = os_1.default.homedir();
16
+ // ---------------------------------------------------------------------------
17
+ // Zod schemas
18
+ // ---------------------------------------------------------------------------
19
+ const authorSchema = zod_1.z.object({
20
+ name: zod_1.z.string(),
21
+ email: zod_1.z.string(),
22
+ });
23
+ const embeddingConfigSchema = zod_1.z.object({
24
+ model: zod_1.z.string(),
25
+ pooling: zod_1.z.enum(['mean', 'cls']).optional(),
26
+ normalize: zod_1.z.boolean().optional(),
27
+ dtype: zod_1.z.string().optional(),
28
+ queryPrefix: zod_1.z.string().optional(),
29
+ documentPrefix: zod_1.z.string().optional(),
30
+ batchSize: zod_1.z.number().int().positive().optional(),
31
+ });
32
+ const graphEmbeddingOverridesSchema = zod_1.z.object({
33
+ docs: embeddingConfigSchema.partial().optional(),
34
+ code: embeddingConfigSchema.partial().optional(),
35
+ knowledge: embeddingConfigSchema.partial().optional(),
36
+ tasks: embeddingConfigSchema.partial().optional(),
37
+ files: embeddingConfigSchema.partial().optional(),
38
+ skills: embeddingConfigSchema.partial().optional(),
39
+ });
40
+ const projectSchema = zod_1.z.object({
41
+ projectDir: zod_1.z.string(),
42
+ graphMemory: zod_1.z.string().optional(),
43
+ docsPattern: zod_1.z.string().optional(),
44
+ codePattern: zod_1.z.string().optional(),
45
+ excludePattern: zod_1.z.string().optional(),
46
+ tsconfig: zod_1.z.string().optional(),
47
+ chunkDepth: zod_1.z.number().int().positive().optional(),
48
+ maxTokensDefault: zod_1.z.number().int().positive().optional(),
49
+ embedMaxChars: zod_1.z.number().int().positive().optional(),
50
+ embedding: embeddingConfigSchema.optional(),
51
+ graphs: graphEmbeddingOverridesSchema.optional(),
52
+ author: authorSchema.optional(),
53
+ });
54
+ const serverSchema = zod_1.z.object({
55
+ host: zod_1.z.string().optional(),
56
+ port: zod_1.z.number().int().positive().optional(),
57
+ sessionTimeout: zod_1.z.number().int().positive().optional(),
58
+ modelsDir: zod_1.z.string().optional(),
59
+ embedding: embeddingConfigSchema.optional(),
60
+ });
61
+ const wsGraphOverridesSchema = zod_1.z.object({
62
+ knowledge: embeddingConfigSchema.partial().optional(),
63
+ tasks: embeddingConfigSchema.partial().optional(),
64
+ skills: embeddingConfigSchema.partial().optional(),
65
+ });
66
+ const workspaceSchema = zod_1.z.object({
67
+ projects: zod_1.z.array(zod_1.z.string()),
68
+ graphMemory: zod_1.z.string().optional(),
69
+ mirrorDir: zod_1.z.string().optional(),
70
+ embedding: embeddingConfigSchema.optional(),
71
+ graphs: wsGraphOverridesSchema.optional(),
72
+ author: authorSchema.optional(),
73
+ });
74
+ const configFileSchema = zod_1.z.object({
75
+ author: authorSchema.optional(),
76
+ server: serverSchema.optional(),
77
+ projects: zod_1.z.record(zod_1.z.string(), projectSchema),
78
+ workspaces: zod_1.z.record(zod_1.z.string(), workspaceSchema).optional(),
79
+ });
80
+ exports.GRAPH_NAMES = ['docs', 'code', 'knowledge', 'tasks', 'files', 'skills'];
81
+ /**
82
+ * Build a stable fingerprint string from embedding config fields that affect stored vectors.
83
+ * Used to detect config changes that require re-indexing.
84
+ *
85
+ * queryPrefix is intentionally excluded: it only affects query-time embeddings,
86
+ * not the stored document vectors — so changing it does not require re-indexing.
87
+ */
88
+ function embeddingFingerprint(config) {
89
+ return `${config.model}|${config.pooling}|${config.normalize}|${config.dtype ?? ''}|${config.documentPrefix}`;
90
+ }
91
+ // ---------------------------------------------------------------------------
92
+ // Helpers
93
+ // ---------------------------------------------------------------------------
94
+ const AUTHOR_DEFAULT = { name: '', email: '' };
95
+ /**
96
+ * Format an author as a git-style string: "Name <email>".
97
+ * Returns empty string if name is not set.
98
+ */
99
+ function formatAuthor(author) {
100
+ if (!author.name)
101
+ return '';
102
+ return `${author.name} <${author.email}>`;
103
+ }
104
+ // ---------------------------------------------------------------------------
105
+ // Defaults
106
+ // ---------------------------------------------------------------------------
107
+ const EMBEDDING_DEFAULTS = {
108
+ model: 'Xenova/bge-m3',
109
+ pooling: 'cls',
110
+ normalize: true,
111
+ queryPrefix: '',
112
+ documentPrefix: '',
113
+ batchSize: 1,
114
+ };
115
+ const SERVER_DEFAULTS = {
116
+ host: '127.0.0.1',
117
+ port: 3000,
118
+ sessionTimeout: 1800,
119
+ modelsDir: path_1.default.join(HOME, '.graph-memory/models'),
120
+ embedding: EMBEDDING_DEFAULTS,
121
+ };
122
+ const PROJECT_DEFAULTS = {
123
+ docsPattern: '**/*.md',
124
+ codePattern: '**/*.{js,ts,jsx,tsx}',
125
+ excludePattern: 'node_modules/**',
126
+ chunkDepth: 4,
127
+ maxTokensDefault: 4000,
128
+ embedMaxChars: 2000,
129
+ };
130
+ // ---------------------------------------------------------------------------
131
+ // Resolution
132
+ // ---------------------------------------------------------------------------
133
+ /**
134
+ * Merge embedding configs with inheritance:
135
+ * override fields → base fields → defaults.
136
+ */
137
+ function mergeEmbeddingConfig(base, override) {
138
+ if (!override)
139
+ return base;
140
+ return {
141
+ model: override.model ?? base.model,
142
+ pooling: override.pooling ?? base.pooling,
143
+ normalize: override.normalize ?? base.normalize,
144
+ dtype: override.dtype ?? base.dtype,
145
+ queryPrefix: override.queryPrefix ?? base.queryPrefix,
146
+ documentPrefix: override.documentPrefix ?? base.documentPrefix,
147
+ batchSize: override.batchSize ?? base.batchSize,
148
+ };
149
+ }
150
+ /**
151
+ * Resolve a full EmbeddingConfig from the Zod-parsed raw embedding object,
152
+ * falling back to defaults for missing fields.
153
+ */
154
+ function resolveEmbeddingConfig(raw, fallback) {
155
+ if (!raw)
156
+ return fallback;
157
+ return {
158
+ model: raw.model,
159
+ pooling: raw.pooling ?? fallback.pooling,
160
+ normalize: raw.normalize ?? fallback.normalize,
161
+ dtype: raw.dtype ?? fallback.dtype,
162
+ queryPrefix: raw.queryPrefix ?? fallback.queryPrefix,
163
+ documentPrefix: raw.documentPrefix ?? fallback.documentPrefix,
164
+ batchSize: raw.batchSize ?? fallback.batchSize,
165
+ };
166
+ }
167
+ // ---------------------------------------------------------------------------
168
+ // Load
169
+ // ---------------------------------------------------------------------------
170
+ /**
171
+ * Load and validate a `graph-memory.yaml` config file.
172
+ * Resolves all paths to absolute, applies defaults.
173
+ */
174
+ function loadMultiConfig(yamlPath) {
175
+ const raw = fs_1.default.readFileSync(yamlPath, 'utf-8');
176
+ const parsed = (0, yaml_1.parse)(raw);
177
+ const validated = configFileSchema.parse(parsed);
178
+ const srv = validated.server ?? {};
179
+ const globalAuthor = validated.author ?? AUTHOR_DEFAULT;
180
+ const globalEmbedding = resolveEmbeddingConfig(srv.embedding, EMBEDDING_DEFAULTS);
181
+ const server = {
182
+ host: srv.host ?? SERVER_DEFAULTS.host,
183
+ port: srv.port ?? SERVER_DEFAULTS.port,
184
+ sessionTimeout: srv.sessionTimeout ?? SERVER_DEFAULTS.sessionTimeout,
185
+ modelsDir: path_1.default.resolve(srv.modelsDir ?? SERVER_DEFAULTS.modelsDir),
186
+ embedding: globalEmbedding,
187
+ };
188
+ const projects = new Map();
189
+ for (const [id, raw] of Object.entries(validated.projects)) {
190
+ const projectDir = path_1.default.resolve(raw.projectDir);
191
+ const graphMemory = raw.graphMemory
192
+ ? path_1.default.resolve(projectDir, raw.graphMemory)
193
+ : path_1.default.join(projectDir, '.graph-memory');
194
+ // Project-level embedding (inherits from server)
195
+ const projectEmbedding = resolveEmbeddingConfig(raw.embedding, globalEmbedding);
196
+ // Per-graph embeddings (inherit from project)
197
+ const graphOverrides = raw.graphs ?? {};
198
+ const graphEmbeddings = {};
199
+ for (const gn of exports.GRAPH_NAMES) {
200
+ graphEmbeddings[gn] = mergeEmbeddingConfig(projectEmbedding, graphOverrides[gn]);
201
+ }
202
+ projects.set(id, {
203
+ projectDir,
204
+ graphMemory,
205
+ docsPattern: raw.docsPattern ?? PROJECT_DEFAULTS.docsPattern,
206
+ codePattern: raw.codePattern ?? PROJECT_DEFAULTS.codePattern,
207
+ excludePattern: raw.excludePattern ?? PROJECT_DEFAULTS.excludePattern,
208
+ tsconfig: raw.tsconfig,
209
+ chunkDepth: raw.chunkDepth ?? PROJECT_DEFAULTS.chunkDepth,
210
+ maxTokensDefault: raw.maxTokensDefault ?? PROJECT_DEFAULTS.maxTokensDefault,
211
+ embedMaxChars: raw.embedMaxChars ?? PROJECT_DEFAULTS.embedMaxChars,
212
+ embedding: projectEmbedding,
213
+ graphEmbeddings,
214
+ author: raw.author ?? globalAuthor,
215
+ });
216
+ }
217
+ // --- Workspaces ---
218
+ const workspaces = new Map();
219
+ if (validated.workspaces) {
220
+ for (const [wsId, raw] of Object.entries(validated.workspaces)) {
221
+ // Validate that all referenced projects exist
222
+ for (const projId of raw.projects) {
223
+ if (!projects.has(projId)) {
224
+ throw new Error(`Workspace "${wsId}" references unknown project "${projId}"`);
225
+ }
226
+ }
227
+ const firstProject = projects.get(raw.projects[0]);
228
+ const graphMemory = raw.graphMemory
229
+ ? path_1.default.resolve(raw.graphMemory)
230
+ : path_1.default.join(firstProject.projectDir, '.graph-memory', 'workspace');
231
+ const mirrorDir = raw.mirrorDir
232
+ ? path_1.default.resolve(raw.mirrorDir)
233
+ : graphMemory;
234
+ // Workspace-level embedding (inherits from server)
235
+ const wsEmbedding = resolveEmbeddingConfig(raw.embedding, globalEmbedding);
236
+ // Per-graph embeddings for workspace's shared graphs (knowledge, tasks, skills)
237
+ const graphOverrides = raw.graphs ?? {};
238
+ const graphEmbeddings = {
239
+ knowledge: mergeEmbeddingConfig(wsEmbedding, graphOverrides.knowledge),
240
+ tasks: mergeEmbeddingConfig(wsEmbedding, graphOverrides.tasks),
241
+ skills: mergeEmbeddingConfig(wsEmbedding, graphOverrides.skills),
242
+ };
243
+ workspaces.set(wsId, {
244
+ projects: raw.projects,
245
+ graphMemory,
246
+ mirrorDir,
247
+ embedding: wsEmbedding,
248
+ graphEmbeddings,
249
+ author: raw.author ?? globalAuthor,
250
+ });
251
+ }
252
+ }
253
+ return { author: globalAuthor, server, projects, workspaces };
254
+ }
@@ -0,0 +1,246 @@
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.getProject = getProject;
7
+ exports.resetProject = resetProject;
8
+ exports.parseCodeFile = parseCodeFile;
9
+ const path_1 = __importDefault(require("path"));
10
+ const ts_morph_1 = require("ts-morph");
11
+ // Keyed cache: one ts-morph Project per codeDir — avoids re-parsing tsconfig
12
+ const _projects = new Map();
13
+ function getProject(codeDir, tsconfig) {
14
+ const existing = _projects.get(codeDir);
15
+ if (existing)
16
+ return existing;
17
+ const tsconfigPath = tsconfig ?? path_1.default.join(codeDir, 'tsconfig.json');
18
+ let project;
19
+ try {
20
+ project = new ts_morph_1.Project({
21
+ tsConfigFilePath: tsconfigPath,
22
+ skipAddingFilesFromTsConfig: true, // we add files manually
23
+ skipFileDependencyResolution: false,
24
+ });
25
+ }
26
+ catch {
27
+ // No tsconfig — use minimal compiler options
28
+ project = new ts_morph_1.Project({
29
+ compilerOptions: { allowJs: true, strict: false },
30
+ skipFileDependencyResolution: true,
31
+ });
32
+ }
33
+ _projects.set(codeDir, project);
34
+ return project;
35
+ }
36
+ /** Reset cached project for a specific codeDir, or all if omitted. */
37
+ function resetProject(codeDir) {
38
+ if (codeDir) {
39
+ _projects.delete(codeDir);
40
+ }
41
+ else {
42
+ _projects.clear();
43
+ }
44
+ }
45
+ function parseCodeFile(absolutePath, codeDir, mtime, project) {
46
+ const fileId = path_1.default.relative(codeDir, absolutePath);
47
+ // Add or refresh the source file in the project
48
+ let sourceFile = project.getSourceFile(absolutePath);
49
+ if (sourceFile) {
50
+ sourceFile.refreshFromFileSystemSync();
51
+ }
52
+ else {
53
+ sourceFile = project.addSourceFileAtPath(absolutePath);
54
+ }
55
+ const nodes = [];
56
+ const edges = [];
57
+ const fileNodeId = fileId;
58
+ // --- File root node ---
59
+ const fileDocComment = extractFileDocComment(sourceFile);
60
+ const importSummary = buildImportSummary(sourceFile);
61
+ nodes.push({
62
+ id: fileNodeId,
63
+ attrs: {
64
+ kind: 'file',
65
+ fileId,
66
+ name: path_1.default.basename(fileId),
67
+ signature: fileId,
68
+ docComment: fileDocComment,
69
+ body: importSummary,
70
+ startLine: 1,
71
+ endLine: sourceFile.getEndLineNumber(),
72
+ isExported: false,
73
+ embedding: [],
74
+ fileEmbedding: [],
75
+ mtime,
76
+ },
77
+ });
78
+ // --- Top-level declarations ---
79
+ for (const decl of sourceFile.getStatements()) {
80
+ processStatement(decl, fileId, fileNodeId, mtime, nodes, edges);
81
+ }
82
+ // --- Import edges: file → imported file ---
83
+ for (const imp of sourceFile.getImportDeclarations()) {
84
+ const moduleSpecifier = imp.getModuleSpecifierValue();
85
+ if (!isRelative(moduleSpecifier))
86
+ continue;
87
+ const resolved = imp.getModuleSpecifierSourceFile();
88
+ if (!resolved)
89
+ continue;
90
+ const targetAbsolute = resolved.getFilePath();
91
+ if (!targetAbsolute.startsWith(codeDir))
92
+ continue;
93
+ const targetFileId = path_1.default.relative(codeDir, targetAbsolute);
94
+ const targetNodeId = targetFileId;
95
+ if (targetNodeId !== fileNodeId) {
96
+ edges.push({
97
+ from: fileNodeId,
98
+ to: targetNodeId,
99
+ attrs: { kind: 'imports' },
100
+ });
101
+ }
102
+ }
103
+ return { fileId, mtime, nodes, edges };
104
+ }
105
+ // ---------------------------------------------------------------------------
106
+ // Statement processing
107
+ // ---------------------------------------------------------------------------
108
+ function processStatement(node, fileId, parentId, mtime, nodes, edges) {
109
+ // Function declarations
110
+ if (ts_morph_1.Node.isFunctionDeclaration(node)) {
111
+ const name = node.getName();
112
+ if (!name)
113
+ return;
114
+ const id = makeId(fileId, name);
115
+ nodes.push({ id, attrs: buildAttrs('function', fileId, name, node, mtime) });
116
+ edges.push({ from: parentId, to: id, attrs: { kind: 'contains' } });
117
+ return;
118
+ }
119
+ // Class declarations
120
+ if (ts_morph_1.Node.isClassDeclaration(node)) {
121
+ const name = node.getName();
122
+ if (!name)
123
+ return;
124
+ const id = makeId(fileId, name);
125
+ nodes.push({ id, attrs: buildAttrs('class', fileId, name, node, mtime) });
126
+ edges.push({ from: parentId, to: id, attrs: { kind: 'contains' } });
127
+ // extends
128
+ const base = node.getBaseClass();
129
+ if (base) {
130
+ const baseName = base.getName?.() ?? base.getSymbol()?.getName();
131
+ if (baseName) {
132
+ edges.push({ from: id, to: makeId(fileId, baseName), attrs: { kind: 'extends' } });
133
+ }
134
+ }
135
+ // implements
136
+ for (const impl of node.getImplements()) {
137
+ const implName = impl.getExpression().getText();
138
+ edges.push({ from: id, to: makeId(fileId, implName), attrs: { kind: 'implements' } });
139
+ }
140
+ // Methods
141
+ for (const method of node.getMethods()) {
142
+ const methodName = method.getName();
143
+ const methodId = makeId(fileId, name, methodName);
144
+ nodes.push({ id: methodId, attrs: buildAttrs('method', fileId, methodName, method, mtime) });
145
+ edges.push({ from: id, to: methodId, attrs: { kind: 'contains' } });
146
+ }
147
+ return;
148
+ }
149
+ // Interface declarations
150
+ if (ts_morph_1.Node.isInterfaceDeclaration(node)) {
151
+ const name = node.getName();
152
+ const id = makeId(fileId, name);
153
+ nodes.push({ id, attrs: buildAttrs('interface', fileId, name, node, mtime) });
154
+ edges.push({ from: parentId, to: id, attrs: { kind: 'contains' } });
155
+ return;
156
+ }
157
+ // Type alias declarations
158
+ if (ts_morph_1.Node.isTypeAliasDeclaration(node)) {
159
+ const name = node.getName();
160
+ const id = makeId(fileId, name);
161
+ nodes.push({ id, attrs: buildAttrs('type', fileId, name, node, mtime) });
162
+ edges.push({ from: parentId, to: id, attrs: { kind: 'contains' } });
163
+ return;
164
+ }
165
+ // Enum declarations
166
+ if (ts_morph_1.Node.isEnumDeclaration(node)) {
167
+ const name = node.getName();
168
+ const id = makeId(fileId, name);
169
+ nodes.push({ id, attrs: buildAttrs('enum', fileId, name, node, mtime) });
170
+ edges.push({ from: parentId, to: id, attrs: { kind: 'contains' } });
171
+ return;
172
+ }
173
+ // Variable statements: export const foo = ..., export const bar = () => ...
174
+ if (ts_morph_1.Node.isVariableStatement(node)) {
175
+ for (const decl of node.getDeclarations()) {
176
+ const name = decl.getName();
177
+ if (typeof name !== 'string')
178
+ continue;
179
+ const initializer = decl.getInitializer();
180
+ const isArrow = initializer?.getKind() === ts_morph_1.SyntaxKind.ArrowFunction ||
181
+ initializer?.getKind() === ts_morph_1.SyntaxKind.FunctionExpression;
182
+ const kind = isArrow ? 'function' : 'variable';
183
+ const id = makeId(fileId, name);
184
+ nodes.push({ id, attrs: buildAttrs(kind, fileId, name, node, mtime) });
185
+ edges.push({ from: parentId, to: id, attrs: { kind: 'contains' } });
186
+ }
187
+ }
188
+ }
189
+ // ---------------------------------------------------------------------------
190
+ // Helpers
191
+ // ---------------------------------------------------------------------------
192
+ function makeId(fileId, ...parts) {
193
+ return [fileId, ...parts].join('::');
194
+ }
195
+ function buildAttrs(kind, fileId, name, node, mtime) {
196
+ const start = node.getStartLineNumber();
197
+ const end = node.getEndLineNumber();
198
+ const fullText = node.getFullText().trim();
199
+ // Signature = first line (up to opening brace or end of line)
200
+ const firstLine = fullText.split('\n')[0].trim();
201
+ const signature = firstLine.length > 200 ? firstLine.slice(0, 200) + '…' : firstLine;
202
+ // JSDoc = leading comment of the node
203
+ const jsDocs = 'getJsDocs' in node
204
+ ? node.getJsDocs()
205
+ : [];
206
+ const docComment = jsDocs.map(d => d.getFullText().trim()).join('\n').trim();
207
+ // isExported
208
+ const isExported = 'isExported' in node
209
+ ? node.isExported()
210
+ : false;
211
+ return {
212
+ kind,
213
+ fileId,
214
+ name,
215
+ signature,
216
+ docComment,
217
+ body: fullText,
218
+ startLine: start,
219
+ endLine: end,
220
+ isExported,
221
+ embedding: [],
222
+ fileEmbedding: [],
223
+ mtime,
224
+ };
225
+ }
226
+ function extractFileDocComment(sourceFile) {
227
+ // First statement's leading trivia that looks like a file-level JSDoc
228
+ const firstStatement = sourceFile.getStatements()[0];
229
+ if (!firstStatement)
230
+ return '';
231
+ const leadingComments = firstStatement.getLeadingCommentRanges();
232
+ return leadingComments
233
+ .map(r => r.getText())
234
+ .filter(t => t.startsWith('/**') || t.startsWith('//'))
235
+ .join('\n')
236
+ .trim();
237
+ }
238
+ function buildImportSummary(sourceFile) {
239
+ const imports = sourceFile.getImportDeclarations();
240
+ if (imports.length === 0)
241
+ return '';
242
+ return imports.map(i => i.getText().trim()).join('\n');
243
+ }
244
+ function isRelative(moduleSpecifier) {
245
+ return moduleSpecifier.startsWith('./') || moduleSpecifier.startsWith('../');
246
+ }
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractSymbols = extractSymbols;
4
+ const ts_morph_1 = require("ts-morph");
5
+ const TS_LANGUAGES = new Set([
6
+ 'ts', 'typescript',
7
+ 'js', 'javascript',
8
+ 'tsx', 'jsx',
9
+ ]);
10
+ /**
11
+ * Extract top-level symbol names from a code block using ts-morph.
12
+ * Returns [] for non-TS/JS languages or on parse failure.
13
+ */
14
+ function extractSymbols(code, language) {
15
+ if (!TS_LANGUAGES.has(language.toLowerCase()))
16
+ return [];
17
+ try {
18
+ const project = new ts_morph_1.Project({
19
+ useInMemoryFileSystem: true,
20
+ compilerOptions: { allowJs: true, strict: false },
21
+ });
22
+ const ext = language.toLowerCase().startsWith('ts') || language.toLowerCase() === 'tsx'
23
+ ? '.tsx'
24
+ : '.jsx';
25
+ const sourceFile = project.createSourceFile(`__snippet${ext}`, code);
26
+ const symbols = [];
27
+ for (const stmt of sourceFile.getStatements()) {
28
+ if (ts_morph_1.Node.isFunctionDeclaration(stmt)) {
29
+ const name = stmt.getName();
30
+ if (name)
31
+ symbols.push(name);
32
+ }
33
+ else if (ts_morph_1.Node.isClassDeclaration(stmt)) {
34
+ const name = stmt.getName();
35
+ if (name)
36
+ symbols.push(name);
37
+ }
38
+ else if (ts_morph_1.Node.isInterfaceDeclaration(stmt)) {
39
+ symbols.push(stmt.getName());
40
+ }
41
+ else if (ts_morph_1.Node.isTypeAliasDeclaration(stmt)) {
42
+ symbols.push(stmt.getName());
43
+ }
44
+ else if (ts_morph_1.Node.isEnumDeclaration(stmt)) {
45
+ symbols.push(stmt.getName());
46
+ }
47
+ else if (ts_morph_1.Node.isVariableStatement(stmt)) {
48
+ for (const decl of stmt.getDeclarations()) {
49
+ const name = decl.getName();
50
+ if (typeof name === 'string')
51
+ symbols.push(name);
52
+ }
53
+ }
54
+ else if (ts_morph_1.Node.isExportAssignment(stmt)) {
55
+ // export default ... — skip, no named symbol
56
+ }
57
+ else if (ts_morph_1.Node.isExpressionStatement(stmt)) {
58
+ // e.g. `app.use(...)` — skip
59
+ }
60
+ }
61
+ return symbols;
62
+ }
63
+ catch {
64
+ return [];
65
+ }
66
+ }