fraim-framework 2.0.27 → 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.
Files changed (98) hide show
  1. package/.github/workflows/deploy-fraim.yml +1 -1
  2. package/dist/registry/scripts/build-scripts-generator.js +205 -0
  3. package/dist/registry/scripts/cleanup-branch.js +258 -0
  4. package/dist/registry/scripts/evaluate-code-quality.js +66 -0
  5. package/dist/registry/scripts/exec-with-timeout.js +142 -0
  6. package/dist/registry/scripts/fraim-config.js +61 -0
  7. package/dist/registry/scripts/generate-engagement-emails.js +630 -0
  8. package/dist/registry/scripts/generic-issues-api.js +100 -0
  9. package/dist/registry/scripts/newsletter-helpers.js +731 -0
  10. package/dist/registry/scripts/openapi-generator.js +664 -0
  11. package/dist/registry/scripts/performance/profile-server.js +390 -0
  12. package/dist/registry/scripts/run-thank-you-workflow.js +92 -0
  13. package/dist/registry/scripts/send-newsletter-simple.js +85 -0
  14. package/dist/registry/scripts/send-thank-you-emails.js +54 -0
  15. package/dist/registry/scripts/validate-openapi-limits.js +311 -0
  16. package/dist/registry/scripts/validate-test-coverage.js +262 -0
  17. package/dist/registry/scripts/verify-test-coverage.js +66 -0
  18. package/dist/src/cli/commands/init.js +14 -14
  19. package/dist/src/cli/commands/sync.js +4 -4
  20. package/dist/src/cli/fraim.js +24 -22
  21. package/dist/src/cli/setup/first-run.js +13 -6
  22. package/dist/src/fraim/db-service.js +26 -15
  23. package/dist/src/fraim/issues.js +67 -0
  24. package/dist/src/fraim-mcp-server.js +272 -18
  25. package/dist/src/utils/git-utils.js +1 -1
  26. package/dist/tests/debug-tools.js +79 -0
  27. package/dist/tests/esm-compat.js +11 -0
  28. package/dist/tests/test-chalk-esm-issue.js +159 -0
  29. package/dist/tests/test-chalk-real-world.js +265 -0
  30. package/dist/tests/test-chalk-regression.js +327 -0
  31. package/dist/tests/test-chalk-resolution-issue.js +304 -0
  32. package/dist/tests/test-fraim-install-chalk-issue.js +254 -0
  33. package/dist/tests/test-fraim-issues.js +59 -0
  34. package/dist/tests/test-genericization.js +1 -1
  35. package/dist/tests/test-mcp-connection.js +166 -0
  36. package/dist/tests/test-mcp-issue-integration.js +144 -0
  37. package/dist/tests/test-mcp-lifecycle-methods.js +312 -0
  38. package/dist/tests/test-node-compatibility.js +71 -0
  39. package/dist/tests/test-npm-install.js +66 -0
  40. package/dist/tests/test-npm-resolution-diagnostic.js +140 -0
  41. package/dist/tests/test-session-rehydration.js +145 -0
  42. package/dist/tests/test-standalone.js +2 -8
  43. package/dist/tests/test-telemetry.js +190 -0
  44. package/package.json +10 -8
  45. package/registry/agent-guardrails.md +62 -54
  46. package/registry/rules/agent-success-criteria.md +52 -0
  47. package/registry/rules/agent-testing-guidelines.md +502 -502
  48. package/registry/rules/communication.md +121 -121
  49. package/registry/rules/continuous-learning.md +54 -54
  50. package/registry/rules/ephemeral-execution.md +10 -5
  51. package/registry/rules/hitl-ppe-record-analysis.md +302 -302
  52. package/registry/rules/local-development.md +251 -251
  53. package/registry/rules/software-development-lifecycle.md +104 -104
  54. package/registry/rules/successful-debugging-patterns.md +482 -478
  55. package/registry/rules/telemetry.md +67 -0
  56. package/registry/scripts/build-scripts-generator.ts +216 -215
  57. package/registry/scripts/cleanup-branch.ts +303 -284
  58. package/registry/scripts/code-quality-check.sh +559 -559
  59. package/registry/scripts/detect-tautological-tests.sh +38 -38
  60. package/registry/scripts/evaluate-code-quality.ts +1 -1
  61. package/registry/scripts/generate-engagement-emails.ts +744 -744
  62. package/registry/scripts/generic-issues-api.ts +110 -150
  63. package/registry/scripts/newsletter-helpers.ts +874 -874
  64. package/registry/scripts/openapi-generator.ts +695 -693
  65. package/registry/scripts/performance/profile-server.ts +5 -3
  66. package/registry/scripts/prep-issue.sh +468 -455
  67. package/registry/scripts/validate-openapi-limits.ts +366 -365
  68. package/registry/scripts/validate-test-coverage.ts +280 -280
  69. package/registry/scripts/verify-pr-comments.sh +70 -70
  70. package/registry/scripts/verify-test-coverage.ts +1 -1
  71. package/registry/templates/bootstrap/ARCHITECTURE-TEMPLATE.md +53 -53
  72. package/registry/templates/evidence/Implementation-BugEvidence.md +85 -85
  73. package/registry/templates/evidence/Implementation-FeatureEvidence.md +120 -120
  74. package/registry/templates/marketing/HBR-ARTICLE-TEMPLATE.md +66 -0
  75. package/registry/workflows/bootstrap/create-architecture.md +2 -2
  76. package/registry/workflows/bootstrap/evaluate-code-quality.md +3 -3
  77. package/registry/workflows/bootstrap/verify-test-coverage.md +2 -2
  78. package/registry/workflows/customer-development/insight-analysis.md +156 -156
  79. package/registry/workflows/customer-development/interview-preparation.md +421 -421
  80. package/registry/workflows/customer-development/strategic-brainstorming.md +146 -146
  81. package/registry/workflows/customer-development/thank-customers.md +193 -191
  82. package/registry/workflows/customer-development/weekly-newsletter.md +362 -352
  83. package/registry/workflows/improve-fraim/contribute.md +32 -0
  84. package/registry/workflows/improve-fraim/file-issue.md +32 -0
  85. package/registry/workflows/marketing/hbr-article.md +73 -0
  86. package/registry/workflows/performance/analyze-performance.md +63 -59
  87. package/registry/workflows/product-building/design.md +3 -2
  88. package/registry/workflows/product-building/implement.md +4 -3
  89. package/registry/workflows/product-building/prep-issue.md +28 -17
  90. package/registry/workflows/product-building/resolve.md +3 -2
  91. package/registry/workflows/product-building/retrospect.md +3 -2
  92. package/registry/workflows/product-building/spec.md +5 -4
  93. package/registry/workflows/product-building/test.md +3 -2
  94. package/registry/workflows/quality-assurance/iterative-improvement-cycle.md +562 -562
  95. package/registry/workflows/replicate/website-discovery-analysis.md +3 -3
  96. package/registry/workflows/reviewer/review-implementation-vs-design-spec.md +632 -632
  97. package/registry/workflows/reviewer/review-implementation-vs-feature-spec.md +669 -669
  98. package/tsconfig.json +2 -1
@@ -0,0 +1,71 @@
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 test_utils_1 = require("./test-utils");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const os_1 = __importDefault(require("os"));
11
+ async function testInitOnNodeVersion(version) {
12
+ console.log(` 🧪 Testing Node v${version}...`);
13
+ // Create a temp directory for each version test
14
+ const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), `fraim-node-${version}-`));
15
+ try {
16
+ const platform = process.platform;
17
+ const npx = platform === 'win32' ? 'npx.cmd' : 'npx';
18
+ const binPath = path_1.default.resolve(__dirname, '../bin/fraim.js');
19
+ // Run: npx -y -p node@<version> node bin/fraim.js init
20
+ // We use -y to skip prompt, and -p node@version to ensure that specific node binary is used
21
+ const execNode = (args) => {
22
+ return new Promise((resolve) => {
23
+ const ps = (0, node_child_process_1.spawn)(npx, ['-y', '-p', `node@${version}`, 'node', `"${binPath}"`, ...args], {
24
+ cwd: tempDir,
25
+ env: { ...process.env, TEST_MODE: 'true' },
26
+ shell: true
27
+ });
28
+ let stdout = '';
29
+ let stderr = '';
30
+ ps.stdout.on('data', d => stdout += d.toString());
31
+ ps.stderr.on('data', d => stderr += d.toString());
32
+ ps.on('close', (code) => resolve({ stdout, stderr, code }));
33
+ });
34
+ };
35
+ console.log(` Running "fraim init" on Node ${version}...`);
36
+ const result = await execNode(['init']);
37
+ if (result.code !== 0) {
38
+ console.error(` ❌ Failed on Node ${version}`);
39
+ console.error(` Error: ${result.stderr}`);
40
+ return false;
41
+ }
42
+ const configPath = path_1.default.join(tempDir, '.fraim', 'config.json');
43
+ if (fs_1.default.existsSync(configPath)) {
44
+ console.log(` ✅ Success on Node ${version}`);
45
+ return true;
46
+ }
47
+ else {
48
+ console.error(` ❌ Config not found on Node ${version}`);
49
+ return false;
50
+ }
51
+ }
52
+ catch (e) {
53
+ console.error(` ❌ Test Logic Error on Node ${version}:`, e.message);
54
+ return false;
55
+ }
56
+ finally {
57
+ try {
58
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
59
+ }
60
+ catch (e) { }
61
+ }
62
+ }
63
+ const nodeVersions = ['16', '18', '20', '22'];
64
+ const testCases = nodeVersions.map(v => ({
65
+ name: `Node v${v} Compatibility`,
66
+ version: v,
67
+ tags: ['node', 'compatibility']
68
+ }));
69
+ (0, test_utils_1.runTests)(testCases, async (t) => await testInitOnNodeVersion(t.version), 'Node Compatibility Matrix')
70
+ .then(() => process.exit(0))
71
+ .catch(() => process.exit(1));
@@ -0,0 +1,66 @@
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 test_utils_1 = require("./test-utils");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const os_1 = __importDefault(require("os"));
11
+ async function testCleanNpmInstall(version) {
12
+ console.log(` 🧪 Testing NPM Install on Node v${version}...`);
13
+ // Create a temp directory for the install test
14
+ const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), `fraim-npm-${version}-`));
15
+ try {
16
+ const platform = process.platform;
17
+ const npx = platform === 'win32' ? 'npx.cmd' : 'npx';
18
+ // Copy package.json to temp dir
19
+ const packageJsonPath = path_1.default.resolve(__dirname, '../package.json');
20
+ fs_1.default.copyFileSync(packageJsonPath, path_1.default.join(tempDir, 'package.json'));
21
+ // Run: npx -p node@<version> npm install --package-lock-only
22
+ // This uses the npm bundled with that specific Node version
23
+ const execNpm = () => {
24
+ return new Promise((resolve) => {
25
+ const ps = (0, node_child_process_1.spawn)(npx, ['-y', '-p', `node@${version}`, 'npm', 'install', '--package-lock-only', '--quiet', '--no-audit'], {
26
+ cwd: tempDir,
27
+ env: { ...process.env },
28
+ shell: true
29
+ });
30
+ let stdout = '';
31
+ let stderr = '';
32
+ ps.stdout.on('data', d => stdout += d.toString());
33
+ ps.stderr.on('data', d => stderr += d.toString());
34
+ ps.on('close', (code) => resolve({ stdout, stderr, code }));
35
+ });
36
+ };
37
+ console.log(` Running "npm install" with Node ${version}...`);
38
+ const result = await execNpm();
39
+ if (result.code !== 0) {
40
+ console.error(` ❌ NPM Install Failed on Node ${version}`);
41
+ console.error(` Stderr: ${result.stderr}`);
42
+ return false;
43
+ }
44
+ console.log(` ✅ NPM Resolution Success on Node ${version}`);
45
+ return true;
46
+ }
47
+ catch (e) {
48
+ console.error(` ❌ Test Logic Error on Node ${version}:`, e.message);
49
+ return false;
50
+ }
51
+ finally {
52
+ try {
53
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
54
+ }
55
+ catch (e) { }
56
+ }
57
+ }
58
+ const nodeVersions = ['16', '18', '20', '22'];
59
+ const testCases = nodeVersions.map(v => ({
60
+ name: `NPM Compatibility (Node v${v})`,
61
+ version: v,
62
+ tags: ['npm', 'compatibility']
63
+ }));
64
+ (0, test_utils_1.runTests)(testCases, async (t) => await testCleanNpmInstall(t.version), 'NPM Configuration Matrix')
65
+ .then(() => process.exit(0))
66
+ .catch(() => process.exit(1));
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ /**
3
+ * Diagnostic test to understand npm's chalk resolution behavior
4
+ *
5
+ * This test will show us exactly what npm does when there's a version conflict
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
+ const node_child_process_1 = require("node:child_process");
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const os_1 = __importDefault(require("os"));
15
+ async function main() {
16
+ console.log('🔍 NPM Chalk Resolution Diagnostic\n');
17
+ const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'npm-diagnostic-'));
18
+ console.log(`📂 Temp dir: ${tempDir}\n`);
19
+ try {
20
+ // 1. Pack fraim-framework
21
+ console.log('📦 Packing fraim-framework...');
22
+ const projectRoot = process.cwd();
23
+ const packResult = (0, node_child_process_1.execSync)('npm pack', {
24
+ cwd: projectRoot,
25
+ encoding: 'utf-8'
26
+ });
27
+ const tarballName = packResult.trim().split('\n').pop()?.trim();
28
+ if (!tarballName) {
29
+ console.log('❌ Failed to pack');
30
+ return;
31
+ }
32
+ const tarballPath = path_1.default.join(projectRoot, tarballName);
33
+ console.log(`✅ Created: ${tarballName}\n`);
34
+ // 2. Create test project with chalk v5
35
+ console.log('📝 Creating test project with chalk v5 dependency...');
36
+ const packageJson = {
37
+ name: 'diagnostic-test',
38
+ version: '1.0.0',
39
+ dependencies: {
40
+ 'chalk': '^5.0.0',
41
+ 'fraim-framework': `file:${tarballPath}`
42
+ }
43
+ };
44
+ fs_1.default.writeFileSync(path_1.default.join(tempDir, 'package.json'), JSON.stringify(packageJson, null, 2));
45
+ // 3. Install
46
+ console.log('📥 Running npm install...\n');
47
+ try {
48
+ const installOutput = (0, node_child_process_1.execSync)('npm install', {
49
+ cwd: tempDir,
50
+ encoding: 'utf-8',
51
+ stdio: 'pipe'
52
+ });
53
+ console.log('✅ Install completed\n');
54
+ }
55
+ catch (error) {
56
+ console.log('⚠️ Install had issues:\n', error.stdout || error.message);
57
+ }
58
+ // 4. Check what was installed
59
+ console.log('📋 Checking installed chalk versions...\n');
60
+ // Root chalk
61
+ const rootChalkPath = path_1.default.join(tempDir, 'node_modules', 'chalk', 'package.json');
62
+ if (fs_1.default.existsSync(rootChalkPath)) {
63
+ const pkg = JSON.parse(fs_1.default.readFileSync(rootChalkPath, 'utf-8'));
64
+ console.log(` Root chalk: ${pkg.version}`);
65
+ console.log(` Type: ${pkg.type || 'commonjs'}`);
66
+ console.log(` Main: ${pkg.main || 'N/A'}`);
67
+ console.log(` Exports: ${pkg.exports ? 'Yes' : 'No'}\n`);
68
+ }
69
+ else {
70
+ console.log(' ❌ No root chalk found\n');
71
+ }
72
+ // Fraim's chalk
73
+ const fraimChalkPath = path_1.default.join(tempDir, 'node_modules', 'fraim-framework', 'node_modules', 'chalk', 'package.json');
74
+ if (fs_1.default.existsSync(fraimChalkPath)) {
75
+ const pkg = JSON.parse(fs_1.default.readFileSync(fraimChalkPath, 'utf-8'));
76
+ console.log(` fraim-framework's chalk: ${pkg.version}`);
77
+ console.log(` Type: ${pkg.type || 'commonjs'}`);
78
+ console.log(` Main: ${pkg.main || 'N/A'}\n`);
79
+ }
80
+ else {
81
+ console.log(' ℹ️ fraim-framework has no separate chalk (using root)\n');
82
+ }
83
+ // 5. Run npm ls to see the dependency tree
84
+ console.log('🌳 Dependency tree (npm ls chalk):\n');
85
+ try {
86
+ const lsOutput = (0, node_child_process_1.execSync)('npm ls chalk', {
87
+ cwd: tempDir,
88
+ encoding: 'utf-8'
89
+ });
90
+ console.log(lsOutput);
91
+ }
92
+ catch (error) {
93
+ console.log(error.stdout || error.message);
94
+ }
95
+ // 6. Check fraim-framework's package.json in node_modules
96
+ console.log('\n📋 fraim-framework\'s chalk dependency in node_modules:\n');
97
+ const fraimPkgPath = path_1.default.join(tempDir, 'node_modules', 'fraim-framework', 'package.json');
98
+ if (fs_1.default.existsSync(fraimPkgPath)) {
99
+ const fraimPkg = JSON.parse(fs_1.default.readFileSync(fraimPkgPath, 'utf-8'));
100
+ console.log(` Chalk version in package.json: ${fraimPkg.dependencies?.chalk || 'N/A'}\n`);
101
+ }
102
+ // 7. Try to load fraim CLI
103
+ console.log('🚀 Testing fraim CLI load...\n');
104
+ const testScript = `
105
+ try {
106
+ const fraimCli = require('./node_modules/fraim-framework/dist/src/cli/fraim.js');
107
+ console.log('✅ SUCCESS: fraim CLI loaded');
108
+ } catch (error) {
109
+ console.log('❌ ERROR:', error.code || 'Unknown');
110
+ console.log('Message:', error.message.substring(0, 200));
111
+ }
112
+ `;
113
+ fs_1.default.writeFileSync(path_1.default.join(tempDir, 'test.js'), testScript);
114
+ try {
115
+ const testOutput = (0, node_child_process_1.execSync)('node test.js', {
116
+ cwd: tempDir,
117
+ encoding: 'utf-8'
118
+ });
119
+ console.log(testOutput);
120
+ }
121
+ catch (error) {
122
+ console.log(error.stdout || error.message);
123
+ }
124
+ // Cleanup
125
+ fs_1.default.unlinkSync(tarballPath);
126
+ console.log('\n✅ Diagnostic complete');
127
+ }
128
+ catch (error) {
129
+ console.error('\n❌ Diagnostic failed:', error);
130
+ }
131
+ finally {
132
+ try {
133
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
134
+ }
135
+ catch (e) {
136
+ console.log('\n⚠️ Could not clean up temp directory');
137
+ }
138
+ }
139
+ }
140
+ main();
@@ -0,0 +1,145 @@
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 node_assert_1 = __importDefault(require("node:assert"));
11
+ const tree_kill_1 = __importDefault(require("tree-kill"));
12
+ const path_1 = __importDefault(require("path"));
13
+ async function testRehydrationAndExemptions() {
14
+ console.log(' 🚀 Testing Session Re-hydration and Initialization Exemptions...');
15
+ let fraimProcess;
16
+ let dbService;
17
+ const PORT = Math.floor(Math.random() * 1000) + 12000;
18
+ const TEST_API_KEY = 'test-rehydration-key';
19
+ const TEST_ADMIN_KEY = 'test-admin-key';
20
+ const BASE_URL = `http://localhost:${PORT}`;
21
+ const startServer = async () => {
22
+ console.log(` Starting server on port ${PORT}...`);
23
+ const npxCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
24
+ const serverScript = path_1.default.resolve(__dirname, '../src/fraim-mcp-server.ts');
25
+ const proc = (0, node_child_process_1.spawn)(npxCommand, ['tsx', `"${serverScript}"`], {
26
+ env: {
27
+ ...process.env,
28
+ FRAIM_MCP_PORT: PORT.toString(),
29
+ FRAIM_ADMIN_KEY: TEST_ADMIN_KEY,
30
+ FRAIM_SKIP_INDEX_ON_START: 'true'
31
+ },
32
+ stdio: 'inherit',
33
+ shell: true
34
+ });
35
+ let started = false;
36
+ for (let i = 0; i < 15; i++) {
37
+ try {
38
+ await axios_1.default.get(`${BASE_URL}/health`, { timeout: 500 });
39
+ started = true;
40
+ break;
41
+ }
42
+ catch (e) {
43
+ await new Promise(resolve => setTimeout(resolve, 1000));
44
+ }
45
+ }
46
+ if (!started)
47
+ throw new Error('Server failed to start');
48
+ return proc;
49
+ };
50
+ const stopServer = async (proc) => {
51
+ if (proc && proc.pid) {
52
+ await new Promise(resolve => (0, tree_kill_1.default)(proc.pid, 'SIGKILL', () => resolve()));
53
+ }
54
+ };
55
+ try {
56
+ // 1. Setup DB and Key
57
+ dbService = new db_service_js_1.FraimDbService();
58
+ await dbService.connect();
59
+ const db = dbService.db;
60
+ await db.collection('fraim_api_keys').deleteOne({ key: TEST_API_KEY });
61
+ await db.collection('fraim_telemetry_sessions').deleteMany({ userId: 'test-user-recalc' });
62
+ await db.collection('fraim_api_keys').insertOne({
63
+ key: TEST_API_KEY,
64
+ userId: 'test-user-recalc',
65
+ orgId: 'test-org',
66
+ isActive: true,
67
+ createdAt: new Date()
68
+ });
69
+ // 2. Start Server (Instance 1)
70
+ fraimProcess = await startServer();
71
+ // 3. Verify Initialization Exemptions
72
+ console.log(' Testing standard MCP exemptions (initialize, tools/list)...');
73
+ const initRes = await axios_1.default.post(`${BASE_URL}/mcp`, {
74
+ jsonrpc: '2.0', id: 1, method: 'initialize', params: {}
75
+ }, { headers: { 'x-api-key': TEST_API_KEY } });
76
+ node_assert_1.default.strictEqual(initRes.status, 200, 'initialize should be exempt');
77
+ const listRes = await axios_1.default.post(`${BASE_URL}/mcp`, {
78
+ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {}
79
+ }, { headers: { 'x-api-key': TEST_API_KEY } });
80
+ node_assert_1.default.strictEqual(listRes.status, 200, 'tools/list should be exempt');
81
+ console.log(' Testing discovery tools exemptions (get_fraim_init)...');
82
+ const discoveryRes = await axios_1.default.post(`${BASE_URL}/mcp`, {
83
+ jsonrpc: '2.0', id: 3, method: 'tools/call',
84
+ params: { name: 'get_fraim_init', arguments: {} }
85
+ }, { headers: { 'x-api-key': TEST_API_KEY } });
86
+ node_assert_1.default.strictEqual(discoveryRes.status, 200, 'get_fraim_init should be exempt');
87
+ console.log(' ✅ Exemptions verified.');
88
+ // 4. Start Session
89
+ console.log(' Starting session...');
90
+ await axios_1.default.post(`${BASE_URL}/mcp`, {
91
+ jsonrpc: '2.0', id: 4, method: 'tools/call',
92
+ params: {
93
+ name: 'fraim_connect',
94
+ arguments: {
95
+ machine: { hostname: 'repro-host' },
96
+ repo: { url: 'http://github.com/repro' }
97
+ }
98
+ }
99
+ }, { headers: { 'x-api-key': TEST_API_KEY } });
100
+ // 5. Restart Server (Instance 2)
101
+ console.log(' ♻️ Swapping server instances (Simulating restart)...');
102
+ await stopServer(fraimProcess);
103
+ fraimProcess = await startServer();
104
+ // 6. Verify Re-hydration
105
+ console.log(' Checking if session re-hydrated from DB...');
106
+ const secureRes = await axios_1.default.post(`${BASE_URL}/mcp`, {
107
+ jsonrpc: '2.0', id: 5, method: 'tools/call',
108
+ params: { name: 'get_fraim_workflow', arguments: { workflow: 'test' } }
109
+ }, { headers: { 'x-api-key': TEST_API_KEY } });
110
+ node_assert_1.default.strictEqual(secureRes.status, 200, 'Request should be authorized via re-hydrated session');
111
+ console.log(' ✅ Re-hydration successful!');
112
+ return true;
113
+ }
114
+ catch (error) {
115
+ console.error(' ❌ Test failed:', error.message);
116
+ if (error.response?.data) {
117
+ console.error(' 🔍 Server Error Detail:', JSON.stringify(error.response.data, null, 2));
118
+ }
119
+ return false;
120
+ }
121
+ finally {
122
+ if (dbService) {
123
+ const db = dbService.db;
124
+ await db.collection('fraim_api_keys').deleteOne({ key: TEST_API_KEY }).catch(() => { });
125
+ await db.collection('fraim_telemetry_sessions').deleteMany({ userId: 'test-user-recalc' }).catch(() => { });
126
+ await dbService.close();
127
+ }
128
+ if (fraimProcess)
129
+ await stopServer(fraimProcess);
130
+ }
131
+ }
132
+ const testCases = [
133
+ {
134
+ name: 'MCP Session Re-hydration and Initialization',
135
+ description: 'Verifies re-hydration from DB after restart and initialization exemptions',
136
+ testFunction: testRehydrationAndExemptions,
137
+ tags: ['session', 'rehydration']
138
+ }
139
+ ];
140
+ (0, test_utils_1.runTests)(testCases, async (t) => t.testFunction(), 'Fraim Session System')
141
+ .then(() => process.exit(0))
142
+ .catch((err) => {
143
+ console.error('Test runner failed:', err);
144
+ process.exit(1);
145
+ });
@@ -98,13 +98,7 @@ async function testServerStartsAndResponds() {
98
98
  });
99
99
  node_assert_1.default.strictEqual(mcpResponse.status, 200);
100
100
  node_assert_1.default.ok(mcpResponse.data.result.tools.length > 0);
101
- // 7. Verify usage logging
102
- console.log(' Verifying usage logging...');
103
- await new Promise(resolve => setTimeout(resolve, 1500));
104
- const log = await db.collection('fraim_usage_logs').findOne({ keyId: TEST_API_KEY });
105
- node_assert_1.default.ok(log, 'Usage log should have been created');
106
- node_assert_1.default.strictEqual(log.userId, 'test-user@ashley.ai');
107
- // 8. Test Admin API - List Keys
101
+ // 7. Test Admin API - List Keys
108
102
  console.log(' Testing Admin API - List Keys...');
109
103
  const listKeysRes = await axios_1.default.get(`${BASE_URL}/admin/keys`, {
110
104
  headers: TEST_ADMIN_HEADER,
@@ -141,7 +135,7 @@ async function runFraimTest(testCase) {
141
135
  const testCases = [
142
136
  {
143
137
  name: 'Fraim Standalone Server Integration',
144
- description: 'Tests server startup, public health check, authenticated MCP access, usage logging, and admin API',
138
+ description: 'Tests server startup, public health check, authenticated MCP access, and admin API',
145
139
  testFunction: testServerStartsAndResponds,
146
140
  tags: ['smoke', 'fraim']
147
141
  }
@@ -0,0 +1,190 @@
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 node_assert_1 = __importDefault(require("node:assert"));
11
+ const tree_kill_1 = __importDefault(require("tree-kill"));
12
+ const path_1 = __importDefault(require("path"));
13
+ async function testTelemetryFlow() {
14
+ console.log(' 🚀 Testing Fraim Telemetry System...');
15
+ let fraimProcess;
16
+ let dbService;
17
+ const PORT = Math.floor(Math.random() * 1000) + 11000; // Use random port to avoid zombie conflicts
18
+ console.log(` 🎲 Selected random port: ${PORT}`);
19
+ const TEST_API_KEY = 'test-fraim-key-telemetry';
20
+ const TEST_ADMIN_KEY = 'test-admin-key-telemetry';
21
+ const BASE_URL = `http://localhost:${PORT}`;
22
+ const DB_CHECK_DELAY = 1000;
23
+ try {
24
+ // 1. Setup DB and Key
25
+ dbService = new db_service_js_1.FraimDbService();
26
+ await dbService.connect();
27
+ const db = dbService.db;
28
+ // Clean previous test data
29
+ await db.collection('fraim_api_keys').deleteOne({ key: TEST_API_KEY });
30
+ await db.collection('fraim_telemetry_sessions').deleteMany({ userId: 'test-user-telemetry' });
31
+ // Insert Test Key
32
+ await db.collection('fraim_api_keys').insertOne({
33
+ key: TEST_API_KEY,
34
+ userId: 'test-user-telemetry',
35
+ orgId: 'test-org',
36
+ isActive: true,
37
+ createdAt: new Date()
38
+ });
39
+ // 2. Start Server
40
+ console.log(` Starting server on port ${PORT}...`);
41
+ const npxCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
42
+ const serverScript = path_1.default.resolve(__dirname, '../src/fraim-mcp-server.ts');
43
+ fraimProcess = (0, node_child_process_1.spawn)(npxCommand, ['tsx', `"${serverScript}"`], {
44
+ env: {
45
+ ...process.env,
46
+ FRAIM_MCP_PORT: PORT.toString(),
47
+ FRAIM_ADMIN_KEY: TEST_ADMIN_KEY,
48
+ FRAIM_SKIP_INDEX_ON_START: 'true',
49
+ FRAIM_TELEMETRY_FLUSH_INTERVAL: '200' // Short interval for testing
50
+ },
51
+ stdio: 'inherit',
52
+ shell: true
53
+ });
54
+ // Wait for start
55
+ let started = false;
56
+ for (let i = 0; i < 15; i++) {
57
+ try {
58
+ await axios_1.default.get(`${BASE_URL}/health`, { timeout: 500 });
59
+ started = true;
60
+ break;
61
+ }
62
+ catch (e) {
63
+ await new Promise(resolve => setTimeout(resolve, 1000));
64
+ }
65
+ }
66
+ if (!started)
67
+ throw new Error('Server failed to start');
68
+ console.log(' Server started!');
69
+ // 3. Test Enforcement: Call tool WITHOUT handshake -> Expect 400
70
+ console.log(' Testing enforcement (No Session)...');
71
+ try {
72
+ const res = await axios_1.default.post(`${BASE_URL}/mcp`, {
73
+ jsonrpc: '2.0', id: 1, method: 'tools/call',
74
+ params: { name: 'get_fraim_workflow', arguments: { workflow: 'spec' } }
75
+ }, { headers: { 'x-api-key': TEST_API_KEY }, timeout: 2000 });
76
+ console.error(' ❌ Should have failed with 400 (Session Not Started)');
77
+ console.error(' 🔍 Unexpected Success Response:', JSON.stringify(res.data, null, 2));
78
+ return false;
79
+ }
80
+ catch (error) {
81
+ if (error.response) {
82
+ node_assert_1.default.strictEqual(error.response.status, 400);
83
+ node_assert_1.default.match(error.response.data?.error?.message, /Session Not Started/);
84
+ console.log(' ✅ Enforcement working: Request blocked.');
85
+ }
86
+ else {
87
+ throw error; // Network error?
88
+ }
89
+ }
90
+ // 4. Test Handshake: Call fraim_connect -> Expect 200 & DB Record
91
+ console.log(' Testing fraim_connect...');
92
+ const connectRes = await axios_1.default.post(`${BASE_URL}/mcp`, {
93
+ jsonrpc: '2.0', id: 2, method: 'tools/call',
94
+ params: {
95
+ name: 'fraim_connect',
96
+ arguments: {
97
+ machine: { hostname: 'test-host', platform: 'test-os' },
98
+ repo: { url: 'http://github.com/test/repo' }
99
+ }
100
+ }
101
+ }, { headers: { 'x-api-key': TEST_API_KEY }, timeout: 2000 });
102
+ node_assert_1.default.strictEqual(connectRes.status, 200);
103
+ const connectResult = connectRes.data.result;
104
+ const connectedMsg = connectResult.content.find((c) => c.text.includes('Connected!'));
105
+ node_assert_1.default.ok(connectedMsg, 'Should find Connected! message in response content');
106
+ // Verify Session ID returned in payload
107
+ node_assert_1.default.ok(connectResult.sessionId, 'Should return sessionId in response');
108
+ console.log(` ✅ Handshake successful. Session ID: ${connectResult.sessionId}`);
109
+ // Verify DB Record Created
110
+ await new Promise(r => setTimeout(r, DB_CHECK_DELAY));
111
+ const session = await db.collection('fraim_telemetry_sessions').findOne({ userId: 'test-user-telemetry' });
112
+ node_assert_1.default.ok(session, 'Session should exist in DB');
113
+ node_assert_1.default.strictEqual(session.machine.hostname, 'test-host');
114
+ const initialLastActive = session.lastActive.getTime();
115
+ console.log(` ✅ DB Session verified. Initial LastActive: ${initialLastActive}`);
116
+ // 5. Test Implicit Flush (Write-Behind)
117
+ // Wait > 200ms (flush interval)
118
+ console.log(' Waiting 1s to exceed flush interval...');
119
+ await new Promise(r => setTimeout(r, 1000));
120
+ // Call a tool (Authorized) - Should trigger flush
121
+ await axios_1.default.post(`${BASE_URL}/mcp`, {
122
+ jsonrpc: '2.0', id: 3, method: 'tools/call',
123
+ params: { name: 'get_fraim_workflow', arguments: { workflow: 'test' } }
124
+ }, { headers: { 'x-api-key': TEST_API_KEY }, timeout: 2000 });
125
+ console.log(' ✅ Tool call successful. Should trigger flush.');
126
+ // Give DB a moment to update
127
+ await new Promise(r => setTimeout(r, DB_CHECK_DELAY));
128
+ const sessionUpdated = await db.collection('fraim_telemetry_sessions').findOne({ userId: 'test-user-telemetry' });
129
+ const updatedLastActive = sessionUpdated.lastActive.getTime();
130
+ console.log(` Updated LastActive: ${updatedLastActive}`);
131
+ node_assert_1.default.ok(updatedLastActive > initialLastActive, 'Implicit flush should have updated the DB timestamp');
132
+ console.log(' ✅ Implicit flush verified.');
133
+ // 6. Test Shutdown Flush
134
+ console.log(' Testing Shutdown Flush...');
135
+ // Trigger ONE MORE activity, but don't wait for implicit flush
136
+ await new Promise(r => setTimeout(r, 10)); // Ensure at least 10ms diff
137
+ await axios_1.default.post(`${BASE_URL}/mcp`, {
138
+ jsonrpc: '2.0', id: 4, method: 'tools/call',
139
+ params: { name: 'get_fraim_workflow', arguments: { workflow: 'test-2' } }
140
+ }, { headers: { 'x-api-key': TEST_API_KEY }, timeout: 2000 });
141
+ console.log(' ✅ Late activity triggered (Pending Flush).');
142
+ // Kill the server with SIGTERM to trigger graceful shutdown
143
+ // Kill the server with SIGTERM to trigger graceful shutdown
144
+ // Use tree-kill to ensure we reach the actual node process through the npx/shell wrapper
145
+ if (fraimProcess && fraimProcess.pid) {
146
+ await new Promise(resolve => (0, tree_kill_1.default)(fraimProcess.pid, 'SIGTERM', () => resolve()));
147
+ }
148
+ // Wait for process to exit and flush
149
+ await new Promise(resolve => setTimeout(resolve, 3000));
150
+ const sessionFinal = await db.collection('fraim_telemetry_sessions').findOne({ userId: 'test-user-telemetry' });
151
+ const finalLastActive = sessionFinal.lastActive.getTime();
152
+ console.log(` Final LastActive: ${finalLastActive}`);
153
+ node_assert_1.default.ok(finalLastActive > updatedLastActive, 'Final Flush should have updated the DB timestamp with the pending activity');
154
+ console.log(' ✅ Shutdown flush verified.');
155
+ return true;
156
+ }
157
+ catch (error) {
158
+ console.error(' ❌ Test failed:', error.message);
159
+ if (error.response?.data) {
160
+ console.error(' 🔍 Server Error Detail:', JSON.stringify(error.response.data, null, 2));
161
+ }
162
+ return false;
163
+ }
164
+ finally {
165
+ if (dbService) {
166
+ const db = dbService.db;
167
+ await db.collection('fraim_api_keys').deleteOne({ key: TEST_API_KEY }).catch(() => { });
168
+ await db.collection('fraim_telemetry_sessions').deleteMany({ userId: 'test-user-telemetry' }).catch(() => { });
169
+ await dbService.close();
170
+ }
171
+ if (fraimProcess && fraimProcess.pid) {
172
+ console.log(' 🔫 Cleanup: Killing server process tree:', fraimProcess.pid);
173
+ await new Promise(resolve => (0, tree_kill_1.default)(fraimProcess.pid, 'SIGKILL', () => resolve()));
174
+ }
175
+ }
176
+ }
177
+ const testCases = [
178
+ {
179
+ name: 'Fraim Telemetry & Session Management',
180
+ description: 'Tests handshake enforcement, session creation, implicit tracking, and write-behind caching',
181
+ testFunction: testTelemetryFlow,
182
+ tags: ['telemetry']
183
+ }
184
+ ];
185
+ (0, test_utils_1.runTests)(testCases, async (t) => t.testFunction(), 'Fraim Telemetry System')
186
+ .then(() => process.exit(0))
187
+ .catch((err) => {
188
+ console.error('Test runner failed:', err);
189
+ process.exit(1);
190
+ });