# kollabor-cli Monorepo Restructure

## Overview

Restructured kollabor-cli from a single Python project into a uv workspace
monorepo with four publishable, reusable packages. Others can now
`pip install kollabor-ai`, `pip install kollabor-events`,
`pip install kollabor-agent`, or `pip install kollabor-tui` independently.

Packages extracted:
- `kollabor-ai` - LLM providers, adapters, response parser
- `kollabor-events` - event bus, hook system, data models
- `kollabor-agent` - tool executor, MCP, file ops, shell
- `kollabor-tui` - terminal state, key parser, visual effects, design system

Additionally, `llm_service.py` was decomposed from 3149 lines to 1070 lines
(66% reduction) by extracting 8 focused modules.

Key commits:
- `dd85c7f` phases 0-2 (workspace + kollabor-ai + kollabor-events)
- `268a926` phase 3 (import rewiring across 76 files)
- `32c04a3` phase 4 (kollabor-agent extraction)
- `b3e1797` phase 4.5A (BackgroundTaskManager)
- `9825a06` phase 4.5B-C (SystemPromptBuilder + StreamingHandler + NativeToolsHandler)
- `6174168` phases 4.5D-F (QueueProcessor + MessageHandler + StatusService)
- `21e7558` phase 4.5G (simplify LLMService __init__)
- `23470e2` phase 5A (kollabor-tui extraction)
- `4a405be` test cleanup (removed 21 broken/stale test files)
- `8c8cff3` new unit tests for all extracted modules

## Phase 0: uv Workspace Infrastructure (COMPLETE)

Converted from setuptools-only to uv workspaces. Zero code changes.

Changes to pyproject.toml:
- Added `[tool.uv.workspace]` with `members = ["packages/*"]`
- Added `[tool.uv]` dev-dependencies section
- Added `[tool.uv.sources]` for workspace package resolution
- Added `httpx>=0.27.0` to dependencies (was missing, gemini_provider needs it)
- Created `packages/` directory
- Generated `uv.lock`

## Phase 1: Extract kollabor-ai (COMPLETE)

Moved all LLM provider code into a standalone package.

Package: `packages/kollabor-ai/` (import as `kollabor_ai`)
Build: hatchling
Deps: pydantic>=2.0.0, aiohttp>=3.10.11, httpx>=0.27.0

Files moved:
```
kollabor/llm/providers/*.py        -> packages/kollabor-ai/src/kollabor_ai/providers/
kollabor/llm/api_adapters/*.py     -> packages/kollabor-ai/src/kollabor_ai/adapters/
kollabor/llm/response_parser.py    -> packages/kollabor-ai/src/kollabor_ai/response_parser.py
```

Package structure:
```
packages/kollabor-ai/src/kollabor_ai/
  __init__.py                    FileOperationParser, ResponseParser
  response_parser.py             moved from kollabor/llm/
  providers/
    __init__.py                  all exports + auto-registration imports
    models.py                    ProviderType, configs, streaming models
    errors.py                    ProviderError hierarchy + mappers
    base.py                      LLMProvider ABC
    registry.py                  ProviderRegistry, @register_provider
    security.py                  4-tier API key storage
    transformers.py              response transformers
    openai_responses_transformer.py
    gemini_transformer.py
    openai_provider.py           7 provider implementations
    anthropic_provider.py
    azure_provider.py
    custom_provider.py
    openrouter_provider.py
    openai_responses_provider.py
    gemini_provider.py
  adapters/
    __init__.py                  exports + get_adapter() factory
    base.py                      BaseAPIAdapter ABC
    openai_adapter.py
    anthropic_adapter.py
```

## Phase 2: Extract kollabor-events (COMPLETE)

Moved the event bus, hook system, and ConversationMessage into a standalone package.

Package: `packages/kollabor-events/` (import as `kollabor_events`)
Build: hatchling
Deps: none (stdlib only)

Files moved:
```
kollabor/events/models.py          -> packages/kollabor-events/src/kollabor_events/models.py
kollabor/events/bus.py             -> packages/kollabor-events/src/kollabor_events/bus.py
kollabor/events/registry.py        -> packages/kollabor-events/src/kollabor_events/registry.py
kollabor/events/executor.py        -> packages/kollabor-events/src/kollabor_events/executor.py
kollabor/events/processor.py       -> packages/kollabor-events/src/kollabor_events/processor.py
kollabor/events/hook_adapter.py    -> packages/kollabor-events/src/kollabor_events/hook_adapter.py
kollabor/events/ready_message.py   -> packages/kollabor-events/src/kollabor_events/ready_message.py
kollabor/models/base.py            -> packages/kollabor-events/src/kollabor_events/data_models.py
```

New: `_utils.py` - inlined `log_and_continue()` and `safe_execute()` from
`kollabor/utils/error_utils.py` (the ONE external dependency).

Package structure:
```
packages/kollabor-events/src/kollabor_events/
  __init__.py                    all public exports
  _utils.py                      inlined log_and_continue, safe_execute
  models.py                      EventType (165 values), Hook, Event, commands
  bus.py                         EventBus
  registry.py                    HookRegistry
  executor.py                    HookExecutor
  processor.py                   EventProcessor
  hook_adapter.py                HookAdapter
  ready_message.py               ReadyMessageCollector
  data_models.py                 ConversationMessage
```

## Phase 3: Rewire Imports (COMPLETE)

Replaced all `from kollabor.*` and relative shim-routed imports with direct
package imports across 76 files. Used 4 parallel agents for the bulk work.

Files rewired:
- kollabor/ (30 files: application, commands, fullscreen, io, llm, ui, config, utils)
- plugins/ (9 files)
- tests/unit/ (27 files)
- tests/integration/ (8 files)
- tests/fixes/ (2 files)

Import mapping:
```
from kollabor.events.*            -> from kollabor_events.*
from kollabor.llm.providers.*    -> from kollabor_ai.providers.*
from kollabor.llm.api_adapters   -> from kollabor_ai.adapters
from kollabor.llm.response_parser -> from kollabor_ai.response_parser
from kollabor.models.*           -> from kollabor_events.data_models
```

DO NOT CHANGE:
- `from kollabor.models.resume import` (resume module was NOT extracted)
- `from .models import` in kollabor/llm/permissions/ (different models module)
- `patch('kollabor.llm.api_communication_service.ProviderRegistry')` in tests

## Phase 4: Extract kollabor-agent (COMPLETE)

Moved agent execution modules into a standalone package.

Package: `packages/kollabor-agent/` (import as `kollabor_agent`)
Build: hatchling
Deps: kollabor-events

Files moved:
```
kollabor/llm/tool_executor.py              -> packages/kollabor-agent/src/kollabor_agent/
kollabor/llm/file_operations_executor.py   -> packages/kollabor-agent/src/kollabor_agent/
kollabor/llm/mcp_integration.py            -> packages/kollabor-agent/src/kollabor_agent/
kollabor/llm/shell_command_service.py      -> packages/kollabor-agent/src/kollabor_agent/
kollabor/utils/shell_executor.py           -> packages/kollabor-agent/src/kollabor_agent/
```

Key finding: all 5 modules received dependencies via constructor injection
so extraction was trivial despite deep integration with the app.

## Phase 4.5: Refactor llm_service.py (COMPLETE)

llm_service.py was a 3149-line god object with 61 methods doing 10 jobs.
Extracted 8 focused modules into kollabor/llm/. Full spec at:
docs/specs/llm-service-refactor-spec.md

Sub-phases:

Phase A - BackgroundTaskManager (498 lines)
  - Task tracking and lifecycle (create, monitor, cancel, wait)
  - Circuit breaker pattern for task error containment

Phase B - SystemPromptBuilder (272 lines) + StreamingHandler (301 lines) + NativeToolsHandler (200 lines)
  - System prompt construction, rebuilding, plugin additions
  - Streaming LLM responses with think-tag handling
  - Native tool definition management

Phase C - SessionManager (228 lines)
  - Session lifecycle (init, restart, context setting)

Phase D - QueueProcessor (634 lines)
  - Message queue management and overflow strategies
  - Main conversation loop (enqueue, process, continue)

Phase E - MessageHandler (369 lines)
  - Event bus message handlers (user input, cancel, context injection)

Phase F - StatusService (236 lines)
  - Status line generation and queue metrics

Phase G - Simplify __init__
  - Broke 295-line monolithic __init__ into 7 focused _init_* methods

Final state:
- llm_service.py: 1070 lines (was 3149, 66% reduction)
- 8 extracted modules: ~2740 lines total
- llm_service.py is now pure orchestration + thin forwarding wrappers

Gotchas found:
- NativeToolsHandler must be created AFTER api_service in __init__ (ordering)
- Tests patch `kollabor.llm.llm_service.Path` so keep Path import even if unused
- execute_tool_calls() takes tool_executor as param to avoid circular dep
- Properties needed for native_tools/mcp_discovery_complete (many internal refs)

## Phase 5: Extract kollabor-tui (COMPLETE)

Moved terminal UI primitives into a standalone package with zero external deps.

Package: `packages/kollabor-tui/` (import as `kollabor_tui`)
Build: hatchling
Deps: none

### Phase 5A: File Extraction

Files moved:
```
kollabor/io/terminal_state.py          -> packages/kollabor-tui/src/kollabor_tui/
kollabor/io/key_parser.py              -> packages/kollabor-tui/src/kollabor_tui/
kollabor/io/visual_effects.py          -> packages/kollabor-tui/src/kollabor_tui/
kollabor/ui/design_system/*.py         -> packages/kollabor-tui/src/kollabor_tui/design_system/
```

Internal import fixes:
```
# visual_effects.py:
from kollabor.ui.design_system -> from .design_system
from kollabor.io.terminal_state -> from .terminal_state

# theme.py:
from kollabor.utils.config_utils import get_config_directory -> Path.home() / ".kollabor-cli"
```

Backward-compat shims at original paths (kollabor/io/terminal_state.py, etc.)

### Phase 5B: Import Rewiring

Rewrote all imports to direct `kollabor_tui` imports across ~45 files.

Import mapping:
```
from kollabor.io.terminal_state  -> from kollabor_tui.terminal_state
from kollabor.io.key_parser      -> from kollabor_tui.key_parser
from kollabor.io.visual_effects  -> from kollabor_tui.visual_effects
from kollabor.ui.design_system   -> from kollabor_tui.design_system
```

Files rewired:
- kollabor/io/ (15 files)
- kollabor/ui/ (12 files)
- kollabor/fullscreen/ (5 files)
- kollabor/commands/ (2 files)
- kollabor/application.py, kollabor/llm/message_display_service.py
- plugins/ (13 files)
- tests/ (10 files)

## Phase 6: Rename kollabor/ -> kollabor/ (COMPLETE)

Renamed the application directory from `kollabor/` to `kollabor/` to follow
Python naming conventions and match the package name.

Scope:
- Renamed directory: `kollabor/` -> `kollabor/`
- Updated all `from kollabor.` imports to `from kollabor.`
- Updated pyproject.toml package references
- Updated documentation (CLAUDE.md, agent bundles, scripts)
- No functional changes, pure rename

## Test Baseline

After all phases + test cleanup:
- 1015 passed, 0 failed, 82 skipped
- Runtime: ~9.3s
- Skipped tests: missing optional deps (cryptography, openai, hypothesis)

Test cleanup performed:
- Removed 21 broken/stale test files (-11,771 lines)
- Wrote fresh tests for all extracted modules (7 LLM + 3 TUI files)
- Fixed exponential backoff in tests (patched asyncio.sleep, 222s -> 9.3s)
- Fixed timezone-aware vs naive datetime comparison in conversation sorting

## What Stays in kollabor/ (Too Coupled to Extract)

- `kollabor/llm/llm_service.py` (1070 lines - pure orchestration)
- `kollabor/llm/*.py` (8 extracted modules that stay in kollabor/llm/)
- `kollabor/io/` (terminal renderer, input handler, layout - coupled to ui/)
- `kollabor/config/` (loader pulls in prompt_renderer, plugin_config)
- `kollabor/commands/` (coupled to events, io)
- `kollabor/ui/` (modals, widgets - coupled to io/)
- `kollabor/plugins/` (coupled to everything)
- `kollabor/application.py` (main orchestrator)

Note: terminal_state, key_parser, visual_effects, and design_system
were extracted to kollabor-tui. Shims remain at original paths.

Note: `kollabor/` was renamed to `kollabor/` in Phase 6 to follow Python
naming conventions and match the package name.

## Root pyproject.toml State

```toml
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
dependencies = [
    "aiohttp>=3.10.11",
    "httpx>=0.27.0",
    "kollabor-ai",
    "kollabor-agent",
    "kollabor-events",
    "kollabor-tui",
    "psutil>=5.9.0",
    "packaging>=23.0",
    "pydantic>=2.0.0",
]

[tool.uv.sources]
kollabor-ai = { workspace = true }
kollabor-agent = { workspace = true }
kollabor-events = { workspace = true }
kollabor-tui = { workspace = true }

[tool.uv.workspace]
members = ["packages/*"]
```

## All Phases Complete

Phases 0-6 are done. The monorepo restructure is finished.

## Architecture Decisions

### No Shared Utils Package
Anti-pattern that becomes a dump. Instead:
- `log_and_continue`: inlined into kollabor-events/_utils.py
- Everything else stays in kollabor/utils/
- Future packages inline or copy what they need

### All 165 EventType Values Stay Together
Splitting into "core" vs "app-specific" creates more pain than it
solves. Apps that extend can use string-typed events.

### Shim Strategy
Every extracted module has a backward-compat shim at the original
path. This means:
- Zero breakage for existing code
- Gradual migration possible
- Third-party plugins continue to work
- Shims can later add deprecation warnings

### Provider Auto-Registration
Providers use `@register_provider` decorator triggered by import.
The `__init__.py` in both the package and the shim imports all
providers to trigger registration. This pattern is preserved.

### uv + setuptools Coexistence
uv respects the setuptools build-backend. The root app package
keeps setuptools while extracted packages use hatchling. Both
work under `uv sync`.
