context-mcp-server 1.0.6 → 1.0.8

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/src/guard.js CHANGED
@@ -1,7 +1,13 @@
1
1
  import { resolve, sep } from 'node:path';
2
+ import { realpathSync } from 'node:fs';
3
+
4
+ // Follow symlinks to get the true path; fall back to resolve() if path doesn't exist yet.
5
+ function safeStat(p) {
6
+ try { return realpathSync(p); } catch { return resolve(p); }
7
+ }
2
8
 
3
9
  /**
4
- * Resolves `inputPath` and asserts it falls within `allowedRoot`.
10
+ * Resolves `inputPath` (following symlinks) and asserts it falls within `allowedRoot`.
5
11
  * Throws if no root is configured or if the path escapes the root.
6
12
  * Returns the resolved absolute path on success.
7
13
  */
@@ -11,8 +17,8 @@ export function guardPath(inputPath, allowedRoot) {
11
17
  'Project root not configured. Call context with action:"resume" and include rootPath to enable file/git access.'
12
18
  );
13
19
  }
14
- const resolved = resolve(inputPath);
15
- const root = resolve(allowedRoot);
20
+ const resolved = safeStat(inputPath);
21
+ const root = safeStat(allowedRoot);
16
22
  if (resolved !== root && !resolved.startsWith(root + sep)) {
17
23
  throw new Error(`Access denied: "${resolved}" is outside the project root "${root}"`);
18
24
  }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * migrator.js — one-time migration from flat JSON files to per-project directory structure.
3
+ *
4
+ * Old layout:
5
+ * ~/.context-mcp/contexts.json all entries flat
6
+ * ~/.context-mcp/discussions.json all discussions flat
7
+ * ~/.context-mcp/graphs.json all graph build records flat
8
+ *
9
+ * New layout:
10
+ * ~/.context-mcp/projects/<slug>/context.json
11
+ * ~/.context-mcp/projects/<slug>/graph.json { build, entries[] }
12
+ * ~/.context-mcp/projects/<slug>/summary.json
13
+ * ~/.context-mcp/projects/<slug>/discussions.json
14
+ */
15
+
16
+ import { readFileSync, existsSync, mkdirSync, unlinkSync } from 'node:fs';
17
+ import { join } from 'node:path';
18
+
19
+ function normPath(p) {
20
+ return p ? p.toLowerCase().replace(/\\/g, '/').replace(/\/$/, '') : '';
21
+ }
22
+
23
+ function treeFor(entry) {
24
+ if (entry.type === 'compaction') return 'summary';
25
+ return 'context';
26
+ }
27
+
28
+ function readArr(filePath, key) {
29
+ if (!existsSync(filePath)) return [];
30
+ try {
31
+ const d = JSON.parse(readFileSync(filePath, 'utf8'));
32
+ return Array.isArray(d[key]) ? d[key] : (Array.isArray(d) ? d : []);
33
+ } catch { return []; }
34
+ }
35
+
36
+ /**
37
+ * Run migration if legacy flat files are present.
38
+ * @param {object} opts
39
+ * @param {string} opts.dataDir - base data dir (~/.context-mcp)
40
+ * @param {string} opts.projectsDir - projects sub-dir
41
+ * @param {string} opts.projectsPath - path to projects.json
42
+ * @param {Function} opts.slugify - name → filesystem slug
43
+ * @param {Function} opts.flushFile - (filePath, content) atomic write
44
+ * @param {Array} opts.projectsIndex - current projects.json array (mutated in place)
45
+ */
46
+ export function runMigration({ dataDir, projectsDir, projectsPath, slugify, flushFile, projectsIndex }) {
47
+ const legacyContexts = join(dataDir, 'contexts.json');
48
+ const legacyDiscussions = join(dataDir, 'discussions.json');
49
+ const legacyGraphs = join(dataDir, 'graphs.json');
50
+
51
+ const hasLegacy = existsSync(legacyContexts)
52
+ || existsSync(legacyDiscussions)
53
+ || existsSync(legacyGraphs);
54
+
55
+ if (!hasLegacy) return false;
56
+
57
+ const oldContexts = readArr(legacyContexts, 'contexts');
58
+ const oldDiscussions = readArr(legacyDiscussions, 'discussions');
59
+ const oldGraphs = readArr(legacyGraphs, 'graphs');
60
+
61
+ // Remap legacy type names to current 4-type schema
62
+ const TYPE_MAP = { architecture: 'note', code: 'note', error: 'bug', summary: 'compaction' };
63
+ const remapType = entry => {
64
+ if (TYPE_MAP[entry.type]) entry.type = TYPE_MAP[entry.type];
65
+ return entry;
66
+ };
67
+
68
+ // Group everything by project name
69
+ const byProject = {};
70
+ const ensure = name => {
71
+ if (!byProject[name]) byProject[name] = {
72
+ context: [], graph: { build: null }, summary: [], discussions: [],
73
+ };
74
+ return byProject[name];
75
+ };
76
+
77
+ for (const entry of oldContexts) {
78
+ remapType(entry);
79
+ const p = entry.project || 'global';
80
+ const d = ensure(p);
81
+ if (treeFor(entry) === 'summary') d.summary.push(entry);
82
+ else d.context.push(entry);
83
+ }
84
+
85
+ for (const disc of oldDiscussions) {
86
+ ensure(disc.project || 'global').discussions.push(disc);
87
+ }
88
+
89
+ // Match graph build records to projects via rootPath
90
+ for (const graph of oldGraphs) {
91
+ const proj = projectsIndex.find(p => normPath(p.rootPath) === normPath(graph.path));
92
+ const name = proj ? proj.name : 'global';
93
+ const d = ensure(name);
94
+ d.graph.build = {
95
+ path: graph.path, nodes: graph.nodes, edges: graph.edges,
96
+ communities: graph.communities, cached: graph.cached || 0,
97
+ changed: graph.changed || 0, time_ms: graph.time_ms || 0,
98
+ summary: graph.summary || '', builtAt: graph.builtAt,
99
+ };
100
+ }
101
+
102
+ // Write per-project files
103
+ for (const [name, data] of Object.entries(byProject)) {
104
+ const dir = join(projectsDir, slugify(name));
105
+ mkdirSync(dir, { recursive: true });
106
+ flushFile(join(dir, 'context.json'), { entries: data.context });
107
+ flushFile(join(dir, 'graph.json'), data.graph);
108
+ flushFile(join(dir, 'summary.json'), { entries: data.summary });
109
+ flushFile(join(dir, 'discussions.json'), { discussions: data.discussions });
110
+ }
111
+
112
+ // Stamp dataDir onto each project record
113
+ for (const proj of projectsIndex) {
114
+ if (!proj.dataDir) proj.dataDir = `projects/${slugify(proj.name)}`;
115
+ }
116
+ flushFile(projectsPath, { projects: projectsIndex });
117
+
118
+ // Remove legacy flat files
119
+ try { unlinkSync(legacyContexts); } catch {}
120
+ try { unlinkSync(legacyDiscussions); } catch {}
121
+ try { unlinkSync(legacyGraphs); } catch {}
122
+
123
+ return true;
124
+ }
package/src/server.js CHANGED
@@ -5,7 +5,7 @@ import { getConfig } from './config.js';
5
5
 
6
6
  import * as contextTool from './tools/context.js';
7
7
  import * as searchTool from './tools/search.js';
8
- import * as discussionTool from './tools/discussion.js';
8
+ import * as planTool from './tools/plan.js';
9
9
  import * as errorCheckTool from './tools/errorCheck.js';
10
10
  import * as fileTool from './tools/fileTools.js';
11
11
  import * as gitTool from './tools/gitTools.js';
@@ -17,8 +17,9 @@ const CODEGRAPH_TOOL_NAMES = codegraphTool.TOOL_NAMES;
17
17
 
18
18
  export function createServer({ enableFileTools = false, enableGitTools = getConfig().access_git === true } = {}) {
19
19
  const state = {
20
- sessionProject: null,
21
- discussionId: null,
20
+ sessionProject: null,
21
+ discussionId: null,
22
+ projectRootPath: null,
22
23
  };
23
24
 
24
25
  const server = new Server(
@@ -30,7 +31,7 @@ export function createServer({ enableFileTools = false, enableGitTools = getConf
30
31
  const tools = [
31
32
  contextTool.definition,
32
33
  searchTool.definition,
33
- discussionTool.definition,
34
+ planTool.definition,
34
35
  errorCheckTool.definition,
35
36
  ];
36
37
  if (enableFileTools) tools.push(...fileTool.definitions);
@@ -56,8 +57,8 @@ export function createServer({ enableFileTools = false, enableGitTools = getConf
56
57
  result = await contextTool.handle(args, state);
57
58
  } else if (name === searchTool.definition.name) {
58
59
  result = await searchTool.handle(args, state);
59
- } else if (name === discussionTool.definition.name) {
60
- result = await discussionTool.handle(args, state);
60
+ } else if (name === planTool.definition.name) {
61
+ result = await planTool.handle(args, state);
61
62
  } else if (name === errorCheckTool.definition.name) {
62
63
  result = await errorCheckTool.handle(args, state);
63
64
  } else if (FILE_TOOL_NAMES.has(name)) {
@@ -11,7 +11,7 @@ Call the `context` MCP tool with `action: "resume"`, `project: "<project-name>"`
11
11
 
12
12
  Returns:
13
13
  - `recentEntries` — decisions, bugs, notes from previous conversations
14
- - `activeDiscussions` — ongoing topics
14
+ - `activePlans` — active AI-created plans for this project
15
15
  - `codegraph` — `{ built: true/false, nodes, edges, communities }`
16
16
 
17
17
  Then:
@@ -24,7 +24,7 @@ Then:
24
24
 
25
25
  **After graph build or rebuild** — every time `codegraph_build` completes:
26
26
  ```
27
- context.save type: "architecture" title: "ContextGraph built — <project>"
27
+ context.save type: "note" title: "ContextGraph built — <project>"
28
28
  content: "nodes: X | edges: Y | communities: Z"
29
29
  ```
30
30
 
@@ -35,18 +35,12 @@ content: "nodes: X | edges: Y | communities: Z"
35
35
  | What happened | Type |
36
36
  |--------------|------|
37
37
  | Approach / library / pattern decided | `decision` |
38
- | Bug found (root cause known) or fixed | `bug` |
39
- | System structure understood | `architecture` |
40
- | Gotcha, constraint, non-obvious behavior | `note` |
41
- | Config / env var / secret key discovered | `config` |
42
- | External API or service integration learned | `note` |
43
- | Performance insight (why something is slow/fast) | `note` |
44
- | How to run tests / test pattern discovered | `note` |
45
- | Deploy / release step discovered | `note` |
46
- | Milestone / feature / task completed | `note` |
38
+ | Bug found, root cause known, or fixed | `bug` |
39
+ | Gotcha, constraint, discovery, structure understood | `note` |
40
+ | Config / env var / secret / deploy step | `config` |
47
41
 
48
42
  Do NOT save: routine reads, search results, temporary debugging dead-ends.
49
- Feature spans sessions → `discussion.save` status: `"active"`.
43
+ **Making any kind of plan** call `plan.save` immediately with the plan summary and `planDir` pointing to your platform's plans folder.
50
44
  Need past info → `search` before asking. Always pass `project`.
51
45
 
52
46
  ---
@@ -63,7 +57,6 @@ codegraph_build(path) → AST graph: functions, classes, imports, edges
63
57
  ### Step 2 — Query (free, instant)
64
58
  ```
65
59
  codegraph_query(path, question) → fetch any details about the codebase
66
- codegraph_explain(path, node) → single node: type, file, connections
67
60
  codegraph_path(path, from, to) → shortest path
68
61
  codegraph_nodes(path, type) → list nodes by type
69
62
  codegraph_report(path) → full graph analysis
@@ -24,7 +24,7 @@ Both fields are required: `project` names the memory bucket, `rootPath` enables
24
24
 
25
25
  Returns:
26
26
  - `recentEntries` — decisions, bugs, notes from previous conversations
27
- - `activeDiscussions` — ongoing topics (auto-linked if exactly one active)
27
+ - `activePlans` — active AI-created plans for this project
28
28
  - `codegraph` — `{ built: true/false, nodes, edges, communities }`
29
29
 
30
30
  Then:
@@ -39,7 +39,7 @@ Then:
39
39
 
40
40
  **After graph build or rebuild** — every time `codegraph_build` completes:
41
41
  ```
42
- context.save project: "<project>" type: "architecture" title: "ContextGraph built — <project>"
42
+ context.save project: "<project>" type: "note" title: "ContextGraph built — <project>"
43
43
  content: "nodes: X | edges: Y | communities: Z"
44
44
  ```
45
45
 
@@ -50,19 +50,13 @@ content: "nodes: X | edges: Y | communities: Z"
50
50
  | What happened | Type |
51
51
  |--------------|------|
52
52
  | Approach / library / pattern decided | `decision` |
53
- | Bug found (root cause known) or fixed | `bug` |
54
- | System structure understood | `architecture` |
55
- | Gotcha, constraint, non-obvious behavior | `note` |
56
- | Config / env var / secret key discovered | `config` |
57
- | External API or service integration learned | `note` |
58
- | Performance insight (why something is slow/fast) | `note` |
59
- | How to run tests / test pattern discovered | `note` |
60
- | Deploy / release step discovered | `note` |
61
- | Milestone / feature / task completed | `note` |
53
+ | Bug found, root cause known, or fixed | `bug` |
54
+ | Gotcha, constraint, discovery, structure understood | `note` |
55
+ | Config / env var / secret / deploy step | `config` |
62
56
 
63
57
  Do NOT save: routine reads, search results, temporary debugging dead-ends.
64
58
 
65
- Feature spans multiple sessions → `discussion.create` or `discussion.update`.
59
+ **Making any kind of plan** call `plan.save` immediately with the plan summary and `planDir` pointing to your platform's plans folder.
66
60
  Need past info → `search` before asking user.
67
61
  Always pass `project`. Auto-compact fires at >20 entries.
68
62
 
@@ -11,7 +11,7 @@ Call the `context` MCP tool with `action: "resume"`, `project: "<project-name>"`
11
11
 
12
12
  Returns:
13
13
  - `recentEntries` — decisions, bugs, notes from previous conversations
14
- - `activeDiscussions` — ongoing topics (auto-linked if exactly one active)
14
+ - `activePlans` — active AI-created plans for this project
15
15
  - `codegraph` — `{ built: true/false, nodes, edges, communities }`
16
16
 
17
17
  Then:
@@ -24,7 +24,7 @@ Then:
24
24
 
25
25
  **After graph build or rebuild** — every time `codegraph_build` completes:
26
26
  ```
27
- context.save type: "architecture" title: "ContextGraph built — <project>"
27
+ context.save type: "note" title: "ContextGraph built — <project>"
28
28
  content: "nodes: X | edges: Y | communities: Z"
29
29
  ```
30
30
 
@@ -35,18 +35,12 @@ content: "nodes: X | edges: Y | communities: Z"
35
35
  | What happened | Type |
36
36
  |--------------|------|
37
37
  | Approach / library / pattern decided | `decision` |
38
- | Bug found (root cause known) or fixed | `bug` |
39
- | System structure understood | `architecture` |
40
- | Gotcha, constraint, non-obvious behavior | `note` |
41
- | Config / env var / secret key discovered | `config` |
42
- | External API or service integration learned | `note` |
43
- | Performance insight (why something is slow/fast) | `note` |
44
- | How to run tests / test pattern discovered | `note` |
45
- | Deploy / release step discovered | `note` |
46
- | Milestone / feature / task completed | `note` |
38
+ | Bug found, root cause known, or fixed | `bug` |
39
+ | Gotcha, constraint, discovery, structure understood | `note` |
40
+ | Config / env var / secret / deploy step | `config` |
47
41
 
48
42
  Do NOT save: routine reads, search results, temporary debugging dead-ends.
49
- Feature spans sessions → `discussion.save` then `discussion.update`.
43
+ **Making any kind of plan** call `plan.save` immediately with the plan summary and `planDir` pointing to your platform's plans folder.
50
44
  Need past info → `search` before asking. Always pass `project`.
51
45
 
52
46
  ---
@@ -63,7 +57,6 @@ codegraph_build(path) → AST graph: functions, classes, imports, edges
63
57
  ### Step 2 — Query (free, instant)
64
58
  ```
65
59
  codegraph_query(path, question) → fetch any details about the codebase
66
- codegraph_explain(path, node) → single node: type, file, connections
67
60
  codegraph_path(path, from, to) → shortest path
68
61
  codegraph_nodes(path, type) → list nodes by type
69
62
  codegraph_report(path) → full graph analysis
@@ -4,7 +4,7 @@ Both `project` and `rootPath` are required: `project` names the memory bucket, `
4
4
 
5
5
  This loads:
6
6
  - Recent decisions, bugs, and notes from past sessions
7
- - Active discussions
7
+ - Active plans
8
8
  - ContextGraph status (built or not)
9
9
 
10
10
  If `codegraph.built` is false in the response, immediately call `codegraph_build` on the project path before proceeding.
@@ -4,6 +4,6 @@ Parse `$ARGUMENTS` to determine:
4
4
  - `project`: infer from current working directory name if not specified
5
5
  - `title`: first sentence or phrase from the argument
6
6
  - `content`: full argument text
7
- - `type`: auto-detect — if mentions a bug/fix → `"bug"`, decision/chose/decided → `"decision"`, structure/architecture → `"architecture"`, otherwise `"note"`
7
+ - `type`: auto-detect — if mentions a bug/fix → `"bug"`, decision/chose/decided → `"decision"`, config/env/secret/deploy → `"config"`, otherwise `"note"`
8
8
 
9
9
  Confirm to the user what was saved (title, type, project).
@@ -13,7 +13,7 @@ Every conversation starts with `context.resume`. Every structural question uses
13
13
 
14
14
  Call `context` tool, `action: "resume"`, `project: "<project-name>"` before anything else.
15
15
 
16
- Returns: `recentEntries`, `activeDiscussions`, `codegraph { built, nodes, edges }`.
16
+ Returns: `recentEntries`, `activePlans`, `codegraph { built, nodes, edges }`.
17
17
 
18
18
  - `codegraph.built: true` → use `codegraph_query` before reading files
19
19
  - `codegraph.built: false` → run `codegraph_build(path)` first
@@ -24,8 +24,8 @@ Returns: `recentEntries`, `activeDiscussions`, `codegraph { built, nodes, edges
24
24
  |-----------|--------|
25
25
  | Decision made | `context.save` type: `"decision"` |
26
26
  | Bug found/fixed | `context.save` type: `"bug"` |
27
- | Architecture understood | `context.save` type: `"architecture"` |
28
- | Multi-session feature | `discussion.create` |
27
+ | Discovery / structure understood | `context.save` type: `"note"` |
28
+ | Making any plan | `plan.save` + `planDir` |
29
29
 
30
30
  ## 3. CodeGraph
31
31
 
@@ -27,7 +27,7 @@ Both fields are required: `project` names the memory bucket, `rootPath` enables
27
27
 
28
28
  Returns:
29
29
  - `recentEntries` — decisions, bugs, notes from past sessions
30
- - `activeDiscussions` — ongoing topics (auto-linked if exactly one active)
30
+ - `activePlans` — active AI-created plans for this project
31
31
  - `codegraph` — `{ built: true/false, nodes, edges, communities }`
32
32
 
33
33
  Then:
@@ -43,7 +43,7 @@ Then:
43
43
  **1. After graph build or rebuild**
44
44
  Every time `codegraph_build` completes successfully, immediately call:
45
45
  ```
46
- context.save project: "<project>" type: "architecture" title: "ContextGraph built — <project>"
46
+ context.save project: "<project>" type: "note" title: "ContextGraph built — <project>"
47
47
  content: "nodes: X | edges: Y | communities: Z | built: <timestamp>"
48
48
  ```
49
49
 
@@ -63,17 +63,11 @@ Do NOT save for: routine file reads, search results, explanations of existing co
63
63
  | What happened | Type |
64
64
  |--------------|------|
65
65
  | Approach / library / pattern decided | `decision` |
66
- | Bug found (root cause known) or fixed | `bug` |
67
- | System structure understood | `architecture` |
68
- | Gotcha, constraint, non-obvious behavior | `note` |
69
- | Config / env var / secret key discovered | `config` |
70
- | External API or service integration learned | `note` |
71
- | Performance insight (why something is slow/fast) | `note` |
72
- | How to run tests / test pattern discovered | `note` |
73
- | Deploy / release step discovered | `note` |
74
- | Milestone / feature / task completed | `note` |
75
-
76
- Always pass `project`. Feature spans multiple sessions → `discussion.create` or `discussion.update`. Need past info → `search` before asking user. Auto-compact fires at >20 entries.
66
+ | Bug found, root cause known, or fixed | `bug` |
67
+ | Gotcha, constraint, discovery, structure understood | `note` |
68
+ | Config / env var / secret / deploy step | `config` |
69
+
70
+ Always pass `project`. **Making any plan** `plan.save` immediately (planDir = your platform's plans folder). Need past info → `search` before asking user. Auto-compact fires at >20 entries.
77
71
 
78
72
  ---
79
73
 
@@ -89,9 +83,8 @@ Parses codebase into AST graph via tree-sitter. Extracts functions, classes, imp
89
83
 
90
84
  ### Query (free, instant, forever)
91
85
  ```
92
- codegraph_query(path, question) find functions, classes, files, dependencies, callers
93
- codegraph_explain(path, node) one node: type, file, depends_on, used_by
94
- codegraph_path(path, from, to) → shortest path between two concepts
86
+ codegraph_query(path, question?, node?) structural question OR single-node lookup (or both in one call)
87
+ codegraph_path(path, from, to) shortest path between two concepts
95
88
  codegraph_nodes(path, type) → list all nodes of a type
96
89
  codegraph_report(path) → god nodes, clusters, surprises
97
90
  ```
@@ -7,7 +7,7 @@ Every conversation starts with `context.resume`. Every structural question uses
7
7
 
8
8
  Call `context` tool, `action: "resume"`, `project: "<project-name>"` before anything else.
9
9
 
10
- Returns: `recentEntries`, `activeDiscussions`, `codegraph { built, nodes, edges }`.
10
+ Returns: `recentEntries`, `activePlans`, `codegraph { built, nodes, edges }`.
11
11
 
12
12
  - `codegraph.built: true` → use `codegraph_query` before reading files
13
13
  - `codegraph.built: false` → run `codegraph_build(path)` first
@@ -18,8 +18,8 @@ Returns: `recentEntries`, `activeDiscussions`, `codegraph { built, nodes, edges
18
18
  |-----------|--------|
19
19
  | Decision made | `context.save` type: `"decision"` |
20
20
  | Bug found/fixed | `context.save` type: `"bug"` |
21
- | Architecture understood | `context.save` type: `"architecture"` |
22
- | Multi-session feature | `discussion.create` |
21
+ | Discovery / structure understood | `context.save` type: `"note"` |
22
+ | Making any plan | `plan.save` + `planDir` |
23
23
 
24
24
  ## 3. CodeGraph
25
25
 
@@ -40,92 +40,23 @@ export const definitions = [
40
40
  required: ['path'],
41
41
  },
42
42
  },
43
- {
44
- name: 'codegraph_extract',
45
- description:
46
- 'Return raw content of changed code and doc/PDF files so the AI can write descriptions. ' +
47
- 'Code files: lists existing AST nodes — AI writes a description for each. ' +
48
- 'Doc files: AI extracts new concept nodes. ' +
49
- 'Call after codegraph_build, then call codegraph_add_nodes with results. ' +
50
- 'Pass force:true to re-enrich all files (not just changed ones).',
51
- inputSchema: {
52
- type: 'object',
53
- properties: {
54
- path: { type: 'string', description: 'Project root (same as codegraph_build)' },
55
- limit: { type: 'integer', description: 'Max files to return per call (default 10)' },
56
- force: { type: 'boolean', description: 'Return all files, not just changed (for re-enrichment)' },
57
- },
58
- required: ['path'],
59
- },
60
- },
61
- {
62
- name: 'codegraph_add_nodes',
63
- description:
64
- 'Add concept nodes extracted by the AI into the graph. ' +
65
- 'Call after reading codegraph_extract output. ' +
66
- 'Each node: name, type, file, and optionally description and relations.',
67
- inputSchema: {
68
- type: 'object',
69
- properties: {
70
- path: { type: 'string', description: 'Project root' },
71
- nodes: {
72
- type: 'array',
73
- description: 'Concept nodes to add',
74
- items: {
75
- type: 'object',
76
- properties: {
77
- name: { type: 'string' },
78
- type: { type: 'string', description: 'class|function|concept|service|decision|requirement' },
79
- file: { type: 'string', description: 'Relative file path this concept came from' },
80
- description: { type: 'string' },
81
- relations: {
82
- type: 'array',
83
- items: {
84
- type: 'object',
85
- properties: {
86
- name: { type: 'string' },
87
- relation: { type: 'string', description: 'depends-on|uses|implements|defines|documents' },
88
- },
89
- },
90
- },
91
- },
92
- required: ['name', 'type', 'file'],
93
- },
94
- },
95
- },
96
- required: ['path', 'nodes'],
97
- },
98
- },
99
43
  {
100
44
  name: 'codegraph_query',
101
45
  description:
102
- 'Ask a structural/dependency question about the codebase. ' +
103
- 'Pure graph traversal returns NODE/EDGE structured text truncated to token_budget. ' +
104
- 'Good for: "what does module X depend on?", "what calls function Y?", "what is the path from A to B?". ' +
105
- 'NOT for: bug investigation, logic errors, or understanding what code actually does — read the file directly for those.',
46
+ 'Ask a structural question about the codebase OR look up a specific node by name — or both in one call. ' +
47
+ 'Pass `question` for natural-language traversal: "what does module X depend on?", "what calls function Y?". ' +
48
+ 'Pass `node` for fast single-node lookup: returns type, file, depends_on, used_by. ' +
49
+ 'Pass both to get node detail + surrounding graph context together. ' +
50
+ 'Returns structured text within token_budget. Use before reading any files.',
106
51
  inputSchema: {
107
52
  type: 'object',
108
53
  properties: {
109
54
  path: { type: 'string', description: 'Project root' },
110
- question: { type: 'string', description: 'Natural language question' },
55
+ question: { type: 'string', description: 'Natural language question about the codebase' },
56
+ node: { type: 'string', description: 'Node name or partial name to look up (type, file, deps, callers)' },
111
57
  token_budget: { type: 'integer', description: 'Max tokens in response (default 2000)' },
112
58
  },
113
- required: ['path', 'question'],
114
- },
115
- },
116
- {
117
- name: 'codegraph_explain',
118
- description:
119
- 'Look up a node by name — returns description, type, file, and direct neighbors (depends_on + used_by). ' +
120
- 'Use to understand what a specific function/class/module does and how it connects. ' +
121
- 'Descriptions are AI-written via codegraph_add_nodes.',
122
- inputSchema: {
123
- type: 'object',
124
- properties: {
125
- path: { type: 'string', description: 'Project root' },
126
- node: { type: 'string', description: 'Node name or partial name' },
127
- },
128
- required: ['path', 'node'],
59
+ required: ['path'],
129
60
  },
130
61
  },
131
62
  {
@@ -28,9 +28,9 @@ export const definition = {
28
28
  `Factual memory — record what happened, what was decided, what broke, what was built.\n` +
29
29
  `• "resume" — START HERE every conversation. Loads recent context, active discussions, and graph status for a project.\n` +
30
30
  `• "save" — Store a note, decision, bug, or code snippet. Auto-deduplicates.\n` +
31
- `• "get" — Load recent entries (compact previews). Auto-digests when large.\n` +
31
+ `• "get" — Load entries. Pass id/ids to fetch specific ones, or project/tags/limit for recent.\n` +
32
32
  `• "update" — Edit an existing entry by id (any field).\n` +
33
- `• "delete" — Remove an entry by id.\n` +
33
+ `• "delete" — Remove one entry (id) or multiple at once (ids: [...]).\n` +
34
34
  `• "list_projects"— Show all projects and entry counts.`,
35
35
  inputSchema: {
36
36
  type: 'object',
@@ -40,17 +40,17 @@ export const definition = {
40
40
  title: { type: 'string' },
41
41
  project: { type: 'string' },
42
42
  rootPath: { type: 'string', description: 'Absolute path to the project root directory. Stored on first call and used to sandbox file/git tool access.' },
43
- type: { type: 'string', enum: ['decision', 'note', 'code', 'bug', 'architecture', 'config', 'summary', 'error'] },
43
+ type: { type: 'string', enum: ['decision', 'bug', 'note', 'config'] },
44
44
  status: { type: 'string', enum: ['active', 'archived'] },
45
45
  tags: { type: 'array', items: { type: 'string' } },
46
46
  source: { type: 'string', enum: ['user', 'ai-summary', 'file', 'web', 'cli', 'auto'] },
47
47
  files: { type: 'array', items: { type: 'object' } },
48
48
  codeRefs: { type: 'array', items: { type: 'object' } },
49
- relations: { type: 'array', items: { type: 'object' } },
50
49
  expiresAt: { type: 'string' },
51
50
  limit: { type: 'number' },
52
51
  includeArchived: { type: 'boolean' },
53
- id: { type: 'string' },
52
+ id: { type: 'string', description: 'Single entry ID (get/update/delete)' },
53
+ ids: { type: 'array', items: { type: 'string' }, description: 'Multiple entry IDs — fetch or delete several at once' },
54
54
  },
55
55
  required: ['action'],
56
56
  },
@@ -116,7 +116,7 @@ export async function handle(args, state) {
116
116
 
117
117
  return {
118
118
  recentEntries: entries,
119
- activeDiscussions: discussions,
119
+ activePlans: discussions,
120
120
  restoredDiscussion: discussions.length === 1 ? { id: discussions[0].id, name: discussions[0].name } : null,
121
121
  codegraph: graphStatus,
122
122
  digest: digest || undefined,
@@ -164,13 +164,12 @@ export async function handle(args, state) {
164
164
  type: args.type || dupe.type, status: args.status || dupe.status,
165
165
  expiresAt: args.expiresAt !== undefined ? args.expiresAt : dupe.expiresAt,
166
166
  files: args.files || dupe.files, codeRefs: args.codeRefs || dupe.codeRefs,
167
- relations: args.relations || dupe.relations,
168
167
  });
169
168
  fireAutoLink(updated.id, state);
170
169
  return { success: true, id: updated.id, deduplicated: true,
171
170
  message: `Updated existing entry "${updated.title || updated.id}" (auto-dedup).` };
172
171
  }
173
- const entry = saveContext({ ...args });
172
+ const entry = saveContext({ ...args, rootPath: state.projectRootPath || undefined });
174
173
  fireAutoLink(entry.id, state);
175
174
 
176
175
  // Auto-compact when too many entries accumulate
@@ -189,8 +188,20 @@ export async function handle(args, state) {
189
188
 
190
189
  case 'get': {
191
190
  if (!args.project && state.sessionProject) args = { ...args, project: state.sessionProject };
192
- archiveExpired(args.project);
193
191
  const includeArchived = args.includeArchived === true;
192
+
193
+ // Fetch by specific ID(s) — bypass project/tag/limit filters
194
+ const ids = args.ids || (args.id ? [args.id] : null);
195
+ if (ids) {
196
+ const entries = getContext({ ids, compact: false })
197
+ .filter(e => includeArchived || e.status !== 'archived');
198
+ return {
199
+ entries, count: entries.length,
200
+ message: entries.length ? `Found ${entries.length} entries.` : 'No entries found for given IDs.',
201
+ };
202
+ }
203
+
204
+ archiveExpired(args.project);
194
205
  let entries = getContext({ project: args.project, tags: args.tags, limit: args.limit, compact: true });
195
206
  if (!includeArchived) entries = entries.filter(e => e.status !== 'archived');
196
207
  const fullEntries = entries.length > 10
@@ -216,8 +227,9 @@ export async function handle(args, state) {
216
227
  }
217
228
 
218
229
  case 'delete': {
219
- if (!args.id) throw new Error('id is required for delete');
220
- return deleteContext(args);
230
+ if (!args.id && !args.ids) throw new Error('id or ids is required for delete');
231
+ const result = deleteContext(args);
232
+ return { ...result, message: `Deleted ${result.deleted} entr${result.deleted === 1 ? 'y' : 'ies'}.` };
221
233
  }
222
234
 
223
235
  case 'list_projects': {
@@ -26,10 +26,8 @@ function autoDetectRoot(fromDir) {
26
26
  }
27
27
 
28
28
  function resolveCwd(args, state) {
29
- // Auto-detect project root on first use if not already configured.
30
29
  if (!state.projectRootPath) {
31
- const detected = autoDetectRoot(args.cwd ? pathResolve(args.cwd) : process.cwd());
32
- state.projectRootPath = detected || process.cwd();
30
+ throw new Error('No project root configured. Call context.resume with rootPath before using git tools.');
33
31
  }
34
32
  const raw = args.cwd ? pathResolve(args.cwd) : state.projectRootPath;
35
33
  return guardPath(raw, state.projectRootPath);