# Repository Guidelines ByteRover CLI (`brv`) - Interactive REPL with React/Ink TUI ## Commands ```bash npm run build # Compile to dist/ npm run dev # Kill daemon + build + run dev mode npm test # All tests npx mocha --forbid-only "test/path/to/file.test.ts" # Single test npm run lint # ESLint npm run typecheck # TypeScript (root + src/webui/tsconfig.json) npm run build:ui # Build the web UI bundle (Vite); runs automatically as part of `build` npm run dev:ui # Vite dev server for the web UI npm run dev:kill # Kill the running daemon (re-run before `npm run dev`) npm run lint:fix # ESLint with --fix npm run build:ui:submodule # Build web UI from the local submodule (vs default published package) npm run dev:ui:package # Vite dev server resolving shared UI from the installed package ./bin/dev.js [command] # Dev mode (ts-node) ./bin/run.js [command] # Prod mode ``` **Test dirs**: `test/commands/`, `test/unit/`, `test/integration/`, `test/hooks/`, `test/learning/`, `test/helpers/`, `test/shared/` **Note**: Run tests from project root, not within test directories ## Development Standards **TypeScript**: - Avoid `as Type` assertions - use type guards or proper typing instead - Avoid `any` type - use `unknown` with type narrowing or proper generics - Functions with >3 parameters must use object parameters - Prefer `type` for data-only shapes (DTOs, payloads, configs); prefer `interface` for behavioral contracts with method signatures (services, repositories, strategies) - Default to `undefined` over `null`; reserve `null` only for external boundaries (storage, HTTP APIs) that force it — normalize to `undefined` before the value flows into internal modules - Avoid `!` non-null assertions — narrow with type guards or throw explicitly. Lazy-initialized singletons (e.g. `this.services!.foo` after a guaranteed init step) are the only acceptable exception - Use `??` for nullish defaults (not `||`, which also triggers on `0`/`''`/`false`) and `?.` for safe property access - Prefer optional properties (`foo?: T`) over `foo: T | undefined` when a key may legitimately be absent **Testing (Strict TDD — MANDATORY)**: - You MUST follow Test-Driven Development. This is non-negotiable. - **Step 1 — Write failing tests FIRST**: Before writing ANY implementation code, write or update tests that describe the expected behavior. Do NOT write implementation and tests together or in reverse order. - **Step 2 — Run tests to confirm they fail**: Execute the relevant test file to verify the new tests fail for the right reason (missing implementation, not a syntax error). - **Step 3 — Write the minimal implementation**: Write only enough code to make the failing tests pass. Do not add untested behavior. - **Step 4 — Run tests to confirm they pass**: Execute tests again to verify all tests pass. - **Step 5 — Refactor if needed**: Clean up while keeping tests green. - If you catch yourself writing implementation code without a failing test, STOP and write the test first. - 80% coverage minimum, critical paths must be covered. - Suppress console logging in tests to keep output clean. - Unit tests must run fast and run completely in memory. Proper stubbing and mocking must be implemented. **Feature Development (Outside-In Approach — applies to ALL work: planning, reviewing, coding, and auditing)**: - This is a foundational principle, not just a coding guideline. Apply it when writing code, reviewing plans, designing milestones, evaluating project structure, or auditing existing work. If a plan, project, or milestone ordering violates Outside-In, flag it. - Start from the consumer (oclif command, REPL command, or TUI component) — understand what it needs - Define the minimal interface — only what the consumer actually requires - Implement the service — fulfill the interface contract - Extract entities only if needed — when shared structure emerges across multiple consumers - Avoid designing in isolation — always have a concrete consumer driving requirements - When reviewing or planning: if entities, types, or store interfaces are designed before any consumer exists to validate them, that is Inside-Out and must be flagged ## Architecture ### Source Layout (`src/`) - `agent/` — LLM agent: `core/` (interfaces/domain), `infra/` (modules including llm, memory, map, swarm, sandbox, session, tools, document-parser), `resources/` (prompts YAML, tool `.txt` descriptions) - `server/` — Daemon infrastructure: `config/`, `core/` (domain/interfaces), `infra/` (modules including vc, git, hub, mcp, cogit, connectors, project, provider-oauth, session, space, dream, webui, billing, transport, executor, storage, context-tree), `templates/`, `utils/` - `shared/` — Cross-module: constants, types, transport events, utils - `tui/` — React/Ink TUI: app (router/pages), components, features (24 modules, including vc, worktree, source, hub, curate, settings), hooks, lib, providers, stores - `webui/` — Browser dashboard (React/Vite). Entry `src/webui/index.tsx`; `features/` (16 panels), `pages/` (8 pages: home, changes, configuration, contexts, tasks, analytics, project-selector, not-found — configuration sub-routes live under `pages/configuration/`: layout, general, connectors, version-control), `layouts/`, `stores/`. Connects to the daemon via Socket.IO; no imports from `server/`, `agent/`, or `tui/` (same boundary rule) - `oclif/` — Commands grouped by topic (`vc/`, `hub/`, `worktree/`, `source/`, `space/`, `review/`, `connectors/`, `curate/`, `model/`, `providers/`, `swarm/`, `query-log/`, `settings/`) + top-level `.ts` commands (`webui`, `dream`, `review`, `search`, `locations`, `query`, `login`, `logout`, `init`, `mcp`, `pull`, `push`, `restart`, `status`, `debug`) + hidden internals (`main` — default `brv` REPL entry; `hook-prompt-submit` — emits `brv-instructions` template for coding-agent pre-prompt hooks, e.g. Claude Code `UserPromptSubmit`); hooks, lib (daemon-client, task-client, json-response) **Import boundary** (ESLint-enforced): `tui/` must not import from `server/`, `agent/`, or `oclif/`. Use transport events or `shared/`. ### REPL + TUI - `brv` (no args) starts REPL (`src/tui/repl-startup.tsx`) - Esc cancels streaming responses and long-running commands - Slash commands in `src/tui/features/commands/definitions/` (order in `index.ts` = suggestion order) ### Daemon - Global daemon (`server/infra/daemon/`) hosts Socket.IO transport; clients connect via `@campfirein/brv-transport-client` - Agent pool manages forked child processes per project; task routing in `server/infra/process/` - MCP server in `server/infra/mcp/` exposes tools via Model Context Protocol; `tools/` subdir has dedicated implementations (`brv-query-tool`, `brv-curate-tool`) - Connector subsystem (`server/infra/connectors/`): `rules/`, `skill/`, `mcp/`, `hook/`, `shared/`. Shared rules files (e.g. `AGENTS.md` shared by Amp/Codex/OpenCode) embed an agent-name footer inside `` markers — see `shared/constants.ts` (`BRV_RULE_MARKERS`, `BRV_RULE_TAG`, `extractInstalledAgentFromBrvSection`) — so each agent owns its own segment. Preserve the footer when editing rule-section logic ### Task Cancellation - `brv curate | query | dream --cancel ` — emits `TaskEvents.CANCEL`; shared CLI helper `src/oclif/lib/cancel-task.ts` formats text or JSON envelope. SIGINT in the foreground command triggers the same cancel path (`task-client-sigint`) - Daemon: `server/infra/daemon/agent-cancel-listener.ts` routes the cancel into the running agent process; `agent-executor-error.ts` distinguishes user-cancel from real failures so cancelled tasks aren't recorded as errors in billing/history - TUI: Esc cancels streaming (covered in REPL section); Tasks tab supports cancel keybind via `tui/features/tasks/hooks/use-cancel-running-task-keybind.ts` + `select-cancel-target.ts`, mounted through `cancel-keybind-initializer.tsx` in app providers - WebUI: cancel button in `webui/features/tasks/components/task-list-table.tsx` + `task-detail-header.tsx`, wired via `webui/features/tasks/api/cancel-task.ts` and `utils/row-action-kind.ts` ### Web UI (`src/webui/`, `src/server/infra/webui/`) - `brv webui [-p, --port ]` opens the dashboard in the default browser. Port is persisted via daemon events (`webui:getPort` / `webui:setPort`); first-run default is 7700 - Server side: `server/infra/webui/` — `webui-server.ts` (standalone HTTP server), `webui-middleware.ts` (Express static files + `/api/ui/config`), `webui-state.ts` (port persistence). CSP headers applied - Browser bootstraps by fetching `/api/ui/config` to discover the daemon's dynamic Socket.IO port, then connects cross-origin - Daemon `ClientType` includes `'webui'` alongside `'tui' | 'cli' | 'agent' | 'mcp' | 'extension'` - Build/dev: `npm run build:ui` (Vite, runs as part of `npm run build`); `npm run dev:ui` for live reload. `typecheck` runs both the root and `src/webui/tsconfig.json` - Shared UI components live in a git submodule at `packages/byterover-packages/` (published as `@campfirein/byterover-packages`). `dev:ui` / `build:ui:submodule` read from the submodule; `build:ui` / `dev:ui:package` read from the installed `node_modules` copy. Override Vite's resolution with `BRV_UI_SOURCE=submodule|package`. Fresh clones have an empty submodule dir — run `git submodule update --init --recursive` before using submodule mode or Vite resolution will fail ### VC, Worktrees & Knowledge Sources - `brv vc` — isomorphic-git version control (add, branch, checkout, clone, commit, config, diff, fetch, init, log, merge, pull, push, `remote add|remove|set-url`, reset, status); git plumbing in `server/infra/git/` (`isomorphic-git-service.ts`), VC config store in `server/infra/vc/` - `brv worktree` (add/list/remove) — git-style worktree pointer model: `.brv/` is either a real project directory OR a pointer file to a parent project; parent stores registry in `.brv/worktrees//link.json` - `brv source` (add/list/remove) — link another project's context tree as a read-only knowledge source with write isolation - `brv search ` — pure BM25 retrieval over the context tree (minisearch, no LLM, no token cost); structured results with paths/scores. Pairs with `brv query` (LLM-synthesized answer). Engine: `server/infra/executor/search-executor.ts` - `brv locations` — lists all registered projects with context-tree status (text or `--format json`); reads from `LocationsEvents` over the daemon transport - `brv query-log view [id]` / `brv query-log summary` — inspect query history and recall metrics (coverage, cache hit rate, top topics); store: `server/infra/storage/file-query-log-store.ts`, summary use-case in `server/infra/usecase/` - `brv dream [--force] [--undo] [--detach]` — background context-tree consolidation; operations in `server/infra/dream/operations/` (synthesize, consolidate, prune); lock/state via `dream-lock-service.ts` + `dream-state-service.ts` - Runtime signals sidecar — file-level usage/maturity data lives in `RuntimeSignalStore` (`server/infra/context-tree/runtime-signal-store.ts`, `IKeyStorage` keys `["signals", ...pathSegments]`), NOT in synthesized markdown frontmatter. Read: search-knowledge + manifest service; write: curate-service + dream-executor synthesize. Schema: `server/core/domain/knowledge/runtime-signals-schema.ts` - `parentTaskId` threading — `generateSummary` / `propagateStaleness` on `file-context-tree-summary-service.ts` take an optional `parentTaskId`. Curate, dream, and folder-pack executors (`server/infra/executor/`) MUST thread the operation's `taskId` through so child summary regenerations roll up under one parent task instead of N detached billing rows - Canonical project resolver: `resolveProject()` in `server/infra/project/` — priority `flag > direct > linked > walked-up > null`. `projectRoot` and `worktreeRoot` are threaded through transport schemas, task routing, and all executors - All commands are daemon-routed: `oclif/` and `tui/` never import from `server/` - Oclif: `src/oclif/commands/{vc,worktree,source}/`; TUI: `src/tui/features/{vc,worktree,source}/`; slash commands (`vc-*`, `worktree`, `source`) in `src/tui/features/commands/definitions/` - `brv curate` runs Phases 1–3 in the foreground and detaches Phase 4 (post-curate finalization: summary regeneration, manifest rebuild) to the daemon's `PostWorkRegistry`, which serializes per project and coordinates with `dream-lock-service.ts` to prevent concurrent `_index.md` writes. `--detach` makes the entire run background. Overlapping curate runs for the same project are still rejected. Behavioral contract lives in `src/server/templates/sections/` (`brv-instructions.md`, `workflow.md`, `skill/SKILL.md`) — the in-daemon agent reads these at runtime - `brv review [--disable | --enable]` — toggle the project-scoped HITL review log; `brv review pending` lists items, `brv review approve ` / `brv review reject ` resolve them. When disabled, sync curate skips the "X operations require review" prompt, detached curate stops emitting per-operation review markers, and `brv dream` no longer surfaces `needsReview` operations. The flag is snapshotted at task creation and propagated via `AsyncLocalStorage` (`resolveReviewDisabled`) so mid-task toggles do not race - HITL WebUI: pending operations render in the Changes tab (`webui/features/vc/`); the on/off toggle (`hitl-settings-panel.tsx`) is mounted on the Configuration → Version Control sub-page (`webui/pages/configuration/version-control.tsx`). Reject restores the pre-operation file snapshot from `file-review-backup-store.ts` (`server/infra/storage/`); transport via `review-handler.ts` + `shared/transport/events/review-events.ts` - `brv login` defaults to OAuth (interactive provider picker); pass `--api-key` only for CI. `brv logout` clears credentials ### LLM Billing - Paid LLM operations (`brv query`, `brv curate`, status checks) call `ensureBillingFunds()` (`src/oclif/lib/insufficient-credits.ts`) before dispatching, throwing `InsufficientCreditsError` if no paid team has credits - Server: `server/infra/billing/` — `http-billing-service.ts` (HTTP client against `BRV_BILLING_BASE_URL`), `resolve-billing-source.ts` (priority: project-pinned team > org default), `resolve-billing-team.ts`, `build-status-billing.ts` (status payload), `paid-organizations-endpoint.ts`, `billing-state-endpoint.ts`. Pinned-team selection persisted via `server/infra/storage/file-billing-config-store.ts` - Transport: `BillingEvents` / `TeamEvents` in `shared/transport/events/` (resolve, get/list usage, get/set pinned team, get free-user limit, list teams). Handlers: `billing-handler.ts`, `team-handler.ts`, `status-handler.ts` - CLI status line: `brv status` / `brv providers list` render credits via `oclif/lib/billing-line.ts` + `format-billing-line.ts` - `brv providers connect` (byterover provider) runs a team-select step that emits `BillingEvents.SET_PINNED_TEAM` - WebUI: credits pill (`webui/features/provider/components/credits-pill.tsx`), team-select step in `provider-flow-dialog.tsx`, billing API wrappers in `webui/features/provider/api/` (`list-billing-usage`, `list-teams`, `get-pinned-team`, `set-pinned-team`, `get-free-user-limit`) ### Settings - `brv settings` (bare = list) / `brv settings get ` / `set ` / `reset ` — user-configurable settings persisted at `/settings.json`. Most settings need `brv restart` to take effect; boolean settings like `update.checkForUpdates` apply live (`restartRequired: false` on the descriptor) - Categories: `concurrency`, `llm`, `task-history`, `updates`. Keys: `agentPool.maxSize`, `agentPool.maxConcurrentTasksPerProject`, `llm.iterationBudgetMs`, `llm.requestTimeoutMs`, `taskHistory.maxEntries`, `update.checkForUpdates`. Descriptors in `server/core/domain/entities/settings.ts` are discriminated on `type: 'integer' | 'boolean'` and reference `src/constants.ts` so a constant change updates the setting's default automatically. `update.checkForUpdates` gates the `update-notifier` init hook and the `block-autoupdate-when-off` init hook (which rejects `brv update` when the user has opted out). Agent process consumes settings via `agent/infra/settings/agent-settings-snapshot.ts` forwarded by `server/infra/daemon/agent-process.ts` - Server: `server/infra/storage/file-settings-store.ts` + `settings-validator.ts`; `server/infra/daemon/settings-bootstrap.ts` reads settings at boot and feeds AgentPool (maxSize, maxConcurrentTasks) + task-history cache. Transport: `shared/transport/events/settings-events.ts` + `server/infra/transport/handlers/settings-handler.ts` - UI: TUI `tui/features/settings/`, WebUI `webui/features/settings/` (rendered under `pages/configuration/` sub-routes). Task heartbeat (`server/infra/process/task-heartbeat-manager.ts`) and task-history cache (`server/infra/process/task-history-store-cache.ts`) are tuned via these keys; oclif task-client uses heartbeats — explicit task timeouts are deprecated ### Other oclif topic groups - `brv hub install | list | registry (add|list|remove)` — Context Hub (npm-style package manager for context); registry config is per-project - `brv space list | switch` — Context Hub spaces (team/space scoping) - `brv connectors install | list` — agent-side connector plugins (e.g., OpenClaude registered as a connector) - `brv model list | switch` — pick the LLM model for the active provider - `brv providers connect | disconnect | list | switch` — provider OAuth / API-key management - `brv curate view` — read-only inspect of a curate operation; the main curate entry is the REPL slash command ### Agent (`src/agent/`) - Tools: definitions in `resources/tools/*.txt`, implementations in `infra/tools/implementations/`, registry in `infra/tools/tool-registry.ts` - Tool categories: file ops (read/write/edit/glob/grep/list-dir), bash (exec/output), knowledge (create/expand/search), memory (read/write/edit/delete/list), swarm (query/store), todos (read/write), curate, code exec, batch, detect domains, kill process, search history - LLM: 20 providers in `infra/llm/providers/` (incl. deepseek, glm, glm-coding-plan, xai); compression strategies in `infra/llm/context/compression/` - System prompts: contributor pattern (XML sections) in `infra/system-prompt/` - Map/memory: `infra/map/` (agentic map, context-tree store, LLM map memory, worker pool); `infra/memory/` (memory-manager, deduplicator) - Storage: file-based blob (`infra/blob/`) and key storage (`infra/storage/`) — no SQLite ### Swarm (`src/agent/infra/swarm/`, `src/oclif/commands/swarm/`) - Multi-provider memory/knowledge federation: routes queries and writes across pluggable adapters (byterover, gbrain, local-markdown, memory-wiki, obsidian) - `brv swarm query` — RRF-fused search across providers; flags: `--explain`, `--format`, `-n` - `brv swarm curate` — auto-routes content to best provider; flags: `--provider`, `--format` - `brv swarm onboard` — interactive wizard (`@inquirer/prompts`) to scaffold swarm config; uses snake_case YAML keys (`eslint-disable camelcase`) - `brv swarm status` — pre-flight health check for configured providers - Agent tools: `swarm_query.txt`, `swarm_store.txt` in `resources/tools/` - Config: `swarm/config/` (loader + schema), `swarm/validation/` (config validator) - CLI-only (oclif) — no TUI feature dir; swarm queries flow through existing `tui/features/query/` ## Testing Gotchas - **HTTP (nock)**: Must verify `.matchHeader('authorization', ...)` + `.matchHeader('x-byterover-session-id', ...)` - **ES Modules**: Cannot stub ES exports with sinon; test utils with real filesystem (`tmpdir()`) ## Conventions - ES modules with `.js` import extensions required - `I` prefix for interfaces; `toJson()`/`fromJson()` (capital J) for serialization - Snake_case APIs: `/* eslint-disable camelcase */` ## Environment - `BRV_ENV` — `development` | `production` (dev-only commands require `development`, set by `bin/dev.js` and `bin/run.js`) - Required service base URLs (loaded from `.env.development` / `.env.production`, see `.env.example`): `BRV_IAM_BASE_URL`, `BRV_COGIT_BASE_URL`, `BRV_LLM_BASE_URL`, `BRV_BILLING_BASE_URL`, `BRV_WEB_APP_URL`, `BRV_GIT_REMOTE_BASE_URL`. Loaded in `server/config/environment.ts`; IAM/COGIT/BILLING are additionally validated by `assertRootDomain` (no paths allowed). All are normalized to strip trailing slashes - `BRV_WEBUI_PORT` — override the web UI port (default `7700`) - `BRV_UI_SOURCE` — `submodule` | `package` — forces Vite's shared-UI resolution mode - `BRV_DATA_DIR` — override the global data dir (default `~/.brv`) - `BRV_GIT_REMOTE_BASE_URL` — override git remote base URL (beta vs prod testing) - `BRV_QUEUE_TRACE` — set to `1` to log queue/agent map traces (cipher-agent, abstract-queue) - `BRV_SESSION_LOG` — file path for daemon/agent session logs (auto-set by `brv-server`; can override for debugging) - `BRV_E2E_MODE` — `true` switches the daemon to e2e-friendly stdio handling - `BRV_AGENT_PROCESS_PATH` / `BRV_AGENT_PORT` / `BRV_AGENT_PROJECT_PATH` — auto-set by the daemon when forking agent child processes; do not set manually ## Stack oclif v4, TypeScript (ES2022, Node16 modules, strict), React/Ink (TUI), Zustand, axios, socket.io, isomorphic-git, Mocha + Chai + Sinon + Nock