@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.
- package/dist/analysis-worker.d.ts +4 -3
- package/dist/analysis-worker.d.ts.map +1 -1
- package/dist/analysis-worker.js +8 -203
- package/dist/analysis-worker.js.map +1 -1
- package/dist/analysis.d.ts +10 -3
- package/dist/analysis.d.ts.map +1 -1
- package/dist/analysis.js +130 -62
- package/dist/analysis.js.map +1 -1
- package/dist/config.d.ts +5 -11
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -128
- package/dist/config.js.map +1 -1
- package/dist/definitions/analysis-tools.d.ts +6 -0
- package/dist/definitions/analysis-tools.d.ts.map +1 -0
- package/dist/definitions/analysis-tools.js +125 -0
- package/dist/definitions/analysis-tools.js.map +1 -0
- package/dist/definitions/context-tools.d.ts +6 -0
- package/dist/definitions/context-tools.d.ts.map +1 -0
- package/dist/definitions/context-tools.js +144 -0
- package/dist/definitions/context-tools.js.map +1 -0
- package/dist/definitions/graph-tools.d.ts +7 -0
- package/dist/definitions/graph-tools.d.ts.map +1 -0
- package/dist/definitions/graph-tools.js +124 -0
- package/dist/definitions/graph-tools.js.map +1 -0
- package/dist/definitions/graphql-tools.d.ts +6 -0
- package/dist/definitions/graphql-tools.d.ts.map +1 -0
- package/dist/definitions/graphql-tools.js +62 -0
- package/dist/definitions/graphql-tools.js.map +1 -0
- package/dist/definitions/guarantee-tools.d.ts +6 -0
- package/dist/definitions/guarantee-tools.d.ts.map +1 -0
- package/dist/definitions/guarantee-tools.js +136 -0
- package/dist/definitions/guarantee-tools.js.map +1 -0
- package/dist/definitions/index.d.ts +7 -0
- package/dist/definitions/index.d.ts.map +1 -0
- package/dist/definitions/index.js +24 -0
- package/dist/definitions/index.js.map +1 -0
- package/dist/definitions/knowledge-tools.d.ts +10 -0
- package/dist/definitions/knowledge-tools.d.ts.map +1 -0
- package/dist/definitions/knowledge-tools.js +300 -0
- package/dist/definitions/knowledge-tools.js.map +1 -0
- package/dist/definitions/notation-tools.d.ts +9 -0
- package/dist/definitions/notation-tools.d.ts.map +1 -0
- package/dist/definitions/notation-tools.js +62 -0
- package/dist/definitions/notation-tools.js.map +1 -0
- package/dist/definitions/project-tools.d.ts +6 -0
- package/dist/definitions/project-tools.d.ts.map +1 -0
- package/dist/definitions/project-tools.js +181 -0
- package/dist/definitions/project-tools.js.map +1 -0
- package/dist/definitions/query-tools.d.ts +6 -0
- package/dist/definitions/query-tools.d.ts.map +1 -0
- package/dist/definitions/query-tools.js +245 -0
- package/dist/definitions/query-tools.js.map +1 -0
- package/dist/definitions/types.d.ts +21 -0
- package/dist/definitions/types.d.ts.map +1 -0
- package/dist/definitions/types.js +5 -0
- package/dist/definitions/types.js.map +1 -0
- package/dist/dev-proxy.d.ts +29 -0
- package/dist/dev-proxy.d.ts.map +1 -0
- package/dist/dev-proxy.js +267 -0
- package/dist/dev-proxy.js.map +1 -0
- package/dist/handlers/analysis-handlers.d.ts.map +1 -1
- package/dist/handlers/analysis-handlers.js +34 -4
- package/dist/handlers/analysis-handlers.js.map +1 -1
- package/dist/handlers/context-handlers.d.ts +5 -6
- package/dist/handlers/context-handlers.d.ts.map +1 -1
- package/dist/handlers/context-handlers.js +19 -16
- package/dist/handlers/context-handlers.js.map +1 -1
- package/dist/handlers/coverage-handlers.js +1 -1
- package/dist/handlers/dataflow-handlers.d.ts +2 -0
- package/dist/handlers/dataflow-handlers.d.ts.map +1 -1
- package/dist/handlers/dataflow-handlers.js +68 -46
- package/dist/handlers/dataflow-handlers.js.map +1 -1
- package/dist/handlers/documentation-handlers.d.ts.map +1 -1
- package/dist/handlers/documentation-handlers.js +56 -2
- package/dist/handlers/documentation-handlers.js.map +1 -1
- package/dist/handlers/graph-handlers.d.ts +23 -0
- package/dist/handlers/graph-handlers.d.ts.map +1 -0
- package/dist/handlers/graph-handlers.js +155 -0
- package/dist/handlers/graph-handlers.js.map +1 -0
- package/dist/handlers/graphql-handlers.d.ts +9 -0
- package/dist/handlers/graphql-handlers.d.ts.map +1 -0
- package/dist/handlers/graphql-handlers.js +57 -0
- package/dist/handlers/graphql-handlers.js.map +1 -0
- package/dist/handlers/guarantee-handlers.js +1 -1
- package/dist/handlers/guard-handlers.d.ts.map +1 -1
- package/dist/handlers/guard-handlers.js +6 -3
- package/dist/handlers/guard-handlers.js.map +1 -1
- package/dist/handlers/index.d.ts +4 -0
- package/dist/handlers/index.d.ts.map +1 -1
- package/dist/handlers/index.js +6 -0
- package/dist/handlers/index.js.map +1 -1
- package/dist/handlers/issue-handlers.d.ts.map +1 -1
- package/dist/handlers/issue-handlers.js +10 -15
- package/dist/handlers/issue-handlers.js.map +1 -1
- package/dist/handlers/knowledge-handlers.d.ts +25 -0
- package/dist/handlers/knowledge-handlers.d.ts.map +1 -0
- package/dist/handlers/knowledge-handlers.js +208 -0
- package/dist/handlers/knowledge-handlers.js.map +1 -0
- package/dist/handlers/notation-handlers.d.ts +6 -0
- package/dist/handlers/notation-handlers.d.ts.map +1 -0
- package/dist/handlers/notation-handlers.js +53 -0
- package/dist/handlers/notation-handlers.js.map +1 -0
- package/dist/handlers/project-handlers.js +1 -1
- package/dist/handlers/query-handlers.d.ts.map +1 -1
- package/dist/handlers/query-handlers.js +166 -20
- package/dist/handlers/query-handlers.js.map +1 -1
- package/dist/prompts.js +1 -1
- package/dist/server.d.ts +19 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +93 -3
- package/dist/server.js.map +1 -1
- package/dist/state.d.ts +10 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +61 -8
- package/dist/state.js.map +1 -1
- package/dist/types.d.ts +75 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +4 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +18 -1
- package/dist/utils.js.map +1 -1
- package/package.json +4 -3
- package/src/analysis-worker.ts +9 -301
- package/src/analysis.ts +151 -77
- package/src/config.ts +6 -193
- package/src/definitions/analysis-tools.ts +127 -0
- package/src/definitions/context-tools.ts +147 -0
- package/src/definitions/graph-tools.ts +126 -0
- package/src/definitions/graphql-tools.ts +64 -0
- package/src/definitions/guarantee-tools.ts +138 -0
- package/src/definitions/index.ts +28 -0
- package/src/definitions/knowledge-tools.ts +302 -0
- package/src/definitions/notation-tools.ts +64 -0
- package/src/definitions/project-tools.ts +183 -0
- package/src/definitions/query-tools.ts +247 -0
- package/src/definitions/types.ts +22 -0
- package/src/dev-proxy.ts +336 -0
- package/src/handlers/analysis-handlers.ts +35 -4
- package/src/handlers/context-handlers.ts +19 -15
- package/src/handlers/coverage-handlers.ts +1 -1
- package/src/handlers/dataflow-handlers.ts +74 -56
- package/src/handlers/documentation-handlers.ts +56 -2
- package/src/handlers/graph-handlers.ts +212 -0
- package/src/handlers/graphql-handlers.ts +70 -0
- package/src/handlers/guarantee-handlers.ts +1 -1
- package/src/handlers/guard-handlers.ts +7 -3
- package/src/handlers/index.ts +6 -0
- package/src/handlers/issue-handlers.ts +10 -15
- package/src/handlers/knowledge-handlers.ts +242 -0
- package/src/handlers/notation-handlers.ts +71 -0
- package/src/handlers/project-handlers.ts +1 -1
- package/src/handlers/query-handlers.ts +186 -22
- package/src/prompts.ts +1 -1
- package/src/server.ts +126 -2
- package/src/state.ts +68 -8
- package/src/types.ts +98 -3
- package/src/utils.ts +22 -1
- package/src/definitions.ts +0 -665
package/src/analysis-worker.ts
CHANGED
|
@@ -1,306 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Analysis Worker
|
|
2
|
+
* Analysis Worker — DEPRECATED
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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
|
-
*
|
|
7
|
+
* Use analysis.ts ensureAnalyzed() which spawns grafema-orchestrator.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
6
|
-
import {
|
|
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
|
|
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,
|
|
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
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|