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.
Files changed (70) hide show
  1. package/README.md +445 -0
  2. package/bin/fraim.js +23 -0
  3. package/dist/src/cli/api/get-provider-client.js +41 -0
  4. package/dist/src/cli/api/provider-client.js +107 -0
  5. package/dist/src/cli/commands/add-ide.js +430 -0
  6. package/dist/src/cli/commands/add-provider.js +233 -0
  7. package/dist/src/cli/commands/doctor.js +149 -0
  8. package/dist/src/cli/commands/init-project.js +301 -0
  9. package/dist/src/cli/commands/list-overridable.js +184 -0
  10. package/dist/src/cli/commands/list.js +57 -0
  11. package/dist/src/cli/commands/login.js +84 -0
  12. package/dist/src/cli/commands/mcp.js +15 -0
  13. package/dist/src/cli/commands/migrate-project-fraim.js +42 -0
  14. package/dist/src/cli/commands/override.js +177 -0
  15. package/dist/src/cli/commands/setup.js +651 -0
  16. package/dist/src/cli/commands/sync.js +162 -0
  17. package/dist/src/cli/commands/test-mcp.js +171 -0
  18. package/dist/src/cli/doctor/check-runner.js +199 -0
  19. package/dist/src/cli/doctor/checks/global-setup-checks.js +220 -0
  20. package/dist/src/cli/doctor/checks/ide-config-checks.js +250 -0
  21. package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +381 -0
  22. package/dist/src/cli/doctor/checks/project-setup-checks.js +282 -0
  23. package/dist/src/cli/doctor/checks/scripts-checks.js +157 -0
  24. package/dist/src/cli/doctor/checks/workflow-checks.js +251 -0
  25. package/dist/src/cli/doctor/reporters/console-reporter.js +96 -0
  26. package/dist/src/cli/doctor/reporters/json-reporter.js +11 -0
  27. package/dist/src/cli/doctor/types.js +6 -0
  28. package/dist/src/cli/fraim.js +100 -0
  29. package/dist/src/cli/internal/device-flow-service.js +83 -0
  30. package/dist/src/cli/mcp/ide-formats.js +243 -0
  31. package/dist/src/cli/mcp/mcp-server-builder.js +48 -0
  32. package/dist/src/cli/mcp/mcp-server-registry.js +160 -0
  33. package/dist/src/cli/mcp/types.js +3 -0
  34. package/dist/src/cli/providers/local-provider-registry.js +166 -0
  35. package/dist/src/cli/providers/provider-registry.js +230 -0
  36. package/dist/src/cli/setup/auto-mcp-setup.js +331 -0
  37. package/dist/src/cli/setup/codex-local-config.js +37 -0
  38. package/dist/src/cli/setup/first-run.js +242 -0
  39. package/dist/src/cli/setup/ide-detector.js +179 -0
  40. package/dist/src/cli/setup/mcp-config-generator.js +192 -0
  41. package/dist/src/cli/setup/provider-prompts.js +339 -0
  42. package/dist/src/cli/utils/agent-adapters.js +126 -0
  43. package/dist/src/cli/utils/digest-utils.js +47 -0
  44. package/dist/src/cli/utils/fraim-gitignore.js +40 -0
  45. package/dist/src/cli/utils/platform-detection.js +258 -0
  46. package/dist/src/cli/utils/project-bootstrap.js +93 -0
  47. package/dist/src/cli/utils/remote-sync.js +315 -0
  48. package/dist/src/cli/utils/script-sync-utils.js +221 -0
  49. package/dist/src/cli/utils/version-utils.js +32 -0
  50. package/dist/src/core/ai-mentor.js +230 -0
  51. package/dist/src/core/config-loader.js +114 -0
  52. package/dist/src/core/config-writer.js +75 -0
  53. package/dist/src/core/types.js +23 -0
  54. package/dist/src/core/utils/git-utils.js +95 -0
  55. package/dist/src/core/utils/include-resolver.js +92 -0
  56. package/dist/src/core/utils/inheritance-parser.js +288 -0
  57. package/dist/src/core/utils/job-parser.js +176 -0
  58. package/dist/src/core/utils/local-registry-resolver.js +616 -0
  59. package/dist/src/core/utils/object-utils.js +11 -0
  60. package/dist/src/core/utils/project-fraim-migration.js +103 -0
  61. package/dist/src/core/utils/project-fraim-paths.js +38 -0
  62. package/dist/src/core/utils/provider-utils.js +18 -0
  63. package/dist/src/core/utils/server-startup.js +34 -0
  64. package/dist/src/core/utils/stub-generator.js +147 -0
  65. package/dist/src/core/utils/workflow-parser.js +174 -0
  66. package/dist/src/local-mcp-server/learning-context-builder.js +229 -0
  67. package/dist/src/local-mcp-server/stdio-server.js +1698 -0
  68. package/dist/src/local-mcp-server/usage-collector.js +264 -0
  69. package/index.js +85 -0
  70. 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
+ }