mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-31 06:42:03 +00:00
This commit fixes two issues introduced during prompt customization: 1. **Restored Full Planning Prompts from Main** - Lite Mode: Added "Silently analyze the codebase first" instruction - Spec Mode: Restored detailed task format rules, [TASK_START]/[TASK_COMPLETE] markers - Full Mode: Restored comprehensive SDD format with [PHASE_COMPLETE] markers - Fixed table structures (Files to Modify, Technical Context, Risks & Mitigations) - Ensured all critical instructions for Auto Mode functionality are preserved 2. **Fixed Test Suite (774 tests passing)** - Made getPlanningPromptPrefix() async-aware in all 11 planning tests - Replaced console.log/error mocks with createLogger mocks (settings-helpers, agent-service) - Updated test expectations to match restored prompts - Fixed variable hoisting issue in agent-service mock setup - Built prompts library to apply changes The planning prompts now match the detailed, production-ready versions from main branch, ensuring Auto Mode has all necessary instructions for proper task execution. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
337 lines
12 KiB
TypeScript
337 lines
12 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import { AutoModeService } from '@/services/auto-mode-service.js';
|
|
|
|
describe('auto-mode-service.ts - Planning Mode', () => {
|
|
let service: AutoModeService;
|
|
const mockEvents = {
|
|
subscribe: vi.fn(),
|
|
emit: vi.fn(),
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
service = new AutoModeService(mockEvents as any);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
// Clean up any running processes
|
|
await service.stopAutoLoop().catch(() => {});
|
|
});
|
|
|
|
describe('getPlanningPromptPrefix', () => {
|
|
// Access private method through any cast for testing
|
|
const getPlanningPromptPrefix = (svc: any, feature: any) => {
|
|
return svc.getPlanningPromptPrefix(feature);
|
|
};
|
|
|
|
it('should return empty string for skip mode', async () => {
|
|
const feature = { id: 'test', planningMode: 'skip' as const };
|
|
const result = await getPlanningPromptPrefix(service, feature);
|
|
expect(result).toBe('');
|
|
});
|
|
|
|
it('should return empty string when planningMode is undefined', async () => {
|
|
const feature = { id: 'test' };
|
|
const result = await getPlanningPromptPrefix(service, feature);
|
|
expect(result).toBe('');
|
|
});
|
|
|
|
it('should return lite prompt for lite mode without approval', async () => {
|
|
const feature = {
|
|
id: 'test',
|
|
planningMode: 'lite' as const,
|
|
requirePlanApproval: false,
|
|
};
|
|
const result = await getPlanningPromptPrefix(service, feature);
|
|
expect(result).toContain('Planning Phase (Lite Mode)');
|
|
expect(result).toContain('[PLAN_GENERATED]');
|
|
expect(result).toContain('Feature Request');
|
|
});
|
|
|
|
it('should return lite_with_approval prompt for lite mode with approval', async () => {
|
|
const feature = {
|
|
id: 'test',
|
|
planningMode: 'lite' as const,
|
|
requirePlanApproval: true,
|
|
};
|
|
const result = await getPlanningPromptPrefix(service, feature);
|
|
expect(result).toContain('## Planning Phase (Lite Mode)');
|
|
expect(result).toContain('[SPEC_GENERATED]');
|
|
expect(result).toContain(
|
|
'DO NOT proceed with implementation until you receive explicit approval'
|
|
);
|
|
});
|
|
|
|
it('should return spec prompt for spec mode', async () => {
|
|
const feature = {
|
|
id: 'test',
|
|
planningMode: 'spec' as const,
|
|
};
|
|
const result = await getPlanningPromptPrefix(service, feature);
|
|
expect(result).toContain('## Specification Phase (Spec Mode)');
|
|
expect(result).toContain('```tasks');
|
|
expect(result).toContain('T001');
|
|
expect(result).toContain('[TASK_START]');
|
|
expect(result).toContain('[TASK_COMPLETE]');
|
|
});
|
|
|
|
it('should return full prompt for full mode', async () => {
|
|
const feature = {
|
|
id: 'test',
|
|
planningMode: 'full' as const,
|
|
};
|
|
const result = await getPlanningPromptPrefix(service, feature);
|
|
expect(result).toContain('## Full Specification Phase (Full SDD Mode)');
|
|
expect(result).toContain('Phase 1: Foundation');
|
|
expect(result).toContain('Phase 2: Core Implementation');
|
|
expect(result).toContain('Phase 3: Integration & Testing');
|
|
});
|
|
|
|
it('should include the separator and Feature Request header', async () => {
|
|
const feature = {
|
|
id: 'test',
|
|
planningMode: 'spec' as const,
|
|
};
|
|
const result = await getPlanningPromptPrefix(service, feature);
|
|
expect(result).toContain('---');
|
|
expect(result).toContain('## Feature Request');
|
|
});
|
|
|
|
it('should instruct agent to NOT output exploration text', async () => {
|
|
const modes = ['lite', 'spec', 'full'] as const;
|
|
for (const mode of modes) {
|
|
const feature = { id: 'test', planningMode: mode };
|
|
const result = await getPlanningPromptPrefix(service, feature);
|
|
// All modes should have the IMPORTANT instruction about not outputting exploration text
|
|
expect(result).toContain('IMPORTANT: Do NOT output exploration text');
|
|
expect(result).toContain('Silently analyze the codebase first');
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('parseTasksFromSpec (via module)', () => {
|
|
// We need to test the module-level function
|
|
// Import it directly for testing
|
|
it('should parse tasks from a valid tasks block', async () => {
|
|
// This tests the internal logic through integration
|
|
// The function is module-level, so we verify behavior through the service
|
|
const specContent = `
|
|
## Specification
|
|
|
|
\`\`\`tasks
|
|
- [ ] T001: Create user model | File: src/models/user.ts
|
|
- [ ] T002: Add API endpoint | File: src/routes/users.ts
|
|
- [ ] T003: Write unit tests | File: tests/user.test.ts
|
|
\`\`\`
|
|
`;
|
|
// Since parseTasksFromSpec is a module-level function,
|
|
// we verify its behavior indirectly through plan parsing
|
|
expect(specContent).toContain('T001');
|
|
expect(specContent).toContain('T002');
|
|
expect(specContent).toContain('T003');
|
|
});
|
|
|
|
it('should handle tasks block with phases', () => {
|
|
const specContent = `
|
|
\`\`\`tasks
|
|
## Phase 1: Setup
|
|
- [ ] T001: Initialize project | File: package.json
|
|
- [ ] T002: Configure TypeScript | File: tsconfig.json
|
|
|
|
## Phase 2: Implementation
|
|
- [ ] T003: Create main module | File: src/index.ts
|
|
\`\`\`
|
|
`;
|
|
expect(specContent).toContain('Phase 1');
|
|
expect(specContent).toContain('Phase 2');
|
|
expect(specContent).toContain('T001');
|
|
expect(specContent).toContain('T003');
|
|
});
|
|
});
|
|
|
|
describe('plan approval flow', () => {
|
|
it('should track pending approvals correctly', () => {
|
|
expect(service.hasPendingApproval('test-feature')).toBe(false);
|
|
});
|
|
|
|
it('should allow cancelling non-existent approval without error', () => {
|
|
expect(() => service.cancelPlanApproval('non-existent')).not.toThrow();
|
|
});
|
|
|
|
it('should return running features count after stop', async () => {
|
|
const count = await service.stopAutoLoop();
|
|
expect(count).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('resolvePlanApproval', () => {
|
|
it('should return error when no pending approval exists', async () => {
|
|
const result = await service.resolvePlanApproval(
|
|
'non-existent-feature',
|
|
true,
|
|
undefined,
|
|
undefined,
|
|
undefined
|
|
);
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toContain('No pending approval');
|
|
});
|
|
|
|
it('should handle approval with edited plan', async () => {
|
|
// Without a pending approval, this should fail gracefully
|
|
const result = await service.resolvePlanApproval(
|
|
'test-feature',
|
|
true,
|
|
'Edited plan content',
|
|
undefined,
|
|
undefined
|
|
);
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should handle rejection with feedback', async () => {
|
|
const result = await service.resolvePlanApproval(
|
|
'test-feature',
|
|
false,
|
|
undefined,
|
|
'Please add more details',
|
|
undefined
|
|
);
|
|
expect(result.success).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('buildFeaturePrompt', () => {
|
|
const buildFeaturePrompt = (svc: any, feature: any) => {
|
|
return svc.buildFeaturePrompt(feature);
|
|
};
|
|
|
|
it('should include feature ID and description', () => {
|
|
const feature = {
|
|
id: 'feat-123',
|
|
description: 'Add user authentication',
|
|
};
|
|
const result = buildFeaturePrompt(service, feature);
|
|
expect(result).toContain('feat-123');
|
|
expect(result).toContain('Add user authentication');
|
|
});
|
|
|
|
it('should include specification when present', () => {
|
|
const feature = {
|
|
id: 'feat-123',
|
|
description: 'Test feature',
|
|
spec: 'Detailed specification here',
|
|
};
|
|
const result = buildFeaturePrompt(service, feature);
|
|
expect(result).toContain('Specification:');
|
|
expect(result).toContain('Detailed specification here');
|
|
});
|
|
|
|
it('should include image paths when present', () => {
|
|
const feature = {
|
|
id: 'feat-123',
|
|
description: 'Test feature',
|
|
imagePaths: [
|
|
{ path: '/tmp/image1.png', filename: 'image1.png', mimeType: 'image/png' },
|
|
'/tmp/image2.jpg',
|
|
],
|
|
};
|
|
const result = buildFeaturePrompt(service, feature);
|
|
expect(result).toContain('Context Images Attached');
|
|
expect(result).toContain('image1.png');
|
|
expect(result).toContain('/tmp/image2.jpg');
|
|
});
|
|
|
|
it('should include summary tags instruction', () => {
|
|
const feature = {
|
|
id: 'feat-123',
|
|
description: 'Test feature',
|
|
};
|
|
const result = buildFeaturePrompt(service, feature);
|
|
expect(result).toContain('<summary>');
|
|
expect(result).toContain('</summary>');
|
|
});
|
|
});
|
|
|
|
describe('extractTitleFromDescription', () => {
|
|
const extractTitle = (svc: any, description: string) => {
|
|
return svc.extractTitleFromDescription(description);
|
|
};
|
|
|
|
it("should return 'Untitled Feature' for empty description", () => {
|
|
expect(extractTitle(service, '')).toBe('Untitled Feature');
|
|
expect(extractTitle(service, ' ')).toBe('Untitled Feature');
|
|
});
|
|
|
|
it('should return first line if under 60 characters', () => {
|
|
const description = 'Add user login\nWith email validation';
|
|
expect(extractTitle(service, description)).toBe('Add user login');
|
|
});
|
|
|
|
it('should truncate long first lines to 60 characters', () => {
|
|
const description =
|
|
'This is a very long feature description that exceeds the sixty character limit significantly';
|
|
const result = extractTitle(service, description);
|
|
expect(result.length).toBe(60);
|
|
expect(result).toContain('...');
|
|
});
|
|
});
|
|
|
|
describe('PLANNING_PROMPTS structure', () => {
|
|
const getPlanningPromptPrefix = (svc: any, feature: any) => {
|
|
return svc.getPlanningPromptPrefix(feature);
|
|
};
|
|
|
|
it('should have all required planning modes', async () => {
|
|
const modes = ['lite', 'spec', 'full'] as const;
|
|
for (const mode of modes) {
|
|
const feature = { id: 'test', planningMode: mode };
|
|
const result = await getPlanningPromptPrefix(service, feature);
|
|
expect(result.length).toBeGreaterThan(100);
|
|
}
|
|
});
|
|
|
|
it('lite prompt should include correct structure', async () => {
|
|
const feature = { id: 'test', planningMode: 'lite' as const };
|
|
const result = await getPlanningPromptPrefix(service, feature);
|
|
expect(result).toContain('Goal');
|
|
expect(result).toContain('Approach');
|
|
expect(result).toContain('Files to Touch');
|
|
expect(result).toContain('Tasks');
|
|
expect(result).toContain('Risks');
|
|
});
|
|
|
|
it('spec prompt should include task format instructions', async () => {
|
|
const feature = { id: 'test', planningMode: 'spec' as const };
|
|
const result = await getPlanningPromptPrefix(service, feature);
|
|
expect(result).toContain('Problem');
|
|
expect(result).toContain('Solution');
|
|
expect(result).toContain('Acceptance Criteria');
|
|
expect(result).toContain('GIVEN-WHEN-THEN');
|
|
expect(result).toContain('Implementation Tasks');
|
|
expect(result).toContain('Verification');
|
|
});
|
|
|
|
it('full prompt should include phases', async () => {
|
|
const feature = { id: 'test', planningMode: 'full' as const };
|
|
const result = await getPlanningPromptPrefix(service, feature);
|
|
expect(result).toContain('1. **Problem Statement**');
|
|
expect(result).toContain('2. **User Story**');
|
|
expect(result).toContain('4. **Technical Context**');
|
|
expect(result).toContain('5. **Non-Goals**');
|
|
expect(result).toContain('Phase 1');
|
|
expect(result).toContain('Phase 2');
|
|
expect(result).toContain('Phase 3');
|
|
});
|
|
});
|
|
|
|
describe('status management', () => {
|
|
it('should report correct status', () => {
|
|
const status = service.getStatus();
|
|
expect(status.runningFeatures).toEqual([]);
|
|
expect(status.isRunning).toBe(false);
|
|
expect(status.runningCount).toBe(0);
|
|
});
|
|
});
|
|
});
|