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.
- package/dist/registry/providers/ado.json +19 -0
- package/dist/registry/providers/github.json +19 -0
- package/dist/src/ai-manager/ai-manager.js +8 -1
- package/dist/src/cli/commands/init-project.js +5 -4
- package/dist/src/cli/commands/init.js +8 -7
- package/dist/src/cli/commands/sync.js +54 -31
- package/dist/src/cli/setup/first-run.js +116 -29
- package/dist/src/fraim/config-loader.js +58 -23
- package/dist/src/fraim/issue-tracking/ado-provider.js +304 -0
- package/dist/src/fraim/issue-tracking/factory.js +63 -0
- package/dist/src/fraim/issue-tracking/github-provider.js +200 -0
- package/dist/src/fraim/issue-tracking/types.js +7 -0
- package/dist/src/fraim/issue-tracking-config.js +83 -0
- package/dist/src/fraim/issues.js +25 -23
- package/dist/src/fraim/setup-wizard.js +5 -3
- package/dist/src/fraim/template-processor.js +156 -30
- package/dist/src/fraim/types.js +21 -23
- package/dist/src/fraim-mcp-server.js +192 -31
- package/dist/src/utils/git-utils.js +38 -3
- package/dist/src/utils/platform-detection.js +213 -0
- package/dist/tests/test-cli.js +6 -10
- package/dist/tests/test-debug-session.js +130 -0
- package/dist/tests/test-enhanced-session-init.js +184 -0
- package/dist/tests/test-first-run-interactive.js +1 -0
- package/dist/tests/test-first-run-journey.js +274 -54
- package/dist/tests/test-fraim-issues.js +1 -1
- package/dist/tests/test-genericization.js +5 -25
- package/dist/tests/test-mcp-issue-integration.js +6 -2
- package/dist/tests/test-mcp-template-processing.js +156 -0
- package/dist/tests/test-modular-issue-tracking.js +161 -0
- package/dist/tests/test-package-size.js +7 -0
- package/dist/tests/test-workflow-discovery.js +242 -0
- 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
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
198
|
-
|
|
199
|
-
// Use
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
217
|
-
|
|
224
|
+
});
|
|
225
|
+
req.on('timeout', () => {
|
|
226
|
+
req.destroy();
|
|
227
|
+
resolve(null);
|
|
228
|
+
});
|
|
218
229
|
});
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
|
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', '
|
|
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
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
60
|
-
console.
|
|
61
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
console.log(chalk_1.default.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
//
|
|
25
|
+
const config = JSON.parse(configContent); // Use any for backward compatibility
|
|
26
|
+
// Handle backward compatibility and migration
|
|
26
27
|
const mergedConfig = {
|
|
27
|
-
|
|
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
|
-
|
|
34
|
-
...types_1.DEFAULT_FRAIM_CONFIG.
|
|
35
|
-
...(config.
|
|
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
|
-
//
|
|
55
|
-
if (
|
|
56
|
-
mergedConfig.
|
|
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", "
|
|
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
|
+
}
|