fraim-framework 2.0.24 → 2.0.27

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 (37) hide show
  1. package/.github/workflows/deploy-fraim.yml +3 -1
  2. package/dist/src/cli/commands/init.js +2 -0
  3. package/dist/src/cli/commands/sync.js +17 -0
  4. package/dist/src/fraim/config-loader.js +11 -11
  5. package/dist/src/fraim/setup-wizard.js +1 -42
  6. package/dist/src/fraim-mcp-server.js +22 -11
  7. package/dist/src/utils/git-utils.js +1 -1
  8. package/dist/src/utils/version-utils.js +32 -0
  9. package/dist/tests/test-cli.js +167 -0
  10. package/dist/tests/test-first-run-journey.js +108 -0
  11. package/dist/tests/test-genericization.js +64 -0
  12. package/{test-prep-issue.ts → dist/tests/test-prep-issue.js} +93 -101
  13. package/{test-standalone.ts → dist/tests/test-standalone.js} +149 -161
  14. package/dist/tests/test-sync-version-update.js +93 -0
  15. package/dist/tests/test-user-journey.js +231 -0
  16. package/dist/tests/test-utils.js +96 -0
  17. package/{test-wizard.ts → dist/tests/test-wizard.js} +71 -81
  18. package/package.json +9 -5
  19. package/registry/rules/architecture.md +1 -1
  20. package/registry/scripts/code-quality-check.sh +5 -4
  21. package/registry/scripts/evaluate-code-quality.ts +36 -0
  22. package/registry/scripts/{validate-coverage.ts → validate-test-coverage.ts} +39 -39
  23. package/registry/scripts/verify-test-coverage.ts +36 -0
  24. package/registry/templates/bootstrap/ARCHITECTURE-TEMPLATE.md +53 -0
  25. package/registry/templates/bootstrap/CODE-QUALITY-REPORT-TEMPLATE.md +37 -0
  26. package/registry/templates/bootstrap/TEST-COVERAGE-REPORT-TEMPLATE.md +35 -0
  27. package/registry/templates/business-development/PRICING-STRATEGY-TEMPLATE.md +126 -0
  28. package/registry/workflows/bootstrap/create-architecture.md +13 -12
  29. package/registry/workflows/bootstrap/evaluate-code-quality.md +30 -0
  30. package/registry/workflows/bootstrap/verify-test-coverage.md +31 -0
  31. package/registry/workflows/business-development/price-product.md +325 -0
  32. package/tsconfig.json +4 -4
  33. package/test-cli.ts +0 -155
  34. package/test-first-run-journey.ts +0 -122
  35. package/test-genericization.ts +0 -74
  36. package/test-user-journey.ts +0 -244
  37. 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/
@@ -12,6 +12,7 @@ const first_run_js_1 = require("../setup/first-run.js");
12
12
  const sync_js_1 = require("./sync.js");
13
13
  const types_js_1 = require("../../fraim/types.js");
14
14
  const git_utils_js_1 = require("../../utils/git-utils.js");
15
+ const version_utils_js_1 = require("../../utils/version-utils.js");
15
16
  const runInit = async () => {
16
17
  const projectRoot = process.cwd();
17
18
  const fraimDir = path_1.default.join(projectRoot, '.fraim');
@@ -28,6 +29,7 @@ const runInit = async () => {
28
29
  const remoteInfo = (0, git_utils_js_1.getGitRemoteInfo)();
29
30
  const config = {
30
31
  ...types_js_1.DEFAULT_FRAIM_CONFIG,
32
+ version: (0, version_utils_js_1.getFraimVersion)(),
31
33
  project: {
32
34
  ...types_js_1.DEFAULT_FRAIM_CONFIG.project,
33
35
  name: path_1.default.basename(projectRoot)
@@ -11,6 +11,7 @@ const chalk_1 = __importDefault(require("chalk"));
11
11
  const digest_utils_1 = require("../../utils/digest-utils");
12
12
  const stub_generator_1 = require("../../utils/stub-generator");
13
13
  const config_loader_js_1 = require("../../fraim/config-loader.js");
14
+ const version_utils_js_1 = require("../../utils/version-utils.js");
14
15
  const runSync = async (options) => {
15
16
  const projectRoot = process.cwd();
16
17
  const config = (0, config_loader_js_1.loadFraimConfig)();
@@ -37,6 +38,22 @@ const runSync = async (options) => {
37
38
  if (!fs_1.default.existsSync(workflowsDir)) {
38
39
  fs_1.default.mkdirSync(workflowsDir, { recursive: true });
39
40
  }
41
+ // Update version in config.json
42
+ const configPath = path_1.default.join(fraimDir, 'config.json');
43
+ if (fs_1.default.existsSync(configPath)) {
44
+ try {
45
+ const currentConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
46
+ const newVersion = (0, version_utils_js_1.getFraimVersion)();
47
+ if (currentConfig.version !== newVersion) {
48
+ currentConfig.version = newVersion;
49
+ fs_1.default.writeFileSync(configPath, JSON.stringify(currentConfig, null, 2));
50
+ console.log(chalk_1.default.green(`✅ Updated FRAIM version to ${newVersion} in config.`));
51
+ }
52
+ }
53
+ catch (e) {
54
+ console.warn(chalk_1.default.yellow('⚠️ Could not update version in config.json.'));
55
+ }
56
+ }
40
57
  console.log(chalk_1.default.blue('🔄 Syncing FRAIM workflows...'));
41
58
  const currentDigest = await (0, digest_utils_1.generateDigest)(registryPath);
42
59
  const existingDigest = fs_1.default.existsSync(digestPath) ? fs_1.default.readFileSync(digestPath, 'utf8') : '';
@@ -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,43 @@ 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
41
  persona: {
42
- ...types_js_1.DEFAULT_FRAIM_CONFIG.persona,
42
+ ...types_1.DEFAULT_FRAIM_CONFIG.persona,
43
43
  ...(config.persona || {})
44
44
  },
45
45
  marketing: {
46
- ...types_js_1.DEFAULT_FRAIM_CONFIG.marketing,
46
+ ...types_1.DEFAULT_FRAIM_CONFIG.marketing,
47
47
  ...(config.marketing || {})
48
48
  },
49
49
  database: {
50
- ...types_js_1.DEFAULT_FRAIM_CONFIG.database,
50
+ ...types_1.DEFAULT_FRAIM_CONFIG.database,
51
51
  ...(config.database || {})
52
52
  }
53
53
  };
54
54
  // Validate version
55
55
  if (!mergedConfig.version) {
56
- mergedConfig.version = types_js_1.DEFAULT_FRAIM_CONFIG.version;
56
+ mergedConfig.version = types_1.DEFAULT_FRAIM_CONFIG.version;
57
57
  }
58
58
  console.log(`📋 Loaded FRAIM config from .fraim/config.json (version ${mergedConfig.version})`);
59
59
  return mergedConfig;
@@ -61,7 +61,7 @@ function loadFraimConfig() {
61
61
  catch (error) {
62
62
  console.warn(`⚠️ Failed to load .fraim/config.json: ${error instanceof Error ? error.message : 'Unknown error'}`);
63
63
  console.warn(' Using default configuration');
64
- return { ...types_js_1.DEFAULT_FRAIM_CONFIG };
64
+ return { ...types_1.DEFAULT_FRAIM_CONFIG };
65
65
  }
66
66
  }
67
67
  /**
@@ -7,8 +7,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.generateConfigFromAnswers = generateConfigFromAnswers;
8
8
  exports.writeConfig = writeConfig;
9
9
  exports.createDirectoryStructure = createDirectoryStructure;
10
- exports.generateArchitectureTemplate = generateArchitectureTemplate;
11
- exports.writeArchitectureTemplate = writeArchitectureTemplate;
12
10
  exports.runSetup = runSetup;
13
11
  const fs_1 = require("fs");
14
12
  const path_1 = require("path");
@@ -65,43 +63,6 @@ function createDirectoryStructure() {
65
63
  }
66
64
  }
67
65
  }
68
- /**
69
- * Generate architecture.md template
70
- */
71
- function generateArchitectureTemplate(config) {
72
- let template = `# Project Architecture
73
-
74
- This document describes the architecture patterns and conventions for ${config.project.name}.
75
-
76
- ## Overview
77
-
78
- - **Project Name:** ${config.project.name}
79
-
80
- ## Development Guidelines
81
-
82
- ### Git Workflow
83
- - **Default Branch:** ${config.git.defaultBranch}
84
-
85
- ## Customization
86
-
87
- This project uses FRAIM with customizations in \`.fraim/\`:
88
- - Custom rules: \`.fraim/rules/\`
89
- - Custom workflows: \`.fraim/workflows/\`
90
- - Custom templates: \`.fraim/templates/\`
91
- - Custom scripts: \`.fraim/scripts/\`
92
- `;
93
- return template;
94
- }
95
- /**
96
- * Write architecture.md template
97
- */
98
- function writeArchitectureTemplate(config) {
99
- const fraimDir = (0, path_1.join)(process.cwd(), '.fraim');
100
- const archPath = (0, path_1.join)(fraimDir, 'architecture.md');
101
- const template = generateArchitectureTemplate(config);
102
- (0, fs_1.writeFileSync)(archPath, template, 'utf-8');
103
- console.log(`✅ Created .fraim/architecture.md`);
104
- }
105
66
  /**
106
67
  * Run setup wizard
107
68
  */
@@ -121,11 +82,9 @@ function runSetup(answers) {
121
82
  createDirectoryStructure();
122
83
  // Write config
123
84
  writeConfig(config);
124
- // Write architecture template
125
- writeArchitectureTemplate(config);
126
85
  return {
127
86
  success: true,
128
- message: `✅ FRAIM setup complete!\n\nCreated:\n- .fraim/config.json\n- .fraim/architecture.md\n- Directory structure\n\nNext steps:\n1. Review .fraim/config.json and adjust as needed\n2. Add custom rules to .fraim/rules/\n3. Add custom workflows to .fraim/workflows/\n4. Customize templates in .fraim/templates/`,
87
+ message: `✅ FRAIM setup complete!\n\nCreated:\n- .fraim/config.json\n- Directory structure\n\nNext steps:\n1. Review .fraim/config.json and adjust as needed\n2. Add custom rules to .fraim/rules/\n3. Add custom workflows to .fraim/workflows/\n4. Customize templates in .fraim/templates/`,
129
88
  config
130
89
  };
131
90
  }
@@ -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,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,167 @@
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.mcp, 'mcp should not be in generated config');
114
+ // 2. Test `fraim sync`
115
+ // We need a fake registry for sync to work against
116
+ const registryDir = path_1.default.join(tempDir, 'registry', 'workflows');
117
+ fs_1.default.mkdirSync(registryDir, { recursive: true });
118
+ const dummyWorkflow = `---
119
+ intent: Test Intent
120
+ principles:
121
+ - Test Principle
122
+ ---
123
+ # Test Workflow`;
124
+ fs_1.default.writeFileSync(path_1.default.join(registryDir, 'test-workflow.md'), dummyWorkflow);
125
+ console.log(' Testing "fraim sync"...');
126
+ const syncRes = await runFraim(['sync']);
127
+ if (syncRes.code === 0) {
128
+ console.log(' ✅ Sync successful');
129
+ }
130
+ else {
131
+ console.log(' ⚠️ Sync warning (env dependent):', syncRes.stderr);
132
+ }
133
+ // 3. Test `fraim list`
134
+ console.log(' Testing "fraim list"...');
135
+ const listRes = await runFraim(['list']);
136
+ node_assert_1.default.strictEqual(listRes.code, 0);
137
+ // 4. Test `fraim doctor`
138
+ console.log(' Testing "fraim doctor"...');
139
+ const doctorRes = await runFraim(['doctor']);
140
+ node_assert_1.default.strictEqual(doctorRes.code, 0);
141
+ node_assert_1.default.ok(doctorRes.stdout.includes('Everything looks great'), 'Doctor should be happy');
142
+ console.log(' ✅ CLI Lifecycle verified!');
143
+ return true;
144
+ }
145
+ catch (error) {
146
+ console.error(' ❌ CLI Test failed:', error);
147
+ return false;
148
+ }
149
+ finally {
150
+ try {
151
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
152
+ }
153
+ catch (e) { }
154
+ }
155
+ }
156
+ async function runCliTest(testCase) {
157
+ return await testCase.testFunction();
158
+ }
159
+ const testCases = [
160
+ {
161
+ name: 'Fraim CLI Lifecycle',
162
+ description: 'Tests init, sync, list, and doctor commands',
163
+ testFunction: testCliLifecycle,
164
+ tags: ['cli', 'fraim']
165
+ },
166
+ ];
167
+ (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,64 @@
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.mcp, 'mcp should still be removed');
16
+ return true;
17
+ }
18
+ catch (e) {
19
+ console.error(' ❌ Default configuration verification failed:', e);
20
+ return false;
21
+ }
22
+ }
23
+ async function testTemplateProcessor() {
24
+ console.log(' 🧪 Testing Template Processor with new config fields...');
25
+ try {
26
+ const mockConfig = {
27
+ ...types_1.DEFAULT_FRAIM_CONFIG,
28
+ project: { ...types_1.DEFAULT_FRAIM_CONFIG.project, name: 'Generic Bot' },
29
+ persona: { ...types_1.DEFAULT_FRAIM_CONFIG.persona, name: 'Ava' },
30
+ marketing: {
31
+ ...types_1.DEFAULT_FRAIM_CONFIG.marketing,
32
+ websiteUrl: 'https://ava.ai',
33
+ chatUrl: 'https://chat.ava.ai'
34
+ }
35
+ };
36
+ const template = 'Welcome to {{config.project.name}}. I am {{config.persona.name}}. Visit {{config.marketing.websiteUrl}} or chat at {{config.marketing.chatUrl}}.';
37
+ const processed = (0, template_processor_1.processTemplateVariables)(template, mockConfig);
38
+ const expected = 'Welcome to Generic Bot. I am Ava. Visit https://ava.ai or chat at https://chat.ava.ai.';
39
+ node_assert_1.default.strictEqual(processed, expected, 'Template variables should be correctly replaced');
40
+ return true;
41
+ }
42
+ catch (e) {
43
+ console.error(' ❌ Template processing verification failed:', e);
44
+ return false;
45
+ }
46
+ }
47
+ const testCases = [
48
+ {
49
+ name: 'Config Defaults',
50
+ description: 'Verify DEFAULT_FRAIM_CONFIG has empty placeholders',
51
+ testFunction: testConfigDefaults,
52
+ tags: ['config', 'genericization']
53
+ },
54
+ {
55
+ name: 'Template Processor',
56
+ description: 'Verify template variables for new config fields',
57
+ testFunction: testTemplateProcessor,
58
+ tags: ['templates', 'genericization']
59
+ }
60
+ ];
61
+ async function runGenericizationTests(testCase) {
62
+ return await testCase.testFunction();
63
+ }
64
+ (0, test_utils_1.runTests)(testCases, runGenericizationTests, 'FRAIM Genericization');