fraim-framework 2.0.46 → 2.0.48

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 (33) hide show
  1. package/dist/registry/providers/ado.json +19 -0
  2. package/dist/registry/providers/github.json +19 -0
  3. package/dist/src/ai-manager/ai-manager.js +8 -1
  4. package/dist/src/cli/commands/init-project.js +5 -4
  5. package/dist/src/cli/commands/init.js +8 -7
  6. package/dist/src/cli/commands/sync.js +54 -31
  7. package/dist/src/cli/setup/first-run.js +116 -29
  8. package/dist/src/fraim/config-loader.js +58 -23
  9. package/dist/src/fraim/issue-tracking/ado-provider.js +304 -0
  10. package/dist/src/fraim/issue-tracking/factory.js +63 -0
  11. package/dist/src/fraim/issue-tracking/github-provider.js +200 -0
  12. package/dist/src/fraim/issue-tracking/types.js +7 -0
  13. package/dist/src/fraim/issue-tracking-config.js +83 -0
  14. package/dist/src/fraim/issues.js +25 -23
  15. package/dist/src/fraim/setup-wizard.js +5 -3
  16. package/dist/src/fraim/template-processor.js +156 -30
  17. package/dist/src/fraim/types.js +21 -23
  18. package/dist/src/fraim-mcp-server.js +192 -31
  19. package/dist/src/utils/git-utils.js +38 -3
  20. package/dist/src/utils/platform-detection.js +213 -0
  21. package/dist/tests/test-cli.js +6 -10
  22. package/dist/tests/test-debug-session.js +130 -0
  23. package/dist/tests/test-enhanced-session-init.js +184 -0
  24. package/dist/tests/test-first-run-interactive.js +1 -0
  25. package/dist/tests/test-first-run-journey.js +274 -54
  26. package/dist/tests/test-fraim-issues.js +1 -1
  27. package/dist/tests/test-genericization.js +5 -25
  28. package/dist/tests/test-mcp-issue-integration.js +6 -2
  29. package/dist/tests/test-mcp-template-processing.js +156 -0
  30. package/dist/tests/test-modular-issue-tracking.js +161 -0
  31. package/dist/tests/test-package-size.js +7 -0
  32. package/dist/tests/test-workflow-discovery.js +242 -0
  33. package/package.json +1 -1
@@ -0,0 +1,19 @@
1
+ {
2
+ "get_issue": "mcp_ado_get_work_item({ organization: '{{repository.organization}}', project: '{{repository.project}}', workItemId: {{issue_number}} })",
3
+ "update_issue_status": "mcp_ado_update_work_item({ organization: '{{repository.organization}}', project: '{{repository.project}}', workItemId: {{issue_number}}, state: 'Active' })",
4
+ "create_pr": "mcp_ado_create_pull_request({ organization: '{{repository.organization}}', project: '{{repository.project}}', repository: '{{repository.name}}', title: '{{title}}', sourceBranch: '{{branch}}', targetBranch: 'main', description: '{{body}}' })",
5
+ "get_pr": "mcp_ado_get_pull_request({ organization: '{{repository.organization}}', project: '{{repository.project}}', repository: '{{repository.name}}', pullRequestId: {{pr_number}} })",
6
+ "get_pr_comments": "mcp_ado_get_pr_comments({ organization: '{{repository.organization}}', project: '{{repository.project}}', repository: '{{repository.name}}', pullRequestId: {{pr_number}} })",
7
+ "get_pr_review_comments": "mcp_ado_get_pr_review_comments({ organization: '{{repository.organization}}', project: '{{repository.project}}', repository: '{{repository.name}}', pullRequestId: {{pr_number}} })",
8
+ "get_pr_reviews": "mcp_ado_get_pr_reviews({ organization: '{{repository.organization}}', project: '{{repository.project}}', repository: '{{repository.name}}', pullRequestId: {{pr_number}} })",
9
+ "add_pr_comment": "mcp_ado_add_pr_comment({ organization: '{{repository.organization}}', project: '{{repository.project}}', repository: '{{repository.name}}', pullRequestId: {{pr_number}}, content: '{{comment}}' })",
10
+ "merge_pr": "mcp_ado_merge_pull_request({ organization: '{{repository.organization}}', project: '{{repository.project}}', repository: '{{repository.name}}', pullRequestId: {{pr_number}} })",
11
+ "create_branch": "git checkout -b feature/{{issue_number}}-{{slug}}",
12
+ "switch_branch": "git checkout {{branch}}",
13
+ "commit_changes": "git add . && git commit -m '{{message}}'",
14
+ "push_branch": "git push origin {{branch}}",
15
+ "list_issues": "mcp_ado_list_work_items({ organization: '{{repository.organization}}', project: '{{repository.project}}', state: 'Active' })",
16
+ "create_issue": "mcp_ado_create_work_item({ organization: '{{repository.organization}}', project: '{{repository.project}}', workItemType: 'Bug', title: '{{title}}', description: '{{body}}' })",
17
+ "add_issue_comment": "mcp_ado_add_work_item_comment({ organization: '{{repository.organization}}', project: '{{repository.project}}', workItemId: {{issue_number}}, text: '{{comment}}' })",
18
+ "assign_issue": "mcp_ado_update_work_item({ organization: '{{repository.organization}}', project: '{{repository.project}}', workItemId: {{issue_number}}, assignedTo: '{{assignee}}' })"
19
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "get_issue": "mcp_github_issue_read({ owner: '{{repository.owner}}', repo: '{{repository.name}}', issue_number: {{issue_number}}, method: 'get' })",
3
+ "update_issue_status": "mcp_github_issue_write({ owner: '{{repository.owner}}', repo: '{{repository.name}}', issue_number: {{issue_number}}, method: 'update', labels: ['status:in-progress'] })",
4
+ "create_pr": "mcp_github_create_pull_request({ owner: '{{repository.owner}}', repo: '{{repository.name}}', title: '{{title}}', head: '{{branch}}', base: 'main', body: '{{body}}' })",
5
+ "get_pr": "mcp_github_pull_request_read({ owner: '{{repository.owner}}', repo: '{{repository.name}}', pullNumber: {{pr_number}}, method: 'get' })",
6
+ "get_pr_comments": "mcp_github_pull_request_read({ owner: '{{repository.owner}}', repo: '{{repository.name}}', pullNumber: {{pr_number}}, method: 'get_comments' })",
7
+ "get_pr_review_comments": "mcp_github_pull_request_read({ owner: '{{repository.owner}}', repo: '{{repository.name}}', pullNumber: {{pr_number}}, method: 'get_review_comments' })",
8
+ "get_pr_reviews": "mcp_github_pull_request_read({ owner: '{{repository.owner}}', repo: '{{repository.name}}', pullNumber: {{pr_number}}, method: 'get_reviews' })",
9
+ "add_pr_comment": "mcp_github_add_issue_comment({ owner: '{{repository.owner}}', repo: '{{repository.name}}', issue_number: {{pr_number}}, body: '{{comment}}' })",
10
+ "merge_pr": "mcp_github_merge_pull_request({ owner: '{{repository.owner}}', repo: '{{repository.name}}', pullNumber: {{pr_number}} })",
11
+ "create_branch": "git checkout -b feature/{{issue_number}}-{{slug}}",
12
+ "switch_branch": "git checkout {{branch}}",
13
+ "commit_changes": "git add . && git commit -m '{{message}}'",
14
+ "push_branch": "git push origin {{branch}}",
15
+ "list_issues": "mcp_github_list_issues({ owner: '{{repository.owner}}', repo: '{{repository.name}}', state: 'OPEN' })",
16
+ "create_issue": "mcp_github_issue_write({ owner: '{{repository.owner}}', repo: '{{repository.name}}', method: 'create', title: '{{title}}', body: '{{body}}' })",
17
+ "add_issue_comment": "mcp_github_add_issue_comment({ owner: '{{repository.owner}}', repo: '{{repository.name}}', issue_number: {{issue_number}}, body: '{{comment}}' })",
18
+ "assign_issue": "mcp_github_issue_write({ owner: '{{repository.owner}}', repo: '{{repository.name}}', issue_number: {{issue_number}}, method: 'update', assignees: ['{{assignee}}'] })"
19
+ }
@@ -50,11 +50,12 @@ class AICoach {
50
50
  * Handle coaching request from agent
51
51
  */
52
52
  async handleCoachingRequest(args) {
53
- // console.log(`šŸ¤– AI Coach: Providing guidance for ${args.workflowType} workflow, phase: ${args.currentPhase}, status: ${args.status}`);
53
+ console.log(`šŸ¤– AI Coach: Providing guidance for ${args.workflowType} workflow, phase: ${args.currentPhase}, status: ${args.status}`);
54
54
  // Validate required parameters
55
55
  const requiredParams = ['workflowType', 'currentPhase', 'status'];
56
56
  const missingParams = requiredParams.filter(param => !args[param]);
57
57
  if (missingParams.length > 0) {
58
+ console.log(`āŒ AI Coach: Missing required parameters: ${missingParams.join(', ')}`);
58
59
  throw new Error(`Missing required parameters: ${missingParams.join(', ')}`);
59
60
  }
60
61
  // Handle missing or empty issue number gracefully
@@ -78,10 +79,12 @@ Example: \`seekCoachingOnNextStep({ workflowType: "${args.workflowType}", issueN
78
79
  // Validate workflow type
79
80
  const validWorkflowTypes = ['implement', 'spec', 'design', 'test'];
80
81
  if (!validWorkflowTypes.includes(args.workflowType)) {
82
+ console.log(`āŒ AI Coach: Invalid workflow type: ${args.workflowType}`);
81
83
  throw new Error(`Invalid workflow type: ${args.workflowType}. Valid types: ${validWorkflowTypes.join(', ')}`);
82
84
  }
83
85
  // Validate phase for workflow type
84
86
  const issueType = this.extractIssueType(args.findings, args.evidence);
87
+ console.log(`šŸ” AI Coach: Extracted issue type: ${issueType}`);
85
88
  if (!(0, phase_flow_js_1.isPhaseValidForWorkflow)(args.currentPhase, args.workflowType, issueType)) {
86
89
  // Special handling for implement workflow to provide better error messages
87
90
  if (args.workflowType === 'implement') {
@@ -132,15 +135,19 @@ ${this.getValidPhasesForIssueType(issueType).join(' → ')}
132
135
  }
133
136
  }
134
137
  if (args.status === 'complete') {
138
+ console.log(`āœ… AI Coach: Generating completion message`);
135
139
  return await this.generateCompletionMessage(args);
136
140
  }
137
141
  else if (args.status === 'incomplete') {
142
+ console.log(`šŸ¤ AI Coach: Generating help message`);
138
143
  return await this.generateHelpMessage(args);
139
144
  }
140
145
  else if (args.status === 'failure') {
146
+ console.log(`āŒ AI Coach: Generating failure message`);
141
147
  return await this.generateFailureMessage(args);
142
148
  }
143
149
  else {
150
+ console.log(`šŸ“– AI Coach: Getting phase instructions for ${args.currentPhase}`);
144
151
  return await this.getPhaseInstructions(args.currentPhase, args.workflowType);
145
152
  }
146
153
  }
@@ -46,10 +46,11 @@ const runInitProject = async () => {
46
46
  ...types_1.DEFAULT_FRAIM_CONFIG.project,
47
47
  name: remoteInfo.repo || path_1.default.basename(projectRoot)
48
48
  },
49
- git: {
50
- ...types_1.DEFAULT_FRAIM_CONFIG.git,
51
- repoOwner: remoteInfo.owner || types_1.DEFAULT_FRAIM_CONFIG.git.repoOwner,
52
- repoName: remoteInfo.repo || path_1.default.basename(projectRoot)
49
+ repository: {
50
+ provider: 'github',
51
+ owner: remoteInfo.owner || 'your-username',
52
+ name: remoteInfo.repo || path_1.default.basename(projectRoot),
53
+ defaultBranch: 'main'
53
54
  }
54
55
  };
55
56
  fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
@@ -10,7 +10,6 @@ const path_1 = __importDefault(require("path"));
10
10
  const chalk_1 = __importDefault(require("chalk"));
11
11
  const first_run_1 = require("../setup/first-run");
12
12
  const sync_1 = require("./sync");
13
- const types_1 = require("../../fraim/types");
14
13
  const git_utils_1 = require("../../utils/git-utils");
15
14
  const version_utils_1 = require("../../utils/version-utils");
16
15
  const script_sync_utils_1 = require("../../utils/script-sync-utils");
@@ -35,16 +34,18 @@ const runInit = async () => {
35
34
  process.exit(1);
36
35
  }
37
36
  const config = {
38
- ...types_1.DEFAULT_FRAIM_CONFIG,
39
37
  version: (0, version_utils_1.getFraimVersion)(),
40
38
  project: {
41
- ...types_1.DEFAULT_FRAIM_CONFIG.project,
42
39
  name: remoteInfo.repo
43
40
  },
44
- git: {
45
- ...types_1.DEFAULT_FRAIM_CONFIG.git,
46
- repoOwner: remoteInfo.owner || types_1.DEFAULT_FRAIM_CONFIG.git.repoOwner,
47
- repoName: remoteInfo.repo
41
+ repository: {
42
+ provider: 'github',
43
+ owner: remoteInfo.owner || 'your-username',
44
+ name: remoteInfo.repo,
45
+ defaultBranch: remoteInfo.defaultBranch || 'main'
46
+ },
47
+ customizations: {
48
+ workflowsPath: '.fraim/workflows'
48
49
  }
49
50
  };
50
51
  fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
@@ -170,18 +170,18 @@ async function checkAndUpdateCLI() {
170
170
  try {
171
171
  console.log(chalk_1.default.blue('šŸ” Checking for FRAIM CLI updates...'));
172
172
  const currentVersion = (0, version_utils_1.getFraimVersion)();
173
- const latestVersion = await getLatestNpmVersion('fraim-framework');
173
+ const latestVersion = await getLatestNpmVersionHttp('fraim-framework');
174
174
  if (!latestVersion) {
175
175
  console.log(chalk_1.default.yellow('āš ļø Could not check for updates. Continuing with current version.'));
176
176
  return;
177
177
  }
178
- if (currentVersion === latestVersion) {
178
+ if (isVersionUpToDate(currentVersion, latestVersion)) {
179
179
  console.log(chalk_1.default.green(`āœ… FRAIM CLI is up to date (${currentVersion})`));
180
180
  return;
181
181
  }
182
182
  console.log(chalk_1.default.yellow(`šŸ“¦ New version available: ${currentVersion} → ${latestVersion}`));
183
183
  console.log(chalk_1.default.blue('šŸ”„ Auto-updating FRAIM CLI...'));
184
- const success = await updateGlobalPackage('fraim-framework', latestVersion);
184
+ const success = await updateGlobalPackageHttp('fraim-framework', latestVersion);
185
185
  if (success) {
186
186
  console.log(chalk_1.default.green(`āœ… Successfully updated to FRAIM ${latestVersion}`));
187
187
  console.log(chalk_1.default.gray(' Restart may be required for some changes to take effect.'));
@@ -194,42 +194,65 @@ async function checkAndUpdateCLI() {
194
194
  console.log(chalk_1.default.yellow('āš ļø Could not check for updates. Continuing with current version.'));
195
195
  }
196
196
  }
197
- async function getLatestNpmVersion(packageName) {
198
- return new Promise((resolve) => {
199
- // Use npm.cmd on Windows, npm on Unix
200
- const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
201
- const npmProcess = (0, child_process_1.spawn)(npmCommand, ['view', packageName, 'version'], {
202
- stdio: ['ignore', 'pipe', 'ignore']
203
- });
204
- let output = '';
205
- npmProcess.stdout.on('data', (data) => {
206
- output += data.toString();
207
- });
208
- npmProcess.on('close', (code) => {
209
- if (code === 0) {
210
- resolve(output.trim());
211
- }
212
- else {
197
+ async function getLatestNpmVersionHttp(packageName) {
198
+ try {
199
+ // Use Node.js built-in https module instead of spawn
200
+ const https = require('https');
201
+ return new Promise((resolve) => {
202
+ const req = https.get(`https://registry.npmjs.org/${packageName}/latest`, {
203
+ timeout: 5000,
204
+ headers: {
205
+ 'User-Agent': 'fraim-framework-cli'
206
+ }
207
+ }, (res) => {
208
+ let data = '';
209
+ res.on('data', (chunk) => {
210
+ data += chunk;
211
+ });
212
+ res.on('end', () => {
213
+ try {
214
+ const packageInfo = JSON.parse(data);
215
+ resolve(packageInfo.version || null);
216
+ }
217
+ catch (e) {
218
+ resolve(null);
219
+ }
220
+ });
221
+ });
222
+ req.on('error', () => {
213
223
  resolve(null);
214
- }
215
- });
216
- npmProcess.on('error', () => {
217
- resolve(null);
224
+ });
225
+ req.on('timeout', () => {
226
+ req.destroy();
227
+ resolve(null);
228
+ });
218
229
  });
219
- // Timeout after 5 seconds
220
- setTimeout(() => {
221
- npmProcess.kill();
222
- resolve(null);
223
- }, 5000);
224
- });
230
+ }
231
+ catch (error) {
232
+ return null;
233
+ }
234
+ }
235
+ function isVersionUpToDate(current, latest) {
236
+ // Simple version comparison - assumes semantic versioning
237
+ const currentParts = current.split('.').map(n => parseInt(n, 10));
238
+ const latestParts = latest.split('.').map(n => parseInt(n, 10));
239
+ for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
240
+ const currentPart = currentParts[i] || 0;
241
+ const latestPart = latestParts[i] || 0;
242
+ if (currentPart < latestPart)
243
+ return false;
244
+ if (currentPart > latestPart)
245
+ return true;
246
+ }
247
+ return true; // Versions are equal
225
248
  }
226
- async function updateGlobalPackage(packageName, version) {
249
+ async function updateGlobalPackageHttp(packageName, version) {
227
250
  return new Promise((resolve) => {
228
251
  console.log(chalk_1.default.gray(` Running: npm install -g ${packageName}@${version}`));
229
252
  // Use npm.cmd on Windows, npm on Unix
230
253
  const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
231
254
  const npmProcess = (0, child_process_1.spawn)(npmCommand, ['install', '-g', `${packageName}@${version}`], {
232
- stdio: ['ignore', 'pipe', 'pipe']
255
+ stdio: ['ignore', 'ignore', 'ignore'] // Suppress output for cleaner experience
233
256
  });
234
257
  npmProcess.on('close', (code) => {
235
258
  resolve(code === 0);
@@ -39,6 +39,71 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.runFirstRunExperience = void 0;
40
40
  const prompts_1 = __importDefault(require("prompts"));
41
41
  const chalk_1 = __importDefault(require("chalk"));
42
+ const fs_1 = __importDefault(require("fs"));
43
+ const path_1 = __importDefault(require("path"));
44
+ const os_1 = __importDefault(require("os"));
45
+ const loadGlobalConfig = () => {
46
+ const globalConfigPath = path_1.default.join(os_1.default.homedir(), '.fraim', 'config.json');
47
+ if (!fs_1.default.existsSync(globalConfigPath)) {
48
+ return null;
49
+ }
50
+ try {
51
+ const config = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
52
+ return {
53
+ fraimKey: config.apiKey,
54
+ githubToken: config.githubToken
55
+ };
56
+ }
57
+ catch (e) {
58
+ return null;
59
+ }
60
+ };
61
+ const checkMCPConfigurations = () => {
62
+ const sources = [];
63
+ // Check Kiro MCP config
64
+ const kiroConfigPath = path_1.default.join(os_1.default.homedir(), '.kiro', 'settings', 'mcp.json');
65
+ if (fs_1.default.existsSync(kiroConfigPath)) {
66
+ try {
67
+ const kiroConfig = JSON.parse(fs_1.default.readFileSync(kiroConfigPath, 'utf8'));
68
+ if (kiroConfig.mcpServers?.fraim) {
69
+ sources.push('Kiro');
70
+ }
71
+ }
72
+ catch (e) {
73
+ // Ignore parsing errors
74
+ }
75
+ }
76
+ // Check Claude Desktop config
77
+ const claudeConfigPath = path_1.default.join(os_1.default.homedir(), 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json');
78
+ if (fs_1.default.existsSync(claudeConfigPath)) {
79
+ try {
80
+ const claudeConfig = JSON.parse(fs_1.default.readFileSync(claudeConfigPath, 'utf8'));
81
+ if (claudeConfig.mcpServers?.fraim) {
82
+ sources.push('Claude Desktop');
83
+ }
84
+ }
85
+ catch (e) {
86
+ // Ignore parsing errors
87
+ }
88
+ }
89
+ // Check macOS Claude config path
90
+ const claudeMacPath = path_1.default.join(os_1.default.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
91
+ if (fs_1.default.existsSync(claudeMacPath)) {
92
+ try {
93
+ const claudeConfig = JSON.parse(fs_1.default.readFileSync(claudeMacPath, 'utf8'));
94
+ if (claudeConfig.mcpServers?.fraim) {
95
+ sources.push('Claude Desktop (macOS)');
96
+ }
97
+ }
98
+ catch (e) {
99
+ // Ignore parsing errors
100
+ }
101
+ }
102
+ return {
103
+ found: sources.length > 0,
104
+ sources
105
+ };
106
+ };
42
107
  const runFirstRunExperience = async () => {
43
108
  // Skip interactive setup in CI/Test environments
44
109
  if (process.env.CI === 'true' || process.env.TEST_MODE === 'true') {
@@ -46,39 +111,61 @@ const runFirstRunExperience = async () => {
46
111
  return;
47
112
  }
48
113
  console.log(chalk_1.default.blue('\nšŸ‘‹ Welcome to FRAIM! Let\'s get you set up.\n'));
49
- // 1. Ask for FRAIM key
50
- let response;
51
- try {
52
- response = await (0, prompts_1.default)({
53
- type: 'text',
54
- name: 'fraimKey',
55
- message: 'Do you have a FRAIM key? (Input key or press Enter to skip)',
56
- validate: (value) => true // Optional input
57
- });
114
+ // Check for existing configuration
115
+ const globalConfig = loadGlobalConfig();
116
+ const mcpCheck = checkMCPConfigurations();
117
+ if (globalConfig && globalConfig.fraimKey) {
118
+ console.log(chalk_1.default.green('āœ… Found existing FRAIM configuration'));
119
+ if (mcpCheck.found) {
120
+ console.log(chalk_1.default.green(`āœ… Found FRAIM MCP configuration in: ${mcpCheck.sources.join(', ')}`));
121
+ console.log(chalk_1.default.blue('šŸŽ‰ You\'re all set! FRAIM is ready to use.\n'));
122
+ }
123
+ else {
124
+ console.log(chalk_1.default.yellow('āš ļø FRAIM key found but no MCP configuration detected.'));
125
+ console.log(chalk_1.default.cyan('šŸ’” Consider running "fraim add-ide" to configure your IDE.\n'));
126
+ }
58
127
  }
59
- catch (e) {
60
- console.warn(chalk_1.default.yellow('\nāš ļø Interactive prompts experienced an issue. Skipping setup.\n'));
61
- return;
128
+ else if (mcpCheck.found) {
129
+ console.log(chalk_1.default.green(`āœ… Found FRAIM MCP configuration in: ${mcpCheck.sources.join(', ')}`));
130
+ console.log(chalk_1.default.yellow('āš ļø But no global FRAIM configuration found.'));
131
+ console.log(chalk_1.default.cyan('šŸ’” Your MCP setup looks good! Skipping key setup.\n'));
62
132
  }
63
- if (response.fraimKey && response.fraimKey.trim().length > 0) {
64
- const key = response.fraimKey.trim();
65
- console.log(chalk_1.default.green('\nāœ… Key received.'));
66
- console.log(chalk_1.default.yellow('\nPlease add the following to your IDE\'s MCP config (e.g. claude_desktop_config.json):'));
67
- const mcpConfig = {
68
- mcpServers: {
69
- fraim: {
70
- serverUrl: "https://fraim.wellnessatwork.me",
71
- headers: {
72
- "x-api-key": key
133
+ else {
134
+ // No existing configuration found - ask for FRAIM key
135
+ console.log(chalk_1.default.yellow('šŸ” No existing FRAIM configuration detected.\n'));
136
+ let response;
137
+ try {
138
+ response = await (0, prompts_1.default)({
139
+ type: 'text',
140
+ name: 'fraimKey',
141
+ message: 'Do you have a FRAIM key? (Input key or press Enter to skip)',
142
+ validate: (value) => true // Optional input
143
+ });
144
+ }
145
+ catch (e) {
146
+ console.warn(chalk_1.default.yellow('\nāš ļø Interactive prompts experienced an issue. Skipping setup.\n'));
147
+ return;
148
+ }
149
+ if (response.fraimKey && response.fraimKey.trim().length > 0) {
150
+ const key = response.fraimKey.trim();
151
+ console.log(chalk_1.default.green('\nāœ… Key received.'));
152
+ console.log(chalk_1.default.yellow('\nPlease add the following to your IDE\'s MCP config (e.g. claude_desktop_config.json):'));
153
+ const mcpConfig = {
154
+ mcpServers: {
155
+ fraim: {
156
+ serverUrl: "https://fraim.wellnessatwork.me",
157
+ headers: {
158
+ "x-api-key": key
159
+ }
73
160
  }
74
161
  }
75
- }
76
- };
77
- console.log(chalk_1.default.cyan(JSON.stringify(mcpConfig, null, 2)));
78
- console.log('\n');
79
- }
80
- else {
81
- console.log(chalk_1.default.yellow('\nā„¹ļø If you need a key, please email sid.mathur@gmail.com to request one.\n'));
162
+ };
163
+ console.log(chalk_1.default.cyan(JSON.stringify(mcpConfig, null, 2)));
164
+ console.log('\n');
165
+ }
166
+ else {
167
+ console.log(chalk_1.default.yellow('\nā„¹ļø If you need a key, please email sid.mathur@gmail.com to request one.\n'));
168
+ }
82
169
  }
83
170
  // 2. Ask about Architecture Document
84
171
  const archResponse = await (0, prompts_1.default)({
@@ -6,6 +6,7 @@
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.loadFraimConfig = loadFraimConfig;
8
8
  exports.getConfigValue = getConfigValue;
9
+ exports.getRepositoryInfo = getRepositoryInfo;
9
10
  const fs_1 = require("fs");
10
11
  const path_1 = require("path");
11
12
  const types_1 = require("./types");
@@ -21,41 +22,51 @@ function loadFraimConfig() {
21
22
  }
22
23
  try {
23
24
  const configContent = (0, fs_1.readFileSync)(configPath, 'utf-8');
24
- const config = JSON.parse(configContent);
25
- // Merge with defaults to ensure all required fields exist
25
+ const config = JSON.parse(configContent); // Use any for backward compatibility
26
+ // Handle backward compatibility and migration
26
27
  const mergedConfig = {
27
- ...types_1.DEFAULT_FRAIM_CONFIG,
28
- ...config,
28
+ version: config.version || types_1.DEFAULT_FRAIM_CONFIG.version,
29
29
  project: {
30
30
  ...types_1.DEFAULT_FRAIM_CONFIG.project,
31
31
  ...(config.project || {})
32
32
  },
33
- git: {
34
- ...types_1.DEFAULT_FRAIM_CONFIG.git,
35
- ...(config.git || {})
33
+ repository: {
34
+ ...types_1.DEFAULT_FRAIM_CONFIG.repository,
35
+ ...(config.repository || {}),
36
+ // Migrate from old git config if repository is missing
37
+ ...((!config.repository && config.git) ? {
38
+ provider: config.git.provider || 'github',
39
+ owner: config.git.repoOwner,
40
+ name: config.git.repoName,
41
+ defaultBranch: config.git.defaultBranch || 'main'
42
+ } : {})
36
43
  },
37
44
  customizations: {
38
45
  ...types_1.DEFAULT_FRAIM_CONFIG.customizations,
39
46
  ...(config.customizations || {})
40
- },
41
- persona: {
42
- ...types_1.DEFAULT_FRAIM_CONFIG.persona,
43
- ...(config.persona || {})
44
- },
45
- marketing: {
46
- ...types_1.DEFAULT_FRAIM_CONFIG.marketing,
47
- ...(config.marketing || {})
48
- },
49
- database: {
50
- ...types_1.DEFAULT_FRAIM_CONFIG.database,
51
- ...(config.database || {})
52
47
  }
53
48
  };
54
- // Validate version
55
- if (!mergedConfig.version) {
56
- mergedConfig.version = types_1.DEFAULT_FRAIM_CONFIG.version;
49
+ // Add optional fields only if they exist in the config
50
+ if (config.persona) {
51
+ mergedConfig.persona = config.persona;
52
+ }
53
+ if (config.marketing) {
54
+ mergedConfig.marketing = config.marketing;
55
+ }
56
+ if (config.database) {
57
+ mergedConfig.database = config.database;
58
+ }
59
+ if (config.compliance) {
60
+ mergedConfig.compliance = config.compliance;
61
+ }
62
+ if (config.learning) {
63
+ mergedConfig.learning = config.learning;
57
64
  }
58
65
  console.log(`šŸ“‹ Loaded FRAIM config from .fraim/config.json (version ${mergedConfig.version})`);
66
+ // Warn about deprecated git config
67
+ if (config.git && !config.repository) {
68
+ console.warn('āš ļø Deprecated: "git" config detected. Consider migrating to "repository" config.');
69
+ }
59
70
  return mergedConfig;
60
71
  }
61
72
  catch (error) {
@@ -65,7 +76,7 @@ function loadFraimConfig() {
65
76
  }
66
77
  }
67
78
  /**
68
- * Get configuration value by path (e.g., "project.name", "testing.framework")
79
+ * Get configuration value by path (e.g., "project.name", "repository.owner")
69
80
  */
70
81
  function getConfigValue(config, path) {
71
82
  const parts = path.split('.');
@@ -80,3 +91,27 @@ function getConfigValue(config, path) {
80
91
  }
81
92
  return value;
82
93
  }
94
+ /**
95
+ * Get repository info with backward compatibility
96
+ */
97
+ function getRepositoryInfo(config) {
98
+ // Prefer new repository config
99
+ if (config.repository) {
100
+ return {
101
+ owner: config.repository.owner || config.repository.organization,
102
+ name: config.repository.name,
103
+ provider: config.repository.provider,
104
+ defaultBranch: config.repository.defaultBranch
105
+ };
106
+ }
107
+ // Fall back to old git config
108
+ if (config.git) {
109
+ return {
110
+ owner: config.git.repoOwner,
111
+ name: config.git.repoName,
112
+ provider: config.git.provider || 'github',
113
+ defaultBranch: config.git.defaultBranch
114
+ };
115
+ }
116
+ return {};
117
+ }