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
@@ -1,61 +1,68 @@
1
1
  "use strict";
2
2
  /**
3
- * Workflow checks for FRAIM doctor command
4
- * Validates workflow installation and versions
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.getWorkflowChecks = getWorkflowChecks;
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 workflows directory exists
23
+ * Check if jobs directory exists
17
24
  */
18
- function checkWorkflowsDirectoryExists() {
25
+ function checkJobsDirectoryExists() {
19
26
  return {
20
- name: 'Workflows directory exists',
21
- category: 'workflows',
27
+ name: 'Jobs directory exists',
28
+ category: 'jobs',
22
29
  critical: true,
23
30
  run: async () => {
24
- const workflowsDir = path_1.default.join(process.cwd(), '.fraim', 'workflows');
25
- if (fs_1.default.existsSync(workflowsDir)) {
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: 'Workflows directory exists',
29
- details: { path: workflowsDir }
36
+ message: 'Jobs directory exists',
37
+ details: { paths: existingJobDirs }
30
38
  };
31
39
  }
32
40
  return {
33
41
  status: 'error',
34
- message: 'Workflows directory missing',
35
- suggestion: 'Run fraim sync to fetch workflows',
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 workflow stubs are present
50
+ * Check if job stubs are present
43
51
  */
44
- function checkWorkflowStubsPresent() {
52
+ function checkJobStubsPresent() {
45
53
  return {
46
- name: 'Workflow stubs present',
47
- category: 'workflows',
54
+ name: 'Job stubs present',
55
+ category: 'jobs',
48
56
  critical: false,
49
57
  run: async () => {
50
- const workflowsDir = path_1.default.join(process.cwd(), '.fraim', 'workflows');
51
- if (!fs_1.default.existsSync(workflowsDir)) {
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 - workflows directory missing'
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(workflowsDir);
78
+ jobDirs.forEach(countStubs);
72
79
  if (stubCount > 0) {
73
80
  return {
74
81
  status: 'passed',
75
- message: `${stubCount} workflow stubs found`,
82
+ message: `${stubCount} job stubs found`,
76
83
  details: { stubCount }
77
84
  };
78
85
  }
79
86
  return {
80
87
  status: 'warning',
81
- message: 'No workflow stubs found',
82
- suggestion: 'Run fraim sync to fetch workflows',
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 workflow stubs',
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 workflows are up to date
104
+ * Check if jobs are up to date
98
105
  */
99
- function checkWorkflowsUpToDate() {
106
+ function checkJobsUpToDate() {
100
107
  return {
101
- name: 'Workflows up to date',
102
- category: 'workflows',
108
+ name: 'Jobs up to date',
109
+ category: 'jobs',
103
110
  critical: false,
104
111
  run: async () => {
105
- const configPath = path_1.default.join(process.cwd(), '.fraim', 'config.json');
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 workflow version - config missing'
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'), // From dist/src/cli/doctor/checks
117
- path_1.default.join(__dirname, '../../../package.json'), // From src/cli/doctor/checks
118
- path_1.default.join(process.cwd(), 'package.json') // From project root
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: `Workflows up to date (v${currentVersion})`,
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: `Workflows outdated (v${configVersion} v${currentVersion})`,
145
- suggestion: 'Update workflows with fraim sync',
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 workflow version',
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: 'workflows',
172
+ category: 'jobs',
167
173
  critical: false,
168
174
  run: async () => {
169
- const personalizedDir = path_1.default.join(process.cwd(), '.fraim', 'personalized-employee');
170
- const legacyOverridesDir = path_1.default.join(process.cwd(), '.fraim', 'overrides');
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
- if (fs_1.default.existsSync(personalizedDir)) {
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 primaryPath = path_1.default.join(personalizedDir, override);
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 workflow checks
242
+ * Get all job checks
247
243
  */
248
- function getWorkflowChecks() {
244
+ function getJobChecks() {
249
245
  return [
250
- checkWorkflowsDirectoryExists(),
251
- checkWorkflowStubsPresent(),
252
- checkWorkflowsUpToDate(),
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
- workflows: 'Workflows',
16
+ jobs: 'Jobs',
17
17
  ideConfiguration: 'IDE Configuration',
18
18
  mcpConnectivity: 'MCP Server Connectivity',
19
19
  scripts: 'Scripts'
@@ -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 workflows and rules')
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 workflow orchestration and mentoring',
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 workflows)'));
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. Ask your AI agent: "list fraim workflows"'));
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 = path.resolve(process.cwd(), '.fraim', 'config.json');
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('\n❌ .fraim/config.json not found. Could not save preference.'));
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('\n✅ Workflows are installed in .fraim/workflows.\n'));
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
- '.fraim/workflows/',
13
- '.fraim/docs/',
14
- '.fraim/ai-employee/',
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
- exports.FRAIM_SYNC_GITIGNORE_START,
31
- '# Synced by fraim init-project (generated content)',
32
- ...exports.FRAIM_SYNC_GITIGNORE_ENTRIES,
33
- exports.FRAIM_SYNC_GITIGNORE_END
34
- ].join('\n');
35
- const blockPattern = new RegExp(`${escapeRegExp(exports.FRAIM_SYNC_GITIGNORE_START)}[\\s\\S]*?${escapeRegExp(exports.FRAIM_SYNC_GITIGNORE_END)}\\n?`, 'm');
36
- const hasManagedBlock = blockPattern.test(normalized);
37
- const updatedNormalized = hasManagedBlock
38
- ? normalized.replace(blockPattern, `${managedBlock}\n`)
39
- : `${normalized.trimEnd()}${normalized.trimEnd().length > 0 ? '\n\n' : ''}${managedBlock}\n`;
40
- if (updatedNormalized !== normalized) {
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;