feat: Migrate Task Master to generateObject for structured AI responses (#1262)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Ben Vargas <ben@example.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { jest } from '@jest/globals';
|
||||
import { PromptManager } from '../../../scripts/modules/prompt-manager.js';
|
||||
import { ExpandTaskResponseSchema } from '../../../src/schemas/expand-task.js';
|
||||
import { SubtaskSchema } from '../../../src/schemas/base-schemas.js';
|
||||
|
||||
describe('expand-task prompt template', () => {
|
||||
let promptManager;
|
||||
@@ -74,30 +75,25 @@ describe('expand-task prompt template', () => {
|
||||
expect(userPrompt).toContain(`Current details: ${testTask.details}`);
|
||||
|
||||
// Also includes the expansion prompt
|
||||
expect(userPrompt).toContain('Expansion Guidance:');
|
||||
expect(userPrompt).toContain(params.expansionPrompt);
|
||||
expect(userPrompt).toContain(params.complexityReasoningContext);
|
||||
});
|
||||
|
||||
test('all variants request JSON format with subtasks array', () => {
|
||||
const variants = ['default', 'research', 'complexity-report'];
|
||||
test('ExpandTaskResponseSchema defines required subtask fields', () => {
|
||||
// Test the schema definition directly instead of weak substring matching
|
||||
const schema = ExpandTaskResponseSchema;
|
||||
const subtasksSchema = schema.shape.subtasks;
|
||||
const subtaskSchema = subtasksSchema.element;
|
||||
|
||||
variants.forEach((variant) => {
|
||||
const params =
|
||||
variant === 'complexity-report'
|
||||
? { ...baseParams, expansionPrompt: 'test' }
|
||||
: baseParams;
|
||||
|
||||
const { systemPrompt, userPrompt } = promptManager.loadPrompt(
|
||||
'expand-task',
|
||||
params,
|
||||
variant
|
||||
);
|
||||
const combined = systemPrompt + userPrompt;
|
||||
|
||||
expect(combined.toLowerCase()).toContain('subtasks');
|
||||
expect(combined).toContain('JSON');
|
||||
});
|
||||
// Verify the schema has the required fields
|
||||
expect(subtaskSchema).toBe(SubtaskSchema);
|
||||
expect(SubtaskSchema.shape).toHaveProperty('id');
|
||||
expect(SubtaskSchema.shape).toHaveProperty('title');
|
||||
expect(SubtaskSchema.shape).toHaveProperty('description');
|
||||
expect(SubtaskSchema.shape).toHaveProperty('dependencies');
|
||||
expect(SubtaskSchema.shape).toHaveProperty('details');
|
||||
expect(SubtaskSchema.shape).toHaveProperty('status');
|
||||
expect(SubtaskSchema.shape).toHaveProperty('testStrategy');
|
||||
});
|
||||
|
||||
test('complexity-report variant fails without task context regression test', () => {
|
||||
|
||||
55
tests/unit/prompts/prompt-migration.test.js
Normal file
55
tests/unit/prompts/prompt-migration.test.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const promptsDir = path.join(__dirname, '../../../src/prompts');
|
||||
|
||||
describe('Prompt Migration Validation', () => {
|
||||
const bannedPhrases = [
|
||||
'Respond ONLY with',
|
||||
'Return only the',
|
||||
'valid JSON',
|
||||
'Do not include any explanatory text',
|
||||
'Do not include any explanation',
|
||||
'code block markers'
|
||||
];
|
||||
|
||||
// Map banned phrases to contexts where they're allowed
|
||||
const allowedContexts = {
|
||||
'respond only with': ['Use markdown formatting for better readability'],
|
||||
'return only the': ['Use markdown formatting for better readability']
|
||||
};
|
||||
|
||||
test('prompts should not contain JSON formatting instructions', () => {
|
||||
const promptFiles = fs
|
||||
.readdirSync(promptsDir)
|
||||
.filter((file) => file.endsWith('.json') && !file.includes('schema'))
|
||||
// Exclude update-subtask.json as it returns plain strings, not JSON
|
||||
.filter((file) => file !== 'update-subtask.json');
|
||||
|
||||
promptFiles.forEach((file) => {
|
||||
const content = fs.readFileSync(path.join(promptsDir, file), 'utf8');
|
||||
|
||||
bannedPhrases.forEach((phrase) => {
|
||||
const lowerContent = content.toLowerCase();
|
||||
const lowerPhrase = phrase.toLowerCase();
|
||||
|
||||
if (lowerContent.includes(lowerPhrase)) {
|
||||
// Check if this phrase is allowed in its context
|
||||
const allowedInContext = allowedContexts[lowerPhrase];
|
||||
const isAllowed =
|
||||
allowedInContext &&
|
||||
allowedInContext.some((context) =>
|
||||
lowerContent.includes(context.toLowerCase())
|
||||
);
|
||||
|
||||
expect(isAllowed).toBe(
|
||||
true,
|
||||
`File ${file} contains banned phrase "${phrase}" without allowed context`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user