@grafema/mcp 0.2.5-beta → 0.2.7

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 (79) hide show
  1. package/README.md +49 -25
  2. package/dist/analysis-worker.js +8 -4
  3. package/dist/analysis-worker.js.map +1 -1
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +15 -3
  6. package/dist/config.js.map +1 -1
  7. package/dist/definitions.d.ts.map +1 -1
  8. package/dist/definitions.js +69 -0
  9. package/dist/definitions.js.map +1 -1
  10. package/dist/handlers/analysis-handlers.d.ts +9 -0
  11. package/dist/handlers/analysis-handlers.d.ts.map +1 -0
  12. package/dist/handlers/analysis-handlers.js +73 -0
  13. package/dist/handlers/analysis-handlers.js.map +1 -0
  14. package/dist/handlers/context-handlers.d.ts +21 -0
  15. package/dist/handlers/context-handlers.d.ts.map +1 -0
  16. package/dist/handlers/context-handlers.js +330 -0
  17. package/dist/handlers/context-handlers.js.map +1 -0
  18. package/dist/handlers/coverage-handlers.d.ts +6 -0
  19. package/dist/handlers/coverage-handlers.d.ts.map +1 -0
  20. package/dist/handlers/coverage-handlers.js +42 -0
  21. package/dist/handlers/coverage-handlers.js.map +1 -0
  22. package/dist/handlers/dataflow-handlers.d.ts +8 -0
  23. package/dist/handlers/dataflow-handlers.d.ts.map +1 -0
  24. package/dist/handlers/dataflow-handlers.js +140 -0
  25. package/dist/handlers/dataflow-handlers.js.map +1 -0
  26. package/dist/handlers/documentation-handlers.d.ts +6 -0
  27. package/dist/handlers/documentation-handlers.d.ts.map +1 -0
  28. package/dist/handlers/documentation-handlers.js +79 -0
  29. package/dist/handlers/documentation-handlers.js.map +1 -0
  30. package/dist/handlers/guarantee-handlers.d.ts +21 -0
  31. package/dist/handlers/guarantee-handlers.d.ts.map +1 -0
  32. package/dist/handlers/guarantee-handlers.js +251 -0
  33. package/dist/handlers/guarantee-handlers.js.map +1 -0
  34. package/dist/handlers/guard-handlers.d.ts +14 -0
  35. package/dist/handlers/guard-handlers.d.ts.map +1 -0
  36. package/dist/handlers/guard-handlers.js +77 -0
  37. package/dist/handlers/guard-handlers.js.map +1 -0
  38. package/dist/handlers/index.d.ts +14 -0
  39. package/dist/handlers/index.d.ts.map +1 -0
  40. package/dist/handlers/index.js +14 -0
  41. package/dist/handlers/index.js.map +1 -0
  42. package/dist/handlers/issue-handlers.d.ts +6 -0
  43. package/dist/handlers/issue-handlers.d.ts.map +1 -0
  44. package/dist/handlers/issue-handlers.js +66 -0
  45. package/dist/handlers/issue-handlers.js.map +1 -0
  46. package/dist/handlers/project-handlers.d.ts +7 -0
  47. package/dist/handlers/project-handlers.d.ts.map +1 -0
  48. package/dist/handlers/project-handlers.js +153 -0
  49. package/dist/handlers/project-handlers.js.map +1 -0
  50. package/dist/handlers/query-handlers.d.ts +8 -0
  51. package/dist/handlers/query-handlers.d.ts.map +1 -0
  52. package/dist/handlers/query-handlers.js +171 -0
  53. package/dist/handlers/query-handlers.js.map +1 -0
  54. package/dist/handlers.d.ts +3 -1
  55. package/dist/handlers.d.ts.map +1 -1
  56. package/dist/handlers.js +199 -4
  57. package/dist/handlers.js.map +1 -1
  58. package/dist/server.js +7 -1
  59. package/dist/server.js.map +1 -1
  60. package/dist/types.d.ts +9 -0
  61. package/dist/types.d.ts.map +1 -1
  62. package/package.json +3 -3
  63. package/src/analysis-worker.ts +10 -2
  64. package/src/config.ts +24 -0
  65. package/src/definitions.ts +70 -0
  66. package/src/handlers/analysis-handlers.ts +105 -0
  67. package/src/handlers/context-handlers.ts +410 -0
  68. package/src/handlers/coverage-handlers.ts +56 -0
  69. package/src/handlers/dataflow-handlers.ts +193 -0
  70. package/src/handlers/documentation-handlers.ts +89 -0
  71. package/src/handlers/guarantee-handlers.ts +278 -0
  72. package/src/handlers/guard-handlers.ts +100 -0
  73. package/src/handlers/index.ts +14 -0
  74. package/src/handlers/issue-handlers.ts +81 -0
  75. package/src/handlers/project-handlers.ts +200 -0
  76. package/src/handlers/query-handlers.ts +232 -0
  77. package/src/server.ts +13 -1
  78. package/src/types.ts +15 -0
  79. package/src/handlers.ts +0 -1373
@@ -0,0 +1,193 @@
1
+ /**
2
+ * MCP Dataflow Handlers
3
+ */
4
+
5
+ import { ensureAnalyzed } from '../analysis.js';
6
+ import { getProjectPath } from '../state.js';
7
+ import {
8
+ serializeBigInt,
9
+ textResult,
10
+ errorResult,
11
+ } from '../utils.js';
12
+ import type {
13
+ ToolResult,
14
+ TraceAliasArgs,
15
+ TraceDataFlowArgs,
16
+ CheckInvariantArgs,
17
+ GraphNode,
18
+ } from '../types.js';
19
+
20
+ // === TRACE HANDLERS ===
21
+
22
+ export async function handleTraceAlias(args: TraceAliasArgs): Promise<ToolResult> {
23
+ const db = await ensureAnalyzed();
24
+ const { variableName, file } = args;
25
+ const _projectPath = getProjectPath();
26
+
27
+ let varNode: GraphNode | null = null;
28
+
29
+ for await (const node of db.queryNodes({ type: 'VARIABLE' })) {
30
+ if (node.name === variableName && node.file?.includes(file || '')) {
31
+ varNode = node;
32
+ break;
33
+ }
34
+ }
35
+
36
+ if (!varNode) {
37
+ for await (const node of db.queryNodes({ type: 'CONSTANT' })) {
38
+ if (node.name === variableName && node.file?.includes(file || '')) {
39
+ varNode = node;
40
+ break;
41
+ }
42
+ }
43
+ }
44
+
45
+ if (!varNode) {
46
+ return errorResult(`Variable "${variableName}" not found in ${file || 'project'}`);
47
+ }
48
+
49
+ const chain: unknown[] = [];
50
+ const visited = new Set<string>();
51
+ let current: GraphNode | null = varNode;
52
+ const MAX_DEPTH = 20;
53
+
54
+ while (current && chain.length < MAX_DEPTH) {
55
+ if (visited.has(current.id)) {
56
+ chain.push({ type: 'CYCLE_DETECTED', id: current.id });
57
+ break;
58
+ }
59
+ visited.add(current.id);
60
+
61
+ chain.push({
62
+ type: current.type,
63
+ name: current.name,
64
+ file: current.file,
65
+ line: current.line,
66
+ });
67
+
68
+ const edges = await db.getOutgoingEdges(current.id, ['ASSIGNED_FROM']);
69
+ if (edges.length === 0) break;
70
+
71
+ current = await db.getNode(edges[0].dst);
72
+ }
73
+
74
+ return textResult(
75
+ `Alias chain for "${variableName}" (${chain.length} steps):\n\n${JSON.stringify(
76
+ serializeBigInt(chain),
77
+ null,
78
+ 2
79
+ )}`
80
+ );
81
+ }
82
+
83
+ export async function handleTraceDataFlow(args: TraceDataFlowArgs): Promise<ToolResult> {
84
+ const db = await ensureAnalyzed();
85
+ const { source, direction = 'forward', max_depth = 10 } = args;
86
+
87
+ // Find source node
88
+ let sourceNode: GraphNode | null = null;
89
+
90
+ // Try to find by ID first
91
+ sourceNode = await db.getNode(source);
92
+
93
+ // If not found, search by name
94
+ if (!sourceNode) {
95
+ for await (const node of db.queryNodes({ name: source })) {
96
+ sourceNode = node;
97
+ break;
98
+ }
99
+ }
100
+
101
+ if (!sourceNode) {
102
+ return errorResult(`Source "${source}" not found`);
103
+ }
104
+
105
+ const visited = new Set<string>();
106
+ const paths: unknown[] = [];
107
+
108
+ async function trace(nodeId: string, depth: number, path: string[]): Promise<void> {
109
+ if (depth > max_depth || visited.has(nodeId)) return;
110
+ visited.add(nodeId);
111
+
112
+ const newPath = [...path, nodeId];
113
+
114
+ if (direction === 'forward' || direction === 'both') {
115
+ const outEdges = await db.getOutgoingEdges(nodeId, [
116
+ 'ASSIGNED_FROM',
117
+ 'DERIVES_FROM',
118
+ 'PASSES_ARGUMENT',
119
+ ]);
120
+ for (const edge of outEdges) {
121
+ await trace(edge.dst, depth + 1, newPath);
122
+ }
123
+ }
124
+
125
+ if (direction === 'backward' || direction === 'both') {
126
+ const inEdges = await db.getIncomingEdges(nodeId, [
127
+ 'ASSIGNED_FROM',
128
+ 'DERIVES_FROM',
129
+ 'PASSES_ARGUMENT',
130
+ ]);
131
+ for (const edge of inEdges) {
132
+ await trace(edge.src, depth + 1, newPath);
133
+ }
134
+ }
135
+
136
+ if (depth > 0) {
137
+ paths.push(newPath);
138
+ }
139
+ }
140
+
141
+ await trace(sourceNode.id, 0, []);
142
+
143
+ return textResult(
144
+ `Data flow from "${source}" (${paths.length} paths):\n\n${JSON.stringify(paths, null, 2)}`
145
+ );
146
+ }
147
+
148
+ export async function handleCheckInvariant(args: CheckInvariantArgs): Promise<ToolResult> {
149
+ const db = await ensureAnalyzed();
150
+ const { rule, name: description } = args;
151
+
152
+ if (!('checkGuarantee' in db)) {
153
+ return errorResult('Backend does not support Datalog queries');
154
+ }
155
+
156
+ try {
157
+ const checkFn = (db as unknown as { checkGuarantee: (q: string) => Promise<Array<{ bindings: Array<{ name: string; value: string }> }>> }).checkGuarantee;
158
+ const violations = await checkFn(rule);
159
+ const total = violations.length;
160
+
161
+ if (total === 0) {
162
+ return textResult(`✅ Invariant holds: ${description || 'No violations found'}`);
163
+ }
164
+
165
+ const enrichedViolations: unknown[] = [];
166
+ for (const v of violations.slice(0, 20)) {
167
+ const nodeId = v.bindings?.find((b: any) => b.name === 'X')?.value;
168
+ if (nodeId) {
169
+ const node = await db.getNode(nodeId);
170
+ if (node) {
171
+ enrichedViolations.push({
172
+ id: nodeId,
173
+ type: node.type,
174
+ name: node.name,
175
+ file: node.file,
176
+ line: node.line,
177
+ });
178
+ }
179
+ }
180
+ }
181
+
182
+ return textResult(
183
+ `❌ ${total} violation(s) found:\n\n${JSON.stringify(
184
+ serializeBigInt(enrichedViolations),
185
+ null,
186
+ 2
187
+ )}${total > 20 ? `\n\n... and ${total - 20} more` : ''}`
188
+ );
189
+ } catch (error) {
190
+ const message = error instanceof Error ? error.message : String(error);
191
+ return errorResult(message);
192
+ }
193
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * MCP Documentation Handlers
3
+ */
4
+
5
+ import { getOnboardingInstruction } from '@grafema/core';
6
+ import {
7
+ textResult,
8
+ } from '../utils.js';
9
+ import type {
10
+ ToolResult,
11
+ GetDocumentationArgs,
12
+ } from '../types.js';
13
+
14
+ // === DOCUMENTATION ===
15
+
16
+ export async function handleGetDocumentation(args: GetDocumentationArgs): Promise<ToolResult> {
17
+ const { topic = 'overview' } = args;
18
+
19
+ const docs: Record<string, string> = {
20
+ onboarding: getOnboardingInstruction(),
21
+ overview: `
22
+ # Grafema Code Analysis
23
+
24
+ Grafema is a static code analyzer that builds a graph of your codebase.
25
+
26
+ ## Key Tools
27
+ - query_graph: Execute Datalog queries
28
+ - find_calls: Find function/method calls
29
+ - trace_alias: Trace variable aliases
30
+ - check_invariant: Verify code invariants
31
+
32
+ ## Quick Start
33
+ 1. Use get_stats to see graph size
34
+ 2. Use find_nodes to explore the codebase
35
+ 3. Use query_graph for complex queries
36
+ `,
37
+ queries: `
38
+ # Datalog Queries
39
+
40
+ ## Syntax
41
+ violation(X) :- node(X, "TYPE"), attr(X, "name", "value").
42
+
43
+ ## Available Predicates
44
+ - node(Id, Type) - match nodes
45
+ - edge(Src, Dst, Type) - match edges
46
+ - attr(Id, Name, Value) - match attributes
47
+ - \\+ - negation (not)
48
+
49
+ ## Examples
50
+ Find all functions:
51
+ violation(X) :- node(X, "FUNCTION").
52
+
53
+ Find unresolved calls:
54
+ violation(X) :- node(X, "CALL"), \\+ edge(X, _, "CALLS").
55
+ `,
56
+ types: `
57
+ # Node & Edge Types
58
+
59
+ ## Core Node Types
60
+ - MODULE, FUNCTION, CLASS, METHOD, VARIABLE
61
+ - CALL, PROPERTY_ACCESS, IMPORT, EXPORT, PARAMETER
62
+
63
+ ## HTTP/Network
64
+ - http:route, http:request, db:query
65
+
66
+ ## Edge Types
67
+ - CONTAINS, CALLS, DEPENDS_ON
68
+ - ASSIGNED_FROM, INSTANCE_OF, PASSES_ARGUMENT
69
+ `,
70
+ guarantees: `
71
+ # Code Guarantees
72
+
73
+ Guarantees are persistent code invariants.
74
+
75
+ ## Create
76
+ Use create_guarantee with a name and Datalog rule.
77
+
78
+ ## Check
79
+ Use check_guarantees to verify all guarantees.
80
+
81
+ ## Example
82
+ Name: no-eval
83
+ Rule: violation(X) :- node(X, "CALL"), attr(X, "name", "eval").
84
+ `,
85
+ };
86
+
87
+ const content = docs[topic] || docs.overview;
88
+ return textResult(content.trim());
89
+ }
@@ -0,0 +1,278 @@
1
+ /**
2
+ * MCP Guarantee Handlers
3
+ */
4
+
5
+ import { getOrCreateBackend, getGuaranteeManager, getGuaranteeAPI } from '../state.js';
6
+ import {
7
+ textResult,
8
+ errorResult,
9
+ } from '../utils.js';
10
+ import type {
11
+ ToolResult,
12
+ CreateGuaranteeArgs,
13
+ CheckGuaranteesArgs,
14
+ DeleteGuaranteeArgs,
15
+ } from '../types.js';
16
+ import { isGuaranteeType } from '@grafema/core';
17
+
18
+ // === GUARANTEE HANDLERS ===
19
+
20
+ /**
21
+ * Create a new guarantee (Datalog-based or contract-based)
22
+ */
23
+ export async function handleCreateGuarantee(args: CreateGuaranteeArgs): Promise<ToolResult> {
24
+ await getOrCreateBackend(); // Ensure managers are initialized
25
+
26
+ const { name, rule, type, priority, status, owner, schema, condition, description, governs, severity } = args;
27
+
28
+ try {
29
+ // Determine if this is a contract-based guarantee
30
+ if (type && isGuaranteeType(type)) {
31
+ // Contract-based guarantee
32
+ const api = getGuaranteeAPI();
33
+ if (!api) {
34
+ return errorResult('GuaranteeAPI not initialized');
35
+ }
36
+
37
+ const guarantee = await api.createGuarantee({
38
+ type,
39
+ name,
40
+ priority,
41
+ status,
42
+ owner,
43
+ schema,
44
+ condition,
45
+ description,
46
+ governs,
47
+ });
48
+
49
+ return textResult(
50
+ `✅ Created contract-based guarantee: ${guarantee.id}\n` +
51
+ `Type: ${guarantee.type}\n` +
52
+ `Priority: ${guarantee.priority}\n` +
53
+ `Status: ${guarantee.status}` +
54
+ (guarantee.description ? `\nDescription: ${guarantee.description}` : '')
55
+ );
56
+ } else {
57
+ // Datalog-based guarantee
58
+ if (!rule) {
59
+ return errorResult('Datalog-based guarantee requires "rule" field');
60
+ }
61
+
62
+ const manager = getGuaranteeManager();
63
+ if (!manager) {
64
+ return errorResult('GuaranteeManager not initialized');
65
+ }
66
+
67
+ const guarantee = await manager.create({
68
+ id: name,
69
+ name,
70
+ rule,
71
+ severity: severity || 'warning',
72
+ governs: governs || ['**/*.js'],
73
+ });
74
+
75
+ return textResult(
76
+ `✅ Created Datalog-based guarantee: ${guarantee.id}\n` +
77
+ `Rule: ${guarantee.rule}\n` +
78
+ `Severity: ${guarantee.severity}`
79
+ );
80
+ }
81
+ } catch (error) {
82
+ const message = error instanceof Error ? error.message : String(error);
83
+ return errorResult(`Failed to create guarantee: ${message}`);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * List all guarantees (both Datalog-based and contract-based)
89
+ */
90
+ export async function handleListGuarantees(): Promise<ToolResult> {
91
+ await getOrCreateBackend(); // Ensure managers are initialized
92
+
93
+ const results: string[] = [];
94
+
95
+ try {
96
+ // List Datalog-based guarantees
97
+ const manager = getGuaranteeManager();
98
+ if (manager) {
99
+ const datalogGuarantees = await manager.list();
100
+ if (datalogGuarantees.length > 0) {
101
+ results.push('## Datalog-based Guarantees\n');
102
+ for (const g of datalogGuarantees) {
103
+ results.push(`- **${g.id}** (${g.severity})`);
104
+ results.push(` Rule: ${g.rule.substring(0, 80)}${g.rule.length > 80 ? '...' : ''}`);
105
+ }
106
+ }
107
+ }
108
+
109
+ // List contract-based guarantees
110
+ const api = getGuaranteeAPI();
111
+ if (api) {
112
+ const contractGuarantees = await api.findGuarantees();
113
+ if (contractGuarantees.length > 0) {
114
+ if (results.length > 0) results.push('\n');
115
+ results.push('## Contract-based Guarantees\n');
116
+ for (const g of contractGuarantees) {
117
+ results.push(`- **${g.id}** [${g.priority}] (${g.status})`);
118
+ if (g.description) results.push(` ${g.description}`);
119
+ }
120
+ }
121
+ }
122
+
123
+ if (results.length === 0) {
124
+ return textResult('No guarantees defined yet.');
125
+ }
126
+
127
+ return textResult(results.join('\n'));
128
+ } catch (error) {
129
+ const message = error instanceof Error ? error.message : String(error);
130
+ return errorResult(`Failed to list guarantees: ${message}`);
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Check guarantees (both Datalog-based and contract-based)
136
+ */
137
+ export async function handleCheckGuarantees(args: CheckGuaranteesArgs): Promise<ToolResult> {
138
+ await getOrCreateBackend(); // Ensure managers are initialized
139
+
140
+ const { names } = args;
141
+ const results: string[] = [];
142
+ let totalPassed = 0;
143
+ let totalFailed = 0;
144
+
145
+ try {
146
+ const manager = getGuaranteeManager();
147
+ const api = getGuaranteeAPI();
148
+
149
+ if (names && names.length > 0) {
150
+ // Check specific guarantees
151
+ for (const name of names) {
152
+ // Try Datalog-based first
153
+ if (manager) {
154
+ try {
155
+ const result = await manager.check(name);
156
+ if (result.passed) {
157
+ totalPassed++;
158
+ results.push(`✅ ${result.guaranteeId}: PASSED`);
159
+ } else {
160
+ totalFailed++;
161
+ results.push(`❌ ${result.guaranteeId}: FAILED (${result.violationCount} violations)`);
162
+ for (const v of result.violations.slice(0, 5)) {
163
+ results.push(` - ${v.file}:${v.line} (${v.type})`);
164
+ }
165
+ if (result.violationCount > 5) {
166
+ results.push(` ... and ${result.violationCount - 5} more`);
167
+ }
168
+ }
169
+ continue;
170
+ } catch {
171
+ // Not a Datalog guarantee, try contract-based
172
+ }
173
+ }
174
+
175
+ // Try contract-based
176
+ if (api) {
177
+ try {
178
+ const result = await api.checkGuarantee(name);
179
+ if (result.passed) {
180
+ totalPassed++;
181
+ results.push(`✅ ${result.id}: PASSED`);
182
+ } else {
183
+ totalFailed++;
184
+ results.push(`❌ ${result.id}: FAILED`);
185
+ for (const err of result.errors.slice(0, 5)) {
186
+ results.push(` - ${err}`);
187
+ }
188
+ }
189
+ } catch {
190
+ results.push(`⚠️ ${name}: Not found`);
191
+ }
192
+ }
193
+ }
194
+ } else {
195
+ // Check all guarantees
196
+ if (manager) {
197
+ const datalogResult = await manager.checkAll();
198
+ totalPassed += datalogResult.passed;
199
+ totalFailed += datalogResult.failed;
200
+
201
+ if (datalogResult.total > 0) {
202
+ results.push('## Datalog Guarantees\n');
203
+ for (const r of datalogResult.results) {
204
+ if (r.passed) {
205
+ results.push(`✅ ${r.guaranteeId}: PASSED`);
206
+ } else {
207
+ results.push(`❌ ${r.guaranteeId}: FAILED (${r.violationCount} violations)`);
208
+ }
209
+ }
210
+ }
211
+ }
212
+
213
+ if (api) {
214
+ const contractResult = await api.checkAllGuarantees();
215
+ totalPassed += contractResult.passed;
216
+ totalFailed += contractResult.failed;
217
+
218
+ if (contractResult.total > 0) {
219
+ if (results.length > 0) results.push('\n');
220
+ results.push('## Contract Guarantees\n');
221
+ for (const r of contractResult.results) {
222
+ if (r.passed) {
223
+ results.push(`✅ ${r.id}: PASSED`);
224
+ } else {
225
+ results.push(`❌ ${r.id}: FAILED`);
226
+ }
227
+ }
228
+ }
229
+ }
230
+ }
231
+
232
+ if (results.length === 0) {
233
+ return textResult('No guarantees to check.');
234
+ }
235
+
236
+ const summary = `\n---\nTotal: ${totalPassed + totalFailed} | ✅ Passed: ${totalPassed} | ❌ Failed: ${totalFailed}`;
237
+ return textResult(results.join('\n') + summary);
238
+ } catch (error) {
239
+ const message = error instanceof Error ? error.message : String(error);
240
+ return errorResult(`Failed to check guarantees: ${message}`);
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Delete a guarantee
246
+ */
247
+ export async function handleDeleteGuarantee(args: DeleteGuaranteeArgs): Promise<ToolResult> {
248
+ await getOrCreateBackend(); // Ensure managers are initialized
249
+
250
+ const { name } = args;
251
+
252
+ try {
253
+ // Try Datalog-based first
254
+ const manager = getGuaranteeManager();
255
+ if (manager) {
256
+ try {
257
+ await manager.delete(name);
258
+ return textResult(`✅ Deleted Datalog guarantee: ${name}`);
259
+ } catch {
260
+ // Not found in Datalog, try contract-based
261
+ }
262
+ }
263
+
264
+ // Try contract-based
265
+ const api = getGuaranteeAPI();
266
+ if (api) {
267
+ const deleted = await api.deleteGuarantee(name);
268
+ if (deleted) {
269
+ return textResult(`✅ Deleted contract guarantee: ${name}`);
270
+ }
271
+ }
272
+
273
+ return errorResult(`Guarantee not found: ${name}`);
274
+ } catch (error) {
275
+ const message = error instanceof Error ? error.message : String(error);
276
+ return errorResult(`Failed to delete guarantee: ${message}`);
277
+ }
278
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * MCP Guard Handlers
3
+ */
4
+
5
+ import { getOrCreateBackend } from '../state.js';
6
+ import {
7
+ serializeBigInt,
8
+ textResult,
9
+ errorResult,
10
+ } from '../utils.js';
11
+ import type {
12
+ ToolResult,
13
+ FindGuardsArgs,
14
+ GuardInfo,
15
+ } from '../types.js';
16
+
17
+ // === FIND GUARDS (REG-274) ===
18
+
19
+ /**
20
+ * Find conditional guards protecting a node.
21
+ *
22
+ * Walks up the containment tree via CONTAINS edges, collecting
23
+ * SCOPE nodes that have conditional=true (if_statement, else_statement, etc.).
24
+ *
25
+ * Returns guards in inner-to-outer order.
26
+ */
27
+ export async function handleFindGuards(args: FindGuardsArgs): Promise<ToolResult> {
28
+ const db = await getOrCreateBackend();
29
+ const { nodeId } = args;
30
+
31
+ // Verify target node exists
32
+ const targetNode = await db.getNode(nodeId);
33
+ if (!targetNode) {
34
+ return errorResult(`Node not found: ${nodeId}`);
35
+ }
36
+
37
+ const guards: GuardInfo[] = [];
38
+ const visited = new Set<string>();
39
+ let currentId = nodeId;
40
+
41
+ // Walk up the containment tree
42
+ while (true) {
43
+ if (visited.has(currentId)) break;
44
+ visited.add(currentId);
45
+
46
+ // Get parent via incoming CONTAINS edge
47
+ const incomingEdges = await db.getIncomingEdges(currentId, ['CONTAINS']);
48
+ if (incomingEdges.length === 0) break;
49
+
50
+ const parentId = incomingEdges[0].src;
51
+ const parentNode = await db.getNode(parentId);
52
+
53
+ if (!parentNode) break;
54
+
55
+ // Check if this is a conditional scope
56
+ if (parentNode.conditional) {
57
+ // Parse constraints if stored as string
58
+ let constraints = parentNode.constraints;
59
+ if (typeof constraints === 'string') {
60
+ try {
61
+ constraints = JSON.parse(constraints);
62
+ } catch {
63
+ // Keep as string if not valid JSON
64
+ }
65
+ }
66
+
67
+ guards.push({
68
+ scopeId: parentNode.id,
69
+ scopeType: (parentNode.scopeType as string) || 'unknown',
70
+ condition: parentNode.condition as string | undefined,
71
+ constraints: constraints as unknown[] | undefined,
72
+ file: parentNode.file || '',
73
+ line: (parentNode.line as number) || 0,
74
+ });
75
+ }
76
+
77
+ currentId = parentId;
78
+ }
79
+
80
+ if (guards.length === 0) {
81
+ return textResult(
82
+ `No guards found for node: ${nodeId}\n` +
83
+ `The node is not protected by any conditional scope (if/else/switch/etc.).`
84
+ );
85
+ }
86
+
87
+ const summary = guards.map((g, i) => {
88
+ const indent = ' '.repeat(i);
89
+ return `${indent}${i + 1}. ${g.scopeType} at ${g.file}:${g.line}` +
90
+ (g.condition ? `\n${indent} condition: ${g.condition}` : '');
91
+ }).join('\n');
92
+
93
+ return textResult(
94
+ `Found ${guards.length} guard(s) for node: ${nodeId}\n` +
95
+ `(inner to outer order)\n\n` +
96
+ summary +
97
+ `\n\n` +
98
+ JSON.stringify(serializeBigInt(guards), null, 2)
99
+ );
100
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * MCP Tool Handlers — barrel export
3
+ */
4
+
5
+ export { handleQueryGraph, handleFindCalls, handleFindNodes } from './query-handlers.js';
6
+ export { handleTraceAlias, handleTraceDataFlow, handleCheckInvariant } from './dataflow-handlers.js';
7
+ export { handleAnalyzeProject, handleGetAnalysisStatus, handleGetStats, handleGetSchema } from './analysis-handlers.js';
8
+ export { handleCreateGuarantee, handleListGuarantees, handleCheckGuarantees, handleDeleteGuarantee } from './guarantee-handlers.js';
9
+ export { handleGetFunctionDetails, handleGetContext, handleGetFileOverview } from './context-handlers.js';
10
+ export { handleReadProjectStructure, handleWriteConfig } from './project-handlers.js';
11
+ export { handleGetCoverage } from './coverage-handlers.js';
12
+ export { handleFindGuards } from './guard-handlers.js';
13
+ export { handleGetDocumentation } from './documentation-handlers.js';
14
+ export { handleReportIssue } from './issue-handlers.js';