fraim-framework 2.0.30 → 2.0.33

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 (67) hide show
  1. package/dist/src/cli/commands/init.js +29 -2
  2. package/dist/src/cli/commands/sync.js +18 -1
  3. package/dist/src/utils/script-sync-utils.js +218 -0
  4. package/dist/tests/debug-tools.js +6 -5
  5. package/dist/tests/test-chalk-regression.js +58 -8
  6. package/dist/tests/test-cli.js +70 -5
  7. package/dist/tests/test-end-to-end-hybrid-validation.js +349 -0
  8. package/dist/tests/test-first-run-journey.js +43 -3
  9. package/dist/tests/test-hybrid-script-execution.js +369 -0
  10. package/dist/tests/test-mcp-connection.js +2 -2
  11. package/dist/tests/test-mcp-issue-integration.js +12 -4
  12. package/dist/tests/test-mcp-lifecycle-methods.js +4 -4
  13. package/dist/tests/test-node-compatibility.js +24 -2
  14. package/dist/tests/test-prep-issue.js +4 -1
  15. package/dist/tests/test-script-location-independence.js +173 -0
  16. package/dist/tests/test-script-sync.js +557 -0
  17. package/dist/tests/test-session-rehydration.js +2 -2
  18. package/dist/tests/test-standalone.js +3 -3
  19. package/dist/tests/test-sync-version-update.js +1 -1
  20. package/dist/tests/test-telemetry.js +2 -2
  21. package/dist/tests/test-user-journey.js +8 -4
  22. package/dist/tests/test-utils.js +13 -0
  23. package/dist/tests/test-wizard.js +2 -2
  24. package/package.json +3 -3
  25. package/registry/rules/agent-testing-guidelines.md +502 -502
  26. package/registry/rules/ephemeral-execution.md +37 -27
  27. package/registry/rules/local-development.md +253 -251
  28. package/registry/rules/successful-debugging-patterns.md +491 -482
  29. package/registry/scripts/prep-issue.sh +468 -468
  30. package/registry/workflows/bootstrap/evaluate-code-quality.md +8 -2
  31. package/registry/workflows/bootstrap/verify-test-coverage.md +8 -2
  32. package/registry/workflows/customer-development/thank-customers.md +203 -193
  33. package/registry/workflows/customer-development/weekly-newsletter.md +366 -362
  34. package/registry/workflows/performance/analyze-performance.md +65 -63
  35. package/registry/workflows/product-building/implement.md +6 -2
  36. package/registry/workflows/product-building/prep-issue.md +11 -24
  37. package/registry/workflows/product-building/resolve.md +5 -1
  38. package/registry/workflows/replicate/replicate-discovery.md +336 -0
  39. package/registry/workflows/replicate/replicate-to-issues.md +319 -0
  40. package/registry/workflows/reviewer/review-implementation-vs-design-spec.md +632 -632
  41. package/.windsurf/rules/windsurf-rules.md +0 -7
  42. package/.windsurf/workflows/resolve-issue.md +0 -6
  43. package/.windsurf/workflows/retrospect.md +0 -6
  44. package/.windsurf/workflows/start-design.md +0 -6
  45. package/.windsurf/workflows/start-impl.md +0 -6
  46. package/.windsurf/workflows/start-spec.md +0 -6
  47. package/.windsurf/workflows/start-tests.md +0 -6
  48. package/bin/fraim.js +0 -23
  49. package/registry/scripts/build-scripts-generator.ts +0 -216
  50. package/registry/scripts/cleanup-branch.ts +0 -303
  51. package/registry/scripts/fraim-config.ts +0 -63
  52. package/registry/scripts/generate-engagement-emails.ts +0 -744
  53. package/registry/scripts/generic-issues-api.ts +0 -110
  54. package/registry/scripts/newsletter-helpers.ts +0 -874
  55. package/registry/scripts/openapi-generator.ts +0 -695
  56. package/registry/scripts/performance/profile-server.ts +0 -370
  57. package/registry/scripts/run-thank-you-workflow.ts +0 -122
  58. package/registry/scripts/send-newsletter-simple.ts +0 -104
  59. package/registry/scripts/send-thank-you-emails.ts +0 -57
  60. package/registry/workflows/replicate/re-implementation-strategy.md +0 -226
  61. package/registry/workflows/replicate/use-case-extraction.md +0 -135
  62. package/registry/workflows/replicate/visual-analysis.md +0 -154
  63. package/registry/workflows/replicate/website-discovery-analysis.md +0 -231
  64. package/sample_package.json +0 -18
  65. /package/registry/scripts/{replicate/comprehensive-explorer.py → comprehensive-explorer.py} +0 -0
  66. /package/registry/scripts/{replicate/interactive-explorer.py → interactive-explorer.py} +0 -0
  67. /package/registry/scripts/{replicate/scrape-site.py → scrape-site.py} +0 -0
@@ -13,6 +13,7 @@ const sync_1 = require("./sync");
13
13
  const types_1 = require("../../fraim/types");
14
14
  const git_utils_1 = require("../../utils/git-utils");
15
15
  const version_utils_1 = require("../../utils/version-utils");
16
+ const script_sync_utils_1 = require("../../utils/script-sync-utils");
16
17
  const runInit = async () => {
17
18
  const projectRoot = process.cwd();
18
19
  const fraimDir = path_1.default.join(projectRoot, '.fraim');
@@ -27,17 +28,23 @@ const runInit = async () => {
27
28
  }
28
29
  if (!fs_1.default.existsSync(configPath)) {
29
30
  const remoteInfo = (0, git_utils_1.getGitRemoteInfo)();
31
+ if (!remoteInfo.repo) {
32
+ console.log(chalk_1.default.red('❌ Error: No git remote found. FRAIM requires a git repository with remote origin.'));
33
+ console.log(chalk_1.default.yellow('💡 Make sure you\'re in a git repository and have set up a remote origin:'));
34
+ console.log(chalk_1.default.gray(' git remote add origin <your-repo-url>'));
35
+ process.exit(1);
36
+ }
30
37
  const config = {
31
38
  ...types_1.DEFAULT_FRAIM_CONFIG,
32
39
  version: (0, version_utils_1.getFraimVersion)(),
33
40
  project: {
34
41
  ...types_1.DEFAULT_FRAIM_CONFIG.project,
35
- name: path_1.default.basename(projectRoot)
42
+ name: remoteInfo.repo
36
43
  },
37
44
  git: {
38
45
  ...types_1.DEFAULT_FRAIM_CONFIG.git,
39
46
  repoOwner: remoteInfo.owner || types_1.DEFAULT_FRAIM_CONFIG.git.repoOwner,
40
- repoName: remoteInfo.repo || types_1.DEFAULT_FRAIM_CONFIG.git.repoName
47
+ repoName: remoteInfo.repo
41
48
  }
42
49
  };
43
50
  fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
@@ -54,6 +61,26 @@ const runInit = async () => {
54
61
  console.log(chalk_1.default.blue('\n🎉 FRAIM initialized successfully!'));
55
62
  // Sync workflows from registry
56
63
  await (0, sync_1.runSync)({});
64
+ // Sync scripts to user directory
65
+ console.log(chalk_1.default.blue('🔄 Syncing FRAIM scripts to user directory...'));
66
+ // Find registry path (same logic as sync command)
67
+ let registryPath = path_1.default.join(__dirname, '../../../../registry');
68
+ if (!fs_1.default.existsSync(registryPath)) {
69
+ registryPath = path_1.default.join(__dirname, '../../../registry');
70
+ }
71
+ if (!fs_1.default.existsSync(registryPath)) {
72
+ registryPath = path_1.default.join(projectRoot, 'registry');
73
+ }
74
+ if (fs_1.default.existsSync(registryPath)) {
75
+ const syncResult = (0, script_sync_utils_1.syncScriptsToUserDirectory)(registryPath);
76
+ console.log(chalk_1.default.green(`✅ Synced ${syncResult.synced} self-contained scripts to user directory.`));
77
+ if (syncResult.ephemeral > 0) {
78
+ console.log(chalk_1.default.gray(` ${syncResult.ephemeral} dependent scripts will use ephemeral execution.`));
79
+ }
80
+ }
81
+ else {
82
+ console.log(chalk_1.default.yellow('⚠️ Registry not found, skipping script sync.'));
83
+ }
57
84
  // Trigger First Run Experience
58
85
  await (0, first_run_1.runFirstRunExperience)();
59
86
  process.exit(0);
@@ -12,6 +12,7 @@ const digest_utils_1 = require("../../utils/digest-utils");
12
12
  const stub_generator_1 = require("../../utils/stub-generator");
13
13
  const config_loader_1 = require("../../fraim/config-loader");
14
14
  const version_utils_1 = require("../../utils/version-utils");
15
+ const script_sync_utils_1 = require("../../utils/script-sync-utils");
15
16
  const runSync = async (options) => {
16
17
  const projectRoot = process.cwd();
17
18
  const config = (0, config_loader_1.loadFraimConfig)();
@@ -134,7 +135,23 @@ const runSync = async (options) => {
134
135
  }
135
136
  }
136
137
  fs_1.default.writeFileSync(digestPath, currentDigest);
137
- console.log(chalk_1.default.green(`\n✅ Sync complete. Generated ${generatedStubs.length} stubs.`));
138
+ console.log(chalk_1.default.green(`\n✅ Workflow sync complete. Generated ${generatedStubs.length} stubs.`));
139
+ // Sync scripts to user directory
140
+ console.log(chalk_1.default.blue('\n🔄 Syncing FRAIM scripts to user directory...'));
141
+ const syncResult = (0, script_sync_utils_1.syncScriptsToUserDirectory)(registryPath);
142
+ const cleanedScriptCount = (0, script_sync_utils_1.cleanupObsoleteUserScripts)(registryPath);
143
+ if (syncResult.synced > 0 || cleanedScriptCount > 0) {
144
+ console.log(chalk_1.default.green(`✅ Script sync complete. Updated ${syncResult.synced} self-contained scripts, removed ${cleanedScriptCount} obsolete scripts.`));
145
+ if (syncResult.ephemeral > 0) {
146
+ console.log(chalk_1.default.gray(` ${syncResult.ephemeral} dependent scripts will use ephemeral execution.`));
147
+ }
148
+ }
149
+ else {
150
+ console.log(chalk_1.default.green('✅ Scripts are already in sync.'));
151
+ if (syncResult.ephemeral > 0) {
152
+ console.log(chalk_1.default.gray(` ${syncResult.ephemeral} dependent scripts will use ephemeral execution.`));
153
+ }
154
+ }
138
155
  };
139
156
  exports.runSync = runSync;
140
157
  exports.syncCommand = new commander_1.Command('sync')
@@ -0,0 +1,218 @@
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.getUserFraimDir = getUserFraimDir;
7
+ exports.getUserScriptsDir = getUserScriptsDir;
8
+ exports.ensureUserFraimDirectories = ensureUserFraimDirectories;
9
+ exports.isScriptSelfContained = isScriptSelfContained;
10
+ exports.getRegistryScripts = getRegistryScripts;
11
+ exports.copyScriptToUserDirectory = copyScriptToUserDirectory;
12
+ exports.syncScriptsToUserDirectory = syncScriptsToUserDirectory;
13
+ exports.cleanupObsoleteUserScripts = cleanupObsoleteUserScripts;
14
+ exports.getUserScriptPath = getUserScriptPath;
15
+ exports.userScriptExists = userScriptExists;
16
+ exports.getDependentRegistryScripts = getDependentRegistryScripts;
17
+ exports.validateUserScripts = validateUserScripts;
18
+ const fs_1 = __importDefault(require("fs"));
19
+ const path_1 = __importDefault(require("path"));
20
+ const os_1 = __importDefault(require("os"));
21
+ const chalk_1 = __importDefault(require("chalk"));
22
+ /**
23
+ * Get the user-level FRAIM directory path
24
+ * Can be overridden with FRAIM_USER_DIR environment variable for testing
25
+ */
26
+ function getUserFraimDir() {
27
+ return process.env.FRAIM_USER_DIR || path_1.default.join(os_1.default.homedir(), '.fraim');
28
+ }
29
+ /**
30
+ * Get the user-level scripts directory path
31
+ */
32
+ function getUserScriptsDir() {
33
+ return path_1.default.join(getUserFraimDir(), 'scripts');
34
+ }
35
+ /**
36
+ * Ensure the user-level FRAIM directories exist
37
+ */
38
+ function ensureUserFraimDirectories() {
39
+ const userFraimDir = getUserFraimDir();
40
+ const userScriptsDir = getUserScriptsDir();
41
+ if (!fs_1.default.existsSync(userFraimDir)) {
42
+ fs_1.default.mkdirSync(userFraimDir, { recursive: true });
43
+ }
44
+ if (!fs_1.default.existsSync(userScriptsDir)) {
45
+ fs_1.default.mkdirSync(userScriptsDir, { recursive: true });
46
+ }
47
+ }
48
+ /**
49
+ * Check if a script is self-contained (no relative imports to FRAIM source)
50
+ */
51
+ function isScriptSelfContained(scriptPath) {
52
+ // Shell scripts are always considered self-contained
53
+ if (scriptPath.endsWith('.sh')) {
54
+ return true;
55
+ }
56
+ // For TypeScript/JavaScript files, check for relative imports to src/
57
+ if (scriptPath.endsWith('.ts') || scriptPath.endsWith('.js')) {
58
+ try {
59
+ const content = fs_1.default.readFileSync(scriptPath, 'utf-8');
60
+ // Look for imports that reference ../../src/ or similar patterns
61
+ const hasRelativeImports = /import.*['"]\.\.\/.*src\//.test(content) ||
62
+ /require\(['"]\.\.\/.*src\//.test(content) ||
63
+ /from ['"]\.\.\/.*src\//.test(content);
64
+ return !hasRelativeImports;
65
+ }
66
+ catch (error) {
67
+ // If we can't read the file, assume it's not self-contained
68
+ return false;
69
+ }
70
+ }
71
+ return false;
72
+ }
73
+ /**
74
+ * Get all self-contained script files from the registry
75
+ */
76
+ function getRegistryScripts(registryPath) {
77
+ const scriptsPath = path_1.default.join(registryPath, 'scripts');
78
+ if (!fs_1.default.existsSync(scriptsPath)) {
79
+ return [];
80
+ }
81
+ const entries = fs_1.default.readdirSync(scriptsPath, { withFileTypes: true });
82
+ const allScripts = entries
83
+ .filter(entry => entry.isFile() && (entry.name.endsWith('.sh') || entry.name.endsWith('.ts') || entry.name.endsWith('.js')))
84
+ .map(entry => path_1.default.join(scriptsPath, entry.name));
85
+ // Only return self-contained scripts
86
+ return allScripts.filter(scriptPath => isScriptSelfContained(scriptPath));
87
+ }
88
+ /**
89
+ * Copy a script file to the user scripts directory with proper permissions
90
+ */
91
+ function copyScriptToUserDirectory(sourcePath, scriptName) {
92
+ const userScriptsDir = getUserScriptsDir();
93
+ const targetPath = path_1.default.join(userScriptsDir, scriptName);
94
+ // Copy the file
95
+ fs_1.default.copyFileSync(sourcePath, targetPath);
96
+ // Set executable permissions on Unix systems
97
+ if (process.platform !== 'win32' && scriptName.endsWith('.sh')) {
98
+ try {
99
+ fs_1.default.chmodSync(targetPath, 0o755);
100
+ }
101
+ catch (error) {
102
+ console.warn(chalk_1.default.yellow(`⚠️ Could not set executable permissions for ${scriptName}`));
103
+ }
104
+ }
105
+ }
106
+ /**
107
+ * Sync all scripts from registry to user directory
108
+ */
109
+ function syncScriptsToUserDirectory(registryPath) {
110
+ ensureUserFraimDirectories();
111
+ const selfContainedScripts = getRegistryScripts(registryPath);
112
+ const dependentScripts = getDependentRegistryScripts(registryPath);
113
+ let syncedCount = 0;
114
+ console.log(chalk_1.default.gray(` Self-contained scripts (will be synced to ~/.fraim/scripts/):`));
115
+ for (const scriptPath of selfContainedScripts) {
116
+ const scriptName = path_1.default.basename(scriptPath);
117
+ const userScriptPath = path_1.default.join(getUserScriptsDir(), scriptName);
118
+ try {
119
+ // Check if script needs updating
120
+ let needsUpdate = true;
121
+ if (fs_1.default.existsSync(userScriptPath)) {
122
+ const registryContent = fs_1.default.readFileSync(scriptPath, 'utf-8');
123
+ const userContent = fs_1.default.readFileSync(userScriptPath, 'utf-8');
124
+ needsUpdate = registryContent !== userContent;
125
+ }
126
+ if (needsUpdate) {
127
+ copyScriptToUserDirectory(scriptPath, scriptName);
128
+ syncedCount++;
129
+ console.log(chalk_1.default.gray(` + ${scriptName}`));
130
+ }
131
+ else {
132
+ console.log(chalk_1.default.gray(` = ${scriptName} (up to date)`));
133
+ }
134
+ }
135
+ catch (error) {
136
+ console.warn(chalk_1.default.yellow(`⚠️ Could not sync script ${scriptName}: ${error}`));
137
+ }
138
+ }
139
+ if (dependentScripts.length > 0) {
140
+ console.log(chalk_1.default.gray(` Dependent scripts (will use ephemeral execution):`));
141
+ for (const scriptPath of dependentScripts) {
142
+ const scriptName = path_1.default.basename(scriptPath);
143
+ console.log(chalk_1.default.gray(` ~ ${scriptName} (has FRAIM dependencies)`));
144
+ }
145
+ }
146
+ return { synced: syncedCount, ephemeral: dependentScripts.length };
147
+ }
148
+ /**
149
+ * Clean up scripts in user directory that no longer exist in registry
150
+ */
151
+ function cleanupObsoleteUserScripts(registryPath) {
152
+ const userScriptsDir = getUserScriptsDir();
153
+ if (!fs_1.default.existsSync(userScriptsDir)) {
154
+ return 0;
155
+ }
156
+ const registryScripts = getRegistryScripts(registryPath);
157
+ const registryScriptNames = registryScripts.map(scriptPath => path_1.default.basename(scriptPath));
158
+ const userScripts = fs_1.default.readdirSync(userScriptsDir)
159
+ .filter(file => file.endsWith('.sh') || file.endsWith('.ts') || file.endsWith('.js'));
160
+ let cleanedCount = 0;
161
+ for (const userScript of userScripts) {
162
+ if (!registryScriptNames.includes(userScript)) {
163
+ try {
164
+ const userScriptPath = path_1.default.join(userScriptsDir, userScript);
165
+ fs_1.default.unlinkSync(userScriptPath);
166
+ cleanedCount++;
167
+ console.log(chalk_1.default.yellow(` - ${userScript} (removed from registry)`));
168
+ }
169
+ catch (error) {
170
+ console.warn(chalk_1.default.yellow(`⚠️ Could not remove obsolete script ${userScript}: ${error}`));
171
+ }
172
+ }
173
+ }
174
+ return cleanedCount;
175
+ }
176
+ /**
177
+ * Get the path to a script in the user directory
178
+ */
179
+ function getUserScriptPath(scriptName) {
180
+ return path_1.default.join(getUserScriptsDir(), scriptName);
181
+ }
182
+ /**
183
+ * Check if a script exists in the user directory
184
+ */
185
+ function userScriptExists(scriptName) {
186
+ return fs_1.default.existsSync(getUserScriptPath(scriptName));
187
+ }
188
+ /**
189
+ * Get all dependent script files from the registry (those with FRAIM source dependencies)
190
+ */
191
+ function getDependentRegistryScripts(registryPath) {
192
+ const scriptsPath = path_1.default.join(registryPath, 'scripts');
193
+ if (!fs_1.default.existsSync(scriptsPath)) {
194
+ return [];
195
+ }
196
+ const entries = fs_1.default.readdirSync(scriptsPath, { withFileTypes: true });
197
+ const allScripts = entries
198
+ .filter(entry => entry.isFile() && (entry.name.endsWith('.sh') || entry.name.endsWith('.ts') || entry.name.endsWith('.js')))
199
+ .map(entry => path_1.default.join(scriptsPath, entry.name));
200
+ // Only return scripts that have dependencies
201
+ return allScripts.filter(scriptPath => !isScriptSelfContained(scriptPath));
202
+ }
203
+ /**
204
+ * Validate that all expected scripts are present in user directory
205
+ */
206
+ function validateUserScripts(expectedScripts) {
207
+ const missing = [];
208
+ const present = [];
209
+ for (const scriptName of expectedScripts) {
210
+ if (userScriptExists(scriptName)) {
211
+ present.push(scriptName);
212
+ }
213
+ else {
214
+ missing.push(scriptName);
215
+ }
216
+ }
217
+ return { missing, present };
218
+ }
@@ -13,7 +13,7 @@ async function debugListTools() {
13
13
  console.log(' 🔍 Debugging Available Tools...');
14
14
  let fraimProcess;
15
15
  let dbService;
16
- const PORT = 10003;
16
+ const PORT = Math.floor(Math.random() * 1000) + 10000; // Random port to avoid conflicts
17
17
  const TEST_API_KEY = 'debug-tools-key';
18
18
  const BASE_URL = `http://localhost:${PORT}`;
19
19
  try {
@@ -24,8 +24,8 @@ async function debugListTools() {
24
24
  await db.collection('fraim_api_keys').updateOne({ key: TEST_API_KEY }, { $set: { userId: 'debug@test.com', orgId: 'debug-org', isActive: true, createdAt: new Date() } }, { upsert: true });
25
25
  // 2. Start server
26
26
  const npxCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
27
- const serverScript = path_1.default.resolve(__dirname, '../src/fraim-mcp-server.ts');
28
- fraimProcess = (0, node_child_process_1.spawn)(npxCommand, ['tsx', `"${serverScript}"`], {
27
+ const serverScript = path_1.default.resolve(__dirname, '../dist/src/fraim-mcp-server.js');
28
+ fraimProcess = (0, node_child_process_1.spawn)(npxCommand, ['node', `"${serverScript}"`], {
29
29
  env: { ...process.env, FRAIM_MCP_PORT: PORT.toString(), FRAIM_SKIP_INDEX_ON_START: 'true' },
30
30
  shell: true
31
31
  });
@@ -65,8 +65,9 @@ async function debugListTools() {
65
65
  finally {
66
66
  if (dbService)
67
67
  await dbService.close().catch(() => { });
68
- if (fraimProcess && fraimProcess.pid)
69
- (0, tree_kill_1.default)(fraimProcess.pid);
68
+ if (fraimProcess && fraimProcess.pid) {
69
+ await new Promise((resolve) => (0, tree_kill_1.default)(fraimProcess.pid, 'SIGKILL', () => resolve()));
70
+ }
70
71
  }
71
72
  }
72
73
  async function runTest(testCase) {
@@ -41,10 +41,21 @@ async function testChalkConflictScenario() {
41
41
  console.log(' This ensures pinned chalk v4 works even with chalk v5 present\n');
42
42
  const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'chalk-regression-'));
43
43
  console.log(` 📂 Temp dir: ${tempDir}`);
44
+ let tarballPath = null;
44
45
  try {
45
46
  // 1. Pack fraim-framework
46
47
  console.log(' 📦 Packing fraim-framework...');
47
48
  const projectRoot = process.cwd();
49
+ // Clean up any existing tarballs first
50
+ const existingTarballs = fs_1.default.readdirSync(projectRoot).filter(f => f.startsWith('fraim-framework-') && f.endsWith('.tgz'));
51
+ for (const tarball of existingTarballs) {
52
+ try {
53
+ fs_1.default.unlinkSync(path_1.default.join(projectRoot, tarball));
54
+ }
55
+ catch (e) {
56
+ // Ignore cleanup errors
57
+ }
58
+ }
48
59
  const packResult = (0, node_child_process_1.execSync)('npm pack', {
49
60
  cwd: projectRoot,
50
61
  encoding: 'utf-8'
@@ -54,7 +65,7 @@ async function testChalkConflictScenario() {
54
65
  console.log(' ❌ Failed to create tarball');
55
66
  return false;
56
67
  }
57
- const tarballPath = path_1.default.join(projectRoot, tarballName);
68
+ tarballPath = path_1.default.join(projectRoot, tarballName);
58
69
  console.log(` ✅ Created: ${tarballName}`);
59
70
  // 2. Create test project with chalk v5
60
71
  console.log(' 📝 Creating test project with chalk v5...');
@@ -161,7 +172,9 @@ try {
161
172
  console.log(' ⚠️ This might cause issues in some environments');
162
173
  console.log(' ⚠️ But CLI loaded successfully, so test passes');
163
174
  }
164
- fs_1.default.unlinkSync(tarballPath);
175
+ if (tarballPath && fs_1.default.existsSync(tarballPath)) {
176
+ fs_1.default.unlinkSync(tarballPath);
177
+ }
165
178
  return true;
166
179
  }
167
180
  else if (hasERR_REQUIRE_ESM) {
@@ -173,19 +186,31 @@ try {
173
186
  else {
174
187
  console.log(' ❌ fraim-framework has chalk v4 but still got error (unexpected)');
175
188
  }
176
- fs_1.default.unlinkSync(tarballPath);
189
+ if (tarballPath && fs_1.default.existsSync(tarballPath)) {
190
+ fs_1.default.unlinkSync(tarballPath);
191
+ }
177
192
  return false;
178
193
  }
179
194
  else {
180
195
  console.log(' ❌ Got unexpected error (exit code: ' + testResult.code + ')');
181
196
  console.log(' ❌ Check the error output above');
182
- fs_1.default.unlinkSync(tarballPath);
197
+ if (tarballPath && fs_1.default.existsSync(tarballPath)) {
198
+ fs_1.default.unlinkSync(tarballPath);
199
+ }
183
200
  return false;
184
201
  }
185
202
  }
186
203
  catch (error) {
187
204
  console.error(' ❌ Conflict scenario test failed with exception:', error);
188
205
  console.error(' ❌ This is likely a test infrastructure issue, not a chalk issue');
206
+ if (tarballPath && fs_1.default.existsSync(tarballPath)) {
207
+ try {
208
+ fs_1.default.unlinkSync(tarballPath);
209
+ }
210
+ catch (e) {
211
+ // Ignore cleanup errors
212
+ }
213
+ }
189
214
  return false;
190
215
  }
191
216
  finally {
@@ -203,10 +228,21 @@ async function testFreshInstall() {
203
228
  console.log(' This simulates: npx fraim-framework init\n');
204
229
  const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'chalk-fresh-'));
205
230
  console.log(` 📂 Temp dir: ${tempDir}`);
231
+ let tarballPath = null;
206
232
  try {
207
233
  // 1. Pack fraim-framework
208
234
  console.log(' 📦 Packing fraim-framework...');
209
235
  const projectRoot = process.cwd();
236
+ // Clean up any existing tarballs first
237
+ const existingTarballs = fs_1.default.readdirSync(projectRoot).filter(f => f.startsWith('fraim-framework-') && f.endsWith('.tgz'));
238
+ for (const tarball of existingTarballs) {
239
+ try {
240
+ fs_1.default.unlinkSync(path_1.default.join(projectRoot, tarball));
241
+ }
242
+ catch (e) {
243
+ // Ignore cleanup errors
244
+ }
245
+ }
210
246
  const packResult = (0, node_child_process_1.execSync)('npm pack', {
211
247
  cwd: projectRoot,
212
248
  encoding: 'utf-8'
@@ -216,7 +252,7 @@ async function testFreshInstall() {
216
252
  console.log(' ❌ Failed to create tarball');
217
253
  return false;
218
254
  }
219
- const tarballPath = path_1.default.join(projectRoot, tarballName);
255
+ tarballPath = path_1.default.join(projectRoot, tarballName);
220
256
  console.log(` ✅ Created: ${tarballName}`);
221
257
  // 2. Create empty project
222
258
  console.log(' 📝 Creating empty project...');
@@ -277,24 +313,38 @@ try {
277
313
  const cliLoaded = testResult.stdout.includes('✓ FRAIM CLI loaded successfully') || testResult.stderr.includes('Usage: fraim');
278
314
  if (cliLoaded && !hasERR_REQUIRE_ESM) {
279
315
  console.log(' ✅ SUCCESS: Fresh install works correctly');
280
- fs_1.default.unlinkSync(tarballPath);
316
+ if (tarballPath && fs_1.default.existsSync(tarballPath)) {
317
+ fs_1.default.unlinkSync(tarballPath);
318
+ }
281
319
  return true;
282
320
  }
283
321
  else if (hasERR_REQUIRE_ESM) {
284
322
  console.log(' ❌ REGRESSION: Got ERR_REQUIRE_ESM error!');
285
- fs_1.default.unlinkSync(tarballPath);
323
+ if (tarballPath && fs_1.default.existsSync(tarballPath)) {
324
+ fs_1.default.unlinkSync(tarballPath);
325
+ }
286
326
  return false;
287
327
  }
288
328
  else {
289
329
  console.log(' ❌ Got unexpected error (exit code: ' + testResult.code + ')');
290
330
  console.log(' ❌ Check the error output above');
291
- fs_1.default.unlinkSync(tarballPath);
331
+ if (tarballPath && fs_1.default.existsSync(tarballPath)) {
332
+ fs_1.default.unlinkSync(tarballPath);
333
+ }
292
334
  return false;
293
335
  }
294
336
  }
295
337
  catch (error) {
296
338
  console.error(' ❌ Fresh install test failed with exception:', error);
297
339
  console.error(' ❌ This is likely a test infrastructure issue, not a chalk issue');
340
+ if (tarballPath && fs_1.default.existsSync(tarballPath)) {
341
+ try {
342
+ fs_1.default.unlinkSync(tarballPath);
343
+ }
344
+ catch (e) {
345
+ // Ignore cleanup errors
346
+ }
347
+ }
298
348
  return false;
299
349
  }
300
350
  finally {
@@ -42,6 +42,64 @@ const node_assert_1 = __importDefault(require("node:assert"));
42
42
  const fs_1 = __importDefault(require("fs"));
43
43
  const path_1 = __importDefault(require("path"));
44
44
  const os_1 = __importDefault(require("os"));
45
+ async function testRepoNameRequirement() {
46
+ console.log(' 🚀 Testing Repo Name Requirement...');
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-repo-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 = (0, test_utils_1.resolveProjectPath)('dist/src/cli/fraim.js');
54
+ // Helper to run CLI commands
55
+ const runFraim = (args, cwdOverride) => {
56
+ return new Promise((resolve) => {
57
+ const ps = (0, node_child_process_1.spawn)(npx, ['node', `"${cliScript}"`, ...args], {
58
+ cwd: cwdOverride || tempDir,
59
+ env: { ...process.env, TEST_MODE: 'true' },
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
+ // Test 1: Init without git should fail
72
+ console.log(' Testing init without git remote (should fail)...');
73
+ const initWithoutGit = await runFraim(['init'], tempDir);
74
+ node_assert_1.default.strictEqual(initWithoutGit.code, 1, 'Init without git remote should fail');
75
+ node_assert_1.default.ok(initWithoutGit.stdout.includes('No git remote found') || initWithoutGit.stderr.includes('No git remote found'), 'Should show git remote error');
76
+ // Test 2: Init with git remote should succeed and use repo name
77
+ console.log(' Testing init with git remote (should succeed)...');
78
+ const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
79
+ execSync('git init', { cwd: tempDir });
80
+ execSync('git remote add origin https://github.com/test-owner/my-awesome-project.git', { cwd: tempDir });
81
+ const initWithGit = await runFraim(['init'], tempDir);
82
+ node_assert_1.default.strictEqual(initWithGit.code, 0, 'Init with git remote should succeed');
83
+ const configPath = path_1.default.join(tempDir, '.fraim', 'config.json');
84
+ node_assert_1.default.ok(fs_1.default.existsSync(configPath), 'config.json should exist');
85
+ const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
86
+ node_assert_1.default.strictEqual(config.project.name, 'my-awesome-project', 'project.name should use repo name');
87
+ node_assert_1.default.strictEqual(config.git.repoName, 'my-awesome-project', 'git.repoName should match');
88
+ node_assert_1.default.strictEqual(config.git.repoOwner, 'test-owner', 'git.repoOwner should be detected');
89
+ console.log(' ✅ Repo name requirement verified!');
90
+ return true;
91
+ }
92
+ catch (error) {
93
+ console.error(' ❌ Repo name test failed:', error);
94
+ return false;
95
+ }
96
+ finally {
97
+ try {
98
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
99
+ }
100
+ catch (e) { }
101
+ }
102
+ }
45
103
  async function testCliLifecycle() {
46
104
  console.log(' 🚀 Testing Fraim CLI Lifecycle...');
47
105
  // Create a temp directory for the test project
@@ -50,11 +108,11 @@ async function testCliLifecycle() {
50
108
  try {
51
109
  const platform = process.platform;
52
110
  const npx = platform === 'win32' ? 'npx.cmd' : 'npx';
53
- const cliScript = path_1.default.resolve(__dirname, '../src/cli/fraim.ts');
111
+ const cliScript = (0, test_utils_1.resolveProjectPath)('dist/src/cli/fraim.js');
54
112
  // Helper to run CLI commands
55
113
  const runFraim = (args, cwdOverride) => {
56
- return new Promise((resolve, reject) => {
57
- const ps = (0, node_child_process_1.spawn)(npx, ['tsx', `"${cliScript}"`, ...args], {
114
+ return new Promise((resolve) => {
115
+ const ps = (0, node_child_process_1.spawn)(npx, ['node', `"${cliScript}"`, ...args], {
58
116
  cwd: cwdOverride || tempDir,
59
117
  env: { ...process.env, TEST_MODE: 'true' }, // Disable interactive first-run
60
118
  shell: true
@@ -102,11 +160,12 @@ async function testCliLifecycle() {
102
160
  if (hasGit) {
103
161
  node_assert_1.default.strictEqual(config.git.repoOwner, 'test-owner', 'repoOwner should be detected from git');
104
162
  node_assert_1.default.strictEqual(config.git.repoName, 'test-repo', 'repoName should be detected from git');
163
+ node_assert_1.default.strictEqual(config.project.name, 'test-repo', 'project.name should use repo name, not folder name');
105
164
  }
106
165
  else {
107
166
  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');
167
+ // Without git remote, init should fail with our new logic
168
+ console.warn(' ⚠️ Init should have failed without git remote in new logic');
110
169
  }
111
170
  node_assert_1.default.ok(config.customizations, 'config.customizations should exist');
112
171
  node_assert_1.default.strictEqual(config.customizations.workflowsPath, '.fraim/workflows');
@@ -157,6 +216,12 @@ async function runCliTest(testCase) {
157
216
  return await testCase.testFunction();
158
217
  }
159
218
  const testCases = [
219
+ {
220
+ name: 'Repo Name Requirement',
221
+ description: 'Tests that init requires git remote and uses repo name for project.name',
222
+ testFunction: testRepoNameRequirement,
223
+ tags: ['cli', 'fraim', 'git']
224
+ },
160
225
  {
161
226
  name: 'Fraim CLI Lifecycle',
162
227
  description: 'Tests init, sync, list, and doctor commands',