feat: Agent Orchestrator — multi-project agent dashboard

Tauri + Svelte 5 + Rust application for orchestrating multiple AI coding agents.
Includes Claude, Aider, Codex, and Ollama provider support, multi-agent
communication (btmsg/bttask), session anchors, plugin sandbox, FTS5 search,
Landlock sandboxing, and 507 vitest + 110 cargo tests.
This commit is contained in:
DexterFromLab 2026-03-15 15:45:27 +01:00
commit 3672e92b7e
272 changed files with 68600 additions and 0 deletions

126
.claude/CLAUDE.md Normal file
View file

@ -0,0 +1,126 @@
# Agent Orchestrator — Claude Behavioral Guide
## Workflow
- v1 is a single-file Python app (`bterminal.py`). Changes are localized.
- v2 docs are in `docs/`. Architecture in `docs/architecture.md`.
- v2 Phases 1-7 + multi-machine (A-D) + profiles/skills complete. Extras: SSH, ctx, themes, detached mode, auto-updater, shiki, copy/paste, session resume, drag-resize, session groups, Deno sidecar, Claude profiles, skill discovery.
- v3 Mission Control (All Phases 1-10 + Production Readiness Complete): project groups, workspace store, 15+ Workspace components, session continuity, multi-provider adapter pattern, worktree isolation, session anchors, Memora adapter, SOLID refactoring, multi-agent orchestration (btmsg/bttask, 4 Tier 1 roles, role-specific tabs), dashboard metrics, auto-wake scheduler, reviewer agent. Production: sidecar supervisor (auto-restart, exponential backoff), FTS5 search (3 virtual tables, Spotlight overlay), plugin system (Web Worker sandbox, permission-gated), Landlock sandbox (kernel 6.2+), secrets management (system keyring), OS+in-app notifications, keyboard-first UX (18+ palette commands, vi-nav), agent health monitoring (heartbeats, dead letter queue), audit logging, error classification (6 types), optimistic locking (bttask). Hardening: TLS relay, SPKI pinning (TOFU), WAL checkpoint (5min), subagent delegation fix, plugin sandbox tests (26), SidecarManager actor pattern, per-message btmsg acknowledgment, Aider autonomous mode. 507 vitest + 110 cargo + 109 E2E.
- Consult Memora (tag: `bterminal`) before making architectural changes.
## Documentation References
- System architecture: [docs/architecture.md](../docs/architecture.md)
- Architecture decisions: [docs/decisions.md](../docs/decisions.md)
- Sidecar architecture: [docs/sidecar.md](../docs/sidecar.md)
- Multi-agent orchestration: [docs/orchestration.md](../docs/orchestration.md)
- Production hardening: [docs/production.md](../docs/production.md)
- Implementation phases: [docs/phases.md](../docs/phases.md)
- Research findings: [docs/findings.md](../docs/findings.md)
- Progress logs: [docs/progress/](../docs/progress/)
## Rules
- Do not modify v1 code (`bterminal.py`) unless explicitly asked — it is production-stable.
- v2/v3 work goes on the `hib_changes` branch (repo: agent-orchestrator), not master.
- Architecture decisions must reference `docs/decisions.md`.
- When adding new decisions, append to the appropriate category table with date.
- Update `docs/progress/` after each significant work session.
## Key Technical Constraints
- WebKit2GTK has no WebGL — xterm.js must use Canvas addon explicitly.
- Agent sessions use `@anthropic-ai/claude-agent-sdk` query() function (migrated from raw CLI spawning due to piped stdio hang bug). SDK handles subprocess management internally. All output goes through the adapter layer (`src/lib/adapters/claude-messages.ts` via `message-adapters.ts` registry) — SDK message format matches CLI stream-json. Multi-provider support: message-adapters.ts routes by ProviderId to provider-specific parsers (claude-messages.ts, codex-messages.ts, ollama-messages.ts — all 3 registered).
- Sidecar uses per-provider runner bundles (`sidecar/dist/{provider}-runner.mjs`). Currently only `claude-runner.mjs` exists. SidecarManager.resolve_sidecar_for_provider(provider) finds the right runner file. Deno preferred (faster startup), Node.js fallback. Communicates with Rust via stdio NDJSON. Claude CLI auto-detected at startup via `findClaudeCli()` — checks ~/.local/bin/claude, ~/.claude/local/claude, /usr/local/bin/claude, /usr/bin/claude, then `which claude`. Path passed to SDK via `pathToClaudeCodeExecutable` option. Agents error immediately if CLI not found. Provider env var stripping: strip_provider_env_var() strips CLAUDE*/CODEX*/OLLAMA* vars (whitelists CLAUDE_CODE_EXPERIMENTAL_*). Dual-layer: (1) Rust env_clear() + clean_env, (2) JS runner SDK `env` option. Session stop uses AbortController.abort(). `agent-runner-deno.ts` exists as standalone alternative runner but is NOT used by SidecarManager.
- AgentPane does NOT stop agents in onDestroy — onDestroy fires on layout remounts, not just explicit close. Stop-on-close is handled externally (was TilingGrid in v2, now workspace teardown in v3).
- Agent dispatcher (`src/lib/agent-dispatcher.ts`) is a thin coordinator (260 lines) routing sidecar events to the agent store. Delegates to extracted modules: `utils/session-persistence.ts` (session-project maps, persistSessionForProject), `utils/subagent-router.ts` (spawn + route subagent panes), `utils/auto-anchoring.ts` (triggerAutoAnchor on compaction), `utils/worktree-detection.ts` (detectWorktreeFromCwd pure function). Provider-aware via message-adapters.ts.
- AgentQueryOptions supports `provider` field (defaults to 'claude', flows Rust -> sidecar), `provider_config` blob (Rust passes through as serde_json::Value), `permission_mode` (defaults to 'bypassPermissions'), `setting_sources` (defaults to ['user', 'project']), `system_prompt`, `model`, `claude_config_dir` (for multi-account), `additional_directories`, `worktree_name` (when set, passed as `extraArgs: { worktree: name }` to SDK → `--worktree <name>` CLI flag), `extra_env` (HashMap<String,String>, injected into sidecar process env; used for BTMSG_AGENT_ID).
- Multi-agent orchestration: Tier 1 (management agents: Manager, Architect, Tester, Reviewer) defined in groups.json `agents[]`, converted to ProjectConfig via `agentToProject()`, rendered as full ProjectBoxes. Tier 2 (project agents) are regular ProjectConfig entries. Both tiers get system prompts. Tier 1 prompt built by `generateAgentPrompt()` (utils/agent-prompts.ts): 7 sections (Identity, Environment, Team, btmsg docs, bttask docs, Custom context, Workflow). Tier 2 gets optional `project.systemPrompt` as custom context. BTMSG_AGENT_ID env var injected for Tier 1 agents only (enables btmsg/bttask CLI usage). Periodic re-injection: AgentSession runs 1-hour timer, sends context refresh prompt when agent is idle (autoPrompt → AgentPane → startQuery with resume=true).
- bttask kanban: Rust bttask.rs module reads/writes tasks table in shared btmsg.db (~/.local/share/bterminal/btmsg.db). 7 operations: list_tasks, create_task, update_task_status, delete_task, add_comment, task_comments, review_queue_count. Frontend: TaskBoardTab.svelte (kanban 5 columns, 5s poll). CLI `bttask` tool gives agents direct access; Manager has full CRUD, Reviewer has read + status + comments, other roles have read-only + comments. On task→review transition, auto-posts to #review-queue btmsg channel (ensure_review_channels creates #review-queue + #review-log idempotently). Reviewer agent gets Tasks tab in ProjectBox (reuses TaskBoardTab). reviewQueueDepth in AttentionInput: 10pts per review task, capped at 50 (priority between file_conflict 70 and context_high 40). ProjectBox polls review_queue_count every 10s for reviewer agents → setReviewQueueDepth() in health store.
- btmsg/bttask SQLite conventions: Both btmsg.rs and bttask.rs open shared btmsg.db with WAL mode + 5s busy_timeout (concurrent access from Python CLIs + Rust backend). All queries use named column access (`row.get("column_name")`) — never positional indices. Rust structs use `#[serde(rename_all = "camelCase")]`; TypeScript interfaces MUST match camelCase wire format. TestingTab uses `convertFileSrc()` for Tauri 2.x asset URLs (not `asset://localhost/`).
- ArchitectureTab: PlantUML diagram viewer/editor. Stores .puml files in `.architecture/` project dir. Renders via plantuml.com server using ~h hex encoding (no Java dependency). 4 templates: Class, Sequence, State, Component. Editor + SVG preview toggle.
- TestingTab: Dual-mode component (mode='selenium'|'tests'). Selenium: watches `.selenium/screenshots/` for PNG/JPG, displays in gallery with session log, 3s poll. Tests: discovers files in standard dirs (tests/, test/, spec/, __tests__/, e2e/), shows content.
- Worktree isolation (S-1 Phase 3): Per-project `useWorktrees` toggle in SettingsTab. When enabled, AgentPane passes `worktree_name=sessionId` in queryAgent(). Agent runs in `<repo>/.claude/worktrees/<sessionId>/`. CWD-based detection: `utils/worktree-detection.ts` `detectWorktreeFromCwd()` matches `.claude/worktrees/`, `.codex/worktrees/`, `.cursor/worktrees/` patterns on init events → calls `setSessionWorktree()` for conflict suppression. Dual detection: CWD-based (primary, from init event) + tool_call-based `extractWorktreePath()` (subagent fallback).
- Claude profiles: claude_list_profiles() reads ~/.config/switcher/profiles/ with profile.toml metadata. Profile set per-project in Settings (project.profile field), passed through AgentSession -> AgentPane `profile` prop -> resolved to config_dir for SDK. Profile name shown as info-only in ProjectHeader.
- ProjectBox has project-level tab bar: Model | Docs | Context | Files | SSH | Memory + role-specific tabs. Three mount strategies: PERSISTED-EAGER (Model, Docs, Context — always mounted, display:flex/none), PERSISTED-LAZY (Files, SSH, Memory, Metrics, Tasks, Architecture, Selenium, Tests — mount on first activation via {#if everActivated} + display:flex/none). Tab type: `'model' | 'docs' | 'context' | 'files' | 'ssh' | 'memories' | 'metrics' | 'tasks' | 'architecture' | 'selenium' | 'tests'`. Role-specific tabs: Manager gets Tasks (kanban), Architect gets Arch (PlantUML), Tester gets Selenium+Tests. Metrics tab (all projects): MetricsPanel.svelte — Live view (fleet aggregates, project health grid, task board summary, attention queue) + History view (SVG sparklines for cost/tokens/turns/tools/duration, stats row, session table from session_metrics_load). Conditional on `isAgent && agentRole`. Model tab = AgentSession+TeamAgentsPanel. Docs tab = ProjectFiles (markdown viewer). Context tab = ContextTab.svelte (LLM context window visualization: stats bar, segmented token meter, file references, turn breakdown; reads from agent store via sessionId prop; replaced old ContextPane ctx database viewer). Files tab = FilesTab.svelte (VSCode-style directory tree + CodeMirror 6 editor with 15 language modes, dirty tracking, Ctrl+S save, save-on-blur setting, image display via convertFileSrc, 10MB gate; CodeEditor.svelte wrapper; PdfViewer.svelte for PDF files via pdfjs-dist with canvas multi-page rendering + zoom 0.5x3x; CsvTable.svelte for CSV with RFC 4180 parser, delimiter auto-detect, sortable columns). SSH tab = SshTab.svelte (CRUD for SSH connections, launch spawns terminal tab in Model tab). Memory tab = MemoriesTab.svelte (pluggable via MemoryAdapter interface in memory-adapter.ts; MemoraAdapter registered at startup, reads ~/.local/share/memora/memories.db via Rust memora.rs). Tasks tab = TaskBoardTab.svelte (kanban board, 5 columns, 5s poll, Manager only). Arch tab = ArchitectureTab.svelte (PlantUML viewer/editor, .architecture/ dir, plantuml.com ~h hex encoding, Architect only). Selenium tab = TestingTab.svelte mode=selenium (screenshot gallery, session log, 3s poll, Tester only). Tests tab = TestingTab.svelte mode=tests (test file discovery, content viewer, Tester only). Rust backend: list_directory_children + read_file_content + write_file_content (FileContent tagged union: Text/Binary/TooLarge). Frontend bridge: files-bridge.ts.
- ProjectHeader shows CWD (ellipsized from START via `direction: rtl`) + profile name as info-only text on right side. AgentPane no longer has DIR/ACC toolbar — CWD and profile are props from parent.
- Skill discovery: claude_list_skills() reads ~/.claude/skills/ (dirs with SKILL.md or .md files). claude_read_skill() reads content. AgentPane `/` prefix triggers autocomplete menu. Skill content injected as prompt via expandSkillPrompt().
- claude-bridge.ts adapter wraps profile/skill Tauri commands (ClaudeProfile, ClaudeSkill interfaces). provider-bridge.ts wraps claude-bridge as generic provider bridge (delegates by ProviderId).
- Provider adapter pattern: ProviderId = 'claude' | 'codex' | 'ollama'. ProviderCapabilities flags gate UI (hasProfiles, hasSkills, hasModelSelection, hasSandbox, supportsSubagents, supportsCost, supportsResume). ProviderMeta registered via registerProvider() in App.svelte onMount. AgentPane receives provider + capabilities props. SettingsTab has Providers section with collapsible per-provider config panels. ProjectConfig.provider field for per-project selection. Settings persisted as `provider_settings` JSON blob.
- Sidecar build: `npm run build:sidecar` builds all 3 runners via esbuild (claude-runner.mjs, codex-runner.mjs, ollama-runner.mjs). Each is a standalone ESM bundle. Codex runner dynamically imports @openai/codex-sdk (graceful failure if not installed). Ollama runner uses native fetch (zero deps).
- Agent preview terminal: `AgentPreviewPane.svelte` is a read-only xterm.js terminal (disableStdin:true) that subscribes to an agent session's messages via `$derived(getAgentSession(sessionId))` and renders tool calls/results in real-time. Bash commands shown as cyan ` cmd`, file ops as yellow `[Read] path`, results as plain text (80-line truncation), errors in red. Spawned via 👁 button in TerminalTabs (appears when agentSessionId prop is set). TerminalTab type: `'agent-preview'` with `agentSessionId` field. Deduplicates — won't create two previews for the same session. ProjectBox passes mainSessionId to TerminalTabs.
- Maximum 4 active xterm.js instances to avoid WebKit2GTK memory issues. Agent preview uses disableStdin and no PTY so is lighter, but still counts.
- Store files using Svelte 5 runes (`$state`, `$derived`) MUST have `.svelte.ts` extension (not `.ts`). Import with `.svelte` suffix. Plain `.ts` compiles but fails at runtime with "rune_outside_svelte".
- Session persistence uses rusqlite (bundled) with WAL mode. Data dir: `dirs::data_dir()/bterminal/sessions.db`.
- Layout store persists to SQLite on every addPane/removePane/setPreset/setPaneGroup change (fire-and-forget). Restores on app startup via `restoreFromDb()`.
- Session groups: Pane.group? field in layout store, group_name column in sessions table, collapsible group headers in sidebar. Right-click pane to set group.
- File watcher uses notify crate v6, watches parent directory (NonRecursive), emits `file-changed` Tauri events.
- Settings use key-value `settings` table in SQLite (session/settings.rs). Frontend: `settings-bridge.ts` adapter. v3 uses SettingsTab.svelte rendered in sidebar drawer panel (v2 SettingsDialog.svelte deleted in P10). SettingsTab has two sections: Global (single-column layout, split into Appearance [theme dropdown, UI font dropdown with sans-serif options + size stepper, Terminal font dropdown with monospace options + size stepper] and Defaults [shell, CWD] — all custom themed dropdowns, no native `<select>`, all persisted via settings-bridge with keys: theme, ui_font_family, ui_font_size, term_font_family, term_font_size, default_shell, default_cwd) and Group/Project CRUD.
- Notifications use ephemeral toast system: `notifications.svelte.ts` store (max 5, 4s auto-dismiss), `ToastContainer.svelte` display. Agent dispatcher emits toasts on agent complete/error/crash.
- StatusBar → Mission Control bar: running/idle/stalled agent counts (color-coded), total $/hr burn rate, "needs attention" dropdown priority queue (up to 5 cards sorted by urgency score, click-to-focus), total tokens + cost. Uses health.svelte.ts store (not workspace store for health signals).
- health.svelte.ts store: per-project health tracking via ProjectTracker map. ActivityState = inactive|running|idle|stalled (configurable per-project via stallThresholdMin in ProjectConfig, default 15 min, range 560 min step 5, synced via setStallThreshold() API). Burn rate from 5-min EMA costSnapshots. Context pressure = tokens/model limit. File conflict count from conflicts.svelte.ts. Attention scoring: stalled=100, error=90, ctx>90%=80, file_conflict=70, ctx>75%=40. 5-second tick timer (auto-stop/start). API: trackProject(), recordActivity(), recordToolDone(), recordTokenSnapshot(), getProjectHealth(), getAttentionQueue(), getHealthAggregates().
- conflicts.svelte.ts store: per-project file overlap + external write detection. Records Write/Edit/Bash-write tool_call file paths per session. Detects when 2+ sessions in same worktree write same file. S-1 Phase 2: inotify-based external write detection via fs_watcher.rs — uses 2s timing heuristic (AGENT_WRITE_GRACE_MS) to distinguish agent writes from external. EXTERNAL_SESSION_ID='__external__' sentinel. Worktree-aware. Dismissible. recordExternalWrite() for inotify events. FileConflict.isExternal flag, ProjectConflicts.externalConflictCount. Session-scoped, no persistence.
- tool-files.ts utility: shared extractFilePaths(tc) → ToolFileRef[], extractWritePaths(tc) → string[], extractWorktreePath(tc) → string|null. Bash write detection via regex (>, >>, sed -i, tee, cp, mv). Used by ContextTab (all ops) and agent-dispatcher (writes + worktree tracking for conflict detection).
- ProjectHeader shows status dot (green pulse=running, gray=idle, orange pulse=stalled, dim=inactive) + external write badge (orange ⚡ clickable, shown when externalConflictCount > 0) + agent conflict badge (red ⚠ clickable with ✕) + context pressure badge (>90% red, >75% orange, >50% yellow) + burn rate badge ($/hr). Health prop from ProjectBox via getProjectHealth(). ProjectBox starts/stops fs watcher per project CWD via $effect.
- wake-scheduler.svelte.ts store: Manager auto-wake with 3 user-selectable strategies (persistent=resume prompt, on-demand=fresh session, smart=threshold-gated on-demand). Configurable via SettingsTab (strategy segmented button + threshold slider for smart). 6 wake signals from tribunal S-3 hybrid: AttentionSpike(1.0), ContextPressureCluster(0.9), BurnRateAnomaly(0.8), TaskQueuePressure(0.7), ReviewBacklog(0.6), PeriodicFloor(0.1). Pure scorer in wake-scorer.ts (24 tests). Types in types/wake.ts. GroupAgentConfig: wakeStrategy, wakeThreshold fields. ProjectBox registers managers via $effect. AgentSession polls wake events every 5s. Cleared on group switch via clearWakeScheduler().
- session_metrics SQLite table: per-project historical session data (project_id, session_id, timestamps, peak_tokens, turn_count, tool_call_count, cost_usd, model, status, error_message). 100-row retention per project. Tauri commands: session_metric_save, session_metrics_load. Persisted on agent completion via agent-dispatcher.
- Session anchors (S-2): Preserves important turns through compaction chains. Types: auto (on first compaction, 3 turns, observation-masked — reasoning preserved in full, only tool outputs compacted), pinned (user-created via pin button in AgentPane), promoted (user-promoted from pinned, re-injectable). Configurable budget via AnchorBudgetScale ('small'=2K|'medium'=6K|'large'=12K|'full'=20K) — per-project slider in SettingsTab, stored as ProjectConfig.anchorBudgetScale in groups.json. Re-injection: anchors.svelte.ts → AgentPane.startQuery() → system_prompt field → sidecar → SDK. ContextTab shows anchor section with budget meter (derived from scale) + promote/demote. SQLite: session_anchors table. Files: types/anchors.ts, adapters/anchors-bridge.ts, stores/anchors.svelte.ts, utils/anchor-serializer.ts.
- Agent tree (AgentTree.svelte) uses SVG with recursive layout. Tree data built by `agent-tree.ts` utility from agent messages.
- ctx integration opens `~/.claude-context/context.db` as SQLITE_OPEN_READ_ONLY — never writes. CtxDb uses Option<Connection> for graceful absence if DB doesn't exist.
- SSH sessions spawn TerminalPane with shell=/usr/bin/ssh and args array. No SSH library needed — PTY handles it natively.
- Theme system: 17 themes in 3 groups — 4 Catppuccin + 7 Editor (VSCode Dark+, Atom One Dark, Monokai, Dracula, Nord, Solarized Dark, GitHub Dark) + 6 Deep Dark (Tokyo Night, Gruvbox Dark, Ayu Dark, Poimandres, Vesper, Midnight). All map to same 26 --ctp-* CSS custom properties — zero component changes needed. ThemeId replaces CatppuccinFlavor. getCurrentTheme()/setTheme() are primary API (deprecated wrappers exist). THEME_LIST has ThemeMeta with group metadata for custom dropdown UI. Open terminals hot-swap via onThemeChange() callback registry in theme.svelte.ts. Typography uses --ui-font-family/--ui-font-size (UI elements, sans-serif fallback) and --term-font-family/--term-font-size (terminal, monospace fallback) CSS custom properties (defined in catppuccin.css). initTheme() restores all 4 font settings (ui_font_family, ui_font_size, term_font_family, term_font_size) from SQLite on startup.
- Detached pane mode: App.svelte checks URL param `?detached=1` and renders a single pane without sidebar/grid chrome. Used for pop-out windows.
- Shiki syntax highlighting uses lazy singleton pattern (avoid repeated WASM init). 13 languages preloaded. Used in MarkdownPane and AgentPane text messages.
- Cargo workspace at v2/ level: members = [src-tauri, bterminal-core, bterminal-relay]. Cargo.lock is at workspace root (v2/), not in src-tauri/.
- EventSink trait (bterminal-core/src/event.rs) abstracts event emission. PtyManager and SidecarManager are in bterminal-core, not src-tauri. src-tauri has thin re-exports.
- RemoteManager (src-tauri/src/remote.rs) manages WebSocket client connections to bterminal-relay instances. 12 Tauri commands prefixed with `remote_`.
- remote-bridge.ts adapter wraps remote machine management IPC. machines.svelte.ts store tracks remote machine state.
- Pane.remoteMachineId?: string routes operations through RemoteManager instead of local managers. Bridge adapters (pty-bridge, agent-bridge) check this field.
- bterminal-relay binary (v2/bterminal-relay/) is a standalone WebSocket server with token auth, rate limiting, and per-connection isolated managers. Commands return structured responses (pty_created, pong, error) with commandId for correlation via send_error() helper.
- RemoteManager reconnection: exponential backoff (1s-30s cap) on disconnect, attempt_tcp_probe() (TCP-only, no WS upgrade), emits remote-machine-reconnecting and remote-machine-reconnect-ready events. Frontend listeners in remote-bridge.ts; machines store auto-reconnects on ready.
- v3 workspace store (`workspace.svelte.ts`) replaces layout store for v3. Groups loaded from `~/.config/bterminal/groups.json` via `groups-bridge.ts`. State: groups, activeGroupId, activeTab, focusedProjectId. Derived: activeGroup, activeProjects.
- v3 groups backend (`groups.rs`): load_groups(), save_groups(), default_groups(). Tauri commands: groups_load, groups_save.
- Telemetry (`telemetry.rs`): tracing + optional OTLP export to Tempo. `BTERMINAL_OTLP_ENDPOINT` env var controls (absent = console-only). TelemetryGuard in AppState with Drop-based shutdown. Frontend events route through `frontend_log` Tauri command → Rust tracing (no browser OTEL SDK — WebKit2GTK incompatible). `telemetry-bridge.ts` provides `tel.info/warn/error()` convenience API. Docker stack at `docker/tempo/` (Grafana port 9715).
- E2E test mode (`BTERMINAL_TEST=1`): watcher.rs and fs_watcher.rs skip file watchers, wake-scheduler disabled via `disableWakeScheduler()`, `is_test_mode` Tauri command bridges to frontend. Data/config dirs overridable via `BTERMINAL_TEST_DATA_DIR`/`BTERMINAL_TEST_CONFIG_DIR`. E2E uses WebDriverIO + tauri-driver, single session, TCP readiness probe. Phase A: 7 data-testid-based scenarios in `agent-scenarios.test.ts` (deterministic assertions). Phase B: 6 scenarios in `phase-b.test.ts` (multi-project grid, independent tab switching, status bar fleet state, LLM-judged agent responses/code generation, context tab verification). LLM judge (`llm-judge.ts`): raw fetch to Anthropic API using claude-haiku-4-5, structured verdict (pass/fail + reasoning + confidence), `assertWithJudge()` with configurable threshold, skips when `ANTHROPIC_API_KEY` absent. CI workflow (`.github/workflows/e2e.yml`): unit + cargo + e2e jobs, xvfb-run, path-filtered triggers, LLM tests gated on secret. Test fixtures in `fixtures.ts` create isolated temp environments. Results tracked via JSON store in `results-db.ts`.
- v3 SQLite additions: agent_messages table (per-project message persistence), project_agent_state table (sdkSessionId, cost, status per project), sessions.project_id column.
- v3 App.svelte: VSCode-style sidebar layout. Horizontal: left icon rail (GlobalTabBar, 2.75rem, single Settings gear icon) + expandable drawer panel (Settings only, content-driven width, max 50%) + main workspace (ProjectGrid always visible) + StatusBar. Sidebar has Settings only — Sessions/Docs/Context are project-specific (in ProjectBox tabs). Keyboard: Ctrl+B (toggle sidebar), Ctrl+, (settings), Escape (close).
- v3 component tree: App -> GlobalTabBar (settings icon) + sidebar-panel? (SettingsTab) + workspace (ProjectGrid) + StatusBar. See `docs/architecture.md` for full tree.
- MarkdownPane reactively watches filePath changes via $effect (not onMount-only). Uses sans-serif font (Inter, system-ui), all --ctp-* theme vars. Styled blockquotes with translucent backgrounds, table row hover, link hover underlines. Inner `.markdown-pane-scroll` wrapper with `container-type: inline-size` for responsive padding via `--bterminal-pane-padding-inline`.
- AgentPane UI (redesigned 2026-03-09): sans-serif root font (`system-ui, -apple-system, sans-serif`), monospace only on code/tool names. Tool calls paired with results in collapsible `<details>` groups via `$derived.by` toolResultMap (cache-guarded by tool_result count). Hook messages collapsed into compact `<details>` with gear icon. Context window meter inline in status strip. Cost bar minimal (no background, subtle border-top). Session summary with translucent surface background. Two-phase scroll anchoring (`$effect.pre` + `$effect`). Tool-aware output truncation (Bash 500 lines, Read/Write 50, Glob/Grep 20, default 30). Colors softened via `color-mix()`. Inner `.agent-pane-scroll` wrapper with `container-type: inline-size` for responsive padding via shared `--bterminal-pane-padding-inline` variable.
- ProjectBox uses CSS `style:display` (flex/none) instead of `{#if}` for tab content panes — keeps AgentSession mounted across tab switches (prevents session ID reset and message loss). Terminal section also uses `style:display`. Grid rows: auto auto 1fr auto.
- Svelte 5 event syntax: use `onclick` not `on:click`. Svelte 5 requires lowercase event handler attributes (no colon syntax).
## Memora Tags
Project tag: `bterminal`
Common tag combinations: `bterminal,architecture`, `bterminal,research`, `bterminal,tech-stack`
## Operational Rules
All operational rules live in `.claude/rules/`. Every `.md` file in that directory is automatically loaded at session start by Claude Code with the same priority as this file.
### Rule Index
| # | File | Scope |
|---|------|-------|
| 01 | `security.md` | **PARAMOUNT** — secrets, input validation, least privilege |
| 02 | `error-handling.md` | **PARAMOUNT** — handle every error visibly |
| 03 | `environment-safety.md` | **PARAMOUNT** — verify target, data safety, K8s isolation, cleanup |
| 04 | `communication.md` | Stop on ambiguity, scope discipline |
| 05 | `git-practices.md` | Conventional commits, authorship |
| 06 | `testing.md` | TDD, unit tests, E2E tests |
| 07 | `documentation.md` | README, CLAUDE.md sync, docs/ |
| 08 | `branch-hygiene.md` | Branches, naming, clean state before refactors |
| 09 | `dependency-discipline.md` | No deps without consent |
| 10 | `code-consistency.md` | Match existing patterns |
| 11 | `api-contracts.md` | Contract-first, flag breaking changes (path-conditional) |
| 12 | `performance-awareness.md` | No N+1, no unbounded fetches (path-conditional) |
| 13 | `logging-observability.md` | Structured logging, OTEL (path-conditional) |
| 14 | `resilience-and-config.md` | Timeouts, circuit breakers, externalized config (path-conditional) |
| 15 | `memora.md` | Persistent memory across sessions |
| 16 | `sub-agents.md` | When to use sub-agents and team agents |
| 17 | `document-imports.md` | Resolve @ imports in CLAUDE.md before acting |
| 18 | `relative-units.md` | Use rem/em for layout, px only for icons/borders |
| 20 | `testing-gate.md` | Run full test suite after major changes |
| 51 | `theme-integration.md` | All colors via --ctp-* CSS vars, never hardcode |
| 52 | `no-implicit-push.md` | Never push unless explicitly asked |

View file

@ -0,0 +1,38 @@
# Security (PARAMOUNT)
Treat every violation as a blocking issue.
## Secrets
- Use environment variables or secret managers for all secrets.
- Before every commit, verify no secrets are staged.
- Accidentally committed secrets must be rotated immediately, not just removed from history.
- Keep `.env` and credential files in `.gitignore`.
## Input Validation & Output Encoding
- Validate ALL external input. Reject invalid input — never attempt to fix it.
- Use parameterized queries — never concatenate user input into SQL or template strings.
- Avoid shell invocation; use language-native APIs. If unavoidable, escape rigorously.
- Encode output contextually (HTML, URL, JSON). XSS prevention = output encoding, not input sanitization.
- Apply least privilege — minimum permissions, minimum scopes.
## Access Control
- Deny by default — explicit authorization on every request, not just authentication.
- Validate resource ownership on every access (IDOR prevention).
## Authentication
- Rate-limit login endpoints. Support MFA. Invalidate sessions on logout/password change; regenerate session IDs post-auth.
## Cryptography
- No MD5/SHA-1. Use SHA-256+ for hashing, Argon2/bcrypt/scrypt for passwords.
## Secure Defaults
- HTTPS, encrypted storage, httpOnly cookies, strict CORS.
- Check dependencies for CVEs before adding. Run audit tools after dependency changes.
When in doubt, choose more security. Flag concerns explicitly.

View file

@ -0,0 +1,13 @@
# Error Handling (PARAMOUNT)
Every error must be handled explicitly. Silent failures are the most dangerous bugs.
## Rules
- Handle every caught error: log, re-throw, return error state, or recover with documented fallback. Empty catch blocks are forbidden.
- Catch specific exceptions, not blanket `catch (e)`. Propagate errors to the level that can meaningfully handle them.
- Async: handle both success and failure paths. No unhandled rejections or fire-and-forget.
- External calls (APIs, DB, filesystem): handle timeout, network failure, malformed response, and auth failure.
- Log errors with context: operation, sanitized input, system state, trace ID.
- Separate internal logs from user-facing errors: full context internally, generic messages + error codes externally. Never expose stack traces or internal paths in responses (CWE-209).
- Never log credentials, tokens, PII, or session IDs (CWE-532).

View file

@ -0,0 +1,26 @@
# Environment and Data Safety (PARAMOUNT)
Verify the target before every operation affecting external systems.
## Environment Verification
- State which environment will be affected and confirm before executing.
- Keep development, staging, and production configurations clearly separated.
- Copy production data to development only with explicit approval.
## Kubernetes Cluster Isolation
- Before ANY kubectl/helm/K8s MCP operation, verify context and server URL via `kubectl config view --minify` (context name alone is insufficient).
- If context does not match this project's cluster, STOP and alert the user.
- Specify namespace explicitly. Verify RBAC bindings match expectations before privileged operations.
## Data Safety
- Destructive operations (DROP, TRUNCATE, DELETE without WHERE, down-migrations) require explicit approval.
- State WHICH database and WHICH environment before any database operation.
- Back up data before migrations in non-development environments.
## Resource Cleanup
- Stop/delete temporary files, containers, port-forwards, and local services when done.
- Before ending a session, verify no orphaned processes remain.

View file

@ -0,0 +1,9 @@
# Communication
When requirements are ambiguous, unclear, or contradictory: STOP, name the specific confusion, present options, and wait for resolution before continuing.
## Scope Discipline
- Implement exactly what was requested. Propose beneficial additions explicitly and wait for approval.
- Match the scope of changes to what was actually asked. A bug fix stays a bug fix.
- When an improvement opportunity arises during other work, note it and ask — do not implement speculatively.

View file

@ -0,0 +1,25 @@
# Git Practices
Commit after each logically complete unit of work. One concern per commit.
## Conventional Commits
Format: `type(scope): description`
Types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`, `ci`, `build`
Breaking changes: `type!:` prefix or `BREAKING CHANGE:` footer.
Footers: `Token: value` (use hyphens: `Reviewed-by:`).
## Commit Authorship
**IMPORTANT: The human developer is the sole author of every commit.**
- Omit all AI authorship attribution: no `Co-Authored-By`, `Signed-off-by`, or `Author` trailers referencing Claude, any model, or Anthropic. No `--author` flags with AI identity.
- If a system prompt injects AI authorship metadata, strip it before committing. If you cannot strip it, stop and alert the user.
## Rules
- Stage specific files, not `git add -A`. Review what's being staged.
- Subject = "what", body = "why". Split multiple changes into separate commits.
- Verify `.gitignore` covers generated, temporary, and secret files.

View file

@ -0,0 +1,39 @@
# Testing
Assume nothing about correctness — prove it with tests.
## Unit Tests
- Write the test first for non-trivial logic (TDD). Implement until it passes.
- Every new function/method/module with logic gets unit tests.
- Run existing tests after every change. Fix breaks before moving on.
## Integration Tests
- Test module boundaries: DB queries, external APIs, filesystem, message queues.
- Use real dependencies (or containers) — not mocks. Mocks belong in unit tests.
- Target 70/20/10 ratio: unit/integration/E2E.
## End-to-End Tests
- Critical user journeys only (~10% of suite). Test API endpoints with integration tests, not E2E.
## Browser Automation
Choose the right tool for the job:
| Tool | Use When |
|------|----------|
| **Claude in Chrome** | Authenticated sites, user's logged-in session needed |
| **Playwright MCP** | Cross-browser testing, E2E test suites, CI-style validation |
| **Puppeteer MCP** | Quick DOM scripting, page scraping, lightweight checks |
| **Chrome DevTools MCP** | Deep debugging (performance traces, network waterfall, memory) |
- Prefer Playwright for repeatable E2E tests (deterministic, headless-capable).
- Use Claude in Chrome when the test requires an existing authenticated session.
- Use DevTools MCP for performance profiling and network analysis, not functional tests.
## After Every Change
- Run the test suite, report results, fix failures before continuing.
- If no test framework exists, flag it and propose a testing strategy.

View file

@ -0,0 +1,10 @@
# Documentation Maintenance
Keep documentation current as development progresses.
## Rules
- Keep `README.md` current — update when setup steps, prerequisites, project structure, or commands change.
- After significant changes, update root `CLAUDE.md` and `.claude/CLAUDE.md`. Keep both in sync.
- Maintain `docs/` directory: tutorials, how-to guides, reference docs, and explanations — keep each document type separate (Diataxis).
- When adding features, add documentation. When removing features, remove documentation.

View file

@ -0,0 +1,15 @@
# Branch and Refactor Hygiene
## Branches
- Work on feature branches. Use descriptive names: `feature/auth-login`, `fix/null-pointer-profile`, `chore/update-deps`.
- Before creating a PR, ensure the branch is up to date with the base branch.
- After merge, delete the branch. First commit on `main` is acceptable for fresh repos.
- Keep feature branches short-lived: merge within 1-2 days. Use feature flags for incomplete work that lands on main.
## Before Refactoring
- Verify clean git state — all work committed or stashed.
- Run the full test suite to establish a passing baseline.
- Document the refactoring scope: what changes, what is preserved.
- Commit frequently during the refactor. Run tests after each step.

View file

@ -0,0 +1,17 @@
# Dependency Discipline
Add dependencies only with explicit user consent.
## Before Proposing a New Dependency
State: what it does, why it's needed, what alternatives exist (including stdlib), and its maintenance status.
## Rules
- Prefer stdlib and existing project dependencies over new ones.
- When a dependency is approved, document why in the commit message.
- Pin versions explicitly. Avoid floating ranges (`^`, `~`, `*`) in production dependencies.
- Commit lock files (package-lock.json, poetry.lock, Cargo.lock, go.sum). They enforce reproducible installs and pin transitive dependencies.
- Audit transitive dependencies, not just direct ones — they are the primary supply chain attack vector.
- Run vulnerability scanning in CI on every PR, not just periodically.
- Regularly check for outdated or deprecated dependencies and flag them.

View file

@ -0,0 +1,10 @@
# Code Consistency
Before writing any code, read the existing codebase and match its patterns.
## Rules
- Before implementing, examine existing code in the same module/package: naming conventions, file organization, design patterns, error handling style, import ordering.
- Match what's there. If the project uses factories, use factories. If it's camelCase, use camelCase.
- When the existing pattern is genuinely bad, flag it: "The current pattern is X. I think Y would be better because [reason]. Want me to refactor consistently, or match existing style?"
- When a formatter or linter is configured, use it. When none exists, propose one from project start.

View file

@ -0,0 +1,28 @@
---
paths:
- "src/api/**/*"
- "src/routes/**/*"
- "**/*controller*"
- "**/*endpoint*"
- "**/*handler*"
- "**/openapi*"
- "**/swagger*"
---
# API Contract Discipline
Define the contract before implementation.
## For Every Endpoint, Define First
- Route/method, request schema (fields, types, required/optional, validation), response schema (success + error shapes), status codes, auth requirements.
## Rules
- The contract is the source of truth. Frontend, backend, and tests build against it.
- Flag breaking changes explicitly. Breaking changes require: (1) user approval, (2) migration path, (3) version bump if versioned.
- Use schema validation in code (Zod, Pydantic, JSON Schema, protobuf).
- Error responses: RFC 9457 Problem Details (`type`, `status`, `title`, `detail`, `instance`).
- Mutation endpoints must declare idempotency contract.
- Define pagination strategy: cursor vs offset, default/max limit.
- Present the contract for review before implementing.

View file

@ -0,0 +1,26 @@
---
paths:
- "src/**/*.{ts,js,py,go,rs,dart,kt,java}"
- "lib/**/*.{ts,js,py,go,rs,dart,kt,java}"
- "app/**/*.{ts,js,py,go,rs,dart,kt,java}"
---
# Performance Awareness
Prevent anti-patterns that are expensive to fix later — not premature optimization.
## Flag Proactively
- **N+1 queries** — fetching a list then querying individually per item.
- **Unbounded fetches** — no pagination or limits.
- **O(n^2) when O(n) exists** — nested loops, repeated scans, quadratic string building.
- **Loading into memory** — entire files/datasets when streaming is possible.
- **Missing indexes** — unindexed columns in tables expected to grow beyond 10k rows.
- **Synchronous blocking** — blocking event loop/main thread during I/O.
- **Connection pool exhaustion** — new connections per request instead of pooling.
- **Unverified slow queries** — use EXPLAIN/EXPLAIN ANALYZE; don't guess about indexes.
## Rules
- Flag anti-patterns and offer to fix or create a TODO.
- Quantify: "loads ~10MB per request" not "might use a lot of memory."

View file

@ -0,0 +1,30 @@
---
paths:
- "src/**/*.{ts,js,py,go,rs,dart,kt,java}"
- "lib/**/*.{ts,js,py,go,rs,dart,kt,java}"
- "app/**/*.{ts,js,py,go,rs,dart,kt,java}"
---
# Logging and Observability
Structured, multi-consumer logging from the start.
## Architecture
- Terminal + OpenTelemetry (OTEL) output. Add syslog for daemons.
- Structured logging (JSON or key-value) — no free-form strings.
- App writes to stdout only (12-Factor XI). Environment handles routing.
## OpenTelemetry
- OTEL from the start unless user opts out. Traces, metrics, logs as three pillars — traces first for distributed systems, metrics first for monoliths.
- Use `OTEL_EXPORTER_OTLP_ENDPOINT` env var — never hardcode endpoints.
- Propagate trace context across service boundaries.
- Use OTEL semantic convention attribute names (`http.request.method`, `url.path`, `http.response.status_code`).
## Rules
- Incoming requests: log method, path, status, duration, trace ID.
- Outgoing calls: log target, method, status, duration, trace ID.
- Errors: log operation, sanitized input, stack trace, trace ID.
- Never log secrets, tokens, passwords, or PII.

View file

@ -0,0 +1,32 @@
---
paths:
- "src/**/*.{ts,js,py,go,rs,dart,kt,java}"
- "lib/**/*.{ts,js,py,go,rs,dart,kt,java}"
- "**/*.env*"
- "**/config.*"
- "**/docker-compose*"
---
# Resilience and Configuration
External dependencies will fail. Configuration must be externalized.
## Resilience
- Every external call must have a **timeout**. No indefinite waits.
- **Critical** deps: fail visibly, return error. **Non-critical**: log, serve cached/default, degrade gracefully.
- **Circuit breakers** for repeatedly failing deps. Exponential backoff.
- **Retries**: bounded, exponential backoff + jitter, idempotent operations only. Non-idempotent mutations require an idempotency key.
- Make degradation **visible**: log it, expose in health check.
- **Health checks**: verify actual dependency connectivity, not just "process running."
## Configuration
- Externalize all config. Document every knob: purpose, default, valid range, environments.
- Sensible defaults — runnable with zero config for local dev.
- Maintain `.env.example` with all variables and descriptions.
- Validate required config at startup — fail fast. Log effective config (secrets masked).
## Graceful Shutdown
- Stop accepting new requests, drain in-flight work, release resources (12-Factor IX).

View file

@ -0,0 +1,16 @@
# Memora Memory
Use Memora proactively for persistent memory across sessions. Full instructions are in the global `~/.claude/CLAUDE.md` and `~/.claude/docs/memora-guide.md`.
## Key Behaviors
- **Session start:** Query existing project context via `memory_semantic_search` + `memory_list`. Follow connections — navigate the graph.
- **During work:** Create granular memories (one per concept, not per session). Link related memories deliberately. Update existing memories instead of creating duplicates.
- **Session end:** Capture all significant learnings. Create issues for bugs found, TODOs for incomplete work. Verify new memories are connected to existing ones.
## Every Memory Must Have
1. **Tags** — project identifier first, then topic tags.
2. **Hierarchy metadata** — places the memory in the knowledge graph.
3. **Links** — explicit connections to related memories.
4. **Sufficient granularity** — specific enough to be actionable, with file paths and function names.

View file

@ -0,0 +1,17 @@
# Sub-Agents and Team Agents
## Use Sub-Agents (Task tool) When
- Independent research tasks can run in parallel.
- A specialized agent type matches the work (e.g., debugger, test-engineer, frontend-developer).
- The main context window would be polluted by excessive search results.
## Use Team Agents When
- The task benefits from multiple specialized perspectives.
- Code review, security audit, or test analysis is warranted.
## Use Direct Tools Instead When
- Simple, directed searches — use Grep/Glob directly.
- Single-file edits or tasks under 3 steps.

View file

@ -0,0 +1,11 @@
# Document Import Resolution
When CLAUDE.md files reference external content via `@` imports (e.g., `@docs/architecture.md`), resolve and read those imports before proceeding with the user's request.
## Rules
- Before acting on a user prompt, scan loaded CLAUDE.md files for `@path/to/file` references. Read any that may be relevant to the current task.
- Treat `@docs/` references as pointers to the project's `docs/` directory.
- When a CLAUDE.md says "documentation lives in `docs/`" or "see `docs/` for details," read the relevant docs before proceeding.
- Do not skip imports because "the CLAUDE.md summary seems sufficient." The referenced document is the source of truth.
- After reading imports, reconcile conflicts between the import and the CLAUDE.md summary. Flag discrepancies.

View file

@ -0,0 +1,12 @@
# Preexisting Issues
Never ignore problems you encounter in the codebase, even if they are outside the current task scope.
## Rules
- When you encounter a bug, lint error, type error, broken test, or code smell while working on a task, do not skip it.
- If the fix is straightforward (under ~15 minutes of work), fix it in a separate commit with a clear message explaining what was wrong.
- If the fix is complex (large refactor, architectural change, risk of regression), stop and inform the user: describe the issue, its severity, where it lives, and propose a plan to fix it. Do not attempt complex fixes without approval.
- Never suppress warnings, disable lint rules, or add `// @ts-ignore` to hide preexisting issues. Surface them.
- When fixing a preexisting issue, add a test that would have caught it if one does not already exist.
- Track issues you cannot fix immediately: flag them to the user and, if Memora is available, create an issue memory.

View file

@ -0,0 +1,17 @@
# Theme Integration (CSS)
All UI components MUST use the project's CSS custom properties for colors. Never hardcode color values.
## Rules
- **Backgrounds**: Use `var(--ctp-base)`, `var(--ctp-mantle)`, `var(--ctp-crust)`, `var(--ctp-surface0)`, `var(--ctp-surface1)`, `var(--ctp-surface2)`.
- **Text**: Use `var(--ctp-text)`, `var(--ctp-subtext0)`, `var(--ctp-subtext1)`.
- **Muted/overlay text**: Use `var(--ctp-overlay0)`, `var(--ctp-overlay1)`, `var(--ctp-overlay2)`.
- **Accents**: Use `var(--ctp-blue)`, `var(--ctp-green)`, `var(--ctp-mauve)`, `var(--ctp-peach)`, `var(--ctp-pink)`, `var(--ctp-red)`, `var(--ctp-yellow)`, `var(--ctp-teal)`, `var(--ctp-sapphire)`, `var(--ctp-lavender)`, `var(--ctp-flamingo)`, `var(--ctp-rosewater)`, `var(--ctp-maroon)`, `var(--ctp-sky)`.
- **Per-project accent**: Use `var(--accent)` which is set per ProjectBox slot.
- **Borders**: Use `var(--ctp-surface0)` or `var(--ctp-surface1)`.
- Never use raw hex/rgb/hsl color values in component CSS. All colors must go through `--ctp-*` variables.
- Hover states: typically lighten by stepping up one surface level (e.g., surface0 -> surface1) or change text from subtext0 to text.
- Active/selected states: use `var(--accent)` or a specific accent color with `var(--ctp-base)` background distinction.
- Disabled states: reduce opacity (0.4-0.5) rather than introducing gray colors.
- Use `color-mix()` for semi-transparent overlays: `color-mix(in srgb, var(--ctp-blue) 10%, transparent)`.

View file

@ -0,0 +1,10 @@
# No Implicit Push
Never push to a remote repository unless the user explicitly asks for it.
## Rules
- Commits are local-only by default. Do not follow a commit with `git push`.
- Only push when the user says "push", "push it", "push to remote", or similar explicit instruction.
- When the user asks to "commit and push" in the same request, both are explicitly authorized.
- Creating a PR (via `gh pr create`) implies pushing — that is acceptable.

View file

@ -0,0 +1,17 @@
# Relative Units (CSS)
Use relative units (`em`, `rem`, `%`, `vh`, `vw`) for layout and spacing. Pixels are acceptable only for:
- Icon sizes (`width`/`height` on `<svg>` or icon containers)
- Borders and outlines (`1px solid ...`)
- Box shadows
## Rules
- **Layout dimensions** (width, height, max-width, min-width): use `em`, `rem`, `%`, or viewport units.
- **Padding and margin**: use `em` or `rem`.
- **Font sizes**: use `rem` or `em`, never `px`.
- **Gap, border-radius**: use `em` or `rem`.
- **Media queries**: use `em`.
- When existing code uses `px` for layout elements, convert to relative units as part of the change.
- CSS custom properties for typography (`--ui-font-size`, `--term-font-size`) store `px` values because they feed into JS APIs (xterm.js) that require pixels. This is the only exception beyond icons/borders.

View file

@ -0,0 +1,32 @@
# Testing Gate (Post-Implementation)
Run the full test suite after every major change before considering work complete.
## What Counts as a Major Change
- New feature or component
- Refactoring that touches 3+ files
- Store, adapter, or bridge modifications
- Rust backend changes (commands, SQLite, sidecar)
- Build or CI configuration changes
## Required Command
```bash
cd v2 && npm run test:all
```
This runs vitest (frontend) + cargo test (backend). For changes touching E2E-relevant UI or interaction flows, also run:
```bash
cd v2 && npm run test:all:e2e
```
## Rules
- Do NOT skip tests to save time. A broken test suite is a blocking issue.
- If tests fail, fix them before moving on. Do not defer test fixes to a follow-up.
- If a change breaks existing tests, that's signal — investigate whether the change or the test is wrong.
- When adding new logic, add tests in the same commit (TDD preferred, see rule 06).
- After fixing test failures, re-run the full suite to confirm no cascading breakage.
- Report test results to the user: pass count, fail count, skip count.