fraim-framework 2.0.68 → 2.0.70

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 (37) hide show
  1. package/bin/fraim.js +1 -1
  2. package/dist/src/cli/commands/doctor.js +1 -1
  3. package/dist/src/cli/commands/init-project.js +8 -14
  4. package/dist/src/cli/commands/list.js +1 -1
  5. package/dist/src/cli/commands/setup.js +16 -30
  6. package/dist/src/cli/commands/sync.js +39 -146
  7. package/dist/src/cli/fraim.js +0 -4
  8. package/dist/src/cli/setup/first-run.js +4 -4
  9. package/dist/src/cli/setup/ide-detector.js +15 -5
  10. package/dist/src/{utils → cli/utils}/version-utils.js +5 -5
  11. package/dist/src/{utils → core/utils}/git-utils.js +2 -2
  12. package/dist/src/core/utils/object-utils.js +11 -0
  13. package/dist/src/core/utils/provider-utils.js +14 -0
  14. package/dist/src/local-mcp-server/stdio-server.js +170 -181
  15. package/index.js +1 -1
  16. package/package.json +15 -13
  17. package/dist/src/cli/commands/init.js +0 -148
  18. package/dist/src/cli/commands/mcp.js +0 -65
  19. package/dist/src/cli/commands/wizard.js +0 -35
  20. package/dist/src/fraim/db-service.js +0 -135
  21. package/dist/src/fraim/issue-tracking/ado-provider.js +0 -304
  22. package/dist/src/fraim/issue-tracking/factory.js +0 -63
  23. package/dist/src/fraim/issue-tracking/github-provider.js +0 -200
  24. package/dist/src/fraim/issue-tracking/types.js +0 -7
  25. package/dist/src/fraim/issue-tracking-config.js +0 -83
  26. package/dist/src/fraim/issues.js +0 -69
  27. package/dist/src/fraim/retrospective-learner.js +0 -301
  28. package/dist/src/fraim/setup-wizard.js +0 -99
  29. package/dist/src/fraim/template-processor.js +0 -166
  30. package/dist/src/{utils → cli/utils}/digest-utils.js +0 -0
  31. package/dist/src/{utils → cli/utils}/platform-detection.js +0 -0
  32. package/dist/src/{utils → cli/utils}/remote-sync.js +0 -0
  33. package/dist/src/{utils → cli/utils}/script-sync-utils.js +0 -0
  34. package/dist/src/{fraim → core}/config-loader.js +0 -0
  35. package/dist/src/{fraim → core}/types.js +0 -0
  36. package/dist/src/{utils → core/utils}/stub-generator.js +0 -0
  37. package/dist/src/{utils → core/utils}/workflow-parser.js +7 -7
@@ -1,65 +0,0 @@
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.mcpCommand = void 0;
7
- const commander_1 = require("commander");
8
- const server_1 = require("../../local-mcp-server/server");
9
- const chalk_1 = __importDefault(require("chalk"));
10
- exports.mcpCommand = new commander_1.Command('mcp')
11
- .description('Start the local FRAIM MCP server')
12
- .option('-p, --port <port>', 'Port to run the server on', '3003')
13
- .option('--remote-only', 'Use remote server only (skip local proxy)', false)
14
- .action(async (options) => {
15
- try {
16
- if (options.remoteOnly) {
17
- console.log(chalk_1.default.yellow('⚠️ Remote-only mode not yet implemented'));
18
- console.log(chalk_1.default.blue('ℹ️ Starting local MCP server instead...'));
19
- }
20
- const port = parseInt(options.port);
21
- if (isNaN(port) || port < 1 || port > 65535) {
22
- console.error(chalk_1.default.red('❌ Invalid port number. Must be between 1 and 65535.'));
23
- process.exit(1);
24
- }
25
- console.log(chalk_1.default.blue('🚀 Starting FRAIM Local MCP Server...'));
26
- console.log(chalk_1.default.gray(`📡 Port: ${port}`));
27
- console.log(chalk_1.default.gray(`🔄 Auto-updates: Every 5 minutes`));
28
- console.log(chalk_1.default.gray(`📁 Cache: ~/.fraim/cache`));
29
- console.log('');
30
- // Set the port in environment
31
- process.env.FRAIM_LOCAL_MCP_PORT = port.toString();
32
- // Start the server
33
- await (0, server_1.startLocalMCPServer)();
34
- // Server is now running, show configuration instructions
35
- console.log('');
36
- console.log(chalk_1.default.green('✅ FRAIM Local MCP Server is running!'));
37
- console.log('');
38
- console.log(chalk_1.default.bold('📋 Agent Configuration:'));
39
- console.log('');
40
- console.log(chalk_1.default.gray('Add this to your agent\'s MCP configuration:'));
41
- console.log('');
42
- console.log(chalk_1.default.cyan(JSON.stringify({
43
- "mcpServers": {
44
- "fraim": {
45
- "command": "fraim",
46
- "args": ["mcp"],
47
- "env": {}
48
- }
49
- }
50
- }, null, 2)));
51
- console.log('');
52
- console.log(chalk_1.default.bold('🔗 Endpoints:'));
53
- console.log(chalk_1.default.gray(` MCP: http://localhost:${port}/mcp`));
54
- console.log(chalk_1.default.gray(` Health: http://localhost:${port}/health`));
55
- console.log('');
56
- console.log(chalk_1.default.bold('🛑 To stop the server:'));
57
- console.log(chalk_1.default.gray(' Press Ctrl+C'));
58
- console.log('');
59
- }
60
- catch (error) {
61
- console.error(chalk_1.default.red('❌ Failed to start Local MCP Server:'));
62
- console.error(chalk_1.default.red(error instanceof Error ? error.message : String(error)));
63
- process.exit(1);
64
- }
65
- });
@@ -1,35 +0,0 @@
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.wizardCommand = void 0;
7
- const commander_1 = require("commander");
8
- const chalk_1 = __importDefault(require("chalk"));
9
- const init_js_1 = require("./init.js");
10
- const sync_js_1 = require("./sync.js");
11
- exports.wizardCommand = new commander_1.Command('wizard')
12
- .description('Run the interactive setup wizard')
13
- .action(async () => {
14
- console.log(chalk_1.default.cyan('🔮 FRAIM Interactive Setup Wizard\n'));
15
- console.log('Running V2 setup (Init + Sync)...\n');
16
- try {
17
- // Run initialization
18
- try {
19
- await (0, init_js_1.runInit)();
20
- }
21
- catch (err) {
22
- console.log(chalk_1.default.yellow('⚠️ Init notice:'), err.message);
23
- }
24
- console.log(''); // newline
25
- // Run sync (Force not strictly required if init is fresh, but safe)
26
- // But if user runs wizard on existing project, maybe they want to resync
27
- await (0, sync_js_1.runSync)({ force: false });
28
- console.log(chalk_1.default.blue('\n✨ Wizard completed successfully!'));
29
- console.log(chalk_1.default.gray('Tip: In the future, you can run `fraim sync` to update workflows.'));
30
- }
31
- catch (error) {
32
- console.error(chalk_1.default.red('❌ Wizard failed:'), error);
33
- process.exit(1);
34
- }
35
- });
@@ -1,135 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FraimDbService = void 0;
4
- const mongodb_1 = require("mongodb");
5
- const git_utils_1 = require("../utils/git-utils");
6
- class FraimDbService {
7
- constructor() {
8
- const url = process.env.MONGODB_URI || process.env.MONGO_DATABASE_URL;
9
- if (!url) {
10
- throw new Error('MONGODB_URI or MONGO_DATABASE_URL not found in environment');
11
- }
12
- this.client = new mongodb_1.MongoClient(url);
13
- }
14
- async connect() {
15
- await this.client.connect();
16
- const dbName = (0, git_utils_1.determineDatabaseName)();
17
- this.db = this.client.db(dbName);
18
- // Use fraim_ prefix for standalone server collections
19
- this.keysCollection = this.db.collection('fraim_api_keys');
20
- this.sessionsCollection = this.db.collection('fraim_telemetry_sessions');
21
- this.signupsCollection = this.db.collection('fraim_website_signups');
22
- this.salesCollection = this.db.collection('fraim_sales_inquiries');
23
- // Create indexes
24
- await this.keysCollection.createIndex({ key: 1 }, { unique: true });
25
- await this.sessionsCollection.createIndex({ sessionId: 1 }, { unique: true });
26
- await this.sessionsCollection.createIndex({ userId: 1 });
27
- await this.sessionsCollection.createIndex({ lastActive: -1 });
28
- await this.signupsCollection.createIndex({ email: 1 }, { unique: true });
29
- await this.signupsCollection.createIndex({ timestamp: -1 });
30
- await this.salesCollection.createIndex({ email: 1 });
31
- await this.salesCollection.createIndex({ timestamp: -1 });
32
- console.log(`✅ Connected to Fraim DB: ${dbName}`);
33
- }
34
- async createSession(session) {
35
- if (!this.sessionsCollection)
36
- throw new Error('DB not connected');
37
- await this.sessionsCollection.insertOne(session);
38
- }
39
- async updateSessionActivity(sessionId, lastActive) {
40
- if (!this.sessionsCollection)
41
- return; // Fail silently
42
- await this.sessionsCollection.updateOne({ sessionId }, { $set: { lastActive } });
43
- }
44
- async verifyApiKey(key) {
45
- if (!this.keysCollection)
46
- throw new Error('DB not connected');
47
- const apiKey = await this.keysCollection.findOne({ key, isActive: true });
48
- return apiKey;
49
- }
50
- async getActiveSessionByApiKey(key) {
51
- if (!this.keysCollection || !this.sessionsCollection)
52
- throw new Error('DB not connected');
53
- // 1. Get user for this key
54
- const apiKeyData = await this.verifyApiKey(key);
55
- if (!apiKeyData)
56
- return null;
57
- // 2. Get latest session for this user (within last 24h)
58
- const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
59
- const session = await this.sessionsCollection.findOne({ userId: apiKeyData.userId, lastActive: { $gt: dayAgo } }, { sort: { lastActive: -1 } });
60
- return session;
61
- }
62
- // ... (rest of methods)
63
- async listApiKeys() {
64
- if (!this.keysCollection)
65
- throw new Error('DB not connected');
66
- return await this.keysCollection.find({}).toArray();
67
- }
68
- async createApiKey(data) {
69
- if (!this.keysCollection)
70
- throw new Error('DB not connected');
71
- await this.keysCollection.insertOne({
72
- ...data,
73
- isActive: true,
74
- createdAt: new Date()
75
- });
76
- }
77
- async revokeApiKey(key) {
78
- if (!this.keysCollection)
79
- throw new Error('DB not connected');
80
- const result = await this.keysCollection.updateOne({ key }, { $set: { isActive: false } });
81
- return result.matchedCount > 0;
82
- }
83
- /**
84
- * Generates a secure API key
85
- */
86
- generateApiKey(userId, orgId) {
87
- const prefix = 'fraim_';
88
- const random = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
89
- return `${prefix}${random}`;
90
- }
91
- async createWebsiteSignup(signup) {
92
- if (!this.signupsCollection)
93
- throw new Error('DB not connected');
94
- try {
95
- await this.signupsCollection.insertOne(signup);
96
- }
97
- catch (error) {
98
- // Handle duplicate email gracefully
99
- if (error.code === 11000) {
100
- throw new Error('Email already registered');
101
- }
102
- throw error;
103
- }
104
- }
105
- async getWebsiteSignups(limit = 100) {
106
- if (!this.signupsCollection)
107
- throw new Error('DB not connected');
108
- return await this.signupsCollection.find({})
109
- .sort({ timestamp: -1 })
110
- .limit(limit)
111
- .toArray();
112
- }
113
- async getSignupByEmail(email) {
114
- if (!this.signupsCollection)
115
- throw new Error('DB not connected');
116
- return await this.signupsCollection.findOne({ email });
117
- }
118
- async createSalesInquiry(inquiry) {
119
- if (!this.salesCollection)
120
- throw new Error('DB not connected');
121
- await this.salesCollection.insertOne(inquiry);
122
- }
123
- async getSalesInquiries(limit = 100) {
124
- if (!this.salesCollection)
125
- throw new Error('DB not connected');
126
- return await this.salesCollection.find({})
127
- .sort({ timestamp: -1 })
128
- .limit(limit)
129
- .toArray();
130
- }
131
- async close() {
132
- await this.client.close();
133
- }
134
- }
135
- exports.FraimDbService = FraimDbService;
@@ -1,304 +0,0 @@
1
- "use strict";
2
- /**
3
- * Azure DevOps (ADO) Issue Tracking Provider
4
- *
5
- * Implements the IssueTrackingProvider interface for Azure DevOps Work Items
6
- */
7
- var __importDefault = (this && this.__importDefault) || function (mod) {
8
- return (mod && mod.__esModule) ? mod : { "default": mod };
9
- };
10
- Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.AdoIssueProvider = void 0;
12
- const axios_1 = __importDefault(require("axios"));
13
- class AdoIssueProvider {
14
- constructor(config) {
15
- this.config = config;
16
- // Support both cloud and on-premise ADO
17
- this.baseUrl = config.baseUrl || `https://dev.azure.com/${config.organization}`;
18
- }
19
- async createIssue(params) {
20
- const { title, body, labels, assignee, priority, dryRun } = params;
21
- // ADO uses PATCH with JSON Patch format for work item creation
22
- const patchDocument = [
23
- {
24
- op: 'add',
25
- path: '/fields/System.Title',
26
- value: title
27
- },
28
- {
29
- op: 'add',
30
- path: '/fields/System.Description',
31
- value: body
32
- }
33
- ];
34
- // Add assignee if provided
35
- if (assignee) {
36
- patchDocument.push({
37
- op: 'add',
38
- path: '/fields/System.AssignedTo',
39
- value: assignee
40
- });
41
- }
42
- // Map priority to ADO priority values
43
- if (priority) {
44
- const adoPriority = this.mapPriorityToAdo(priority);
45
- patchDocument.push({
46
- op: 'add',
47
- path: '/fields/Microsoft.VSTS.Common.Priority',
48
- value: adoPriority
49
- });
50
- }
51
- // Add tags (ADO equivalent of labels)
52
- if (labels && labels.length > 0) {
53
- patchDocument.push({
54
- op: 'add',
55
- path: '/fields/System.Tags',
56
- value: labels.join('; ')
57
- });
58
- }
59
- if (dryRun) {
60
- return {
61
- success: true,
62
- dryRun: true,
63
- provider: 'ado',
64
- message: `[DRY RUN] Would create ADO work item: "${title}" in ${this.config.organization}/${this.config.project}`
65
- };
66
- }
67
- try {
68
- const url = `${this.baseUrl}/${this.config.project}/_apis/wit/workitems/$Bug?api-version=7.0`;
69
- const response = await axios_1.default.post(url, patchDocument, {
70
- headers: {
71
- 'Authorization': `Basic ${Buffer.from(`:${this.config.token}`).toString('base64')}`,
72
- 'Content-Type': 'application/json-patch+json',
73
- },
74
- });
75
- return {
76
- success: true,
77
- issueId: response.data.id,
78
- issueNumber: response.data.id,
79
- htmlUrl: response.data._links.html.href,
80
- provider: 'ado'
81
- };
82
- }
83
- catch (error) {
84
- return {
85
- success: false,
86
- provider: 'ado',
87
- message: this.formatError(error)
88
- };
89
- }
90
- }
91
- async getIssue(issueId) {
92
- try {
93
- const url = `${this.baseUrl}/${this.config.project}/_apis/wit/workitems/${issueId}?api-version=7.0`;
94
- const response = await axios_1.default.get(url, {
95
- headers: {
96
- 'Authorization': `Basic ${Buffer.from(`:${this.config.token}`).toString('base64')}`,
97
- },
98
- });
99
- const workItem = response.data;
100
- const fields = workItem.fields;
101
- return {
102
- id: workItem.id,
103
- title: fields['System.Title'] || '',
104
- body: fields['System.Description'] || '',
105
- state: this.mapAdoStateToGeneric(fields['System.State']),
106
- assignee: fields['System.AssignedTo']?.displayName,
107
- labels: fields['System.Tags'] ? fields['System.Tags'].split('; ') : [],
108
- createdAt: new Date(fields['System.CreatedDate']),
109
- updatedAt: new Date(fields['System.ChangedDate']),
110
- htmlUrl: workItem._links.html.href
111
- };
112
- }
113
- catch (error) {
114
- if (error.response?.status === 404) {
115
- return null;
116
- }
117
- throw new Error(this.formatError(error));
118
- }
119
- }
120
- async updateIssue(issueId, updates) {
121
- const patchDocument = [];
122
- if (updates.title) {
123
- patchDocument.push({
124
- op: 'replace',
125
- path: '/fields/System.Title',
126
- value: updates.title
127
- });
128
- }
129
- if (updates.body) {
130
- patchDocument.push({
131
- op: 'replace',
132
- path: '/fields/System.Description',
133
- value: updates.body
134
- });
135
- }
136
- if (updates.assignee) {
137
- patchDocument.push({
138
- op: 'replace',
139
- path: '/fields/System.AssignedTo',
140
- value: updates.assignee
141
- });
142
- }
143
- if (updates.priority) {
144
- patchDocument.push({
145
- op: 'replace',
146
- path: '/fields/Microsoft.VSTS.Common.Priority',
147
- value: this.mapPriorityToAdo(updates.priority)
148
- });
149
- }
150
- if (updates.labels) {
151
- patchDocument.push({
152
- op: 'replace',
153
- path: '/fields/System.Tags',
154
- value: updates.labels.join('; ')
155
- });
156
- }
157
- if (updates.dryRun) {
158
- return {
159
- success: true,
160
- dryRun: true,
161
- provider: 'ado',
162
- message: `[DRY RUN] Would update ADO work item #${issueId} in ${this.config.organization}/${this.config.project}`
163
- };
164
- }
165
- try {
166
- const url = `${this.baseUrl}/${this.config.project}/_apis/wit/workitems/${issueId}?api-version=7.0`;
167
- const response = await axios_1.default.patch(url, patchDocument, {
168
- headers: {
169
- 'Authorization': `Basic ${Buffer.from(`:${this.config.token}`).toString('base64')}`,
170
- 'Content-Type': 'application/json-patch+json',
171
- },
172
- });
173
- return {
174
- success: true,
175
- issueId: response.data.id,
176
- issueNumber: response.data.id,
177
- htmlUrl: response.data._links.html.href,
178
- provider: 'ado'
179
- };
180
- }
181
- catch (error) {
182
- return {
183
- success: false,
184
- provider: 'ado',
185
- message: this.formatError(error)
186
- };
187
- }
188
- }
189
- async listIssues(filters) {
190
- let wiql = `SELECT [System.Id], [System.Title], [System.State], [System.AssignedTo], [System.CreatedDate], [System.ChangedDate] FROM WorkItems WHERE [System.TeamProject] = '${this.config.project}'`;
191
- // Add state filter
192
- if (filters?.state && filters.state !== 'all') {
193
- const adoState = filters.state === 'open' ? 'Active' : 'Closed';
194
- wiql += ` AND [System.State] = '${adoState}'`;
195
- }
196
- // Add assignee filter
197
- if (filters?.assignee) {
198
- wiql += ` AND [System.AssignedTo] = '${filters.assignee}'`;
199
- }
200
- // Add label filter (tags in ADO)
201
- if (filters?.labels && filters.labels.length > 0) {
202
- const tagConditions = filters.labels.map(label => `[System.Tags] CONTAINS '${label}'`).join(' OR ');
203
- wiql += ` AND (${tagConditions})`;
204
- }
205
- wiql += ' ORDER BY [System.CreatedDate] DESC';
206
- try {
207
- // First, execute WIQL query to get work item IDs
208
- const queryUrl = `${this.baseUrl}/${this.config.project}/_apis/wit/wiql?api-version=7.0`;
209
- const queryResponse = await axios_1.default.post(queryUrl, { query: wiql }, {
210
- headers: {
211
- 'Authorization': `Basic ${Buffer.from(`:${this.config.token}`).toString('base64')}`,
212
- 'Content-Type': 'application/json',
213
- },
214
- });
215
- const workItemIds = queryResponse.data.workItems.map((wi) => wi.id);
216
- if (workItemIds.length === 0) {
217
- return [];
218
- }
219
- // Limit results
220
- const limitedIds = workItemIds.slice(0, filters?.limit || 30);
221
- // Get detailed work item information
222
- const detailsUrl = `${this.baseUrl}/${this.config.project}/_apis/wit/workitems?ids=${limitedIds.join(',')}&api-version=7.0`;
223
- const detailsResponse = await axios_1.default.get(detailsUrl, {
224
- headers: {
225
- 'Authorization': `Basic ${Buffer.from(`:${this.config.token}`).toString('base64')}`,
226
- },
227
- });
228
- return detailsResponse.data.value.map((workItem) => {
229
- const fields = workItem.fields;
230
- return {
231
- id: workItem.id,
232
- title: fields['System.Title'] || '',
233
- body: fields['System.Description'] || '',
234
- state: this.mapAdoStateToGeneric(fields['System.State']),
235
- assignee: fields['System.AssignedTo']?.displayName,
236
- labels: fields['System.Tags'] ? fields['System.Tags'].split('; ') : [],
237
- createdAt: new Date(fields['System.CreatedDate']),
238
- updatedAt: new Date(fields['System.ChangedDate']),
239
- htmlUrl: workItem._links.html.href
240
- };
241
- });
242
- }
243
- catch (error) {
244
- throw new Error(this.formatError(error));
245
- }
246
- }
247
- async validateConfig() {
248
- try {
249
- // Test by getting project info
250
- const url = `${this.baseUrl}/_apis/projects/${this.config.project}?api-version=7.0`;
251
- const response = await axios_1.default.get(url, {
252
- headers: {
253
- 'Authorization': `Basic ${Buffer.from(`:${this.config.token}`).toString('base64')}`,
254
- },
255
- });
256
- return {
257
- valid: true,
258
- message: `Connected to ADO project: ${response.data.name}`
259
- };
260
- }
261
- catch (error) {
262
- return {
263
- valid: false,
264
- message: this.formatError(error)
265
- };
266
- }
267
- }
268
- mapPriorityToAdo(priority) {
269
- switch (priority) {
270
- case 'critical': return 1;
271
- case 'high': return 2;
272
- case 'medium': return 3;
273
- case 'low': return 4;
274
- default: return 3;
275
- }
276
- }
277
- mapAdoStateToGeneric(adoState) {
278
- switch (adoState?.toLowerCase()) {
279
- case 'active':
280
- case 'new':
281
- case 'approved':
282
- return 'open';
283
- case 'resolved':
284
- case 'closed':
285
- case 'done':
286
- return 'closed';
287
- case 'committed':
288
- case 'in progress':
289
- return 'in-progress';
290
- default:
291
- return 'open';
292
- }
293
- }
294
- formatError(error) {
295
- if (axios_1.default.isAxiosError(error)) {
296
- return `ADO API Error: ${error.response?.status} - ${JSON.stringify(error.response?.data)}`;
297
- }
298
- else if (error instanceof Error) {
299
- return `ADO Error: ${error.message}`;
300
- }
301
- return 'Unknown ADO error';
302
- }
303
- }
304
- exports.AdoIssueProvider = AdoIssueProvider;
@@ -1,63 +0,0 @@
1
- "use strict";
2
- /**
3
- * Issue Tracking Provider Factory
4
- *
5
- * Creates the appropriate issue tracking provider based on configuration
6
- */
7
- Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.IssueTrackingFactory = void 0;
9
- const github_provider_1 = require("./github-provider");
10
- const ado_provider_1 = require("./ado-provider");
11
- class IssueTrackingFactory {
12
- /**
13
- * Create an issue tracking provider based on configuration
14
- */
15
- static createProvider(config) {
16
- switch (config.provider) {
17
- case 'github':
18
- if (!config.github) {
19
- throw new Error('GitHub configuration is required when provider is "github"');
20
- }
21
- return new github_provider_1.GitHubIssueProvider(config.github);
22
- case 'ado':
23
- if (!config.ado) {
24
- throw new Error('ADO configuration is required when provider is "ado"');
25
- }
26
- return new ado_provider_1.AdoIssueProvider(config.ado);
27
- default:
28
- throw new Error(`Unsupported issue tracking provider: ${config.provider}`);
29
- }
30
- }
31
- /**
32
- * Get available providers
33
- */
34
- static getAvailableProviders() {
35
- return ['github', 'ado'];
36
- }
37
- /**
38
- * Validate configuration without creating provider
39
- */
40
- static validateConfig(config) {
41
- switch (config.provider) {
42
- case 'github':
43
- if (!config.github) {
44
- return { valid: false, message: 'GitHub configuration is required' };
45
- }
46
- if (!config.github.owner || !config.github.repo || !config.github.token) {
47
- return { valid: false, message: 'GitHub configuration must include owner, repo, and token' };
48
- }
49
- return { valid: true };
50
- case 'ado':
51
- if (!config.ado) {
52
- return { valid: false, message: 'ADO configuration is required' };
53
- }
54
- if (!config.ado.organization || !config.ado.project || !config.ado.token) {
55
- return { valid: false, message: 'ADO configuration must include organization, project, and token' };
56
- }
57
- return { valid: true };
58
- default:
59
- return { valid: false, message: `Unsupported provider: ${config.provider}` };
60
- }
61
- }
62
- }
63
- exports.IssueTrackingFactory = IssueTrackingFactory;