diff --git a/src/backends/progressModel.ts b/src/backends/progressModel.ts index 5fa7ed33..ab189d74 100644 --- a/src/backends/progressModel.ts +++ b/src/backends/progressModel.ts @@ -22,7 +22,7 @@ export interface ProgressContext { completedTasks?: { subject: string; summary: string; timestamp: number }[]; } -const PROGRESS_SYSTEM_PROMPT = `You are a progress reporter for an AI coding agent called CASCADE. Write a brief, informative progress update based on the agent's current state. Be concise (3-5 sentences max). Focus on what has been accomplished, what's currently in progress, and what remains. Synthesize the agent's own commentary, tool call details (file paths, commands), and completed task summaries into a coherent narrative โ€” do not just list tool names. Use markdown formatting. Write in first person (e.g. "I'm implementing...", "I've completed...", "I'm currently working on..."). Start with a bold header using the exact header provided in the user prompt context (e.g. "**๐Ÿง‘โ€๐Ÿ’ป Implementation Update** (X min)"). Do not include a progress bar โ€” the system adds that separately.`; +const PROGRESS_SYSTEM_PROMPT = `You are a progress reporter for an AI coding agent called CASCADE. Write a brief, informative progress update based on the agent's current state. Be concise (3-5 sentences max). Focus on what has been accomplished, what's currently in progress, and what remains. Synthesize the agent's own commentary, tool call details (file paths, commands), and completed task summaries into a coherent narrative โ€” do not just list tool names. Use markdown formatting. Write in first person (e.g. "I'm implementing...", "I've completed...", "I'm currently working on..."). Start with a bold header using the exact header provided in the user prompt context (e.g. "**๐Ÿง‘โ€๐Ÿ’ป Implementation Update** (X min)").`; function formatProgressUserPrompt(context: ProgressContext): string { const { diff --git a/src/backends/progressMonitor.ts b/src/backends/progressMonitor.ts index 32928df8..25336fb3 100644 --- a/src/backends/progressMonitor.ts +++ b/src/backends/progressMonitor.ts @@ -196,11 +196,7 @@ export class ProgressMonitor implements ProgressReporter { captureException(err instanceof Error ? err : new Error(String(err)), { tags: { source: 'progress_model', agentType: this.config.agentType }, }); - summary = formatStatusMessage( - progressContext.iteration, - progressContext.maxIterations, - this.config.agentType, - ); + summary = formatStatusMessage(this.config.agentType); } // Post to PM provider (Trello/JIRA) @@ -217,12 +213,7 @@ export class ProgressMonitor implements ProgressReporter { // Post to GitHub if (this.githubPoster) { try { - await this.githubPoster.update( - summary, - progressContext.iteration, - progressContext.maxIterations, - this.config.agentType, - ); + await this.githubPoster.update(summary, this.config.agentType); } catch (err) { this.config.logWriter('WARN', 'Failed to update GitHub PR comment', { error: String(err), diff --git a/src/backends/progressState/githubPoster.ts b/src/backends/progressState/githubPoster.ts index 52a26be9..638b3d1a 100644 --- a/src/backends/progressState/githubPoster.ts +++ b/src/backends/progressState/githubPoster.ts @@ -21,21 +21,11 @@ export interface GitHubProgressPosterConfig { export class GitHubProgressPoster { constructor(private readonly config: GitHubProgressPosterConfig) {} - async update( - summary: string, - iteration: number, - maxIterations: number, - agentType: string, - ): Promise { + async update(summary: string, agentType: string): Promise { const { initialCommentId } = getSessionState(); if (!initialCommentId) return; - const body = formatGitHubProgressComment( - this.config.headerMessage, - iteration, - maxIterations, - agentType, - ); + const body = formatGitHubProgressComment(this.config.headerMessage, agentType); // Replace the todo section with the AI-generated summary const bodyWithSummary = body.replace(/\n\n๐Ÿ“‹[\s\S]*?\n\n/, `\n\n${summary}\n\n`); await githubClient.updatePRComment( diff --git a/src/config/statusUpdateConfig.ts b/src/config/statusUpdateConfig.ts index edbaaaa9..ba6eb2e1 100644 --- a/src/config/statusUpdateConfig.ts +++ b/src/config/statusUpdateConfig.ts @@ -52,19 +52,10 @@ export function getStatusUpdateConfig(agentType: string): StatusUpdateConfig { /** * Format a status update message for posting to Trello. * - * @param iteration - Current iteration number - * @param maxIterations - Maximum allowed iterations * @param agentType - Type of agent posting the update * @returns Formatted markdown message */ -export function formatStatusMessage( - iteration: number, - maxIterations: number, - agentType: string, -): string { - const progress = Math.round((iteration / maxIterations) * 100); - const progressBar = createProgressBar(progress); - +export function formatStatusMessage(agentType: string): string { // Get current todo status const todos = loadTodos(); const inProgressTodo = todos.find((t) => t.status === 'in_progress'); @@ -73,11 +64,7 @@ export function formatStatusMessage( const { emoji, label } = getAgentLabel(agentType); - const lines = [ - `**${emoji} ${label}** (${agentType})`, - '', - `${progressBar} ${progress}% (iteration ${iteration}/${maxIterations})`, - ]; + const lines = [`**${emoji} ${label}** (${agentType})`]; if (totalCount > 0) { lines.push('', `**Tasks:** ${doneCount}/${totalCount} complete`); @@ -92,46 +79,17 @@ export function formatStatusMessage( /** * Format a GitHub progress comment that updates the initial PR comment. * - * Renders a progress bar, todo list, and metadata footer. + * Renders a todo list and metadata footer. * * @param headerMessage - Original comment text preserved as header (e.g., "๐Ÿ” Reviewing PR...") - * @param iteration - Current iteration number - * @param maxIterations - Maximum allowed iterations * @param agentType - Type of agent posting the update * @returns Formatted markdown comment body */ -export function formatGitHubProgressComment( - headerMessage: string, - iteration: number, - maxIterations: number, - agentType: string, -): string { - const progress = Math.round((iteration / maxIterations) * 100); - const progressBar = createProgressBar(progress); - +export function formatGitHubProgressComment(headerMessage: string, agentType: string): string { const todos = loadTodos(); const todoSection = formatTodoList(todos); - const lines = [ - headerMessage, - '', - '---', - '', - `**Progress:** ${progressBar} ${progress}% (iteration ${iteration}/${maxIterations})`, - '', - todoSection, - '', - `Last updated: iteration ${iteration} ยท ${agentType}`, - ]; + const lines = [headerMessage, '', '---', '', todoSection, '', `${agentType}`]; return lines.join('\n'); } - -/** - * Create a text-based progress bar. - */ -function createProgressBar(percent: number): string { - const filled = Math.round(percent / 10); - const empty = 10 - filled; - return `[${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}]`; -} diff --git a/tests/unit/backends/githubPoster.test.ts b/tests/unit/backends/githubPoster.test.ts index c25f2200..77f5334f 100644 --- a/tests/unit/backends/githubPoster.test.ts +++ b/tests/unit/backends/githubPoster.test.ts @@ -44,7 +44,7 @@ describe('GitHubProgressPoster โ€” update()', () => { }); const poster = makePoster(); - await poster.update('summary', 3, 20, 'implementation'); + await poster.update('summary', 'implementation'); expect(mockGithubClient.updatePRComment).not.toHaveBeenCalled(); }); @@ -62,12 +62,10 @@ describe('GitHubProgressPoster โ€” update()', () => { mockGithubClient.updatePRComment.mockResolvedValue(undefined as never); const poster = makePoster(); - await poster.update('AI-generated summary', 5, 20, 'implementation'); + await poster.update('AI-generated summary', 'implementation'); expect(mockFormatGitHubProgressComment).toHaveBeenCalledWith( '**๐Ÿง‘โ€๐Ÿ’ป Implementation Update**', - 5, - 20, 'implementation', ); expect(mockGithubClient.updatePRComment).toHaveBeenCalledWith( @@ -94,7 +92,7 @@ describe('GitHubProgressPoster โ€” update()', () => { mockGithubClient.updatePRComment.mockResolvedValue(undefined as never); const poster = makePoster(); - await poster.update('My AI summary', 2, 10, 'review'); + await poster.update('My AI summary', 'review'); const callArg = mockGithubClient.updatePRComment.mock.calls[0][3]; expect(callArg).toContain('My AI summary'); @@ -120,7 +118,7 @@ describe('GitHubProgressPoster โ€” update()', () => { headerMessage: 'Header', logWriter, }); - await poster.update('summary', 1, 5, 'review'); + await poster.update('summary', 'review'); expect(logWriter).toHaveBeenCalledWith( 'INFO', diff --git a/tests/unit/config/statusUpdateConfig.test.ts b/tests/unit/config/statusUpdateConfig.test.ts index 0845ecf9..1418fe00 100644 --- a/tests/unit/config/statusUpdateConfig.test.ts +++ b/tests/unit/config/statusUpdateConfig.test.ts @@ -104,50 +104,23 @@ describe('config/statusUpdateConfig', () => { }); describe('formatStatusMessage', () => { - it('includes agent-specific emoji/label and progress bar', () => { + it('includes agent-specific emoji/label', () => { vi.mocked(loadTodos).mockReturnValue([]); - const message = formatStatusMessage(5, 20, 'implementation'); + const message = formatStatusMessage('implementation'); expect(message).toContain('**๐Ÿง‘โ€๐Ÿ’ป Implementation Update**'); expect(message).toContain('implementation'); - expect(message).toContain('25%'); // (5/20) * 100 - expect(message).toContain('iteration 5/20'); }); - it('renders progress bar correctly at 0%', () => { + it('does not include progress bar or iteration counters', () => { vi.mocked(loadTodos).mockReturnValue([]); - const message = formatStatusMessage(0, 20, 'planning'); + const message = formatStatusMessage('implementation'); - expect(message).toContain('[โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘]'); - expect(message).toContain('0%'); - }); - - it('renders progress bar correctly at 50%', () => { - vi.mocked(loadTodos).mockReturnValue([]); - - const message = formatStatusMessage(10, 20, 'implementation'); - - expect(message).toContain('[โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘โ–‘]'); - expect(message).toContain('50%'); - }); - - it('renders progress bar correctly at 100%', () => { - vi.mocked(loadTodos).mockReturnValue([]); - - const message = formatStatusMessage(20, 20, 'implementation'); - - expect(message).toContain('[โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ]'); - expect(message).toContain('100%'); - }); - - it('rounds progress percentage', () => { - vi.mocked(loadTodos).mockReturnValue([]); - - const message = formatStatusMessage(7, 20, 'planning'); - - expect(message).toContain('35%'); // 7/20 = 0.35 -> 35% + expect(message).not.toMatch(/\[โ–ˆ/); + expect(message).not.toMatch(/iteration \d+\/\d+/); + expect(message).not.toMatch(/\d+%/); }); it('includes task counts when todos exist', () => { @@ -157,7 +130,7 @@ describe('config/statusUpdateConfig', () => { { id: '3', content: 'Task 3', status: 'pending' }, ]); - const message = formatStatusMessage(10, 20, 'implementation'); + const message = formatStatusMessage('implementation'); expect(message).toContain('**Tasks:** 2/3 complete'); }); @@ -168,7 +141,7 @@ describe('config/statusUpdateConfig', () => { { id: '2', content: 'Fix linting', status: 'pending' }, ]); - const message = formatStatusMessage(5, 20, 'implementation'); + const message = formatStatusMessage('implementation'); expect(message).toContain('**Working on:** Write tests'); }); @@ -176,7 +149,7 @@ describe('config/statusUpdateConfig', () => { it('does not include task section when no todos', () => { vi.mocked(loadTodos).mockReturnValue([]); - const message = formatStatusMessage(5, 20, 'implementation'); + const message = formatStatusMessage('implementation'); expect(message).not.toContain('**Tasks:**'); expect(message).not.toContain('**Working on:**'); @@ -188,7 +161,7 @@ describe('config/statusUpdateConfig', () => { { id: '2', content: 'Task 2', status: 'pending' }, ]); - const message = formatStatusMessage(8, 20, 'planning'); + const message = formatStatusMessage('planning'); expect(message).toContain('**Tasks:**'); expect(message).not.toContain('**Working on:**'); @@ -197,57 +170,59 @@ describe('config/statusUpdateConfig', () => { it('formats message with proper markdown structure', () => { vi.mocked(loadTodos).mockReturnValue([]); - const message = formatStatusMessage(10, 20, 'implementation'); + const message = formatStatusMessage('implementation'); const lines = message.split('\n'); expect(lines[0]).toBe('**๐Ÿง‘โ€๐Ÿ’ป Implementation Update** (implementation)'); - expect(lines[1]).toBe(''); - expect(lines[2]).toContain('[โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘โ–‘]'); }); }); describe('formatGitHubProgressComment', () => { - it('includes header message and progress bar', () => { + it('includes header message', () => { vi.mocked(loadTodos).mockReturnValue([]); vi.mocked(formatTodoList).mockReturnValue('- [ ] Task 1'); - const comment = formatGitHubProgressComment('๐Ÿ” Reviewing PR...', 8, 20, 'review'); + const comment = formatGitHubProgressComment('๐Ÿ” Reviewing PR...', 'review'); expect(comment).toContain('๐Ÿ” Reviewing PR...'); - expect(comment).toContain('**Progress:**'); - expect(comment).toContain('40%'); // (8/20) * 100 - expect(comment).toContain('iteration 8/20'); + }); + + it('does not include progress bar or iteration counters', () => { + vi.mocked(loadTodos).mockReturnValue([]); + vi.mocked(formatTodoList).mockReturnValue(''); + + const comment = formatGitHubProgressComment('Header', 'review'); + + expect(comment).not.toMatch(/\[โ–ˆ/); + expect(comment).not.toMatch(/iteration \d+/); + expect(comment).not.toMatch(/\d+%/); + expect(comment).not.toContain('**Progress:**'); }); it('includes formatted todo list', () => { vi.mocked(loadTodos).mockReturnValue([{ id: '1', content: 'Task 1', status: 'pending' }]); vi.mocked(formatTodoList).mockReturnValue('- [ ] Task 1\n- [x] Task 2'); - const comment = formatGitHubProgressComment('๐Ÿ” Reviewing PR...', 5, 20, 'review'); + const comment = formatGitHubProgressComment('๐Ÿ” Reviewing PR...', 'review'); expect(comment).toContain('- [ ] Task 1'); expect(comment).toContain('- [x] Task 2'); }); - it('includes metadata footer with iteration and agent type', () => { + it('includes metadata footer with agent type', () => { vi.mocked(loadTodos).mockReturnValue([]); vi.mocked(formatTodoList).mockReturnValue(''); - const comment = formatGitHubProgressComment( - '๐Ÿš€ Implementing feature...', - 12, - 25, - 'implementation', - ); + const comment = formatGitHubProgressComment('๐Ÿš€ Implementing feature...', 'implementation'); - expect(comment).toContain('Last updated: iteration 12 ยท implementation'); + expect(comment).toContain('implementation'); }); it('separates sections with horizontal rule', () => { vi.mocked(loadTodos).mockReturnValue([]); vi.mocked(formatTodoList).mockReturnValue(''); - const comment = formatGitHubProgressComment('Header text', 5, 20, 'review'); + const comment = formatGitHubProgressComment('Header text', 'review'); const lines = comment.split('\n'); expect(lines).toContain('---'); @@ -258,7 +233,7 @@ describe('config/statusUpdateConfig', () => { vi.mocked(formatTodoList).mockReturnValue(''); const headerWithMarkdown = '๐Ÿ” **Reviewing PR** #123\n\nThis is a test.'; - const comment = formatGitHubProgressComment(headerWithMarkdown, 5, 20, 'review'); + const comment = formatGitHubProgressComment(headerWithMarkdown, 'review'); expect(comment.startsWith(headerWithMarkdown)).toBe(true); }); @@ -271,55 +246,11 @@ describe('config/statusUpdateConfig', () => { vi.mocked(loadTodos).mockReturnValue(todos); vi.mocked(formatTodoList).mockReturnValue('formatted todos'); - const comment = formatGitHubProgressComment('Header', 10, 20, 'implementation'); + const comment = formatGitHubProgressComment('Header', 'implementation'); expect(loadTodos).toHaveBeenCalled(); expect(formatTodoList).toHaveBeenCalledWith(todos); expect(comment).toContain('formatted todos'); }); - - it('renders progress bar at different percentages', () => { - vi.mocked(loadTodos).mockReturnValue([]); - vi.mocked(formatTodoList).mockReturnValue(''); - - const comment25 = formatGitHubProgressComment('Header', 5, 20, 'review'); - expect(comment25).toContain('[โ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘]'); // 25% -> rounds to 3 blocks - - const comment75 = formatGitHubProgressComment('Header', 15, 20, 'review'); - expect(comment75).toContain('[โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘]'); // 75% -> rounds to 8 blocks - }); - }); - - describe('progress bar rendering', () => { - it('progress bar has exactly 10 blocks', () => { - vi.mocked(loadTodos).mockReturnValue([]); - - const message = formatStatusMessage(7, 20, 'implementation'); - - const progressBarMatch = message.match(/\[([โ–ˆโ–‘]+)\]/); - expect(progressBarMatch).toBeTruthy(); - expect(progressBarMatch?.[1].length).toBe(10); - }); - - it('progress bar uses filled and empty blocks', () => { - vi.mocked(loadTodos).mockReturnValue([]); - - const message = formatStatusMessage(6, 20, 'planning'); // 30% - - // 30% -> 3 filled, 7 empty - expect(message).toContain('[โ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘]'); - }); - - it('handles edge case percentages', () => { - vi.mocked(loadTodos).mockReturnValue([]); - - // 1/20 = 5% -> 0.5 rounds to 1 - const message1 = formatStatusMessage(1, 20, 'planning'); - expect(message1).toContain('[โ–ˆโ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘]'); - - // 19/20 = 95% -> 9.5 rounds to 10 - const message19 = formatStatusMessage(19, 20, 'planning'); - expect(message19).toContain('[โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ]'); - }); }); });