chore: fix formatting issues

This commit is contained in:
Ben Vargas
2025-07-22 17:46:53 -06:00
committed by Ralph Khreish
parent 2063dc4b7d
commit a2de49dd90
23 changed files with 2444 additions and 1220 deletions

View File

@@ -15,83 +15,89 @@ import addTask from '../../../scripts/modules/task-manager/add-task.js';
import parsePRD from '../../../scripts/modules/task-manager/parse-prd.js';
describe('GenerateObject Migration - Comprehensive Integration Tests', () => {
const testDir = path.join(process.cwd(), 'test-integration-output');
const testTasksFile = path.join(testDir, 'test-tasks.json');
const testPrdFile = path.join(testDir, 'test-prd.md');
beforeAll(() => {
// Create test directory
if (!fs.existsSync(testDir)) {
fs.mkdirSync(testDir, { recursive: true });
}
});
beforeEach(() => {
// Create initial test data
const initialTasks = {
master: {
tasks: [
{
id: 1,
title: "Setup project infrastructure",
description: "Initialize the project with proper structure and dependencies",
status: "done",
dependencies: [],
priority: "high",
details: "Created project structure with src, tests, and docs folders",
testStrategy: "Manual verification of folder structure",
subtasks: []
},
{
id: 2,
title: "Implement authentication system",
description: "Add user authentication with JWT tokens and OAuth2 support",
status: "in-progress",
dependencies: [1],
priority: "high",
details: "Need to support both OAuth2 and traditional email/password login",
testStrategy: "Unit tests for auth logic, integration tests for endpoints",
subtasks: [
{
id: 1,
title: "Design authentication flow",
description: "Create detailed flow diagrams for auth process",
status: "done",
dependencies: []
},
{
id: 2,
title: "Implement JWT token generation",
description: "Create secure JWT token generation and validation",
status: "pending",
dependencies: []
}
]
},
{
id: 3,
title: "Build RESTful API",
description: "Create comprehensive REST API endpoints",
status: "pending",
dependencies: [2],
priority: "medium",
details: "Use Express.js with proper middleware and error handling",
testStrategy: null,
subtasks: []
}
],
metadata: {
created: new Date().toISOString(),
updated: new Date().toISOString(),
description: "Test project tasks"
}
}
};
fs.writeFileSync(testTasksFile, JSON.stringify(initialTasks, null, 2));
// Create test PRD file
const testPrd = `# Product Requirements Document
const testDir = path.join(process.cwd(), 'test-integration-output');
const testTasksFile = path.join(testDir, 'test-tasks.json');
const testPrdFile = path.join(testDir, 'test-prd.md');
beforeAll(() => {
// Create test directory
if (!fs.existsSync(testDir)) {
fs.mkdirSync(testDir, { recursive: true });
}
});
beforeEach(() => {
// Create initial test data
const initialTasks = {
master: {
tasks: [
{
id: 1,
title: 'Setup project infrastructure',
description:
'Initialize the project with proper structure and dependencies',
status: 'done',
dependencies: [],
priority: 'high',
details:
'Created project structure with src, tests, and docs folders',
testStrategy: 'Manual verification of folder structure',
subtasks: []
},
{
id: 2,
title: 'Implement authentication system',
description:
'Add user authentication with JWT tokens and OAuth2 support',
status: 'in-progress',
dependencies: [1],
priority: 'high',
details:
'Need to support both OAuth2 and traditional email/password login',
testStrategy:
'Unit tests for auth logic, integration tests for endpoints',
subtasks: [
{
id: 1,
title: 'Design authentication flow',
description: 'Create detailed flow diagrams for auth process',
status: 'done',
dependencies: []
},
{
id: 2,
title: 'Implement JWT token generation',
description:
'Create secure JWT token generation and validation',
status: 'pending',
dependencies: []
}
]
},
{
id: 3,
title: 'Build RESTful API',
description: 'Create comprehensive REST API endpoints',
status: 'pending',
dependencies: [2],
priority: 'medium',
details: 'Use Express.js with proper middleware and error handling',
testStrategy: null,
subtasks: []
}
],
metadata: {
created: new Date().toISOString(),
updated: new Date().toISOString(),
description: 'Test project tasks'
}
}
};
fs.writeFileSync(testTasksFile, JSON.stringify(initialTasks, null, 2));
// Create test PRD file
const testPrd = `# Product Requirements Document
## Overview
We need to build a modern task management system with real-time collaboration features.
@@ -114,321 +120,329 @@ We need to build a modern task management system with real-time collaboration fe
- Support 10,000+ concurrent users
- Sub-100ms API response times
- 99.9% uptime SLA`;
fs.writeFileSync(testPrdFile, testPrd);
});
afterEach(() => {
// Clean up test files
if (fs.existsSync(testTasksFile)) {
fs.unlinkSync(testTasksFile);
}
if (fs.existsSync(testPrdFile)) {
fs.unlinkSync(testPrdFile);
}
});
afterAll(() => {
// Clean up test directory
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true });
}
});
describe('analyze-complexity command', () => {
test('should analyze task complexity with structured output', async () => {
const result = await analyzeTaskComplexity(
testTasksFile,
2, // Analyze task ID 2
false, // Don't use research mode
{
projectRoot: process.cwd(),
tag: 'master'
},
'json' // JSON output format
);
expect(result).toBeDefined();
expect(result.complexityAnalysis).toBeDefined();
expect(result.complexityAnalysis.overallComplexity).toMatch(/low|medium|high|very high/i);
expect(result.complexityAnalysis.factors).toBeDefined();
expect(Array.isArray(result.complexityAnalysis.factors)).toBe(true);
expect(result.complexityAnalysis.timeEstimate).toBeDefined();
expect(result.complexityAnalysis.riskAssessment).toBeDefined();
expect(result.telemetryData).toBeDefined();
}, 30000);
});
describe('add-task command', () => {
test('should add a new task with structured output', async () => {
const result = await addTask(
testTasksFile,
'Implement caching layer with Redis for improved performance',
[2], // Depends on task 2
'medium',
{
projectRoot: process.cwd(),
tag: 'master'
},
'json',
null, // No manual task data
false // Don't use research mode
);
expect(result).toBeDefined();
expect(result.newTaskId).toBe(4); // Should be the next ID
expect(result.telemetryData).toBeDefined();
// Verify task was added
const updatedData = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const newTask = updatedData.master.tasks.find(t => t.id === 4);
expect(newTask).toBeDefined();
expect(newTask.title).toContain('caching');
expect(newTask.priority).toBe('medium');
expect(newTask.dependencies).toContain(2);
}, 30000);
});
describe('expand-task command', () => {
test('should expand task into subtasks with structured output', async () => {
const result = await expandTask(
testTasksFile,
3, // Expand task ID 3
5, // Generate 5 subtasks
false, // Don't use research mode
{
projectRoot: process.cwd(),
tag: 'master'
},
'json'
);
expect(result).toBeDefined();
expect(result.expandedTask).toBeDefined();
expect(result.generatedSubtasks).toBeDefined();
expect(Array.isArray(result.generatedSubtasks)).toBe(true);
expect(result.generatedSubtasks.length).toBeGreaterThan(0);
expect(result.generatedSubtasks.length).toBeLessThanOrEqual(5);
// Verify subtasks were added
const updatedData = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const task3 = updatedData.master.tasks.find(t => t.id === 3);
expect(task3.subtasks).toBeDefined();
expect(task3.subtasks.length).toBeGreaterThan(0);
}, 30000);
});
describe('update-task-by-id command', () => {
test('should update task with structured output (full update mode)', async () => {
const result = await updateTaskById(
testTasksFile,
3, // Update task ID 3
'Add GraphQL support alongside REST API for more flexible queries',
false, // Append mode off (full update)
false, // Don't use research mode
{
projectRoot: process.cwd(),
tag: 'master'
},
'json'
);
expect(result).toBeDefined();
expect(result.updatedTask).toBeDefined();
expect(result.updatedTask.id).toBe(3);
expect(result.updatedTask.description.toLowerCase()).toContain('graphql');
expect(result.telemetryData).toBeDefined();
}, 30000);
test('should append to task details (append mode)', async () => {
const result = await updateTaskById(
testTasksFile,
2, // Update task ID 2
'Add support for multi-factor authentication',
true, // Append mode on
false, // Don't use research mode
{
projectRoot: process.cwd(),
tag: 'master'
},
'json'
);
expect(result).toBeDefined();
expect(result.updatedTask).toBeDefined();
expect(result.updatedTask.details).toContain('multi-factor authentication');
expect(result.telemetryData).toBeDefined();
}, 30000);
});
describe('update-tasks command', () => {
test('should update multiple tasks with structured output', async () => {
const result = await updateTasks(
testTasksFile,
2, // Update from task ID 2 onwards
'Migrate to microservices architecture for better scalability',
false, // Don't use research mode
{
projectRoot: process.cwd(),
tag: 'master'
},
'json'
);
expect(result).toBeDefined();
expect(result.success).toBe(true);
expect(result.updatedTasks).toBeDefined();
expect(Array.isArray(result.updatedTasks)).toBe(true);
expect(result.updatedTasks.length).toBeGreaterThan(0);
// Tasks 2 and 3 should be updated (not done)
const task2 = result.updatedTasks.find(t => t.id === 2);
const task3 = result.updatedTasks.find(t => t.id === 3);
expect(task2).toBeDefined();
expect(task3).toBeDefined();
expect(task2.description.toLowerCase()).toMatch(/microservice|scalability/);
expect(task3.description.toLowerCase()).toMatch(/microservice|scalability/);
}, 30000);
});
describe('parse-prd command', () => {
test('should parse PRD and generate tasks with structured output', async () => {
// Use a new file for PRD output to avoid conflicts
const prdTasksFile = path.join(testDir, 'prd-tasks.json');
const result = await parsePRD(
testPrdFile,
prdTasksFile,
5, // Generate 5 tasks
{
projectRoot: process.cwd(),
force: true,
append: false,
research: false,
tag: 'master'
}
);
expect(result).toBeDefined();
expect(result.success).toBe(true);
expect(result.tasksPath).toBe(prdTasksFile);
expect(result.telemetryData).toBeDefined();
// Verify tasks were generated
const generatedData = JSON.parse(fs.readFileSync(prdTasksFile, 'utf8'));
expect(generatedData.master).toBeDefined();
expect(generatedData.master.tasks).toBeDefined();
expect(generatedData.master.tasks.length).toBeGreaterThan(0);
expect(generatedData.master.tasks.length).toBeLessThanOrEqual(5);
// Verify task quality
const firstTask = generatedData.master.tasks[0];
expect(firstTask.title).toBeTruthy();
expect(firstTask.description).toBeTruthy();
expect(firstTask.status).toBe('pending');
expect(firstTask.priority).toMatch(/low|medium|high/);
// Clean up
fs.unlinkSync(prdTasksFile);
}, 30000);
});
describe('Command Integration Flow', () => {
test('should handle a complete workflow with multiple commands', async () => {
// 1. Add a new task
const addResult = await addTask(
testTasksFile,
'Implement comprehensive logging system',
[1],
'high',
{ projectRoot: process.cwd(), tag: 'master' },
'json'
);
const newTaskId = addResult.newTaskId;
// 2. Analyze its complexity
const complexityResult = await analyzeTaskComplexity(
testTasksFile,
newTaskId,
false,
{ projectRoot: process.cwd(), tag: 'master' },
'json'
);
expect(complexityResult.complexityAnalysis).toBeDefined();
// 3. Expand it into subtasks
const expandResult = await expandTask(
testTasksFile,
newTaskId,
3,
false,
{ projectRoot: process.cwd(), tag: 'master' },
'json'
);
expect(expandResult.generatedSubtasks.length).toBeGreaterThan(0);
// 4. Update the task with additional context
const updateResult = await updateTaskById(
testTasksFile,
newTaskId,
'Include structured logging with JSON format and log aggregation support',
false,
false,
{ projectRoot: process.cwd(), tag: 'master' },
'json'
);
expect(updateResult.updatedTask.description).toContain('JSON format');
// 5. Verify final state
const finalData = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const finalTask = finalData.master.tasks.find(t => t.id === newTaskId);
expect(finalTask).toBeDefined();
expect(finalTask.subtasks.length).toBeGreaterThan(0);
expect(finalTask.description).toContain('JSON format');
}, 60000); // Longer timeout for multiple operations
});
describe('Error Handling', () => {
test('should handle invalid task IDs gracefully', async () => {
await expect(
analyzeTaskComplexity(
testTasksFile,
999, // Non-existent task ID
false,
{ projectRoot: process.cwd(), tag: 'master' },
'json'
)
).rejects.toThrow('Task with ID 999 not found');
});
test('should handle empty prompts', async () => {
await expect(
addTask(
testTasksFile,
'', // Empty prompt
[],
'medium',
{ projectRoot: process.cwd(), tag: 'master' },
'json'
)
).rejects.toThrow();
});
test('should handle invalid dependencies', async () => {
const result = await addTask(
testTasksFile,
'New task with invalid dependency',
[999], // Non-existent dependency
'medium',
{ projectRoot: process.cwd(), tag: 'master' },
'json'
);
// Should succeed but filter out invalid dependency
expect(result.newTaskId).toBeDefined();
const data = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const newTask = data.master.tasks.find(t => t.id === result.newTaskId);
expect(newTask.dependencies).not.toContain(999);
});
});
});
fs.writeFileSync(testPrdFile, testPrd);
});
afterEach(() => {
// Clean up test files
if (fs.existsSync(testTasksFile)) {
fs.unlinkSync(testTasksFile);
}
if (fs.existsSync(testPrdFile)) {
fs.unlinkSync(testPrdFile);
}
});
afterAll(() => {
// Clean up test directory
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true });
}
});
describe('analyze-complexity command', () => {
test('should analyze task complexity with structured output', async () => {
const result = await analyzeTaskComplexity(
testTasksFile,
2, // Analyze task ID 2
false, // Don't use research mode
{
projectRoot: process.cwd(),
tag: 'master'
},
'json' // JSON output format
);
expect(result).toBeDefined();
expect(result.complexityAnalysis).toBeDefined();
expect(result.complexityAnalysis.overallComplexity).toMatch(
/low|medium|high|very high/i
);
expect(result.complexityAnalysis.factors).toBeDefined();
expect(Array.isArray(result.complexityAnalysis.factors)).toBe(true);
expect(result.complexityAnalysis.timeEstimate).toBeDefined();
expect(result.complexityAnalysis.riskAssessment).toBeDefined();
expect(result.telemetryData).toBeDefined();
}, 30000);
});
describe('add-task command', () => {
test('should add a new task with structured output', async () => {
const result = await addTask(
testTasksFile,
'Implement caching layer with Redis for improved performance',
[2], // Depends on task 2
'medium',
{
projectRoot: process.cwd(),
tag: 'master'
},
'json',
null, // No manual task data
false // Don't use research mode
);
expect(result).toBeDefined();
expect(result.newTaskId).toBe(4); // Should be the next ID
expect(result.telemetryData).toBeDefined();
// Verify task was added
const updatedData = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const newTask = updatedData.master.tasks.find((t) => t.id === 4);
expect(newTask).toBeDefined();
expect(newTask.title).toContain('caching');
expect(newTask.priority).toBe('medium');
expect(newTask.dependencies).toContain(2);
}, 30000);
});
describe('expand-task command', () => {
test('should expand task into subtasks with structured output', async () => {
const result = await expandTask(
testTasksFile,
3, // Expand task ID 3
5, // Generate 5 subtasks
false, // Don't use research mode
{
projectRoot: process.cwd(),
tag: 'master'
},
'json'
);
expect(result).toBeDefined();
expect(result.expandedTask).toBeDefined();
expect(result.generatedSubtasks).toBeDefined();
expect(Array.isArray(result.generatedSubtasks)).toBe(true);
expect(result.generatedSubtasks.length).toBeGreaterThan(0);
expect(result.generatedSubtasks.length).toBeLessThanOrEqual(5);
// Verify subtasks were added
const updatedData = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const task3 = updatedData.master.tasks.find((t) => t.id === 3);
expect(task3.subtasks).toBeDefined();
expect(task3.subtasks.length).toBeGreaterThan(0);
}, 30000);
});
describe('update-task-by-id command', () => {
test('should update task with structured output (full update mode)', async () => {
const result = await updateTaskById(
testTasksFile,
3, // Update task ID 3
'Add GraphQL support alongside REST API for more flexible queries',
false, // Append mode off (full update)
false, // Don't use research mode
{
projectRoot: process.cwd(),
tag: 'master'
},
'json'
);
expect(result).toBeDefined();
expect(result.updatedTask).toBeDefined();
expect(result.updatedTask.id).toBe(3);
expect(result.updatedTask.description.toLowerCase()).toContain('graphql');
expect(result.telemetryData).toBeDefined();
}, 30000);
test('should append to task details (append mode)', async () => {
const result = await updateTaskById(
testTasksFile,
2, // Update task ID 2
'Add support for multi-factor authentication',
true, // Append mode on
false, // Don't use research mode
{
projectRoot: process.cwd(),
tag: 'master'
},
'json'
);
expect(result).toBeDefined();
expect(result.updatedTask).toBeDefined();
expect(result.updatedTask.details).toContain(
'multi-factor authentication'
);
expect(result.telemetryData).toBeDefined();
}, 30000);
});
describe('update-tasks command', () => {
test('should update multiple tasks with structured output', async () => {
const result = await updateTasks(
testTasksFile,
2, // Update from task ID 2 onwards
'Migrate to microservices architecture for better scalability',
false, // Don't use research mode
{
projectRoot: process.cwd(),
tag: 'master'
},
'json'
);
expect(result).toBeDefined();
expect(result.success).toBe(true);
expect(result.updatedTasks).toBeDefined();
expect(Array.isArray(result.updatedTasks)).toBe(true);
expect(result.updatedTasks.length).toBeGreaterThan(0);
// Tasks 2 and 3 should be updated (not done)
const task2 = result.updatedTasks.find((t) => t.id === 2);
const task3 = result.updatedTasks.find((t) => t.id === 3);
expect(task2).toBeDefined();
expect(task3).toBeDefined();
expect(task2.description.toLowerCase()).toMatch(
/microservice|scalability/
);
expect(task3.description.toLowerCase()).toMatch(
/microservice|scalability/
);
}, 30000);
});
describe('parse-prd command', () => {
test('should parse PRD and generate tasks with structured output', async () => {
// Use a new file for PRD output to avoid conflicts
const prdTasksFile = path.join(testDir, 'prd-tasks.json');
const result = await parsePRD(
testPrdFile,
prdTasksFile,
5, // Generate 5 tasks
{
projectRoot: process.cwd(),
force: true,
append: false,
research: false,
tag: 'master'
}
);
expect(result).toBeDefined();
expect(result.success).toBe(true);
expect(result.tasksPath).toBe(prdTasksFile);
expect(result.telemetryData).toBeDefined();
// Verify tasks were generated
const generatedData = JSON.parse(fs.readFileSync(prdTasksFile, 'utf8'));
expect(generatedData.master).toBeDefined();
expect(generatedData.master.tasks).toBeDefined();
expect(generatedData.master.tasks.length).toBeGreaterThan(0);
expect(generatedData.master.tasks.length).toBeLessThanOrEqual(5);
// Verify task quality
const firstTask = generatedData.master.tasks[0];
expect(firstTask.title).toBeTruthy();
expect(firstTask.description).toBeTruthy();
expect(firstTask.status).toBe('pending');
expect(firstTask.priority).toMatch(/low|medium|high/);
// Clean up
fs.unlinkSync(prdTasksFile);
}, 30000);
});
describe('Command Integration Flow', () => {
test('should handle a complete workflow with multiple commands', async () => {
// 1. Add a new task
const addResult = await addTask(
testTasksFile,
'Implement comprehensive logging system',
[1],
'high',
{ projectRoot: process.cwd(), tag: 'master' },
'json'
);
const newTaskId = addResult.newTaskId;
// 2. Analyze its complexity
const complexityResult = await analyzeTaskComplexity(
testTasksFile,
newTaskId,
false,
{ projectRoot: process.cwd(), tag: 'master' },
'json'
);
expect(complexityResult.complexityAnalysis).toBeDefined();
// 3. Expand it into subtasks
const expandResult = await expandTask(
testTasksFile,
newTaskId,
3,
false,
{ projectRoot: process.cwd(), tag: 'master' },
'json'
);
expect(expandResult.generatedSubtasks.length).toBeGreaterThan(0);
// 4. Update the task with additional context
const updateResult = await updateTaskById(
testTasksFile,
newTaskId,
'Include structured logging with JSON format and log aggregation support',
false,
false,
{ projectRoot: process.cwd(), tag: 'master' },
'json'
);
expect(updateResult.updatedTask.description).toContain('JSON format');
// 5. Verify final state
const finalData = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const finalTask = finalData.master.tasks.find((t) => t.id === newTaskId);
expect(finalTask).toBeDefined();
expect(finalTask.subtasks.length).toBeGreaterThan(0);
expect(finalTask.description).toContain('JSON format');
}, 60000); // Longer timeout for multiple operations
});
describe('Error Handling', () => {
test('should handle invalid task IDs gracefully', async () => {
await expect(
analyzeTaskComplexity(
testTasksFile,
999, // Non-existent task ID
false,
{ projectRoot: process.cwd(), tag: 'master' },
'json'
)
).rejects.toThrow('Task with ID 999 not found');
});
test('should handle empty prompts', async () => {
await expect(
addTask(
testTasksFile,
'', // Empty prompt
[],
'medium',
{ projectRoot: process.cwd(), tag: 'master' },
'json'
)
).rejects.toThrow();
});
test('should handle invalid dependencies', async () => {
const result = await addTask(
testTasksFile,
'New task with invalid dependency',
[999], // Non-existent dependency
'medium',
{ projectRoot: process.cwd(), tag: 'master' },
'json'
);
// Should succeed but filter out invalid dependency
expect(result.newTaskId).toBeDefined();
const data = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const newTask = data.master.tasks.find((t) => t.id === result.newTaskId);
expect(newTask.dependencies).not.toContain(999);
});
});
});

View File

@@ -4,74 +4,74 @@ import fs from 'fs';
import path from 'path';
describe('analyze-complexity with generateObject', () => {
const testTasksFile = path.join(process.cwd(), 'test-tasks.json');
const testComplexityFile = path.join(process.cwd(), 'test-complexity.json');
beforeEach(() => {
// Create a test tasks file
const testTasks = {
projectName: "Test Project",
tasks: [
{
id: 1,
title: "Setup project structure",
description: "Initialize the project with proper folder structure",
status: "pending",
dependencies: [],
priority: "high"
},
{
id: 2,
title: "Implement authentication",
description: "Add user authentication with JWT tokens",
status: "pending",
dependencies: [1],
priority: "high"
}
]
};
fs.writeFileSync(testTasksFile, JSON.stringify(testTasks, null, 2));
});
afterEach(() => {
// Clean up test files
if (fs.existsSync(testTasksFile)) {
fs.unlinkSync(testTasksFile);
}
if (fs.existsSync(testComplexityFile)) {
fs.unlinkSync(testComplexityFile);
}
});
test('should return structured complexity analysis', async () => {
const result = await analyzeTaskComplexity({
file: testTasksFile,
output: testComplexityFile,
threshold: 5
});
const testTasksFile = path.join(process.cwd(), 'test-tasks.json');
const testComplexityFile = path.join(process.cwd(), 'test-complexity.json');
expect(result).toHaveProperty('report');
expect(result.report).toHaveProperty('complexityAnalysis');
expect(Array.isArray(result.report.complexityAnalysis)).toBe(true);
if (result.report.complexityAnalysis.length > 0) {
const analysis = result.report.complexityAnalysis[0];
expect(analysis).toHaveProperty('taskId');
expect(analysis).toHaveProperty('taskTitle');
expect(analysis).toHaveProperty('complexityScore');
expect(analysis).toHaveProperty('recommendedSubtasks');
expect(analysis).toHaveProperty('expansionPrompt');
expect(analysis).toHaveProperty('reasoning');
// Check that the values are of the correct type
expect(typeof analysis.taskId).toBe('number');
expect(typeof analysis.taskTitle).toBe('string');
expect(typeof analysis.complexityScore).toBe('number');
expect(analysis.complexityScore).toBeGreaterThanOrEqual(1);
expect(analysis.complexityScore).toBeLessThanOrEqual(10);
expect(typeof analysis.recommendedSubtasks).toBe('number');
expect(typeof analysis.expansionPrompt).toBe('string');
expect(typeof analysis.reasoning).toBe('string');
}
}, 30000); // Increase timeout for AI call
});
beforeEach(() => {
// Create a test tasks file
const testTasks = {
projectName: 'Test Project',
tasks: [
{
id: 1,
title: 'Setup project structure',
description: 'Initialize the project with proper folder structure',
status: 'pending',
dependencies: [],
priority: 'high'
},
{
id: 2,
title: 'Implement authentication',
description: 'Add user authentication with JWT tokens',
status: 'pending',
dependencies: [1],
priority: 'high'
}
]
};
fs.writeFileSync(testTasksFile, JSON.stringify(testTasks, null, 2));
});
afterEach(() => {
// Clean up test files
if (fs.existsSync(testTasksFile)) {
fs.unlinkSync(testTasksFile);
}
if (fs.existsSync(testComplexityFile)) {
fs.unlinkSync(testComplexityFile);
}
});
test('should return structured complexity analysis', async () => {
const result = await analyzeTaskComplexity({
file: testTasksFile,
output: testComplexityFile,
threshold: 5
});
expect(result).toHaveProperty('report');
expect(result.report).toHaveProperty('complexityAnalysis');
expect(Array.isArray(result.report.complexityAnalysis)).toBe(true);
if (result.report.complexityAnalysis.length > 0) {
const analysis = result.report.complexityAnalysis[0];
expect(analysis).toHaveProperty('taskId');
expect(analysis).toHaveProperty('taskTitle');
expect(analysis).toHaveProperty('complexityScore');
expect(analysis).toHaveProperty('recommendedSubtasks');
expect(analysis).toHaveProperty('expansionPrompt');
expect(analysis).toHaveProperty('reasoning');
// Check that the values are of the correct type
expect(typeof analysis.taskId).toBe('number');
expect(typeof analysis.taskTitle).toBe('string');
expect(typeof analysis.complexityScore).toBe('number');
expect(analysis.complexityScore).toBeGreaterThanOrEqual(1);
expect(analysis.complexityScore).toBeLessThanOrEqual(10);
expect(typeof analysis.recommendedSubtasks).toBe('number');
expect(typeof analysis.expansionPrompt).toBe('string');
expect(typeof analysis.reasoning).toBe('string');
}
}, 30000); // Increase timeout for AI call
});

View File

@@ -4,131 +4,145 @@ import fs from 'fs';
import path from 'path';
describe('expand-task with generateObject', () => {
const testTasksFile = path.join(process.cwd(), 'test-tasks.json');
beforeEach(() => {
// Create a test tasks file
const testTasks = {
projectName: "Test Project",
tasks: [
{
id: 1,
title: "Setup project structure",
description: "Initialize the project with proper folder structure",
status: "done",
dependencies: [],
priority: "high",
details: "Create folders for src, tests, docs",
testStrategy: "Manual verification",
subtasks: []
},
{
id: 2,
title: "Implement authentication",
description: "Add user authentication with JWT tokens",
status: "pending",
dependencies: [1],
priority: "high",
details: "Need to support OAuth2 and traditional login",
testStrategy: null,
subtasks: []
},
{
id: 3,
title: "Build API endpoints",
description: "Create RESTful API endpoints",
status: "pending",
dependencies: [2],
priority: "medium",
details: null,
testStrategy: null,
subtasks: [
{
id: 1,
title: "Design API schema",
description: "Create OpenAPI specification",
dependencies: [],
details: "Use OpenAPI 3.0 specification",
status: "done"
}
]
}
]
};
fs.writeFileSync(testTasksFile, JSON.stringify(testTasks, null, 2));
});
afterEach(() => {
// Clean up test files
if (fs.existsSync(testTasksFile)) {
fs.unlinkSync(testTasksFile);
}
});
test('should expand task with structured subtasks', async () => {
const result = await expandTask(
testTasksFile,
'2', // taskId as string
3, // numSubtasks
false, // force
'Break down authentication into implementation steps' // additionalContext
);
const testTasksFile = path.join(process.cwd(), 'test-tasks.json');
expect(result).toHaveProperty('task');
expect(result).toHaveProperty('telemetryData');
const { task } = result;
// Verify task was expanded
expect(task.id).toBe(2);
expect(task.subtasks).toBeDefined();
expect(Array.isArray(task.subtasks)).toBe(true);
expect(task.subtasks.length).toBeGreaterThan(0);
// Verify subtask structure
const subtask = task.subtasks[0];
expect(subtask).toHaveProperty('id');
expect(subtask).toHaveProperty('title');
expect(subtask).toHaveProperty('description');
expect(subtask).toHaveProperty('dependencies');
expect(subtask).toHaveProperty('details');
expect(subtask).toHaveProperty('status', 'pending');
// Verify task was written back to file
const savedData = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const savedTask = savedData.tasks.find(t => t.id === 2);
expect(savedTask.subtasks.length).toBe(task.subtasks.length);
}, 30000); // Increase timeout for AI call
test('should append subtasks when force=false', async () => {
// First expansion
await expandTask(testTasksFile, '3', 2, false);
const dataAfterFirst = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const taskAfterFirst = dataAfterFirst.tasks.find(t => t.id === 3);
const initialSubtaskCount = taskAfterFirst.subtasks.length;
// Second expansion (append)
await expandTask(testTasksFile, '3', 2, false, 'Add more implementation details');
const dataAfterSecond = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const taskAfterSecond = dataAfterSecond.tasks.find(t => t.id === 3);
// Should have more subtasks than before
expect(taskAfterSecond.subtasks.length).toBeGreaterThan(initialSubtaskCount);
}, 60000);
test('should replace subtasks when force=true', async () => {
// First expansion
await expandTask(testTasksFile, '3', 2, false);
// Second expansion with force=true
const result = await expandTask(testTasksFile, '3', 3, true, 'Complete redesign needed');
const savedData = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const savedTask = savedData.tasks.find(t => t.id === 3);
// Should have exactly 3 subtasks (replaced, not appended)
expect(savedTask.subtasks.length).toBe(3);
}, 60000);
});
beforeEach(() => {
// Create a test tasks file
const testTasks = {
projectName: 'Test Project',
tasks: [
{
id: 1,
title: 'Setup project structure',
description: 'Initialize the project with proper folder structure',
status: 'done',
dependencies: [],
priority: 'high',
details: 'Create folders for src, tests, docs',
testStrategy: 'Manual verification',
subtasks: []
},
{
id: 2,
title: 'Implement authentication',
description: 'Add user authentication with JWT tokens',
status: 'pending',
dependencies: [1],
priority: 'high',
details: 'Need to support OAuth2 and traditional login',
testStrategy: null,
subtasks: []
},
{
id: 3,
title: 'Build API endpoints',
description: 'Create RESTful API endpoints',
status: 'pending',
dependencies: [2],
priority: 'medium',
details: null,
testStrategy: null,
subtasks: [
{
id: 1,
title: 'Design API schema',
description: 'Create OpenAPI specification',
dependencies: [],
details: 'Use OpenAPI 3.0 specification',
status: 'done'
}
]
}
]
};
fs.writeFileSync(testTasksFile, JSON.stringify(testTasks, null, 2));
});
afterEach(() => {
// Clean up test files
if (fs.existsSync(testTasksFile)) {
fs.unlinkSync(testTasksFile);
}
});
test('should expand task with structured subtasks', async () => {
const result = await expandTask(
testTasksFile,
'2', // taskId as string
3, // numSubtasks
false, // force
'Break down authentication into implementation steps' // additionalContext
);
expect(result).toHaveProperty('task');
expect(result).toHaveProperty('telemetryData');
const { task } = result;
// Verify task was expanded
expect(task.id).toBe(2);
expect(task.subtasks).toBeDefined();
expect(Array.isArray(task.subtasks)).toBe(true);
expect(task.subtasks.length).toBeGreaterThan(0);
// Verify subtask structure
const subtask = task.subtasks[0];
expect(subtask).toHaveProperty('id');
expect(subtask).toHaveProperty('title');
expect(subtask).toHaveProperty('description');
expect(subtask).toHaveProperty('dependencies');
expect(subtask).toHaveProperty('details');
expect(subtask).toHaveProperty('status', 'pending');
// Verify task was written back to file
const savedData = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const savedTask = savedData.tasks.find((t) => t.id === 2);
expect(savedTask.subtasks.length).toBe(task.subtasks.length);
}, 30000); // Increase timeout for AI call
test('should append subtasks when force=false', async () => {
// First expansion
await expandTask(testTasksFile, '3', 2, false);
const dataAfterFirst = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const taskAfterFirst = dataAfterFirst.tasks.find((t) => t.id === 3);
const initialSubtaskCount = taskAfterFirst.subtasks.length;
// Second expansion (append)
await expandTask(
testTasksFile,
'3',
2,
false,
'Add more implementation details'
);
const dataAfterSecond = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const taskAfterSecond = dataAfterSecond.tasks.find((t) => t.id === 3);
// Should have more subtasks than before
expect(taskAfterSecond.subtasks.length).toBeGreaterThan(
initialSubtaskCount
);
}, 60000);
test('should replace subtasks when force=true', async () => {
// First expansion
await expandTask(testTasksFile, '3', 2, false);
// Second expansion with force=true
const result = await expandTask(
testTasksFile,
'3',
3,
true,
'Complete redesign needed'
);
const savedData = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const savedTask = savedData.tasks.find((t) => t.id === 3);
// Should have exactly 3 subtasks (replaced, not appended)
expect(savedTask.subtasks.length).toBe(3);
}, 60000);
});

View File

@@ -4,86 +4,86 @@ import fs from 'fs';
import path from 'path';
describe('update-task-by-id with generateObject', () => {
const testTasksFile = path.join(process.cwd(), 'test-tasks.json');
beforeEach(() => {
// Create a test tasks file
const testTasks = {
projectName: "Test Project",
tasks: [
{
id: 1,
title: "Setup project structure",
description: "Initialize the project with proper folder structure",
status: "pending",
dependencies: [],
priority: "high",
details: "Create folders for src, tests, docs",
testStrategy: "Manual verification"
},
{
id: 2,
title: "Implement authentication",
description: "Add user authentication with JWT tokens",
status: "pending",
dependencies: [1],
priority: "high",
details: null,
testStrategy: null
}
]
};
fs.writeFileSync(testTasksFile, JSON.stringify(testTasks, null, 2));
});
afterEach(() => {
// Clean up test files
if (fs.existsSync(testTasksFile)) {
fs.unlinkSync(testTasksFile);
}
});
test('should update task with structured data', async () => {
const result = await updateTaskById({
file: testTasksFile,
prompt: 'Update the description to include OAuth2 support',
id: 2
});
const testTasksFile = path.join(process.cwd(), 'test-tasks.json');
expect(result).toHaveProperty('updatedTask');
const { updatedTask } = result;
// Verify the task structure
expect(updatedTask).toHaveProperty('id', 2);
expect(updatedTask).toHaveProperty('title');
expect(updatedTask).toHaveProperty('description');
expect(updatedTask).toHaveProperty('status');
expect(updatedTask).toHaveProperty('dependencies');
expect(updatedTask).toHaveProperty('priority');
// Check that description was updated
expect(updatedTask.description.toLowerCase()).toContain('oauth');
// Verify task was written back to file
const savedData = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const savedTask = savedData.tasks.find(t => t.id === 2);
expect(savedTask.description).toBe(updatedTask.description);
}, 30000); // Increase timeout for AI call
test('should handle append mode with plain text', async () => {
const result = await updateTaskById({
file: testTasksFile,
prompt: 'Add information about refresh tokens',
id: 2,
append: true
});
beforeEach(() => {
// Create a test tasks file
const testTasks = {
projectName: 'Test Project',
tasks: [
{
id: 1,
title: 'Setup project structure',
description: 'Initialize the project with proper folder structure',
status: 'pending',
dependencies: [],
priority: 'high',
details: 'Create folders for src, tests, docs',
testStrategy: 'Manual verification'
},
{
id: 2,
title: 'Implement authentication',
description: 'Add user authentication with JWT tokens',
status: 'pending',
dependencies: [1],
priority: 'high',
details: null,
testStrategy: null
}
]
};
fs.writeFileSync(testTasksFile, JSON.stringify(testTasks, null, 2));
});
expect(result).toHaveProperty('updatedTask');
const { updatedTask } = result;
// Check that details were appended
expect(updatedTask.details).toBeTruthy();
expect(updatedTask.details).toContain('<info added on');
expect(updatedTask.details.toLowerCase()).toContain('refresh token');
}, 30000);
});
afterEach(() => {
// Clean up test files
if (fs.existsSync(testTasksFile)) {
fs.unlinkSync(testTasksFile);
}
});
test('should update task with structured data', async () => {
const result = await updateTaskById({
file: testTasksFile,
prompt: 'Update the description to include OAuth2 support',
id: 2
});
expect(result).toHaveProperty('updatedTask');
const { updatedTask } = result;
// Verify the task structure
expect(updatedTask).toHaveProperty('id', 2);
expect(updatedTask).toHaveProperty('title');
expect(updatedTask).toHaveProperty('description');
expect(updatedTask).toHaveProperty('status');
expect(updatedTask).toHaveProperty('dependencies');
expect(updatedTask).toHaveProperty('priority');
// Check that description was updated
expect(updatedTask.description.toLowerCase()).toContain('oauth');
// Verify task was written back to file
const savedData = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const savedTask = savedData.tasks.find((t) => t.id === 2);
expect(savedTask.description).toBe(updatedTask.description);
}, 30000); // Increase timeout for AI call
test('should handle append mode with plain text', async () => {
const result = await updateTaskById({
file: testTasksFile,
prompt: 'Add information about refresh tokens',
id: 2,
append: true
});
expect(result).toHaveProperty('updatedTask');
const { updatedTask } = result;
// Check that details were appended
expect(updatedTask.details).toBeTruthy();
expect(updatedTask.details).toContain('<info added on');
expect(updatedTask.details.toLowerCase()).toContain('refresh token');
}, 30000);
});

View File

@@ -4,138 +4,144 @@ import fs from 'fs';
import path from 'path';
describe('update-tasks with generateObject', () => {
const testTasksFile = path.join(process.cwd(), 'test-tasks.json');
beforeEach(() => {
// Create a test tasks file
const testTasks = {
projectName: "Test Project",
tasks: [
{
id: 1,
title: "Setup project structure",
description: "Initialize the project with proper folder structure",
status: "done",
dependencies: [],
priority: "high",
details: "Create folders for src, tests, docs",
testStrategy: "Manual verification",
subtasks: []
},
{
id: 2,
title: "Implement authentication",
description: "Add user authentication with JWT tokens",
status: "pending",
dependencies: [1],
priority: "high",
details: "Need to support OAuth2 and traditional login",
testStrategy: null,
subtasks: [
{
id: 1,
title: "Design auth flow",
description: "Create authentication flow diagrams",
status: "done",
dependencies: []
}
]
},
{
id: 3,
title: "Build API endpoints",
description: "Create RESTful API endpoints",
status: "in-progress",
dependencies: [2],
priority: "medium",
details: "Use Express.js for the API",
testStrategy: "Integration tests with Jest",
subtasks: []
},
{
id: 4,
title: "Add database layer",
description: "Implement database models and migrations",
status: "pending",
dependencies: [1],
priority: "high",
details: null,
testStrategy: null,
subtasks: []
}
]
};
fs.writeFileSync(testTasksFile, JSON.stringify(testTasks, null, 2));
});
afterEach(() => {
// Clean up test files
if (fs.existsSync(testTasksFile)) {
fs.unlinkSync(testTasksFile);
}
});
test('should update multiple tasks with structured data', async () => {
const result = await updateTasks(
testTasksFile,
2, // Update from task ID 2 onwards
'Switch to microservices architecture with Docker containers'
);
const testTasksFile = path.join(process.cwd(), 'test-tasks.json');
expect(result).toBeDefined();
expect(result).toHaveProperty('updatedTasks');
expect(result).toHaveProperty('telemetryData');
// Read the updated file
const updatedData = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
// Task 1 should remain unchanged (status: done)
const task1 = updatedData.tasks.find(t => t.id === 1);
expect(task1.title).toBe("Setup project structure");
expect(task1.status).toBe("done");
// Tasks 2, 3, and 4 should be updated
const task2 = updatedData.tasks.find(t => t.id === 2);
expect(task2.description.toLowerCase()).toContain('microservice');
// Completed subtasks should be preserved
expect(task2.subtasks.find(st => st.id === 1 && st.status === 'done')).toBeDefined();
const task3 = updatedData.tasks.find(t => t.id === 3);
expect(task3.description.toLowerCase()).toContain('docker');
const task4 = updatedData.tasks.find(t => t.id === 4);
expect(task4.description.toLowerCase()).toMatch(/microservice|docker|container/);
}, 30000); // Increase timeout for AI call
test('should preserve completed subtasks when updating', async () => {
await updateTasks(
testTasksFile,
2,
'Add comprehensive error handling and logging'
);
const updatedData = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const task2 = updatedData.tasks.find(t => t.id === 2);
// Find the completed subtask
const completedSubtask = task2.subtasks.find(st => st.id === 1);
expect(completedSubtask).toBeDefined();
expect(completedSubtask.status).toBe('done');
expect(completedSubtask.title).toBe("Design auth flow");
expect(completedSubtask.description).toBe("Create authentication flow diagrams");
}, 30000);
test('should handle no tasks to update', async () => {
const result = await updateTasks(
testTasksFile,
10, // Start from non-existent task ID
'Update all tasks'
);
expect(result).toBeUndefined();
// File should remain unchanged
const data = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
expect(data.tasks.length).toBe(4);
}, 30000);
});
beforeEach(() => {
// Create a test tasks file
const testTasks = {
projectName: 'Test Project',
tasks: [
{
id: 1,
title: 'Setup project structure',
description: 'Initialize the project with proper folder structure',
status: 'done',
dependencies: [],
priority: 'high',
details: 'Create folders for src, tests, docs',
testStrategy: 'Manual verification',
subtasks: []
},
{
id: 2,
title: 'Implement authentication',
description: 'Add user authentication with JWT tokens',
status: 'pending',
dependencies: [1],
priority: 'high',
details: 'Need to support OAuth2 and traditional login',
testStrategy: null,
subtasks: [
{
id: 1,
title: 'Design auth flow',
description: 'Create authentication flow diagrams',
status: 'done',
dependencies: []
}
]
},
{
id: 3,
title: 'Build API endpoints',
description: 'Create RESTful API endpoints',
status: 'in-progress',
dependencies: [2],
priority: 'medium',
details: 'Use Express.js for the API',
testStrategy: 'Integration tests with Jest',
subtasks: []
},
{
id: 4,
title: 'Add database layer',
description: 'Implement database models and migrations',
status: 'pending',
dependencies: [1],
priority: 'high',
details: null,
testStrategy: null,
subtasks: []
}
]
};
fs.writeFileSync(testTasksFile, JSON.stringify(testTasks, null, 2));
});
afterEach(() => {
// Clean up test files
if (fs.existsSync(testTasksFile)) {
fs.unlinkSync(testTasksFile);
}
});
test('should update multiple tasks with structured data', async () => {
const result = await updateTasks(
testTasksFile,
2, // Update from task ID 2 onwards
'Switch to microservices architecture with Docker containers'
);
expect(result).toBeDefined();
expect(result).toHaveProperty('updatedTasks');
expect(result).toHaveProperty('telemetryData');
// Read the updated file
const updatedData = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
// Task 1 should remain unchanged (status: done)
const task1 = updatedData.tasks.find((t) => t.id === 1);
expect(task1.title).toBe('Setup project structure');
expect(task1.status).toBe('done');
// Tasks 2, 3, and 4 should be updated
const task2 = updatedData.tasks.find((t) => t.id === 2);
expect(task2.description.toLowerCase()).toContain('microservice');
// Completed subtasks should be preserved
expect(
task2.subtasks.find((st) => st.id === 1 && st.status === 'done')
).toBeDefined();
const task3 = updatedData.tasks.find((t) => t.id === 3);
expect(task3.description.toLowerCase()).toContain('docker');
const task4 = updatedData.tasks.find((t) => t.id === 4);
expect(task4.description.toLowerCase()).toMatch(
/microservice|docker|container/
);
}, 30000); // Increase timeout for AI call
test('should preserve completed subtasks when updating', async () => {
await updateTasks(
testTasksFile,
2,
'Add comprehensive error handling and logging'
);
const updatedData = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
const task2 = updatedData.tasks.find((t) => t.id === 2);
// Find the completed subtask
const completedSubtask = task2.subtasks.find((st) => st.id === 1);
expect(completedSubtask).toBeDefined();
expect(completedSubtask.status).toBe('done');
expect(completedSubtask.title).toBe('Design auth flow');
expect(completedSubtask.description).toBe(
'Create authentication flow diagrams'
);
}, 30000);
test('should handle no tasks to update', async () => {
const result = await updateTasks(
testTasksFile,
10, // Start from non-existent task ID
'Update all tasks'
);
expect(result).toBeUndefined();
// File should remain unchanged
const data = JSON.parse(fs.readFileSync(testTasksFile, 'utf8'));
expect(data.tasks.length).toBe(4);
}, 30000);
});

View File

@@ -11,205 +11,220 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
describe('GenerateObject Migration Verification', () => {
const scriptsDir = path.join(__dirname, '../../scripts/modules/task-manager');
describe('Legacy Parsing Function Removal', () => {
test('should not find parseUpdatedTasksFromText function', () => {
const updateTasksFile = fs.readFileSync(
path.join(scriptsDir, 'update-tasks.js'),
'utf8'
);
// The function should still exist but only for reference
// It's not being used anywhere in the actual command flow
const hasParsingFunction = updateTasksFile.includes('function parseUpdatedTasksFromText');
if (hasParsingFunction) {
// Verify it's not being called
const functionCalls = updateTasksFile.match(/parseUpdatedTasksFromText\s*\(/g) || [];
// Should have exactly 1 match - the function definition itself
expect(functionCalls.length).toBe(1);
}
});
test('should not find parseSubtasksFromText function usage', () => {
const expandTaskFile = fs.readFileSync(
path.join(scriptsDir, 'expand-task.js'),
'utf8'
);
// Should not contain the parsing function at all
expect(expandTaskFile).not.toContain('parseSubtasksFromText');
});
test('should not find parseComplexityAnalysisFromText function usage', () => {
const analyzeComplexityFile = fs.readFileSync(
path.join(scriptsDir, 'analyze-task-complexity.js'),
'utf8'
);
// Should not contain the parsing function at all
expect(analyzeComplexityFile).not.toContain('parseComplexityAnalysisFromText');
});
});
describe('GenerateObject Service Usage', () => {
const commandFiles = [
'analyze-task-complexity.js',
'update-task-by-id.js',
'expand-task.js',
'update-tasks.js',
'add-task.js',
'parse-prd.js'
];
commandFiles.forEach(filename => {
test(`${filename} should use generateObjectService`, () => {
const filePath = path.join(scriptsDir, filename);
const fileContent = fs.readFileSync(filePath, 'utf8');
// Should import generateObjectService
expect(fileContent).toMatch(/import\s+.*generateObjectService.*from\s+['"]\.\.\/ai-services-unified\.js['"]/);
// Should call generateObjectService
expect(fileContent).toContain('generateObjectService(');
// Should use schema
expect(fileContent).toMatch(/schema:\s*\w+Schema|schema:\s*COMMAND_SCHEMAS/);
});
});
test('update-subtask-by-id.js should continue using generateTextService', () => {
const filePath = path.join(scriptsDir, 'update-subtask-by-id.js');
const fileContent = fs.readFileSync(filePath, 'utf8');
// Should still use generateTextService for appending text
expect(fileContent).toContain('generateTextService');
expect(fileContent).not.toContain('generateObjectService');
});
});
describe('Schema Registry Usage', () => {
test('should have a complete schema registry', () => {
const registryPath = path.join(__dirname, '../../src/schemas/registry.js');
const registryContent = fs.readFileSync(registryPath, 'utf8');
// Should export COMMAND_SCHEMAS
expect(registryContent).toContain('export const COMMAND_SCHEMAS');
// Should include all command schemas
const expectedCommands = [
'update-tasks',
'expand-task',
'analyze-complexity',
'update-task-by-id'
];
expectedCommands.forEach(command => {
expect(registryContent).toContain(`'${command}':`);
});
});
test('update-tasks.js should use schema from registry', () => {
const filePath = path.join(scriptsDir, 'update-tasks.js');
const fileContent = fs.readFileSync(filePath, 'utf8');
// Should import from registry
expect(fileContent).toContain("import { COMMAND_SCHEMAS } from '../../../src/schemas/registry.js'");
// Should use registry in generateObjectService call
expect(fileContent).toContain("COMMAND_SCHEMAS['update-tasks']");
});
});
describe('Prompt Template Updates', () => {
const promptsDir = path.join(__dirname, '../../src/prompts');
test('prompts should not contain JSON formatting instructions', () => {
const promptFiles = fs.readdirSync(promptsDir)
.filter(f => f.endsWith('.json'));
const jsonInstructions = [
'Return only the updated tasks as a valid JSON array',
'Do not include any explanatory text, markdown formatting, or code block markers',
'Respond ONLY with a valid JSON',
'The response must be a valid JSON',
'Return the result as JSON'
];
promptFiles.forEach(filename => {
// Skip update-subtask.json as it returns plain text
if (filename === 'update-subtask.json') return;
const filePath = path.join(promptsDir, filename);
const content = fs.readFileSync(filePath, 'utf8');
jsonInstructions.forEach(instruction => {
expect(content).not.toContain(instruction);
});
});
});
});
describe('Direct Object Access Patterns', () => {
test('commands should access data directly from mainResult', () => {
const patterns = [
{
file: 'analyze-task-complexity.js',
pattern: /aiServiceResponse\.mainResult\.complexityAnalysis/
},
{
file: 'expand-task.js',
pattern: /aiServiceResponse\.mainResult\.subtasks/
},
{
file: 'update-tasks.js',
pattern: /aiServiceResponse\.mainResult\.tasks/
},
{
file: 'update-task-by-id.js',
pattern: /aiServiceResponse\.mainResult\.task/
}
];
patterns.forEach(({ file, pattern }) => {
const filePath = path.join(scriptsDir, file);
const fileContent = fs.readFileSync(filePath, 'utf8');
expect(fileContent).toMatch(pattern);
});
});
});
describe('Error Handling Updates', () => {
test('commands should not have AI response JSON parsing error handling', () => {
const commandFiles = [
'analyze-task-complexity.js',
'expand-task.js',
'update-task-by-id.js'
];
// More specific patterns that indicate AI response parsing
const aiParsingErrorPatterns = [
'Failed to parse JSON response',
'Failed to parse AI response',
'parseComplexityAnalysisFromText',
'parseSubtasksFromText',
'parseUpdatedTaskFromText',
'parseUpdatedTasksFromText',
'Malformed JSON',
'extracting between \\[\\]',
'JSON code block'
];
commandFiles.forEach(filename => {
const filePath = path.join(scriptsDir, filename);
const fileContent = fs.readFileSync(filePath, 'utf8');
// Check for AI response parsing patterns
aiParsingErrorPatterns.forEach(pattern => {
expect(fileContent).not.toMatch(new RegExp(pattern, 'i'));
});
});
});
});
});
const scriptsDir = path.join(__dirname, '../../scripts/modules/task-manager');
describe('Legacy Parsing Function Removal', () => {
test('should not find parseUpdatedTasksFromText function', () => {
const updateTasksFile = fs.readFileSync(
path.join(scriptsDir, 'update-tasks.js'),
'utf8'
);
// The function should still exist but only for reference
// It's not being used anywhere in the actual command flow
const hasParsingFunction = updateTasksFile.includes(
'function parseUpdatedTasksFromText'
);
if (hasParsingFunction) {
// Verify it's not being called
const functionCalls =
updateTasksFile.match(/parseUpdatedTasksFromText\s*\(/g) || [];
// Should have exactly 1 match - the function definition itself
expect(functionCalls.length).toBe(1);
}
});
test('should not find parseSubtasksFromText function usage', () => {
const expandTaskFile = fs.readFileSync(
path.join(scriptsDir, 'expand-task.js'),
'utf8'
);
// Should not contain the parsing function at all
expect(expandTaskFile).not.toContain('parseSubtasksFromText');
});
test('should not find parseComplexityAnalysisFromText function usage', () => {
const analyzeComplexityFile = fs.readFileSync(
path.join(scriptsDir, 'analyze-task-complexity.js'),
'utf8'
);
// Should not contain the parsing function at all
expect(analyzeComplexityFile).not.toContain(
'parseComplexityAnalysisFromText'
);
});
});
describe('GenerateObject Service Usage', () => {
const commandFiles = [
'analyze-task-complexity.js',
'update-task-by-id.js',
'expand-task.js',
'update-tasks.js',
'add-task.js',
'parse-prd.js'
];
commandFiles.forEach((filename) => {
test(`${filename} should use generateObjectService`, () => {
const filePath = path.join(scriptsDir, filename);
const fileContent = fs.readFileSync(filePath, 'utf8');
// Should import generateObjectService
expect(fileContent).toMatch(
/import\s+.*generateObjectService.*from\s+['"]\.\.\/ai-services-unified\.js['"]/
);
// Should call generateObjectService
expect(fileContent).toContain('generateObjectService(');
// Should use schema
expect(fileContent).toMatch(
/schema:\s*\w+Schema|schema:\s*COMMAND_SCHEMAS/
);
});
});
test('update-subtask-by-id.js should continue using generateTextService', () => {
const filePath = path.join(scriptsDir, 'update-subtask-by-id.js');
const fileContent = fs.readFileSync(filePath, 'utf8');
// Should still use generateTextService for appending text
expect(fileContent).toContain('generateTextService');
expect(fileContent).not.toContain('generateObjectService');
});
});
describe('Schema Registry Usage', () => {
test('should have a complete schema registry', () => {
const registryPath = path.join(
__dirname,
'../../src/schemas/registry.js'
);
const registryContent = fs.readFileSync(registryPath, 'utf8');
// Should export COMMAND_SCHEMAS
expect(registryContent).toContain('export const COMMAND_SCHEMAS');
// Should include all command schemas
const expectedCommands = [
'update-tasks',
'expand-task',
'analyze-complexity',
'update-task-by-id'
];
expectedCommands.forEach((command) => {
expect(registryContent).toContain(`'${command}':`);
});
});
test('update-tasks.js should use schema from registry', () => {
const filePath = path.join(scriptsDir, 'update-tasks.js');
const fileContent = fs.readFileSync(filePath, 'utf8');
// Should import from registry
expect(fileContent).toContain(
"import { COMMAND_SCHEMAS } from '../../../src/schemas/registry.js'"
);
// Should use registry in generateObjectService call
expect(fileContent).toContain("COMMAND_SCHEMAS['update-tasks']");
});
});
describe('Prompt Template Updates', () => {
const promptsDir = path.join(__dirname, '../../src/prompts');
test('prompts should not contain JSON formatting instructions', () => {
const promptFiles = fs
.readdirSync(promptsDir)
.filter((f) => f.endsWith('.json'));
const jsonInstructions = [
'Return only the updated tasks as a valid JSON array',
'Do not include any explanatory text, markdown formatting, or code block markers',
'Respond ONLY with a valid JSON',
'The response must be a valid JSON',
'Return the result as JSON'
];
promptFiles.forEach((filename) => {
// Skip update-subtask.json as it returns plain text
if (filename === 'update-subtask.json') return;
const filePath = path.join(promptsDir, filename);
const content = fs.readFileSync(filePath, 'utf8');
jsonInstructions.forEach((instruction) => {
expect(content).not.toContain(instruction);
});
});
});
});
describe('Direct Object Access Patterns', () => {
test('commands should access data directly from mainResult', () => {
const patterns = [
{
file: 'analyze-task-complexity.js',
pattern: /aiServiceResponse\.mainResult\.complexityAnalysis/
},
{
file: 'expand-task.js',
pattern: /aiServiceResponse\.mainResult\.subtasks/
},
{
file: 'update-tasks.js',
pattern: /aiServiceResponse\.mainResult\.tasks/
},
{
file: 'update-task-by-id.js',
pattern: /aiServiceResponse\.mainResult\.task/
}
];
patterns.forEach(({ file, pattern }) => {
const filePath = path.join(scriptsDir, file);
const fileContent = fs.readFileSync(filePath, 'utf8');
expect(fileContent).toMatch(pattern);
});
});
});
describe('Error Handling Updates', () => {
test('commands should not have AI response JSON parsing error handling', () => {
const commandFiles = [
'analyze-task-complexity.js',
'expand-task.js',
'update-task-by-id.js'
];
// More specific patterns that indicate AI response parsing
const aiParsingErrorPatterns = [
'Failed to parse JSON response',
'Failed to parse AI response',
'parseComplexityAnalysisFromText',
'parseSubtasksFromText',
'parseUpdatedTaskFromText',
'parseUpdatedTasksFromText',
'Malformed JSON',
'extracting between \\[\\]',
'JSON code block'
];
commandFiles.forEach((filename) => {
const filePath = path.join(scriptsDir, filename);
const fileContent = fs.readFileSync(filePath, 'utf8');
// Check for AI response parsing patterns
aiParsingErrorPatterns.forEach((pattern) => {
expect(fileContent).not.toMatch(new RegExp(pattern, 'i'));
});
});
});
});
});

View File

@@ -6,50 +6,51 @@ 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'
];
const bannedPhrases = [
'Respond ONLY with',
'Return only the',
'valid JSON',
'Do not include any explanatory text',
'Do not include any explanation',
'code block markers'
];
// Special cases where phrases are okay in different contexts
const allowedContexts = {
'markdown formatting': ['Use markdown formatting for better readability']
};
// Special cases where phrases are okay in different contexts
const allowedContexts = {
'markdown formatting': ['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');
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');
const promptData = JSON.parse(content);
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];
if (allowedInContext) {
const isAllowed = allowedInContext.some(context =>
lowerContent.includes(context.toLowerCase())
);
if (isAllowed) {
return; // Skip this phrase - it's allowed in this context
}
}
// If we get here, the phrase is not allowed
expect(lowerContent).not.toContain(lowerPhrase);
}
});
});
});
});
promptFiles.forEach((file) => {
const content = fs.readFileSync(path.join(promptsDir, file), 'utf8');
const promptData = JSON.parse(content);
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];
if (allowedInContext) {
const isAllowed = allowedInContext.some((context) =>
lowerContent.includes(context.toLowerCase())
);
if (isAllowed) {
return; // Skip this phrase - it's allowed in this context
}
}
// If we get here, the phrase is not allowed
expect(lowerContent).not.toContain(lowerPhrase);
}
});
});
});
});

View File

@@ -831,7 +831,9 @@ describe('expandTask', () => {
projectRoot: '/mock/project/root'
};
generateObjectService.mockRejectedValueOnce(new Error('AI service error'));
generateObjectService.mockRejectedValueOnce(
new Error('AI service error')
);
// Act & Assert
await expect(

View File

@@ -44,24 +44,22 @@ jest.unstable_mockModule(
generateTextService: jest
.fn()
.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: {}
})
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: {}
})
})
);