fraim-framework 2.0.55 → 2.0.57
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/CHANGELOG.md +10 -0
- package/dist/src/cli/commands/init-project.js +10 -4
- package/dist/src/cli/setup/mcp-config-generator.js +23 -15
- package/dist/src/local-mcp-server/stdio-server.js +207 -0
- package/dist/src/utils/validate-workflows.js +101 -0
- package/dist/src/utils/workflow-parser.js +81 -0
- package/package.json +16 -11
- package/registry/scripts/pdf-styles.css +172 -0
- package/registry/scripts/prep-issue.sh +46 -4
- package/registry/scripts/profile-server.ts +131 -130
- package/registry/stubs/workflows/customer-development/user-survey-dispatch.md +1 -1
- package/registry/stubs/workflows/customer-development/users-to-target.md +1 -1
- package/registry/stubs/workflows/product-building/design.md +1 -1
- package/registry/stubs/workflows/product-building/implement.md +1 -1
- package/Claude.md +0 -1
- package/dist/registry/ai-manager-rules/design-phases/design-completeness-review.md +0 -73
- package/dist/registry/ai-manager-rules/design-phases/design-design.md +0 -145
- package/dist/registry/ai-manager-rules/implement-phases/implement-code.md +0 -283
- package/dist/registry/ai-manager-rules/implement-phases/implement-completeness-review.md +0 -120
- package/dist/registry/ai-manager-rules/implement-phases/implement-regression.md +0 -173
- package/dist/registry/ai-manager-rules/implement-phases/implement-repro.md +0 -104
- package/dist/registry/ai-manager-rules/implement-phases/implement-scoping.md +0 -100
- package/dist/registry/ai-manager-rules/implement-phases/implement-smoke.md +0 -237
- package/dist/registry/ai-manager-rules/implement-phases/implement-spike.md +0 -121
- package/dist/registry/ai-manager-rules/implement-phases/implement-validate.md +0 -375
- package/dist/registry/ai-manager-rules/retrospective.md +0 -116
- package/dist/registry/ai-manager-rules/shared-phases/address-pr-feedback.md +0 -188
- package/dist/registry/ai-manager-rules/shared-phases/submit-pr.md +0 -202
- package/dist/registry/ai-manager-rules/shared-phases/wait-for-pr-review.md +0 -170
- package/dist/registry/ai-manager-rules/spec-phases/spec-competitor-analysis.md +0 -105
- package/dist/registry/ai-manager-rules/spec-phases/spec-completeness-review.md +0 -66
- package/dist/registry/ai-manager-rules/spec-phases/spec-spec.md +0 -139
- package/dist/registry/providers/ado.json +0 -19
- package/dist/registry/providers/github.json +0 -19
- package/dist/registry/scripts/cleanup-branch.js +0 -287
- package/dist/registry/scripts/evaluate-code-quality.js +0 -66
- package/dist/registry/scripts/exec-with-timeout.js +0 -142
- package/dist/registry/scripts/generate-engagement-emails.js +0 -705
- package/dist/registry/scripts/newsletter-helpers.js +0 -671
- package/dist/registry/scripts/profile-server.js +0 -388
- package/dist/registry/scripts/run-thank-you-workflow.js +0 -92
- package/dist/registry/scripts/send-newsletter-simple.js +0 -85
- package/dist/registry/scripts/send-thank-you-emails.js +0 -54
- package/dist/registry/scripts/validate-openapi-limits.js +0 -311
- package/dist/registry/scripts/validate-test-coverage.js +0 -262
- package/dist/registry/scripts/verify-test-coverage.js +0 -66
- package/dist/scripts/build-stub-registry.js +0 -108
- package/dist/src/ai-manager/ai-manager.js +0 -482
- package/dist/src/ai-manager/phase-flow.js +0 -357
- package/dist/src/ai-manager/types.js +0 -5
- package/dist/src/fraim-mcp-server.js +0 -1885
- package/dist/tests/debug-tools.js +0 -80
- package/dist/tests/shared-server-utils.js +0 -57
- package/dist/tests/test-add-ide.js +0 -283
- package/dist/tests/test-ai-coach-edge-cases.js +0 -420
- package/dist/tests/test-ai-coach-mcp-integration.js +0 -450
- package/dist/tests/test-ai-coach-performance.js +0 -328
- package/dist/tests/test-ai-coach-phase-content.js +0 -264
- package/dist/tests/test-ai-coach-workflows.js +0 -514
- package/dist/tests/test-cli.js +0 -228
- package/dist/tests/test-client-scripts-validation.js +0 -167
- package/dist/tests/test-complete-setup-flow.js +0 -110
- package/dist/tests/test-config-system.js +0 -279
- package/dist/tests/test-debug-session.js +0 -134
- package/dist/tests/test-end-to-end-hybrid-validation.js +0 -328
- package/dist/tests/test-enhanced-session-init.js +0 -188
- package/dist/tests/test-first-run-journey.js +0 -368
- package/dist/tests/test-fraim-issues.js +0 -59
- package/dist/tests/test-genericization.js +0 -44
- package/dist/tests/test-hybrid-script-execution.js +0 -340
- package/dist/tests/test-ide-detector.js +0 -46
- package/dist/tests/test-improved-setup.js +0 -121
- package/dist/tests/test-mcp-config-generator.js +0 -99
- package/dist/tests/test-mcp-connection.js +0 -107
- package/dist/tests/test-mcp-issue-integration.js +0 -156
- package/dist/tests/test-mcp-lifecycle-methods.js +0 -240
- package/dist/tests/test-mcp-shared-server.js +0 -308
- package/dist/tests/test-mcp-template-processing.js +0 -160
- package/dist/tests/test-modular-issue-tracking.js +0 -165
- package/dist/tests/test-node-compatibility.js +0 -95
- package/dist/tests/test-npm-install.js +0 -68
- package/dist/tests/test-package-size.js +0 -108
- package/dist/tests/test-pr-review-workflow.js +0 -307
- package/dist/tests/test-prep-issue.js +0 -129
- package/dist/tests/test-productivity-integration.js +0 -157
- package/dist/tests/test-script-location-independence.js +0 -198
- package/dist/tests/test-script-sync.js +0 -557
- package/dist/tests/test-server-utils.js +0 -32
- package/dist/tests/test-session-rehydration.js +0 -148
- package/dist/tests/test-setup-integration.js +0 -98
- package/dist/tests/test-setup-scenarios.js +0 -322
- package/dist/tests/test-standalone.js +0 -143
- package/dist/tests/test-stub-registry.js +0 -136
- package/dist/tests/test-sync-stubs.js +0 -143
- package/dist/tests/test-sync-version-update.js +0 -93
- package/dist/tests/test-telemetry.js +0 -193
- package/dist/tests/test-token-validator.js +0 -30
- package/dist/tests/test-user-journey.js +0 -236
- package/dist/tests/test-users-to-target-workflow.js +0 -253
- package/dist/tests/test-utils.js +0 -109
- package/dist/tests/test-wizard.js +0 -71
- package/dist/tests/test-workflow-discovery.js +0 -242
- package/labels.json +0 -52
- package/registry/agent-guardrails.md +0 -63
- package/registry/fraim.md +0 -48
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase1-customer-profiling.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase1-survey-scoping.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase2-platform-discovery.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase2-survey-build-linkedin.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase3-prospect-qualification.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase3-survey-build-reddit.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase4-inventory-compilation.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase4-survey-build-x.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase5-survey-build-facebook.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase6-survey-build-custom.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase7-survey-dispatch.md +0 -11
- package/registry/stubs/workflows/customer-development/templates/customer-persona-template.md +0 -11
- package/registry/stubs/workflows/customer-development/templates/search-strategy-template.md +0 -11
- package/setup.js +0 -171
- package/tsconfig.json +0 -23
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
|
|
6
|
+
## [2.1.0] - 2026-02-04
|
|
7
|
+
|
|
8
|
+
### 🔄 Workflow Refinement
|
|
9
|
+
- **Consolidated Workflows**: Inlined all phase content into main workflow files (`spec.md`, `design.md`, `implement.md`) for better context retention and reduced file scatter.
|
|
10
|
+
- **Removed Redundant Files**: Deleted separate phase folders (`design-phases`, `implement-phases`, `spec-phases`, etc.) to streamline repository structure.
|
|
11
|
+
- **Terminology Update**: Renamed "AI Coach" to "AI Mentor" across all workflows to align with new branding.
|
|
12
|
+
- **Enhanced Spec Workflow**: Added `spec-competitor-analysis` phase and inlined validation steps.
|
|
13
|
+
- **Retrospective Integration**: Inlined retrospective phase content into all major workflows.
|
|
14
|
+
|
|
5
15
|
## [2.0.0] - 2024-12-19
|
|
6
16
|
|
|
7
17
|
### 🚀 Major Release: FRAIM v2 - The Future of AI-Assisted Development
|
|
@@ -39,18 +39,24 @@ const runInitProject = async () => {
|
|
|
39
39
|
if (!fs_1.default.existsSync(configPath)) {
|
|
40
40
|
const remoteInfo = (0, git_utils_1.getGitRemoteInfo)();
|
|
41
41
|
// Git remote is optional for project init
|
|
42
|
+
const repoOwner = remoteInfo.owner || 'your-username';
|
|
43
|
+
const repoName = remoteInfo.repo || path_1.default.basename(projectRoot);
|
|
44
|
+
const repoUrl = remoteInfo.owner && remoteInfo.repo
|
|
45
|
+
? `https://github.com/${remoteInfo.owner}/${remoteInfo.repo}.git`
|
|
46
|
+
: `https://github.com/${repoOwner}/${repoName}.git`;
|
|
42
47
|
const config = {
|
|
43
48
|
...types_1.DEFAULT_FRAIM_CONFIG,
|
|
44
49
|
version: (0, version_utils_1.getFraimVersion)(),
|
|
45
50
|
project: {
|
|
46
51
|
...types_1.DEFAULT_FRAIM_CONFIG.project,
|
|
47
|
-
name:
|
|
52
|
+
name: repoName
|
|
48
53
|
},
|
|
49
54
|
repository: {
|
|
50
55
|
provider: 'github',
|
|
51
|
-
owner:
|
|
52
|
-
name:
|
|
53
|
-
|
|
56
|
+
owner: repoOwner,
|
|
57
|
+
name: repoName,
|
|
58
|
+
url: repoUrl,
|
|
59
|
+
defaultBranch: remoteInfo.defaultBranch || 'main'
|
|
54
60
|
}
|
|
55
61
|
};
|
|
56
62
|
fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
@@ -18,9 +18,11 @@ const generateStandardMCPServers = (fraimKey, githubToken) => ({
|
|
|
18
18
|
args: ["-y", "@playwright/mcp"]
|
|
19
19
|
},
|
|
20
20
|
fraim: {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
command: "npx",
|
|
22
|
+
args: ["-y", "fraim-mcp"],
|
|
23
|
+
env: {
|
|
24
|
+
FRAIM_API_KEY: fraimKey,
|
|
25
|
+
FRAIM_REMOTE_URL: "https://fraim.wellnessatwork.me"
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
}
|
|
@@ -44,10 +46,11 @@ const generateClaudeMCPServers = (fraimKey, githubToken) => ({
|
|
|
44
46
|
args: ["-y", "@playwright/mcp"]
|
|
45
47
|
},
|
|
46
48
|
fraim: {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
command: "npx",
|
|
50
|
+
args: ["-y", "fraim-mcp"],
|
|
51
|
+
env: {
|
|
52
|
+
FRAIM_API_KEY: fraimKey,
|
|
53
|
+
FRAIM_REMOTE_URL: "https://fraim.wellnessatwork.me"
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
}
|
|
@@ -70,9 +73,11 @@ const generateKiroMCPServers = (fraimKey, githubToken) => ({
|
|
|
70
73
|
args: ["-y", "@playwright/mcp"]
|
|
71
74
|
},
|
|
72
75
|
fraim: {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
command: "npx",
|
|
77
|
+
args: ["-y", "fraim-mcp"],
|
|
78
|
+
env: {
|
|
79
|
+
FRAIM_API_KEY: fraimKey,
|
|
80
|
+
FRAIM_REMOTE_URL: "https://fraim.wellnessatwork.me"
|
|
76
81
|
}
|
|
77
82
|
}
|
|
78
83
|
}
|
|
@@ -92,10 +97,12 @@ command = "npx"
|
|
|
92
97
|
args = ["-y", "@playwright/mcp"]
|
|
93
98
|
|
|
94
99
|
[mcp_servers.fraim]
|
|
95
|
-
|
|
100
|
+
command = "npx"
|
|
101
|
+
args = ["-y", "fraim-mcp"]
|
|
96
102
|
|
|
97
|
-
[mcp_servers.fraim.
|
|
98
|
-
|
|
103
|
+
[mcp_servers.fraim.env]
|
|
104
|
+
FRAIM_API_KEY = "${fraimKey}"
|
|
105
|
+
FRAIM_REMOTE_URL = "https://fraim.wellnessatwork.me"
|
|
99
106
|
`;
|
|
100
107
|
exports.generateCodexMCPServers = generateCodexMCPServers;
|
|
101
108
|
const generateWindsurfMCPServers = (fraimKey, githubToken) => ({
|
|
@@ -117,9 +124,10 @@ const generateWindsurfMCPServers = (fraimKey, githubToken) => ({
|
|
|
117
124
|
},
|
|
118
125
|
fraim: {
|
|
119
126
|
command: "npx",
|
|
120
|
-
args: ["-y", "
|
|
127
|
+
args: ["-y", "fraim-mcp"],
|
|
121
128
|
env: {
|
|
122
|
-
FRAIM_API_KEY: fraimKey
|
|
129
|
+
FRAIM_API_KEY: fraimKey,
|
|
130
|
+
FRAIM_REMOTE_URL: "https://fraim.wellnessatwork.me"
|
|
123
131
|
}
|
|
124
132
|
}
|
|
125
133
|
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* FRAIM Local MCP Server - STDIO Version
|
|
5
|
+
*
|
|
6
|
+
* Simple proxy that:
|
|
7
|
+
* 1. Accepts MCP requests via stdin/stdout
|
|
8
|
+
* 2. Proxies to remote FRAIM server
|
|
9
|
+
* 3. Performs template substitution using local .fraim/config.json
|
|
10
|
+
* 4. Returns processed content to agent
|
|
11
|
+
*/
|
|
12
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
13
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
|
+
};
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.FraimLocalMCPServer = void 0;
|
|
17
|
+
const fs_1 = require("fs");
|
|
18
|
+
const path_1 = require("path");
|
|
19
|
+
const axios_1 = __importDefault(require("axios"));
|
|
20
|
+
class FraimLocalMCPServer {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.config = null;
|
|
23
|
+
this.remoteUrl = process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
|
|
24
|
+
this.apiKey = process.env.FRAIM_API_KEY || '';
|
|
25
|
+
if (!this.apiKey) {
|
|
26
|
+
this.logError('❌ FRAIM_API_KEY environment variable is required');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
this.loadConfig();
|
|
30
|
+
this.log('🚀 FRAIM Local MCP Server starting...');
|
|
31
|
+
this.log(`📡 Remote server: ${this.remoteUrl}`);
|
|
32
|
+
this.log(`🔑 API key: ${this.apiKey.substring(0, 10)}...`);
|
|
33
|
+
}
|
|
34
|
+
log(message) {
|
|
35
|
+
// Log to stderr (stdout is reserved for MCP protocol)
|
|
36
|
+
console.error(`[FRAIM] ${message}`);
|
|
37
|
+
}
|
|
38
|
+
logError(message) {
|
|
39
|
+
console.error(`[FRAIM ERROR] ${message}`);
|
|
40
|
+
}
|
|
41
|
+
loadConfig() {
|
|
42
|
+
try {
|
|
43
|
+
const configPath = (0, path_1.join)(process.cwd(), '.fraim', 'config.json');
|
|
44
|
+
if ((0, fs_1.existsSync)(configPath)) {
|
|
45
|
+
const configContent = (0, fs_1.readFileSync)(configPath, 'utf8');
|
|
46
|
+
this.config = JSON.parse(configContent);
|
|
47
|
+
this.log('✅ Loaded local .fraim/config.json');
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
this.log('⚠️ No .fraim/config.json found - template substitution disabled');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
this.logError(`Failed to load config: ${error}`);
|
|
55
|
+
this.config = null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Substitute template variables in content
|
|
60
|
+
* Replaces {{config.path.to.value}} with actual values from config
|
|
61
|
+
*/
|
|
62
|
+
substituteTemplates(content) {
|
|
63
|
+
if (!this.config)
|
|
64
|
+
return content;
|
|
65
|
+
return content.replace(/\{\{config\.([^}]+)\}\}/g, (match, path) => {
|
|
66
|
+
try {
|
|
67
|
+
const keys = path.split('.');
|
|
68
|
+
let value = this.config;
|
|
69
|
+
for (const key of keys) {
|
|
70
|
+
if (value && typeof value === 'object' && key in value) {
|
|
71
|
+
value = value[key];
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// Keep original placeholder if path not found
|
|
75
|
+
return match;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return String(value);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
// Keep original placeholder on error
|
|
82
|
+
return match;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Process template substitution in MCP response
|
|
88
|
+
*/
|
|
89
|
+
processResponse(response) {
|
|
90
|
+
if (!response.result)
|
|
91
|
+
return response;
|
|
92
|
+
// Process content fields that might contain templates
|
|
93
|
+
const processValue = (value) => {
|
|
94
|
+
if (typeof value === 'string') {
|
|
95
|
+
return this.substituteTemplates(value);
|
|
96
|
+
}
|
|
97
|
+
else if (Array.isArray(value)) {
|
|
98
|
+
return value.map(processValue);
|
|
99
|
+
}
|
|
100
|
+
else if (value && typeof value === 'object') {
|
|
101
|
+
const processed = {};
|
|
102
|
+
for (const [key, val] of Object.entries(value)) {
|
|
103
|
+
processed[key] = processValue(val);
|
|
104
|
+
}
|
|
105
|
+
return processed;
|
|
106
|
+
}
|
|
107
|
+
return value;
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
...response,
|
|
111
|
+
result: processValue(response.result)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Proxy request to remote FRAIM server
|
|
116
|
+
*/
|
|
117
|
+
async proxyToRemote(request) {
|
|
118
|
+
try {
|
|
119
|
+
const response = await axios_1.default.post(`${this.remoteUrl}/mcp`, request, {
|
|
120
|
+
headers: {
|
|
121
|
+
'Content-Type': 'application/json',
|
|
122
|
+
'x-api-key': this.apiKey
|
|
123
|
+
},
|
|
124
|
+
timeout: 30000
|
|
125
|
+
});
|
|
126
|
+
return response.data;
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
this.logError(`Remote request failed: ${error.message}`);
|
|
130
|
+
return {
|
|
131
|
+
jsonrpc: '2.0',
|
|
132
|
+
id: request.id,
|
|
133
|
+
error: {
|
|
134
|
+
code: -32603,
|
|
135
|
+
message: `Remote server error: ${error.message}`
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Handle incoming MCP request
|
|
142
|
+
*/
|
|
143
|
+
async handleRequest(request) {
|
|
144
|
+
this.log(`📥 ${request.method}`);
|
|
145
|
+
// Proxy to remote server
|
|
146
|
+
const response = await this.proxyToRemote(request);
|
|
147
|
+
// Process template substitution
|
|
148
|
+
const processedResponse = this.processResponse(response);
|
|
149
|
+
this.log(`📤 ${request.method} → ${processedResponse.error ? 'ERROR' : 'OK'}`);
|
|
150
|
+
return processedResponse;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Start STDIO server
|
|
154
|
+
*/
|
|
155
|
+
start() {
|
|
156
|
+
let buffer = '';
|
|
157
|
+
process.stdin.setEncoding('utf8');
|
|
158
|
+
process.stdin.on('data', async (chunk) => {
|
|
159
|
+
buffer += chunk;
|
|
160
|
+
// Process complete JSON-RPC messages (newline-delimited)
|
|
161
|
+
let newlineIndex;
|
|
162
|
+
while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
|
|
163
|
+
const line = buffer.slice(0, newlineIndex).trim();
|
|
164
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
165
|
+
if (line) {
|
|
166
|
+
try {
|
|
167
|
+
const request = JSON.parse(line);
|
|
168
|
+
const response = await this.handleRequest(request);
|
|
169
|
+
// Send response to stdout
|
|
170
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
this.logError(`Request processing failed: ${error.message}`);
|
|
174
|
+
// Send error response
|
|
175
|
+
const errorResponse = {
|
|
176
|
+
jsonrpc: '2.0',
|
|
177
|
+
error: {
|
|
178
|
+
code: -32700,
|
|
179
|
+
message: `Parse error: ${error.message}`
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
process.stdout.write(JSON.stringify(errorResponse) + '\n');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
process.stdin.on('end', () => {
|
|
188
|
+
this.log('🛑 Stdin closed, shutting down...');
|
|
189
|
+
process.exit(0);
|
|
190
|
+
});
|
|
191
|
+
process.on('SIGTERM', () => {
|
|
192
|
+
this.log('🛑 SIGTERM received, shutting down...');
|
|
193
|
+
process.exit(0);
|
|
194
|
+
});
|
|
195
|
+
process.on('SIGINT', () => {
|
|
196
|
+
this.log('🛑 SIGINT received, shutting down...');
|
|
197
|
+
process.exit(0);
|
|
198
|
+
});
|
|
199
|
+
this.log('✅ FRAIM Local MCP Server ready');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
exports.FraimLocalMCPServer = FraimLocalMCPServer;
|
|
203
|
+
// Start server if run directly
|
|
204
|
+
if (require.main === module) {
|
|
205
|
+
const server = new FraimLocalMCPServer();
|
|
206
|
+
server.start();
|
|
207
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateAllWorkflows = validateAllWorkflows;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const workflow_parser_js_1 = require("./workflow-parser.js");
|
|
7
|
+
/**
|
|
8
|
+
* Validate all workflows in the registry
|
|
9
|
+
*/
|
|
10
|
+
function validateAllWorkflows(registryPath) {
|
|
11
|
+
const errors = [];
|
|
12
|
+
const workflowFiles = [];
|
|
13
|
+
function findWorkflows(dir) {
|
|
14
|
+
if (!(0, fs_1.existsSync)(dir))
|
|
15
|
+
return;
|
|
16
|
+
const entries = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
const fullPath = (0, path_1.join)(dir, entry.name);
|
|
19
|
+
if (entry.isDirectory()) {
|
|
20
|
+
findWorkflows(fullPath);
|
|
21
|
+
}
|
|
22
|
+
else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
23
|
+
const content = (0, fs_1.readFileSync)(fullPath, 'utf-8');
|
|
24
|
+
if (content.includes('"initialPhase"')) {
|
|
25
|
+
workflowFiles.push(fullPath);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
findWorkflows(registryPath);
|
|
31
|
+
console.log(`🔍 Validating ${workflowFiles.length} workflows...`);
|
|
32
|
+
for (const filePath of workflowFiles) {
|
|
33
|
+
const relativePath = filePath.replace(registryPath, '');
|
|
34
|
+
try {
|
|
35
|
+
const wf = workflow_parser_js_1.WorkflowParser.parse(filePath);
|
|
36
|
+
if (!wf) {
|
|
37
|
+
errors.push(`❌ ${relativePath}: Failed to parse. Ensure JSON metadata is present and valid.`);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const { metadata, phases } = wf;
|
|
41
|
+
// 1. Initial Phase check
|
|
42
|
+
if (!phases.has(metadata.initialPhase)) {
|
|
43
|
+
errors.push(`❌ ${relativePath}: initialPhase "${metadata.initialPhase}" does not exist in phases.`);
|
|
44
|
+
}
|
|
45
|
+
// 2. Validate Phase Flow
|
|
46
|
+
for (const [phaseId, flow] of Object.entries(metadata.phases)) {
|
|
47
|
+
// Check if phase exists in markdown
|
|
48
|
+
if (!phases.has(phaseId)) {
|
|
49
|
+
errors.push(`❌ ${relativePath}: Phase "${phaseId}" defined in metadata but missing in markdown.`);
|
|
50
|
+
}
|
|
51
|
+
// Check Success transition
|
|
52
|
+
if (flow.onSuccess && flow.onSuccess !== 'null') {
|
|
53
|
+
if (typeof flow.onSuccess === 'string') {
|
|
54
|
+
if (!phases.has(flow.onSuccess)) {
|
|
55
|
+
errors.push(`❌ ${relativePath}: Phase "${phaseId}" transition onSuccess -> "${flow.onSuccess}" target not found.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// Validate each branch of conditional success
|
|
60
|
+
for (const [branch, target] of Object.entries(flow.onSuccess)) {
|
|
61
|
+
if (target && target !== 'null' && !phases.has(target)) {
|
|
62
|
+
errors.push(`❌ ${relativePath}: Phase "${phaseId}" conditional transition onSuccess[${branch}] -> "${target}" target not found.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Check Failure transition
|
|
68
|
+
if (flow.onFailure && !phases.has(flow.onFailure)) {
|
|
69
|
+
errors.push(`❌ ${relativePath}: Phase "${phaseId}" transition onFailure -> "${flow.onFailure}" target not found.`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// 3. Check for orphaned phases in markdown
|
|
73
|
+
for (const phaseId of phases.keys()) {
|
|
74
|
+
if (!metadata.phases[phaseId]) {
|
|
75
|
+
errors.push(`⚠️ ${relativePath}: Phase "${phaseId}" exists in markdown but is NOT defined in JSON metadata.`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
errors.push(`❌ ${relativePath}: Unexpected error: ${e.message}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
valid: errors.filter(e => e.startsWith('❌')).length === 0,
|
|
85
|
+
errors
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Run if called directly
|
|
89
|
+
if (process.argv[1]?.includes('validate-workflows')) {
|
|
90
|
+
const registryPath = process.argv[2] || (0, path_1.join)(process.cwd(), 'registry');
|
|
91
|
+
const result = validateAllWorkflows(registryPath);
|
|
92
|
+
result.errors.forEach(err => console.error(err));
|
|
93
|
+
if (result.valid) {
|
|
94
|
+
console.log('✅ All workflows validated successfully.');
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
console.error('❌ Workflow validation failed.');
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WorkflowParser = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
class WorkflowParser {
|
|
6
|
+
/**
|
|
7
|
+
* Parse a workflow markdown file into a structured definition
|
|
8
|
+
*/
|
|
9
|
+
static parse(filePath) {
|
|
10
|
+
if (!(0, fs_1.existsSync)(filePath))
|
|
11
|
+
return null;
|
|
12
|
+
const content = (0, fs_1.readFileSync)(filePath, 'utf-8');
|
|
13
|
+
// 1. Extract JSON Metadata
|
|
14
|
+
const metadataMatch = content.match(/^---\r?\n([\s\S]+?)\r?\n---/);
|
|
15
|
+
if (!metadataMatch)
|
|
16
|
+
return null;
|
|
17
|
+
let metadata;
|
|
18
|
+
try {
|
|
19
|
+
metadata = JSON.parse(metadataMatch[1]);
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
console.error(`❌ Failed to parse JSON metadata in ${filePath}:`, e);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
// 2. Extract Overview (Content after metadata but before first phase header)
|
|
26
|
+
const contentAfterMetadata = content.substring(metadataMatch[0].length).trim();
|
|
27
|
+
const firstPhaseIndex = contentAfterMetadata.search(/^##\s+Phase:/m);
|
|
28
|
+
let overview = '';
|
|
29
|
+
let restOfContent = '';
|
|
30
|
+
if (firstPhaseIndex !== -1) {
|
|
31
|
+
overview = contentAfterMetadata.substring(0, firstPhaseIndex).trim();
|
|
32
|
+
restOfContent = contentAfterMetadata.substring(firstPhaseIndex);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
overview = contentAfterMetadata;
|
|
36
|
+
}
|
|
37
|
+
// 3. Extract Phases (id -> content)
|
|
38
|
+
const phases = new Map();
|
|
39
|
+
const phaseSections = restOfContent.split(/^##\s+Phase:\s+/m);
|
|
40
|
+
// Skip the first part (empty or overview overlap)
|
|
41
|
+
for (let i = 1; i < phaseSections.length; i++) {
|
|
42
|
+
const section = phaseSections[i];
|
|
43
|
+
const sectionLines = section.split('\n');
|
|
44
|
+
const firstLine = sectionLines[0].trim();
|
|
45
|
+
// Extract phase ID (slug before any (Phase X) or space)
|
|
46
|
+
const id = firstLine.split(/[ (]/)[0].trim().toLowerCase();
|
|
47
|
+
// The content includes the header (we'll reconstruct it for the agent or just return the body)
|
|
48
|
+
// But usually, the agent wants the whole section including the Phase ID header.
|
|
49
|
+
// We'll store the whole section but prepend the header because split removed it.
|
|
50
|
+
phases.set(id, `## Phase: ${section.trim()}`);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
metadata,
|
|
54
|
+
overview,
|
|
55
|
+
phases
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Extract description for listing workflows (intent or first para of overview)
|
|
60
|
+
*/
|
|
61
|
+
static extractDescription(filePath) {
|
|
62
|
+
const wf = this.parse(filePath);
|
|
63
|
+
if (!wf)
|
|
64
|
+
return '';
|
|
65
|
+
// Try to find Intent section in overview
|
|
66
|
+
const intentMatch = wf.overview.match(/## Intent\s+([\s\S]+?)(?:\r?\n##|$)/);
|
|
67
|
+
if (intentMatch)
|
|
68
|
+
return intentMatch[1].trim().split(/\r?\n/)[0];
|
|
69
|
+
// Fallback to first non-header line
|
|
70
|
+
const firstPara = wf.overview.split(/\r?\n/).find(l => l.trim() !== '' && !l.startsWith('#'));
|
|
71
|
+
return firstPara ? firstPara.trim() : '';
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get just the overview for an agent starting a workflow
|
|
75
|
+
*/
|
|
76
|
+
static getOverview(filePath) {
|
|
77
|
+
const wf = this.parse(filePath);
|
|
78
|
+
return wf ? wf.overview : null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.WorkflowParser = WorkflowParser;
|
package/package.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-framework",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.57",
|
|
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": {
|
|
7
7
|
"fraim": "./index.js",
|
|
8
|
-
"fraim-
|
|
8
|
+
"fraim-mcp": "./bin/fraim-mcp.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"setup": "node setup.js",
|
|
12
11
|
"dev": "tsx --watch src/fraim-mcp-server.ts",
|
|
13
12
|
"build": "tsc && node scripts/copy-ai-manager-rules.js && npm run build:stubs && npm run validate:registry",
|
|
14
13
|
"build:stubs": "tsx scripts/build-stub-registry.ts",
|
|
@@ -26,7 +25,8 @@
|
|
|
26
25
|
"release": "npm version patch && npm publish",
|
|
27
26
|
"test-smoke-ci": "tsx --test tests/test-genericization.ts tests/test-cli.ts tests/test-stub-registry.ts tests/test-sync-stubs.ts",
|
|
28
27
|
"test-all-ci": "tsx --test tests/test-*.ts",
|
|
29
|
-
"validate:registry": "tsx scripts/verify-registry-paths.ts"
|
|
28
|
+
"validate:registry": "tsx scripts/verify-registry-paths.ts && npm run validate:workflows",
|
|
29
|
+
"validate:workflows": "tsx scripts/validate-workflows.ts"
|
|
30
30
|
},
|
|
31
31
|
"repository": {
|
|
32
32
|
"type": "git",
|
|
@@ -69,14 +69,19 @@
|
|
|
69
69
|
"typescript": "^5.0.0"
|
|
70
70
|
},
|
|
71
71
|
"files": [
|
|
72
|
-
"dist/",
|
|
72
|
+
"dist/src/local-mcp-server/",
|
|
73
|
+
"dist/src/cli/",
|
|
74
|
+
"dist/src/fraim/",
|
|
75
|
+
"dist/src/utils/",
|
|
73
76
|
"registry/stubs/",
|
|
74
77
|
"registry/scripts/",
|
|
75
|
-
"bin/",
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
78
|
+
"bin/fraim.js",
|
|
79
|
+
"bin/fraim-mcp.js",
|
|
80
|
+
"index.js",
|
|
81
|
+
"README.md",
|
|
82
|
+
"CHANGELOG.md",
|
|
83
|
+
"LICENSE",
|
|
84
|
+
"package.json"
|
|
80
85
|
],
|
|
81
86
|
"publishConfig": {
|
|
82
87
|
"access": "public"
|
|
@@ -92,7 +97,7 @@
|
|
|
92
97
|
"markdown-it-highlightjs": "^4.2.0",
|
|
93
98
|
"mongodb": "^7.0.0",
|
|
94
99
|
"prompts": "^2.4.2",
|
|
95
|
-
"puppeteer": "^24.
|
|
100
|
+
"puppeteer": "^24.36.1",
|
|
96
101
|
"sharp": "^0.34.5",
|
|
97
102
|
"tree-kill": "^1.2.2"
|
|
98
103
|
}
|