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.
Files changed (70) hide show
  1. package/README.md +445 -0
  2. package/bin/fraim.js +23 -0
  3. package/dist/src/cli/api/get-provider-client.js +41 -0
  4. package/dist/src/cli/api/provider-client.js +107 -0
  5. package/dist/src/cli/commands/add-ide.js +430 -0
  6. package/dist/src/cli/commands/add-provider.js +233 -0
  7. package/dist/src/cli/commands/doctor.js +149 -0
  8. package/dist/src/cli/commands/init-project.js +301 -0
  9. package/dist/src/cli/commands/list-overridable.js +184 -0
  10. package/dist/src/cli/commands/list.js +57 -0
  11. package/dist/src/cli/commands/login.js +84 -0
  12. package/dist/src/cli/commands/mcp.js +15 -0
  13. package/dist/src/cli/commands/migrate-project-fraim.js +42 -0
  14. package/dist/src/cli/commands/override.js +177 -0
  15. package/dist/src/cli/commands/setup.js +651 -0
  16. package/dist/src/cli/commands/sync.js +162 -0
  17. package/dist/src/cli/commands/test-mcp.js +171 -0
  18. package/dist/src/cli/doctor/check-runner.js +199 -0
  19. package/dist/src/cli/doctor/checks/global-setup-checks.js +220 -0
  20. package/dist/src/cli/doctor/checks/ide-config-checks.js +250 -0
  21. package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +381 -0
  22. package/dist/src/cli/doctor/checks/project-setup-checks.js +282 -0
  23. package/dist/src/cli/doctor/checks/scripts-checks.js +157 -0
  24. package/dist/src/cli/doctor/checks/workflow-checks.js +251 -0
  25. package/dist/src/cli/doctor/reporters/console-reporter.js +96 -0
  26. package/dist/src/cli/doctor/reporters/json-reporter.js +11 -0
  27. package/dist/src/cli/doctor/types.js +6 -0
  28. package/dist/src/cli/fraim.js +100 -0
  29. package/dist/src/cli/internal/device-flow-service.js +83 -0
  30. package/dist/src/cli/mcp/ide-formats.js +243 -0
  31. package/dist/src/cli/mcp/mcp-server-builder.js +48 -0
  32. package/dist/src/cli/mcp/mcp-server-registry.js +160 -0
  33. package/dist/src/cli/mcp/types.js +3 -0
  34. package/dist/src/cli/providers/local-provider-registry.js +166 -0
  35. package/dist/src/cli/providers/provider-registry.js +230 -0
  36. package/dist/src/cli/setup/auto-mcp-setup.js +331 -0
  37. package/dist/src/cli/setup/codex-local-config.js +37 -0
  38. package/dist/src/cli/setup/first-run.js +242 -0
  39. package/dist/src/cli/setup/ide-detector.js +179 -0
  40. package/dist/src/cli/setup/mcp-config-generator.js +192 -0
  41. package/dist/src/cli/setup/provider-prompts.js +339 -0
  42. package/dist/src/cli/utils/agent-adapters.js +126 -0
  43. package/dist/src/cli/utils/digest-utils.js +47 -0
  44. package/dist/src/cli/utils/fraim-gitignore.js +40 -0
  45. package/dist/src/cli/utils/platform-detection.js +258 -0
  46. package/dist/src/cli/utils/project-bootstrap.js +93 -0
  47. package/dist/src/cli/utils/remote-sync.js +315 -0
  48. package/dist/src/cli/utils/script-sync-utils.js +221 -0
  49. package/dist/src/cli/utils/version-utils.js +32 -0
  50. package/dist/src/core/ai-mentor.js +230 -0
  51. package/dist/src/core/config-loader.js +114 -0
  52. package/dist/src/core/config-writer.js +75 -0
  53. package/dist/src/core/types.js +23 -0
  54. package/dist/src/core/utils/git-utils.js +95 -0
  55. package/dist/src/core/utils/include-resolver.js +92 -0
  56. package/dist/src/core/utils/inheritance-parser.js +288 -0
  57. package/dist/src/core/utils/job-parser.js +176 -0
  58. package/dist/src/core/utils/local-registry-resolver.js +616 -0
  59. package/dist/src/core/utils/object-utils.js +11 -0
  60. package/dist/src/core/utils/project-fraim-migration.js +103 -0
  61. package/dist/src/core/utils/project-fraim-paths.js +38 -0
  62. package/dist/src/core/utils/provider-utils.js +18 -0
  63. package/dist/src/core/utils/server-startup.js +34 -0
  64. package/dist/src/core/utils/stub-generator.js +147 -0
  65. package/dist/src/core/utils/workflow-parser.js +174 -0
  66. package/dist/src/local-mcp-server/learning-context-builder.js +229 -0
  67. package/dist/src/local-mcp-server/stdio-server.js +1698 -0
  68. package/dist/src/local-mcp-server/usage-collector.js +264 -0
  69. package/index.js +85 -0
  70. package/package.json +139 -0
@@ -0,0 +1,339 @@
1
+ "use strict";
2
+ // Generic provider prompt system - no hardcoded provider knowledge
3
+ // All provider information comes from the server via ProviderClient
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || (function () {
21
+ var ownKeys = function(o) {
22
+ ownKeys = Object.getOwnPropertyNames || function (o) {
23
+ var ar = [];
24
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
25
+ return ar;
26
+ };
27
+ return ownKeys(o);
28
+ };
29
+ return function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ })();
37
+ var __importDefault = (this && this.__importDefault) || function (mod) {
38
+ return (mod && mod.__esModule) ? mod : { "default": mod };
39
+ };
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.promptForProviders = promptForProviders;
42
+ exports.promptForSingleProvider = promptForSingleProvider;
43
+ exports.promptForProviderToken = promptForProviderToken;
44
+ exports.promptForProviderConfig = promptForProviderConfig;
45
+ exports.promptForProviderCredentials = promptForProviderCredentials;
46
+ const chalk_1 = __importDefault(require("chalk"));
47
+ const prompts_1 = __importDefault(require("prompts"));
48
+ /**
49
+ * Prompt user to select multiple providers
50
+ */
51
+ async function promptForProviders(client, preselectedIds) {
52
+ console.log(chalk_1.default.blue('\n🔧 Platform Selection'));
53
+ console.log(chalk_1.default.gray('Which platforms do you want to integrate with?\n'));
54
+ // Get providers that support integrated mode
55
+ const integratedProviders = await client.getProvidersWithCapability('integrated');
56
+ // Default to first integrated provider (typically GitHub)
57
+ const defaultProviderId = integratedProviders[0]?.id;
58
+ const choices = integratedProviders.map(provider => ({
59
+ title: provider.displayName,
60
+ value: provider.id,
61
+ selected: preselectedIds?.includes(provider.id) ?? provider.id === defaultProviderId
62
+ }));
63
+ if (process.env.FRAIM_NON_INTERACTIVE) {
64
+ console.log(chalk_1.default.yellow(`\nℹ️ Non-interactive mode: defaulting to ${integratedProviders[0]?.displayName || 'first available provider'}`));
65
+ return [defaultProviderId];
66
+ }
67
+ const response = await (0, prompts_1.default)({
68
+ type: 'multiselect',
69
+ name: 'providers',
70
+ message: 'Select platforms (Space to select, Enter to confirm)',
71
+ choices,
72
+ min: 1
73
+ });
74
+ if (!response.providers || response.providers.length === 0) {
75
+ const defaultProvider = await client.getProvider(defaultProviderId);
76
+ console.log(chalk_1.default.yellow(`\nℹ️ No platforms selected, defaulting to ${defaultProvider?.displayName || 'first available provider'}`));
77
+ return [defaultProviderId];
78
+ }
79
+ console.log(chalk_1.default.blue('\n✓ Selected platforms:'));
80
+ for (const id of response.providers) {
81
+ const provider = await client.getProvider(id);
82
+ if (provider) {
83
+ console.log(chalk_1.default.gray(` - ${provider.displayName}`));
84
+ }
85
+ }
86
+ console.log();
87
+ return response.providers;
88
+ }
89
+ /**
90
+ * Prompt user to select a single provider (for code repo or issue tracking)
91
+ */
92
+ async function promptForSingleProvider(client, purpose, availableIds) {
93
+ const purposeLabel = purpose === 'code' ? 'Code Repository' : 'Issue Tracking';
94
+ const purposeDesc = purpose === 'code' ? 'code hosting' : 'issue tracking';
95
+ console.log(chalk_1.default.blue(`\n📦 ${purposeLabel} Platform`));
96
+ console.log(chalk_1.default.gray(`Select the platform for ${purposeDesc}:\n`));
97
+ // Use capability-based filtering if no explicit filter provided
98
+ let providers;
99
+ if (availableIds) {
100
+ providers = [];
101
+ for (const id of availableIds) {
102
+ const provider = await client.getProvider(id);
103
+ if (provider)
104
+ providers.push(provider);
105
+ }
106
+ }
107
+ else {
108
+ providers = await client.getProvidersWithCapability(purpose);
109
+ }
110
+ // Default to first available provider
111
+ const defaultProviderId = providers[0]?.id;
112
+ const choices = providers.map(provider => ({
113
+ title: provider.displayName,
114
+ value: provider.id
115
+ }));
116
+ if (process.env.FRAIM_NON_INTERACTIVE) {
117
+ console.log(chalk_1.default.yellow(`\nℹ️ Non-interactive mode: defaulting to ${providers[0]?.displayName || 'first available provider'}`));
118
+ return defaultProviderId;
119
+ }
120
+ const response = await (0, prompts_1.default)({
121
+ type: 'select',
122
+ name: 'provider',
123
+ message: `Select ${purposeDesc} platform`,
124
+ choices,
125
+ initial: 0
126
+ });
127
+ if (!response.provider) {
128
+ const defaultProvider = await client.getProvider(defaultProviderId);
129
+ console.log(chalk_1.default.yellow(`\nℹ️ No platform selected, defaulting to ${defaultProvider?.displayName || 'first available provider'}`));
130
+ return defaultProviderId;
131
+ }
132
+ const provider = await client.getProvider(response.provider);
133
+ if (provider) {
134
+ console.log(chalk_1.default.blue(`\n✓ ${purposeLabel}: ${provider.displayName}\n`));
135
+ }
136
+ return response.provider;
137
+ }
138
+ /**
139
+ * Prompt for a provider's token
140
+ */
141
+ async function promptForProviderToken(client, providerId) {
142
+ let provider;
143
+ try {
144
+ provider = await client.getProvider(providerId);
145
+ }
146
+ catch (error) {
147
+ // Server unavailable - use local fallback
148
+ const { getLocalProvider } = await Promise.resolve().then(() => __importStar(require('../providers/local-provider-registry')));
149
+ provider = getLocalProvider(providerId);
150
+ }
151
+ if (!provider) {
152
+ throw new Error(`Unknown provider: ${providerId}`);
153
+ }
154
+ console.log(chalk_1.default.blue(`\n🔧 ${provider.displayName} Integration Setup`));
155
+ console.log(`FRAIM requires a ${provider.displayName} token for integration.\n`);
156
+ if (process.env.FRAIM_NON_INTERACTIVE) {
157
+ throw new Error(`Non-interactive mode: ${provider.displayName} token is missing and cannot prompt.`);
158
+ }
159
+ const hasToken = await (0, prompts_1.default)({
160
+ type: 'confirm',
161
+ name: 'hasToken',
162
+ message: `Do you have a ${provider.displayName} token?`,
163
+ initial: false
164
+ });
165
+ if (!hasToken.hasToken) {
166
+ if (providerId === 'github') {
167
+ const loginChoice = await (0, prompts_1.default)({
168
+ type: 'confirm',
169
+ name: 'login',
170
+ message: `Would you like to login to ${provider.displayName} now using your browser? (Recommended)`,
171
+ initial: true
172
+ });
173
+ if (loginChoice.login) {
174
+ const { DeviceFlowService } = await Promise.resolve().then(() => __importStar(require('../internal/device-flow-service')));
175
+ if (!provider.deviceFlowConfig) {
176
+ throw new Error(`Device flow configuration not found for provider: ${providerId}`);
177
+ }
178
+ const deviceFlow = new DeviceFlowService(provider.deviceFlowConfig);
179
+ try {
180
+ return await deviceFlow.login();
181
+ }
182
+ catch (e) {
183
+ console.log(chalk_1.default.yellow('\nBrowser login failed or was cancelled. Fallback to manual token entry.'));
184
+ }
185
+ }
186
+ }
187
+ console.log(chalk_1.default.yellow(`\n📝 To create a ${provider.displayName} token:`));
188
+ console.log(chalk_1.default.gray(` ${provider.setupInstructions}`));
189
+ console.log(chalk_1.default.gray(` Visit: ${provider.docsUrl}\n`));
190
+ }
191
+ let token = null;
192
+ let attempts = 0;
193
+ const maxAttempts = 3;
194
+ while (!token && attempts < maxAttempts) {
195
+ const tokenResponse = await (0, prompts_1.default)({
196
+ type: 'password',
197
+ name: 'token',
198
+ message: attempts === 0
199
+ ? `Enter your ${provider.displayName} token`
200
+ : `Enter your ${provider.displayName} token (attempt ${attempts + 1}/${maxAttempts})`,
201
+ validate: (value) => {
202
+ if (!value)
203
+ return `${provider.displayName} token is required`;
204
+ return true;
205
+ }
206
+ });
207
+ if (!tokenResponse.token) {
208
+ console.log(chalk_1.default.red(`\n❌ ${provider.displayName} token is required.`));
209
+ attempts++;
210
+ if (attempts < maxAttempts) {
211
+ const retry = await (0, prompts_1.default)({
212
+ type: 'confirm',
213
+ name: 'retry',
214
+ message: 'Would you like to try entering the token again?',
215
+ initial: true
216
+ });
217
+ if (!retry.retry)
218
+ break;
219
+ }
220
+ continue;
221
+ }
222
+ token = tokenResponse.token;
223
+ console.log(chalk_1.default.green(`✅ ${provider.displayName} token received\n`));
224
+ }
225
+ if (!token) {
226
+ throw new Error(`Failed to get ${provider.displayName} token`);
227
+ }
228
+ return token;
229
+ }
230
+ /**
231
+ * Prompt for additional provider configuration (e.g., Jira URL and email)
232
+ */
233
+ async function promptForProviderConfig(client, providerId) {
234
+ let provider;
235
+ let schema;
236
+ try {
237
+ provider = await client.getProvider(providerId);
238
+ schema = await client.getProviderSchema(providerId);
239
+ }
240
+ catch (error) {
241
+ // Server unavailable - use local fallback
242
+ const { getLocalProvider, getLocalProviderConfigRequirements } = await Promise.resolve().then(() => __importStar(require('../providers/local-provider-registry')));
243
+ provider = getLocalProvider(providerId);
244
+ if (!provider) {
245
+ throw new Error(`Unknown provider: ${providerId}`);
246
+ }
247
+ schema = { configRequirements: getLocalProviderConfigRequirements(providerId) };
248
+ }
249
+ if (!provider) {
250
+ throw new Error(`Unknown provider: ${providerId}`);
251
+ }
252
+ const requirements = schema.configRequirements;
253
+ if (requirements.length === 0) {
254
+ return {};
255
+ }
256
+ console.log(chalk_1.default.blue(`\n🔧 ${provider.displayName} Configuration`));
257
+ console.log(`Additional configuration required for ${provider.displayName}.\n`);
258
+ const config = {};
259
+ for (const req of requirements) {
260
+ if (process.env.FRAIM_NON_INTERACTIVE) {
261
+ if (req.required) {
262
+ throw new Error(`Non-interactive mode: Required configuration "${req.displayName}" for ${provider.displayName} is missing.`);
263
+ }
264
+ console.log(chalk_1.default.yellow(`\nℹ️ Non-interactive mode: skipping optional configuration "${req.displayName}"`));
265
+ continue;
266
+ }
267
+ const response = await (0, prompts_1.default)({
268
+ type: req.type === 'email' ? 'text' : req.type === 'url' ? 'text' : 'text',
269
+ name: 'value',
270
+ message: `Enter ${req.displayName}`,
271
+ validate: (value) => {
272
+ if (req.required && !value) {
273
+ return `${req.displayName} is required`;
274
+ }
275
+ if (req.type === 'email' && value && !value.includes('@')) {
276
+ return 'Please enter a valid email address';
277
+ }
278
+ if (req.type === 'url' && value) {
279
+ try {
280
+ new URL(value.startsWith('http') ? value : `https://${value}`);
281
+ }
282
+ catch {
283
+ return 'Please enter a valid URL';
284
+ }
285
+ }
286
+ return true;
287
+ }
288
+ });
289
+ if (!response.value && req.required) {
290
+ throw new Error(`${req.displayName} is required`);
291
+ }
292
+ config[req.key] = response.value;
293
+ }
294
+ console.log(chalk_1.default.green(`✅ ${provider.displayName} configuration received\n`));
295
+ return config;
296
+ }
297
+ /**
298
+ * Get token and config for a provider (handles both token and additional config)
299
+ */
300
+ async function promptForProviderCredentials(client, providerId, existingToken, existingConfig) {
301
+ let provider;
302
+ let schema;
303
+ try {
304
+ provider = await client.getProvider(providerId);
305
+ schema = await client.getProviderSchema(providerId);
306
+ }
307
+ catch (error) {
308
+ // Server unavailable - use local fallback
309
+ const { getLocalProvider, getLocalProviderConfigRequirements } = await Promise.resolve().then(() => __importStar(require('../providers/local-provider-registry')));
310
+ provider = getLocalProvider(providerId);
311
+ if (!provider) {
312
+ throw new Error(`Unknown provider: ${providerId}`);
313
+ }
314
+ schema = { configRequirements: getLocalProviderConfigRequirements(providerId) };
315
+ }
316
+ if (!provider) {
317
+ throw new Error(`Unknown provider: ${providerId}`);
318
+ }
319
+ // Get token if not provided
320
+ const token = existingToken || await promptForProviderToken(client, providerId);
321
+ // Get additional config if needed
322
+ const requirements = schema.configRequirements;
323
+ let config;
324
+ if (requirements.length > 0) {
325
+ // Check if we have all required config
326
+ const hasAllConfig = existingConfig && requirements.every(req => !req.required || existingConfig[req.key]);
327
+ if (!hasAllConfig) {
328
+ config = await promptForProviderConfig(client, providerId);
329
+ // Merge with existing config
330
+ if (existingConfig) {
331
+ config = { ...existingConfig, ...config };
332
+ }
333
+ }
334
+ else {
335
+ config = existingConfig;
336
+ }
337
+ }
338
+ return { token, config };
339
+ }
@@ -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
+ }
@@ -0,0 +1,47 @@
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.generateDigest = generateDigest;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ /**
11
+ * Generates a digest (hash) for a directory or file.
12
+ * This is used to compare the framework's registry with the project's local stubs.
13
+ */
14
+ async function generateDigest(targetPath) {
15
+ if (!fs_1.default.existsSync(targetPath)) {
16
+ return '';
17
+ }
18
+ const stats = fs_1.default.statSync(targetPath);
19
+ if (stats.isFile()) {
20
+ const content = fs_1.default.readFileSync(targetPath);
21
+ return crypto_1.default.createHash('md5').update(content).digest('hex');
22
+ }
23
+ if (stats.isDirectory()) {
24
+ const getAllFiles = (dir) => {
25
+ const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
26
+ let files = [];
27
+ for (const entry of entries) {
28
+ const fullPath = path_1.default.join(dir, entry.name);
29
+ if (entry.isDirectory()) {
30
+ files = files.concat(getAllFiles(fullPath));
31
+ }
32
+ else {
33
+ files.push(fullPath);
34
+ }
35
+ }
36
+ return files;
37
+ };
38
+ const allFiles = getAllFiles(targetPath);
39
+ const hashes = [];
40
+ for (const file of allFiles.sort()) {
41
+ const content = fs_1.default.readFileSync(file);
42
+ hashes.push(crypto_1.default.createHash('md5').update(content).digest('hex'));
43
+ }
44
+ return crypto_1.default.createHash('md5').update(hashes.join('')).digest('hex');
45
+ }
46
+ return '';
47
+ }
@@ -0,0 +1,40 @@
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.ensureFraimSyncedContentIgnored = exports.FRAIM_SYNC_GITIGNORE_ENTRIES = exports.FRAIM_SYNC_GITIGNORE_END = exports.FRAIM_SYNC_GITIGNORE_START = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ exports.FRAIM_SYNC_GITIGNORE_START = '# BEGIN FRAIM SYNCED CONTENT';
10
+ exports.FRAIM_SYNC_GITIGNORE_END = '# END FRAIM SYNCED CONTENT';
11
+ exports.FRAIM_SYNC_GITIGNORE_ENTRIES = [
12
+ 'fraim/ai-employee/',
13
+ 'fraim/ai-manager/',
14
+ 'fraim/docs/',
15
+ ];
16
+ const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
17
+ const ensureFraimSyncedContentIgnored = (projectRoot) => {
18
+ const gitignorePath = path_1.default.join(projectRoot, '.gitignore');
19
+ const existingRaw = fs_1.default.existsSync(gitignorePath)
20
+ ? fs_1.default.readFileSync(gitignorePath, 'utf8')
21
+ : '';
22
+ const newline = existingRaw.includes('\r\n') ? '\r\n' : '\n';
23
+ const normalized = existingRaw.replace(/\r\n/g, '\n');
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');
36
+ return true;
37
+ }
38
+ return false;
39
+ };
40
+ exports.ensureFraimSyncedContentIgnored = ensureFraimSyncedContentIgnored;