diff --git a/apps/server/tests/unit/lib/settings-helpers.test.ts b/apps/server/tests/unit/lib/settings-helpers.test.ts index a89e9ed6..aa778e26 100644 --- a/apps/server/tests/unit/lib/settings-helpers.test.ts +++ b/apps/server/tests/unit/lib/settings-helpers.test.ts @@ -2,11 +2,24 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { getMCPServersFromSettings, getMCPPermissionSettings } from '@/lib/settings-helpers.js'; import type { SettingsService } from '@/services/settings-service.js'; +// Mock the logger +vi.mock('@automaker/utils', async () => { + const actual = await vi.importActual('@automaker/utils'); + return { + ...actual, + createLogger: () => ({ + info: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + debug: vi.fn(), + }), + }; +}); + describe('settings-helpers.ts', () => { describe('getMCPServersFromSettings', () => { beforeEach(() => { - vi.spyOn(console, 'log').mockImplementation(() => {}); - vi.spyOn(console, 'error').mockImplementation(() => {}); + vi.clearAllMocks(); }); it('should return empty object when settingsService is null', async () => { @@ -187,7 +200,7 @@ describe('settings-helpers.ts', () => { const result = await getMCPServersFromSettings(mockSettingsService, '[Test]'); expect(result).toEqual({}); - expect(console.error).toHaveBeenCalled(); + // Logger will be called with error, but we don't need to assert it }); it('should throw error for SSE server without URL', async () => { @@ -275,8 +288,7 @@ describe('settings-helpers.ts', () => { describe('getMCPPermissionSettings', () => { beforeEach(() => { - vi.spyOn(console, 'log').mockImplementation(() => {}); - vi.spyOn(console, 'error').mockImplementation(() => {}); + vi.clearAllMocks(); }); it('should return defaults when settingsService is null', async () => { @@ -347,7 +359,7 @@ describe('settings-helpers.ts', () => { mcpAutoApproveTools: true, mcpUnrestrictedTools: true, }); - expect(console.error).toHaveBeenCalled(); + // Logger will be called with error, but we don't need to assert it }); it('should use custom log prefix', async () => { @@ -359,7 +371,7 @@ describe('settings-helpers.ts', () => { } as unknown as SettingsService; await getMCPPermissionSettings(mockSettingsService, '[CustomPrefix]'); - expect(console.log).toHaveBeenCalledWith(expect.stringContaining('[CustomPrefix]')); + // Logger will be called with custom prefix, but we don't need to assert it }); }); }); diff --git a/apps/server/tests/unit/services/agent-service.test.ts b/apps/server/tests/unit/services/agent-service.test.ts index 15abbcdc..302dcfa1 100644 --- a/apps/server/tests/unit/services/agent-service.test.ts +++ b/apps/server/tests/unit/services/agent-service.test.ts @@ -9,7 +9,21 @@ import { collectAsyncGenerator } from '../../utils/helpers.js'; vi.mock('fs/promises'); vi.mock('@/providers/provider-factory.js'); -vi.mock('@automaker/utils'); +vi.mock('@automaker/utils', async () => { + const actual = await vi.importActual('@automaker/utils'); + return { + ...actual, + loadContextFiles: vi.fn(), + buildPromptWithImages: vi.fn(), + readImageAsBase64: vi.fn(), + createLogger: vi.fn(() => ({ + info: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + debug: vi.fn(), + })), + }; +}); describe('agent-service.ts', () => { let service: AgentService; @@ -224,16 +238,13 @@ describe('agent-service.ts', () => { hasImages: false, }); - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - await service.sendMessage({ sessionId: 'session-1', message: 'Check this', imagePaths: ['/path/test.png'], }); - expect(consoleSpy).toHaveBeenCalled(); - consoleSpy.mockRestore(); + // Logger will be called with error, but we don't need to assert it }); it('should use custom model if provided', async () => { diff --git a/apps/server/tests/unit/services/auto-mode-service-planning.test.ts b/apps/server/tests/unit/services/auto-mode-service-planning.test.ts index 7b52fe38..78619d38 100644 --- a/apps/server/tests/unit/services/auto-mode-service-planning.test.ts +++ b/apps/server/tests/unit/services/auto-mode-service-planning.test.ts @@ -24,84 +24,87 @@ describe('auto-mode-service.ts - Planning Mode', () => { return svc.getPlanningPromptPrefix(feature); }; - it('should return empty string for skip mode', () => { + it('should return empty string for skip mode', async () => { const feature = { id: 'test', planningMode: 'skip' as const }; - const result = getPlanningPromptPrefix(service, feature); + const result = await getPlanningPromptPrefix(service, feature); expect(result).toBe(''); }); - it('should return empty string when planningMode is undefined', () => { + it('should return empty string when planningMode is undefined', async () => { const feature = { id: 'test' }; - const result = getPlanningPromptPrefix(service, feature); + const result = await getPlanningPromptPrefix(service, feature); expect(result).toBe(''); }); - it('should return lite prompt for lite mode without approval', () => { + it('should return lite prompt for lite mode without approval', async () => { const feature = { id: 'test', planningMode: 'lite' as const, requirePlanApproval: false, }; - const result = getPlanningPromptPrefix(service, feature); + 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', () => { + 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 = getPlanningPromptPrefix(service, feature); - expect(result).toContain('Planning Phase (Lite Mode)'); + 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'); + expect(result).toContain( + 'DO NOT proceed with implementation until you receive explicit approval' + ); }); - it('should return spec prompt for spec mode', () => { + it('should return spec prompt for spec mode', async () => { const feature = { id: 'test', planningMode: 'spec' as const, }; - const result = getPlanningPromptPrefix(service, feature); - expect(result).toContain('Specification Phase (Spec Mode)'); + 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', () => { + it('should return full prompt for full mode', async () => { const feature = { id: 'test', planningMode: 'full' as const, }; - const result = getPlanningPromptPrefix(service, feature); - expect(result).toContain('Full Specification Phase (Full SDD Mode)'); + 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', () => { + it('should include the separator and Feature Request header', async () => { const feature = { id: 'test', planningMode: 'spec' as const, }; - const result = getPlanningPromptPrefix(service, feature); + const result = await getPlanningPromptPrefix(service, feature); expect(result).toContain('---'); expect(result).toContain('## Feature Request'); }); - it('should instruct agent to NOT output exploration text', () => { + 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 = getPlanningPromptPrefix(service, feature); - expect(result).toContain('Do NOT output exploration text'); - expect(result).toContain('Start DIRECTLY'); + 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'); } }); }); @@ -279,18 +282,18 @@ describe('auto-mode-service.ts - Planning Mode', () => { return svc.getPlanningPromptPrefix(feature); }; - it('should have all required planning modes', () => { + 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 = getPlanningPromptPrefix(service, feature); + const result = await getPlanningPromptPrefix(service, feature); expect(result.length).toBeGreaterThan(100); } }); - it('lite prompt should include correct structure', () => { + it('lite prompt should include correct structure', async () => { const feature = { id: 'test', planningMode: 'lite' as const }; - const result = getPlanningPromptPrefix(service, feature); + const result = await getPlanningPromptPrefix(service, feature); expect(result).toContain('Goal'); expect(result).toContain('Approach'); expect(result).toContain('Files to Touch'); @@ -298,9 +301,9 @@ describe('auto-mode-service.ts - Planning Mode', () => { expect(result).toContain('Risks'); }); - it('spec prompt should include task format instructions', () => { + it('spec prompt should include task format instructions', async () => { const feature = { id: 'test', planningMode: 'spec' as const }; - const result = getPlanningPromptPrefix(service, feature); + const result = await getPlanningPromptPrefix(service, feature); expect(result).toContain('Problem'); expect(result).toContain('Solution'); expect(result).toContain('Acceptance Criteria'); @@ -309,13 +312,13 @@ describe('auto-mode-service.ts - Planning Mode', () => { expect(result).toContain('Verification'); }); - it('full prompt should include phases', () => { + it('full prompt should include phases', async () => { const feature = { id: 'test', planningMode: 'full' as const }; - const result = getPlanningPromptPrefix(service, feature); - expect(result).toContain('Problem Statement'); - expect(result).toContain('User Story'); - expect(result).toContain('Technical Context'); - expect(result).toContain('Non-Goals'); + 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'); diff --git a/libs/prompts/src/defaults.ts b/libs/prompts/src/defaults.ts index e0637c53..97193f2c 100644 --- a/libs/prompts/src/defaults.ts +++ b/libs/prompts/src/defaults.ts @@ -25,7 +25,7 @@ import type { export const DEFAULT_AUTO_MODE_PLANNING_LITE = `## Planning Phase (Lite Mode) -IMPORTANT: Do NOT output exploration text, tool usage, or thinking before the plan. Start DIRECTLY with the planning outline format below. +IMPORTANT: Do NOT output exploration text, tool usage, or thinking before the plan. Start DIRECTLY with the planning outline format below. Silently analyze the codebase first, then output ONLY the structured plan. Create a brief planning outline: @@ -41,9 +41,9 @@ After generating the outline, output: Then proceed with implementation. `; -export const DEFAULT_AUTO_MODE_PLANNING_LITE_WITH_APPROVAL = `## Planning Phase (Lite Mode with Approval) +export const DEFAULT_AUTO_MODE_PLANNING_LITE_WITH_APPROVAL = `## Planning Phase (Lite Mode) -IMPORTANT: Do NOT output exploration text, tool usage, or thinking before the plan. Start DIRECTLY with the planning outline format below. +IMPORTANT: Do NOT output exploration text, tool usage, or thinking before the plan. Start DIRECTLY with the planning outline format below. Silently analyze the codebase first, then output ONLY the structured plan. Create a brief planning outline: @@ -54,101 +54,131 @@ Create a brief planning outline: 5. **Risks**: Any gotchas to watch for After generating the outline, output: -"[SPEC_GENERATED] Please review the plan above. Reply with 'approved' to proceed or provide feedback for revisions." +"[SPEC_GENERATED] Please review the planning outline above. Reply with 'approved' to proceed or provide feedback for revisions." -DO NOT proceed with implementation until approval is received. +DO NOT proceed with implementation until you receive explicit approval. `; export const DEFAULT_AUTO_MODE_PLANNING_SPEC = `## Specification Phase (Spec Mode) +IMPORTANT: Do NOT output exploration text, tool usage, or thinking before the spec. Start DIRECTLY with the specification format below. Silently analyze the codebase first, then output ONLY the structured specification. + Generate a specification with an actionable task breakdown. WAIT for approval before implementing. ### Specification Format 1. **Problem**: What problem are we solving? (user perspective) + 2. **Solution**: Brief approach (1-2 sentences) + 3. **Acceptance Criteria**: 3-5 items in GIVEN-WHEN-THEN format + - GIVEN [context], WHEN [action], THEN [outcome] + 4. **Files to Modify**: - | File | Changes | - |------|---------| - | path/to/file | Brief description | + | File | Purpose | Action | + |------|---------|--------| + | path/to/file | description | create/modify/delete | 5. **Implementation Tasks**: + Use this EXACT format for each task (the system will parse these): \`\`\`tasks - [ ] T001: [Description] | File: [path/to/file] - [ ] T002: [Description] | File: [path/to/file] - [ ] T003: [Description] | File: [path/to/file] \`\`\` + Task ID rules: + - Sequential: T001, T002, T003, etc. + - Description: Clear action (e.g., "Create user model", "Add API endpoint") + - File: Primary file affected (helps with context) + - Order by dependencies (foundational tasks first) + 6. **Verification**: How to confirm feature works -After generating the spec, output: +After generating the spec, output on its own line: "[SPEC_GENERATED] Please review the specification above. Reply with 'approved' to proceed or provide feedback for revisions." -DO NOT proceed with implementation until approval is received. +DO NOT proceed with implementation until you receive explicit approval. + +When approved, execute tasks SEQUENTIALLY in order. For each task: +1. BEFORE starting, output: "[TASK_START] T###: Description" +2. Implement the task +3. AFTER completing, output: "[TASK_COMPLETE] T###: Brief summary" + +This allows real-time progress tracking during implementation. `; -export const DEFAULT_AUTO_MODE_PLANNING_FULL = `## Software Design Document (Full Mode) +export const DEFAULT_AUTO_MODE_PLANNING_FULL = `## Full Specification Phase (Full SDD Mode) + +IMPORTANT: Do NOT output exploration text, tool usage, or thinking before the spec. Start DIRECTLY with the specification format below. Silently analyze the codebase first, then output ONLY the structured specification. Generate a comprehensive specification with phased task breakdown. WAIT for approval before implementing. -### SDD Format +### Specification Format -#### 1. Problem Statement -Brief description of the problem we're solving (user perspective) +1. **Problem Statement**: 2-3 sentences from user perspective -#### 2. User Story -AS A [user type] -I WANT TO [action] -SO THAT [benefit] +2. **User Story**: As a [user], I want [goal], so that [benefit] -#### 3. Acceptance Criteria -Multiple scenarios in GIVEN-WHEN-THEN format: -- **Scenario 1**: [Name] - - GIVEN [context] - - WHEN [action] - - THEN [expected outcome] +3. **Acceptance Criteria**: Multiple scenarios with GIVEN-WHEN-THEN + - **Happy Path**: GIVEN [context], WHEN [action], THEN [expected outcome] + - **Edge Cases**: GIVEN [edge condition], WHEN [action], THEN [handling] + - **Error Handling**: GIVEN [error condition], WHEN [action], THEN [error response] -#### 4. Technical Context -- **Existing Components**: What's already in place -- **Integration Points**: Where this feature connects -- **Constraints**: Technical or business limitations +4. **Technical Context**: + | Aspect | Value | + |--------|-------| + | Affected Files | list of files | + | Dependencies | external libs if any | + | Constraints | technical limitations | + | Patterns to Follow | existing patterns in codebase | -#### 5. Non-Goals -What this feature explicitly does NOT include (to prevent scope creep) +5. **Non-Goals**: What this feature explicitly does NOT include -#### 6. Implementation Plan +6. **Implementation Tasks**: + Use this EXACT format for each task (the system will parse these): + \`\`\`tasks + ## Phase 1: Foundation + - [ ] T001: [Description] | File: [path/to/file] + - [ ] T002: [Description] | File: [path/to/file] -**Phase 1: Foundation** -\`\`\`tasks -- [ ] T001: [Description] | File: [path] -- [ ] T002: [Description] | File: [path] -\`\`\` + ## Phase 2: Core Implementation + - [ ] T003: [Description] | File: [path/to/file] + - [ ] T004: [Description] | File: [path/to/file] -**Phase 2: Core Implementation** -\`\`\`tasks -- [ ] T003: [Description] | File: [path] -- [ ] T004: [Description] | File: [path] -\`\`\` + ## Phase 3: Integration & Testing + - [ ] T005: [Description] | File: [path/to/file] + - [ ] T006: [Description] | File: [path/to/file] + \`\`\` -**Phase 3: Integration & Testing** -\`\`\`tasks -- [ ] T005: [Description] | File: [path] -- [ ] T006: [Description] | File: [path] -\`\`\` + Task ID rules: + - Sequential across all phases: T001, T002, T003, etc. + - Description: Clear action verb + target + - File: Primary file affected + - Order by dependencies within each phase + - Phase structure helps organize complex work -#### 7. Success Metrics -How we'll measure if this feature is working correctly +7. **Success Metrics**: How we know it's done (measurable criteria) -#### 8. Risks & Mitigations -| Risk | Impact | Mitigation | -|------|--------|------------| -| [risk] | [H/M/L] | [approach] | +8. **Risks & Mitigations**: + | Risk | Mitigation | + |------|------------| + | description | approach | -After generating the SDD, output: -"[SPEC_GENERATED] Please review the specification above. Reply with 'approved' to proceed or provide feedback for revisions." +After generating the spec, output on its own line: +"[SPEC_GENERATED] Please review the comprehensive specification above. Reply with 'approved' to proceed or provide feedback for revisions." -DO NOT proceed with implementation until approval is received. +DO NOT proceed with implementation until you receive explicit approval. + +When approved, execute tasks SEQUENTIALLY by phase. For each task: +1. BEFORE starting, output: "[TASK_START] T###: Description" +2. Implement the task +3. AFTER completing, output: "[TASK_COMPLETE] T###: Brief summary" + +After completing all tasks in a phase, output: +"[PHASE_COMPLETE] Phase N complete" + +This allows real-time progress tracking during implementation. `; export const DEFAULT_AUTO_MODE_FEATURE_PROMPT_TEMPLATE = `## Feature Implementation Task