fraim 2.0.335 → 2.0.337

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.
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.doctorCommand = void 0;
4
4
  exports.getAllChecks = getAllChecks;
5
5
  exports.runFixMcpRepair = runFixMcpRepair;
6
+ exports.runRepairMcpCache = runRepairMcpCache;
6
7
  const commander_1 = require("commander");
7
8
  const fs_1 = require("fs");
8
9
  const path_1 = require("path");
@@ -17,6 +18,8 @@ const mcp_connectivity_checks_1 = require("../doctor/checks/mcp-connectivity-che
17
18
  const scripts_checks_1 = require("../doctor/checks/scripts-checks");
18
19
  const agent_cli_health_checks_1 = require("../doctor/checks/agent-cli-health-checks");
19
20
  const llm_auth_route_checks_1 = require("../doctor/checks/llm-auth-route-checks");
21
+ const fraim_mcp_cache_repair_1 = require("../doctor/checks/fraim-mcp-cache-repair");
22
+ const fraim_mcp_latest_launcher_1 = require("../mcp/fraim-mcp-latest-launcher");
20
23
  const add_ide_1 = require("./add-ide");
21
24
  // Read version from package.json
22
25
  const getFramVersion = () => {
@@ -85,18 +88,34 @@ async function runFixMcpRepair(options, repair = add_ide_1.runAddIDE) {
85
88
  console.log = originalLog;
86
89
  }
87
90
  }
91
+ async function runRepairMcpCache(options, dependencies = {}) {
92
+ if (!options.repairMcpCache) {
93
+ return;
94
+ }
95
+ const ensureShims = dependencies.ensureShims ?? fraim_mcp_latest_launcher_1.ensureFraimMcpLatestLauncher;
96
+ const repairCache = dependencies.repairCache ?? fraim_mcp_cache_repair_1.repairFraimMcpPackageCache;
97
+ const warmPackage = dependencies.warmPackage ?? fraim_mcp_cache_repair_1.warmFraimPackage;
98
+ ensureShims();
99
+ const result = repairCache({});
100
+ if (!options.json) {
101
+ console.log(`FRAIM MCP package cache repair removed ${result.removedBrokenNpxEntries.length} broken _npx entr${result.removedBrokenNpxEntries.length === 1 ? 'y' : 'ies'} and ${result.removedStaleRetryCaches.length} stale retry cache${result.removedStaleRetryCaches.length === 1 ? '' : 's'}.`);
102
+ }
103
+ await warmPackage(FRAIM_VERSION === 'unknown' ? 'latest' : FRAIM_VERSION);
104
+ }
88
105
  exports.doctorCommand = new commander_1.Command('doctor')
89
106
  .description('Validate FRAIM installation and configuration')
90
107
  .option('--test-mcp', 'Test only MCP server connectivity')
91
108
  .option('--test-config', 'Validate only configuration files')
92
109
  .option('--test-jobs', 'Check only job status')
93
110
  .option('--fix-mcp', 'Repair FRAIM-owned MCP server entries in detected IDE configs, then validate MCP connectivity')
111
+ .option('--repair-mcp-cache', 'Repair corrupted FRAIM MCP package cache entries, then validate MCP connectivity')
94
112
  .option('--verbose', 'Show detailed output including successful checks')
95
113
  .option('--json', 'Output results as JSON')
96
114
  .action(async (cmdOptions) => {
97
115
  const startTime = Date.now();
98
116
  const options = {
99
- testMcp: cmdOptions.testMcp || cmdOptions.fixMcp,
117
+ testMcp: cmdOptions.testMcp || cmdOptions.fixMcp || cmdOptions.repairMcpCache,
118
+ repairMcpCache: cmdOptions.repairMcpCache,
100
119
  testConfig: cmdOptions.testConfig,
101
120
  testJobs: cmdOptions.testJobs,
102
121
  fixMcp: cmdOptions.fixMcp,
@@ -110,6 +129,7 @@ exports.doctorCommand = new commander_1.Command('doctor')
110
129
  testConfig: options.testConfig || false,
111
130
  testJobs: options.testJobs || false,
112
131
  fixMcp: options.fixMcp || false,
132
+ repairMcpCache: options.repairMcpCache || false,
113
133
  verbose: options.verbose || false,
114
134
  json: options.json || false
115
135
  }
@@ -125,12 +145,15 @@ exports.doctorCommand = new commander_1.Command('doctor')
125
145
  trackMetric('doctor.flags.test_jobs', 1);
126
146
  if (options.fixMcp)
127
147
  trackMetric('doctor.flags.fix_mcp', 1);
148
+ if (options.repairMcpCache)
149
+ trackMetric('doctor.flags.repair_mcp_cache', 1);
128
150
  if (options.verbose)
129
151
  trackMetric('doctor.flags.verbose', 1);
130
152
  if (options.json)
131
153
  trackMetric('doctor.flags.json', 1);
132
154
  try {
133
155
  await runFixMcpRepair(options);
156
+ await runRepairMcpCache(options);
134
157
  // Collect all checks
135
158
  const checks = getAllChecks();
136
159
  // Run checks
@@ -311,13 +311,11 @@ const runSync = async (options) => {
311
311
  if (ignoreUpdate.gitignoreUpdated) {
312
312
  console.log(chalk_1.default.green('Removed legacy FRAIM sync block from .gitignore'));
313
313
  }
314
- if (options.projectAdapters) {
315
- const allowedTypes = resolveAllowedConfigTypes();
316
- await cleanupStaleAdapterFiles(projectRoot, allowedTypes);
317
- const adapterUpdates = (0, agent_adapters_1.ensureAgentAdapterFiles)(projectRoot, allowedTypes);
318
- if (adapterUpdates.length > 0) {
319
- console.log(chalk_1.default.green(`Updated FRAIM agent adapter files: ${adapterUpdates.join(', ')}`));
320
- }
314
+ const allowedLocalTypes = resolveAllowedConfigTypes();
315
+ await cleanupStaleAdapterFiles(projectRoot, allowedLocalTypes);
316
+ const localAdapterUpdates = (0, agent_adapters_1.ensureAgentAdapterFiles)(projectRoot, allowedLocalTypes);
317
+ if (localAdapterUpdates.length > 0) {
318
+ console.log(chalk_1.default.green(`Updated FRAIM agent adapter files: ${localAdapterUpdates.join(', ')}`));
321
319
  }
322
320
  await refreshOrgCache(localUrl, 'local-dev');
323
321
  await refreshManagerCache(localUrl, 'local-dev');
@@ -437,13 +435,11 @@ const runSync = async (options) => {
437
435
  if (ignoreUpdate.gitignoreUpdated) {
438
436
  console.log(chalk_1.default.green('Removed legacy FRAIM sync block from .gitignore'));
439
437
  }
440
- if (options.projectAdapters) {
441
- const allowedTypes = resolveAllowedConfigTypes();
442
- await cleanupStaleAdapterFiles(projectRoot, allowedTypes);
443
- const adapterUpdates = (0, agent_adapters_1.ensureAgentAdapterFiles)(projectRoot, allowedTypes);
444
- if (adapterUpdates.length > 0) {
445
- console.log(chalk_1.default.green(`Updated FRAIM agent adapter files: ${adapterUpdates.join(', ')}`));
446
- }
438
+ const allowedTypes = resolveAllowedConfigTypes();
439
+ await cleanupStaleAdapterFiles(projectRoot, allowedTypes);
440
+ const adapterUpdates = (0, agent_adapters_1.ensureAgentAdapterFiles)(projectRoot, allowedTypes);
441
+ if (adapterUpdates.length > 0) {
442
+ console.log(chalk_1.default.green(`Updated FRAIM agent adapter files: ${adapterUpdates.join(', ')}`));
447
443
  }
448
444
  console.log(chalk_1.default.green('✅ Project-level sync complete.'));
449
445
  };
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DEFAULT_RETRY_CACHE_AGE_FLOOR_MS = exports.CORRUPTED_NPX_PACKAGE_CACHE_CLASSIFICATION = exports.FRAIM_MCP_CACHE_REPAIR_DIAGNOSTIC_VERSION = void 0;
7
+ exports.getFraimMcpCacheRoot = getFraimMcpCacheRoot;
8
+ exports.inspectFraimMcpPackageCache = inspectFraimMcpPackageCache;
9
+ exports.repairFraimMcpPackageCache = repairFraimMcpPackageCache;
10
+ exports.buildFraimMcpCacheDiagnostic = buildFraimMcpCacheDiagnostic;
11
+ exports.warmFraimPackage = warmFraimPackage;
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const os_1 = __importDefault(require("os"));
14
+ const path_1 = __importDefault(require("path"));
15
+ const child_process_1 = require("child_process");
16
+ const fraim_mcp_latest_launcher_1 = require("../../mcp/fraim-mcp-latest-launcher");
17
+ exports.FRAIM_MCP_CACHE_REPAIR_DIAGNOSTIC_VERSION = 'fraim-mcp-cache-repair-v1';
18
+ exports.CORRUPTED_NPX_PACKAGE_CACHE_CLASSIFICATION = 'corrupted-npx-package-cache';
19
+ exports.DEFAULT_RETRY_CACHE_AGE_FLOOR_MS = 7 * 24 * 60 * 60 * 1000;
20
+ const FRAIM_MCP_CACHE_REPAIR_COMMAND = 'npx --yes fraim@latest doctor --repair-mcp-cache';
21
+ const VALID_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/;
22
+ function userDir() {
23
+ return process.env.FRAIM_USER_DIR || path_1.default.join(os_1.default.homedir(), '.fraim');
24
+ }
25
+ function getFraimMcpCacheRoot() {
26
+ return path_1.default.join(userDir(), 'npm-cache', 'mcp');
27
+ }
28
+ function versionFromCacheDir(versionCacheDir) {
29
+ const base = path_1.default.basename(versionCacheDir);
30
+ if (!base.startsWith('fraim-'))
31
+ return null;
32
+ const version = base.slice('fraim-'.length);
33
+ return VALID_VERSION_PATTERN.test(version) ? version : null;
34
+ }
35
+ function safeChild(parent, child) {
36
+ const parentResolved = path_1.default.resolve(parent);
37
+ const childResolved = path_1.default.resolve(child);
38
+ return childResolved === parentResolved || childResolved.startsWith(parentResolved + path_1.default.sep);
39
+ }
40
+ function directoryExists(dir) {
41
+ try {
42
+ return fs_1.default.statSync(dir).isDirectory();
43
+ }
44
+ catch {
45
+ return false;
46
+ }
47
+ }
48
+ function findBrokenNpxEntries(versionCacheDir) {
49
+ const version = versionFromCacheDir(versionCacheDir);
50
+ if (!version)
51
+ return [];
52
+ const npxRoot = path_1.default.join(versionCacheDir, '_npx');
53
+ if (!directoryExists(npxRoot))
54
+ return [];
55
+ return fs_1.default.readdirSync(npxRoot, { withFileTypes: true })
56
+ .filter(entry => entry.isDirectory())
57
+ .map(entry => path_1.default.join(npxRoot, entry.name))
58
+ .filter(entryPath => safeChild(npxRoot, entryPath))
59
+ .filter(entryPath => (directoryExists(path_1.default.join(entryPath, 'node_modules')) &&
60
+ !fs_1.default.existsSync(path_1.default.join(entryPath, 'package.json'))))
61
+ .map(entryPath => ({ version, path: entryPath }));
62
+ }
63
+ function findStaleRetryCaches(cacheRoot, options = {}) {
64
+ if (!directoryExists(cacheRoot))
65
+ return [];
66
+ const nowMs = options.nowMs ?? Date.now();
67
+ const ageFloorMs = options.ageFloorMs ?? exports.DEFAULT_RETRY_CACHE_AGE_FLOOR_MS;
68
+ return fs_1.default.readdirSync(cacheRoot, { withFileTypes: true })
69
+ .filter(entry => entry.isDirectory() && /^retry-\d+-\d+$/.test(entry.name))
70
+ .map(entry => {
71
+ const entryPath = path_1.default.join(cacheRoot, entry.name);
72
+ const stat = fs_1.default.statSync(entryPath);
73
+ return { path: entryPath, mtimeMs: stat.mtimeMs, ageMs: nowMs - stat.mtimeMs };
74
+ })
75
+ .filter(entry => entry.ageMs >= ageFloorMs && safeChild(cacheRoot, entry.path));
76
+ }
77
+ function inspectFraimMcpPackageCache(options = {}) {
78
+ const cacheRoot = options.cacheRoot ?? getFraimMcpCacheRoot();
79
+ const brokenNpxEntries = [];
80
+ if (directoryExists(cacheRoot)) {
81
+ for (const entry of fs_1.default.readdirSync(cacheRoot, { withFileTypes: true })) {
82
+ if (!entry.isDirectory())
83
+ continue;
84
+ const versionCacheDir = path_1.default.join(cacheRoot, entry.name);
85
+ brokenNpxEntries.push(...findBrokenNpxEntries(versionCacheDir));
86
+ }
87
+ }
88
+ const counts = new Map();
89
+ for (const entry of brokenNpxEntries) {
90
+ counts.set(entry.version, (counts.get(entry.version) ?? 0) + 1);
91
+ }
92
+ return {
93
+ cacheRoot,
94
+ brokenNpxEntries,
95
+ staleRetryCaches: findStaleRetryCaches(cacheRoot, {
96
+ nowMs: options.nowMs,
97
+ ageFloorMs: options.retryAgeFloorMs,
98
+ }),
99
+ affectedVersions: [...counts.entries()]
100
+ .sort(([a], [b]) => a.localeCompare(b))
101
+ .map(([version, brokenNpxEntryCount]) => ({ version, brokenNpxEntryCount })),
102
+ };
103
+ }
104
+ function repairFraimMcpPackageCache(options = {}) {
105
+ const inspection = inspectFraimMcpPackageCache(options);
106
+ const removedBrokenNpxEntries = [];
107
+ const removedStaleRetryCaches = [];
108
+ if (!options.dryRun) {
109
+ for (const entry of inspection.brokenNpxEntries) {
110
+ if (!safeChild(inspection.cacheRoot, entry.path))
111
+ continue;
112
+ fs_1.default.rmSync(entry.path, { recursive: true, force: true });
113
+ removedBrokenNpxEntries.push(entry);
114
+ }
115
+ for (const entry of inspection.staleRetryCaches) {
116
+ if (!safeChild(inspection.cacheRoot, entry.path))
117
+ continue;
118
+ fs_1.default.rmSync(entry.path, { recursive: true, force: true });
119
+ removedStaleRetryCaches.push(entry);
120
+ }
121
+ }
122
+ return {
123
+ ...inspection,
124
+ removedBrokenNpxEntries,
125
+ removedStaleRetryCaches,
126
+ };
127
+ }
128
+ function buildFraimMcpCacheDiagnostic(options = {}) {
129
+ const inspection = inspectFraimMcpPackageCache({
130
+ cacheRoot: options.cacheRoot,
131
+ nowMs: options.nowMs,
132
+ });
133
+ if (inspection.brokenNpxEntries.length === 0) {
134
+ return {
135
+ status: 'passed',
136
+ message: 'FRAIM MCP package cache has no corrupted _npx entries',
137
+ details: {
138
+ diagnosticVersion: exports.FRAIM_MCP_CACHE_REPAIR_DIAGNOSTIC_VERSION,
139
+ cacheRoot: inspection.cacheRoot,
140
+ affectedVersions: [],
141
+ staleRetryCacheCount: inspection.staleRetryCaches.length,
142
+ },
143
+ };
144
+ }
145
+ const status = options.mcpRuntimeStatus === 'error' ? 'error' : 'warning';
146
+ return {
147
+ status,
148
+ message: 'FRAIM MCP package cache is corrupted',
149
+ suggestion: `Run: ${FRAIM_MCP_CACHE_REPAIR_COMMAND}`,
150
+ details: {
151
+ diagnosticVersion: exports.FRAIM_MCP_CACHE_REPAIR_DIAGNOSTIC_VERSION,
152
+ classification: exports.CORRUPTED_NPX_PACKAGE_CACHE_CLASSIFICATION,
153
+ cacheRoot: inspection.cacheRoot,
154
+ affectedVersions: inspection.affectedVersions,
155
+ brokenNpxEntryCount: inspection.brokenNpxEntries.length,
156
+ staleRetryCacheCount: inspection.staleRetryCaches.length,
157
+ repairCommand: FRAIM_MCP_CACHE_REPAIR_COMMAND,
158
+ },
159
+ };
160
+ }
161
+ function readRecordedLauncherVersion() {
162
+ try {
163
+ const state = JSON.parse(fs_1.default.readFileSync((0, fraim_mcp_latest_launcher_1.getFraimMcpLauncherStatePath)(), 'utf8'));
164
+ const version = typeof state?.version === 'string' ? state.version : '';
165
+ return VALID_VERSION_PATTERN.test(version) ? version : null;
166
+ }
167
+ catch {
168
+ return null;
169
+ }
170
+ }
171
+ function versionCacheDirName(version) {
172
+ return `fraim-${version.replace(/[^a-zA-Z0-9._-]/g, '_')}`;
173
+ }
174
+ async function warmFraimPackage(versionFallback) {
175
+ const version = readRecordedLauncherVersion() || versionFallback;
176
+ const cacheDir = path_1.default.join(getFraimMcpCacheRoot(), versionCacheDirName(version));
177
+ const command = process.platform === 'win32' ? (process.env.ComSpec || 'cmd.exe') : 'npx';
178
+ const args = process.platform === 'win32'
179
+ ? ['/d', '/s', '/c', `npx --yes --package fraim@${version} fraim --version`]
180
+ : ['--yes', '--package', `fraim@${version}`, 'fraim', '--version'];
181
+ fs_1.default.mkdirSync(cacheDir, { recursive: true });
182
+ (0, child_process_1.execFileSync)(command, args, {
183
+ stdio: ['ignore', 'pipe', 'pipe'],
184
+ windowsHide: true,
185
+ env: { ...process.env, npm_config_cache: cacheDir },
186
+ timeout: 120000,
187
+ });
188
+ }
@@ -59,6 +59,7 @@ const redact_secrets_1 = require("../../../core/utils/redact-secrets");
59
59
  const ide_detector_1 = require("../../setup/ide-detector");
60
60
  const fraim_mcp_latest_launcher_1 = require("../../mcp/fraim-mcp-latest-launcher");
61
61
  const fraim_mcp_diagnostics_1 = require("./fraim-mcp-diagnostics");
62
+ const fraim_mcp_cache_repair_1 = require("./fraim-mcp-cache-repair");
62
63
  // Cache the npm major version so execFileSync is called at most once per process.
63
64
  // Without caching, each IDE config check calls diagnoseFraimMcpLaunchPlan which calls
64
65
  // getNpmMajorVersion, and execFileSync blocks the event loop for ~2s on Windows.
@@ -1001,8 +1002,18 @@ function getStdioMCPRuntimeChecks() {
1001
1002
  /**
1002
1003
  * Get all MCP connectivity checks
1003
1004
  */
1004
- function getMCPConnectivityChecks() {
1005
+ function getMCPConnectivityChecks(dependencies = {}) {
1005
1006
  const checks = [];
1007
+ const ensureLauncher = dependencies.ensureLauncher ?? fraim_mcp_latest_launcher_1.ensureFraimMcpLatestLauncher;
1008
+ const runStdioServer = dependencies.testStdioServer ?? testStdioMCPServer;
1009
+ ensureLauncher();
1010
+ const npx = dependencies.npxPath ?? (0, fraim_mcp_latest_launcher_1.getFraimNpxShimPath)();
1011
+ const fraimMcp = dependencies.fraimMcpPath ?? (0, fraim_mcp_latest_launcher_1.getFraimMcpShimPath)();
1012
+ let fraimRuntimeCheck = null;
1013
+ const runFraimRuntimeCheck = () => {
1014
+ fraimRuntimeCheck ??= runStdioServer('fraim', fraimMcp, []);
1015
+ return fraimRuntimeCheck;
1016
+ };
1006
1017
  // Check 1: Test FRAIM server connectivity with actual API key
1007
1018
  checks.push({
1008
1019
  name: 'FRAIM server connectivity',
@@ -1016,6 +1027,14 @@ function getMCPConnectivityChecks() {
1016
1027
  critical: false,
1017
1028
  run: testFraimLocalProxyTroubleshooting
1018
1029
  });
1030
+ checks.push({
1031
+ name: 'FRAIM MCP package cache health',
1032
+ category: 'mcpConnectivity',
1033
+ critical: false,
1034
+ run: async () => (0, fraim_mcp_cache_repair_1.buildFraimMcpCacheDiagnostic)({
1035
+ mcpRuntimeStatus: (await runFraimRuntimeCheck()).status
1036
+ })
1037
+ });
1019
1038
  // Check 2: Validate each installed IDE's MCP config
1020
1039
  const installedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
1021
1040
  for (const ide of installedIDEs) {
@@ -1029,6 +1048,23 @@ function getMCPConnectivityChecks() {
1029
1048
  // Check 3 (Issue #532): Confirm stdio MCP servers respond at runtime.
1030
1049
  // "Disconnected" in mcp list is expected on-demand-spawn behavior. These checks
1031
1050
  // verify that each server can actually start and complete a JSON-RPC handshake.
1032
- checks.push(...getStdioMCPRuntimeChecks());
1051
+ checks.push({
1052
+ name: 'git stdio runtime check',
1053
+ category: 'mcpConnectivity',
1054
+ critical: false,
1055
+ run: () => runStdioServer('git', npx, ['-y', '@cyanheads/git-mcp-server'])
1056
+ });
1057
+ checks.push({
1058
+ name: 'playwright stdio runtime check',
1059
+ category: 'mcpConnectivity',
1060
+ critical: false,
1061
+ run: () => runStdioServer('playwright', npx, ['-y', '@playwright/mcp'])
1062
+ });
1063
+ checks.push({
1064
+ name: 'fraim stdio runtime check',
1065
+ category: 'mcpConnectivity',
1066
+ critical: false,
1067
+ run: runFraimRuntimeCheck
1068
+ });
1033
1069
  return checks;
1034
1070
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim",
3
- "version": "2.0.335",
3
+ "version": "2.0.337",
4
4
  "description": "FRAIM core CLI and MCP package.",
5
5
  "main": "index.js",
6
6
  "bin": {