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.
- 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 +124 -51
- package/dist/src/local-mcp-server/usage-collector.js +109 -27
- package/index.js +1 -1
- package/package.json +3 -4
|
@@ -1,61 +1,68 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* Validates
|
|
3
|
+
* Job checks for FRAIM doctor command
|
|
4
|
+
* Validates job installation and versions
|
|
5
5
|
* Issue #144: Enhanced doctor command
|
|
6
6
|
*/
|
|
7
7
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
8
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
9
|
};
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.
|
|
11
|
+
exports.getJobChecks = getJobChecks;
|
|
12
12
|
const fs_1 = __importDefault(require("fs"));
|
|
13
13
|
const path_1 = __importDefault(require("path"));
|
|
14
14
|
const inheritance_parser_1 = require("../../../core/utils/inheritance-parser");
|
|
15
|
+
const project_fraim_paths_1 = require("../../../core/utils/project-fraim-paths");
|
|
16
|
+
function getJobStubDirs() {
|
|
17
|
+
return [
|
|
18
|
+
(0, project_fraim_paths_1.getWorkspaceFraimPath)(process.cwd(), 'ai-employee', 'jobs'),
|
|
19
|
+
(0, project_fraim_paths_1.getWorkspaceFraimPath)(process.cwd(), 'ai-manager', 'jobs')
|
|
20
|
+
];
|
|
21
|
+
}
|
|
15
22
|
/**
|
|
16
|
-
* Check if
|
|
23
|
+
* Check if jobs directory exists
|
|
17
24
|
*/
|
|
18
|
-
function
|
|
25
|
+
function checkJobsDirectoryExists() {
|
|
19
26
|
return {
|
|
20
|
-
name: '
|
|
21
|
-
category: '
|
|
27
|
+
name: 'Jobs directory exists',
|
|
28
|
+
category: 'jobs',
|
|
22
29
|
critical: true,
|
|
23
30
|
run: async () => {
|
|
24
|
-
const
|
|
25
|
-
|
|
31
|
+
const jobDirs = getJobStubDirs();
|
|
32
|
+
const existingJobDirs = jobDirs.filter(fs_1.default.existsSync);
|
|
33
|
+
if ((0, project_fraim_paths_1.workspaceFraimExists)(process.cwd()) && existingJobDirs.length > 0) {
|
|
26
34
|
return {
|
|
27
35
|
status: 'passed',
|
|
28
|
-
message: '
|
|
29
|
-
details: {
|
|
36
|
+
message: 'Jobs directory exists',
|
|
37
|
+
details: { paths: existingJobDirs }
|
|
30
38
|
};
|
|
31
39
|
}
|
|
32
40
|
return {
|
|
33
41
|
status: 'error',
|
|
34
|
-
message: '
|
|
35
|
-
suggestion: 'Run fraim sync to
|
|
42
|
+
message: 'Jobs directory missing',
|
|
43
|
+
suggestion: 'Run fraim sync to hydrate local job stubs. Onboarding can still start through MCP if your agent is already connected.',
|
|
36
44
|
command: 'fraim sync'
|
|
37
45
|
};
|
|
38
46
|
}
|
|
39
47
|
};
|
|
40
48
|
}
|
|
41
49
|
/**
|
|
42
|
-
* Check if
|
|
50
|
+
* Check if job stubs are present
|
|
43
51
|
*/
|
|
44
|
-
function
|
|
52
|
+
function checkJobStubsPresent() {
|
|
45
53
|
return {
|
|
46
|
-
name: '
|
|
47
|
-
category: '
|
|
54
|
+
name: 'Job stubs present',
|
|
55
|
+
category: 'jobs',
|
|
48
56
|
critical: false,
|
|
49
57
|
run: async () => {
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
58
|
+
const jobDirs = getJobStubDirs().filter(fs_1.default.existsSync);
|
|
59
|
+
if (jobDirs.length === 0) {
|
|
52
60
|
return {
|
|
53
61
|
status: 'error',
|
|
54
|
-
message: 'Cannot check stubs -
|
|
62
|
+
message: 'Cannot check stubs - jobs directory missing'
|
|
55
63
|
};
|
|
56
64
|
}
|
|
57
65
|
try {
|
|
58
|
-
// Count all .md files recursively
|
|
59
66
|
let stubCount = 0;
|
|
60
67
|
const countStubs = (dir) => {
|
|
61
68
|
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
@@ -68,25 +75,25 @@ function checkWorkflowStubsPresent() {
|
|
|
68
75
|
}
|
|
69
76
|
}
|
|
70
77
|
};
|
|
71
|
-
countStubs
|
|
78
|
+
jobDirs.forEach(countStubs);
|
|
72
79
|
if (stubCount > 0) {
|
|
73
80
|
return {
|
|
74
81
|
status: 'passed',
|
|
75
|
-
message: `${stubCount}
|
|
82
|
+
message: `${stubCount} job stubs found`,
|
|
76
83
|
details: { stubCount }
|
|
77
84
|
};
|
|
78
85
|
}
|
|
79
86
|
return {
|
|
80
87
|
status: 'warning',
|
|
81
|
-
message: 'No
|
|
82
|
-
suggestion: 'Run fraim sync to
|
|
88
|
+
message: 'No job stubs found',
|
|
89
|
+
suggestion: 'Run fraim sync to hydrate local job stubs. This does not block agent-led onboarding if MCP access is healthy.',
|
|
83
90
|
command: 'fraim sync'
|
|
84
91
|
};
|
|
85
92
|
}
|
|
86
93
|
catch (error) {
|
|
87
94
|
return {
|
|
88
95
|
status: 'error',
|
|
89
|
-
message: 'Failed to count
|
|
96
|
+
message: 'Failed to count job stubs',
|
|
90
97
|
details: { error: error.message }
|
|
91
98
|
};
|
|
92
99
|
}
|
|
@@ -94,28 +101,27 @@ function checkWorkflowStubsPresent() {
|
|
|
94
101
|
};
|
|
95
102
|
}
|
|
96
103
|
/**
|
|
97
|
-
* Check if
|
|
104
|
+
* Check if jobs are up to date
|
|
98
105
|
*/
|
|
99
|
-
function
|
|
106
|
+
function checkJobsUpToDate() {
|
|
100
107
|
return {
|
|
101
|
-
name: '
|
|
102
|
-
category: '
|
|
108
|
+
name: 'Jobs up to date',
|
|
109
|
+
category: 'jobs',
|
|
103
110
|
critical: false,
|
|
104
111
|
run: async () => {
|
|
105
|
-
const configPath =
|
|
112
|
+
const configPath = (0, project_fraim_paths_1.getWorkspaceConfigPath)(process.cwd());
|
|
106
113
|
if (!fs_1.default.existsSync(configPath)) {
|
|
107
114
|
return {
|
|
108
115
|
status: 'warning',
|
|
109
|
-
message: 'Cannot check
|
|
116
|
+
message: 'Cannot check job version - config missing'
|
|
110
117
|
};
|
|
111
118
|
}
|
|
112
119
|
try {
|
|
113
120
|
const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
114
|
-
// Try multiple paths to find package.json
|
|
115
121
|
const possiblePaths = [
|
|
116
|
-
path_1.default.join(__dirname, '../../../../package.json'),
|
|
117
|
-
path_1.default.join(__dirname, '../../../package.json'),
|
|
118
|
-
path_1.default.join(process.cwd(), 'package.json')
|
|
122
|
+
path_1.default.join(__dirname, '../../../../package.json'),
|
|
123
|
+
path_1.default.join(__dirname, '../../../package.json'),
|
|
124
|
+
path_1.default.join(process.cwd(), 'package.json')
|
|
119
125
|
];
|
|
120
126
|
let packageJson = null;
|
|
121
127
|
for (const pkgPath of possiblePaths) {
|
|
@@ -135,14 +141,14 @@ function checkWorkflowsUpToDate() {
|
|
|
135
141
|
if (configVersion === currentVersion) {
|
|
136
142
|
return {
|
|
137
143
|
status: 'passed',
|
|
138
|
-
message: `
|
|
144
|
+
message: `Jobs up to date (v${currentVersion})`,
|
|
139
145
|
details: { version: currentVersion }
|
|
140
146
|
};
|
|
141
147
|
}
|
|
142
148
|
return {
|
|
143
149
|
status: 'warning',
|
|
144
|
-
message: `
|
|
145
|
-
suggestion: 'Update
|
|
150
|
+
message: `Jobs outdated (v${configVersion} -> v${currentVersion})`,
|
|
151
|
+
suggestion: 'Update local job stubs with fraim sync.',
|
|
146
152
|
command: 'fraim sync',
|
|
147
153
|
details: { configVersion, currentVersion }
|
|
148
154
|
};
|
|
@@ -150,7 +156,7 @@ function checkWorkflowsUpToDate() {
|
|
|
150
156
|
catch (error) {
|
|
151
157
|
return {
|
|
152
158
|
status: 'error',
|
|
153
|
-
message: 'Failed to check
|
|
159
|
+
message: 'Failed to check job version',
|
|
154
160
|
details: { error: error.message }
|
|
155
161
|
};
|
|
156
162
|
}
|
|
@@ -163,12 +169,11 @@ function checkWorkflowsUpToDate() {
|
|
|
163
169
|
function checkOverrideSyntaxValid() {
|
|
164
170
|
return {
|
|
165
171
|
name: 'Override syntax valid',
|
|
166
|
-
category: '
|
|
172
|
+
category: 'jobs',
|
|
167
173
|
critical: false,
|
|
168
174
|
run: async () => {
|
|
169
|
-
const personalizedDir =
|
|
170
|
-
|
|
171
|
-
if (!fs_1.default.existsSync(personalizedDir) && !fs_1.default.existsSync(legacyOverridesDir)) {
|
|
175
|
+
const personalizedDir = (0, project_fraim_paths_1.getWorkspaceFraimPath)(process.cwd(), 'personalized-employee');
|
|
176
|
+
if (!fs_1.default.existsSync(personalizedDir)) {
|
|
172
177
|
return {
|
|
173
178
|
status: 'passed',
|
|
174
179
|
message: 'No overrides (not required)',
|
|
@@ -180,7 +185,6 @@ function checkOverrideSyntaxValid() {
|
|
|
180
185
|
const overrides = [];
|
|
181
186
|
let invalidCount = 0;
|
|
182
187
|
const errors = [];
|
|
183
|
-
// Scan for all override files
|
|
184
188
|
const scanDir = (dir, base = '') => {
|
|
185
189
|
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
186
190
|
for (const entry of entries) {
|
|
@@ -193,17 +197,9 @@ function checkOverrideSyntaxValid() {
|
|
|
193
197
|
}
|
|
194
198
|
}
|
|
195
199
|
};
|
|
196
|
-
|
|
197
|
-
scanDir(personalizedDir);
|
|
198
|
-
}
|
|
199
|
-
if (fs_1.default.existsSync(legacyOverridesDir)) {
|
|
200
|
-
scanDir(legacyOverridesDir);
|
|
201
|
-
}
|
|
202
|
-
// Validate each override
|
|
200
|
+
scanDir(personalizedDir);
|
|
203
201
|
for (const override of overrides) {
|
|
204
|
-
const
|
|
205
|
-
const legacyPath = path_1.default.join(legacyOverridesDir, override);
|
|
206
|
-
const overridePath = fs_1.default.existsSync(primaryPath) ? primaryPath : legacyPath;
|
|
202
|
+
const overridePath = path_1.default.join(personalizedDir, override);
|
|
207
203
|
const content = fs_1.default.readFileSync(overridePath, 'utf-8');
|
|
208
204
|
const parseResult = parser.parse(content);
|
|
209
205
|
if (parseResult.hasImports) {
|
|
@@ -243,13 +239,13 @@ function checkOverrideSyntaxValid() {
|
|
|
243
239
|
};
|
|
244
240
|
}
|
|
245
241
|
/**
|
|
246
|
-
* Get all
|
|
242
|
+
* Get all job checks
|
|
247
243
|
*/
|
|
248
|
-
function
|
|
244
|
+
function getJobChecks() {
|
|
249
245
|
return [
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
246
|
+
checkJobsDirectoryExists(),
|
|
247
|
+
checkJobStubsPresent(),
|
|
248
|
+
checkJobsUpToDate(),
|
|
253
249
|
checkOverrideSyntaxValid()
|
|
254
250
|
];
|
|
255
251
|
}
|
|
@@ -13,7 +13,7 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
13
13
|
const CATEGORY_NAMES = {
|
|
14
14
|
globalSetup: 'Global Setup',
|
|
15
15
|
projectSetup: 'Project Setup',
|
|
16
|
-
|
|
16
|
+
jobs: 'Jobs',
|
|
17
17
|
ideConfiguration: 'IDE Configuration',
|
|
18
18
|
mcpConnectivity: 'MCP Server Connectivity',
|
|
19
19
|
scripts: 'Scripts'
|
package/dist/src/cli/fraim.js
CHANGED
|
@@ -49,6 +49,7 @@ const override_1 = require("./commands/override");
|
|
|
49
49
|
const list_overridable_1 = require("./commands/list-overridable");
|
|
50
50
|
const login_1 = require("./commands/login");
|
|
51
51
|
const mcp_1 = require("./commands/mcp");
|
|
52
|
+
const migrate_project_fraim_1 = require("./commands/migrate-project-fraim");
|
|
52
53
|
const fs_1 = __importDefault(require("fs"));
|
|
53
54
|
const path_1 = __importDefault(require("path"));
|
|
54
55
|
const program = new commander_1.Command();
|
|
@@ -74,7 +75,7 @@ catch (e) {
|
|
|
74
75
|
}
|
|
75
76
|
program
|
|
76
77
|
.name('fraim')
|
|
77
|
-
.description('FRAIM Framework CLI - Manage your AI
|
|
78
|
+
.description('FRAIM Framework CLI - Manage your AI jobs and rules')
|
|
78
79
|
.version(packageJson.version);
|
|
79
80
|
program.addCommand(sync_1.syncCommand);
|
|
80
81
|
program.addCommand(doctor_1.doctorCommand);
|
|
@@ -87,6 +88,7 @@ program.addCommand(override_1.overrideCommand);
|
|
|
87
88
|
program.addCommand(list_overridable_1.listOverridableCommand);
|
|
88
89
|
program.addCommand(login_1.loginCommand);
|
|
89
90
|
program.addCommand(mcp_1.mcpCommand);
|
|
91
|
+
program.addCommand(migrate_project_fraim_1.migrateProjectFraimCommand);
|
|
90
92
|
// Wait for async command initialization before parsing
|
|
91
93
|
(async () => {
|
|
92
94
|
// Import the initialization promise from setup command
|
|
@@ -31,7 +31,7 @@ exports.BASE_MCP_SERVERS = [
|
|
|
31
31
|
{
|
|
32
32
|
id: 'fraim',
|
|
33
33
|
name: 'FRAIM',
|
|
34
|
-
description: 'FRAIM
|
|
34
|
+
description: 'FRAIM job orchestration and mentoring',
|
|
35
35
|
buildServer: (fraimKey) => ({
|
|
36
36
|
command: 'npx',
|
|
37
37
|
args: ['-y', 'fraim-framework@latest', 'mcp'],
|
|
@@ -0,0 +1,83 @@
|
|
|
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.DeviceFlowService = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
class DeviceFlowService {
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Start the Device Flow Login
|
|
15
|
+
*/
|
|
16
|
+
async login() {
|
|
17
|
+
console.log(chalk_1.default.blue('\n🔗 Starting Authentication...'));
|
|
18
|
+
try {
|
|
19
|
+
// 1. Request device and user codes
|
|
20
|
+
const deviceCode = await this.requestDeviceCode();
|
|
21
|
+
console.log(chalk_1.default.yellow('\nACTION REQUIRED:'));
|
|
22
|
+
console.log(`1. Go to: ${chalk_1.default.cyan.underline(deviceCode.verification_uri)}`);
|
|
23
|
+
console.log(`2. Enter the code: ${chalk_1.default.bold.green(deviceCode.user_code)}`);
|
|
24
|
+
console.log(chalk_1.default.gray(`\nWaiting for authorization (expires in ${Math.floor(deviceCode.expires_in / 60)} minutes)...`));
|
|
25
|
+
// 2. Poll for the access token
|
|
26
|
+
const token = await this.pollForToken(deviceCode.device_code, deviceCode.interval);
|
|
27
|
+
console.log(chalk_1.default.green('\n✅ Authentication Successful!'));
|
|
28
|
+
return token;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
console.error(chalk_1.default.red(`\n❌ Authentication failed: ${error.message}`));
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async requestDeviceCode() {
|
|
36
|
+
const response = await axios_1.default.post(this.config.authUrl, {
|
|
37
|
+
client_id: this.config.clientId,
|
|
38
|
+
scope: this.config.scope
|
|
39
|
+
}, {
|
|
40
|
+
headers: { Accept: 'application/json' }
|
|
41
|
+
});
|
|
42
|
+
return response.data;
|
|
43
|
+
}
|
|
44
|
+
async pollForToken(deviceCode, interval) {
|
|
45
|
+
let currentInterval = interval * 1000;
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const poll = async () => {
|
|
48
|
+
try {
|
|
49
|
+
const response = await axios_1.default.post(this.config.tokenUrl, {
|
|
50
|
+
client_id: this.config.clientId,
|
|
51
|
+
device_code: deviceCode,
|
|
52
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code'
|
|
53
|
+
}, {
|
|
54
|
+
headers: { Accept: 'application/json' }
|
|
55
|
+
});
|
|
56
|
+
if (response.data.access_token) {
|
|
57
|
+
resolve(response.data.access_token);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (response.data.error) {
|
|
61
|
+
const error = response.data.error;
|
|
62
|
+
if (error === 'authorization_pending') {
|
|
63
|
+
// Keep polling
|
|
64
|
+
setTimeout(poll, currentInterval);
|
|
65
|
+
}
|
|
66
|
+
else if (error === 'slow_down') {
|
|
67
|
+
currentInterval += 5000;
|
|
68
|
+
setTimeout(poll, currentInterval);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
reject(new Error(response.data.error_description || error));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
reject(error);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
setTimeout(poll, currentInterval);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.DeviceFlowService = DeviceFlowService;
|
|
@@ -64,7 +64,7 @@ const promptForIDESelection = async (detectedIDEs, tokenInput) => {
|
|
|
64
64
|
console.log(chalk_1.default.gray(` Config: ${ide.configPath}`));
|
|
65
65
|
});
|
|
66
66
|
console.log(chalk_1.default.blue('FRAIM will add these MCP servers to selected IDEs:'));
|
|
67
|
-
console.log(chalk_1.default.gray(' • fraim (required for FRAIM
|
|
67
|
+
console.log(chalk_1.default.gray(' • fraim (required for FRAIM jobs)'));
|
|
68
68
|
console.log(chalk_1.default.gray(' • git (version control integration)'));
|
|
69
69
|
// Show configured provider MCP servers dynamically
|
|
70
70
|
const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
|
|
@@ -325,7 +325,7 @@ const autoConfigureMCP = async (fraimKey, tokenInput, selectedIDEs, providerConf
|
|
|
325
325
|
console.log(chalk_1.default.cyan(' 1. Restart your configured IDEs'));
|
|
326
326
|
console.log(chalk_1.default.cyan(' 2. Go to any project directory'));
|
|
327
327
|
console.log(chalk_1.default.cyan(' 3. Run: npx fraim-framework@latest init-project'));
|
|
328
|
-
console.log(chalk_1.default.cyan(' 4.
|
|
328
|
+
console.log(chalk_1.default.cyan(' 4. Tell your AI agent: "Onboard this project"'));
|
|
329
329
|
}
|
|
330
330
|
};
|
|
331
331
|
exports.autoConfigureMCP = autoConfigureMCP;
|
|
@@ -43,6 +43,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
43
43
|
const path_1 = __importDefault(require("path"));
|
|
44
44
|
const os_1 = __importDefault(require("os"));
|
|
45
45
|
const script_sync_utils_1 = require("../utils/script-sync-utils");
|
|
46
|
+
const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
|
|
46
47
|
const loadGlobalConfig = () => {
|
|
47
48
|
const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
|
|
48
49
|
if (!fs_1.default.existsSync(globalConfigPath)) {
|
|
@@ -214,7 +215,7 @@ const runFirstRunExperience = async () => {
|
|
|
214
215
|
const resolvedPath = path.resolve(process.cwd(), pathResponse.archDocPath);
|
|
215
216
|
if (fs.existsSync(resolvedPath)) {
|
|
216
217
|
try {
|
|
217
|
-
const configPath =
|
|
218
|
+
const configPath = (0, project_fraim_paths_1.getWorkspaceConfigPath)(process.cwd());
|
|
218
219
|
if (fs.existsSync(configPath)) {
|
|
219
220
|
const configContent = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
220
221
|
if (!configContent.customizations)
|
|
@@ -224,7 +225,7 @@ const runFirstRunExperience = async () => {
|
|
|
224
225
|
console.log(chalk_1.default.green(`\n✅ Linked architecture document: ${pathResponse.archDocPath}`));
|
|
225
226
|
}
|
|
226
227
|
else {
|
|
227
|
-
console.log(chalk_1.default.red(
|
|
228
|
+
console.log(chalk_1.default.red(`\n❌ ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json')} not found. Could not save preference.`));
|
|
228
229
|
}
|
|
229
230
|
}
|
|
230
231
|
catch (e) {
|
|
@@ -236,6 +237,6 @@ const runFirstRunExperience = async () => {
|
|
|
236
237
|
}
|
|
237
238
|
}
|
|
238
239
|
}
|
|
239
|
-
console.log(chalk_1.default.green(
|
|
240
|
+
console.log(chalk_1.default.green(`\n✅ Jobs are installed in ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('ai-employee/jobs')} and ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('ai-manager/jobs')}.\n`));
|
|
240
241
|
};
|
|
241
242
|
exports.runFirstRunExperience = runFirstRunExperience;
|
|
@@ -0,0 +1,126 @@
|
|
|
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.ensureAgentAdapterFiles = ensureAgentAdapterFiles;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
|
|
10
|
+
const START_MARKER = '<!-- FRAIM_AGENT_ADAPTER_START -->';
|
|
11
|
+
const END_MARKER = '<!-- FRAIM_AGENT_ADAPTER_END -->';
|
|
12
|
+
const CURSOR_RULE_PATH = path_1.default.join('.cursor', 'rules', 'fraim.mdc');
|
|
13
|
+
const CURSOR_FRONTMATTER = `---
|
|
14
|
+
description: FRAIM discovery and execution contract
|
|
15
|
+
alwaysApply: true
|
|
16
|
+
---`;
|
|
17
|
+
function buildManagedSection(body) {
|
|
18
|
+
return `${START_MARKER}
|
|
19
|
+
${body.trim()}
|
|
20
|
+
${END_MARKER}
|
|
21
|
+
`;
|
|
22
|
+
}
|
|
23
|
+
function mergeManagedSection(existingContent, managedSection) {
|
|
24
|
+
const normalized = existingContent.replace(/\r\n/g, '\n');
|
|
25
|
+
const startIndex = normalized.indexOf(START_MARKER);
|
|
26
|
+
const endIndex = normalized.indexOf(END_MARKER);
|
|
27
|
+
if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
|
|
28
|
+
const before = normalized.slice(0, startIndex).trimEnd();
|
|
29
|
+
const after = normalized.slice(endIndex + END_MARKER.length).trimStart();
|
|
30
|
+
const pieces = [before, managedSection.trim(), after].filter(Boolean);
|
|
31
|
+
return `${pieces.join('\n\n').trim()}\n`;
|
|
32
|
+
}
|
|
33
|
+
if (!normalized.trim()) {
|
|
34
|
+
return `${managedSection.trim()}\n`;
|
|
35
|
+
}
|
|
36
|
+
return `${normalized.trimEnd()}\n\n${managedSection.trim()}\n`;
|
|
37
|
+
}
|
|
38
|
+
function mergeCursorRule(existingContent, managedSection) {
|
|
39
|
+
const normalized = existingContent.replace(/\r\n/g, '\n').trimStart();
|
|
40
|
+
const bodyWithoutLeadingFrontmatter = normalized.replace(/^(?:---\n[\s\S]*?\n---(?:\n+)?)*/, '');
|
|
41
|
+
const mergedBody = mergeManagedSection(bodyWithoutLeadingFrontmatter, managedSection).trim();
|
|
42
|
+
return `${CURSOR_FRONTMATTER}\n\n${mergedBody}\n`;
|
|
43
|
+
}
|
|
44
|
+
function getAdapterFiles() {
|
|
45
|
+
const fraimRoot = (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)().replace(/\/$/, '');
|
|
46
|
+
const employeeJobsPath = (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('ai-employee/jobs');
|
|
47
|
+
const managerJobsPath = (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('ai-manager/jobs');
|
|
48
|
+
const employeeSkillsPath = (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('ai-employee/skills');
|
|
49
|
+
const employeeRulesPath = (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('ai-employee/rules');
|
|
50
|
+
const personalizedRootPath = (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('personalized-employee');
|
|
51
|
+
const projectRulesPath = (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('personalized-employee/rules/project_rules.md');
|
|
52
|
+
const markdownBody = buildManagedSection(`
|
|
53
|
+
## FRAIM
|
|
54
|
+
|
|
55
|
+
This repository uses FRAIM.
|
|
56
|
+
|
|
57
|
+
- The FRAIM discovery catalog lives under \`${fraimRoot}/\`.
|
|
58
|
+
- Jobs under \`${employeeJobsPath}/\` and \`${managerJobsPath}/\` are FRAIM's primary execution units. Treat them like first-class workflows when deciding how to execute work.
|
|
59
|
+
- Skills under \`${employeeSkillsPath}/\` are reusable capabilities that jobs compose.
|
|
60
|
+
- Rules under \`${employeeRulesPath}/\` are always-on constraints and conventions.
|
|
61
|
+
- Repo-specific overrides and learning artifacts live under \`${personalizedRootPath}/\` and take precedence over synced baseline content.
|
|
62
|
+
- Use stub filenames and summaries to discover what to invoke. Once you identify a relevant job, call \`get_fraim_job({ job: "<job-name>" })\`.
|
|
63
|
+
- For deeper capability detail, call \`get_fraim_file({ path: "skills/<category>/<skill-name>.md" })\` or \`get_fraim_file({ path: "rules/<category>/<rule-name>.md" })\`.
|
|
64
|
+
- Read \`${projectRulesPath}\` if it exists before doing work.
|
|
65
|
+
`);
|
|
66
|
+
const cursorManagedBody = buildManagedSection(`
|
|
67
|
+
Use FRAIM as the repo's execution framework.
|
|
68
|
+
|
|
69
|
+
- Discover available jobs, skills, and rules under \`${fraimRoot}/\`.
|
|
70
|
+
- Jobs are the primary execution units; treat them like first-class workflows.
|
|
71
|
+
- Skills are reusable capability modules jobs compose.
|
|
72
|
+
- Rules are always-on constraints.
|
|
73
|
+
- Repo-specific overrides and learnings under \`${personalizedRootPath}/\` take precedence.
|
|
74
|
+
- Choose a relevant job from the stubs, then call \`get_fraim_job(...)\` for the full phased instructions.
|
|
75
|
+
`);
|
|
76
|
+
const copilotBody = buildManagedSection(`
|
|
77
|
+
## FRAIM
|
|
78
|
+
|
|
79
|
+
- Use \`${fraimRoot}/\` as the repository's FRAIM catalog.
|
|
80
|
+
- FRAIM jobs are the primary execution units and should be treated like first-class workflows.
|
|
81
|
+
- FRAIM skills are reusable capabilities jobs compose.
|
|
82
|
+
- FRAIM rules are always-on constraints and conventions.
|
|
83
|
+
- Repo-specific overrides and learnings live under \`${personalizedRootPath}/\`.
|
|
84
|
+
- Use the stubs to identify which job to invoke before fetching full content with FRAIM MCP tools.
|
|
85
|
+
`);
|
|
86
|
+
const fraimReadme = `# FRAIM Catalog
|
|
87
|
+
|
|
88
|
+
This directory is the repository-visible FRAIM surface.
|
|
89
|
+
|
|
90
|
+
- \`ai-employee/jobs/\`: employee job stubs
|
|
91
|
+
- \`ai-manager/jobs/\`: manager job stubs
|
|
92
|
+
- \`ai-employee/skills/\`: skill stubs
|
|
93
|
+
- \`ai-employee/rules/\`: rule stubs
|
|
94
|
+
- \`personalized-employee/\`: repo-specific overrides and learnings
|
|
95
|
+
|
|
96
|
+
Use the stubs here to discover which FRAIM job, skill, or rule is relevant, then load the full content through FRAIM MCP tools.
|
|
97
|
+
`;
|
|
98
|
+
return [
|
|
99
|
+
{ path: 'AGENTS.md', content: markdownBody },
|
|
100
|
+
{ path: 'CLAUDE.md', content: markdownBody },
|
|
101
|
+
{ path: path_1.default.join('.github', 'copilot-instructions.md'), content: copilotBody },
|
|
102
|
+
{ path: CURSOR_RULE_PATH, content: cursorManagedBody },
|
|
103
|
+
{ path: path_1.default.join(project_fraim_paths_1.WORKSPACE_FRAIM_DIRNAME, 'README.md'), content: fraimReadme }
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
function ensureAgentAdapterFiles(projectRoot) {
|
|
107
|
+
const updatedPaths = [];
|
|
108
|
+
for (const file of getAdapterFiles()) {
|
|
109
|
+
const fullPath = path_1.default.join(projectRoot, file.path);
|
|
110
|
+
const dir = path_1.default.dirname(fullPath);
|
|
111
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
112
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
113
|
+
}
|
|
114
|
+
const existing = fs_1.default.existsSync(fullPath) ? fs_1.default.readFileSync(fullPath, 'utf8') : '';
|
|
115
|
+
const next = file.path === CURSOR_RULE_PATH
|
|
116
|
+
? mergeCursorRule(existing, file.content)
|
|
117
|
+
: file.path.endsWith('README.md')
|
|
118
|
+
? file.content
|
|
119
|
+
: mergeManagedSection(existing, file.content);
|
|
120
|
+
if (existing !== next) {
|
|
121
|
+
fs_1.default.writeFileSync(fullPath, next, 'utf8');
|
|
122
|
+
updatedPaths.push(file.path.replace(/\\/g, '/'));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return updatedPaths;
|
|
126
|
+
}
|
|
@@ -9,16 +9,11 @@ const path_1 = __importDefault(require("path"));
|
|
|
9
9
|
exports.FRAIM_SYNC_GITIGNORE_START = '# BEGIN FRAIM SYNCED CONTENT';
|
|
10
10
|
exports.FRAIM_SYNC_GITIGNORE_END = '# END FRAIM SYNCED CONTENT';
|
|
11
11
|
exports.FRAIM_SYNC_GITIGNORE_ENTRIES = [
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'.fraim/ai-manager/'
|
|
12
|
+
'fraim/ai-employee/',
|
|
13
|
+
'fraim/ai-manager/',
|
|
14
|
+
'fraim/docs/',
|
|
16
15
|
];
|
|
17
16
|
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
18
|
-
/**
|
|
19
|
-
* Ensures the repo .gitignore contains a managed FRAIM synced-content block.
|
|
20
|
-
* Returns true when the file was modified.
|
|
21
|
-
*/
|
|
22
17
|
const ensureFraimSyncedContentIgnored = (projectRoot) => {
|
|
23
18
|
const gitignorePath = path_1.default.join(projectRoot, '.gitignore');
|
|
24
19
|
const existingRaw = fs_1.default.existsSync(gitignorePath)
|
|
@@ -26,19 +21,18 @@ const ensureFraimSyncedContentIgnored = (projectRoot) => {
|
|
|
26
21
|
: '';
|
|
27
22
|
const newline = existingRaw.includes('\r\n') ? '\r\n' : '\n';
|
|
28
23
|
const normalized = existingRaw.replace(/\r\n/g, '\n');
|
|
29
|
-
const managedBlock =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
fs_1.default.writeFileSync(gitignorePath, updatedNormalized.replace(/\n/g, newline), 'utf8');
|
|
24
|
+
const managedBlock = `${exports.FRAIM_SYNC_GITIGNORE_START}\n${exports.FRAIM_SYNC_GITIGNORE_ENTRIES.join('\n')}\n${exports.FRAIM_SYNC_GITIGNORE_END}`;
|
|
25
|
+
const blockPattern = new RegExp(`\\n?${escapeRegExp(exports.FRAIM_SYNC_GITIGNORE_START)}[\\s\\S]*?${escapeRegExp(exports.FRAIM_SYNC_GITIGNORE_END)}\\n?`, 'm');
|
|
26
|
+
const withoutManagedBlock = normalized.replace(blockPattern, '\n').trimEnd();
|
|
27
|
+
const cleaned = withoutManagedBlock
|
|
28
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
29
|
+
.replace(/^\n+/, '')
|
|
30
|
+
.trimEnd();
|
|
31
|
+
const next = cleaned.length > 0
|
|
32
|
+
? `${cleaned}\n\n${managedBlock}\n`
|
|
33
|
+
: `${managedBlock}\n`;
|
|
34
|
+
if (next !== normalized) {
|
|
35
|
+
fs_1.default.writeFileSync(gitignorePath, next.replace(/\n/g, newline), 'utf8');
|
|
42
36
|
return true;
|
|
43
37
|
}
|
|
44
38
|
return false;
|