Skip to content

orchestrator implementation + persistence layer - #98

Merged
Kitenite merged 7 commits into
mainfrom
persistence
Nov 18, 2025
Merged

Kitenite merged 7 commits into
mainfrom
persistence

Conversation

@Kitenite

@Kitenite Kitenite commented Nov 18, 2025 •

Copy link
Copy Markdown
Member
  • add change types
  • add persistence and implementation layer

Summary by CodeRabbit

  • New Features

    • Local CLI persistence for environments, workspaces, processes, changes, file diffs and agent summaries with cascade deletion
    • Process lifecycle management (create, stop, stop-all) and workspace/environment CRUD
    • Configurable storage location via environment variable
  • Tests

    • Extensive unit tests covering full CRUD lifecycles, cascade behavior, date serialization, and concurrent writes
  • Documentation

    • Usage guides, examples, architecture overview, and migration notes for the persistence layer

@coderabbitai

coderabbitai Bot commented Nov 18, 2025 •

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Walkthrough

Adds a JSON-backed persistence layer (StorageAdapter + LowdbAdapter), storage types/config, orchestrators for environments/workspaces/processes/changes with cascade-delete behavior, comprehensive tests and docs, plus dependency and minor import/tsconfig adjustments.

Changes

Cohort / File(s) Change Summary
Dependencies & TS config
apps/cli/package.json, apps/cli/tsconfig.json, packages/scripts/tsconfig.json, packages/typescript-config/base.json
Added runtime dependency lowdb@^7.0.1 and devDependency bun-types@^1.3.2; adjusted TypeScript types entries across configs (added bun-types in base.json; removed in packages/scripts tsconfig; removed explicit empty types in apps/cli tsconfig).
Storage API & Config
apps/cli/src/lib/storage/adapter.ts, apps/cli/src/lib/storage/config.ts, apps/cli/src/lib/storage/index.ts
Introduced StorageAdapter interface, storage path utilities (getStorageDir, getDbPath, ensureStorageDir), and barrel exports for storage types/adapters.
Lowdb Adapter & Types
apps/cli/src/lib/storage/lowdb-adapter.ts, apps/cli/src/lib/storage/types.ts
Implemented LowdbAdapter with file-backed JSON persistence, date serialization/deserialization, lazy init, collection/item CRUD, clear; added Database/SerializedDatabase schema, Serialized<T> utility, and createEmptyDatabase.
Storage Tests
apps/cli/src/lib/storage/__tests__/lowdb-adapter.test.ts
Added tests covering init, CRUD, collection ops, date serialization, concurrent writes, and per-test temp DB isolation.
Orchestrators Implementation
apps/cli/src/lib/orchestrators/*.ts, apps/cli/src/lib/orchestrators/index.ts
Added Change, Environment, Process, and Workspace orchestrators implementing CRUD, ID/timestamp generation, agent-specific handling, and cascade-delete helpers; added barrel export.
Orchestrator Tests
apps/cli/src/lib/orchestrators/__tests__/*
Added comprehensive test suites for Change, Environment, Process, and Workspace orchestrators validating create/get/list/update/delete, cascade deletions, and storage interactions using temporary Lowdb stores.
Type Exports
apps/cli/src/types/index.ts
Exported ./change and reordered workspace export after process.
Persistence Documentation
apps/cli/src/lib/persistence_docs/*
Added PLAN.md, SUMMARY.md, EXAMPLE.md documenting architecture, data model, cascade rules, usage examples, testing guidance, and migration notes.
Minor Import Reordering & misc
apps/cli/server.ts, apps/cli/src/cli.tsx, apps/cli/test.js
Reordered some imports (no behavioral changes).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Orch as Orchestrator
    participant Store as StorageAdapter
    participant Lowdb as LowdbAdapter
    participant FS as Filesystem

    Client->>Orch: create(entity)
    Orch->>Orch: generate id, createdAt
    Orch->>Store: set(collection, id, entity)
    Store->>Lowdb: set(...)
    Lowdb->>Lowdb: init() / deserializeDates()
    Lowdb->>FS: read db.json
    Lowdb->>Lowdb: serializeDates()
    Lowdb->>FS: write db.json
    Lowdb-->>Store: ok
    Store-->>Orch: ok
    Orch-->>Client: entity(with id)
Loading
sequenceDiagram
    participant Client
    participant WS as WorkspaceOrchestrator
    participant Store as StorageAdapter
    participant Lowdb as LowdbAdapter
    participant FS as Filesystem

    Client->>WS: delete(workspaceId)

    rect rgb(220,240,255)
    Note over WS,Store: Cascade: for each process -> delete agentSummaries then process
    WS->>Store: getCollection("processes")
    WS->>Store: delete("agentSummaries", ...) / delete("processes", ...)
    end

    rect rgb(240,220,255)
    Note over WS,Store: Cascade: for each change -> delete fileDiffs then change
    WS->>Store: getCollection("changes")
    WS->>Store: delete("fileDiffs", ...) / delete("changes", ...)
    end

    rect rgb(255,235,220)
    Note over WS,Store: Finally delete workspace
    WS->>Store: delete("workspaces", workspaceId)
    end

    Store->>Lowdb: persist
    Lowdb->>FS: write db.json
    Lowdb-->>Store: ok
    Store-->>WS: ok
    WS-->>Client: ok
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • LowdbAdapter: date (de)serialization, init/lazy behavior, concurrent writes and file IO error handling.
    • Cascade-delete logic in Environment/Workspace/Change/Process orchestrators: deletion order, agentSummary cleanup, and isolation (no cross-workspace leakage).
    • Storage types mapping (Serialized<T> and Serialized* aliases) vs. adapter serialization.
    • Test suites: ensure temp DB isolation and timestamp/assertion correctness.

Possibly related PRs

  • Add more types #97 — Adds Environment/Process/Workspace type definitions and reorganizes type exports; strongly related to the new orchestrator implementations and type usage.

Poem

🐰 I hopped through JSON, nose to the key,
I planted IDs where timestamps could be,
Orchestrators hum, Lowdb keeps the score,
Cascades tidy up every door,
Hooray — data safe, and gardens are free! 🌿

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is minimal and lacks required template sections. It only lists two bullet points without providing context, related issues, type of change, testing details, or additional notes as specified in the template. Expand the description to include all template sections: provide clear context for the changes, link related issues, specify the type of change, document testing performed, and add any additional context needed for reviewers.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'orchestrator implementation + persistence layer' accurately reflects the main changes in the PR, which include four new orchestrator classes and a complete persistence layer with storage adapters.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 62fb568 and d117ab2.

📒 Files selected for processing (3)
  • apps/cli/tsconfig.json (1 hunks)
  • packages/scripts/tsconfig.json (1 hunks)
  • packages/typescript-config/base.json (1 hunks)

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

@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.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/cli/package.json (1)

1-59: Fix three critical test failures blocking the PR.

The tests are failing because of incompatibilities in test file setup:

  1. apps/cli/test.js (line 2): Imports "./source/app.js" which doesn't exist. The app is at ./src/app.tsx. Change to import App from "./src/app.tsx";

  2. All 5 test files in apps/cli/src/tests/: Import from "bun:test" framework, but package.json test script runs AVA. Convert these to use AVA imports (import test from "ava") and AVA syntax (test() instead of describe() and test()), or switch the test runner to Bun in package.json.

The test command runs AVA but encounters bun:test syntax, causing it to fail with exit code 1.

🧹 Nitpick comments (9)
apps/cli/src/lib/orchestrators/__tests__/environment-orchestrator.test.ts (1)

1-10: Fix import-order lint warning via formatter

CI reported lint/source/organizeImports for this file. The current imports are already reasonably grouped, so this is likely just a formatting rule (e.g., sort alphabetically or group by module type). Rather than hand-tuning, I’d suggest running the project’s lint/format command locally (or bun lint --fix if available) to auto-fix the ordering here.

apps/cli/src/lib/orchestrators/change-orchestrator.ts (1)

45-58: Consider sharing file-diff cascade logic with workspace/environment orchestrators

The delete implementation performs a fileDiff cascade that is structurally similar to what WorkspaceOrchestrator.delete and EnvironmentOrchestrator.deleteWorkspaceCascade already do when removing changes. To reduce duplication and keep cascade rules in sync, you may want to centralize this fileDiff deletion logic (e.g., a shared helper or a small internal utility) and call it from all three locations.

apps/cli/PERSISTENCE_PLAN.md (1)

12-31: Align markdown fences and relationship diagram with implementation

Two small doc nits:

  1. MD040 fenced-code warnings
    The tree and relationship diagrams use bare triple backticks without a language. To satisfy markdownlint and improve readability, consider annotating them, e.g.:

- +text
apps/cli/src/
...

...
-``` 
+```text
Environment (id)
...
  1. AgentSummary parent in the diagram
    The “Type Relationships” section currently shows AgentSummary (agentId) as a child of Change, but the code cascades agentSummaries from Process (see ProcessOrchestrator.delete and the workspace/environment cascade helpers that filter by summary.agentId === processId). To match the implementation, you may want something like:

    Environment (id)
      └── Workspace (environmentId)
          ├── Process (workspaceId)
          │   ├── Agent/Terminal
          │   └── AgentSummary (agentId)
          └── Change (workspaceId)
              └── FileDiff (changeId)
    

Also applies to: 80-88

apps/cli/src/lib/orchestrators/workspace-orchestrator.ts (1)

65-111: Reduce duplication and repeated collection reads in cascade delete

The delete method correctly enforces cascade behavior, but there are two maintainability/perf concerns:

  • The cascade logic (processes → agentSummaries, changes → fileDiffs) is nearly identical to EnvironmentOrchestrator.deleteWorkspaceCascade, so any future change must be replicated in two places.
  • Within the loops, getCollection("agentSummaries") and getCollection("fileDiffs") are called for each process/change ID, which repeatedly reads the full collection.

Consider:

  • Extracting a shared internal helper (e.g., in a small cascade.ts module or a private utility on a common base) that both orchestrators can reuse.
  • Hoisting the getCollection calls out of the loops, then filtering in-memory for each ID:
const agentSummaries = await this.storage.getCollection("agentSummaries");
// compute summaryIds per processId from this one snapshot

This keeps behavior the same while reducing duplication and I/O.

apps/cli/src/lib/orchestrators/environment-orchestrator.ts (1)

37-51: Consolidate workspace cascade logic and avoid repeated full-collection reads

The environment orchestrator’s delete path is functionally sound and respects the cascade rules, but:

  • deleteWorkspaceCascade is almost a copy of WorkspaceOrchestrator.delete, which risks divergence if cascade behavior changes in one place but not the other.
  • Inside the process/change loops, getCollection("agentSummaries") and getCollection("fileDiffs") are called for each ID, repeatedly loading whole collections.

To improve maintainability and efficiency, consider:

  • Extracting shared cascade helpers (e.g., deleteWorkspaceWithChildren(storage, workspaceId)) and calling those from both EnvironmentOrchestrator and WorkspaceOrchestrator.
  • Hoisting getCollection("agentSummaries") / getCollection("fileDiffs") outside of the per-ID loops and filtering from a single snapshot.

The external behavior stays identical but will be easier to evolve.

Also applies to: 53-67, 69-116

apps/cli/src/lib/orchestrators/process-orchestrator.ts (2)

79-115: Prefer using type to detect agents instead of "status" in updated

Using "status" in updated works today because only agents have a status field, but it’s a bit brittle if other process types ever gain a status-like property.

You can make the intent clearer and safer by checking type:

-		// Update status for agents
-		if ("status" in updated) {
-			(updated as Agent).status = "stopped";
-		}
+		// Update status for agents
+		if (existing.type === ProcessType.AGENT) {
+			(updated as Agent).status = "stopped";
+		}

and similarly in stopAll, use process.type === ProcessType.AGENT to decide whether to set status.


117-130: Batch agent summary deletions to reduce redundant reads/writes

delete calls storage.delete once per related agentSummary. With the current LowdbAdapter, each delete incurs a full read + write of the JSON file, which can be expensive if a process has many summaries.

A more efficient pattern is:

  1. Load agentSummaries once.
  2. Remove matching entries in-memory.
  3. Call updateCollection("agentSummaries", updatedSummaries) once.

This keeps behavior the same while reducing I/O.

apps/cli/src/lib/storage/types.ts (1)

37-44: Consider future‑proofing Serialized<T> for Date | null fields

Right now the conditional covers Date and Date | undefined, which matches current models. If you later introduce Date | null fields, they won't be serialized as strings by this helper.

If that becomes relevant, you could extend the condition to include Date | null as well.

apps/cli/src/lib/storage/lowdb-adapter.ts (1)

93-167: Potential I/O optimization and concurrency refinement (non‑blocking)

Each mutating method (set, delete, per‑entity operations in orchestrators) does read → mutate → write. With higher write volumes, you may want to:

  • Batch logical operations at the orchestrator level (e.g., deleting many summaries with a single updateCollection), and/or
  • Introduce a simple initPromise to ensure only one JSONFilePreset instance is constructed even under concurrent calls.

Not required for correctness, but worth considering if CLI usage grows.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d59e2a and cd0cdf4.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • apps/cli/PERSISTENCE_PLAN.md (1 hunks)
  • apps/cli/package.json (1 hunks)
  • apps/cli/src/lib/orchestrators/__tests__/change-orchestrator.test.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/__tests__/environment-orchestrator.test.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/__tests__/process-orchestrator.test.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/__tests__/workspace-orchestrator.test.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/change-orchestrator.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/environment-orchestrator.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/index.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/process-orchestrator.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/workspace-orchestrator.ts (1 hunks)
  • apps/cli/src/lib/storage/__tests__/lowdb-adapter.test.ts (1 hunks)
  • apps/cli/src/lib/storage/adapter.ts (1 hunks)
  • apps/cli/src/lib/storage/config.ts (1 hunks)
  • apps/cli/src/lib/storage/index.ts (1 hunks)
  • apps/cli/src/lib/storage/lowdb-adapter.ts (1 hunks)
  • apps/cli/src/lib/storage/types.ts (1 hunks)
  • apps/cli/src/types/index.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
apps/cli/src/lib/storage/__tests__/lowdb-adapter.test.ts (4)
apps/cli/src/lib/storage/lowdb-adapter.ts (1)
  • LowdbAdapter (14-168)
apps/cli/src/types/environment.ts (1)
  • Environment (1-3)
apps/cli/src/types/process.ts (1)
  • Process (8-18)
apps/cli/src/types/change.ts (1)
  • Change (1-6)
apps/cli/src/lib/orchestrators/environment-orchestrator.ts (2)
apps/cli/src/lib/storage/adapter.ts (1)
  • StorageAdapter (7-70)
apps/cli/src/types/environment.ts (1)
  • Environment (1-3)
apps/cli/src/lib/storage/adapter.ts (1)
apps/cli/src/lib/storage/types.ts (1)
  • Database (15-22)
apps/cli/src/lib/orchestrators/workspace-orchestrator.ts (2)
apps/cli/src/lib/storage/adapter.ts (1)
  • StorageAdapter (7-70)
apps/cli/src/types/workspace.ts (1)
  • LocalWorkspace (12-15)
apps/cli/src/lib/storage/types.ts (3)
apps/cli/src/types/environment.ts (1)
  • Environment (1-3)
apps/cli/src/types/process.ts (2)
  • Process (8-18)
  • Agent (29-32)
apps/cli/src/types/change.ts (2)
  • Change (1-6)
  • AgentSummary (25-30)
apps/cli/src/lib/orchestrators/__tests__/workspace-orchestrator.test.ts (4)
apps/cli/src/lib/storage/lowdb-adapter.ts (1)
  • LowdbAdapter (14-168)
apps/cli/src/lib/orchestrators/workspace-orchestrator.ts (1)
  • WorkspaceOrchestrator (14-112)
apps/cli/src/lib/orchestrators/change-orchestrator.ts (1)
  • ChangeOrchestrator (12-59)
apps/cli/src/types/workspace.ts (1)
  • LocalWorkspace (12-15)
apps/cli/src/lib/orchestrators/__tests__/environment-orchestrator.test.ts (4)
apps/cli/src/lib/storage/lowdb-adapter.ts (1)
  • LowdbAdapter (14-168)
apps/cli/src/lib/orchestrators/environment-orchestrator.ts (1)
  • EnvironmentOrchestrator (12-117)
apps/cli/src/lib/orchestrators/workspace-orchestrator.ts (1)
  • WorkspaceOrchestrator (14-112)
apps/cli/src/lib/orchestrators/process-orchestrator.ts (1)
  • ProcessOrchestrator (17-131)
apps/cli/src/lib/storage/lowdb-adapter.ts (3)
apps/cli/src/lib/storage/adapter.ts (1)
  • StorageAdapter (7-70)
apps/cli/src/lib/storage/types.ts (3)
  • SerializedDatabase (28-35)
  • createEmptyDatabase (57-64)
  • Database (15-22)
apps/cli/src/lib/storage/config.ts (2)
  • getDbPath (23-25)
  • ensureStorageDir (31-37)
apps/cli/src/lib/orchestrators/__tests__/process-orchestrator.test.ts (5)
apps/cli/src/lib/storage/lowdb-adapter.ts (1)
  • LowdbAdapter (14-168)
apps/cli/src/lib/orchestrators/process-orchestrator.ts (1)
  • ProcessOrchestrator (17-131)
apps/cli/src/lib/orchestrators/workspace-orchestrator.ts (1)
  • WorkspaceOrchestrator (14-112)
apps/cli/src/lib/orchestrators/environment-orchestrator.ts (1)
  • EnvironmentOrchestrator (12-117)
apps/cli/src/types/process.ts (1)
  • Agent (29-32)
apps/cli/src/lib/orchestrators/process-orchestrator.ts (2)
apps/cli/src/lib/storage/adapter.ts (1)
  • StorageAdapter (7-70)
apps/cli/src/types/process.ts (3)
  • Process (8-18)
  • Agent (29-32)
  • Terminal (20-22)
apps/cli/src/lib/orchestrators/__tests__/change-orchestrator.test.ts (4)
apps/cli/src/lib/storage/lowdb-adapter.ts (1)
  • LowdbAdapter (14-168)
apps/cli/src/lib/orchestrators/change-orchestrator.ts (1)
  • ChangeOrchestrator (12-59)
apps/cli/src/lib/orchestrators/workspace-orchestrator.ts (1)
  • WorkspaceOrchestrator (14-112)
apps/cli/src/lib/orchestrators/environment-orchestrator.ts (1)
  • EnvironmentOrchestrator (12-117)
apps/cli/src/lib/orchestrators/change-orchestrator.ts (2)
apps/cli/src/lib/storage/adapter.ts (1)
  • StorageAdapter (7-70)
apps/cli/src/types/change.ts (1)
  • Change (1-6)
🪛 GitHub Actions: CI
apps/cli/PERSISTENCE_PLAN.md

[error] 1-1: cli#test: bun run test exited with code 1 for path '/home/runner/work/superset/superset/apps/cli'.

apps/cli/package.json

[error] 1-1: cli#test: bun run test exited with code 1 for path '/home/runner/work/superset/superset/apps/cli'.

apps/cli/src/lib/storage/config.ts

[error] 1-1: cli#test: bun run test exited with code 1 for path '/home/runner/work/superset/superset/apps/cli'.

apps/cli/src/lib/storage/__tests__/lowdb-adapter.test.ts

[error] 1-1: cli#test: bun run test exited with code 1 for path '/home/runner/work/superset/superset/apps/cli'.

apps/cli/src/lib/orchestrators/environment-orchestrator.ts

[error] 1-1: cli#test: bun run test exited with code 1 for path '/home/runner/work/superset/superset/apps/cli'.

apps/cli/src/lib/storage/adapter.ts

[error] 1-1: cli#test: bun run test exited with code 1 for path '/home/runner/work/superset/superset/apps/cli'.

apps/cli/src/lib/orchestrators/workspace-orchestrator.ts

[error] 1-1: cli#test: bun run test exited with code 1 for path '/home/runner/work/superset/superset/apps/cli'.

apps/cli/src/lib/storage/types.ts

[error] 1-1: cli#test: bun run test exited with code 1 for path '/home/runner/work/superset/superset/apps/cli'.

apps/cli/src/lib/orchestrators/index.ts

[error] 1-1: cli#test: bun run test exited with code 1 for path '/home/runner/work/superset/superset/apps/cli'.

apps/cli/src/lib/orchestrators/__tests__/workspace-orchestrator.test.ts

[error] 1-1: cli#test: bun run test exited with code 1 for path '/home/runner/work/superset/superset/apps/cli'.

apps/cli/src/lib/orchestrators/__tests__/environment-orchestrator.test.ts

[warning] 1-1: lint/source/organizeImports FIXABLE: The imports and exports are not sorted.

apps/cli/src/lib/storage/lowdb-adapter.ts

[error] 1-1: cli#test: bun run test exited with code 1 for path '/home/runner/work/superset/superset/apps/cli'.

apps/cli/src/lib/storage/index.ts

[error] 1-1: cli#test: bun run test exited with code 1 for path '/home/runner/work/superset/superset/apps/cli'.

apps/cli/src/lib/orchestrators/__tests__/process-orchestrator.test.ts

[error] 1-1: cli#test: bun run test exited with code 1 for path '/home/runner/work/superset/superset/apps/cli'.

apps/cli/src/lib/orchestrators/process-orchestrator.ts

[error] 1-1: cli#test: bun run test exited with code 1 for path '/home/runner/work/superset/superset/apps/cli'.

apps/cli/src/lib/orchestrators/__tests__/change-orchestrator.test.ts

[warning] 1-1: lint/source/organizeImports FIXABLE: The imports and exports are not sorted.

apps/cli/src/lib/orchestrators/change-orchestrator.ts

[error] 1-1: cli#test: bun run test exited with code 1 for path '/home/runner/work/superset/superset/apps/cli'.

apps/cli/src/types/index.ts

[error] 1-1: cli#test: bun run test exited with code 1 for path '/home/runner/work/superset/superset/apps/cli'.

🪛 markdownlint-cli2 (0.18.1)
apps/cli/PERSISTENCE_PLAN.md

12-12: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


80-80: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (13)
apps/cli/src/types/index.ts (1)

1-4: LGTM!

The addition of Change types export and reordering of exports are appropriate for the new persistence layer.

apps/cli/src/lib/storage/config.ts (1)

1-37: LGTM! Good security practices.

The storage configuration is well-implemented with:

  • Environment variable override for flexibility
  • Restrictive permissions (0o700) for the storage directory
  • Proper path construction and directory creation

The existsSync check followed by mkdir with recursive: true is safe as Node.js handles concurrent creation attempts gracefully.

apps/cli/src/lib/orchestrators/__tests__/process-orchestrator.test.ts (1)

1-287: LGTM! Comprehensive test coverage.

The test suite is well-structured with:

  • Proper setup/teardown using temporary directories
  • Comprehensive CRUD operation coverage
  • Cascade deletion validation
  • Edge case handling (non-existent processes, already stopped processes)
  • Good test isolation
apps/cli/src/lib/orchestrators/__tests__/workspace-orchestrator.test.ts (1)

1-199: LGTM! Well-structured test suite.

Excellent test coverage with:

  • Both workspace types (local/cloud) tested
  • Cascade deletion verified through multiple levels (workspace → changes → fileDiffs)
  • Proper filtering tests
  • Clean setup/teardown
apps/cli/src/lib/orchestrators/__tests__/change-orchestrator.test.ts (1)

11-250: LGTM! Thorough cascade deletion tests.

The test suite provides excellent coverage including:

  • Change creation with proper ID/timestamp generation
  • Workspace-filtered listing
  • Update validation with error handling for non-existent changes
  • Multi-level cascade deletion (change → fileDiffs)
  • Isolation tests ensuring deletion only affects related entities
apps/cli/src/lib/orchestrators/index.ts (1)

1-4: LGTM!

Clean barrel export providing a unified API surface for all orchestrators.

apps/cli/src/lib/storage/index.ts (1)

1-5: LGTM!

Well-organized barrel export exposing the complete storage API surface including types, adapters, and utilities.

apps/cli/package.json (1)

26-26: lowdb version verified as latest and secure.

Verification confirms that lowdb@7.0.1 is the current latest stable release with no known security vulnerabilities. The dependency specification is appropriate.

apps/cli/src/lib/storage/__tests__/lowdb-adapter.test.ts (1)

29-44: LowdbAdapter tests look comprehensive and aligned with behavior

The test suite does a good job covering initialization, CRUD, collection-level operations, date serialization (including optional and “nested” dates), and simple concurrent writes with a shared adapter instance and temp-file isolation. The cleanup logic around mkdtemp/rm is also sound.

No changes needed here from my side.

Also applies to: 46-116, 118-171, 173-184

apps/cli/src/lib/orchestrators/process-orchestrator.ts (1)

13-37: Nice, cohesive orchestrator abstraction over the storage adapter

The overall shape (typed constructor, get/list/create using StorageAdapter and domain types) is clean and aligns well with the new persistence layer.

apps/cli/src/lib/storage/adapter.ts (1)

7-70: StorageAdapter surface looks well‑factored and sufficient

The interface cleanly separates database-wide operations from collection/entity-level CRUD and matches how the orchestrators and LowdbAdapter use it. No issues from a type or responsibility standpoint.

apps/cli/src/lib/storage/types.ts (1)

15-64: Typed storage schema and serialization helpers are consistent with domain types

Database / SerializedDatabase plus the Serialized<T> helper map the current Date-bearing entities correctly, and createEmptyDatabase provides a clear, typed initializer for persistence.

apps/cli/src/lib/storage/lowdb-adapter.ts (1)

14-34: LowdbAdapter wiring and lazy initialization align with StorageAdapter

The adapter correctly wraps the JSON preset, uses createEmptyDatabase for defaults, and lazily initializes with ensureStorageDir and a stored dbPath. The public methods consistently call init before use.

Comment on lines +56 to +60
test("throws error for non-existent environment", async () => {
expect(orchestrator.get("non-existent")).rejects.toThrow(
"Environment with id non-existent not found",
);
});

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Await .rejects expectations in async tests

All tests using expect(...).rejects.toThrow(...) are async but never await the expectation (e.g., Lines 56, 91, 99, 116, 133, 154). This means the rejection assertions may not be executed before the test completes and can lead to false positives or unhandled rejections.

Consider updating them like:

-		test("throws error for non-existent environment", async () => {
-			expect(orchestrator.get("non-existent")).rejects.toThrow(
-				"Environment with id non-existent not found",
-			);
-		});
+		test("throws error for non-existent environment", async () => {
+			await expect(orchestrator.get("non-existent")).rejects.toThrow(
+				"Environment with id non-existent not found",
+			);
+		});

Apply the same pattern to:

  • Line 93: orchestrator.update("non-existent", { id: "new" })
  • Line 103: orchestrator.get(env.id)
  • Line 116: workspaceOrchestrator.get(workspace.id)
  • Line 133: processOrchestrator.get(process.id)
  • Lines 154–155: workspaceOrchestrator.get(ws1.id) / get(ws2.id)

You can re-run the CLI tests after this change:

Also applies to: 91-95, 99-104, 106-117, 119-135, 136-157


🏁 Script executed:

#!/bin/bash
cd apps/cli
bun test --filter EnvironmentOrchestrator

Length of output: 41


Let me examine the test file directly to verify the issue.


🏁 Script executed:

#!/bin/bash
# Read the test file to verify the async/await pattern issue
cat apps/cli/src/lib/orchestrators/__tests__/environment-orchestrator.test.ts

Length of output: 4822


Await .rejects expectations in async tests

All tests using expect(...).rejects.toThrow() are async but never await the expectation. This means rejection assertions may not execute before the test completes, causing false positives or unhandled rejections. The issue affects:

  • Line 56–60: orchestrator.get("non-existent")
  • Line 93–97: orchestrator.update("non-existent", { id: "new" })
  • Line 103–109: orchestrator.get(env.id)
  • Line 113–114: workspaceOrchestrator.get(workspace.id)
  • Line 125–126: processOrchestrator.get(process.id)
  • Line 142–144: workspaceOrchestrator.get(ws1.id) and get(ws2.id)

Add await before each expect(...).rejects:

-			expect(orchestrator.get("non-existent")).rejects.toThrow(
+			await expect(orchestrator.get("non-existent")).rejects.toThrow(

Apply this pattern to all six locations listed above.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test("throws error for non-existent environment", async () => {
expect(orchestrator.get("non-existent")).rejects.toThrow(
"Environment with id non-existent not found",
);
});
test("throws error for non-existent environment", async () => {
await expect(orchestrator.get("non-existent")).rejects.toThrow(
"Environment with id non-existent not found",
);
});
🤖 Prompt for AI Agents
In apps/cli/src/lib/orchestrators/__tests__/environment-orchestrator.test.ts
around lines 56–60 (and also update the other listed locations: lines 93–97,
103–109, 113–114, 125–126, 142–144), the tests use expect(...).rejects.toThrow()
without awaiting the expectation which can lead to false positives; fix by
prefixing each expect(...).rejects... with await (e.g. await
expect(orchestrator.get("non-existent")).rejects.toThrow(...)) so the test
runner waits for the rejection assertions to complete.

Comment on lines +22 to +29
async create(
change: Omit<Change, "id" | "timestamp">,
): Promise<Change> {
const newChange: Change = {
...change,
id: randomUUID(),
createdAt: new Date(),
};

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Fix create parameter type to match Change shape

Change is defined with createdAt: Date and no timestamp property, but the create signature is:

async create(
	change: Omit<Change, "id" | "timestamp">,
): Promise<Change> {

This has two issues:

  • timestamp doesn’t exist on Change, so the type is misleading.
  • The Change payload still includes createdAt, even though create always sets createdAt: new Date() itself. Callers are forced to provide a field that is ignored.

A more accurate signature is:

-	async create(
-		change: Omit<Change, "id" | "timestamp">,
-	): Promise<Change> {
+	async create(
+		change: Omit<Change, "id" | "createdAt">,
+	): Promise<Change> {

This way callers only supply workspaceId and summary, and create owns ID and timestamp generation.

Please also verify that the ChangeOrchestrator interface in apps/cli/src/types/change.ts reflects this corrected signature and adjust tests accordingly.


I'll verify the review comment by examining the relevant type definitions and interface implementations in the codebase.


🏁 Script executed:

#!/bin/bash
# Find and examine the Change type definition
echo "=== Searching for Change type definition ==="
rg -n "type Change|interface Change" --type=ts

echo -e "\n=== Examining change.ts type file ==="
fd -n "change.ts" --type=f | grep -E "types/change"

echo -e "\n=== Searching for ChangeOrchestrator interface ==="
rg -n "interface ChangeOrchestrator|type ChangeOrchestrator" --type=ts

echo -e "\n=== Examining change-orchestrator.ts implementation ==="
fd -n "change-orchestrator.ts" --type=f | head -1

Length of output: 851


🏁 Script executed:

#!/bin/bash
# Get detailed content of type definitions
echo "=== Change type from types/change.ts ==="
find . -path "./apps/cli/src/types/change.ts" -type f -exec cat {} \;

echo -e "\n=== ChangeOrchestrator interface from types/change.ts ==="
find . -path "./apps/cli/src/types/change.ts" -type f -exec grep -A 20 "interface ChangeOrchestrator" {} \;

echo -e "\n=== Change orchestrator create method ==="
cat ./apps/cli/src/lib/orchestrators/change-orchestrator.ts | head -40

Length of output: 2501


🏁 Script executed:

#!/bin/bash
# Search for usages of create method and tests
echo "=== Tests related to change orchestrator ==="
fd -n ".*\.test\.|.*\.spec\." --type=f | xargs rg -l "create.*change|ChangeOrchestrator" 2>/dev/null | head -5

echo -e "\n=== Usages of orchestrator.create ==="
rg -n "\.create\(" --type=ts -B 3 -A 1 ./apps/cli/src/lib/orchestrators/change-orchestrator.ts

echo -e "\n=== Callers of orchestrator.create ==="
rg -n "changeOrchestrator\.create|orchestrator\.create" --type=ts -B 2 -A 2 | head -40

Length of output: 4435


🏁 Script executed:

#!/bin/bash
# Examine the change orchestrator test file
echo "=== Change orchestrator tests ==="
cat ./apps/cli/src/lib/orchestrators/__tests__/change-orchestrator.test.ts

Length of output: 7010


🏁 Script executed:

#!/bin/bash
# Search for actual calls to changeOrchestrator.create() with parameters
echo "=== Calls to changeOrchestrator.create with parameters ==="
rg -n "changeOrchestrator\.create\(" --type=ts -A 3 -B 1

Length of output: 1109


Fix create parameter type in both interface and implementation to use "createdAt" instead of non-existent "timestamp"

The Change type has createdAt: Date but no timestamp property. Both the ChangeOrchestrator interface and its implementation incorrectly specify:

create: (change: Omit<Change, "id" | "timestamp">) => Promise<Change>;

This creates two problems:

  • Referencing a non-existent timestamp key is misleading
  • Callers are forced to pass createdAt, which the implementation ignores and always overwrites with new Date()

Required fixes:

  1. apps/cli/src/types/change.ts (line 33): Change interface signature from Omit<Change, "id" | "timestamp"> to Omit<Change, "id" | "createdAt">
  2. apps/cli/src/lib/orchestrators/change-orchestrator.ts (line 24): Update implementation signature to match

Existing tests already pass the correct structure ({workspaceId, summary, createdAt}), so they will continue to work once the interface is corrected.

🤖 Prompt for AI Agents
In apps/cli/src/types/change.ts around line 33 and
apps/cli/src/lib/orchestrators/change-orchestrator.ts around line 24, the create
method parameter uses Omit<Change, "id" | "timestamp"> but Change defines
createdAt (not timestamp) and the implementation overwrites createdAt anyway;
update both the interface signature and the implementation signature to use
Omit<Change, "id" | "createdAt"> so callers are not misled and the types align
with the actual Change shape and behavior.

Comment on lines +38 to +83
private deserializeDates<T>(obj: any): T {
if (obj === null || obj === undefined) return obj;

if (typeof obj === "string" && this.isISODate(obj)) {
return new Date(obj) as any;
}

if (Array.isArray(obj)) {
return obj.map((item) => this.deserializeDates(item)) as any;
}

if (typeof obj === "object") {
const result: any = {};
for (const [key, value] of Object.entries(obj)) {
result[key] = this.deserializeDates(value);
}
return result;
}

return obj;
}

/**
* Serialize dates to ISO strings
*/
private serializeDates<T>(obj: T): any {
if (obj === null || obj === undefined) return obj;

if (obj instanceof Date) {
return obj.toISOString();
}

if (Array.isArray(obj)) {
return obj.map((item) => this.serializeDates(item));
}

if (typeof obj === "object") {
const result: any = {};
for (const [key, value] of Object.entries(obj)) {
result[key] = this.serializeDates(value);
}
return result;
}

return obj;
}

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.

⚠️ Potential issue | 🟡 Minor

ISO‑date heuristic can accidentally convert user strings into Date objects

deserializeDates converts any string matching the ISO pattern into a Date, regardless of which field it belongs to. This means a user‑provided field (e.g. a summary string) that happens to be "2025-01-01T00:00:00.000Z" would come back as a Date instead of a string, violating the domain type.

To tighten this up, consider one of:

  • Restricting conversion to known date field names (e.g. createdAt, updatedAt, endedAt) by passing the key into deserializeDates and checking it, or
  • Using more explicit per-entity serializers/deserializers that know exactly which fields are dates.

That keeps the generic helper from surprising consumers on edge-case values.

@Kitenite Kitenite changed the title persistence orchestrator implementation + persistence layer Nov 18, 2025

@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.

Actionable comments posted: 1

♻️ Duplicate comments (9)
apps/cli/src/lib/orchestrators/__tests__/environment-orchestrator.test.ts (6)

56-60: Missing await on rejection assertion

This test will complete before the rejection is verified, potentially causing false positives.


91-95: Missing await on rejection assertion

This test will complete before the rejection is verified, potentially causing false positives.


99-104: Missing await on rejection assertion

This test will complete before the rejection is verified, potentially causing false positives.


106-117: Missing await on rejection assertion at line 116

This test will complete before the rejection is verified, potentially causing false positives.


119-134: Missing await on rejection assertion at line 133

This test will complete before the rejection is verified, potentially causing false positives.


136-157: Missing await on rejection assertions at lines 154-155

These tests will complete before the rejections are verified, potentially causing false positives.

apps/cli/src/lib/orchestrators/change-orchestrator.ts (1)

22-31: Fix parameter type to use "createdAt" instead of non-existent "timestamp"

The Change type has createdAt: Date but the signature references "timestamp" which doesn't exist.

apps/cli/src/lib/orchestrators/process-orchestrator.ts (1)

69-77: Guard against updating the id field to prevent storage key mismatch

The update method allows updates.id to overwrite the stored id field while the storage key remains unchanged, causing inconsistency.

apps/cli/src/lib/storage/lowdb-adapter.ts (1)

45-93: ISO‑string date deserialization is overly broad and may change user data types

This helper will convert any ISO‑looking string anywhere in the object graph into a Date, not just known date fields. That means a user field (e.g. a summary or label) that happens to be "2025-01-01T00:00:00.000Z" will round‑trip as a Date instead of a string, violating consumers’ expectations.

Consider restricting conversion to explicit date fields (e.g. createdAt, updatedAt, endedAt) or using per-entity serializers/deserializers that know exactly which properties are dates.

🧹 Nitpick comments (6)
apps/cli/test.js (1)

2-4: Cosmetic import reordering unrelated to PR objectives.

This change only reorders imports with no functional impact. It appears unrelated to the persistence layer additions described in the PR objectives. Consider whether this was an accidental inclusion.

apps/cli/src/lib/persistence_docs/PLAN.md (1)

12-31: Consider adding language identifiers to code blocks for better rendering

Adding language specifiers helps with syntax highlighting and editor support.

Apply this pattern:

-```
+```text
 apps/cli/src/
 ...

And for the TypeScript interface:

-```typescript
+```typescript
 interface Database {
   ...
 }

Also applies to: 52-61, 80-88

apps/cli/src/lib/persistence_docs/SUMMARY.md (1)

11-30: Consider adding language identifiers to diagram code blocks

Adding text as the language identifier improves markdown rendering consistency.

Example:

-```
+```text
 ┌─────────────────────────────────────────┐
 │         Orchestrators                    │
 ...

Also applies to: 36-57, 76-84

apps/cli/src/lib/storage/lowdb-adapter.ts (3)

17-43: Constructor and lazy initialization look solid; directory creation can be simplified

The lazy init plus configurable dbPath is clean. You can drop the existsSync check and just call mkdir(parentDir, { recursive: true, mode: 0o700 }) unconditionally—simplifies the code, avoids a TOCTOU check, and removes a sync FS call.


115-121: getCollection() is correct; consider the cost of full read + deep clone

The implementation is functionally sound, but every call does a full read() and deep deserializeDates of the collection. For larger datasets or hot paths, you might eventually want a lighter-weight path (e.g. caching or narrower deserialization) if performance becomes an issue.


164-171: has() works; consider hasOwnProperty if you ever change collection prototypes

Using id in this.db!.data[collection] is fine given collections are plain object literals. If you ever move to Object.create(null) or tweak prototypes, a more defensive pattern would be:

return Object.prototype.hasOwnProperty.call(this.db!.data[collection], id);

Not necessary now, just a future-proofing option.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd0cdf4 and 8902c38.

📒 Files selected for processing (17)
  • apps/cli/server.ts (1 hunks)
  • apps/cli/src/cli.tsx (1 hunks)
  • apps/cli/src/lib/orchestrators/__tests__/change-orchestrator.test.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/__tests__/environment-orchestrator.test.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/__tests__/process-orchestrator.test.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/__tests__/workspace-orchestrator.test.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/change-orchestrator.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/environment-orchestrator.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/process-orchestrator.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/workspace-orchestrator.ts (1 hunks)
  • apps/cli/src/lib/persistence_docs/EXAMPLE.md (1 hunks)
  • apps/cli/src/lib/persistence_docs/PLAN.md (1 hunks)
  • apps/cli/src/lib/persistence_docs/SUMMARY.md (1 hunks)
  • apps/cli/src/lib/storage/__tests__/lowdb-adapter.test.ts (1 hunks)
  • apps/cli/src/lib/storage/adapter.ts (1 hunks)
  • apps/cli/src/lib/storage/lowdb-adapter.ts (1 hunks)
  • apps/cli/test.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/cli/src/lib/orchestrators/tests/process-orchestrator.test.ts
  • apps/cli/src/lib/orchestrators/tests/workspace-orchestrator.test.ts
  • apps/cli/src/lib/orchestrators/tests/change-orchestrator.test.ts
  • apps/cli/src/lib/orchestrators/workspace-orchestrator.ts
🧰 Additional context used
🧬 Code graph analysis (7)
apps/cli/src/lib/orchestrators/environment-orchestrator.ts (2)
apps/cli/src/lib/storage/adapter.ts (1)
  • StorageAdapter (7-62)
apps/cli/src/types/environment.ts (1)
  • Environment (1-3)
apps/cli/src/lib/orchestrators/change-orchestrator.ts (2)
apps/cli/src/lib/storage/adapter.ts (1)
  • StorageAdapter (7-62)
apps/cli/src/types/change.ts (1)
  • Change (1-6)
apps/cli/src/lib/storage/__tests__/lowdb-adapter.test.ts (4)
apps/cli/src/lib/storage/lowdb-adapter.ts (1)
  • LowdbAdapter (17-178)
apps/cli/src/types/environment.ts (1)
  • Environment (1-3)
apps/cli/src/types/process.ts (1)
  • Process (8-18)
apps/cli/src/types/change.ts (1)
  • Change (1-6)
apps/cli/src/lib/orchestrators/process-orchestrator.ts (2)
apps/cli/src/lib/storage/adapter.ts (1)
  • StorageAdapter (7-62)
apps/cli/src/types/process.ts (3)
  • Process (8-18)
  • Agent (29-32)
  • Terminal (20-22)
apps/cli/src/lib/storage/adapter.ts (1)
apps/cli/src/lib/storage/types.ts (1)
  • Database (15-22)
apps/cli/src/lib/storage/lowdb-adapter.ts (3)
apps/cli/src/lib/storage/adapter.ts (1)
  • StorageAdapter (7-62)
apps/cli/src/lib/storage/types.ts (3)
  • SerializedDatabase (28-35)
  • createEmptyDatabase (57-64)
  • Database (15-22)
apps/cli/src/lib/storage/config.ts (1)
  • getDbPath (23-25)
apps/cli/src/lib/orchestrators/__tests__/environment-orchestrator.test.ts (4)
apps/cli/src/lib/storage/lowdb-adapter.ts (1)
  • LowdbAdapter (17-178)
apps/cli/src/lib/orchestrators/environment-orchestrator.ts (1)
  • EnvironmentOrchestrator (12-113)
apps/cli/src/lib/orchestrators/workspace-orchestrator.ts (1)
  • WorkspaceOrchestrator (14-111)
apps/cli/src/lib/orchestrators/process-orchestrator.ts (1)
  • ProcessOrchestrator (17-131)
🪛 markdownlint-cli2 (0.18.1)
apps/cli/src/lib/persistence_docs/PLAN.md

12-12: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


80-80: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

apps/cli/src/lib/persistence_docs/SUMMARY.md

11-11: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


36-36: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


76-76: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (13)
apps/cli/server.ts (1)

2-2: LGTM!

The import reordering (type imports before value imports) is a harmless style adjustment with no functional impact.

apps/cli/src/cli.tsx (1)

4-4: Import reordering is harmless.

The React import has been moved to appear after the meow import. This change has no functional impact and appears unrelated to the PR's persistence layer objectives.

apps/cli/src/lib/storage/__tests__/lowdb-adapter.test.ts (1)

1-187: LGTM! Comprehensive test coverage

The test suite thoroughly validates the LowdbAdapter with proper setup/teardown, CRUD operations, date serialization edge cases, and concurrent writes. All async operations are properly awaited.

apps/cli/src/lib/persistence_docs/EXAMPLE.md (1)

1-303: LGTM! Well-structured usage documentation

The examples provide clear guidance on common workflows, error handling, testing setup, and migration paths. All examples align with the implemented API.

apps/cli/src/lib/orchestrators/environment-orchestrator.ts (1)

1-113: LGTM! Well-implemented cascade deletion logic

The EnvironmentOrchestrator properly implements CRUD operations with comprehensive cascade deletion following the correct order: agentSummaries → processes → fileDiffs → changes → workspaces → environment. The update method correctly handles ID changes by managing storage keys.

apps/cli/src/lib/storage/adapter.ts (1)

7-61: StorageAdapter interface is well-shaped and aligns with the Database model

The async CRUD surface is coherent, type-safe against Database, and gives a clean abstraction for pluggable backends. No changes needed from my side here.

apps/cli/src/lib/storage/lowdb-adapter.ts (7)

95-101: ISO date regex is precise and appropriately narrow

The regex only matches full UTC ISO timestamps with optional milliseconds, which is a good balance between correctness and avoiding accidental matches on partial date strings.


103-107: read() correctly initializes, reloads, and normalizes dates

The read implementation does lazy init, reloads from disk, and runs through deserializeDates to return a fully-typed Database. This matches the StorageAdapter contract well.


109-113: write() correctly serializes dates and persists atomically via Lowdb

Serializing dates before assigning to db.data and then calling write() is straightforward and keeps on-disk data in the serialized shape expected by Lowdb.


123-131: updateCollection() matches the intended semantics

Reading before mutation, serializing, and then writing the updated collection back is clear and correct for a Lowdb-backed Record<string, Entity> collection.


143-151: set() implementation is straightforward and consistent with updateCollection()

The logic (init → read → serialize value → assign → write) is predictable and consistent with the collection update behavior.


154-161: delete() implementation is correct for Record-based collections

Using delete on the in-memory Record<string, Entity> followed by write() is appropriate here; no issues spotted.


173-177: clear() correctly resets the database using the shared factory

Reinitializing db.data via createEmptyDatabase() and writing it back is clean and works well for tests and “reset” behavior.

Comment on lines +133 to +141
async get<K extends keyof Database>(
collection: K,
id: string,
): Promise<Database[K][string] | undefined> {
await this.init();
await this.db!.read();
const item = this.db!.data[collection][id];
return item ? this.deserializeDates(item) : undefined;
}

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.

⚠️ Potential issue | 🟡 Minor

Avoid truthiness check when deciding whether an item exists

item ? this.deserializeDates(item) : undefined will treat any falsy stored value (0, false, "") as “not found”. Today your entities are objects so this is fine, but for robustness it’s safer to check explicitly:

- const item = this.db!.data[collection][id];
- return item ? this.deserializeDates(item) : undefined;
+ const item = this.db!.data[collection][id];
+ return item === undefined ? undefined : this.deserializeDates(item);
🤖 Prompt for AI Agents
In apps/cli/src/lib/storage/lowdb-adapter.ts around lines 133 to 141, the code
uses a truthiness check (item ? this.deserializeDates(item) : undefined) which
treats falsy stored values like 0, false, or "" as “not found”; change the
conditional to explicitly check for null/undefined (e.g., if (item !== undefined
&& item !== null) return this.deserializeDates(item); else return undefined) so
valid falsy values are returned correctly while still returning undefined only
for missing entries.

@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.

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/cli/src/lib/orchestrators/__tests__/workspace-orchestrator.test.ts (1)

155-159: Remove redundant createdAt field from test data.

The createdAt field is automatically set by ChangeOrchestrator.create() and the passed value is ignored. Including it in the test data may confuse readers into thinking the provided timestamp is used.

Apply this diff to remove the redundant field:

 const change = await changeOrchestrator.create({
 	workspaceId: workspace.id,
 	summary: "Test change",
-	createdAt: new Date(),
 });

Also applies to: 174-178

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8902c38 and 62fb568.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • apps/cli/package.json (2 hunks)
  • apps/cli/src/lib/orchestrators/__tests__/change-orchestrator.test.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/__tests__/environment-orchestrator.test.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/__tests__/process-orchestrator.test.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/__tests__/workspace-orchestrator.test.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/change-orchestrator.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/environment-orchestrator.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/process-orchestrator.ts (1 hunks)
  • apps/cli/src/lib/orchestrators/workspace-orchestrator.ts (1 hunks)
  • apps/cli/tsconfig.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/cli/src/lib/orchestrators/tests/environment-orchestrator.test.ts
  • apps/cli/src/lib/orchestrators/change-orchestrator.ts
  • apps/cli/src/lib/orchestrators/workspace-orchestrator.ts
  • apps/cli/src/lib/orchestrators/tests/change-orchestrator.test.ts
  • apps/cli/src/lib/orchestrators/environment-orchestrator.ts
🧰 Additional context used
🧬 Code graph analysis (3)
apps/cli/src/lib/orchestrators/__tests__/workspace-orchestrator.test.ts (5)
apps/cli/src/lib/storage/lowdb-adapter.ts (1)
  • LowdbAdapter (17-178)
apps/cli/src/lib/orchestrators/workspace-orchestrator.ts (1)
  • WorkspaceOrchestrator (14-115)
apps/cli/src/lib/orchestrators/environment-orchestrator.ts (1)
  • EnvironmentOrchestrator (12-110)
apps/cli/src/lib/orchestrators/change-orchestrator.ts (1)
  • ChangeOrchestrator (12-60)
apps/cli/src/types/workspace.ts (1)
  • LocalWorkspace (12-15)
apps/cli/src/lib/orchestrators/__tests__/process-orchestrator.test.ts (5)
apps/cli/src/lib/storage/lowdb-adapter.ts (1)
  • LowdbAdapter (17-178)
apps/cli/src/lib/orchestrators/process-orchestrator.ts (1)
  • ProcessOrchestrator (17-135)
apps/cli/src/lib/orchestrators/workspace-orchestrator.ts (1)
  • WorkspaceOrchestrator (14-115)
apps/cli/src/lib/orchestrators/environment-orchestrator.ts (1)
  • EnvironmentOrchestrator (12-110)
apps/cli/src/types/process.ts (1)
  • Agent (29-32)
apps/cli/src/lib/orchestrators/process-orchestrator.ts (2)
apps/cli/src/lib/storage/adapter.ts (1)
  • StorageAdapter (7-62)
apps/cli/src/types/process.ts (3)
  • Process (8-18)
  • Agent (29-32)
  • Terminal (20-22)
🔇 Additional comments (10)
apps/cli/tsconfig.json (1)

7-7: LGTM: Bun types configuration aligned with storage layer migration.

Adding Bun type definitions to the compiler options correctly enables TypeScript type resolution for the Bun runtime environment that the new persistence layer (lowdb-adapter) and orchestrators depend on.

apps/cli/package.json (2)

26-26: LGTM: Appropriate runtime dependency for persistence layer.

lowdb is a lightweight JSON database well-suited for the persistence layer (StorageAdapter + LowdbAdapter) introduced in this PR. The semantic version constraint (^7.0.1) is reasonable.


35-35: bun-types@1.3.2 is verified as the latest stable version with no known vulnerabilities.

bun-types 1.3.2 is the latest stable version as of Nov 18, 2025, and there are no known direct security vulnerabilities affecting this version in public vulnerability databases. The dependency specification is correct and up-to-date.

apps/cli/src/lib/orchestrators/__tests__/workspace-orchestrator.test.ts (1)

1-196: Comprehensive test coverage for WorkspaceOrchestrator.

The test suite thoroughly covers CRUD operations, filtering, error handling, and cascade deletion behavior. Test isolation via temporary storage is properly implemented.

apps/cli/src/lib/orchestrators/__tests__/process-orchestrator.test.ts (3)

170-198: Excellent test for immutable field protection.

This test effectively verifies that immutable fields (id, workspaceId, createdAt) cannot be modified through the update method, which is essential for maintaining data integrity.


267-288: Well-designed idempotence test.

This test ensures stopAll doesn't modify already-stopped processes, which is important for avoiding unintended timestamp updates and maintaining accurate audit trails.


1-335: Comprehensive test coverage for ProcessOrchestrator.

The test suite thoroughly covers all lifecycle operations (create, get, list, update, stop, stopAll, delete), cascade deletion behavior, and edge cases like idempotence. The test structure demonstrates best practices with proper isolation and cleanup.

apps/cli/src/lib/orchestrators/process-orchestrator.ts (3)

69-81: Immutable field protection successfully implemented.

The past review comment has been addressed. The update method now properly filters out immutable fields (id, workspaceId, createdAt) before merging updates, preventing record desynchronization.


91-94: Duck-typing approach for agent detection is acceptable.

The "status" in updated check effectively distinguishes agents from other process types. While checking process.type === ProcessType.AGENT would be more explicit, the current approach is valid given the type hierarchy and is consistently applied.

Also applies to: 112-114


1-135: Well-structured orchestrator with proper CRUD and lifecycle management.

The implementation correctly handles process lifecycle operations, maintains immutable field integrity, and implements cascade deletion for related entities. The code is clean and follows consistent patterns with other orchestrators.

@Kitenite
Kitenite merged commit 89561af into main Nov 18, 2025
0 of 5 checks passed
@Kitenite
Kitenite deleted the persistence branch November 18, 2025 07:47
@coderabbitai coderabbitai Bot mentioned this pull request Nov 20, 2025
4 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Nov 21, 2025
2 of 5 tasks
This was referenced Nov 21, 2025
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.

1 participant