fraim-framework 2.0.95 → 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.
Files changed (40) hide show
  1. package/bin/fraim.js +1 -1
  2. package/dist/src/cli/commands/add-ide.js +1 -1
  3. package/dist/src/cli/commands/doctor.js +6 -6
  4. package/dist/src/cli/commands/init-project.js +63 -52
  5. package/dist/src/cli/commands/list-overridable.js +33 -55
  6. package/dist/src/cli/commands/list.js +35 -9
  7. package/dist/src/cli/commands/migrate-project-fraim.js +42 -0
  8. package/dist/src/cli/commands/override.js +18 -39
  9. package/dist/src/cli/commands/setup.js +1 -1
  10. package/dist/src/cli/commands/sync.js +34 -27
  11. package/dist/src/cli/doctor/check-runner.js +3 -3
  12. package/dist/src/cli/doctor/checks/global-setup-checks.js +13 -13
  13. package/dist/src/cli/doctor/checks/project-setup-checks.js +12 -12
  14. package/dist/src/cli/doctor/checks/scripts-checks.js +2 -2
  15. package/dist/src/cli/doctor/checks/workflow-checks.js +56 -60
  16. package/dist/src/cli/doctor/reporters/console-reporter.js +1 -1
  17. package/dist/src/cli/fraim.js +3 -1
  18. package/dist/src/cli/mcp/mcp-server-registry.js +1 -1
  19. package/dist/src/cli/services/device-flow-service.js +83 -0
  20. package/dist/src/cli/setup/auto-mcp-setup.js +2 -2
  21. package/dist/src/cli/setup/first-run.js +4 -3
  22. package/dist/src/cli/utils/agent-adapters.js +126 -0
  23. package/dist/src/cli/utils/fraim-gitignore.js +15 -21
  24. package/dist/src/cli/utils/project-bootstrap.js +93 -0
  25. package/dist/src/cli/utils/remote-sync.js +20 -67
  26. package/dist/src/core/ai-mentor.js +31 -49
  27. package/dist/src/core/config-loader.js +57 -62
  28. package/dist/src/core/config-writer.js +75 -0
  29. package/dist/src/core/types.js +1 -1
  30. package/dist/src/core/utils/job-parser.js +176 -0
  31. package/dist/src/core/utils/local-registry-resolver.js +61 -71
  32. package/dist/src/core/utils/project-fraim-migration.js +103 -0
  33. package/dist/src/core/utils/project-fraim-paths.js +38 -0
  34. package/dist/src/core/utils/stub-generator.js +41 -75
  35. package/dist/src/core/utils/workflow-parser.js +5 -3
  36. package/dist/src/local-mcp-server/learning-context-builder.js +229 -0
  37. package/dist/src/local-mcp-server/stdio-server.js +124 -51
  38. package/dist/src/local-mcp-server/usage-collector.js +109 -27
  39. package/index.js +1 -1
  40. package/package.json +3 -4
package/bin/fraim.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * FRAIM Framework CLI Entry Point
@@ -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. Ask your AI agent: "list fraim workflows"'));
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.getWorkflowChecks)(),
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-workflows', 'Check only workflow status')
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
- testWorkflows: cmdOptions.testWorkflows,
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
- testWorkflows: options.testWorkflows || false,
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.testWorkflows)
96
- trackMetric('doctor.flags.test_workflows', 1);
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('\nšŸŽ« Jira Project Configuration'));
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(`\nā„¹ļø Non-interactive mode: using Jira project key "${defaultKey}"`));
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('\nāŒ Jira project key is required for split mode'));
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 fraimDir = path_1.default.join(projectRoot, '.fraim');
151
- const configPath = path_1.default.join(fraimDir, 'config.json');
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('Created .fraim directory'));
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('.fraim directory already exists'));
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
- // Determine issue tracking configuration
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: 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
- fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
238
- console.log(chalk_1.default.green('Created .fraim/config.json'));
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
- ['workflows', 'ai-employee/jobs', 'ai-employee/skills', 'ai-manager/jobs', 'personalized-employee'].forEach((dir) => {
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
- console.log(chalk_1.default.green(`Created .fraim/${dir}`));
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 with FRAIM synced content ignore rules'));
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
- console.log(chalk_1.default.green('\nFRAIM project initialized!'));
267
- if (!process.env.FRAIM_NON_INTERACTIVE) {
268
- const response = await (0, prompts_1.default)({
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 workflow category (e.g., product-building, customer-development, marketing)')
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 fraimDir = path_1.default.join(projectRoot, '.fraim');
18
- const configPath = path_1.default.join(fraimDir, 'config.json');
19
- const personalizedDir = path_1.default.join(fraimDir, 'personalized-employee');
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('āŒ Could not find FRAIM registry. Please ensure @fraim/framework is installed.'));
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('šŸ“‹ Overridable FRAIM Registry Paths:\n'));
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 workflowsDir = path_1.default.join(registryRoot, 'workflows', category);
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(workflowsDir)) {
91
- console.log(chalk_1.default.red(`āŒ Category "${category}" not found.`));
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, 'workflows');
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
- // Show workflows for this category
102
- console.log(chalk_1.default.bold.cyan(`Workflows (${category}):\n`));
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 workflowFiles) {
107
- const filePath = `workflows/${category}/${file}`;
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
- // Default: Show available categories and existing overrides
135
- console.log(chalk_1.default.bold.cyan('Available Workflow Categories:\n'));
136
- const workflowsDir = path_1.default.join(registryRoot, 'workflows');
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(` • ${cat.padEnd(30)}`)).join('');
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
- workflows: [],
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('workflows/')) {
161
- overridesByType.workflows.push(override);
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.workflows.length > 0) {
174
- console.log(chalk_1.default.gray(' Workflows:'));
175
- overridesByType.workflows.forEach(o => console.log(` ${chalk_1.default.green('[OVERRIDDEN]')} ${o}`));
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(' • Use "fraim override <path> --inherit" to inherit from global'));
201
- console.log(chalk_1.default.gray(' • Use "fraim override <path> --copy" to copy current content'));
202
- console.log(chalk_1.default.gray(' • Use --job-category <category> to see category-specific items'));
203
- console.log(chalk_1.default.gray(' • Use --rules to see all overridable rules'));
204
- console.log(chalk_1.default.gray(' • Overrides are stored in .fraim/personalized-employee/'));
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 workflows in the project')
34
+ .description('List available FRAIM jobs in the project')
13
35
  .action(() => {
14
36
  const projectRoot = process.cwd();
15
- const workflowsDir = path_1.default.join(projectRoot, '.fraim', 'workflows');
16
- if (!fs_1.default.existsSync(workflowsDir)) {
17
- console.log(chalk_1.default.yellow('āš ļø No .fraim/workflows directory found. Run "fraim setup" or "fraim init-project" first.'));
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 = fs_1.default.readdirSync(workflowsDir).filter(f => f.endsWith('.md'));
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 workflows found.'));
48
+ console.log(chalk_1.default.gray('No jobs found.'));
23
49
  return;
24
50
  }
25
- console.log(chalk_1.default.blue(`šŸ“‹ ${stubs.length} Local Workflows Found:`));
51
+ console.log(chalk_1.default.blue(`${stubs.length} Local Jobs Found:`));
26
52
  stubs.sort().forEach(stub => {
27
- const workflowName = stub.replace('.md', '');
28
- console.log(chalk_1.default.white(` - ${workflowName}`));
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);