agentleFS
Sign inSign up

the-pair

timwuhaotian/the-pair/CLAUDE.md

AGENTS.md contains the longer architectural reference (module table, component table, state machine, release process). Read it when you need that depth; this file covers the essentials for getting productive fast. The Pair is a Tauri 2 desktop app (Rust backend, React 19 + TypeScript frontend) that orchestrates two AI agents working together on a coding task: The Rust backend spawns local provider CLIs (opencode, claude, codex, gemini, kimi, pi, kiro, aider, grok, muse), parses their JSON event streams, runs a…

CLAUDE.md363 starsChanged 4 months ago
  • Installs packages
  • Commits and pushes
# CLAUDE.md

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

`AGENTS.md` contains the longer architectural reference (module table, component table, state machine, release process). Read it when you need that depth; this file covers the essentials for getting productive fast.

## What this app is

The Pair is a Tauri 2 desktop app (Rust backend, React 19 + TypeScript frontend) that orchestrates two AI agents working together on a coding task:

- **Mentor** — read-only planner/reviewer
- **Executor** — code writer / command runner

The Rust backend spawns local provider CLIs (`opencode`, `claude`, `codex`, `gemini`, `kimi`, `pi`, `kiro`, `aider`, `grok`, `muse`), parses their JSON event streams, runs a turn-based state machine, monitors process resources, and tracks git changes. The React frontend renders the conversation, status, and controls.

## Commands

```bash
# Setup
npm install
npm run preflight              # Verify rust/node/CLI tools are present

# Dev (runs Tauri shell + Vite renderer on :5173)
npm run dev                    # Uses scripts/run-with-rustup.mjs to inject PATH
npm run dev:mock               # Same, with THE_PAIR_E2E_MOCK=true (no real CLI spawns)
npm run dev:renderer           # Renderer-only (browser at :5173, no Tauri APIs)

# Quality gates (run before committing)
npm run lint                   # eslint --cache
npm run typecheck              # Runs both typecheck:node and typecheck:web
npm test                       # JS tests then `cargo test` for src-tauri
npm run test:js                # node --test against tests/*.test.ts (tsx loader)
npm run test:rust              # cargo test in src-tauri

# Run a single JS test file
node --import tsx --test tests/handoffGuard.test.ts

# Run a single Rust test
cargo test -q --manifest-path src-tauri/Cargo.toml <test_name>

# Builds (each runs preflight + ensures rust targets first)
npm run build:mac              # universal-apple-darwin DMG
npm run build:mac:release      # Release ZIP bundle used in GitHub Releases
npm run build:win              # x86_64-pc-windows-msvc
npm run build:linux            # x86_64-unknown-linux-gnu

# E2E (Appium + WebdriverIO, macOS only, mock mode)
npm run e2e:setup              # One-time: appium driver install mac2
npm run e2e

# Release
npm run bump <version>         # Updates package.json, package-lock.json, src-tauri/Cargo.toml + Cargo.lock
npm run validate:changelog
```

`npm test` does **not** invoke the renderer or run e2e — `tests/*.test.ts` are pure-Node unit tests covering lib utilities, store logic, and build scripts. Renderer components are tested by reasoning about pure helpers extracted out of them (see `tests/dashboardPairs.test.ts`, `tests/pairListSection.test.ts`).

## Architecture in 60 seconds

```
src/renderer/src/          React 19 frontend
  components/              UI (PascalCase.tsx); primitives live in components/ui/
  store/                   Zustand stores: usePairStore, useUpdateStore, useThemeStore, useLocaleStore
  lib/                     Pure helpers — testable without React. tauri-api.ts wraps invoke()
  hooks/                   Cross-component React hooks
  locales/  i18n.ts        i18next setup (en/zh/ja/ko)
  assets/main.css          Tailwind v4 + theme tokens (light/dark)

src-tauri/src/             Rust backend (see AGENTS.md for the full module table)
  lib.rs                   Registers every Tauri command in invoke_handler![]
  pair_manager.rs          Pair CRUD + task assignment
  message_broker.rs        Turn-coordination state machine
  process_spawner.rs       Spawns provider CLIs, parses JSON event streams
  provider_adapter.rs      Per-provider Input/Output/Session/Permission/Cwd strategies
  provider_registry.rs     Detect installed CLIs + their model catalogs
  session_snapshot.rs      Persist/restore full pair state
  resource_monitor.rs      Per-agent CPU/memory polling
  git_tracker.rs           Diff vs baseline commit
  recent_activity.rs       Recent activity feed shown on the dashboard

tests/                     Node --test unit tests (TypeScript via tsx)
e2e/                       Appium + WDIO end-to-end specs (macOS, mock mode)
scripts/                   Node-based build/release tooling
```

### How frontend talks to backend

Always go through `src/renderer/src/lib/tauri-api.ts`, which wraps `invoke()` from `@tauri-apps/api/core` with typed helpers. The renderer never imports Node built-ins; the only escape hatch is `tauri-shim.ts`, which provides safe fallbacks when running under `npm run dev:renderer` (no Tauri host).

### Adding a Tauri command

1. Implement `#[tauri::command]` in the appropriate `src-tauri/src/<module>.rs`.
2. Add the function to the `tauri::generate_handler![]` list in `src-tauri/src/lib.rs`.
3. Add a typed wrapper in `src/renderer/src/lib/tauri-api.ts`.
4. Cover with a unit test in `tests/` (pure logic) and/or a Rust test in the module file.

### State machine

`Idle → Mentoring → Executing → Reviewing → (loop | Finished | Paused | Awaiting Human Review | Error)`

Pairs hit an `iteration_limit` and pause for human review rather than running unbounded. `src/renderer/src/lib/handoffGuard.ts` prevents duplicate handoff events firing after a pair finishes — keep using it when adding new transition logic.

### Provider support

Ten kinds: `opencode`, `codex` (OpenAI), `claude` (Claude Code), `gemini`, `kimi` (Kimi Code CLI), `pi`, `kiro`, `aider`, `grok` (xAI Grok Build CLI), `muse` (Meta Muse Code CLI). Each has its own transport / session / permission / cwd strategy in `provider_adapter.rs`, implemented as a `Provider` impl under `src-tauri/src/providers/`. Detection logic and model catalogs live in `provider_registry.rs` and `model_catalog.rs`. When adding provider features, update both.

## Conventions

- **TypeScript only** in the renderer — no `.js` files, no `any`. Use `interface` for object shapes, export shared types from a local `types.ts`.
- **Tailwind v4 only** for styling — no inline styles, no CSS modules. Use `cn()` from `src/renderer/src/lib/utils.ts` to merge classes. Theme tokens are defined in `src/renderer/src/assets/main.css`.
- **Zustand** for global state; component-level state via hooks. Keep stores minimal — derive computed values rather than caching them.
- **Path aliases**: `@renderer/*` and `@/*` both map to `src/renderer/src/*` (see `vite.config.ts`).
- **Conventional Commits** for messages (`feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `style`). PRs are squash-merged.
- **Husky + lint-staged** run `eslint --fix` and `prettier --write` on staged files on commit. Do not use `--no-verify` unless explicitly asked.

## Mock mode

Set `THE_PAIR_E2E_MOCK=true` (e.g. via `npm run dev:mock`) to short-circuit real CLI process spawning. `THE_PAIR_E2E_MOCK_SCENARIO=dev-smoke` seeds a richer mock dataset. This is the path used by e2e specs and is useful when iterating on UI without provider CLIs installed.

## Releases — DO NOT push tags manually

GitHub Actions (`build-signed-mac.yml`) auto-tags and publishes when a version bump lands on `main`. The full flow:

1. `npm run bump <version>` (updates `package.json`, the root entries of `package-lock.json`, the `[package]` version in `src-tauri/Cargo.toml` and the app entry in `src-tauri/Cargo.lock` in lock-step; `tauri.conf.json` reads its version from `package.json`)
2. Update `CHANGELOG.md` — the heading must start its line as `## [X.Y.Z] - YYYY-MM-DD` and the section must not be empty (the release gate and the release-notes extraction share `scripts/changelog.mjs`)
3. Run `npm test && npm run typecheck && npm run lint && npm run build`
4. `git commit -m "chore: bump version to X.Y.Z"` and `git push` — that's it

Never run `git tag` or `git push --tags`. The workflow tags the pushed commit (`--target $GITHUB_SHA`), runs one release at a time (a run queued behind a release skips once the tag exists), and fails rather than overwrite an existing release. Fallback if the workflow misses the bump: `gh workflow run build-signed-mac.yml`. See `docs/RELEASE_CHECKLIST.md`.

### Version bump keywords (semantic versioning)

When deciding which version part to bump:

| Conventional commit type                                           | Release type      | Version bump           |
| ------------------------------------------------------------------ | ----------------- | ---------------------- |
| `fix:`, `docs:`, `chore:`, `style:`                                | **patch release** | `Z` +1 (2.0.0 → 2.0.1) |
| `feat:` (backward-compatible)                                      | **minor release** | `Y` +1 (2.0.0 → 2.1.0) |
| `feat:` with breaking changes, API removals, state machine changes | **major release** | `X` +1 (2.0.0 → 3.0.0) |

**Rule of thumb:** If you're only fixing bugs, updating docs, or doing internal refactors → patch. If you're adding new features/components/commands without breaking existing behavior → minor. If you're removing public APIs, redesigning the UI, or changing the state machine → major.

Discussion

Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.

Posts are public.Sign in to post

No one has posted yet. Be the first.