Files
LazyBearWorks/CLAUDE.md
Shahovalov MIkhail 9b27cd7dc2 fix: исправить навигацию клавиатуры в TUI через GlobalKeyboardService
- Добавить GlobalKeyboardService — выделенный поток с блокирующим
  Console.ReadKey, единственный источник клавишных событий для TUI
- Убрать FocusManager из App.razor: перехватывал Tab до компонентов
- Удалить @onkeydown с <Select>: RazorConsole не пробрасывает Tab/стрелки
  через этот механизм
- Использовать FocusedValue вместо Value в Select для корректной подсветки
- Обновить CLAUDE.md и AGENTS.md: архитектура TUI, RazorConsole gotchas
- Добавить docs/tui_log.md: разбор проблемы и справочник по RazorConsole

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 23:31:53 +03:00

82 lines
5.0 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Language Conventions
- **Output / explanations:** Russian
- **Code:** English
- **Comments, commit messages:** Russian
## Commands
```bash
# Build
dotnet build
# Run (starts TUI + HTTP MCP endpoint on port 5000)
dotnet run --project LazyBear.MCP
# Test MCP tool wiring (only needed after changing transport or tool registration)
npx @modelcontextprotocol/inspector dotnet run --project LazyBear.MCP
```
There are **no test projects**. After making changes, run `dotnet build`. If MCP wiring changed (new tool, new module, transport changes), also run the inspector.
Port is controlled via the `ASPNETCORE_URLS` environment variable (default `http://localhost:5000`). Ignore `launchSettings.json` — it shows a different port that is not used.
## Architecture
LazyBear is a .NET 10 MCP server exposing Jira, Confluence, and Kubernetes integrations to AI clients. It runs in two simultaneous modes that share a single DI container:
1. **TUI (foreground)** — RazorConsole terminal UI (`App.razor`), owns the console, handles keyboard navigation
2. **HTTP MCP endpoint (background)**`McpWebHostedService` runs as a hosted service, serves MCP tool calls on port 5000
Both share the same singletons: `ToolRegistryService`, `InMemoryLogSink`, `GlobalKeyboardService`, and the three client providers.
### Plugin System (IToolModule)
Each integration implements `IToolModule` (in `Services/ToolRegistry/IToolModule.cs`) and declares its `ModuleName`, `ToolNames[]`, and `Description`. Tool classes are auto-discovered at startup via reflection using `WithToolsFromAssembly()` — classes are annotated with `[McpServerToolType]`, methods with `[McpServerTool]`. Registering a new module requires only: implement `IToolModule`, create tool classes with attributes, and add DI registration in `Program.cs`.
### ToolRegistryService
Singleton that tracks enabled/disabled state for both modules and individual tools at runtime (no restart needed). Uses `ConcurrentDictionary` for thread safety. Fires `StateChanged` event on toggles; TUI components subscribe to re-render. Tool keys use the format `"ModuleName::ToolName"`.
### Provider Pattern
Each client (`K8sClientProvider`, `JiraClientProvider`, `ConfluenceClientProvider`) is a lazy singleton. If initialization fails (missing config, unreachable endpoint), it captures an `InitializationError` string. **Tools return error strings instead of throwing exceptions** — this is intentional so MCP clients see the configuration issue gracefully.
### Logging
`InMemoryLogSink` maintains a 500-entry circular `ConcurrentQueue`. All .NET logs flow through `InMemoryLoggerProvider` → sink → `OnLog` event → TUI Logs tab live view.
### TUI Keyboard Input
`GlobalKeyboardService` (`TUI/GlobalKeyboardService.cs`) is the **single source of keyboard events** for the entire TUI. It runs a dedicated background thread with a blocking `Console.ReadKey(intercept: true)` — no polling. `App.razor` subscribes to `OnKeyPressed`, converts `ConsoleKeyInfo` to `KeyboardEventArgs`, and dispatches via `InvokeAsync`.
Do **not** use `@onkeydown` on `<Select>` or other RazorConsole components for navigation logic — the framework intercepts Tab and arrow keys internally before they reach component-level callbacks. See `docs/tui_log.md` for the full breakdown.
### TUI Navigation
Tabs: Overview → Logs → Settings (switch with `Tab`/`Shift+Tab`). In Settings: arrow keys navigate the module→tool tree, `Space` toggles enable/disable, `Enter` expands/collapses. In Overview, `Enter` on a module jumps to its Settings entry.
## Configuration Gotchas
- **`Jira:Url`** is required; if missing, all Jira tools return string errors
- **K8s kubeconfig fallback order:** explicit `Kubernetes:KubeconfigPath``~/.kube/config` → in-cluster config
- **Source of truth:** `Program.cs`, not `README.md` (README is aspirational)
- `Pages/` directory exists but Razor Pages are **not enabled** in `Program.cs` — do not use them
- **RazorConsole keyboard gotchas:** `@onkeydown` on interactive components doesn't propagate Tab/arrows; `FocusManager` intercepts Tab globally; there is no public global key-intercept API. Full notes: `docs/tui_log.md`
- **`GlobalKeyboardService` registration pattern** — registered as both singleton and hosted service so it can be injected into Razor components: `services.AddSingleton<T>()` + `services.AddHostedService(sp => sp.GetRequiredService<T>())`
- **`Console.KeyAvailable` polling causes rendering lag** — it acquires the console mutex on every call and competes with RazorConsole's renderer. Always use blocking `Console.ReadKey` in a dedicated thread instead
## Key Dependencies
| Package | Role |
|---------|------|
| `ModelContextProtocol.AspNetCore` 1.2.0 | HTTP MCP transport |
| `KubernetesClient` 19.0.2 | K8s API |
| `RestSharp` 112.0.0 | Jira / Confluence HTTP |
| `RazorConsole.Core` 0.5.0 | Terminal UI framework |
| `Polly` 8.4.2 | Retry/resilience policies |