@grafema/mcp 0.2.11 → 0.3.0-beta

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 (158) hide show
  1. package/dist/analysis-worker.d.ts +4 -3
  2. package/dist/analysis-worker.d.ts.map +1 -1
  3. package/dist/analysis-worker.js +8 -203
  4. package/dist/analysis-worker.js.map +1 -1
  5. package/dist/analysis.d.ts +10 -3
  6. package/dist/analysis.d.ts.map +1 -1
  7. package/dist/analysis.js +130 -62
  8. package/dist/analysis.js.map +1 -1
  9. package/dist/config.d.ts +5 -11
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +6 -128
  12. package/dist/config.js.map +1 -1
  13. package/dist/definitions/analysis-tools.d.ts +6 -0
  14. package/dist/definitions/analysis-tools.d.ts.map +1 -0
  15. package/dist/definitions/analysis-tools.js +125 -0
  16. package/dist/definitions/analysis-tools.js.map +1 -0
  17. package/dist/definitions/context-tools.d.ts +6 -0
  18. package/dist/definitions/context-tools.d.ts.map +1 -0
  19. package/dist/definitions/context-tools.js +144 -0
  20. package/dist/definitions/context-tools.js.map +1 -0
  21. package/dist/definitions/graph-tools.d.ts +7 -0
  22. package/dist/definitions/graph-tools.d.ts.map +1 -0
  23. package/dist/definitions/graph-tools.js +124 -0
  24. package/dist/definitions/graph-tools.js.map +1 -0
  25. package/dist/definitions/graphql-tools.d.ts +6 -0
  26. package/dist/definitions/graphql-tools.d.ts.map +1 -0
  27. package/dist/definitions/graphql-tools.js +62 -0
  28. package/dist/definitions/graphql-tools.js.map +1 -0
  29. package/dist/definitions/guarantee-tools.d.ts +6 -0
  30. package/dist/definitions/guarantee-tools.d.ts.map +1 -0
  31. package/dist/definitions/guarantee-tools.js +136 -0
  32. package/dist/definitions/guarantee-tools.js.map +1 -0
  33. package/dist/definitions/index.d.ts +7 -0
  34. package/dist/definitions/index.d.ts.map +1 -0
  35. package/dist/definitions/index.js +24 -0
  36. package/dist/definitions/index.js.map +1 -0
  37. package/dist/definitions/knowledge-tools.d.ts +10 -0
  38. package/dist/definitions/knowledge-tools.d.ts.map +1 -0
  39. package/dist/definitions/knowledge-tools.js +300 -0
  40. package/dist/definitions/knowledge-tools.js.map +1 -0
  41. package/dist/definitions/notation-tools.d.ts +9 -0
  42. package/dist/definitions/notation-tools.d.ts.map +1 -0
  43. package/dist/definitions/notation-tools.js +62 -0
  44. package/dist/definitions/notation-tools.js.map +1 -0
  45. package/dist/definitions/project-tools.d.ts +6 -0
  46. package/dist/definitions/project-tools.d.ts.map +1 -0
  47. package/dist/definitions/project-tools.js +181 -0
  48. package/dist/definitions/project-tools.js.map +1 -0
  49. package/dist/definitions/query-tools.d.ts +6 -0
  50. package/dist/definitions/query-tools.d.ts.map +1 -0
  51. package/dist/definitions/query-tools.js +245 -0
  52. package/dist/definitions/query-tools.js.map +1 -0
  53. package/dist/definitions/types.d.ts +21 -0
  54. package/dist/definitions/types.d.ts.map +1 -0
  55. package/dist/definitions/types.js +5 -0
  56. package/dist/definitions/types.js.map +1 -0
  57. package/dist/dev-proxy.d.ts +29 -0
  58. package/dist/dev-proxy.d.ts.map +1 -0
  59. package/dist/dev-proxy.js +267 -0
  60. package/dist/dev-proxy.js.map +1 -0
  61. package/dist/handlers/analysis-handlers.d.ts.map +1 -1
  62. package/dist/handlers/analysis-handlers.js +34 -4
  63. package/dist/handlers/analysis-handlers.js.map +1 -1
  64. package/dist/handlers/context-handlers.d.ts +5 -6
  65. package/dist/handlers/context-handlers.d.ts.map +1 -1
  66. package/dist/handlers/context-handlers.js +19 -16
  67. package/dist/handlers/context-handlers.js.map +1 -1
  68. package/dist/handlers/coverage-handlers.js +1 -1
  69. package/dist/handlers/dataflow-handlers.d.ts +2 -0
  70. package/dist/handlers/dataflow-handlers.d.ts.map +1 -1
  71. package/dist/handlers/dataflow-handlers.js +68 -46
  72. package/dist/handlers/dataflow-handlers.js.map +1 -1
  73. package/dist/handlers/documentation-handlers.d.ts.map +1 -1
  74. package/dist/handlers/documentation-handlers.js +56 -2
  75. package/dist/handlers/documentation-handlers.js.map +1 -1
  76. package/dist/handlers/graph-handlers.d.ts +23 -0
  77. package/dist/handlers/graph-handlers.d.ts.map +1 -0
  78. package/dist/handlers/graph-handlers.js +155 -0
  79. package/dist/handlers/graph-handlers.js.map +1 -0
  80. package/dist/handlers/graphql-handlers.d.ts +9 -0
  81. package/dist/handlers/graphql-handlers.d.ts.map +1 -0
  82. package/dist/handlers/graphql-handlers.js +57 -0
  83. package/dist/handlers/graphql-handlers.js.map +1 -0
  84. package/dist/handlers/guarantee-handlers.js +1 -1
  85. package/dist/handlers/guard-handlers.d.ts.map +1 -1
  86. package/dist/handlers/guard-handlers.js +6 -3
  87. package/dist/handlers/guard-handlers.js.map +1 -1
  88. package/dist/handlers/index.d.ts +4 -0
  89. package/dist/handlers/index.d.ts.map +1 -1
  90. package/dist/handlers/index.js +6 -0
  91. package/dist/handlers/index.js.map +1 -1
  92. package/dist/handlers/issue-handlers.d.ts.map +1 -1
  93. package/dist/handlers/issue-handlers.js +10 -15
  94. package/dist/handlers/issue-handlers.js.map +1 -1
  95. package/dist/handlers/knowledge-handlers.d.ts +25 -0
  96. package/dist/handlers/knowledge-handlers.d.ts.map +1 -0
  97. package/dist/handlers/knowledge-handlers.js +208 -0
  98. package/dist/handlers/knowledge-handlers.js.map +1 -0
  99. package/dist/handlers/notation-handlers.d.ts +6 -0
  100. package/dist/handlers/notation-handlers.d.ts.map +1 -0
  101. package/dist/handlers/notation-handlers.js +53 -0
  102. package/dist/handlers/notation-handlers.js.map +1 -0
  103. package/dist/handlers/project-handlers.js +1 -1
  104. package/dist/handlers/query-handlers.d.ts.map +1 -1
  105. package/dist/handlers/query-handlers.js +166 -20
  106. package/dist/handlers/query-handlers.js.map +1 -1
  107. package/dist/prompts.js +1 -1
  108. package/dist/server.d.ts +19 -1
  109. package/dist/server.d.ts.map +1 -1
  110. package/dist/server.js +93 -3
  111. package/dist/server.js.map +1 -1
  112. package/dist/state.d.ts +10 -1
  113. package/dist/state.d.ts.map +1 -1
  114. package/dist/state.js +61 -8
  115. package/dist/state.js.map +1 -1
  116. package/dist/types.d.ts +75 -3
  117. package/dist/types.d.ts.map +1 -1
  118. package/dist/utils.d.ts +4 -0
  119. package/dist/utils.d.ts.map +1 -1
  120. package/dist/utils.js +18 -1
  121. package/dist/utils.js.map +1 -1
  122. package/package.json +4 -3
  123. package/src/analysis-worker.ts +9 -301
  124. package/src/analysis.ts +151 -77
  125. package/src/config.ts +6 -193
  126. package/src/definitions/analysis-tools.ts +127 -0
  127. package/src/definitions/context-tools.ts +147 -0
  128. package/src/definitions/graph-tools.ts +126 -0
  129. package/src/definitions/graphql-tools.ts +64 -0
  130. package/src/definitions/guarantee-tools.ts +138 -0
  131. package/src/definitions/index.ts +28 -0
  132. package/src/definitions/knowledge-tools.ts +302 -0
  133. package/src/definitions/notation-tools.ts +64 -0
  134. package/src/definitions/project-tools.ts +183 -0
  135. package/src/definitions/query-tools.ts +247 -0
  136. package/src/definitions/types.ts +22 -0
  137. package/src/dev-proxy.ts +336 -0
  138. package/src/handlers/analysis-handlers.ts +35 -4
  139. package/src/handlers/context-handlers.ts +19 -15
  140. package/src/handlers/coverage-handlers.ts +1 -1
  141. package/src/handlers/dataflow-handlers.ts +74 -56
  142. package/src/handlers/documentation-handlers.ts +56 -2
  143. package/src/handlers/graph-handlers.ts +212 -0
  144. package/src/handlers/graphql-handlers.ts +70 -0
  145. package/src/handlers/guarantee-handlers.ts +1 -1
  146. package/src/handlers/guard-handlers.ts +7 -3
  147. package/src/handlers/index.ts +6 -0
  148. package/src/handlers/issue-handlers.ts +10 -15
  149. package/src/handlers/knowledge-handlers.ts +242 -0
  150. package/src/handlers/notation-handlers.ts +71 -0
  151. package/src/handlers/project-handlers.ts +1 -1
  152. package/src/handlers/query-handlers.ts +186 -22
  153. package/src/prompts.ts +1 -1
  154. package/src/server.ts +126 -2
  155. package/src/state.ts +68 -8
  156. package/src/types.ts +98 -3
  157. package/src/utils.ts +22 -1
  158. package/src/definitions.ts +0 -665
@@ -1,306 +1,14 @@
1
1
  /**
2
- * Analysis Worker - runs in separate process to avoid blocking MCP server
2
+ * Analysis Worker DEPRECATED
3
3
  *
4
- * Usage: node analysis-worker.js <projectPath> [serviceName]
4
+ * Analysis is now handled by the grafema-orchestrator Rust binary.
5
+ * This file is kept as a stub to prevent import errors from any remaining references.
5
6
  *
6
- * Sends progress updates via IPC to parent process
7
+ * Use analysis.ts ensureAnalyzed() which spawns grafema-orchestrator.
7
8
  */
8
9
 
9
- import { join } from 'path';
10
- import { existsSync, readdirSync, readFileSync } from 'fs';
11
- import { pathToFileURL } from 'url';
12
-
13
- import {
14
- Orchestrator,
15
- RFDBServerBackend,
16
- // Indexing
17
- JSModuleIndexer,
18
- // Analysis
19
- JSASTAnalyzer,
20
- ExpressRouteAnalyzer,
21
- ExpressResponseAnalyzer,
22
- NestJSRouteAnalyzer,
23
- SocketIOAnalyzer,
24
- DatabaseAnalyzer,
25
- FetchAnalyzer,
26
- ServiceLayerAnalyzer,
27
- ReactAnalyzer,
28
- // Enrichment
29
- MethodCallResolver,
30
- ArgumentParameterLinker,
31
- AliasTracker,
32
- ValueDomainAnalyzer,
33
- MountPointResolver,
34
- PrefixEvaluator,
35
- ConfigRoutingMapBuilder,
36
- ServiceConnectionEnricher,
37
- RejectionPropagationEnricher,
38
- // Validation
39
- CallResolverValidator,
40
- EvalBanValidator,
41
- SQLInjectionValidator,
42
- AwaitInLoopValidator,
43
- ShadowingDetector,
44
- GraphConnectivityValidator,
45
- DataFlowValidator,
46
- UnconnectedRouteValidator,
47
- } from '@grafema/core';
48
- import type { ParallelConfig ,
49
- Plugin} from '@grafema/core';
50
-
51
- /**
52
- * Config structure
53
- */
54
- interface WorkerConfig {
55
- plugins?: Record<string, string[]>;
56
- analysis?: {
57
- parallel?: ParallelConfig & { workers?: number; socketPath?: string };
58
- };
59
- }
60
-
61
- /**
62
- * Progress message
63
- */
64
- interface ProgressMessage {
65
- type: 'progress';
66
- phase?: string;
67
- message?: string;
68
- servicesDiscovered?: number;
69
- servicesAnalyzed?: number;
70
- }
71
-
72
- /**
73
- * Complete message
74
- */
75
- interface CompleteMessage {
76
- type: 'complete';
77
- nodeCount: number;
78
- edgeCount: number;
79
- totalTime: string;
80
- }
81
-
82
- /**
83
- * Error message
84
- */
85
- interface ErrorMessage {
86
- type: 'error';
87
- message: string;
88
- stack?: string;
89
- }
90
-
91
-
92
- const projectPath = process.argv[2];
93
- const serviceName = process.argv[3] && process.argv[3] !== '' ? process.argv[3] : null;
94
- const indexOnly = process.argv[4] === 'indexOnly';
95
-
96
- if (!projectPath) {
97
- console.error('Usage: node analysis-worker.js <projectPath> [serviceName] [indexOnly]');
98
- process.exit(1);
99
- }
100
-
101
- function sendProgress(data: Omit<ProgressMessage, 'type'>): void {
102
- if (process.send) {
103
- process.send({ type: 'progress', ...data } as ProgressMessage);
104
- }
105
- }
106
-
107
- function sendComplete(data: Omit<CompleteMessage, 'type'>): void {
108
- if (process.send) {
109
- process.send({ type: 'complete', ...data } as CompleteMessage);
110
- }
111
- }
112
-
113
- function sendError(error: Error): void {
114
- if (process.send) {
115
- process.send({ type: 'error', message: error.message, stack: error.stack } as ErrorMessage);
116
- }
117
- }
118
-
119
- async function loadConfig(): Promise<WorkerConfig> {
120
- const configPath = join(projectPath, '.grafema', 'config.json');
121
- if (existsSync(configPath)) {
122
- return JSON.parse(readFileSync(configPath, 'utf8')) as WorkerConfig;
123
- }
124
- return { plugins: {} };
125
- }
126
-
127
- async function loadCustomPlugins(): Promise<Record<string, new () => Plugin>> {
128
- const pluginsDir = join(projectPath, '.grafema', 'plugins');
129
- const customPlugins: Record<string, new () => Plugin> = {};
130
-
131
- if (!existsSync(pluginsDir)) {
132
- return customPlugins;
133
- }
134
-
135
- const files = readdirSync(pluginsDir).filter(f => f.endsWith('.mjs') || f.endsWith('.js'));
136
- for (const file of files) {
137
- try {
138
- const module = await import(pathToFileURL(join(pluginsDir, file)).href);
139
- const PluginClass = module.default as new () => Plugin;
140
- if (PluginClass) {
141
- customPlugins[PluginClass.name] = PluginClass;
142
- }
143
- } catch (err) {
144
- console.error(`Failed to load plugin ${file}:`, (err as Error).message);
145
- }
146
- }
147
-
148
- return customPlugins;
149
- }
150
-
151
- async function run(): Promise<void> {
152
- const startTime = Date.now();
153
- let db: RFDBServerBackend | null = null;
154
-
155
- try {
156
- sendProgress({ phase: 'starting', message: 'Loading configuration...' });
157
-
158
- const config = await loadConfig();
159
- const customPlugins = await loadCustomPlugins();
160
-
161
- // Built-in plugins map
162
- const builtinPlugins: Record<string, () => Plugin> = {
163
- JSModuleIndexer: () => new JSModuleIndexer(),
164
- JSASTAnalyzer: () => new JSASTAnalyzer(),
165
- ExpressRouteAnalyzer: () => new ExpressRouteAnalyzer(),
166
- ExpressResponseAnalyzer: () => new ExpressResponseAnalyzer(),
167
- NestJSRouteAnalyzer: () => new NestJSRouteAnalyzer(),
168
- SocketIOAnalyzer: () => new SocketIOAnalyzer(),
169
- DatabaseAnalyzer: () => new DatabaseAnalyzer(),
170
- FetchAnalyzer: () => new FetchAnalyzer(),
171
- ServiceLayerAnalyzer: () => new ServiceLayerAnalyzer(),
172
- ReactAnalyzer: () => new ReactAnalyzer(),
173
- MethodCallResolver: () => new MethodCallResolver(),
174
- ArgumentParameterLinker: () => new ArgumentParameterLinker(),
175
- AliasTracker: () => new AliasTracker(),
176
- ValueDomainAnalyzer: () => new ValueDomainAnalyzer(),
177
- MountPointResolver: () => new MountPointResolver(),
178
- PrefixEvaluator: () => new PrefixEvaluator(),
179
- ConfigRoutingMapBuilder: () => new ConfigRoutingMapBuilder(),
180
- ServiceConnectionEnricher: () => new ServiceConnectionEnricher(),
181
- RejectionPropagationEnricher: () => new RejectionPropagationEnricher(),
182
- CallResolverValidator: () => new CallResolverValidator(),
183
- EvalBanValidator: () => new EvalBanValidator(),
184
- SQLInjectionValidator: () => new SQLInjectionValidator(),
185
- AwaitInLoopValidator: () => new AwaitInLoopValidator(),
186
- ShadowingDetector: () => new ShadowingDetector(),
187
- GraphConnectivityValidator: () => new GraphConnectivityValidator(),
188
- DataFlowValidator: () => new DataFlowValidator(),
189
- UnconnectedRouteValidator: () => new UnconnectedRouteValidator(),
190
- };
191
-
192
- // Add custom plugins
193
- for (const [name, PluginClass] of Object.entries(customPlugins)) {
194
- builtinPlugins[name] = () => new PluginClass();
195
- }
196
-
197
- // Build plugins array from config
198
- const plugins: Plugin[] = [];
199
- for (const [_phase, pluginNames] of Object.entries(config.plugins || {})) {
200
- for (const name of pluginNames) {
201
- if (builtinPlugins[name]) {
202
- plugins.push(builtinPlugins[name]());
203
- } else if (customPlugins[name]) {
204
- plugins.push(new customPlugins[name]());
205
- console.log(`[Worker] Loaded custom plugin: ${name}`);
206
- } else {
207
- console.warn(`[Worker] Plugin not found: ${name}`);
208
- }
209
- }
210
- }
211
-
212
- console.log(`[Worker] Loaded ${plugins.length} plugins:`, plugins.map(p => p.metadata?.name || p.constructor?.name || 'unknown'));
213
- sendProgress({ phase: 'starting', message: `Loaded ${plugins.length} plugins` });
214
-
215
- // Get parallel analysis config
216
- const parallelConfig = config.analysis?.parallel;
217
- if (parallelConfig?.enabled) {
218
- console.log(`[Worker] Queue-based parallel mode enabled: workers=${parallelConfig.workers}`);
219
- }
220
-
221
- // Connect to RFDB server (shared with MCP server)
222
- // The MCP server starts the RFDB server if not running
223
- const dbPath = join(projectPath, '.grafema', 'graph.rfdb');
224
- const socketPath = config.analysis?.parallel?.socketPath || '/tmp/rfdb.sock';
225
-
226
- console.log(`[Worker] Connecting to RFDB server: socket=${socketPath}, db=${dbPath}`);
227
- db = new RFDBServerBackend({ socketPath, dbPath });
228
- await db.connect();
229
-
230
- // NOTE: db.clear() is NOT called here.
231
- // MCP server clears DB INSIDE the analysis lock BEFORE spawning this worker.
232
- // This prevents race conditions where concurrent analysis calls could both
233
- // clear the database. Worker assumes DB is already clean.
234
- // See: REG-159 implementation, Phase 2.5 (Worker Clear Coordination)
235
-
236
- sendProgress({ phase: 'discovery', message: 'Starting analysis...' });
237
-
238
- // Create orchestrator
239
- const orchestrator = new Orchestrator({
240
- graph: db,
241
- plugins,
242
- parallel: parallelConfig as ParallelConfig | undefined, // Pass parallel config for queue-based analysis
243
- serviceFilter: serviceName,
244
- indexOnly: indexOnly,
245
- onProgress: (progress) => {
246
- sendProgress({
247
- phase: progress.phase,
248
- message: progress.message,
249
- servicesAnalyzed: progress.servicesAnalyzed
250
- });
251
- }
252
- });
253
-
254
- // Run analysis
255
- await orchestrator.run(projectPath);
256
-
257
- // Get final stats
258
- let nodeCount = 0;
259
- let edgeCount = 0;
260
-
261
- // Use async methods for RFDBServerBackend
262
- const allEdges = await db.getAllEdgesAsync();
263
- edgeCount = allEdges.length;
264
-
265
- for await (const _node of db.queryNodes({})) {
266
- nodeCount++;
267
- }
268
-
269
- // Flush to disk using proper async method
270
- console.log('[Worker] Flushing database to disk...');
271
- await db.flush();
272
- console.log('[Worker] Database flushed successfully');
273
-
274
- const totalTime = ((Date.now() - startTime) / 1000).toFixed(2);
275
-
276
- sendComplete({
277
- nodeCount,
278
- edgeCount,
279
- totalTime
280
- });
281
-
282
- // Close database properly before exit
283
- await db.close();
284
- console.log('[Worker] Database closed');
285
-
286
- process.exit(0);
287
- } finally {
288
- // Ensure database connection is closed even on error
289
- if (db && db.connected) {
290
- try {
291
- await db.close();
292
- console.log('[Worker] Database connection closed in cleanup');
293
- } catch (closeErr) {
294
- const message = closeErr instanceof Error ? closeErr.message : String(closeErr);
295
- console.error('[Worker] Error closing database connection:', message);
296
- }
297
- }
298
- }
299
- }
300
-
301
- run().catch(err => {
302
- const error = err instanceof Error ? err : new Error(String(err));
303
- sendError(error);
304
- console.error('Analysis failed:', err);
305
- process.exit(1);
306
- });
10
+ throw new Error(
11
+ 'Analysis is now handled by grafema-orchestrator. ' +
12
+ 'Use analysis.ts ensureAnalyzed() instead. ' +
13
+ 'The worker pattern is no longer needed since we shell out to a native binary.'
14
+ );
package/src/analysis.ts CHANGED
@@ -1,9 +1,15 @@
1
1
  /**
2
2
  * MCP Analysis Orchestration
3
+ *
4
+ * Shells out to the `grafema-orchestrator` Rust binary instead of using
5
+ * the JS Orchestrator class. The binary handles file discovery, parsing,
6
+ * analysis, resolution, and RFDB ingestion.
3
7
  */
4
8
 
5
- import type { Plugin } from '@grafema/core';
6
- import { Orchestrator } from '@grafema/core';
9
+ import { existsSync } from 'fs';
10
+ import { join } from 'path';
11
+ import { spawn } from 'child_process';
12
+ import { findOrchestratorBinary, getBinaryNotFoundMessage } from '@grafema/util';
7
13
  import {
8
14
  getOrCreateBackend,
9
15
  getProjectPath,
@@ -13,11 +19,103 @@ import {
13
19
  setAnalysisStatus,
14
20
  isAnalysisRunning,
15
21
  acquireAnalysisLock,
22
+ getKnowledgeBase,
16
23
  } from './state.js';
17
- import { loadConfig, loadCustomPlugins, createPlugins } from './config.js';
24
+ import { loadConfig } from './config.js';
18
25
  import { log } from './utils.js';
19
26
  import type { GraphBackend } from '@grafema/types';
20
27
 
28
+ /**
29
+ * Resolve the config file path for grafema-orchestrator.
30
+ *
31
+ * The orchestrator expects a YAML config with `root`, `include`, `exclude` fields.
32
+ * Looks for:
33
+ * 1. grafema.config.yaml in project root
34
+ * 2. .grafema/config.yaml as fallback
35
+ */
36
+ function findConfigPath(projectPath: string): string | null {
37
+ const candidates = [
38
+ join(projectPath, 'grafema.config.yaml'),
39
+ join(projectPath, '.grafema', 'config.yaml'),
40
+ ];
41
+
42
+ for (const candidate of candidates) {
43
+ if (existsSync(candidate)) return candidate;
44
+ }
45
+
46
+ return null;
47
+ }
48
+
49
+ /**
50
+ * Resolve the RFDB socket path for the current project.
51
+ *
52
+ * Uses the same logic as state.ts getOrCreateBackend():
53
+ * config.analysis.parallel.socketPath > default derived from dbPath
54
+ */
55
+ function resolveSocketPath(projectPath: string): string {
56
+ const config = loadConfig(projectPath);
57
+ const configSocket = (config as any).analysis?.parallel?.socketPath;
58
+ if (configSocket) return configSocket;
59
+
60
+ // Default: same as RFDBServerBackend derives from dbPath
61
+ return join(projectPath, '.grafema', 'rfdb.sock');
62
+ }
63
+
64
+ /**
65
+ * Spawn grafema-orchestrator and wait for it to complete.
66
+ *
67
+ * @returns Promise that resolves on success, rejects on failure
68
+ */
69
+ function runOrchestrator(
70
+ binaryPath: string,
71
+ args: string[],
72
+ ): Promise<void> {
73
+ return new Promise((resolve, reject) => {
74
+ log(`[Grafema MCP] Spawning: ${binaryPath} ${args.join(' ')}`);
75
+
76
+ const child = spawn(binaryPath, args, {
77
+ stdio: ['ignore', 'pipe', 'pipe'],
78
+ });
79
+
80
+ let stdout = '';
81
+ let stderr = '';
82
+
83
+ child.stdout.on('data', (data: Buffer) => {
84
+ const text = data.toString();
85
+ stdout += text;
86
+ // Forward orchestrator output to MCP log
87
+ for (const line of text.split('\n').filter(Boolean)) {
88
+ log(`[orchestrator] ${line}`);
89
+ }
90
+ });
91
+
92
+ child.stderr.on('data', (data: Buffer) => {
93
+ const text = data.toString();
94
+ stderr += text;
95
+ for (const line of text.split('\n').filter(Boolean)) {
96
+ log(`[orchestrator] ${line}`);
97
+ }
98
+ });
99
+
100
+ child.on('error', (err: Error) => {
101
+ reject(new Error(`Failed to spawn grafema-orchestrator: ${err.message}`));
102
+ });
103
+
104
+ child.on('close', (code: number | null) => {
105
+ if (code === 0) {
106
+ resolve();
107
+ } else {
108
+ reject(
109
+ new Error(
110
+ `grafema-orchestrator exited with code ${code}\n` +
111
+ (stderr || stdout || '(no output)')
112
+ )
113
+ );
114
+ }
115
+ });
116
+ });
117
+ }
118
+
21
119
  /**
22
120
  * Ensure project is analyzed, optionally filtering to a single service.
23
121
  *
@@ -26,8 +124,8 @@ import type { GraphBackend } from '@grafema/types';
26
124
  * - Concurrent calls wait for the current analysis to complete
27
125
  * - force=true while analysis is running returns an error immediately
28
126
  *
29
- * @param serviceName - Optional service to analyze (null = all)
30
- * @param force - If true, clear DB and re-analyze even if already analyzed.
127
+ * @param serviceName - Optional service to analyze (null = all) — currently unused by the orchestrator
128
+ * @param force - If true, add --force flag to re-analyze all files.
31
129
  * ERROR if another analysis is already running.
32
130
  * @throws Error if force=true and analysis is already running
33
131
  */
@@ -62,8 +160,6 @@ export async function ensureAnalyzed(
62
160
  }
63
161
 
64
162
  // Clear DB inside lock, BEFORE running analysis
65
- // This is critical for worker coordination: MCP server clears DB here,
66
- // worker does NOT call db.clear() (see analysis-worker.ts)
67
163
  if (force || !getIsAnalyzed()) {
68
164
  log('[Grafema MCP] Clearing database before analysis...');
69
165
  if (db.clear) {
@@ -76,45 +172,48 @@ export async function ensureAnalyzed(
76
172
  `[Grafema MCP] Analyzing project: ${projectPath}${serviceName ? ` (service: ${serviceName})` : ''}`
77
173
  );
78
174
 
79
- const config = loadConfig(projectPath);
80
- const { pluginMap: customPluginMap } = await loadCustomPlugins(projectPath);
81
-
82
- // Create plugins from config
83
- const plugins = createPlugins(config.plugins, customPluginMap);
84
-
85
- log(`[Grafema MCP] Total plugins: ${plugins.length}`);
86
-
87
- // Check for parallel analysis config
88
- const parallelConfig = (config as any).analysis?.parallel;
89
- log(`[Grafema MCP] Config analysis section: ${JSON.stringify((config as any).analysis)}`);
175
+ // Find the orchestrator binary
176
+ const binaryPath = findOrchestratorBinary();
177
+ if (!binaryPath) {
178
+ throw new Error(getBinaryNotFoundMessage('grafema-orchestrator'));
179
+ }
90
180
 
91
- if (parallelConfig?.enabled) {
92
- log(
93
- `[Grafema MCP] Parallel analysis enabled: maxWorkers=${parallelConfig.maxWorkers || 'auto'}, socket=${parallelConfig.socketPath || '/tmp/rfdb.sock'}`
181
+ // Find config file
182
+ const configPath = findConfigPath(projectPath);
183
+ if (!configPath) {
184
+ throw new Error(
185
+ `No config file found for grafema-orchestrator.\n` +
186
+ `Expected one of:\n` +
187
+ ` - ${join(projectPath, 'grafema.config.yaml')}\n` +
188
+ ` - ${join(projectPath, '.grafema', 'config.yaml')}\n`
94
189
  );
95
190
  }
96
191
 
192
+ // Resolve socket path
193
+ const socketPath = resolveSocketPath(projectPath);
194
+
97
195
  const analysisStatus = getAnalysisStatus();
98
196
  const startTime = Date.now();
99
197
 
100
- const orchestrator = new Orchestrator({
101
- graph: db,
102
- plugins: plugins as Plugin[],
103
- parallel: parallelConfig,
104
- serviceFilter: serviceName,
105
- onProgress: (progress: any) => {
106
- log(`[Grafema MCP] ${progress.phase}: ${progress.message}`);
107
-
108
- setAnalysisStatus({
109
- phase: progress.phase,
110
- message: progress.message,
111
- servicesDiscovered: progress.servicesDiscovered || analysisStatus.servicesDiscovered,
112
- servicesAnalyzed: progress.servicesAnalyzed || analysisStatus.servicesAnalyzed,
113
- });
114
- },
198
+ setAnalysisStatus({
199
+ phase: 'starting',
200
+ message: 'Spawning grafema-orchestrator...',
201
+ servicesDiscovered: analysisStatus.servicesDiscovered,
202
+ servicesAnalyzed: analysisStatus.servicesAnalyzed,
115
203
  });
116
204
 
117
- await orchestrator.run(projectPath);
205
+ // Build args
206
+ const args = ['analyze', '--config', configPath, '--socket', socketPath];
207
+ if (force) {
208
+ args.push('--force');
209
+ }
210
+
211
+ log(`[Grafema MCP] Binary: ${binaryPath}`);
212
+ log(`[Grafema MCP] Config: ${configPath}`);
213
+ log(`[Grafema MCP] Socket: ${socketPath}`);
214
+
215
+ // Run the orchestrator
216
+ await runOrchestrator(binaryPath, args);
118
217
 
119
218
  // Flush if available
120
219
  if ('flush' in db && typeof db.flush === 'function') {
@@ -123,8 +222,17 @@ export async function ensureAnalyzed(
123
222
 
124
223
  setIsAnalyzed(true);
125
224
 
225
+ // Bump KB resolver generation so cached resolutions are re-evaluated
226
+ const kb = getKnowledgeBase();
227
+ if (kb) {
228
+ kb.invalidateResolutionCache();
229
+ log('[Grafema MCP] KnowledgeBase resolution cache invalidated after analysis');
230
+ }
231
+
126
232
  const totalTime = ((Date.now() - startTime) / 1000).toFixed(2);
127
233
  setAnalysisStatus({
234
+ phase: 'complete',
235
+ message: `Analysis complete in ${totalTime}s`,
128
236
  timings: {
129
237
  ...analysisStatus.timings,
130
238
  total: parseFloat(totalTime),
@@ -141,47 +249,13 @@ export async function ensureAnalyzed(
141
249
  }
142
250
 
143
251
  /**
144
- * Discover services without running full analysis
252
+ * Discover services without running full analysis.
253
+ *
254
+ * Service discovery is now handled by grafema-orchestrator's file discovery.
255
+ * This returns an empty array — the orchestrator handles discovery internally.
145
256
  */
146
257
  export async function discoverServices(): Promise<unknown[]> {
147
- const db = await getOrCreateBackend();
148
258
  const projectPath = getProjectPath();
149
-
150
- log(`[Grafema MCP] Discovering services in: ${projectPath}`);
151
-
152
- const config = loadConfig(projectPath);
153
- const { pluginMap: customPluginMap } = await loadCustomPlugins(projectPath);
154
-
155
- const availablePlugins: Record<string, () => unknown> = {
156
- ...Object.fromEntries(
157
- Object.entries(customPluginMap).map(([name, PluginClass]) => [
158
- name,
159
- () => new PluginClass(),
160
- ])
161
- ),
162
- };
163
-
164
- const plugins: unknown[] = [];
165
- const discoveryPluginNames = config.plugins.discovery ?? [];
166
-
167
- for (const name of discoveryPluginNames) {
168
- const factory = availablePlugins[name];
169
- if (factory) {
170
- plugins.push(factory());
171
- log(`[Grafema MCP] Enabled discovery plugin: ${name}`);
172
- } else {
173
- log(`[Grafema MCP] Warning: Unknown discovery plugin ${name}`);
174
- }
175
- }
176
-
177
- const orchestrator = new Orchestrator({
178
- graph: db,
179
- plugins: plugins as Plugin[],
180
- });
181
-
182
- const manifest = await orchestrator.discover(projectPath);
183
-
184
- log(`[Grafema MCP] Discovery complete: found ${manifest.services?.length || 0} services`);
185
-
186
- return manifest.services || [];
259
+ log(`[Grafema MCP] Service discovery is handled by grafema-orchestrator. Project: ${projectPath}`);
260
+ return [];
187
261
  }