fraim-framework 2.0.96 → 2.0.98
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 +1 -1
- package/dist/src/cli/commands/add-ide.js +1 -1
- package/dist/src/cli/commands/doctor.js +6 -6
- package/dist/src/cli/commands/init-project.js +63 -52
- package/dist/src/cli/commands/list-overridable.js +33 -55
- package/dist/src/cli/commands/list.js +35 -9
- package/dist/src/cli/commands/migrate-project-fraim.js +42 -0
- package/dist/src/cli/commands/override.js +18 -39
- package/dist/src/cli/commands/setup.js +1 -1
- package/dist/src/cli/commands/sync.js +34 -27
- package/dist/src/cli/doctor/check-runner.js +3 -3
- package/dist/src/cli/doctor/checks/global-setup-checks.js +13 -13
- package/dist/src/cli/doctor/checks/project-setup-checks.js +12 -12
- package/dist/src/cli/doctor/checks/scripts-checks.js +2 -2
- package/dist/src/cli/doctor/checks/workflow-checks.js +56 -60
- package/dist/src/cli/doctor/reporters/console-reporter.js +1 -1
- package/dist/src/cli/fraim.js +3 -1
- package/dist/src/cli/mcp/mcp-server-registry.js +1 -1
- package/dist/src/cli/services/device-flow-service.js +83 -0
- package/dist/src/cli/setup/auto-mcp-setup.js +2 -2
- package/dist/src/cli/setup/first-run.js +4 -3
- package/dist/src/cli/utils/agent-adapters.js +126 -0
- package/dist/src/cli/utils/fraim-gitignore.js +15 -21
- package/dist/src/cli/utils/project-bootstrap.js +93 -0
- package/dist/src/cli/utils/remote-sync.js +20 -67
- package/dist/src/core/ai-mentor.js +31 -49
- package/dist/src/core/config-loader.js +57 -62
- package/dist/src/core/config-writer.js +75 -0
- package/dist/src/core/types.js +1 -1
- package/dist/src/core/utils/job-parser.js +176 -0
- package/dist/src/core/utils/local-registry-resolver.js +61 -71
- package/dist/src/core/utils/project-fraim-migration.js +103 -0
- package/dist/src/core/utils/project-fraim-paths.js +38 -0
- package/dist/src/core/utils/stub-generator.js +41 -75
- package/dist/src/core/utils/workflow-parser.js +5 -3
- package/dist/src/local-mcp-server/learning-context-builder.js +229 -0
- package/dist/src/local-mcp-server/stdio-server.js +103 -43
- package/dist/src/local-mcp-server/usage-collector.js +126 -27
- package/index.js +1 -1
- package/package.json +3 -4
|
@@ -10,27 +10,25 @@ const path_1 = __importDefault(require("path"));
|
|
|
10
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
11
|
const axios_1 = __importDefault(require("axios"));
|
|
12
12
|
const git_utils_1 = require("../../core/utils/git-utils");
|
|
13
|
+
const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
|
|
13
14
|
exports.overrideCommand = new commander_1.Command('override')
|
|
14
15
|
.description('Create a local override for a FRAIM registry file')
|
|
15
|
-
.argument('<path>', 'Registry path to override (e.g.,
|
|
16
|
+
.argument('<path>', 'Registry path to override (e.g., jobs/product-building/spec.md)')
|
|
16
17
|
.option('--inherit', 'Create override with {{ import }} directive (inherits from global)')
|
|
17
18
|
.option('--copy', 'Copy current content from server to local')
|
|
18
19
|
.option('--local', 'Fetch from local development server (port derived from git branch)')
|
|
19
20
|
.action(async (registryPath, options) => {
|
|
20
21
|
const projectRoot = process.cwd();
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
console.log(chalk_1.default.red('❌ .fraim/ directory not found. Run "fraim setup" or "fraim init-project" first.'));
|
|
22
|
+
const configPath = (0, project_fraim_paths_1.getWorkspaceConfigPath)(projectRoot);
|
|
23
|
+
const personalizedRootDisplayPath = (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('personalized-employee');
|
|
24
|
+
if (!(0, project_fraim_paths_1.workspaceFraimExists)(projectRoot)) {
|
|
25
|
+
console.log(chalk_1.default.red(`❌ ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)()} directory not found. Run "fraim setup" or "fraim init-project" first.`));
|
|
26
26
|
process.exit(1);
|
|
27
27
|
}
|
|
28
|
-
// Validate path format
|
|
29
28
|
if (registryPath.includes('..') || path_1.default.isAbsolute(registryPath)) {
|
|
30
29
|
console.log(chalk_1.default.red('❌ Invalid path. Path must be relative and cannot contain ".."'));
|
|
31
30
|
process.exit(1);
|
|
32
31
|
}
|
|
33
|
-
// Ensure exactly one option is specified
|
|
34
32
|
if (options.inherit && options.copy) {
|
|
35
33
|
console.log(chalk_1.default.red('❌ Cannot use both --inherit and --copy. Choose one.'));
|
|
36
34
|
process.exit(1);
|
|
@@ -39,13 +37,11 @@ exports.overrideCommand = new commander_1.Command('override')
|
|
|
39
37
|
console.log(chalk_1.default.red('❌ Must specify either --inherit or --copy.'));
|
|
40
38
|
process.exit(1);
|
|
41
39
|
}
|
|
42
|
-
|
|
43
|
-
const overridePath = path_1.default.join(fraimDir, 'personalized-employee', registryPath);
|
|
40
|
+
const overridePath = (0, project_fraim_paths_1.getWorkspaceFraimPath)(projectRoot, 'personalized-employee', registryPath);
|
|
44
41
|
const overrideDir = path_1.default.dirname(overridePath);
|
|
45
42
|
if (!fs_1.default.existsSync(overrideDir)) {
|
|
46
43
|
fs_1.default.mkdirSync(overrideDir, { recursive: true });
|
|
47
44
|
}
|
|
48
|
-
// Check if override already exists
|
|
49
45
|
if (fs_1.default.existsSync(overridePath)) {
|
|
50
46
|
console.log(chalk_1.default.yellow(`⚠️ Override already exists: ${registryPath}`));
|
|
51
47
|
console.log(chalk_1.default.gray(` Location: ${overridePath}`));
|
|
@@ -53,22 +49,18 @@ exports.overrideCommand = new commander_1.Command('override')
|
|
|
53
49
|
}
|
|
54
50
|
let content;
|
|
55
51
|
if (options.inherit) {
|
|
56
|
-
// Create file with import directive
|
|
57
52
|
content = `{{ import: ${registryPath} }}\n\n<!-- Add your custom content below -->\n`;
|
|
58
|
-
console.log(chalk_1.default.blue(
|
|
53
|
+
console.log(chalk_1.default.blue(`Creating inherited override for: ${registryPath}`));
|
|
59
54
|
}
|
|
60
55
|
else {
|
|
61
|
-
// Fetch current content from server using MCP protocol
|
|
62
56
|
const isLocal = options.local || false;
|
|
63
57
|
const serverType = isLocal ? 'local server' : 'remote server';
|
|
64
|
-
console.log(chalk_1.default.blue(
|
|
58
|
+
console.log(chalk_1.default.blue(`Fetching current content from ${serverType}: ${registryPath}`));
|
|
65
59
|
try {
|
|
66
|
-
// Read config to get remote URL
|
|
67
60
|
let config = {};
|
|
68
61
|
if (fs_1.default.existsSync(configPath)) {
|
|
69
62
|
config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
|
|
70
63
|
}
|
|
71
|
-
// Determine server URL
|
|
72
64
|
let serverUrl;
|
|
73
65
|
if (isLocal) {
|
|
74
66
|
const localPort = process.env.FRAIM_MCP_PORT ? parseInt(process.env.FRAIM_MCP_PORT) : (0, git_utils_1.getPort)();
|
|
@@ -78,13 +70,10 @@ exports.overrideCommand = new commander_1.Command('override')
|
|
|
78
70
|
serverUrl = config.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
|
|
79
71
|
}
|
|
80
72
|
const apiKey = isLocal ? 'local-dev' : (config.apiKey || process.env.FRAIM_API_KEY);
|
|
81
|
-
const headers = {
|
|
82
|
-
'Content-Type': 'application/json'
|
|
83
|
-
};
|
|
73
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
84
74
|
if (apiKey) {
|
|
85
75
|
headers['x-api-key'] = apiKey;
|
|
86
76
|
}
|
|
87
|
-
// First, establish session with fraim_connect
|
|
88
77
|
const connectRequest = {
|
|
89
78
|
jsonrpc: '2.0',
|
|
90
79
|
id: 0,
|
|
@@ -113,19 +102,15 @@ exports.overrideCommand = new commander_1.Command('override')
|
|
|
113
102
|
if (typeof sessionId !== 'string' || sessionId.trim().length === 0) {
|
|
114
103
|
throw new Error('fraim_connect did not return a sessionId');
|
|
115
104
|
}
|
|
116
|
-
// Now determine MCP tool and arguments based on path
|
|
117
105
|
let toolName;
|
|
118
106
|
let toolArgs;
|
|
119
107
|
if (registryPath.startsWith('workflows/')) {
|
|
120
|
-
// Extract workflow name from path
|
|
121
|
-
// e.g., "workflows/product-building/spec.md" -> "spec"
|
|
122
108
|
const parts = registryPath.split('/');
|
|
123
109
|
const workflowName = parts[parts.length - 1].replace('.md', '');
|
|
124
110
|
toolName = 'get_fraim_workflow';
|
|
125
111
|
toolArgs = { workflow: workflowName };
|
|
126
112
|
}
|
|
127
113
|
else if (registryPath.startsWith('jobs/')) {
|
|
128
|
-
// e.g., "jobs/product-building/feature-specification.md" -> "feature-specification"
|
|
129
114
|
const parts = registryPath.split('/');
|
|
130
115
|
const jobName = parts[parts.length - 1].replace('.md', '');
|
|
131
116
|
toolName = 'get_fraim_job';
|
|
@@ -136,7 +121,6 @@ exports.overrideCommand = new commander_1.Command('override')
|
|
|
136
121
|
toolArgs = { path: registryPath };
|
|
137
122
|
}
|
|
138
123
|
console.log(chalk_1.default.gray(` Using MCP tool: ${toolName}`));
|
|
139
|
-
// Make MCP JSON-RPC request
|
|
140
124
|
const mcpRequest = {
|
|
141
125
|
jsonrpc: '2.0',
|
|
142
126
|
id: 1,
|
|
@@ -144,19 +128,16 @@ exports.overrideCommand = new commander_1.Command('override')
|
|
|
144
128
|
params: {
|
|
145
129
|
name: toolName,
|
|
146
130
|
sessionId: sessionId.trim(),
|
|
147
|
-
arguments:
|
|
131
|
+
arguments: {
|
|
132
|
+
...toolArgs,
|
|
133
|
+
sessionId: sessionId.trim()
|
|
134
|
+
}
|
|
148
135
|
}
|
|
149
136
|
};
|
|
150
|
-
// Mirror sessionId in arguments for compatibility with both server and proxy paths.
|
|
151
|
-
mcpRequest.params.arguments = {
|
|
152
|
-
...mcpRequest.params.arguments,
|
|
153
|
-
sessionId: sessionId.trim()
|
|
154
|
-
};
|
|
155
137
|
const response = await axios_1.default.post(`${serverUrl}/mcp`, mcpRequest, {
|
|
156
138
|
headers,
|
|
157
139
|
timeout: 30000
|
|
158
140
|
});
|
|
159
|
-
// Extract content from MCP response
|
|
160
141
|
if (response.data.error) {
|
|
161
142
|
throw new Error(response.data.error.message || 'MCP request failed');
|
|
162
143
|
}
|
|
@@ -164,7 +145,6 @@ exports.overrideCommand = new commander_1.Command('override')
|
|
|
164
145
|
if (!result || !result.content || !Array.isArray(result.content)) {
|
|
165
146
|
throw new Error('Unexpected MCP response format');
|
|
166
147
|
}
|
|
167
|
-
// Extract text content from MCP response
|
|
168
148
|
const textContent = result.content.find((c) => c.type === 'text');
|
|
169
149
|
if (!textContent || !textContent.text) {
|
|
170
150
|
throw new Error('No text content in MCP response');
|
|
@@ -175,21 +155,20 @@ exports.overrideCommand = new commander_1.Command('override')
|
|
|
175
155
|
catch (error) {
|
|
176
156
|
console.log(chalk_1.default.red(`❌ Failed to fetch content from ${serverType}: ${error.message}`));
|
|
177
157
|
if (isLocal) {
|
|
178
|
-
console.log(chalk_1.default.gray(
|
|
158
|
+
console.log(chalk_1.default.gray(' Tip: Make sure the FRAIM server is running locally (npm run start:fraim)'));
|
|
179
159
|
}
|
|
180
160
|
else {
|
|
181
|
-
console.log(chalk_1.default.gray(
|
|
182
|
-
console.log(chalk_1.default.gray(
|
|
161
|
+
console.log(chalk_1.default.gray(' Tip: Check your API key and network connection'));
|
|
162
|
+
console.log(chalk_1.default.gray(' Or use --local to fetch from a locally running server'));
|
|
183
163
|
}
|
|
184
164
|
process.exit(1);
|
|
185
165
|
}
|
|
186
166
|
}
|
|
187
|
-
// Write override file
|
|
188
167
|
try {
|
|
189
168
|
fs_1.default.writeFileSync(overridePath, content, 'utf-8');
|
|
190
169
|
console.log(chalk_1.default.green(`✅ Created override: ${registryPath}`));
|
|
191
170
|
console.log(chalk_1.default.gray(` Location: ${overridePath}`));
|
|
192
|
-
console.log(chalk_1.default.gray(`
|
|
171
|
+
console.log(chalk_1.default.gray(` Overrides are stored in ${personalizedRootDisplayPath}`));
|
|
193
172
|
}
|
|
194
173
|
catch (error) {
|
|
195
174
|
console.log(chalk_1.default.red(`❌ Failed to write override file: ${error.message}`));
|
|
@@ -594,7 +594,7 @@ const runSetup = async (options) => {
|
|
|
594
594
|
console.log(chalk_1.default.cyan('\n📝 For future projects:'));
|
|
595
595
|
console.log(chalk_1.default.cyan(' 1. cd into any project directory'));
|
|
596
596
|
console.log(chalk_1.default.cyan(' 2. Run: fraim init-project'));
|
|
597
|
-
console.log(chalk_1.default.cyan(' 3.
|
|
597
|
+
console.log(chalk_1.default.cyan(' 3. Tell your AI agent: "Onboard this project"'));
|
|
598
598
|
if (mode === 'integrated') {
|
|
599
599
|
const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
|
|
600
600
|
const unconfiguredProviders = allProviderIds.filter(id => !tokens[id]);
|
|
@@ -45,6 +45,8 @@ const config_loader_1 = require("../../core/config-loader");
|
|
|
45
45
|
const version_utils_1 = require("../utils/version-utils");
|
|
46
46
|
const script_sync_utils_1 = require("../utils/script-sync-utils");
|
|
47
47
|
const git_utils_1 = require("../../core/utils/git-utils");
|
|
48
|
+
const agent_adapters_1 = require("../utils/agent-adapters");
|
|
49
|
+
const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
|
|
48
50
|
/**
|
|
49
51
|
* Load API key from user-level config (~/.fraim/config.json)
|
|
50
52
|
*/
|
|
@@ -57,8 +59,8 @@ function loadUserApiKey() {
|
|
|
57
59
|
const userConfig = JSON.parse(fs_1.default.readFileSync(userConfigPath, 'utf8'));
|
|
58
60
|
return userConfig.apiKey;
|
|
59
61
|
}
|
|
60
|
-
catch
|
|
61
|
-
console.warn(chalk_1.default.yellow('
|
|
62
|
+
catch {
|
|
63
|
+
console.warn(chalk_1.default.yellow('Failed to read user-level config for API key'));
|
|
62
64
|
return undefined;
|
|
63
65
|
}
|
|
64
66
|
}
|
|
@@ -73,29 +75,27 @@ function updateVersionInConfig(fraimDir) {
|
|
|
73
75
|
if (currentConfig.version !== newVersion) {
|
|
74
76
|
currentConfig.version = newVersion;
|
|
75
77
|
fs_1.default.writeFileSync(configPath, JSON.stringify(currentConfig, null, 2));
|
|
76
|
-
console.log(chalk_1.default.green(
|
|
78
|
+
console.log(chalk_1.default.green(`Updated FRAIM version to ${newVersion} in config.`));
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
|
-
catch
|
|
80
|
-
console.warn(chalk_1.default.yellow('
|
|
81
|
+
catch {
|
|
82
|
+
console.warn(chalk_1.default.yellow('Could not update version in config.json.'));
|
|
81
83
|
}
|
|
82
84
|
}
|
|
83
85
|
const runSync = async (options) => {
|
|
84
86
|
const projectRoot = process.cwd();
|
|
85
87
|
const config = (0, config_loader_1.loadFraimConfig)();
|
|
86
|
-
const fraimDir =
|
|
87
|
-
// Check if running via npx
|
|
88
|
+
const fraimDir = (0, project_fraim_paths_1.getWorkspaceFraimDir)(projectRoot);
|
|
88
89
|
const isNpx = process.env.npm_config_prefix === undefined || process.env.npm_lifecycle_event === 'npx';
|
|
89
90
|
const isGlobal = !isNpx && (process.env.npm_config_global === 'true' || process.env.npm_config_prefix);
|
|
90
91
|
if (isGlobal && !options.skipUpdates) {
|
|
91
|
-
console.log(chalk_1.default.yellow('
|
|
92
|
-
console.log(chalk_1.default.gray('
|
|
93
|
-
console.log(chalk_1.default.cyan('
|
|
92
|
+
console.log(chalk_1.default.yellow('You are running a global installation of FRAIM.'));
|
|
93
|
+
console.log(chalk_1.default.gray('Updates are not automatic in this mode.'));
|
|
94
|
+
console.log(chalk_1.default.cyan('Recommended: Use "npx fraim-framework@latest sync" instead.\n'));
|
|
94
95
|
}
|
|
95
96
|
const { syncFromRemote } = await Promise.resolve().then(() => __importStar(require('../utils/remote-sync')));
|
|
96
|
-
// Path 1: --local flag → hit localhost MCP server
|
|
97
97
|
if (options.local) {
|
|
98
|
-
console.log(chalk_1.default.blue('
|
|
98
|
+
console.log(chalk_1.default.blue('Syncing FRAIM jobs from local server...'));
|
|
99
99
|
const localPort = process.env.FRAIM_MCP_PORT ? parseInt(process.env.FRAIM_MCP_PORT) : (0, git_utils_1.getPort)();
|
|
100
100
|
const result = await syncFromRemote({
|
|
101
101
|
remoteUrl: `http://localhost:${localPort}`,
|
|
@@ -104,51 +104,58 @@ const runSync = async (options) => {
|
|
|
104
104
|
skipUpdates: true
|
|
105
105
|
});
|
|
106
106
|
if (result.success) {
|
|
107
|
-
console.log(chalk_1.default.green(
|
|
107
|
+
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`));
|
|
108
108
|
updateVersionInConfig(fraimDir);
|
|
109
|
+
const adapterUpdates = (0, agent_adapters_1.ensureAgentAdapterFiles)(projectRoot);
|
|
110
|
+
if (adapterUpdates.length > 0) {
|
|
111
|
+
console.log(chalk_1.default.green(`Updated FRAIM agent adapter files: ${adapterUpdates.join(', ')}`));
|
|
112
|
+
}
|
|
109
113
|
return;
|
|
110
114
|
}
|
|
111
|
-
console.error(chalk_1.default.red(
|
|
112
|
-
console.error(chalk_1.default.yellow('
|
|
115
|
+
console.error(chalk_1.default.red(`Local sync failed: ${result.error}`));
|
|
116
|
+
console.error(chalk_1.default.yellow('Make sure the FRAIM MCP server is running locally (npm run dev).'));
|
|
113
117
|
process.exit(1);
|
|
114
118
|
}
|
|
115
|
-
// Path 2: Remote sync with API key
|
|
116
119
|
let apiKey = loadUserApiKey() || config.apiKey || process.env.FRAIM_API_KEY;
|
|
117
120
|
if (!apiKey) {
|
|
118
121
|
if (process.env.TEST_MODE === 'true') {
|
|
119
|
-
console.log(chalk_1.default.yellow('
|
|
122
|
+
console.log(chalk_1.default.yellow('TEST_MODE: No API key configured. Using test placeholder key.'));
|
|
120
123
|
apiKey = 'test-mode-key';
|
|
121
124
|
}
|
|
122
125
|
else {
|
|
123
|
-
console.error(chalk_1.default.red('
|
|
124
|
-
console.error(chalk_1.default.yellow(
|
|
125
|
-
console.error(chalk_1.default.yellow('
|
|
126
|
+
console.error(chalk_1.default.red('No API key configured. Cannot sync.'));
|
|
127
|
+
console.error(chalk_1.default.yellow(`Set FRAIM_API_KEY in your environment, or add apiKey to ~/.fraim/config.json or ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json')}`));
|
|
128
|
+
console.error(chalk_1.default.yellow('Or use --local to sync from a locally running FRAIM server.'));
|
|
126
129
|
process.exit(1);
|
|
127
130
|
}
|
|
128
131
|
}
|
|
129
|
-
console.log(chalk_1.default.blue('
|
|
132
|
+
console.log(chalk_1.default.blue('Syncing FRAIM jobs from remote server...'));
|
|
130
133
|
const result = await syncFromRemote({
|
|
131
134
|
remoteUrl: config.remoteUrl,
|
|
132
|
-
apiKey
|
|
135
|
+
apiKey,
|
|
133
136
|
projectRoot,
|
|
134
137
|
skipUpdates: options.skipUpdates || false
|
|
135
138
|
});
|
|
136
139
|
if (!result.success) {
|
|
137
|
-
console.error(chalk_1.default.red(
|
|
138
|
-
console.error(chalk_1.default.yellow('
|
|
140
|
+
console.error(chalk_1.default.red(`Remote sync failed: ${result.error}`));
|
|
141
|
+
console.error(chalk_1.default.yellow('Check your API key and network connection.'));
|
|
139
142
|
if (process.env.TEST_MODE === 'true') {
|
|
140
|
-
console.log(chalk_1.default.yellow('
|
|
143
|
+
console.log(chalk_1.default.yellow('TEST_MODE: Continuing without remote sync (server may be unavailable).'));
|
|
141
144
|
updateVersionInConfig(fraimDir);
|
|
142
145
|
return;
|
|
143
146
|
}
|
|
144
147
|
process.exit(1);
|
|
145
148
|
}
|
|
146
|
-
console.log(chalk_1.default.green(
|
|
149
|
+
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`));
|
|
147
150
|
updateVersionInConfig(fraimDir);
|
|
151
|
+
const adapterUpdates = (0, agent_adapters_1.ensureAgentAdapterFiles)(projectRoot);
|
|
152
|
+
if (adapterUpdates.length > 0) {
|
|
153
|
+
console.log(chalk_1.default.green(`Updated FRAIM agent adapter files: ${adapterUpdates.join(', ')}`));
|
|
154
|
+
}
|
|
148
155
|
};
|
|
149
156
|
exports.runSync = runSync;
|
|
150
157
|
exports.syncCommand = new commander_1.Command('sync')
|
|
151
|
-
.description('Sync
|
|
158
|
+
.description('Sync job stubs from the framework registry')
|
|
152
159
|
.option('-f, --force', 'Force sync even if digest matches')
|
|
153
160
|
.option('--skip-updates', 'Skip checking for CLI updates (legacy)')
|
|
154
161
|
.option('--local', 'Sync from local development server (port derived from git branch)')
|
|
@@ -76,7 +76,7 @@ async function runCheckWithTimeout(check, timeout = CHECK_TIMEOUT) {
|
|
|
76
76
|
*/
|
|
77
77
|
function filterChecks(checks, options) {
|
|
78
78
|
// If no specific flags, run all checks
|
|
79
|
-
if (!options.testMcp && !options.testConfig && !options.
|
|
79
|
+
if (!options.testMcp && !options.testConfig && !options.testJobs) {
|
|
80
80
|
return checks;
|
|
81
81
|
}
|
|
82
82
|
return checks.filter(check => {
|
|
@@ -84,7 +84,7 @@ function filterChecks(checks, options) {
|
|
|
84
84
|
return true;
|
|
85
85
|
if (options.testConfig && (check.category === 'globalSetup' || check.category === 'projectSetup' || check.category === 'ideConfiguration'))
|
|
86
86
|
return true;
|
|
87
|
-
if (options.
|
|
87
|
+
if (options.testJobs && check.category === 'jobs')
|
|
88
88
|
return true;
|
|
89
89
|
return false;
|
|
90
90
|
});
|
|
@@ -127,7 +127,7 @@ async function runChecks(checks, options, version) {
|
|
|
127
127
|
const categories = {
|
|
128
128
|
globalSetup: { checks: [] },
|
|
129
129
|
projectSetup: { checks: [] },
|
|
130
|
-
|
|
130
|
+
jobs: { checks: [] },
|
|
131
131
|
ideConfiguration: { checks: [] },
|
|
132
132
|
mcpConnectivity: { checks: [] },
|
|
133
133
|
scripts: { checks: [] }
|
|
@@ -12,7 +12,7 @@ exports.getGlobalSetupChecks = getGlobalSetupChecks;
|
|
|
12
12
|
const fs_1 = __importDefault(require("fs"));
|
|
13
13
|
const path_1 = __importDefault(require("path"));
|
|
14
14
|
const os_1 = __importDefault(require("os"));
|
|
15
|
-
const
|
|
15
|
+
const getGlobalConfigPath = () => path_1.default.join(os_1.default.homedir(), '.fraim', 'config.json');
|
|
16
16
|
/**
|
|
17
17
|
* Check if global config exists
|
|
18
18
|
*/
|
|
@@ -22,11 +22,11 @@ function checkGlobalConfigExists() {
|
|
|
22
22
|
category: 'globalSetup',
|
|
23
23
|
critical: true,
|
|
24
24
|
run: async () => {
|
|
25
|
-
if (fs_1.default.existsSync(
|
|
25
|
+
if (fs_1.default.existsSync(getGlobalConfigPath())) {
|
|
26
26
|
return {
|
|
27
27
|
status: 'passed',
|
|
28
28
|
message: 'Global config exists',
|
|
29
|
-
details: { path:
|
|
29
|
+
details: { path: getGlobalConfigPath() }
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
return {
|
|
@@ -48,13 +48,13 @@ function checkModeValid() {
|
|
|
48
48
|
critical: false,
|
|
49
49
|
run: async () => {
|
|
50
50
|
try {
|
|
51
|
-
if (!fs_1.default.existsSync(
|
|
51
|
+
if (!fs_1.default.existsSync(getGlobalConfigPath())) {
|
|
52
52
|
return {
|
|
53
53
|
status: 'error',
|
|
54
54
|
message: 'Cannot check mode - global config missing'
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
|
-
const config = JSON.parse(fs_1.default.readFileSync(
|
|
57
|
+
const config = JSON.parse(fs_1.default.readFileSync(getGlobalConfigPath(), 'utf8'));
|
|
58
58
|
const mode = config.mode || 'conversational';
|
|
59
59
|
const validModes = ['conversational', 'integrated', 'split'];
|
|
60
60
|
if (validModes.includes(mode)) {
|
|
@@ -91,13 +91,13 @@ function checkApiKeyConfigured() {
|
|
|
91
91
|
critical: false,
|
|
92
92
|
run: async () => {
|
|
93
93
|
try {
|
|
94
|
-
if (!fs_1.default.existsSync(
|
|
94
|
+
if (!fs_1.default.existsSync(getGlobalConfigPath())) {
|
|
95
95
|
return {
|
|
96
96
|
status: 'error',
|
|
97
97
|
message: 'Cannot check API key - global config missing'
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
|
-
const config = JSON.parse(fs_1.default.readFileSync(
|
|
100
|
+
const config = JSON.parse(fs_1.default.readFileSync(getGlobalConfigPath(), 'utf8'));
|
|
101
101
|
if (config.apiKey) {
|
|
102
102
|
const maskedKey = config.apiKey.substring(0, 10) + '...';
|
|
103
103
|
return {
|
|
@@ -110,7 +110,7 @@ function checkApiKeyConfigured() {
|
|
|
110
110
|
status: 'warning',
|
|
111
111
|
message: 'API key not configured',
|
|
112
112
|
suggestion: 'Add apiKey to global config for remote features',
|
|
113
|
-
details: { configPath:
|
|
113
|
+
details: { configPath: getGlobalConfigPath() }
|
|
114
114
|
};
|
|
115
115
|
}
|
|
116
116
|
catch (error) {
|
|
@@ -133,13 +133,13 @@ function checkGitHubTokenConfigured() {
|
|
|
133
133
|
critical: false,
|
|
134
134
|
run: async () => {
|
|
135
135
|
try {
|
|
136
|
-
if (!fs_1.default.existsSync(
|
|
136
|
+
if (!fs_1.default.existsSync(getGlobalConfigPath())) {
|
|
137
137
|
return {
|
|
138
138
|
status: 'error',
|
|
139
139
|
message: 'Cannot check GitHub token - global config missing'
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
|
-
const config = JSON.parse(fs_1.default.readFileSync(
|
|
142
|
+
const config = JSON.parse(fs_1.default.readFileSync(getGlobalConfigPath(), 'utf8'));
|
|
143
143
|
// Check if repository provider is GitHub
|
|
144
144
|
if (config.repository?.provider === 'github') {
|
|
145
145
|
// Token would be in IDE MCP configs, not global config
|
|
@@ -175,13 +175,13 @@ function checkRemoteUrlConfigured() {
|
|
|
175
175
|
critical: false,
|
|
176
176
|
run: async () => {
|
|
177
177
|
try {
|
|
178
|
-
if (!fs_1.default.existsSync(
|
|
178
|
+
if (!fs_1.default.existsSync(getGlobalConfigPath())) {
|
|
179
179
|
return {
|
|
180
180
|
status: 'error',
|
|
181
181
|
message: 'Cannot check remote URL - global config missing'
|
|
182
182
|
};
|
|
183
183
|
}
|
|
184
|
-
const config = JSON.parse(fs_1.default.readFileSync(
|
|
184
|
+
const config = JSON.parse(fs_1.default.readFileSync(getGlobalConfigPath(), 'utf8'));
|
|
185
185
|
if (config.remoteUrl) {
|
|
186
186
|
return {
|
|
187
187
|
status: 'passed',
|
|
@@ -193,7 +193,7 @@ function checkRemoteUrlConfigured() {
|
|
|
193
193
|
return {
|
|
194
194
|
status: 'passed',
|
|
195
195
|
message: 'Remote URL not configured (optional)',
|
|
196
|
-
details: { configPath:
|
|
196
|
+
details: { configPath: getGlobalConfigPath(), optional: true }
|
|
197
197
|
};
|
|
198
198
|
}
|
|
199
199
|
catch (error) {
|
|
@@ -10,8 +10,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.getProjectSetupChecks = getProjectSetupChecks;
|
|
12
12
|
const fs_1 = __importDefault(require("fs"));
|
|
13
|
-
const path_1 = __importDefault(require("path"));
|
|
14
13
|
const child_process_1 = require("child_process");
|
|
14
|
+
const project_fraim_paths_1 = require("../../../core/utils/project-fraim-paths");
|
|
15
15
|
/**
|
|
16
16
|
* Check if project is initialized
|
|
17
17
|
*/
|
|
@@ -21,8 +21,8 @@ function checkProjectInitialized() {
|
|
|
21
21
|
category: 'projectSetup',
|
|
22
22
|
critical: true,
|
|
23
23
|
run: async () => {
|
|
24
|
-
const fraimDir =
|
|
25
|
-
if (
|
|
24
|
+
const fraimDir = (0, project_fraim_paths_1.getWorkspaceFraimDir)(process.cwd());
|
|
25
|
+
if ((0, project_fraim_paths_1.workspaceFraimExists)(process.cwd())) {
|
|
26
26
|
return {
|
|
27
27
|
status: 'passed',
|
|
28
28
|
message: 'Project initialized',
|
|
@@ -47,7 +47,7 @@ function checkProjectConfigValid() {
|
|
|
47
47
|
category: 'projectSetup',
|
|
48
48
|
critical: true,
|
|
49
49
|
run: async () => {
|
|
50
|
-
const configPath =
|
|
50
|
+
const configPath = (0, project_fraim_paths_1.getWorkspaceConfigPath)(process.cwd());
|
|
51
51
|
if (!fs_1.default.existsSync(configPath)) {
|
|
52
52
|
return {
|
|
53
53
|
status: 'error',
|
|
@@ -62,7 +62,7 @@ function checkProjectConfigValid() {
|
|
|
62
62
|
return {
|
|
63
63
|
status: 'warning',
|
|
64
64
|
message: 'Project config incomplete',
|
|
65
|
-
suggestion:
|
|
65
|
+
suggestion: `Add project name to ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json')}`
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
return {
|
|
@@ -75,7 +75,7 @@ function checkProjectConfigValid() {
|
|
|
75
75
|
return {
|
|
76
76
|
status: 'error',
|
|
77
77
|
message: 'Project config corrupted',
|
|
78
|
-
suggestion:
|
|
78
|
+
suggestion: `Fix JSON syntax in ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json')}`,
|
|
79
79
|
details: { error: error.message }
|
|
80
80
|
};
|
|
81
81
|
}
|
|
@@ -129,7 +129,7 @@ function checkProviderMatches() {
|
|
|
129
129
|
category: 'projectSetup',
|
|
130
130
|
critical: false,
|
|
131
131
|
run: async () => {
|
|
132
|
-
const configPath =
|
|
132
|
+
const configPath = (0, project_fraim_paths_1.getWorkspaceConfigPath)(process.cwd());
|
|
133
133
|
if (!fs_1.default.existsSync(configPath)) {
|
|
134
134
|
return {
|
|
135
135
|
status: 'error',
|
|
@@ -163,7 +163,7 @@ function checkProviderMatches() {
|
|
|
163
163
|
return {
|
|
164
164
|
status: 'warning',
|
|
165
165
|
message: `Provider mismatch: config says ${provider} but remote is ${remote}`,
|
|
166
|
-
suggestion:
|
|
166
|
+
suggestion: `Update repository.provider in ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json')}`
|
|
167
167
|
};
|
|
168
168
|
}
|
|
169
169
|
catch {
|
|
@@ -193,7 +193,7 @@ function checkConfigMatchesMode() {
|
|
|
193
193
|
category: 'projectSetup',
|
|
194
194
|
critical: false,
|
|
195
195
|
run: async () => {
|
|
196
|
-
const configPath =
|
|
196
|
+
const configPath = (0, project_fraim_paths_1.getWorkspaceConfigPath)(process.cwd());
|
|
197
197
|
if (!fs_1.default.existsSync(configPath)) {
|
|
198
198
|
return {
|
|
199
199
|
status: 'error',
|
|
@@ -217,7 +217,7 @@ function checkConfigMatchesMode() {
|
|
|
217
217
|
return {
|
|
218
218
|
status: 'error',
|
|
219
219
|
message: 'Integrated mode requires repository configuration',
|
|
220
|
-
suggestion:
|
|
220
|
+
suggestion: `Add repository config to ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json')}`
|
|
221
221
|
};
|
|
222
222
|
}
|
|
223
223
|
return {
|
|
@@ -232,14 +232,14 @@ function checkConfigMatchesMode() {
|
|
|
232
232
|
return {
|
|
233
233
|
status: 'error',
|
|
234
234
|
message: 'Split mode requires repository configuration',
|
|
235
|
-
suggestion:
|
|
235
|
+
suggestion: `Add repository config to ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json')}`
|
|
236
236
|
};
|
|
237
237
|
}
|
|
238
238
|
if (!config.issueTracking?.provider) {
|
|
239
239
|
return {
|
|
240
240
|
status: 'error',
|
|
241
241
|
message: 'Split mode requires issue tracking configuration',
|
|
242
|
-
suggestion:
|
|
242
|
+
suggestion: `Add issueTracking config to ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json')}`
|
|
243
243
|
};
|
|
244
244
|
}
|
|
245
245
|
return {
|
|
@@ -32,7 +32,7 @@ function checkScriptsDirectoryExists() {
|
|
|
32
32
|
return {
|
|
33
33
|
status: 'warning',
|
|
34
34
|
message: 'Scripts directory missing',
|
|
35
|
-
suggestion: 'Run fraim sync to
|
|
35
|
+
suggestion: 'Run fraim sync to hydrate local scripts for the repo.',
|
|
36
36
|
command: 'fraim sync'
|
|
37
37
|
};
|
|
38
38
|
}
|
|
@@ -65,7 +65,7 @@ function checkScriptsSynced() {
|
|
|
65
65
|
return {
|
|
66
66
|
status: 'warning',
|
|
67
67
|
message: 'No scripts found',
|
|
68
|
-
suggestion: 'Run fraim sync to
|
|
68
|
+
suggestion: 'Run fraim sync to hydrate local scripts.',
|
|
69
69
|
command: 'fraim sync'
|
|
70
70
|
};
|
|
71
71
|
}
|