fraim-framework 2.0.26 โ 2.0.30
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/.github/workflows/deploy-fraim.yml +1 -1
- package/dist/registry/scripts/build-scripts-generator.js +205 -0
- package/dist/registry/scripts/cleanup-branch.js +258 -0
- package/dist/registry/scripts/evaluate-code-quality.js +66 -0
- package/dist/registry/scripts/exec-with-timeout.js +142 -0
- package/dist/registry/scripts/fraim-config.js +61 -0
- package/dist/registry/scripts/generate-engagement-emails.js +630 -0
- package/dist/registry/scripts/generic-issues-api.js +100 -0
- package/dist/registry/scripts/newsletter-helpers.js +731 -0
- package/dist/registry/scripts/openapi-generator.js +664 -0
- package/dist/registry/scripts/performance/profile-server.js +390 -0
- package/dist/registry/scripts/run-thank-you-workflow.js +92 -0
- package/dist/registry/scripts/send-newsletter-simple.js +85 -0
- package/dist/registry/scripts/send-thank-you-emails.js +54 -0
- package/dist/registry/scripts/validate-openapi-limits.js +311 -0
- package/dist/registry/scripts/validate-test-coverage.js +262 -0
- package/dist/registry/scripts/verify-test-coverage.js +66 -0
- package/dist/src/cli/commands/init.js +14 -12
- package/dist/src/cli/commands/sync.js +19 -2
- package/dist/src/cli/fraim.js +24 -22
- package/dist/src/cli/setup/first-run.js +13 -6
- package/dist/src/fraim/config-loader.js +0 -8
- package/dist/src/fraim/db-service.js +26 -15
- package/dist/src/fraim/issues.js +67 -0
- package/dist/src/fraim/setup-wizard.js +1 -69
- package/dist/src/fraim/types.js +0 -11
- package/dist/src/fraim-mcp-server.js +272 -18
- package/dist/src/utils/git-utils.js +1 -1
- package/dist/src/utils/version-utils.js +32 -0
- package/dist/tests/debug-tools.js +79 -0
- package/dist/tests/esm-compat.js +11 -0
- package/dist/tests/test-chalk-esm-issue.js +159 -0
- package/dist/tests/test-chalk-real-world.js +265 -0
- package/dist/tests/test-chalk-regression.js +327 -0
- package/dist/tests/test-chalk-resolution-issue.js +304 -0
- package/dist/tests/test-cli.js +0 -2
- package/dist/tests/test-fraim-install-chalk-issue.js +254 -0
- package/dist/tests/test-fraim-issues.js +59 -0
- package/dist/tests/test-genericization.js +1 -3
- package/dist/tests/test-mcp-connection.js +166 -0
- package/dist/tests/test-mcp-issue-integration.js +144 -0
- package/dist/tests/test-mcp-lifecycle-methods.js +312 -0
- package/dist/tests/test-node-compatibility.js +71 -0
- package/dist/tests/test-npm-install.js +66 -0
- package/dist/tests/test-npm-resolution-diagnostic.js +140 -0
- package/dist/tests/test-session-rehydration.js +145 -0
- package/dist/tests/test-standalone.js +2 -8
- package/dist/tests/test-sync-version-update.js +93 -0
- package/dist/tests/test-telemetry.js +190 -0
- package/package.json +10 -8
- package/registry/agent-guardrails.md +62 -54
- package/registry/rules/agent-success-criteria.md +52 -0
- package/registry/rules/agent-testing-guidelines.md +502 -502
- package/registry/rules/communication.md +121 -121
- package/registry/rules/continuous-learning.md +54 -54
- package/registry/rules/ephemeral-execution.md +10 -5
- package/registry/rules/hitl-ppe-record-analysis.md +302 -302
- package/registry/rules/local-development.md +251 -251
- package/registry/rules/software-development-lifecycle.md +104 -104
- package/registry/rules/successful-debugging-patterns.md +482 -478
- package/registry/rules/telemetry.md +67 -0
- package/registry/scripts/build-scripts-generator.ts +216 -215
- package/registry/scripts/cleanup-branch.ts +303 -284
- package/registry/scripts/code-quality-check.sh +559 -559
- package/registry/scripts/detect-tautological-tests.sh +38 -38
- package/registry/scripts/evaluate-code-quality.ts +1 -1
- package/registry/scripts/generate-engagement-emails.ts +744 -744
- package/registry/scripts/generic-issues-api.ts +110 -150
- package/registry/scripts/newsletter-helpers.ts +874 -874
- package/registry/scripts/openapi-generator.ts +695 -693
- package/registry/scripts/performance/profile-server.ts +5 -3
- package/registry/scripts/prep-issue.sh +468 -455
- package/registry/scripts/validate-openapi-limits.ts +366 -365
- package/registry/scripts/validate-test-coverage.ts +280 -280
- package/registry/scripts/verify-pr-comments.sh +70 -70
- package/registry/scripts/verify-test-coverage.ts +1 -1
- package/registry/templates/bootstrap/ARCHITECTURE-TEMPLATE.md +53 -53
- package/registry/templates/evidence/Implementation-BugEvidence.md +85 -85
- package/registry/templates/evidence/Implementation-FeatureEvidence.md +120 -120
- package/registry/templates/marketing/HBR-ARTICLE-TEMPLATE.md +66 -0
- package/registry/workflows/bootstrap/create-architecture.md +2 -2
- package/registry/workflows/bootstrap/evaluate-code-quality.md +3 -3
- package/registry/workflows/bootstrap/verify-test-coverage.md +2 -2
- package/registry/workflows/customer-development/insight-analysis.md +156 -156
- package/registry/workflows/customer-development/interview-preparation.md +421 -421
- package/registry/workflows/customer-development/strategic-brainstorming.md +146 -146
- package/registry/workflows/customer-development/thank-customers.md +193 -191
- package/registry/workflows/customer-development/weekly-newsletter.md +362 -352
- package/registry/workflows/improve-fraim/contribute.md +32 -0
- package/registry/workflows/improve-fraim/file-issue.md +32 -0
- package/registry/workflows/marketing/hbr-article.md +73 -0
- package/registry/workflows/performance/analyze-performance.md +63 -59
- package/registry/workflows/product-building/design.md +3 -2
- package/registry/workflows/product-building/implement.md +4 -3
- package/registry/workflows/product-building/prep-issue.md +28 -17
- package/registry/workflows/product-building/resolve.md +3 -2
- package/registry/workflows/product-building/retrospect.md +3 -2
- package/registry/workflows/product-building/spec.md +5 -4
- package/registry/workflows/product-building/test.md +3 -2
- package/registry/workflows/quality-assurance/iterative-improvement-cycle.md +562 -562
- package/registry/workflows/replicate/website-discovery-analysis.md +3 -3
- package/registry/workflows/reviewer/review-implementation-vs-design-spec.md +632 -632
- package/registry/workflows/reviewer/review-implementation-vs-feature-spec.md +669 -669
- package/tsconfig.json +2 -1
|
@@ -45,6 +45,8 @@ const path_1 = require("path");
|
|
|
45
45
|
const git_utils_1 = require("./utils/git-utils");
|
|
46
46
|
const config_loader_1 = require("./fraim/config-loader");
|
|
47
47
|
const db_service_1 = require("./fraim/db-service");
|
|
48
|
+
const issues_1 = require("./fraim/issues");
|
|
49
|
+
const crypto_1 = require("crypto");
|
|
48
50
|
const dotenv = __importStar(require("dotenv"));
|
|
49
51
|
// Load environment variables
|
|
50
52
|
dotenv.config();
|
|
@@ -62,17 +64,82 @@ console.log('๐ FRAIM MCP Server starting up...');
|
|
|
62
64
|
console.log(`๐ Working Directory: ${process.cwd()}`);
|
|
63
65
|
console.log(`๐ Dirname: ${__dirname}`);
|
|
64
66
|
try {
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (fs.existsSync(distPath)) {
|
|
70
|
-
console.log('files in dist:', fs.readdirSync(distPath).join(', '));
|
|
67
|
+
console.log('files in current dir:', (0, fs_1.readdirSync)(process.cwd()).join(', '));
|
|
68
|
+
const distPath = (0, path_1.join)(process.cwd(), 'dist');
|
|
69
|
+
if ((0, fs_1.existsSync)(distPath)) {
|
|
70
|
+
console.log('files in dist:', (0, fs_1.readdirSync)(distPath).join(', '));
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
catch (e) {
|
|
74
74
|
console.warn('โ ๏ธ Could not log directory structure:', e.message);
|
|
75
75
|
}
|
|
76
|
+
class SessionManager {
|
|
77
|
+
constructor(dbService) {
|
|
78
|
+
this.sessions = new Map();
|
|
79
|
+
this.dbService = dbService;
|
|
80
|
+
// Default 1m, configurable for testing
|
|
81
|
+
this.FLUSH_INTERVAL_MS = process.env.FRAIM_TELEMETRY_FLUSH_INTERVAL
|
|
82
|
+
? parseInt(process.env.FRAIM_TELEMETRY_FLUSH_INTERVAL)
|
|
83
|
+
: 60 * 1000;
|
|
84
|
+
// Flush on shutdown
|
|
85
|
+
const cleanup = async () => {
|
|
86
|
+
console.log('๐ Flushing telemetry sessions before shutdown...');
|
|
87
|
+
// Race flush with a 2s timeout to guarantee exit
|
|
88
|
+
const timeout = new Promise(resolve => setTimeout(resolve, 2000));
|
|
89
|
+
await Promise.race([this.flushAll(), timeout]);
|
|
90
|
+
console.log('๐ Shutdown flush complete (or timed out). Exiting.');
|
|
91
|
+
process.exit(0);
|
|
92
|
+
};
|
|
93
|
+
process.on('SIGTERM', cleanup);
|
|
94
|
+
process.on('SIGINT', cleanup);
|
|
95
|
+
}
|
|
96
|
+
registerSession(apiKey, sessionId) {
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
this.sessions.set(apiKey, { sessionId, lastWrite: now, lastActive: now });
|
|
99
|
+
}
|
|
100
|
+
async updateActivity(apiKey) {
|
|
101
|
+
let session = this.sessions.get(apiKey);
|
|
102
|
+
const now = Date.now();
|
|
103
|
+
if (!session) {
|
|
104
|
+
// Re-hydration: Check DB for an active session for this API Key
|
|
105
|
+
try {
|
|
106
|
+
const dbSession = await this.dbService.getActiveSessionByApiKey(apiKey);
|
|
107
|
+
if (dbSession) {
|
|
108
|
+
console.log(`๐ Re-hydrating session for API Key: ${apiKey}`);
|
|
109
|
+
session = {
|
|
110
|
+
sessionId: dbSession.sessionId,
|
|
111
|
+
lastWrite: now,
|
|
112
|
+
lastActive: now
|
|
113
|
+
};
|
|
114
|
+
this.sessions.set(apiKey, session);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
console.error('โ ๏ธ Failed to re-hydrate session from DB:', e);
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
session.lastActive = now;
|
|
126
|
+
// Write-behind: Only flush to DB if > Interval elapsed
|
|
127
|
+
if (now - session.lastWrite > this.FLUSH_INTERVAL_MS) {
|
|
128
|
+
// Async write
|
|
129
|
+
this.dbService.updateSessionActivity(session.sessionId, new Date(now)).catch(e => console.error('โ ๏ธ Failed to flush session activity:', e));
|
|
130
|
+
session.lastWrite = now;
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
async flushAll() {
|
|
135
|
+
const promises = [];
|
|
136
|
+
for (const [_, data] of this.sessions) {
|
|
137
|
+
// Flush the memory lastActive time
|
|
138
|
+
promises.push(this.dbService.updateSessionActivity(data.sessionId, new Date(data.lastActive)));
|
|
139
|
+
}
|
|
140
|
+
await Promise.allSettled(promises);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
76
143
|
class FraimMCPServer {
|
|
77
144
|
constructor() {
|
|
78
145
|
this.fileIndex = new Map();
|
|
@@ -101,6 +168,7 @@ class FraimMCPServer {
|
|
|
101
168
|
}
|
|
102
169
|
// Initialize database service
|
|
103
170
|
this.dbService = new db_service_1.FraimDbService();
|
|
171
|
+
this.sessionManager = new SessionManager(this.dbService);
|
|
104
172
|
// Load FRAIM configuration
|
|
105
173
|
this.config = (0, config_loader_1.loadFraimConfig)();
|
|
106
174
|
// Find registry directory (check dist first for production, then source)
|
|
@@ -121,6 +189,7 @@ class FraimMCPServer {
|
|
|
121
189
|
});
|
|
122
190
|
});
|
|
123
191
|
this.app.use(this.authMiddleware.bind(this));
|
|
192
|
+
this.app.use(this.telemetryMiddleware.bind(this));
|
|
124
193
|
this.setupRoutes();
|
|
125
194
|
}
|
|
126
195
|
/**
|
|
@@ -139,16 +208,19 @@ class FraimMCPServer {
|
|
|
139
208
|
console.info(`${keyStr} ${method} ${path} ${status} ${duration}ms ${params}`);
|
|
140
209
|
}
|
|
141
210
|
// Log usage if authenticated
|
|
211
|
+
// DISABLE: Granular logging disabled in favor of Session Keep-Alive
|
|
212
|
+
/*
|
|
142
213
|
if (apiKey) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
214
|
+
this.dbService.logUsage({
|
|
215
|
+
keyId: apiKey.key,
|
|
216
|
+
userId: apiKey.userId,
|
|
217
|
+
method,
|
|
218
|
+
path,
|
|
219
|
+
status,
|
|
220
|
+
duration
|
|
221
|
+
}).catch(e => console.error('โ ๏ธ Failed to log usage:', (e as Error).message));
|
|
151
222
|
}
|
|
223
|
+
*/
|
|
152
224
|
});
|
|
153
225
|
next();
|
|
154
226
|
}
|
|
@@ -246,6 +318,60 @@ class FraimMCPServer {
|
|
|
246
318
|
}
|
|
247
319
|
return next();
|
|
248
320
|
}
|
|
321
|
+
/**
|
|
322
|
+
* Middleware to enforce Session Handshake and track activity
|
|
323
|
+
*/
|
|
324
|
+
async telemetryMiddleware(req, res, next) {
|
|
325
|
+
// Skip for non-authenticated or exempt paths
|
|
326
|
+
const exemptPaths = ['/health', '/admin', '/mcp'];
|
|
327
|
+
if (exemptPaths.some(p => req.path.startsWith(p)) && req.method === 'GET') {
|
|
328
|
+
return next();
|
|
329
|
+
}
|
|
330
|
+
// 1. Standard MCP Lifecycle & Discovery methods (Initialize, List Tools/Resources/Prompts, etc.)
|
|
331
|
+
const exemptMethods = [
|
|
332
|
+
'initialize',
|
|
333
|
+
'notifications/initialized',
|
|
334
|
+
'tools/list',
|
|
335
|
+
'resources/list',
|
|
336
|
+
'prompts/list',
|
|
337
|
+
'logging/setLevel'
|
|
338
|
+
];
|
|
339
|
+
if (req.body && exemptMethods.includes(req.body.method)) {
|
|
340
|
+
return next();
|
|
341
|
+
}
|
|
342
|
+
// 2. FRAIM Bootstrap/Discovery Tools that don't need an active session
|
|
343
|
+
if (req.body && req.body.method === 'tools/call') {
|
|
344
|
+
const toolName = req.body.params?.name;
|
|
345
|
+
const safeTools = [
|
|
346
|
+
'fraim_connect',
|
|
347
|
+
'get_fraim_init',
|
|
348
|
+
'list_fraim_workflows',
|
|
349
|
+
'fraim_get_local_config'
|
|
350
|
+
];
|
|
351
|
+
if (safeTools.includes(toolName))
|
|
352
|
+
return next();
|
|
353
|
+
}
|
|
354
|
+
const apiKey = req.apiKeyData?.key;
|
|
355
|
+
if (!apiKey)
|
|
356
|
+
return next(); // Auth middleware handles 401 earlier
|
|
357
|
+
// Check Session
|
|
358
|
+
const isActive = await this.sessionManager.updateActivity(apiKey);
|
|
359
|
+
if (!isActive) {
|
|
360
|
+
console.log(`โ Telemetry blocked request: ${req.method} ${req.path} (No Session for ${apiKey})`);
|
|
361
|
+
// Enforce Handshake
|
|
362
|
+
res.status(400).json({
|
|
363
|
+
jsonrpc: '2.0',
|
|
364
|
+
error: {
|
|
365
|
+
code: -32600,
|
|
366
|
+
message: "โ Session Not Started. Please call the 'fraim_connect' tool first to register your environment and begin the session."
|
|
367
|
+
},
|
|
368
|
+
id: req.body?.id || null
|
|
369
|
+
});
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
console.log(`โ
Telemetry active for ${apiKey}`);
|
|
373
|
+
return next();
|
|
374
|
+
}
|
|
249
375
|
/**
|
|
250
376
|
* Get the Express application for mounting in another server
|
|
251
377
|
* This allows FRAIM routes to be accessible on the main server port (e.g., on Azure)
|
|
@@ -584,8 +710,18 @@ class FraimMCPServer {
|
|
|
584
710
|
'Connection': 'keep-alive',
|
|
585
711
|
'Access-Control-Allow-Origin': '*'
|
|
586
712
|
});
|
|
587
|
-
|
|
588
|
-
res.
|
|
713
|
+
// Handle client disconnects to prevent server crash
|
|
714
|
+
res.on('error', (err) => {
|
|
715
|
+
console.error('โ ๏ธ SSE stream error:', err);
|
|
716
|
+
});
|
|
717
|
+
res.write('event: endpoint\n');
|
|
718
|
+
// Use absolute URL to be safe
|
|
719
|
+
const protocol = req.protocol;
|
|
720
|
+
const host = req.get('host');
|
|
721
|
+
res.write(`data: ${protocol}://${host}/mcp\n\n`);
|
|
722
|
+
// Remove confusing "connected" message that might interfere with protocol
|
|
723
|
+
// res.write(': FRAIM MCP Server\n\n');
|
|
724
|
+
// res.write('data: connected\n\n');
|
|
589
725
|
return;
|
|
590
726
|
}
|
|
591
727
|
// Regular GET request
|
|
@@ -631,8 +767,23 @@ class FraimMCPServer {
|
|
|
631
767
|
else if (method === 'tools/list') {
|
|
632
768
|
result = this.handleListTools();
|
|
633
769
|
}
|
|
770
|
+
else if (method === 'resources/list') {
|
|
771
|
+
result = this.handleListResources();
|
|
772
|
+
}
|
|
773
|
+
else if (method === 'prompts/list') {
|
|
774
|
+
result = this.handleListPrompts();
|
|
775
|
+
}
|
|
634
776
|
else if (method === 'tools/call') {
|
|
635
|
-
|
|
777
|
+
// Pass API Key context to handleToolCall
|
|
778
|
+
const context = {
|
|
779
|
+
apiKey: req.apiKeyData?.key,
|
|
780
|
+
userId: req.apiKeyData?.userId
|
|
781
|
+
};
|
|
782
|
+
result = await this.handleToolCall(params, context);
|
|
783
|
+
}
|
|
784
|
+
else if (method === 'notifications/initialized') {
|
|
785
|
+
// Client initialized notification - no response needed but must not error
|
|
786
|
+
return res.json({ jsonrpc: '2.0', id: id || null, result: true });
|
|
636
787
|
}
|
|
637
788
|
else {
|
|
638
789
|
throw new Error(`Unknown method: ${method}`);
|
|
@@ -699,6 +850,28 @@ Call this before fetching any workflow or file to understand the system.`,
|
|
|
699
850
|
required: []
|
|
700
851
|
}
|
|
701
852
|
},
|
|
853
|
+
{
|
|
854
|
+
name: 'fraim_connect',
|
|
855
|
+
description: `Handshake to start a FRAIM session. MUST be called at the start of every session.
|
|
856
|
+
Registers your environment (machine/repo) for telemetry and unlocks other tools.`,
|
|
857
|
+
inputSchema: {
|
|
858
|
+
type: 'object',
|
|
859
|
+
properties: {
|
|
860
|
+
machine: {
|
|
861
|
+
type: 'object',
|
|
862
|
+
description: 'Machine specs (hostname, platform, memory, cpus)',
|
|
863
|
+
additionalProperties: true
|
|
864
|
+
},
|
|
865
|
+
repo: {
|
|
866
|
+
type: 'object',
|
|
867
|
+
description: 'Repository context (owner, name, url)',
|
|
868
|
+
required: ['url'],
|
|
869
|
+
additionalProperties: true
|
|
870
|
+
}
|
|
871
|
+
},
|
|
872
|
+
required: ['machine', 'repo']
|
|
873
|
+
}
|
|
874
|
+
},
|
|
702
875
|
{
|
|
703
876
|
name: 'get_fraim_file',
|
|
704
877
|
description: `Get a specific file from FRAIM by path.
|
|
@@ -776,11 +949,51 @@ Use this to discover which workflow to call with get_fraim_workflow.`,
|
|
|
776
949
|
properties: {},
|
|
777
950
|
required: []
|
|
778
951
|
}
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
name: 'file_issue',
|
|
955
|
+
description: `Create a GitHub issue in the FRAIM repository.
|
|
956
|
+
|
|
957
|
+
Use this tool when you need to report a bug, request a feature, or track a task.
|
|
958
|
+
Supports dry-run mode to preview the operation.`,
|
|
959
|
+
inputSchema: {
|
|
960
|
+
type: 'object',
|
|
961
|
+
properties: {
|
|
962
|
+
title: {
|
|
963
|
+
type: 'string',
|
|
964
|
+
description: 'Title of the issue'
|
|
965
|
+
},
|
|
966
|
+
body: {
|
|
967
|
+
type: 'string',
|
|
968
|
+
description: 'Body/Content of the issue'
|
|
969
|
+
},
|
|
970
|
+
labels: {
|
|
971
|
+
type: 'array',
|
|
972
|
+
items: { type: 'string' },
|
|
973
|
+
description: 'List of labels to apply'
|
|
974
|
+
},
|
|
975
|
+
dryRun: {
|
|
976
|
+
type: 'boolean',
|
|
977
|
+
description: 'If true, simulates the creation without actually notifying GitHub'
|
|
978
|
+
}
|
|
979
|
+
},
|
|
980
|
+
required: ['title', 'body']
|
|
981
|
+
}
|
|
779
982
|
}
|
|
780
983
|
]
|
|
781
984
|
};
|
|
782
985
|
}
|
|
783
|
-
|
|
986
|
+
handleListResources() {
|
|
987
|
+
return {
|
|
988
|
+
resources: []
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
handleListPrompts() {
|
|
992
|
+
return {
|
|
993
|
+
prompts: []
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
async handleToolCall(params, context = {}) {
|
|
784
997
|
const { name: toolName, arguments: toolArgs } = params;
|
|
785
998
|
switch (toolName) {
|
|
786
999
|
case 'get_fraim_init':
|
|
@@ -793,6 +1006,16 @@ Use this to discover which workflow to call with get_fraim_workflow.`,
|
|
|
793
1006
|
return await this.handleListWorkflows();
|
|
794
1007
|
case 'fraim_get_local_config':
|
|
795
1008
|
return await this.handleGetLocalConfig();
|
|
1009
|
+
case 'file_issue':
|
|
1010
|
+
const result = await (0, issues_1.fileFraimIssue)(toolArgs);
|
|
1011
|
+
return {
|
|
1012
|
+
content: [{
|
|
1013
|
+
type: 'text',
|
|
1014
|
+
text: JSON.stringify(result, null, 2)
|
|
1015
|
+
}]
|
|
1016
|
+
};
|
|
1017
|
+
case 'fraim_connect':
|
|
1018
|
+
return await this.handleFraimConnect(toolArgs, context.apiKey, context.userId);
|
|
796
1019
|
default:
|
|
797
1020
|
throw new Error(`Unknown tool: ${toolName} `);
|
|
798
1021
|
}
|
|
@@ -1196,6 +1419,37 @@ If \`.fraim/config.json\` doesn't exist:
|
|
|
1196
1419
|
]
|
|
1197
1420
|
};
|
|
1198
1421
|
}
|
|
1422
|
+
async handleFraimConnect(args, apiKey, userId) {
|
|
1423
|
+
if (!apiKey) {
|
|
1424
|
+
throw new Error('No API Key found in context for fraim_connect');
|
|
1425
|
+
}
|
|
1426
|
+
// 1. Generate Session ID
|
|
1427
|
+
const sessionId = (0, crypto_1.randomUUID)();
|
|
1428
|
+
// userId passed from context
|
|
1429
|
+
const finalUserId = userId || 'unknown';
|
|
1430
|
+
// 2. Create Session in DB
|
|
1431
|
+
const session = {
|
|
1432
|
+
sessionId,
|
|
1433
|
+
userId: finalUserId,
|
|
1434
|
+
machine: args.machine || {},
|
|
1435
|
+
repo: args.repo || {},
|
|
1436
|
+
startTime: new Date(),
|
|
1437
|
+
lastActive: new Date()
|
|
1438
|
+
};
|
|
1439
|
+
const keyData = await this.dbService.verifyApiKey(apiKey);
|
|
1440
|
+
if (keyData)
|
|
1441
|
+
session.userId = keyData.userId;
|
|
1442
|
+
await this.dbService.createSession(session);
|
|
1443
|
+
// 3. Register in SessionManager
|
|
1444
|
+
this.sessionManager.registerSession(apiKey, sessionId);
|
|
1445
|
+
return {
|
|
1446
|
+
content: [{
|
|
1447
|
+
type: 'text',
|
|
1448
|
+
text: `โ
Connected! Session ID: ${sessionId}. Telemetry active.`
|
|
1449
|
+
}],
|
|
1450
|
+
sessionId: sessionId
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1199
1453
|
async start(port = 3002) {
|
|
1200
1454
|
try {
|
|
1201
1455
|
// Connect to database before starting server
|
|
@@ -40,7 +40,7 @@ function determineDatabaseName() {
|
|
|
40
40
|
catch (e) {
|
|
41
41
|
// Silently fail
|
|
42
42
|
}
|
|
43
|
-
return process.env.MONGODB_DB_NAME || 'fraim_dev';
|
|
43
|
+
return process.env.MONGODB_DB_NAME || (process.env.NODE_ENV === 'production' ? 'fraim_prod' : 'fraim_dev');
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* Gets the current git branch name
|
|
@@ -0,0 +1,32 @@
|
|
|
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.getFraimVersion = getFraimVersion;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function getFraimVersion() {
|
|
10
|
+
// Try reliable paths to find package.json relative to this file
|
|
11
|
+
// locally: src/utils/version-utils.ts -> package.json is ../../package.json
|
|
12
|
+
// dist: dist/src/utils/version-utils.js -> package.json is ../../../package.json
|
|
13
|
+
const possiblePaths = [
|
|
14
|
+
path_1.default.join(__dirname, '../../package.json'), // Local dev (src)
|
|
15
|
+
path_1.default.join(__dirname, '../../../package.json'), // Dist (dist/src)
|
|
16
|
+
path_1.default.join(process.cwd(), 'package.json') // Fallback to CWD (unlikely to be correct for global install but safe fallback)
|
|
17
|
+
];
|
|
18
|
+
for (const pkgPath of possiblePaths) {
|
|
19
|
+
if (fs_1.default.existsSync(pkgPath)) {
|
|
20
|
+
try {
|
|
21
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf-8'));
|
|
22
|
+
if (pkg.name === 'fraim-framework') {
|
|
23
|
+
return pkg.version;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
// Ignore parsing errors
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return '1.0.0'; // Fallback
|
|
32
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
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
|
+
const node_child_process_1 = require("node:child_process");
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const db_service_js_1 = require("../src/fraim/db-service.js");
|
|
9
|
+
const test_utils_1 = require("./test-utils");
|
|
10
|
+
const tree_kill_1 = __importDefault(require("tree-kill"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
async function debugListTools() {
|
|
13
|
+
console.log(' ๐ Debugging Available Tools...');
|
|
14
|
+
let fraimProcess;
|
|
15
|
+
let dbService;
|
|
16
|
+
const PORT = 10003;
|
|
17
|
+
const TEST_API_KEY = 'debug-tools-key';
|
|
18
|
+
const BASE_URL = `http://localhost:${PORT}`;
|
|
19
|
+
try {
|
|
20
|
+
// 1. Seed API key
|
|
21
|
+
dbService = new db_service_js_1.FraimDbService();
|
|
22
|
+
await dbService.connect();
|
|
23
|
+
const db = dbService.db;
|
|
24
|
+
await db.collection('fraim_api_keys').updateOne({ key: TEST_API_KEY }, { $set: { userId: 'debug@test.com', orgId: 'debug-org', isActive: true, createdAt: new Date() } }, { upsert: true });
|
|
25
|
+
// 2. Start server
|
|
26
|
+
const npxCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
27
|
+
const serverScript = path_1.default.resolve(__dirname, '../src/fraim-mcp-server.ts');
|
|
28
|
+
fraimProcess = (0, node_child_process_1.spawn)(npxCommand, ['tsx', `"${serverScript}"`], {
|
|
29
|
+
env: { ...process.env, FRAIM_MCP_PORT: PORT.toString(), FRAIM_SKIP_INDEX_ON_START: 'true' },
|
|
30
|
+
shell: true
|
|
31
|
+
});
|
|
32
|
+
// 3. Wait for start
|
|
33
|
+
let started = false;
|
|
34
|
+
for (let i = 0; i < 15; i++) {
|
|
35
|
+
try {
|
|
36
|
+
await axios_1.default.get(`${BASE_URL}/health`, { timeout: 1000 });
|
|
37
|
+
started = true;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!started)
|
|
45
|
+
return false;
|
|
46
|
+
// 4. Call tools/list
|
|
47
|
+
const response = await axios_1.default.post(`${BASE_URL}/mcp`, {
|
|
48
|
+
jsonrpc: '2.0',
|
|
49
|
+
id: 1,
|
|
50
|
+
method: 'tools/list',
|
|
51
|
+
params: {}
|
|
52
|
+
}, { headers: { 'x-api-key': TEST_API_KEY } });
|
|
53
|
+
console.log(' ๐ ๏ธ Tools found:');
|
|
54
|
+
response.data.result.tools.forEach((t) => {
|
|
55
|
+
console.log(` - ${t.name}: ${t.description.split('\n')[0]}`);
|
|
56
|
+
});
|
|
57
|
+
const hasFraimConnect = response.data.result.tools.some((t) => t.name === 'fraim_connect');
|
|
58
|
+
console.log(` โ Has fraim_connect: ${hasFraimConnect}`);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
console.error(e);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
if (dbService)
|
|
67
|
+
await dbService.close().catch(() => { });
|
|
68
|
+
if (fraimProcess && fraimProcess.pid)
|
|
69
|
+
(0, tree_kill_1.default)(fraimProcess.pid);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function runTest(testCase) {
|
|
73
|
+
return await testCase.testFunction();
|
|
74
|
+
}
|
|
75
|
+
(0, test_utils_1.runTests)([{
|
|
76
|
+
name: 'Debug Tools List',
|
|
77
|
+
testFunction: debugListTools,
|
|
78
|
+
tags: ['debug']
|
|
79
|
+
}], runTest, 'Debug Tools');
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ESM Compatibility Helper
|
|
4
|
+
* Provides __dirname and __filename for ESM modules
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.__dirname = exports.__filename = void 0;
|
|
8
|
+
const url_1 = require("url");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
exports.__filename = (0, url_1.fileURLToPath)(import.meta.url);
|
|
11
|
+
exports.__dirname = (0, path_1.dirname)(exports.__filename);
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Test to reproduce the ERR_REQUIRE_ESM issue with chalk
|
|
4
|
+
*
|
|
5
|
+
* This test simulates what happens when npm resolves chalk to v5 (ESM-only)
|
|
6
|
+
* while the code tries to use require() (CommonJS)
|
|
7
|
+
*/
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const node_child_process_1 = require("node:child_process");
|
|
13
|
+
const test_utils_1 = require("./test-utils");
|
|
14
|
+
const fs_1 = __importDefault(require("fs"));
|
|
15
|
+
const path_1 = __importDefault(require("path"));
|
|
16
|
+
const os_1 = __importDefault(require("os"));
|
|
17
|
+
async function testChalkESMIssue() {
|
|
18
|
+
console.log(' ๐งช Testing Chalk ESM Issue Reproduction...');
|
|
19
|
+
// Create a temp directory for the test
|
|
20
|
+
const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'chalk-esm-test-'));
|
|
21
|
+
console.log(` ๐ Created temp dir: ${tempDir}`);
|
|
22
|
+
try {
|
|
23
|
+
// 1. Create a minimal package.json that forces chalk v5
|
|
24
|
+
const packageJson = {
|
|
25
|
+
name: 'chalk-esm-test',
|
|
26
|
+
version: '1.0.0',
|
|
27
|
+
type: 'commonjs', // Using CommonJS
|
|
28
|
+
dependencies: {
|
|
29
|
+
'chalk': '^5.0.0' // Force chalk v5 (ESM-only)
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
fs_1.default.writeFileSync(path_1.default.join(tempDir, 'package.json'), JSON.stringify(packageJson, null, 2));
|
|
33
|
+
// 2. Create a test script that uses require('chalk')
|
|
34
|
+
const testScript = `
|
|
35
|
+
const chalk = require('chalk');
|
|
36
|
+
console.log(chalk.blue('This should fail with ERR_REQUIRE_ESM'));
|
|
37
|
+
`;
|
|
38
|
+
fs_1.default.writeFileSync(path_1.default.join(tempDir, 'test.js'), testScript);
|
|
39
|
+
// 3. Install dependencies
|
|
40
|
+
console.log(' ๐ฆ Installing chalk v5...');
|
|
41
|
+
const installResult = await new Promise((resolve) => {
|
|
42
|
+
const proc = (0, node_child_process_1.spawn)('npm', ['install', '--silent'], {
|
|
43
|
+
cwd: tempDir,
|
|
44
|
+
stdio: 'pipe',
|
|
45
|
+
shell: true
|
|
46
|
+
});
|
|
47
|
+
proc.on('close', (code) => {
|
|
48
|
+
resolve({ code });
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
if (installResult.code !== 0) {
|
|
52
|
+
console.log(' โ ๏ธ npm install failed (expected in some environments)');
|
|
53
|
+
return true; // Skip test if npm install fails
|
|
54
|
+
}
|
|
55
|
+
// 4. Try to run the script - should fail with ERR_REQUIRE_ESM
|
|
56
|
+
console.log(' ๐ฅ Attempting to require() chalk v5 (should fail)...');
|
|
57
|
+
const runResult = await new Promise((resolve) => {
|
|
58
|
+
const proc = (0, node_child_process_1.spawn)('node', ['test.js'], {
|
|
59
|
+
cwd: tempDir,
|
|
60
|
+
stdio: 'pipe',
|
|
61
|
+
shell: true
|
|
62
|
+
});
|
|
63
|
+
let stderr = '';
|
|
64
|
+
proc.stderr?.on('data', (data) => {
|
|
65
|
+
stderr += data.toString();
|
|
66
|
+
});
|
|
67
|
+
proc.on('close', (code) => {
|
|
68
|
+
resolve({ code, stderr });
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
// 5. Verify we got the ERR_REQUIRE_ESM error
|
|
72
|
+
console.log(' ๐ Checking for ERR_REQUIRE_ESM error...');
|
|
73
|
+
if (runResult.code === 0) {
|
|
74
|
+
console.log(' โ Script succeeded when it should have failed!');
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
if (!runResult.stderr.includes('ERR_REQUIRE_ESM')) {
|
|
78
|
+
console.log(' โ Got wrong error. Expected ERR_REQUIRE_ESM');
|
|
79
|
+
console.log(` Stderr: ${runResult.stderr.substring(0, 200)}`);
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
console.log(' โ
Successfully reproduced ERR_REQUIRE_ESM error!');
|
|
83
|
+
console.log(' โ
This confirms chalk v5 cannot be used with require()');
|
|
84
|
+
// 6. Now test with chalk v4 (should work)
|
|
85
|
+
console.log(' ๐ Testing with chalk v4 (should work)...');
|
|
86
|
+
const packageJsonV4 = {
|
|
87
|
+
name: 'chalk-esm-test',
|
|
88
|
+
version: '1.0.0',
|
|
89
|
+
type: 'commonjs',
|
|
90
|
+
dependencies: {
|
|
91
|
+
'chalk': '4.1.2' // Exact version 4.1.2
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
fs_1.default.writeFileSync(path_1.default.join(tempDir, 'package.json'), JSON.stringify(packageJsonV4, null, 2));
|
|
95
|
+
// Remove node_modules and reinstall
|
|
96
|
+
fs_1.default.rmSync(path_1.default.join(tempDir, 'node_modules'), { recursive: true, force: true });
|
|
97
|
+
const installV4Result = await new Promise((resolve) => {
|
|
98
|
+
const proc = (0, node_child_process_1.spawn)('npm', ['install', '--silent'], {
|
|
99
|
+
cwd: tempDir,
|
|
100
|
+
stdio: 'pipe',
|
|
101
|
+
shell: true
|
|
102
|
+
});
|
|
103
|
+
proc.on('close', (code) => {
|
|
104
|
+
resolve({ code });
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
if (installV4Result.code !== 0) {
|
|
108
|
+
console.log(' โ ๏ธ npm install v4 failed');
|
|
109
|
+
return true; // Skip this part
|
|
110
|
+
}
|
|
111
|
+
const runV4Result = await new Promise((resolve) => {
|
|
112
|
+
const proc = (0, node_child_process_1.spawn)('node', ['test.js'], {
|
|
113
|
+
cwd: tempDir,
|
|
114
|
+
stdio: 'pipe',
|
|
115
|
+
shell: true
|
|
116
|
+
});
|
|
117
|
+
let stdout = '';
|
|
118
|
+
proc.stdout?.on('data', (data) => {
|
|
119
|
+
stdout += data.toString();
|
|
120
|
+
});
|
|
121
|
+
proc.on('close', (code) => {
|
|
122
|
+
resolve({ code, stdout });
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
if (runV4Result.code !== 0) {
|
|
126
|
+
console.log(' โ Chalk v4 failed when it should have worked!');
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
console.log(' โ
Chalk v4 works correctly with require()');
|
|
130
|
+
console.log(' โ
Test confirms: pinning to 4.1.2 prevents the issue');
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
console.error(' โ Test failed with error:', error);
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
// Cleanup
|
|
139
|
+
try {
|
|
140
|
+
fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
141
|
+
console.log(' ๐งน Cleaned up temp directory');
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
console.log(' โ ๏ธ Could not clean up temp directory');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async function runChalkTest(testCase) {
|
|
149
|
+
return await testCase.testFunction();
|
|
150
|
+
}
|
|
151
|
+
const testCases = [
|
|
152
|
+
{
|
|
153
|
+
name: 'Chalk ESM Issue Reproduction',
|
|
154
|
+
description: 'Reproduces the ERR_REQUIRE_ESM error when chalk v5 is used with require()',
|
|
155
|
+
testFunction: testChalkESMIssue,
|
|
156
|
+
tags: ['chalk', 'esm', 'reproduction']
|
|
157
|
+
}
|
|
158
|
+
];
|
|
159
|
+
(0, test_utils_1.runTests)(testCases, runChalkTest, 'Chalk ESM Issue Test');
|