Skip to content

refactor(services): centralize workspace determination in resolve_workspace_context (#91)#96

Open
bradjin8 wants to merge 4 commits into
masterfrom
feat/workspace-determination-ceremony-extraction
Open

refactor(services): centralize workspace determination in resolve_workspace_context (#91)#96
bradjin8 wants to merge 4 commits into
masterfrom
feat/workspace-determination-ceremony-extraction

Conversation

@bradjin8
Copy link
Copy Markdown
Collaborator

@bradjin8 bradjin8 commented Jun 4, 2026

Summary

  • Adds services/workspace_context.py with a typed WorkspaceContext dataclass and resolve_workspace_context() orchestrator that runs the shared workspace-determination ceremony (entries, invalid IDs, project-name/path maps, composer→workspace map).
  • Adds enrich_workspace_context_from_global_db() for optional global KV maps (project_layouts_map, bubble_map) after open_global_db.
  • Refactors all four consumers to use the shared orchestrator instead of duplicating the 6-function setup:
    • services/workspace_listing.py
    • services/workspace_tabs.py (summaries, single-tab, full tabs)
    • scripts/export.py
    • api/export_api.py (minimal context: skips invalid/path maps; enriches bubbles only)
  • Adds tests/test_workspace_context.py for orchestrator behavior and flag handling.

Closes #91.

Test plan

  • python -m pytest tests/test_workspace_context.py -q
  • python -m pytest -q (full suite: 393 passed, 4 skipped)
  • Manual smoke: GET /api/workspaces and GET /api/workspaces/<id>/tabs unchanged
  • Manual smoke: POST /api/export and python scripts/export.py still assign chats to the expected workspace folders

Summary by CodeRabbit

  • Refactor

    • Centralized workspace discovery and optional global-database enrichment into a unified workspace context service for consistent resolution.
  • Refactor

    • Export tooling and CLI now consume the simplified workspace context (including a minimal variant for HTTP export) instead of bespoke scanning logic.
  • Refactor

    • Workspace listing and tab assembly now read mappings from the centralized context; message/context decoding and project layout extraction standardized.
  • Tests

    • Added end-to-end tests for context resolution, caching behavior, pre-collected entries, invalid-workspace detection, and DB-driven enrichment.

@bradjin8 bradjin8 self-assigned this Jun 4, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 16a11bdc-0495-48a1-8b0a-c365342c0387

📥 Commits

Reviewing files that changed from the base of the PR and between 14ddb43 and d14ad1f.

📒 Files selected for processing (2)
  • services/workspace_context.py
  • tests/test_workspace_context.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • services/workspace_context.py

📝 Walkthrough

Walkthrough

Centralizes workspace discovery into a frozen WorkspaceContext with resolve/enrich entrypoints; consumers (scripts/export, api/export_api, services/workspace_listing, services/workspace_tabs) now call the resolvers; tests validate resolution, caching, short-circuiting, and SQLite-based enrichment.

Changes

Workspace context orchestration

Layer / File(s) Summary
Core workspace context abstraction
services/workspace_context.py
WorkspaceContext dataclass holds workspace entries, invalid IDs, and derived lookup maps. resolve_workspace_context()/resolve_workspace_context_cached()/resolve_workspace_context_minimal() produce consistent context objects; enrich_workspace_context_from_global_db() conditionally loads project layouts and bubble map.
Workspace context test coverage
tests/test_workspace_context.py
Tests validate minimal and full resolution, invalid-workspace detection, cached composer-map behavior, acceptance of pre-collected entries, short-circuiting, and enrichment from an in-memory SQLite cursorDiskKV table.
Export script consumer refactor
scripts/export.py
Replaces inline workspace discovery and map building with resolve_workspace_context() and uses enrich_workspace_context_from_global_db() to populate project_layouts_map and bubble_map.
Export API consumer refactor
api/export_api.py
Replaces export route workspace scanning with resolve_workspace_context_minimal() and reads workspace_entries and composer_id_to_workspace_id from the returned context.
Workspace listing consumer refactor
services/workspace_listing.py
Replaces inline workspace entry collection and map building with resolve_workspace_context_cached() and consumes invalid IDs and lookup maps from the context.
Workspace tabs consumer refactor
services/workspace_tabs.py
Refactors _build_workspace_tab_summaries_uncached, assemble_single_tab, and assemble_workspace_tabs to use resolve_workspace_context_cached(); updates messageRequestContext:% KV parsing to _loads_kv_value_logged and derives project layouts from parsed KV.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant resolve_workspace_context
  participant collect_workspace_entries
  participant build_composer_id_map_cached
  participant build_composer_id_map_uncached
  participant build_project_workspace_maps
  participant global_db
  Caller->>resolve_workspace_context: workspace_path, rules?, workspace_entries?
  resolve_workspace_context->>collect_workspace_entries: collect entries (if not provided)
  collect_workspace_entries-->>resolve_workspace_context: workspace_entries
  resolve_workspace_context->>build_composer_id_map_cached: build cached composer map (if rules)
  resolve_workspace_context->>build_composer_id_map_uncached: build uncached composer map (otherwise)
  build_composer_id_map_cached-->>resolve_workspace_context: composer_id_to_workspace_id
  build_composer_id_map_uncached-->>resolve_workspace_context: composer_id_to_workspace_id
  resolve_workspace_context->>build_project_workspace_maps: derive project_name & workspace_path maps
  build_project_workspace_maps-->>resolve_workspace_context: project_name_to_workspace_id, workspace_path_to_id
  alt populate_project_layouts or populate_bubble_map
    resolve_workspace_context->>global_db: load project_layouts_map / bubble_map
    global_db-->>resolve_workspace_context: project_layouts_map / bubble_map
  end
  resolve_workspace_context-->>Caller: WorkspaceContext
  Caller->>enrich_workspace_context_from_global_db: ctx, global_db, flags
  enrich_workspace_context_from_global_db->>global_db: query cursorDiskKV for requested keys
  global_db-->>enrich_workspace_context_from_global_db: serialized KV blobs
  enrich_workspace_context_from_global_db-->>Caller: enriched WorkspaceContext
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • clean6378-max-it
  • timon0305
  • wpak-ai

Poem

🐰 I hopped through folders, maps, and keys,
Collected entries with nimble knees.
One context now holds each bubble and name,
Four callers hop home and call it the same.
The rabbit smiles — no duplication remains.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.63% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly describes the main change: refactoring to centralize workspace determination in a new resolve_workspace_context function, directly addressing issue #91.
Linked Issues check ✅ Passed The PR successfully implements all acceptance criteria from issue #91: creates a WorkspaceContext dataclass, provides resolve_workspace_context() orchestrator, refactors all four call sites, and adds comprehensive tests demonstrating behavior preservation.
Out of Scope Changes check ✅ Passed All changes directly support the issue #91 objectives: the new workspace_context module, refactored consumers, and test coverage are precisely scoped to the workspace-determination ceremony consolidation without introducing unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 feat/workspace-determination-ceremony-extraction

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
services/workspace_context.py (1)

98-104: 💤 Low value

Silent no-op when populate_* flags are True but global_db is None.

If a caller passes populate_project_layouts=True or populate_bubble_map=True without providing global_db, the maps remain empty with no warning. This could mask misconfiguration where the caller expects data but receives empty maps.

Consider either:

  1. Raising ValueError when a populate flag is True but global_db is None, or
  2. Logging a warning to aid debugging.
Option 1: Raise on misconfiguration
     project_layouts: dict[str, list] = {}
     bubble_map: dict[str, dict] = {}
+    if (populate_project_layouts or populate_bubble_map) and global_db is None:
+        raise ValueError(
+            "global_db is required when populate_project_layouts or populate_bubble_map is True"
+        )
     if global_db is not None:
         if populate_project_layouts:
             project_layouts = load_project_layouts_map(global_db)
🤖 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 `@services/workspace_context.py` around lines 98 - 104, If
populate_project_layouts or populate_bubble_map is True while global_db is None,
raise a ValueError (instead of silently returning empty maps) so callers are
alerted to the misconfiguration; update the logic around project_layouts,
bubble_map, populate_project_layouts, populate_bubble_map and global_db to check
for this case and raise a clear error message referencing which flag was set,
and keep existing calls to load_project_layouts_map and load_bubble_map
unchanged for the normal (global_db is not None) path.
🤖 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 `@services/workspace_context.py`:
- Around line 98-104: If populate_project_layouts or populate_bubble_map is True
while global_db is None, raise a ValueError (instead of silently returning empty
maps) so callers are alerted to the misconfiguration; update the logic around
project_layouts, bubble_map, populate_project_layouts, populate_bubble_map and
global_db to check for this case and raise a clear error message referencing
which flag was set, and keep existing calls to load_project_layouts_map and
load_bubble_map unchanged for the normal (global_db is not None) path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 248e1efb-633a-46c3-8df2-e04bd134a69e

📥 Commits

Reviewing files that changed from the base of the PR and between 08667a5 and 183a4a8.

📒 Files selected for processing (6)
  • api/export_api.py
  • scripts/export.py
  • services/workspace_context.py
  • services/workspace_listing.py
  • services/workspace_tabs.py
  • tests/test_workspace_context.py

@bradjin8 bradjin8 requested a review from timon0305 June 4, 2026 16:02
Copy link
Copy Markdown
Collaborator

@timon0305 timon0305 left a comment

Choose a reason for hiding this comment

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

Nice consolidation — four call sites going through one orchestrator reads cleanly. A few thoughts on the surface area inline.

Comment thread services/workspace_context.py Outdated
Comment thread services/workspace_context.py Outdated
Comment thread services/workspace_context.py Outdated
Comment thread api/export_api.py Outdated
Comment thread tests/test_workspace_context.py
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
services/workspace_context.py (1)

98-108: 💤 Low value

Consider accepting workspace_entries for consistency.

Unlike resolve_workspace_context() and resolve_workspace_context_cached(), the minimal resolver doesn't accept a workspace_entries parameter. If a caller already has collected entries, they cannot reuse them with this function, forcing redundant collection or requiring them to use a different resolver and ignore the extra fields.

♻️ Optional refactor for API consistency
-def resolve_workspace_context_minimal(workspace_path: str) -> WorkspaceContext:
+def resolve_workspace_context_minimal(
+    workspace_path: str,
+    *,
+    workspace_entries: list[dict] | None = None,
+) -> WorkspaceContext:
     """Entries, project-name, and composer maps only (HTTP export)."""
-    entries = collect_workspace_entries(workspace_path)
+    entries = _entries(workspace_path, workspace_entries)
     return _assemble_context(
🤖 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 `@services/workspace_context.py` around lines 98 - 108,
resolve_workspace_context_minimal currently always calls
collect_workspace_entries forcing redundant work; change its signature to accept
an optional workspace_entries parameter (e.g., workspace_entries:
Optional[Iterable[WorkspaceEntry]] = None), update the body to use the provided
workspace_entries when non-None and only call
collect_workspace_entries(workspace_path) otherwise, and keep the rest of the
function calling _assemble_context with the entries; mirror the parameter
name/semantics used by resolve_workspace_context and
resolve_workspace_context_cached and update the docstring to mention the
optional pre-collected entries.
tests/test_workspace_context.py (1)

1-171: ⚡ Quick win

Consider adding tests for remaining enrichment and caching scenarios.

Current coverage is solid, but a few additional scenarios would strengthen confidence:

  1. nocache=True: Test that resolve_workspace_context_cached(..., nocache=True) properly bypasses the cache.
  2. Both enrichment flags: Test enrich_workspace_context_from_global_db with both populate_project_layouts=True and populate_bubble_map=True to verify both maps are populated simultaneously.
  3. No-op enrichment: Test enrich_workspace_context_from_global_db with both flags False (or omitted) to verify the original context is returned unchanged (line 124-125 path).
🤖 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 `@tests/test_workspace_context.py` around lines 1 - 171, Add three unit tests:
one exercising resolve_workspace_context_cached with nocache=True to assert
build_composer_id_to_workspace_id is called (not cached path) and cached builder
is not used; one calling enrich_workspace_context_from_global_db with both
populate_project_layouts=True and populate_bubble_map=True and asserting both
enriched.project_layouts_map and enriched.bubble_map are populated; and one
calling enrich_workspace_context_from_global_db with both flags False (or
omitted) asserting the returned context equals the original (no changes). Use
the existing helpers (_make_workspace_root, _open_global_db) and the functions
resolve_workspace_context_cached and enrich_workspace_context_from_global_db
from the diff to locate where to add these tests.
🤖 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 `@services/workspace_context.py`:
- Around line 98-108: resolve_workspace_context_minimal currently always calls
collect_workspace_entries forcing redundant work; change its signature to accept
an optional workspace_entries parameter (e.g., workspace_entries:
Optional[Iterable[WorkspaceEntry]] = None), update the body to use the provided
workspace_entries when non-None and only call
collect_workspace_entries(workspace_path) otherwise, and keep the rest of the
function calling _assemble_context with the entries; mirror the parameter
name/semantics used by resolve_workspace_context and
resolve_workspace_context_cached and update the docstring to mention the
optional pre-collected entries.

In `@tests/test_workspace_context.py`:
- Around line 1-171: Add three unit tests: one exercising
resolve_workspace_context_cached with nocache=True to assert
build_composer_id_to_workspace_id is called (not cached path) and cached builder
is not used; one calling enrich_workspace_context_from_global_db with both
populate_project_layouts=True and populate_bubble_map=True and asserting both
enriched.project_layouts_map and enriched.bubble_map are populated; and one
calling enrich_workspace_context_from_global_db with both flags False (or
omitted) asserting the returned context equals the original (no changes). Use
the existing helpers (_make_workspace_root, _open_global_db) and the functions
resolve_workspace_context_cached and enrich_workspace_context_from_global_db
from the diff to locate where to add these tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 93388dd1-75fd-49da-9676-e95f2501622f

📥 Commits

Reviewing files that changed from the base of the PR and between 3430976 and 14ddb43.

📒 Files selected for processing (5)
  • api/export_api.py
  • services/workspace_context.py
  • services/workspace_listing.py
  • services/workspace_tabs.py
  • tests/test_workspace_context.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • services/workspace_listing.py

@bradjin8 bradjin8 requested a review from timon0305 June 5, 2026 01:40
@timon0305 timon0305 requested a review from wpak-ai June 5, 2026 01:45
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.

Workspace determination ceremony extraction

2 participants