fraim-framework 2.0.41 ā 2.0.42
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 +25 -1
- package/dist/registry/ai-manager-rules/design.json +97 -0
- package/dist/registry/ai-manager-rules/implement.json +153 -0
- package/dist/registry/ai-manager-rules/spec.json +112 -0
- package/dist/registry/ai-manager-rules/test.json +98 -0
- package/dist/src/cli/commands/add-ide.js +310 -0
- package/dist/src/cli/commands/setup.js +3 -2
- package/dist/src/cli/fraim.js +2 -0
- package/dist/src/fraim-mcp-server.js +1 -1
- package/dist/tests/test-add-ide.js +283 -0
- package/dist/tests/test-ai-manager.js +16 -0
- package/dist/tests/test-client-scripts-validation.js +18 -6
- package/dist/tests/test-config-system.js +279 -0
- package/dist/tests/test-setup-integration.js +1 -1
- package/dist/tests/test-setup-scenarios.js +322 -0
- package/package.json +2 -2
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.saveGitHubTokenToConfig = exports.loadGlobalConfig = exports.addIDECommand = exports.runAddIDE = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
const ide_detector_1 = require("../setup/ide-detector");
|
|
14
|
+
const mcp_config_generator_1 = require("../setup/mcp-config-generator");
|
|
15
|
+
const loadGlobalConfig = () => {
|
|
16
|
+
const globalConfigPath = path_1.default.join(os_1.default.homedir(), '.fraim', 'config.json');
|
|
17
|
+
if (!fs_1.default.existsSync(globalConfigPath)) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const config = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
|
|
22
|
+
return {
|
|
23
|
+
fraimKey: config.apiKey,
|
|
24
|
+
githubToken: config.githubToken
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
exports.loadGlobalConfig = loadGlobalConfig;
|
|
32
|
+
const promptForGitHubToken = async () => {
|
|
33
|
+
console.log(chalk_1.default.yellow('\nš GitHub token needed for MCP configuration'));
|
|
34
|
+
console.log(chalk_1.default.gray('This is required for git and GitHub MCP servers to function properly.\n'));
|
|
35
|
+
const tokenResponse = await (0, prompts_1.default)({
|
|
36
|
+
type: 'password',
|
|
37
|
+
name: 'token',
|
|
38
|
+
message: 'Enter your GitHub token',
|
|
39
|
+
validate: (value) => {
|
|
40
|
+
if (!value)
|
|
41
|
+
return 'GitHub token is required';
|
|
42
|
+
if (value.startsWith('ghp_') || value.startsWith('github_pat_'))
|
|
43
|
+
return true;
|
|
44
|
+
return 'Please enter a valid GitHub token (starts with ghp_ or github_pat_)';
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
if (!tokenResponse.token) {
|
|
48
|
+
console.log(chalk_1.default.red('GitHub token is required. Exiting.'));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
return tokenResponse.token;
|
|
52
|
+
};
|
|
53
|
+
const saveGitHubTokenToConfig = (githubToken) => {
|
|
54
|
+
const globalConfigPath = path_1.default.join(os_1.default.homedir(), '.fraim', 'config.json');
|
|
55
|
+
if (fs_1.default.existsSync(globalConfigPath)) {
|
|
56
|
+
try {
|
|
57
|
+
const config = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
|
|
58
|
+
config.githubToken = githubToken;
|
|
59
|
+
fs_1.default.writeFileSync(globalConfigPath, JSON.stringify(config, null, 2));
|
|
60
|
+
console.log(chalk_1.default.green('ā
GitHub token saved to global config'));
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
console.log(chalk_1.default.yellow('ā ļø Could not save GitHub token to config'));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
exports.saveGitHubTokenToConfig = saveGitHubTokenToConfig;
|
|
68
|
+
const configureIDEMCP = async (ide, fraimKey, githubToken) => {
|
|
69
|
+
const configPath = (0, ide_detector_1.expandPath)(ide.configPath);
|
|
70
|
+
console.log(chalk_1.default.blue(`š§ Configuring ${ide.name}...`));
|
|
71
|
+
// Create backup if config exists
|
|
72
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
73
|
+
const backupPath = `${configPath}.fraim-backup-${Date.now()}`;
|
|
74
|
+
fs_1.default.copyFileSync(configPath, backupPath);
|
|
75
|
+
console.log(chalk_1.default.gray(` š Backup created: ${backupPath}`));
|
|
76
|
+
}
|
|
77
|
+
// Ensure directory exists
|
|
78
|
+
const configDir = path_1.default.dirname(configPath);
|
|
79
|
+
if (!fs_1.default.existsSync(configDir)) {
|
|
80
|
+
fs_1.default.mkdirSync(configDir, { recursive: true });
|
|
81
|
+
console.log(chalk_1.default.gray(` š Created directory: ${configDir}`));
|
|
82
|
+
}
|
|
83
|
+
let existingConfig = {};
|
|
84
|
+
let existingMCPServers = {};
|
|
85
|
+
if (fs_1.default.existsSync(configPath) && ide.configFormat === 'json') {
|
|
86
|
+
try {
|
|
87
|
+
existingConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
88
|
+
existingMCPServers = existingConfig.mcpServers || {};
|
|
89
|
+
console.log(chalk_1.default.gray(` š Found existing config with ${Object.keys(existingMCPServers).length} MCP servers`));
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
console.log(chalk_1.default.yellow(` ā ļø Could not parse existing ${ide.name} config, creating new one`));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (ide.configFormat === 'toml') {
|
|
96
|
+
// Handle TOML format (Codex)
|
|
97
|
+
let existingTomlContent = '';
|
|
98
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
99
|
+
existingTomlContent = fs_1.default.readFileSync(configPath, 'utf8');
|
|
100
|
+
console.log(chalk_1.default.gray(` š Found existing TOML config`));
|
|
101
|
+
}
|
|
102
|
+
const newTomlContent = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, githubToken);
|
|
103
|
+
let finalContent = existingTomlContent;
|
|
104
|
+
const serversToAdd = ['fraim', 'git', 'github', 'playwright'];
|
|
105
|
+
const addedServers = [];
|
|
106
|
+
for (const server of serversToAdd) {
|
|
107
|
+
if (!existingTomlContent.includes(`[mcp_servers.${server}]`)) {
|
|
108
|
+
const serverRegex = new RegExp(`\\[mcp_servers\\.${server}\\][\\s\\S]*?(?=\\[mcp_servers\\.|$)`, 'g');
|
|
109
|
+
const serverMatch = newTomlContent.match(serverRegex);
|
|
110
|
+
if (serverMatch) {
|
|
111
|
+
finalContent += '\n' + serverMatch[0].trim() + '\n';
|
|
112
|
+
addedServers.push(server);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
console.log(chalk_1.default.gray(` āļø Skipped ${server} (already exists)`));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
fs_1.default.writeFileSync(configPath, finalContent);
|
|
120
|
+
addedServers.forEach(server => {
|
|
121
|
+
console.log(chalk_1.default.green(` ā
Added ${server} MCP server`));
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
// Handle JSON format
|
|
126
|
+
const newConfig = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, githubToken);
|
|
127
|
+
const newMCPServers = newConfig.mcpServers || {};
|
|
128
|
+
// Merge MCP servers intelligently
|
|
129
|
+
const mergedMCPServers = { ...existingMCPServers };
|
|
130
|
+
const addedServers = [];
|
|
131
|
+
const skippedServers = [];
|
|
132
|
+
for (const [serverName, serverConfig] of Object.entries(newMCPServers)) {
|
|
133
|
+
if (!existingMCPServers[serverName]) {
|
|
134
|
+
mergedMCPServers[serverName] = serverConfig;
|
|
135
|
+
addedServers.push(serverName);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
skippedServers.push(serverName);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Merge with existing config
|
|
142
|
+
const mergedConfig = {
|
|
143
|
+
...existingConfig,
|
|
144
|
+
...newConfig,
|
|
145
|
+
mcpServers: mergedMCPServers
|
|
146
|
+
};
|
|
147
|
+
// Write updated config
|
|
148
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2));
|
|
149
|
+
addedServers.forEach(server => {
|
|
150
|
+
console.log(chalk_1.default.green(` ā
Added ${server} MCP server`));
|
|
151
|
+
});
|
|
152
|
+
skippedServers.forEach(server => {
|
|
153
|
+
console.log(chalk_1.default.gray(` āļø Skipped ${server} (already exists)`));
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
console.log(chalk_1.default.green(`ā
Updated ${configPath}`));
|
|
157
|
+
};
|
|
158
|
+
const listSupportedIDEs = () => {
|
|
159
|
+
const allIDEs = (0, ide_detector_1.getAllSupportedIDEs)();
|
|
160
|
+
const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
|
|
161
|
+
console.log(chalk_1.default.blue('š Supported IDEs:\n'));
|
|
162
|
+
allIDEs.forEach(ide => {
|
|
163
|
+
const isDetected = detectedIDEs.some(detected => detected.name === ide.name);
|
|
164
|
+
const statusIcon = isDetected ? 'ā
' : 'ā';
|
|
165
|
+
const statusText = isDetected ? 'detected' : 'not found';
|
|
166
|
+
console.log(chalk_1.default.white(` ${statusIcon} ${ide.name} (${statusText})`));
|
|
167
|
+
console.log(chalk_1.default.gray(` ${ide.description}`));
|
|
168
|
+
console.log(chalk_1.default.gray(` Config: ${ide.configPath}\n`));
|
|
169
|
+
});
|
|
170
|
+
console.log(chalk_1.default.yellow('š” Use "fraim add-ide --ide <name>" to configure a specific IDE'));
|
|
171
|
+
console.log(chalk_1.default.yellow(' Example: fraim add-ide --ide claude'));
|
|
172
|
+
};
|
|
173
|
+
const promptForIDESelection = async (availableIDEs) => {
|
|
174
|
+
console.log(chalk_1.default.green(`ā
Found ${availableIDEs.length} IDEs that can be configured:\n`));
|
|
175
|
+
availableIDEs.forEach((ide, index) => {
|
|
176
|
+
const configExists = fs_1.default.existsSync((0, ide_detector_1.expandPath)(ide.configPath));
|
|
177
|
+
const statusIcon = configExists ? 'š' : 'š';
|
|
178
|
+
const statusText = configExists ? 'has config' : 'new config';
|
|
179
|
+
console.log(chalk_1.default.white(` ${index + 1}. ${ide.name} ${statusIcon} (${statusText})`));
|
|
180
|
+
});
|
|
181
|
+
console.log(chalk_1.default.blue('\nFRAIM will add these MCP servers:'));
|
|
182
|
+
console.log(chalk_1.default.gray(' ⢠fraim (workflows and AI management)'));
|
|
183
|
+
console.log(chalk_1.default.gray(' ⢠git (version control integration)'));
|
|
184
|
+
console.log(chalk_1.default.gray(' ⢠github (GitHub API access)'));
|
|
185
|
+
console.log(chalk_1.default.gray(' ⢠playwright (browser automation)'));
|
|
186
|
+
const response = await (0, prompts_1.default)({
|
|
187
|
+
type: 'text',
|
|
188
|
+
name: 'selection',
|
|
189
|
+
message: 'Configure which IDEs? (Enter \'all\' or numbers like \'1,3\')',
|
|
190
|
+
initial: 'all',
|
|
191
|
+
validate: (value) => {
|
|
192
|
+
if (value.toLowerCase() === 'all')
|
|
193
|
+
return true;
|
|
194
|
+
if (value.toLowerCase() === 'none')
|
|
195
|
+
return true;
|
|
196
|
+
const numbers = value.split(',').map(n => parseInt(n.trim()));
|
|
197
|
+
const valid = numbers.every(n => n >= 1 && n <= availableIDEs.length && !isNaN(n));
|
|
198
|
+
return valid || 'Please enter "all", "none", or valid numbers (e.g., "1,3")';
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
if (response.selection.toLowerCase() === 'all') {
|
|
202
|
+
return availableIDEs;
|
|
203
|
+
}
|
|
204
|
+
if (response.selection.toLowerCase() === 'none') {
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
const selectedIndices = response.selection.split(',').map((n) => parseInt(n.trim()) - 1);
|
|
208
|
+
return selectedIndices.map((i) => availableIDEs[i]).filter(Boolean);
|
|
209
|
+
};
|
|
210
|
+
const runAddIDE = async (options) => {
|
|
211
|
+
if (options.list) {
|
|
212
|
+
listSupportedIDEs();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
console.log(chalk_1.default.blue('š§ FRAIM IDE Configuration\n'));
|
|
216
|
+
// Load existing configuration
|
|
217
|
+
const globalConfig = loadGlobalConfig();
|
|
218
|
+
if (!globalConfig || !globalConfig.fraimKey) {
|
|
219
|
+
console.log(chalk_1.default.red('ā No FRAIM configuration found.'));
|
|
220
|
+
console.log(chalk_1.default.yellow('š” Please run "fraim setup" first to configure your FRAIM and GitHub keys.'));
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
let githubToken = globalConfig.githubToken;
|
|
224
|
+
if (!githubToken) {
|
|
225
|
+
console.log(chalk_1.default.yellow('ā ļø No GitHub token found in configuration.'));
|
|
226
|
+
githubToken = await promptForGitHubToken();
|
|
227
|
+
saveGitHubTokenToConfig(githubToken);
|
|
228
|
+
}
|
|
229
|
+
console.log(chalk_1.default.green('ā
Using existing FRAIM configuration\n'));
|
|
230
|
+
// Detect available IDEs
|
|
231
|
+
const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
|
|
232
|
+
if (detectedIDEs.length === 0) {
|
|
233
|
+
console.log(chalk_1.default.yellow('ā ļø No supported IDEs detected on your system.'));
|
|
234
|
+
console.log(chalk_1.default.gray('Supported IDEs: Claude, Antigravity, Kiro, Cursor, VSCode, Codex, Windsurf'));
|
|
235
|
+
console.log(chalk_1.default.blue('\nš” Install an IDE and run this command again.'));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
let idesToConfigure;
|
|
239
|
+
if (options.ide) {
|
|
240
|
+
// Configure specific IDE
|
|
241
|
+
const requestedIDE = (0, ide_detector_1.findIDEByName)(options.ide);
|
|
242
|
+
if (!requestedIDE) {
|
|
243
|
+
console.log(chalk_1.default.red(`ā IDE "${options.ide}" not supported.`));
|
|
244
|
+
console.log(chalk_1.default.yellow('š” Use "fraim add-ide --list" to see supported IDEs.'));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const detectedIDE = detectedIDEs.find(ide => ide.name === requestedIDE.name);
|
|
248
|
+
if (!detectedIDE) {
|
|
249
|
+
console.log(chalk_1.default.red(`ā ${requestedIDE.name} not found on your system.`));
|
|
250
|
+
console.log(chalk_1.default.yellow(`š” Please install ${requestedIDE.name} and try again.`));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
idesToConfigure = [detectedIDE];
|
|
254
|
+
}
|
|
255
|
+
else if (options.all) {
|
|
256
|
+
// Configure all detected IDEs
|
|
257
|
+
idesToConfigure = detectedIDEs;
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
// Interactive selection
|
|
261
|
+
idesToConfigure = await promptForIDESelection(detectedIDEs);
|
|
262
|
+
}
|
|
263
|
+
if (idesToConfigure.length === 0) {
|
|
264
|
+
console.log(chalk_1.default.yellow('ā ļø No IDEs selected for configuration.'));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
console.log(chalk_1.default.blue(`\nš Configuring ${idesToConfigure.length} IDE(s)...\n`));
|
|
268
|
+
const results = {
|
|
269
|
+
successful: [],
|
|
270
|
+
failed: []
|
|
271
|
+
};
|
|
272
|
+
for (const ide of idesToConfigure) {
|
|
273
|
+
try {
|
|
274
|
+
await configureIDEMCP(ide, globalConfig.fraimKey, githubToken);
|
|
275
|
+
results.successful.push(ide.name);
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
279
|
+
results.failed.push({ ide: ide.name, error: errorMessage });
|
|
280
|
+
console.log(chalk_1.default.red(`ā Failed to configure ${ide.name}: ${errorMessage}`));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// Summary
|
|
284
|
+
console.log(chalk_1.default.green(`\nš Configuration Complete:`));
|
|
285
|
+
if (results.successful.length > 0) {
|
|
286
|
+
console.log(chalk_1.default.green(` ā
Successfully configured: ${results.successful.length} IDE(s)`));
|
|
287
|
+
results.successful.forEach(ide => {
|
|
288
|
+
console.log(chalk_1.default.green(` ⢠${ide}`));
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
if (results.failed.length > 0) {
|
|
292
|
+
console.log(chalk_1.default.red(` ā Failed to configure: ${results.failed.length} IDE(s)`));
|
|
293
|
+
results.failed.forEach(({ ide, error }) => {
|
|
294
|
+
console.log(chalk_1.default.red(` ⢠${ide}: ${error}`));
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
if (results.successful.length > 0) {
|
|
298
|
+
console.log(chalk_1.default.blue('\nš Next steps:'));
|
|
299
|
+
console.log(chalk_1.default.cyan(' 1. Restart your configured IDEs'));
|
|
300
|
+
console.log(chalk_1.default.cyan(' 2. Ask your AI agent: "list fraim workflows"'));
|
|
301
|
+
console.log(chalk_1.default.blue('\nš” Use "fraim test-mcp" to verify the configuration.'));
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
exports.runAddIDE = runAddIDE;
|
|
305
|
+
exports.addIDECommand = new commander_1.Command('add-ide')
|
|
306
|
+
.description('Add FRAIM configuration to additional IDEs')
|
|
307
|
+
.option('--ide <name>', 'Configure specific IDE (claude, antigravity, kiro, cursor, vscode, codex, windsurf)')
|
|
308
|
+
.option('--all', 'Configure all detected IDEs')
|
|
309
|
+
.option('--list', 'List all supported IDEs and their detection status')
|
|
310
|
+
.action(exports.runAddIDE);
|
|
@@ -66,7 +66,7 @@ const promptForFraimKey = async () => {
|
|
|
66
66
|
console.log(chalk_1.default.gray('Please ensure you have a valid FRAIM key and try again.'));
|
|
67
67
|
process.exit(1);
|
|
68
68
|
};
|
|
69
|
-
const saveGlobalConfig = (fraimKey) => {
|
|
69
|
+
const saveGlobalConfig = (fraimKey, githubToken) => {
|
|
70
70
|
const globalConfigDir = path_1.default.join(os_1.default.homedir(), '.fraim');
|
|
71
71
|
const globalConfigPath = path_1.default.join(globalConfigDir, 'config.json');
|
|
72
72
|
if (!fs_1.default.existsSync(globalConfigDir)) {
|
|
@@ -75,6 +75,7 @@ const saveGlobalConfig = (fraimKey) => {
|
|
|
75
75
|
const config = {
|
|
76
76
|
version: (0, version_utils_1.getFraimVersion)(),
|
|
77
77
|
apiKey: fraimKey,
|
|
78
|
+
githubToken: githubToken,
|
|
78
79
|
configuredAt: new Date().toISOString(),
|
|
79
80
|
userPreferences: {
|
|
80
81
|
autoSync: true,
|
|
@@ -149,7 +150,7 @@ const runSetup = async (options) => {
|
|
|
149
150
|
}
|
|
150
151
|
// Save global configuration
|
|
151
152
|
console.log(chalk_1.default.blue('š¾ Saving global configuration...'));
|
|
152
|
-
saveGlobalConfig(fraimKey);
|
|
153
|
+
saveGlobalConfig(fraimKey, githubToken);
|
|
153
154
|
// Sync global scripts
|
|
154
155
|
syncGlobalScripts();
|
|
155
156
|
// Configure IDEs
|
package/dist/src/cli/fraim.js
CHANGED
|
@@ -13,6 +13,7 @@ const wizard_1 = require("./commands/wizard");
|
|
|
13
13
|
const setup_1 = require("./commands/setup");
|
|
14
14
|
const init_project_1 = require("./commands/init-project");
|
|
15
15
|
const test_mcp_1 = require("./commands/test-mcp");
|
|
16
|
+
const add_ide_1 = require("./commands/add-ide");
|
|
16
17
|
const fs_1 = __importDefault(require("fs"));
|
|
17
18
|
const path_1 = __importDefault(require("path"));
|
|
18
19
|
const program = new commander_1.Command();
|
|
@@ -48,4 +49,5 @@ program.addCommand(wizard_1.wizardCommand);
|
|
|
48
49
|
program.addCommand(setup_1.setupCommand);
|
|
49
50
|
program.addCommand(init_project_1.initProjectCommand);
|
|
50
51
|
program.addCommand(test_mcp_1.testMCPCommand);
|
|
52
|
+
program.addCommand(add_ide_1.addIDECommand);
|
|
51
53
|
program.parse(process.argv);
|
|
@@ -993,7 +993,7 @@ The AI Manager provides detailed review instructions including:
|
|
|
993
993
|
- Grading guidelines and reporting format
|
|
994
994
|
|
|
995
995
|
Use this when you believe your work is complete and ready for review.
|
|
996
|
-
Currently supports: spec
|
|
996
|
+
Currently supports: spec, implement phases (more phases coming soon).`,
|
|
997
997
|
inputSchema: {
|
|
998
998
|
type: 'object',
|
|
999
999
|
properties: {
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const node_test_1 = require("node:test");
|
|
40
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
41
|
+
const fs_1 = __importDefault(require("fs"));
|
|
42
|
+
const path_1 = __importDefault(require("path"));
|
|
43
|
+
const os_1 = __importDefault(require("os"));
|
|
44
|
+
// Test the add-ide command functionality without interactive prompts
|
|
45
|
+
(0, node_test_1.test)('add-ide command - list option shows supported IDEs', async () => {
|
|
46
|
+
// Import the module dynamically to avoid issues with mocking
|
|
47
|
+
const { runAddIDE } = await Promise.resolve().then(() => __importStar(require('../src/cli/commands/add-ide')));
|
|
48
|
+
let consoleOutput = [];
|
|
49
|
+
const originalConsoleLog = console.log;
|
|
50
|
+
console.log = (...args) => {
|
|
51
|
+
consoleOutput.push(args.join(' '));
|
|
52
|
+
};
|
|
53
|
+
try {
|
|
54
|
+
await runAddIDE({ list: true });
|
|
55
|
+
// Check that output contains supported IDEs
|
|
56
|
+
const output = consoleOutput.join('\n');
|
|
57
|
+
(0, node_assert_1.default)(output.includes('Supported IDEs'), 'Should show supported IDEs header');
|
|
58
|
+
(0, node_assert_1.default)(output.includes('Claude'), 'Should list Claude');
|
|
59
|
+
(0, node_assert_1.default)(output.includes('Antigravity'), 'Should list Antigravity');
|
|
60
|
+
(0, node_assert_1.default)(output.includes('Kiro'), 'Should list Kiro');
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
console.log = originalConsoleLog;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
(0, node_test_1.test)('add-ide command - validates IDE names', async () => {
|
|
67
|
+
// Test the findIDEByName function directly to avoid interactive prompts
|
|
68
|
+
const { findIDEByName } = await Promise.resolve().then(() => __importStar(require('../src/cli/setup/ide-detector')));
|
|
69
|
+
// Test valid IDE names
|
|
70
|
+
(0, node_assert_1.default)(findIDEByName('claude'), 'Should find Claude');
|
|
71
|
+
(0, node_assert_1.default)(findIDEByName('antigravity'), 'Should find Antigravity');
|
|
72
|
+
(0, node_assert_1.default)(findIDEByName('kiro'), 'Should find Kiro');
|
|
73
|
+
(0, node_assert_1.default)(findIDEByName('cursor'), 'Should find Cursor');
|
|
74
|
+
// Test case insensitive matching
|
|
75
|
+
(0, node_assert_1.default)(findIDEByName('CLAUDE'), 'Should find Claude case insensitive');
|
|
76
|
+
(0, node_assert_1.default)(findIDEByName('AntiGravity'), 'Should find Antigravity case insensitive');
|
|
77
|
+
// Test invalid IDE name
|
|
78
|
+
(0, node_assert_1.default)(!findIDEByName('nonexistent-ide'), 'Should not find nonexistent IDE');
|
|
79
|
+
});
|
|
80
|
+
(0, node_test_1.test)('add-ide command - detects IDEs correctly', async () => {
|
|
81
|
+
const { detectInstalledIDEs, getAllSupportedIDEs } = await Promise.resolve().then(() => __importStar(require('../src/cli/setup/ide-detector')));
|
|
82
|
+
const allIDEs = getAllSupportedIDEs();
|
|
83
|
+
const detectedIDEs = detectInstalledIDEs();
|
|
84
|
+
// Should have all supported IDEs defined
|
|
85
|
+
(0, node_assert_1.default)(allIDEs.length >= 7, 'Should have at least 7 supported IDEs');
|
|
86
|
+
// Detected IDEs should be a subset of all IDEs
|
|
87
|
+
(0, node_assert_1.default)(detectedIDEs.length <= allIDEs.length, 'Detected IDEs should not exceed total supported');
|
|
88
|
+
// Each detected IDE should have required properties
|
|
89
|
+
detectedIDEs.forEach(ide => {
|
|
90
|
+
(0, node_assert_1.default)(ide.name, 'IDE should have a name');
|
|
91
|
+
(0, node_assert_1.default)(ide.configPath, 'IDE should have a config path');
|
|
92
|
+
(0, node_assert_1.default)(['json', 'toml'].includes(ide.configFormat), 'IDE should have valid config format');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
(0, node_test_1.test)('add-ide command - loadGlobalConfig scenarios', async () => {
|
|
96
|
+
const { loadGlobalConfig } = await Promise.resolve().then(() => __importStar(require('../src/cli/commands/add-ide')));
|
|
97
|
+
const tempHomeDir = path_1.default.join(os_1.default.tmpdir(), '.fraim-test-add-ide-' + Date.now());
|
|
98
|
+
const originalHomedir = os_1.default.homedir;
|
|
99
|
+
os_1.default.homedir = () => tempHomeDir;
|
|
100
|
+
try {
|
|
101
|
+
// Test 1: No global config exists
|
|
102
|
+
const result1 = loadGlobalConfig();
|
|
103
|
+
node_assert_1.default.strictEqual(result1, null, 'Should return null when no config exists');
|
|
104
|
+
// Test 2: Valid config with both keys
|
|
105
|
+
const globalConfigDir = path_1.default.join(tempHomeDir, '.fraim');
|
|
106
|
+
const globalConfigPath = path_1.default.join(globalConfigDir, 'config.json');
|
|
107
|
+
fs_1.default.mkdirSync(globalConfigDir, { recursive: true });
|
|
108
|
+
const validConfig = {
|
|
109
|
+
version: '2.0.27',
|
|
110
|
+
apiKey: 'fraim_test_key',
|
|
111
|
+
githubToken: 'ghp_test_token',
|
|
112
|
+
configuredAt: new Date().toISOString()
|
|
113
|
+
};
|
|
114
|
+
fs_1.default.writeFileSync(globalConfigPath, JSON.stringify(validConfig, null, 2));
|
|
115
|
+
const result2 = loadGlobalConfig();
|
|
116
|
+
(0, node_assert_1.default)(result2, 'Should return config object');
|
|
117
|
+
node_assert_1.default.strictEqual(result2.fraimKey, 'fraim_test_key', 'Should extract FRAIM key');
|
|
118
|
+
node_assert_1.default.strictEqual(result2.githubToken, 'ghp_test_token', 'Should extract GitHub token');
|
|
119
|
+
// Test 3: Config missing GitHub token
|
|
120
|
+
const configWithoutGithub = {
|
|
121
|
+
version: '2.0.27',
|
|
122
|
+
apiKey: 'fraim_test_key_no_github',
|
|
123
|
+
configuredAt: new Date().toISOString()
|
|
124
|
+
};
|
|
125
|
+
fs_1.default.writeFileSync(globalConfigPath, JSON.stringify(configWithoutGithub, null, 2));
|
|
126
|
+
const result3 = loadGlobalConfig();
|
|
127
|
+
(0, node_assert_1.default)(result3, 'Should return config object even without GitHub token');
|
|
128
|
+
node_assert_1.default.strictEqual(result3.fraimKey, 'fraim_test_key_no_github', 'Should extract FRAIM key');
|
|
129
|
+
node_assert_1.default.strictEqual(result3.githubToken, undefined, 'GitHub token should be undefined');
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
// Cleanup
|
|
133
|
+
fs_1.default.rmSync(tempHomeDir, { recursive: true, force: true });
|
|
134
|
+
os_1.default.homedir = originalHomedir;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
(0, node_test_1.test)('add-ide command - saveGitHubTokenToConfig functionality', async () => {
|
|
138
|
+
const { saveGitHubTokenToConfig } = await Promise.resolve().then(() => __importStar(require('../src/cli/commands/add-ide')));
|
|
139
|
+
const tempHomeDir = path_1.default.join(os_1.default.tmpdir(), '.fraim-test-save-token-' + Date.now());
|
|
140
|
+
const originalHomedir = os_1.default.homedir;
|
|
141
|
+
os_1.default.homedir = () => tempHomeDir;
|
|
142
|
+
try {
|
|
143
|
+
const globalConfigDir = path_1.default.join(tempHomeDir, '.fraim');
|
|
144
|
+
const globalConfigPath = path_1.default.join(globalConfigDir, 'config.json');
|
|
145
|
+
// Create initial config without GitHub token
|
|
146
|
+
fs_1.default.mkdirSync(globalConfigDir, { recursive: true });
|
|
147
|
+
const initialConfig = {
|
|
148
|
+
version: '2.0.27',
|
|
149
|
+
apiKey: 'fraim_test_key',
|
|
150
|
+
configuredAt: new Date().toISOString(),
|
|
151
|
+
userPreferences: {
|
|
152
|
+
autoSync: true,
|
|
153
|
+
backupConfigs: true
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
fs_1.default.writeFileSync(globalConfigPath, JSON.stringify(initialConfig, null, 2));
|
|
157
|
+
// Mock console.log to capture output
|
|
158
|
+
let consoleOutput = [];
|
|
159
|
+
const originalConsoleLog = console.log;
|
|
160
|
+
console.log = (...args) => {
|
|
161
|
+
consoleOutput.push(args.join(' '));
|
|
162
|
+
};
|
|
163
|
+
try {
|
|
164
|
+
// Save GitHub token
|
|
165
|
+
saveGitHubTokenToConfig('ghp_newly_saved_token');
|
|
166
|
+
// Verify token was saved
|
|
167
|
+
const updatedConfig = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
|
|
168
|
+
node_assert_1.default.strictEqual(updatedConfig.githubToken, 'ghp_newly_saved_token', 'GitHub token should be saved');
|
|
169
|
+
node_assert_1.default.strictEqual(updatedConfig.apiKey, 'fraim_test_key', 'FRAIM key should be preserved');
|
|
170
|
+
(0, node_assert_1.default)(updatedConfig.userPreferences, 'User preferences should be preserved');
|
|
171
|
+
// Check console output
|
|
172
|
+
const output = consoleOutput.join('\n');
|
|
173
|
+
(0, node_assert_1.default)(output.includes('GitHub token saved'), 'Should show success message');
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
console.log = originalConsoleLog;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
// Cleanup
|
|
181
|
+
fs_1.default.rmSync(tempHomeDir, { recursive: true, force: true });
|
|
182
|
+
os_1.default.homedir = originalHomedir;
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
(0, node_test_1.test)('add-ide command - error handling for missing setup', async () => {
|
|
186
|
+
const { runAddIDE } = await Promise.resolve().then(() => __importStar(require('../src/cli/commands/add-ide')));
|
|
187
|
+
const tempHomeDir = path_1.default.join(os_1.default.tmpdir(), '.fraim-test-no-setup-' + Date.now());
|
|
188
|
+
const originalHomedir = os_1.default.homedir;
|
|
189
|
+
// Mock process.exit to prevent actual exit
|
|
190
|
+
let exitCode = null;
|
|
191
|
+
const originalExit = process.exit;
|
|
192
|
+
process.exit = ((code) => {
|
|
193
|
+
exitCode = code || 0;
|
|
194
|
+
throw new Error(`Process exit called with code ${code}`);
|
|
195
|
+
});
|
|
196
|
+
// Mock console.log to capture output
|
|
197
|
+
let consoleOutput = [];
|
|
198
|
+
const originalConsoleLog = console.log;
|
|
199
|
+
console.log = (...args) => {
|
|
200
|
+
consoleOutput.push(args.join(' '));
|
|
201
|
+
};
|
|
202
|
+
os_1.default.homedir = () => tempHomeDir;
|
|
203
|
+
try {
|
|
204
|
+
// Try to run add-ide without any global config
|
|
205
|
+
await runAddIDE({});
|
|
206
|
+
node_assert_1.default.fail('Should have exited due to missing config');
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
// Expected to throw due to process.exit mock
|
|
210
|
+
node_assert_1.default.strictEqual(exitCode, 1, 'Should exit with code 1');
|
|
211
|
+
const output = consoleOutput.join('\n');
|
|
212
|
+
(0, node_assert_1.default)(output.includes('No FRAIM configuration found'), 'Should show missing config error');
|
|
213
|
+
(0, node_assert_1.default)(output.includes('fraim setup'), 'Should suggest running setup');
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
// Restore mocks
|
|
217
|
+
process.exit = originalExit;
|
|
218
|
+
console.log = originalConsoleLog;
|
|
219
|
+
os_1.default.homedir = originalHomedir;
|
|
220
|
+
// Cleanup
|
|
221
|
+
fs_1.default.rmSync(tempHomeDir, { recursive: true, force: true });
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
(0, node_test_1.test)('add-ide command - handles invalid IDE name gracefully', async () => {
|
|
225
|
+
const { runAddIDE } = await Promise.resolve().then(() => __importStar(require('../src/cli/commands/add-ide')));
|
|
226
|
+
const tempHomeDir = path_1.default.join(os_1.default.tmpdir(), '.fraim-test-invalid-ide-' + Date.now());
|
|
227
|
+
const originalHomedir = os_1.default.homedir;
|
|
228
|
+
os_1.default.homedir = () => tempHomeDir;
|
|
229
|
+
// Mock process.exit to prevent actual exit
|
|
230
|
+
let exitCalled = false;
|
|
231
|
+
const originalExit = process.exit;
|
|
232
|
+
process.exit = ((code) => {
|
|
233
|
+
exitCalled = true;
|
|
234
|
+
throw new Error(`Process exit called with code ${code}`);
|
|
235
|
+
});
|
|
236
|
+
try {
|
|
237
|
+
// Create valid global config
|
|
238
|
+
const globalConfigDir = path_1.default.join(tempHomeDir, '.fraim');
|
|
239
|
+
const globalConfigPath = path_1.default.join(globalConfigDir, 'config.json');
|
|
240
|
+
fs_1.default.mkdirSync(globalConfigDir, { recursive: true });
|
|
241
|
+
const validConfig = {
|
|
242
|
+
version: '2.0.27',
|
|
243
|
+
apiKey: 'fraim_test_key',
|
|
244
|
+
githubToken: 'ghp_test_token',
|
|
245
|
+
configuredAt: new Date().toISOString()
|
|
246
|
+
};
|
|
247
|
+
fs_1.default.writeFileSync(globalConfigPath, JSON.stringify(validConfig, null, 2));
|
|
248
|
+
// Create fake IDE directory to make detectInstalledIDEs return at least one IDE
|
|
249
|
+
const fakeClaudeDir = path_1.default.join(tempHomeDir, '.claude.json');
|
|
250
|
+
fs_1.default.writeFileSync(fakeClaudeDir, '{}'); // Create fake Claude config file
|
|
251
|
+
// Mock console.log to capture output
|
|
252
|
+
let consoleOutput = [];
|
|
253
|
+
const originalConsoleLog = console.log;
|
|
254
|
+
console.log = (...args) => {
|
|
255
|
+
consoleOutput.push(args.join(' '));
|
|
256
|
+
};
|
|
257
|
+
try {
|
|
258
|
+
// Try to configure invalid IDE
|
|
259
|
+
await runAddIDE({ ide: 'nonexistent-ide' });
|
|
260
|
+
const output = consoleOutput.join('\n');
|
|
261
|
+
// Strip ANSI color codes for testing
|
|
262
|
+
const cleanOutput = output.replace(/\u001b\[[0-9;]*m/g, '');
|
|
263
|
+
(0, node_assert_1.default)(cleanOutput.includes('not supported.'), 'Should show IDE not supported error');
|
|
264
|
+
(0, node_assert_1.default)(cleanOutput.includes('fraim add-ide --list'), 'Should suggest list command');
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
// If process.exit was called, that's not what we're testing
|
|
268
|
+
if (exitCalled) {
|
|
269
|
+
node_assert_1.default.fail('Process.exit was called - config loading failed');
|
|
270
|
+
}
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
finally {
|
|
274
|
+
console.log = originalConsoleLog;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
// Cleanup
|
|
279
|
+
process.exit = originalExit;
|
|
280
|
+
fs_1.default.rmSync(tempHomeDir, { recursive: true, force: true });
|
|
281
|
+
os_1.default.homedir = originalHomedir;
|
|
282
|
+
}
|
|
283
|
+
});
|
|
@@ -29,6 +29,22 @@ const ai_manager_1 = require("../src/ai-manager/ai-manager");
|
|
|
29
29
|
(0, node_assert_1.default)(instructions.includes('iterationCount'));
|
|
30
30
|
(0, node_assert_1.default)(instructions.includes('Maximum 3 iterations'));
|
|
31
31
|
});
|
|
32
|
+
(0, node_test_1.test)('should generate implement workflow instructions', () => {
|
|
33
|
+
const context = {
|
|
34
|
+
workflowType: 'implement',
|
|
35
|
+
issueNumber: '456',
|
|
36
|
+
phase: 'implementation'
|
|
37
|
+
};
|
|
38
|
+
const instructions = aiManager.generateReviewInstructions(context);
|
|
39
|
+
(0, node_assert_1.default)(typeof instructions === 'string');
|
|
40
|
+
(0, node_assert_1.default)(instructions.includes('AI Manager Review Instructions'));
|
|
41
|
+
(0, node_assert_1.default)(instructions.includes('IMPLEMENT'));
|
|
42
|
+
(0, node_assert_1.default)(instructions.includes('456'));
|
|
43
|
+
(0, node_assert_1.default)(instructions.includes('design completeness'));
|
|
44
|
+
(0, node_assert_1.default)(instructions.includes('test quality'));
|
|
45
|
+
(0, node_assert_1.default)(instructions.includes('architecture standards'));
|
|
46
|
+
(0, node_assert_1.default)(instructions.includes('iterationCount'));
|
|
47
|
+
});
|
|
32
48
|
(0, node_test_1.test)('should throw error for unknown workflow type', () => {
|
|
33
49
|
const context = {
|
|
34
50
|
workflowType: 'unknown',
|