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:
Ralph Khreish
2025-10-02 16:23:34 +02:00
committed by GitHub
parent c7418c4594
commit 738ec51c04
34 changed files with 862 additions and 1034 deletions

View File

@@ -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', () => {

View 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`
);
}
});
});
});
});