fraim-framework 2.0.27 → 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.
- package/.github/workflows/deploy-fraim.yml +1 -1
- package/dist/registry/scripts/build-scripts-generator.js +205 -0
- package/dist/registry/scripts/cleanup-branch.js +258 -0
- package/dist/registry/scripts/evaluate-code-quality.js +66 -0
- package/dist/registry/scripts/exec-with-timeout.js +142 -0
- package/dist/registry/scripts/fraim-config.js +61 -0
- package/dist/registry/scripts/generate-engagement-emails.js +630 -0
- package/dist/registry/scripts/generic-issues-api.js +100 -0
- package/dist/registry/scripts/newsletter-helpers.js +731 -0
- package/dist/registry/scripts/openapi-generator.js +664 -0
- package/{registry/scripts/performance/profile-server.ts → dist/registry/scripts/performance/profile-server.js} +390 -368
- package/dist/registry/scripts/run-thank-you-workflow.js +92 -0
- package/dist/registry/scripts/send-newsletter-simple.js +85 -0
- package/dist/registry/scripts/send-thank-you-emails.js +54 -0
- package/dist/registry/scripts/validate-openapi-limits.js +311 -0
- package/dist/registry/scripts/validate-test-coverage.js +262 -0
- package/dist/registry/scripts/verify-test-coverage.js +66 -0
- package/dist/src/cli/commands/init.js +42 -15
- package/dist/src/cli/commands/sync.js +22 -5
- package/dist/src/cli/fraim.js +24 -22
- package/dist/src/cli/setup/first-run.js +13 -6
- package/dist/src/fraim/db-service.js +26 -15
- package/dist/src/fraim/issues.js +67 -0
- package/dist/src/fraim-mcp-server.js +272 -18
- package/dist/src/utils/git-utils.js +1 -1
- package/dist/src/utils/script-sync-utils.js +218 -0
- package/dist/tests/debug-tools.js +80 -0
- package/dist/tests/esm-compat.js +11 -0
- package/dist/tests/test-chalk-esm-issue.js +159 -0
- package/dist/tests/test-chalk-real-world.js +265 -0
- package/dist/tests/test-chalk-regression.js +377 -0
- package/dist/tests/test-chalk-resolution-issue.js +304 -0
- package/dist/tests/test-cli.js +70 -5
- package/dist/tests/test-end-to-end-hybrid-validation.js +349 -0
- package/dist/tests/test-first-run-journey.js +43 -3
- package/dist/tests/test-fraim-install-chalk-issue.js +254 -0
- package/dist/tests/test-fraim-issues.js +59 -0
- package/dist/tests/test-genericization.js +1 -1
- package/dist/tests/test-hybrid-script-execution.js +369 -0
- package/dist/tests/test-mcp-connection.js +166 -0
- package/dist/tests/test-mcp-issue-integration.js +152 -0
- package/dist/tests/test-mcp-lifecycle-methods.js +312 -0
- package/dist/tests/test-node-compatibility.js +93 -0
- package/dist/tests/test-npm-install.js +66 -0
- package/dist/tests/test-npm-resolution-diagnostic.js +140 -0
- package/dist/tests/test-prep-issue.js +4 -1
- package/dist/tests/test-script-location-independence.js +173 -0
- package/dist/tests/test-script-sync.js +557 -0
- package/dist/tests/test-session-rehydration.js +145 -0
- package/dist/tests/test-standalone.js +5 -11
- package/dist/tests/test-sync-version-update.js +1 -1
- package/dist/tests/test-telemetry.js +190 -0
- package/dist/tests/test-user-journey.js +8 -4
- package/dist/tests/test-utils.js +13 -0
- package/dist/tests/test-wizard.js +2 -2
- package/package.json +11 -9
- package/registry/agent-guardrails.md +62 -54
- package/registry/rules/agent-success-criteria.md +52 -0
- package/registry/rules/agent-testing-guidelines.md +7 -7
- package/registry/rules/communication.md +121 -121
- package/registry/rules/continuous-learning.md +54 -54
- package/registry/rules/ephemeral-execution.md +39 -24
- package/registry/rules/hitl-ppe-record-analysis.md +302 -302
- package/registry/rules/local-development.md +7 -5
- package/registry/rules/software-development-lifecycle.md +104 -104
- package/registry/rules/successful-debugging-patterns.md +29 -16
- package/registry/rules/telemetry.md +67 -0
- package/registry/scripts/code-quality-check.sh +559 -559
- package/registry/scripts/detect-tautological-tests.sh +38 -38
- package/registry/scripts/evaluate-code-quality.ts +1 -1
- package/registry/scripts/prep-issue.sh +16 -3
- package/registry/scripts/validate-openapi-limits.ts +366 -365
- package/registry/scripts/validate-test-coverage.ts +280 -280
- package/registry/scripts/verify-pr-comments.sh +70 -70
- package/registry/scripts/verify-test-coverage.ts +1 -1
- package/registry/templates/bootstrap/ARCHITECTURE-TEMPLATE.md +53 -53
- package/registry/templates/evidence/Implementation-BugEvidence.md +85 -85
- package/registry/templates/evidence/Implementation-FeatureEvidence.md +120 -120
- package/registry/templates/marketing/HBR-ARTICLE-TEMPLATE.md +66 -0
- package/registry/workflows/bootstrap/create-architecture.md +2 -2
- package/registry/workflows/bootstrap/evaluate-code-quality.md +10 -4
- package/registry/workflows/bootstrap/verify-test-coverage.md +9 -3
- package/registry/workflows/customer-development/insight-analysis.md +156 -156
- package/registry/workflows/customer-development/interview-preparation.md +421 -421
- package/registry/workflows/customer-development/strategic-brainstorming.md +146 -146
- package/registry/workflows/customer-development/thank-customers.md +14 -2
- package/registry/workflows/customer-development/weekly-newsletter.md +27 -13
- package/registry/workflows/improve-fraim/contribute.md +32 -0
- package/registry/workflows/improve-fraim/file-issue.md +32 -0
- package/registry/workflows/marketing/hbr-article.md +73 -0
- package/registry/workflows/performance/analyze-performance.md +10 -4
- package/registry/workflows/product-building/design.md +3 -2
- package/registry/workflows/product-building/implement.md +10 -5
- package/registry/workflows/product-building/prep-issue.md +16 -18
- package/registry/workflows/product-building/resolve.md +8 -3
- package/registry/workflows/product-building/retrospect.md +3 -2
- package/registry/workflows/product-building/spec.md +5 -4
- package/registry/workflows/product-building/test.md +3 -2
- package/registry/workflows/quality-assurance/iterative-improvement-cycle.md +562 -562
- package/registry/workflows/replicate/replicate-discovery.md +336 -0
- package/registry/workflows/replicate/replicate-to-issues.md +319 -0
- package/registry/workflows/reviewer/review-implementation-vs-design-spec.md +3 -3
- package/registry/workflows/reviewer/review-implementation-vs-feature-spec.md +669 -669
- package/tsconfig.json +2 -1
- package/.windsurf/rules/windsurf-rules.md +0 -7
- package/.windsurf/workflows/resolve-issue.md +0 -6
- package/.windsurf/workflows/retrospect.md +0 -6
- package/.windsurf/workflows/start-design.md +0 -6
- package/.windsurf/workflows/start-impl.md +0 -6
- package/.windsurf/workflows/start-spec.md +0 -6
- package/.windsurf/workflows/start-tests.md +0 -6
- package/bin/fraim.js +0 -23
- package/registry/scripts/build-scripts-generator.ts +0 -215
- package/registry/scripts/cleanup-branch.ts +0 -284
- package/registry/scripts/fraim-config.ts +0 -63
- package/registry/scripts/generate-engagement-emails.ts +0 -744
- package/registry/scripts/generic-issues-api.ts +0 -150
- package/registry/scripts/newsletter-helpers.ts +0 -874
- package/registry/scripts/openapi-generator.ts +0 -693
- package/registry/scripts/run-thank-you-workflow.ts +0 -122
- package/registry/scripts/send-newsletter-simple.ts +0 -104
- package/registry/scripts/send-thank-you-emails.ts +0 -57
- package/registry/workflows/replicate/re-implementation-strategy.md +0 -226
- package/registry/workflows/replicate/use-case-extraction.md +0 -135
- package/registry/workflows/replicate/visual-analysis.md +0 -154
- package/registry/workflows/replicate/website-discovery-analysis.md +0 -231
- package/sample_package.json +0 -18
- /package/registry/scripts/{replicate/comprehensive-explorer.py → comprehensive-explorer.py} +0 -0
- /package/registry/scripts/{replicate/interactive-explorer.py → interactive-explorer.py} +0 -0
- /package/registry/scripts/{replicate/scrape-site.py → scrape-site.py} +0 -0
|
@@ -0,0 +1,369 @@
|
|
|
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 test_utils_1 = require("./test-utils");
|
|
40
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
41
|
+
const fs_1 = __importDefault(require("fs"));
|
|
42
|
+
const path_1 = __importDefault(require("path"));
|
|
43
|
+
const os_1 = __importDefault(require("os"));
|
|
44
|
+
async function testSelfContainedScriptDetection() {
|
|
45
|
+
console.log(' 🚀 Testing Self-Contained Script Detection...');
|
|
46
|
+
const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'fraim-hybrid-test-'));
|
|
47
|
+
const scriptsDir = path_1.default.join(tempDir, 'scripts');
|
|
48
|
+
fs_1.default.mkdirSync(scriptsDir, { recursive: true });
|
|
49
|
+
try {
|
|
50
|
+
// Create a self-contained shell script
|
|
51
|
+
const shellScript = `#!/bin/bash
|
|
52
|
+
set -e
|
|
53
|
+
echo "This is a self-contained shell script"
|
|
54
|
+
echo "Working directory: $(pwd)"
|
|
55
|
+
`;
|
|
56
|
+
fs_1.default.writeFileSync(path_1.default.join(scriptsDir, 'self-contained.sh'), shellScript);
|
|
57
|
+
// Create a self-contained TypeScript script (no relative imports)
|
|
58
|
+
const selfContainedTS = `#!/usr/bin/env node
|
|
59
|
+
console.log('This is a self-contained TypeScript script');
|
|
60
|
+
console.log('Working directory:', process.cwd());
|
|
61
|
+
`;
|
|
62
|
+
fs_1.default.writeFileSync(path_1.default.join(scriptsDir, 'self-contained.ts'), selfContainedTS);
|
|
63
|
+
// Create a dependent TypeScript script (has relative imports to src/)
|
|
64
|
+
const dependentTS = `#!/usr/bin/env node
|
|
65
|
+
import { loadFraimConfig } from '../../src/fraim/config-loader';
|
|
66
|
+
import { someUtility } from '../../src/utils/helper';
|
|
67
|
+
|
|
68
|
+
const config = loadFraimConfig();
|
|
69
|
+
console.log('This script depends on FRAIM source code');
|
|
70
|
+
`;
|
|
71
|
+
fs_1.default.writeFileSync(path_1.default.join(scriptsDir, 'dependent.ts'), dependentTS);
|
|
72
|
+
// Create another dependent script with require syntax
|
|
73
|
+
const dependentJS = `#!/usr/bin/env node
|
|
74
|
+
const { loadFraimConfig } = require('../../src/fraim/config-loader');
|
|
75
|
+
const helper = require('../../src/utils/helper');
|
|
76
|
+
|
|
77
|
+
console.log('This script also depends on FRAIM source code');
|
|
78
|
+
`;
|
|
79
|
+
fs_1.default.writeFileSync(path_1.default.join(scriptsDir, 'dependent.js'), dependentJS);
|
|
80
|
+
// Test the detection logic
|
|
81
|
+
const { isScriptSelfContained, getRegistryScripts, getDependentRegistryScripts } = await Promise.resolve().then(() => __importStar(require('../src/utils/script-sync-utils')));
|
|
82
|
+
// Test individual script detection
|
|
83
|
+
node_assert_1.default.ok(isScriptSelfContained(path_1.default.join(scriptsDir, 'self-contained.sh')), 'Shell script should be self-contained');
|
|
84
|
+
node_assert_1.default.ok(isScriptSelfContained(path_1.default.join(scriptsDir, 'self-contained.ts')), 'Self-contained TS should be detected');
|
|
85
|
+
node_assert_1.default.ok(!isScriptSelfContained(path_1.default.join(scriptsDir, 'dependent.ts')), 'Dependent TS should not be self-contained');
|
|
86
|
+
node_assert_1.default.ok(!isScriptSelfContained(path_1.default.join(scriptsDir, 'dependent.js')), 'Dependent JS should not be self-contained');
|
|
87
|
+
// Test batch detection
|
|
88
|
+
const selfContainedScripts = getRegistryScripts(tempDir);
|
|
89
|
+
const dependentScripts = getDependentRegistryScripts(tempDir);
|
|
90
|
+
node_assert_1.default.strictEqual(selfContainedScripts.length, 2, 'Should find 2 self-contained scripts');
|
|
91
|
+
node_assert_1.default.strictEqual(dependentScripts.length, 2, 'Should find 2 dependent scripts');
|
|
92
|
+
// Verify correct categorization
|
|
93
|
+
const selfContainedNames = selfContainedScripts.map(p => path_1.default.basename(p)).sort();
|
|
94
|
+
const dependentNames = dependentScripts.map(p => path_1.default.basename(p)).sort();
|
|
95
|
+
node_assert_1.default.deepStrictEqual(selfContainedNames, ['self-contained.sh', 'self-contained.ts'], 'Self-contained scripts correctly identified');
|
|
96
|
+
node_assert_1.default.deepStrictEqual(dependentNames, ['dependent.js', 'dependent.ts'], 'Dependent scripts correctly identified');
|
|
97
|
+
console.log(' ✅ Self-contained script detection verified!');
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
console.error(' ❌ Script detection test failed:', error);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
try {
|
|
106
|
+
fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
107
|
+
}
|
|
108
|
+
catch (e) { }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function testHybridSyncBehavior() {
|
|
112
|
+
console.log(' 🚀 Testing Hybrid Sync Behavior...');
|
|
113
|
+
const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'fraim-hybrid-sync-test-'));
|
|
114
|
+
const userFraimDir = path_1.default.join(os_1.default.homedir(), '.fraim-hybrid-test');
|
|
115
|
+
const userScriptsDir = path_1.default.join(userFraimDir, 'scripts');
|
|
116
|
+
try {
|
|
117
|
+
// Clean up any existing test user directory
|
|
118
|
+
if (fs_1.default.existsSync(userFraimDir)) {
|
|
119
|
+
fs_1.default.rmSync(userFraimDir, { recursive: true, force: true });
|
|
120
|
+
}
|
|
121
|
+
// Set up test registry with mixed scripts
|
|
122
|
+
const registryDir = path_1.default.join(tempDir, 'registry');
|
|
123
|
+
const scriptsDir = path_1.default.join(registryDir, 'scripts');
|
|
124
|
+
fs_1.default.mkdirSync(scriptsDir, { recursive: true });
|
|
125
|
+
// Create self-contained scripts
|
|
126
|
+
const shellScript = `#!/bin/bash
|
|
127
|
+
echo "Self-contained shell script"
|
|
128
|
+
`;
|
|
129
|
+
fs_1.default.writeFileSync(path_1.default.join(scriptsDir, 'shell-script.sh'), shellScript);
|
|
130
|
+
const selfContainedTS = `#!/usr/bin/env node
|
|
131
|
+
console.log('Self-contained TypeScript script');
|
|
132
|
+
`;
|
|
133
|
+
fs_1.default.writeFileSync(path_1.default.join(scriptsDir, 'self-contained.ts'), selfContainedTS);
|
|
134
|
+
// Create dependent scripts
|
|
135
|
+
const dependentTS = `#!/usr/bin/env node
|
|
136
|
+
import { loadFraimConfig } from '../../src/fraim/config-loader';
|
|
137
|
+
console.log('Dependent TypeScript script');
|
|
138
|
+
`;
|
|
139
|
+
fs_1.default.writeFileSync(path_1.default.join(scriptsDir, 'dependent.ts'), dependentTS);
|
|
140
|
+
// Test sync behavior
|
|
141
|
+
const { syncScriptsToUserDirectory } = await Promise.resolve().then(() => __importStar(require('../src/utils/script-sync-utils')));
|
|
142
|
+
// Override user directory for testing
|
|
143
|
+
process.env.FRAIM_USER_DIR = userFraimDir;
|
|
144
|
+
const syncResult = syncScriptsToUserDirectory(registryDir);
|
|
145
|
+
// Verify sync results
|
|
146
|
+
node_assert_1.default.strictEqual(syncResult.synced, 2, 'Should sync 2 self-contained scripts');
|
|
147
|
+
node_assert_1.default.strictEqual(syncResult.ephemeral, 1, 'Should identify 1 dependent script for ephemeral execution');
|
|
148
|
+
// Verify only self-contained scripts were copied
|
|
149
|
+
node_assert_1.default.ok(fs_1.default.existsSync(path_1.default.join(userScriptsDir, 'shell-script.sh')), 'Shell script should be synced');
|
|
150
|
+
node_assert_1.default.ok(fs_1.default.existsSync(path_1.default.join(userScriptsDir, 'self-contained.ts')), 'Self-contained TS should be synced');
|
|
151
|
+
node_assert_1.default.ok(!fs_1.default.existsSync(path_1.default.join(userScriptsDir, 'dependent.ts')), 'Dependent TS should NOT be synced');
|
|
152
|
+
// Verify executable permissions on Unix
|
|
153
|
+
if (process.platform !== 'win32') {
|
|
154
|
+
const shellScriptPath = path_1.default.join(userScriptsDir, 'shell-script.sh');
|
|
155
|
+
const stats = fs_1.default.statSync(shellScriptPath);
|
|
156
|
+
node_assert_1.default.ok(stats.mode & 0o111, 'Shell script should be executable');
|
|
157
|
+
}
|
|
158
|
+
console.log(' ✅ Hybrid sync behavior verified!');
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
console.error(' ❌ Hybrid sync test failed:', error);
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
try {
|
|
167
|
+
fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
168
|
+
if (fs_1.default.existsSync(userFraimDir)) {
|
|
169
|
+
fs_1.default.rmSync(userFraimDir, { recursive: true, force: true });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (e) { }
|
|
173
|
+
delete process.env.FRAIM_USER_DIR;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async function testActualRegistryScriptCategorization() {
|
|
177
|
+
console.log(' 🚀 Testing Actual Registry Script Categorization...');
|
|
178
|
+
try {
|
|
179
|
+
const registryPath = path_1.default.resolve(__dirname, '../../registry');
|
|
180
|
+
if (!fs_1.default.existsSync(registryPath)) {
|
|
181
|
+
console.log(' ⚠️ Registry not found, skipping actual script categorization test');
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
const { getRegistryScripts, getDependentRegistryScripts } = await Promise.resolve().then(() => __importStar(require('../src/utils/script-sync-utils')));
|
|
185
|
+
const selfContainedScripts = getRegistryScripts(registryPath);
|
|
186
|
+
const dependentScripts = getDependentRegistryScripts(registryPath);
|
|
187
|
+
console.log(` 📊 Found ${selfContainedScripts.length} self-contained scripts:`);
|
|
188
|
+
for (const script of selfContainedScripts) {
|
|
189
|
+
console.log(` ✅ ${path_1.default.basename(script)}`);
|
|
190
|
+
}
|
|
191
|
+
console.log(` 📊 Found ${dependentScripts.length} dependent scripts:`);
|
|
192
|
+
for (const script of dependentScripts) {
|
|
193
|
+
console.log(` 🔗 ${path_1.default.basename(script)}`);
|
|
194
|
+
}
|
|
195
|
+
// Verify expected scripts are categorized correctly
|
|
196
|
+
const selfContainedNames = selfContainedScripts.map(p => path_1.default.basename(p));
|
|
197
|
+
const dependentNames = dependentScripts.map(p => path_1.default.basename(p));
|
|
198
|
+
// prep-issue.sh should be self-contained (it's a shell script)
|
|
199
|
+
node_assert_1.default.ok(selfContainedNames.includes('prep-issue.sh'), 'prep-issue.sh should be self-contained');
|
|
200
|
+
// Scripts with FRAIM imports should be dependent
|
|
201
|
+
const expectedDependentScripts = [
|
|
202
|
+
'fraim-config.ts',
|
|
203
|
+
'build-scripts-generator.ts'
|
|
204
|
+
];
|
|
205
|
+
for (const expectedDependent of expectedDependentScripts) {
|
|
206
|
+
if (fs_1.default.existsSync(path_1.default.join(registryPath, 'scripts', expectedDependent))) {
|
|
207
|
+
node_assert_1.default.ok(dependentNames.includes(expectedDependent), `${expectedDependent} should be dependent`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Scripts that should be self-contained
|
|
211
|
+
const expectedSelfContainedScripts = [
|
|
212
|
+
'prep-issue.sh',
|
|
213
|
+
'evaluate-code-quality.ts', // This only uses __dirname and standard modules
|
|
214
|
+
'code-quality-check.sh'
|
|
215
|
+
];
|
|
216
|
+
for (const expectedSelfContained of expectedSelfContainedScripts) {
|
|
217
|
+
if (fs_1.default.existsSync(path_1.default.join(registryPath, 'scripts', expectedSelfContained))) {
|
|
218
|
+
node_assert_1.default.ok(selfContainedNames.includes(expectedSelfContained), `${expectedSelfContained} should be self-contained`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Verify we have a reasonable split
|
|
222
|
+
const totalScripts = selfContainedScripts.length + dependentScripts.length;
|
|
223
|
+
node_assert_1.default.ok(totalScripts > 0, 'Should find some scripts in registry');
|
|
224
|
+
node_assert_1.default.ok(selfContainedScripts.length > 0, 'Should have some self-contained scripts');
|
|
225
|
+
console.log(' ✅ Actual registry script categorization verified!');
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
console.error(' ❌ Registry script categorization test failed:', error);
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
async function testSelfContainedScriptExecution() {
|
|
234
|
+
console.log(' 🚀 Testing Self-Contained Script Execution...');
|
|
235
|
+
const tempProjectDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'fraim-self-contained-test-'));
|
|
236
|
+
const userFraimDir = path_1.default.join(os_1.default.homedir(), '.fraim-self-contained-test');
|
|
237
|
+
const userScriptsDir = path_1.default.join(userFraimDir, 'scripts');
|
|
238
|
+
try {
|
|
239
|
+
// Clean up and setup
|
|
240
|
+
if (fs_1.default.existsSync(userFraimDir)) {
|
|
241
|
+
fs_1.default.rmSync(userFraimDir, { recursive: true, force: true });
|
|
242
|
+
}
|
|
243
|
+
fs_1.default.mkdirSync(userScriptsDir, { recursive: true });
|
|
244
|
+
// Create a self-contained script that mimics prep-issue.sh behavior
|
|
245
|
+
const selfContainedScript = `#!/bin/bash
|
|
246
|
+
set -e
|
|
247
|
+
|
|
248
|
+
echo "=== Self-Contained Script Test ==="
|
|
249
|
+
|
|
250
|
+
# Validate we can find .fraim/config.json in working directory
|
|
251
|
+
if [ ! -f ".fraim/config.json" ]; then
|
|
252
|
+
echo "ERROR: Cannot find .fraim/config.json in working directory"
|
|
253
|
+
exit 1
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
# Read project name from config using node
|
|
257
|
+
PROJECT_NAME=$(node -e "
|
|
258
|
+
const fs = require('fs');
|
|
259
|
+
const config = JSON.parse(fs.readFileSync('.fraim/config.json', 'utf8'));
|
|
260
|
+
console.log(config.project.name);
|
|
261
|
+
")
|
|
262
|
+
|
|
263
|
+
echo "Project name from config: $PROJECT_NAME"
|
|
264
|
+
|
|
265
|
+
# Validate we can create files in working directory
|
|
266
|
+
echo "test content" > self-contained-test-output.txt
|
|
267
|
+
if [ -f "self-contained-test-output.txt" ]; then
|
|
268
|
+
echo "File creation: SUCCESS"
|
|
269
|
+
rm self-contained-test-output.txt
|
|
270
|
+
else
|
|
271
|
+
echo "File creation: FAILED"
|
|
272
|
+
exit 1
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
echo "Working directory: $(pwd)"
|
|
276
|
+
echo "Script directory: $(dirname "$0")"
|
|
277
|
+
|
|
278
|
+
# Validate they are different
|
|
279
|
+
if [ "$(pwd)" = "$(dirname "$0")" ]; then
|
|
280
|
+
echo "ERROR: Working directory should not be same as script directory"
|
|
281
|
+
exit 1
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
echo "Self-contained script execution: SUCCESS"
|
|
285
|
+
`;
|
|
286
|
+
const scriptPath = path_1.default.join(userScriptsDir, 'self-contained-test.sh');
|
|
287
|
+
fs_1.default.writeFileSync(scriptPath, selfContainedScript);
|
|
288
|
+
if (process.platform !== 'win32') {
|
|
289
|
+
fs_1.default.chmodSync(scriptPath, 0o755);
|
|
290
|
+
}
|
|
291
|
+
// Set up project directory
|
|
292
|
+
const projectFraimDir = path_1.default.join(tempProjectDir, '.fraim');
|
|
293
|
+
fs_1.default.mkdirSync(projectFraimDir, { recursive: true });
|
|
294
|
+
const testConfig = {
|
|
295
|
+
project: { name: 'self-contained-test' },
|
|
296
|
+
git: { repoOwner: 'test', repoName: 'self-contained-test' }
|
|
297
|
+
};
|
|
298
|
+
fs_1.default.writeFileSync(path_1.default.join(projectFraimDir, 'config.json'), JSON.stringify(testConfig, null, 2));
|
|
299
|
+
// Execute script from project directory
|
|
300
|
+
console.log(' Executing self-contained script...');
|
|
301
|
+
const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
302
|
+
const bashCommand = process.platform === 'win32'
|
|
303
|
+
? `"C:\\Program Files\\Git\\bin\\bash.exe" "${scriptPath}"`
|
|
304
|
+
: `"${scriptPath}"`;
|
|
305
|
+
let output;
|
|
306
|
+
try {
|
|
307
|
+
output = execSync(bashCommand, {
|
|
308
|
+
cwd: tempProjectDir,
|
|
309
|
+
encoding: 'utf-8'
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
if (process.platform === 'win32' && error.message.includes('not recognized')) {
|
|
314
|
+
console.log(' ⚠️ Git Bash not found on Windows, skipping self-contained script test');
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
throw error;
|
|
318
|
+
}
|
|
319
|
+
// Validate output
|
|
320
|
+
node_assert_1.default.ok(output.includes('Self-contained script execution: SUCCESS'), 'Script should execute successfully');
|
|
321
|
+
node_assert_1.default.ok(output.includes('Project name from config: self-contained-test'), 'Should read config correctly');
|
|
322
|
+
node_assert_1.default.ok(output.includes('File creation: SUCCESS'), 'Should be able to create files in working directory');
|
|
323
|
+
console.log(' ✅ Self-contained script execution verified!');
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
console.error(' ❌ Self-contained script execution test failed:', error);
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
finally {
|
|
331
|
+
try {
|
|
332
|
+
fs_1.default.rmSync(tempProjectDir, { recursive: true, force: true });
|
|
333
|
+
if (fs_1.default.existsSync(userFraimDir)) {
|
|
334
|
+
fs_1.default.rmSync(userFraimDir, { recursive: true, force: true });
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
catch (e) { }
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
async function runHybridScriptTest(testCase) {
|
|
341
|
+
return await testCase.testFunction();
|
|
342
|
+
}
|
|
343
|
+
const testCases = [
|
|
344
|
+
{
|
|
345
|
+
name: 'Self-Contained Script Detection',
|
|
346
|
+
description: 'Tests the logic that determines which scripts are self-contained vs dependent',
|
|
347
|
+
testFunction: testSelfContainedScriptDetection,
|
|
348
|
+
tags: ['hybrid', 'detection']
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: 'Hybrid Sync Behavior',
|
|
352
|
+
description: 'Tests that sync only copies self-contained scripts to user directory',
|
|
353
|
+
testFunction: testHybridSyncBehavior,
|
|
354
|
+
tags: ['hybrid', 'sync']
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
name: 'Actual Registry Script Categorization',
|
|
358
|
+
description: 'Tests categorization of real scripts in the registry',
|
|
359
|
+
testFunction: testActualRegistryScriptCategorization,
|
|
360
|
+
tags: ['hybrid', 'registry']
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
name: 'Self-Contained Script Execution',
|
|
364
|
+
description: 'Tests that self-contained scripts work correctly when executed from user directory',
|
|
365
|
+
testFunction: testSelfContainedScriptExecution,
|
|
366
|
+
tags: ['hybrid', 'execution']
|
|
367
|
+
}
|
|
368
|
+
];
|
|
369
|
+
(0, test_utils_1.runTests)(testCases, runHybridScriptTest, 'Hybrid Script Execution Tests');
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_child_process_1 = require("node:child_process");
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const test_utils_1 = require("./test-utils");
|
|
9
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
10
|
+
const tree_kill_1 = __importDefault(require("tree-kill"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const db_service_js_1 = require("../src/fraim/db-service.js");
|
|
13
|
+
async function testMcpConnection() {
|
|
14
|
+
console.log(' 🚀 Testing MCP Connection Reliability...');
|
|
15
|
+
let fraimProcess;
|
|
16
|
+
let dbService;
|
|
17
|
+
const PORT = Math.floor(Math.random() * 1000) + 12000;
|
|
18
|
+
const BASE_URL = `http://localhost:${PORT}`;
|
|
19
|
+
const TEST_API_KEY = 'test-fraim-key-mcp-conn';
|
|
20
|
+
const TEST_ADMIN_KEY = 'test-admin-key-mcp-conn';
|
|
21
|
+
try {
|
|
22
|
+
// 0. Setup DB and Key
|
|
23
|
+
dbService = new db_service_js_1.FraimDbService();
|
|
24
|
+
await dbService.connect();
|
|
25
|
+
const db = dbService.db;
|
|
26
|
+
await db.collection('fraim_api_keys').deleteOne({ key: TEST_API_KEY });
|
|
27
|
+
await db.collection('fraim_api_keys').insertOne({
|
|
28
|
+
key: TEST_API_KEY,
|
|
29
|
+
userId: 'test-user-mcp',
|
|
30
|
+
orgId: 'test-org',
|
|
31
|
+
isActive: true,
|
|
32
|
+
createdAt: new Date()
|
|
33
|
+
});
|
|
34
|
+
// 1. Start Server
|
|
35
|
+
console.log(` Starting server on port ${PORT}...`);
|
|
36
|
+
const npxCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
37
|
+
const serverScript = path_1.default.resolve(__dirname, '../dist/src/fraim-mcp-server.js');
|
|
38
|
+
fraimProcess = (0, node_child_process_1.spawn)(npxCommand, ['node', `"${serverScript}"`], {
|
|
39
|
+
env: {
|
|
40
|
+
...process.env,
|
|
41
|
+
FRAIM_MCP_PORT: PORT.toString(),
|
|
42
|
+
FRAIM_ADMIN_KEY: TEST_ADMIN_KEY,
|
|
43
|
+
FRAIM_SKIP_INDEX_ON_START: 'true'
|
|
44
|
+
},
|
|
45
|
+
stdio: 'pipe',
|
|
46
|
+
shell: true
|
|
47
|
+
});
|
|
48
|
+
if (fraimProcess.stdout)
|
|
49
|
+
fraimProcess.stdout.pipe(process.stdout);
|
|
50
|
+
if (fraimProcess.stderr)
|
|
51
|
+
fraimProcess.stderr.pipe(process.stderr);
|
|
52
|
+
// Wait for start
|
|
53
|
+
let started = false;
|
|
54
|
+
for (let i = 0; i < 15; i++) {
|
|
55
|
+
try {
|
|
56
|
+
await axios_1.default.get(`${BASE_URL}/health`, { timeout: 500 });
|
|
57
|
+
started = true;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!started)
|
|
65
|
+
throw new Error('Server failed to start');
|
|
66
|
+
console.log(' Server started!');
|
|
67
|
+
const authHeaders = { 'x-api-key': TEST_API_KEY };
|
|
68
|
+
// 2. Test notifications/initialized (Fix 1 verification)
|
|
69
|
+
console.log(' Testing notifications/initialized...');
|
|
70
|
+
try {
|
|
71
|
+
const res = await axios_1.default.post(`${BASE_URL}/mcp`, {
|
|
72
|
+
jsonrpc: '2.0',
|
|
73
|
+
method: 'notifications/initialized',
|
|
74
|
+
params: {}
|
|
75
|
+
}, { headers: authHeaders });
|
|
76
|
+
// Should be 200 OK now, not 500
|
|
77
|
+
node_assert_1.default.strictEqual(res.status, 200, 'Should return 200 OK for notifications/initialized');
|
|
78
|
+
node_assert_1.default.strictEqual(res.data.result, true, 'Should return result: true');
|
|
79
|
+
console.log(' ✅ notifications/initialized handled correctly.');
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error(' ❌ Notification failed:', error.message);
|
|
83
|
+
if (error.response) {
|
|
84
|
+
console.error(' Response:', JSON.stringify(error.response.data));
|
|
85
|
+
}
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
// 3. Test DELETE request crash (Fix 2 verification)
|
|
89
|
+
console.log(' Testing DELETE request (simulate disconnect crash)...');
|
|
90
|
+
try {
|
|
91
|
+
// DELETE requests often have no body.
|
|
92
|
+
// The bug was reading req.body.id => crash if req.body is undefined/empty
|
|
93
|
+
await axios_1.default.delete(`${BASE_URL}/mcp`, {
|
|
94
|
+
headers: authHeaders,
|
|
95
|
+
validateStatus: (status) => true // Accept any status
|
|
96
|
+
});
|
|
97
|
+
console.log(' ✅ DELETE request completed without network error (Server survived).');
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
console.error(' ❌ Server likely crashed on DELETE:', error.message);
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
// Verify Server is STILL ALIVE after DELETE
|
|
104
|
+
const healthRes = await axios_1.default.get(`${BASE_URL}/health`);
|
|
105
|
+
node_assert_1.default.strictEqual(healthRes.status, 200, 'Server should be alive after DELETE request');
|
|
106
|
+
console.log(' ✅ Server verified alive after malformed request.');
|
|
107
|
+
// 4. Test SSE Endpoint Handshake
|
|
108
|
+
console.log(' Testing SSE Handshake...');
|
|
109
|
+
const sseRes = await axios_1.default.get(`${BASE_URL}/mcp`, {
|
|
110
|
+
headers: {
|
|
111
|
+
'Accept': 'text/event-stream',
|
|
112
|
+
...authHeaders
|
|
113
|
+
},
|
|
114
|
+
responseType: 'stream'
|
|
115
|
+
});
|
|
116
|
+
const stream = sseRes.data;
|
|
117
|
+
let dataReceived = '';
|
|
118
|
+
await new Promise((resolve, reject) => {
|
|
119
|
+
stream.on('data', (chunk) => {
|
|
120
|
+
dataReceived += chunk.toString();
|
|
121
|
+
if (dataReceived.includes('event: endpoint')) {
|
|
122
|
+
stream.destroy();
|
|
123
|
+
resolve();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
stream.on('error', reject);
|
|
127
|
+
setTimeout(() => { stream.destroy(); resolve(); }, 2000);
|
|
128
|
+
});
|
|
129
|
+
node_assert_1.default.ok(dataReceived.includes('event: endpoint'), 'Should receive endpoint event');
|
|
130
|
+
// Verify absolute URL format
|
|
131
|
+
const endpointLine = dataReceived.split('\n').find(l => l.startsWith('data: http'));
|
|
132
|
+
node_assert_1.default.ok(endpointLine, 'Should find data line with absolute URL starting with http');
|
|
133
|
+
node_assert_1.default.ok(endpointLine?.includes(`localhost:${PORT}/mcp`), 'Absolute URL should point to correct port/path');
|
|
134
|
+
console.log(' ✅ SSE Handshake verified with absolute URL.');
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
console.error(' ❌ Test failed:', error.message);
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
if (dbService) {
|
|
143
|
+
const db = dbService.db;
|
|
144
|
+
await db.collection('fraim_api_keys').deleteOne({ key: TEST_API_KEY }).catch(() => { });
|
|
145
|
+
await dbService.close();
|
|
146
|
+
}
|
|
147
|
+
if (fraimProcess && fraimProcess.pid) {
|
|
148
|
+
console.log(' Cleanup: Killing server...');
|
|
149
|
+
await new Promise(resolve => (0, tree_kill_1.default)(fraimProcess.pid, 'SIGKILL', () => resolve()));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const testCases = [
|
|
154
|
+
{
|
|
155
|
+
name: 'MCP Connection & Stability',
|
|
156
|
+
description: 'Verifies connection protocol, notification handling, and crash resilience',
|
|
157
|
+
testFunction: testMcpConnection,
|
|
158
|
+
tags: ['mcp', 'connection']
|
|
159
|
+
}
|
|
160
|
+
];
|
|
161
|
+
(0, test_utils_1.runTests)(testCases, async (t) => t.testFunction(), 'MCP Connection Reliability')
|
|
162
|
+
.then(() => process.exit(0))
|
|
163
|
+
.catch((err) => {
|
|
164
|
+
console.error('Test runner failed:', err);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
});
|