devsmind-mcp 3.0.0 → 4.0.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 +11 -20
- package/dist/cli/diff.js +1 -1
- package/dist/cli/diff.js.map +1 -1
- package/dist/cli/index.js +20 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/integrations/mcp.js +22 -1
- package/dist/cli/integrations/mcp.js.map +1 -1
- package/dist/cli/integrations/memory-topics.d.ts +21 -10
- package/dist/cli/integrations/memory-topics.js +32 -9
- package/dist/cli/integrations/memory-topics.js.map +1 -1
- package/dist/cli/integrations/memory.d.ts +14 -9
- package/dist/cli/integrations/memory.js +47 -229
- package/dist/cli/integrations/memory.js.map +1 -1
- package/dist/cli/integrations/registry.d.ts +27 -16
- package/dist/cli/integrations/registry.js +66 -34
- package/dist/cli/integrations/registry.js.map +1 -1
- package/dist/cli/rule.js +11 -16
- package/dist/cli/rule.js.map +1 -1
- package/dist/db/activity-graph.d.ts +55 -0
- package/dist/db/activity-graph.js +314 -0
- package/dist/db/activity-graph.js.map +1 -0
- package/dist/db/activity.d.ts +21 -0
- package/dist/db/activity.js +3 -2
- package/dist/db/activity.js.map +1 -1
- package/dist/db/database.d.ts +72 -4
- package/dist/db/database.js +89 -8
- package/dist/db/database.js.map +1 -1
- package/dist/db/index-build.d.ts +75 -0
- package/dist/db/index-build.js +177 -0
- package/dist/db/index-build.js.map +1 -0
- package/dist/db/revert.js +1 -1
- package/dist/db/revert.js.map +1 -1
- package/dist/db/schema.d.ts +2 -2
- package/dist/db/staging.d.ts +3 -2
- package/dist/db/staging.js +1 -1
- package/dist/db/staging.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +215 -208
- package/dist/mcp/server.js.map +1 -1
- package/dist/utils/config.d.ts +15 -0
- package/dist/utils/config.js +27 -0
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/scanner.d.ts +6 -4
- package/dist/utils/scanner.js +6 -4
- package/dist/utils/scanner.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,250 +1,68 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.handleMemory = handleMemory;
|
|
37
|
-
const path = __importStar(require("path"));
|
|
38
|
-
const config_1 = require("../../utils/config");
|
|
39
4
|
const registry_1 = require("./registry");
|
|
40
5
|
const memory_topics_1 = require("./memory-topics");
|
|
41
6
|
const prompt_1 = require("./prompt");
|
|
42
|
-
const MEMORY_FILE_HEADER = '<!-- Seeded by `devsmind memory` — the DevsMind team code-graph MCP server -->\n\n';
|
|
43
7
|
/**
|
|
44
|
-
* `devsmind memory` —
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
8
|
+
* `devsmind memory` — prints ONE natural-language block to paste into any AI chat, framed as an
|
|
9
|
+
* explicit "remember this" request. DevsMind writes nothing on your behalf.
|
|
10
|
+
*
|
|
11
|
+
* Why: research across all 9 tools DevsMind integrates with turned up the same finding stated
|
|
12
|
+
* independently in several of those tools' own docs — background/automatic memory is
|
|
13
|
+
* discretionary by design (Windsurf, Codex, and Qwen say so outright; e.g. "auto-memory is
|
|
14
|
+
* best-effort, QWEN.md is guaranteed"), while an EXPLICIT in-chat request is the one thing that
|
|
15
|
+
* reliably lands. A silently-written file never crosses that trigger at all. And 5 of the 9 tools
|
|
16
|
+
* (Cursor, VS Code/Copilot, Windsurf, Kiro, Qwen) have no file DevsMind could safely write to in
|
|
17
|
+
* the first place — hand-writing into an undocumented or auto-generated store risks corrupting
|
|
18
|
+
* it. So instead of maintaining 9 different internal file formats, this hands the user one prompt
|
|
19
|
+
* and lets each tool's own native memory feature do whatever it already does best with it.
|
|
50
20
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* (Antigravity Skills) — see memory-topics.ts for why.
|
|
21
|
+
* `--tool` only changes the framing line (which feature name to call out, e.g. "Cursor calls
|
|
22
|
+
* this Memories") — the prompt itself, from `renderMemoryPrompt`, is the same for everyone.
|
|
54
23
|
*/
|
|
55
24
|
async function handleMemory(opts) {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
try {
|
|
67
|
-
const target = await (0, prompt_1.pickTarget)();
|
|
68
|
-
const mem = target.memory;
|
|
69
|
-
if (!mem.supported) {
|
|
70
|
-
const divider = '─'.repeat(70);
|
|
71
|
-
console.log(`\n${divider}`);
|
|
72
|
-
console.log(` ${target.label} — ${mem.featureName}`);
|
|
73
|
-
console.log(`${divider}\n`);
|
|
74
|
-
console.log(`⚠️ Nothing written — there's no safe way to pre-seed this.\n`);
|
|
75
|
-
console.log(mem.note);
|
|
76
|
-
console.log('');
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
console.log(`\nℹ️ ${target.label} calls this "${mem.featureName}".`);
|
|
80
|
-
console.log(` ${mem.note}`);
|
|
81
|
-
const mode = await (0, prompt_1.pickMode)();
|
|
82
|
-
const scope = await (0, prompt_1.pickMemoryScope)(target);
|
|
83
|
-
const docs = buildDocs(target, scope);
|
|
84
|
-
if (mode === 'manual') {
|
|
85
|
-
printManual(target.label, scope, workspaceRoot, docs, mem.pointerFile?.file);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
// Automatic mode.
|
|
89
|
-
const targetDir = await resolveMemoryDir(scope, target.label, workspaceRoot);
|
|
90
|
-
const merged = docs.map(doc => {
|
|
91
|
-
const filePath = path.join(targetDir, doc.file);
|
|
92
|
-
return { doc, filePath, ...(0, prompt_1.mergeRuleFile)(filePath, doc.content, 'standalone') };
|
|
93
|
-
});
|
|
94
|
-
console.log(`\n📝 Target: ${targetDir.replace(/\\/g, '/')}`);
|
|
95
|
-
if (merged.length === 1) {
|
|
96
|
-
console.log(` ${merged[0].doc.file} (${merged[0].existed ? 'overwrite our own file' : 'create new'})`);
|
|
97
|
-
console.log(`\nContent to be written:\n`);
|
|
98
|
-
console.log(indent(merged[0].content));
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
const fresh = merged.filter(m => !m.existed).length;
|
|
102
|
-
console.log(` ${merged.length} files — ${fresh} new, ${merged.length - fresh} overwritten (all DevsMind's own):\n`);
|
|
103
|
-
for (const m of merged) {
|
|
104
|
-
console.log(` ${m.existed ? '↻' : '+'} ${m.doc.file}`);
|
|
105
|
-
if (m.doc.summary)
|
|
106
|
-
console.log(` ${m.doc.summary}`);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
const indexLines = mem.pointerFile && docs.length > 1 ? memory_topics_1.MEMORY_TOPICS.map(memory_topics_1.renderIndexLine).join('\n') : null;
|
|
110
|
-
if (indexLines && mem.pointerFile) {
|
|
111
|
-
console.log(`\n …plus an index block in ${mem.pointerFile.file} (that file is what loads every session — without it these are never found):\n`);
|
|
112
|
-
console.log(indent(indexLines));
|
|
113
|
-
}
|
|
114
|
-
const ok = await (0, prompt_1.confirmPrompt)('Write this?', true);
|
|
115
|
-
if (!ok) {
|
|
116
|
-
console.log('\nAborted — nothing written.');
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
for (const m of merged)
|
|
120
|
-
(0, prompt_1.writeConfigFile)(m.filePath, m.content);
|
|
121
|
-
console.log(`\n✅ Seeded ${target.label}'s ${mem.featureName} — ` +
|
|
122
|
-
`${merged.length === 1 ? merged[0].filePath.replace(/\\/g, '/') : `${merged.length} files in ${targetDir.replace(/\\/g, '/')}`}`);
|
|
123
|
-
if (mem.pointerFile) {
|
|
124
|
-
const pointerPath = path.join(targetDir, mem.pointerFile.file);
|
|
125
|
-
const body = indexLines ?? singlePointerLine(docs[0].file);
|
|
126
|
-
const pointerConfirm = await (0, prompt_1.confirmPrompt)(`\nAlso write the ${indexLines ? 'index block' : 'pointer line'} into ${mem.pointerFile.file}, so this gets found ` +
|
|
127
|
-
`(it only loads "on demand" otherwise)?`, true);
|
|
128
|
-
if (pointerConfirm) {
|
|
129
|
-
const pointerMerged = (0, prompt_1.mergeRuleFile)(pointerPath, body, mem.pointerFile.style);
|
|
130
|
-
if (pointerMerged.error) {
|
|
131
|
-
console.error(`❌ ${pointerMerged.error}`);
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
(0, prompt_1.writeConfigFile)(pointerPath, pointerMerged.content);
|
|
135
|
-
console.log(`✅ ${indexLines ? 'Index' : 'Pointer'} written to ${pointerPath.replace(/\\/g, '/')}`);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
catch (err) {
|
|
141
|
-
if (err instanceof prompt_1.CancelledError) {
|
|
142
|
-
console.log('\nCancelled.');
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
throw err;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
/** The tools that have a memory/skills store DevsMind can safely write to — the only valid `--tool` values. */
|
|
149
|
-
function memoryCapableTargets() {
|
|
150
|
-
return registry_1.TARGETS.filter(t => t.memory.supported && (t.memory.scopes?.length ?? 0) > 0);
|
|
25
|
+
const target = await resolveTarget(opts);
|
|
26
|
+
if (!target)
|
|
27
|
+
return; // already reported: unknown --tool, or the interactive picker was cancelled
|
|
28
|
+
console.log(`\nℹ️ ${target.label} calls this "${target.memory.featureName}".`);
|
|
29
|
+
if (target.memory.note)
|
|
30
|
+
console.log(` ${target.memory.note}`);
|
|
31
|
+
console.log(`\n📋 Paste this into your ${target.label} chat and ask it to remember it:\n`);
|
|
32
|
+
console.log(indent((0, memory_topics_1.renderMemoryPrompt)()));
|
|
33
|
+
console.log('');
|
|
151
34
|
}
|
|
152
35
|
/**
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* which tool you mean. `--tool` prints exactly what that tool would receive instead.
|
|
36
|
+
* `--tool` resolves directly. Otherwise, an interactive TTY offers the same picker `devsmind
|
|
37
|
+
* rule`/`devsmind mcp` use (purely for the framing line — nothing here depends on the answer
|
|
38
|
+
* the way file placement used to); a non-interactive/piped run — or an explicit `--print` — falls
|
|
39
|
+
* back to Claude Code's framing and says so, so scripted use never blocks on a prompt.
|
|
158
40
|
*/
|
|
159
|
-
function
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
let target;
|
|
163
|
-
if (toolId) {
|
|
164
|
-
target = (0, registry_1.getTarget)(toolId);
|
|
41
|
+
async function resolveTarget(opts) {
|
|
42
|
+
if (opts.tool) {
|
|
43
|
+
const target = (0, registry_1.getTarget)(opts.tool);
|
|
165
44
|
if (!target) {
|
|
166
|
-
console.error(`❌ Unknown tool "${
|
|
45
|
+
console.error(`❌ Unknown tool "${opts.tool}". Valid values for --tool: ${registry_1.TARGETS.map(t => t.id).join(', ')}`);
|
|
167
46
|
process.exit(1);
|
|
168
|
-
return;
|
|
47
|
+
return undefined;
|
|
169
48
|
}
|
|
170
|
-
|
|
171
|
-
console.error(`❌ ${target.label} has no memory store DevsMind can pre-seed.\n` +
|
|
172
|
-
` ${target.memory.note}\n` +
|
|
173
|
-
` Tools that do: ${valid}`);
|
|
174
|
-
process.exit(1);
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
else {
|
|
179
|
-
target = capable.find(t => t.id === 'claude-code') ?? capable[0];
|
|
180
|
-
console.log(`ℹ️ No --tool given — showing the ${target.label} shape. Others: ${valid}\n`);
|
|
181
|
-
}
|
|
182
|
-
const scope = target.memory.scopes[0];
|
|
183
|
-
printManual(target.label, scope, workspaceRoot, buildDocs(target, scope), target.memory.pointerFile?.file);
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* Turn the shared topics into the file shape this particular store reads:
|
|
187
|
-
* one file per topic when it ranks files individually, one combined document
|
|
188
|
-
* otherwise. The tool's own `wrap` (e.g. Skills frontmatter) applies only to the
|
|
189
|
-
* combined shape — per-topic files carry their own frontmatter already.
|
|
190
|
-
*/
|
|
191
|
-
function buildDocs(target, scope) {
|
|
192
|
-
if (scope.format === 'memory-files') {
|
|
193
|
-
return memory_topics_1.MEMORY_TOPICS.map(topic => ({
|
|
194
|
-
file: `${topic.name}.md`,
|
|
195
|
-
content: (0, memory_topics_1.renderTopicFile)(topic, MEMORY_FILE_HEADER),
|
|
196
|
-
summary: topic.description,
|
|
197
|
-
}));
|
|
198
|
-
}
|
|
199
|
-
const combined = (0, memory_topics_1.renderCombined)(MEMORY_FILE_HEADER);
|
|
200
|
-
const content = scope.format === 'skill-md' && target.memory.wrap ? target.memory.wrap(combined) : combined;
|
|
201
|
-
return [{ file: scope.file ?? 'devsmind.md', content }];
|
|
202
|
-
}
|
|
203
|
-
/** Fallback for a single-file store whose index still wants one line pointing at it. */
|
|
204
|
-
function singlePointerLine(file) {
|
|
205
|
-
return `See \`${file}\` in this folder for the DevsMind workflow contract — \`start_session\` before your ` +
|
|
206
|
-
`first write (every write call requires that session_id; reads don't), \`search_nodes\` (query and/or a ` +
|
|
207
|
-
`real-regex \`pattern\`) before any grep, \`edit_node\` for every file you write, ` +
|
|
208
|
-
`\`commit_changes\` (with \`message\`, \`reasoning\`, and \`feedback\`) before the turn ends.`;
|
|
209
|
-
}
|
|
210
|
-
/** Resolve the directory to write into, prompting the user to navigate when the exact path isn't knowable. */
|
|
211
|
-
async function resolveMemoryDir(scope, label, workspaceRoot) {
|
|
212
|
-
if (scope.needsUserConfirmedDir) {
|
|
213
|
-
const start = (0, registry_1.resolveOsPath)(scope.dir);
|
|
214
|
-
return (0, prompt_1.pickDirectory)(start, `Navigate to the correct folder for ${label} (e.g. .../projects/<your-project-hash>/memory)`);
|
|
215
|
-
}
|
|
216
|
-
if (scope.scope === 'project') {
|
|
217
|
-
const base = await (0, prompt_1.pickDirectory)(workspaceRoot, `Where is the project root for ${label}?`);
|
|
218
|
-
return path.join(base, (0, registry_1.resolveOsPath)(scope.dir));
|
|
219
|
-
}
|
|
220
|
-
return (0, registry_1.resolveScopeFile)(scope.dir, 'global', workspaceRoot);
|
|
221
|
-
}
|
|
222
|
-
function printManual(label, scope, workspaceRoot, docs, pointerFile) {
|
|
223
|
-
const divider = '─'.repeat(70);
|
|
224
|
-
const dirHint = scope.needsUserConfirmedDir
|
|
225
|
-
? `${(0, registry_1.resolveOsPath)(scope.dir)}/<your-project-hash>/...`
|
|
226
|
-
: scope.scope === 'project'
|
|
227
|
-
? path.join(workspaceRoot, (0, registry_1.resolveOsPath)(scope.dir)).replace(/\\/g, '/')
|
|
228
|
-
: (0, registry_1.resolveOsPath)(scope.dir);
|
|
229
|
-
const dir = String(dirHint).replace(/\\/g, '/');
|
|
230
|
-
console.log(`\n${divider}`);
|
|
231
|
-
console.log(` Seed DevsMind into ${label}`);
|
|
232
|
-
console.log(`${divider}`);
|
|
233
|
-
console.log(`\n1. Create ${docs.length === 1 ? 'this file' : `these ${docs.length} files`} under:`);
|
|
234
|
-
console.log(` ${dir}/`);
|
|
235
|
-
for (const doc of docs) {
|
|
236
|
-
console.log(`\n${divider}`);
|
|
237
|
-
console.log(` ${dir}/${doc.file}`);
|
|
238
|
-
console.log(`${divider}\n`);
|
|
239
|
-
console.log(indent(doc.content));
|
|
49
|
+
return target;
|
|
240
50
|
}
|
|
241
|
-
if (
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
51
|
+
if (!opts.print && process.stdin.isTTY && process.stdout.isTTY) {
|
|
52
|
+
try {
|
|
53
|
+
return await (0, prompt_1.pickTarget)();
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
if (err instanceof prompt_1.CancelledError) {
|
|
57
|
+
console.log('\nCancelled.');
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
247
62
|
}
|
|
63
|
+
const fallback = (0, registry_1.getTarget)('claude-code');
|
|
64
|
+
console.log(`ℹ️ No --tool given — showing generic phrasing (the prompt itself is identical for every tool). Others: ${registry_1.TARGETS.map(t => t.id).join(', ')}`);
|
|
65
|
+
return fallback;
|
|
248
66
|
}
|
|
249
67
|
function indent(text) {
|
|
250
68
|
return text.split('\n').map(l => ' ' + l).join('\n');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../../src/cli/integrations/memory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../../src/cli/integrations/memory.ts"],"names":[],"mappings":";;AAqBA,oCASC;AA9BD,yCAA2D;AAC3D,mDAAqD;AACrD,qCAAsD;AAEtD;;;;;;;;;;;;;;;;GAgBG;AACI,KAAK,UAAU,YAAY,CAAC,IAAuD;IACxF,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,CAAC,MAAM;QAAE,OAAO,CAAC,4EAA4E;IAEjG,OAAO,CAAC,GAAG,CAAC,SAAS,MAAM,CAAC,KAAK,gBAAgB,MAAM,CAAC,MAAM,CAAC,WAAW,IAAI,CAAC,CAAC;IAChF,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI;QAAE,OAAO,CAAC,GAAG,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAChE,OAAO,CAAC,GAAG,CAAC,6BAA6B,MAAM,CAAC,KAAK,oCAAoC,CAAC,CAAC;IAC3F,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAA,kCAAkB,GAAE,CAAC,CAAC,CAAC;IAC1C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,aAAa,CAAC,IAAwC;IACnE,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,MAAM,MAAM,GAAG,IAAA,oBAAS,EAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,KAAK,CAAC,mBAAmB,IAAI,CAAC,IAAI,+BAA+B,kBAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC9G,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAChB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAC/D,IAAI,CAAC;YACH,OAAO,MAAM,IAAA,mBAAU,GAAE,CAAC;QAC5B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,uBAAc,EAAE,CAAC;gBAClC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;gBAC5B,OAAO,SAAS,CAAC;YACnB,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,IAAA,oBAAS,EAAC,aAAa,CAAE,CAAC;IAC3C,OAAO,CAAC,GAAG,CAAC,2GAA2G,kBAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5J,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,MAAM,CAAC,IAAY;IAC1B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzD,CAAC"}
|
|
@@ -55,8 +55,8 @@ export interface IdeTarget {
|
|
|
55
55
|
scopes: McpScope[];
|
|
56
56
|
/** Supported transports; first is the preferred default. */
|
|
57
57
|
transports: Transport[];
|
|
58
|
-
/** The value object placed under serverMap['devsmind'] for a given transport. */
|
|
59
|
-
entry: (t: Transport, ctx: EntryContext) => Record<string, unknown>;
|
|
58
|
+
/** The value object placed under serverMap['devsmind'] for a given transport and scope. */
|
|
59
|
+
entry: (t: Transport, ctx: EntryContext, scope: Scope) => Record<string, unknown>;
|
|
60
60
|
/** Optional CLI one-liner installer (e.g. `claude mcp add ...`). */
|
|
61
61
|
cliInstaller?: (t: Transport, ctx: EntryContext) => string;
|
|
62
62
|
/** Extra guidance printed in manual mode. */
|
|
@@ -70,16 +70,19 @@ export interface IdeTarget {
|
|
|
70
70
|
wrap?: (body: string) => string;
|
|
71
71
|
};
|
|
72
72
|
memory: {
|
|
73
|
-
/**
|
|
73
|
+
/** `devsmind memory` writes nothing for any tool (see integrations/memory.ts) — this and the
|
|
74
|
+
* fields below are read only for their FRAMING value (the tool's own feature name + a short
|
|
75
|
+
* caveat), not to decide whether or how to write. `scopes`/`wrap`/`pointerFile` describe a
|
|
76
|
+
* write path that no longer exists; kept only as still-accurate research about each tool's
|
|
77
|
+
* own store, in case a genuinely safe write target ever becomes available again. */
|
|
74
78
|
supported: boolean;
|
|
75
79
|
/** The tool's own name for this feature — use it verbatim in prompts, not a generic "memory". */
|
|
76
80
|
featureName: string;
|
|
77
81
|
scopes?: MemoryScope[];
|
|
78
82
|
wrap?: (body: string) => string;
|
|
79
|
-
/**
|
|
83
|
+
/** The one-line caveat shown alongside the printed prompt. */
|
|
80
84
|
note: string;
|
|
81
|
-
/**
|
|
82
|
-
* unreferenced topic file (loaded only "on demand") is actually discoverable. */
|
|
85
|
+
/** Unused by `devsmind memory` (see above) — retained only alongside `scopes` as research. */
|
|
83
86
|
pointerFile?: {
|
|
84
87
|
file: string;
|
|
85
88
|
style: 'append-section';
|
|
@@ -92,18 +95,26 @@ export interface EntryContext {
|
|
|
92
95
|
port: number;
|
|
93
96
|
}
|
|
94
97
|
/**
|
|
95
|
-
* Standard stdio entry: the IDE spawns `devsmind start --stdio --path <devmindDir
|
|
98
|
+
* Standard stdio entry: the IDE spawns `devsmind start --stdio [--path <devmindDir>]`.
|
|
96
99
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
100
|
+
* `--path` is included for `project` scope only. A project-scoped config file lives inside — and
|
|
101
|
+
* so only ever serves — the one project it was written for, so baking in the absolute brain path
|
|
102
|
+
* (which the registration flow already knows via `ctx.devmindDir`) makes binding deterministic
|
|
103
|
+
* regardless of what cwd the IDE happens to spawn from.
|
|
104
|
+
*
|
|
105
|
+
* A `global` config is different: ONE file, read by every project on the machine. Baking a single
|
|
106
|
+
* project's path into it would silently point every other project at that same brain — the exact
|
|
107
|
+
* bug this scope split exists to avoid. So a global entry omits `--path` entirely and leans on the
|
|
108
|
+
* server's own auto-detect (`bindServerToProject` walks up from its cwd looking for `.devmind`),
|
|
109
|
+
* which works because the IDE spawns the stdio server from whichever workspace is actually open.
|
|
110
|
+
* If an IDE spawns from somewhere else instead (not the common case), the server starts unbound and
|
|
111
|
+
* falls back to per-call `devmind_path` — degraded, but never silently wrong.
|
|
112
|
+
*
|
|
113
|
+
* Path may contain spaces; it's a separate argv element, so no quoting needed.
|
|
104
114
|
*/
|
|
105
|
-
export declare function stdioEntry(ctx: EntryContext): Record<string, unknown>;
|
|
106
|
-
/** Standard HTTP entry: connect to the already-running server.
|
|
115
|
+
export declare function stdioEntry(ctx: EntryContext, scope: Scope): Record<string, unknown>;
|
|
116
|
+
/** Standard HTTP entry: connect to the already-running server. Scope-independent — the URL never
|
|
117
|
+
* names a project, since the server itself is single-project no matter which config points at it. */
|
|
107
118
|
export declare function httpEntry(ctx: EntryContext): Record<string, unknown>;
|
|
108
119
|
/** Resolve an {@link OsPath} to a concrete string for the current platform, expanding a leading `~`. */
|
|
109
120
|
export declare function resolveOsPath(p: OsPath): string;
|
|
@@ -43,20 +43,31 @@ const os = __importStar(require("os"));
|
|
|
43
43
|
const path = __importStar(require("path"));
|
|
44
44
|
// ─── Shared entry payloads ───────────────────────────────────────────────────
|
|
45
45
|
/**
|
|
46
|
-
* Standard stdio entry: the IDE spawns `devsmind start --stdio --path <devmindDir
|
|
46
|
+
* Standard stdio entry: the IDE spawns `devsmind start --stdio [--path <devmindDir>]`.
|
|
47
47
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
48
|
+
* `--path` is included for `project` scope only. A project-scoped config file lives inside — and
|
|
49
|
+
* so only ever serves — the one project it was written for, so baking in the absolute brain path
|
|
50
|
+
* (which the registration flow already knows via `ctx.devmindDir`) makes binding deterministic
|
|
51
|
+
* regardless of what cwd the IDE happens to spawn from.
|
|
52
|
+
*
|
|
53
|
+
* A `global` config is different: ONE file, read by every project on the machine. Baking a single
|
|
54
|
+
* project's path into it would silently point every other project at that same brain — the exact
|
|
55
|
+
* bug this scope split exists to avoid. So a global entry omits `--path` entirely and leans on the
|
|
56
|
+
* server's own auto-detect (`bindServerToProject` walks up from its cwd looking for `.devmind`),
|
|
57
|
+
* which works because the IDE spawns the stdio server from whichever workspace is actually open.
|
|
58
|
+
* If an IDE spawns from somewhere else instead (not the common case), the server starts unbound and
|
|
59
|
+
* falls back to per-call `devmind_path` — degraded, but never silently wrong.
|
|
60
|
+
*
|
|
61
|
+
* Path may contain spaces; it's a separate argv element, so no quoting needed.
|
|
55
62
|
*/
|
|
56
|
-
function stdioEntry(ctx) {
|
|
57
|
-
|
|
63
|
+
function stdioEntry(ctx, scope) {
|
|
64
|
+
const args = ['start', '--stdio'];
|
|
65
|
+
if (scope === 'project')
|
|
66
|
+
args.push('--path', ctx.devmindDir);
|
|
67
|
+
return { command: 'devsmind', args };
|
|
58
68
|
}
|
|
59
|
-
/** Standard HTTP entry: connect to the already-running server.
|
|
69
|
+
/** Standard HTTP entry: connect to the already-running server. Scope-independent — the URL never
|
|
70
|
+
* names a project, since the server itself is single-project no matter which config points at it. */
|
|
60
71
|
function httpEntry(ctx) {
|
|
61
72
|
return { url: `http://localhost:${ctx.port}/mcp` };
|
|
62
73
|
}
|
|
@@ -85,18 +96,32 @@ function resolveScopeFile(file, scope, workspaceRoot) {
|
|
|
85
96
|
// and some require an explicit `type`. These builders capture each tool's shape.
|
|
86
97
|
const httpUrl = (ctx) => `http://localhost:${ctx.port}/mcp`;
|
|
87
98
|
/** Cursor / Kiro: bare `url`, no type. */
|
|
88
|
-
const entryUrl = (t, ctx) => t === 'stdio' ? stdioEntry(ctx) : { url: httpUrl(ctx) };
|
|
99
|
+
const entryUrl = (t, ctx, scope) => t === 'stdio' ? stdioEntry(ctx, scope) : { url: httpUrl(ctx) };
|
|
89
100
|
/** VS Code / Claude Code: explicit `type` + url. */
|
|
90
|
-
const entryTyped = (t, ctx) => t === 'stdio'
|
|
91
|
-
? { type: 'stdio', ...stdioEntry(ctx) }
|
|
101
|
+
const entryTyped = (t, ctx, scope) => t === 'stdio'
|
|
102
|
+
? { type: 'stdio', ...stdioEntry(ctx, scope) }
|
|
92
103
|
: { type: 'http', url: httpUrl(ctx) };
|
|
93
104
|
/** Windsurf / Antigravity: HTTP endpoint keyed as `serverUrl`. */
|
|
94
|
-
const entryServerUrl = (t, ctx) => t === 'stdio' ? stdioEntry(ctx) : { serverUrl: httpUrl(ctx) };
|
|
105
|
+
const entryServerUrl = (t, ctx, scope) => t === 'stdio' ? stdioEntry(ctx, scope) : { serverUrl: httpUrl(ctx) };
|
|
95
106
|
/** Qwen Code: Streamable-HTTP endpoint keyed as `httpUrl`. */
|
|
96
|
-
const entryHttpUrl = (t, ctx) => t === 'stdio' ? stdioEntry(ctx) : { httpUrl: httpUrl(ctx) };
|
|
107
|
+
const entryHttpUrl = (t, ctx, scope) => t === 'stdio' ? stdioEntry(ctx, scope) : { httpUrl: httpUrl(ctx) };
|
|
97
108
|
const cursorMdcWrap = (body) => `---\ndescription: DevsMind — Team AI Brain workspace rule\nalwaysApply: true\n---\n\n${body}\n`;
|
|
98
|
-
/**
|
|
99
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Skill format: YAML frontmatter (name + description) + markdown body.
|
|
111
|
+
*
|
|
112
|
+
* Shared by every target that reads `.agents/skills/` — Antigravity (IDE + CLI) and Codex all
|
|
113
|
+
* discover the SAME `.agents/skills/devsmind/SKILL.md`. One wrap, deliberately: these writes are
|
|
114
|
+
* whole-file (`standalone`), so per-tool frontmatter would mean whichever command ran last
|
|
115
|
+
* silently rewrote the others' file. Identical bytes make that collision a no-op instead.
|
|
116
|
+
*/
|
|
117
|
+
const skillMdWrap = (body) => `---\nname: devsmind\ndescription: DevsMind team code-graph MCP server — when and how to use it\n---\n\n${body}\n`;
|
|
118
|
+
/** The one skill location all `.agents/skills/` readers share. See {@link skillMdWrap}. */
|
|
119
|
+
const AGENTS_SKILL_SCOPE = {
|
|
120
|
+
scope: 'project',
|
|
121
|
+
dir: '.agents/skills/devsmind',
|
|
122
|
+
file: 'SKILL.md',
|
|
123
|
+
format: 'skill-md',
|
|
124
|
+
};
|
|
100
125
|
// VS Code user-profile mcp.json lives in the platform user-data dir.
|
|
101
126
|
const VSCODE_GLOBAL = {
|
|
102
127
|
win32: '~/AppData/Roaming/Code/User/mcp.json',
|
|
@@ -128,7 +153,7 @@ exports.TARGETS = [
|
|
|
128
153
|
memory: {
|
|
129
154
|
supported: false,
|
|
130
155
|
featureName: 'Memories',
|
|
131
|
-
note: 'Cursor\'s Memories are stored in an internal, undocumented database and only save after the agent proposes one and you approve it — there is no file DevsMind can safely write to. Ask the agent to remember the DevsMind workflow in conversation (e.g. "remember to always search_nodes before grep, and
|
|
156
|
+
note: 'Cursor\'s Memories are stored in an internal, undocumented database and only save after the agent proposes one and you approve it — there is no file DevsMind can safely write to. Ask the agent to remember the DevsMind workflow in conversation (e.g. "remember to always search_nodes before grep, and edit_node + commit_changes after every change") and approve the memory Cursor proposes.',
|
|
132
157
|
},
|
|
133
158
|
},
|
|
134
159
|
{
|
|
@@ -195,7 +220,7 @@ exports.TARGETS = [
|
|
|
195
220
|
memory: {
|
|
196
221
|
supported: false,
|
|
197
222
|
featureName: 'Knowledge / PR-comment learning',
|
|
198
|
-
note: 'Kiro has no file-based memory: its manual "Knowledge" store uses JSON + embeddings (not something safe to hand-write), and its autonomous agent\'s PR-comment-driven learning is an undocumented, AWS-internal, non-file-based store. The one thing DevsMind CAN influence — steering docs — is already handled by `devsmind rule`. To also engage the autonomous agent\'s learning, leave a PR review comment once, e.g. "always call search_nodes before grep, and
|
|
223
|
+
note: 'Kiro has no file-based memory: its manual "Knowledge" store uses JSON + embeddings (not something safe to hand-write), and its autonomous agent\'s PR-comment-driven learning is an undocumented, AWS-internal, non-file-based store. The one thing DevsMind CAN influence — steering docs — is already handled by `devsmind rule`. To also engage the autonomous agent\'s learning, leave a PR review comment once, e.g. "always call search_nodes before grep, and edit_node + commit_changes after every change."',
|
|
199
224
|
},
|
|
200
225
|
},
|
|
201
226
|
{
|
|
@@ -218,11 +243,9 @@ exports.TARGETS = [
|
|
|
218
243
|
memory: {
|
|
219
244
|
supported: true,
|
|
220
245
|
featureName: 'Skills (/learn)',
|
|
221
|
-
scopes: [
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
wrap: antigravitySkillWrap,
|
|
225
|
-
note: 'Antigravity discovers skills by scanning .agents/skills/ for any SKILL.md — same mechanism whether it was created via /learn or placed here directly.',
|
|
246
|
+
scopes: [AGENTS_SKILL_SCOPE],
|
|
247
|
+
wrap: skillMdWrap,
|
|
248
|
+
note: 'Antigravity discovers skills by scanning .agents/skills/ for any SKILL.md — same mechanism whether it was created via /learn or placed here directly. Codex reads this same file.',
|
|
226
249
|
},
|
|
227
250
|
},
|
|
228
251
|
// ── CLI tools ───────────────────────────────────────────────────────────────
|
|
@@ -233,6 +256,11 @@ exports.TARGETS = [
|
|
|
233
256
|
mcp: {
|
|
234
257
|
scopes: [
|
|
235
258
|
{ scope: 'project', file: '.mcp.json', format: 'json', serverMapPath: ['mcpServers'] },
|
|
259
|
+
// User-scope entries live at the TOP LEVEL of ~/.claude.json under `mcpServers` — not
|
|
260
|
+
// nested under that file's `projects` map (that's "local" scope, which this registry
|
|
261
|
+
// doesn't model). Same JSON shape as project scope, so the generic merge/write path
|
|
262
|
+
// handles it unchanged; confirmed against Claude Code's own MCP quickstart docs.
|
|
263
|
+
{ scope: 'global', file: '~/.claude.json', format: 'json', serverMapPath: ['mcpServers'] },
|
|
236
264
|
],
|
|
237
265
|
transports: ['stdio', 'http'],
|
|
238
266
|
entry: entryTyped,
|
|
@@ -250,8 +278,7 @@ exports.TARGETS = [
|
|
|
250
278
|
scopes: [
|
|
251
279
|
{ scope: 'global', dir: '~/.claude/projects', format: 'memory-files', needsUserConfirmedDir: true },
|
|
252
280
|
],
|
|
253
|
-
note: '
|
|
254
|
-
pointerFile: { file: 'MEMORY.md', style: 'append-section' },
|
|
281
|
+
note: 'Auto Memory saves reliably from an EXPLICIT in-chat request ("remember this") — that\'s exactly what the prompt below is. A silently-written file never triggers that path at all, which is why `devsmind memory` no longer writes one.',
|
|
255
282
|
},
|
|
256
283
|
},
|
|
257
284
|
{
|
|
@@ -274,10 +301,8 @@ exports.TARGETS = [
|
|
|
274
301
|
memory: {
|
|
275
302
|
supported: true,
|
|
276
303
|
featureName: 'Skills (/learn)',
|
|
277
|
-
scopes: [
|
|
278
|
-
|
|
279
|
-
],
|
|
280
|
-
wrap: antigravitySkillWrap,
|
|
304
|
+
scopes: [AGENTS_SKILL_SCOPE],
|
|
305
|
+
wrap: skillMdWrap,
|
|
281
306
|
note: 'Same Skills mechanism as the Antigravity IDE — the CLI\'s /skills command browses this same .agents/skills/ directory.',
|
|
282
307
|
},
|
|
283
308
|
},
|
|
@@ -302,9 +327,16 @@ exports.TARGETS = [
|
|
|
302
327
|
style: 'append-section',
|
|
303
328
|
},
|
|
304
329
|
memory: {
|
|
305
|
-
supported:
|
|
306
|
-
featureName: '
|
|
307
|
-
|
|
330
|
+
supported: true,
|
|
331
|
+
featureName: 'Skills',
|
|
332
|
+
scopes: [AGENTS_SKILL_SCOPE],
|
|
333
|
+
wrap: skillMdWrap,
|
|
334
|
+
// Codex has two stores and only one of them is ours to write. `~/.codex/memories/` is
|
|
335
|
+
// generated state its own docs warn against hand-editing ("don't rely on editing them by
|
|
336
|
+
// hand") and a background job regenerates — untouched, here and everywhere else. Skills are
|
|
337
|
+
// the human-authored surface, and Codex scans the same `.agents/skills/` directory
|
|
338
|
+
// Antigravity does, so this is one file serving both tools rather than a new integration.
|
|
339
|
+
note: 'Codex discovers skills by scanning .agents/skills/ for any SKILL.md — the same file Antigravity reads, so seeding once covers both. Codex\'s own ~/.codex/memories/ is generated state and is never touched. Pair this with `devsmind rule` (AGENTS.md): a skill loads only when the task matches its description, while AGENTS.md is read every turn.',
|
|
308
340
|
},
|
|
309
341
|
},
|
|
310
342
|
{
|