fraim-framework 2.0.177 → 2.0.179

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.
@@ -44,17 +44,7 @@ const prompts_1 = __importDefault(require("prompts"));
44
44
  const ide_detector_1 = require("./ide-detector");
45
45
  const mcp_config_generator_1 = require("./mcp-config-generator");
46
46
  const codex_local_config_1 = require("./codex-local-config");
47
- const provider_registry_1 = require("../providers/provider-registry");
48
- const normalizePlatformTokens = (tokenInput) => {
49
- if (!tokenInput)
50
- return {};
51
- if (typeof tokenInput === 'string') {
52
- return tokenInput ? { github: tokenInput } : {};
53
- }
54
- return tokenInput;
55
- };
56
- const promptForIDESelection = async (detectedIDEs, tokenInput) => {
57
- const tokens = normalizePlatformTokens(tokenInput);
47
+ const promptForIDESelection = async (detectedIDEs) => {
58
48
  if (process.env.FRAIM_NON_INTERACTIVE) {
59
49
  console.log(chalk_1.default.yellow(`\nℹ️ Non-interactive mode: configuring all detected IDEs (${detectedIDEs.length})`));
60
50
  return detectedIDEs;
@@ -70,17 +60,8 @@ const promptForIDESelection = async (detectedIDEs, tokenInput) => {
70
60
  console.log(chalk_1.default.blue('FRAIM will add these MCP servers to selected IDEs:'));
71
61
  console.log(chalk_1.default.gray(' • fraim (required for FRAIM jobs)'));
72
62
  console.log(chalk_1.default.gray(' • git (version control integration)'));
73
- // Show configured provider MCP servers dynamically
74
- const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
75
- for (const providerId of allProviderIds) {
76
- if (tokens[providerId]) {
77
- const provider = await (0, provider_registry_1.getProvider)(providerId);
78
- if (provider) {
79
- console.log(chalk_1.default.gray(` - ${providerId} (${provider.displayName} API access)`));
80
- }
81
- }
82
- }
83
63
  console.log(chalk_1.default.gray(' • playwright (browser automation)'));
64
+ console.log(chalk_1.default.blue('\n💡 Run "fraim add-provider <provider>" after setup to connect GitHub, GitLab, Jira, or other tools.'));
84
65
  console.log(chalk_1.default.yellow('\n💡 Existing MCP servers will be preserved - only missing servers will be added.'));
85
66
  const response = await (0, prompts_1.default)({
86
67
  type: 'text',
@@ -153,8 +134,7 @@ const validateSetupResults = async (configuredIDEs) => {
153
134
  }
154
135
  };
155
136
  exports.validateSetupResults = validateSetupResults;
156
- const configureIDEMCP = async (ide, fraimKey, tokenInput, providerConfigs) => {
157
- const tokens = normalizePlatformTokens(tokenInput);
137
+ const configureIDEMCP = async (ide, fraimKey) => {
158
138
  const configPath = (0, ide_detector_1.expandPath)(ide.configPath);
159
139
  console.log(chalk_1.default.blue(`🔧 Configuring ${ide.name}...`));
160
140
  // Create backup
@@ -185,11 +165,9 @@ const configureIDEMCP = async (ide, fraimKey, tokenInput, providerConfigs) => {
185
165
  existingTomlContent = fs_1.default.readFileSync(configPath, 'utf8');
186
166
  console.log(chalk_1.default.gray(` 📋 Found existing TOML config`));
187
167
  }
188
- const newTomlContent = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens, providerConfigs);
168
+ const newTomlContent = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, {});
189
169
  const { getAllMCPServerIds } = await Promise.resolve().then(() => __importStar(require('../mcp/mcp-server-registry')));
190
- const baseServerIds = getAllMCPServerIds();
191
- const providerServerIds = Object.keys(tokens).filter(id => tokens[id]);
192
- const serversToAdd = [...baseServerIds, ...providerServerIds];
170
+ const serversToAdd = getAllMCPServerIds();
193
171
  const mergeResult = (0, mcp_config_generator_1.mergeTomlMCPServers)(existingTomlContent, newTomlContent, serversToAdd);
194
172
  fs_1.default.writeFileSync(configPath, mergeResult.content);
195
173
  mergeResult.addedServers.forEach(server => {
@@ -204,7 +182,7 @@ const configureIDEMCP = async (ide, fraimKey, tokenInput, providerConfigs) => {
204
182
  }
205
183
  else {
206
184
  // For JSON configs - intelligent merging
207
- const newConfig = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens, providerConfigs);
185
+ const newConfig = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, {});
208
186
  const newMCPServers = newConfig[serversKey] || newConfig.mcpServers || {};
209
187
  // Merge MCP servers intelligently
210
188
  const mergedMCPServers = { ...existingMCPServers };
@@ -251,8 +229,7 @@ const configureIDEMCP = async (ide, fraimKey, tokenInput, providerConfigs) => {
251
229
  console.log(chalk_1.default.green(` ✅ ${status} local Codex config: ${localResult.path}`));
252
230
  }
253
231
  };
254
- const autoConfigureMCP = async (fraimKey, tokenInput, selectedIDEs, providerConfigs) => {
255
- const tokens = normalizePlatformTokens(tokenInput);
232
+ const autoConfigureMCP = async (fraimKey, selectedIDEs) => {
256
233
  const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
257
234
  if (detectedIDEs.length === 0 && (!selectedIDEs || selectedIDEs.length === 0)) {
258
235
  console.log(chalk_1.default.yellow('⚠️ No supported IDEs detected.'));
@@ -294,7 +271,7 @@ const autoConfigureMCP = async (fraimKey, tokenInput, selectedIDEs, providerConf
294
271
  }
295
272
  else {
296
273
  // Interactive selection
297
- idesToConfigure = await (0, exports.promptForIDESelection)(detectedIDEs, tokens);
274
+ idesToConfigure = await (0, exports.promptForIDESelection)(detectedIDEs);
298
275
  }
299
276
  if (idesToConfigure.length === 0) {
300
277
  console.log(chalk_1.default.yellow('⚠️ No IDEs selected for configuration.'));
@@ -308,7 +285,7 @@ const autoConfigureMCP = async (fraimKey, tokenInput, selectedIDEs, providerConf
308
285
  };
309
286
  for (const ide of idesToConfigure) {
310
287
  try {
311
- await configureIDEMCP(ide, fraimKey, tokens, providerConfigs);
288
+ await configureIDEMCP(ide, fraimKey);
312
289
  results.successful.push(ide.name);
313
290
  }
314
291
  catch (error) {
@@ -24,15 +24,25 @@ const checkMultiplePaths = (paths) => {
24
24
  return paths.some(p => fs_1.default.existsSync(expandPath(p)));
25
25
  };
26
26
  // Issue #646 (Bug 2): a config directory left behind after uninstall (e.g. ~/.cursor,
27
- // ~/Library/Application Support/Cursor) made config-surface detection report a GUI app
28
- // as "Installed" when it was not. On macOS an installed GUI app keeps its .app bundle,
29
- // which is the reliable signal. So on macOS we require app-bundle evidence for GUI apps;
30
- // on Windows/Linux we keep the existing config-surface check unchanged (no reported
31
- // false-positive there, and those install paths are not validated on this platform).
32
- const macAppBundlePaths = (appName) => [
27
+ // ~/Library/Application Support/Cursor) made config-surface detection report Cursor
28
+ // as "Installed" when it was not. App evidence is the reliable signal for Cursor;
29
+ // other GUI surfaces keep their existing config-surface checks for compatibility
30
+ // with the #640 first-run detection contract.
31
+ const appBundlePaths = (appName) => [
33
32
  `/Applications/${appName}.app`,
34
33
  `~/Applications/${appName}.app`,
35
34
  ];
35
+ const windowsUserAppPaths = (appName) => {
36
+ switch (appName) {
37
+ case 'Cursor':
38
+ return [
39
+ '~/AppData/Local/Programs/Cursor/Cursor.exe',
40
+ '~/AppData/Local/Cursor/Cursor.exe',
41
+ ];
42
+ default:
43
+ return [];
44
+ }
45
+ };
36
46
  // Issue #646 follow-up: a user may run a GUI app from a non-standard location
37
47
  // (e.g. straight from a mounted DMG: /Volumes/.../Cursor.app) that the
38
48
  // /Applications checks miss. A running process of the app is definitive proof it
@@ -48,10 +58,19 @@ const isMacAppRunning = (appName) => {
48
58
  const result = (0, child_process_1.spawnSync)('pgrep', ['-f', `${appName}.app/Contents`], { encoding: 'utf8', timeout: 1500 });
49
59
  return result.status === 0 && Boolean((result.stdout || '').trim());
50
60
  };
51
- const guiAppDetect = (configSurfaceCheck, macAppName) => {
61
+ const hasAppEvidence = (appName) => {
62
+ const evidencePaths = process.platform === 'win32'
63
+ ? [...appBundlePaths(appName), ...windowsUserAppPaths(appName)]
64
+ : appBundlePaths(appName);
65
+ return checkMultiplePaths(evidencePaths) || isMacAppRunning(appName);
66
+ };
67
+ const guiAppDetect = (configSurfaceCheck, appName, options = {}) => {
52
68
  return () => {
69
+ if (options.requireAppEvidence) {
70
+ return hasAppEvidence(appName);
71
+ }
53
72
  if (process.platform === 'darwin') {
54
- return checkMultiplePaths(macAppBundlePaths(macAppName)) || isMacAppRunning(macAppName);
73
+ return hasAppEvidence(appName);
55
74
  }
56
75
  return configSurfaceCheck();
57
76
  };
@@ -81,12 +100,12 @@ const detectVSCode = () => {
81
100
  ];
82
101
  return checkMultiplePaths(paths);
83
102
  };
84
- const detectCursor = () => {
103
+ const detectCursorMcpConfig = () => {
85
104
  const paths = [
86
- '~/.cursor',
87
- '~/Library/Application Support/Cursor',
88
- '~/AppData/Roaming/Cursor',
89
- '~/.config/Cursor'
105
+ '~/.cursor/mcp.json',
106
+ '~/Library/Application Support/Cursor/mcp.json',
107
+ '~/AppData/Roaming/Cursor/mcp.json',
108
+ '~/.config/Cursor/mcp.json'
90
109
  ];
91
110
  return checkMultiplePaths(paths);
92
111
  };
@@ -104,6 +123,7 @@ const detectGeminiCli = () => {
104
123
  };
105
124
  const detectGeminiSurface = () => {
106
125
  const paths = [
126
+ '~/.gemini',
107
127
  '~/.gemini/settings.json',
108
128
  '~/AppData/Roaming/gemini/settings.json',
109
129
  '~/.config/gemini/settings.json'
@@ -196,7 +216,7 @@ exports.IDE_CONFIGS = [
196
216
  configType: 'kiro',
197
217
  adapterConfigType: 'cursor',
198
218
  invocationProfile: 'cursor-mention',
199
- detectMethod: guiAppDetect(detectCursor, 'Cursor'),
219
+ detectMethod: () => detectCursorMcpConfig() || hasAppEvidence('Cursor'),
200
220
  alternativePaths: [
201
221
  '~/Library/Application Support/Cursor/mcp.json',
202
222
  '~/AppData/Roaming/Cursor/mcp.json',
@@ -40,7 +40,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
40
40
  personaKey: 'beza',
41
41
  bundleId: 'persona-beza-core',
42
42
  catalogMetadata: buildCatalogMetadata('beza', ['competitive-analysis', 'review-business-strategy', 'business-plan-creation']),
43
- protectedJobs: ['competitive-analysis', 'review-business-strategy', 'business-plan-creation', 'problem-statement-crystallization', 'business-idea-validation-and-scoping', 'founder-market-fit-analysis', 'rfp-response-preparation', 'aws-activate-credits-application', 'community-funding-preparation', 'fundraising-prospect-discovery', 'google-cloud-credits-application', 'investor-pitch-preparation', 'microsoft-azure-credits-application', 'review-funding-preparation', 'advisory-board-development', 'advisor-interview', 'advisory-board-selection'],
43
+ protectedJobs: ['competitive-analysis', 'review-business-strategy', 'business-plan-creation', 'problem-statement-crystallization', 'business-idea-validation-and-scoping', 'founder-market-fit-analysis', 'community-funding-preparation', 'fundraising-prospect-discovery', 'investor-pitch-preparation', 'review-funding-preparation', 'advisory-board-development', 'advisor-interview', 'advisory-board-selection', 'blue-sky-brainstorming', 'domain-registration-research'],
44
44
  protectedAliases: ['business-strategy', 'company-strategy'],
45
45
  defaultHireMode: 'job',
46
46
  lockCopy: 'Hire BeZa to unlock business strategy work for this request.'
@@ -49,7 +49,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
49
49
  personaKey: 'pam',
50
50
  bundleId: 'persona-pam-core',
51
51
  catalogMetadata: buildCatalogMetadata('pam', ['feature-specification', 'technical-design', 'issue-preparation']),
52
- protectedJobs: ['feature-specification', 'technical-design', 'issue-preparation', 'implementation-design-review', 'issue-retrospective', 'work-completion', 'scrum-sprint-planning', 'mvp-validation-plan'],
52
+ protectedJobs: ['feature-specification', 'technical-design', 'issue-preparation', 'implementation-design-review', 'issue-retrospective', 'work-completion', 'scrum-sprint-planning', 'mvp-validation-plan', 'sprint-planning', 'customer-prospect-discovery', 'interview-preparation', 'participant-recruitment', 'process-interview-notes', 'review-customer-development', 'triage-customer-needs'],
53
53
  protectedAliases: ['product-management', 'product-spec'],
54
54
  defaultHireMode: 'job',
55
55
  lockCopy: 'Hire PaM to unlock product-management work for this request.'
@@ -58,7 +58,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
58
58
  personaKey: 'swen',
59
59
  bundleId: 'persona-swen-core',
60
60
  catalogMetadata: buildCatalogMetadata('swen', ['feature-implementation', 'code-refactoring', 'pr-iteration']),
61
- protectedJobs: ['feature-implementation', 'code-refactoring', 'pr-iteration', 'mobile-app-development', 'mcp-server-creation', 'cloud-application-deployment', 'cloud-cost-optimization', 'cloud-performance-diagnosis', 'route-llm-spend-to-cloud-credits', 'set-up-cloud-cost-alerts', 'gitlabs-to-github', 'system-migration', 'cross-cloud-migration', 'data-pipeline-design', 'data-quality-monitoring', 'data-platform-architecture', 'write-dev-docs', 'database-schema-design'],
61
+ protectedJobs: ['feature-implementation', 'code-refactoring', 'pr-iteration', 'mobile-app-development', 'mcp-server-creation', 'cloud-application-deployment', 'cloud-cost-optimization', 'cloud-performance-diagnosis', 'route-llm-spend-to-cloud-credits', 'set-up-cloud-cost-alerts', 'gitlabs-to-github', 'system-migration', 'cross-cloud-migration', 'data-pipeline-design', 'data-quality-monitoring', 'data-platform-architecture', 'write-dev-docs', 'database-schema-design', 'create-architecture', 'project-scaffolding', 'codebase-analysis-and-ideation', 'github-org-setup', 'google-workspace-setup', 'mobile-app-rejection-response', 'mobile-app-submission', 'publish-mcp-app', 'application-replication-workflow'],
62
62
  protectedAliases: ['software-engineering', 'implementation'],
63
63
  defaultHireMode: 'job',
64
64
  lockCopy: 'Hire SWEn to unlock software-engineering delivery for this request.'
@@ -76,7 +76,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
76
76
  personaKey: 'huxley',
77
77
  bundleId: 'persona-huxley-core',
78
78
  catalogMetadata: buildCatalogMetadata('huxley', ['design-system-creation', 'user-facing-prototyping', 'ux-research-synthesis']),
79
- protectedJobs: ['design-system-creation', 'user-facing-prototyping', 'website-creation', 'linkedin-carousel-from-deck', 'brand-creation', 'branding-quality-audit', 'ux-research-synthesis', 'user-journey-mapping'],
79
+ protectedJobs: ['design-system-creation', 'user-facing-prototyping', 'website-creation', 'brand-creation', 'branding-quality-audit', 'ux-research-synthesis', 'user-journey-mapping'],
80
80
  protectedAliases: ['ux-design', 'brand-design'],
81
81
  defaultHireMode: 'job',
82
82
  lockCopy: 'Hire hUXley to unlock design-system, UX research, and brand design work for this request.'
@@ -85,7 +85,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
85
85
  personaKey: 'gautam',
86
86
  bundleId: 'persona-gautam-core',
87
87
  catalogMetadata: buildCatalogMetadata('gautam', ['analyze-revenue-system', 'build-gtm-motion', 'ppc-campaign-management']),
88
- protectedJobs: ['pricing-strategy-definition', 'marketing-strategy-definition', 'product-launch-management', 'customer-prospect-discovery', 'evangelist-content-development', 'funnel-analysis', 'growth-loop-design', 'analyze-revenue-system', 'design-gtm-system', 'build-gtm-motion', 'build-gtm-stack', 'plan-gtm-team', 'marketing-content-creation', 'ppc-campaign-management', 'paid-social-strategy', 'ad-performance-analysis', 'tracking-and-attribution-setup', 'developer-advocacy', 'seo-strategy', 'marketing-analytics-review'],
88
+ protectedJobs: ['pricing-strategy-definition', 'marketing-strategy-definition', 'product-launch-management', 'evangelist-content-development', 'funnel-analysis', 'growth-loop-design', 'analyze-revenue-system', 'design-gtm-system', 'build-gtm-motion', 'build-gtm-stack', 'plan-gtm-team', 'marketing-content-creation', 'ppc-campaign-management', 'paid-social-strategy', 'ad-performance-analysis', 'tracking-and-attribution-setup', 'developer-advocacy', 'seo-strategy', 'marketing-analytics-review', 'linkedin-company-page-setup', 'x-account-setup', 'social-engagement-campaign', 'thought-leadership-engagement', 'promo-video-creation', 'linkedin-carousel-from-deck'],
89
89
  protectedAliases: ['gtm', 'marketing'],
90
90
  defaultHireMode: 'job',
91
91
  lockCopy: 'Hire GauTaM to unlock go-to-market and paid media work for this request.'
@@ -94,7 +94,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
94
94
  personaKey: 'cela',
95
95
  bundleId: 'persona-cela-core',
96
96
  catalogMetadata: buildCatalogMetadata('cela', ['contract-review-analysis', 'nda-creation', 'saas-contract-package-creation']),
97
- protectedJobs: ['contract-review-analysis', 'nda-creation', 'saas-contract-package-creation', 'trademark-registration-management', 'provisional-patent-application-creation', 'opensign-cloud-esign-dispatch', 'w9-creation', 'invoice-generation', 'entity-type-selection', 'state-incorporation-filing', 'business-tax-registration', 'ein-application', 'cap-table-construction', 'founder-and-equity-agreements', 'ip-assignment-agreement-creation', 'employment-structure-decision', 'business-banking-setup'],
97
+ protectedJobs: ['contract-review-analysis', 'nda-creation', 'saas-contract-package-creation', 'trademark-registration-management', 'provisional-patent-application-creation', 'opensign-cloud-esign-dispatch', 'w9-creation', 'entity-type-selection', 'state-incorporation-filing', 'business-tax-registration', 'ein-application', 'cap-table-construction', 'founder-and-equity-agreements', 'ip-assignment-agreement-creation', 'employment-structure-decision'],
98
98
  protectedAliases: ['legal', 'counsel'],
99
99
  defaultHireMode: 'job',
100
100
  lockCopy: 'Hire CELiA to unlock legal workflow support for this request.'
@@ -103,7 +103,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
103
103
  personaKey: 'sekhar',
104
104
  bundleId: 'persona-sekhar-core',
105
105
  catalogMetadata: buildCatalogMetadata('sekhar', ['security-review', 'ai-native-security-setup', 'security-findings-command-center']),
106
- protectedJobs: ['security-review', 'ai-native-security-setup', 'security-findings-command-center', 'vulnerability-triage-and-remediation', 'production-readiness-review', 'compliance-review', 'compliance-continuous-monitoring', 'compliance-policy-authoring', 'compliance-questionnaire-response', 'compliance-requirements-detection', 'generate-audit-evidence', 'regulation-evidence-management', 'soc2-evidence-management'],
106
+ protectedJobs: ['security-review', 'ai-native-security-setup', 'security-findings-command-center', 'vulnerability-triage-and-remediation', 'compliance-review', 'compliance-continuous-monitoring', 'compliance-policy-authoring', 'compliance-questionnaire-response', 'compliance-requirements-detection', 'generate-audit-evidence', 'regulation-evidence-management', 'soc2-evidence-management'],
107
107
  protectedAliases: ['security', 'appsec'],
108
108
  defaultHireMode: 'job',
109
109
  lockCopy: 'Hire SEChar to unlock security setup, review, and findings-command work for this request.'
@@ -112,7 +112,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
112
112
  personaKey: 'ashley',
113
113
  bundleId: 'persona-ashley-core',
114
114
  catalogMetadata: buildCatalogMetadata('ashley', ['executive-assistant', 'weekly-operating-review', 'send-thank-you-notes']),
115
- protectedJobs: ['executive-assistant', 'weekly-operating-review', 'send-thank-you-notes', 'send-newsletter', 'analyze-transcript', 'send-stakeholder-update', 'portfolio-impact-report'],
115
+ protectedJobs: ['executive-assistant', 'weekly-operating-review', 'send-thank-you-notes', 'send-newsletter', 'analyze-transcript', 'send-stakeholder-update'],
116
116
  protectedAliases: ['executive-assistant', 'operations-assistant'],
117
117
  defaultHireMode: 'job',
118
118
  lockCopy: 'Hire AshLey to unlock executive-assistant work for this request.'
@@ -121,7 +121,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
121
121
  personaKey: 'mandy',
122
122
  bundleId: 'persona-mandy-core',
123
123
  catalogMetadata: buildCatalogMetadata('mandy', ['fully-delegate', 'project-plan-creation', 'stakeholder-status-reporting']),
124
- protectedJobs: ['fully-delegate', 'delivery-governance-review', 'project-plan-creation', 'stakeholder-status-reporting', 'experiment-tracking', 'cross-functional-dependency-management'],
124
+ protectedJobs: ['fully-delegate', 'delivery-governance-review', 'project-plan-creation', 'stakeholder-status-reporting', 'experiment-tracking', 'cross-functional-dependency-management', 'portfolio-impact-report'],
125
125
  protectedAliases: ['manager', 'team-lead', 'orchestrator'],
126
126
  defaultHireMode: 'job',
127
127
  lockCopy: 'Hire MANdy to unlock autonomous multi-role orchestration — MANdy plans the job sequence, runs sub-agents in parallel, coaches them through verification loops, and hands back a synthesized DRAFT for your approval.'
@@ -175,7 +175,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
175
175
  personaKey: 'casey',
176
176
  bundleId: 'persona-casey-core',
177
177
  catalogMetadata: buildCatalogMetadata('casey', ['support-case-resolution', 'support-sop-operationalization', 'support-system-operationalization', 'support-playbook-evaluation']),
178
- protectedJobs: ['crm-account-health-review', 'crm-case-resolution', 'customer-health-review', 'loyalty-program-management', 'survey-campaign-management', 'support-queue-management', 'support-case-resolution', 'support-sop-operationalization', 'support-system-operationalization', 'support-playbook-evaluation'],
178
+ protectedJobs: ['crm-account-health-review', 'crm-case-resolution', 'customer-health-review', 'loyalty-program-management', 'survey-campaign-management', 'support-queue-management', 'support-case-resolution', 'support-sop-operationalization', 'support-system-operationalization', 'support-playbook-evaluation', 'user-survey-management'],
179
179
  protectedAliases: ['customer-success', 'customer-support', 'csm', 'support'],
180
180
  defaultHireMode: 'job',
181
181
  lockCopy: 'Hire CaSey to unlock customer success and support work for this request.'
@@ -205,7 +205,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
205
205
  personaKey: 'mona',
206
206
  bundleId: 'persona-mona-core',
207
207
  catalogMetadata: buildCatalogMetadata('mona', ['financial-analysis', 'fpa-and-forecasting', 'monthly-close-review']),
208
- protectedJobs: ['financial-analysis', 'fpa-and-forecasting', 'monthly-close-review', 'tax-strategy-planning'],
208
+ protectedJobs: ['financial-analysis', 'fpa-and-forecasting', 'monthly-close-review', 'tax-strategy-planning', 'aws-activate-credits-application', 'google-cloud-credits-application', 'microsoft-azure-credits-application', 'invoice-generation', 'business-banking-setup'],
209
209
  protectedAliases: ['finance', 'financial-modeling', 'fpa', 'bookkeeping'],
210
210
  defaultHireMode: 'job',
211
211
  lockCopy: 'Hire MONa to unlock financial modeling, FP&A, and tax strategy work for this request.'
@@ -214,7 +214,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
214
214
  personaKey: 'sreya',
215
215
  bundleId: 'persona-sreya-core',
216
216
  catalogMetadata: buildCatalogMetadata('sreya', ['slo-design-and-implementation', 'incident-response', 'reliability-review']),
217
- protectedJobs: ['slo-design-and-implementation', 'incident-response', 'reliability-review'],
217
+ protectedJobs: ['slo-design-and-implementation', 'incident-response', 'reliability-review', 'production-readiness-review'],
218
218
  protectedAliases: ['sre', 'reliability', 'incident-response', 'on-call'],
219
219
  defaultHireMode: 'job',
220
220
  lockCopy: 'Hire SREya to unlock SLO governance, incident response, and production reliability work for this request.'
@@ -231,7 +231,8 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
231
231
  'procurement-negotiation-prep',
232
232
  'purchase-approval-and-po-package',
233
233
  'equipment-acceptance-and-supplier-retrospective',
234
- 'supplier-performance-review'
234
+ 'supplier-performance-review',
235
+ 'rfp-response-preparation'
235
236
  ],
236
237
  protectedAliases: ['procurement', 'sourcing', 'purchasing', 'supplier-management'],
237
238
  defaultHireMode: 'job',
@@ -266,6 +267,9 @@ function getPersonaCapabilityBundle(personaKey) {
266
267
  return exports.PERSONA_CAPABILITY_BUNDLES[personaKey];
267
268
  }
268
269
  function getProtectedPersonaForJob(jobName) {
270
+ if (isFreeJob(jobName)) {
271
+ return null;
272
+ }
269
273
  return PROTECTED_JOB_TO_PERSONA.get(jobName) || null;
270
274
  }
271
275
  function listPersonaCapabilityBundles() {
@@ -607,12 +607,12 @@ class FirstRunSessionService {
607
607
  persistShellPath();
608
608
  const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
609
609
  if (detectedIDEs.length > 0) {
610
- await (0, auto_mcp_setup_1.autoConfigureMCP)(this.key, {}, detectedIDEs.map((ide) => ide.name), {});
610
+ await (0, auto_mcp_setup_1.autoConfigureMCP)(this.key, detectedIDEs.map((ide) => ide.name));
611
611
  }
612
612
  // Write config.json after autoConfigureMCP so detectRowsOnLoad can use its
613
613
  // existence as a signal that IDE setup completed. Writing it before would cause
614
614
  // the wizard to mark this row 'ok' on retry before IDEs are actually configured.
615
- (0, setup_1.saveGlobalConfig)(this.key, 'conversational', {}, {});
615
+ (0, setup_1.saveGlobalConfig)(this.key, 'conversational');
616
616
  const { syncUserLevelArtifacts } = await Promise.resolve().then(() => __importStar(require('../cli/setup/user-level-sync')));
617
617
  await syncUserLevelArtifacts();
618
618
  const { installSlashCommands, installGlobalRules } = await Promise.resolve().then(() => __importStar(require('../cli/setup/ide-global-integration')));
@@ -413,6 +413,7 @@ class FraimLocalMCPServer {
413
413
  this.otlpServer = null;
414
414
  this.isShutdown = false;
415
415
  this.mentoringResponseCache = null;
416
+ this.jobStartCache = new Map();
416
417
  this.connectSyncInFlight = null;
417
418
  this.latestConnectSyncWarning = null;
418
419
  this.orgCacheRefreshInFlight = false;
@@ -1996,7 +1997,7 @@ class FraimLocalMCPServer {
1996
1997
  if (!this.mentoringResponseCache) {
1997
1998
  this.mentoringResponseCache = new Map();
1998
1999
  }
1999
- this.mentoringResponseCache.set(requestId, {
2000
+ this.mentoringResponseCache.set(request.id, {
2000
2001
  nextPhase: tutoringResponse.nextPhase,
2001
2002
  jobId: args.jobId || requestSessionId // Use jobId from args, fallback to sessionId
2002
2003
  });
@@ -2063,10 +2064,13 @@ class FraimLocalMCPServer {
2063
2064
  const overview = await mentor.getJobOverview(name);
2064
2065
  if (overview) {
2065
2066
  this.log(`✅ Local override found for get_fraim_job: ${name}`);
2067
+ const jobId = (0, crypto_1.randomUUID)();
2068
+ this.jobStartCache.set(request.id, { jobId, jobName: name });
2069
+ this.log(`📊 Generated jobId=${jobId} for job=${name}`);
2066
2070
  let responseText = overview.overview;
2067
2071
  if (!overview.isSimple) {
2068
2072
  responseText = `${mentor.getCompactPhaseAuthority()}\n\n${responseText}`;
2069
- responseText += `\n\n---\n\n**This job has phases.** Use \`seekMentoring\` to get phase-specific instructions.`;
2073
+ responseText += `\n\n---\n\n**Job ID:** \`${jobId}\`\n\n**This job has phases.** Use \`seekMentoring\` with the jobId above to get phase-specific instructions.`;
2070
2074
  }
2071
2075
  // Inject local learning context for job requests (RFC 177).
2072
2076
  const userEmail = this.ensureEngine().getUserEmail();
@@ -2356,7 +2360,7 @@ class FraimLocalMCPServer {
2356
2360
  if (!this.repoInfo) {
2357
2361
  this.detectRepoInfo();
2358
2362
  }
2359
- // Bug fix: Enrich seekMentoring args with response data
2363
+ // Enrich seekMentoring args with response data (nextPhase, jobId from cache)
2360
2364
  if (toolName === 'seekMentoring' && this.mentoringResponseCache) {
2361
2365
  const requestId = request.id;
2362
2366
  const cachedResponse = this.mentoringResponseCache.get(requestId);
@@ -2366,10 +2370,20 @@ class FraimLocalMCPServer {
2366
2370
  nextPhase: cachedResponse.nextPhase,
2367
2371
  jobId: cachedResponse.jobId
2368
2372
  };
2369
- this.mentoringResponseCache.delete(requestId); // Clean up
2373
+ this.mentoringResponseCache.delete(requestId);
2370
2374
  this.log(`📊 Enriched seekMentoring args with nextPhase=${cachedResponse.nextPhase}, jobId=${cachedResponse.jobId}`);
2371
2375
  }
2372
2376
  }
2377
+ // Enrich get_fraim_job args with the UUID jobId and action:start from jobStartCache
2378
+ if (toolName === 'get_fraim_job') {
2379
+ const requestId = request.id;
2380
+ const cached = this.jobStartCache.get(requestId);
2381
+ if (cached) {
2382
+ args = { ...args, jobId: cached.jobId, action: 'start' };
2383
+ this.jobStartCache.delete(requestId);
2384
+ this.log(`📊 Enriched get_fraim_job args with jobId=${cached.jobId}, action=start`);
2385
+ }
2386
+ }
2373
2387
  // Capture the current queue size before collection
2374
2388
  const beforeCount = this.usageCollector.getEventCount();
2375
2389
  try {
@@ -2410,6 +2424,16 @@ class FraimLocalMCPServer {
2410
2424
  this.log(`📊 🔍 Debug: get_fraim_file path="${args.path}"`);
2411
2425
  }
2412
2426
  }
2427
+ // Emit job-complete event when seekMentoring reaches the final phase
2428
+ if (toolName === 'seekMentoring' && args.nextPhase === null && args.jobName && args.jobId) {
2429
+ try {
2430
+ this.usageCollector.collectJobComplete(args.jobName, args.jobId, requestSessionId);
2431
+ this.log(`📊 ✅ Job complete event emitted: ${args.jobName} (jobId: ${args.jobId})`);
2432
+ }
2433
+ catch (err) {
2434
+ this.log(`📊 ⚠️ Job complete event failed (non-blocking): ${err.message}`);
2435
+ }
2436
+ }
2413
2437
  }
2414
2438
  catch (error) {
2415
2439
  this.log(`📊 ❌ Usage collection error: ${error.message}`);
@@ -142,6 +142,8 @@ class UsageCollector {
142
142
  case 'get_fraim_job':
143
143
  if (args.job)
144
144
  analyticsArgs.job = args.job;
145
+ if (args.action)
146
+ analyticsArgs.action = args.action;
145
147
  break;
146
148
  case 'fraim_connect':
147
149
  if (args.agent?.name)
@@ -179,6 +181,28 @@ class UsageCollector {
179
181
  return null;
180
182
  }
181
183
  }
184
+ /**
185
+ * Emit a job-completion event when seekMentoring returns nextPhase === null.
186
+ * Called by the local proxy after the final mentoring event is queued.
187
+ */
188
+ collectJobComplete(jobName, jobId, sessionId) {
189
+ const event = {
190
+ type: 'job',
191
+ name: jobName,
192
+ userId: this.userId || 'unknown',
193
+ sessionId,
194
+ success: true,
195
+ jobId,
196
+ repoIdentifier: this.repoIdentifier || undefined,
197
+ agentName: this.agentName || undefined,
198
+ agentModel: this.agentModel || undefined,
199
+ args: { action: 'complete', jobId },
200
+ };
201
+ this.events.push(event);
202
+ const msg = `[UsageCollector] ✅ Job complete event: ${jobName} (jobId: ${jobId}, queue: ${this.events.length})`;
203
+ console.error(msg);
204
+ process.stderr.write(msg + '\n');
205
+ }
182
206
  /**
183
207
  * Get collected events for upload and clear the queue
184
208
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim-framework",
3
- "version": "2.0.177",
3
+ "version": "2.0.179",
4
4
  "description": "FRAIM: AI Workforce Infrastructure — the organizational capability that turns AI agents into an accountable workforce, their operators into capable AI managers, and executives into leaders with clear optics on AI proficiency.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -9,7 +9,7 @@
9
9
  "scripts": {
10
10
  "dev": "tsx --watch src/fraim-mcp-server.ts > server.log 2>&1",
11
11
  "dev:prod": "npm run build && node dist/src/fraim-mcp-server.js > server.log 2>&1",
12
- "build": "tsx scripts/build-fraim-config-schema-template.ts && npm run typecheck:scripts && tsc && npm run build:stubs && npm run build:fraim-brain && node scripts/copy-registry.js && npm run validate:registry && npm run validate:fraim-pro-assets && npm run validate:employee-catalog && npm run validate:learning-format-contract && tsx scripts/validate-purity.ts",
12
+ "build": "tsx scripts/build-fraim-config-schema-template.ts && npm run typecheck:scripts && tsc && npm run build:stubs && npm run build:fraim-brain && node scripts/copy-registry.js && npm run validate:registry && npm run validate:marketplaces && npm run validate:fraim-pro-assets && npm run validate:employee-catalog && npm run validate:learning-format-contract && tsx scripts/validate-purity.ts",
13
13
  "validate:learning-format-contract": "tsx scripts/validate-learning-format-contract.ts",
14
14
  "build:stubs": "tsx scripts/build-stub-registry.ts",
15
15
  "build:fraim-brain": "node scripts/generate-fraim-brain.js",
@@ -50,6 +50,7 @@
50
50
  "publish-fraim-only": "node scripts/publish-fraim.js",
51
51
  "publish-both-manual": "node scripts/publish-both.js",
52
52
  "validate:registry": "tsx scripts/verify-registry-paths.ts && npm run validate:jobs && npm run validate:skills && npm run validate:registry-references && npm run validate:platform-agnostic && npm run validate:template-namespaces && npm run validate:config-fallbacks && npm run validate:bootstrap-config-coverage && npm run validate:provider-action-mappings && npm run validate:fidelity && npm run validate:config-tokens && npm run validate:brain-mapping && npm run validate:template-syntax",
53
+ "validate:marketplaces": "tsx scripts/validate-marketplace-bundles.ts",
53
54
  "typecheck:scripts": "tsc -p tsconfig.scripts.json --pretty false",
54
55
  "validate:registry-references": "tsx scripts/validate-registry-references.ts",
55
56
  "validate:brain-mapping": "tsx scripts/validate-brain-mapping.ts",
@@ -144,8 +144,20 @@
144
144
  <!-- Overview sub-view: dashboard project cards -->
145
145
  <div id="proj-overview">
146
146
  <div class="hub-area-page">
147
- <div class="sec-label">All projects</div>
148
- <div class="proj-grid" id="proj-grid"></div>
147
+ <!-- Issue #671: Cards / Kanban view toggle -->
148
+ <div class="kb-view-toggle" id="kb-view-toggle">
149
+ <button class="kb-vtab on" data-view="cards" type="button">Cards</button>
150
+ <button class="kb-vtab" data-view="kanban" type="button">Kanban</button>
151
+ </div>
152
+ <div id="proj-cards-view">
153
+ <div class="sec-label" id="proj-cards-label">All projects</div>
154
+ <div class="proj-grid" id="proj-grid"></div>
155
+ <div id="kb-runs-grid" hidden></div>
156
+ </div>
157
+ </div>
158
+ <!-- Issue #671: Kanban board — rendered by tfRenderKanban() -->
159
+ <div id="proj-kanban" hidden>
160
+ <div id="kb-stats" class="kb-stats"></div>
149
161
  </div>
150
162
  </div>
151
163