devsmind-mcp 2.0.4 → 2.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/LICENSE +21 -21
- package/README.md +178 -73
- package/dist/cli/index.js +46 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +2 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/rule.js +35 -15
- package/dist/cli/rule.js.map +1 -1
- package/dist/cli/runner.d.ts +24 -0
- package/dist/cli/runner.js +502 -376
- package/dist/cli/runner.js.map +1 -1
- package/dist/db/database.d.ts +113 -4
- package/dist/db/database.js +453 -53
- package/dist/db/database.js.map +1 -1
- package/dist/db/edges.d.ts +42 -0
- package/dist/db/edges.js +162 -0
- package/dist/db/edges.js.map +1 -0
- package/dist/db/indexer.d.ts +3 -3
- package/dist/db/indexer.js +8 -8
- package/dist/db/indexer.js.map +1 -1
- package/dist/db/schema.js +39 -39
- package/dist/db/staging.d.ts +39 -0
- package/dist/db/staging.js +137 -0
- package/dist/db/staging.js.map +1 -0
- package/dist/mcp/server.js +192 -218
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/visualizer_2d.html +635 -635
- package/dist/mcp/visualizer_3d.html +613 -628
- package/dist/utils/ast.d.ts +38 -0
- package/dist/utils/ast.js +1079 -0
- package/dist/utils/ast.js.map +1 -0
- package/package.json +4 -4
package/dist/mcp/server.js
CHANGED
|
@@ -52,8 +52,39 @@ const database_1 = require("../db/database");
|
|
|
52
52
|
const visualizer_1 = require("./visualizer");
|
|
53
53
|
const indexer_1 = require("../db/indexer");
|
|
54
54
|
const scanner_1 = require("../utils/scanner");
|
|
55
|
+
const staging_1 = require("../db/staging");
|
|
55
56
|
// ─── Port: devs→D(4)E(5)=45 + mind→M(13)=13 → 4513 ─────────────────────────
|
|
56
57
|
exports.DEVSMIND_PORT = 4513;
|
|
58
|
+
// Shared node-type taxonomy description, reused by update_history and stage_change.
|
|
59
|
+
const NODE_TYPE_DESCRIPTION = 'The type of node. Be highly specific and framework-aware. Choose from the taxonomy below (or use a custom value if nothing fits).\n\n' +
|
|
60
|
+
'UNIVERSAL: function | method | class | abstract_class | interface | type_alias | enum | constant | variable | module | namespace | decorator\n\n' +
|
|
61
|
+
'NESTJS: nest_module | nest_controller | nest_service | nest_provider | nest_guard | nest_interceptor | nest_pipe | nest_filter | nest_decorator | nest_middleware | nest_gateway | nest_resolver | nest_schema | nest_dto\n\n' +
|
|
62
|
+
'EXPRESS/FASTIFY/KOA/HONO: route_handler | middleware | router\n\n' +
|
|
63
|
+
'SPRING (Java): spring_controller | spring_service | spring_repository | spring_component | spring_bean | spring_config | spring_entity\n\n' +
|
|
64
|
+
'DJANGO/FASTAPI (Python): django_view | django_model | django_serializer | django_form | django_signal | fastapi_router | fastapi_dependency\n\n' +
|
|
65
|
+
'GO: go_handler | go_middleware | go_struct | go_interface | go_func\n\n' +
|
|
66
|
+
'RUST: rust_struct | rust_impl | rust_trait | rust_enum | rust_fn | rust_macro\n\n' +
|
|
67
|
+
'REACT: react_component | react_hook | react_context | react_hoc | react_page\n\n' +
|
|
68
|
+
'NEXT.JS: next_page | next_layout | next_api_route | next_server_action | next_middleware\n\n' +
|
|
69
|
+
'VUE: vue_component | vue_composable | vue_directive | vue_store_module\n\n' +
|
|
70
|
+
'ANGULAR: ng_component | ng_service | ng_directive | ng_pipe | ng_module | ng_guard | ng_interceptor | ng_resolver\n\n' +
|
|
71
|
+
'SVELTE: svelte_component | svelte_store | svelte_action\n\n' +
|
|
72
|
+
'ORM — PRISMA: prisma_model | prisma_query | prisma_migration\n' +
|
|
73
|
+
'ORM — TYPEORM: typeorm_entity | typeorm_repository | typeorm_migration\n' +
|
|
74
|
+
'ORM — MONGOOSE: mongoose_model | mongoose_schema\n' +
|
|
75
|
+
'ORM — SQLALCHEMY: sqlalchemy_model | sqlalchemy_query\n' +
|
|
76
|
+
'ORM — SEQUELIZE: sequelize_model | sequelize_migration\n\n' +
|
|
77
|
+
'REST/API: api_endpoint | rest_controller\n' +
|
|
78
|
+
'GRAPHQL: graphql_resolver | graphql_query | graphql_mutation | graphql_subscription | graphql_schema | graphql_directive\n' +
|
|
79
|
+
'GRPC/PROTO: grpc_service | grpc_method | proto_message\n' +
|
|
80
|
+
'WEBSOCKET: ws_gateway | ws_handler\n' +
|
|
81
|
+
'MESSAGE QUEUE: mq_producer | mq_consumer | mq_handler\n\n' +
|
|
82
|
+
'CONFIG/AUTH: config_loader | env_config | feature_flag | auth_guard | auth_strategy | jwt_util | permission_policy\n' +
|
|
83
|
+
'OBSERVABILITY: logger | metric | trace_span\n' +
|
|
84
|
+
'CLI: cli_command | cli_option\n' +
|
|
85
|
+
'SCRIPTS: build_script | migration_script | seed_script\n' +
|
|
86
|
+
'TESTS: test_suite | test_case | test_helper | mock | fixture\n' +
|
|
87
|
+
'UTILITY: util_function | helper | transformer | validator | formatter';
|
|
57
88
|
// Cache database connections by their resolved path to avoid re-opening constantly
|
|
58
89
|
const dbCache = new Map();
|
|
59
90
|
// Walk up from a start directory to find a .devmind folder containing config.json
|
|
@@ -162,7 +193,7 @@ function createMcpServer() {
|
|
|
162
193
|
},
|
|
163
194
|
{
|
|
164
195
|
name: 'get_node_code',
|
|
165
|
-
description: '
|
|
196
|
+
description: "Get a single node's CURRENT source code, parsed live from its file on disk — token-efficient, since it returns only that function/class/route rather than the whole file. Call this instead of reading a file whenever you need one specific entity. Response fields: `source: \"live\"` means the code was read from disk and is current. `source: \"cached\"` means the symbol could not be located in its file (not a TS/JS file, or it was renamed/moved/deleted) so a possibly-stale cached snapshot was returned — verify it against the file before relying on it. `snapshot_outdated: true` means the stored graph has drifted from disk; re-stage the node with stage_change + commit_changes to bring the brain back in sync. To fetch a whole call flow at once, prefer get_node_graph with include_code instead of calling this repeatedly.",
|
|
166
197
|
inputSchema: {
|
|
167
198
|
type: 'object',
|
|
168
199
|
properties: {
|
|
@@ -178,103 +209,10 @@ function createMcpServer() {
|
|
|
178
209
|
required: ['devmind_path', 'node_id']
|
|
179
210
|
}
|
|
180
211
|
},
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
type: 'object',
|
|
186
|
-
properties: {
|
|
187
|
-
devmind_path: {
|
|
188
|
-
type: 'string',
|
|
189
|
-
description: 'Absolute path to the .devmind directory'
|
|
190
|
-
},
|
|
191
|
-
node_id: {
|
|
192
|
-
type: 'string',
|
|
193
|
-
description: 'Unique identifier for the node (e.g. function/method identifier)'
|
|
194
|
-
},
|
|
195
|
-
file_path: {
|
|
196
|
-
type: 'string',
|
|
197
|
-
description: 'Source file path where the node is located'
|
|
198
|
-
},
|
|
199
|
-
code_snapshot: {
|
|
200
|
-
type: 'string',
|
|
201
|
-
description: 'Full source code content of the node at this moment'
|
|
202
|
-
},
|
|
203
|
-
reasoning: {
|
|
204
|
-
type: 'object',
|
|
205
|
-
description: 'Structured details about this change',
|
|
206
|
-
properties: {
|
|
207
|
-
what_changed: {
|
|
208
|
-
type: 'string',
|
|
209
|
-
description: 'Brief description of the modified code'
|
|
210
|
-
},
|
|
211
|
-
why: { type: 'string', description: 'The reason this change was made' },
|
|
212
|
-
goal: { type: 'string', description: 'What was being achieved' },
|
|
213
|
-
requirement: {
|
|
214
|
-
type: 'string',
|
|
215
|
-
description: 'Ticket / issue / user request ID if applicable'
|
|
216
|
-
},
|
|
217
|
-
previous_state: {
|
|
218
|
-
type: 'string',
|
|
219
|
-
description: 'What the code looked like before and why it was a problem'
|
|
220
|
-
},
|
|
221
|
-
decision: {
|
|
222
|
-
type: 'string',
|
|
223
|
-
description: 'Architectural or implementation decision and why'
|
|
224
|
-
},
|
|
225
|
-
developer: { type: 'string', description: 'Name of the developer' },
|
|
226
|
-
model: { type: 'string', description: 'AI model name used' }
|
|
227
|
-
},
|
|
228
|
-
required: ['what_changed', 'why', 'goal']
|
|
229
|
-
},
|
|
230
|
-
name: {
|
|
231
|
-
type: 'string',
|
|
232
|
-
description: 'Display name of the node (optional, will be inferred if omitted)'
|
|
233
|
-
},
|
|
234
|
-
type: {
|
|
235
|
-
type: 'string',
|
|
236
|
-
description: '(optional, defaults to function) The type of node. Be highly specific and framework-aware. Choose from the taxonomy below (or use a custom value if nothing fits).\n\n' +
|
|
237
|
-
'UNIVERSAL: function | method | class | abstract_class | interface | type_alias | enum | constant | variable | module | namespace | decorator\n\n' +
|
|
238
|
-
'NESTJS: nest_module | nest_controller | nest_service | nest_provider | nest_guard | nest_interceptor | nest_pipe | nest_filter | nest_decorator | nest_middleware | nest_gateway | nest_resolver | nest_schema | nest_dto\n\n' +
|
|
239
|
-
'EXPRESS/FASTIFY/KOA/HONO: route_handler | middleware | router\n\n' +
|
|
240
|
-
'SPRING (Java): spring_controller | spring_service | spring_repository | spring_component | spring_bean | spring_config | spring_entity\n\n' +
|
|
241
|
-
'DJANGO/FASTAPI (Python): django_view | django_model | django_serializer | django_form | django_signal | fastapi_router | fastapi_dependency\n\n' +
|
|
242
|
-
'GO: go_handler | go_middleware | go_struct | go_interface | go_func\n\n' +
|
|
243
|
-
'RUST: rust_struct | rust_impl | rust_trait | rust_enum | rust_fn | rust_macro\n\n' +
|
|
244
|
-
'REACT: react_component | react_hook | react_context | react_hoc | react_page\n\n' +
|
|
245
|
-
'NEXT.JS: next_page | next_layout | next_api_route | next_server_action | next_middleware\n\n' +
|
|
246
|
-
'VUE: vue_component | vue_composable | vue_directive | vue_store_module\n\n' +
|
|
247
|
-
'ANGULAR: ng_component | ng_service | ng_directive | ng_pipe | ng_module | ng_guard | ng_interceptor | ng_resolver\n\n' +
|
|
248
|
-
'SVELTE: svelte_component | svelte_store | svelte_action\n\n' +
|
|
249
|
-
'ORM — PRISMA: prisma_model | prisma_query | prisma_migration\n' +
|
|
250
|
-
'ORM — TYPEORM: typeorm_entity | typeorm_repository | typeorm_migration\n' +
|
|
251
|
-
'ORM — MONGOOSE: mongoose_model | mongoose_schema\n' +
|
|
252
|
-
'ORM — SQLALCHEMY: sqlalchemy_model | sqlalchemy_query\n' +
|
|
253
|
-
'ORM — SEQUELIZE: sequelize_model | sequelize_migration\n\n' +
|
|
254
|
-
'REST/API: api_endpoint | rest_controller\n' +
|
|
255
|
-
'GRAPHQL: graphql_resolver | graphql_query | graphql_mutation | graphql_subscription | graphql_schema | graphql_directive\n' +
|
|
256
|
-
'GRPC/PROTO: grpc_service | grpc_method | proto_message\n' +
|
|
257
|
-
'WEBSOCKET: ws_gateway | ws_handler\n' +
|
|
258
|
-
'MESSAGE QUEUE: mq_producer | mq_consumer | mq_handler\n\n' +
|
|
259
|
-
'CONFIG/AUTH: config_loader | env_config | feature_flag | auth_guard | auth_strategy | jwt_util | permission_policy\n' +
|
|
260
|
-
'OBSERVABILITY: logger | metric | trace_span\n' +
|
|
261
|
-
'CLI: cli_command | cli_option\n' +
|
|
262
|
-
'SCRIPTS: build_script | migration_script | seed_script\n' +
|
|
263
|
-
'TESTS: test_suite | test_case | test_helper | mock | fixture\n' +
|
|
264
|
-
'UTILITY: util_function | helper | transformer | validator | formatter'
|
|
265
|
-
},
|
|
266
|
-
signature: {
|
|
267
|
-
type: 'string',
|
|
268
|
-
description: 'Function parameter types and return type signature (optional)'
|
|
269
|
-
},
|
|
270
|
-
session_id: {
|
|
271
|
-
type: 'string',
|
|
272
|
-
description: 'Session identifier to associate with this history update (optional)'
|
|
273
|
-
}
|
|
274
|
-
},
|
|
275
|
-
required: ['devmind_path', 'node_id', 'file_path', 'code_snapshot', 'reasoning']
|
|
276
|
-
}
|
|
277
|
-
},
|
|
212
|
+
// NOTE: `update_history`, `add_node`, and `add_connection` are intentionally NOT listed
|
|
213
|
+
// here. They are deprecated in favour of `stage_change` + `commit_changes` (to avoid
|
|
214
|
+
// confusing the AI with overlapping write tools), but their handlers are retained below
|
|
215
|
+
// so any direct/legacy call still works.
|
|
278
216
|
// ────────────────── Indexing tools ─────────────────────────────────────────
|
|
279
217
|
{
|
|
280
218
|
name: 'index_start',
|
|
@@ -331,63 +269,47 @@ function createMcpServer() {
|
|
|
331
269
|
}
|
|
332
270
|
},
|
|
333
271
|
{
|
|
334
|
-
name: '
|
|
335
|
-
description: '
|
|
272
|
+
name: 'stage_change',
|
|
273
|
+
description: 'Stage ONE changed code node (function/class/method/etc.) into a buffer without writing to the graph yet. Call this once for EVERY file/entity you touched during a task — passing only the code and reasoning; you do NOT reason about connections here. When you are done with all the files, call commit_changes ONCE — it creates every node, writes every history entry, and resolves all connections between them via local AST in a single pass (so a call from one changed file into another resolves correctly no matter which order you staged them). Staging is buffered on disk, so it survives a context reset. ⚠️ YOU MUST CALL commit_changes at the end, or nothing is written to the graph.',
|
|
336
274
|
inputSchema: {
|
|
337
275
|
type: 'object',
|
|
338
276
|
properties: {
|
|
339
277
|
devmind_path: { type: 'string', description: 'Absolute path to the .devmind directory' },
|
|
340
|
-
node_id: { type: 'string', description: 'Unique identifier e.g. "CartService.applyPromoCode" or "calculateDiscount"' },
|
|
341
|
-
|
|
342
|
-
type:
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
'
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
'
|
|
351
|
-
'
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
'ORM — PRISMA: prisma_model | prisma_query | prisma_migration\n' +
|
|
358
|
-
'ORM — TYPEORM: typeorm_entity | typeorm_repository | typeorm_migration\n' +
|
|
359
|
-
'ORM — MONGOOSE: mongoose_model | mongoose_schema\n' +
|
|
360
|
-
'ORM — SQLALCHEMY: sqlalchemy_model | sqlalchemy_query\n' +
|
|
361
|
-
'ORM — SEQUELIZE: sequelize_model | sequelize_migration\n\n' +
|
|
362
|
-
'REST/API: api_endpoint | rest_controller\n' +
|
|
363
|
-
'GRAPHQL: graphql_resolver | graphql_query | graphql_mutation | graphql_subscription | graphql_schema | graphql_directive\n' +
|
|
364
|
-
'GRPC/PROTO: grpc_service | grpc_method | proto_message\n' +
|
|
365
|
-
'WEBSOCKET: ws_gateway | ws_handler\n' +
|
|
366
|
-
'MESSAGE QUEUE: mq_producer | mq_consumer | mq_handler\n\n' +
|
|
367
|
-
'CONFIG/AUTH: config_loader | env_config | feature_flag | auth_guard | auth_strategy | jwt_util | permission_policy\n' +
|
|
368
|
-
'OBSERVABILITY: logger | metric | trace_span\n' +
|
|
369
|
-
'CLI: cli_command | cli_option\n' +
|
|
370
|
-
'SCRIPTS: build_script | migration_script | seed_script\n' +
|
|
371
|
-
'TESTS: test_suite | test_case | test_helper | mock | fixture\n' +
|
|
372
|
-
'UTILITY: util_function | helper | transformer | validator | formatter'
|
|
278
|
+
node_id: { type: 'string', description: 'Unique identifier for the node (e.g. "CartService.applyPromoCode" or "calculateDiscount")' },
|
|
279
|
+
file_path: { type: 'string', description: 'Source file path where the node is located' },
|
|
280
|
+
code_snapshot: { type: 'string', description: 'Full source code content of the node at this moment' },
|
|
281
|
+
reasoning: {
|
|
282
|
+
type: 'object',
|
|
283
|
+
description: 'Structured details about this change',
|
|
284
|
+
properties: {
|
|
285
|
+
what_changed: { type: 'string', description: 'Brief description of the modified code' },
|
|
286
|
+
why: { type: 'string', description: 'The reason this change was made' },
|
|
287
|
+
goal: { type: 'string', description: 'What was being achieved' },
|
|
288
|
+
requirement: { type: 'string', description: 'Ticket / issue / user request ID if applicable' },
|
|
289
|
+
previous_state: { type: 'string', description: 'What the code looked like before and why it was a problem' },
|
|
290
|
+
decision: { type: 'string', description: 'Architectural or implementation decision and why' },
|
|
291
|
+
developer: { type: 'string', description: 'Name of the developer' },
|
|
292
|
+
model: { type: 'string', description: 'AI model name used' }
|
|
293
|
+
},
|
|
294
|
+
required: ['what_changed', 'why', 'goal']
|
|
373
295
|
},
|
|
374
|
-
|
|
375
|
-
|
|
296
|
+
name: { type: 'string', description: 'Display name of the node (optional, inferred if omitted)' },
|
|
297
|
+
type: { type: 'string', description: '(optional, defaults to function) ' + NODE_TYPE_DESCRIPTION },
|
|
298
|
+
signature: { type: 'string', description: 'Parameter types + return type signature (optional)' },
|
|
299
|
+
session_id: { type: 'string', description: 'Session identifier to associate with this change (optional)' }
|
|
376
300
|
},
|
|
377
|
-
required: ['devmind_path', 'node_id', '
|
|
301
|
+
required: ['devmind_path', 'node_id', 'file_path', 'code_snapshot', 'reasoning']
|
|
378
302
|
}
|
|
379
303
|
},
|
|
380
304
|
{
|
|
381
|
-
name: '
|
|
382
|
-
description: '
|
|
305
|
+
name: 'commit_changes',
|
|
306
|
+
description: 'Commit all buffered stage_change entries in one atomic pass: creates/updates every staged node, writes every history snapshot, then resolves all connections between the staged nodes (and into the existing graph) via local AST — auto-creating any referenced-but-missing target nodes. Clears the buffer on success. Call this exactly once after you have finished staging every file you touched.',
|
|
383
307
|
inputSchema: {
|
|
384
308
|
type: 'object',
|
|
385
309
|
properties: {
|
|
386
|
-
devmind_path: { type: 'string', description: 'Absolute path to the .devmind directory' }
|
|
387
|
-
source_node_id: { type: 'string', description: 'The node that calls or uses the target' },
|
|
388
|
-
target_node_id: { type: 'string', description: 'The node being called or used' }
|
|
310
|
+
devmind_path: { type: 'string', description: 'Absolute path to the .devmind directory' }
|
|
389
311
|
},
|
|
390
|
-
required: ['devmind_path'
|
|
312
|
+
required: ['devmind_path']
|
|
391
313
|
}
|
|
392
314
|
},
|
|
393
315
|
{
|
|
@@ -416,13 +338,26 @@ function createMcpServer() {
|
|
|
416
338
|
},
|
|
417
339
|
{
|
|
418
340
|
name: 'get_node_graph',
|
|
419
|
-
description: 'Get a
|
|
341
|
+
description: 'Get a node\'s dependency graph — connected nodes and the relationships between them. Set direction:"out" AND include_code:true to pull an ENTIRE CALL FLOW in a single call: the starting node plus everything it transitively calls, each with its current source code read from disk. Use that combination whenever you are tracing how a request, endpoint, or feature flows through the codebase — it replaces a long chain of get_node_code calls with one round trip. Use direction:"in" to find every caller of a node (impact analysis before a change). If `code_truncated` is true in the response, the character budget ran out and `nodes_without_code` nodes came back with metadata but no code — fetch those individually with get_node_code, or raise code_char_budget.',
|
|
420
342
|
inputSchema: {
|
|
421
343
|
type: 'object',
|
|
422
344
|
properties: {
|
|
423
345
|
devmind_path: { type: 'string', description: 'Absolute path to the .devmind directory' },
|
|
424
346
|
node_id: { type: 'string', description: 'Unique identifier for the starting node (e.g. function or class name)' },
|
|
425
|
-
max_depth: { type: 'number', description: 'Maximum depth to traverse (optional, default 6)' }
|
|
347
|
+
max_depth: { type: 'number', description: 'Maximum depth to traverse (optional, default 6). For a call flow with include_code, 2-3 is usually right.' },
|
|
348
|
+
direction: {
|
|
349
|
+
type: 'string',
|
|
350
|
+
enum: ['out', 'in', 'both'],
|
|
351
|
+
description: '"out" = only what this node calls, transitively (a call flow — use this for tracing). "in" = only what calls this node (impact analysis). "both" = the surrounding neighborhood in both directions (default).'
|
|
352
|
+
},
|
|
353
|
+
include_code: {
|
|
354
|
+
type: 'boolean',
|
|
355
|
+
description: 'Attach each node\'s current source code, read live from disk (default: false). Combine with direction:"out" to retrieve a whole call flow in one call.'
|
|
356
|
+
},
|
|
357
|
+
code_char_budget: {
|
|
358
|
+
type: 'number',
|
|
359
|
+
description: 'Max total characters of code to return, spent on the nodes nearest the starting node first (default: 60000). Only applies when include_code is true.'
|
|
360
|
+
}
|
|
426
361
|
},
|
|
427
362
|
required: ['devmind_path', 'node_id']
|
|
428
363
|
}
|
|
@@ -515,6 +450,20 @@ function createMcpServer() {
|
|
|
515
450
|
required: ['devmind_path', 'query']
|
|
516
451
|
}
|
|
517
452
|
},
|
|
453
|
+
{
|
|
454
|
+
name: 'search_code',
|
|
455
|
+
description: 'Regex or string search over cached codebase code snapshots. Returns matches grouped by DevsMind Node ID, file path, and matching lines, along with matching statistics (ratio, count). Prefer this over direct grep search.',
|
|
456
|
+
inputSchema: {
|
|
457
|
+
type: 'object',
|
|
458
|
+
properties: {
|
|
459
|
+
devmind_path: { type: 'string', description: 'Absolute path to the .devmind directory' },
|
|
460
|
+
query: { type: 'string', description: 'Regex or substring pattern to search for in code' },
|
|
461
|
+
is_regex: { type: 'boolean', description: 'Whether the query is a regex pattern (default: false)' },
|
|
462
|
+
case_insensitive: { type: 'boolean', description: 'Perform case-insensitive search (default: true)' }
|
|
463
|
+
},
|
|
464
|
+
required: ['devmind_path', 'query']
|
|
465
|
+
}
|
|
466
|
+
},
|
|
518
467
|
{
|
|
519
468
|
name: 'get_orphaned_nodes',
|
|
520
469
|
description: 'Find disconnected code nodes in the graph that have no incoming or outgoing connections.',
|
|
@@ -594,75 +543,39 @@ function createMcpServer() {
|
|
|
594
543
|
const devmindPath = resolveDevmindPath(args.devmind_path);
|
|
595
544
|
const nodeId = String(args.node_id);
|
|
596
545
|
const db = getDatabase(devmindPath);
|
|
597
|
-
const result = db.
|
|
598
|
-
if (!result) {
|
|
599
|
-
return {
|
|
600
|
-
content: [{ type: 'text', text: JSON.stringify({
|
|
601
|
-
exists: false,
|
|
602
|
-
node_id: nodeId,
|
|
603
|
-
message: 'No code snapshot found. Read the source file, then call update_history to cache the code so future agents skip the file read entirely.'
|
|
604
|
-
}) }]
|
|
605
|
-
};
|
|
606
|
-
}
|
|
546
|
+
const result = db.getLiveCode(nodeId);
|
|
607
547
|
return {
|
|
608
|
-
content: [{ type: 'text', text: JSON.stringify(
|
|
609
|
-
exists: true,
|
|
610
|
-
node_id: nodeId,
|
|
611
|
-
updated_at: result.updated_at,
|
|
612
|
-
code_snapshot: result.code_snapshot
|
|
613
|
-
}) }]
|
|
548
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
|
|
614
549
|
};
|
|
615
550
|
}
|
|
616
551
|
case 'update_history': {
|
|
617
552
|
const devmindPath = resolveDevmindPath(args.devmind_path);
|
|
618
|
-
const rawNodeId = String(args.node_id);
|
|
619
553
|
const filePath = String(args.file_path);
|
|
620
|
-
const codeSnapshot = String(args.code_snapshot);
|
|
621
|
-
const reasoning = args.reasoning;
|
|
622
554
|
const db = getDatabase(devmindPath);
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
let nodeType = args.type ? String(args.type) : undefined;
|
|
628
|
-
const signature = args.signature ? String(args.signature) : undefined;
|
|
629
|
-
const sessionId = args.session_id ? String(args.session_id) : undefined;
|
|
630
|
-
// Infer name and type if not provided
|
|
631
|
-
if (!nodeName) {
|
|
632
|
-
nodeName = rawNodeId.includes('.') ? rawNodeId.split('.').pop() : rawNodeId;
|
|
633
|
-
}
|
|
634
|
-
if (!nodeType) {
|
|
635
|
-
nodeType = rawNodeId.includes('.') ? 'method' : 'function';
|
|
636
|
-
}
|
|
637
|
-
// 1. Ensure node exists (Upsert)
|
|
638
|
-
db.upsertNode({
|
|
639
|
-
id: nodeId,
|
|
640
|
-
name: nodeName,
|
|
641
|
-
type: nodeType,
|
|
555
|
+
// Single-shot path: stage one entry and commit it immediately, so a lone edit still
|
|
556
|
+
// gets its node, history, AND outgoing edges resolved via the shared commit logic.
|
|
557
|
+
const entry = {
|
|
558
|
+
node_id: String(args.node_id),
|
|
642
559
|
file_path: filePath,
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
}
|
|
560
|
+
code_snapshot: String(args.code_snapshot),
|
|
561
|
+
reasoning: args.reasoning,
|
|
562
|
+
name: args.name ? String(args.name) : undefined,
|
|
563
|
+
type: args.type ? String(args.type) : undefined,
|
|
564
|
+
signature: args.signature ? String(args.signature) : undefined,
|
|
565
|
+
session_id: args.session_id ? String(args.session_id) : undefined
|
|
566
|
+
};
|
|
567
|
+
const summary = (0, staging_1.commitStagedChanges)(db, devmindPath, [entry]);
|
|
568
|
+
const nodeId = entry.node_id.includes('#') ? entry.node_id : `${db.toRepoRelativePath(filePath)}#${entry.node_id}`;
|
|
652
569
|
return {
|
|
653
570
|
content: [
|
|
654
571
|
{
|
|
655
572
|
type: 'text',
|
|
656
573
|
text: JSON.stringify({
|
|
657
574
|
success: true,
|
|
658
|
-
message: 'History updated
|
|
659
|
-
node: { id: nodeId
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
session_id: historyEntry.session_id,
|
|
663
|
-
created_at: historyEntry.created_at,
|
|
664
|
-
updated_at: historyEntry.updated_at
|
|
665
|
-
}
|
|
575
|
+
message: 'History updated and connections resolved.',
|
|
576
|
+
node: { id: nodeId },
|
|
577
|
+
edges_added: summary.edges_added,
|
|
578
|
+
missing_nodes_filled: summary.missing_filled
|
|
666
579
|
}, null, 2)
|
|
667
580
|
}
|
|
668
581
|
]
|
|
@@ -683,37 +596,30 @@ function createMcpServer() {
|
|
|
683
596
|
content: [{
|
|
684
597
|
type: 'text',
|
|
685
598
|
text: JSON.stringify({
|
|
686
|
-
message: 'Indexing session started.
|
|
599
|
+
message: 'Indexing session started. Extract nodes with stage_change (one call per entity), then call commit_changes to write them all and resolve connections automatically via AST. Call index_checkpoint every 10 files.',
|
|
687
600
|
scratchpad: pad,
|
|
688
601
|
repos: repoSummaries,
|
|
689
602
|
total_files,
|
|
690
603
|
instructions: [
|
|
691
604
|
'⚠️⚠️⚠️ CRITICAL INSTRUCTION FOR THE INDEXING AGENT — MUST READ ⚠️⚠️⚠️ ',
|
|
692
|
-
'YOU MUST EXPLICITLY CALL THE "
|
|
605
|
+
'YOU MUST EXPLICITLY CALL THE "stage_change" MCP TOOL FOR EVERY ENTITY YOU EXTRACT, THEN "commit_changes" TO WRITE THEM.',
|
|
693
606
|
'DO NOT JUST PRINT THE RESULTS AS TEXT IN THE CHAT WINDOW. PRINTING RESULTS WITHOUT CALLING THE MCP TOOLS DOES NOT WRITE THEM TO THE DATABASE AND MAKES THE ENTIRE INDEXING RUN A WASTE OF TIME AND TOKENS.',
|
|
694
607
|
'NEVER WRITE OR EXECUTE EXTERNAL SCRIPTS (like Python, Node.js, Bash, etc.) to automate or lazy load indexing. You must read files and call the MCP tools step-by-step natively in the chat. This ensures progress is tracked in the SQLite scratchpad database and can be resumed/continued in subsequent chats if context limits are hit.',
|
|
695
608
|
'ONCE YOU START INDEXING, DO NOT STOP or pause to ask for confirmation between checkpoints. Keep executing and indexing files continuously until the codebase is fully indexed or your context token limit is reached.',
|
|
696
609
|
'IF YOU ENCOUNTER CONTEXT RESETS, RESUME WORK BY CALLING "index_continue" AND CONTINUOUSLY COMMIT PROGRESS BY CALLING "index_checkpoint" EVERY 10 FILES.',
|
|
697
610
|
'',
|
|
698
611
|
'📋 CODE EXCLUSION & PRECISION RULES:',
|
|
699
|
-
'1. EXCLUDE Language Globals / Built-ins: Do NOT
|
|
700
|
-
'2. EXCLUDE Primitive/Native Types: Do NOT
|
|
701
|
-
'3. EXCLUDE External / Third-party Modules: Do NOT
|
|
702
|
-
'4. INTERNAL ENTITIES ONLY: Only
|
|
612
|
+
'1. EXCLUDE Language Globals / Built-ins: Do NOT stage nodes for Promise, Map, Set, JSON, console, Error, Object, Array, RegExp, Date, Math, etc.',
|
|
613
|
+
'2. EXCLUDE Primitive/Native Types: Do NOT stage nodes for string, number, boolean, any, void, unknown, never, null, undefined, dict, list, etc.',
|
|
614
|
+
'3. EXCLUDE External / Third-party Modules: Do NOT stage nodes for lodash, express, react, @nestjs/common, etc.',
|
|
615
|
+
'4. INTERNAL ENTITIES ONLY: Only stage nodes for constructs defined inside this codebase.',
|
|
703
616
|
'',
|
|
704
|
-
'📋
|
|
705
|
-
'PHASE 1: NODE & CODE EXTRACTION',
|
|
617
|
+
'📋 STAGE → COMMIT INDEXING PROTOCOL:',
|
|
706
618
|
'1. For each file in each repo: read it, extract ALL defined nodes — functions, methods, classes, interfaces, types, DTOs, routing handlers, schemas, resolvers, etc.',
|
|
707
|
-
'2. Call
|
|
708
|
-
'3. Call
|
|
709
|
-
'4.
|
|
710
|
-
'',
|
|
711
|
-
'PHASE 2: CONNECTION RESOLUTION (LINKING)',
|
|
712
|
-
'1. Once all files have been indexed in Phase 1, get the list of all nodes using list_nodes.',
|
|
713
|
-
'2. Iterate through all nodes. For each node, review its code snapshot and identify which other active codebase nodes it calls/references.',
|
|
714
|
-
'3. Call add_connection (source_node_id, target_node_id) to link them.',
|
|
715
|
-
'4. Call index_checkpoint every 10 nodes to save progress.',
|
|
716
|
-
'5. When all connections have been linked, call index_complete.',
|
|
619
|
+
'2. Call stage_change for EVERY entity found — pass its node_id, file_path, code_snapshot, reasoning, and the most specific taxonomy type. You do NOT need to figure out connections; commit_changes resolves them from the code via AST.',
|
|
620
|
+
'3. Call index_checkpoint every 10 files to save progress.',
|
|
621
|
+
'4. Every ~50 entities (or at the end of a repo), call commit_changes to flush the staged buffer — it creates all nodes, writes all history, and resolves all connections (including into already-committed nodes) in one pass. Committing in batches keeps the buffer small.',
|
|
622
|
+
'5. When the whole codebase is staged and committed, call index_complete.',
|
|
717
623
|
'6. AFTER index_complete, CALL "recheck_graph" to automatically prune any spurious, built-in, or orphaned nodes and ensure high graph precision.'
|
|
718
624
|
]
|
|
719
625
|
}, null, 2)
|
|
@@ -793,6 +699,56 @@ function createMcpServer() {
|
|
|
793
699
|
}]
|
|
794
700
|
};
|
|
795
701
|
}
|
|
702
|
+
case 'stage_change': {
|
|
703
|
+
const devmindPath = resolveDevmindPath(args.devmind_path);
|
|
704
|
+
const entry = {
|
|
705
|
+
node_id: String(args.node_id),
|
|
706
|
+
file_path: String(args.file_path),
|
|
707
|
+
code_snapshot: String(args.code_snapshot),
|
|
708
|
+
reasoning: args.reasoning,
|
|
709
|
+
name: args.name ? String(args.name) : undefined,
|
|
710
|
+
type: args.type ? String(args.type) : undefined,
|
|
711
|
+
signature: args.signature ? String(args.signature) : undefined,
|
|
712
|
+
session_id: args.session_id ? String(args.session_id) : undefined
|
|
713
|
+
};
|
|
714
|
+
const pendingCount = (0, staging_1.stageEntry)(devmindPath, entry);
|
|
715
|
+
return {
|
|
716
|
+
content: [{
|
|
717
|
+
type: 'text',
|
|
718
|
+
text: JSON.stringify({
|
|
719
|
+
staged: true,
|
|
720
|
+
node_id: entry.node_id,
|
|
721
|
+
pending_count: pendingCount,
|
|
722
|
+
reminder: 'Call commit_changes once you have staged every touched file, or nothing is written to the graph.'
|
|
723
|
+
})
|
|
724
|
+
}]
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
case 'commit_changes': {
|
|
728
|
+
const devmindPath = resolveDevmindPath(args.devmind_path);
|
|
729
|
+
const db = getDatabase(devmindPath);
|
|
730
|
+
const entries = (0, staging_1.readStaged)(devmindPath);
|
|
731
|
+
if (entries.length === 0) {
|
|
732
|
+
return {
|
|
733
|
+
content: [{ type: 'text', text: JSON.stringify({ committed: false, message: 'Nothing staged. Call stage_change first.' }) }]
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
const summary = (0, staging_1.commitStagedChanges)(db, devmindPath, entries);
|
|
737
|
+
(0, staging_1.clearStaged)(devmindPath);
|
|
738
|
+
return {
|
|
739
|
+
content: [{
|
|
740
|
+
type: 'text',
|
|
741
|
+
text: JSON.stringify({
|
|
742
|
+
committed: true,
|
|
743
|
+
message: `✅ Committed ${summary.nodes} node(s), ${summary.history_entries} history entr(ies), ${summary.edges_added} connection(s) resolved` +
|
|
744
|
+
(summary.missing_filled > 0 ? `, ${summary.missing_filled} missing node(s) auto-created.` : '.'),
|
|
745
|
+
...summary
|
|
746
|
+
}, null, 2)
|
|
747
|
+
}]
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
// ── Deprecated write handlers: NOT advertised in ListTools (superseded by
|
|
751
|
+
// stage_change/commit_changes), but retained so any direct/legacy call still works. ──
|
|
796
752
|
case 'add_node': {
|
|
797
753
|
const devmindPath = resolveDevmindPath(args.devmind_path);
|
|
798
754
|
const rawNodeId = String(args.node_id);
|
|
@@ -851,8 +807,15 @@ function createMcpServer() {
|
|
|
851
807
|
const devmindPath = resolveDevmindPath(args.devmind_path);
|
|
852
808
|
const nodeId = String(args.node_id);
|
|
853
809
|
const maxDepth = args.max_depth ? Number(args.max_depth) : 6;
|
|
810
|
+
const direction = args.direction === 'out' || args.direction === 'in' || args.direction === 'both'
|
|
811
|
+
? args.direction
|
|
812
|
+
: 'both';
|
|
854
813
|
const db = getDatabase(devmindPath);
|
|
855
|
-
const graph = db.getGraph(nodeId, maxDepth
|
|
814
|
+
const graph = db.getGraph(nodeId, maxDepth, {
|
|
815
|
+
direction,
|
|
816
|
+
includeCode: args.include_code === true,
|
|
817
|
+
codeCharBudget: args.code_char_budget ? Number(args.code_char_budget) : undefined
|
|
818
|
+
});
|
|
856
819
|
return {
|
|
857
820
|
content: [{ type: 'text', text: JSON.stringify(graph, null, 2) }]
|
|
858
821
|
};
|
|
@@ -924,6 +887,17 @@ function createMcpServer() {
|
|
|
924
887
|
content: [{ type: 'text', text: JSON.stringify(decisions, null, 2) }]
|
|
925
888
|
};
|
|
926
889
|
}
|
|
890
|
+
case 'search_code': {
|
|
891
|
+
const devmindPath = resolveDevmindPath(args.devmind_path);
|
|
892
|
+
const query = String(args.query);
|
|
893
|
+
const isRegex = args.is_regex === true;
|
|
894
|
+
const caseInsensitive = args.case_insensitive !== false;
|
|
895
|
+
const db = getDatabase(devmindPath);
|
|
896
|
+
const results = db.searchCode({ query, is_regex: isRegex, case_insensitive: caseInsensitive });
|
|
897
|
+
return {
|
|
898
|
+
content: [{ type: 'text', text: JSON.stringify(results, null, 2) }]
|
|
899
|
+
};
|
|
900
|
+
}
|
|
927
901
|
case 'get_orphaned_nodes': {
|
|
928
902
|
const devmindPath = resolveDevmindPath(args.devmind_path);
|
|
929
903
|
const db = getDatabase(devmindPath);
|