fraim-framework 2.0.57 → 2.0.59

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.
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * FRAIM MCP Server Entry Point
4
+ *
5
+ * This is called by MCP clients (Cursor, Claude Desktop, Windsurf, Kiro, etc.)
6
+ * It starts the local STDIO MCP server that proxies to the remote FRAIM server
7
+ * and performs template substitution.
8
+ */
9
+
10
+ // Load and start the server
11
+ const { FraimLocalMCPServer } = require('../dist/src/local-mcp-server/stdio-server.js');
12
+
13
+ const server = new FraimLocalMCPServer();
14
+ server.start();
package/bin/fraim.js ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * FRAIM Framework CLI Entry Point
5
+ *
6
+ * This file delegates to the compiled TypeScript implementation.
7
+ */
8
+
9
+ try {
10
+ // In production/installed package, code is in dist/
11
+ require('../dist/src/cli/fraim.js');
12
+ } catch (error) {
13
+ if (error.code === 'MODULE_NOT_FOUND') {
14
+ // In development (local clone), we might use tsx if dist is missing
15
+ // But typically we should just tell user to build.
16
+ console.error('❌ Could not find FRAIM CLI implementation.');
17
+ console.error(' If you are running from source, please run "npm run build" first.');
18
+ console.error(` Error details: ${error.message}`);
19
+ process.exit(1);
20
+ } else {
21
+ throw error;
22
+ }
23
+ }
@@ -0,0 +1,65 @@
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
+ });
@@ -18,8 +18,7 @@ const generateStandardMCPServers = (fraimKey, githubToken) => ({
18
18
  args: ["-y", "@playwright/mcp"]
19
19
  },
20
20
  fraim: {
21
- command: "npx",
22
- args: ["-y", "fraim-mcp"],
21
+ command: "fraim-mcp",
23
22
  env: {
24
23
  FRAIM_API_KEY: fraimKey,
25
24
  FRAIM_REMOTE_URL: "https://fraim.wellnessatwork.me"
@@ -46,8 +45,7 @@ const generateClaudeMCPServers = (fraimKey, githubToken) => ({
46
45
  args: ["-y", "@playwright/mcp"]
47
46
  },
48
47
  fraim: {
49
- command: "npx",
50
- args: ["-y", "fraim-mcp"],
48
+ command: "fraim-mcp",
51
49
  env: {
52
50
  FRAIM_API_KEY: fraimKey,
53
51
  FRAIM_REMOTE_URL: "https://fraim.wellnessatwork.me"
@@ -73,8 +71,7 @@ const generateKiroMCPServers = (fraimKey, githubToken) => ({
73
71
  args: ["-y", "@playwright/mcp"]
74
72
  },
75
73
  fraim: {
76
- command: "npx",
77
- args: ["-y", "fraim-mcp"],
74
+ command: "fraim-mcp",
78
75
  env: {
79
76
  FRAIM_API_KEY: fraimKey,
80
77
  FRAIM_REMOTE_URL: "https://fraim.wellnessatwork.me"
@@ -97,8 +94,7 @@ command = "npx"
97
94
  args = ["-y", "@playwright/mcp"]
98
95
 
99
96
  [mcp_servers.fraim]
100
- command = "npx"
101
- args = ["-y", "fraim-mcp"]
97
+ command = "fraim-mcp"
102
98
 
103
99
  [mcp_servers.fraim.env]
104
100
  FRAIM_API_KEY = "${fraimKey}"
@@ -123,8 +119,7 @@ const generateWindsurfMCPServers = (fraimKey, githubToken) => ({
123
119
  args: ["-y", "@playwright/mcp"]
124
120
  },
125
121
  fraim: {
126
- command: "npx",
127
- args: ["-y", "fraim-mcp"],
122
+ command: "fraim-mcp",
128
123
  env: {
129
124
  FRAIM_API_KEY: fraimKey,
130
125
  FRAIM_REMOTE_URL: "https://fraim.wellnessatwork.me"
@@ -0,0 +1,304 @@
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;
@@ -0,0 +1,63 @@
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;
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+ /**
3
+ * GitHub Issue Tracking Provider
4
+ *
5
+ * Implements the IssueTrackingProvider interface for GitHub Issues
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.GitHubIssueProvider = void 0;
12
+ const axios_1 = __importDefault(require("axios"));
13
+ class GitHubIssueProvider {
14
+ constructor(config) {
15
+ this.config = config;
16
+ this.baseUrl = `https://api.github.com/repos/${config.owner}/${config.repo}`;
17
+ }
18
+ async createIssue(params) {
19
+ const { title, body, labels, assignee, dryRun } = params;
20
+ const payload = {
21
+ title,
22
+ body
23
+ };
24
+ if (labels && labels.length > 0) {
25
+ payload.labels = labels;
26
+ }
27
+ if (assignee) {
28
+ payload.assignee = assignee;
29
+ }
30
+ if (dryRun) {
31
+ return {
32
+ success: true,
33
+ dryRun: true,
34
+ provider: 'github',
35
+ message: `[DRY RUN] Would create GitHub issue: "${title}" in ${this.config.owner}/${this.config.repo}`
36
+ };
37
+ }
38
+ try {
39
+ const response = await axios_1.default.post(`${this.baseUrl}/issues`, payload, {
40
+ headers: {
41
+ 'Authorization': `Bearer ${this.config.token}`,
42
+ 'Accept': 'application/vnd.github.v3+json',
43
+ 'Content-Type': 'application/json',
44
+ },
45
+ });
46
+ return {
47
+ success: true,
48
+ issueNumber: response.data.number,
49
+ issueId: response.data.number,
50
+ htmlUrl: response.data.html_url,
51
+ provider: 'github'
52
+ };
53
+ }
54
+ catch (error) {
55
+ return {
56
+ success: false,
57
+ provider: 'github',
58
+ message: this.formatError(error)
59
+ };
60
+ }
61
+ }
62
+ async getIssue(issueId) {
63
+ try {
64
+ const response = await axios_1.default.get(`${this.baseUrl}/issues/${issueId}`, {
65
+ headers: {
66
+ 'Authorization': `Bearer ${this.config.token}`,
67
+ 'Accept': 'application/vnd.github.v3+json',
68
+ },
69
+ });
70
+ const issue = response.data;
71
+ return {
72
+ id: issue.number,
73
+ title: issue.title,
74
+ body: issue.body || '',
75
+ state: issue.state === 'open' ? 'open' : 'closed',
76
+ assignee: issue.assignee?.login,
77
+ labels: issue.labels.map((label) => label.name),
78
+ createdAt: new Date(issue.created_at),
79
+ updatedAt: new Date(issue.updated_at),
80
+ htmlUrl: issue.html_url
81
+ };
82
+ }
83
+ catch (error) {
84
+ if (error.response?.status === 404) {
85
+ return null;
86
+ }
87
+ throw new Error(this.formatError(error));
88
+ }
89
+ }
90
+ async updateIssue(issueId, updates) {
91
+ const payload = {};
92
+ if (updates.title)
93
+ payload.title = updates.title;
94
+ if (updates.body)
95
+ payload.body = updates.body;
96
+ if (updates.labels)
97
+ payload.labels = updates.labels;
98
+ if (updates.assignee)
99
+ payload.assignee = updates.assignee;
100
+ if (updates.dryRun) {
101
+ return {
102
+ success: true,
103
+ dryRun: true,
104
+ provider: 'github',
105
+ message: `[DRY RUN] Would update GitHub issue #${issueId} in ${this.config.owner}/${this.config.repo}`
106
+ };
107
+ }
108
+ try {
109
+ const response = await axios_1.default.patch(`${this.baseUrl}/issues/${issueId}`, payload, {
110
+ headers: {
111
+ 'Authorization': `Bearer ${this.config.token}`,
112
+ 'Accept': 'application/vnd.github.v3+json',
113
+ 'Content-Type': 'application/json',
114
+ },
115
+ });
116
+ return {
117
+ success: true,
118
+ issueNumber: response.data.number,
119
+ issueId: response.data.number,
120
+ htmlUrl: response.data.html_url,
121
+ provider: 'github'
122
+ };
123
+ }
124
+ catch (error) {
125
+ return {
126
+ success: false,
127
+ provider: 'github',
128
+ message: this.formatError(error)
129
+ };
130
+ }
131
+ }
132
+ async listIssues(filters) {
133
+ const params = {
134
+ per_page: filters?.limit || 30
135
+ };
136
+ if (filters?.state && filters.state !== 'all') {
137
+ params.state = filters.state;
138
+ }
139
+ if (filters?.assignee) {
140
+ params.assignee = filters.assignee;
141
+ }
142
+ if (filters?.labels && filters.labels.length > 0) {
143
+ params.labels = filters.labels.join(',');
144
+ }
145
+ try {
146
+ const response = await axios_1.default.get(`${this.baseUrl}/issues`, {
147
+ headers: {
148
+ 'Authorization': `Bearer ${this.config.token}`,
149
+ 'Accept': 'application/vnd.github.v3+json',
150
+ },
151
+ params
152
+ });
153
+ return response.data.map((issue) => ({
154
+ id: issue.number,
155
+ title: issue.title,
156
+ body: issue.body || '',
157
+ state: issue.state === 'open' ? 'open' : 'closed',
158
+ assignee: issue.assignee?.login,
159
+ labels: issue.labels.map((label) => label.name),
160
+ createdAt: new Date(issue.created_at),
161
+ updatedAt: new Date(issue.updated_at),
162
+ htmlUrl: issue.html_url
163
+ }));
164
+ }
165
+ catch (error) {
166
+ throw new Error(this.formatError(error));
167
+ }
168
+ }
169
+ async validateConfig() {
170
+ try {
171
+ // Test by getting repository info
172
+ const response = await axios_1.default.get(this.baseUrl, {
173
+ headers: {
174
+ 'Authorization': `Bearer ${this.config.token}`,
175
+ 'Accept': 'application/vnd.github.v3+json',
176
+ },
177
+ });
178
+ return {
179
+ valid: true,
180
+ message: `Connected to ${response.data.full_name}`
181
+ };
182
+ }
183
+ catch (error) {
184
+ return {
185
+ valid: false,
186
+ message: this.formatError(error)
187
+ };
188
+ }
189
+ }
190
+ formatError(error) {
191
+ if (axios_1.default.isAxiosError(error)) {
192
+ return `GitHub API Error: ${error.response?.status} - ${JSON.stringify(error.response?.data)}`;
193
+ }
194
+ else if (error instanceof Error) {
195
+ return `GitHub Error: ${error.message}`;
196
+ }
197
+ return 'Unknown GitHub error';
198
+ }
199
+ }
200
+ exports.GitHubIssueProvider = GitHubIssueProvider;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * Issue Tracking System Types
4
+ *
5
+ * Defines the common interface for all issue tracking systems (GitHub, ADO, etc.)
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ /**
3
+ * Issue Tracking Configuration Helper
4
+ *
5
+ * Provides guidance on which MCP tools to use based on configuration.
6
+ * This replaces custom providers with MCP tool recommendations.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.getIssueTrackingGuidance = getIssueTrackingGuidance;
10
+ exports.getRepositoryInfo = getRepositoryInfo;
11
+ const config_loader_1 = require("./config-loader");
12
+ /**
13
+ * Get guidance on which MCP tools to use for issue tracking
14
+ */
15
+ function getIssueTrackingGuidance() {
16
+ const config = (0, config_loader_1.loadFraimConfig)();
17
+ // Check for ADO configuration
18
+ if (config.repository?.provider === 'ado') {
19
+ return {
20
+ provider: 'ado',
21
+ mcpTools: {
22
+ createIssue: 'mcp_ado_create_work_item',
23
+ getIssue: 'mcp_ado_get_work_item',
24
+ updateIssue: 'mcp_ado_update_work_item',
25
+ listIssues: 'mcp_ado_list_work_items',
26
+ createPR: 'mcp_ado_create_pull_request',
27
+ getPR: 'mcp_ado_get_pull_request',
28
+ mergePR: 'mcp_ado_merge_pull_request'
29
+ },
30
+ config: {
31
+ organization: config.repository.organization,
32
+ project: config.repository.project
33
+ },
34
+ environmentVars: ['ADO_TOKEN', 'AZURE_DEVOPS_TOKEN'],
35
+ setupInstructions: `Configure ADO MCP server in your IDE with:
36
+ - Organization: ${config.repository.organization}
37
+ - Project: ${config.repository.project}
38
+ - Token: Set ADO_TOKEN or AZURE_DEVOPS_TOKEN environment variable`
39
+ };
40
+ }
41
+ // Default to GitHub
42
+ const owner = config.repository?.owner || config.git?.repoOwner || 'mathursrus';
43
+ const repo = config.repository?.name || config.git?.repoName || 'FRAIM';
44
+ return {
45
+ provider: 'github',
46
+ mcpTools: {
47
+ createIssue: 'mcp_github_issue_write',
48
+ getIssue: 'mcp_github_issue_read',
49
+ updateIssue: 'mcp_github_issue_write',
50
+ listIssues: 'mcp_github_list_issues',
51
+ createPR: 'mcp_github_create_pull_request',
52
+ getPR: 'mcp_github_pull_request_read',
53
+ mergePR: 'mcp_github_merge_pull_request'
54
+ },
55
+ config: {
56
+ owner,
57
+ repo
58
+ },
59
+ environmentVars: ['GITHUB_TOKEN'],
60
+ setupInstructions: `GitHub MCP tools are configured for:
61
+ - Repository: ${owner}/${repo}
62
+ - Token: Set GITHUB_TOKEN environment variable
63
+ - MCP Server: Ensure GitHub MCP server is configured in your IDE`
64
+ };
65
+ }
66
+ /**
67
+ * Get repository information from .fraim/config.json
68
+ */
69
+ function getRepositoryInfo() {
70
+ try {
71
+ const config = (0, config_loader_1.loadFraimConfig)();
72
+ const owner = config.repository?.owner || config.git?.repoOwner;
73
+ const repo = config.repository?.name || config.git?.repoName;
74
+ return {
75
+ owner,
76
+ repo,
77
+ url: owner && repo ? `https://github.com/${owner}/${repo}.git` : undefined
78
+ };
79
+ }
80
+ catch (e) {
81
+ return {};
82
+ }
83
+ }
@@ -38,9 +38,25 @@ class FraimLocalMCPServer {
38
38
  logError(message) {
39
39
  console.error(`[FRAIM ERROR] ${message}`);
40
40
  }
41
+ findProjectRoot() {
42
+ // Start from cwd and search upwards for .fraim directory
43
+ let currentDir = process.cwd();
44
+ const root = (0, path_1.parse)(currentDir).root;
45
+ while (currentDir !== root) {
46
+ const fraimDir = (0, path_1.join)(currentDir, '.fraim');
47
+ if ((0, fs_1.existsSync)(fraimDir)) {
48
+ return currentDir;
49
+ }
50
+ currentDir = (0, path_1.dirname)(currentDir);
51
+ }
52
+ return null;
53
+ }
41
54
  loadConfig() {
42
55
  try {
43
- const configPath = (0, path_1.join)(process.cwd(), '.fraim', 'config.json');
56
+ // Try to find project root by searching for .fraim directory
57
+ const projectDir = this.findProjectRoot() || process.cwd();
58
+ const configPath = (0, path_1.join)(projectDir, '.fraim', 'config.json');
59
+ this.log(`🔍 Looking for config at: ${configPath}`);
44
60
  if ((0, fs_1.existsSync)(configPath)) {
45
61
  const configContent = (0, fs_1.readFileSync)(configPath, 'utf8');
46
62
  this.config = JSON.parse(configContent);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim-framework",
3
- "version": "2.0.57",
3
+ "version": "2.0.59",
4
4
  "description": "FRAIM v2: Framework for Rigor-based AI Management - Transform from solo developer to AI manager orchestrating production-ready code with enterprise-grade discipline",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -8,7 +8,7 @@
8
8
  "fraim-mcp": "./bin/fraim-mcp.js"
9
9
  },
10
10
  "scripts": {
11
- "dev": "tsx --watch src/fraim-mcp-server.ts",
11
+ "dev": "tsx --watch src/fraim-mcp-server.ts > server.log 2>&1",
12
12
  "build": "tsc && node scripts/copy-ai-manager-rules.js && npm run build:stubs && npm run validate:registry",
13
13
  "build:stubs": "tsx scripts/build-stub-registry.ts",
14
14
  "test": "node scripts/test-with-server.js",
@@ -1,101 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validateAllWorkflows = validateAllWorkflows;
4
- const fs_1 = require("fs");
5
- const path_1 = require("path");
6
- const workflow_parser_js_1 = require("./workflow-parser.js");
7
- /**
8
- * Validate all workflows in the registry
9
- */
10
- function validateAllWorkflows(registryPath) {
11
- const errors = [];
12
- const workflowFiles = [];
13
- function findWorkflows(dir) {
14
- if (!(0, fs_1.existsSync)(dir))
15
- return;
16
- const entries = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
17
- for (const entry of entries) {
18
- const fullPath = (0, path_1.join)(dir, entry.name);
19
- if (entry.isDirectory()) {
20
- findWorkflows(fullPath);
21
- }
22
- else if (entry.isFile() && entry.name.endsWith('.md')) {
23
- const content = (0, fs_1.readFileSync)(fullPath, 'utf-8');
24
- if (content.includes('"initialPhase"')) {
25
- workflowFiles.push(fullPath);
26
- }
27
- }
28
- }
29
- }
30
- findWorkflows(registryPath);
31
- console.log(`🔍 Validating ${workflowFiles.length} workflows...`);
32
- for (const filePath of workflowFiles) {
33
- const relativePath = filePath.replace(registryPath, '');
34
- try {
35
- const wf = workflow_parser_js_1.WorkflowParser.parse(filePath);
36
- if (!wf) {
37
- errors.push(`❌ ${relativePath}: Failed to parse. Ensure JSON metadata is present and valid.`);
38
- continue;
39
- }
40
- const { metadata, phases } = wf;
41
- // 1. Initial Phase check
42
- if (!phases.has(metadata.initialPhase)) {
43
- errors.push(`❌ ${relativePath}: initialPhase "${metadata.initialPhase}" does not exist in phases.`);
44
- }
45
- // 2. Validate Phase Flow
46
- for (const [phaseId, flow] of Object.entries(metadata.phases)) {
47
- // Check if phase exists in markdown
48
- if (!phases.has(phaseId)) {
49
- errors.push(`❌ ${relativePath}: Phase "${phaseId}" defined in metadata but missing in markdown.`);
50
- }
51
- // Check Success transition
52
- if (flow.onSuccess && flow.onSuccess !== 'null') {
53
- if (typeof flow.onSuccess === 'string') {
54
- if (!phases.has(flow.onSuccess)) {
55
- errors.push(`❌ ${relativePath}: Phase "${phaseId}" transition onSuccess -> "${flow.onSuccess}" target not found.`);
56
- }
57
- }
58
- else {
59
- // Validate each branch of conditional success
60
- for (const [branch, target] of Object.entries(flow.onSuccess)) {
61
- if (target && target !== 'null' && !phases.has(target)) {
62
- errors.push(`❌ ${relativePath}: Phase "${phaseId}" conditional transition onSuccess[${branch}] -> "${target}" target not found.`);
63
- }
64
- }
65
- }
66
- }
67
- // Check Failure transition
68
- if (flow.onFailure && !phases.has(flow.onFailure)) {
69
- errors.push(`❌ ${relativePath}: Phase "${phaseId}" transition onFailure -> "${flow.onFailure}" target not found.`);
70
- }
71
- }
72
- // 3. Check for orphaned phases in markdown
73
- for (const phaseId of phases.keys()) {
74
- if (!metadata.phases[phaseId]) {
75
- errors.push(`⚠️ ${relativePath}: Phase "${phaseId}" exists in markdown but is NOT defined in JSON metadata.`);
76
- }
77
- }
78
- }
79
- catch (e) {
80
- errors.push(`❌ ${relativePath}: Unexpected error: ${e.message}`);
81
- }
82
- }
83
- return {
84
- valid: errors.filter(e => e.startsWith('❌')).length === 0,
85
- errors
86
- };
87
- }
88
- // Run if called directly
89
- if (process.argv[1]?.includes('validate-workflows')) {
90
- const registryPath = process.argv[2] || (0, path_1.join)(process.cwd(), 'registry');
91
- const result = validateAllWorkflows(registryPath);
92
- result.errors.forEach(err => console.error(err));
93
- if (result.valid) {
94
- console.log('✅ All workflows validated successfully.');
95
- process.exit(0);
96
- }
97
- else {
98
- console.error('❌ Workflow validation failed.');
99
- process.exit(1);
100
- }
101
- }