fraim-framework 2.0.96 ā 2.0.97
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 +109 -27
- package/index.js +1 -1
- package/package.json +3 -4
package/bin/fraim.js
CHANGED
|
@@ -417,7 +417,7 @@ const runAddIDE = async (options) => {
|
|
|
417
417
|
if (results.successful.length > 0) {
|
|
418
418
|
console.log(chalk_1.default.blue('\nš Next steps:'));
|
|
419
419
|
console.log(chalk_1.default.cyan(' 1. Restart your configured IDEs'));
|
|
420
|
-
console.log(chalk_1.default.cyan(' 2.
|
|
420
|
+
console.log(chalk_1.default.cyan(' 2. In any FRAIM-enabled repo, tell your AI agent: "Onboard this project"'));
|
|
421
421
|
console.log(chalk_1.default.blue('\nš” Use "fraim doctor --test-mcp" to verify the configuration.'));
|
|
422
422
|
}
|
|
423
423
|
};
|
|
@@ -53,7 +53,7 @@ function getAllChecks() {
|
|
|
53
53
|
return [
|
|
54
54
|
...(0, global_setup_checks_1.getGlobalSetupChecks)(),
|
|
55
55
|
...(0, project_setup_checks_1.getProjectSetupChecks)(),
|
|
56
|
-
...(0, workflow_checks_1.
|
|
56
|
+
...(0, workflow_checks_1.getJobChecks)(),
|
|
57
57
|
...(0, ide_config_checks_1.getIDEConfigChecks)(),
|
|
58
58
|
...(0, mcp_connectivity_checks_1.getMCPConnectivityChecks)(),
|
|
59
59
|
...(0, scripts_checks_1.getScriptsChecks)()
|
|
@@ -63,7 +63,7 @@ exports.doctorCommand = new commander_1.Command('doctor')
|
|
|
63
63
|
.description('Validate FRAIM installation and configuration')
|
|
64
64
|
.option('--test-mcp', 'Test only MCP server connectivity')
|
|
65
65
|
.option('--test-config', 'Validate only configuration files')
|
|
66
|
-
.option('--test-
|
|
66
|
+
.option('--test-jobs', 'Check only job status')
|
|
67
67
|
.option('--verbose', 'Show detailed output including successful checks')
|
|
68
68
|
.option('--json', 'Output results as JSON')
|
|
69
69
|
.action(async (cmdOptions) => {
|
|
@@ -71,7 +71,7 @@ exports.doctorCommand = new commander_1.Command('doctor')
|
|
|
71
71
|
const options = {
|
|
72
72
|
testMcp: cmdOptions.testMcp,
|
|
73
73
|
testConfig: cmdOptions.testConfig,
|
|
74
|
-
|
|
74
|
+
testJobs: cmdOptions.testJobs,
|
|
75
75
|
verbose: cmdOptions.verbose,
|
|
76
76
|
json: cmdOptions.json
|
|
77
77
|
};
|
|
@@ -80,7 +80,7 @@ exports.doctorCommand = new commander_1.Command('doctor')
|
|
|
80
80
|
flags: {
|
|
81
81
|
testMcp: options.testMcp || false,
|
|
82
82
|
testConfig: options.testConfig || false,
|
|
83
|
-
|
|
83
|
+
testJobs: options.testJobs || false,
|
|
84
84
|
verbose: options.verbose || false,
|
|
85
85
|
json: options.json || false
|
|
86
86
|
}
|
|
@@ -92,8 +92,8 @@ exports.doctorCommand = new commander_1.Command('doctor')
|
|
|
92
92
|
trackMetric('doctor.flags.test_mcp', 1);
|
|
93
93
|
if (options.testConfig)
|
|
94
94
|
trackMetric('doctor.flags.test_config', 1);
|
|
95
|
-
if (options.
|
|
96
|
-
trackMetric('doctor.flags.
|
|
95
|
+
if (options.testJobs)
|
|
96
|
+
trackMetric('doctor.flags.test_jobs', 1);
|
|
97
97
|
if (options.verbose)
|
|
98
98
|
trackMetric('doctor.flags.verbose', 1);
|
|
99
99
|
if (options.json)
|
|
@@ -18,13 +18,17 @@ const ide_detector_1 = require("../setup/ide-detector");
|
|
|
18
18
|
const codex_local_config_1 = require("../setup/codex-local-config");
|
|
19
19
|
const provider_registry_1 = require("../providers/provider-registry");
|
|
20
20
|
const fraim_gitignore_1 = require("../utils/fraim-gitignore");
|
|
21
|
+
const config_writer_1 = require("../../core/config-writer");
|
|
22
|
+
const agent_adapters_1 = require("../utils/agent-adapters");
|
|
23
|
+
const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
|
|
24
|
+
const project_bootstrap_1 = require("../utils/project-bootstrap");
|
|
21
25
|
const promptForJiraProjectKey = async (jiraBaseUrl) => {
|
|
22
|
-
console.log(chalk_1.default.blue('\
|
|
26
|
+
console.log(chalk_1.default.blue('\nJira Project Configuration'));
|
|
23
27
|
console.log(chalk_1.default.gray(`Jira instance: ${jiraBaseUrl}`));
|
|
24
28
|
console.log(chalk_1.default.gray('Enter the Jira project key for this repository (e.g., TEAM, PROJ, DEV)\n'));
|
|
25
29
|
if (process.env.FRAIM_NON_INTERACTIVE) {
|
|
26
30
|
const defaultKey = process.env.FRAIM_JIRA_PROJECT_KEY || 'PROJ';
|
|
27
|
-
console.log(chalk_1.default.yellow(`\
|
|
31
|
+
console.log(chalk_1.default.yellow(`\nNon-interactive mode: using Jira project key "${defaultKey}"`));
|
|
28
32
|
return defaultKey;
|
|
29
33
|
}
|
|
30
34
|
const response = await (0, prompts_1.default)({
|
|
@@ -42,7 +46,7 @@ const promptForJiraProjectKey = async (jiraBaseUrl) => {
|
|
|
42
46
|
}
|
|
43
47
|
});
|
|
44
48
|
if (!response.projectKey) {
|
|
45
|
-
console.log(chalk_1.default.red('\
|
|
49
|
+
console.log(chalk_1.default.red('\nJira project key is required for split mode'));
|
|
46
50
|
process.exit(1);
|
|
47
51
|
}
|
|
48
52
|
return response.projectKey.trim().toUpperCase();
|
|
@@ -66,18 +70,12 @@ const checkGlobalSetup = () => {
|
|
|
66
70
|
return { exists: true, mode: 'integrated', tokens: {} };
|
|
67
71
|
}
|
|
68
72
|
};
|
|
69
|
-
/**
|
|
70
|
-
* Install GitHub workflow files to .github/workflows/
|
|
71
|
-
* Gracefully handles cases where gh CLI is not available
|
|
72
|
-
*/
|
|
73
73
|
const installGitHubWorkflows = (projectRoot) => {
|
|
74
74
|
const workflowsDir = path_1.default.join(projectRoot, '.github', 'workflows');
|
|
75
|
-
// Find registry directory (works in both dev and installed package)
|
|
76
75
|
const registryDir = fs_1.default.existsSync(path_1.default.join(__dirname, '..', '..', '..', 'registry'))
|
|
77
76
|
? path_1.default.join(__dirname, '..', '..', '..', 'registry')
|
|
78
77
|
: path_1.default.join(__dirname, '..', '..', 'registry');
|
|
79
78
|
const sourceDir = path_1.default.join(registryDir, 'github', 'workflows');
|
|
80
|
-
// Create .github/workflows directory if it doesn't exist
|
|
81
79
|
if (!fs_1.default.existsSync(workflowsDir)) {
|
|
82
80
|
fs_1.default.mkdirSync(workflowsDir, { recursive: true });
|
|
83
81
|
}
|
|
@@ -94,12 +92,7 @@ const installGitHubWorkflows = (projectRoot) => {
|
|
|
94
92
|
}
|
|
95
93
|
});
|
|
96
94
|
};
|
|
97
|
-
/**
|
|
98
|
-
* Create GitHub labels using gh CLI
|
|
99
|
-
* Gracefully handles cases where gh CLI is not available
|
|
100
|
-
*/
|
|
101
95
|
const createGitHubLabels = (projectRoot) => {
|
|
102
|
-
// Check if gh CLI is available
|
|
103
96
|
try {
|
|
104
97
|
(0, child_process_1.execSync)('gh --version', { stdio: 'ignore' });
|
|
105
98
|
}
|
|
@@ -108,7 +101,6 @@ const createGitHubLabels = (projectRoot) => {
|
|
|
108
101
|
console.log(chalk_1.default.gray('Install gh CLI to enable automatic label creation: https://cli.github.com/'));
|
|
109
102
|
return;
|
|
110
103
|
}
|
|
111
|
-
// Read labels from labels.json
|
|
112
104
|
const labelsPath = path_1.default.join(__dirname, '..', '..', '..', 'labels.json');
|
|
113
105
|
if (!fs_1.default.existsSync(labelsPath)) {
|
|
114
106
|
console.log(chalk_1.default.yellow('labels.json not found. Skipping label creation.'));
|
|
@@ -118,12 +110,10 @@ const createGitHubLabels = (projectRoot) => {
|
|
|
118
110
|
const labels = JSON.parse(fs_1.default.readFileSync(labelsPath, 'utf8'));
|
|
119
111
|
labels.forEach((label) => {
|
|
120
112
|
try {
|
|
121
|
-
// Try to create the label, ignore if it already exists
|
|
122
113
|
(0, child_process_1.execSync)(`gh label create "${label.name}" --color "${label.color}" --description "${label.description}"`, { cwd: projectRoot, stdio: 'ignore' });
|
|
123
114
|
console.log(chalk_1.default.green(`Created label: ${label.name}`));
|
|
124
115
|
}
|
|
125
116
|
catch (error) {
|
|
126
|
-
// Label might already exist, which is fine
|
|
127
117
|
if (error.message && error.message.includes('already exists')) {
|
|
128
118
|
console.log(chalk_1.default.gray(`Label already exists: ${label.name}`));
|
|
129
119
|
}
|
|
@@ -147,18 +137,25 @@ const runInitProject = async () => {
|
|
|
147
137
|
process.exit(1);
|
|
148
138
|
}
|
|
149
139
|
const projectRoot = process.cwd();
|
|
150
|
-
const
|
|
151
|
-
const
|
|
140
|
+
const fraimDirDisplayPath = (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)();
|
|
141
|
+
const configDisplayPath = (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json');
|
|
142
|
+
const fraimDir = (0, project_fraim_paths_1.getWorkspaceFraimDir)(projectRoot);
|
|
143
|
+
const configPath = (0, project_fraim_paths_1.getWorkspaceConfigPath)(projectRoot);
|
|
144
|
+
const projectName = path_1.default.basename(projectRoot);
|
|
145
|
+
const result = (0, project_bootstrap_1.createInitProjectResult)(projectName, globalSetup.mode || 'integrated');
|
|
146
|
+
result.bootstrapNeeded = !fs_1.default.existsSync(configPath);
|
|
152
147
|
if (!fs_1.default.existsSync(fraimDir)) {
|
|
153
148
|
fs_1.default.mkdirSync(fraimDir, { recursive: true });
|
|
154
|
-
console.log(chalk_1.default.green(
|
|
149
|
+
console.log(chalk_1.default.green(`Created ${fraimDirDisplayPath} directory`));
|
|
150
|
+
(0, project_bootstrap_1.recordPathStatus)(result, fraimDirDisplayPath, true);
|
|
155
151
|
}
|
|
156
152
|
else {
|
|
157
|
-
console.log(chalk_1.default.yellow(
|
|
153
|
+
console.log(chalk_1.default.yellow(`${fraimDirDisplayPath} directory already exists`));
|
|
154
|
+
(0, project_bootstrap_1.recordPathStatus)(result, fraimDirDisplayPath, false);
|
|
158
155
|
}
|
|
159
156
|
if (!fs_1.default.existsSync(configPath)) {
|
|
160
|
-
const projectName = path_1.default.basename(projectRoot);
|
|
161
157
|
const preferredMode = globalSetup.mode || 'integrated';
|
|
158
|
+
result.mode = preferredMode;
|
|
162
159
|
let config;
|
|
163
160
|
if (preferredMode === 'conversational') {
|
|
164
161
|
config = {
|
|
@@ -174,20 +171,19 @@ const runInitProject = async () => {
|
|
|
174
171
|
else {
|
|
175
172
|
const detection = (0, platform_detection_1.detectPlatformFromGit)();
|
|
176
173
|
if (detection.provider !== 'unknown' && detection.repository) {
|
|
177
|
-
|
|
174
|
+
result.repositoryDetected = true;
|
|
178
175
|
let issueTracking;
|
|
179
|
-
// In split mode with Jira configured, use Jira for issue tracking
|
|
180
176
|
const jiraConfig = globalSetup.providerConfigs?.jiraConfig;
|
|
181
177
|
if (preferredMode === 'split' && globalSetup.tokens?.jira && jiraConfig?.baseUrl) {
|
|
182
|
-
// Prompt for Jira project key (project-specific)
|
|
183
178
|
const projectKey = process.env.FRAIM_JIRA_PROJECT_KEY ||
|
|
184
179
|
await promptForJiraProjectKey(jiraConfig.baseUrl);
|
|
185
180
|
issueTracking = {
|
|
186
181
|
provider: 'jira',
|
|
187
182
|
baseUrl: jiraConfig.baseUrl,
|
|
188
|
-
projectKey
|
|
183
|
+
projectKey,
|
|
189
184
|
email: jiraConfig.email
|
|
190
185
|
};
|
|
186
|
+
result.issueTrackingDetected = true;
|
|
191
187
|
console.log(chalk_1.default.blue(` Code Repository: ${detection.provider.toUpperCase()}`));
|
|
192
188
|
console.log(chalk_1.default.blue(` Issue Tracking: JIRA (${projectKey})`));
|
|
193
189
|
}
|
|
@@ -196,6 +192,11 @@ const runInitProject = async () => {
|
|
|
196
192
|
provider: detection.provider,
|
|
197
193
|
...detection.repository
|
|
198
194
|
};
|
|
195
|
+
result.issueTrackingDetected = true;
|
|
196
|
+
if (preferredMode === 'split') {
|
|
197
|
+
result.mode = 'integrated';
|
|
198
|
+
result.warnings.push('Split mode is not fully configured for this repo yet, so issue tracking fell back to the repository provider.');
|
|
199
|
+
}
|
|
199
200
|
const providerDef = await (0, provider_registry_1.getProvider)(detection.provider);
|
|
200
201
|
console.log(chalk_1.default.blue(` Platform: ${providerDef?.displayName || detection.provider.toUpperCase()}`));
|
|
201
202
|
}
|
|
@@ -208,7 +209,6 @@ const runInitProject = async () => {
|
|
|
208
209
|
issueTracking,
|
|
209
210
|
customizations: {}
|
|
210
211
|
};
|
|
211
|
-
// Display repository info based on available fields
|
|
212
212
|
const repo = detection.repository;
|
|
213
213
|
if (repo.owner && repo.name) {
|
|
214
214
|
console.log(chalk_1.default.gray(` Repository: ${repo.owner}/${repo.name}`));
|
|
@@ -231,31 +231,57 @@ const runInitProject = async () => {
|
|
|
231
231
|
},
|
|
232
232
|
customizations: {}
|
|
233
233
|
};
|
|
234
|
+
result.mode = 'conversational';
|
|
235
|
+
result.warnings.push('No git remote detected. FRAIM fell back to conversational project setup.');
|
|
234
236
|
console.log(chalk_1.default.yellow(' No git remote detected. Falling back to conversational mode.'));
|
|
235
237
|
}
|
|
236
238
|
}
|
|
237
|
-
|
|
238
|
-
console.log(chalk_1.default.green(
|
|
239
|
+
(0, config_writer_1.writeFraimConfig)(configPath, config);
|
|
240
|
+
console.log(chalk_1.default.green(`Created ${configDisplayPath}`));
|
|
241
|
+
(0, project_bootstrap_1.recordPathStatus)(result, configDisplayPath, true);
|
|
239
242
|
}
|
|
240
|
-
|
|
243
|
+
else {
|
|
244
|
+
(0, project_bootstrap_1.recordPathStatus)(result, configDisplayPath, false);
|
|
245
|
+
try {
|
|
246
|
+
const existingConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
247
|
+
if (existingConfig.mode === 'conversational' || existingConfig.mode === 'integrated' || existingConfig.mode === 'split') {
|
|
248
|
+
result.mode = existingConfig.mode;
|
|
249
|
+
}
|
|
250
|
+
result.repositoryDetected = Boolean(existingConfig.repository?.provider);
|
|
251
|
+
result.issueTrackingDetected = Boolean(existingConfig.issueTracking?.provider);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
result.warnings.push(`Existing ${configDisplayPath} could not be parsed for summary details.`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
['jobs', 'ai-employee/jobs', 'ai-employee/skills', 'ai-manager/jobs', 'personalized-employee'].forEach((dir) => {
|
|
241
258
|
const dirPath = path_1.default.join(fraimDir, dir);
|
|
242
259
|
if (!fs_1.default.existsSync(dirPath)) {
|
|
243
260
|
fs_1.default.mkdirSync(dirPath, { recursive: true });
|
|
244
|
-
|
|
261
|
+
const displayPath = (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)(dir);
|
|
262
|
+
console.log(chalk_1.default.green(`Created ${displayPath}`));
|
|
263
|
+
(0, project_bootstrap_1.recordPathStatus)(result, displayPath, true);
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
(0, project_bootstrap_1.recordPathStatus)(result, (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)(dir), false);
|
|
245
267
|
}
|
|
246
268
|
});
|
|
247
269
|
if ((0, fraim_gitignore_1.ensureFraimSyncedContentIgnored)(projectRoot)) {
|
|
248
|
-
console.log(chalk_1.default.green('Updated .gitignore
|
|
270
|
+
console.log(chalk_1.default.green('Updated .gitignore FRAIM managed block'));
|
|
249
271
|
}
|
|
250
|
-
// Install GitHub workflows and create labels for GitHub repositories
|
|
251
272
|
const detection = (0, platform_detection_1.detectPlatformFromGit)();
|
|
252
273
|
if (detection.provider === 'github') {
|
|
253
274
|
console.log(chalk_1.default.blue('\nSetting up GitHub workflows and labels...'));
|
|
254
275
|
installGitHubWorkflows(projectRoot);
|
|
255
276
|
createGitHubLabels(projectRoot);
|
|
277
|
+
result.repositoryDetected = true;
|
|
256
278
|
}
|
|
257
279
|
if (!process.env.FRAIM_SKIP_SYNC) {
|
|
258
280
|
await (0, sync_1.runSync)({});
|
|
281
|
+
result.syncPerformed = true;
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
result.warnings.push('Sync was skipped for this run.');
|
|
259
285
|
}
|
|
260
286
|
const codexAvailable = (0, ide_detector_1.detectInstalledIDEs)().some((ide) => ide.configType === 'codex');
|
|
261
287
|
if (codexAvailable) {
|
|
@@ -263,26 +289,11 @@ const runInitProject = async () => {
|
|
|
263
289
|
const status = codexLocalResult.created ? 'Created' : codexLocalResult.updated ? 'Updated' : 'Verified';
|
|
264
290
|
console.log(chalk_1.default.green(`${status} project Codex config at ${codexLocalResult.path}`));
|
|
265
291
|
}
|
|
266
|
-
|
|
267
|
-
if (
|
|
268
|
-
|
|
269
|
-
type: 'confirm',
|
|
270
|
-
name: 'runOnboarding',
|
|
271
|
-
message: 'Would you like to set up your AI employee for success? Use the "ai-manager/jobs/project-setup/project-onboarding" job now.',
|
|
272
|
-
initial: true
|
|
273
|
-
});
|
|
274
|
-
if (response.runOnboarding) {
|
|
275
|
-
console.log(chalk_1.default.blue('\nš Proactive Onboarding: Project Success Setup'));
|
|
276
|
-
console.log(chalk_1.default.gray('Simply tell your AI agent: "run the project-onboarding job under ai-manager/project-setup"'));
|
|
277
|
-
console.log(chalk_1.default.gray('This will help the AI understand your project goals, tech stack, and industry context.'));
|
|
278
|
-
}
|
|
279
|
-
else {
|
|
280
|
-
console.log(chalk_1.default.cyan('\nTip: You can always ask your AI agent to "list fraim jobs" to see what\'s available.'));
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
console.log(chalk_1.default.cyan('\nWould you like to set up your AI employee for success? Use the "ai-manager/jobs/project-setup/project-onboarding" job now.'));
|
|
292
|
+
const adapterUpdates = (0, agent_adapters_1.ensureAgentAdapterFiles)(projectRoot);
|
|
293
|
+
if (adapterUpdates.length > 0) {
|
|
294
|
+
console.log(chalk_1.default.green(`Updated FRAIM agent adapter files: ${adapterUpdates.join(', ')}`));
|
|
285
295
|
}
|
|
296
|
+
(0, project_bootstrap_1.printInitProjectSummary)(result);
|
|
286
297
|
};
|
|
287
298
|
exports.runInitProject = runInitProject;
|
|
288
299
|
exports.initProjectCommand = new commander_1.Command('init-project')
|
|
@@ -8,22 +8,18 @@ const commander_1 = require("commander");
|
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
|
|
11
12
|
exports.listOverridableCommand = new commander_1.Command('list-overridable')
|
|
12
13
|
.description('List all FRAIM registry paths that can be overridden')
|
|
13
|
-
.option('--job-category <category>', 'Filter by
|
|
14
|
+
.option('--job-category <category>', 'Filter by job category (e.g., product-building, customer-development, marketing)')
|
|
14
15
|
.option('--rules', 'Show all overridable rules')
|
|
15
16
|
.action(async (options) => {
|
|
16
17
|
const projectRoot = process.cwd();
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const legacyOverridesDir = path_1.default.join(fraimDir, 'overrides');
|
|
21
|
-
// Validate .fraim directory exists
|
|
22
|
-
if (!fs_1.default.existsSync(fraimDir)) {
|
|
23
|
-
console.log(chalk_1.default.red('ā .fraim/ directory not found. Run "fraim setup" or "fraim init-project" first.'));
|
|
18
|
+
const personalizedDir = (0, project_fraim_paths_1.getWorkspaceFraimPath)(projectRoot, 'personalized-employee');
|
|
19
|
+
if (!(0, project_fraim_paths_1.workspaceFraimExists)(projectRoot)) {
|
|
20
|
+
console.log(chalk_1.default.red(`${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)()} directory not found. Run "fraim setup" or "fraim init-project" first.`));
|
|
24
21
|
process.exit(1);
|
|
25
22
|
}
|
|
26
|
-
// Determine registry location (try framework root first, then node_modules)
|
|
27
23
|
let registryRoot = null;
|
|
28
24
|
const frameworkRoot = path_1.default.join(__dirname, '..', '..', '..');
|
|
29
25
|
const frameworkRegistry = path_1.default.join(frameworkRoot, 'registry');
|
|
@@ -37,11 +33,10 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
|
|
|
37
33
|
}
|
|
38
34
|
}
|
|
39
35
|
if (!registryRoot) {
|
|
40
|
-
console.log(chalk_1.default.red('
|
|
36
|
+
console.log(chalk_1.default.red('Could not find FRAIM registry. Please ensure @fraim/framework is installed.'));
|
|
41
37
|
process.exit(1);
|
|
42
38
|
}
|
|
43
|
-
console.log(chalk_1.default.blue('
|
|
44
|
-
// Get list of existing overrides
|
|
39
|
+
console.log(chalk_1.default.blue('Overridable FRAIM Registry Paths:\n'));
|
|
45
40
|
const existingOverrides = new Set();
|
|
46
41
|
const scanDir = (dir, base = '') => {
|
|
47
42
|
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
@@ -58,10 +53,6 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
|
|
|
58
53
|
if (fs_1.default.existsSync(personalizedDir)) {
|
|
59
54
|
scanDir(personalizedDir);
|
|
60
55
|
}
|
|
61
|
-
if (fs_1.default.existsSync(legacyOverridesDir)) {
|
|
62
|
-
scanDir(legacyOverridesDir);
|
|
63
|
-
}
|
|
64
|
-
// Handle --rules flag
|
|
65
56
|
if (options.rules) {
|
|
66
57
|
const rulesDir = path_1.default.join(registryRoot, 'rules');
|
|
67
58
|
if (fs_1.default.existsSync(rulesDir)) {
|
|
@@ -72,9 +63,7 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
|
|
|
72
63
|
for (const file of ruleFiles) {
|
|
73
64
|
const filePath = `rules/${file}`;
|
|
74
65
|
const hasOverride = existingOverrides.has(filePath);
|
|
75
|
-
const status = hasOverride
|
|
76
|
-
? chalk_1.default.green('[OVERRIDDEN]')
|
|
77
|
-
: chalk_1.default.gray('[OVERRIDABLE]');
|
|
66
|
+
const status = hasOverride ? chalk_1.default.green('[OVERRIDDEN]') : chalk_1.default.gray('[OVERRIDABLE]');
|
|
78
67
|
console.log(` ${status} ${filePath}`);
|
|
79
68
|
}
|
|
80
69
|
console.log('');
|
|
@@ -82,15 +71,14 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
|
|
|
82
71
|
showTips();
|
|
83
72
|
return;
|
|
84
73
|
}
|
|
85
|
-
// Handle --job-category flag
|
|
86
74
|
if (options.jobCategory) {
|
|
87
75
|
const category = options.jobCategory;
|
|
88
|
-
const
|
|
76
|
+
const jobsDir = path_1.default.join(registryRoot, 'jobs', category);
|
|
89
77
|
const templatesDir = path_1.default.join(registryRoot, 'templates', category);
|
|
90
|
-
if (!fs_1.default.existsSync(
|
|
91
|
-
console.log(chalk_1.default.red(
|
|
78
|
+
if (!fs_1.default.existsSync(jobsDir)) {
|
|
79
|
+
console.log(chalk_1.default.red(`Category "${category}" not found.`));
|
|
92
80
|
console.log(chalk_1.default.gray('\nAvailable categories:'));
|
|
93
|
-
const categoriesDir = path_1.default.join(registryRoot, '
|
|
81
|
+
const categoriesDir = path_1.default.join(registryRoot, 'jobs');
|
|
94
82
|
const categories = fs_1.default.readdirSync(categoriesDir, { withFileTypes: true })
|
|
95
83
|
.filter(d => d.isDirectory())
|
|
96
84
|
.map(d => d.name)
|
|
@@ -98,21 +86,17 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
|
|
|
98
86
|
categories.forEach(c => console.log(chalk_1.default.gray(` - ${c}`)));
|
|
99
87
|
process.exit(1);
|
|
100
88
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const workflowFiles = fs_1.default.readdirSync(workflowsDir)
|
|
89
|
+
console.log(chalk_1.default.bold.cyan(`Jobs (${category}):\n`));
|
|
90
|
+
const jobFiles = fs_1.default.readdirSync(jobsDir)
|
|
104
91
|
.filter(f => f.endsWith('.md'))
|
|
105
92
|
.sort();
|
|
106
|
-
for (const file of
|
|
107
|
-
const filePath = `
|
|
93
|
+
for (const file of jobFiles) {
|
|
94
|
+
const filePath = `jobs/${category}/${file}`;
|
|
108
95
|
const hasOverride = existingOverrides.has(filePath);
|
|
109
|
-
const status = hasOverride
|
|
110
|
-
? chalk_1.default.green('[OVERRIDDEN]')
|
|
111
|
-
: chalk_1.default.gray('[OVERRIDABLE]');
|
|
96
|
+
const status = hasOverride ? chalk_1.default.green('[OVERRIDDEN]') : chalk_1.default.gray('[OVERRIDABLE]');
|
|
112
97
|
console.log(` ${status} ${filePath}`);
|
|
113
98
|
}
|
|
114
99
|
console.log('');
|
|
115
|
-
// Show templates for this category (if they exist)
|
|
116
100
|
if (fs_1.default.existsSync(templatesDir)) {
|
|
117
101
|
console.log(chalk_1.default.bold.cyan(`Templates (${category}):\n`));
|
|
118
102
|
const templateFiles = fs_1.default.readdirSync(templatesDir)
|
|
@@ -121,9 +105,7 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
|
|
|
121
105
|
for (const file of templateFiles) {
|
|
122
106
|
const filePath = `templates/${category}/${file}`;
|
|
123
107
|
const hasOverride = existingOverrides.has(filePath);
|
|
124
|
-
const status = hasOverride
|
|
125
|
-
? chalk_1.default.green('[OVERRIDDEN]')
|
|
126
|
-
: chalk_1.default.gray('[OVERRIDABLE]');
|
|
108
|
+
const status = hasOverride ? chalk_1.default.green('[OVERRIDDEN]') : chalk_1.default.gray('[OVERRIDABLE]');
|
|
127
109
|
console.log(` ${status} ${filePath}`);
|
|
128
110
|
}
|
|
129
111
|
console.log('');
|
|
@@ -131,34 +113,30 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
|
|
|
131
113
|
showTips();
|
|
132
114
|
return;
|
|
133
115
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
const categories = fs_1.default.readdirSync(workflowsDir, { withFileTypes: true })
|
|
116
|
+
console.log(chalk_1.default.bold.cyan('Available Job Categories:\n'));
|
|
117
|
+
const jobsDir = path_1.default.join(registryRoot, 'jobs');
|
|
118
|
+
const categories = fs_1.default.readdirSync(jobsDir, { withFileTypes: true })
|
|
138
119
|
.filter(d => d.isDirectory())
|
|
139
120
|
.map(d => d.name)
|
|
140
121
|
.sort();
|
|
141
|
-
// Group categories for better display
|
|
142
122
|
const columns = 3;
|
|
143
123
|
for (let i = 0; i < categories.length; i += columns) {
|
|
144
124
|
const row = categories.slice(i, i + columns);
|
|
145
|
-
const formatted = row.map(cat => chalk_1.default.gray(`
|
|
125
|
+
const formatted = row.map(cat => chalk_1.default.gray(` - ${cat.padEnd(30)}`)).join('');
|
|
146
126
|
console.log(formatted);
|
|
147
127
|
}
|
|
148
128
|
console.log('');
|
|
149
|
-
// Show existing overrides
|
|
150
129
|
if (existingOverrides.size > 0) {
|
|
151
130
|
console.log(chalk_1.default.bold.cyan('Your Active Overrides:\n'));
|
|
152
|
-
// Group by type
|
|
153
131
|
const overridesByType = {
|
|
154
|
-
|
|
132
|
+
jobs: [],
|
|
155
133
|
templates: [],
|
|
156
134
|
rules: [],
|
|
157
135
|
other: []
|
|
158
136
|
};
|
|
159
137
|
for (const override of Array.from(existingOverrides).sort()) {
|
|
160
|
-
if (override.startsWith('
|
|
161
|
-
overridesByType.
|
|
138
|
+
if (override.startsWith('jobs/')) {
|
|
139
|
+
overridesByType.jobs.push(override);
|
|
162
140
|
}
|
|
163
141
|
else if (override.startsWith('templates/')) {
|
|
164
142
|
overridesByType.templates.push(override);
|
|
@@ -170,9 +148,9 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
|
|
|
170
148
|
overridesByType.other.push(override);
|
|
171
149
|
}
|
|
172
150
|
}
|
|
173
|
-
if (overridesByType.
|
|
174
|
-
console.log(chalk_1.default.gray('
|
|
175
|
-
overridesByType.
|
|
151
|
+
if (overridesByType.jobs.length > 0) {
|
|
152
|
+
console.log(chalk_1.default.gray(' Jobs:'));
|
|
153
|
+
overridesByType.jobs.forEach(o => console.log(` ${chalk_1.default.green('[OVERRIDDEN]')} ${o}`));
|
|
176
154
|
console.log('');
|
|
177
155
|
}
|
|
178
156
|
if (overridesByType.templates.length > 0) {
|
|
@@ -197,10 +175,10 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
|
|
|
197
175
|
showTips();
|
|
198
176
|
function showTips() {
|
|
199
177
|
console.log(chalk_1.default.gray('Tips:'));
|
|
200
|
-
console.log(chalk_1.default.gray('
|
|
201
|
-
console.log(chalk_1.default.gray('
|
|
202
|
-
console.log(chalk_1.default.gray('
|
|
203
|
-
console.log(chalk_1.default.gray('
|
|
204
|
-
console.log(chalk_1.default.gray(
|
|
178
|
+
console.log(chalk_1.default.gray(' - Use "fraim override <path> --inherit" to inherit from global'));
|
|
179
|
+
console.log(chalk_1.default.gray(' - Use "fraim override <path> --copy" to copy current content'));
|
|
180
|
+
console.log(chalk_1.default.gray(' - Use --job-category <category> to see category-specific items'));
|
|
181
|
+
console.log(chalk_1.default.gray(' - Use --rules to see all overridable rules'));
|
|
182
|
+
console.log(chalk_1.default.gray(` - Overrides are stored in ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('personalized-employee')}`));
|
|
205
183
|
}
|
|
206
184
|
});
|
|
@@ -8,24 +8,50 @@ const commander_1 = require("commander");
|
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
|
|
12
|
+
function collectRelativeMarkdownPaths(rootDir, currentRelativeDir = '') {
|
|
13
|
+
if (!fs_1.default.existsSync(rootDir)) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
const results = [];
|
|
17
|
+
const entries = fs_1.default.readdirSync(rootDir, { withFileTypes: true });
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
const relativePath = currentRelativeDir
|
|
20
|
+
? path_1.default.join(currentRelativeDir, entry.name)
|
|
21
|
+
: entry.name;
|
|
22
|
+
const fullPath = path_1.default.join(rootDir, entry.name);
|
|
23
|
+
if (entry.isDirectory()) {
|
|
24
|
+
results.push(...collectRelativeMarkdownPaths(fullPath, relativePath));
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
28
|
+
results.push(relativePath.replace(/\\/g, '/'));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return results;
|
|
32
|
+
}
|
|
11
33
|
exports.listCommand = new commander_1.Command('list')
|
|
12
|
-
.description('List available FRAIM
|
|
34
|
+
.description('List available FRAIM jobs in the project')
|
|
13
35
|
.action(() => {
|
|
14
36
|
const projectRoot = process.cwd();
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
37
|
+
const employeeJobsDir = (0, project_fraim_paths_1.getWorkspaceFraimPath)(projectRoot, 'ai-employee', 'jobs');
|
|
38
|
+
const managerJobsDir = (0, project_fraim_paths_1.getWorkspaceFraimPath)(projectRoot, 'ai-manager', 'jobs');
|
|
39
|
+
if (!(0, project_fraim_paths_1.workspaceFraimExists)(projectRoot) || (!fs_1.default.existsSync(employeeJobsDir) && !fs_1.default.existsSync(managerJobsDir))) {
|
|
40
|
+
console.log(chalk_1.default.yellow(`No local job stubs found under ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('ai-employee/jobs')} or ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('ai-manager/jobs')}. Run "fraim setup" or "fraim init-project" first.`));
|
|
18
41
|
return;
|
|
19
42
|
}
|
|
20
|
-
const stubs =
|
|
43
|
+
const stubs = [
|
|
44
|
+
...collectRelativeMarkdownPaths(employeeJobsDir).map(stub => `ai-employee/${stub}`),
|
|
45
|
+
...collectRelativeMarkdownPaths(managerJobsDir).map(stub => `ai-manager/${stub}`)
|
|
46
|
+
];
|
|
21
47
|
if (stubs.length === 0) {
|
|
22
|
-
console.log(chalk_1.default.gray('No
|
|
48
|
+
console.log(chalk_1.default.gray('No jobs found.'));
|
|
23
49
|
return;
|
|
24
50
|
}
|
|
25
|
-
console.log(chalk_1.default.blue(
|
|
51
|
+
console.log(chalk_1.default.blue(`${stubs.length} Local Jobs Found:`));
|
|
26
52
|
stubs.sort().forEach(stub => {
|
|
27
|
-
const
|
|
28
|
-
console.log(chalk_1.default.white(` - ${
|
|
53
|
+
const jobName = stub.replace('.md', '');
|
|
54
|
+
console.log(chalk_1.default.white(` - ${jobName}`));
|
|
29
55
|
});
|
|
30
56
|
console.log(chalk_1.default.gray('\nTip: Use these stubs for AI agent discoverability.'));
|
|
31
57
|
});
|
|
@@ -0,0 +1,42 @@
|
|
|
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.migrateProjectFraimCommand = exports.runMigrateProjectFraim = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const project_fraim_migration_1 = require("../../core/utils/project-fraim-migration");
|
|
10
|
+
const runMigrateProjectFraim = async () => {
|
|
11
|
+
const result = (0, project_fraim_migration_1.migrateLegacyProjectFraimDir)(process.cwd());
|
|
12
|
+
if (!result.legacyDirFound) {
|
|
13
|
+
console.log(chalk_1.default.gray('No project .fraim directory found. Nothing to migrate.'));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (result.migratedPaths.length === 0 && result.prunedLegacyPaths.length === 0 && result.conflictPaths.length === 0) {
|
|
17
|
+
console.log(chalk_1.default.gray('No migratable legacy FRAIM content found in project .fraim/.'));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (result.migratedPaths.length > 0) {
|
|
21
|
+
console.log(chalk_1.default.green(`Migrated into fraim/: ${result.migratedPaths.join(', ')}`));
|
|
22
|
+
}
|
|
23
|
+
if (result.prunedLegacyPaths.length > 0) {
|
|
24
|
+
console.log(chalk_1.default.green(`Removed legacy synced content from project .fraim/: ${result.prunedLegacyPaths.join(', ')}`));
|
|
25
|
+
}
|
|
26
|
+
if (result.conflictPaths.length > 0) {
|
|
27
|
+
console.log(chalk_1.default.yellow(`Conflicts left in project .fraim/: ${result.conflictPaths.join(', ')}`));
|
|
28
|
+
console.log(chalk_1.default.yellow('Review those files and make sure the canonical version in fraim/ is correct before deleting project .fraim/.'));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (result.legacyDirReadyForDeletion) {
|
|
32
|
+
console.log(chalk_1.default.green('Migration complete. Everything that FRAIM still uses is now under fraim/.'));
|
|
33
|
+
console.log(chalk_1.default.yellow('You can now delete the project .fraim/ folder.'));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
console.log(chalk_1.default.green('Migration complete. Everything that FRAIM still uses is now under fraim/.'));
|
|
37
|
+
console.log(chalk_1.default.yellow('After you verify the migrated files, delete the project .fraim/ folder.'));
|
|
38
|
+
};
|
|
39
|
+
exports.runMigrateProjectFraim = runMigrateProjectFraim;
|
|
40
|
+
exports.migrateProjectFraimCommand = new commander_1.Command('migrate-project-fraim')
|
|
41
|
+
.description('One-time migration from project .fraim/ to fraim/')
|
|
42
|
+
.action(exports.runMigrateProjectFraim);
|