fraim 2.0.100
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/README.md +445 -0
- package/bin/fraim.js +23 -0
- package/dist/src/cli/api/get-provider-client.js +41 -0
- package/dist/src/cli/api/provider-client.js +107 -0
- package/dist/src/cli/commands/add-ide.js +430 -0
- package/dist/src/cli/commands/add-provider.js +233 -0
- package/dist/src/cli/commands/doctor.js +149 -0
- package/dist/src/cli/commands/init-project.js +301 -0
- package/dist/src/cli/commands/list-overridable.js +184 -0
- package/dist/src/cli/commands/list.js +57 -0
- package/dist/src/cli/commands/login.js +84 -0
- package/dist/src/cli/commands/mcp.js +15 -0
- package/dist/src/cli/commands/migrate-project-fraim.js +42 -0
- package/dist/src/cli/commands/override.js +177 -0
- package/dist/src/cli/commands/setup.js +651 -0
- package/dist/src/cli/commands/sync.js +162 -0
- package/dist/src/cli/commands/test-mcp.js +171 -0
- package/dist/src/cli/doctor/check-runner.js +199 -0
- package/dist/src/cli/doctor/checks/global-setup-checks.js +220 -0
- package/dist/src/cli/doctor/checks/ide-config-checks.js +250 -0
- package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +381 -0
- package/dist/src/cli/doctor/checks/project-setup-checks.js +282 -0
- package/dist/src/cli/doctor/checks/scripts-checks.js +157 -0
- package/dist/src/cli/doctor/checks/workflow-checks.js +251 -0
- package/dist/src/cli/doctor/reporters/console-reporter.js +96 -0
- package/dist/src/cli/doctor/reporters/json-reporter.js +11 -0
- package/dist/src/cli/doctor/types.js +6 -0
- package/dist/src/cli/fraim.js +100 -0
- package/dist/src/cli/internal/device-flow-service.js +83 -0
- package/dist/src/cli/mcp/ide-formats.js +243 -0
- package/dist/src/cli/mcp/mcp-server-builder.js +48 -0
- package/dist/src/cli/mcp/mcp-server-registry.js +160 -0
- package/dist/src/cli/mcp/types.js +3 -0
- package/dist/src/cli/providers/local-provider-registry.js +166 -0
- package/dist/src/cli/providers/provider-registry.js +230 -0
- package/dist/src/cli/setup/auto-mcp-setup.js +331 -0
- package/dist/src/cli/setup/codex-local-config.js +37 -0
- package/dist/src/cli/setup/first-run.js +242 -0
- package/dist/src/cli/setup/ide-detector.js +179 -0
- package/dist/src/cli/setup/mcp-config-generator.js +192 -0
- package/dist/src/cli/setup/provider-prompts.js +339 -0
- package/dist/src/cli/utils/agent-adapters.js +126 -0
- package/dist/src/cli/utils/digest-utils.js +47 -0
- package/dist/src/cli/utils/fraim-gitignore.js +40 -0
- package/dist/src/cli/utils/platform-detection.js +258 -0
- package/dist/src/cli/utils/project-bootstrap.js +93 -0
- package/dist/src/cli/utils/remote-sync.js +315 -0
- package/dist/src/cli/utils/script-sync-utils.js +221 -0
- package/dist/src/cli/utils/version-utils.js +32 -0
- package/dist/src/core/ai-mentor.js +230 -0
- package/dist/src/core/config-loader.js +114 -0
- package/dist/src/core/config-writer.js +75 -0
- package/dist/src/core/types.js +23 -0
- package/dist/src/core/utils/git-utils.js +95 -0
- package/dist/src/core/utils/include-resolver.js +92 -0
- package/dist/src/core/utils/inheritance-parser.js +288 -0
- package/dist/src/core/utils/job-parser.js +176 -0
- package/dist/src/core/utils/local-registry-resolver.js +616 -0
- package/dist/src/core/utils/object-utils.js +11 -0
- 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/provider-utils.js +18 -0
- package/dist/src/core/utils/server-startup.js +34 -0
- package/dist/src/core/utils/stub-generator.js +147 -0
- package/dist/src/core/utils/workflow-parser.js +174 -0
- package/dist/src/local-mcp-server/learning-context-builder.js +229 -0
- package/dist/src/local-mcp-server/stdio-server.js +1698 -0
- package/dist/src/local-mcp-server/usage-collector.js +264 -0
- package/index.js +85 -0
- package/package.json +139 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.doctorCommand = void 0;
|
|
4
|
+
exports.getAllChecks = getAllChecks;
|
|
5
|
+
const commander_1 = require("commander");
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
const check_runner_1 = require("../doctor/check-runner");
|
|
9
|
+
const console_reporter_1 = require("../doctor/reporters/console-reporter");
|
|
10
|
+
const json_reporter_1 = require("../doctor/reporters/json-reporter");
|
|
11
|
+
const global_setup_checks_1 = require("../doctor/checks/global-setup-checks");
|
|
12
|
+
const project_setup_checks_1 = require("../doctor/checks/project-setup-checks");
|
|
13
|
+
const workflow_checks_1 = require("../doctor/checks/workflow-checks");
|
|
14
|
+
const ide_config_checks_1 = require("../doctor/checks/ide-config-checks");
|
|
15
|
+
const mcp_connectivity_checks_1 = require("../doctor/checks/mcp-connectivity-checks");
|
|
16
|
+
const scripts_checks_1 = require("../doctor/checks/scripts-checks");
|
|
17
|
+
// Read version from package.json
|
|
18
|
+
const getFramVersion = () => {
|
|
19
|
+
try {
|
|
20
|
+
const packageJsonPath = (0, path_1.join)(__dirname, '../../../package.json');
|
|
21
|
+
const packageJson = JSON.parse((0, fs_1.readFileSync)(packageJsonPath, 'utf-8'));
|
|
22
|
+
return packageJson.version;
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
return 'unknown';
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const FRAIM_VERSION = getFramVersion();
|
|
29
|
+
// Simple logger for doctor command
|
|
30
|
+
const logger = {
|
|
31
|
+
info: (...args) => {
|
|
32
|
+
if (process.env.VERBOSE)
|
|
33
|
+
console.log('[DOCTOR]', ...args);
|
|
34
|
+
},
|
|
35
|
+
warn: (...args) => {
|
|
36
|
+
console.warn('[DOCTOR]', ...args);
|
|
37
|
+
},
|
|
38
|
+
error: (...args) => {
|
|
39
|
+
console.error('[DOCTOR]', ...args);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
// Simple metric tracker (no-op for now)
|
|
43
|
+
const trackMetric = (name, value) => {
|
|
44
|
+
if (process.env.DEBUG) {
|
|
45
|
+
console.debug(`[METRIC] ${name}: ${value}`);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Get all checks for doctor command
|
|
50
|
+
* Exported for testing
|
|
51
|
+
*/
|
|
52
|
+
function getAllChecks() {
|
|
53
|
+
return [
|
|
54
|
+
...(0, global_setup_checks_1.getGlobalSetupChecks)(),
|
|
55
|
+
...(0, project_setup_checks_1.getProjectSetupChecks)(),
|
|
56
|
+
...(0, workflow_checks_1.getJobChecks)(),
|
|
57
|
+
...(0, ide_config_checks_1.getIDEConfigChecks)(),
|
|
58
|
+
...(0, mcp_connectivity_checks_1.getMCPConnectivityChecks)(),
|
|
59
|
+
...(0, scripts_checks_1.getScriptsChecks)()
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
exports.doctorCommand = new commander_1.Command('doctor')
|
|
63
|
+
.description('Validate FRAIM installation and configuration')
|
|
64
|
+
.option('--test-mcp', 'Test only MCP server connectivity')
|
|
65
|
+
.option('--test-config', 'Validate only configuration files')
|
|
66
|
+
.option('--test-jobs', 'Check only job status')
|
|
67
|
+
.option('--verbose', 'Show detailed output including successful checks')
|
|
68
|
+
.option('--json', 'Output results as JSON')
|
|
69
|
+
.action(async (cmdOptions) => {
|
|
70
|
+
const startTime = Date.now();
|
|
71
|
+
const options = {
|
|
72
|
+
testMcp: cmdOptions.testMcp,
|
|
73
|
+
testConfig: cmdOptions.testConfig,
|
|
74
|
+
testJobs: cmdOptions.testJobs,
|
|
75
|
+
verbose: cmdOptions.verbose,
|
|
76
|
+
json: cmdOptions.json
|
|
77
|
+
};
|
|
78
|
+
// Log command start
|
|
79
|
+
logger.info('Doctor command started', {
|
|
80
|
+
flags: {
|
|
81
|
+
testMcp: options.testMcp || false,
|
|
82
|
+
testConfig: options.testConfig || false,
|
|
83
|
+
testJobs: options.testJobs || false,
|
|
84
|
+
verbose: options.verbose || false,
|
|
85
|
+
json: options.json || false
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
// Track execution count
|
|
89
|
+
trackMetric('doctor.execution.count', 1);
|
|
90
|
+
// Track flag usage
|
|
91
|
+
if (options.testMcp)
|
|
92
|
+
trackMetric('doctor.flags.test_mcp', 1);
|
|
93
|
+
if (options.testConfig)
|
|
94
|
+
trackMetric('doctor.flags.test_config', 1);
|
|
95
|
+
if (options.testJobs)
|
|
96
|
+
trackMetric('doctor.flags.test_jobs', 1);
|
|
97
|
+
if (options.verbose)
|
|
98
|
+
trackMetric('doctor.flags.verbose', 1);
|
|
99
|
+
if (options.json)
|
|
100
|
+
trackMetric('doctor.flags.json', 1);
|
|
101
|
+
try {
|
|
102
|
+
// Collect all checks
|
|
103
|
+
const checks = getAllChecks();
|
|
104
|
+
// Run checks
|
|
105
|
+
const result = await (0, check_runner_1.runChecks)(checks, options, FRAIM_VERSION);
|
|
106
|
+
const duration = Date.now() - startTime;
|
|
107
|
+
// Log results
|
|
108
|
+
logger.info('Doctor command completed', {
|
|
109
|
+
duration,
|
|
110
|
+
summary: result.summary
|
|
111
|
+
});
|
|
112
|
+
// Track metrics
|
|
113
|
+
trackMetric('doctor.execution.duration', duration);
|
|
114
|
+
trackMetric('doctor.checks.passed', result.summary.passed);
|
|
115
|
+
trackMetric('doctor.checks.warnings', result.summary.warnings);
|
|
116
|
+
trackMetric('doctor.checks.errors', result.summary.errors);
|
|
117
|
+
trackMetric('doctor.checks.total', result.summary.total);
|
|
118
|
+
// Log warnings and errors
|
|
119
|
+
if (result.summary.warnings > 0) {
|
|
120
|
+
logger.warn(`Doctor found ${result.summary.warnings} warning(s)`);
|
|
121
|
+
}
|
|
122
|
+
if (result.summary.errors > 0) {
|
|
123
|
+
logger.error(`Doctor found ${result.summary.errors} error(s)`);
|
|
124
|
+
}
|
|
125
|
+
// Output results
|
|
126
|
+
if (options.json) {
|
|
127
|
+
console.log((0, json_reporter_1.formatJsonOutput)(result));
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
console.log((0, console_reporter_1.formatConsoleOutput)(result, options));
|
|
131
|
+
}
|
|
132
|
+
// Set exit code based on results
|
|
133
|
+
if (result.summary.errors > 0) {
|
|
134
|
+
process.exit(2);
|
|
135
|
+
}
|
|
136
|
+
else if (result.summary.warnings > 0) {
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
process.exit(0);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
const duration = Date.now() - startTime;
|
|
145
|
+
logger.error('Doctor command failed', { error, duration });
|
|
146
|
+
trackMetric('doctor.execution.errors', 1);
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
@@ -0,0 +1,301 @@
|
|
|
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.initProjectCommand = exports.runInitProject = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const os_1 = __importDefault(require("os"));
|
|
11
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
12
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
13
|
+
const child_process_1 = require("child_process");
|
|
14
|
+
const sync_1 = require("./sync");
|
|
15
|
+
const platform_detection_1 = require("../utils/platform-detection");
|
|
16
|
+
const version_utils_1 = require("../utils/version-utils");
|
|
17
|
+
const ide_detector_1 = require("../setup/ide-detector");
|
|
18
|
+
const codex_local_config_1 = require("../setup/codex-local-config");
|
|
19
|
+
const provider_registry_1 = require("../providers/provider-registry");
|
|
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");
|
|
25
|
+
const promptForJiraProjectKey = async (jiraBaseUrl) => {
|
|
26
|
+
console.log(chalk_1.default.blue('\nJira Project Configuration'));
|
|
27
|
+
console.log(chalk_1.default.gray(`Jira instance: ${jiraBaseUrl}`));
|
|
28
|
+
console.log(chalk_1.default.gray('Enter the Jira project key for this repository (e.g., TEAM, PROJ, DEV)\n'));
|
|
29
|
+
if (process.env.FRAIM_NON_INTERACTIVE) {
|
|
30
|
+
const defaultKey = process.env.FRAIM_JIRA_PROJECT_KEY || 'PROJ';
|
|
31
|
+
console.log(chalk_1.default.yellow(`\nNon-interactive mode: using Jira project key "${defaultKey}"`));
|
|
32
|
+
return defaultKey;
|
|
33
|
+
}
|
|
34
|
+
const response = await (0, prompts_1.default)({
|
|
35
|
+
type: 'text',
|
|
36
|
+
name: 'projectKey',
|
|
37
|
+
message: 'Jira Project Key:',
|
|
38
|
+
validate: (value) => {
|
|
39
|
+
if (!value || value.trim().length === 0) {
|
|
40
|
+
return 'Project key is required';
|
|
41
|
+
}
|
|
42
|
+
if (!/^[A-Z][A-Z0-9]*$/.test(value.trim())) {
|
|
43
|
+
return 'Project key must start with a letter and contain only uppercase letters and numbers';
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
if (!response.projectKey) {
|
|
49
|
+
console.log(chalk_1.default.red('\nJira project key is required for split mode'));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
return response.projectKey.trim().toUpperCase();
|
|
53
|
+
};
|
|
54
|
+
const checkGlobalSetup = () => {
|
|
55
|
+
const fraimUserDir = process.env.FRAIM_USER_DIR || path_1.default.join(os_1.default.homedir(), '.fraim');
|
|
56
|
+
const globalConfigPath = path_1.default.join(fraimUserDir, 'config.json');
|
|
57
|
+
if (!fs_1.default.existsSync(globalConfigPath)) {
|
|
58
|
+
return { exists: false };
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const config = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
|
|
62
|
+
return {
|
|
63
|
+
exists: true,
|
|
64
|
+
mode: config.mode || 'integrated',
|
|
65
|
+
tokens: config.tokens || {},
|
|
66
|
+
providerConfigs: config.providerConfigs || {}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return { exists: true, mode: 'integrated', tokens: {} };
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const installGitHubWorkflows = (projectRoot) => {
|
|
74
|
+
const workflowsDir = path_1.default.join(projectRoot, '.github', 'workflows');
|
|
75
|
+
const registryDir = fs_1.default.existsSync(path_1.default.join(__dirname, '..', '..', '..', 'registry'))
|
|
76
|
+
? path_1.default.join(__dirname, '..', '..', '..', 'registry')
|
|
77
|
+
: path_1.default.join(__dirname, '..', '..', 'registry');
|
|
78
|
+
const sourceDir = path_1.default.join(registryDir, 'github', 'workflows');
|
|
79
|
+
if (!fs_1.default.existsSync(workflowsDir)) {
|
|
80
|
+
fs_1.default.mkdirSync(workflowsDir, { recursive: true });
|
|
81
|
+
}
|
|
82
|
+
const workflowFiles = ['phase-change.yml', 'status-change.yml', 'sync-on-pr-review.yml'];
|
|
83
|
+
workflowFiles.forEach((file) => {
|
|
84
|
+
const sourcePath = path_1.default.join(sourceDir, file);
|
|
85
|
+
const destPath = path_1.default.join(workflowsDir, file);
|
|
86
|
+
if (fs_1.default.existsSync(sourcePath)) {
|
|
87
|
+
fs_1.default.copyFileSync(sourcePath, destPath);
|
|
88
|
+
console.log(chalk_1.default.green(`Installed workflow: .github/workflows/${file}`));
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
console.log(chalk_1.default.yellow(`Warning: Workflow not found in registry: ${file}`));
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
const createGitHubLabels = (projectRoot) => {
|
|
96
|
+
try {
|
|
97
|
+
(0, child_process_1.execSync)('gh --version', { stdio: 'ignore' });
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
console.log(chalk_1.default.yellow('GitHub CLI (gh) not found. Skipping label creation.'));
|
|
101
|
+
console.log(chalk_1.default.gray('Install gh CLI to enable automatic label creation: https://cli.github.com/'));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const labelsPath = path_1.default.join(__dirname, '..', '..', '..', 'labels.json');
|
|
105
|
+
if (!fs_1.default.existsSync(labelsPath)) {
|
|
106
|
+
console.log(chalk_1.default.yellow('labels.json not found. Skipping label creation.'));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const labels = JSON.parse(fs_1.default.readFileSync(labelsPath, 'utf8'));
|
|
111
|
+
labels.forEach((label) => {
|
|
112
|
+
try {
|
|
113
|
+
(0, child_process_1.execSync)(`gh label create "${label.name}" --color "${label.color}" --description "${label.description}"`, { cwd: projectRoot, stdio: 'ignore' });
|
|
114
|
+
console.log(chalk_1.default.green(`Created label: ${label.name}`));
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (error.message && error.message.includes('already exists')) {
|
|
118
|
+
console.log(chalk_1.default.gray(`Label already exists: ${label.name}`));
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
console.log(chalk_1.default.yellow(`Could not create label ${label.name}: ${error.message}`));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
console.log(chalk_1.default.yellow(`Error reading labels.json: ${error.message}`));
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const runInitProject = async () => {
|
|
131
|
+
console.log(chalk_1.default.blue('Initializing FRAIM project...'));
|
|
132
|
+
const globalSetup = checkGlobalSetup();
|
|
133
|
+
if (!globalSetup.exists) {
|
|
134
|
+
console.log(chalk_1.default.red('Global FRAIM setup not found.'));
|
|
135
|
+
console.log(chalk_1.default.yellow('Please run global setup first:'));
|
|
136
|
+
console.log(chalk_1.default.cyan(' fraim setup'));
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
const projectRoot = process.cwd();
|
|
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);
|
|
147
|
+
if (!fs_1.default.existsSync(fraimDir)) {
|
|
148
|
+
fs_1.default.mkdirSync(fraimDir, { recursive: true });
|
|
149
|
+
console.log(chalk_1.default.green(`Created ${fraimDirDisplayPath} directory`));
|
|
150
|
+
(0, project_bootstrap_1.recordPathStatus)(result, fraimDirDisplayPath, true);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.log(chalk_1.default.yellow(`${fraimDirDisplayPath} directory already exists`));
|
|
154
|
+
(0, project_bootstrap_1.recordPathStatus)(result, fraimDirDisplayPath, false);
|
|
155
|
+
}
|
|
156
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
157
|
+
const preferredMode = globalSetup.mode || 'integrated';
|
|
158
|
+
result.mode = preferredMode;
|
|
159
|
+
let config;
|
|
160
|
+
if (preferredMode === 'conversational') {
|
|
161
|
+
config = {
|
|
162
|
+
version: (0, version_utils_1.getFraimVersion)(),
|
|
163
|
+
project: {
|
|
164
|
+
name: projectName
|
|
165
|
+
},
|
|
166
|
+
customizations: {}
|
|
167
|
+
};
|
|
168
|
+
console.log(chalk_1.default.blue(' conversational mode: no platform integration'));
|
|
169
|
+
console.log(chalk_1.default.gray(` Project: ${projectName}`));
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
const detection = (0, platform_detection_1.detectPlatformFromGit)();
|
|
173
|
+
if (detection.provider !== 'unknown' && detection.repository) {
|
|
174
|
+
result.repositoryDetected = true;
|
|
175
|
+
let issueTracking;
|
|
176
|
+
const jiraConfig = globalSetup.providerConfigs?.jiraConfig;
|
|
177
|
+
if (preferredMode === 'split' && globalSetup.tokens?.jira && jiraConfig?.baseUrl) {
|
|
178
|
+
const projectKey = process.env.FRAIM_JIRA_PROJECT_KEY ||
|
|
179
|
+
await promptForJiraProjectKey(jiraConfig.baseUrl);
|
|
180
|
+
issueTracking = {
|
|
181
|
+
provider: 'jira',
|
|
182
|
+
baseUrl: jiraConfig.baseUrl,
|
|
183
|
+
projectKey,
|
|
184
|
+
email: jiraConfig.email
|
|
185
|
+
};
|
|
186
|
+
result.issueTrackingDetected = true;
|
|
187
|
+
console.log(chalk_1.default.blue(` Code Repository: ${detection.provider.toUpperCase()}`));
|
|
188
|
+
console.log(chalk_1.default.blue(` Issue Tracking: JIRA (${projectKey})`));
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
issueTracking = {
|
|
192
|
+
provider: detection.provider,
|
|
193
|
+
...detection.repository
|
|
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
|
+
}
|
|
200
|
+
const providerDef = await (0, provider_registry_1.getProvider)(detection.provider);
|
|
201
|
+
console.log(chalk_1.default.blue(` Platform: ${providerDef?.displayName || detection.provider.toUpperCase()}`));
|
|
202
|
+
}
|
|
203
|
+
config = {
|
|
204
|
+
version: (0, version_utils_1.getFraimVersion)(),
|
|
205
|
+
project: {
|
|
206
|
+
name: detection.repository.name || projectName
|
|
207
|
+
},
|
|
208
|
+
repository: detection.repository,
|
|
209
|
+
issueTracking,
|
|
210
|
+
customizations: {}
|
|
211
|
+
};
|
|
212
|
+
const repo = detection.repository;
|
|
213
|
+
if (repo.owner && repo.name) {
|
|
214
|
+
console.log(chalk_1.default.gray(` Repository: ${repo.owner}/${repo.name}`));
|
|
215
|
+
}
|
|
216
|
+
else if (repo.organization && repo.project && repo.name) {
|
|
217
|
+
console.log(chalk_1.default.gray(` Organization: ${repo.organization}`));
|
|
218
|
+
console.log(chalk_1.default.gray(` Project: ${repo.project}`));
|
|
219
|
+
console.log(chalk_1.default.gray(` Repository: ${repo.name}`));
|
|
220
|
+
}
|
|
221
|
+
else if (repo.namespace && repo.name) {
|
|
222
|
+
console.log(chalk_1.default.gray(` Namespace: ${repo.namespace || '(none)'}`));
|
|
223
|
+
console.log(chalk_1.default.gray(` Repository: ${repo.name}`));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
config = {
|
|
228
|
+
version: (0, version_utils_1.getFraimVersion)(),
|
|
229
|
+
project: {
|
|
230
|
+
name: projectName
|
|
231
|
+
},
|
|
232
|
+
customizations: {}
|
|
233
|
+
};
|
|
234
|
+
result.mode = 'conversational';
|
|
235
|
+
result.warnings.push('No git remote detected. FRAIM fell back to conversational project setup.');
|
|
236
|
+
console.log(chalk_1.default.yellow(' No git remote detected. Falling back to conversational mode.'));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
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);
|
|
242
|
+
}
|
|
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) => {
|
|
258
|
+
const dirPath = path_1.default.join(fraimDir, dir);
|
|
259
|
+
if (!fs_1.default.existsSync(dirPath)) {
|
|
260
|
+
fs_1.default.mkdirSync(dirPath, { recursive: true });
|
|
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);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
if ((0, fraim_gitignore_1.ensureFraimSyncedContentIgnored)(projectRoot)) {
|
|
270
|
+
console.log(chalk_1.default.green('Updated .gitignore FRAIM managed block'));
|
|
271
|
+
}
|
|
272
|
+
const detection = (0, platform_detection_1.detectPlatformFromGit)();
|
|
273
|
+
if (detection.provider === 'github') {
|
|
274
|
+
console.log(chalk_1.default.blue('\nSetting up GitHub workflows and labels...'));
|
|
275
|
+
installGitHubWorkflows(projectRoot);
|
|
276
|
+
createGitHubLabels(projectRoot);
|
|
277
|
+
result.repositoryDetected = true;
|
|
278
|
+
}
|
|
279
|
+
if (!process.env.FRAIM_SKIP_SYNC) {
|
|
280
|
+
await (0, sync_1.runSync)({});
|
|
281
|
+
result.syncPerformed = true;
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
result.warnings.push('Sync was skipped for this run.');
|
|
285
|
+
}
|
|
286
|
+
const codexAvailable = (0, ide_detector_1.detectInstalledIDEs)().some((ide) => ide.configType === 'codex');
|
|
287
|
+
if (codexAvailable) {
|
|
288
|
+
const codexLocalResult = (0, codex_local_config_1.ensureCodexLocalConfig)(projectRoot);
|
|
289
|
+
const status = codexLocalResult.created ? 'Created' : codexLocalResult.updated ? 'Updated' : 'Verified';
|
|
290
|
+
console.log(chalk_1.default.green(`${status} project Codex config at ${codexLocalResult.path}`));
|
|
291
|
+
}
|
|
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(', ')}`));
|
|
295
|
+
}
|
|
296
|
+
(0, project_bootstrap_1.printInitProjectSummary)(result);
|
|
297
|
+
};
|
|
298
|
+
exports.runInitProject = runInitProject;
|
|
299
|
+
exports.initProjectCommand = new commander_1.Command('init-project')
|
|
300
|
+
.description('Initialize FRAIM in the current project (requires global setup)')
|
|
301
|
+
.action(exports.runInitProject);
|
|
@@ -0,0 +1,184 @@
|
|
|
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.listOverridableCommand = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
|
|
12
|
+
exports.listOverridableCommand = new commander_1.Command('list-overridable')
|
|
13
|
+
.description('List all FRAIM registry paths that can be overridden')
|
|
14
|
+
.option('--job-category <category>', 'Filter by job category (e.g., product-building, customer-development, marketing)')
|
|
15
|
+
.option('--rules', 'Show all overridable rules')
|
|
16
|
+
.action(async (options) => {
|
|
17
|
+
const projectRoot = process.cwd();
|
|
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.`));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
let registryRoot = null;
|
|
24
|
+
const frameworkRoot = path_1.default.join(__dirname, '..', '..', '..');
|
|
25
|
+
const frameworkRegistry = path_1.default.join(frameworkRoot, 'registry');
|
|
26
|
+
if (fs_1.default.existsSync(frameworkRegistry)) {
|
|
27
|
+
registryRoot = frameworkRegistry;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
const nodeModulesRegistry = path_1.default.join(process.cwd(), 'node_modules', '@fraim', 'framework', 'registry');
|
|
31
|
+
if (fs_1.default.existsSync(nodeModulesRegistry)) {
|
|
32
|
+
registryRoot = nodeModulesRegistry;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!registryRoot) {
|
|
36
|
+
console.log(chalk_1.default.red('Could not find FRAIM registry. Please ensure @fraim/framework is installed.'));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
console.log(chalk_1.default.blue('Overridable FRAIM Registry Paths:\n'));
|
|
40
|
+
const existingOverrides = new Set();
|
|
41
|
+
const scanDir = (dir, base = '') => {
|
|
42
|
+
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
43
|
+
for (const entry of entries) {
|
|
44
|
+
const relativePath = path_1.default.join(base, entry.name);
|
|
45
|
+
if (entry.isDirectory()) {
|
|
46
|
+
scanDir(path_1.default.join(dir, entry.name), relativePath);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
existingOverrides.add(relativePath.replace(/\\/g, '/'));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
if (fs_1.default.existsSync(personalizedDir)) {
|
|
54
|
+
scanDir(personalizedDir);
|
|
55
|
+
}
|
|
56
|
+
if (options.rules) {
|
|
57
|
+
const rulesDir = path_1.default.join(registryRoot, 'rules');
|
|
58
|
+
if (fs_1.default.existsSync(rulesDir)) {
|
|
59
|
+
console.log(chalk_1.default.bold.cyan('Rules:\n'));
|
|
60
|
+
const ruleFiles = fs_1.default.readdirSync(rulesDir)
|
|
61
|
+
.filter(f => f.endsWith('.md'))
|
|
62
|
+
.sort();
|
|
63
|
+
for (const file of ruleFiles) {
|
|
64
|
+
const filePath = `rules/${file}`;
|
|
65
|
+
const hasOverride = existingOverrides.has(filePath);
|
|
66
|
+
const status = hasOverride ? chalk_1.default.green('[OVERRIDDEN]') : chalk_1.default.gray('[OVERRIDABLE]');
|
|
67
|
+
console.log(` ${status} ${filePath}`);
|
|
68
|
+
}
|
|
69
|
+
console.log('');
|
|
70
|
+
}
|
|
71
|
+
showTips();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (options.jobCategory) {
|
|
75
|
+
const category = options.jobCategory;
|
|
76
|
+
const jobsDir = path_1.default.join(registryRoot, 'jobs', category);
|
|
77
|
+
const templatesDir = path_1.default.join(registryRoot, 'templates', category);
|
|
78
|
+
if (!fs_1.default.existsSync(jobsDir)) {
|
|
79
|
+
console.log(chalk_1.default.red(`Category "${category}" not found.`));
|
|
80
|
+
console.log(chalk_1.default.gray('\nAvailable categories:'));
|
|
81
|
+
const categoriesDir = path_1.default.join(registryRoot, 'jobs');
|
|
82
|
+
const categories = fs_1.default.readdirSync(categoriesDir, { withFileTypes: true })
|
|
83
|
+
.filter(d => d.isDirectory())
|
|
84
|
+
.map(d => d.name)
|
|
85
|
+
.sort();
|
|
86
|
+
categories.forEach(c => console.log(chalk_1.default.gray(` - ${c}`)));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
console.log(chalk_1.default.bold.cyan(`Jobs (${category}):\n`));
|
|
90
|
+
const jobFiles = fs_1.default.readdirSync(jobsDir)
|
|
91
|
+
.filter(f => f.endsWith('.md'))
|
|
92
|
+
.sort();
|
|
93
|
+
for (const file of jobFiles) {
|
|
94
|
+
const filePath = `jobs/${category}/${file}`;
|
|
95
|
+
const hasOverride = existingOverrides.has(filePath);
|
|
96
|
+
const status = hasOverride ? chalk_1.default.green('[OVERRIDDEN]') : chalk_1.default.gray('[OVERRIDABLE]');
|
|
97
|
+
console.log(` ${status} ${filePath}`);
|
|
98
|
+
}
|
|
99
|
+
console.log('');
|
|
100
|
+
if (fs_1.default.existsSync(templatesDir)) {
|
|
101
|
+
console.log(chalk_1.default.bold.cyan(`Templates (${category}):\n`));
|
|
102
|
+
const templateFiles = fs_1.default.readdirSync(templatesDir)
|
|
103
|
+
.filter(f => f.endsWith('.md') || f.endsWith('.html') || f.endsWith('.csv') || f.endsWith('.yml'))
|
|
104
|
+
.sort();
|
|
105
|
+
for (const file of templateFiles) {
|
|
106
|
+
const filePath = `templates/${category}/${file}`;
|
|
107
|
+
const hasOverride = existingOverrides.has(filePath);
|
|
108
|
+
const status = hasOverride ? chalk_1.default.green('[OVERRIDDEN]') : chalk_1.default.gray('[OVERRIDABLE]');
|
|
109
|
+
console.log(` ${status} ${filePath}`);
|
|
110
|
+
}
|
|
111
|
+
console.log('');
|
|
112
|
+
}
|
|
113
|
+
showTips();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
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 })
|
|
119
|
+
.filter(d => d.isDirectory())
|
|
120
|
+
.map(d => d.name)
|
|
121
|
+
.sort();
|
|
122
|
+
const columns = 3;
|
|
123
|
+
for (let i = 0; i < categories.length; i += columns) {
|
|
124
|
+
const row = categories.slice(i, i + columns);
|
|
125
|
+
const formatted = row.map(cat => chalk_1.default.gray(` - ${cat.padEnd(30)}`)).join('');
|
|
126
|
+
console.log(formatted);
|
|
127
|
+
}
|
|
128
|
+
console.log('');
|
|
129
|
+
if (existingOverrides.size > 0) {
|
|
130
|
+
console.log(chalk_1.default.bold.cyan('Your Active Overrides:\n'));
|
|
131
|
+
const overridesByType = {
|
|
132
|
+
jobs: [],
|
|
133
|
+
templates: [],
|
|
134
|
+
rules: [],
|
|
135
|
+
other: []
|
|
136
|
+
};
|
|
137
|
+
for (const override of Array.from(existingOverrides).sort()) {
|
|
138
|
+
if (override.startsWith('jobs/')) {
|
|
139
|
+
overridesByType.jobs.push(override);
|
|
140
|
+
}
|
|
141
|
+
else if (override.startsWith('templates/')) {
|
|
142
|
+
overridesByType.templates.push(override);
|
|
143
|
+
}
|
|
144
|
+
else if (override.startsWith('rules/')) {
|
|
145
|
+
overridesByType.rules.push(override);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
overridesByType.other.push(override);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
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}`));
|
|
154
|
+
console.log('');
|
|
155
|
+
}
|
|
156
|
+
if (overridesByType.templates.length > 0) {
|
|
157
|
+
console.log(chalk_1.default.gray(' Templates:'));
|
|
158
|
+
overridesByType.templates.forEach(o => console.log(` ${chalk_1.default.green('[OVERRIDDEN]')} ${o}`));
|
|
159
|
+
console.log('');
|
|
160
|
+
}
|
|
161
|
+
if (overridesByType.rules.length > 0) {
|
|
162
|
+
console.log(chalk_1.default.gray(' Rules:'));
|
|
163
|
+
overridesByType.rules.forEach(o => console.log(` ${chalk_1.default.green('[OVERRIDDEN]')} ${o}`));
|
|
164
|
+
console.log('');
|
|
165
|
+
}
|
|
166
|
+
if (overridesByType.other.length > 0) {
|
|
167
|
+
console.log(chalk_1.default.gray(' Other:'));
|
|
168
|
+
overridesByType.other.forEach(o => console.log(` ${chalk_1.default.green('[OVERRIDDEN]')} ${o}`));
|
|
169
|
+
console.log('');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
console.log(chalk_1.default.gray('No active overrides yet.\n'));
|
|
174
|
+
}
|
|
175
|
+
showTips();
|
|
176
|
+
function showTips() {
|
|
177
|
+
console.log(chalk_1.default.gray('Tips:'));
|
|
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')}`));
|
|
183
|
+
}
|
|
184
|
+
});
|