context-mcp-server 1.0.8 → 1.1.1
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/README.md +29 -7
- package/codegraph/__pycache__/affected.cpython-313.pyc +0 -0
- package/codegraph/__pycache__/cache.cpython-313.pyc +0 -0
- package/codegraph/__pycache__/callflow_html.cpython-313.pyc +0 -0
- package/codegraph/__pycache__/export.cpython-313.pyc +0 -0
- package/codegraph/__pycache__/report.cpython-313.pyc +0 -0
- package/codegraph/__pycache__/server.cpython-313.pyc +0 -0
- package/codegraph/__pycache__/tree_html.cpython-313.pyc +0 -0
- package/codegraph/affected.py +233 -0
- package/codegraph/cache.py +51 -2
- package/codegraph/callflow_html.py +273 -0
- package/codegraph/export.py +544 -0
- package/codegraph/extractors/__pycache__/ast_extractor.cpython-313.pyc +0 -0
- package/codegraph/extractors/ast_extractor.py +143 -16
- package/codegraph/graph/__pycache__/builder.cpython-313.pyc +0 -0
- package/codegraph/graph/__pycache__/clustering.cpython-313.pyc +0 -0
- package/codegraph/graph/__pycache__/query.cpython-313.pyc +0 -0
- package/codegraph/graph/__pycache__/symbol_resolution.cpython-313.pyc +0 -0
- package/codegraph/graph/builder.py +10 -0
- package/codegraph/graph/clustering.py +247 -10
- package/codegraph/graph/query.py +99 -0
- package/codegraph/graph/symbol_resolution.py +112 -0
- package/codegraph/report.py +53 -0
- package/codegraph/server.py +112 -20
- package/codegraph/tree_html.py +241 -0
- package/package.json +2 -2
- package/pyproject.toml +4 -1
- package/src/cli.js +329 -227
- package/src/db.js +79 -102
- package/src/search.js +73 -9
- package/src/server.js +7 -1
- package/src/templates/antigravity/GEMINI.md +96 -0
- package/src/templates/antigravity/hooks/context-mcp-post-tool-use.js +62 -0
- package/src/templates/antigravity/workflows/context-resume.md +20 -0
- package/src/templates/antigravity/workflows/graph-build.md +23 -0
- package/src/templates/antigravity/workflows/save-context.md +29 -0
- package/src/templates/claude/CLAUDE.md +140 -0
- package/src/templates/claude/commands/graph-build.md +9 -0
- package/src/templates/claude/commands/save-context.md +19 -0
- package/src/templates/claude/hooks/context-mcp-post-tool-use.js +59 -0
- package/src/templates/claude/hooks/context-mcp-pre-tool-use.js +26 -0
- package/src/templates/claude/skills/SKILL.md +144 -0
- package/src/templates/codex/AGENTS.md +107 -0
- package/src/templates/codex/hooks/context-mcp-post-tool-use.js +46 -0
- package/src/templates/codex/hooks/context-mcp-pre-tool-use.js +23 -0
- package/src/templates/codex/prompts/context-resume.md +15 -0
- package/src/templates/codex/prompts/graph-build.md +14 -0
- package/src/templates/codex/prompts/save-context.md +24 -0
- package/src/templates/cursor/commands/context-resume.md +7 -0
- package/src/templates/cursor/commands/graph-build.md +7 -0
- package/src/templates/cursor/commands/save-context.md +12 -0
- package/src/templates/{cursor-rules.mdc → cursor/cursor-rules.mdc} +13 -3
- package/src/templates/cursor/hooks/context-mcp-post-tool-use.js +55 -0
- package/src/templates/gemini/GEMINI.md +92 -0
- package/src/templates/gemini/commands/context-resume.toml +15 -0
- package/src/templates/gemini/commands/graph-build.toml +14 -0
- package/src/templates/gemini/commands/save-context.toml +24 -0
- package/src/templates/gemini/hooks/context-mcp-after-tool.js +59 -0
- package/src/templates/gemini/hooks/context-mcp-before-tool.js +26 -0
- package/src/templates/vscode/commands/context-resume.prompt.md +15 -0
- package/src/templates/vscode/commands/graph-build.prompt.md +10 -0
- package/src/templates/vscode/commands/save-context.prompt.md +16 -0
- package/src/templates/vscode/hooks/context-mcp-post-tool-use.js +58 -0
- package/src/templates/windsurf/hooks/context-mcp-post-run-command.js +57 -0
- package/src/templates/windsurf/windsurf-rules.md +86 -0
- package/src/templates/windsurf/workflows/context-resume.md +11 -0
- package/src/templates/windsurf/workflows/graph-build.md +11 -0
- package/src/templates/windsurf/workflows/save-context.md +18 -0
- package/src/tools/codegraph.js +83 -43
- package/src/tools/context.js +42 -24
- package/src/tools/plan.js +14 -11
- package/uv.lock +1101 -4
- package/src/migrator.js +0 -124
- package/src/templates/AGENTS.md +0 -80
- package/src/templates/CLAUDE.md +0 -103
- package/src/templates/GEMINI.md +0 -80
- package/src/templates/commands/graph-build.md +0 -5
- package/src/templates/commands/save-context.md +0 -9
- package/src/templates/skills/SKILL.md +0 -108
- package/src/templates/windsurf-rules.md +0 -35
- /package/src/templates/{commands → claude/commands}/context-resume.md +0 -0
package/src/tools/codegraph.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { spawnSync } from 'node:child_process';
|
|
7
7
|
import { dirname, join } from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { saveGraph, saveContext, updateContext, getContext, flushToDisk } from '../db.js';
|
|
9
10
|
|
|
10
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
12
|
const REPO_ROOT = join(__dirname, '..', '..');
|
|
@@ -82,16 +83,55 @@ export const definitions = [
|
|
|
82
83
|
},
|
|
83
84
|
},
|
|
84
85
|
{
|
|
85
|
-
name: '
|
|
86
|
-
description:
|
|
86
|
+
name: 'codegraph_arch',
|
|
87
|
+
description:
|
|
88
|
+
'Return a module map of the project — every file with its exported functions/classes and its imports. ' +
|
|
89
|
+
'Use this to understand project structure without reading any files. ' +
|
|
90
|
+
'Call after codegraph_build. Much faster than reading each file individually.',
|
|
87
91
|
inputSchema: {
|
|
88
92
|
type: 'object',
|
|
89
93
|
properties: {
|
|
90
|
-
path:
|
|
91
|
-
|
|
92
|
-
to: { type: 'string' },
|
|
94
|
+
path: { type: 'string', description: 'Project root' },
|
|
95
|
+
limit: { type: 'integer', description: 'Max files in output (default 100)' },
|
|
93
96
|
},
|
|
94
|
-
required: ['path'
|
|
97
|
+
required: ['path'],
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'codegraph_affected',
|
|
102
|
+
description:
|
|
103
|
+
'BFS traversal: given a node name, find every node that would be affected if you change it — ' +
|
|
104
|
+
'callers, importers, inheritors, etc. Use before refactoring to understand blast radius. ' +
|
|
105
|
+
'Returns affected nodes with file paths, relation types, and traversal depth.',
|
|
106
|
+
inputSchema: {
|
|
107
|
+
type: 'object',
|
|
108
|
+
properties: {
|
|
109
|
+
path: { type: 'string', description: 'Project root' },
|
|
110
|
+
node: { type: 'string', description: 'Node name, ID, or file path to start from' },
|
|
111
|
+
depth: { type: 'integer', description: 'BFS depth (default 2, max 5)' },
|
|
112
|
+
},
|
|
113
|
+
required: ['path', 'node'],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'codegraph_html',
|
|
118
|
+
description:
|
|
119
|
+
'Generate interactive visualizations from the knowledge graph. ' +
|
|
120
|
+
'Outputs: graph.html (vis.js force graph, dark theme, search, community toggle), ' +
|
|
121
|
+
'tree.html (D3 collapsible file tree), callflow.html (Mermaid architecture diagrams), ' +
|
|
122
|
+
'graph.graphml (Gephi/yEd), obsidian/ vault (per-node .md with wikilinks). ' +
|
|
123
|
+
'Run after codegraph_build. Pass formats array to select specific outputs.',
|
|
124
|
+
inputSchema: {
|
|
125
|
+
type: 'object',
|
|
126
|
+
properties: {
|
|
127
|
+
path: { type: 'string', description: 'Project root' },
|
|
128
|
+
formats: {
|
|
129
|
+
type: 'array',
|
|
130
|
+
items: { type: 'string', enum: ['html', 'tree', 'callflow', 'graphml', 'obsidian'] },
|
|
131
|
+
description: 'Formats to generate (default: all)',
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
required: ['path'],
|
|
95
135
|
},
|
|
96
136
|
},
|
|
97
137
|
];
|
|
@@ -103,46 +143,46 @@ export function handle(name, args, state) {
|
|
|
103
143
|
|
|
104
144
|
// Persist graph metadata + save/update a context entry as a visible build record
|
|
105
145
|
if (name === 'codegraph_build' && result.success) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
146
|
+
saveGraph({
|
|
147
|
+
path: args.path,
|
|
148
|
+
nodes: result.nodes,
|
|
149
|
+
edges: result.edges,
|
|
150
|
+
communities: result.communities,
|
|
151
|
+
cached: result.cached,
|
|
152
|
+
changed: result.changed,
|
|
153
|
+
time_ms: result.time_ms,
|
|
154
|
+
summary: result.summary || '',
|
|
155
|
+
});
|
|
156
|
+
flushToDisk(); // write graph.json to disk immediately so ctx list sees it
|
|
117
157
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
158
|
+
const inferredProject = args.path
|
|
159
|
+
? args.path.replace(/\\/g, '/').replace(/\/$/, '').split('/').pop()
|
|
160
|
+
: null;
|
|
161
|
+
const project = state?.sessionProject || inferredProject || null;
|
|
162
|
+
const title = `ContextGraph built — ${args.path}`;
|
|
163
|
+
const content = [
|
|
164
|
+
`nodes: ${result.nodes} | edges: ${result.edges} | communities: ${result.communities}`,
|
|
165
|
+
`cached: ${result.cached} | changed: ${result.changed} | time: ${result.time_ms}ms`,
|
|
166
|
+
result.summary || '',
|
|
167
|
+
].filter(Boolean).join('\n');
|
|
128
168
|
|
|
129
|
-
|
|
130
|
-
|
|
169
|
+
// Search all projects — same path always produces same title regardless of session
|
|
170
|
+
const existing = getContext({ tags: ['codegraph'], limit: 100 })
|
|
171
|
+
.find(e => e.title === title);
|
|
131
172
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}).catch(() => {});
|
|
173
|
+
if (existing) {
|
|
174
|
+
updateContext({ id: existing.id, content, status: 'active' });
|
|
175
|
+
} else {
|
|
176
|
+
saveContext({
|
|
177
|
+
project,
|
|
178
|
+
sessionId: state?.sessionId || null,
|
|
179
|
+
title,
|
|
180
|
+
content,
|
|
181
|
+
type: 'note',
|
|
182
|
+
source: 'auto',
|
|
183
|
+
tags: ['codegraph', 'graph-build'],
|
|
184
|
+
});
|
|
185
|
+
}
|
|
146
186
|
}
|
|
147
187
|
|
|
148
188
|
return result;
|
package/src/tools/context.js
CHANGED
|
@@ -25,22 +25,24 @@ function autoDigest(entries, project) {
|
|
|
25
25
|
export const definition = {
|
|
26
26
|
name: 'context',
|
|
27
27
|
description:
|
|
28
|
-
`Factual memory —
|
|
29
|
-
`• "resume" —
|
|
30
|
-
`• "save" —
|
|
31
|
-
`• "get" —
|
|
32
|
-
`• "update" —
|
|
33
|
-
`• "delete" —
|
|
34
|
-
`• "list_projects"—
|
|
28
|
+
`Factual memory — decisions, bugs, notes, discoveries.\n` +
|
|
29
|
+
`• "resume" — call first every session. Returns recent entries, active plans, graph status.\n` +
|
|
30
|
+
`• "save" — store an entry. Auto-deduplicates by content similarity.\n` +
|
|
31
|
+
`• "get" — fetch by id/ids, or filter by project/tags/limit.\n` +
|
|
32
|
+
`• "update" — edit an entry by id.\n` +
|
|
33
|
+
`• "delete" — remove one entry (id) or several (ids: [...]).\n` +
|
|
34
|
+
`• "list_projects"— list all projects and entry counts.`,
|
|
35
35
|
inputSchema: {
|
|
36
36
|
type: 'object',
|
|
37
37
|
properties: {
|
|
38
38
|
action: { type: 'string', enum: ['resume', 'save', 'get', 'update', 'delete', 'list_projects'] },
|
|
39
39
|
content: { type: 'string' },
|
|
40
|
-
title: { type: 'string' },
|
|
40
|
+
title: { type: 'string', description: 'Up to 120 chars' },
|
|
41
|
+
why: { type: 'string', description: 'Why it mattered' },
|
|
42
|
+
outcome: { type: 'string', description: 'What the result was' },
|
|
41
43
|
project: { type: 'string' },
|
|
42
44
|
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: ['
|
|
45
|
+
type: { type: 'string', enum: ['note', 'compaction'] },
|
|
44
46
|
status: { type: 'string', enum: ['active', 'archived'] },
|
|
45
47
|
tags: { type: 'array', items: { type: 'string' } },
|
|
46
48
|
source: { type: 'string', enum: ['user', 'ai-summary', 'file', 'web', 'cli', 'auto'] },
|
|
@@ -49,8 +51,8 @@ export const definition = {
|
|
|
49
51
|
expiresAt: { type: 'string' },
|
|
50
52
|
limit: { type: 'number' },
|
|
51
53
|
includeArchived: { type: 'boolean' },
|
|
52
|
-
id: { type: 'string', description: 'Single entry ID
|
|
53
|
-
ids: { type: 'array', items: { type: 'string' }, description: 'Multiple entry IDs
|
|
54
|
+
id: { type: 'string', description: 'Single entry ID' },
|
|
55
|
+
ids: { type: 'array', items: { type: 'string' }, description: 'Multiple entry IDs' },
|
|
54
56
|
},
|
|
55
57
|
required: ['action'],
|
|
56
58
|
},
|
|
@@ -78,18 +80,27 @@ export async function handle(args, state) {
|
|
|
78
80
|
const proj = args.project || null;
|
|
79
81
|
archiveExpired(proj);
|
|
80
82
|
|
|
81
|
-
// Set project on state so autoLink works for subsequent saves
|
|
82
83
|
if (proj) state.sessionProject = proj;
|
|
83
84
|
|
|
84
|
-
// Store rootPath with project (first time only) and load it onto session state.
|
|
85
|
-
// Auto-detect from git if neither provided nor previously stored.
|
|
86
85
|
const storedRoot = proj ? getProjectRoot(proj) : null;
|
|
87
86
|
const resolvedRoot = args.rootPath || storedRoot || detectGitRoot() || null;
|
|
88
87
|
if (proj) ensureProject(proj, resolvedRoot || undefined);
|
|
89
88
|
state.projectRootPath = resolvedRoot;
|
|
90
89
|
|
|
91
|
-
const
|
|
90
|
+
const rawEntries = getContext({ project: proj, limit: 15, compact: false })
|
|
92
91
|
.filter(e => e.status !== 'archived');
|
|
92
|
+
// Newest 5 get full content; older entries get a lightweight preview
|
|
93
|
+
const entries = rawEntries.map((e, i) => {
|
|
94
|
+
if (i < 5) return e;
|
|
95
|
+
return {
|
|
96
|
+
id: e.id, project: e.project, title: e.title, type: e.type,
|
|
97
|
+
status: e.status, tags: e.tags, source: e.source,
|
|
98
|
+
createdAt: e.createdAt, updatedAt: e.updatedAt,
|
|
99
|
+
...(e.why ? { why: e.why } : {}),
|
|
100
|
+
...(e.outcome ? { outcome: e.outcome } : {}),
|
|
101
|
+
preview: (e.content || '').slice(0, 200),
|
|
102
|
+
};
|
|
103
|
+
});
|
|
93
104
|
const discussions = listDiscussions({ project: proj, status: 'active' });
|
|
94
105
|
const allGraphs = listGraphs();
|
|
95
106
|
const np = p => (p || '').toLowerCase().replace(/\\/g, '/');
|
|
@@ -103,7 +114,6 @@ export async function handle(args, state) {
|
|
|
103
114
|
: null;
|
|
104
115
|
const totalEntries = countContext(proj);
|
|
105
116
|
|
|
106
|
-
// Auto-restore single active discussion
|
|
107
117
|
if (discussions.length === 1) state.discussionId = discussions[0].id;
|
|
108
118
|
|
|
109
119
|
const digest = totalEntries > 10
|
|
@@ -127,7 +137,7 @@ export async function handle(args, state) {
|
|
|
127
137
|
? `All file and git operations are sandboxed to: ${state.projectRootPath} — do not use paths outside this root.`
|
|
128
138
|
: 'No project root configured — pass rootPath to restrict file/git access to a directory.',
|
|
129
139
|
hint: graphStatus.built
|
|
130
|
-
? `Graph ready (${graphStatus.nodes} nodes). Use codegraph_query for
|
|
140
|
+
? `Graph ready (${graphStatus.nodes} nodes). Use codegraph_arch for module map, codegraph_query for specific symbol lookups.`
|
|
131
141
|
: 'No graph built yet. Call codegraph_build on the project root to enable graph queries.',
|
|
132
142
|
};
|
|
133
143
|
}
|
|
@@ -135,7 +145,6 @@ export async function handle(args, state) {
|
|
|
135
145
|
case 'save': {
|
|
136
146
|
if (!args.content) throw new Error('content is required for save');
|
|
137
147
|
if (!args.project && state.sessionProject) args = { ...args, project: state.sessionProject };
|
|
138
|
-
// Auto-detect and store project root if not yet configured
|
|
139
148
|
if (args.project) {
|
|
140
149
|
const existing = getProjectRoot(args.project);
|
|
141
150
|
if (!existing) {
|
|
@@ -146,7 +155,6 @@ export async function handle(args, state) {
|
|
|
146
155
|
}
|
|
147
156
|
}
|
|
148
157
|
}
|
|
149
|
-
// Validate file paths in files[] and codeRefs[] stay within project root
|
|
150
158
|
if (state.projectRootPath) {
|
|
151
159
|
if (Array.isArray(args.files)) {
|
|
152
160
|
args.files.forEach(f => { if (f.path) guardPath(f.path, state.projectRootPath); });
|
|
@@ -162,6 +170,8 @@ export async function handle(args, state) {
|
|
|
162
170
|
id: dupe.id, content: args.content,
|
|
163
171
|
title: args.title || dupe.title, tags: args.tags || dupe.tags,
|
|
164
172
|
type: args.type || dupe.type, status: args.status || dupe.status,
|
|
173
|
+
why: args.why !== undefined ? args.why : dupe.why,
|
|
174
|
+
outcome: args.outcome !== undefined ? args.outcome : dupe.outcome,
|
|
165
175
|
expiresAt: args.expiresAt !== undefined ? args.expiresAt : dupe.expiresAt,
|
|
166
176
|
files: args.files || dupe.files, codeRefs: args.codeRefs || dupe.codeRefs,
|
|
167
177
|
});
|
|
@@ -172,13 +182,21 @@ export async function handle(args, state) {
|
|
|
172
182
|
const entry = saveContext({ ...args, rootPath: state.projectRootPath || undefined });
|
|
173
183
|
fireAutoLink(entry.id, state);
|
|
174
184
|
|
|
175
|
-
// Auto-compact when too many entries accumulate
|
|
185
|
+
// Auto-compact when too many entries accumulate.
|
|
186
|
+
// If the AI just saved a compaction entry, use that content as the summary
|
|
187
|
+
// instead of running TF-IDF on top of it.
|
|
176
188
|
let compaction = null;
|
|
177
189
|
if (shouldCompact(entry.project)) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
190
|
+
if (entry.type === 'compaction') {
|
|
191
|
+
// AI wrote a proper summary — compact old entries without creating a duplicate summary
|
|
192
|
+
compaction = compactProject(entry.project, entry.content, { skipSummaryEntry: true });
|
|
193
|
+
} else {
|
|
194
|
+
// AI didn't write a summary — fall back to TF-IDF extractive summarization
|
|
195
|
+
const old = getContext({ project: entry.project, limit: 500 });
|
|
196
|
+
const { summarizeEntries: summarize } = await import('../summarizer.js');
|
|
197
|
+
const summaryContent = summarize(old.slice(old.length - 30), { project: entry.project || 'global', sessionLabel: 'auto-compaction', topN: 5 });
|
|
198
|
+
compaction = compactProject(entry.project, summaryContent);
|
|
199
|
+
}
|
|
182
200
|
}
|
|
183
201
|
|
|
184
202
|
return { success: true, id: entry.id, deduplicated: false,
|
package/src/tools/plan.js
CHANGED
|
@@ -19,13 +19,12 @@ function writePlanFile(planDir, name, content, title) {
|
|
|
19
19
|
export const definition = {
|
|
20
20
|
name: 'plan',
|
|
21
21
|
description:
|
|
22
|
-
`
|
|
23
|
-
|
|
24
|
-
`• "
|
|
25
|
-
`• "
|
|
26
|
-
`• "
|
|
27
|
-
`• "
|
|
28
|
-
`• "delete" — Remove a plan by name or id.`,
|
|
22
|
+
`Plan storage — saves to project store, optionally writes a .md file to planDir.\n` +
|
|
23
|
+
`• "save" — store a new plan or overwrite by name.\n` +
|
|
24
|
+
`• "update" — patch title/content/status.\n` +
|
|
25
|
+
`• "get" — retrieve a plan by name or id.\n` +
|
|
26
|
+
`• "list" — list all plans for the project.\n` +
|
|
27
|
+
`• "delete" — remove a plan by name or id.`,
|
|
29
28
|
inputSchema: {
|
|
30
29
|
type: 'object',
|
|
31
30
|
properties: {
|
|
@@ -33,7 +32,7 @@ export const definition = {
|
|
|
33
32
|
name: { type: 'string', description: 'Short slug-style identifier for the plan, e.g. "auth-refactor"' },
|
|
34
33
|
id: { type: 'string' },
|
|
35
34
|
project: { type: 'string' },
|
|
36
|
-
title: { type: 'string', description: '
|
|
35
|
+
title: { type: 'string', description: 'Plan title' },
|
|
37
36
|
content: { type: 'string', description: 'Full plan summary in markdown' },
|
|
38
37
|
status: { type: 'string', enum: ['active', 'done'] },
|
|
39
38
|
tags: { type: 'array', items: { type: 'string' } },
|
|
@@ -83,6 +82,11 @@ export async function handle(args, state) {
|
|
|
83
82
|
|
|
84
83
|
case 'update': {
|
|
85
84
|
if (!args.name && !args.id) throw new Error('name or id is required for update');
|
|
85
|
+
if (args.status === 'done') {
|
|
86
|
+
const result = deleteDiscussion({ name: args.name, id: args.id });
|
|
87
|
+
state.discussionId = null;
|
|
88
|
+
return { success: true, message: `Plan "${args.name || args.id}" completed and removed.` };
|
|
89
|
+
}
|
|
86
90
|
const updated = updateDiscussion({
|
|
87
91
|
name: args.name,
|
|
88
92
|
id: args.id,
|
|
@@ -92,13 +96,12 @@ export async function handle(args, state) {
|
|
|
92
96
|
tags: args.tags,
|
|
93
97
|
});
|
|
94
98
|
if (!updated) throw new Error(`No plan found for "${args.name || args.id}".`);
|
|
95
|
-
if (updated.status !== 'active' && state.discussionId === updated.id) state.discussionId = null;
|
|
96
99
|
if (updated.status === 'active') state.discussionId = updated.id;
|
|
97
100
|
const filePath = writePlanFile(args.planDir, updated.name, updated.content, updated.title);
|
|
98
101
|
return {
|
|
99
|
-
success: true, id: updated.id, name: updated.name,
|
|
102
|
+
success: true, id: updated.id, name: updated.name,
|
|
100
103
|
filePath: filePath || undefined,
|
|
101
|
-
message: `Plan "${updated.name}" updated
|
|
104
|
+
message: `Plan "${updated.name}" updated.`,
|
|
102
105
|
};
|
|
103
106
|
}
|
|
104
107
|
|