Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ export class OrchestrationStateManager {
};

const nodes = this.buildDependencyGraph();
const taskMap = new Map([...this.state.taskQueue, ...this.state.completedTasks].map((t) => [t.id, t]));

for (const [taskId, node] of nodes) {
if (node.status === 'complete') continue;
Expand All @@ -86,14 +85,14 @@ export class OrchestrationStateManager {

if (pendingDeps.length === 0) {
// Check if task has an assignee
const task = taskMap.get(taskId);
const task = this.state.taskQueue.find((t) => t.id === taskId);
if (task?.assignee) {
plan.ready.push(taskId);
}
} else {
// Check for failed dependencies
const failedDeps = pendingDeps.filter((depId) => {
const task = taskMap.get(depId);
const task = this.state.taskQueue.find((t) => t.id === depId);
return task?.status === 'cancelled';
});
Comment on lines +88 to 97
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "OrchestrationState.ts" -type f

Repository: agentic-dev-library/thumbcode

Length of output: 150


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && wc -l OrchestrationState.ts

Repository: agentic-dev-library/thumbcode

Length of output: 98


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && sed -n '75,110p' OrchestrationState.ts

Repository: agentic-dev-library/thumbcode

Length of output: 1085


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && sed -n '179,185p' OrchestrationState.ts

Repository: agentic-dev-library/thumbcode

Length of output: 273


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && git log --oneline --all -- OrchestrationState.ts | head -10

Repository: agentic-dev-library/thumbcode

Length of output: 185


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && git show 7abe8b2:OrchestrationState.ts | sed -n '75,110p'

Repository: agentic-dev-library/thumbcode

Length of output: 346


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && git show 7abe8b2:OrchestrationState.ts | sed -n '1,50p'

Repository: agentic-dev-library/thumbcode

Length of output: 346


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && git diff 7abe8b2^..7abe8b2 -- OrchestrationState.ts | head -100

Repository: agentic-dev-library/thumbcode

Length of output: 112


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && rg -n "completedTasks" -C2 OrchestrationState.ts

Repository: agentic-dev-library/thumbcode

Length of output: 837


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && rg -n "status.*cancelled" -C2 OrchestrationState.ts

Repository: agentic-dev-library/thumbcode

Length of output: 279


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && git show 7abe8b2:packages/agent-intelligence/src/services/orchestrator/OrchestrationState.ts | sed -n '80,110p'

Repository: agentic-dev-library/thumbcode

Length of output: 873


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && git show 7abe8b2~1:packages/agent-intelligence/src/services/orchestrator/OrchestrationState.ts | sed -n '1,50p'

Repository: agentic-dev-library/thumbcode

Length of output: 112


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && git log --all --oneline --follow -- OrchestrationState.ts | head -5

Repository: agentic-dev-library/thumbcode

Length of output: 185


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && git show 7abe8b2 | grep -A 20 "taskMap"

Repository: agentic-dev-library/thumbcode

Length of output: 1383


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && rg -n "push.*completedTasks\|completedTasks.*push\|status.*=.*cancel" -C2

Repository: agentic-dev-library/thumbcode

Length of output: 55


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && sed -n '100,160p' OrchestrationState.ts

Repository: agentic-dev-library/thumbcode

Length of output: 1698


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && rg -n "updateTaskStatus\|moveTask" -C3 OrchestrationState.ts

Repository: agentic-dev-library/thumbcode

Length of output: 55


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && rg -n "completedTasks" OrchestrationState.ts

Repository: agentic-dev-library/thumbcode

Length of output: 388


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && sed -n '1,80p' OrchestrationState.ts

Repository: agentic-dev-library/thumbcode

Length of output: 2001


🏁 Script executed:

find . -name "*.ts" -path "*/orchestrator/*" -type f | head -20

Repository: agentic-dev-library/thumbcode

Length of output: 678


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && grep -rn "completedTasks\." . --include="*.ts" | head -20

Repository: agentic-dev-library/thumbcode

Length of output: 455


🏁 Script executed:

cd packages/agent-intelligence/src/services && grep -rn "status.*=.*cancelled\|cancelled.*status" . --include="*.ts" -A2 -B2 | head -40

Repository: agentic-dev-library/thumbcode

Length of output: 1267


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && sed -n '130,150p' AgentCoordinator.ts

Repository: agentic-dev-library/thumbcode

Length of output: 813


🏁 Script executed:

cd packages/agent-intelligence/src/services/orchestrator && sed -n '130,150p' AgentCoordinator.ts

Repository: agentic-dev-library/thumbcode

Length of output: 813


Use getTask() helper to search both taskQueue and completedTasks when looking up task dependencies.

The refactoring removed the local taskMap optimization but incompletely—lines 88 and 95 now only search taskQueue, whereas the previous taskMap combined both taskQueue and completedTasks. This introduces a correctness bug.

Line 95 is critical: When a task is cancelled, its status is set to 'cancelled' (AgentCoordinator.ts:136) and then moved to completedTasks (line 143). However, line 95 only searches taskQueue, so cancelled dependencies in completedTasks are never found. The filter evaluates task?.status === 'cancelled' to false (since task is undefined), and the dependent task is incorrectly placed in waiting instead of blocked, causing incorrect execution plan behavior.

The solution is simple: both lines 88 and 95 should use the existing getTask() helper (lines 180–183), which already correctly searches both arrays. This maintains consistency and correctness.

Proposed fix
       if (pendingDeps.length === 0) {
-        const task = this.state.taskQueue.find((t) => t.id === taskId);
+        const task = this.getTask(taskId);
         if (task?.assignee) {
           plan.ready.push(taskId);
         }
       } else {
         const failedDeps = pendingDeps.filter((depId) => {
-          const task = this.state.taskQueue.find((t) => t.id === depId);
+          const task = this.getTask(depId);
           return task?.status === 'cancelled';
         });
🤖 Prompt for AI Agents
In `@packages/agent-intelligence/src/services/orchestrator/OrchestrationState.ts`
around lines 88 - 97, The logic that looks up tasks by id currently queries
this.state.taskQueue directly (affecting the block that pushes ids into
plan.ready and the failedDeps filter), but it should use the existing
getTask(taskId) helper so lookups include both taskQueue and completedTasks;
replace the direct finds at the two sites (the find used before pushing to
plan.ready and the find used inside pendingDeps.filter that checks status ===
'cancelled') with calls to getTask(taskId) to ensure cancelled dependencies
moved to completedTasks are detected.


Expand Down
11 changes: 7 additions & 4 deletions src/components/code/DiffViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Supports unified and split view modes.
*/

import { useState } from 'react';
import { useMemo, useState } from 'react';
import { ChevronDownIcon } from '@/components/icons';
import { Text } from '@/components/ui';
import { organicBorderRadius } from '@/lib/organic-styles';
Expand Down Expand Up @@ -95,9 +95,12 @@ export function DiffViewer({
}: Readonly<DiffViewerProps>) {
const [collapsed, setCollapsed] = useState(false);

const lines = diff || (oldContent && newContent ? parseDiff(oldContent, newContent) : []);
const additions = lines.filter((l) => l.type === 'add').length;
const deletions = lines.filter((l) => l.type === 'remove').length;
const lines = useMemo(
() => diff || (oldContent && newContent ? parseDiff(oldContent, newContent) : []),
[diff, oldContent, newContent]
);
const additions = useMemo(() => lines.filter((l) => l.type === 'add').length, [lines]);
const deletions = useMemo(() => lines.filter((l) => l.type === 'remove').length, [lines]);
Comment on lines +102 to +103

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To optimize further, you can calculate additions and deletions in a single pass over the lines array. This avoids iterating over the potentially large array twice and combines two useMemo calls into one.

  const [additions, deletions] = useMemo(() => {
    let adds = 0;
    let dels = 0;
    for (const line of lines) {
      if (line.type === 'add') {
        adds++;
      } else if (line.type === 'remove') {
        dels++;
      }
    }
    return [adds, dels];
  }, [lines]);


const getLineStyle = (type: DiffLine['type']) => {
switch (type) {
Expand Down
50 changes: 50 additions & 0 deletions src/components/code/__tests__/DiffViewer.perf.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { act, fireEvent, render, screen } from '@testing-library/react';
import { DiffViewer } from '../DiffViewer';

// Mock dependencies to isolate DiffViewer performance
vi.mock('@/components/icons', () => ({
ChevronDownIcon: () => <span>ChevronDown</span>,
}));

vi.mock('@/components/ui', () => ({
// biome-ignore lint/suspicious/noExplicitAny: mock
Text: ({ children, ...props }: any) => <span {...props}>{children}</span>,
}));

vi.mock('@/lib/organic-styles', () => ({
organicBorderRadius: { card: {} },
}));

describe('DiffViewer Performance', () => {
it('measures re-render time with large diffs', async () => {
// Generate large content (~5000 lines) to make parsing expensive
const linesCount = 5000;
const oldContent = Array.from({ length: linesCount }, (_, i) => `original line ${i}`).join(
'\n'
);
const newContent = Array.from({ length: linesCount }, (_, i) => `modified line ${i}`).join(
'\n'
);

// Initial Render
const startRender = performance.now();
render(<DiffViewer oldContent={oldContent} newContent={newContent} filename="benchmark.ts" />);
const endRender = performance.now();
console.log(`[Benchmark] Initial render time: ${(endRender - startRender).toFixed(2)}ms`);

const button = screen.getByRole('button');

// Trigger re-render by collapsing
const startUpdate = performance.now();
await act(async () => {
fireEvent.click(button);
});
const endUpdate = performance.now();

const updateTime = endUpdate - startUpdate;
console.log(`[Benchmark] Re-render time (collapse): ${updateTime.toFixed(2)}ms`);

// Sanity check
expect(updateTime).toBeGreaterThan(0);
Comment on lines 44 to 48

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This performance test is a great addition! However, it currently relies on console.log and a very weak assertion to measure performance, which doesn't automatically verify the optimization or prevent regressions.

To make this test more robust, I recommend verifying the behavior of the optimization directly: ensure the expensive parseDiff function is not called on re-render.

This would involve:

  1. Extracting parseDiff into its own mockable module (e.g., src/lib/diff-parser.ts).
  2. Mocking that module in this test file.
  3. Asserting that parseDiff is called once on initial render, and then not called again after the re-render is triggered.

This change would make the test a true regression guard for the memoization logic.

}, 10000);
});
Loading