fraim 2.0.100
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/README.md +445 -0
- package/bin/fraim.js +23 -0
- package/dist/src/cli/api/get-provider-client.js +41 -0
- package/dist/src/cli/api/provider-client.js +107 -0
- package/dist/src/cli/commands/add-ide.js +430 -0
- package/dist/src/cli/commands/add-provider.js +233 -0
- package/dist/src/cli/commands/doctor.js +149 -0
- package/dist/src/cli/commands/init-project.js +301 -0
- package/dist/src/cli/commands/list-overridable.js +184 -0
- package/dist/src/cli/commands/list.js +57 -0
- package/dist/src/cli/commands/login.js +84 -0
- package/dist/src/cli/commands/mcp.js +15 -0
- package/dist/src/cli/commands/migrate-project-fraim.js +42 -0
- package/dist/src/cli/commands/override.js +177 -0
- package/dist/src/cli/commands/setup.js +651 -0
- package/dist/src/cli/commands/sync.js +162 -0
- package/dist/src/cli/commands/test-mcp.js +171 -0
- package/dist/src/cli/doctor/check-runner.js +199 -0
- package/dist/src/cli/doctor/checks/global-setup-checks.js +220 -0
- package/dist/src/cli/doctor/checks/ide-config-checks.js +250 -0
- package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +381 -0
- package/dist/src/cli/doctor/checks/project-setup-checks.js +282 -0
- package/dist/src/cli/doctor/checks/scripts-checks.js +157 -0
- package/dist/src/cli/doctor/checks/workflow-checks.js +251 -0
- package/dist/src/cli/doctor/reporters/console-reporter.js +96 -0
- package/dist/src/cli/doctor/reporters/json-reporter.js +11 -0
- package/dist/src/cli/doctor/types.js +6 -0
- package/dist/src/cli/fraim.js +100 -0
- package/dist/src/cli/internal/device-flow-service.js +83 -0
- package/dist/src/cli/mcp/ide-formats.js +243 -0
- package/dist/src/cli/mcp/mcp-server-builder.js +48 -0
- package/dist/src/cli/mcp/mcp-server-registry.js +160 -0
- package/dist/src/cli/mcp/types.js +3 -0
- package/dist/src/cli/providers/local-provider-registry.js +166 -0
- package/dist/src/cli/providers/provider-registry.js +230 -0
- package/dist/src/cli/setup/auto-mcp-setup.js +331 -0
- package/dist/src/cli/setup/codex-local-config.js +37 -0
- package/dist/src/cli/setup/first-run.js +242 -0
- package/dist/src/cli/setup/ide-detector.js +179 -0
- package/dist/src/cli/setup/mcp-config-generator.js +192 -0
- package/dist/src/cli/setup/provider-prompts.js +339 -0
- package/dist/src/cli/utils/agent-adapters.js +126 -0
- package/dist/src/cli/utils/digest-utils.js +47 -0
- package/dist/src/cli/utils/fraim-gitignore.js +40 -0
- package/dist/src/cli/utils/platform-detection.js +258 -0
- package/dist/src/cli/utils/project-bootstrap.js +93 -0
- package/dist/src/cli/utils/remote-sync.js +315 -0
- package/dist/src/cli/utils/script-sync-utils.js +221 -0
- package/dist/src/cli/utils/version-utils.js +32 -0
- package/dist/src/core/ai-mentor.js +230 -0
- package/dist/src/core/config-loader.js +114 -0
- package/dist/src/core/config-writer.js +75 -0
- package/dist/src/core/types.js +23 -0
- package/dist/src/core/utils/git-utils.js +95 -0
- package/dist/src/core/utils/include-resolver.js +92 -0
- package/dist/src/core/utils/inheritance-parser.js +288 -0
- package/dist/src/core/utils/job-parser.js +176 -0
- package/dist/src/core/utils/local-registry-resolver.js +616 -0
- package/dist/src/core/utils/object-utils.js +11 -0
- package/dist/src/core/utils/project-fraim-migration.js +103 -0
- package/dist/src/core/utils/project-fraim-paths.js +38 -0
- package/dist/src/core/utils/provider-utils.js +18 -0
- package/dist/src/core/utils/server-startup.js +34 -0
- package/dist/src/core/utils/stub-generator.js +147 -0
- package/dist/src/core/utils/workflow-parser.js +174 -0
- package/dist/src/local-mcp-server/learning-context-builder.js +229 -0
- package/dist/src/local-mcp-server/stdio-server.js +1698 -0
- package/dist/src/local-mcp-server/usage-collector.js +264 -0
- package/index.js +85 -0
- package/package.json +139 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MCP connectivity checks for FRAIM doctor command
|
|
4
|
+
* Tests FRAIM server connectivity and validates IDE MCP configurations
|
|
5
|
+
* Issue #144: Enhanced doctor command
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
41
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
42
|
+
};
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.getMCPConnectivityChecks = getMCPConnectivityChecks;
|
|
45
|
+
const fs_1 = __importDefault(require("fs"));
|
|
46
|
+
const path_1 = __importDefault(require("path"));
|
|
47
|
+
const os_1 = __importDefault(require("os"));
|
|
48
|
+
const axios_1 = __importDefault(require("axios"));
|
|
49
|
+
const toml = __importStar(require("toml"));
|
|
50
|
+
const ide_detector_1 = require("../../setup/ide-detector");
|
|
51
|
+
/**
|
|
52
|
+
* Test FRAIM connectivity by calling fraim_connect
|
|
53
|
+
*/
|
|
54
|
+
async function testFraimConnectivity() {
|
|
55
|
+
try {
|
|
56
|
+
// Skip network calls in test environment
|
|
57
|
+
if (process.env.NODE_ENV === 'test') {
|
|
58
|
+
return {
|
|
59
|
+
status: 'passed',
|
|
60
|
+
message: 'FRAIM connectivity check skipped (test mode)',
|
|
61
|
+
details: {
|
|
62
|
+
testMode: true,
|
|
63
|
+
skipped: true
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const globalConfigPath = path_1.default.join(os_1.default.homedir(), '.fraim', 'config.json');
|
|
68
|
+
let apiKey;
|
|
69
|
+
try {
|
|
70
|
+
const config = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
|
|
71
|
+
apiKey = config.apiKey || '';
|
|
72
|
+
if (!apiKey) {
|
|
73
|
+
return {
|
|
74
|
+
status: 'error',
|
|
75
|
+
message: 'FRAIM API key not found',
|
|
76
|
+
suggestion: 'Add apiKey to ~/.fraim/config.json'
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// Warn if using special bypass keys
|
|
80
|
+
if (apiKey === 'local-dev') {
|
|
81
|
+
return {
|
|
82
|
+
status: 'warning',
|
|
83
|
+
message: 'Using local-dev API key (bypasses validation)',
|
|
84
|
+
suggestion: 'This key is for local development only. Get a real API key from https://fraim.wellnessatwork.me',
|
|
85
|
+
details: {
|
|
86
|
+
apiKey: 'local-dev',
|
|
87
|
+
bypassMode: true
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
return {
|
|
94
|
+
status: 'error',
|
|
95
|
+
message: 'Failed to read FRAIM config',
|
|
96
|
+
suggestion: 'Check ~/.fraim/config.json is valid JSON'
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// Test connectivity by making a request to the FRAIM server
|
|
100
|
+
const startTime = Date.now();
|
|
101
|
+
const remoteUrl = process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
|
|
102
|
+
// Debug: Log what we're testing (helpful for troubleshooting)
|
|
103
|
+
if (process.env.DEBUG_DOCTOR) {
|
|
104
|
+
console.log('[DOCTOR DEBUG] Testing FRAIM connectivity:');
|
|
105
|
+
console.log(' URL:', remoteUrl);
|
|
106
|
+
console.log(' API Key:', apiKey.substring(0, 15) + '...');
|
|
107
|
+
console.log(' NODE_ENV:', process.env.NODE_ENV || 'not set');
|
|
108
|
+
}
|
|
109
|
+
// Warn if testing against localhost (might be in test mode)
|
|
110
|
+
if (remoteUrl.includes('localhost') || remoteUrl.includes('127.0.0.1')) {
|
|
111
|
+
return {
|
|
112
|
+
status: 'warning',
|
|
113
|
+
message: 'Testing against local server (may bypass validation)',
|
|
114
|
+
suggestion: 'Unset FRAIM_REMOTE_URL to test against production server',
|
|
115
|
+
details: {
|
|
116
|
+
url: remoteUrl,
|
|
117
|
+
localServer: true
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// First, test with the correct API key
|
|
122
|
+
try {
|
|
123
|
+
const response = await axios_1.default.post(`${remoteUrl}/mcp`, {
|
|
124
|
+
jsonrpc: '2.0',
|
|
125
|
+
id: 1,
|
|
126
|
+
method: 'tools/list',
|
|
127
|
+
params: {}
|
|
128
|
+
}, {
|
|
129
|
+
headers: {
|
|
130
|
+
'x-api-key': apiKey,
|
|
131
|
+
'Content-Type': 'application/json'
|
|
132
|
+
},
|
|
133
|
+
timeout: 5000
|
|
134
|
+
});
|
|
135
|
+
const latency = Date.now() - startTime;
|
|
136
|
+
if (response.status === 200 && response.data?.result) {
|
|
137
|
+
// Now verify that an invalid key would actually fail
|
|
138
|
+
// This ensures we're not in test mode where all keys are accepted
|
|
139
|
+
try {
|
|
140
|
+
const invalidResponse = await axios_1.default.post(`${remoteUrl}/mcp`, {
|
|
141
|
+
jsonrpc: '2.0',
|
|
142
|
+
id: 1,
|
|
143
|
+
method: 'tools/list',
|
|
144
|
+
params: {}
|
|
145
|
+
}, {
|
|
146
|
+
headers: {
|
|
147
|
+
'x-api-key': 'invalid-key-for-testing-' + Date.now(),
|
|
148
|
+
'Content-Type': 'application/json'
|
|
149
|
+
},
|
|
150
|
+
timeout: 5000
|
|
151
|
+
});
|
|
152
|
+
// If invalid key succeeds, server is in test mode
|
|
153
|
+
if (invalidResponse.status === 200 && invalidResponse.data?.result) {
|
|
154
|
+
return {
|
|
155
|
+
status: 'warning',
|
|
156
|
+
message: `FRAIM server connected but not validating API keys (test mode?)`,
|
|
157
|
+
suggestion: 'Server may be in test mode - API key validation is bypassed',
|
|
158
|
+
details: {
|
|
159
|
+
latency,
|
|
160
|
+
url: remoteUrl,
|
|
161
|
+
authenticated: false,
|
|
162
|
+
testMode: true
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch (invalidError) {
|
|
168
|
+
// Good! Invalid key was rejected (401/403)
|
|
169
|
+
if (invalidError.response?.status === 401 || invalidError.response?.status === 403) {
|
|
170
|
+
return {
|
|
171
|
+
status: 'passed',
|
|
172
|
+
message: `FRAIM server connected and validating API keys (${latency}ms)`,
|
|
173
|
+
details: {
|
|
174
|
+
latency,
|
|
175
|
+
url: remoteUrl,
|
|
176
|
+
authenticated: true,
|
|
177
|
+
validationWorking: true
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// If we get here, invalid key didn't fail with 401/403
|
|
183
|
+
return {
|
|
184
|
+
status: 'passed',
|
|
185
|
+
message: `FRAIM server connected (${latency}ms)`,
|
|
186
|
+
details: {
|
|
187
|
+
latency,
|
|
188
|
+
url: remoteUrl,
|
|
189
|
+
authenticated: true
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
status: 'error',
|
|
195
|
+
message: `FRAIM server returned unexpected response`,
|
|
196
|
+
suggestion: 'Check FRAIM server status'
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
const latency = Date.now() - startTime;
|
|
201
|
+
if (error.response?.status === 401 || error.response?.status === 403) {
|
|
202
|
+
return {
|
|
203
|
+
status: 'error',
|
|
204
|
+
message: 'FRAIM API key is invalid',
|
|
205
|
+
suggestion: 'Get a valid API key from https://fraim.wellnessatwork.me and update ~/.fraim/config.json',
|
|
206
|
+
details: {
|
|
207
|
+
statusCode: error.response?.status,
|
|
208
|
+
responseData: error.response?.data
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (error.code === 'ECONNREFUSED') {
|
|
213
|
+
return {
|
|
214
|
+
status: 'error',
|
|
215
|
+
message: 'Cannot connect to FRAIM server',
|
|
216
|
+
suggestion: `Check network connection and server status: ${remoteUrl}`
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
if (error.code === 'ETIMEDOUT' || error.message.includes('timeout')) {
|
|
220
|
+
return {
|
|
221
|
+
status: 'error',
|
|
222
|
+
message: 'FRAIM server timeout',
|
|
223
|
+
suggestion: 'Check network connection or try again later'
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
status: 'error',
|
|
228
|
+
message: `FRAIM server error: ${error.message}`,
|
|
229
|
+
suggestion: 'Check network connection and FRAIM server status',
|
|
230
|
+
details: {
|
|
231
|
+
error: error.message,
|
|
232
|
+
code: error.code,
|
|
233
|
+
statusCode: error.response?.status,
|
|
234
|
+
latency
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
return {
|
|
241
|
+
status: 'error',
|
|
242
|
+
message: `Failed to test FRAIM connectivity: ${error.message}`,
|
|
243
|
+
suggestion: 'Check FRAIM configuration'
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Validate IDE MCP configuration format
|
|
249
|
+
*/
|
|
250
|
+
async function validateIDEMCPConfig(ide) {
|
|
251
|
+
const configPath = (0, ide_detector_1.expandPath)(ide.configPath);
|
|
252
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
253
|
+
return {
|
|
254
|
+
status: 'warning',
|
|
255
|
+
message: `${ide.name} MCP config not found`,
|
|
256
|
+
suggestion: `Run: fraim add-ide --ide "${ide.name.toLowerCase()}"`,
|
|
257
|
+
details: { configPath }
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
let servers = {};
|
|
262
|
+
// Parse based on format
|
|
263
|
+
if (ide.configFormat === 'toml') {
|
|
264
|
+
const content = fs_1.default.readFileSync(configPath, 'utf8');
|
|
265
|
+
const config = toml.parse(content);
|
|
266
|
+
servers = config.mcp_servers || {};
|
|
267
|
+
// Debug: Log what we found
|
|
268
|
+
if (process.env.DEBUG_DOCTOR) {
|
|
269
|
+
console.log(`[DOCTOR DEBUG] ${ide.name} TOML config:`, {
|
|
270
|
+
configPath,
|
|
271
|
+
hasConfig: !!config,
|
|
272
|
+
hasMcpServers: !!config.mcp_servers,
|
|
273
|
+
serverKeys: Object.keys(servers),
|
|
274
|
+
hasFraim: !!servers['fraim']
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
// JSON format
|
|
280
|
+
const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
281
|
+
// Get servers - prefer mcpServers if it has content, otherwise use servers
|
|
282
|
+
servers = config.mcpServers || {};
|
|
283
|
+
if (Object.keys(servers).length === 0 && config.servers) {
|
|
284
|
+
servers = config.servers;
|
|
285
|
+
}
|
|
286
|
+
// Debug: Log what we found
|
|
287
|
+
if (process.env.DEBUG_DOCTOR) {
|
|
288
|
+
console.log(`[DOCTOR DEBUG] ${ide.name} config:`, {
|
|
289
|
+
configPath,
|
|
290
|
+
hasConfig: !!config,
|
|
291
|
+
hasMcpServers: !!config.mcpServers,
|
|
292
|
+
hasServers: !!config.servers,
|
|
293
|
+
mcpServersKeys: config.mcpServers ? Object.keys(config.mcpServers) : [],
|
|
294
|
+
serversKeys: config.servers ? Object.keys(config.servers) : [],
|
|
295
|
+
finalServerKeys: Object.keys(servers),
|
|
296
|
+
hasFraim: !!servers['fraim']
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// Check if FRAIM server is configured
|
|
301
|
+
const fraimServer = servers['fraim'];
|
|
302
|
+
if (!fraimServer) {
|
|
303
|
+
return {
|
|
304
|
+
status: 'warning',
|
|
305
|
+
message: `${ide.name} missing FRAIM MCP server`,
|
|
306
|
+
suggestion: `Run: fraim add-ide --ide "${ide.name.toLowerCase()}"`,
|
|
307
|
+
details: { configPath, serverKeys: Object.keys(servers) }
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
// Validate FRAIM server config format
|
|
311
|
+
if (!fraimServer.command && !fraimServer.url) {
|
|
312
|
+
return {
|
|
313
|
+
status: 'error',
|
|
314
|
+
message: `${ide.name} FRAIM server config invalid`,
|
|
315
|
+
suggestion: `FRAIM server must have 'command' or 'url' field`,
|
|
316
|
+
details: { configPath }
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
// Check for API key in env (for stdio servers)
|
|
320
|
+
if (fraimServer.command && !fraimServer.env?.FRAIM_API_KEY) {
|
|
321
|
+
return {
|
|
322
|
+
status: 'error',
|
|
323
|
+
message: `${ide.name} FRAIM server missing API key`,
|
|
324
|
+
suggestion: `Add FRAIM_API_KEY to env section in ${configPath}`,
|
|
325
|
+
details: { configPath }
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
// Check for auth header (for HTTP servers)
|
|
329
|
+
if (fraimServer.url) {
|
|
330
|
+
const hasAuth = fraimServer.headers?.Authorization || fraimServer.http_headers?.Authorization;
|
|
331
|
+
if (!hasAuth) {
|
|
332
|
+
return {
|
|
333
|
+
status: 'error',
|
|
334
|
+
message: `${ide.name} FRAIM server missing authorization`,
|
|
335
|
+
suggestion: `Add Authorization header in ${configPath}`,
|
|
336
|
+
details: { configPath }
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
status: 'passed',
|
|
342
|
+
message: `${ide.name} MCP config valid`,
|
|
343
|
+
details: {
|
|
344
|
+
configPath,
|
|
345
|
+
serverCount: Object.keys(servers).length
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
return {
|
|
351
|
+
status: 'error',
|
|
352
|
+
message: `${ide.name} MCP config parse error`,
|
|
353
|
+
suggestion: `Fix ${ide.configFormat.toUpperCase()} syntax in ${configPath}`,
|
|
354
|
+
details: { configPath, error: error.message }
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Get all MCP connectivity checks
|
|
360
|
+
*/
|
|
361
|
+
function getMCPConnectivityChecks() {
|
|
362
|
+
const checks = [];
|
|
363
|
+
// Check 1: Test FRAIM server connectivity with actual API key
|
|
364
|
+
checks.push({
|
|
365
|
+
name: 'FRAIM server connectivity',
|
|
366
|
+
category: 'mcpConnectivity',
|
|
367
|
+
critical: true,
|
|
368
|
+
run: testFraimConnectivity
|
|
369
|
+
});
|
|
370
|
+
// Check 2: Validate each installed IDE's MCP config
|
|
371
|
+
const installedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
|
|
372
|
+
for (const ide of installedIDEs) {
|
|
373
|
+
checks.push({
|
|
374
|
+
name: `${ide.name} MCP configuration`,
|
|
375
|
+
category: 'mcpConnectivity',
|
|
376
|
+
critical: false,
|
|
377
|
+
run: async () => validateIDEMCPConfig(ide)
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
return checks;
|
|
381
|
+
}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Project setup checks for FRAIM doctor command
|
|
4
|
+
* Validates project configuration and initialization
|
|
5
|
+
* Issue #144: Enhanced doctor command
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.getProjectSetupChecks = getProjectSetupChecks;
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const child_process_1 = require("child_process");
|
|
14
|
+
const project_fraim_paths_1 = require("../../../core/utils/project-fraim-paths");
|
|
15
|
+
/**
|
|
16
|
+
* Check if project is initialized
|
|
17
|
+
*/
|
|
18
|
+
function checkProjectInitialized() {
|
|
19
|
+
return {
|
|
20
|
+
name: 'Project initialized',
|
|
21
|
+
category: 'projectSetup',
|
|
22
|
+
critical: true,
|
|
23
|
+
run: async () => {
|
|
24
|
+
const fraimDir = (0, project_fraim_paths_1.getWorkspaceFraimDir)(process.cwd());
|
|
25
|
+
if ((0, project_fraim_paths_1.workspaceFraimExists)(process.cwd())) {
|
|
26
|
+
return {
|
|
27
|
+
status: 'passed',
|
|
28
|
+
message: 'Project initialized',
|
|
29
|
+
details: { path: fraimDir }
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
status: 'error',
|
|
34
|
+
message: 'Project not initialized',
|
|
35
|
+
suggestion: 'Initialize project with fraim init-project',
|
|
36
|
+
command: 'fraim init-project'
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Check if project config is valid
|
|
43
|
+
*/
|
|
44
|
+
function checkProjectConfigValid() {
|
|
45
|
+
return {
|
|
46
|
+
name: 'Project config valid',
|
|
47
|
+
category: 'projectSetup',
|
|
48
|
+
critical: true,
|
|
49
|
+
run: async () => {
|
|
50
|
+
const configPath = (0, project_fraim_paths_1.getWorkspaceConfigPath)(process.cwd());
|
|
51
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
52
|
+
return {
|
|
53
|
+
status: 'error',
|
|
54
|
+
message: 'Project config missing',
|
|
55
|
+
suggestion: 'Initialize project with fraim init-project',
|
|
56
|
+
command: 'fraim init-project'
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
61
|
+
if (!config.project || !config.project.name) {
|
|
62
|
+
return {
|
|
63
|
+
status: 'warning',
|
|
64
|
+
message: 'Project config incomplete',
|
|
65
|
+
suggestion: `Add project name to ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json')}`
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
status: 'passed',
|
|
70
|
+
message: 'Project config valid',
|
|
71
|
+
details: { projectName: config.project.name }
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
return {
|
|
76
|
+
status: 'error',
|
|
77
|
+
message: 'Project config corrupted',
|
|
78
|
+
suggestion: `Fix JSON syntax in ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json')}`,
|
|
79
|
+
details: { error: error.message }
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Check if git remote is detected
|
|
87
|
+
*/
|
|
88
|
+
function checkGitRemoteDetected() {
|
|
89
|
+
return {
|
|
90
|
+
name: 'Git remote detected',
|
|
91
|
+
category: 'projectSetup',
|
|
92
|
+
critical: false,
|
|
93
|
+
run: async () => {
|
|
94
|
+
try {
|
|
95
|
+
const remote = (0, child_process_1.execSync)('git remote get-url origin', {
|
|
96
|
+
encoding: 'utf8',
|
|
97
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
98
|
+
}).trim();
|
|
99
|
+
if (remote) {
|
|
100
|
+
return {
|
|
101
|
+
status: 'passed',
|
|
102
|
+
message: `Git remote: ${remote}`,
|
|
103
|
+
details: { remote }
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
status: 'warning',
|
|
108
|
+
message: 'No git remote configured',
|
|
109
|
+
suggestion: 'Add git remote with: git remote add origin <url>'
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
return {
|
|
114
|
+
status: 'warning',
|
|
115
|
+
message: 'Not a git repository',
|
|
116
|
+
suggestion: 'Initialize git with: git init',
|
|
117
|
+
details: { error: error.message }
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Check if repository provider matches config
|
|
125
|
+
*/
|
|
126
|
+
function checkProviderMatches() {
|
|
127
|
+
return {
|
|
128
|
+
name: 'Repository provider matches',
|
|
129
|
+
category: 'projectSetup',
|
|
130
|
+
critical: false,
|
|
131
|
+
run: async () => {
|
|
132
|
+
const configPath = (0, project_fraim_paths_1.getWorkspaceConfigPath)(process.cwd());
|
|
133
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
134
|
+
return {
|
|
135
|
+
status: 'error',
|
|
136
|
+
message: 'Cannot check provider - config missing'
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
141
|
+
if (!config.repository?.provider) {
|
|
142
|
+
return {
|
|
143
|
+
status: 'passed',
|
|
144
|
+
message: 'No repository provider configured (conversational mode)',
|
|
145
|
+
details: { mode: config.mode || 'conversational' }
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// Try to get git remote
|
|
149
|
+
try {
|
|
150
|
+
const remote = (0, child_process_1.execSync)('git remote get-url origin', {
|
|
151
|
+
encoding: 'utf8',
|
|
152
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
153
|
+
}).trim();
|
|
154
|
+
const provider = config.repository.provider;
|
|
155
|
+
const remoteMatches = remote.includes(provider);
|
|
156
|
+
if (remoteMatches) {
|
|
157
|
+
return {
|
|
158
|
+
status: 'passed',
|
|
159
|
+
message: `Repository provider: ${provider}`,
|
|
160
|
+
details: { provider, remote }
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
status: 'warning',
|
|
165
|
+
message: `Provider mismatch: config says ${provider} but remote is ${remote}`,
|
|
166
|
+
suggestion: `Update repository.provider in ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json')}`
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return {
|
|
171
|
+
status: 'warning',
|
|
172
|
+
message: 'Cannot verify provider - no git remote',
|
|
173
|
+
details: { configuredProvider: config.repository.provider }
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
return {
|
|
179
|
+
status: 'error',
|
|
180
|
+
message: 'Failed to check provider',
|
|
181
|
+
details: { error: error.message }
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Check if config matches mode
|
|
189
|
+
*/
|
|
190
|
+
function checkConfigMatchesMode() {
|
|
191
|
+
return {
|
|
192
|
+
name: 'Config matches mode',
|
|
193
|
+
category: 'projectSetup',
|
|
194
|
+
critical: false,
|
|
195
|
+
run: async () => {
|
|
196
|
+
const configPath = (0, project_fraim_paths_1.getWorkspaceConfigPath)(process.cwd());
|
|
197
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
198
|
+
return {
|
|
199
|
+
status: 'error',
|
|
200
|
+
message: 'Cannot check mode - config missing'
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
205
|
+
const mode = config.mode || 'conversational';
|
|
206
|
+
if (mode === 'conversational') {
|
|
207
|
+
// No repository or issue tracking required
|
|
208
|
+
return {
|
|
209
|
+
status: 'passed',
|
|
210
|
+
message: 'Conversational mode (no platform required)',
|
|
211
|
+
details: { mode }
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
if (mode === 'integrated') {
|
|
215
|
+
// Repository required, issue tracking should match
|
|
216
|
+
if (!config.repository?.provider) {
|
|
217
|
+
return {
|
|
218
|
+
status: 'error',
|
|
219
|
+
message: 'Integrated mode requires repository configuration',
|
|
220
|
+
suggestion: `Add repository config to ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json')}`
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
status: 'passed',
|
|
225
|
+
message: 'Integrated mode configured',
|
|
226
|
+
details: { mode, provider: config.repository.provider }
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (mode === 'split') {
|
|
230
|
+
// Both repository and issue tracking required
|
|
231
|
+
if (!config.repository?.provider) {
|
|
232
|
+
return {
|
|
233
|
+
status: 'error',
|
|
234
|
+
message: 'Split mode requires repository configuration',
|
|
235
|
+
suggestion: `Add repository config to ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json')}`
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
if (!config.issueTracking?.provider) {
|
|
239
|
+
return {
|
|
240
|
+
status: 'error',
|
|
241
|
+
message: 'Split mode requires issue tracking configuration',
|
|
242
|
+
suggestion: `Add issueTracking config to ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json')}`
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
status: 'passed',
|
|
247
|
+
message: 'Split mode configured',
|
|
248
|
+
details: {
|
|
249
|
+
mode,
|
|
250
|
+
repository: config.repository.provider,
|
|
251
|
+
issueTracking: config.issueTracking.provider
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
status: 'error',
|
|
257
|
+
message: `Unknown mode: ${mode}`,
|
|
258
|
+
suggestion: 'Mode must be conversational, integrated, or split'
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
return {
|
|
263
|
+
status: 'error',
|
|
264
|
+
message: 'Failed to check mode configuration',
|
|
265
|
+
details: { error: error.message }
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Get all project setup checks
|
|
273
|
+
*/
|
|
274
|
+
function getProjectSetupChecks() {
|
|
275
|
+
return [
|
|
276
|
+
checkProjectInitialized(),
|
|
277
|
+
checkProjectConfigValid(),
|
|
278
|
+
checkGitRemoteDetected(),
|
|
279
|
+
checkProviderMatches(),
|
|
280
|
+
checkConfigMatchesMode()
|
|
281
|
+
];
|
|
282
|
+
}
|