fraim-framework 2.0.30 → 2.0.34

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/bin/fraim.js +3 -18
  2. package/dist/src/cli/commands/init.js +29 -2
  3. package/dist/src/cli/commands/sync.js +82 -70
  4. package/dist/src/utils/script-sync-utils.js +216 -0
  5. package/dist/tests/debug-tools.js +6 -5
  6. package/dist/tests/test-chalk-regression.js +58 -8
  7. package/dist/tests/test-cli.js +70 -5
  8. package/dist/tests/test-end-to-end-hybrid-validation.js +328 -0
  9. package/dist/tests/test-first-run-journey.js +43 -3
  10. package/dist/tests/test-hybrid-script-execution.js +340 -0
  11. package/dist/tests/test-mcp-connection.js +2 -2
  12. package/dist/tests/test-mcp-issue-integration.js +12 -4
  13. package/dist/tests/test-mcp-lifecycle-methods.js +4 -4
  14. package/dist/tests/test-node-compatibility.js +24 -2
  15. package/dist/tests/test-prep-issue.js +4 -1
  16. package/dist/tests/test-script-location-independence.js +173 -0
  17. package/dist/tests/test-script-sync.js +557 -0
  18. package/dist/tests/test-session-rehydration.js +2 -2
  19. package/dist/tests/test-standalone.js +3 -3
  20. package/dist/tests/test-sync-version-update.js +1 -1
  21. package/dist/tests/test-telemetry.js +2 -2
  22. package/dist/tests/test-user-journey.js +8 -4
  23. package/dist/tests/test-utils.js +13 -0
  24. package/dist/tests/test-wizard.js +2 -2
  25. package/package.json +3 -3
  26. package/registry/rules/agent-testing-guidelines.md +502 -502
  27. package/registry/rules/ephemeral-execution.md +37 -27
  28. package/registry/rules/local-development.md +253 -251
  29. package/registry/rules/successful-debugging-patterns.md +491 -482
  30. package/registry/scripts/prep-issue.sh +468 -468
  31. package/registry/workflows/bootstrap/evaluate-code-quality.md +8 -2
  32. package/registry/workflows/bootstrap/verify-test-coverage.md +8 -2
  33. package/registry/workflows/customer-development/thank-customers.md +203 -193
  34. package/registry/workflows/customer-development/weekly-newsletter.md +366 -362
  35. package/registry/workflows/performance/analyze-performance.md +65 -63
  36. package/registry/workflows/product-building/implement.md +6 -2
  37. package/registry/workflows/product-building/prep-issue.md +11 -24
  38. package/registry/workflows/product-building/resolve.md +5 -1
  39. package/registry/workflows/replicate/replicate-discovery.md +336 -0
  40. package/registry/workflows/replicate/replicate-to-issues.md +319 -0
  41. package/registry/workflows/reviewer/review-implementation-vs-design-spec.md +632 -632
  42. package/.windsurf/rules/windsurf-rules.md +0 -7
  43. package/.windsurf/workflows/resolve-issue.md +0 -6
  44. package/.windsurf/workflows/retrospect.md +0 -6
  45. package/.windsurf/workflows/start-design.md +0 -6
  46. package/.windsurf/workflows/start-impl.md +0 -6
  47. package/.windsurf/workflows/start-spec.md +0 -6
  48. package/.windsurf/workflows/start-tests.md +0 -6
  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
package/bin/fraim.js CHANGED
@@ -1,23 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * FRAIM Framework CLI Entry Point
5
- *
6
- * This file delegates to the compiled TypeScript implementation.
4
+ * FRAIM CLI Entry Point
5
+ * This wrapper loads and executes the compiled CLI from dist/
7
6
  */
8
7
 
9
- try {
10
- // In production/installed package, code is in dist/
11
- require('../dist/src/cli/fraim.js');
12
- } catch (error) {
13
- if (error.code === 'MODULE_NOT_FOUND') {
14
- // In development (local clone), we might use tsx if dist is missing
15
- // But typically we should just tell user to build.
16
- console.error('❌ Could not find FRAIM CLI implementation.');
17
- console.error(' If you are running from source, please run "npm run build" first.');
18
- console.error(` Error details: ${error.message}`);
19
- process.exit(1);
20
- } else {
21
- throw error;
22
- }
23
- }
8
+ require('../dist/src/cli/fraim.js');
@@ -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)();
@@ -59,82 +60,93 @@ const runSync = async (options) => {
59
60
  const existingDigest = fs_1.default.existsSync(digestPath) ? fs_1.default.readFileSync(digestPath, 'utf8') : '';
60
61
  if (currentDigest === existingDigest && !options.force) {
61
62
  console.log(chalk_1.default.green('✅ Workflows are already in sync.'));
62
- return;
63
63
  }
64
- const registryWorkflowsPath = path_1.default.join(registryPath, 'workflows');
65
- if (!fs_1.default.existsSync(registryWorkflowsPath)) {
66
- console.log(chalk_1.default.yellow('⚠️ No workflows found in registry.'));
67
- return;
68
- }
69
- // Get all workflows from registry (recursive)
70
- const getFiles = (dir) => {
71
- const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
72
- const files = entries
73
- .filter(e => !e.isDirectory() && e.name.endsWith('.md'))
74
- .map(e => path_1.default.join(dir, e.name));
75
- const folders = entries.filter(e => e.isDirectory());
76
- for (const folder of folders) {
77
- files.push(...getFiles(path_1.default.join(dir, folder.name)));
78
- }
79
- return files;
80
- };
81
- const registryFiles = getFiles(registryWorkflowsPath);
82
- const generatedStubs = [];
83
- for (const file of registryFiles) {
84
- const content = fs_1.default.readFileSync(file, 'utf8');
85
- const { intent, principles } = (0, stub_generator_1.parseRegistryWorkflow)(content);
86
- const fileName = path_1.default.basename(file);
87
- const workflowName = fileName.replace('.md', '');
88
- // Calculate relative path from registry/workflows to preserve structure
89
- const relativePath = path_1.default.relative(registryWorkflowsPath, file);
90
- const relativeDir = path_1.default.dirname(relativePath);
91
- // Ensure target directory exists
92
- const targetDir = path_1.default.join(workflowsDir, relativeDir);
93
- if (!fs_1.default.existsSync(targetDir)) {
94
- fs_1.default.mkdirSync(targetDir, { recursive: true });
64
+ else {
65
+ const registryWorkflowsPath = path_1.default.join(registryPath, 'workflows');
66
+ if (!fs_1.default.existsSync(registryWorkflowsPath)) {
67
+ console.log(chalk_1.default.yellow('⚠️ No workflows found in registry.'));
95
68
  }
96
- const stubContent = (0, stub_generator_1.generateWorkflowStub)(workflowName, file, intent, principles);
97
- const stubPath = path_1.default.join(targetDir, fileName);
98
- fs_1.default.writeFileSync(stubPath, stubContent);
99
- generatedStubs.push(relativePath); // Store relative path for cleanup tracking
100
- console.log(chalk_1.default.gray(` + ${workflowName} (${relativeDir === '.' ? 'root' : relativeDir})`));
101
- }
102
- // Cleanup stubs that no longer exist in registry
103
- // Cleanup stubs that no longer exist in registry
104
- // Helper to get all local stubs recursively
105
- const getLocalStubs = (dir) => {
106
- if (!fs_1.default.existsSync(dir))
107
- return [];
108
- const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
109
- const files = entries
110
- .filter(e => !e.isDirectory() && e.name.endsWith('.md'))
111
- .map(e => path_1.default.relative(workflowsDir, path_1.default.join(dir, e.name)));
112
- const folders = entries.filter(e => e.isDirectory());
113
- for (const folder of folders) {
114
- files.push(...getLocalStubs(path_1.default.join(dir, folder.name)));
115
- }
116
- return files;
117
- };
118
- const localStubs = getLocalStubs(workflowsDir);
119
- for (const stub of localStubs) {
120
- // standardise path separators for comparison
121
- const normalizedStub = stub.replace(/\\/g, '/');
122
- const normalizedGenerated = generatedStubs.map(s => s.replace(/\\/g, '/'));
123
- if (!normalizedGenerated.includes(normalizedStub)) {
124
- fs_1.default.unlinkSync(path_1.default.join(workflowsDir, stub));
125
- console.log(chalk_1.default.yellow(` - ${stub} (removed from registry)`));
126
- // Cleanup empty directories
127
- try {
128
- const dir = path_1.default.dirname(path_1.default.join(workflowsDir, stub));
129
- if (fs_1.default.readdirSync(dir).length === 0) {
130
- fs_1.default.rmdirSync(dir);
69
+ else {
70
+ // Get all workflows from registry (recursive)
71
+ const getFiles = (dir) => {
72
+ const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
73
+ const files = entries
74
+ .filter(e => !e.isDirectory() && e.name.endsWith('.md'))
75
+ .map(e => path_1.default.join(dir, e.name));
76
+ const folders = entries.filter(e => e.isDirectory());
77
+ for (const folder of folders) {
78
+ files.push(...getFiles(path_1.default.join(dir, folder.name)));
79
+ }
80
+ return files;
81
+ };
82
+ const registryFiles = getFiles(registryWorkflowsPath);
83
+ const generatedStubs = [];
84
+ for (const file of registryFiles) {
85
+ const content = fs_1.default.readFileSync(file, 'utf8');
86
+ const { intent, principles } = (0, stub_generator_1.parseRegistryWorkflow)(content);
87
+ const fileName = path_1.default.basename(file);
88
+ const workflowName = fileName.replace('.md', '');
89
+ // Calculate relative path from registry/workflows to preserve structure
90
+ const relativePath = path_1.default.relative(registryWorkflowsPath, file);
91
+ const relativeDir = path_1.default.dirname(relativePath);
92
+ // Ensure target directory exists
93
+ const targetDir = path_1.default.join(workflowsDir, relativeDir);
94
+ if (!fs_1.default.existsSync(targetDir)) {
95
+ fs_1.default.mkdirSync(targetDir, { recursive: true });
131
96
  }
97
+ const stubContent = (0, stub_generator_1.generateWorkflowStub)(workflowName, file, intent, principles);
98
+ const stubPath = path_1.default.join(targetDir, fileName);
99
+ fs_1.default.writeFileSync(stubPath, stubContent);
100
+ generatedStubs.push(relativePath); // Store relative path for cleanup tracking
101
+ console.log(chalk_1.default.gray(` + ${workflowName} (${relativeDir === '.' ? 'root' : relativeDir})`));
132
102
  }
133
- catch (e) { }
103
+ // Cleanup stubs that no longer exist in registry
104
+ // Helper to get all local stubs recursively
105
+ const getLocalStubs = (dir) => {
106
+ if (!fs_1.default.existsSync(dir))
107
+ return [];
108
+ const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
109
+ const files = entries
110
+ .filter(e => !e.isDirectory() && e.name.endsWith('.md'))
111
+ .map(e => path_1.default.relative(workflowsDir, path_1.default.join(dir, e.name)));
112
+ const folders = entries.filter(e => e.isDirectory());
113
+ for (const folder of folders) {
114
+ files.push(...getLocalStubs(path_1.default.join(dir, folder.name)));
115
+ }
116
+ return files;
117
+ };
118
+ const localStubs = getLocalStubs(workflowsDir);
119
+ for (const stub of localStubs) {
120
+ // standardise path separators for comparison
121
+ const normalizedStub = stub.replace(/\\/g, '/');
122
+ const normalizedGenerated = generatedStubs.map(s => s.replace(/\\/g, '/'));
123
+ if (!normalizedGenerated.includes(normalizedStub)) {
124
+ fs_1.default.unlinkSync(path_1.default.join(workflowsDir, stub));
125
+ console.log(chalk_1.default.yellow(` - ${stub} (removed from registry)`));
126
+ // Cleanup empty directories
127
+ try {
128
+ const dir = path_1.default.dirname(path_1.default.join(workflowsDir, stub));
129
+ if (fs_1.default.readdirSync(dir).length === 0) {
130
+ fs_1.default.rmdirSync(dir);
131
+ }
132
+ }
133
+ catch (e) { }
134
+ }
135
+ }
136
+ fs_1.default.writeFileSync(digestPath, currentDigest);
137
+ console.log(chalk_1.default.green(`\n✅ Workflow sync complete. Generated ${generatedStubs.length} stubs.`));
134
138
  }
135
139
  }
136
- fs_1.default.writeFileSync(digestPath, currentDigest);
137
- console.log(chalk_1.default.green(`\n Sync complete. Generated ${generatedStubs.length} stubs.`));
140
+ // Always sync scripts, regardless of workflow sync status
141
+ console.log(chalk_1.default.blue('\n🔄 Syncing FRAIM scripts to user directory...'));
142
+ const syncResult = (0, script_sync_utils_1.syncScriptsToUserDirectory)(registryPath);
143
+ const cleanedScriptCount = (0, script_sync_utils_1.cleanupObsoleteUserScripts)(registryPath);
144
+ if (syncResult.synced > 0 || cleanedScriptCount > 0) {
145
+ console.log(chalk_1.default.green(`✅ Script sync complete. Updated ${syncResult.synced} scripts, removed ${cleanedScriptCount} obsolete scripts.`));
146
+ }
147
+ else {
148
+ console.log(chalk_1.default.green('✅ Scripts are already in sync.'));
149
+ }
138
150
  };
139
151
  exports.runSync = runSync;
140
152
  exports.syncCommand = new commander_1.Command('sync')
@@ -0,0 +1,216 @@
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.getRegistryScripts = getRegistryScripts;
10
+ exports.copyScriptToUserDirectory = copyScriptToUserDirectory;
11
+ exports.syncScriptsToUserDirectory = syncScriptsToUserDirectory;
12
+ exports.cleanupObsoleteUserScripts = cleanupObsoleteUserScripts;
13
+ exports.getUserScriptPath = getUserScriptPath;
14
+ exports.userScriptExists = userScriptExists;
15
+ exports.validateUserScripts = validateUserScripts;
16
+ const fs_1 = __importDefault(require("fs"));
17
+ const path_1 = __importDefault(require("path"));
18
+ const os_1 = __importDefault(require("os"));
19
+ const chalk_1 = __importDefault(require("chalk"));
20
+ /**
21
+ * Get the user-level FRAIM directory path
22
+ * Can be overridden with FRAIM_USER_DIR environment variable for testing
23
+ */
24
+ function getUserFraimDir() {
25
+ return process.env.FRAIM_USER_DIR || path_1.default.join(os_1.default.homedir(), '.fraim');
26
+ }
27
+ /**
28
+ * Get the user-level scripts directory path
29
+ */
30
+ function getUserScriptsDir() {
31
+ return path_1.default.join(getUserFraimDir(), 'scripts');
32
+ }
33
+ /**
34
+ * Ensure the user-level FRAIM directories exist
35
+ */
36
+ function ensureUserFraimDirectories() {
37
+ const userFraimDir = getUserFraimDir();
38
+ const userScriptsDir = getUserScriptsDir();
39
+ if (!fs_1.default.existsSync(userFraimDir)) {
40
+ fs_1.default.mkdirSync(userFraimDir, { recursive: true });
41
+ }
42
+ if (!fs_1.default.existsSync(userScriptsDir)) {
43
+ fs_1.default.mkdirSync(userScriptsDir, { recursive: true });
44
+ }
45
+ }
46
+ /**
47
+ * Get all script files from the registry (recursively)
48
+ */
49
+ function getRegistryScripts(registryPath) {
50
+ const scriptsPath = path_1.default.join(registryPath, 'scripts');
51
+ if (!fs_1.default.existsSync(scriptsPath)) {
52
+ return [];
53
+ }
54
+ const getAllFiles = (dir, baseDir = dir) => {
55
+ const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
56
+ let files = [];
57
+ for (const entry of entries) {
58
+ const fullPath = path_1.default.join(dir, entry.name);
59
+ if (entry.isDirectory()) {
60
+ // Recursively get files from subdirectories
61
+ files = files.concat(getAllFiles(fullPath, baseDir));
62
+ }
63
+ else {
64
+ // Include all files (not just specific extensions)
65
+ files.push(fullPath);
66
+ }
67
+ }
68
+ return files;
69
+ };
70
+ return getAllFiles(scriptsPath);
71
+ }
72
+ /**
73
+ * Copy a script file to the user scripts directory with proper permissions
74
+ * Preserves directory structure from registry/scripts/
75
+ */
76
+ function copyScriptToUserDirectory(sourcePath, registryScriptsPath) {
77
+ const userScriptsDir = getUserScriptsDir();
78
+ // Calculate relative path from registry/scripts to preserve structure
79
+ const relativePath = path_1.default.relative(registryScriptsPath, sourcePath);
80
+ const targetPath = path_1.default.join(userScriptsDir, relativePath);
81
+ // Ensure target directory exists
82
+ const targetDir = path_1.default.dirname(targetPath);
83
+ if (!fs_1.default.existsSync(targetDir)) {
84
+ fs_1.default.mkdirSync(targetDir, { recursive: true });
85
+ }
86
+ // Copy the file
87
+ fs_1.default.copyFileSync(sourcePath, targetPath);
88
+ // Set executable permissions on Unix systems for shell scripts
89
+ if (process.platform !== 'win32' && sourcePath.endsWith('.sh')) {
90
+ try {
91
+ fs_1.default.chmodSync(targetPath, 0o755);
92
+ }
93
+ catch (error) {
94
+ console.warn(chalk_1.default.yellow(`⚠️ Could not set executable permissions for ${relativePath}`));
95
+ }
96
+ }
97
+ }
98
+ /**
99
+ * Sync all scripts from registry to user directory
100
+ */
101
+ function syncScriptsToUserDirectory(registryPath) {
102
+ ensureUserFraimDirectories();
103
+ const registryScriptsPath = path_1.default.join(registryPath, 'scripts');
104
+ const allScripts = getRegistryScripts(registryPath);
105
+ let syncedCount = 0;
106
+ console.log(chalk_1.default.gray(` Syncing all scripts to ~/.fraim/scripts/:`));
107
+ for (const scriptPath of allScripts) {
108
+ const relativePath = path_1.default.relative(registryScriptsPath, scriptPath);
109
+ const userScriptPath = path_1.default.join(getUserScriptsDir(), relativePath);
110
+ try {
111
+ // Check if script needs updating
112
+ let needsUpdate = true;
113
+ if (fs_1.default.existsSync(userScriptPath)) {
114
+ const registryContent = fs_1.default.readFileSync(scriptPath, 'utf-8');
115
+ const userContent = fs_1.default.readFileSync(userScriptPath, 'utf-8');
116
+ needsUpdate = registryContent !== userContent;
117
+ }
118
+ if (needsUpdate) {
119
+ copyScriptToUserDirectory(scriptPath, registryScriptsPath);
120
+ syncedCount++;
121
+ console.log(chalk_1.default.gray(` + ${relativePath}`));
122
+ }
123
+ else {
124
+ console.log(chalk_1.default.gray(` = ${relativePath} (up to date)`));
125
+ }
126
+ }
127
+ catch (error) {
128
+ console.warn(chalk_1.default.yellow(`⚠️ Could not sync script ${relativePath}: ${error}`));
129
+ }
130
+ }
131
+ return { synced: syncedCount, ephemeral: 0 };
132
+ }
133
+ /**
134
+ * Clean up scripts in user directory that no longer exist in registry
135
+ */
136
+ function cleanupObsoleteUserScripts(registryPath) {
137
+ const userScriptsDir = getUserScriptsDir();
138
+ if (!fs_1.default.existsSync(userScriptsDir)) {
139
+ return 0;
140
+ }
141
+ const registryScriptsPath = path_1.default.join(registryPath, 'scripts');
142
+ const registryScripts = getRegistryScripts(registryPath);
143
+ const registryRelativePaths = registryScripts.map(scriptPath => path_1.default.relative(registryScriptsPath, scriptPath));
144
+ // Get all files in user scripts directory recursively
145
+ const getAllUserFiles = (dir, baseDir = dir) => {
146
+ if (!fs_1.default.existsSync(dir))
147
+ return [];
148
+ const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
149
+ let files = [];
150
+ for (const entry of entries) {
151
+ const fullPath = path_1.default.join(dir, entry.name);
152
+ if (entry.isDirectory()) {
153
+ files = files.concat(getAllUserFiles(fullPath, baseDir));
154
+ }
155
+ else {
156
+ files.push(path_1.default.relative(baseDir, fullPath));
157
+ }
158
+ }
159
+ return files;
160
+ };
161
+ const userFiles = getAllUserFiles(userScriptsDir);
162
+ let cleanedCount = 0;
163
+ for (const userFile of userFiles) {
164
+ // Normalize path separators for comparison
165
+ const normalizedUserFile = userFile.replace(/\\/g, '/');
166
+ const normalizedRegistry = registryRelativePaths.map(p => p.replace(/\\/g, '/'));
167
+ if (!normalizedRegistry.includes(normalizedUserFile)) {
168
+ try {
169
+ const userFilePath = path_1.default.join(userScriptsDir, userFile);
170
+ fs_1.default.unlinkSync(userFilePath);
171
+ cleanedCount++;
172
+ console.log(chalk_1.default.yellow(` - ${userFile} (removed from registry)`));
173
+ // Cleanup empty directories
174
+ try {
175
+ const dir = path_1.default.dirname(userFilePath);
176
+ if (fs_1.default.existsSync(dir) && fs_1.default.readdirSync(dir).length === 0) {
177
+ fs_1.default.rmdirSync(dir);
178
+ }
179
+ }
180
+ catch (e) { }
181
+ }
182
+ catch (error) {
183
+ console.warn(chalk_1.default.yellow(`⚠️ Could not remove obsolete script ${userFile}: ${error}`));
184
+ }
185
+ }
186
+ }
187
+ return cleanedCount;
188
+ }
189
+ /**
190
+ * Get the path to a script in the user directory
191
+ */
192
+ function getUserScriptPath(scriptName) {
193
+ return path_1.default.join(getUserScriptsDir(), scriptName);
194
+ }
195
+ /**
196
+ * Check if a script exists in the user directory
197
+ */
198
+ function userScriptExists(scriptName) {
199
+ return fs_1.default.existsSync(getUserScriptPath(scriptName));
200
+ }
201
+ /**
202
+ * Validate that all expected scripts are present in user directory
203
+ */
204
+ function validateUserScripts(expectedScripts) {
205
+ const missing = [];
206
+ const present = [];
207
+ for (const scriptName of expectedScripts) {
208
+ if (userScriptExists(scriptName)) {
209
+ present.push(scriptName);
210
+ }
211
+ else {
212
+ missing.push(scriptName);
213
+ }
214
+ }
215
+ return { missing, present };
216
+ }
@@ -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 {