@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,26 @@
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', {
7
+ description: 'Create a new skill (reusable recipe/procedure) in the skill graph. ' +
8
+ 'The skill is automatically embedded for semantic search. ' +
9
+ 'Returns the generated skillId (slug from title). ' +
10
+ 'Use link_skill to connect skills, or create_skill_link to link to docs/code/files/knowledge/tasks.',
11
+ inputSchema: {
12
+ title: zod_1.z.string().describe('Short title for the skill, e.g. "Deploy to staging"'),
13
+ description: zod_1.z.string().describe('Full description of the skill (markdown)'),
14
+ steps: zod_1.z.array(zod_1.z.string()).optional().describe('Ordered steps to execute this skill (default [])'),
15
+ triggers: zod_1.z.array(zod_1.z.string()).optional().describe('Conditions or cues that suggest using this skill (default [])'),
16
+ inputHints: zod_1.z.array(zod_1.z.string()).optional().describe('Expected inputs or prerequisites (default [])'),
17
+ filePatterns: zod_1.z.array(zod_1.z.string()).optional().describe('Glob patterns for files this skill applies to (default [])'),
18
+ tags: zod_1.z.array(zod_1.z.string()).optional().describe('Optional tags for filtering, e.g. ["deploy", "ci"]'),
19
+ source: zod_1.z.enum(['user', 'learned']).optional().describe('How this skill was created (default "user")'),
20
+ confidence: zod_1.z.number().min(0).max(1).optional().describe('Confidence score 0–1 (default 1)'),
21
+ },
22
+ }, async ({ title, description, steps, triggers, inputHints, filePatterns, tags, source, confidence }) => {
23
+ const skillId = await mgr.createSkill(title, description, steps ?? [], triggers ?? [], inputHints ?? [], filePatterns ?? [], tags ?? [], source ?? 'user', confidence ?? 1);
24
+ return { content: [{ type: 'text', text: JSON.stringify({ skillId }, null, 2) }] };
25
+ });
26
+ }
@@ -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_skill_link', {
7
+ description: 'Remove a cross-graph link from a skill to a node in the docs, code, files, knowledge, or tasks graph. ' +
8
+ 'Orphaned proxy nodes are cleaned up automatically.',
9
+ inputSchema: {
10
+ skillId: zod_1.z.string().describe('Source skill ID'),
11
+ targetId: zod_1.z.string().describe('Target node ID in the external graph'),
12
+ targetGraph: zod_1.z.enum(['docs', 'code', 'files', 'knowledge', 'tasks'])
13
+ .describe('Which graph the target belongs to'),
14
+ projectId: zod_1.z.string().optional().describe('Project ID that the target node belongs to. Defaults to the current project.'),
15
+ },
16
+ }, async ({ skillId, targetId, targetGraph, projectId }) => {
17
+ const deleted = mgr.deleteCrossLink(skillId, targetId, targetGraph, projectId);
18
+ if (!deleted) {
19
+ return { content: [{ type: 'text', text: 'Cross-graph link not found.' }], isError: true };
20
+ }
21
+ return { content: [{ type: 'text', text: JSON.stringify({ skillId, targetId, targetGraph, deleted: true }, null, 2) }] };
22
+ });
23
+ }
@@ -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('delete_skill', {
7
+ description: 'Delete a skill and all its edges (relations, cross-graph links). ' +
8
+ 'Orphaned proxy nodes are cleaned up automatically. ' +
9
+ 'This action is irreversible.',
10
+ inputSchema: {
11
+ skillId: zod_1.z.string().describe('Skill ID to delete'),
12
+ },
13
+ }, async ({ skillId }) => {
14
+ const deleted = mgr.deleteSkill(skillId);
15
+ if (!deleted) {
16
+ return { content: [{ type: 'text', text: `Skill "${skillId}" not found.` }], isError: true };
17
+ }
18
+ return { content: [{ type: 'text', text: JSON.stringify({ skillId, deleted: true }, null, 2) }] };
19
+ });
20
+ }
@@ -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_skills', {
7
+ description: 'Find all skills that link to a specific node in the docs, code, files, knowledge, or tasks graph. ' +
8
+ 'This is a reverse lookup — given a target, returns all skills that reference it. ' +
9
+ 'Returns an array of { skillId, title, kind, source, confidence, tags }. ' +
10
+ 'Use get_skill to fetch full content of a returned skill.',
11
+ inputSchema: {
12
+ targetGraph: zod_1.z.enum(['docs', 'code', 'files', 'knowledge', 'tasks'])
13
+ .describe('Which graph the target belongs to'),
14
+ targetNodeId: zod_1.z.string().describe('Target node ID in the external graph'),
15
+ kind: zod_1.z.string().optional().describe('Filter by relation kind. 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 ({ targetGraph, targetNodeId, kind, projectId }) => {
19
+ const results = mgr.findLinkedSkills(targetGraph, targetNodeId, kind, projectId);
20
+ if (results.length === 0) {
21
+ return { content: [{ type: 'text', text: `No skills linked to ${targetGraph}::${targetNodeId}` }] };
22
+ }
23
+ return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
24
+ });
25
+ }
@@ -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_skill', {
7
+ description: 'Get full details of a skill by ID, including steps, triggers, inputHints, filePatterns, ' +
8
+ 'usage stats, and cross-graph links. ' +
9
+ 'Returns: id, title, description, steps, triggers, inputHints, filePatterns, tags, ' +
10
+ 'source, confidence, usageCount, lastUsedAt, createdAt, updatedAt, crossLinks[].',
11
+ inputSchema: {
12
+ skillId: zod_1.z.string().describe('Skill ID to retrieve'),
13
+ },
14
+ }, async ({ skillId }) => {
15
+ const skill = mgr.getSkill(skillId);
16
+ if (!skill) {
17
+ return { content: [{ type: 'text', text: `Skill "${skillId}" not found.` }], isError: true };
18
+ }
19
+ return { content: [{ type: 'text', text: JSON.stringify(skill, null, 2) }] };
20
+ });
21
+ }
@@ -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('link_skill', {
7
+ description: 'Create a directed relation between two skills. ' +
8
+ '"depends_on": fromId depends on toId. ' +
9
+ '"related_to": free association between skills. ' +
10
+ '"variant_of": fromId is a variant of toId.',
11
+ inputSchema: {
12
+ fromId: zod_1.z.string().describe('Source skill ID'),
13
+ toId: zod_1.z.string().describe('Target skill ID'),
14
+ kind: zod_1.z.enum(['depends_on', 'related_to', 'variant_of']).describe('Relation type'),
15
+ },
16
+ }, async ({ fromId, toId, kind }) => {
17
+ const created = mgr.linkSkills(fromId, toId, kind);
18
+ if (!created) {
19
+ return { content: [{ type: 'text', text: 'Could not create relation — one or both skills not found, or relation already exists.' }], isError: true };
20
+ }
21
+ return { content: [{ type: 'text', text: JSON.stringify({ fromId, toId, kind, created: true }, null, 2) }] };
22
+ });
23
+ }
@@ -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('list_skills', {
7
+ description: 'List skills with optional filters. ' +
8
+ 'Returns an array of { id, title, description, tags, source, confidence, usageCount, lastUsedAt, createdAt, updatedAt }. ' +
9
+ 'Use search_skills for semantic search.',
10
+ inputSchema: {
11
+ source: zod_1.z.enum(['user', 'learned']).optional().describe('Filter by source'),
12
+ tag: zod_1.z.string().optional().describe('Filter by tag (exact match, case-insensitive)'),
13
+ filter: zod_1.z.string().optional().describe('Substring match on title or ID'),
14
+ limit: zod_1.z.number().optional().describe('Max results (default 50)'),
15
+ },
16
+ }, async ({ source, tag, filter, limit }) => {
17
+ const results = mgr.listSkills({ source, tag, filter, limit });
18
+ return { content: [{ type: 'text', text: JSON.stringify(results, 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('recall_skills', {
7
+ description: 'Recall relevant skills for a given task context. Like search_skills but with lower ' +
8
+ 'minScore default (0.3) for higher recall. Use at the start of a task to find applicable recipes.',
9
+ inputSchema: {
10
+ context: zod_1.z.string().describe('Description of the current task or context to match skills against'),
11
+ topK: zod_1.z.number().optional().describe('How many top similar skills to use as seeds (default 5)'),
12
+ minScore: zod_1.z.number().min(0).max(1).optional().describe('Minimum relevance score 0–1 (default 0.3)'),
13
+ },
14
+ }, async ({ context, topK, minScore }) => {
15
+ const results = await mgr.searchSkills(context, { topK, minScore: minScore ?? 0.3 });
16
+ return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
17
+ });
18
+ }
@@ -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_skill_attachment', {
7
+ description: 'Remove an attachment from a skill. The file is deleted from disk.',
8
+ inputSchema: {
9
+ skillId: zod_1.z.string().describe('ID of the skill'),
10
+ filename: zod_1.z.string().describe('Filename of the attachment to remove'),
11
+ },
12
+ }, async ({ skillId, filename }) => {
13
+ const ok = mgr.removeAttachment(skillId, 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_skills', {
7
+ description: 'Semantic search over the skill graph. ' +
8
+ 'Finds the most relevant skills using vector similarity, then expands results ' +
9
+ 'by traversing relations between skills (graph walk). ' +
10
+ 'Returns an array sorted by relevance score (0–1), each with: ' +
11
+ 'id, title, description, tags, source, confidence, 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 skills 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.searchSkills(query, { topK, bfsDepth, maxResults, minScore, bfsDecay, searchMode });
23
+ return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
24
+ });
25
+ }
@@ -0,0 +1,58 @@
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_skill', {
8
+ description: 'Update an existing skill. Only provided fields are changed. ' +
9
+ 'Re-embeds automatically when title or description changes. ' +
10
+ 'Pass expectedVersion to enable optimistic locking.',
11
+ inputSchema: {
12
+ skillId: zod_1.z.string().describe('Skill ID to update'),
13
+ title: zod_1.z.string().optional().describe('New title'),
14
+ description: zod_1.z.string().optional().describe('New description'),
15
+ steps: zod_1.z.array(zod_1.z.string()).optional().describe('Replace steps array'),
16
+ triggers: zod_1.z.array(zod_1.z.string()).optional().describe('Replace triggers array'),
17
+ inputHints: zod_1.z.array(zod_1.z.string()).optional().describe('Replace inputHints array'),
18
+ filePatterns: zod_1.z.array(zod_1.z.string()).optional().describe('Replace filePatterns array'),
19
+ tags: zod_1.z.array(zod_1.z.string()).optional().describe('Replace tags array'),
20
+ source: zod_1.z.enum(['user', 'learned']).optional().describe('New source'),
21
+ confidence: zod_1.z.number().min(0).max(1).optional().describe('New confidence score 0–1'),
22
+ expectedVersion: zod_1.z.number().int().positive().optional().describe('Current version for optimistic locking — request fails with version_conflict if the skill has been updated since'),
23
+ },
24
+ }, async ({ skillId, title, description, steps, triggers, inputHints, filePatterns, tags, source, confidence, expectedVersion }) => {
25
+ const patch = {};
26
+ if (title !== undefined)
27
+ patch.title = title;
28
+ if (description !== undefined)
29
+ patch.description = description;
30
+ if (steps !== undefined)
31
+ patch.steps = steps;
32
+ if (triggers !== undefined)
33
+ patch.triggers = triggers;
34
+ if (inputHints !== undefined)
35
+ patch.inputHints = inputHints;
36
+ if (filePatterns !== undefined)
37
+ patch.filePatterns = filePatterns;
38
+ if (tags !== undefined)
39
+ patch.tags = tags;
40
+ if (source !== undefined)
41
+ patch.source = source;
42
+ if (confidence !== undefined)
43
+ patch.confidence = confidence;
44
+ try {
45
+ const updated = await mgr.updateSkill(skillId, patch, expectedVersion);
46
+ if (!updated) {
47
+ return { content: [{ type: 'text', text: `Skill "${skillId}" not found.` }], isError: true };
48
+ }
49
+ return { content: [{ type: 'text', text: JSON.stringify({ skillId, updated: true }, null, 2) }] };
50
+ }
51
+ catch (err) {
52
+ if (err instanceof manager_types_1.VersionConflictError) {
53
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'version_conflict', current: err.current, expected: err.expected }) }], isError: true };
54
+ }
55
+ throw err;
56
+ }
57
+ });
58
+ }
@@ -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_task_attachment', {
12
+ description: 'Attach a file to a task. Provide the absolute path to a local file. ' +
13
+ 'The file is copied into the task directory (.tasks/{taskId}/). ' +
14
+ 'Returns attachment metadata (filename, mimeType, size).',
15
+ inputSchema: {
16
+ taskId: zod_1.z.string().describe('ID of the task to attach the file to'),
17
+ filePath: zod_1.z.string().describe('Absolute path to the file on disk'),
18
+ },
19
+ }, async ({ taskId, 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(taskId, filename, data);
26
+ if (!meta) {
27
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'Task 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,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_task_link', {
7
+ description: 'Link a task to a node in the docs, code, files, or knowledge graph. ' +
8
+ 'Creates a cross-graph relation from the task to the target node. ' +
9
+ 'The kind is a free-form string, e.g. "references", "fixes", "implements", "documents".',
10
+ inputSchema: {
11
+ taskId: zod_1.z.string().describe('Source task 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")'),
13
+ targetGraph: zod_1.z.enum(['docs', 'code', 'files', 'knowledge'])
14
+ .describe('Which graph the target belongs to'),
15
+ kind: zod_1.z.string().describe('Relation type, e.g. "references", "fixes", "implements"'),
16
+ projectId: zod_1.z.string().optional().describe('Project ID that the target node belongs to. Defaults to the current project.'),
17
+ },
18
+ }, async ({ taskId, targetId, targetGraph, kind, projectId }) => {
19
+ const created = mgr.createCrossLink(taskId, targetId, targetGraph, kind, projectId);
20
+ if (!created) {
21
+ return { content: [{ type: 'text', text: `Could not create cross-graph link — task not found, target not found in ${targetGraph} graph, or link already exists.` }], isError: true };
22
+ }
23
+ return { content: [{ type: 'text', text: JSON.stringify({ taskId, targetId, targetGraph, kind, created: true }, null, 2) }] };
24
+ });
25
+ }
@@ -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_task', {
7
+ description: 'Create a new task in the task graph. ' +
8
+ 'The task is automatically embedded for semantic search. ' +
9
+ 'Returns the generated taskId (slug from title). ' +
10
+ 'Use link_task to connect tasks, or create_task_link to link to docs/code/files/knowledge.',
11
+ inputSchema: {
12
+ title: zod_1.z.string().describe('Short title for the task, e.g. "Fix auth redirect loop"'),
13
+ description: zod_1.z.string().describe('Full description of the task (markdown)'),
14
+ priority: zod_1.z.enum(['critical', 'high', 'medium', 'low']).describe('Task priority'),
15
+ status: zod_1.z.enum(['backlog', 'todo', 'in_progress', 'review', 'done', 'cancelled']).optional()
16
+ .describe('Initial status (default "backlog")'),
17
+ tags: zod_1.z.array(zod_1.z.string()).optional().describe('Optional tags for filtering, e.g. ["bug", "auth"]'),
18
+ dueDate: zod_1.z.number().optional().describe('Due date as Unix timestamp in milliseconds'),
19
+ estimate: zod_1.z.number().optional().describe('Estimated effort in hours'),
20
+ },
21
+ }, async ({ title, description, priority, status, tags, dueDate, estimate }) => {
22
+ const taskId = await mgr.createTask(title, description, status ?? 'backlog', priority, tags ?? [], dueDate ?? null, estimate ?? null);
23
+ return { content: [{ type: 'text', text: JSON.stringify({ taskId }, null, 2) }] };
24
+ });
25
+ }
@@ -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_task_link', {
7
+ description: 'Remove a cross-graph link from a task to a node in the docs, code, files, or knowledge graph. ' +
8
+ 'Orphaned proxy nodes are cleaned up automatically.',
9
+ inputSchema: {
10
+ taskId: zod_1.z.string().describe('Source task ID'),
11
+ targetId: zod_1.z.string().describe('Target node ID in the external graph'),
12
+ targetGraph: zod_1.z.enum(['docs', 'code', 'files', 'knowledge'])
13
+ .describe('Which graph the target belongs to'),
14
+ projectId: zod_1.z.string().optional().describe('Project ID that the target node belongs to. Defaults to the current project.'),
15
+ },
16
+ }, async ({ taskId, targetId, targetGraph, projectId }) => {
17
+ const deleted = mgr.deleteCrossLink(taskId, targetId, targetGraph, projectId);
18
+ if (!deleted) {
19
+ return { content: [{ type: 'text', text: 'Cross-graph link not found.' }], isError: true };
20
+ }
21
+ return { content: [{ type: 'text', text: JSON.stringify({ taskId, targetId, targetGraph, deleted: true }, null, 2) }] };
22
+ });
23
+ }
@@ -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('delete_task', {
7
+ description: 'Delete a task and all its edges (relations, cross-graph links). ' +
8
+ 'Orphaned proxy nodes are cleaned up automatically. ' +
9
+ 'This action is irreversible.',
10
+ inputSchema: {
11
+ taskId: zod_1.z.string().describe('Task ID to delete'),
12
+ },
13
+ }, async ({ taskId }) => {
14
+ const deleted = mgr.deleteTask(taskId);
15
+ if (!deleted) {
16
+ return { content: [{ type: 'text', text: `Task "${taskId}" not found.` }], isError: true };
17
+ }
18
+ return { content: [{ type: 'text', text: JSON.stringify({ taskId, deleted: true }, null, 2) }] };
19
+ });
20
+ }
@@ -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_tasks', {
7
+ description: 'Find all tasks that link to a specific node in the docs, code, files, or knowledge graph. ' +
8
+ 'This is a reverse lookup — given a target, returns all tasks that reference it. ' +
9
+ 'Returns an array of { taskId, title, kind, status, priority, tags }. ' +
10
+ 'Use get_task to fetch full content of a returned task.',
11
+ inputSchema: {
12
+ targetId: zod_1.z.string().describe('Target node ID in the external graph'),
13
+ targetGraph: zod_1.z.enum(['docs', 'code', 'files', 'knowledge'])
14
+ .describe('Which graph the target belongs to'),
15
+ kind: zod_1.z.string().optional().describe('Filter by relation kind. 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.findLinkedTasks(targetGraph, targetId, kind, projectId);
20
+ if (results.length === 0) {
21
+ return { content: [{ type: 'text', text: `No tasks 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_task', {
7
+ description: 'Get full details of a task by ID, including subtasks, blockers, related tasks, and cross-graph links. ' +
8
+ 'Returns: id, title, description, status, priority, tags, dueDate, estimate, ' +
9
+ 'completedAt, createdAt, updatedAt, subtasks[], blockedBy[], blocks[], related[], crossLinks[].',
10
+ inputSchema: {
11
+ taskId: zod_1.z.string().describe('Task ID to retrieve'),
12
+ },
13
+ }, async ({ taskId }) => {
14
+ const task = mgr.getTask(taskId);
15
+ if (!task) {
16
+ return { content: [{ type: 'text', text: `Task "${taskId}" not found.` }], isError: true };
17
+ }
18
+ return { content: [{ type: 'text', text: JSON.stringify(task, null, 2) }] };
19
+ });
20
+ }
@@ -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('link_task', {
7
+ description: 'Create a directed relation between two tasks. ' +
8
+ '"subtask_of": fromId is a subtask of toId. ' +
9
+ '"blocks": fromId blocks toId. ' +
10
+ '"related_to": free association between tasks.',
11
+ inputSchema: {
12
+ fromId: zod_1.z.string().describe('Source task ID'),
13
+ toId: zod_1.z.string().describe('Target task ID'),
14
+ kind: zod_1.z.enum(['subtask_of', 'blocks', 'related_to']).describe('Relation type'),
15
+ },
16
+ }, async ({ fromId, toId, kind }) => {
17
+ const created = mgr.linkTasks(fromId, toId, kind);
18
+ if (!created) {
19
+ return { content: [{ type: 'text', text: 'Could not create relation — one or both tasks not found, or relation already exists.' }], isError: true };
20
+ }
21
+ return { content: [{ type: 'text', text: JSON.stringify({ fromId, toId, kind, created: true }, null, 2) }] };
22
+ });
23
+ }
@@ -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('list_tasks', {
7
+ description: 'List tasks with optional filters. ' +
8
+ 'Sorted by priority (critical first) then due date (earliest first, nulls last). ' +
9
+ 'Returns an array of { id, title, description, status, priority, tags, dueDate, estimate, completedAt, createdAt, updatedAt }. ' +
10
+ 'Use search_tasks for semantic search.',
11
+ inputSchema: {
12
+ status: zod_1.z.enum(['backlog', 'todo', 'in_progress', 'review', 'done', 'cancelled']).optional()
13
+ .describe('Filter by status'),
14
+ priority: zod_1.z.enum(['critical', 'high', 'medium', 'low']).optional()
15
+ .describe('Filter by priority'),
16
+ tag: zod_1.z.string().optional().describe('Filter by tag (exact match, case-insensitive)'),
17
+ filter: zod_1.z.string().optional().describe('Substring match on title or ID'),
18
+ limit: zod_1.z.number().optional().describe('Max results (default 50)'),
19
+ },
20
+ }, async ({ status, priority, tag, filter, limit }) => {
21
+ const results = mgr.listTasks({ status, priority, tag, filter, limit });
22
+ return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
23
+ });
24
+ }
@@ -0,0 +1,38 @@
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('move_task', {
8
+ description: 'Change a task status. Automatically manages completedAt: ' +
9
+ 'sets it when moving to "done" or "cancelled", clears it when reopening. ' +
10
+ 'Returns the updated task summary. ' +
11
+ 'Pass expectedVersion to enable optimistic locking.',
12
+ inputSchema: {
13
+ taskId: zod_1.z.string().describe('Task ID to move'),
14
+ status: zod_1.z.enum(['backlog', 'todo', 'in_progress', 'review', 'done', 'cancelled'])
15
+ .describe('New status'),
16
+ expectedVersion: zod_1.z.number().int().positive().optional().describe('Current version for optimistic locking — request fails with version_conflict if the task has been updated since'),
17
+ },
18
+ }, async ({ taskId, status, expectedVersion }) => {
19
+ try {
20
+ const moved = mgr.moveTask(taskId, status, expectedVersion);
21
+ if (!moved) {
22
+ return { content: [{ type: 'text', text: `Task "${taskId}" not found.` }], isError: true };
23
+ }
24
+ const task = mgr.getTask(taskId);
25
+ return { content: [{ type: 'text', text: JSON.stringify({
26
+ taskId: task.id,
27
+ status: task.status,
28
+ completedAt: task.completedAt,
29
+ }, null, 2) }] };
30
+ }
31
+ catch (err) {
32
+ if (err instanceof manager_types_1.VersionConflictError) {
33
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'version_conflict', current: err.current, expected: err.expected }) }], isError: true };
34
+ }
35
+ throw err;
36
+ }
37
+ });
38
+ }
@@ -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_task_attachment', {
7
+ description: 'Remove an attachment from a task. The file is deleted from disk.',
8
+ inputSchema: {
9
+ taskId: zod_1.z.string().describe('ID of the task'),
10
+ filename: zod_1.z.string().describe('Filename of the attachment to remove'),
11
+ },
12
+ }, async ({ taskId, filename }) => {
13
+ const ok = mgr.removeAttachment(taskId, 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_tasks', {
7
+ description: 'Semantic search over the task graph. ' +
8
+ 'Finds the most relevant tasks using vector similarity, then expands results ' +
9
+ 'by traversing relations between tasks (graph walk). ' +
10
+ 'Returns an array sorted by relevance score (0–1), each with: ' +
11
+ 'id, title, description, status, priority, 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 tasks 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.searchTasks(query, { topK, bfsDepth, maxResults, minScore, bfsDecay, searchMode });
23
+ return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
24
+ });
25
+ }