Skip to content

feat(TaskRegistry): introduce TaskRegistry and migrate clineStack#1014

Open
edelauna wants to merge 1 commit into
mainfrom
issue/367
Open

feat(TaskRegistry): introduce TaskRegistry and migrate clineStack#1014
edelauna wants to merge 1 commit into
mainfrom
issue/367

Conversation

@edelauna

@edelauna edelauna commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Related GitHub Issue

Closes: #367

Description

Replaces the clineStack: Task[] array in ClineProvider with a TaskRegistry adapter (Expand-Contract migration, Story 3.2a of Epic 3 / Issue #367).

Key implementation details:

  • TaskRegistry wraps a Map<string, Task> (O(1) lookup) and a parallel string[] stack (insertion order). Invariant: tasks.size === stack.length at all times.
  • All 23 clineStack call sites in ClineProvider.ts are migrated; external behaviour is identical at maxConcurrency=1.
  • removeClineFromStack() now removes taskRegistry.current by ID rather than blindly popping the top, fixing a latent bug where focus and top-of-stack could diverge.
  • Rehydration (createTaskWithHistoryItem) uses a new replace() method that swaps the task object in-place, preserving both stack index and focus pointer.
  • New concurrent-access helpers (getRunning(), hasRunning(), setCurrent()) are ready for Epic 3's later stories (TaskScheduler, showTaskWithId split).

Reviewers should pay attention to:

  • TaskRegistry.replace() — used only in the rehydration path; guards against unknown and duplicate IDs.
  • pop() — only resets _currentTaskId when the popped entry was the current focus (not always the top).

Test Procedure

  • 25+ unit tests covering all TaskRegistry operations and the dual-write invariant: src/core/task/__tests__/TaskRegistry.spec.ts
  • Existing delegation and provider specs updated to use the registry: removeClineFromStack-delegation.spec.ts, single-open-invariant.spec.ts, ClineProvider.spec.ts, ClineProvider.flicker-free-cancel.spec.ts
  • A new test covers the regression case: "removes the focused task even when it is not the top stack entry"
  • Full suite: pnpm exec vitest run from src/ — 7006 tests passing
  • Manual smoke test: run the extension locally and issue a subtask prompt; verify parent suspends, child runs, parent restores (task IDs visible in extension host logs)

Pre-Submission Checklist

  • Issue Linked: This PR is linked to an approved GitHub Issue (see "Related GitHub Issue" above).
  • Scope: My changes are focused on the linked issue (one major feature/fix per PR).
  • Self-Review: I have performed a thorough self-review of my code.
  • Testing: New and/or updated tests have been added to cover my changes (if applicable).
  • Documentation Impact: I have considered if my changes require documentation updates (see "Documentation Updates" section below).
  • Contribution Guidelines: I have read and agree to the Contributor Guidelines.

Documentation Updates

  • No documentation updates are required.

Additional Notes

This is a pure adapter migration — no behaviour change at current concurrency settings. The registry surface area (getRunning, hasRunning, setCurrent) is used by follow-on stories in Epic 3 but is otherwise dormant in this PR.

Summary by CodeRabbit

  • Improvements

    • Improved task lifecycle management, including reliable task focus, ordering, removal, replacement, and cleanup.
    • Enhanced task restoration and cancellation behavior to preserve the active task correctly.
    • Improved handling of running, aborted, and abandoned tasks for more consistent state tracking.
  • Bug Fixes

    • Fixed edge cases when removing focused or delegated tasks.
    • Improved behavior when no active task is available.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Introduces TaskRegistry, migrates ClineProvider task lifecycle state from clineStack, and updates provider helpers and tests to use registry-based task identity, ordering, focus, lookup, removal, and replacement.

Changes

TaskRegistry migration

Layer / File(s) Summary
TaskRegistry contract and invariants
src/core/task/TaskRegistry.ts, src/core/task/__tests__/TaskRegistry.spec.ts
Adds map-backed task lookup, LIFO ordering, focus management, lifecycle filtering, replacement, removal, and invariant tests.
ClineProvider registry integration
src/core/webview/ClineProvider.ts
Migrates task insertion, removal, rehydration, lookup, eviction, diagnostics, and clearing to TaskRegistry.
Fixture and lifecycle test migration
src/__tests__/helpers/provider-stub.ts, src/__tests__/*.spec.ts, src/core/webview/__tests__/*.spec.ts
Updates stubs and lifecycle tests to seed and assert registry state instead of clineStack state.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

  • Issue 358: Covers the broader Story 3.2a TaskRegistry introduction and clineStack migration.
  • Issue 22: Concerns replacing ClineProvider stack management with a registry-based task manager.

Possibly related PRs

Suggested labels: awaiting-review

Suggested reviewers: navedmerchant

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly names the new TaskRegistry and the clineStack migration.
Description check ✅ Passed The PR description includes the linked issue, implementation details, testing, checklist, and notes, so it is mostly complete.
Linked Issues check ✅ Passed The changes implement TaskRegistry, migrate ClineProvider call sites, add required methods, and cover the invariant and focus behavior.
Out of Scope Changes check ✅ Passed The modified files are focused on the registry migration and related tests, with no obvious unrelated changes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue/367

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.66667% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/core/webview/ClineProvider.ts 66.66% 5 Missing and 3 partials ⚠️
src/__tests__/helpers/provider-stub.ts 83.33% 0 Missing and 1 partial ⚠️
src/core/task/TaskRegistry.ts 97.77% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@edelauna
edelauna marked this pull request as ready for review July 25, 2026 11:55

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/core/task/__tests__/TaskRegistry.spec.ts (1)

8-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Downstream effect of the invariant-testability gap flagged on TaskRegistry.ts.

This helper is reused by every test in the suite but only checks taskIds.length === registry.length (tautological, both derived from stack) plus per-id getById lookups — it never compares against tasks.size, so it can't catch a map/stack desync. See the companion comment on src/core/task/TaskRegistry.ts (lines 3-11) for the root cause and suggested fix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/task/__tests__/TaskRegistry.spec.ts` around lines 8 - 17, Update the
assertInvariant helper to compare registry.taskIds.length against
registry.tasks.size, rather than registry.length, so the invariant detects
stack/map desynchronization; retain the existing per-ID and current-task checks.
src/core/task/TaskRegistry.ts (1)

3-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Advertised dual-write invariant isn't externally verifiable.

The docstring claims tasks.size === stack.length at all times, but no public accessor exposes tasks.size independently of stack.length (length and taskIds are both derived from stack). Nothing outside the class — including its own test suite (TaskRegistry.spec.ts's assertInvariant, lines 8-17) — can actually detect a future regression where tasks grows out of sync with stack (e.g., an orphaned map entry). The implementation is correct today, but the invariant is currently unfalsifiable by tests.

Consider exposing a minimal size accessor (or having assertInvariant reach into the private map via (registry as any).tasks.size) so the invariant this class explicitly documents can actually be asserted.

♻️ Example fix (test-side, no production API change)
 function assertInvariant(registry: TaskRegistry) {
 	const ids = registry.taskIds
 	expect(ids.length).toBe(registry.length)
+	expect((registry as any).tasks.size).toBe(registry.length)
 	for (const id of ids) {
 		expect(registry.getById(id)).toBeDefined()
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/task/TaskRegistry.ts` around lines 3 - 11, Make the documented
TaskRegistry invariant externally testable by updating TaskRegistry.spec.ts's
assertInvariant to read the private tasks map size via the test-only cast and
compare it with stack length, without changing the production API. Keep the
existing checks for taskIds and length, and ensure orphaned map entries cause
the assertion to fail.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/core/task/__tests__/TaskRegistry.spec.ts`:
- Around line 8-17: Update the assertInvariant helper to compare
registry.taskIds.length against registry.tasks.size, rather than
registry.length, so the invariant detects stack/map desynchronization; retain
the existing per-ID and current-task checks.

In `@src/core/task/TaskRegistry.ts`:
- Around line 3-11: Make the documented TaskRegistry invariant externally
testable by updating TaskRegistry.spec.ts's assertInvariant to read the private
tasks map size via the test-only cast and compare it with stack length, without
changing the production API. Keep the existing checks for taskIds and length,
and ensure orphaned map entries cause the assertion to fail.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a190bacb-aa39-4f90-86bd-d6522abd5751

📥 Commits

Reviewing files that changed from the base of the PR and between d1f3999 and b66196d.

📒 Files selected for processing (8)
  • src/__tests__/helpers/provider-stub.ts
  • src/__tests__/removeClineFromStack-delegation.spec.ts
  • src/__tests__/single-open-invariant.spec.ts
  • src/core/task/TaskRegistry.ts
  • src/core/task/__tests__/TaskRegistry.spec.ts
  • src/core/webview/ClineProvider.ts
  • src/core/webview/__tests__/ClineProvider.flicker-free-cancel.spec.ts
  • src/core/webview/__tests__/ClineProvider.spec.ts

@github-actions github-actions Bot added the awaiting-review PR changes are ready and waiting for maintainer re-review label Jul 25, 2026
it("removes the focused task, aborts it, and clears listeners", async () => {
const { provider, childTask } = buildMockProvider({ childTaskId: "child-1" })
expect(provider.clineStack).toHaveLength(1)
expect((provider as any).taskRegistry.length).toBe(1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is it possible to not use any for assertions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-review PR changes are ready and waiting for maintainer re-review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Story 3.2a] Introduce TaskRegistry and migrate clineStack

2 participants