@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,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.attachWebSocket = attachWebSocket;
4
+ const ws_1 = require("ws");
5
+ /**
6
+ * Attach a WebSocket server to the HTTP server at /api/ws.
7
+ * Broadcasts all ProjectManager events to connected clients.
8
+ * Each event includes projectId — clients filter on their side.
9
+ */
10
+ function attachWebSocket(httpServer, projectManager) {
11
+ const wss = new ws_1.WebSocketServer({ noServer: true });
12
+ // Handle upgrade requests for /api/ws
13
+ httpServer.on('upgrade', (req, socket, head) => {
14
+ if (req.url !== '/api/ws') {
15
+ socket.destroy();
16
+ return;
17
+ }
18
+ wss.handleUpgrade(req, socket, head, (ws) => {
19
+ ws.on('error', (err) => {
20
+ process.stderr.write(`[ws] Client error: ${err}\n`);
21
+ });
22
+ wss.emit('connection', ws, req);
23
+ });
24
+ });
25
+ // Broadcast helper
26
+ function broadcast(event) {
27
+ const msg = JSON.stringify(event);
28
+ for (const client of wss.clients) {
29
+ if (client.readyState === ws_1.WebSocket.OPEN) {
30
+ client.send(msg, (err) => {
31
+ if (err)
32
+ process.stderr.write(`[ws] Send error: ${err}\n`);
33
+ });
34
+ }
35
+ }
36
+ }
37
+ // Subscribe to ProjectManager events
38
+ const events = [
39
+ 'note:created', 'note:updated', 'note:deleted',
40
+ 'note:attachment:added', 'note:attachment:deleted',
41
+ 'task:created', 'task:updated', 'task:deleted', 'task:moved',
42
+ 'task:attachment:added', 'task:attachment:deleted',
43
+ 'skill:created', 'skill:updated', 'skill:deleted',
44
+ 'skill:attachment:added', 'skill:attachment:deleted',
45
+ 'project:indexed',
46
+ ];
47
+ for (const eventType of events) {
48
+ projectManager.on(eventType, (data) => {
49
+ broadcast({ projectId: data.projectId, type: eventType, data });
50
+ });
51
+ }
52
+ // Debounced graph:updated from indexer
53
+ let graphUpdateTimer;
54
+ let pendingGraphUpdates = new Map();
55
+ projectManager.on('graph:updated', (data) => {
56
+ const key = data.projectId;
57
+ if (!pendingGraphUpdates.has(key))
58
+ pendingGraphUpdates.set(key, []);
59
+ pendingGraphUpdates.get(key).push(data.file);
60
+ if (!graphUpdateTimer) {
61
+ graphUpdateTimer = setTimeout(() => {
62
+ for (const [projectId, files] of pendingGraphUpdates) {
63
+ broadcast({ projectId, type: 'graph:updated', data: { files } });
64
+ }
65
+ pendingGraphUpdates = new Map();
66
+ graphUpdateTimer = undefined;
67
+ }, 1000);
68
+ }
69
+ });
70
+ return wss;
71
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.register = register;
4
+ const zod_1 = require("zod");
5
+ function register(server, mgr) {
6
+ server.registerTool('get_file_symbols', {
7
+ description: 'Return all symbols (functions, classes, types, etc.) declared in a specific file. ' +
8
+ 'Use this to understand the structure of a file before reading individual symbols. ' +
9
+ 'Returns an array of { id, kind, name, signature, startLine, endLine, isExported }. ' +
10
+ 'Pass an id to get_symbol to fetch full content including body and docComment.',
11
+ inputSchema: {
12
+ fileId: zod_1.z.string().describe('File path relative to code dir, e.g. "src/lib/graph.ts"'),
13
+ },
14
+ }, async ({ fileId }) => {
15
+ const symbols = mgr.getFileSymbols(fileId);
16
+ if (symbols.length === 0) {
17
+ return { content: [{ type: 'text', text: `File not found: ${fileId}` }], isError: true };
18
+ }
19
+ const result = symbols.map(s => ({
20
+ id: s.id,
21
+ kind: s.kind,
22
+ name: s.name,
23
+ signature: s.signature,
24
+ startLine: s.startLine,
25
+ endLine: s.endLine,
26
+ isExported: s.isExported,
27
+ }));
28
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
29
+ });
30
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.register = register;
4
+ const zod_1 = require("zod");
5
+ function register(server, mgr) {
6
+ server.registerTool('get_symbol', {
7
+ description: 'Return the full content of a specific code symbol. ' +
8
+ 'Use this after search_code or get_file_symbols to read the full implementation. ' +
9
+ 'Node IDs have the form "fileId::symbolName" or "fileId::ClassName::methodName". ' +
10
+ 'Returns id, fileId, kind, name, signature, docComment, body, startLine, endLine, isExported, and crossLinks (notes/tasks linking to this symbol).',
11
+ inputSchema: {
12
+ nodeId: zod_1.z.string().describe('Symbol ID from search_code or get_file_symbols, e.g. "src/lib/graph.ts::updateFile"'),
13
+ },
14
+ }, async ({ nodeId }) => {
15
+ const symbol = mgr.getSymbol(nodeId);
16
+ if (!symbol) {
17
+ return { content: [{ type: 'text', text: `Symbol not found: ${nodeId}` }], isError: true };
18
+ }
19
+ const { embedding: _embedding, mtime: _mtime, ...rest } = symbol;
20
+ return { content: [{ type: 'text', text: JSON.stringify(rest, null, 2) }] };
21
+ });
22
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.register = register;
4
+ const zod_1 = require("zod");
5
+ function register(server, mgr) {
6
+ server.registerTool('list_files', {
7
+ description: 'List indexed source files in the code graph. ' +
8
+ 'Optionally filter by file name substring (case-insensitive) and limit results. ' +
9
+ 'Returns an array of { fileId, symbolCount }. ' +
10
+ 'Pass a fileId to get_file_symbols to see all its declarations.',
11
+ inputSchema: {
12
+ filter: zod_1.z.string().optional().describe('Case-insensitive substring to match against file paths, e.g. "graph" or "src/lib"'),
13
+ limit: zod_1.z.number().optional().describe('Maximum number of results to return (default 20)'),
14
+ },
15
+ }, async ({ filter, limit }) => ({
16
+ content: [{ type: 'text', text: JSON.stringify(mgr.listFiles(filter, limit), null, 2) }],
17
+ }));
18
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.register = register;
4
+ const zod_1 = require("zod");
5
+ function register(server, mgr) {
6
+ server.registerTool('search_code', {
7
+ description: 'Semantic search over the indexed source code. ' +
8
+ 'Finds the most relevant symbols (functions, classes, types) by matching ' +
9
+ 'against their signatures and doc comments using vector similarity, ' +
10
+ 'then expands results by following graph edges (imports, contains, extends). ' +
11
+ 'Returns an array sorted by relevance score (0–1), each with: ' +
12
+ 'id, fileId, kind, name, signature, docComment, startLine, endLine, score. ' +
13
+ 'Pass an id to get_symbol to read the full implementation.',
14
+ inputSchema: {
15
+ query: zod_1.z.string().describe('Natural language or code search query, e.g. "function that loads the graph from disk"'),
16
+ topK: zod_1.z.number().optional().describe('How many top similar symbols to use as seeds (default 5)'),
17
+ bfsDepth: zod_1.z.number().optional().describe('How many hops to follow graph edges from each seed (default 1; 0 = no expansion)'),
18
+ maxResults: zod_1.z.number().optional().describe('Maximum number of results to return (default 20)'),
19
+ minScore: zod_1.z.number().min(0).max(1).optional().describe('Minimum relevance score 0–1; lower values return more results (default 0.5)'),
20
+ bfsDecay: zod_1.z.number().min(0).max(1).optional().describe('Score multiplier per graph hop (default 0.8)'),
21
+ searchMode: zod_1.z.enum(['hybrid', 'vector', 'keyword']).optional().describe('Search mode: hybrid (default, BM25 + vector), vector (embedding only), keyword (BM25 only)'),
22
+ },
23
+ }, async ({ query, topK, bfsDepth, maxResults, minScore, bfsDecay, searchMode }) => {
24
+ const results = await mgr.search(query, { topK, bfsDepth, maxResults, minScore, bfsDecay, searchMode });
25
+ return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
26
+ });
27
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.register = register;
4
+ const zod_1 = require("zod");
5
+ function register(server, mgr) {
6
+ server.registerTool('search_files', {
7
+ description: 'Semantic search over indexed source code files. ' +
8
+ 'Finds the most relevant files by matching query against file-level embeddings ' +
9
+ '(file path) using vector similarity. ' +
10
+ 'Returns an array sorted by relevance score (0–1), each with: ' +
11
+ 'fileId, symbolCount, score. ' +
12
+ 'Use this to discover which source files are relevant before diving into symbols with get_file_symbols or search_code.',
13
+ inputSchema: {
14
+ query: zod_1.z.string().describe('Natural language or path search query, e.g. "graph persistence" or "search module"'),
15
+ topK: zod_1.z.number().optional().describe('Maximum number of results to return (default 10)'),
16
+ minScore: zod_1.z.number().min(0).max(1).optional().describe('Minimum relevance score 0–1 (default 0.3)'),
17
+ },
18
+ }, async ({ query, topK, minScore }) => {
19
+ const results = await mgr.searchFiles(query, { topK, minScore });
20
+ return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
21
+ });
22
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.register = register;
4
+ function register(server, ctx) {
5
+ server.registerTool('get_context', {
6
+ description: 'Returns the current project and workspace context. ' +
7
+ 'Use this to discover which project you are connected to, whether it is part of a workspace, ' +
8
+ 'and which other projects are available in the workspace (for cross-graph links with projectId).',
9
+ inputSchema: {},
10
+ }, async () => {
11
+ const result = {
12
+ projectId: ctx?.projectId ?? null,
13
+ workspaceId: ctx?.workspaceId ?? null,
14
+ workspaceProjects: ctx?.workspaceProjects ?? null,
15
+ hasWorkspace: !!ctx?.workspaceId,
16
+ };
17
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
18
+ });
19
+ }
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.register = register;
4
+ const zod_1 = require("zod");
5
+ function register(server, docMgr, codeMgr) {
6
+ const docGraph = docMgr.graph;
7
+ const codeGraph = codeMgr.graph;
8
+ server.registerTool('cross_references', {
9
+ description: 'Find all references to a symbol across both code and documentation graphs. ' +
10
+ 'Returns: definitions (from CodeGraph — where the symbol is defined), ' +
11
+ 'documentation (text sections in docs that contain examples using the symbol), ' +
12
+ 'and examples (code blocks in docs that contain the symbol). ' +
13
+ 'This is the most comprehensive way to understand a symbol — combining source code, docs, and examples.',
14
+ inputSchema: {
15
+ symbol: zod_1.z.string().describe('Symbol name to look up, e.g. "createUser", "AuthService"'),
16
+ },
17
+ }, async ({ symbol }) => {
18
+ // 1. Search CodeGraph for definitions
19
+ const definitions = [];
20
+ codeGraph.forEachNode((id, attrs) => {
21
+ if (attrs.name === symbol) {
22
+ definitions.push({
23
+ id,
24
+ fileId: attrs.fileId,
25
+ kind: attrs.kind,
26
+ name: attrs.name,
27
+ signature: attrs.signature,
28
+ docComment: attrs.docComment,
29
+ startLine: attrs.startLine,
30
+ endLine: attrs.endLine,
31
+ });
32
+ }
33
+ });
34
+ // 2. Search DocGraph for code blocks containing the symbol
35
+ const examples = [];
36
+ const documentation = [];
37
+ const seenDocs = new Set();
38
+ docGraph.forEachNode((id, attrs) => {
39
+ if (attrs.symbols.length === 0)
40
+ return;
41
+ if (!attrs.symbols.includes(symbol))
42
+ return;
43
+ examples.push({
44
+ id,
45
+ fileId: attrs.fileId,
46
+ language: attrs.language,
47
+ symbols: attrs.symbols,
48
+ content: attrs.content,
49
+ });
50
+ // Find parent text section for documentation context
51
+ for (const neighbor of docGraph.inNeighbors(id)) {
52
+ if (seenDocs.has(neighbor))
53
+ continue;
54
+ const nAttrs = docGraph.getNodeAttributes(neighbor);
55
+ if (nAttrs.fileId === attrs.fileId && nAttrs.language === undefined && nAttrs.level < attrs.level) {
56
+ seenDocs.add(neighbor);
57
+ documentation.push({
58
+ id: neighbor,
59
+ fileId: nAttrs.fileId,
60
+ title: nAttrs.title,
61
+ content: nAttrs.content,
62
+ });
63
+ }
64
+ }
65
+ });
66
+ if (definitions.length === 0 && examples.length === 0) {
67
+ return { content: [{ type: 'text', text: `No references found for symbol: ${symbol}` }] };
68
+ }
69
+ return {
70
+ content: [{
71
+ type: 'text',
72
+ text: JSON.stringify({ definitions, documentation, examples }, null, 2),
73
+ }],
74
+ };
75
+ });
76
+ }
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.register = register;
4
+ const zod_1 = require("zod");
5
+ function register(server, mgr) {
6
+ const graph = mgr.graph;
7
+ server.registerTool('explain_symbol', {
8
+ description: 'Find documentation that explains a specific symbol. ' +
9
+ 'Searches code blocks in docs for the symbol, then returns both the code example ' +
10
+ 'and the surrounding text section that provides context/explanation. ' +
11
+ 'Use this to understand what a function, class, or type does based on documentation.',
12
+ inputSchema: {
13
+ symbol: zod_1.z.string().describe('Symbol name to look up, e.g. "createUser", "AuthService"'),
14
+ limit: zod_1.z.number().optional().describe('Max results to return (default 10)'),
15
+ },
16
+ }, async ({ symbol, limit = 10 }) => {
17
+ const results = [];
18
+ graph.forEachNode((id, attrs) => {
19
+ if (results.length >= limit)
20
+ return;
21
+ if (attrs.symbols.length === 0)
22
+ return;
23
+ if (!attrs.symbols.includes(symbol))
24
+ return;
25
+ // Find the parent text section
26
+ const parent = findParentTextSection(graph, id, attrs);
27
+ results.push({
28
+ codeBlock: {
29
+ id,
30
+ language: attrs.language,
31
+ symbols: attrs.symbols,
32
+ content: attrs.content,
33
+ },
34
+ explanation: parent
35
+ ? { id: parent.id, title: parent.title, content: parent.content }
36
+ : null,
37
+ fileId: attrs.fileId,
38
+ });
39
+ });
40
+ if (results.length === 0) {
41
+ return { content: [{ type: 'text', text: `No documentation found for symbol: ${symbol}` }] };
42
+ }
43
+ return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
44
+ });
45
+ }
46
+ function findParentTextSection(graph, nodeId, attrs) {
47
+ // The code block's in-neighbor with same fileId, lower level, and no language = parent text section
48
+ for (const neighbor of graph.inNeighbors(nodeId)) {
49
+ const nAttrs = graph.getNodeAttributes(neighbor);
50
+ if (nAttrs.fileId === attrs.fileId && nAttrs.language === undefined && nAttrs.level < attrs.level) {
51
+ return { id: neighbor, title: nAttrs.title, content: nAttrs.content };
52
+ }
53
+ }
54
+ return null;
55
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.register = register;
4
+ const zod_1 = require("zod");
5
+ function register(server, mgr) {
6
+ const graph = mgr.graph;
7
+ server.registerTool('find_examples', {
8
+ description: 'Find code examples in documentation that contain a specific symbol (function, class, interface, etc.). ' +
9
+ 'Searches the `symbols` array extracted from fenced code blocks via AST parsing. ' +
10
+ 'Use this to find usage examples of a known symbol in the docs. ' +
11
+ 'Returns matching code block nodes with id, fileId, language, symbols, content, and the parent section context.',
12
+ inputSchema: {
13
+ symbol: zod_1.z.string().describe('Symbol name to search for, e.g. "createUser", "UserService"'),
14
+ limit: zod_1.z.number().optional().describe('Max results to return (default 20)'),
15
+ },
16
+ }, async ({ symbol, limit = 20 }) => {
17
+ const results = [];
18
+ graph.forEachNode((id, attrs) => {
19
+ if (results.length >= limit)
20
+ return;
21
+ if (attrs.symbols.length === 0)
22
+ return;
23
+ if (!attrs.symbols.includes(symbol))
24
+ return;
25
+ // Find parent text section (previous node with lower level and no language)
26
+ const parentId = findParentSection(graph, id, attrs);
27
+ results.push({
28
+ id,
29
+ fileId: attrs.fileId,
30
+ language: attrs.language,
31
+ symbols: attrs.symbols,
32
+ content: attrs.content,
33
+ parentId,
34
+ parentTitle: parentId ? graph.getNodeAttribute(parentId, 'title') : undefined,
35
+ });
36
+ });
37
+ if (results.length === 0) {
38
+ return { content: [{ type: 'text', text: `No code examples found containing symbol: ${symbol}` }] };
39
+ }
40
+ return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
41
+ });
42
+ }
43
+ function findParentSection(graph, nodeId, attrs) {
44
+ // Walk inNeighbors (sibling edges point forward, so the parent is an inNeighbor)
45
+ for (const neighbor of graph.inNeighbors(nodeId)) {
46
+ const nAttrs = graph.getNodeAttributes(neighbor);
47
+ if (nAttrs.fileId === attrs.fileId && nAttrs.language === undefined && nAttrs.level < attrs.level) {
48
+ return neighbor;
49
+ }
50
+ }
51
+ return undefined;
52
+ }
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.register = register;
4
+ const zod_1 = require("zod");
5
+ function register(server, mgr) {
6
+ server.registerTool('get_node', {
7
+ description: 'Return the full content of a specific node (file root or section). ' +
8
+ 'Use this after search or get_toc to read the full text of a result. ' +
9
+ 'Node IDs have two forms: ' +
10
+ '"docs/auth.md" for the file root (intro text before any headings), ' +
11
+ '"docs/auth.md::Overview" for a named section. ' +
12
+ 'Returns id, fileId, title, content, level, links, mtime, and crossLinks (notes/tasks linking to this node).',
13
+ inputSchema: {
14
+ nodeId: zod_1.z.string().describe('Node ID from search results or get_toc, e.g. "docs/auth.md" or "docs/auth.md::Overview"'),
15
+ },
16
+ }, async ({ nodeId }) => {
17
+ const node = mgr.getNode(nodeId);
18
+ if (!node) {
19
+ return { content: [{ type: 'text', text: `Node not found: ${nodeId}` }], isError: true };
20
+ }
21
+ const { embedding: _embedding, ...rest } = node;
22
+ return { content: [{ type: 'text', text: JSON.stringify(rest, null, 2) }] };
23
+ });
24
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.register = register;
4
+ const zod_1 = require("zod");
5
+ function register(server, mgr) {
6
+ server.registerTool('get_toc', {
7
+ description: 'Return the table of contents (headings hierarchy) for a specific file. ' +
8
+ 'Use this to understand the structure of a file before deciding which sections to read. ' +
9
+ 'Returns an array of { id, title, level } objects. ' +
10
+ 'The id field is a node ID you can pass directly to get_node to fetch full content.',
11
+ inputSchema: {
12
+ fileId: zod_1.z.string().describe('File path relative to docs dir, e.g. "docs/auth.md"'),
13
+ },
14
+ }, async ({ fileId }) => {
15
+ const chunks = mgr.getFileChunks(fileId);
16
+ if (chunks.length === 0) {
17
+ return { content: [{ type: 'text', text: `File not found: ${fileId}` }], isError: true };
18
+ }
19
+ const toc = chunks.map(c => ({ id: c.id, title: c.title, level: c.level }));
20
+ return { content: [{ type: 'text', text: JSON.stringify(toc, null, 2) }] };
21
+ });
22
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.register = register;
4
+ const zod_1 = require("zod");
5
+ function register(server, mgr) {
6
+ const graph = mgr.graph;
7
+ server.registerTool('list_snippets', {
8
+ description: 'List code snippets extracted from documentation files. ' +
9
+ 'Returns code block nodes with language, symbols, and a content preview. ' +
10
+ 'Supports filtering by file, language, and content substring. ' +
11
+ 'Use this to discover what code examples exist in the docs.',
12
+ inputSchema: {
13
+ fileId: zod_1.z.string().optional().describe('Filter by file, e.g. "docs/auth.md"'),
14
+ filter: zod_1.z.string().optional().describe('Case-insensitive substring match on content'),
15
+ language: zod_1.z.string().optional().describe('Filter by language, e.g. "typescript"'),
16
+ limit: zod_1.z.number().optional().describe('Max results to return (default 20)'),
17
+ },
18
+ }, async ({ fileId, filter, language, limit = 20 }) => {
19
+ const lowerFilter = filter?.toLowerCase();
20
+ const lowerLang = language?.toLowerCase();
21
+ const results = [];
22
+ graph.forEachNode((id, attrs) => {
23
+ if (results.length >= limit)
24
+ return;
25
+ if (attrs.language === undefined)
26
+ return; // skip text chunks
27
+ if (fileId && attrs.fileId !== fileId)
28
+ return;
29
+ if (lowerLang && attrs.language !== lowerLang)
30
+ return;
31
+ if (lowerFilter && !attrs.content.toLowerCase().includes(lowerFilter))
32
+ return;
33
+ results.push({
34
+ id,
35
+ fileId: attrs.fileId,
36
+ language: attrs.language,
37
+ symbols: attrs.symbols,
38
+ preview: attrs.content.length > 200 ? attrs.content.slice(0, 200) + '…' : attrs.content,
39
+ });
40
+ });
41
+ if (results.length === 0) {
42
+ return { content: [{ type: 'text', text: 'No code snippets found matching the criteria.' }] };
43
+ }
44
+ return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
45
+ });
46
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.register = register;
4
+ const zod_1 = require("zod");
5
+ function register(server, mgr) {
6
+ server.registerTool('list_topics', {
7
+ description: 'List indexed documentation files. ' +
8
+ 'Optionally filter by file name substring (case-insensitive) and limit results. ' +
9
+ 'Returns an array of { fileId, title, chunks }. ' +
10
+ 'Pass a fileId to get_toc to see its structure, or to search to query it.',
11
+ inputSchema: {
12
+ filter: zod_1.z.string().optional().describe('Case-insensitive substring to match against file paths, e.g. "auth" or "api"'),
13
+ limit: zod_1.z.number().optional().describe('Maximum number of results to return (default 20)'),
14
+ },
15
+ }, async ({ filter, limit }) => ({
16
+ content: [{ type: 'text', text: JSON.stringify(mgr.listFiles(filter, limit), null, 2) }],
17
+ }));
18
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.register = register;
4
+ const zod_1 = require("zod");
5
+ function register(server, mgr) {
6
+ server.registerTool('search_topic_files', {
7
+ description: 'Semantic search over indexed documentation files. ' +
8
+ 'Finds the most relevant files by matching query against file-level embeddings ' +
9
+ '(file path + title/content summary) using vector similarity. ' +
10
+ 'Returns an array sorted by relevance score (0–1), each with: ' +
11
+ 'fileId, title, chunks, score. ' +
12
+ 'Use this to discover which doc files are relevant before diving into content with search or get_toc.',
13
+ inputSchema: {
14
+ query: zod_1.z.string().describe('Natural language search query, e.g. "authentication setup" or "API endpoints"'),
15
+ topK: zod_1.z.number().optional().describe('Maximum number of results to return (default 10)'),
16
+ minScore: zod_1.z.number().min(0).max(1).optional().describe('Minimum relevance score 0–1 (default 0.3)'),
17
+ },
18
+ }, async ({ query, topK, minScore }) => {
19
+ const results = await mgr.searchFiles(query, { topK, minScore });
20
+ return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
21
+ });
22
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.register = register;
4
+ const zod_1 = require("zod");
5
+ function register(server, mgr) {
6
+ const graph = mgr.graph;
7
+ server.registerTool('search_snippets', {
8
+ description: 'Semantic search over code snippets extracted from documentation. ' +
9
+ 'Finds the most relevant code examples using vector similarity. ' +
10
+ 'Only searches nodes that are fenced code blocks (have a language tag). ' +
11
+ 'Use this when looking for code examples by description, e.g. "authentication example" or "database query". ' +
12
+ 'Returns code block nodes sorted by relevance score.',
13
+ inputSchema: {
14
+ query: zod_1.z.string().describe('Natural language search query'),
15
+ topK: zod_1.z.number().optional().describe('Max results to return (default 10)'),
16
+ minScore: zod_1.z.number().min(0).max(1).optional().describe('Minimum relevance score 0–1 (default 0.3)'),
17
+ language: zod_1.z.string().optional().describe('Filter by language, e.g. "typescript", "python"'),
18
+ },
19
+ }, async ({ query, topK = 10, minScore = 0.3, language }) => {
20
+ // Search all nodes via mgr.search (no BFS expansion), then filter to code blocks only
21
+ const allResults = await mgr.search(query, { topK: topK * 3, maxResults: topK * 5, minScore, bfsDepth: 0 });
22
+ const filtered = allResults.filter(r => {
23
+ const attrs = graph.getNodeAttributes(r.id);
24
+ if (attrs.language === undefined)
25
+ return false;
26
+ if (language && attrs.language !== language.toLowerCase())
27
+ return false;
28
+ return true;
29
+ }).slice(0, topK);
30
+ const results = filtered.map(r => {
31
+ const attrs = graph.getNodeAttributes(r.id);
32
+ return {
33
+ id: r.id,
34
+ fileId: attrs.fileId,
35
+ language: attrs.language,
36
+ symbols: attrs.symbols,
37
+ content: attrs.content,
38
+ score: r.score,
39
+ };
40
+ });
41
+ return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
42
+ });
43
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.register = register;
4
+ const zod_1 = require("zod");
5
+ function register(server, mgr) {
6
+ server.registerTool('search', {
7
+ description: 'Semantic search over the indexed documentation. ' +
8
+ 'Finds the most relevant sections using vector similarity, then expands results ' +
9
+ 'by traversing links between documents (graph walk). ' +
10
+ 'Returns an array of chunks sorted by relevance score (0–1), each with: ' +
11
+ 'id, fileId, title, content, level, score. ' +
12
+ 'Use the id from results to fetch full content with get_node. ' +
13
+ 'Prefer this tool when looking for information without knowing which file contains it.',
14
+ inputSchema: {
15
+ query: zod_1.z.string().describe('Natural language search query'),
16
+ topK: zod_1.z.number().optional().describe('How many top similar sections to use as seeds for graph expansion (default 5)'),
17
+ bfsDepth: zod_1.z.number().optional().describe('How many hops to follow cross-document links from each seed (default 1; 0 = no expansion)'),
18
+ maxResults: zod_1.z.number().optional().describe('Maximum number of results to return (default 20)'),
19
+ minScore: zod_1.z.number().min(0).max(1).optional().describe('Minimum relevance score threshold 0–1; lower values return more results (default 0.5)'),
20
+ bfsDecay: zod_1.z.number().min(0).max(1).optional().describe('Score multiplier applied per graph hop; controls how quickly relevance fades with distance (default 0.8)'),
21
+ searchMode: zod_1.z.enum(['hybrid', 'vector', 'keyword']).optional().describe('Search mode: hybrid (default, BM25 + vector), vector (embedding only), keyword (BM25 only)'),
22
+ },
23
+ }, async ({ query, topK, bfsDepth, maxResults, minScore, bfsDecay, searchMode }) => {
24
+ const results = await mgr.search(query, { topK, bfsDepth, maxResults, minScore, bfsDecay, searchMode });
25
+ return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
26
+ });
27
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.register = register;
4
+ const zod_1 = require("zod");
5
+ function register(server, mgr) {
6
+ server.registerTool('get_file_info', {
7
+ description: 'Get full metadata for a specific file or directory by path. ' +
8
+ 'For files: returns filePath, kind, fileName, directory, extension, language, mimeType, size, mtime, and crossLinks (notes/tasks linking to this file). ' +
9
+ 'For directories: returns filePath, kind, fileName, directory, fileCount, size (total of direct children). ' +
10
+ 'Use "." for the project root.',
11
+ inputSchema: {
12
+ filePath: zod_1.z.string().describe('Relative file or directory path (e.g. "src/lib/config.ts" or "src/lib")'),
13
+ },
14
+ }, async ({ filePath }) => {
15
+ const info = mgr.getFileInfo(filePath);
16
+ if (!info) {
17
+ return { content: [{ type: 'text', text: `File or directory not found: ${filePath}` }], isError: true };
18
+ }
19
+ return { content: [{ type: 'text', text: JSON.stringify(info, null, 2) }] };
20
+ });
21
+ }