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.
Files changed (120) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/src/cli/commands/init-project.js +10 -4
  3. package/dist/src/cli/setup/mcp-config-generator.js +23 -15
  4. package/dist/src/local-mcp-server/stdio-server.js +207 -0
  5. package/dist/src/utils/validate-workflows.js +101 -0
  6. package/dist/src/utils/workflow-parser.js +81 -0
  7. package/package.json +16 -11
  8. package/registry/scripts/pdf-styles.css +172 -0
  9. package/registry/scripts/prep-issue.sh +46 -4
  10. package/registry/scripts/profile-server.ts +131 -130
  11. package/registry/stubs/workflows/customer-development/user-survey-dispatch.md +1 -1
  12. package/registry/stubs/workflows/customer-development/users-to-target.md +1 -1
  13. package/registry/stubs/workflows/product-building/design.md +1 -1
  14. package/registry/stubs/workflows/product-building/implement.md +1 -1
  15. package/Claude.md +0 -1
  16. package/dist/registry/ai-manager-rules/design-phases/design-completeness-review.md +0 -73
  17. package/dist/registry/ai-manager-rules/design-phases/design-design.md +0 -145
  18. package/dist/registry/ai-manager-rules/implement-phases/implement-code.md +0 -283
  19. package/dist/registry/ai-manager-rules/implement-phases/implement-completeness-review.md +0 -120
  20. package/dist/registry/ai-manager-rules/implement-phases/implement-regression.md +0 -173
  21. package/dist/registry/ai-manager-rules/implement-phases/implement-repro.md +0 -104
  22. package/dist/registry/ai-manager-rules/implement-phases/implement-scoping.md +0 -100
  23. package/dist/registry/ai-manager-rules/implement-phases/implement-smoke.md +0 -237
  24. package/dist/registry/ai-manager-rules/implement-phases/implement-spike.md +0 -121
  25. package/dist/registry/ai-manager-rules/implement-phases/implement-validate.md +0 -375
  26. package/dist/registry/ai-manager-rules/retrospective.md +0 -116
  27. package/dist/registry/ai-manager-rules/shared-phases/address-pr-feedback.md +0 -188
  28. package/dist/registry/ai-manager-rules/shared-phases/submit-pr.md +0 -202
  29. package/dist/registry/ai-manager-rules/shared-phases/wait-for-pr-review.md +0 -170
  30. package/dist/registry/ai-manager-rules/spec-phases/spec-competitor-analysis.md +0 -105
  31. package/dist/registry/ai-manager-rules/spec-phases/spec-completeness-review.md +0 -66
  32. package/dist/registry/ai-manager-rules/spec-phases/spec-spec.md +0 -139
  33. package/dist/registry/providers/ado.json +0 -19
  34. package/dist/registry/providers/github.json +0 -19
  35. package/dist/registry/scripts/cleanup-branch.js +0 -287
  36. package/dist/registry/scripts/evaluate-code-quality.js +0 -66
  37. package/dist/registry/scripts/exec-with-timeout.js +0 -142
  38. package/dist/registry/scripts/generate-engagement-emails.js +0 -705
  39. package/dist/registry/scripts/newsletter-helpers.js +0 -671
  40. package/dist/registry/scripts/profile-server.js +0 -388
  41. package/dist/registry/scripts/run-thank-you-workflow.js +0 -92
  42. package/dist/registry/scripts/send-newsletter-simple.js +0 -85
  43. package/dist/registry/scripts/send-thank-you-emails.js +0 -54
  44. package/dist/registry/scripts/validate-openapi-limits.js +0 -311
  45. package/dist/registry/scripts/validate-test-coverage.js +0 -262
  46. package/dist/registry/scripts/verify-test-coverage.js +0 -66
  47. package/dist/scripts/build-stub-registry.js +0 -108
  48. package/dist/src/ai-manager/ai-manager.js +0 -482
  49. package/dist/src/ai-manager/phase-flow.js +0 -357
  50. package/dist/src/ai-manager/types.js +0 -5
  51. package/dist/src/fraim-mcp-server.js +0 -1885
  52. package/dist/tests/debug-tools.js +0 -80
  53. package/dist/tests/shared-server-utils.js +0 -57
  54. package/dist/tests/test-add-ide.js +0 -283
  55. package/dist/tests/test-ai-coach-edge-cases.js +0 -420
  56. package/dist/tests/test-ai-coach-mcp-integration.js +0 -450
  57. package/dist/tests/test-ai-coach-performance.js +0 -328
  58. package/dist/tests/test-ai-coach-phase-content.js +0 -264
  59. package/dist/tests/test-ai-coach-workflows.js +0 -514
  60. package/dist/tests/test-cli.js +0 -228
  61. package/dist/tests/test-client-scripts-validation.js +0 -167
  62. package/dist/tests/test-complete-setup-flow.js +0 -110
  63. package/dist/tests/test-config-system.js +0 -279
  64. package/dist/tests/test-debug-session.js +0 -134
  65. package/dist/tests/test-end-to-end-hybrid-validation.js +0 -328
  66. package/dist/tests/test-enhanced-session-init.js +0 -188
  67. package/dist/tests/test-first-run-journey.js +0 -368
  68. package/dist/tests/test-fraim-issues.js +0 -59
  69. package/dist/tests/test-genericization.js +0 -44
  70. package/dist/tests/test-hybrid-script-execution.js +0 -340
  71. package/dist/tests/test-ide-detector.js +0 -46
  72. package/dist/tests/test-improved-setup.js +0 -121
  73. package/dist/tests/test-mcp-config-generator.js +0 -99
  74. package/dist/tests/test-mcp-connection.js +0 -107
  75. package/dist/tests/test-mcp-issue-integration.js +0 -156
  76. package/dist/tests/test-mcp-lifecycle-methods.js +0 -240
  77. package/dist/tests/test-mcp-shared-server.js +0 -308
  78. package/dist/tests/test-mcp-template-processing.js +0 -160
  79. package/dist/tests/test-modular-issue-tracking.js +0 -165
  80. package/dist/tests/test-node-compatibility.js +0 -95
  81. package/dist/tests/test-npm-install.js +0 -68
  82. package/dist/tests/test-package-size.js +0 -108
  83. package/dist/tests/test-pr-review-workflow.js +0 -307
  84. package/dist/tests/test-prep-issue.js +0 -129
  85. package/dist/tests/test-productivity-integration.js +0 -157
  86. package/dist/tests/test-script-location-independence.js +0 -198
  87. package/dist/tests/test-script-sync.js +0 -557
  88. package/dist/tests/test-server-utils.js +0 -32
  89. package/dist/tests/test-session-rehydration.js +0 -148
  90. package/dist/tests/test-setup-integration.js +0 -98
  91. package/dist/tests/test-setup-scenarios.js +0 -322
  92. package/dist/tests/test-standalone.js +0 -143
  93. package/dist/tests/test-stub-registry.js +0 -136
  94. package/dist/tests/test-sync-stubs.js +0 -143
  95. package/dist/tests/test-sync-version-update.js +0 -93
  96. package/dist/tests/test-telemetry.js +0 -193
  97. package/dist/tests/test-token-validator.js +0 -30
  98. package/dist/tests/test-user-journey.js +0 -236
  99. package/dist/tests/test-users-to-target-workflow.js +0 -253
  100. package/dist/tests/test-utils.js +0 -109
  101. package/dist/tests/test-wizard.js +0 -71
  102. package/dist/tests/test-workflow-discovery.js +0 -242
  103. package/labels.json +0 -52
  104. package/registry/agent-guardrails.md +0 -63
  105. package/registry/fraim.md +0 -48
  106. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase1-customer-profiling.md +0 -11
  107. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase1-survey-scoping.md +0 -11
  108. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase2-platform-discovery.md +0 -11
  109. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase2-survey-build-linkedin.md +0 -11
  110. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase3-prospect-qualification.md +0 -11
  111. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase3-survey-build-reddit.md +0 -11
  112. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase4-inventory-compilation.md +0 -11
  113. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase4-survey-build-x.md +0 -11
  114. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase5-survey-build-facebook.md +0 -11
  115. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase6-survey-build-custom.md +0 -11
  116. package/registry/stubs/workflows/customer-development/ai-coach-phases/phase7-survey-dispatch.md +0 -11
  117. package/registry/stubs/workflows/customer-development/templates/customer-persona-template.md +0 -11
  118. package/registry/stubs/workflows/customer-development/templates/search-strategy-template.md +0 -11
  119. package/setup.js +0 -171
  120. 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: remoteInfo.repo || path_1.default.basename(projectRoot)
52
+ name: repoName
48
53
  },
49
54
  repository: {
50
55
  provider: 'github',
51
- owner: remoteInfo.owner || 'your-username',
52
- name: remoteInfo.repo || path_1.default.basename(projectRoot),
53
- defaultBranch: 'main'
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
- serverUrl: "https://fraim.wellnessatwork.me",
22
- headers: {
23
- "x-api-key": fraimKey
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
- type: "http",
48
- url: "https://fraim.wellnessatwork.me",
49
- headers: {
50
- "x-api-key": fraimKey
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
- url: "https://fraim.wellnessatwork.me",
74
- headers: {
75
- "x-api-key": fraimKey
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
- url = "https://fraim.wellnessatwork.me/"
100
+ command = "npx"
101
+ args = ["-y", "fraim-mcp"]
96
102
 
97
- [mcp_servers.fraim.headers]
98
- x-api-key = "${fraimKey}"
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", "@modelcontextprotocol/server-fetch", "https://fraim.wellnessatwork.me"],
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.55",
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-setup": "./index.js"
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
- "*.js",
77
- "*.ts",
78
- "*.md",
79
- "*.json"
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.35.0",
100
+ "puppeteer": "^24.36.1",
96
101
  "sharp": "^0.34.5",
97
102
  "tree-kill": "^1.2.2"
98
103
  }