@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.
- package/LICENSE +15 -0
- package/README.md +512 -0
- package/dist/api/index.js +473 -0
- package/dist/api/rest/code.js +78 -0
- package/dist/api/rest/docs.js +80 -0
- package/dist/api/rest/files.js +64 -0
- package/dist/api/rest/graph.js +56 -0
- package/dist/api/rest/index.js +117 -0
- package/dist/api/rest/knowledge.js +238 -0
- package/dist/api/rest/skills.js +284 -0
- package/dist/api/rest/tasks.js +272 -0
- package/dist/api/rest/tools.js +126 -0
- package/dist/api/rest/validation.js +191 -0
- package/dist/api/rest/websocket.js +65 -0
- package/dist/api/tools/code/get-file-symbols.js +30 -0
- package/dist/api/tools/code/get-symbol.js +22 -0
- package/dist/api/tools/code/list-files.js +18 -0
- package/dist/api/tools/code/search-code.js +27 -0
- package/dist/api/tools/code/search-files.js +22 -0
- package/dist/api/tools/context/get-context.js +19 -0
- package/dist/api/tools/docs/cross-references.js +76 -0
- package/dist/api/tools/docs/explain-symbol.js +55 -0
- package/dist/api/tools/docs/find-examples.js +52 -0
- package/dist/api/tools/docs/get-node.js +24 -0
- package/dist/api/tools/docs/get-toc.js +22 -0
- package/dist/api/tools/docs/list-snippets.js +46 -0
- package/dist/api/tools/docs/list-topics.js +18 -0
- package/dist/api/tools/docs/search-files.js +22 -0
- package/dist/api/tools/docs/search-snippets.js +43 -0
- package/dist/api/tools/docs/search.js +27 -0
- package/dist/api/tools/file-index/get-file-info.js +21 -0
- package/dist/api/tools/file-index/list-all-files.js +28 -0
- package/dist/api/tools/file-index/search-all-files.js +24 -0
- package/dist/api/tools/knowledge/add-attachment.js +31 -0
- package/dist/api/tools/knowledge/create-note.js +20 -0
- package/dist/api/tools/knowledge/create-relation.js +29 -0
- package/dist/api/tools/knowledge/delete-note.js +19 -0
- package/dist/api/tools/knowledge/delete-relation.js +23 -0
- package/dist/api/tools/knowledge/find-linked-notes.js +25 -0
- package/dist/api/tools/knowledge/get-note.js +20 -0
- package/dist/api/tools/knowledge/list-notes.js +18 -0
- package/dist/api/tools/knowledge/list-relations.js +17 -0
- package/dist/api/tools/knowledge/remove-attachment.js +19 -0
- package/dist/api/tools/knowledge/search-notes.js +25 -0
- package/dist/api/tools/knowledge/update-note.js +34 -0
- package/dist/api/tools/skills/add-attachment.js +31 -0
- package/dist/api/tools/skills/bump-usage.js +19 -0
- package/dist/api/tools/skills/create-skill-link.js +25 -0
- package/dist/api/tools/skills/create-skill.js +26 -0
- package/dist/api/tools/skills/delete-skill-link.js +23 -0
- package/dist/api/tools/skills/delete-skill.js +20 -0
- package/dist/api/tools/skills/find-linked-skills.js +25 -0
- package/dist/api/tools/skills/get-skill.js +21 -0
- package/dist/api/tools/skills/link-skill.js +23 -0
- package/dist/api/tools/skills/list-skills.js +20 -0
- package/dist/api/tools/skills/recall-skills.js +18 -0
- package/dist/api/tools/skills/remove-attachment.js +19 -0
- package/dist/api/tools/skills/search-skills.js +25 -0
- package/dist/api/tools/skills/update-skill.js +58 -0
- package/dist/api/tools/tasks/add-attachment.js +31 -0
- package/dist/api/tools/tasks/create-task-link.js +25 -0
- package/dist/api/tools/tasks/create-task.js +25 -0
- package/dist/api/tools/tasks/delete-task-link.js +23 -0
- package/dist/api/tools/tasks/delete-task.js +20 -0
- package/dist/api/tools/tasks/find-linked-tasks.js +25 -0
- package/dist/api/tools/tasks/get-task.js +20 -0
- package/dist/api/tools/tasks/link-task.js +23 -0
- package/dist/api/tools/tasks/list-tasks.js +24 -0
- package/dist/api/tools/tasks/move-task.js +38 -0
- package/dist/api/tools/tasks/remove-attachment.js +19 -0
- package/dist/api/tools/tasks/search-tasks.js +25 -0
- package/dist/api/tools/tasks/update-task.js +55 -0
- package/dist/cli/index.js +451 -0
- package/dist/cli/indexer.js +277 -0
- package/dist/graphs/attachment-types.js +74 -0
- package/dist/graphs/code-types.js +10 -0
- package/dist/graphs/code.js +172 -0
- package/dist/graphs/docs.js +198 -0
- package/dist/graphs/file-index-types.js +10 -0
- package/dist/graphs/file-index.js +310 -0
- package/dist/graphs/file-lang.js +119 -0
- package/dist/graphs/knowledge-types.js +32 -0
- package/dist/graphs/knowledge.js +764 -0
- package/dist/graphs/manager-types.js +87 -0
- package/dist/graphs/skill-types.js +10 -0
- package/dist/graphs/skill.js +1013 -0
- package/dist/graphs/task-types.js +17 -0
- package/dist/graphs/task.js +960 -0
- package/dist/lib/embedder.js +101 -0
- package/dist/lib/events-log.js +400 -0
- package/dist/lib/file-import.js +327 -0
- package/dist/lib/file-mirror.js +446 -0
- package/dist/lib/frontmatter.js +17 -0
- package/dist/lib/mirror-watcher.js +637 -0
- package/dist/lib/multi-config.js +254 -0
- package/dist/lib/parsers/code.js +246 -0
- package/dist/lib/parsers/codeblock.js +66 -0
- package/dist/lib/parsers/docs.js +196 -0
- package/dist/lib/project-manager.js +418 -0
- package/dist/lib/promise-queue.js +22 -0
- package/dist/lib/search/bm25.js +167 -0
- package/dist/lib/search/code.js +103 -0
- package/dist/lib/search/docs.js +108 -0
- package/dist/lib/search/file-index.js +31 -0
- package/dist/lib/search/files.js +61 -0
- package/dist/lib/search/knowledge.js +101 -0
- package/dist/lib/search/skills.js +104 -0
- package/dist/lib/search/tasks.js +103 -0
- package/dist/lib/watcher.js +67 -0
- package/package.json +83 -0
- package/ui/README.md +54 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
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_all_files', {
|
|
7
|
+
description: 'List all indexed project files and directories with optional filters. ' +
|
|
8
|
+
'When directory is set, returns immediate children (files + subdirectories) — use this to browse the project tree. ' +
|
|
9
|
+
'Without directory, returns all files matching filters (no directories in flat listing). ' +
|
|
10
|
+
'Returns an array of { filePath, kind, fileName, extension, language, mimeType, size, fileCount }. ' +
|
|
11
|
+
'Use search_all_files for semantic search or get_file_info for detailed metadata on a specific path.',
|
|
12
|
+
inputSchema: {
|
|
13
|
+
directory: zod_1.z.string().optional()
|
|
14
|
+
.describe('List immediate children of this directory (e.g. ".", "src/lib"). Default: lists all files'),
|
|
15
|
+
extension: zod_1.z.string().optional()
|
|
16
|
+
.describe('Filter by extension (e.g. ".ts", ".md", ".png")'),
|
|
17
|
+
language: zod_1.z.string().optional()
|
|
18
|
+
.describe('Filter by language (e.g. "typescript", "markdown", "json")'),
|
|
19
|
+
filter: zod_1.z.string().optional()
|
|
20
|
+
.describe('Substring filter on file path (case-insensitive)'),
|
|
21
|
+
limit: zod_1.z.number().optional().default(50)
|
|
22
|
+
.describe('Max results (default 50)'),
|
|
23
|
+
},
|
|
24
|
+
}, async ({ directory, extension, language, filter, limit }) => {
|
|
25
|
+
const results = mgr.listAllFiles({ directory, extension, language, filter, limit });
|
|
26
|
+
return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -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('search_all_files', {
|
|
7
|
+
description: 'Semantic search over all indexed project files by file path. ' +
|
|
8
|
+
'Finds the most relevant files by matching query against file path embeddings using vector similarity. ' +
|
|
9
|
+
'Searches file nodes only (not directories). ' +
|
|
10
|
+
'Returns an array sorted by relevance score (0–1), each with: ' +
|
|
11
|
+
'filePath, fileName, extension, language, size, score. ' +
|
|
12
|
+
'Use this to discover which project files are relevant to a topic.',
|
|
13
|
+
inputSchema: {
|
|
14
|
+
query: zod_1.z.string().describe('Search query'),
|
|
15
|
+
topK: zod_1.z.number().optional().default(10)
|
|
16
|
+
.describe('Max results (default 10)'),
|
|
17
|
+
minScore: zod_1.z.number().optional().default(0.3)
|
|
18
|
+
.describe('Minimum cosine similarity score (default 0.3)'),
|
|
19
|
+
},
|
|
20
|
+
}, async ({ query, topK, minScore }) => {
|
|
21
|
+
const results = await mgr.search(query, { topK, minScore });
|
|
22
|
+
return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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.register = register;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const zod_1 = require("zod");
|
|
10
|
+
function register(server, mgr) {
|
|
11
|
+
server.registerTool('add_note_attachment', {
|
|
12
|
+
description: 'Attach a file to a note. Provide the absolute path to a local file. ' +
|
|
13
|
+
'The file is copied into the note directory (.notes/{noteId}/). ' +
|
|
14
|
+
'Returns attachment metadata (filename, mimeType, size).',
|
|
15
|
+
inputSchema: {
|
|
16
|
+
noteId: zod_1.z.string().describe('ID of the note to attach the file to'),
|
|
17
|
+
filePath: zod_1.z.string().describe('Absolute path to the file on disk'),
|
|
18
|
+
},
|
|
19
|
+
}, async ({ noteId, filePath }) => {
|
|
20
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
21
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: 'File not found' }) }], isError: true };
|
|
22
|
+
}
|
|
23
|
+
const data = fs_1.default.readFileSync(filePath);
|
|
24
|
+
const filename = path_1.default.basename(filePath);
|
|
25
|
+
const meta = mgr.addAttachment(noteId, filename, data);
|
|
26
|
+
if (!meta) {
|
|
27
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: 'Note not found or no project dir' }) }], isError: true };
|
|
28
|
+
}
|
|
29
|
+
return { content: [{ type: 'text', text: JSON.stringify(meta, null, 2) }] };
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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('create_note', {
|
|
7
|
+
description: 'Create a new note or fact in the knowledge graph. ' +
|
|
8
|
+
'The note is automatically embedded for semantic search. ' +
|
|
9
|
+
'Returns the generated noteId (slug from title). ' +
|
|
10
|
+
'Use create_relation to link notes together.',
|
|
11
|
+
inputSchema: {
|
|
12
|
+
title: zod_1.z.string().describe('Short title for the note, e.g. "Auth uses JWT tokens"'),
|
|
13
|
+
content: zod_1.z.string().describe('Full text content of the note'),
|
|
14
|
+
tags: zod_1.z.array(zod_1.z.string()).optional().describe('Optional tags for filtering, e.g. ["architecture", "decision"]'),
|
|
15
|
+
},
|
|
16
|
+
}, async ({ title, content, tags }) => {
|
|
17
|
+
const noteId = await mgr.createNote(title, content, tags ?? []);
|
|
18
|
+
return { content: [{ type: 'text', text: JSON.stringify({ noteId }, null, 2) }] };
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
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('create_relation', {
|
|
7
|
+
description: 'Create a directed relation from a note to another note or to a node in the docs/code/files graph. ' +
|
|
8
|
+
'The kind is a free-form string describing the relationship, ' +
|
|
9
|
+
'e.g. "relates_to", "depends_on", "contradicts", "supports", "part_of", "references". ' +
|
|
10
|
+
'Set targetGraph to "docs", "code", "files", or "tasks" to link to a doc chunk, code symbol, file/directory, or task.',
|
|
11
|
+
inputSchema: {
|
|
12
|
+
fromId: zod_1.z.string().describe('Source note ID'),
|
|
13
|
+
toId: zod_1.z.string().describe('Target note ID, or target node ID in docs/code/files/tasks graph'),
|
|
14
|
+
kind: zod_1.z.string().describe('Relation type, e.g. "depends_on", "references"'),
|
|
15
|
+
targetGraph: zod_1.z.enum(['docs', 'code', 'files', 'tasks']).optional()
|
|
16
|
+
.describe('Set to "docs", "code", "files", or "tasks" to create a cross-graph link instead of note-to-note'),
|
|
17
|
+
projectId: zod_1.z.string().optional().describe('Project ID that the target node belongs to. Defaults to the current project.'),
|
|
18
|
+
},
|
|
19
|
+
}, async ({ fromId, toId, kind, targetGraph, projectId }) => {
|
|
20
|
+
const created = mgr.createRelation(fromId, toId, kind, targetGraph, projectId);
|
|
21
|
+
if (!created) {
|
|
22
|
+
const msg = targetGraph
|
|
23
|
+
? 'Could not create cross-graph relation — note not found, target not found in ' + targetGraph + ' graph, or relation already exists.'
|
|
24
|
+
: 'Could not create relation — one or both notes not found, or relation already exists.';
|
|
25
|
+
return { content: [{ type: 'text', text: msg }], isError: true };
|
|
26
|
+
}
|
|
27
|
+
return { content: [{ type: 'text', text: JSON.stringify({ fromId, toId, kind, targetGraph, created: true }, null, 2) }] };
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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('delete_note', {
|
|
7
|
+
description: 'Delete a note from the knowledge graph. ' +
|
|
8
|
+
'Also removes all relations (edges) connected to this note.',
|
|
9
|
+
inputSchema: {
|
|
10
|
+
noteId: zod_1.z.string().describe('ID of the note to delete'),
|
|
11
|
+
},
|
|
12
|
+
}, async ({ noteId }) => {
|
|
13
|
+
const deleted = mgr.deleteNote(noteId);
|
|
14
|
+
if (!deleted) {
|
|
15
|
+
return { content: [{ type: 'text', text: `Note not found: ${noteId}` }], isError: true };
|
|
16
|
+
}
|
|
17
|
+
return { content: [{ type: 'text', text: JSON.stringify({ noteId, deleted: true }, null, 2) }] };
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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('delete_relation', {
|
|
7
|
+
description: 'Delete a directed relation between a note and another note or a cross-graph target. ' +
|
|
8
|
+
'Set targetGraph to "docs", "code", "files", or "tasks" when deleting a cross-graph link.',
|
|
9
|
+
inputSchema: {
|
|
10
|
+
fromId: zod_1.z.string().describe('Source note ID'),
|
|
11
|
+
toId: zod_1.z.string().describe('Target note ID, or target node ID in docs/code/files/tasks graph'),
|
|
12
|
+
targetGraph: zod_1.z.enum(['docs', 'code', 'files', 'tasks']).optional()
|
|
13
|
+
.describe('Set to "docs", "code", "files", or "tasks" when deleting a cross-graph link'),
|
|
14
|
+
projectId: zod_1.z.string().optional().describe('Project ID that the target node belongs to. Defaults to the current project.'),
|
|
15
|
+
},
|
|
16
|
+
}, async ({ fromId, toId, targetGraph, projectId }) => {
|
|
17
|
+
const deleted = mgr.deleteRelation(fromId, toId, targetGraph, projectId);
|
|
18
|
+
if (!deleted) {
|
|
19
|
+
return { content: [{ type: 'text', text: 'Relation not found.' }], isError: true };
|
|
20
|
+
}
|
|
21
|
+
return { content: [{ type: 'text', text: JSON.stringify({ fromId, toId, targetGraph, deleted: true }, null, 2) }] };
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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('find_linked_notes', {
|
|
7
|
+
description: 'Find all notes in the knowledge graph that link to a specific node in the docs, code, files, or tasks graph. ' +
|
|
8
|
+
'This is a reverse lookup — given a target (e.g. a file, a code symbol, or a doc section), ' +
|
|
9
|
+
'returns all notes that reference it via cross-graph relations. ' +
|
|
10
|
+
'Returns an array of { noteId, title, kind, tags }. ' +
|
|
11
|
+
'Use get_note to fetch full content of a returned note.',
|
|
12
|
+
inputSchema: {
|
|
13
|
+
targetId: zod_1.z.string().describe('Target node ID in the external graph (e.g. "src/config.ts", "src/auth.ts::login", "docs/api.md::Setup")'),
|
|
14
|
+
targetGraph: zod_1.z.enum(['docs', 'code', 'files', 'tasks']).describe('Which graph the target belongs to: "docs", "code", "files", or "tasks"'),
|
|
15
|
+
kind: zod_1.z.string().optional().describe('Filter by relation kind (e.g. "references", "depends_on"). If omitted, returns all relations.'),
|
|
16
|
+
projectId: zod_1.z.string().optional().describe('Project ID that the target node belongs to. Defaults to the current project.'),
|
|
17
|
+
},
|
|
18
|
+
}, async ({ targetId, targetGraph, kind, projectId }) => {
|
|
19
|
+
const results = mgr.findLinkedNotes(targetGraph, targetId, kind, projectId);
|
|
20
|
+
if (results.length === 0) {
|
|
21
|
+
return { content: [{ type: 'text', text: `No notes linked to ${targetGraph}::${targetId}` }] };
|
|
22
|
+
}
|
|
23
|
+
return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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_note', {
|
|
7
|
+
description: 'Return the full content of a note by its ID. ' +
|
|
8
|
+
'Returns id, title, content, tags, createdAt, updatedAt, and relations (including cross-graph links from/to tasks, docs, code, files).',
|
|
9
|
+
inputSchema: {
|
|
10
|
+
noteId: zod_1.z.string().describe('Note ID, e.g. "auth-uses-jwt-tokens"'),
|
|
11
|
+
},
|
|
12
|
+
}, async ({ noteId }) => {
|
|
13
|
+
const note = mgr.getNote(noteId);
|
|
14
|
+
if (!note) {
|
|
15
|
+
return { content: [{ type: 'text', text: `Note not found: ${noteId}` }], isError: true };
|
|
16
|
+
}
|
|
17
|
+
const { embedding: _embedding, ...rest } = note;
|
|
18
|
+
return { content: [{ type: 'text', text: JSON.stringify(rest, null, 2) }] };
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -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_notes', {
|
|
7
|
+
description: 'List notes in the knowledge graph. ' +
|
|
8
|
+
'Optionally filter by title/id substring and/or tag. ' +
|
|
9
|
+
'Returns an array of { id, title, tags, updatedAt } sorted by most recently updated.',
|
|
10
|
+
inputSchema: {
|
|
11
|
+
filter: zod_1.z.string().optional().describe('Case-insensitive substring to match against note title or ID'),
|
|
12
|
+
tag: zod_1.z.string().optional().describe('Filter by tag (exact match, case-insensitive)'),
|
|
13
|
+
limit: zod_1.z.number().optional().describe('Maximum number of results (default 20)'),
|
|
14
|
+
},
|
|
15
|
+
}, async ({ filter, tag, limit }) => ({
|
|
16
|
+
content: [{ type: 'text', text: JSON.stringify(mgr.listNotes(filter, tag, limit), null, 2) }],
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
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_relations', {
|
|
7
|
+
description: 'List all relations (incoming and outgoing) for a note. ' +
|
|
8
|
+
'Returns an array of { fromId, toId, kind, targetGraph? }. ' +
|
|
9
|
+
'Cross-graph links include targetGraph ("docs", "code", "files", or "tasks") and resolve the real node ID.',
|
|
10
|
+
inputSchema: {
|
|
11
|
+
noteId: zod_1.z.string().describe('Note ID to list relations for'),
|
|
12
|
+
},
|
|
13
|
+
}, async ({ noteId }) => {
|
|
14
|
+
const relations = mgr.listRelations(noteId);
|
|
15
|
+
return { content: [{ type: 'text', text: JSON.stringify(relations, null, 2) }] };
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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('remove_note_attachment', {
|
|
7
|
+
description: 'Remove an attachment from a note. The file is deleted from disk.',
|
|
8
|
+
inputSchema: {
|
|
9
|
+
noteId: zod_1.z.string().describe('ID of the note'),
|
|
10
|
+
filename: zod_1.z.string().describe('Filename of the attachment to remove'),
|
|
11
|
+
},
|
|
12
|
+
}, async ({ noteId, filename }) => {
|
|
13
|
+
const ok = mgr.removeAttachment(noteId, filename);
|
|
14
|
+
if (!ok) {
|
|
15
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: 'Attachment not found' }) }], isError: true };
|
|
16
|
+
}
|
|
17
|
+
return { content: [{ type: 'text', text: JSON.stringify({ deleted: filename }) }] };
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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_notes', {
|
|
7
|
+
description: 'Semantic search over the knowledge graph (facts and notes). ' +
|
|
8
|
+
'Finds the most relevant notes using vector similarity, then expands results ' +
|
|
9
|
+
'by traversing relations between notes (graph walk). ' +
|
|
10
|
+
'Returns an array sorted by relevance score (0–1), each with: ' +
|
|
11
|
+
'id, title, content, tags, score.',
|
|
12
|
+
inputSchema: {
|
|
13
|
+
query: zod_1.z.string().describe('Natural language search query'),
|
|
14
|
+
topK: zod_1.z.number().optional().describe('How many top similar notes to use as seeds (default 5)'),
|
|
15
|
+
bfsDepth: zod_1.z.number().optional().describe('How many hops to follow relations from each seed (default 1; 0 = no expansion)'),
|
|
16
|
+
maxResults: zod_1.z.number().optional().describe('Maximum number of results to return (default 20)'),
|
|
17
|
+
minScore: zod_1.z.number().min(0).max(1).optional().describe('Minimum relevance score 0–1 (default 0.5)'),
|
|
18
|
+
bfsDecay: zod_1.z.number().min(0).max(1).optional().describe('Score multiplier per hop (default 0.8)'),
|
|
19
|
+
searchMode: zod_1.z.enum(['hybrid', 'vector', 'keyword']).optional().describe('Search mode: hybrid (default, BM25 + vector), vector (embedding only), keyword (BM25 only)'),
|
|
20
|
+
},
|
|
21
|
+
}, async ({ query, topK, bfsDepth, maxResults, minScore, bfsDecay, searchMode }) => {
|
|
22
|
+
const results = await mgr.searchNotes(query, { topK, bfsDepth, maxResults, minScore, bfsDecay, searchMode });
|
|
23
|
+
return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.register = register;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const manager_types_1 = require("../../../graphs/manager-types");
|
|
6
|
+
function register(server, mgr) {
|
|
7
|
+
server.registerTool('update_note', {
|
|
8
|
+
description: 'Update an existing note in the knowledge graph. ' +
|
|
9
|
+
'Only the provided fields are changed; others remain unchanged. ' +
|
|
10
|
+
'Re-embeds automatically if title or content changes. ' +
|
|
11
|
+
'Pass expectedVersion to enable optimistic locking.',
|
|
12
|
+
inputSchema: {
|
|
13
|
+
noteId: zod_1.z.string().describe('ID of the note to update'),
|
|
14
|
+
title: zod_1.z.string().optional().describe('New title'),
|
|
15
|
+
content: zod_1.z.string().optional().describe('New content'),
|
|
16
|
+
tags: zod_1.z.array(zod_1.z.string()).optional().describe('New tags (replaces existing)'),
|
|
17
|
+
expectedVersion: zod_1.z.number().int().positive().optional().describe('Current version for optimistic locking — request fails with version_conflict if the note has been updated since'),
|
|
18
|
+
},
|
|
19
|
+
}, async ({ noteId, title, content, tags, expectedVersion }) => {
|
|
20
|
+
try {
|
|
21
|
+
const updated = await mgr.updateNote(noteId, { title, content, tags }, expectedVersion);
|
|
22
|
+
if (!updated) {
|
|
23
|
+
return { content: [{ type: 'text', text: `Note not found: ${noteId}` }], isError: true };
|
|
24
|
+
}
|
|
25
|
+
return { content: [{ type: 'text', text: JSON.stringify({ noteId, updated: true }, null, 2) }] };
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
if (err instanceof manager_types_1.VersionConflictError) {
|
|
29
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: 'version_conflict', current: err.current, expected: err.expected }) }], isError: true };
|
|
30
|
+
}
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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.register = register;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const zod_1 = require("zod");
|
|
10
|
+
function register(server, mgr) {
|
|
11
|
+
server.registerTool('add_skill_attachment', {
|
|
12
|
+
description: 'Attach a file to a skill. Provide the absolute path to a local file. ' +
|
|
13
|
+
'The file is copied into the skill directory (.skills/{skillId}/). ' +
|
|
14
|
+
'Returns attachment metadata (filename, mimeType, size).',
|
|
15
|
+
inputSchema: {
|
|
16
|
+
skillId: zod_1.z.string().describe('ID of the skill to attach the file to'),
|
|
17
|
+
filePath: zod_1.z.string().describe('Absolute path to the file on disk'),
|
|
18
|
+
},
|
|
19
|
+
}, async ({ skillId, filePath }) => {
|
|
20
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
21
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: 'File not found' }) }], isError: true };
|
|
22
|
+
}
|
|
23
|
+
const data = fs_1.default.readFileSync(filePath);
|
|
24
|
+
const filename = path_1.default.basename(filePath);
|
|
25
|
+
const meta = mgr.addAttachment(skillId, filename, data);
|
|
26
|
+
if (!meta) {
|
|
27
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: 'Skill not found or no project dir' }) }], isError: true };
|
|
28
|
+
}
|
|
29
|
+
return { content: [{ type: 'text', text: JSON.stringify(meta, null, 2) }] };
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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('bump_skill_usage', {
|
|
7
|
+
description: 'Record that a skill was used. Increments usageCount and updates lastUsedAt timestamp. ' +
|
|
8
|
+
'Call this after successfully applying a skill.',
|
|
9
|
+
inputSchema: {
|
|
10
|
+
skillId: zod_1.z.string().describe('Skill ID to bump usage for'),
|
|
11
|
+
},
|
|
12
|
+
}, async ({ skillId }) => {
|
|
13
|
+
const ok = mgr.bumpUsage(skillId);
|
|
14
|
+
if (!ok) {
|
|
15
|
+
return { content: [{ type: 'text', text: `Skill "${skillId}" not found.` }], isError: true };
|
|
16
|
+
}
|
|
17
|
+
return { content: [{ type: 'text', text: JSON.stringify({ skillId, bumped: true }, null, 2) }] };
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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('create_skill_link', {
|
|
7
|
+
description: 'Link a skill to a node in the docs, code, files, knowledge, or tasks graph. ' +
|
|
8
|
+
'Creates a cross-graph relation from the skill to the target node. ' +
|
|
9
|
+
'The kind is a free-form string, e.g. "references", "implements", "documents".',
|
|
10
|
+
inputSchema: {
|
|
11
|
+
skillId: zod_1.z.string().describe('Source skill ID'),
|
|
12
|
+
targetId: zod_1.z.string().describe('Target node ID in the external graph (e.g. "src/auth.ts::login", "api.md::Setup", "my-note", "my-task")'),
|
|
13
|
+
targetGraph: zod_1.z.enum(['docs', 'code', 'files', 'knowledge', 'tasks'])
|
|
14
|
+
.describe('Which graph the target belongs to'),
|
|
15
|
+
kind: zod_1.z.string().describe('Relation type, e.g. "references", "implements", "documents"'),
|
|
16
|
+
projectId: zod_1.z.string().optional().describe('Project ID that the target node belongs to. Defaults to the current project.'),
|
|
17
|
+
},
|
|
18
|
+
}, async ({ skillId, targetId, targetGraph, kind, projectId }) => {
|
|
19
|
+
const created = mgr.createCrossLink(skillId, targetId, targetGraph, kind, projectId);
|
|
20
|
+
if (!created) {
|
|
21
|
+
return { content: [{ type: 'text', text: `Could not create cross-graph link — skill not found, target not found in ${targetGraph} graph, or link already exists.` }], isError: true };
|
|
22
|
+
}
|
|
23
|
+
return { content: [{ type: 'text', text: JSON.stringify({ skillId, targetId, targetGraph, kind, created: true }, null, 2) }] };
|
|
24
|
+
});
|
|
25
|
+
}
|