feat: Complete generateObject migration with JSON mode support
This commit is contained in:
committed by
Ralph Khreish
parent
604b94baa9
commit
b16023ab2f
@@ -50,7 +50,7 @@ jest.unstable_mockModule(
|
||||
() => ({
|
||||
generateObjectService: jest.fn().mockResolvedValue({
|
||||
mainResult: {
|
||||
tasks: []
|
||||
complexityAnalysis: []
|
||||
},
|
||||
telemetryData: {
|
||||
timestamp: new Date().toISOString(),
|
||||
@@ -307,10 +307,15 @@ describe('analyzeTaskComplexity', () => {
|
||||
return { task: task || null, originalSubtaskCount: null };
|
||||
});
|
||||
|
||||
generateTextService.mockResolvedValue(sampleApiResponse);
|
||||
generateObjectService.mockResolvedValue({
|
||||
mainResult: {
|
||||
complexityAnalysis: JSON.parse(sampleApiResponse.mainResult).tasks
|
||||
},
|
||||
telemetryData: sampleApiResponse.telemetryData
|
||||
});
|
||||
});
|
||||
|
||||
test('should call generateTextService with the correct parameters', async () => {
|
||||
test('should call generateObjectService with the correct parameters', async () => {
|
||||
// Arrange
|
||||
const options = {
|
||||
file: 'tasks/tasks.json',
|
||||
@@ -338,7 +343,7 @@ describe('analyzeTaskComplexity', () => {
|
||||
'/mock/project/root',
|
||||
undefined
|
||||
);
|
||||
expect(generateTextService).toHaveBeenCalledWith(expect.any(Object));
|
||||
expect(generateObjectService).toHaveBeenCalledWith(expect.any(Object));
|
||||
expect(mockWriteFileSync).toHaveBeenCalledWith(
|
||||
expect.stringContaining('task-complexity-report.json'),
|
||||
expect.stringContaining('"thresholdScore": 5'),
|
||||
@@ -369,7 +374,7 @@ describe('analyzeTaskComplexity', () => {
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(generateTextService).toHaveBeenCalledWith(
|
||||
expect(generateObjectService).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
role: 'research' // This should be present when research is true
|
||||
})
|
||||
@@ -454,7 +459,7 @@ describe('analyzeTaskComplexity', () => {
|
||||
|
||||
// Assert
|
||||
// Check if the prompt sent to AI doesn't include the completed task (id: 3)
|
||||
expect(generateTextService).toHaveBeenCalledWith(
|
||||
expect(generateObjectService).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
prompt: expect.not.stringContaining('"id": 3')
|
||||
})
|
||||
@@ -471,7 +476,7 @@ describe('analyzeTaskComplexity', () => {
|
||||
};
|
||||
|
||||
// Force API error
|
||||
generateTextService.mockRejectedValueOnce(new Error('API Error'));
|
||||
generateObjectService.mockRejectedValueOnce(new Error('API Error'));
|
||||
|
||||
const mockMcpLog = {
|
||||
info: jest.fn(),
|
||||
|
||||
@@ -196,9 +196,62 @@ jest.unstable_mockModule(
|
||||
currency: 'USD'
|
||||
}
|
||||
}),
|
||||
generateObjectService: jest.fn().mockResolvedValue({
|
||||
mainResult: {
|
||||
object: {
|
||||
generateObjectService: jest.fn().mockImplementation((params) => {
|
||||
const commandName = params?.commandName || 'default';
|
||||
|
||||
if (commandName === 'analyze-complexity') {
|
||||
// Check if this is for a specific tag test by looking at the prompt
|
||||
const isFeatureTag =
|
||||
params?.prompt?.includes('feature') || params?.role === 'feature';
|
||||
const isMasterTag =
|
||||
params?.prompt?.includes('master') || params?.role === 'master';
|
||||
|
||||
let taskTitle = 'Test Task';
|
||||
if (isFeatureTag) {
|
||||
taskTitle = 'Feature Task 1';
|
||||
} else if (isMasterTag) {
|
||||
taskTitle = 'Master Task 1';
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
mainResult: {
|
||||
complexityAnalysis: [
|
||||
{
|
||||
taskId: 1,
|
||||
taskTitle: taskTitle,
|
||||
complexityScore: 7,
|
||||
recommendedSubtasks: 4,
|
||||
expansionPrompt: 'Break down this task',
|
||||
reasoning: 'This task is moderately complex'
|
||||
},
|
||||
{
|
||||
taskId: 2,
|
||||
taskTitle: 'Task 2',
|
||||
complexityScore: 5,
|
||||
recommendedSubtasks: 3,
|
||||
expansionPrompt: 'Break down this task with a focus on task 2.',
|
||||
reasoning:
|
||||
'Automatically added due to missing analysis in AI response.'
|
||||
}
|
||||
]
|
||||
},
|
||||
telemetryData: {
|
||||
timestamp: new Date().toISOString(),
|
||||
commandName: 'analyze-complexity',
|
||||
modelUsed: 'claude-3-5-sonnet',
|
||||
providerName: 'anthropic',
|
||||
inputTokens: 1000,
|
||||
outputTokens: 500,
|
||||
totalTokens: 1500,
|
||||
totalCost: 0.012414,
|
||||
currency: 'USD'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Default response for expand-task and others
|
||||
return Promise.resolve({
|
||||
mainResult: {
|
||||
subtasks: [
|
||||
{
|
||||
id: 1,
|
||||
@@ -210,19 +263,19 @@ jest.unstable_mockModule(
|
||||
testStrategy: 'Test strategy'
|
||||
}
|
||||
]
|
||||
},
|
||||
telemetryData: {
|
||||
timestamp: new Date().toISOString(),
|
||||
commandName: 'expand-task',
|
||||
modelUsed: 'claude-3-5-sonnet',
|
||||
providerName: 'anthropic',
|
||||
inputTokens: 1000,
|
||||
outputTokens: 500,
|
||||
totalTokens: 1500,
|
||||
totalCost: 0.012414,
|
||||
currency: 'USD'
|
||||
}
|
||||
},
|
||||
telemetryData: {
|
||||
timestamp: new Date().toISOString(),
|
||||
commandName: 'expand-task',
|
||||
modelUsed: 'claude-3-5-sonnet',
|
||||
providerName: 'anthropic',
|
||||
inputTokens: 1000,
|
||||
outputTokens: 500,
|
||||
totalTokens: 1500,
|
||||
totalCost: 0.012414,
|
||||
currency: 'USD'
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -421,9 +474,8 @@ const { readJSON, writeJSON, getTagAwareFilePath } = await import(
|
||||
'../../../../../scripts/modules/utils.js'
|
||||
);
|
||||
|
||||
const { generateTextService, streamTextService } = await import(
|
||||
'../../../../../scripts/modules/ai-services-unified.js'
|
||||
);
|
||||
const { generateTextService, generateObjectService, streamTextService } =
|
||||
await import('../../../../../scripts/modules/ai-services-unified.js');
|
||||
|
||||
// Import the modules under test
|
||||
const { default: analyzeTaskComplexity } = await import(
|
||||
|
||||
@@ -65,8 +65,8 @@ jest.unstable_mockModule('../../../../../scripts/modules/ui.js', () => ({
|
||||
jest.unstable_mockModule(
|
||||
'../../../../../scripts/modules/ai-services-unified.js',
|
||||
() => ({
|
||||
generateTextService: jest.fn().mockResolvedValue({
|
||||
mainResult: JSON.stringify({
|
||||
generateObjectService: jest.fn().mockResolvedValue({
|
||||
mainResult: {
|
||||
subtasks: [
|
||||
{
|
||||
id: 1,
|
||||
@@ -101,7 +101,7 @@ jest.unstable_mockModule(
|
||||
testStrategy: 'UI tests and visual regression testing'
|
||||
}
|
||||
]
|
||||
}),
|
||||
},
|
||||
telemetryData: {
|
||||
timestamp: new Date().toISOString(),
|
||||
userId: '1234567890',
|
||||
@@ -213,7 +213,7 @@ const {
|
||||
findProjectRoot
|
||||
} = await import('../../../../../scripts/modules/utils.js');
|
||||
|
||||
const { generateTextService } = await import(
|
||||
const { generateObjectService } = await import(
|
||||
'../../../../../scripts/modules/ai-services-unified.js'
|
||||
);
|
||||
|
||||
@@ -373,7 +373,7 @@ describe('expandTask', () => {
|
||||
'/mock/project/root',
|
||||
undefined
|
||||
);
|
||||
expect(generateTextService).toHaveBeenCalledWith(expect.any(Object));
|
||||
expect(generateObjectService).toHaveBeenCalledWith(expect.any(Object));
|
||||
expect(writeJSON).toHaveBeenCalledWith(
|
||||
tasksPath,
|
||||
expect.objectContaining({
|
||||
@@ -458,7 +458,7 @@ describe('expandTask', () => {
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(generateTextService).toHaveBeenCalledWith(
|
||||
expect(generateObjectService).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
role: 'research',
|
||||
commandName: expect.any(String)
|
||||
@@ -496,7 +496,7 @@ describe('expandTask', () => {
|
||||
telemetryData: expect.any(Object)
|
||||
})
|
||||
);
|
||||
expect(generateTextService).toHaveBeenCalled();
|
||||
expect(generateObjectService).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -743,8 +743,8 @@ describe('expandTask', () => {
|
||||
// Act
|
||||
await expandTask(tasksPath, taskId, undefined, false, '', context, false);
|
||||
|
||||
// Assert - generateTextService called with systemPrompt for 5 subtasks
|
||||
const callArg = generateTextService.mock.calls[0][0];
|
||||
// Assert - generateObjectService called with systemPrompt for 5 subtasks
|
||||
const callArg = generateObjectService.mock.calls[0][0];
|
||||
expect(callArg.systemPrompt).toContain('Generate exactly 5 subtasks');
|
||||
|
||||
// Assert - Should use complexity-report variant with expansion prompt
|
||||
@@ -831,7 +831,7 @@ describe('expandTask', () => {
|
||||
projectRoot: '/mock/project/root'
|
||||
};
|
||||
|
||||
generateTextService.mockRejectedValueOnce(new Error('AI service error'));
|
||||
generateObjectService.mockRejectedValueOnce(new Error('AI service error'));
|
||||
|
||||
// Act & Assert
|
||||
await expect(
|
||||
@@ -941,7 +941,7 @@ describe('expandTask', () => {
|
||||
await expandTask(tasksPath, taskId, 3, false, '', context, false);
|
||||
|
||||
// Assert - Should work with empty context (but may include project context)
|
||||
expect(generateTextService).toHaveBeenCalledWith(
|
||||
expect(generateObjectService).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
prompt: expect.stringMatching(/.*/) // Just ensure prompt exists
|
||||
})
|
||||
@@ -1074,7 +1074,7 @@ describe('expandTask', () => {
|
||||
|
||||
// Assert - Should complete successfully
|
||||
expect(result).toBeDefined();
|
||||
expect(generateTextService).toHaveBeenCalled();
|
||||
expect(generateObjectService).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should use dynamic prompting when numSubtasks is 0', async () => {
|
||||
@@ -1095,11 +1095,11 @@ describe('expandTask', () => {
|
||||
// Act
|
||||
await expandTask(tasksPath, taskId, 0, false, '', context, false);
|
||||
|
||||
// Assert - Verify generateTextService was called
|
||||
expect(generateTextService).toHaveBeenCalled();
|
||||
// Assert - Verify generateObjectService was called
|
||||
expect(generateObjectService).toHaveBeenCalled();
|
||||
|
||||
// Get the call arguments to verify the system prompt
|
||||
const callArgs = generateTextService.mock.calls[0][0];
|
||||
const callArgs = generateObjectService.mock.calls[0][0];
|
||||
expect(callArgs.systemPrompt).toContain(
|
||||
'an appropriate number of specific subtasks'
|
||||
);
|
||||
@@ -1122,11 +1122,11 @@ describe('expandTask', () => {
|
||||
// Act
|
||||
await expandTask(tasksPath, taskId, 5, false, '', context, false);
|
||||
|
||||
// Assert - Verify generateTextService was called
|
||||
expect(generateTextService).toHaveBeenCalled();
|
||||
// Assert - Verify generateObjectService was called
|
||||
expect(generateObjectService).toHaveBeenCalled();
|
||||
|
||||
// Get the call arguments to verify the system prompt
|
||||
const callArgs = generateTextService.mock.calls[0][0];
|
||||
const callArgs = generateObjectService.mock.calls[0][0];
|
||||
expect(callArgs.systemPrompt).toContain('5 specific subtasks');
|
||||
});
|
||||
|
||||
@@ -1151,8 +1151,8 @@ describe('expandTask', () => {
|
||||
await expandTask(tasksPath, taskId, -3, false, '', context, false);
|
||||
|
||||
// Assert - Should use default value instead of negative
|
||||
expect(generateTextService).toHaveBeenCalled();
|
||||
const callArgs = generateTextService.mock.calls[0][0];
|
||||
expect(generateObjectService).toHaveBeenCalled();
|
||||
const callArgs = generateObjectService.mock.calls[0][0];
|
||||
expect(callArgs.systemPrompt).toContain('4 specific subtasks');
|
||||
});
|
||||
|
||||
@@ -1177,8 +1177,8 @@ describe('expandTask', () => {
|
||||
await expandTask(tasksPath, taskId, undefined, false, '', context, false);
|
||||
|
||||
// Assert - Should use default value
|
||||
expect(generateTextService).toHaveBeenCalled();
|
||||
const callArgs = generateTextService.mock.calls[0][0];
|
||||
expect(generateObjectService).toHaveBeenCalled();
|
||||
const callArgs = generateObjectService.mock.calls[0][0];
|
||||
expect(callArgs.systemPrompt).toContain('6 specific subtasks');
|
||||
});
|
||||
|
||||
@@ -1203,8 +1203,8 @@ describe('expandTask', () => {
|
||||
await expandTask(tasksPath, taskId, null, false, '', context, false);
|
||||
|
||||
// Assert - Should use default value
|
||||
expect(generateTextService).toHaveBeenCalled();
|
||||
const callArgs = generateTextService.mock.calls[0][0];
|
||||
expect(generateObjectService).toHaveBeenCalled();
|
||||
const callArgs = generateObjectService.mock.calls[0][0];
|
||||
expect(callArgs.systemPrompt).toContain('7 specific subtasks');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,7 +43,25 @@ jest.unstable_mockModule(
|
||||
() => ({
|
||||
generateTextService: jest
|
||||
.fn()
|
||||
.mockResolvedValue({ mainResult: { content: '{}' }, telemetryData: {} })
|
||||
.mockResolvedValue({ mainResult: { content: '{}' }, telemetryData: {} }),
|
||||
generateObjectService: jest
|
||||
.fn()
|
||||
.mockResolvedValue({
|
||||
mainResult: {
|
||||
task: {
|
||||
id: 1,
|
||||
title: 'Updated Task',
|
||||
description: 'Updated description',
|
||||
status: 'pending',
|
||||
dependencies: [],
|
||||
priority: 'medium',
|
||||
details: null,
|
||||
testStrategy: null,
|
||||
subtasks: []
|
||||
}
|
||||
},
|
||||
telemetryData: {}
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -30,6 +30,12 @@ jest.unstable_mockModule(
|
||||
generateTextService: jest.fn().mockResolvedValue({
|
||||
mainResult: '[]', // mainResult is the text string directly
|
||||
telemetryData: {}
|
||||
}),
|
||||
generateObjectService: jest.fn().mockResolvedValue({
|
||||
mainResult: {
|
||||
tasks: [] // generateObject returns structured data
|
||||
},
|
||||
telemetryData: {}
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -84,7 +90,7 @@ const { readJSON, writeJSON, log } = await import(
|
||||
'../../../../../scripts/modules/utils.js'
|
||||
);
|
||||
|
||||
const { generateTextService } = await import(
|
||||
const { generateObjectService } = await import(
|
||||
'../../../../../scripts/modules/ai-services-unified.js'
|
||||
);
|
||||
|
||||
@@ -154,7 +160,9 @@ describe('updateTasks', () => {
|
||||
];
|
||||
|
||||
const mockApiResponse = {
|
||||
mainResult: JSON.stringify(mockUpdatedTasks), // mainResult is the JSON string directly
|
||||
mainResult: {
|
||||
tasks: mockUpdatedTasks // generateObject returns structured data
|
||||
},
|
||||
telemetryData: {}
|
||||
};
|
||||
|
||||
@@ -164,7 +172,7 @@ describe('updateTasks', () => {
|
||||
tag: 'master',
|
||||
_rawTaggedData: mockInitialTasks
|
||||
});
|
||||
generateTextService.mockResolvedValue(mockApiResponse);
|
||||
generateObjectService.mockResolvedValue(mockApiResponse);
|
||||
|
||||
// Act
|
||||
const result = await updateTasks(
|
||||
@@ -185,7 +193,7 @@ describe('updateTasks', () => {
|
||||
);
|
||||
|
||||
// 2. AI Service called with correct args
|
||||
expect(generateTextService).toHaveBeenCalledWith(expect.any(Object));
|
||||
expect(generateObjectService).toHaveBeenCalledWith(expect.any(Object));
|
||||
|
||||
// 3. Write JSON called with correctly merged tasks
|
||||
expect(writeJSON).toHaveBeenCalledWith(
|
||||
@@ -252,7 +260,7 @@ describe('updateTasks', () => {
|
||||
'/mock/path',
|
||||
'master'
|
||||
);
|
||||
expect(generateTextService).not.toHaveBeenCalled();
|
||||
expect(generateObjectService).not.toHaveBeenCalled();
|
||||
expect(writeJSON).not.toHaveBeenCalled();
|
||||
expect(log).toHaveBeenCalledWith(
|
||||
'info',
|
||||
@@ -327,8 +335,10 @@ describe('updateTasks', () => {
|
||||
_rawTaggedData: mockTaggedData
|
||||
});
|
||||
|
||||
generateTextService.mockResolvedValue({
|
||||
mainResult: JSON.stringify(mockUpdatedTasks),
|
||||
generateObjectService.mockResolvedValue({
|
||||
mainResult: {
|
||||
tasks: mockUpdatedTasks
|
||||
},
|
||||
telemetryData: { commandName: 'update-tasks', totalCost: 0.05 }
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user