| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { describe, it, expect } from 'vitest'; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| function parsePhaseSummaries(summary: string | undefined): Map<string, string> { |
| const phaseSummaries = new Map<string, string>(); |
|
|
| if (!summary || !summary.trim()) { |
| return phaseSummaries; |
| } |
|
|
| |
| const sections = summary.split(/\n\n---\n\n/); |
|
|
| for (const section of sections) { |
| |
| const headerMatch = section.match(/^###\s+(.+?)(?:\n|$)/); |
| if (headerMatch) { |
| const phaseName = headerMatch[1].trim().toLowerCase(); |
| |
| const content = section.substring(headerMatch[0].length).trim(); |
| phaseSummaries.set(phaseName, content); |
| } |
| } |
|
|
| return phaseSummaries; |
| } |
|
|
| |
| |
| |
| function extractPhaseSummary(summary: string | undefined, phaseName: string): string | null { |
| const phaseSummaries = parsePhaseSummaries(summary); |
| const normalizedPhaseName = phaseName.toLowerCase(); |
| return phaseSummaries.get(normalizedPhaseName) || null; |
| } |
|
|
| |
| |
| |
| function extractImplementationSummary(summary: string | undefined): string | null { |
| if (!summary || !summary.trim()) { |
| return null; |
| } |
|
|
| const phaseSummaries = parsePhaseSummaries(summary); |
|
|
| |
| const implementationContent = phaseSummaries.get('implementation'); |
| if (implementationContent) { |
| return implementationContent; |
| } |
|
|
| |
| for (const [phaseName, content] of phaseSummaries) { |
| if (phaseName.includes('implement')) { |
| return content; |
| } |
| } |
|
|
| |
| |
| |
| if (!summary.includes('### ') && !summary.includes('\n---\n')) { |
| return summary; |
| } |
|
|
| return null; |
| } |
|
|
| |
| |
| |
| function isAccumulatedSummary(summary: string | undefined): boolean { |
| if (!summary || !summary.trim()) { |
| return false; |
| } |
|
|
| |
| const hasMultiplePhases = |
| summary.includes('\n\n---\n\n') && summary.match(/###\s+.+/g)?.length > 0; |
|
|
| return hasMultiplePhases; |
| } |
|
|
| describe('phase summary parser', () => { |
| describe('parsePhaseSummaries', () => { |
| it('should parse single phase summary', () => { |
| const summary = `### Implementation |
| |
| Created auth module with login functionality.`; |
|
|
| const result = parsePhaseSummaries(summary); |
|
|
| expect(result.size).toBe(1); |
| expect(result.get('implementation')).toBe('Created auth module with login functionality.'); |
| }); |
|
|
| it('should parse multiple phase summaries', () => { |
| const summary = `### Implementation |
| |
| Created auth module. |
| |
| --- |
| |
| ### Testing |
| |
| All tests pass. |
| |
| --- |
| |
| ### Code Review |
| |
| Approved with minor suggestions.`; |
|
|
| const result = parsePhaseSummaries(summary); |
|
|
| expect(result.size).toBe(3); |
| expect(result.get('implementation')).toBe('Created auth module.'); |
| expect(result.get('testing')).toBe('All tests pass.'); |
| expect(result.get('code review')).toBe('Approved with minor suggestions.'); |
| }); |
|
|
| it('should handle empty input', () => { |
| expect(parsePhaseSummaries('').size).toBe(0); |
| expect(parsePhaseSummaries(undefined).size).toBe(0); |
| expect(parsePhaseSummaries(' \n\n ').size).toBe(0); |
| }); |
|
|
| it('should handle phase names with spaces', () => { |
| const summary = `### Code Review |
| |
| Review findings here.`; |
|
|
| const result = parsePhaseSummaries(summary); |
| expect(result.get('code review')).toBe('Review findings here.'); |
| }); |
|
|
| it('should normalize phase names to lowercase', () => { |
| const summary = `### IMPLEMENTATION |
| |
| Content here.`; |
|
|
| const result = parsePhaseSummaries(summary); |
| expect(result.get('implementation')).toBe('Content here.'); |
| expect(result.get('IMPLEMENTATION')).toBeUndefined(); |
| }); |
|
|
| it('should handle content with markdown', () => { |
| const summary = `### Implementation |
| |
| ## Changes Made |
| - Fixed bug in parser.ts |
| - Added error handling |
| |
| \`\`\`typescript |
| const x = 1; |
| \`\`\``; |
|
|
| const result = parsePhaseSummaries(summary); |
| expect(result.get('implementation')).toContain('## Changes Made'); |
| expect(result.get('implementation')).toContain('```typescript'); |
| }); |
|
|
| it('should return empty map for non-accumulated format', () => { |
| |
| const summary = `## Summary |
| |
| This is a simple summary without phase headers.`; |
|
|
| const result = parsePhaseSummaries(summary); |
| expect(result.size).toBe(0); |
| }); |
| }); |
|
|
| describe('extractPhaseSummary', () => { |
| it('should extract specific phase by name (case-insensitive)', () => { |
| const summary = `### Implementation |
| |
| Implementation content. |
| |
| --- |
| |
| ### Testing |
| |
| Testing content.`; |
|
|
| expect(extractPhaseSummary(summary, 'implementation')).toBe('Implementation content.'); |
| expect(extractPhaseSummary(summary, 'IMPLEMENTATION')).toBe('Implementation content.'); |
| expect(extractPhaseSummary(summary, 'Implementation')).toBe('Implementation content.'); |
| expect(extractPhaseSummary(summary, 'testing')).toBe('Testing content.'); |
| }); |
|
|
| it('should return null for non-existent phase', () => { |
| const summary = `### Implementation |
| |
| Content here.`; |
|
|
| expect(extractPhaseSummary(summary, 'code review')).toBeNull(); |
| }); |
|
|
| it('should return null for empty input', () => { |
| expect(extractPhaseSummary('', 'implementation')).toBeNull(); |
| expect(extractPhaseSummary(undefined, 'implementation')).toBeNull(); |
| }); |
| }); |
|
|
| describe('extractImplementationSummary', () => { |
| it('should extract implementation phase from accumulated summary', () => { |
| const summary = `### Implementation |
| |
| Created auth module. |
| |
| --- |
| |
| ### Testing |
| |
| All tests pass. |
| |
| --- |
| |
| ### Code Review |
| |
| Approved.`; |
|
|
| const result = extractImplementationSummary(summary); |
| expect(result).toBe('Created auth module.'); |
| expect(result).not.toContain('Testing'); |
| expect(result).not.toContain('Code Review'); |
| }); |
|
|
| it('should return implementation phase even when not first', () => { |
| const summary = `### Planning |
| |
| Plan created. |
| |
| --- |
| |
| ### Implementation |
| |
| Implemented the feature. |
| |
| --- |
| |
| ### Review |
| |
| Reviewed.`; |
|
|
| const result = extractImplementationSummary(summary); |
| expect(result).toBe('Implemented the feature.'); |
| }); |
|
|
| it('should handle phase with "implementation" in name', () => { |
| const summary = `### Feature Implementation |
| |
| Built the feature.`; |
|
|
| const result = extractImplementationSummary(summary); |
| expect(result).toBe('Built the feature.'); |
| }); |
|
|
| it('should return full summary for non-accumulated format (legacy)', () => { |
| |
| const summary = `## Changes |
| - Fixed bug |
| - Added tests`; |
|
|
| const result = extractImplementationSummary(summary); |
| expect(result).toBe(summary); |
| }); |
|
|
| it('should return null for empty input', () => { |
| expect(extractImplementationSummary('')).toBeNull(); |
| expect(extractImplementationSummary(undefined)).toBeNull(); |
| expect(extractImplementationSummary(' \n\n ')).toBeNull(); |
| }); |
|
|
| it('should return null when no implementation phase in accumulated summary', () => { |
| const summary = `### Testing |
| |
| Tests written. |
| |
| --- |
| |
| ### Code Review |
| |
| Approved.`; |
|
|
| const result = extractImplementationSummary(summary); |
| expect(result).toBeNull(); |
| }); |
| }); |
|
|
| describe('isAccumulatedSummary', () => { |
| it('should return true for accumulated multi-phase summary', () => { |
| const summary = `### Implementation |
| |
| Content. |
| |
| --- |
| |
| ### Testing |
| |
| Content.`; |
|
|
| expect(isAccumulatedSummary(summary)).toBe(true); |
| }); |
|
|
| it('should return false for single phase summary (no separator)', () => { |
| const summary = `### Implementation |
| |
| Content.`; |
|
|
| expect(isAccumulatedSummary(summary)).toBe(false); |
| }); |
|
|
| it('should return false for legacy non-accumulated format', () => { |
| const summary = `## Summary |
| |
| This is a simple summary.`; |
|
|
| expect(isAccumulatedSummary(summary)).toBe(false); |
| }); |
|
|
| it('should return false for empty input', () => { |
| expect(isAccumulatedSummary('')).toBe(false); |
| expect(isAccumulatedSummary(undefined)).toBe(false); |
| expect(isAccumulatedSummary(' \n\n ')).toBe(false); |
| }); |
|
|
| it('should return true even for two phases', () => { |
| const summary = `### Implementation |
| |
| Content A. |
| |
| --- |
| |
| ### Code Review |
| |
| Content B.`; |
|
|
| expect(isAccumulatedSummary(summary)).toBe(true); |
| }); |
| }); |
|
|
| describe('acceptance criteria scenarios', () => { |
| it('AC1: Implementation summary preserved when Testing completes', () => { |
| |
| |
| |
| const summary = `### Implementation |
| |
| - Created auth module |
| - Added user service |
| |
| --- |
| |
| ### Testing |
| |
| - 42 tests pass |
| - 98% coverage`; |
|
|
| const impl = extractImplementationSummary(summary); |
| const testing = extractPhaseSummary(summary, 'testing'); |
|
|
| expect(impl).toBe('- Created auth module\n- Added user service'); |
| expect(testing).toBe('- 42 tests pass\n- 98% coverage'); |
| expect(impl).not.toContain('Testing'); |
| expect(testing).not.toContain('auth module'); |
| }); |
|
|
| it('AC4: Implementation Summary tab shows only implementation phase', () => { |
| |
| |
| |
| |
| const summary = `### Implementation |
| |
| Implementation phase output here. |
| |
| --- |
| |
| ### Testing |
| |
| Testing phase output here. |
| |
| --- |
| |
| ### Code Review |
| |
| Code review output here.`; |
|
|
| const impl = extractImplementationSummary(summary); |
|
|
| expect(impl).toBe('Implementation phase output here.'); |
| expect(impl).not.toContain('Testing'); |
| expect(impl).not.toContain('Code Review'); |
| }); |
|
|
| it('AC5: Empty state when implementation not started', () => { |
| |
| const summary = `### Planning |
| |
| Planning phase complete.`; |
|
|
| const impl = extractImplementationSummary(summary); |
|
|
| |
| expect(impl).toBeNull(); |
| }); |
|
|
| it('AC6: Single phase summary displayed correctly', () => { |
| |
| const summary = `### Implementation |
| |
| Only implementation was done.`; |
|
|
| const impl = extractImplementationSummary(summary); |
|
|
| expect(impl).toBe('Only implementation was done.'); |
| }); |
|
|
| it('AC9: Mid-progress shows only completed phases', () => { |
| |
| |
| const summary = `### Implementation |
| |
| Implementation done. |
| |
| --- |
| |
| ### Testing |
| |
| Testing done.`; |
|
|
| const phases = parsePhaseSummaries(summary); |
|
|
| expect(phases.size).toBe(2); |
| expect(phases.has('implementation')).toBe(true); |
| expect(phases.has('testing')).toBe(true); |
| expect(phases.has('code review')).toBe(false); |
| }); |
|
|
| it('AC10: All phases in chronological order', () => { |
| |
| const summary = `### Implementation |
| |
| First phase content. |
| |
| --- |
| |
| ### Testing |
| |
| Second phase content. |
| |
| --- |
| |
| ### Code Review |
| |
| Third phase content.`; |
|
|
| |
| const phases = parsePhaseSummaries(summary); |
| const phaseNames = [...phases.keys()]; |
|
|
| expect(phaseNames).toEqual(['implementation', 'testing', 'code review']); |
| }); |
|
|
| it('AC17: Retried phase shows only latest', () => { |
| |
| |
| |
| |
| |
| |
| const summary = `### Implementation |
| |
| First attempt content. |
| |
| --- |
| |
| ### Testing |
| |
| First test run. |
| |
| --- |
| |
| ### Implementation |
| |
| Retry content - fixed issues. |
| |
| --- |
| |
| ### Testing |
| |
| Retry - all tests now pass.`; |
|
|
| const phases = parsePhaseSummaries(summary); |
|
|
| |
| expect(phases.get('implementation')).toBe('Retry content - fixed issues.'); |
| expect(phases.get('testing')).toBe('Retry - all tests now pass.'); |
| }); |
| }); |
| }); |
|
|