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
@@ -7,6 +7,7 @@ on:
7
7
  - master
8
8
  paths:
9
9
  - 'src/fraim-mcp-server.ts'
10
+ - 'src/fraim/**'
10
11
  - '.ai-agents/**'
11
12
  - '.fraim/**'
12
13
  - '.github/workflows/deploy-fraim.yml'
@@ -55,7 +56,8 @@ jobs:
55
56
  # Copy necessary files
56
57
  cp -r dist deploy-package/
57
58
  cp -r node_modules deploy-package/
58
- # Include .fraim directory for runtime configuration
59
+ # Include registry and .fraim directory for runtime
60
+ cp -r registry deploy-package/
59
61
  cp -r .fraim deploy-package/
60
62
 
61
63
  cp package.json deploy-package/
@@ -8,7 +8,7 @@ exports.loadFraimConfig = loadFraimConfig;
8
8
  exports.getConfigValue = getConfigValue;
9
9
  const fs_1 = require("fs");
10
10
  const path_1 = require("path");
11
- const types_js_1 = require("./types.js");
11
+ const types_1 = require("./types");
12
12
  /**
13
13
  * Load FRAIM configuration from .fraim/config.json
14
14
  * Falls back to defaults if file doesn't exist
@@ -17,43 +17,51 @@ function loadFraimConfig() {
17
17
  const configPath = (0, path_1.join)(process.cwd(), '.fraim', 'config.json');
18
18
  if (!(0, fs_1.existsSync)(configPath)) {
19
19
  console.log('📋 No .fraim/config.json found, using defaults');
20
- return { ...types_js_1.DEFAULT_FRAIM_CONFIG };
20
+ return { ...types_1.DEFAULT_FRAIM_CONFIG };
21
21
  }
22
22
  try {
23
23
  const configContent = (0, fs_1.readFileSync)(configPath, 'utf-8');
24
24
  const config = JSON.parse(configContent);
25
25
  // Merge with defaults to ensure all required fields exist
26
26
  const mergedConfig = {
27
- ...types_js_1.DEFAULT_FRAIM_CONFIG,
27
+ ...types_1.DEFAULT_FRAIM_CONFIG,
28
28
  ...config,
29
29
  project: {
30
- ...types_js_1.DEFAULT_FRAIM_CONFIG.project,
30
+ ...types_1.DEFAULT_FRAIM_CONFIG.project,
31
31
  ...(config.project || {})
32
32
  },
33
33
  git: {
34
- ...types_js_1.DEFAULT_FRAIM_CONFIG.git,
34
+ ...types_1.DEFAULT_FRAIM_CONFIG.git,
35
35
  ...(config.git || {})
36
36
  },
37
37
  customizations: {
38
- ...types_js_1.DEFAULT_FRAIM_CONFIG.customizations,
38
+ ...types_1.DEFAULT_FRAIM_CONFIG.customizations,
39
39
  ...(config.customizations || {})
40
40
  },
41
+ architecture: {
42
+ ...types_1.DEFAULT_FRAIM_CONFIG.architecture,
43
+ ...(config.architecture || {})
44
+ },
45
+ testing: {
46
+ ...types_1.DEFAULT_FRAIM_CONFIG.testing,
47
+ ...(config.testing || {})
48
+ },
41
49
  persona: {
42
- ...types_js_1.DEFAULT_FRAIM_CONFIG.persona,
50
+ ...types_1.DEFAULT_FRAIM_CONFIG.persona,
43
51
  ...(config.persona || {})
44
52
  },
45
53
  marketing: {
46
- ...types_js_1.DEFAULT_FRAIM_CONFIG.marketing,
54
+ ...types_1.DEFAULT_FRAIM_CONFIG.marketing,
47
55
  ...(config.marketing || {})
48
56
  },
49
57
  database: {
50
- ...types_js_1.DEFAULT_FRAIM_CONFIG.database,
58
+ ...types_1.DEFAULT_FRAIM_CONFIG.database,
51
59
  ...(config.database || {})
52
60
  }
53
61
  };
54
62
  // Validate version
55
63
  if (!mergedConfig.version) {
56
- mergedConfig.version = types_js_1.DEFAULT_FRAIM_CONFIG.version;
64
+ mergedConfig.version = types_1.DEFAULT_FRAIM_CONFIG.version;
57
65
  }
58
66
  console.log(`📋 Loaded FRAIM config from .fraim/config.json (version ${mergedConfig.version})`);
59
67
  return mergedConfig;
@@ -61,7 +69,7 @@ function loadFraimConfig() {
61
69
  catch (error) {
62
70
  console.warn(`⚠️ Failed to load .fraim/config.json: ${error instanceof Error ? error.message : 'Unknown error'}`);
63
71
  console.warn(' Using default configuration');
64
- return { ...types_js_1.DEFAULT_FRAIM_CONFIG };
72
+ return { ...types_1.DEFAULT_FRAIM_CONFIG };
65
73
  }
66
74
  }
67
75
  /**
@@ -69,6 +69,7 @@ function createDirectoryStructure() {
69
69
  * Generate architecture.md template
70
70
  */
71
71
  function generateArchitectureTemplate(config) {
72
+ const arch = config.architecture;
72
73
  let template = `# Project Architecture
73
74
 
74
75
  This document describes the architecture patterns and conventions for ${config.project.name}.
@@ -76,8 +77,34 @@ This document describes the architecture patterns and conventions for ${config.p
76
77
  ## Overview
77
78
 
78
79
  - **Project Name:** ${config.project.name}
80
+ - **Project Type:** ${config.project.type || 'Not specified'}
81
+ - **Primary Language:** ${config.project.primaryLanguage || 'Not specified'}
82
+ - **Database:** ${config.project.database || 'Not specified'}
83
+ - **ORM:** ${config.project.orm || 'Not specified'}
79
84
 
80
- ## Development Guidelines
85
+ `;
86
+ if (arch) {
87
+ template += `## Architecture Pattern
88
+
89
+ **Pattern:** ${arch.pattern || 'Not specified'}
90
+
91
+ `;
92
+ if (arch.llmFramework) {
93
+ template += `### LLM Integration
94
+
95
+ - **Framework:** ${arch.llmFramework}
96
+ - **Usage:** ${arch.llmUsage?.join(', ') || 'Not specified'}
97
+ - **Deterministic Usage:** ${arch.deterministicUsage?.join(', ') || 'Not specified'}
98
+
99
+ `;
100
+ }
101
+ }
102
+ template += `## Development Guidelines
103
+
104
+ ### Testing
105
+ - **Framework:** ${config.testing?.framework || 'Not specified'}
106
+ - **Test Location:** ${config.testing?.testLocation || 'Not specified'}
107
+ - **Test Naming:** ${config.testing?.testNaming || 'Not specified'}
81
108
 
82
109
  ### Git Workflow
83
110
  - **Default Branch:** ${config.git.defaultBranch}
@@ -18,6 +18,17 @@ exports.DEFAULT_FRAIM_CONFIG = {
18
18
  repoOwner: '',
19
19
  repoName: ''
20
20
  },
21
+ architecture: {
22
+ pattern: 'llm-deterministic-separation',
23
+ llmFramework: 'baml',
24
+ llmUsage: [],
25
+ deterministicUsage: ['data-processing', 'api-calls']
26
+ },
27
+ testing: {
28
+ framework: 'tsx-test',
29
+ testLocation: 'root',
30
+ testNaming: 'test-*.ts'
31
+ },
21
32
  customizations: {
22
33
  workflowsPath: '.fraim/workflows'
23
34
  },
@@ -82,9 +82,19 @@ class FraimMCPServer {
82
82
  this.app.use(express_1.default.json());
83
83
  // Load server version
84
84
  try {
85
- const pkgPath = (0, path_1.join)(__dirname, '..', 'package.json');
86
- const pkg = JSON.parse((0, fs_1.readFileSync)(pkgPath, 'utf8'));
87
- this.serverVersion = pkg.version;
85
+ // Try process.cwd() first as it's the root in most deployments
86
+ let pkgPath = (0, path_1.join)(process.cwd(), 'package.json');
87
+ if (!(0, fs_1.existsSync)(pkgPath)) {
88
+ // Fallback to relative to __dirname (dist/src/ -> ../../package.json)
89
+ pkgPath = (0, path_1.join)(__dirname, '..', '..', 'package.json');
90
+ }
91
+ if ((0, fs_1.existsSync)(pkgPath)) {
92
+ const pkg = JSON.parse((0, fs_1.readFileSync)(pkgPath, 'utf8'));
93
+ this.serverVersion = pkg.version;
94
+ }
95
+ else {
96
+ this.serverVersion = 'unknown';
97
+ }
88
98
  }
89
99
  catch (e) {
90
100
  this.serverVersion = 'unknown';
@@ -217,8 +227,11 @@ class FraimMCPServer {
217
227
  });
218
228
  }
219
229
  catch (error) {
220
- console.error('❌ FRAIM AUTH: Error during verification:', error);
221
- res.status(500).json({ error: 'Internal Server Error' });
230
+ const msg = error instanceof Error ? error.message : String(error);
231
+ console.error('❌ FRAIM AUTH: Error during verification:', msg);
232
+ if (error instanceof Error && error.stack)
233
+ console.error(error.stack);
234
+ res.status(500).json({ error: 'Internal Server Error', details: msg });
222
235
  }
223
236
  }
224
237
  adminAuthMiddleware(req, res, next) {
@@ -956,12 +969,10 @@ Use this to discover which workflow to call with get_fraim_workflow.`,
956
969
  const workflowPath = `workflows/${normalizedName}.md`;
957
970
  const metadata = this.fileIndex.get(workflowPath);
958
971
  if (!metadata) {
959
- // Try alternative paths (e.g., customer-development subdirectory)
960
- const alternativePaths = [
961
- `workflows/customer-development/${normalizedName}.md`,
962
- `workflows/business-development/${normalizedName}.md`
963
- ];
964
- for (const altPath of alternativePaths) {
972
+ // Try alternative category paths
973
+ const categories = ['product-building', 'customer-development', 'business-development', 'marketing', 'performance', 'quality-assurance', 'reviewer', 'startup-credits', 'bootstrap', 'deploy'];
974
+ for (const cat of categories) {
975
+ const altPath = `workflows/${cat}/${normalizedName}.md`;
965
976
  const altMetadata = this.fileIndex.get(altPath);
966
977
  if (altMetadata) {
967
978
  return this.returnWorkflowFile(altMetadata);
@@ -24,7 +24,7 @@ function getPort() {
24
24
  catch (e) {
25
25
  // Silently fail and use default
26
26
  }
27
- return Number(process.env.FRAIM_MCP_PORT) || 15300;
27
+ return Number(process.env.PORT) || Number(process.env.WEBSITES_PORT) || Number(process.env.FRAIM_MCP_PORT) || 15300;
28
28
  }
29
29
  /**
30
30
  * Determines the database name based on the git branch
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const node_child_process_1 = require("node:child_process");
40
+ const test_utils_1 = require("./test-utils");
41
+ const node_assert_1 = __importDefault(require("node:assert"));
42
+ const fs_1 = __importDefault(require("fs"));
43
+ const path_1 = __importDefault(require("path"));
44
+ const os_1 = __importDefault(require("os"));
45
+ async function testCliLifecycle() {
46
+ console.log(' 🚀 Testing Fraim CLI Lifecycle...');
47
+ // Create a temp directory for the test project
48
+ const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'fraim-test-'));
49
+ console.log(` 📂 Created temp dir: ${tempDir}`);
50
+ try {
51
+ const platform = process.platform;
52
+ const npx = platform === 'win32' ? 'npx.cmd' : 'npx';
53
+ const cliScript = path_1.default.resolve(__dirname, '../src/cli/fraim.ts');
54
+ // Helper to run CLI commands
55
+ const runFraim = (args, cwdOverride) => {
56
+ return new Promise((resolve, reject) => {
57
+ const ps = (0, node_child_process_1.spawn)(npx, ['tsx', `"${cliScript}"`, ...args], {
58
+ cwd: cwdOverride || tempDir,
59
+ env: { ...process.env, TEST_MODE: 'true' }, // Disable interactive first-run
60
+ shell: true
61
+ });
62
+ let stdout = '';
63
+ let stderr = '';
64
+ ps.stdout.on('data', d => stdout += d.toString());
65
+ ps.stderr.on('data', d => stderr += d.toString());
66
+ ps.on('close', (code) => {
67
+ resolve({ stdout, stderr, code });
68
+ });
69
+ });
70
+ };
71
+ // 1. Test `fraim init`
72
+ console.log(' Testing "fraim init"...');
73
+ // Create a fake git repo to test population
74
+ try {
75
+ const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
76
+ execSync('git init', { cwd: tempDir });
77
+ execSync('git remote add origin https://github.com/test-owner/test-repo.git', { cwd: tempDir });
78
+ }
79
+ catch (e) {
80
+ console.warn(' ⚠️ Could not initialize git in temp dir, skipping population test');
81
+ }
82
+ const initRes = await runFraim(['init'], tempDir);
83
+ if (initRes.code !== 0) {
84
+ console.error('Init failed:', initRes.stderr);
85
+ console.error('STDOUT:', initRes.stdout); // Keep this line for better debugging
86
+ }
87
+ node_assert_1.default.strictEqual(initRes.code, 0, 'Init should exit with 0');
88
+ node_assert_1.default.ok(fs_1.default.existsSync(path_1.default.join(tempDir, '.fraim')), '.fraim dir should exist');
89
+ const configPath = path_1.default.join(tempDir, '.fraim', 'config.json');
90
+ node_assert_1.default.ok(fs_1.default.existsSync(configPath), 'config.json should exist');
91
+ const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
92
+ // Deep structure validation
93
+ node_assert_1.default.ok(config.project, 'config.project should exist');
94
+ node_assert_1.default.strictEqual(typeof config.project.name, 'string', 'project.name should be a string');
95
+ node_assert_1.default.ok(config.persona, 'config.persona should exist');
96
+ node_assert_1.default.strictEqual(config.persona.name, 'AI Agent', 'Default persona name should be correct');
97
+ node_assert_1.default.ok(config.marketing, 'config.marketing should exist');
98
+ node_assert_1.default.strictEqual(config.marketing.websiteUrl, '', 'websiteUrl should be empty placeholder');
99
+ node_assert_1.default.ok(config.git, 'config.git should exist');
100
+ // If git was initialized successfully, these should NOT be empty
101
+ const hasGit = fs_1.default.existsSync(path_1.default.join(tempDir, '.git'));
102
+ if (hasGit) {
103
+ node_assert_1.default.strictEqual(config.git.repoOwner, 'test-owner', 'repoOwner should be detected from git');
104
+ node_assert_1.default.strictEqual(config.git.repoName, 'test-repo', 'repoName should be detected from git');
105
+ }
106
+ else {
107
+ console.warn(' ⚠️ No .git found in tempDir, owner/repo detection skipped');
108
+ node_assert_1.default.strictEqual(config.git.repoOwner, '', 'repoOwner should be empty if no git');
109
+ node_assert_1.default.strictEqual(config.git.repoName, '', 'repoName should be empty if no git');
110
+ }
111
+ node_assert_1.default.ok(config.customizations, 'config.customizations should exist');
112
+ node_assert_1.default.strictEqual(config.customizations.workflowsPath, '.fraim/workflows');
113
+ node_assert_1.default.ok(config.architecture, 'architecture should be in generated config');
114
+ node_assert_1.default.ok(config.testing, 'testing should be in generated config');
115
+ node_assert_1.default.ok(!config.mcp, 'mcp should not be in generated config');
116
+ // 2. Test `fraim sync`
117
+ // We need a fake registry for sync to work against
118
+ const registryDir = path_1.default.join(tempDir, 'registry', 'workflows');
119
+ fs_1.default.mkdirSync(registryDir, { recursive: true });
120
+ const dummyWorkflow = `---
121
+ intent: Test Intent
122
+ principles:
123
+ - Test Principle
124
+ ---
125
+ # Test Workflow`;
126
+ fs_1.default.writeFileSync(path_1.default.join(registryDir, 'test-workflow.md'), dummyWorkflow);
127
+ console.log(' Testing "fraim sync"...');
128
+ const syncRes = await runFraim(['sync']);
129
+ if (syncRes.code === 0) {
130
+ console.log(' ✅ Sync successful');
131
+ }
132
+ else {
133
+ console.log(' ⚠️ Sync warning (env dependent):', syncRes.stderr);
134
+ }
135
+ // 3. Test `fraim list`
136
+ console.log(' Testing "fraim list"...');
137
+ const listRes = await runFraim(['list']);
138
+ node_assert_1.default.strictEqual(listRes.code, 0);
139
+ // 4. Test `fraim doctor`
140
+ console.log(' Testing "fraim doctor"...');
141
+ const doctorRes = await runFraim(['doctor']);
142
+ node_assert_1.default.strictEqual(doctorRes.code, 0);
143
+ node_assert_1.default.ok(doctorRes.stdout.includes('Everything looks great'), 'Doctor should be happy');
144
+ console.log(' ✅ CLI Lifecycle verified!');
145
+ return true;
146
+ }
147
+ catch (error) {
148
+ console.error(' ❌ CLI Test failed:', error);
149
+ return false;
150
+ }
151
+ finally {
152
+ try {
153
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
154
+ }
155
+ catch (e) { }
156
+ }
157
+ }
158
+ async function runCliTest(testCase) {
159
+ return await testCase.testFunction();
160
+ }
161
+ const testCases = [
162
+ {
163
+ name: 'Fraim CLI Lifecycle',
164
+ description: 'Tests init, sync, list, and doctor commands',
165
+ testFunction: testCliLifecycle,
166
+ tags: ['cli', 'fraim']
167
+ },
168
+ ];
169
+ (0, test_utils_1.runTests)(testCases, runCliTest, 'Fraim CLI Tests');
@@ -0,0 +1,108 @@
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 node_assert_1 = __importDefault(require("node:assert"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const os_1 = __importDefault(require("os"));
12
+ /**
13
+ * Helper to run the interactive first-run experience within 'fraim init'
14
+ */
15
+ async function runInteractiveInit(cwd, interactions) {
16
+ const projectRoot = path_1.default.resolve(__dirname, '..');
17
+ const cliScript = path_1.default.resolve(projectRoot, 'src/cli/fraim.ts');
18
+ const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx';
19
+ return new Promise(resolve => {
20
+ const ps = (0, node_child_process_1.spawn)(npx, ['tsx', `"${cliScript}"`, 'init'], {
21
+ cwd,
22
+ env: { ...process.env, CI: 'false', TEST_MODE: 'false' },
23
+ shell: true,
24
+ stdio: ['pipe', 'pipe', 'pipe']
25
+ });
26
+ let stdout = '';
27
+ let stderr = '';
28
+ let currentPromptIndex = 0;
29
+ const timeout = setTimeout(() => {
30
+ console.error(' ❌ Interactive init timed out');
31
+ ps.kill('SIGKILL');
32
+ resolve({ stdout, stderr, code: -1 });
33
+ }, 45000);
34
+ ps.stdout.on('data', d => {
35
+ const chunk = d.toString();
36
+ stdout += chunk;
37
+ if (currentPromptIndex < interactions.length) {
38
+ const target = interactions[currentPromptIndex];
39
+ if (chunk.includes(target.prompt)) {
40
+ currentPromptIndex++;
41
+ setTimeout(() => {
42
+ ps.stdin.write(target.response + '\n');
43
+ }, 500);
44
+ }
45
+ }
46
+ });
47
+ ps.stderr.on('data', d => stderr += d.toString());
48
+ ps.on('close', code => {
49
+ clearTimeout(timeout);
50
+ resolve({ stdout, stderr, code });
51
+ });
52
+ });
53
+ }
54
+ const testCases = [
55
+ {
56
+ name: 'Journey: Setup with Key, No Arch Doc',
57
+ testFunction: async () => {
58
+ const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'fraim-jour-1-'));
59
+ try {
60
+ const res = await runInteractiveInit(tempDir, [
61
+ { prompt: 'Do you have a FRAIM key?', response: 'key-123' },
62
+ { prompt: 'Do you have an architecture document', response: 'n' }
63
+ ]);
64
+ node_assert_1.default.strictEqual(res.code, 0);
65
+ node_assert_1.default.ok(res.stdout.includes('Key received'));
66
+ node_assert_1.default.ok(res.stdout.includes('Run the bootstrap/create-architecture workflow'));
67
+ // VERIFY STUBS EXIST
68
+ const workflowsDir = path_1.default.join(tempDir, '.fraim', 'workflows');
69
+ node_assert_1.default.ok(fs_1.default.existsSync(workflowsDir), 'Workflows dir missing');
70
+ const stubs = fs_1.default.readdirSync(workflowsDir);
71
+ node_assert_1.default.ok(stubs.length > 0, 'No workflow stubs created');
72
+ return true;
73
+ }
74
+ finally {
75
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
76
+ }
77
+ },
78
+ tags: ['cli', 'journey', 'init']
79
+ },
80
+ {
81
+ name: 'Journey: Setup with Arch Doc Path',
82
+ testFunction: async () => {
83
+ const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'fraim-jour-2-'));
84
+ const archPath = 'docs/my-architecture.md';
85
+ try {
86
+ fs_1.default.mkdirSync(path_1.default.join(tempDir, '.fraim'), { recursive: true });
87
+ fs_1.default.writeFileSync(path_1.default.join(tempDir, '.fraim', 'config.json'), JSON.stringify({ version: '1.0.0' }));
88
+ fs_1.default.mkdirSync(path_1.default.join(tempDir, 'docs'), { recursive: true });
89
+ fs_1.default.writeFileSync(path_1.default.join(tempDir, archPath), '# Arch');
90
+ const res = await runInteractiveInit(tempDir, [
91
+ { prompt: 'Do you have a FRAIM key?', response: '' },
92
+ { prompt: 'Do you have an architecture document', response: 'y' },
93
+ { prompt: 'provide the relative path', response: archPath }
94
+ ]);
95
+ node_assert_1.default.strictEqual(res.code, 0);
96
+ node_assert_1.default.ok(res.stdout.includes(`Linked architecture document: ${archPath}`));
97
+ const config = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tempDir, '.fraim', 'config.json'), 'utf8'));
98
+ node_assert_1.default.strictEqual(config.customizations.architectureDoc, archPath);
99
+ return true;
100
+ }
101
+ finally {
102
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
103
+ }
104
+ },
105
+ tags: ['cli', 'journey', 'init']
106
+ }
107
+ ];
108
+ (0, test_utils_1.runTests)(testCases, (tc) => tc.testFunction(), 'Fraim Setup Journeys');
@@ -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 test_utils_1 = require("./test-utils");
7
+ const node_assert_1 = __importDefault(require("node:assert"));
8
+ const types_1 = require("../src/fraim/types");
9
+ const template_processor_1 = require("../src/fraim/template-processor");
10
+ async function testConfigDefaults() {
11
+ console.log(' 🧪 Testing DEFAULT_FRAIM_CONFIG placeholders...');
12
+ try {
13
+ node_assert_1.default.strictEqual(types_1.DEFAULT_FRAIM_CONFIG.persona.name, 'AI Agent', 'Default persona name should be "AI Agent"');
14
+ node_assert_1.default.ok(types_1.DEFAULT_FRAIM_CONFIG.customizations?.workflowsPath, 'workflowsPath should exist');
15
+ node_assert_1.default.ok(types_1.DEFAULT_FRAIM_CONFIG.architecture, 'architecture should be restored');
16
+ node_assert_1.default.ok(types_1.DEFAULT_FRAIM_CONFIG.testing, 'testing should be restored');
17
+ node_assert_1.default.ok(!types_1.DEFAULT_FRAIM_CONFIG.mcp, 'mcp should still be removed');
18
+ return true;
19
+ }
20
+ catch (e) {
21
+ console.error(' ❌ Default configuration verification failed:', e);
22
+ return false;
23
+ }
24
+ }
25
+ async function testTemplateProcessor() {
26
+ console.log(' 🧪 Testing Template Processor with new config fields...');
27
+ try {
28
+ const mockConfig = {
29
+ ...types_1.DEFAULT_FRAIM_CONFIG,
30
+ project: { ...types_1.DEFAULT_FRAIM_CONFIG.project, name: 'Generic Bot' },
31
+ persona: { ...types_1.DEFAULT_FRAIM_CONFIG.persona, name: 'Ava' },
32
+ marketing: {
33
+ ...types_1.DEFAULT_FRAIM_CONFIG.marketing,
34
+ websiteUrl: 'https://ava.ai',
35
+ chatUrl: 'https://chat.ava.ai'
36
+ }
37
+ };
38
+ const template = 'Welcome to {{config.project.name}}. I am {{config.persona.name}}. Visit {{config.marketing.websiteUrl}} or chat at {{config.marketing.chatUrl}}.';
39
+ const processed = (0, template_processor_1.processTemplateVariables)(template, mockConfig);
40
+ const expected = 'Welcome to Generic Bot. I am Ava. Visit https://ava.ai or chat at https://chat.ava.ai.';
41
+ node_assert_1.default.strictEqual(processed, expected, 'Template variables should be correctly replaced');
42
+ return true;
43
+ }
44
+ catch (e) {
45
+ console.error(' ❌ Template processing verification failed:', e);
46
+ return false;
47
+ }
48
+ }
49
+ const testCases = [
50
+ {
51
+ name: 'Config Defaults',
52
+ description: 'Verify DEFAULT_FRAIM_CONFIG has empty placeholders',
53
+ testFunction: testConfigDefaults,
54
+ tags: ['config', 'genericization']
55
+ },
56
+ {
57
+ name: 'Template Processor',
58
+ description: 'Verify template variables for new config fields',
59
+ testFunction: testTemplateProcessor,
60
+ tags: ['templates', 'genericization']
61
+ }
62
+ ];
63
+ async function runGenericizationTests(testCase) {
64
+ return await testCase.testFunction();
65
+ }
66
+ (0, test_utils_1.runTests)(testCases, runGenericizationTests, 'FRAIM Genericization');