fraim-framework 2.0.47 โ 2.0.49
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/dist/registry/ai-manager-rules/implement-phases/implement-code.md +7 -10
- package/dist/registry/ai-manager-rules/implement-phases/implement-smoke.md +14 -7
- package/dist/registry/ai-manager-rules/implement-phases/implement-validate.md +12 -8
- package/dist/registry/ai-manager-rules/shared-phases/address-pr-feedback.md +188 -0
- package/dist/registry/providers/ado.json +19 -0
- package/dist/registry/providers/github.json +19 -0
- package/dist/src/ai-manager/ai-manager.js +8 -1
- package/dist/src/ai-manager/phase-flow.js +36 -8
- package/dist/src/cli/commands/init-project.js +5 -4
- package/dist/src/cli/commands/init.js +8 -7
- package/dist/src/cli/setup/first-run.js +116 -29
- package/dist/src/fraim/config-loader.js +58 -23
- package/dist/src/fraim/db-service.js +46 -0
- package/dist/src/fraim/issue-tracking/ado-provider.js +304 -0
- package/dist/src/fraim/issue-tracking/factory.js +63 -0
- package/dist/src/fraim/issue-tracking/github-provider.js +200 -0
- package/dist/src/fraim/issue-tracking/types.js +7 -0
- package/dist/src/fraim/issue-tracking-config.js +83 -0
- package/dist/src/fraim/issues.js +25 -23
- package/dist/src/fraim/setup-wizard.js +5 -3
- package/dist/src/fraim/template-processor.js +156 -30
- package/dist/src/fraim/types.js +21 -23
- package/dist/src/fraim-mcp-server.js +350 -36
- package/dist/src/utils/git-utils.js +38 -3
- package/dist/src/utils/platform-detection.js +213 -0
- package/dist/tests/test-cli.js +6 -10
- package/dist/tests/test-debug-session.js +130 -0
- package/dist/tests/test-enhanced-session-init.js +184 -0
- package/dist/tests/test-first-run-interactive.js +1 -0
- package/dist/tests/test-first-run-journey.js +274 -54
- package/dist/tests/test-fraim-issues.js +1 -1
- package/dist/tests/test-genericization.js +5 -25
- package/dist/tests/test-mcp-issue-integration.js +6 -2
- package/dist/tests/test-mcp-template-processing.js +156 -0
- package/dist/tests/test-modular-issue-tracking.js +161 -0
- package/dist/tests/test-package-size.js +7 -0
- package/dist/tests/test-pr-review-workflow.js +10 -6
- package/dist/tests/test-session-rehydration.js +5 -1
- package/dist/tests/test-telemetry.js +4 -0
- package/dist/tests/test-workflow-discovery.js +242 -0
- package/package.json +3 -1
- package/registry/scripts/create-website-structure.js +17 -2
- package/registry/scripts/profile-server.ts +1 -0
- package/registry/stubs/workflows/compliance/soc2-evidence-generator.md +11 -0
- package/registry/stubs/workflows/learning/build-skillset.md +11 -0
- package/registry/stubs/workflows/legal/contract-review-analysis.md +11 -0
- package/registry/stubs/workflows/legal/saas-contract-development.md +11 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Test Multi-Provider Platform Support (Issue #39)
|
|
4
|
+
*
|
|
5
|
+
* Verifies that the template engine correctly processes workflows
|
|
6
|
+
* with provider-specific action mappings for GitHub and ADO.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.testMultiProviderSupport = testMultiProviderSupport;
|
|
10
|
+
const template_processor_1 = require("../src/fraim/template-processor");
|
|
11
|
+
const platform_detection_1 = require("../src/utils/platform-detection");
|
|
12
|
+
async function testMultiProviderSupport() {
|
|
13
|
+
console.log('๐งช Testing Multi-Provider Platform Support (Issue #39)...');
|
|
14
|
+
try {
|
|
15
|
+
// Test 1: Template Engine Creation
|
|
16
|
+
console.log(' Test 1: Template engine initialization...');
|
|
17
|
+
const templateEngine = new template_processor_1.TemplateEngine();
|
|
18
|
+
const providers = templateEngine.getAvailableProviders();
|
|
19
|
+
if (!providers.includes('github') || !providers.includes('ado')) {
|
|
20
|
+
throw new Error('Expected GitHub and ADO providers not found');
|
|
21
|
+
}
|
|
22
|
+
console.log(` โ
Loaded providers: ${providers.join(', ')}`);
|
|
23
|
+
// Test 2: GitHub Template Processing
|
|
24
|
+
console.log(' Test 2: GitHub template processing...');
|
|
25
|
+
const githubConfig = {
|
|
26
|
+
version: '2.0.47',
|
|
27
|
+
project: { name: 'Test Project' },
|
|
28
|
+
repository: {
|
|
29
|
+
provider: 'github',
|
|
30
|
+
owner: 'testowner',
|
|
31
|
+
name: 'testrepo',
|
|
32
|
+
defaultBranch: 'main'
|
|
33
|
+
},
|
|
34
|
+
persona: { name: 'Test', voice: 'Test', emailSignature: 'Test', displayNamePattern: 'Test' }
|
|
35
|
+
};
|
|
36
|
+
const testWorkflow = `
|
|
37
|
+
# Test Workflow
|
|
38
|
+
{{get_issue}} to get issue details.
|
|
39
|
+
{{create_pr}} to create pull request.
|
|
40
|
+
{{merge_pr}} to merge when ready.
|
|
41
|
+
`.trim();
|
|
42
|
+
const githubProcessed = templateEngine.processWorkflow(testWorkflow, githubConfig);
|
|
43
|
+
if (!githubProcessed.includes('mcp_github_issue_read')) {
|
|
44
|
+
throw new Error('GitHub template processing failed - missing mcp_github_issue_read');
|
|
45
|
+
}
|
|
46
|
+
if (!githubProcessed.includes('testowner')) {
|
|
47
|
+
throw new Error('GitHub template processing failed - missing owner substitution');
|
|
48
|
+
}
|
|
49
|
+
console.log(' โ
GitHub template processing works');
|
|
50
|
+
// Test 3: ADO Template Processing
|
|
51
|
+
console.log(' Test 3: ADO template processing...');
|
|
52
|
+
const adoConfig = {
|
|
53
|
+
version: '2.0.47',
|
|
54
|
+
project: { name: 'Test Project' },
|
|
55
|
+
repository: {
|
|
56
|
+
provider: 'ado',
|
|
57
|
+
organization: 'testorg',
|
|
58
|
+
project: 'testproject',
|
|
59
|
+
name: 'testrepo',
|
|
60
|
+
defaultBranch: 'main'
|
|
61
|
+
},
|
|
62
|
+
persona: { name: 'Test', voice: 'Test', emailSignature: 'Test', displayNamePattern: 'Test' }
|
|
63
|
+
};
|
|
64
|
+
const adoProcessed = templateEngine.processWorkflow(testWorkflow, adoConfig);
|
|
65
|
+
if (!adoProcessed.includes('mcp_ado_get_work_item')) {
|
|
66
|
+
throw new Error('ADO template processing failed - missing mcp_ado_get_work_item');
|
|
67
|
+
}
|
|
68
|
+
if (!adoProcessed.includes('testorg')) {
|
|
69
|
+
throw new Error('ADO template processing failed - missing organization substitution');
|
|
70
|
+
}
|
|
71
|
+
console.log(' โ
ADO template processing works');
|
|
72
|
+
// Test 4: Platform Detection
|
|
73
|
+
console.log(' Test 4: Platform detection...');
|
|
74
|
+
const githubDetection = (0, platform_detection_1.detectPlatformFromUrl)('https://github.com/owner/repo.git');
|
|
75
|
+
if (githubDetection.provider !== 'github' || githubDetection.repository?.owner !== 'owner') {
|
|
76
|
+
throw new Error('GitHub URL detection failed');
|
|
77
|
+
}
|
|
78
|
+
const adoDetection = (0, platform_detection_1.detectPlatformFromUrl)('https://dev.azure.com/org/project/_git/repo');
|
|
79
|
+
if (adoDetection.provider !== 'ado' || adoDetection.repository?.organization !== 'org') {
|
|
80
|
+
throw new Error('ADO URL detection failed');
|
|
81
|
+
}
|
|
82
|
+
console.log(' โ
Platform detection works');
|
|
83
|
+
// Test 5: Repository Config Validation
|
|
84
|
+
console.log(' Test 5: Repository config validation...');
|
|
85
|
+
const validGitHub = (0, platform_detection_1.validateRepositoryConfig)({
|
|
86
|
+
provider: 'github',
|
|
87
|
+
owner: 'test',
|
|
88
|
+
name: 'repo'
|
|
89
|
+
});
|
|
90
|
+
if (!validGitHub.valid) {
|
|
91
|
+
throw new Error(`Valid GitHub config rejected: ${validGitHub.errors.join(', ')}`);
|
|
92
|
+
}
|
|
93
|
+
const validAdo = (0, platform_detection_1.validateRepositoryConfig)({
|
|
94
|
+
provider: 'ado',
|
|
95
|
+
organization: 'org',
|
|
96
|
+
project: 'proj',
|
|
97
|
+
name: 'repo'
|
|
98
|
+
});
|
|
99
|
+
if (!validAdo.valid) {
|
|
100
|
+
throw new Error(`Valid ADO config rejected: ${validAdo.errors.join(', ')}`);
|
|
101
|
+
}
|
|
102
|
+
const invalidConfig = (0, platform_detection_1.validateRepositoryConfig)({
|
|
103
|
+
provider: 'github'
|
|
104
|
+
// Missing required fields
|
|
105
|
+
});
|
|
106
|
+
if (invalidConfig.valid) {
|
|
107
|
+
throw new Error('Invalid config should have been rejected');
|
|
108
|
+
}
|
|
109
|
+
console.log(' โ
Repository config validation works');
|
|
110
|
+
// Test 6: Template Action Coverage
|
|
111
|
+
console.log(' Test 6: Template action coverage...');
|
|
112
|
+
const githubActions = templateEngine.getProviderActions('github');
|
|
113
|
+
const adoActions = templateEngine.getProviderActions('ado');
|
|
114
|
+
const requiredActions = ['get_issue', 'create_pr', 'merge_pr', 'update_issue_status'];
|
|
115
|
+
for (const action of requiredActions) {
|
|
116
|
+
if (!githubActions.includes(action)) {
|
|
117
|
+
throw new Error(`GitHub missing required action: ${action}`);
|
|
118
|
+
}
|
|
119
|
+
if (!adoActions.includes(action)) {
|
|
120
|
+
throw new Error(`ADO missing required action: ${action}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
console.log(` โ
Action coverage: GitHub(${githubActions.length}), ADO(${adoActions.length})`);
|
|
124
|
+
// Test 7: Workflow Validation
|
|
125
|
+
console.log(' Test 7: Workflow validation...');
|
|
126
|
+
const validation = templateEngine.validateWorkflow(testWorkflow, 'github');
|
|
127
|
+
if (!validation.valid) {
|
|
128
|
+
throw new Error(`Workflow validation failed: ${validation.missingActions.join(', ')}`);
|
|
129
|
+
}
|
|
130
|
+
const invalidWorkflow = '{{nonexistent_action}} should fail';
|
|
131
|
+
const invalidValidation = templateEngine.validateWorkflow(invalidWorkflow, 'github');
|
|
132
|
+
if (invalidValidation.valid) {
|
|
133
|
+
throw new Error('Invalid workflow should have failed validation');
|
|
134
|
+
}
|
|
135
|
+
console.log(' โ
Workflow validation works');
|
|
136
|
+
console.log('โ
All Multi-Provider Platform Support tests passed!');
|
|
137
|
+
console.log(`
|
|
138
|
+
๐ **Summary**:
|
|
139
|
+
- Providers: ${providers.join(', ')}
|
|
140
|
+
- GitHub Actions: ${githubActions.length}
|
|
141
|
+
- ADO Actions: ${adoActions.length}
|
|
142
|
+
- Template Processing: โ
Working
|
|
143
|
+
- Platform Detection: โ
Working
|
|
144
|
+
- Config Validation: โ
Working
|
|
145
|
+
`);
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
console.error('โ Multi-Provider Platform Support test failed:', error.message);
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Run if called directly
|
|
154
|
+
if (require.main === module) {
|
|
155
|
+
testMultiProviderSupport()
|
|
156
|
+
.then(success => process.exit(success ? 0 : 1))
|
|
157
|
+
.catch(err => {
|
|
158
|
+
console.error('Test runner error:', err);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
@@ -26,6 +26,9 @@ function findProjectRoot() {
|
|
|
26
26
|
const projectRoot = findProjectRoot();
|
|
27
27
|
const fullWorkflowsPath = path_1.default.join(projectRoot, 'registry', 'workflows');
|
|
28
28
|
const stubWorkflowsPath = path_1.default.join(projectRoot, 'registry', 'stubs', 'workflows');
|
|
29
|
+
console.log(`Project root: ${projectRoot}`);
|
|
30
|
+
console.log(`Full workflows path: ${fullWorkflowsPath}`);
|
|
31
|
+
console.log(`Stub workflows path: ${stubWorkflowsPath}`);
|
|
29
32
|
(0, node_assert_1.default)(fs_1.default.existsSync(fullWorkflowsPath), 'Full workflows directory should exist');
|
|
30
33
|
(0, node_assert_1.default)(fs_1.default.existsSync(stubWorkflowsPath), 'Stub workflows directory should exist');
|
|
31
34
|
// Calculate sizes
|
|
@@ -39,6 +42,10 @@ function findProjectRoot() {
|
|
|
39
42
|
// Should have same number of files
|
|
40
43
|
const fullFiles = getAllFiles(fullWorkflowsPath, '.md');
|
|
41
44
|
const stubFiles = getAllFiles(stubWorkflowsPath, '.md');
|
|
45
|
+
console.log(`Full files found: ${fullFiles.length}`);
|
|
46
|
+
console.log(`Stub files found: ${stubFiles.length}`);
|
|
47
|
+
console.log(`First few full files: ${fullFiles.slice(0, 3).join(', ')}`);
|
|
48
|
+
console.log(`First few stub files: ${stubFiles.slice(0, 3).join(', ')}`);
|
|
42
49
|
node_assert_1.default.strictEqual(stubFiles.length, fullFiles.length, 'Should have same number of workflow files');
|
|
43
50
|
console.log(`โ
Package size reduced by ${Math.round((1 - stubSize / fullSize) * 100)}% while maintaining all ${stubFiles.length} workflows`);
|
|
44
51
|
});
|
|
@@ -43,9 +43,9 @@ async function testPRReviewFailureHandling() {
|
|
|
43
43
|
// Test submit-pr failure
|
|
44
44
|
const submitPRFailure = (0, phase_flow_js_1.getPhaseOnFailure)('submit-pr', 'implement', 'bug');
|
|
45
45
|
node_assert_1.strict.equal(submitPRFailure, 'implement-completeness-review', 'submit-pr failure should return to completeness-review');
|
|
46
|
-
// Test wait-for-pr-review failure (
|
|
46
|
+
// Test wait-for-pr-review failure (should go to address-pr-feedback)
|
|
47
47
|
const waitPRFailure = (0, phase_flow_js_1.getPhaseOnFailure)('wait-for-pr-review', 'implement', 'bug');
|
|
48
|
-
node_assert_1.strict.equal(waitPRFailure, '
|
|
48
|
+
node_assert_1.strict.equal(waitPRFailure, 'address-pr-feedback', 'wait-for-pr-review failure should go to address-pr-feedback');
|
|
49
49
|
// Test all workflow types have new phases
|
|
50
50
|
const specPhases = (0, phase_flow_js_1.getPhasesForWorkflow)('spec');
|
|
51
51
|
(0, node_assert_1.strict)(specPhases.includes('submit-pr'), 'Spec workflow should include submit-pr');
|
|
@@ -191,10 +191,14 @@ async function testRealWorldPRReviewScenario() {
|
|
|
191
191
|
node_assert_1.strict.equal(finalPhase, null, 'wait-for-pr-review should be final phase when approved');
|
|
192
192
|
// Test PR review with changes requested (failure scenario)
|
|
193
193
|
const failurePhase = (0, phase_flow_js_1.getPhaseOnFailure)('wait-for-pr-review', workflowType, issueType);
|
|
194
|
-
node_assert_1.strict.equal(failurePhase, '
|
|
195
|
-
// Test iteration: after
|
|
196
|
-
let iterationPhase = '
|
|
197
|
-
|
|
194
|
+
node_assert_1.strict.equal(failurePhase, 'address-pr-feedback', 'PR changes requested should route to address-pr-feedback');
|
|
195
|
+
// Test iteration: after addressing feedback, go through validation again
|
|
196
|
+
let iterationPhase = 'address-pr-feedback';
|
|
197
|
+
// After addressing feedback, should go to validation phase
|
|
198
|
+
const nextAfterFeedback = (0, phase_flow_js_1.getNextPhase)(iterationPhase, workflowType, issueType);
|
|
199
|
+
node_assert_1.strict.equal(nextAfterFeedback, 'implement-validate', 'After address-pr-feedback should go to implement-validate');
|
|
200
|
+
iterationPhase = nextAfterFeedback;
|
|
201
|
+
const iterationFlow = ['implement-smoke', 'implement-regression', 'implement-completeness-review', 'submit-pr', 'wait-for-pr-review'];
|
|
198
202
|
for (const expectedNext of iterationFlow) {
|
|
199
203
|
const nextPhase = (0, phase_flow_js_1.getNextPhase)(iterationPhase, workflowType, issueType);
|
|
200
204
|
node_assert_1.strict.equal(nextPhase, expectedNext, `Iteration: after ${iterationPhase} should go to ${expectedNext}`);
|
|
@@ -92,7 +92,11 @@ async function testRehydrationAndExemptions() {
|
|
|
92
92
|
params: {
|
|
93
93
|
name: 'fraim_connect',
|
|
94
94
|
arguments: {
|
|
95
|
-
|
|
95
|
+
agent: {
|
|
96
|
+
name: 'Claude',
|
|
97
|
+
model: 'test-model'
|
|
98
|
+
},
|
|
99
|
+
machine: { hostname: 'repro-host', platform: 'test' },
|
|
96
100
|
repo: { url: 'http://github.com/repro' }
|
|
97
101
|
}
|
|
98
102
|
}
|
|
@@ -94,6 +94,10 @@ async function testTelemetryFlow() {
|
|
|
94
94
|
params: {
|
|
95
95
|
name: 'fraim_connect',
|
|
96
96
|
arguments: {
|
|
97
|
+
agent: {
|
|
98
|
+
name: 'Claude',
|
|
99
|
+
model: 'test-model'
|
|
100
|
+
},
|
|
97
101
|
machine: { hostname: 'test-host', platform: 'test-os' },
|
|
98
102
|
repo: { url: 'http://github.com/test/repo' }
|
|
99
103
|
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests for dynamic workflow discovery in MCP server
|
|
4
|
+
* Ensures workflows from all categories can be found without hardcoded category lists
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
const node_test_1 = require("node:test");
|
|
11
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
12
|
+
const shared_server_utils_js_1 = require("./shared-server-utils.js");
|
|
13
|
+
(0, node_test_1.describe)('Workflow Discovery', () => {
|
|
14
|
+
(0, node_test_1.before)(async () => {
|
|
15
|
+
// Wait for the test server to be ready
|
|
16
|
+
await (0, shared_server_utils_js_1.waitForServer)();
|
|
17
|
+
});
|
|
18
|
+
(0, node_test_1.describe)('Dynamic Category Discovery', () => {
|
|
19
|
+
(0, node_test_1.it)('should find workflows from product-building category', async () => {
|
|
20
|
+
const axios = require('axios');
|
|
21
|
+
const response = await axios.post((0, shared_server_utils_js_1.getMcpEndpoint)(), {
|
|
22
|
+
jsonrpc: '2.0',
|
|
23
|
+
id: 1,
|
|
24
|
+
method: 'tools/call',
|
|
25
|
+
params: {
|
|
26
|
+
name: 'get_fraim_workflow',
|
|
27
|
+
arguments: { workflow: 'spec' }
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
node_assert_1.default.strictEqual(response.status, 200);
|
|
31
|
+
node_assert_1.default.ok(response.data.result);
|
|
32
|
+
node_assert_1.default.ok(response.data.result.content);
|
|
33
|
+
node_assert_1.default.ok(Array.isArray(response.data.result.content));
|
|
34
|
+
const content = response.data.result.content[0];
|
|
35
|
+
node_assert_1.default.strictEqual(content.type, 'text');
|
|
36
|
+
node_assert_1.default.ok(content.text.includes('Workflow: spec'));
|
|
37
|
+
node_assert_1.default.ok(content.text.includes('workflows/product-building/spec.md'));
|
|
38
|
+
});
|
|
39
|
+
(0, node_test_1.it)('should find workflows from compliance category', async () => {
|
|
40
|
+
const axios = require('axios');
|
|
41
|
+
const response = await axios.post((0, shared_server_utils_js_1.getMcpEndpoint)(), {
|
|
42
|
+
jsonrpc: '2.0',
|
|
43
|
+
id: 2,
|
|
44
|
+
method: 'tools/call',
|
|
45
|
+
params: {
|
|
46
|
+
name: 'get_fraim_workflow',
|
|
47
|
+
arguments: { workflow: 'detect-compliance-requirements' }
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
node_assert_1.default.strictEqual(response.status, 200);
|
|
51
|
+
node_assert_1.default.ok(response.data.result);
|
|
52
|
+
node_assert_1.default.ok(response.data.result.content);
|
|
53
|
+
const content = response.data.result.content[0];
|
|
54
|
+
node_assert_1.default.strictEqual(content.type, 'text');
|
|
55
|
+
node_assert_1.default.ok(content.text.includes('Workflow: detect-compliance-requirements'));
|
|
56
|
+
node_assert_1.default.ok(content.text.includes('workflows/compliance/detect-compliance-requirements.md'));
|
|
57
|
+
node_assert_1.default.ok(content.text.includes('Detect Compliance Requirements'));
|
|
58
|
+
});
|
|
59
|
+
(0, node_test_1.it)('should find workflows from brainstorming category', async () => {
|
|
60
|
+
const axios = require('axios');
|
|
61
|
+
const response = await axios.post((0, shared_server_utils_js_1.getMcpEndpoint)(), {
|
|
62
|
+
jsonrpc: '2.0',
|
|
63
|
+
id: 3,
|
|
64
|
+
method: 'tools/call',
|
|
65
|
+
params: {
|
|
66
|
+
name: 'get_fraim_workflow',
|
|
67
|
+
arguments: { workflow: 'blue-sky-brainstorming' }
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
node_assert_1.default.strictEqual(response.status, 200);
|
|
71
|
+
node_assert_1.default.ok(response.data.result);
|
|
72
|
+
node_assert_1.default.ok(response.data.result.content);
|
|
73
|
+
const content = response.data.result.content[0];
|
|
74
|
+
node_assert_1.default.strictEqual(content.type, 'text');
|
|
75
|
+
node_assert_1.default.ok(content.text.includes('Workflow: blue-sky-brainstorming'));
|
|
76
|
+
node_assert_1.default.ok(content.text.includes('workflows/brainstorming/blue-sky-brainstorming.md'));
|
|
77
|
+
});
|
|
78
|
+
(0, node_test_1.it)('should find workflows from learning category', async () => {
|
|
79
|
+
const axios = require('axios');
|
|
80
|
+
const response = await axios.post((0, shared_server_utils_js_1.getMcpEndpoint)(), {
|
|
81
|
+
jsonrpc: '2.0',
|
|
82
|
+
id: 4,
|
|
83
|
+
method: 'tools/call',
|
|
84
|
+
params: {
|
|
85
|
+
name: 'get_fraim_workflow',
|
|
86
|
+
arguments: { workflow: 'synthesize-learnings' }
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
node_assert_1.default.strictEqual(response.status, 200);
|
|
90
|
+
node_assert_1.default.ok(response.data.result);
|
|
91
|
+
node_assert_1.default.ok(response.data.result.content);
|
|
92
|
+
const content = response.data.result.content[0];
|
|
93
|
+
node_assert_1.default.strictEqual(content.type, 'text');
|
|
94
|
+
node_assert_1.default.ok(content.text.includes('Workflow: synthesize-learnings'));
|
|
95
|
+
node_assert_1.default.ok(content.text.includes('workflows/learning/synthesize-learnings.md'));
|
|
96
|
+
});
|
|
97
|
+
(0, node_test_1.it)('should find workflows from replicate category', async () => {
|
|
98
|
+
const axios = require('axios');
|
|
99
|
+
const response = await axios.post((0, shared_server_utils_js_1.getMcpEndpoint)(), {
|
|
100
|
+
jsonrpc: '2.0',
|
|
101
|
+
id: 5,
|
|
102
|
+
method: 'tools/call',
|
|
103
|
+
params: {
|
|
104
|
+
name: 'get_fraim_workflow',
|
|
105
|
+
arguments: { workflow: 'replicate-discovery' }
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
node_assert_1.default.strictEqual(response.status, 200);
|
|
109
|
+
node_assert_1.default.ok(response.data.result);
|
|
110
|
+
node_assert_1.default.ok(response.data.result.content);
|
|
111
|
+
const content = response.data.result.content[0];
|
|
112
|
+
node_assert_1.default.strictEqual(content.type, 'text');
|
|
113
|
+
node_assert_1.default.ok(content.text.includes('Workflow: replicate-discovery'));
|
|
114
|
+
node_assert_1.default.ok(content.text.includes('workflows/replicate/replicate-discovery.md'));
|
|
115
|
+
});
|
|
116
|
+
(0, node_test_1.it)('should find workflows from marketing category', async () => {
|
|
117
|
+
const axios = require('axios');
|
|
118
|
+
const response = await axios.post((0, shared_server_utils_js_1.getMcpEndpoint)(), {
|
|
119
|
+
jsonrpc: '2.0',
|
|
120
|
+
id: 6,
|
|
121
|
+
method: 'tools/call',
|
|
122
|
+
params: {
|
|
123
|
+
name: 'get_fraim_workflow',
|
|
124
|
+
arguments: { workflow: 'create-modern-website' }
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
node_assert_1.default.strictEqual(response.status, 200);
|
|
128
|
+
node_assert_1.default.ok(response.data.result);
|
|
129
|
+
node_assert_1.default.ok(response.data.result.content);
|
|
130
|
+
const content = response.data.result.content[0];
|
|
131
|
+
node_assert_1.default.strictEqual(content.type, 'text');
|
|
132
|
+
node_assert_1.default.ok(content.text.includes('Workflow: create-modern-website'));
|
|
133
|
+
node_assert_1.default.ok(content.text.includes('workflows/marketing/create-modern-website.md'));
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
(0, node_test_1.describe)('Error Handling', () => {
|
|
137
|
+
(0, node_test_1.it)('should return helpful error for non-existent workflow', async () => {
|
|
138
|
+
const axios = require('axios');
|
|
139
|
+
const response = await axios.post((0, shared_server_utils_js_1.getMcpEndpoint)(), {
|
|
140
|
+
jsonrpc: '2.0',
|
|
141
|
+
id: 7,
|
|
142
|
+
method: 'tools/call',
|
|
143
|
+
params: {
|
|
144
|
+
name: 'get_fraim_workflow',
|
|
145
|
+
arguments: { workflow: 'non-existent-workflow' }
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
node_assert_1.default.strictEqual(response.status, 200);
|
|
149
|
+
node_assert_1.default.ok(response.data.result);
|
|
150
|
+
node_assert_1.default.ok(response.data.result.content);
|
|
151
|
+
const content = response.data.result.content[0];
|
|
152
|
+
node_assert_1.default.strictEqual(content.type, 'text');
|
|
153
|
+
node_assert_1.default.ok(content.text.includes('Workflow "non-existent-workflow" not found'));
|
|
154
|
+
node_assert_1.default.ok(content.text.includes('Available workflows:'));
|
|
155
|
+
});
|
|
156
|
+
(0, node_test_1.it)('should handle workflow names with .md extension', async () => {
|
|
157
|
+
const axios = require('axios');
|
|
158
|
+
const response = await axios.post((0, shared_server_utils_js_1.getMcpEndpoint)(), {
|
|
159
|
+
jsonrpc: '2.0',
|
|
160
|
+
id: 8,
|
|
161
|
+
method: 'tools/call',
|
|
162
|
+
params: {
|
|
163
|
+
name: 'get_fraim_workflow',
|
|
164
|
+
arguments: { workflow: 'spec.md' }
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
node_assert_1.default.strictEqual(response.status, 200);
|
|
168
|
+
node_assert_1.default.ok(response.data.result);
|
|
169
|
+
node_assert_1.default.ok(response.data.result.content);
|
|
170
|
+
const content = response.data.result.content[0];
|
|
171
|
+
node_assert_1.default.strictEqual(content.type, 'text');
|
|
172
|
+
node_assert_1.default.ok(content.text.includes('Workflow: spec'));
|
|
173
|
+
node_assert_1.default.ok(content.text.includes('workflows/product-building/spec.md'));
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
(0, node_test_1.describe)('Regression Prevention', () => {
|
|
177
|
+
(0, node_test_1.it)('should discover all workflow categories dynamically', async () => {
|
|
178
|
+
const axios = require('axios');
|
|
179
|
+
// Test that we can find workflows from multiple categories without hardcoding
|
|
180
|
+
const testCases = [
|
|
181
|
+
{ workflow: 'spec', expectedCategory: 'product-building' },
|
|
182
|
+
{ workflow: 'detect-compliance-requirements', expectedCategory: 'compliance' },
|
|
183
|
+
{ workflow: 'blue-sky-brainstorming', expectedCategory: 'brainstorming' },
|
|
184
|
+
{ workflow: 'synthesize-learnings', expectedCategory: 'learning' },
|
|
185
|
+
{ workflow: 'replicate-discovery', expectedCategory: 'replicate' },
|
|
186
|
+
{ workflow: 'create-modern-website', expectedCategory: 'marketing' }
|
|
187
|
+
];
|
|
188
|
+
for (const testCase of testCases) {
|
|
189
|
+
const response = await axios.post((0, shared_server_utils_js_1.getMcpEndpoint)(), {
|
|
190
|
+
jsonrpc: '2.0',
|
|
191
|
+
id: 100 + testCases.indexOf(testCase),
|
|
192
|
+
method: 'tools/call',
|
|
193
|
+
params: {
|
|
194
|
+
name: 'get_fraim_workflow',
|
|
195
|
+
arguments: { workflow: testCase.workflow }
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
node_assert_1.default.strictEqual(response.status, 200, `Failed to find workflow: ${testCase.workflow}`);
|
|
199
|
+
node_assert_1.default.ok(response.data.result, `No result for workflow: ${testCase.workflow}`);
|
|
200
|
+
node_assert_1.default.ok(response.data.result.content, `No content for workflow: ${testCase.workflow}`);
|
|
201
|
+
const content = response.data.result.content[0];
|
|
202
|
+
node_assert_1.default.strictEqual(content.type, 'text');
|
|
203
|
+
node_assert_1.default.ok(content.text.includes(`workflows/${testCase.expectedCategory}/${testCase.workflow}.md`), `Expected workflow ${testCase.workflow} to be found in category ${testCase.expectedCategory}, but got: ${content.text.substring(0, 200)}`);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
(0, node_test_1.it)('should not fail when new categories are added', async () => {
|
|
207
|
+
// This test documents the expected behavior:
|
|
208
|
+
// When new workflow categories are added to the registry,
|
|
209
|
+
// they should be automatically discoverable without code changes
|
|
210
|
+
const axios = require('axios');
|
|
211
|
+
// Get list of all workflows to verify dynamic discovery is working
|
|
212
|
+
const response = await axios.post((0, shared_server_utils_js_1.getMcpEndpoint)(), {
|
|
213
|
+
jsonrpc: '2.0',
|
|
214
|
+
id: 200,
|
|
215
|
+
method: 'tools/call',
|
|
216
|
+
params: {
|
|
217
|
+
name: 'list_fraim_workflows'
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
node_assert_1.default.strictEqual(response.status, 200);
|
|
221
|
+
node_assert_1.default.ok(response.data.result);
|
|
222
|
+
node_assert_1.default.ok(response.data.result.content);
|
|
223
|
+
const content = response.data.result.content[0];
|
|
224
|
+
node_assert_1.default.strictEqual(content.type, 'text');
|
|
225
|
+
// Verify that multiple categories are listed
|
|
226
|
+
const expectedCategories = [
|
|
227
|
+
'bootstrap',
|
|
228
|
+
'brainstorming',
|
|
229
|
+
'business-development',
|
|
230
|
+
'compliance',
|
|
231
|
+
'customer-development',
|
|
232
|
+
'learning',
|
|
233
|
+
'marketing',
|
|
234
|
+
'product-building',
|
|
235
|
+
'replicate'
|
|
236
|
+
];
|
|
237
|
+
for (const category of expectedCategories) {
|
|
238
|
+
node_assert_1.default.ok(content.text.includes(category), `Expected category "${category}" to be listed in workflows, but got: ${content.text.substring(0, 500)}`);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-framework",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.49",
|
|
4
4
|
"description": "FRAIM v2: Framework for Rigor-based AI Management - Transform from solo developer to AI manager orchestrating production-ready code with enterprise-grade discipline",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
"test": "node scripts/test-with-server.js",
|
|
16
16
|
"start:fraim": "tsx src/fraim-mcp-server.ts",
|
|
17
17
|
"dev:fraim": "tsx --watch src/fraim-mcp-server.ts",
|
|
18
|
+
"serve:website": "node fraim-pro/serve.js",
|
|
18
19
|
"watch:fraimlogs": "tsx scripts/watch-fraim-logs.ts",
|
|
19
20
|
"manage-keys": "tsx scripts/fraim/manage-keys.ts",
|
|
21
|
+
"view-signups": "tsx scripts/view-signups.ts",
|
|
20
22
|
"fraim:init": "npm run build && node bin/fraim.js init",
|
|
21
23
|
"fraim:sync": "node bin/fraim.js sync",
|
|
22
24
|
"postinstall": "fraim sync || echo 'FRAIM setup skipped.'",
|
|
@@ -10,7 +10,21 @@
|
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// Read configuration from .fraim/config.json if available
|
|
14
|
+
function loadConfig() {
|
|
15
|
+
const configPath = path.join(process.cwd(), '.fraim', 'config.json');
|
|
16
|
+
if (fs.existsSync(configPath)) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.warn('Warning: Could not parse .fraim/config.json, using defaults');
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function createWebsiteStructure(projectName, outputDir = process.cwd()) {
|
|
14
28
|
const websiteDir = path.join(outputDir, projectName);
|
|
15
29
|
|
|
16
30
|
// Create main directory
|
|
@@ -531,9 +545,10 @@ Thumbs.db
|
|
|
531
545
|
|
|
532
546
|
// CLI usage
|
|
533
547
|
if (require.main === module) {
|
|
548
|
+
const config = loadConfig();
|
|
534
549
|
const args = process.argv.slice(2);
|
|
535
550
|
const projectName = args[0];
|
|
536
|
-
const outputDir = args[1] ||
|
|
551
|
+
const outputDir = args[1] || process.cwd();
|
|
537
552
|
|
|
538
553
|
if (!projectName) {
|
|
539
554
|
console.log('Usage: node create-website-structure.js <project-name> [output-directory]');
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# FRAIM Workflow: soc2-evidence-generator
|
|
2
|
+
|
|
3
|
+
> [!IMPORTANT]
|
|
4
|
+
> This is a **FRAIM-managed workflow stub**.
|
|
5
|
+
> To load the full context (rules, templates, and execution steps), ask your AI agent to:
|
|
6
|
+
> `@fraim get_fraim_workflow("soc2-evidence-generator")`
|
|
7
|
+
>
|
|
8
|
+
> DO NOT EXECUTE.
|
|
9
|
+
|
|
10
|
+
## Intent
|
|
11
|
+
Generate comprehensive SOC2 compliance evidence packages by automatically collecting, documenting, and formatting evidence from project systems to demonstrate adherence to Trust Service Criteria during annual audits.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# FRAIM Workflow: build-skillset
|
|
2
|
+
|
|
3
|
+
> [!IMPORTANT]
|
|
4
|
+
> This is a **FRAIM-managed workflow stub**.
|
|
5
|
+
> To load the full context (rules, templates, and execution steps), ask your AI agent to:
|
|
6
|
+
> `@fraim get_fraim_workflow("build-skillset")`
|
|
7
|
+
>
|
|
8
|
+
> DO NOT EXECUTE.
|
|
9
|
+
|
|
10
|
+
## Intent
|
|
11
|
+
Create new project-specific skills through conversation with the user. This workflow allows agents to learn and document custom workflows, processes, and knowledge that are specific to the current project or team.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# FRAIM Workflow: contract-review-analysis
|
|
2
|
+
|
|
3
|
+
> [!IMPORTANT]
|
|
4
|
+
> This is a **FRAIM-managed workflow stub**.
|
|
5
|
+
> To load the full context (rules, templates, and execution steps), ask your AI agent to:
|
|
6
|
+
> `@fraim get_fraim_workflow("contract-review-analysis")`
|
|
7
|
+
>
|
|
8
|
+
> DO NOT EXECUTE.
|
|
9
|
+
|
|
10
|
+
## Intent
|
|
11
|
+
Provide thorough, strategic legal contract review that identifies risks, strengthens protections, and optimizes business positioning. This skill enables comprehensive analysis of SOWs, NDAs, Terms & Conditions, and other legal documents to protect interests while maintaining business relationships.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# FRAIM Workflow: saas-contract-development
|
|
2
|
+
|
|
3
|
+
> [!IMPORTANT]
|
|
4
|
+
> This is a **FRAIM-managed workflow stub**.
|
|
5
|
+
> To load the full context (rules, templates, and execution steps), ask your AI agent to:
|
|
6
|
+
> `@fraim get_fraim_workflow("saas-contract-development")`
|
|
7
|
+
>
|
|
8
|
+
> DO NOT EXECUTE.
|
|
9
|
+
|
|
10
|
+
## Intent
|
|
11
|
+
Create comprehensive, legally protective contract packages for SaaS engagements including SOWs, Terms & Conditions, and strategic pricing negotiations. This skill enables rapid development of professional-grade legal documents that protect IP, limit liability, and position for business growth.
|