@grafema/mcp 0.3.28 → 0.3.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/definitions/enox-tools.d.ts +10 -0
  2. package/dist/definitions/enox-tools.d.ts.map +1 -0
  3. package/dist/definitions/enox-tools.js +451 -0
  4. package/dist/definitions/enox-tools.js.map +1 -0
  5. package/dist/definitions/index.js +2 -2
  6. package/dist/definitions/index.js.map +1 -1
  7. package/dist/handlers/coverage-handlers.d.ts.map +1 -1
  8. package/dist/handlers/coverage-handlers.js +9 -0
  9. package/dist/handlers/coverage-handlers.js.map +1 -1
  10. package/dist/handlers/documentation-handlers.d.ts.map +1 -1
  11. package/dist/handlers/documentation-handlers.js +26 -5
  12. package/dist/handlers/documentation-handlers.js.map +1 -1
  13. package/dist/handlers/enox-handlers.d.ts +113 -0
  14. package/dist/handlers/enox-handlers.d.ts.map +1 -0
  15. package/dist/handlers/enox-handlers.js +877 -0
  16. package/dist/handlers/enox-handlers.js.map +1 -0
  17. package/dist/handlers/index.d.ts +1 -1
  18. package/dist/handlers/index.d.ts.map +1 -1
  19. package/dist/handlers/index.js +1 -1
  20. package/dist/handlers/index.js.map +1 -1
  21. package/dist/handlers/query-handlers.d.ts +7 -0
  22. package/dist/handlers/query-handlers.d.ts.map +1 -1
  23. package/dist/handlers/query-handlers.js +28 -14
  24. package/dist/handlers/query-handlers.js.map +1 -1
  25. package/dist/server.js +45 -17
  26. package/dist/server.js.map +1 -1
  27. package/dist/types.d.ts +64 -0
  28. package/dist/types.d.ts.map +1 -1
  29. package/package.json +12 -12
  30. package/src/definitions/enox-tools.ts +454 -0
  31. package/src/definitions/index.ts +2 -2
  32. package/src/handlers/coverage-handlers.ts +10 -0
  33. package/src/handlers/documentation-handlers.ts +26 -5
  34. package/src/handlers/enox-handlers.ts +1125 -0
  35. package/src/handlers/index.ts +1 -1
  36. package/src/handlers/query-handlers.ts +25 -12
  37. package/src/server.ts +86 -29
  38. package/src/types.ts +78 -0
  39. package/dist/definitions.d.ts +0 -23
  40. package/dist/definitions.d.ts.map +0 -1
  41. package/dist/definitions.js +0 -644
  42. package/dist/definitions.js.map +0 -1
  43. package/dist/handlers.d.ts +0 -61
  44. package/dist/handlers.d.ts.map +0 -1
  45. package/dist/handlers.js +0 -1310
  46. package/dist/handlers.js.map +0 -1
@@ -0,0 +1,454 @@
1
+ /**
2
+ * Enox Tools — persistent knowledge graph (long-term memory)
3
+ *
4
+ * Enox is a federated knowledge graph shared across sessions and projects.
5
+ * These tools provide CRUD operations on nodes and edges, semantic search,
6
+ * graph traversal, and document storage.
7
+ */
8
+
9
+ import type { ToolDefinition } from './types.js';
10
+
11
+ const RELATION_TYPES = [
12
+ 'depends_on',
13
+ 'supersedes',
14
+ 'implements',
15
+ 'contradicts',
16
+ 'part_of',
17
+ 'extends',
18
+ 'enables',
19
+ 'isomorphic_to',
20
+ 'decided_on',
21
+ 'discussed_on',
22
+ 'changed_on',
23
+ 'created_on',
24
+ 'preceded_by',
25
+ 'triggered_by',
26
+ 'prefers',
27
+ 'distrusts',
28
+ 'values',
29
+ 'rejects',
30
+ 'believes',
31
+ 'about',
32
+ 'references',
33
+ 'blocks',
34
+ 'decomposes_into',
35
+ 'related_to',
36
+ 'similar_to',
37
+ 'requires',
38
+ 'supports',
39
+ 'outperforms',
40
+ 'fails_on',
41
+ 'introduces',
42
+ 'uses',
43
+ 'is_based_on',
44
+ 'equivalent_to',
45
+ 'foundational_for',
46
+ 'builds_on',
47
+ 'contributes_to',
48
+ 'alternative_to',
49
+ 'refutes',
50
+ 'challenges',
51
+ 'applies_to',
52
+ 'exports',
53
+ ] as const;
54
+
55
+ export const ENOX_TOOLS: ToolDefinition[] = [
56
+ {
57
+ name: 'remember',
58
+ description: `Quick knowledge write — store a fact about a subject.
59
+
60
+ Use this when you:
61
+ - Discover something worth remembering across sessions
62
+ - Want to record an experiment result, decision, or observation
63
+ - Need a quick "jot it down" without specifying exact graph structure
64
+
65
+ The subject becomes a node (or reuses an existing one), and the fact is stored
66
+ as an assertion from that node.
67
+
68
+ Example: remember(subject="RFDB compaction", fact="flush_data_only was a no-op in V2 engine", domain="engineering")`,
69
+ inputSchema: {
70
+ type: 'object',
71
+ properties: {
72
+ subject: {
73
+ type: 'string',
74
+ description: 'The entity this fact is about (becomes a node)',
75
+ },
76
+ fact: {
77
+ type: 'string',
78
+ description: 'The fact or observation to record',
79
+ },
80
+ domain: {
81
+ type: 'string',
82
+ description: 'Knowledge domain (default: "memory")',
83
+ },
84
+ confidence: {
85
+ type: 'number',
86
+ description: 'Confidence level 0-1 (default: 0.9)',
87
+ },
88
+ relation: {
89
+ type: 'string',
90
+ description: 'Relation type for the assertion edge',
91
+ enum: [...RELATION_TYPES],
92
+ },
93
+ },
94
+ required: ['subject', 'fact'],
95
+ },
96
+ },
97
+ {
98
+ name: 'recall',
99
+ description: `Broad "what do we know about X" — combines embedding search with graph traversal.
100
+
101
+ Use this at session start or before making decisions to check for prior art,
102
+ known failures, and existing context.
103
+
104
+ Depth controls how far to traverse from matched nodes:
105
+ - 1: direct matches only (fast)
106
+ - 2: matches + their neighbors (default, good balance)
107
+ - 3: two hops out (broader context, slower)
108
+
109
+ Example: recall(query="federation architecture", depth=2)`,
110
+ inputSchema: {
111
+ type: 'object',
112
+ properties: {
113
+ query: {
114
+ type: 'string',
115
+ description: 'What to recall — natural language query',
116
+ },
117
+ depth: {
118
+ type: 'number',
119
+ description: 'Traversal depth from matched nodes: 1-3 (default: 1)',
120
+ },
121
+ },
122
+ required: ['query'],
123
+ },
124
+ },
125
+ {
126
+ name: 'semantic_search',
127
+ description: `Substring search across knowledge-graph node names (case-insensitive).
128
+
129
+ NOTE: despite the name, embedding-based semantic ranking is not wired yet
130
+ (RFD-63). This currently matches substrings of node names — results are
131
+ plain matches, not similarity-ranked. Use recall for broader retrieval.
132
+
133
+ Example: semantic_search(query="auth token", top_k=5, domain="devops")`,
134
+ inputSchema: {
135
+ type: 'object',
136
+ properties: {
137
+ query: {
138
+ type: 'string',
139
+ description: 'Natural language search query',
140
+ },
141
+ top_k: {
142
+ type: 'number',
143
+ description: 'Maximum number of results to return (default: 10)',
144
+ },
145
+ domain: {
146
+ type: 'string',
147
+ description: 'Filter results to a specific domain',
148
+ },
149
+ },
150
+ required: ['query'],
151
+ },
152
+ },
153
+ {
154
+ name: 'enox_explore',
155
+ description: `Get all edges around an entity in the knowledge graph — see everything connected to it.
156
+
157
+ Use this to understand the full context of an entity: what it relates to,
158
+ what depends on it, what contradicts it, etc.
159
+
160
+ Returns all incoming and outgoing edges with connected node summaries.
161
+
162
+ Example: explore(entity="RFDB V2 engine")`,
163
+ inputSchema: {
164
+ type: 'object',
165
+ properties: {
166
+ entity: {
167
+ type: 'string',
168
+ description: 'Name or ID of the entity to explore',
169
+ },
170
+ },
171
+ required: ['entity'],
172
+ },
173
+ },
174
+ {
175
+ name: 'add_assertion',
176
+ description: `Create a precise edge between two nodes in the knowledge graph.
177
+
178
+ Use this when you need exact control over the graph structure:
179
+ - Specific relation type between two entities
180
+ - Confidence level on an assertion
181
+ - Domain scoping
182
+
183
+ Both "from" and "to" become nodes if they don't exist yet.
184
+
185
+ Example: add_assertion(from="Grafema", relation="uses", to="RFDB", context="RFDB is the storage engine for code graphs", confidence=1.0)`,
186
+ inputSchema: {
187
+ type: 'object',
188
+ properties: {
189
+ from: {
190
+ type: 'string',
191
+ description: 'Source entity name or ID',
192
+ },
193
+ relation: {
194
+ type: 'string',
195
+ description: 'Relation type for the edge',
196
+ enum: [...RELATION_TYPES],
197
+ },
198
+ to: {
199
+ type: 'string',
200
+ description: 'Target entity name or ID',
201
+ },
202
+ context: {
203
+ type: 'string',
204
+ description: 'Additional context or evidence for this assertion',
205
+ },
206
+ confidence: {
207
+ type: 'number',
208
+ description: 'Confidence level 0-1',
209
+ },
210
+ domain: {
211
+ type: 'string',
212
+ description: 'Knowledge domain this assertion belongs to',
213
+ },
214
+ },
215
+ required: ['from', 'relation', 'to'],
216
+ },
217
+ },
218
+ {
219
+ name: 'update_assertion',
220
+ description: `Update an existing edge in the knowledge graph.
221
+
222
+ Use this to change the context or confidence of a previously recorded assertion
223
+ without deleting and re-creating it.
224
+
225
+ Example: update_assertion(fact_id="edge-abc123", confidence=0.5, context="Partially confirmed after testing")`,
226
+ inputSchema: {
227
+ type: 'object',
228
+ properties: {
229
+ fact_id: {
230
+ type: 'string',
231
+ description: 'ID of the assertion/edge to update',
232
+ },
233
+ context: {
234
+ type: 'string',
235
+ description: 'Updated context or evidence',
236
+ },
237
+ confidence: {
238
+ type: 'number',
239
+ description: 'Updated confidence level 0-1',
240
+ },
241
+ },
242
+ required: ['fact_id'],
243
+ },
244
+ },
245
+ {
246
+ name: 'delete_assertion',
247
+ description: `Remove an edge from the knowledge graph.
248
+
249
+ Use this when an assertion is wrong, outdated, or no longer relevant.
250
+ Consider using update_assertion to lower confidence instead of deleting,
251
+ or add_assertion with "supersedes" relation to record the replacement.
252
+
253
+ Example: delete_assertion(fact_id="edge-abc123")`,
254
+ inputSchema: {
255
+ type: 'object',
256
+ properties: {
257
+ fact_id: {
258
+ type: 'string',
259
+ description: 'ID of the assertion/edge to remove',
260
+ },
261
+ },
262
+ required: ['fact_id'],
263
+ },
264
+ },
265
+ {
266
+ name: 'enox_query',
267
+ description: `Filter nodes in the knowledge graph by type, domain, or name.
268
+
269
+ Use this for exact filtering when you know what you're looking for.
270
+ Unlike semantic_search, this does exact/substring matching on fields.
271
+
272
+ Example: query_graph(type="decision", domain="engineering", limit=20)`,
273
+ inputSchema: {
274
+ type: 'object',
275
+ properties: {
276
+ type: {
277
+ type: 'string',
278
+ description: 'Filter by node type',
279
+ },
280
+ domain: {
281
+ type: 'string',
282
+ description: 'Filter by knowledge domain',
283
+ },
284
+ name: {
285
+ type: 'string',
286
+ description: 'Filter by node name (substring match)',
287
+ },
288
+ limit: {
289
+ type: 'number',
290
+ description: 'Maximum number of results (default: 50)',
291
+ },
292
+ },
293
+ },
294
+ },
295
+ {
296
+ name: 'enox_traverse',
297
+ description: `Graph traversal from a knowledge entity following specific edge types and direction.
298
+
299
+ Use this for structured exploration:
300
+ - "What does X depend on?" → traverse(start="X", direction="outgoing", edge_types=["depends_on"])
301
+ - "What supersedes X?" → traverse(start="X", direction="incoming", edge_types=["supersedes"])
302
+ - Full neighborhood: traverse(start="X", direction="both", max_depth=2)
303
+
304
+ Returns nodes with depth info (0 = start, 1 = direct, 2+ = transitive).
305
+
306
+ Example: traverse(start="RFDB", direction="outgoing", edge_types=["depends_on", "uses"], max_depth=3)`,
307
+ inputSchema: {
308
+ type: 'object',
309
+ properties: {
310
+ start: {
311
+ type: 'string',
312
+ description: 'Starting entity name or ID',
313
+ },
314
+ direction: {
315
+ type: 'string',
316
+ description: 'Traversal direction (default: "both")',
317
+ enum: ['outgoing', 'incoming', 'both'],
318
+ },
319
+ edge_types: {
320
+ type: 'array',
321
+ items: { type: 'string' },
322
+ description: 'Filter by edge/relation types. Omit for all.',
323
+ },
324
+ max_depth: {
325
+ type: 'number',
326
+ description: 'Maximum traversal depth (default: 2)',
327
+ },
328
+ },
329
+ required: ['start'],
330
+ },
331
+ },
332
+ {
333
+ name: 'enox_stats',
334
+ description: `Get statistics about the Enox knowledge graph.
335
+
336
+ Use this to:
337
+ - Check if the knowledge graph has content
338
+ - See node and edge counts by type
339
+ - Assess graph density and coverage
340
+
341
+ Returns: total nodes, total edges, counts by type, domain distribution.`,
342
+ inputSchema: {
343
+ type: 'object',
344
+ properties: {},
345
+ },
346
+ },
347
+ {
348
+ name: 'recent_activity',
349
+ description: `Get recently created or updated nodes and edges.
350
+
351
+ Use this at session start to see what other sessions have recorded recently.
352
+ Helps avoid duplicating work and provides continuity across sessions.
353
+
354
+ Example: recent_activity(since="2026-05-20T00:00:00Z", limit=10)`,
355
+ inputSchema: {
356
+ type: 'object',
357
+ properties: {
358
+ since: {
359
+ type: 'string',
360
+ description: 'ISO 8601 date — only show activity after this time',
361
+ },
362
+ limit: {
363
+ type: 'number',
364
+ description: 'Maximum number of results (default: 20)',
365
+ },
366
+ },
367
+ },
368
+ },
369
+ {
370
+ name: 'update_node',
371
+ description: `Update metadata on an existing node in the knowledge graph.
372
+
373
+ Use this to rename, re-domain, or add descriptions to existing nodes
374
+ without affecting their edges.
375
+
376
+ Example: update_node(node_id="node-abc123", description="V2 storage engine with segment-based persistence")`,
377
+ inputSchema: {
378
+ type: 'object',
379
+ properties: {
380
+ node_id: {
381
+ type: 'string',
382
+ description: 'ID of the node to update',
383
+ },
384
+ name: {
385
+ type: 'string',
386
+ description: 'Updated node name',
387
+ },
388
+ domain: {
389
+ type: 'string',
390
+ description: 'Updated knowledge domain',
391
+ },
392
+ description: {
393
+ type: 'string',
394
+ description: 'Updated node description',
395
+ },
396
+ },
397
+ required: ['node_id'],
398
+ },
399
+ },
400
+ {
401
+ name: 'crawl_entity',
402
+ description: `Run ontological crawl on a code entity — generate hypotheses and verify against code graph.
403
+ Uses the Grafema code graph for verification. Records findings in knowledge database.
404
+ Example: crawl_entity(entity="compactionEnricher", context="TypeScript enricher creating FEATURE nodes")`,
405
+ inputSchema: {
406
+ type: 'object',
407
+ properties: {
408
+ entity: { type: 'string', description: 'Entity name to crawl' },
409
+ context: { type: 'string', description: 'Brief description of what this entity is' },
410
+ depth: { type: 'number', description: 'How many perspectives to explore (default: 3)' },
411
+ },
412
+ required: ['entity'],
413
+ },
414
+ },
415
+ {
416
+ name: 'save_document',
417
+ description: `Store a document or artifact as a node in the knowledge graph.
418
+
419
+ Use this for longer-form content that should be persisted:
420
+ - ADRs (Architecture Decision Records)
421
+ - Postmortems and incident reports
422
+ - Specifications and design documents
423
+ - Session notes and artifacts
424
+
425
+ The document becomes a node with its content stored. Use relates_to to
426
+ link it to relevant entities in the graph.
427
+
428
+ Example: save_document(title="ADR: Federation via thick client", content="## Context\\n...", doc_type="adr", relates_to=["Grafema", "RFDB"])`,
429
+ inputSchema: {
430
+ type: 'object',
431
+ properties: {
432
+ title: {
433
+ type: 'string',
434
+ description: 'Document title (becomes the node name)',
435
+ },
436
+ content: {
437
+ type: 'string',
438
+ description: 'Full document content (markdown supported)',
439
+ },
440
+ doc_type: {
441
+ type: 'string',
442
+ description: 'Document type (default: "note")',
443
+ enum: ['adr', 'postmortem', 'spec', 'note', 'artifact'],
444
+ },
445
+ relates_to: {
446
+ type: 'array',
447
+ items: { type: 'string' },
448
+ description: 'Node IDs or names of related entities to link to',
449
+ },
450
+ },
451
+ required: ['title', 'content'],
452
+ },
453
+ },
454
+ ];
@@ -11,7 +11,7 @@ import { GUARANTEE_TOOLS } from './guarantee-tools.js';
11
11
  import { CONTEXT_TOOLS } from './context-tools.js';
12
12
  import { PROJECT_TOOLS } from './project-tools.js';
13
13
  import { GRAPH_TOOLS } from './graph-tools.js';
14
- import { KNOWLEDGE_TOOLS } from './knowledge-tools.js';
14
+ import { ENOX_TOOLS } from './enox-tools.js';
15
15
  import { NOTATION_TOOLS } from './notation-tools.js';
16
16
  import { GRAPHQL_TOOLS } from './graphql-tools.js';
17
17
  import { REGISTRY_TOOLS } from './registry-tools.js';
@@ -23,7 +23,7 @@ export const TOOLS: ToolDefinition[] = [
23
23
  ...CONTEXT_TOOLS,
24
24
  ...PROJECT_TOOLS,
25
25
  ...GRAPH_TOOLS,
26
- ...KNOWLEDGE_TOOLS,
26
+ ...ENOX_TOOLS,
27
27
  ...NOTATION_TOOLS,
28
28
  ...GRAPHQL_TOOLS,
29
29
  ...REGISTRY_TOOLS,
@@ -31,9 +31,19 @@ export async function handleGetCoverage(args: GetCoverageArgs): Promise<ToolResu
31
31
  output += `File breakdown:\n`;
32
32
  output += ` Total files: ${result.total}\n`;
33
33
  output += ` Analyzed: ${result.analyzed.count} (${result.percentages.analyzed}%) - in graph\n`;
34
+ if (result.failed.count > 0) {
35
+ output += ` Failed: ${result.failed.count} (${result.percentages.failed}%) - skipped/failed during analysis\n`;
36
+ }
34
37
  output += ` Unsupported: ${result.unsupported.count} (${result.percentages.unsupported}%) - no indexer available\n`;
35
38
  output += ` Unreachable: ${result.unreachable.count} (${result.percentages.unreachable}%) - not imported from entrypoints\n`;
36
39
 
40
+ if (result.failed.count > 0) {
41
+ output += `\nFailed files by reason:\n`;
42
+ for (const [category, files] of Object.entries(result.failed.byCategory)) {
43
+ output += ` ${category}: ${files.length} files\n`;
44
+ }
45
+ }
46
+
37
47
  if (result.unsupported.count > 0) {
38
48
  output += `\nUnsupported files by extension:\n`;
39
49
  for (const [ext, files] of Object.entries(result.unsupported.byExtension)) {
@@ -40,11 +40,21 @@ Grafema is a static code analyzer that builds a graph of your codebase.
40
40
  ## Syntax
41
41
  violation(X) :- node(X, "TYPE"), attr(X, "name", "value").
42
42
 
43
- ## Available Predicates
44
- - type(Id, Type) - match nodes (alias: node)
45
- - edge(Src, Dst, Type) - match edges
46
- - attr(Id, Name, Value) - match node attributes (name, file, line, etc.)
47
- - \\+ - negation (not)
43
+ ## Node / Edge / Attribute Predicates
44
+ - node(Id, Type) - match nodes by type (alias: type(Id, Type))
45
+ - edge(Src, Dst, Type) - match outgoing edges Src -> Dst of the given type
46
+ - incoming(Dst, Src, Type) - match edges pointing TO Dst (reverse of edge)
47
+ - path(Src, Dst) - transitive reachability Src -> Dst (BFS over edges)
48
+ - attr(Id, Name, Value) - match node attributes (name, file, line, ...; nested paths like "a.b" supported)
49
+ - attr_edge(Src, Dst, EdgeType, AttrName, Value) - match EDGE attributes; Src/Dst/EdgeType/AttrName must be bound, Value may be variable/constant/wildcard
50
+ - parent_function(NodeId, FunctionId) - bind the enclosing FUNCTION of NodeId via CONTAINS; NodeId must be bound; empty at module level
51
+
52
+ ## Negation & String Predicates
53
+ - \\+ - negation (not); also for negative joins on a dst-position variable, e.g. \\+ edge(X, _, "CALLS")
54
+ - neq(X, Y) - inequality; BOTH arguments must be bound
55
+ - starts_with(Value, Prefix) - string prefix match
56
+ - not_starts_with(Value, Prefix) - negative string prefix match
57
+ - string_contains(Value, Substring) - substring match
48
58
 
49
59
  ## Numeric Comparison Predicates
50
60
  - gt(Value, Threshold) - greater than
@@ -55,6 +65,11 @@ violation(X) :- node(X, "TYPE"), attr(X, "name", "value").
55
65
  Values are parsed as floating-point numbers. Non-numeric values produce no matches.
56
66
  Use with attr() to filter by metadata values (e.g., metrics, line numbers).
57
67
 
68
+ ## Limitations
69
+ - No aggregations (count/sum/avg), no GROUP BY, no ORDER BY - aggregate/sort client-side.
70
+ - Predicate ORDER matters: bind a variable (via node/edge/attr) before a comparison /
71
+ string / attr_edge predicate uses it, or the query fails to place ("circular dependency").
72
+
58
73
  ## Examples
59
74
  Find all functions:
60
75
  violation(X) :- node(X, "FUNCTION").
@@ -67,6 +82,12 @@ Find files where parsing took > 500ms:
67
82
 
68
83
  Find functions with more than 100 lines:
69
84
  violation(X, Lines) :- node(X, "FUNCTION"), attr(X, "line", Start), attr(X, "endLine", End), gt(End, Start).
85
+
86
+ Find the enclosing function of every call:
87
+ violation(C, F) :- node(C, "CALL"), parent_function(C, F).
88
+
89
+ Find nodes that have incoming CALLS edges (i.e. are called):
90
+ violation(D) :- incoming(D, _, "CALLS").
70
91
  `,
71
92
  types: `
72
93
  # Node & Edge Types