# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with this repository.

## Project Overview

**Kollabor CLI Interface** - Terminal-based LLM chat application where **everything has hooks**. Every action triggers customizable hooks that plugins can attach to for complete customization.

## Architecture

Modular, event-driven system:
- **Core Application** (`kollabor/application.py`): Main orchestrator
- **Event System** (`kollabor/events/`): Event bus + hook system
- **LLM Core** (`kollabor/llm/`): LLM services (API, conversation, tools)
- **I/O System** (`kollabor/io/`): Terminal rendering, input handling, layout
- **Plugin System** (`kollabor/plugins/`, `plugins/`): Dynamic discovery and loading
- **Configuration** (`kollabor/config/`): Flexible configuration management

## Key Components

### LLM Core Services (`kollabor/llm/`)
- `llm_service.py` - Main LLM orchestration
- `api_communication_service.py` - API communication with rate limiting
- `conversation_logger.py` - Conversation persistence (KollaborConversationLogger)
- `conversation_manager.py` - Conversation state and history
- `tool_executor.py` - Tool/function calling execution
- `hook_system.py` - LLM-specific hook management
- `message_display_service.py` - Response formatting and display
- `mcp_integration.py` - Model Context Protocol integration
- `plugin_sdk.py` - Plugin development interface (KollaborPluginSDK)
- `model_router.py` - Model selection and routing
- `response_processor.py` - Response processing
- `response_parser.py` - Response parsing (includes Question Gate detection)
- `permissions/` - Tool permission system
  - `manager.py` - Central permission manager with approval modes
  - `risk_assessor.py` - Risk-based assessment with pattern matching
  - `hook.py` - Event bus integration at SECURITY priority
  - `models.py` - Data structures (ApprovalMode, ToolRiskLevel, PermissionDecision)
  - `config.py` - Configuration defaults
  - `response_handler.py` - User response processing

### Terminal I/O System (`kollabor/io/`)
- `terminal_renderer.py` - Main terminal rendering with status areas
- `input_handler.py` - Raw mode input handling with key parsing
- `layout.py` - Terminal layout management and thinking animations
- `visual_effects.py` - Color palettes, visual effects, terminal color detection
- `status_renderer.py` - Multi-area status display
- `message_coordinator.py` - **CRITICAL** - Message flow AND render state coordination
- `message_renderer.py` - Message display rendering
- `buffer_manager.py` - Terminal buffer management
- `key_parser.py` - Keyboard input parsing
- `terminal_state.py` - Terminal state management
- `core_status_views.py` - Core status view implementations
- `config_status_view.py` - Configuration status display

#### CRITICAL: Render State Management Rules

**NEVER directly manipulate these `terminal_renderer` properties:**
- `input_line_written`, `last_line_count`, `_last_render_content`, `writing_messages`

**ALWAYS use `MessageDisplayCoordinator` methods:**

```python
# Display messages
renderer.message_coordinator.display_message_sequence([...])

# Modal/fullscreen transitions
renderer.message_coordinator.enter_alternate_buffer()  # Before opening modal
renderer.message_coordinator.exit_alternate_buffer()  # After modal closes
renderer.message_coordinator.exit_alternate_buffer(restore_state=True)  # Restore state

# Debug queue status
renderer.message_coordinator.get_queue_status()
```

**Why:** Direct manipulation causes duplicate input boxes, stale renders, and incorrect clearing. The coordinator uses flag-based coordination - `enter_alternate_buffer()` sets `writing_messages=True` to block the render loop.

**Modal exit patterns:**
```python
# Standard exit - restores state and renders input
await self._exit_modal_mode()

# Minimal exit - for commands displaying their own content
await self._exit_modal_mode_minimal()
```

**Safe direct calls:**
- `terminal_renderer.clear_active_area()` - uses state correctly
- `terminal_renderer.invalidate_render_cache()` - just clears cache

### Modal/Overlay Pattern for Status Widgets

**CRITICAL: Any fullscreen modal MUST use the coordinator pattern.**

```python
async def _show_my_modal(self) -> bool:
    self.coordinator.enter_alternate_buffer()
    try:
        sys.stdout.write('\033[?1049h')  # Enter alternate buffer
        sys.stdout.write('\033[?25l')    # Hide cursor
        sys.stdout.flush()
        # ... render and handle input ...
    finally:
        sys.stdout.write('\033[?25h')    # Show cursor
        sys.stdout.write('\033[?1049l')  # Exit alternate buffer
        sys.stdout.flush()
        self.coordinator.exit_alternate_buffer(restore_state=True)
    return result
```

**Why:** `coordinator.enter_alternate_buffer()` pauses the render loop, `exit_alternate_buffer()` resets render state. Without this, artifacts remain after modal closes. The ANSI alternate buffer preserves main screen content.

**Note:** Inline editors (InlineEditorService) edit in place using `render_active_area()` and don't need this pattern.

### Plugin Architecture
- Plugin discovery from `plugins/` directory
- Dynamic instantiation with dependency injection
- Hook registration for event interception
- Configuration merging from plugin configs

### UI Design System (`kollabor_tui.design_system`)

**CRITICAL: Always use existing UI components. Never invent new patterns.**

**Core Imports:**
```python
from kollabor_tui.design_system import T, S, C, Box, TagBox, solid, solid_fg, gradient
```

**Theme Access (`T()`):**
- `T().primary`, `T().dark`, `T().text`, `T().text_dim`
- `T().success`, `T().error`, `T().warning`

**Style Constants (`S`):**
- `S.BOLD`, `S.RESET_BOLD`, `S.DIM`, `S.RESET_DIM`, `S.UNDERLINE`, `S.RESET_UNDERLINE`

**Box Rendering (solid block style ▄▀):**
```python
solid_fg("▄" * width, T().dark[0])              # Top edge
solid(f"   {text:<{w}}", T().dark[0], T().text, w)  # Content
solid_fg("▀" * width, T().dark[0])              # Bottom edge
```

**TagBox (tag + content pattern):**
```python
TagBox.render(
    lines=["content"],
    tag_bg=T().primary[0], tag_fg=T().text_dark, tag_width=3,
    content_colors=T().dark[0], content_fg=T().text,
    content_width=width - 7,
    tag_chars=[" > "],
)
```

**Widgets (`kollabor/ui/widgets/`):**
`CheckboxWidget`, `DropdownWidget`, `SliderWidget`, `TextInputWidget`, `LabelWidget`, `ProgressWidget`, `SpinBoxWidget`

**DO NOT:**
- Create box-drawing with ╭╮╰╯│─ characters (use ▄▀ instead)
- Hardcode colors (use `T()`)
- Access `terminal_state.width` directly (use `get_size()`)
- Invent new widget styles

### Universal Terminal State Management

**CRITICAL: All code must access terminal dimensions through the global state.**

```python
from kollabor_tui.terminal_state import get_terminal_size, get_terminal_width, get_terminal_height

width, height = get_terminal_size()
width = get_terminal_width()
height = get_terminal_height()

# Direct instance
from kollabor_tui.terminal_state import get_global_terminal_state
ts = get_global_terminal_state()
width, height = ts.get_size()
```

**WRONG:**
```python
import shutil
size = shutil.get_terminal_size()  # NO!
import os
size = os.get_terminal_size()  # NO!
```

**For Plugins:**
```python
async def render_frame(self, delta_time: float) -> bool:
    width, height = self.renderer.get_terminal_size()
```

## Development Commands

```bash
# Installation
pip install -e .              # Development mode
pip install -e ".[dev]"       # With dev dependencies
pip install kollabor          # From PyPI

# Running
kollab                        # Installed command
python main.py                # Development mode
echo "query" | kollab -p      # Pipe mode
kollab "query" --timeout 5min # With timeout

# Testing
tests/tmux/lib/test_runner.sh tests/tmux/specs/mcp_memory_usage.json  # JSON test
tests/tmux/run_all_tests.sh    # All tmux tests
python tests/run_tests.py      # Unit tests
python -m unittest tests.test_llm_plugin  # Specific test

# Code quality
python -m black kollabor/ plugins/ tests/ main.py   # Format
python -m mypy kollabor/ plugins/                  # Type check
python -m flake8 kollabor/ plugins/ --max-line-length=88  # Lint
python scripts/clean.py                        # Clean cache

# Building
python scripts/clean.py
python -m build
python -m twine upload --repository testpypi dist/*
```

**Log locations:**
- `~/.kollabor-cli/projects/<encoded-path>/logs/kollabor.log` (daily rotation)
- `~/.kollabor-cli/config.json` (global config)
- `~/.kollabor-cli/projects/<encoded-path>/conversations/` (JSONL)

## Agent Verification Requirements (MANDATORY)

**All agents implementing features MUST create JSON-based tmux verification tests.**

1. **Create JSON test spec** in `tests/tmux/specs/`:
```json
{
  "name": "feature-name",
  "description": "What this test verifies",
  "config": {
    "command": "python main.py",
    "app_init_sleep": 3,
    "show_captures": false
  },
  "steps": [
    { "action": "start_app" },
    { "action": "slash_command", "command": "mcp", "subcommand": "show" },
    { "action": "sleep", "seconds": 1 },
    { "action": "capture" },
    { "action": "assert_contains", "pattern": "MCP|Status" }
  ]
}
```

2. **Available actions:** `start_app`, `slash_command`, `type`, `send_keys`, `capture`, `assert_contains`, `assert_not_contains`, `sleep`, `section`

3. **Run:** `tests/tmux/lib/test_runner.sh tests/tmux/specs/your-test.json`

4. **Report:**
```
implementation complete:
  feature: [name]
  test: tests/tmux/specs/feature-name.json
  result: PASS (X/Y assertions verified)
```

**DO NOT report implementation as complete without a passing test.**

See `tests/tmux/README.md` for complete documentation.

## Entry Points

1. **`kollab` command** (after `pip install`): Entry point `kollab = "kollabor_cli_main:cli_main"` in `pyproject.toml`
2. **`python main.py`** (development): Direct execution from local `kollabor/`

Both initialize `TerminalLLMChat` in `kollabor/application.py`.

## Configuration System

**Dot notation:** `config.get("kollabor.llm.max_history", 90)`

**Global directory (`~/.kollabor-cli/`):**
- `config.json` - User configuration
- `agents/` - Global agent definitions
- `projects/` - Project-specific data

**Project data (`~/.kollabor-cli/projects/<encoded-path>/`):**
- `conversations/` - JSONL logs
- `conversations/raw/` - Raw API logs
- `conversations/memory/` - Intelligence cache
- `conversations/snapshots/` - Snapshots
- `logs/` - Application logs

**Path encoding:** `/Users/dev/myproject` → `Users_dev_myproject`

**Local directory (`.kollabor-cli/` - OPTIONAL):**
- `agents/` - Project-specific agents (override global)

**Agent resolution:** Local → Global

### Dynamic System Prompts with `<trender>` Tags

System prompts support dynamic content rendering at runtime (`kollabor/utils/prompt_renderer.py`):

```markdown
<!-- Project structure -->
<trender type="project_tree" max_depth="3" />

<!-- File listings -->
<trender type="file_list" pattern="**/*.py" exclude="__pycache__" />

<!-- File contents -->
<trender type="file_content" path="README.md" />

<!-- Metadata -->
<trender type="timestamp" format="%Y-%m-%d %H:%M:%S" />
```

**Priority order:** `KOLLABOR_SYSTEM_PROMPT` env var → `KOLLABOR_SYSTEM_PROMPT_FILE` env var → Local `.kollabor-cli/system_prompt/default.md` → Global `~/.kollabor-cli/system_prompt/default.md` → Built-in fallback

## Core Architecture Patterns

### Event-Driven Design
Event bus (`kollabor/events/bus.py`) coordinates:
- **HookRegistry** (`kollabor/events/registry.py`) - Hook registration and lookup
- **HookExecutor** (`kollabor/events/executor.py`) - Hook execution with error handling
- **EventProcessor** (`kollabor/events/processor.py`) - Sequential event processing
- **EventBus** (`kollabor/events/bus.py`) - Central coordinator

### Plugin Lifecycle
1. **Discovery** - `PluginDiscovery` scans `plugins/`
2. **Registry** - `PluginRegistry` maintains metadata
3. **Factory** - `PluginFactory` instantiates with dependency injection
4. **Initialization** - Plugins call `initialize()` and `register_hooks()`
5. **Execution** - Events trigger registered hooks with priority ordering
6. **Cleanup** - Plugins call `shutdown()`

### LLM Service Architecture
`LLMService` (`kollabor/llm/llm_service.py`) orchestrates:
- **APICommunicationService** - HTTP client with rate limiting
- **KollaborConversationLogger** - Persistent conversation history
- **MessageDisplayService** - Response formatting and streaming
- **ToolExecutor** - Function calling execution
- **MCPIntegration** - MCP server discovery
- **KollaborPluginSDK** - Plugin development interface
- **LLMHookSystem** - Request/response interception

### Question Gate Protocol

Suspends tool execution when agent asks clarifying questions using `<question>` tags (prevents runaway loops):

1. Agent includes `<question>...</question>` tag
2. System detects tag, suspends pending tools (stored in `pending_tools`)
3. User responds
4. Suspended tools execute, results injected
5. Agent continues

**Configuration:** `kollabor.llm.question_gate_enabled` (default: `true`)

**Key files:** `response_parser.py` (detection), `llm_service.py` (queue management)

See `docs/features/question-gate-protocol.md`.

### Plugin System Details
**Discovery locations (in order):**
1. `<package_root>/plugins/` (pip install)
2. `./plugins/` (development mode)

**Each plugin can:**
- Register hooks at any priority (CRITICAL, HIGH, NORMAL, LOW)
- Access shared services via dependency injection
- Contribute status line items to areas A, B, or C
- Merge custom configuration into global config
- Register slash commands via CommandRegistry

## Hook System

Plugins can:
- Intercept user input (`pre_user_input`)
- Transform LLM requests (`pre_api_request`)
- Process responses (`post_api_response`)
- Add custom status indicators (`get_status_line()`)
- Create new terminal UI elements

### Config Hooks (JSON, no Python needed)

Claude Code-compatible JSON hooks. Create `.kollabor-cli/hooks.json`:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "file_create|file_edit",
        "hooks": [{ "type": "command", "command": "python guard.py" }]
      }
    ]
  }
}
```

Commands receive event data on stdin (JSON), respond via exit code + stdout JSON.
Exit 0 = proceed, exit 2 = block. Supports Claude Code event names (`PreToolUse`,
`UserPromptSubmit`, etc.) plus Kollabor-native names (`LlmRequestPre`, `McpToolCallPre`, etc.).

**Key file:** `kollabor/config_hooks.py` (ConfigHookLoader)

See `docs/features/config-hooks.md` for full reference.

## Plugin Development

Plugins should:
1. Inherit from base classes in `kollabor/plugins/`
2. Register hooks in `register_hooks()` using `EventType` enum
3. Provide status info via `get_status_line()`
4. Implement `initialize()` and `shutdown()`
5. Use `async def` for all hook handlers

## Project Structure

```
.
├── kollabor/                       # Core application modules
│   ├── application.py             # Main orchestrator
│   ├── config/                    # Configuration management
│   ├── events/                    # Event bus and hook system
│   ├── io/                        # Terminal I/O, rendering, input
│   ├── llm/                       # LLM services (API, tools)
│   ├── models/                    # Data models
│   ├── plugins/                   # Plugin system
│   ├── utils/                     # Utilities
│   ├── commands/                  # Command system
│   ├── ui/                        # UI system (modals, widgets)
│   ├── effects/                   # Visual effects
│   └── logging/                   # Logging configuration
├── plugins/                       # Plugin implementations
│   ├── enhanced_input/           # Enhanced input modules
│   ├── enhanced_input_plugin.py
│   ├── hook_monitoring_plugin.py
│   └── [other plugins]
├── tests/                         # Test suite
│   ├── tmux/                     # JSON-based UI tests
│   │   ├── specs/                # Test specifications
│   │   ├── lib/                  # Test framework
│   │   └── README.md
│   ├── unit/, integration/, visual/
│   └── test_*.py
├── docs/                          # Documentation
│   ├── project-management/
│   ├── reference/
│   ├── sdlc/
│   ├── sop/
│   └── standards/
├── main.py                       # Entry point
└── .github/scripts/              # Repository automation
```

**Runtime data (`~/.kollabor-cli/`):**
```
~/.kollabor-cli/
├── config.json                   # User configuration
├── agents/                       # Global agent definitions
└── projects/                     # Project-specific data
    └── <encoded-path>/
        ├── conversations/        # Conversation history (JSONL)
        └── logs/                 # Application logs
```

## Development Guidelines

### Git Workflow
**Pre-commit:** Commits to `kollabor/`, `plugins/`, `tests/` MUST reference GitHub issue
- Branch: `issue-123-description` or `feature/issue-123-description`
- Commit: `fixes #123`, `closes #123`, `resolves #123`, or `#123`
- Main branch protected - no direct pushes

```bash
gh issue create --title "Fix version display"
git checkout -b issue-8-version-display-fix
git commit -m "Fix version display fixes #8"
git push -u kollaborai issue-8-version-display-fix
gh pr create --title "Fix version display" --body "Fixes #8"
```

### Version Management
Version managed centrally in `pyproject.toml`:
- **Development mode** (`python main.py`): Reads from `pyproject.toml` at runtime
- **Production mode** (pip install): Uses `importlib.metadata.version("kollabor")`

Files reading version: `kollabor/application.py`, `kollabor/cli.py`, `kollabor/config/loader.py`

To update: Only modify `pyproject.toml`.

### Code Standards
- PEP 8, 88-char line length (Black)
- Double quotes for strings, single for chars
- Type hints on public functions
- `async def` for all hooks (even if no await)
- `logging.getLogger(__name__)` for logging

### Async/Await Patterns
- Main event loop: `asyncio.run()` in `cli_main()`
- Concurrent tasks: `asyncio.gather()` for render loop + input handler
- Background tasks: Use `app.create_background_task()` for tracking
- Cleanup: All tasks cancelled in `app.cleanup()` via `finally`
- Plugin hooks: Must be async

### Testing Strategy
- **JSON tmux tests** (`tests/tmux/specs/`) - PRIMARY approach for UI/features
- Unit tests in `tests/unit/`
- Integration tests in `tests/integration/`
- Visual tests in `tests/visual/`
- Component tests (`test_*.py`)

### Known Issues Tracking

**Directory structure:**
```
known_issues/
├── README.md              # Complete guide
├── QUICK_START.md         # Quick reference
├── active/                # Issues needing fixes
├── in_progress/           # Being worked on
├── resolved/              # Fixed issues
├── wont_fix/              # Not being addressed
├── duplicate/             # Duplicate references
└── templates/issue_template.md
```

**Workflow:**
```bash
# Create issue
cp known_issues/templates/issue_template.md known_issues/active/2026-01-17-issue-name.md

# Create JSON test spec in tests/tmux/specs/test_issue_name.json

# Start work
mv known_issues/active/issue.md known_issues/in_progress/

# Mark resolved
mv known_issues/in_progress/issue.md known_issues/resolved/
```

**Each issue must include:** Status, severity, dates, test path, reproduction steps, expected/actual behavior, root cause, impact, fix strategy, related files.

**Best practices:**
1. Create JSON test spec when possible (prevents regressions)
2. Use descriptive test names
3. Document thoroughly with logs/screenshots
4. Update resolved issues with fix details
5. Reference issues in commit messages

### Hook Development
Consider:
- Hook priority (`HookPriority` enum: CRITICAL, HIGH, NORMAL, LOW)
- Error handling - hooks shouldn't crash (caught by HookExecutor)
- Performance - hooks are in hot path
- State management - avoid shared mutable state
- Return modified context from hooks
- All handlers must be async

## Key Features

### Interactive Mode
- Real-time status updates across three areas (A, B, C)
- Thinking animations during LLM processing
- Multi-line input with visual input box
- Conversation history with scrollback
- Plugin-driven extensibility

### Pipe Mode
- `kollab "query"` - Single query and exit
- `echo "query" | kollab -p` - Read from stdin
- `kollab --timeout 5min "task"` - Configurable timeout
- Suppresses interactive UI elements
- Full plugin support (`app.pipe_mode` flag)

### Tool Permission System
- 4 approval modes: CONFIRM_ALL (default), DEFAULT, AUTO_APPROVE_EDITS, TRUST_ALL
- Risk-based assessment with pattern matching
- Inline permission prompts (no modal interruption)
- Session-scoped approvals
- `/permissions` command for runtime management
- Event bus integration at SECURITY priority (900)

**Permission keys:** `a` approve once, `s` session, `p` project, `d` deny, `ESC` cancel
**Context-specific:** `A` always edits, `t` trust tool, `c` cancel

See `kollabor/llm/permissions/README.md`.

### Terminal Color Support

**Auto-detected modes:** TRUE_COLOR (24-bit), EXTENDED (256-color), BASIC (16-color), NONE

**Detection order:** `COLORTERM` env var → `TERM_PROGRAM` → `TERM` variable → Apple Terminal (256-color)

**Manual override:**
```bash
KOLLABOR_COLOR_MODE=256 kollab        # 256-color
KOLLABOR_COLOR_MODE=truecolor kollab  # True color
KOLLABOR_COLOR_MODE=none kollab       # No colors
export KOLLABOR_COLOR_MODE=256        # Persist
```

**Programmatic:**
```python
from kollabor_tui.visual_effects import set_color_support, ColorSupport
set_color_support(ColorSupport.EXTENDED)
```

## Slash Commands

**Built-in:**
- `/help` - Show available commands
- `/save` - Save conversation (transcript|markdown|jsonl|clipboard|both|local)
- `/profile` (aliases: `/prof`, `/llm`) - Manage LLM profiles (list|set|create)
- `/permissions` (aliases: `/perms`, `/security`) - Manage permissions (show|default|strict|trust|stats|clear)
- `/terminal` (aliases: `/tmux`, `/term`, `/t`) - Manage tmux sessions (new|view|list|kill|attach)
- `/matrix` - Matrix rain effect
- `/version` - Show version

**Command menu:** Triggered by `/`, filters with prefix matching, arrow keys navigate, Enter executes. Prioritizes name matches over aliases. When filtered to single command, subcommands appear and are selectable.

**Adding custom commands with subcommands:**
```python
from kollabor.events.models import CommandDefinition, CommandCategory, SubcommandInfo

command_def = CommandDefinition(
    name="mycommand",
    description="My custom command",
    category=CommandCategory.CUSTOM,
    aliases=["mc", "mycmd"],
    handler=my_handler_function,
    subcommands=[
        SubcommandInfo("action1", "", "Execute action 1"),
        SubcommandInfo("action2", "<arg>", "Execute action 2 with argument"),
    ]
)
command_registry.register_command(command_def)
```

## Current State

Recent development:
- Subcommand system with smart execution
- Command menu filtering improvements (prefix matching prioritization)
- Dynamic system prompt rendering with `<trender>` tags
- Environment variable configuration
- Version management automation (reads from pyproject.toml)
- Git workflow enforcement (pre-commit/pre-push hooks)
- Windows compatibility
- Tmux plugin with live modal viewing
- Save conversation plugin with multiple formats

**The codebase uses Python 3.12+ with async/await throughout.**
