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,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;
|