fraim-framework 2.0.24 → 2.0.26

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 (34) hide show
  1. package/.github/workflows/deploy-fraim.yml +3 -1
  2. package/dist/src/fraim/config-loader.js +19 -11
  3. package/dist/src/fraim/setup-wizard.js +28 -1
  4. package/dist/src/fraim/types.js +11 -0
  5. package/dist/src/fraim-mcp-server.js +22 -11
  6. package/dist/src/utils/git-utils.js +1 -1
  7. package/dist/tests/test-cli.js +169 -0
  8. package/dist/tests/test-first-run-journey.js +108 -0
  9. package/dist/tests/test-genericization.js +66 -0
  10. package/{test-prep-issue.ts → dist/tests/test-prep-issue.js} +93 -101
  11. package/{test-standalone.ts → dist/tests/test-standalone.js} +149 -161
  12. package/dist/tests/test-user-journey.js +231 -0
  13. package/dist/tests/test-utils.js +96 -0
  14. package/{test-wizard.ts → dist/tests/test-wizard.js} +71 -81
  15. package/package.json +9 -5
  16. package/registry/rules/architecture.md +1 -1
  17. package/registry/scripts/code-quality-check.sh +5 -4
  18. package/registry/scripts/evaluate-code-quality.ts +36 -0
  19. package/registry/scripts/{validate-coverage.ts → validate-test-coverage.ts} +39 -39
  20. package/registry/scripts/verify-test-coverage.ts +36 -0
  21. package/registry/templates/bootstrap/ARCHITECTURE-TEMPLATE.md +53 -0
  22. package/registry/templates/bootstrap/CODE-QUALITY-REPORT-TEMPLATE.md +37 -0
  23. package/registry/templates/bootstrap/TEST-COVERAGE-REPORT-TEMPLATE.md +35 -0
  24. package/registry/templates/business-development/PRICING-STRATEGY-TEMPLATE.md +126 -0
  25. package/registry/workflows/bootstrap/create-architecture.md +13 -12
  26. package/registry/workflows/bootstrap/evaluate-code-quality.md +30 -0
  27. package/registry/workflows/bootstrap/verify-test-coverage.md +31 -0
  28. package/registry/workflows/business-development/price-product.md +325 -0
  29. package/tsconfig.json +4 -4
  30. package/test-cli.ts +0 -155
  31. package/test-first-run-journey.ts +0 -122
  32. package/test-genericization.ts +0 -74
  33. package/test-user-journey.ts +0 -244
  34. package/test-utils.ts +0 -120
@@ -1,101 +1,93 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { spawnSync } from 'child_process';
4
- import assert from 'assert';
5
- import { BaseTestCase, runTests } from './test-utils';
6
-
7
- // Path to the shell script
8
- const SCRIPT_PATH = path.join(__dirname, 'registry/scripts/prep-issue.sh');
9
-
10
- function extractNodeScript(): string {
11
- const content = fs.readFileSync(SCRIPT_PATH, 'utf8');
12
- // Regex to capture the content inside NODE_SCRIPT="..."
13
- const match = content.match(/NODE_SCRIPT="([\s\S]*?)"/);
14
- if (!match || !match[1]) {
15
- throw new Error('Could not find NODE_SCRIPT variable in prep-issue.sh');
16
- }
17
-
18
- // Unescape characters that are escaped in Bash double-quoted string
19
- // specifically: \` \$ \" \\
20
- return match[1]
21
- .replace(/\\`/g, '`')
22
- .replace(/\\\$/g, '$')
23
- .replace(/\\"/g, '"')
24
- .replace(/\\\\/g, '\\');
25
- }
26
-
27
- function runParsingLogic(script: string, config: any): string {
28
- const input = JSON.stringify(config);
29
- const result = spawnSync('node', ['-e', script], {
30
- input,
31
- encoding: 'utf-8'
32
- });
33
-
34
- if (result.status !== 0) {
35
- throw new Error(`Script failed to run (status ${result.status}). Stderr: ${result.stderr}`);
36
- }
37
- return result.stdout.trim();
38
- }
39
-
40
- async function verifyPrepIssueConfigParsing(): Promise<boolean> {
41
- console.log(' 🔍 Verifying prep-issue.sh config parsing logic...');
42
-
43
- try {
44
- // 1. Verify script exists
45
- assert.ok(fs.existsSync(SCRIPT_PATH), `prep-issue.sh not found at ${SCRIPT_PATH}`);
46
-
47
- // 2. Extract the Node.js parsing logic
48
- const nodeScript = extractNodeScript();
49
- assert.ok(nodeScript.length > 0, 'Extracted script is empty');
50
- console.log(' ✅ Extracted Node.js parsing logic');
51
-
52
- // 3. Test Valid Configuration
53
- const validConfig = {
54
- repository: {
55
- owner: 'test-owner',
56
- name: 'test-repo',
57
- url: 'https://github.com/test-owner/test-repo.git'
58
- }
59
- };
60
- const output = runParsingLogic(nodeScript, validConfig);
61
- assert.strictEqual(output, 'test-owner:test-repo:https://github.com/test-owner/test-repo.git');
62
- console.log(' ✅ Valid config parsed correctly');
63
-
64
- // 4. Test Missing Config
65
- assert.throws(() => {
66
- runParsingLogic(nodeScript, { name: 'Create-Project' });
67
- }, /Script failed/);
68
- console.log(' ✅ Missing config correctly failed');
69
-
70
- // 5. Test Incomplete Config
71
- assert.throws(() => {
72
- runParsingLogic(nodeScript, { repository: { owner: 'test-owner' } });
73
- }, /Script failed/);
74
- console.log(' ✅ Incomplete config correctly failed');
75
-
76
- return true;
77
- } catch (error) {
78
- console.error(' ❌ Test failed:', error);
79
- return false;
80
- }
81
- }
82
-
83
- // Define test cases
84
- const testCases: BaseTestCase[] = [
85
- {
86
- name: 'Prep Issue Config Parsing',
87
- description: 'Verifies correct parsing of repo config from .fraim/config.json',
88
- testFn: verifyPrepIssueConfigParsing,
89
- tags: ['unit', 'scripts']
90
- }
91
- ];
92
-
93
- // Run strict check if executed directly
94
- if (require.main === module) {
95
- runTests(testCases, async (testCase) => {
96
- if (testCase.testFn) {
97
- return await testCase.testFn();
98
- }
99
- return false;
100
- }, 'Prep Issue Script Tests');
101
- }
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 fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const child_process_1 = require("child_process");
9
+ const assert_1 = __importDefault(require("assert"));
10
+ const test_utils_1 = require("./test-utils");
11
+ // Path to the shell script
12
+ const SCRIPT_PATH = path_1.default.join(__dirname, '../registry/scripts/prep-issue.sh');
13
+ function extractNodeScript() {
14
+ const content = fs_1.default.readFileSync(SCRIPT_PATH, 'utf8');
15
+ // Regex to capture the content inside NODE_SCRIPT="..."
16
+ const match = content.match(/NODE_SCRIPT="([\s\S]*?)"/);
17
+ if (!match || !match[1]) {
18
+ throw new Error('Could not find NODE_SCRIPT variable in prep-issue.sh');
19
+ }
20
+ // Unescape characters that are escaped in Bash double-quoted string
21
+ // specifically: \` \$ \" \\
22
+ return match[1]
23
+ .replace(/\\`/g, '`')
24
+ .replace(/\\\$/g, '$')
25
+ .replace(/\\"/g, '"')
26
+ .replace(/\\\\/g, '\\');
27
+ }
28
+ function runParsingLogic(script, config) {
29
+ const input = JSON.stringify(config);
30
+ const result = (0, child_process_1.spawnSync)('node', ['-e', script], {
31
+ input,
32
+ encoding: 'utf-8'
33
+ });
34
+ if (result.status !== 0) {
35
+ throw new Error(`Script failed to run (status ${result.status}). Stderr: ${result.stderr}`);
36
+ }
37
+ return result.stdout.trim();
38
+ }
39
+ async function verifyPrepIssueConfigParsing() {
40
+ console.log(' 🔍 Verifying prep-issue.sh config parsing logic...');
41
+ try {
42
+ // 1. Verify script exists
43
+ assert_1.default.ok(fs_1.default.existsSync(SCRIPT_PATH), `prep-issue.sh not found at ${SCRIPT_PATH}`);
44
+ // 2. Extract the Node.js parsing logic
45
+ const nodeScript = extractNodeScript();
46
+ assert_1.default.ok(nodeScript.length > 0, 'Extracted script is empty');
47
+ console.log(' ✅ Extracted Node.js parsing logic');
48
+ // 3. Test Valid Configuration
49
+ const validConfig = {
50
+ repository: {
51
+ owner: 'test-owner',
52
+ name: 'test-repo',
53
+ url: 'https://github.com/test-owner/test-repo.git'
54
+ }
55
+ };
56
+ const output = runParsingLogic(nodeScript, validConfig);
57
+ assert_1.default.strictEqual(output, 'test-owner:test-repo:https://github.com/test-owner/test-repo.git');
58
+ console.log(' ✅ Valid config parsed correctly');
59
+ // 4. Test Missing Config
60
+ assert_1.default.throws(() => {
61
+ runParsingLogic(nodeScript, { name: 'Create-Project' });
62
+ }, /Script failed/);
63
+ console.log(' ✅ Missing config correctly failed');
64
+ // 5. Test Incomplete Config
65
+ assert_1.default.throws(() => {
66
+ runParsingLogic(nodeScript, { repository: { owner: 'test-owner' } });
67
+ }, /Script failed/);
68
+ console.log(' ✅ Incomplete config correctly failed');
69
+ return true;
70
+ }
71
+ catch (error) {
72
+ console.error(' ❌ Test failed:', error);
73
+ return false;
74
+ }
75
+ }
76
+ // Define test cases
77
+ const testCases = [
78
+ {
79
+ name: 'Prep Issue Config Parsing',
80
+ description: 'Verifies correct parsing of repo config from .fraim/config.json',
81
+ testFn: verifyPrepIssueConfigParsing,
82
+ tags: ['unit', 'scripts']
83
+ }
84
+ ];
85
+ // Run strict check if executed directly
86
+ if (require.main === module) {
87
+ (0, test_utils_1.runTests)(testCases, async (testCase) => {
88
+ if (testCase.testFn) {
89
+ return await testCase.testFn();
90
+ }
91
+ return false;
92
+ }, 'Prep Issue Script Tests');
93
+ }
@@ -1,161 +1,149 @@
1
- import { spawn, ChildProcess } from 'node:child_process';
2
- import axios from 'axios';
3
- import { FraimDbService } from './src/fraim/db-service.js';
4
- import { BaseTestCase, runTests } from './test-utils';
5
- import assert from 'node:assert';
6
- import kill from 'tree-kill';
7
-
8
- interface FraimStandaloneTestCase extends BaseTestCase {
9
- testFunction: () => Promise<boolean>;
10
- }
11
-
12
- async function testServerStartsAndResponds(): Promise<boolean> {
13
- console.log(' 🚀 Testing Fraim Standalone Server...');
14
- let fraimProcess: ChildProcess | undefined;
15
- let dbService: FraimDbService | undefined;
16
- const PORT = 10001; // Use a different port to avoid conflicts
17
- const TEST_API_KEY = 'test-fraim-key-integration';
18
- const TEST_ADMIN_KEY = 'test-admin-key-integration';
19
- const TEST_ADMIN_HEADER = { 'x-admin-key': TEST_ADMIN_KEY };
20
- const BASE_URL = `http://localhost:${PORT}`;
21
-
22
- try {
23
- // 1. Seed the test API key in the database
24
- dbService = new FraimDbService();
25
- await dbService.connect();
26
-
27
- const db = (dbService as any).db;
28
- await db.collection('fraim_api_keys').updateOne(
29
- { key: TEST_API_KEY },
30
- {
31
- $set: {
32
- userId: 'test-user@ashley.ai',
33
- orgId: 'test-org',
34
- isActive: true,
35
- createdAt: new Date()
36
- }
37
- },
38
- { upsert: true }
39
- );
40
-
41
- // 2. Start server in standalone mode
42
- console.log(` Starting server on port ${PORT}...`);
43
- const npxCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
44
- fraimProcess = spawn(npxCommand, ['tsx', 'src/fraim-mcp-server.ts'], {
45
- env: {
46
- ...process.env,
47
- FRAIM_MCP_PORT: PORT.toString(),
48
- FRAIM_ADMIN_KEY: TEST_ADMIN_KEY,
49
- FRAIM_SKIP_INDEX_ON_START: 'true' // Potential optimization if implemented
50
- },
51
- stdio: 'inherit',
52
- shell: true
53
- });
54
-
55
- // 3. Wait for server to start
56
- console.log(' Waiting for server to start...');
57
- let started = false;
58
- for (let i = 0; i < 15; i++) {
59
- try {
60
- await axios.get(`${BASE_URL}/health`, { timeout: 1000 });
61
- started = true;
62
- console.log(' Server started!');
63
- break;
64
- } catch (e) {
65
- await new Promise(resolve => setTimeout(resolve, 1000));
66
- }
67
- }
68
-
69
- if (!started) {
70
- console.error(' ❌ Server failed to start within timeout');
71
- return false;
72
- }
73
-
74
- // 4. Test health check (public)
75
- console.log(' Testing public health check...');
76
- const healthRes = await axios.get(`${BASE_URL}/health`, { timeout: 2000 });
77
- assert.strictEqual(healthRes.status, 200);
78
- assert.strictEqual(healthRes.data.status, 'ok');
79
-
80
- // 5. Test MCP tool list without API key (fail)
81
- console.log(' Testing MCP without auth (should fail)...');
82
- try {
83
- await axios.post(`${BASE_URL}/mcp`, {
84
- jsonrpc: '2.0',
85
- id: 1,
86
- method: 'tools/list',
87
- params: {}
88
- }, { timeout: 2000 });
89
- console.error(' Should have failed without API key');
90
- return false;
91
- } catch (error: any) {
92
- assert.ok(error.response, 'Should have a response');
93
- assert.strictEqual(error.response.status, 401);
94
- }
95
-
96
- // 6. Test MCP tool list with correct API key (success)
97
- console.log(' Testing MCP with correct auth...');
98
- const mcpResponse = await axios.post(`${BASE_URL}/mcp`, {
99
- jsonrpc: '2.0',
100
- id: 1,
101
- method: 'tools/list',
102
- params: {}
103
- }, {
104
- headers: { 'x-api-key': TEST_API_KEY },
105
- timeout: 5000
106
- });
107
- assert.strictEqual(mcpResponse.status, 200);
108
- assert.ok(mcpResponse.data.result.tools.length > 0);
109
-
110
- // 7. Verify usage logging
111
- console.log(' Verifying usage logging...');
112
- await new Promise(resolve => setTimeout(resolve, 1500));
113
- const log = await db.collection('fraim_usage_logs').findOne({ keyId: TEST_API_KEY });
114
- assert.ok(log, 'Usage log should have been created');
115
- assert.strictEqual(log.userId, 'test-user@ashley.ai');
116
-
117
- // 8. Test Admin API - List Keys
118
- console.log(' Testing Admin API - List Keys...');
119
- const listKeysRes = await axios.get(`${BASE_URL}/admin/keys`, {
120
- headers: TEST_ADMIN_HEADER,
121
- timeout: 2000
122
- });
123
- assert.strictEqual(listKeysRes.status, 200);
124
- assert.ok(Array.isArray(listKeysRes.data));
125
- assert.ok(listKeysRes.data.some((k: any) => k.key === TEST_API_KEY));
126
-
127
- return true;
128
- } catch (error) {
129
- console.error(' ❌ Test failed:', error);
130
- return false;
131
- } finally {
132
- console.log(' Cleaning up...');
133
- if (dbService) {
134
- const db = (dbService as any).db;
135
- if (db) {
136
- await db.collection('fraim_api_keys').deleteOne({ key: TEST_API_KEY }).catch(() => { });
137
- }
138
- await dbService.close().catch(() => { });
139
- }
140
- if (fraimProcess && fraimProcess.pid) {
141
- const pid = fraimProcess.pid;
142
- await new Promise<void>((resolve) => kill(pid, 'SIGKILL', () => resolve()));
143
- console.log(` Terminated server process ${pid}`);
144
- }
145
- }
146
- }
147
-
148
- async function runFraimTest(testCase: FraimStandaloneTestCase): Promise<boolean> {
149
- return await testCase.testFunction();
150
- }
151
-
152
- const testCases: FraimStandaloneTestCase[] = [
153
- {
154
- name: 'Fraim Standalone Server Integration',
155
- description: 'Tests server startup, public health check, authenticated MCP access, usage logging, and admin API',
156
- testFunction: testServerStartsAndResponds,
157
- tags: ['smoke', 'fraim']
158
- }
159
- ];
160
-
161
- runTests(testCases, runFraimTest, 'Fraim Standalone Server');
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 testServerStartsAndResponds() {
14
+ console.log(' 🚀 Testing Fraim Standalone Server...');
15
+ let fraimProcess;
16
+ let dbService;
17
+ const PORT = 10001; // Use a different port to avoid conflicts
18
+ const TEST_API_KEY = 'test-fraim-key-integration';
19
+ const TEST_ADMIN_KEY = 'test-admin-key-integration';
20
+ const TEST_ADMIN_HEADER = { 'x-admin-key': TEST_ADMIN_KEY };
21
+ const BASE_URL = `http://localhost:${PORT}`;
22
+ try {
23
+ // 1. Seed the test API key in the database
24
+ dbService = new db_service_js_1.FraimDbService();
25
+ await dbService.connect();
26
+ const db = dbService.db;
27
+ await db.collection('fraim_api_keys').updateOne({ key: TEST_API_KEY }, {
28
+ $set: {
29
+ userId: 'test-user@ashley.ai',
30
+ orgId: 'test-org',
31
+ isActive: true,
32
+ createdAt: new Date()
33
+ }
34
+ }, { upsert: true });
35
+ // 2. Start server in standalone mode
36
+ console.log(` Starting server on port ${PORT}...`);
37
+ const npxCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
38
+ const serverScript = path_1.default.resolve(__dirname, '../src/fraim-mcp-server.ts');
39
+ fraimProcess = (0, node_child_process_1.spawn)(npxCommand, ['tsx', `"${serverScript}"`], {
40
+ env: {
41
+ ...process.env,
42
+ FRAIM_MCP_PORT: PORT.toString(),
43
+ FRAIM_ADMIN_KEY: TEST_ADMIN_KEY,
44
+ FRAIM_SKIP_INDEX_ON_START: 'true' // Potential optimization if implemented
45
+ },
46
+ stdio: 'inherit',
47
+ shell: true
48
+ });
49
+ // 3. Wait for server to start
50
+ console.log(' Waiting for server to start...');
51
+ let started = false;
52
+ for (let i = 0; i < 15; i++) {
53
+ try {
54
+ await axios_1.default.get(`${BASE_URL}/health`, { timeout: 1000 });
55
+ started = true;
56
+ console.log(' Server started!');
57
+ break;
58
+ }
59
+ catch (e) {
60
+ await new Promise(resolve => setTimeout(resolve, 1000));
61
+ }
62
+ }
63
+ if (!started) {
64
+ console.error(' ❌ Server failed to start within timeout');
65
+ return false;
66
+ }
67
+ // 4. Test health check (public)
68
+ console.log(' Testing public health check...');
69
+ const healthRes = await axios_1.default.get(`${BASE_URL}/health`, { timeout: 2000 });
70
+ node_assert_1.default.strictEqual(healthRes.status, 200);
71
+ node_assert_1.default.strictEqual(healthRes.data.status, 'ok');
72
+ // 5. Test MCP tool list without API key (fail)
73
+ console.log(' Testing MCP without auth (should fail)...');
74
+ try {
75
+ await axios_1.default.post(`${BASE_URL}/mcp`, {
76
+ jsonrpc: '2.0',
77
+ id: 1,
78
+ method: 'tools/list',
79
+ params: {}
80
+ }, { timeout: 2000 });
81
+ console.error(' Should have failed without API key');
82
+ return false;
83
+ }
84
+ catch (error) {
85
+ node_assert_1.default.ok(error.response, 'Should have a response');
86
+ node_assert_1.default.strictEqual(error.response.status, 401);
87
+ }
88
+ // 6. Test MCP tool list with correct API key (success)
89
+ console.log(' Testing MCP with correct auth...');
90
+ const mcpResponse = await axios_1.default.post(`${BASE_URL}/mcp`, {
91
+ jsonrpc: '2.0',
92
+ id: 1,
93
+ method: 'tools/list',
94
+ params: {}
95
+ }, {
96
+ headers: { 'x-api-key': TEST_API_KEY },
97
+ timeout: 5000
98
+ });
99
+ node_assert_1.default.strictEqual(mcpResponse.status, 200);
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
108
+ console.log(' Testing Admin API - List Keys...');
109
+ const listKeysRes = await axios_1.default.get(`${BASE_URL}/admin/keys`, {
110
+ headers: TEST_ADMIN_HEADER,
111
+ timeout: 2000
112
+ });
113
+ node_assert_1.default.strictEqual(listKeysRes.status, 200);
114
+ node_assert_1.default.ok(Array.isArray(listKeysRes.data));
115
+ node_assert_1.default.ok(listKeysRes.data.some((k) => k.key === TEST_API_KEY));
116
+ return true;
117
+ }
118
+ catch (error) {
119
+ console.error(' ❌ Test failed:', error);
120
+ return false;
121
+ }
122
+ finally {
123
+ console.log(' Cleaning up...');
124
+ if (dbService) {
125
+ const db = dbService.db;
126
+ if (db) {
127
+ await db.collection('fraim_api_keys').deleteOne({ key: TEST_API_KEY }).catch(() => { });
128
+ }
129
+ await dbService.close().catch(() => { });
130
+ }
131
+ if (fraimProcess && fraimProcess.pid) {
132
+ const pid = fraimProcess.pid;
133
+ await new Promise((resolve) => (0, tree_kill_1.default)(pid, 'SIGKILL', () => resolve()));
134
+ console.log(` Terminated server process ${pid}`);
135
+ }
136
+ }
137
+ }
138
+ async function runFraimTest(testCase) {
139
+ return await testCase.testFunction();
140
+ }
141
+ const testCases = [
142
+ {
143
+ name: 'Fraim Standalone Server Integration',
144
+ description: 'Tests server startup, public health check, authenticated MCP access, usage logging, and admin API',
145
+ testFunction: testServerStartsAndResponds,
146
+ tags: ['smoke', 'fraim']
147
+ }
148
+ ];
149
+ (0, test_utils_1.runTests)(testCases, runFraimTest, 'Fraim Standalone Server');