fraim 2.0.154 → 2.0.160

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +1 -1
  2. package/dist/src/ai-hub/cert-store.js +70 -0
  3. package/dist/src/ai-hub/desktop-main.js +225 -50
  4. package/dist/src/ai-hub/hosts.js +135 -8
  5. package/dist/src/ai-hub/manager-turns.js +38 -0
  6. package/dist/src/ai-hub/office-sideload.js +138 -0
  7. package/dist/src/ai-hub/openclaw-bridge.js +239 -0
  8. package/dist/src/ai-hub/server.js +479 -48
  9. package/dist/src/ai-hub/word-sideload.js +95 -0
  10. package/dist/src/cli/commands/add-ide.js +9 -0
  11. package/dist/src/cli/commands/init-project.js +46 -34
  12. package/dist/src/cli/commands/login.js +1 -2
  13. package/dist/src/cli/commands/setup.js +0 -2
  14. package/dist/src/cli/commands/sync.js +41 -11
  15. package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +66 -2
  16. package/dist/src/cli/doctor/checks/workflow-checks.js +1 -65
  17. package/dist/src/cli/mcp/fraim-mcp-latest-launcher.js +136 -0
  18. package/dist/src/cli/mcp/mcp-server-registry.js +14 -10
  19. package/dist/src/cli/setup/auto-mcp-setup.js +1 -1
  20. package/dist/src/cli/setup/ide-invocation-surfaces.js +2 -2
  21. package/dist/src/cli/utils/fraim-gitignore.js +11 -0
  22. package/dist/src/cli/utils/github-workflow-sync.js +231 -0
  23. package/dist/src/cli/utils/managed-agent-paths.js +1 -1
  24. package/dist/src/cli/utils/project-bootstrap.js +6 -3
  25. package/dist/src/cli/utils/remote-sync.js +1 -1
  26. package/dist/src/core/ai-mentor.js +46 -37
  27. package/dist/src/core/config-loader.js +69 -2
  28. package/dist/src/core/fraim-config-schema.generated.js +267 -6
  29. package/dist/src/core/types.js +0 -1
  30. package/dist/src/core/utils/fraim-labels.js +182 -0
  31. package/dist/src/core/utils/git-utils.js +22 -1
  32. package/dist/src/core/utils/project-fraim-paths.js +58 -0
  33. package/dist/src/first-run/session-service.js +3 -3
  34. package/dist/src/first-run/types.js +1 -1
  35. package/dist/src/local-mcp-server/learning-context-builder.js +77 -52
  36. package/dist/src/local-mcp-server/stdio-server.js +212 -13
  37. package/package.json +6 -2
  38. package/public/ai-hub/index.html +289 -229
  39. package/public/ai-hub/powerpoint-taskpane/icon-64.png +0 -0
  40. package/public/ai-hub/powerpoint-taskpane/index.html +235 -0
  41. package/public/ai-hub/powerpoint-taskpane/manifest.xml +30 -0
  42. package/public/ai-hub/script.js +1155 -586
  43. package/public/ai-hub/styles.css +1226 -722
  44. package/public/first-run/index.html +35 -35
  45. package/public/first-run/script.js +667 -667
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ /**
3
+ * Sideloads the Word add-in manifest so it appears in Word's Developer Add-ins
4
+ * list without admin rights or AppSource publishing.
5
+ *
6
+ * Windows: writes a registry value under HKCU\SOFTWARE\Microsoft\Office\16.0\WEF\Developer
7
+ * macOS: writes an entry to ~/Library/Containers/com.microsoft.Word/Data/Documents/wef/
8
+ *
9
+ * Both paths are non-admin and survive app updates (keyed by manifest GUID).
10
+ * Safe to call multiple times — checks before writing.
11
+ */
12
+ var __importDefault = (this && this.__importDefault) || function (mod) {
13
+ return (mod && mod.__esModule) ? mod : { "default": mod };
14
+ };
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.isSideloaded = isSideloaded;
17
+ exports.sideloadManifest = sideloadManifest;
18
+ exports.removeSideload = removeSideload;
19
+ const fs_1 = __importDefault(require("fs"));
20
+ const path_1 = __importDefault(require("path"));
21
+ const os_1 = __importDefault(require("os"));
22
+ const child_process_1 = require("child_process");
23
+ const MANIFEST_GUID = 'd1090951-50cf-4cf2-9d12-b0f8541d265c';
24
+ function resolveManifestPath(projectPath) {
25
+ const candidates = [
26
+ path_1.default.resolve(projectPath, 'extensions/office-word/manifest.xml'),
27
+ path_1.default.resolve(__dirname, '..', '..', 'extensions/office-word/manifest.xml'),
28
+ path_1.default.resolve(__dirname, '..', '..', '..', 'extensions/office-word/manifest.xml'),
29
+ ];
30
+ return candidates.find(c => fs_1.default.existsSync(c)) ?? null;
31
+ }
32
+ function isSideloaded() {
33
+ if (process.platform === 'win32') {
34
+ const result = (0, child_process_1.spawnSync)('reg', [
35
+ 'query',
36
+ `HKCU\\SOFTWARE\\Microsoft\\Office\\16.0\\WEF\\Developer`,
37
+ '/v', MANIFEST_GUID,
38
+ ], { encoding: 'utf8' });
39
+ return result.status === 0 && result.stdout.includes(MANIFEST_GUID);
40
+ }
41
+ if (process.platform === 'darwin') {
42
+ const wefDir = path_1.default.join(os_1.default.homedir(), 'Library', 'Containers', 'com.microsoft.Word', 'Data', 'Documents', 'wef');
43
+ return fs_1.default.existsSync(path_1.default.join(wefDir, `${MANIFEST_GUID}.xml`));
44
+ }
45
+ return false;
46
+ }
47
+ function sideloadManifest(projectPath) {
48
+ const manifestPath = resolveManifestPath(projectPath);
49
+ if (!manifestPath) {
50
+ return { ok: false, reason: 'Manifest file not found — is extensions/office-word/ present?' };
51
+ }
52
+ if (process.platform === 'win32') {
53
+ const result = (0, child_process_1.spawnSync)('reg', [
54
+ 'add',
55
+ `HKCU\\SOFTWARE\\Microsoft\\Office\\16.0\\WEF\\Developer`,
56
+ '/v', MANIFEST_GUID,
57
+ '/t', 'REG_SZ',
58
+ '/d', manifestPath,
59
+ '/f',
60
+ ], { encoding: 'utf8' });
61
+ if (result.status !== 0) {
62
+ return { ok: false, reason: result.stderr || 'reg add failed' };
63
+ }
64
+ return { ok: true };
65
+ }
66
+ if (process.platform === 'darwin') {
67
+ const wefDir = path_1.default.join(os_1.default.homedir(), 'Library', 'Containers', 'com.microsoft.Word', 'Data', 'Documents', 'wef');
68
+ try {
69
+ fs_1.default.mkdirSync(wefDir, { recursive: true });
70
+ fs_1.default.copyFileSync(manifestPath, path_1.default.join(wefDir, `${MANIFEST_GUID}.xml`));
71
+ return { ok: true };
72
+ }
73
+ catch (err) {
74
+ return { ok: false, reason: String(err) };
75
+ }
76
+ }
77
+ return { ok: false, reason: `Unsupported platform: ${process.platform}` };
78
+ }
79
+ function removeSideload() {
80
+ if (process.platform === 'win32') {
81
+ (0, child_process_1.spawnSync)('reg', [
82
+ 'delete',
83
+ `HKCU\\SOFTWARE\\Microsoft\\Office\\16.0\\WEF\\Developer`,
84
+ '/v', MANIFEST_GUID,
85
+ '/f',
86
+ ], { encoding: 'utf8' });
87
+ return;
88
+ }
89
+ if (process.platform === 'darwin') {
90
+ const wefDir = path_1.default.join(os_1.default.homedir(), 'Library', 'Containers', 'com.microsoft.Word', 'Data', 'Documents', 'wef');
91
+ const target = path_1.default.join(wefDir, `${MANIFEST_GUID}.xml`);
92
+ if (fs_1.default.existsSync(target))
93
+ fs_1.default.unlinkSync(target);
94
+ }
95
+ }
@@ -218,12 +218,18 @@ const configureIDEMCP = async (ide, fraimKey, tokens, providerConfigs) => {
218
218
  // Merge MCP servers intelligently
219
219
  const mergedMCPServers = { ...existingMCPServers };
220
220
  const addedServers = [];
221
+ const updatedServers = [];
221
222
  const skippedServers = [];
223
+ const alwaysUpdateServers = new Set(['fraim', 'github', 'gitlab', 'jira', 'ado', 'linear']);
222
224
  for (const [serverName, serverConfig] of Object.entries(newMCPServers)) {
223
225
  if (!existingMCPServers[serverName]) {
224
226
  mergedMCPServers[serverName] = serverConfig;
225
227
  addedServers.push(serverName);
226
228
  }
229
+ else if (alwaysUpdateServers.has(serverName)) {
230
+ mergedMCPServers[serverName] = serverConfig;
231
+ updatedServers.push(serverName);
232
+ }
227
233
  else {
228
234
  skippedServers.push(serverName);
229
235
  }
@@ -239,6 +245,9 @@ const configureIDEMCP = async (ide, fraimKey, tokens, providerConfigs) => {
239
245
  addedServers.forEach(server => {
240
246
  console.log(chalk_1.default.green(` ✅ Added ${server} MCP server`));
241
247
  });
248
+ updatedServers.forEach(server => {
249
+ console.log(chalk_1.default.blue(` Updated ${server} MCP server`));
250
+ });
242
251
  skippedServers.forEach(server => {
243
252
  console.log(chalk_1.default.gray(` ⏭️ Skipped ${server} (already exists)`));
244
253
  });
@@ -19,6 +19,8 @@ const fraim_gitignore_1 = require("../utils/fraim-gitignore");
19
19
  const agent_adapters_1 = require("../utils/agent-adapters");
20
20
  const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
21
21
  const project_bootstrap_1 = require("../utils/project-bootstrap");
22
+ const config_loader_1 = require("../../core/config-loader");
23
+ const github_workflow_sync_1 = require("../utils/github-workflow-sync");
22
24
  const checkGlobalSetup = () => {
23
25
  const fraimUserDir = process.env.FRAIM_USER_DIR || path_1.default.join(os_1.default.homedir(), '.fraim');
24
26
  const globalConfigPath = path_1.default.join(fraimUserDir, 'config.json');
@@ -63,7 +65,6 @@ const findProjectFile = (filename) => {
63
65
  exports.findProjectFile = findProjectFile;
64
66
  ;
65
67
  const installGitHubWorkflows = (projectRoot) => {
66
- const workflowsDir = path_1.default.join(projectRoot, '.github', 'workflows');
67
68
  const registryDir = (0, exports.findProjectFile)('registry');
68
69
  const sourceDir = path_1.default.join(registryDir, 'github', 'workflows');
69
70
  if (!fs_1.default.existsSync(sourceDir)) {
@@ -73,21 +74,22 @@ const installGitHubWorkflows = (projectRoot) => {
73
74
  console.log(chalk_1.default.gray(`Process cwd: ${process.cwd()}`));
74
75
  return;
75
76
  }
76
- if (!fs_1.default.existsSync(workflowsDir)) {
77
- fs_1.default.mkdirSync(workflowsDir, { recursive: true });
77
+ const config = fs_1.default.existsSync((0, project_fraim_paths_1.getWorkspaceConfigPath)(projectRoot))
78
+ ? (0, config_loader_1.loadFraimConfig)((0, project_fraim_paths_1.getWorkspaceConfigPath)(projectRoot))
79
+ : null;
80
+ if (!(0, github_workflow_sync_1.isGitHubWorkflowAutomationEnabled)(config)) {
81
+ console.log(chalk_1.default.gray('GitHub workflow automation disabled or not configured; skipping managed workflow install.'));
82
+ return;
78
83
  }
79
- const workflowFiles = ['phase-change.yml', 'status-change.yml', 'sync-on-pr-review.yml'];
80
- workflowFiles.forEach((file) => {
81
- const sourcePath = path_1.default.join(sourceDir, file);
82
- const destPath = path_1.default.join(workflowsDir, file);
83
- if (fs_1.default.existsSync(sourcePath)) {
84
- fs_1.default.copyFileSync(sourcePath, destPath);
85
- console.log(chalk_1.default.green(`Installed workflow: .github/workflows/${file}`));
86
- }
87
- else {
88
- console.log(chalk_1.default.yellow(`Warning: Workflow not found in registry: ${file}`));
89
- }
84
+ const bundle = (0, github_workflow_sync_1.loadLocalGitHubWorkflowBundle)(registryDir);
85
+ const result = (0, github_workflow_sync_1.reconcileGitHubWorkflowAssets)({
86
+ projectRoot,
87
+ files: bundle,
88
+ config
90
89
  });
90
+ for (const line of (0, github_workflow_sync_1.formatGitHubWorkflowSyncSummary)(result)) {
91
+ console.log(chalk_1.default.green(line));
92
+ }
91
93
  };
92
94
  const createGitHubLabels = (projectRoot) => {
93
95
  try {
@@ -137,6 +139,9 @@ function formatPlatformLabel(provider) {
137
139
  return provider.toUpperCase();
138
140
  }
139
141
  }
142
+ function isMinimalConversationMode(mode) {
143
+ return mode === 'conversational';
144
+ }
140
145
  const runInitProject = async (options = {}) => {
141
146
  console.log(chalk_1.default.blue('Initializing FRAIM project...'));
142
147
  const failHard = options.failHard ?? 'exit';
@@ -187,7 +192,9 @@ const runInitProject = async (options = {}) => {
187
192
  const detection = (0, platform_detection_1.detectPlatformFromGit)();
188
193
  if (detection.provider !== 'unknown' && detection.repository) {
189
194
  result.repositoryDetected = true;
190
- result.projectName = detection.repository.name || projectName;
195
+ if (!isMinimalConversationMode(preferredMode)) {
196
+ result.projectName = detection.repository.name || projectName;
197
+ }
191
198
  const jiraConfig = globalSetup.providerConfigs?.jiraConfig;
192
199
  if (preferredMode === 'split' && globalSetup.tokens?.jira && jiraConfig?.baseUrl) {
193
200
  result.issueTrackingDetected = true;
@@ -200,26 +207,31 @@ const runInitProject = async (options = {}) => {
200
207
  result.mode = 'integrated';
201
208
  result.warnings.push('Split mode is not fully configured for this repo yet, so issue tracking will fall back to the repository provider during onboarding.');
202
209
  }
203
- console.log(chalk_1.default.blue(` Platform: ${formatPlatformLabel(detection.provider)}`));
204
- }
205
- const repo = detection.repository;
206
- if (repo.owner && repo.name) {
207
- console.log(chalk_1.default.gray(` Repository: ${repo.owner}/${repo.name}`));
208
- }
209
- else if (repo.organization && repo.project && repo.name) {
210
- console.log(chalk_1.default.gray(` Organization: ${repo.organization}`));
211
- console.log(chalk_1.default.gray(` Project: ${repo.project}`));
212
- console.log(chalk_1.default.gray(` Repository: ${repo.name}`));
210
+ if (!isMinimalConversationMode(preferredMode)) {
211
+ console.log(chalk_1.default.blue(` Platform: ${formatPlatformLabel(detection.provider)}`));
212
+ }
213
213
  }
214
- else if (repo.namespace && repo.name) {
215
- console.log(chalk_1.default.gray(` Namespace: ${repo.namespace || '(none)'}`));
216
- console.log(chalk_1.default.gray(` Repository: ${repo.name}`));
214
+ if (!isMinimalConversationMode(preferredMode)) {
215
+ const repo = detection.repository;
216
+ if (repo.owner && repo.name) {
217
+ console.log(chalk_1.default.gray(` Repository: ${repo.owner}/${repo.name}`));
218
+ }
219
+ else if (repo.organization && repo.project && repo.name) {
220
+ console.log(chalk_1.default.gray(` Organization: ${repo.organization}`));
221
+ console.log(chalk_1.default.gray(` Project: ${repo.project}`));
222
+ console.log(chalk_1.default.gray(` Repository: ${repo.name}`));
223
+ }
224
+ else if (repo.namespace && repo.name) {
225
+ console.log(chalk_1.default.gray(` Namespace: ${repo.namespace || '(none)'}`));
226
+ console.log(chalk_1.default.gray(` Repository: ${repo.name}`));
227
+ }
217
228
  }
218
229
  }
219
230
  else {
220
231
  result.mode = 'conversational';
221
- result.warnings.push('No git remote detected. FRAIM fell back to conversational project setup.');
222
- console.log(chalk_1.default.yellow(' No git remote detected. Falling back to conversational mode.'));
232
+ if (!isMinimalConversationMode(preferredMode)) {
233
+ console.log(chalk_1.default.yellow(' Starting conversational project setup.'));
234
+ }
223
235
  }
224
236
  result.warnings.push(`${configDisplayPath} was not created by init-project. The manager \`project-onboarding\` job is the only supported config-writing path.`);
225
237
  }
@@ -236,15 +248,15 @@ const runInitProject = async (options = {}) => {
236
248
  }
237
249
  });
238
250
  const ignoreUpdate = (0, fraim_gitignore_1.ensureFraimSyncedContentLocallyExcluded)(projectRoot);
239
- if (ignoreUpdate.gitInfoExcludeUpdated) {
251
+ if (ignoreUpdate.gitInfoExcludeUpdated && !isMinimalConversationMode(result.mode)) {
240
252
  console.log(chalk_1.default.green('Updated .git/info/exclude FRAIM managed block'));
241
253
  }
242
- if (ignoreUpdate.gitignoreUpdated) {
254
+ if (ignoreUpdate.gitignoreUpdated && !isMinimalConversationMode(result.mode)) {
243
255
  console.log(chalk_1.default.green('Removed legacy FRAIM sync block from .gitignore'));
244
256
  }
245
257
  const detection = (0, platform_detection_1.detectPlatformFromGit)();
246
- if (detection.provider === 'github') {
247
- console.log(chalk_1.default.blue('\nSetting up GitHub workflows and labels...'));
258
+ if (detection.provider === 'github' && !isMinimalConversationMode(result.mode)) {
259
+ console.log(chalk_1.default.blue('\nSetting up GitHub labels...'));
248
260
  installGitHubWorkflows(projectRoot);
249
261
  createGitHubLabels(projectRoot);
250
262
  result.repositoryDetected = true;
@@ -11,7 +11,6 @@ const provider_client_1 = require("../api/provider-client");
11
11
  const script_sync_utils_1 = require("../utils/script-sync-utils");
12
12
  const fs_1 = __importDefault(require("fs"));
13
13
  const path_1 = __importDefault(require("path"));
14
- const version_utils_1 = require("../utils/version-utils");
15
14
  const auto_mcp_setup_1 = require("../setup/auto-mcp-setup");
16
15
  exports.loginCommand = new commander_1.Command('login')
17
16
  .description('Login to platforms (GitHub, etc.)');
@@ -51,7 +50,7 @@ exports.loginCommand
51
50
  ...(config.tokens || {}),
52
51
  github: token
53
52
  };
54
- config.version = config.version || (0, version_utils_1.getFraimVersion)();
53
+ delete config.version;
55
54
  config.updatedAt = new Date().toISOString();
56
55
  fs_1.default.writeFileSync(globalConfigPath, JSON.stringify(config, null, 2));
57
56
  console.log(chalk_1.default.green('\n✅ GitHub token saved to global configuration.'));
@@ -44,7 +44,6 @@ const prompts_1 = __importDefault(require("prompts"));
44
44
  const fs_1 = __importDefault(require("fs"));
45
45
  const path_1 = __importDefault(require("path"));
46
46
  const auto_mcp_setup_1 = require("../setup/auto-mcp-setup");
47
- const version_utils_1 = require("../utils/version-utils");
48
47
  const provider_client_1 = require("../api/provider-client");
49
48
  const provider_prompts_1 = require("../setup/provider-prompts");
50
49
  const provider_registry_1 = require("../providers/provider-registry");
@@ -225,7 +224,6 @@ const saveGlobalConfig = (fraimKey, mode, tokens, configs) => {
225
224
  };
226
225
  });
227
226
  const config = {
228
- version: (0, version_utils_1.getFraimVersion)(),
229
227
  apiKey: fraimKey,
230
228
  mode: mode,
231
229
  tokens: {
@@ -48,6 +48,7 @@ const git_utils_1 = require("../../core/utils/git-utils");
48
48
  const agent_adapters_1 = require("../utils/agent-adapters");
49
49
  const fraim_gitignore_1 = require("../utils/fraim-gitignore");
50
50
  const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
51
+ const github_workflow_sync_1 = require("../utils/github-workflow-sync");
51
52
  function resolveExplicitLocalSyncUrl() {
52
53
  const candidates = [
53
54
  process.env.FRAIM_TEST_SERVER_URL,
@@ -83,22 +84,30 @@ function loadUserApiKey() {
83
84
  return undefined;
84
85
  }
85
86
  }
86
- function updateVersionInConfig(fraimDir) {
87
+ function writeSyncMetadata(fraimDir, mode, remoteUrl) {
88
+ fs_1.default.mkdirSync(fraimDir, { recursive: true });
89
+ fs_1.default.writeFileSync(path_1.default.join(fraimDir, '.sync-metadata.json'), JSON.stringify({
90
+ localVersion: (0, version_utils_1.getFraimVersion)(),
91
+ mode,
92
+ remoteUrl,
93
+ syncedAt: new Date().toISOString()
94
+ }, null, 2), 'utf8');
95
+ }
96
+ function removeLegacyVersionFromConfig(fraimDir) {
87
97
  const configPath = path_1.default.join(fraimDir, 'config.json');
88
98
  if (!fs_1.default.existsSync(configPath)) {
89
99
  return;
90
100
  }
91
101
  try {
92
102
  const currentConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
93
- const newVersion = (0, version_utils_1.getFraimVersion)();
94
- if (currentConfig.version !== newVersion) {
95
- currentConfig.version = newVersion;
103
+ if (Object.prototype.hasOwnProperty.call(currentConfig, 'version')) {
104
+ delete currentConfig.version;
96
105
  fs_1.default.writeFileSync(configPath, JSON.stringify(currentConfig, null, 2));
97
- console.log(chalk_1.default.green(`Updated FRAIM version to ${newVersion} in config.`));
106
+ console.log(chalk_1.default.green('Removed legacy config.version from config.json.'));
98
107
  }
99
108
  }
100
109
  catch {
101
- console.warn(chalk_1.default.yellow('Could not update version in config.json.'));
110
+ console.warn(chalk_1.default.yellow('Could not remove legacy version from config.json.'));
102
111
  }
103
112
  }
104
113
  function failSync(mode, message) {
@@ -124,7 +133,7 @@ const runSync = async (options) => {
124
133
  return;
125
134
  }
126
135
  const projectRoot = options.projectRoot ? path_1.default.resolve(options.projectRoot) : process.cwd();
127
- const config = (0, config_loader_1.loadFraimConfig)();
136
+ const config = (0, config_loader_1.loadFraimConfig)((0, project_fraim_paths_1.getWorkspaceConfigPath)(projectRoot));
128
137
  const fraimDir = (0, project_fraim_paths_1.getWorkspaceFraimDir)(projectRoot);
129
138
  const refreshLocalIgnoreConfig = () => {
130
139
  const ignoreUpdate = (0, fraim_gitignore_1.ensureFraimSyncedContentLocallyExcluded)(projectRoot);
@@ -140,7 +149,7 @@ const runSync = async (options) => {
140
149
  if (isGlobal && !options.skipUpdates) {
141
150
  console.log(chalk_1.default.yellow('You are running a global installation of FRAIM.'));
142
151
  console.log(chalk_1.default.gray('Updates are not automatic in this mode.'));
143
- console.log(chalk_1.default.cyan('Recommended: Use "npx fraim-framework@latest sync" instead.\n'));
152
+ console.log(chalk_1.default.cyan('Recommended: Use "npx fraim@latest sync" instead.\n'));
144
153
  }
145
154
  const { syncFromRemote } = await Promise.resolve().then(() => __importStar(require('../utils/remote-sync')));
146
155
  // Allow `FRAIM_LOCAL_SYNC=1` to flip into local-mode without needing
@@ -162,12 +171,23 @@ const runSync = async (options) => {
162
171
  });
163
172
  if (result.success) {
164
173
  console.log(chalk_1.default.green(`Successfully synced ${result.employeeJobsSynced} ai-employee jobs, ${result.managerJobsSynced} ai-manager jobs, ${result.skillsSynced} skills, ${result.rulesSynced} rules, ${result.scriptsSynced} scripts, and ${result.docsSynced} docs from local server`));
165
- updateVersionInConfig(fraimDir);
174
+ removeLegacyVersionFromConfig(fraimDir);
175
+ writeSyncMetadata(fraimDir, 'local', localUrl);
166
176
  refreshLocalIgnoreConfig();
167
177
  const adapterUpdates = (0, agent_adapters_1.ensureAgentAdapterFiles)(projectRoot);
168
178
  if (adapterUpdates.length > 0) {
169
179
  console.log(chalk_1.default.green(`Updated FRAIM agent adapter files: ${adapterUpdates.join(', ')}`));
170
180
  }
181
+ if (config.repository?.provider === 'github' && (0, github_workflow_sync_1.isGitHubWorkflowAutomationEnabled)(config)) {
182
+ const workflowBundle = await (0, github_workflow_sync_1.fetchGitHubWorkflowBundle)({
183
+ remoteUrl: localUrl,
184
+ apiKey: 'local-dev'
185
+ });
186
+ const workflowResult = (0, github_workflow_sync_1.reconcileGitHubWorkflowAssets)({ projectRoot, files: workflowBundle, config });
187
+ for (const line of (0, github_workflow_sync_1.formatGitHubWorkflowSyncSummary)(workflowResult)) {
188
+ console.log(chalk_1.default.green(line));
189
+ }
190
+ }
171
191
  return;
172
192
  }
173
193
  console.error(chalk_1.default.red(`Local sync failed: ${result.error}`));
@@ -199,14 +219,24 @@ const runSync = async (options) => {
199
219
  console.error(chalk_1.default.yellow('Check your API key and network connection.'));
200
220
  if (process.env.TEST_MODE === 'true') {
201
221
  console.log(chalk_1.default.yellow('TEST_MODE: Continuing without remote sync (server may be unavailable).'));
202
- updateVersionInConfig(fraimDir);
203
222
  return;
204
223
  }
205
224
  failSync(failHard, `Remote sync failed: ${result.error}`);
206
225
  }
207
226
  console.log(chalk_1.default.green(`Successfully synced ${result.employeeJobsSynced} ai-employee jobs, ${result.managerJobsSynced} ai-manager jobs, ${result.skillsSynced} skills, ${result.rulesSynced} rules, ${result.scriptsSynced} scripts, and ${result.docsSynced} docs from remote`));
208
- updateVersionInConfig(fraimDir);
227
+ removeLegacyVersionFromConfig(fraimDir);
228
+ writeSyncMetadata(fraimDir, 'remote', config.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me');
209
229
  refreshLocalIgnoreConfig();
230
+ if (config.repository?.provider === 'github' && (0, github_workflow_sync_1.isGitHubWorkflowAutomationEnabled)(config)) {
231
+ const workflowBundle = await (0, github_workflow_sync_1.fetchGitHubWorkflowBundle)({
232
+ remoteUrl: config.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me',
233
+ apiKey
234
+ });
235
+ const workflowResult = (0, github_workflow_sync_1.reconcileGitHubWorkflowAssets)({ projectRoot, files: workflowBundle, config });
236
+ for (const line of (0, github_workflow_sync_1.formatGitHubWorkflowSyncSummary)(workflowResult)) {
237
+ console.log(chalk_1.default.green(line));
238
+ }
239
+ }
210
240
  const adapterUpdates = (0, agent_adapters_1.ensureAgentAdapterFiles)(projectRoot);
211
241
  if (adapterUpdates.length > 0) {
212
242
  console.log(chalk_1.default.green(`Updated FRAIM agent adapter files: ${adapterUpdates.join(', ')}`));
@@ -41,13 +41,66 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
41
41
  return (mod && mod.__esModule) ? mod : { "default": mod };
42
42
  };
43
43
  Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.getNpmMajorVersion = getNpmMajorVersion;
45
+ exports.diagnoseFraimMcpLaunchPlan = diagnoseFraimMcpLaunchPlan;
44
46
  exports.getMCPConnectivityChecks = getMCPConnectivityChecks;
45
47
  const fs_1 = __importDefault(require("fs"));
46
48
  const path_1 = __importDefault(require("path"));
47
49
  const os_1 = __importDefault(require("os"));
50
+ const child_process_1 = require("child_process");
48
51
  const axios_1 = __importDefault(require("axios"));
49
52
  const toml = __importStar(require("toml"));
50
53
  const ide_detector_1 = require("../../setup/ide-detector");
54
+ const fraim_mcp_latest_launcher_1 = require("../../mcp/fraim-mcp-latest-launcher");
55
+ function getNpmMajorVersion() {
56
+ try {
57
+ const command = process.platform === 'win32' ? (process.env.ComSpec || 'cmd.exe') : 'npm';
58
+ const args = process.platform === 'win32' ? ['/d', '/s', '/c', 'npm --version'] : ['--version'];
59
+ const output = (0, child_process_1.execFileSync)(command, args, {
60
+ encoding: 'utf8',
61
+ stdio: ['ignore', 'pipe', 'ignore']
62
+ });
63
+ const major = Number.parseInt(output.trim().split('.')[0], 10);
64
+ return Number.isFinite(major) ? major : null;
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ }
70
+ function diagnoseFraimMcpLaunchPlan(fraimServer, platform = process.platform, npmMajorVersion = getNpmMajorVersion()) {
71
+ const command = String(fraimServer?.command || '');
72
+ const args = Array.isArray(fraimServer?.args) ? fraimServer.args.map(String) : [];
73
+ const usesDirectLatest = command.toLowerCase().includes('npx')
74
+ && args.some((arg) => arg === 'fraim-framework@latest' || arg === 'fraim@latest');
75
+ if (usesDirectLatest) {
76
+ const isKnownBrokenWindowsNpm = platform === 'win32' && npmMajorVersion !== null && npmMajorVersion >= 11;
77
+ return {
78
+ status: isKnownBrokenWindowsNpm ? 'error' : 'warning',
79
+ message: isKnownBrokenWindowsNpm
80
+ ? 'FRAIM MCP config uses direct npx @latest, which is fragile on Windows npm 11'
81
+ : 'FRAIM MCP config uses direct npx @latest',
82
+ suggestion: 'Run: fraim add-ide to regenerate MCP config with the FRAIM latest launcher',
83
+ details: {
84
+ launchPlanSource: 'direct-npx-latest',
85
+ platform,
86
+ npmMajorVersion,
87
+ args
88
+ }
89
+ };
90
+ }
91
+ const launcherPath = (0, fraim_mcp_latest_launcher_1.getFraimMcpLatestLauncherPath)();
92
+ const usesLatestLauncher = command === process.execPath && args.length > 0 && path_1.default.resolve(args[0]) === path_1.default.resolve(launcherPath);
93
+ if (usesLatestLauncher) {
94
+ return {
95
+ status: 'passed',
96
+ message: 'FRAIM MCP config uses the latest launcher',
97
+ details: {
98
+ launchPlanSource: 'fraim-latest-launcher'
99
+ }
100
+ };
101
+ }
102
+ return null;
103
+ }
51
104
  /**
52
105
  * Test FRAIM connectivity by calling fraim_connect
53
106
  */
@@ -325,6 +378,16 @@ async function validateIDEMCPConfig(ide) {
325
378
  details: { configPath }
326
379
  };
327
380
  }
381
+ const launchPlanDiagnostic = diagnoseFraimMcpLaunchPlan(fraimServer);
382
+ if (launchPlanDiagnostic && launchPlanDiagnostic.status !== 'passed') {
383
+ return {
384
+ ...launchPlanDiagnostic,
385
+ details: {
386
+ ...launchPlanDiagnostic.details,
387
+ configPath
388
+ }
389
+ };
390
+ }
328
391
  // Check for auth header (for HTTP servers)
329
392
  if (fraimServer.url) {
330
393
  const hasAuth = fraimServer.headers?.Authorization || fraimServer.http_headers?.Authorization;
@@ -339,10 +402,11 @@ async function validateIDEMCPConfig(ide) {
339
402
  }
340
403
  return {
341
404
  status: 'passed',
342
- message: `${ide.name} MCP config valid`,
405
+ message: launchPlanDiagnostic?.message || `${ide.name} MCP config valid`,
343
406
  details: {
344
407
  configPath,
345
- serverCount: Object.keys(servers).length
408
+ serverCount: Object.keys(servers).length,
409
+ ...(launchPlanDiagnostic?.details || {})
346
410
  }
347
411
  };
348
412
  }
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
3
  * Job checks for FRAIM doctor command
4
- * Validates job installation and versions
4
+ * Validates job installation
5
5
  * Issue #144: Enhanced doctor command
6
6
  */
7
7
  var __importDefault = (this && this.__importDefault) || function (mod) {
@@ -100,69 +100,6 @@ function checkJobStubsPresent() {
100
100
  }
101
101
  };
102
102
  }
103
- /**
104
- * Check if jobs are up to date
105
- */
106
- function checkJobsUpToDate() {
107
- return {
108
- name: 'Jobs up to date',
109
- category: 'jobs',
110
- critical: false,
111
- run: async () => {
112
- const configPath = (0, project_fraim_paths_1.getWorkspaceConfigPath)(process.cwd());
113
- if (!fs_1.default.existsSync(configPath)) {
114
- return {
115
- status: 'warning',
116
- message: 'Cannot check job version - config missing'
117
- };
118
- }
119
- try {
120
- const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
121
- const possiblePaths = [
122
- path_1.default.join(__dirname, '../../../../package.json'),
123
- path_1.default.join(__dirname, '../../../package.json'),
124
- path_1.default.join(process.cwd(), 'package.json')
125
- ];
126
- let packageJson = null;
127
- for (const pkgPath of possiblePaths) {
128
- if (fs_1.default.existsSync(pkgPath)) {
129
- packageJson = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf8'));
130
- break;
131
- }
132
- }
133
- if (!packageJson) {
134
- return {
135
- status: 'warning',
136
- message: 'Cannot determine package version'
137
- };
138
- }
139
- const currentVersion = packageJson.version;
140
- const configVersion = config.version;
141
- if (configVersion === currentVersion) {
142
- return {
143
- status: 'passed',
144
- message: `Jobs up to date (v${currentVersion})`,
145
- details: { version: currentVersion }
146
- };
147
- }
148
- return {
149
- status: 'warning',
150
- message: `Jobs outdated (v${configVersion} -> v${currentVersion})`,
151
- suggestion: 'Update local job stubs with fraim sync.',
152
- command: 'fraim sync',
153
- details: { configVersion, currentVersion }
154
- };
155
- }
156
- catch (error) {
157
- return {
158
- status: 'error',
159
- message: 'Failed to check job version',
160
- details: { error: error.message }
161
- };
162
- }
163
- }
164
- };
165
- }
166
103
  /**
167
104
  * Check if override syntax is valid
168
105
  */
@@ -245,7 +182,6 @@ function getJobChecks() {
245
182
  return [
246
183
  checkJobsDirectoryExists(),
247
184
  checkJobStubsPresent(),
248
- checkJobsUpToDate(),
249
185
  checkOverrideSyntaxValid()
250
186
  ];
251
187
  }