fraim 2.0.207 → 2.0.209

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/bin/fraim.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  try {
4
4
  require('../dist/src/cli/fraim.js');
@@ -0,0 +1,171 @@
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
+ exports.testMCPCommand = exports.runTestMCP = void 0;
40
+ const commander_1 = require("commander");
41
+ const chalk_1 = __importDefault(require("chalk"));
42
+ const fs_1 = __importDefault(require("fs"));
43
+ const path_1 = __importDefault(require("path"));
44
+ const ide_detector_1 = require("../setup/ide-detector");
45
+ const script_sync_utils_1 = require("../utils/script-sync-utils");
46
+ const testIDEConfig = async (ide) => {
47
+ const result = {
48
+ ide: ide.name,
49
+ configExists: false,
50
+ configValid: false,
51
+ mcpServers: [],
52
+ errors: []
53
+ };
54
+ const configPath = (0, ide_detector_1.expandPath)(ide.configPath);
55
+ if (!fs_1.default.existsSync(configPath)) {
56
+ result.errors.push('Config file does not exist');
57
+ return result;
58
+ }
59
+ result.configExists = true;
60
+ try {
61
+ if (ide.configFormat === 'json') {
62
+ const configContent = fs_1.default.readFileSync(configPath, 'utf8');
63
+ const config = JSON.parse(configContent);
64
+ const servers = ide.configType === 'vscode' ? config.servers : config.mcpServers;
65
+ if (servers) {
66
+ result.configValid = true;
67
+ result.mcpServers = Object.keys(servers);
68
+ }
69
+ else {
70
+ const expectedKey = ide.configType === 'vscode' ? 'servers' : 'mcpServers';
71
+ result.errors.push(`No ${expectedKey} section found`);
72
+ }
73
+ }
74
+ else if (ide.configFormat === 'toml') {
75
+ const configContent = fs_1.default.readFileSync(configPath, 'utf8');
76
+ // Simple TOML parsing for MCP servers
77
+ const serverMatches = configContent.match(/\[mcp_servers\.(\w+)\]/g);
78
+ if (serverMatches) {
79
+ result.configValid = true;
80
+ result.mcpServers = serverMatches.map(match => match.replace(/\[mcp_servers\.(\w+)\]/, '$1'));
81
+ }
82
+ else {
83
+ result.errors.push('No mcp_servers sections found');
84
+ }
85
+ }
86
+ }
87
+ catch (error) {
88
+ result.errors.push(`Failed to parse config: ${error instanceof Error ? error.message : 'Unknown error'}`);
89
+ }
90
+ return result;
91
+ };
92
+ const checkGlobalSetup = () => {
93
+ const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
94
+ return fs_1.default.existsSync(globalConfigPath);
95
+ };
96
+ const runTestMCP = async () => {
97
+ console.log(chalk_1.default.blue('šŸ” Testing MCP configuration...\n'));
98
+ // Check global setup
99
+ if (!checkGlobalSetup()) {
100
+ console.log(chalk_1.default.red('āŒ Global FRAIM setup not found.'));
101
+ console.log(chalk_1.default.yellow('Please run: fraim setup --key=<your-fraim-key>'));
102
+ return;
103
+ }
104
+ console.log(chalk_1.default.green('āœ… Global FRAIM setup found'));
105
+ // Detect IDEs
106
+ const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
107
+ if (detectedIDEs.length === 0) {
108
+ console.log(chalk_1.default.yellow('āš ļø No supported IDEs detected.'));
109
+ return;
110
+ }
111
+ console.log(chalk_1.default.blue(`\nšŸ” Testing ${detectedIDEs.length} detected IDEs...\n`));
112
+ const results = await Promise.all(detectedIDEs.map(ide => testIDEConfig(ide)));
113
+ let totalConfigured = 0;
114
+ let totalWithFRAIM = 0;
115
+ for (const result of results) {
116
+ console.log(chalk_1.default.white(`šŸ“± ${result.ide}`));
117
+ if (!result.configExists) {
118
+ console.log(chalk_1.default.red(' āŒ No MCP config found'));
119
+ console.log(chalk_1.default.gray(` šŸ’” Run: fraim setup --ide=${result.ide.toLowerCase()}`));
120
+ }
121
+ else if (!result.configValid) {
122
+ console.log(chalk_1.default.yellow(' āš ļø Config exists but invalid'));
123
+ result.errors.forEach(error => {
124
+ console.log(chalk_1.default.red(` āŒ ${error}`));
125
+ });
126
+ }
127
+ else {
128
+ totalConfigured++;
129
+ console.log(chalk_1.default.green(` āœ… MCP config valid (${result.mcpServers.length} servers)`));
130
+ // Check for essential servers
131
+ const { BASE_MCP_SERVERS } = await Promise.resolve().then(() => __importStar(require('../mcp/mcp-server-registry')));
132
+ const essentialServers = BASE_MCP_SERVERS.map(s => s.id); // fraim, git, playwright
133
+ const hasEssential = essentialServers.filter(server => result.mcpServers.includes(server));
134
+ if (hasEssential.includes('fraim')) {
135
+ totalWithFRAIM++;
136
+ console.log(chalk_1.default.green(' āœ… FRAIM server configured'));
137
+ }
138
+ else {
139
+ console.log(chalk_1.default.yellow(' āš ļø FRAIM server missing'));
140
+ }
141
+ if (hasEssential.length > 1) {
142
+ console.log(chalk_1.default.green(` āœ… ${hasEssential.length - 1} additional servers: ${hasEssential.filter(s => s !== 'fraim').join(', ')}`));
143
+ }
144
+ const missingEssential = essentialServers.filter(server => !result.mcpServers.includes(server));
145
+ if (missingEssential.length > 0) {
146
+ console.log(chalk_1.default.yellow(` āš ļø Missing servers: ${missingEssential.join(', ')}`));
147
+ }
148
+ }
149
+ console.log(); // Empty line
150
+ }
151
+ // Summary
152
+ console.log(chalk_1.default.blue('šŸ“Š Summary:'));
153
+ console.log(chalk_1.default.green(` āœ… ${totalConfigured}/${detectedIDEs.length} IDEs have valid MCP configs`));
154
+ console.log(chalk_1.default.green(` āœ… ${totalWithFRAIM}/${detectedIDEs.length} IDEs have FRAIM configured`));
155
+ if (totalWithFRAIM === 0) {
156
+ console.log(chalk_1.default.red('\nāŒ No IDEs have FRAIM configured!'));
157
+ console.log(chalk_1.default.yellow('šŸ’” Run: fraim setup --key=<your-fraim-key>'));
158
+ }
159
+ else if (totalWithFRAIM < detectedIDEs.length) {
160
+ console.log(chalk_1.default.yellow(`\nāš ļø ${detectedIDEs.length - totalWithFRAIM} IDEs missing FRAIM configuration`));
161
+ console.log(chalk_1.default.yellow('šŸ’” Run: fraim setup to configure remaining IDEs'));
162
+ }
163
+ else {
164
+ console.log(chalk_1.default.green('\nšŸŽ‰ All detected IDEs have FRAIM configured!'));
165
+ console.log(chalk_1.default.blue('šŸ’” Try running: fraim init-project in any project'));
166
+ }
167
+ };
168
+ exports.runTestMCP = runTestMCP;
169
+ exports.testMCPCommand = new commander_1.Command('test-mcp')
170
+ .description('Test MCP server configurations for all detected IDEs')
171
+ .action(exports.runTestMCP);
@@ -0,0 +1,242 @@
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
+ exports.runFirstRunExperience = void 0;
40
+ const prompts_1 = __importDefault(require("prompts"));
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 script_sync_utils_1 = require("../utils/script-sync-utils");
46
+ const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
47
+ const loadGlobalConfig = () => {
48
+ const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
49
+ if (!fs_1.default.existsSync(globalConfigPath)) {
50
+ return null;
51
+ }
52
+ try {
53
+ const config = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
54
+ return {
55
+ fraimKey: config.apiKey,
56
+ githubToken: config.githubToken
57
+ };
58
+ }
59
+ catch (e) {
60
+ return null;
61
+ }
62
+ };
63
+ const checkMCPConfigurations = () => {
64
+ const sources = [];
65
+ // Check Kiro MCP config
66
+ const kiroConfigPath = path_1.default.join(os_1.default.homedir(), '.kiro', 'settings', 'mcp.json');
67
+ if (fs_1.default.existsSync(kiroConfigPath)) {
68
+ try {
69
+ const kiroConfig = JSON.parse(fs_1.default.readFileSync(kiroConfigPath, 'utf8'));
70
+ if (kiroConfig.mcpServers?.fraim) {
71
+ sources.push('Kiro');
72
+ }
73
+ }
74
+ catch (e) {
75
+ // Ignore parsing errors
76
+ }
77
+ }
78
+ // Check Claude Desktop / Cowork config
79
+ const claudeConfigPath = path_1.default.join(os_1.default.homedir(), 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json');
80
+ if (fs_1.default.existsSync(claudeConfigPath)) {
81
+ try {
82
+ const claudeConfig = JSON.parse(fs_1.default.readFileSync(claudeConfigPath, 'utf8'));
83
+ if (claudeConfig.mcpServers?.fraim) {
84
+ sources.push('Claude Desktop / Cowork');
85
+ }
86
+ }
87
+ catch (e) {
88
+ // Ignore parsing errors
89
+ }
90
+ }
91
+ // Check macOS Claude Desktop / Cowork config path
92
+ const claudeMacPath = path_1.default.join(os_1.default.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
93
+ if (fs_1.default.existsSync(claudeMacPath)) {
94
+ try {
95
+ const claudeConfig = JSON.parse(fs_1.default.readFileSync(claudeMacPath, 'utf8'));
96
+ if (claudeConfig.mcpServers?.fraim) {
97
+ sources.push('Claude Desktop / Cowork (macOS)');
98
+ }
99
+ }
100
+ catch (e) {
101
+ // Ignore parsing errors
102
+ }
103
+ }
104
+ // Check VS Code MCP config (uses "servers" key)
105
+ const vscodePaths = [
106
+ path_1.default.join(os_1.default.homedir(), 'AppData', 'Roaming', 'Code', 'User', 'mcp.json'),
107
+ path_1.default.join(os_1.default.homedir(), 'Library', 'Application Support', 'Code', 'User', 'mcp.json'),
108
+ path_1.default.join(os_1.default.homedir(), '.config', 'Code', 'User', 'mcp.json')
109
+ ];
110
+ for (const vscodePath of vscodePaths) {
111
+ if (fs_1.default.existsSync(vscodePath)) {
112
+ try {
113
+ const vscodeConfig = JSON.parse(fs_1.default.readFileSync(vscodePath, 'utf8'));
114
+ if (vscodeConfig.servers?.fraim) {
115
+ sources.push('VSCode');
116
+ break;
117
+ }
118
+ }
119
+ catch (e) {
120
+ // Ignore parsing errors
121
+ }
122
+ }
123
+ }
124
+ return {
125
+ found: sources.length > 0,
126
+ sources
127
+ };
128
+ };
129
+ const runFirstRunExperience = async () => {
130
+ // Skip interactive setup in CI/Test environments
131
+ if (process.env.CI === 'true' || process.env.TEST_MODE === 'true') {
132
+ console.log(chalk_1.default.yellow('ā„¹ļø Skipping interactive first-run experience (CI/TEST_MODE detected)'));
133
+ return;
134
+ }
135
+ console.log(chalk_1.default.blue('\nšŸ‘‹ Welcome to FRAIM! Let\'s get you set up.\n'));
136
+ // Check for existing configuration
137
+ const globalConfig = loadGlobalConfig();
138
+ const mcpCheck = checkMCPConfigurations();
139
+ if (globalConfig && globalConfig.fraimKey) {
140
+ console.log(chalk_1.default.green('āœ… Found existing FRAIM configuration'));
141
+ if (mcpCheck.found) {
142
+ console.log(chalk_1.default.green(`āœ… Found FRAIM MCP configuration in: ${mcpCheck.sources.join(', ')}`));
143
+ console.log(chalk_1.default.blue('šŸŽ‰ You\'re all set! FRAIM is ready to use.\n'));
144
+ }
145
+ else {
146
+ console.log(chalk_1.default.yellow('āš ļø FRAIM key found but no MCP configuration detected.'));
147
+ console.log(chalk_1.default.cyan('šŸ’” Consider running "fraim add-ide" to configure your IDE.\n'));
148
+ }
149
+ }
150
+ else if (mcpCheck.found) {
151
+ console.log(chalk_1.default.green(`āœ… Found FRAIM MCP configuration in: ${mcpCheck.sources.join(', ')}`));
152
+ console.log(chalk_1.default.yellow('āš ļø But no global FRAIM configuration found.'));
153
+ console.log(chalk_1.default.cyan('šŸ’” Your MCP setup looks good! Skipping key setup.\n'));
154
+ }
155
+ else {
156
+ // No existing configuration found - ask for FRAIM key
157
+ console.log(chalk_1.default.yellow('šŸ” No existing FRAIM configuration detected.\n'));
158
+ let response;
159
+ try {
160
+ response = await (0, prompts_1.default)({
161
+ type: 'text',
162
+ name: 'fraimKey',
163
+ message: 'Do you have a FRAIM key? (Input key or press Enter to skip)',
164
+ validate: (value) => true // Optional input
165
+ });
166
+ }
167
+ catch (e) {
168
+ console.warn(chalk_1.default.yellow('\nāš ļø Interactive prompts experienced an issue. Skipping setup.\n'));
169
+ return;
170
+ }
171
+ if (response.fraimKey && response.fraimKey.trim().length > 0) {
172
+ const key = response.fraimKey.trim();
173
+ console.log(chalk_1.default.green('\nāœ… Key received.'));
174
+ console.log(chalk_1.default.yellow('\nPlease add the following to your IDE\'s MCP config (e.g. claude_desktop_config.json):'));
175
+ const mcpConfig = {
176
+ mcpServers: {
177
+ fraim: {
178
+ serverUrl: "https://fraim.wellnessatwork.me",
179
+ headers: {
180
+ "x-api-key": key
181
+ }
182
+ }
183
+ }
184
+ };
185
+ console.log(chalk_1.default.cyan(JSON.stringify(mcpConfig, null, 2)));
186
+ console.log('\n');
187
+ }
188
+ else {
189
+ console.log(chalk_1.default.yellow('\nā„¹ļø If you need a key, please email sid.mathur@gmail.com to request one.\n'));
190
+ }
191
+ }
192
+ // 2. Ask about Architecture Document
193
+ const archResponse = await (0, prompts_1.default)({
194
+ type: 'confirm',
195
+ name: 'hasArchDoc',
196
+ message: 'Do you have an architecture document for this project?',
197
+ initial: false
198
+ });
199
+ if (!archResponse.hasArchDoc) {
200
+ console.log(chalk_1.default.yellow('\nšŸ’” To create an architecture document, please ask your IDE Agent:'));
201
+ console.log(chalk_1.default.cyan(' "Run the bootstrap/create-architecture workflow"'));
202
+ console.log(chalk_1.default.gray(' (This ensures your agent understands your codebase structure)\n'));
203
+ }
204
+ else {
205
+ const pathResponse = await (0, prompts_1.default)({
206
+ type: 'text',
207
+ name: 'archDocPath',
208
+ message: 'Please provide the relative path to your architecture document:',
209
+ initial: 'docs/architecture.md',
210
+ validate: (value) => value.length > 0 ? true : 'Path cannot be empty'
211
+ });
212
+ if (pathResponse.archDocPath) {
213
+ const fs = await Promise.resolve().then(() => __importStar(require('fs')));
214
+ const path = await Promise.resolve().then(() => __importStar(require('path')));
215
+ const resolvedPath = path.resolve(process.cwd(), pathResponse.archDocPath);
216
+ if (fs.existsSync(resolvedPath)) {
217
+ try {
218
+ const configPath = (0, project_fraim_paths_1.getWorkspaceConfigPath)(process.cwd());
219
+ if (fs.existsSync(configPath)) {
220
+ const configContent = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
221
+ if (!configContent.customizations)
222
+ configContent.customizations = {};
223
+ configContent.customizations.architectureDoc = pathResponse.archDocPath;
224
+ fs.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
225
+ console.log(chalk_1.default.green(`\nāœ… Linked architecture document: ${pathResponse.archDocPath}`));
226
+ }
227
+ else {
228
+ console.log(chalk_1.default.red(`\nāŒ ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json')} not found. Could not save preference.`));
229
+ }
230
+ }
231
+ catch (e) {
232
+ console.error(chalk_1.default.red('\nāŒ Failed to update config:'), e);
233
+ }
234
+ }
235
+ else {
236
+ console.log(chalk_1.default.yellow(`\nāš ļø File not found at ${pathResponse.archDocPath}. We'll skip linking it for now.`));
237
+ }
238
+ }
239
+ }
240
+ console.log(chalk_1.default.green(`\nāœ… Jobs are installed in ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('ai-employee/jobs')} and ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('ai-manager/jobs')}.\n`));
241
+ };
242
+ exports.runFirstRunExperience = runFirstRunExperience;
@@ -56,17 +56,20 @@ function buildDeferredToolBootstrapSection(profile) {
56
56
  function buildFraimInvocationBody(profile = 'none') {
57
57
  return `Follow this process:
58
58
 
59
- ${buildDeferredToolBootstrapSection(profile)}1. **If the user did not specify a FRAIM job or topic**:
59
+ ${buildDeferredToolBootstrapSection(profile)}1. **Confirm FRAIM activation**:
60
+ Use this process only when the user explicitly invokes FRAIM, names a FRAIM job, asks what FRAIM job to run, or the active surface has already selected a FRAIM job. For ordinary requests, answer or work normally; do not scan FRAIM stubs first.
61
+
62
+ 2. **If the user did not specify a FRAIM job or topic after activation**:
60
63
  If local FRAIM job stubs are present in the workspace, inspect those first and match the request locally. Also inspect \`fraim/personalized-employee/jobs/\` for local overrides or repo-specific jobs. If local files are missing or you cannot inspect workspace files, call \`list_fraim_jobs()\` to view the full catalog, including any proxy-discoverable personalized jobs.
61
64
 
62
- 2. **Find the match**:
63
- Match the user's request to a FRAIM job from the local stub catalog, \`fraim/personalized-employee/jobs/\`, or the full \`list_fraim_jobs()\` response. If no job matches, try a likely FRAIM skill with \`get_fraim_file({ path: "skills/<likely-category>/<argument>.md" })\` and confirm the match with the user.
65
+ 3. **Find the match**:
66
+ If the user names an exact FRAIM job, call \`get_fraim_job({ job: "<job-name>" })\` directly. Otherwise, match the user's request to a FRAIM job from the local stub catalog, \`fraim/personalized-employee/jobs/\`, or the full \`list_fraim_jobs()\` response. If no exact or high-confidence job match exists, say that no FRAIM job matches and continue with normal tools or ask one concise clarification. Do not pick the nearest catalog job.
64
67
 
65
- 3. **Load the full content**:
68
+ 4. **Load the full content**:
66
69
  - For jobs, call \`get_fraim_job({ job: "<matched-job-name>" })\`.
67
70
  - For skills, use the content returned by \`get_fraim_file(...)\`.
68
71
 
69
- 4. **Execute**:
72
+ 5. **Execute**:
70
73
  - For jobs, follow the phased instructions and use \`seekMentoring\` when the job requires phase transitions.
71
74
  - For skills, apply the skill steps directly to the user's current context.
72
75
  `;
@@ -69,8 +69,12 @@ This repository uses FRAIM.
69
69
  - Skills under \`${employeeSkillsPath}/\` are reusable capabilities that jobs compose.
70
70
  - Rules under \`${employeeRulesPath}/\` are always-on constraints and conventions.
71
71
  - Repo-specific overrides and learning artifacts live under \`${personalizedRootPath}/\` and take precedence over synced baseline content.
72
- - Before acting on any user request, scan the job stubs under \`${employeeJobsPath}/\` and \`${managerJobsPath}/\` to identify the most appropriate job. Read stub filenames and their Intent/Outcome sections to match the request to the right job.
72
+ - Use FRAIM when the user explicitly invokes FRAIM, names a FRAIM job, asks what FRAIM job to run, or the active surface has already selected a FRAIM job.
73
+ - For ordinary requests, answer or work normally. Do not scan FRAIM stubs first.
74
+ - If the user names an exact FRAIM job, call \`get_fraim_job({ job: "<job-name>" })\` directly.
75
+ - When FRAIM routing is active but the job is not exact, scan the job stubs under \`${employeeJobsPath}/\` and \`${managerJobsPath}/\` to identify the most appropriate job. Read stub filenames and their Intent/Outcome sections before using catalog tools.
73
76
  - Once you identify the relevant job, call \`get_fraim_job({ job: "<job-name>" })\` to get the full phased instructions.
77
+ - If no exact or high-confidence job match exists, say that no FRAIM job matches and continue with normal tools or ask one concise clarification.
74
78
  - For deeper capability detail, call \`get_fraim_file({ path: "skills/<category>/<skill-name>.md" })\` or \`get_fraim_file({ path: "rules/<category>/<rule-name>.md" })\`.
75
79
  - Read \`${projectRulesPath}\` if it exists before doing work.
76
80
  - When users ask for next step recommendations, use recommend-next-job skill under \`${employeeSkillsPath}/\` to gather context before suggesting jobs.
@@ -91,7 +95,11 @@ ${(0, ide_invocation_surfaces_1.buildFraimInvocationBody)('generic-tool-discover
91
95
  - FRAIM skills are reusable capabilities jobs compose.
92
96
  - FRAIM rules are always-on constraints and conventions.
93
97
  - Repo-specific overrides and learnings live under \`${personalizedRootPath}/\`.
94
- - Use the stubs to identify which job to invoke before fetching full content with FRAIM MCP tools.
98
+ - Use FRAIM when the user explicitly invokes FRAIM, names a FRAIM job, asks what FRAIM job to run, or the active surface has already selected a FRAIM job.
99
+ - For ordinary requests, answer or work normally. Do not scan FRAIM stubs first.
100
+ - If the user names an exact FRAIM job, fetch that full job directly with FRAIM MCP tools.
101
+ - When FRAIM routing is active but the job is not exact, use local stubs to identify which job to invoke before fetching full content with FRAIM MCP tools.
102
+ - If no exact or high-confidence job match exists, say that no FRAIM job matches and continue with normal tools or ask one concise clarification.
95
103
  - **Job stubs are for discovery only.** Never execute a job from stub content - always call \`get_fraim_job({ job: "<job-name>" })\` first.
96
104
  `);
97
105
  const fraimReadme = `# FRAIM Catalog
@@ -104,7 +112,7 @@ This directory is the repository-visible FRAIM surface.
104
112
  - \`ai-employee/rules/\`: rule stubs
105
113
  - \`personalized-employee/\`: repo-specific overrides and learnings
106
114
 
107
- Use the stubs here to discover which FRAIM job, skill, or rule is relevant, then load the full content through FRAIM MCP tools.
115
+ When FRAIM routing is active and no exact FRAIM job is named, use the stubs here to discover which FRAIM job, skill, or rule is relevant, then load the full content through FRAIM MCP tools. If an exact FRAIM job is named, load it directly. For ordinary requests, do not scan this catalog first.
108
116
  `;
109
117
  const vscodePrompt = `# FRAIM
110
118
 
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readFraimConfigRaw = readFraimConfigRaw;
4
+ exports.mergeFraimConfig = mergeFraimConfig;
5
+ exports.writeFraimConfigUpdate = writeFraimConfigUpdate;
6
+ exports.writeFraimConfig = writeFraimConfig;
7
+ const fs_1 = require("fs");
8
+ const path_1 = require("path");
9
+ const config_loader_1 = require("./config-loader");
10
+ const types_1 = require("./types");
11
+ function isPlainObject(value) {
12
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
13
+ }
14
+ function deepMerge(baseValue, updateValue) {
15
+ if (updateValue === undefined) {
16
+ return baseValue;
17
+ }
18
+ if (Array.isArray(updateValue)) {
19
+ return [...updateValue];
20
+ }
21
+ if (!isPlainObject(updateValue)) {
22
+ return updateValue;
23
+ }
24
+ const baseObject = isPlainObject(baseValue) ? baseValue : {};
25
+ const merged = { ...baseObject };
26
+ for (const [key, value] of Object.entries(updateValue)) {
27
+ merged[key] = deepMerge(baseObject[key], value);
28
+ }
29
+ return merged;
30
+ }
31
+ function ensureWritableFraimConfigShape(rawConfig) {
32
+ const config = { ...rawConfig };
33
+ if (typeof config.version !== 'string' || config.version.trim().length === 0) {
34
+ config.version = types_1.DEFAULT_FRAIM_CONFIG.version;
35
+ }
36
+ const projectConfig = isPlainObject(config.project) ? config.project : {};
37
+ config.project = {
38
+ ...types_1.DEFAULT_FRAIM_CONFIG.project,
39
+ ...projectConfig
40
+ };
41
+ const customizationsConfig = isPlainObject(config.customizations) ? config.customizations : {};
42
+ config.customizations = {
43
+ ...types_1.DEFAULT_FRAIM_CONFIG.customizations,
44
+ ...customizationsConfig
45
+ };
46
+ return config;
47
+ }
48
+ function readFraimConfigRaw(configPath) {
49
+ if (!(0, fs_1.existsSync)(configPath)) {
50
+ return {};
51
+ }
52
+ const parsed = JSON.parse((0, fs_1.readFileSync)(configPath, 'utf8'));
53
+ if (!isPlainObject(parsed)) {
54
+ throw new Error('FRAIM config must contain a JSON object at the top level.');
55
+ }
56
+ return parsed;
57
+ }
58
+ function mergeFraimConfig(existingRawConfig, update) {
59
+ const rawConfig = ensureWritableFraimConfigShape(deepMerge(existingRawConfig, update));
60
+ return {
61
+ config: (0, config_loader_1.normalizeFraimConfig)(rawConfig),
62
+ created: Object.keys(existingRawConfig).length === 0,
63
+ rawConfig
64
+ };
65
+ }
66
+ function writeFraimConfigUpdate(configPath, update) {
67
+ const existingRawConfig = readFraimConfigRaw(configPath);
68
+ const result = mergeFraimConfig(existingRawConfig, update);
69
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(configPath), { recursive: true });
70
+ (0, fs_1.writeFileSync)(configPath, JSON.stringify(result.rawConfig, null, 2));
71
+ return result;
72
+ }
73
+ function writeFraimConfig(configPath, config) {
74
+ return writeFraimConfigUpdate(configPath, config);
75
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEPRECATED_FRAIM_JOB_NAMES = void 0;
4
+ exports.resolveFraimJobName = resolveFraimJobName;
5
+ exports.isListableFraimJob = isListableFraimJob;
6
+ const DEPRECATED_TO_CANONICAL_JOB_MAP = {
7
+ 'learn-and-scale': 'upskill-employee',
8
+ 'model-behavior': 'upskill-employee',
9
+ 'promote-learning': 'upskill-employee',
10
+ 'refine-jobs': 'upskill-employee',
11
+ 'refine-skills': 'upskill-employee'
12
+ };
13
+ const DIRECT_JOB_ALIASES = {
14
+ 'sleep on learnings': 'sleep-on-learnings'
15
+ };
16
+ exports.DEPRECATED_FRAIM_JOB_NAMES = new Set(Object.keys(DEPRECATED_TO_CANONICAL_JOB_MAP));
17
+ function normalizeJobLookupInput(input) {
18
+ return input.trim().toLowerCase().replace(/[_\s]+/g, '-');
19
+ }
20
+ function resolveFraimJobName(input) {
21
+ const normalizedJobName = normalizeJobLookupInput(input);
22
+ const directAliasTarget = DIRECT_JOB_ALIASES[input.trim().toLowerCase()];
23
+ if (directAliasTarget) {
24
+ return {
25
+ requestedJobName: input,
26
+ normalizedJobName,
27
+ canonicalJobName: directAliasTarget
28
+ };
29
+ }
30
+ const deprecatedAliasTarget = DEPRECATED_TO_CANONICAL_JOB_MAP[normalizedJobName];
31
+ if (deprecatedAliasTarget) {
32
+ return {
33
+ requestedJobName: input,
34
+ normalizedJobName,
35
+ canonicalJobName: deprecatedAliasTarget,
36
+ deprecatedAliasTarget
37
+ };
38
+ }
39
+ return {
40
+ requestedJobName: input,
41
+ normalizedJobName,
42
+ canonicalJobName: normalizedJobName
43
+ };
44
+ }
45
+ function isListableFraimJob(jobName) {
46
+ return !exports.DEPRECATED_FRAIM_JOB_NAMES.has(normalizeJobLookupInput(jobName));
47
+ }