@democratize-quality/mcp-server 1.1.0 → 1.1.2

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.
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Healing Prompts - Fix issues in generated code
3
+ *
4
+ * These prompts are used to fix validation errors in generated code.
5
+ * Applied when validation fails, with specific feedback about issues.
6
+ */
7
+
8
+ module.exports = {
9
+ /**
10
+ * Heal Playwright test code issues
11
+ */
12
+ playwrightHealing: {
13
+ system: `You are a code fixing expert. Fix the issues in the generated Playwright test code while maintaining functionality.
14
+
15
+ Requirements:
16
+ - Fix ONLY the reported issues
17
+ - Maintain all existing functionality
18
+ - Keep the same code structure
19
+ - Use proper indentation (6 spaces for test body)
20
+ - Return ONLY the fixed code, no explanations
21
+ - DO NOT create any additional files
22
+ - DO NOT generate README, SUMMARY, or documentation`,
23
+
24
+ user: (context) => {
25
+ const { originalCode, issues, scenario } = context;
26
+
27
+ let prompt = `Fix the following issues in this Playwright test code:
28
+
29
+ **Original Code:**
30
+ \`\`\`typescript
31
+ ${originalCode}
32
+ \`\`\`
33
+
34
+ **Issues Found:**
35
+ ${issues.map((issue, i) => `${i + 1}. [${issue.severity.toUpperCase()}] ${issue.message}
36
+ Fix: ${issue.fix}`).join('\n')}
37
+
38
+ **Original Scenario Context:**
39
+ - Method: ${scenario.method}
40
+ - Endpoint: ${scenario.endpoint}
41
+ - Expected Status: ${scenario.expect?.status || 200}
42
+
43
+ **Instructions:**
44
+ 1. Fix ALL reported issues
45
+ 2. Maintain the same functionality
46
+ 3. Keep proper indentation (6 spaces)
47
+ 4. For URL construction, use template literals: \`\${baseUrl}/endpoint\`
48
+ 5. Ensure await is used for all async calls
49
+ 6. Return ONLY the fixed code, starting at line 1 of the test body
50
+
51
+ DO NOT:
52
+ - Add explanations or comments about fixes
53
+ - Change the test logic
54
+ - Add or remove functionality
55
+ - Include test() wrapper or imports
56
+ - Create any additional files (README, SUMMARY, GUIDE, etc.)
57
+ - Suggest file creation
58
+
59
+ Return the complete fixed test body code.`;
60
+
61
+ return prompt;
62
+ }
63
+ },
64
+
65
+ /**
66
+ * Heal Jest test code issues
67
+ */
68
+ jestHealing: {
69
+ system: `You are a code fixing expert. Fix the issues in the generated Jest test code while maintaining functionality.
70
+
71
+ Requirements:
72
+ - Fix ONLY the reported issues
73
+ - Maintain all existing functionality
74
+ - Keep the same code structure
75
+ - Use proper indentation (6 spaces for test body)
76
+ - Return ONLY the fixed code, no explanations
77
+ - DO NOT create any additional files
78
+ - DO NOT generate README, SUMMARY, or documentation`,
79
+
80
+ user: (context) => {
81
+ const { originalCode, issues, scenario } = context;
82
+
83
+ let prompt = `Fix the following issues in this Jest test code:
84
+
85
+ **Original Code:**
86
+ \`\`\`typescript
87
+ ${originalCode}
88
+ \`\`\`
89
+
90
+ **Issues Found:**
91
+ ${issues.map((issue, i) => `${i + 1}. [${issue.severity.toUpperCase()}] ${issue.message}
92
+ Fix: ${issue.fix}`).join('\n')}
93
+
94
+ **Original Scenario Context:**
95
+ - Method: ${scenario.method}
96
+ - Endpoint: ${scenario.endpoint}
97
+ - Expected Status: ${scenario.expect?.status || 200}
98
+
99
+ **Instructions:**
100
+ 1. Fix ALL reported issues
101
+ 2. Maintain the same functionality
102
+ 3. Keep proper indentation (6 spaces)
103
+ 4. Use apiUtils.makeRequest() correctly
104
+ 5. Ensure await is used for all async calls
105
+ 6. Return ONLY the fixed code, starting at line 1 of the test body
106
+
107
+ DO NOT:
108
+ - Add explanations or comments about fixes
109
+ - Change the test logic
110
+ - Add or remove functionality
111
+ - Include test() wrapper or imports
112
+ - Create any additional files (README, SUMMARY, GUIDE, etc.)
113
+ - Suggest file creation
114
+
115
+ Return the complete fixed test body code.`;
116
+
117
+ return prompt;
118
+ }
119
+ },
120
+
121
+ /**
122
+ * Generic healing prompt for any format
123
+ */
124
+ genericHealing: {
125
+ system: `You are a code fixing expert. Fix the reported issues in the code while maintaining all functionality.`,
126
+
127
+ user: (context) => {
128
+ const { originalCode, issues, format, language } = context;
129
+
130
+ return `Fix these issues in ${format} ${language} code:
131
+
132
+ **Issues:**
133
+ ${issues.map((issue, i) => `${i + 1}. ${issue.message} - Fix: ${issue.fix}`).join('\n')}
134
+
135
+ **Code:**
136
+ \`\`\`${language}
137
+ ${originalCode}
138
+ \`\`\`
139
+
140
+ Return the fixed code only, no explanations.`;
141
+ }
142
+ }
143
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Prompt Orchestration System
3
+ *
4
+ * Exports all components of the prompt orchestration system for AI-based test generation.
5
+ *
6
+ * @module prompts
7
+ */
8
+
9
+ const generationPrompts = require('./generation-prompts');
10
+ const validationRules = require('./validation-rules');
11
+ const healingPrompts = require('./healing-prompts');
12
+ const PromptOrchestrator = require('./orchestrator');
13
+
14
+ module.exports = {
15
+ // Main orchestrator class
16
+ PromptOrchestrator,
17
+
18
+ // Individual components
19
+ generationPrompts,
20
+ validationRules,
21
+ healingPrompts,
22
+
23
+ // Convenience factory
24
+ createOrchestrator: (options) => new PromptOrchestrator(options)
25
+ };
@@ -0,0 +1,333 @@
1
+ /**
2
+ * Prompt Orchestrator
3
+ *
4
+ * Coordinates the Planning → Generation → Validation → Healing workflow
5
+ * for AI-based test code generation.
6
+ */
7
+
8
+ const generationPrompts = require('./generation-prompts');
9
+ const validationRules = require('./validation-rules');
10
+ const healingPrompts = require('./healing-prompts');
11
+
12
+ class PromptOrchestrator {
13
+ constructor(options = {}) {
14
+ this.maxHealingAttempts = options.maxHealingAttempts || 3;
15
+ this.language = options.language || 'typescript';
16
+ this.outputFormat = options.outputFormat || 'playwright';
17
+ this.verbose = options.verbose || false;
18
+ }
19
+
20
+ /**
21
+ * Generate test code with validation and healing
22
+ *
23
+ * Workflow:
24
+ * 1. Generate code using AI
25
+ * 2. Validate generated code
26
+ * 3. If invalid, heal and retry (up to maxHealingAttempts)
27
+ * 4. Return validated code or fallback
28
+ */
29
+ async generateTestCode(context, aiGenerator) {
30
+ const { scenario, baseUrl } = context;
31
+
32
+ this.log(`📝 Generating ${this.outputFormat} test for: ${scenario.title}`);
33
+
34
+ // Step 1: Generate initial code
35
+ let generatedCode = await this._generateCode(context, aiGenerator);
36
+
37
+ // Step 1.5: Validate single file output (SAFETY NET)
38
+ // Some helpful AI models try to create README, SUMMARY, etc.
39
+ generatedCode = this._validateSingleFileOutput(generatedCode, context.targetFile);
40
+
41
+ // Step 2: Validate
42
+ let validation = this._validateCode(generatedCode);
43
+
44
+ // Step 3: Heal if needed (up to maxHealingAttempts)
45
+ let healingAttempt = 0;
46
+ while (!validation.valid && healingAttempt < this.maxHealingAttempts) {
47
+ healingAttempt++;
48
+ this.log(`🔧 Healing attempt ${healingAttempt}/${this.maxHealingAttempts}...`);
49
+ this.log(` Issues: ${validation.issues.map(i => i.message).join(', ')}`);
50
+
51
+ generatedCode = await this._healCode(generatedCode, validation.issues, context, aiGenerator);
52
+ validation = this._validateCode(generatedCode);
53
+
54
+ if (validation.valid) {
55
+ this.log(`✅ Code healed successfully!`);
56
+ }
57
+ }
58
+
59
+ // Step 4: Return result
60
+ if (!validation.valid) {
61
+ this.log(`⚠️ Warning: Code still has issues after ${healingAttempt} healing attempts`);
62
+ this.log(` Remaining issues: ${validation.issues.map(i => i.message).join(', ')}`);
63
+ }
64
+
65
+ return {
66
+ code: generatedCode,
67
+ valid: validation.valid,
68
+ issues: validation.issues,
69
+ healingAttempts: healingAttempt,
70
+ metadata: {
71
+ scenario: scenario.title,
72
+ format: this.outputFormat,
73
+ language: this.language
74
+ }
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Generate code using AI
80
+ */
81
+ async _generateCode(context, aiGenerator) {
82
+ const promptConfig = this._getGenerationPrompt();
83
+
84
+ const systemPrompt = promptConfig.system;
85
+ const userPrompt = promptConfig.user({
86
+ ...context,
87
+ language: this.language,
88
+ options: { format: this.outputFormat }
89
+ });
90
+
91
+ this.log(`💭 Sending generation prompt to AI...`);
92
+
93
+ // Call AI generator (this would be GitHub Copilot API, OpenAI, etc.)
94
+ const code = await aiGenerator.generate(systemPrompt, userPrompt);
95
+
96
+ return code.trim();
97
+ }
98
+
99
+ /**
100
+ * Validate generated code
101
+ */
102
+ _validateCode(code) {
103
+ this.log(`🔍 Validating generated code...`);
104
+
105
+ const validator = validationRules.getValidator(this.outputFormat);
106
+ const validation = validator.validateAll(code, this.language);
107
+
108
+ if (validation.valid) {
109
+ this.log(`✅ Code validation passed!`);
110
+ } else {
111
+ this.log(`❌ Code validation failed with ${validation.issues.length} issues`);
112
+ }
113
+
114
+ return validation;
115
+ }
116
+
117
+ /**
118
+ * Heal code issues using AI
119
+ */
120
+ async _healCode(originalCode, issues, context, aiGenerator) {
121
+ const healingConfig = this._getHealingPrompt();
122
+
123
+ const systemPrompt = healingConfig.system;
124
+ const userPrompt = healingConfig.user({
125
+ originalCode,
126
+ issues,
127
+ scenario: context.scenario,
128
+ format: this.outputFormat,
129
+ language: this.language
130
+ });
131
+
132
+ this.log(`🏥 Sending healing prompt to AI...`);
133
+
134
+ // Call AI generator for healing
135
+ const healedCode = await aiGenerator.generate(systemPrompt, userPrompt);
136
+
137
+ return healedCode.trim();
138
+ }
139
+
140
+ /**
141
+ * Get generation prompt for current format
142
+ */
143
+ _getGenerationPrompt() {
144
+ switch(this.outputFormat) {
145
+ case 'playwright':
146
+ return generationPrompts.playwrightScenario;
147
+ case 'jest':
148
+ return generationPrompts.jestScenario;
149
+ case 'postman':
150
+ return generationPrompts.postmanTestScript;
151
+ default:
152
+ throw new Error(`Unknown output format: ${this.outputFormat}`);
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Get healing prompt for current format
158
+ */
159
+ _getHealingPrompt() {
160
+ switch(this.outputFormat) {
161
+ case 'playwright':
162
+ return healingPrompts.playwrightHealing;
163
+ case 'jest':
164
+ return healingPrompts.jestHealing;
165
+ default:
166
+ return healingPrompts.genericHealing;
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Log helper
172
+ */
173
+ log(message) {
174
+ if (this.verbose) {
175
+ console.log(message);
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Generate full test file (with imports and structure)
181
+ */
182
+ generateTestFile(testBodies, testPlan, options = {}) {
183
+ const { format, language, sessionId } = options;
184
+
185
+ switch(format) {
186
+ case 'playwright':
187
+ return this._generatePlaywrightFile(testBodies, testPlan, language, sessionId);
188
+ case 'jest':
189
+ return this._generateJestFile(testBodies, testPlan, language, sessionId);
190
+ default:
191
+ throw new Error(`Unknown format: ${format}`);
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Generate complete Playwright test file
197
+ */
198
+ _generatePlaywrightFile(testBodies, testPlan, language, sessionId) {
199
+ const isTS = language === 'typescript';
200
+ const imports = isTS
201
+ ? `import { test, expect } from '@playwright/test';`
202
+ : `const { test, expect } = require('@playwright/test');`;
203
+
204
+ const testCases = testPlan.sections.flatMap((section, sectionIndex) =>
205
+ section.scenarios.map((scenario, scenarioIndex) => {
206
+ const testBody = testBodies[`${sectionIndex}-${scenarioIndex}`];
207
+ return `
208
+ test('${scenario.title}', async ({ request }) => {
209
+ ${testBody}
210
+ });`;
211
+ }).join('\n')
212
+ );
213
+
214
+ return `// Generated API Tests for: ${testPlan.title}
215
+ // Generated by Democratize Quality MCP Server (AI-Generated)
216
+ // Session ID: ${sessionId}
217
+ // Language: ${language}
218
+
219
+ ${imports}
220
+
221
+ test.describe('${testPlan.title} - API Tests', () => {
222
+ const baseUrl = '${testPlan.baseUrl}';
223
+ ${testCases}
224
+ });`;
225
+ }
226
+
227
+ /**
228
+ * Generate complete Jest test file
229
+ */
230
+ _generateJestFile(testBodies, testPlan, language, sessionId) {
231
+ const isTS = language === 'typescript';
232
+ const imports = isTS
233
+ ? `import axios from 'axios';\nimport { ApiTestUtils } from './test-utils';`
234
+ : `const axios = require('axios');\nconst { ApiTestUtils } = require('./test-utils');`;
235
+
236
+ const typeAnnotation = isTS ? ': ApiTestUtils' : '';
237
+
238
+ const testCases = testPlan.sections.flatMap((section, sectionIndex) =>
239
+ section.scenarios.map((scenario, scenarioIndex) => {
240
+ const testBody = testBodies[`${sectionIndex}-${scenarioIndex}`];
241
+ return `
242
+ test('${scenario.title}', async () => {
243
+ ${testBody}
244
+ });`;
245
+ }).join('\n')
246
+ );
247
+
248
+ return `// Generated Jest API Tests for: ${testPlan.title}
249
+ // Generated by Democratize Quality MCP Server (AI-Generated)
250
+ // Session ID: ${sessionId}
251
+ // Language: ${language}
252
+
253
+ ${imports}
254
+
255
+ describe('${testPlan.title} API Tests', () => {
256
+ let apiUtils${typeAnnotation};
257
+ const baseUrl = '${testPlan.baseUrl}';
258
+
259
+ beforeAll(async () => {
260
+ apiUtils = new ApiTestUtils(baseUrl);
261
+ console.log('Test suite initialized');
262
+ });
263
+
264
+ afterAll(async () => {
265
+ await apiUtils.cleanup();
266
+ console.log('Test suite completed');
267
+ });
268
+
269
+ describe('Test Scenarios', () => {${testCases}
270
+ });
271
+ });`;
272
+ }
273
+
274
+ /**
275
+ * Validate that AI generated ONLY the requested file
276
+ * Some helpful models try to create READMEs, summaries, etc.
277
+ * This is a safety net in case prompts are ignored.
278
+ *
279
+ * @param {string} generatedCode - The generated code from AI
280
+ * @param {string} targetFileName - The expected file name
281
+ * @returns {string} - Filtered code with only the test file
282
+ */
283
+ _validateSingleFileOutput(generatedCode, targetFileName) {
284
+ // Check for multiple file indicators
285
+ const multiFilePatterns = [
286
+ /(?:create|generate|add)\s+(?:a\s+)?(?:README|SUMMARY|GUIDE|NOTES)/i,
287
+ /(?:also|additionally),?\s+(?:create|generate|write)/i,
288
+ /^#+\s+(?:README|SUMMARY|Quick Reference|Guide)/m,
289
+ /File:\s+[A-Z_-]+\.md/i,
290
+ /```[a-z]*\s*\n#+\s+(?:README|Summary)/i,
291
+ /\*\*File:\*\*\s+(?:README|SUMMARY|GUIDE)/i
292
+ ];
293
+
294
+ for (const pattern of multiFilePatterns) {
295
+ if (pattern.test(generatedCode)) {
296
+ this.log(`⚠️ Warning: AI attempted to create additional files - filtering...`);
297
+ // Extract just the test code, ignore suggestions
298
+ return this._extractTestCodeOnly(generatedCode);
299
+ }
300
+ }
301
+
302
+ return generatedCode;
303
+ }
304
+
305
+ /**
306
+ * Extract only the actual test code from AI output
307
+ * Remove any file creation suggestions or documentation
308
+ *
309
+ * @param {string} output - The full AI output
310
+ * @returns {string} - Extracted test code only
311
+ */
312
+ _extractTestCodeOnly(output) {
313
+ // Look for code fence or actual code
314
+ const codeFenceMatch = output.match(/```(?:typescript|javascript|ts|js)?\n([\s\S]+?)```/);
315
+ if (codeFenceMatch) {
316
+ this.log(`✅ Extracted test code from code fence`);
317
+ return codeFenceMatch[1];
318
+ }
319
+
320
+ // Try to find the test code by looking for test.describe or describe
321
+ const testDescribeMatch = output.match(/((?:test\.)?describe\(['"`][\s\S]+?\n\}\);)/);
322
+ if (testDescribeMatch) {
323
+ this.log(`✅ Extracted test code by pattern matching`);
324
+ return testDescribeMatch[1];
325
+ }
326
+
327
+ // If no extraction possible, return as-is but warn
328
+ this.log(`⚠️ Could not extract test code - returning full output`);
329
+ return output;
330
+ }
331
+ }
332
+
333
+ module.exports = PromptOrchestrator;