Skip to content

refactor: introduce StateManagement interface and CompositeStateManagement#2

Merged
wei840222 merged 4 commits into
mainfrom
refactor/state-management-abstraction
Jun 21, 2026
Merged

refactor: introduce StateManagement interface and CompositeStateManagement#2
wei840222 merged 4 commits into
mainfrom
refactor/state-management-abstraction

Conversation

@ani6439walc

Copy link
Copy Markdown
Owner

State Management Abstraction Refactor

Summary

This PR introduces a new StateManagement interface and a CompositeStateManagement implementation to improve the architecture of the sequential thinking plugin. The changes refactor the original SessionStateManager into more modular components with clearer separation of concerns.

  • Created StateManagement interface defining core state operations
  • Implemented CompositeStateManagement with dedicated services:
    • SessionMappingService: handles tool call to session mapping
    • StateStorageService: manages state storage and retrieval
    • LifecycleManager: handles cleanup operations
  • Updated dependencies throughout the codebase to use the new interface
  • Maintained backward compatibility through inheritance
  • Added comprehensive tests for the new implementation

Benefits

  • Better separation of concerns (SRP)
  • Improved testability with modular components
  • Enhanced flexibility for future state management strategies
  • Clearer architecture with well-defined interfaces

Changelog

  • Added src/state-interface.ts with StateManagement interface
  • Added src/composite-state-management.ts with new implementation
  • Refactored src/state.ts to use new architecture while maintaining compatibility
  • Updated src/plugin.ts, src/hooks.ts to use new interface
  • Updated documentation in README.md and AGENTS.md
  • Added comprehensive tests in src/composite-state-management.test.ts

Testing

All tests pass, including new tests for the refactored components.

@github-actions

Copy link
Copy Markdown

Jules PR Review

Summary

This PR refactors session state management by introducing a StateManagement interface and a CompositeStateManagement implementation. It splits the original SessionStateManager into smaller services (SessionMappingService, StateStorageService, LifecycleManager). While the interface abstraction improves flexibility, the decomposition introduces encapsulation violations and a memory leak regression.

🔴 Blocking Issues

  • src/composite-state-management.ts, lines 135-139: The purgeSessionState method accesses a private property using bracket notation (this.mappingService['sessionKeyByToolCallId']). This violates encapsulation and bypasses TypeScript's visibility checks. Suggested fix: Add a public method like removeMappingsForSession(sessionKey: string) to SessionMappingService to encapsulate this logic.
  • src/composite-state-management.ts, lines 92-96: LifecycleManager.getCleanupCallback iterates over keys and calls this.storageService.purgeSessionState(key). This fails to clean up the associated tool call mappings in SessionMappingService, causing a memory leak. In the previous implementation, the cleanup callback invoked the manager's purgeSessionState, which cleared both. Suggested fix: LifecycleManager needs a way to invoke the full purgeSessionState on the composite class, or the cleanup logic should be moved back to CompositeStateManagement directly so it can clean up both services.

🟡 Suggestions

  • src/composite-state-management.ts, lines 4-98: The splitting of state management into three distinct internal services (SessionMappingService, StateStorageService, LifecycleManager) seems over-engineered given they just wrap two maps and are tightly coupled (as evidenced by the bugs above). Consider consolidating these back into the main CompositeStateManagement class to reduce complexity.
  • src/state.ts, line 12: export class SessionStateManager extends CompositeStateManagement {}. While this maintains backward compatibility for imports, consider marking it with a JSDoc @deprecated tag so consumers know to migrate to the new interface/class.

🧪 Tests & Verification

  • src/composite-state-management.test.ts: The "should provide cleanup callback" test only asserts that manager.stateCount is 0. It should also verify that tool call mappings are removed (e.g., expect(manager.getStateByToolCallId("tool1")).toBeUndefined()). This would have caught the memory leak regression in the cleanup callback.

Verdict

❌ Request changes
The introduction of the state management interface is a good step, but the internal refactoring introduces a memory leak and breaks encapsulation, which must be fixed before merging.

Jules session: https://jules.google.com/session/12735773767446105664


Reviewed by Jules via GitHub Actions.

- Add public removeMappingsForSession method to SessionMappingService
- Update purgeSessionState to use public API instead of private property access
- Fix LifecycleManager to properly clean up both storage and mapping services
- Add @deprecated tag to SessionStateManager class
- Enhance cleanup callback test to verify tool call mapping removal
@github-actions

Copy link
Copy Markdown

Jules PR Review

Summary

This PR successfully refactors the state management logic by introducing the StateManagement interface and the CompositeStateManagement class, replacing the concrete SessionStateManager class while cleanly preserving backward compatibility via an alias. The separation of concerns into storage, mapping, and lifecycle services is implemented correctly, and the integration into the existing hook system is seamless. Overall, the code is well-structured and the tests effectively cover the primary use cases.

🔴 Blocking Issues

No blocking issues found.

🟡 Suggestions

  • src/composite-state-management.ts (lines 109-115): In LifecycleManager.getCleanupCallback(), iterating over all keys to individually purge states and mappings results in an O(S * T) complexity (where S is sessions and T is tool calls). You can significantly optimize this by simply calling this.storageService.clear() and this.mappingService.clear() inside the callback (or delegating to CompositeStateManagement.reset()).
  • src/composite-state-management.ts (lines 26-35): In SessionMappingService.removeMappingsForSession, you create a toRemove array to hold keys before deleting them. In modern JavaScript (ES6+), it is safe to delete from a Map while iterating over it. You can simplify the code and avoid the extra array allocation by deleting directly: if (sk === sessionKey) this.sessionKeyByToolCallId.delete(toolCallId);.
  • src/composite-state-management.ts (lines 194-197): getThoughtHistory returns a shallow copy of the history array [...state.thoughtHistory]. The comment mentions returning a copy to prevent external mutations. While this stops consumers from mutating the array itself (e.g., push/pop), mutating individual ThoughtData properties within the array will still affect the internal state. Consider clarifying this in the comment or using a deep clone (like structuredClone) if strict immutability is required.
  • src/composite-state-management.ts (lines 4-118): The division into SessionMappingService, StateStorageService, and LifecycleManager introduces substantial boilerplate for what is essentially managing two Maps. While the "Composite" approach is functionally correct, consider whether this heavy object-oriented abstraction is strictly necessary, or if standard private methods inside CompositeStateManagement would suffice to reduce complexity.
  • src/state.test.ts vs src/composite-state-management.test.ts: Assuming state.test.ts still exists for the old SessionStateManager, consider migrating any remaining edge cases into composite-state-management.test.ts and removing state.test.ts entirely to prevent duplicate test maintenance.

🧪 Tests & Verification

  • composite-state-management.test.ts is robust and covers the new mapping and branching logic well.
  • Consider adding an explicit test for the reset() method to ensure both mappings and states are cleared correctly in unison.
  • Consider adding an explicit test for getThoughtHistory() to verify that it does indeed return a fresh copy, and that operations on the returned array do not modify the internal state.

Verdict

✅ Approve
The refactor improves modularity and interface segregation, safely maintaining backward compatibility with a clean, bug-free implementation.

Jules session: https://jules.google.com/session/12363333647322724334


Reviewed by Jules via GitHub Actions.

- Simplify removeMappingsForSession to delete directly while iterating
- Optimize LifecycleManager cleanup callback to use clear() methods
- Clarify getThoughtHistory uses shallow copy in comment
- Add tests for reset() method and getThoughtHistory behavior
@github-actions

Copy link
Copy Markdown

Jules PR Review

Summary

This PR refactors the state management of the plugin by introducing a formal StateManagement interface and a CompositeStateManagement class that delegates to internal services (Mapping, Storage, Lifecycle). It excellently preserves backwards compatibility by aliasing the new implementation to the legacy SessionStateManager class. The logic remains functionally identical, and the new structure sets a good foundation for extensibility.

🔴 Blocking Issues

No blocking issues found.

🟡 Suggestions

  • src/state.ts (Line 1): The RunState import is no longer used in this file. You can safely remove import { RunState } from "./tool.js"; to avoid linter warnings.
  • src/hooks.ts (Line 14): Since StateManagement is purely an interface, consider using import type { StateManagement } from "./state.js"; to ensure it gets completely stripped during transpilation.
  • src/state.ts (Lines 4-5): You re-export StateManagement but leave out StateOperations. If consumers are expected to use the new operations defined in StateOperations, consider re-exporting it here as well.
  • src/composite-state-management.ts (Lines 119-125): The CompositeStateManagement class currently hardcodes the instantiation of its internal services. Consider accepting these services via constructor parameters (with defaults) so they can be mocked in tests or swapped out in the future.
  • AGENTS.md (Lines 18-21): The file tree diagram updates the comment for state.ts but forgets to add the newly created files (src/composite-state-management.ts and src/state-interface.ts).

🧪 Tests & Verification

Test coverage looks good. Ensure that the existing state.test.ts (which tests the legacy SessionStateManager) still passes to verify that the backwards compatibility alias fully satisfies the legacy contract.

Verdict

⚠️ Approve with comments
The refactoring is clean and backwards compatible, but cleaning up the unused imports and addressing the minor documentation gap will ensure the codebase remains pristine.

Jules session: https://jules.google.com/session/6222918651520912282


Reviewed by Jules via GitHub Actions.

- Remove unused RunState import from state.ts
- Use import type for StateManagement in hooks.ts
- Re-export StateOperations interface
- Update documentation to include new files
@github-actions

Copy link
Copy Markdown

Jules PR Review

Summary

This PR refactors state management by introducing StateManagement and StateOperations interfaces, and a CompositeStateManagement class that implements them. It retains backward compatibility by exporting the old SessionStateManager as an alias for CompositeStateManagement. The approach cleanly decouples state mapping, storage, and lifecycle concerns, sets up future extensibility for alternative storage backends, and is thoroughly covered by new unit tests. Overall, it's a solid, well-executed architectural improvement.

🔴 Blocking Issues

No blocking issues found.

🟡 Suggestions

  • src/composite-state-management.ts, lines 118-125: The sub-services (SessionMappingService, StateStorageService, LifecycleManager) are hardcoded and instantiated internally within the CompositeStateManagement constructor. To fully realize the benefits of composition and maximize testability, consider injecting these dependencies via the constructor, or simply keeping them as private members/methods if they are strictly internal implementation details and not meant to be mocked or swapped independently.
  • src/composite-state-management.ts, line 185: The condition if (adjustedInput.branchFromThought && adjustedInput.branchId) relies on truthiness. If branchFromThought could ever validly be 0, the check would incorrectly fail. Consider using a stricter check like if (adjustedInput.branchFromThought != null && adjustedInput.branchId).

🧪 Tests & Verification

Test coverage looks good. The author should perform a follow-up verification to ensure that SequentialThinkingTool (which was not touched in this PR) is updated in a subsequent change to properly utilize the newly encapsulated addThought and addBranch methods instead of directly accessing and mutating thoughtHistory.

Verdict

✅ Approve
The architectural refactor cleanly separates concerns, introduces robust test coverage, and expertly preserves backward compatibility.

Jules session: https://jules.google.com/session/8266547891152359914


Reviewed by Jules via GitHub Actions.

@wei840222
wei840222 merged commit 35391a6 into main Jun 21, 2026
1 check passed
@wei840222
wei840222 deleted the refactor/state-management-abstraction branch June 21, 2026 03:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants