Skip to content

Latest commit

 

History

History
309 lines (232 loc) · 31.7 KB

File metadata and controls

309 lines (232 loc) · 31.7 KB

CLAUDE.md

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

Claudette

Cross-platform desktop orchestrator for parallel Claude Code agents, built with Rust (Tauri 2) and React/TypeScript.

AI assistant configuration

This file is the source of truth for project conventions. Several companion configs read by other AI tools live alongside it and need to stay aligned when the rules here change:

  • .github/copilot-instructions.md — repo-wide guidance read by GitHub Copilot on every PR review and code suggestion.
  • .github/instructions/rust.instructions.md — scoped to **/*.rs via applyTo frontmatter.
  • .github/instructions/frontend.instructions.md — scoped to src/ui/**/*.{ts,tsx,css}.
  • .github/instructions/regression-review.instructions.md — scoped to all files; used during PR review for regression-first checking.
  • AGENTS.md — symlink to this CLAUDE.md, read by Codex / OpenCode / other agent tools that look for that filename. Edit CLAUDE.md, never AGENTS.md.

When you change architecture, commands, code-style, regression rules, or god-file lists in this CLAUDE.md, scan the Copilot files for the same statement and update both. Drift here causes Copilot's PR reviews to flag (or miss) issues that don't match what we tell humans.

Documentation discipline

Always update the user-facing docs in the same PR as the feature change. Adding, removing, or changing user-visible behavior — settings, commands, CLI flags, keyboard shortcuts, environment variables, file locations, plugin manifests, notification triggers — requires a matching docs change. The docs site lives at site/src/content/docs/ (Astro Starlight). Two surfaces matter:

  • site/src/content/docs/features/<topic>.mdx — the per-feature deep-dive page. New cross-cutting feature → new page. Existing feature gained a knob → update the page. Register new pages in the sidebar nav at site/astro.config.mjs so they're reachable.
  • site/src/content/docs/features/settings.mdx — the flat settings reference table. Every Settings panel control belongs in this file's matching ## <Section> table; add the row when you add the control.

If a change is intentionally undocumented (debug-only flag, internal env var nobody outside the repo should touch), call that out in the PR description so the omission is reviewed, not assumed. CI does not enforce this — the discipline does.

Build & test commands

# Backend (Rust)
cargo test -p claudette -p claudette-server -p claudette-cli --all-features  # Run all backend tests (CI runs the same crates via cargo llvm-cov)
cargo test -p claudette --test diff_tests        # Run a single test file
cargo test -p claudette parse_unified -- --exact # Run a single test by name
cargo clippy -p claudette -p claudette-server -p claudette-cli --all-targets --all-features  # Lint (CI command — must pass with zero warnings)
cargo clippy -p claudette-mobile --target aarch64-apple-darwin --all-targets --locked  # Mobile lint (CI command — macOS only)
cargo test -p claudette-mobile --locked          # Mobile tests (CI command — macOS only)
cargo fmt --all --check                          # Check formatting

# Frontend (React/TypeScript)
cd src/ui && bun install                         # Install frontend dependencies
cd src/ui && bun run build                       # Build frontend (runs tsc -b && vite build)
cd src/ui && bunx tsc -b                         # TypeScript type check (same as CI)
cd src/ui && bun run test                        # Run frontend tests (vitest)
cd src/ui && bun run test:watch                  # Run tests in watch mode
cd src/ui && bun run lint                        # ESLint check
cd src/ui && bun run lint:css                    # CSS design-token enforcement (also runs in CI)

# Full app
cargo tauri dev                                  # Dev mode with hot-reload
cargo tauri build                                # Release build

IMPORTANT: CI sets RUSTFLAGS="-Dwarnings" — all compiler warnings are errors. Fix warnings before committing.

IMPORTANT: Always run cd src/ui && bunx tsc -b after modifying TypeScript files (including tests). CI runs tsc -b via bun run buildvitest does not type-check (it uses esbuild), so tests can pass locally while types are broken. Run tsc -b as the final check before committing any frontend change.

CI also enforces bun install --frozen-lockfile — do not modify bun.lock without intention. CI runs cargo llvm-cov for Rust test coverage (uploaded to Codecov, informational/non-blocking). CI clippy lints claudette, claudette-server, and claudette-cli on Linux, runs scripts/stage-cli-sidecar.sh --profile debug and then cargo test -p claudette-tauri --no-default-features --features devtools,server,voice,alternative-backends,pi-sdk --no-run with the Linux Tauri system libraries installed (--no-run so the binary crate's test targets are compiled too — the test job only covers claudette / -server / -cli, so a non-compiling test fixture in claudette-tauri would otherwise slip through), and checks claudette-mobile on macOS (host target aarch64-apple-darwin, desktop-fallback build — iOS target compilation is intentionally not in CI; it requires Xcode + Apple SDK + cargo tauri ios init scaffolding). The Tauri check uses the shared Rust cache plus Bun's package cache for the Pi harness sidecar staging path; keep it cache-friendly when editing. Frontend CI runs bunx tsc --noEmit as a dedicated type-check step before bun run build.

Code style

  • Rust edition 2024 — use modern idioms (let chains, gen blocks if stabilized, etc.)
  • Default rustfmt and clippy rules — no custom overrides
  • Prefer cargo fmt before committing; CI enforces it
  • TypeScript: strict mode, no any types

Commit conventions

  • Conventional commits requiredfeat:, fix:, docs:, refactor:, test:, ci:, chore:, etc.
  • Header max 100 characters
  • PR titles must also follow conventional commit format (validated by CI)
  • Release management is automated via release-please

Architecture

  • GUI: Tauri 2.x (Rust backend) + React/TypeScript (webview frontend)
  • State management: Zustand (single store with domain slices)
  • Async runtime: Tokio for process management and git operations
  • Data persistence: SQLite via rusqlite (bundled)
  • Git operations: Shelling out to git via tokio::process::Command for worktree ops
  • Agent integration: Claude CLI subprocess with JSON streaming, bridged to frontend via Tauri events. The chat-send resolver dispatches on a per-backend runtime_harness (AgentBackendConfig::effective_harness in src/agent_backend.rs), not on AgentBackendKind directly — kind only declares which harnesses are valid; the user picks the active one in Settings > Models > Runtime. Ollama / LM Studio default to Pi, OpenAI cards to the Claude CLI gateway, Codex Native to the Codex app-server. Anthropic / Custom Anthropic / Codex Subscription are locked to Claude CLI so subscription OAuth tokens never reach Pi.
  • Terminal emulation: portable-pty (Rust) + xterm.js (frontend)
  • IPC: Tauri commands (#[tauri::command]) for request/response, Tauri events for streaming

Crate structure

Five crates in a Cargo workspace:

Crate Path Purpose
claudette src/ (workspace root) Core library — models, db, git, diff, agent logic, WSS transport. No UI or Tauri dependencies.
claudette-tauri src-tauri/ Tauri binary (claudette-app). Thin #[tauri::command] wrappers that call into claudette.
claudette-server src-server/ WebSocket server for remote access. Also embeddable in the Tauri binary. Uses PersistentSession so interactive controls (AskUserQuestion / ExitPlanMode) work over WSS.
claudette-cli src-cli/ Command-line client (claudette binary) that drives the running GUI over a local IPC socket.
claudette-mobile src-mobile/ Tauri 2 iOS / Android client — thin WSS remote-control app. Pairs with a running desktop or headless server; doesn't run agents locally. See src-mobile/README.md for the cargo tauri ios init setup.

Feature flags in claudette-tauri:

  • default = ["server", "voice", "devtools", "alternative-backends", "pi-sdk"]
  • voice — pulls in cpal (audio capture), candle-* (Whisper inference), tokenizers, rubato, hound. Linux requires libasound2-dev (ALSA); headless builds drop it via --no-default-features --features tauri/custom-protocol,server.
  • devtools — enables Tauri devtools (tauri/devtools)
  • server — optional dep on claudette-server
  • alternative-backends — surfaces the user-facing alt-backend gate (Codex Native + the Pi runtime option on Ollama / LM Studio / OpenAI cards). Does not by itself compile the Pi sidecar into the binary; pairs with pi-sdk for that.
  • pi-sdk — compiles the Pi coding-agent harness (sidecar wrapper around @earendil-works/pi-coding-agent), bundles binaries/claudette-pi-harness + binaries/pi/package.json, and exposes the Pi card / Pi runtime option in Settings. Independent of alternative-backends — drop it (with --no-default-features --features tauri/custom-protocol,server,voice,devtools,alternative-backends -c src-tauri/tauri.no-pi.conf.json) to ship a Codex-Native-enabled build without Pi. The lib crate mirrors this flag (claudette::pi-sdk) and downstream claudette-server / claudette-cli set default-features = false on their claudette dep so workspace feature unification can't drag Pi back in.

Frontend

  • Vite dev server port is chosen by scripts/dev.sh (default base 14253, probes for the first free port and exports VITE_PORT); strictPort: true so a probe/Vite race fails loudly. Default was moved off Tauri's stock 1420 because other Tauri starter templates default to it and their dev scripts can rebind the port underneath a running webview.
  • TypeScript enforces noUnusedLocals, noUnusedParameters, noFallthroughCasesInSwitch
  • Test runner is vitest (not Jest)
  • macOS uses overlay title bar (titleBarStyle: "Overlay") — affects layout near top of window

Tauri commands

Commands in src-tauri/src/commands/ are organized by domain: agent_backends, apps, auth, boot, cesp, chat, claude_flags, cli, community, data, debug, devtools, diagnostics, dialog, diff, env, files, grammars, mcp, metrics, pinned_prompts, plan, plugin, plugins_runtime, remote, repository, scheduling, scm, settings, shell, slash_commands, storage, terminal, updater, usage, voice, workspace. Each is a thin wrapper — business logic belongs in the claudette crate.

CLI client

claudette (in src-cli/) drives the running GUI over a local IPC socket (Unix domain socket on macOS/Linux, Named Pipes on Windows; interprocess crate). It reuses the same command core as the GUI, so tray, notifications, and workspace list update live. Top-level subcommands include version, capabilities, rpc, workspace (alias ws), chat, repo, batch, plugin, pr, routine, and completion. IPC server lives in src-tauri/src/ipc.rs; CLI surface in src-tauri/src/commands/cli.rs. The CLI requires the GUI to be running — prefer it over poking the SQLite DB directly.

Project structure

Cargo.toml              — workspace root + claudette lib crate
src/
  lib.rs                — library entry point, re-exports backend modules
  db/                   — SQLite database (module dir): connection, migrations, CRUD
  migrations/           — versioned .sql files + MIGRATIONS registry
  git.rs                — async git worktree operations
  diff.rs               — diff parsing and git diff operations
  agent/                — agent runtime (module dir): Claude CLI subprocess + JSON streaming,
                          plus alternative backends (`codex_app_server.rs`, `harness.rs` shared
                          scaffolding). The Pi modules (`pi_sdk.rs`, `pi_control.rs`) are gated
                          behind the lib crate's `pi-sdk` feature.
  fork.rs               — session forking / checkpoint branching
  snapshot.rs           — workspace snapshots
  process.rs            — cross-platform process spawning helpers
  mcp.rs / mcp_supervisor.rs — MCP server config + lifecycle supervision
  plugin.rs             — Claude-Code plugin marketplace integration
  plugin_runtime/       — sandboxed Lua runtime (mlua) shared across plugin kinds
  permissions.rs        — tool/permission policy
  scm/                  — SCM consumer of plugin_runtime: PR/CI types + host/URL detection
  env_provider/         — env-provider consumer: dispatcher, mtime cache, merged ResolvedEnv,
                          plus `nix develop` wrapping for Nix devshell terminals/agents
  slash_commands.rs     — slash command loading and dispatch
  cesp.rs               — CESP (Claudette event stream protocol)
  config.rs / env.rs / path.rs / file_expand.rs — config, env, path helpers
  transport/            — WSS client transport (trait + WebSocket client),
                          shared by `claudette-tauri` and `claudette-mobile`
  model/                — data types (no UI or IO logic); all derive Serialize
  names/                — random workspace name generator
  ui/                   — React/Vite frontend (see src/ui/package.json)
src-tauri/              — Tauri binary crate
  src/commands/         — #[tauri::command] wrappers by domain
  src/state.rs          — managed AppState (db_path, agents, PTYs)
  src/pty.rs            — PTY management via portable-pty
  src/tray.rs           — system tray: icon/menu/tooltip, notifications
  src/remote.rs         — embedded remote server wiring
  src/mdns.rs           — mDNS advertisement for remote discovery
  src/pty_tracker.rs    — PTY foreground-process-group polling for the sidebar command indicator (Unix-only; replaced OSC 133)
src-server/             — Standalone + embeddable remote server
plugins/                — bundled Lua plugins (compiled in via include_str!)
  scm-github/           — GitHub PR / CI provider
  scm-gitlab/           — GitLab PR / CI provider
  env-direnv/           — direnv env activation
  env-mise/             — mise env activation
  env-dotenv/           — `.env` in-process parser
  env-nix-devshell/     — Nix devshell detection/env export; terminals and agents enter via
                          `nix develop` directly instead of through direnv
src-pi-harness/         — TypeScript/Bun sidecar wrapping `@earendil-works/pi-coding-agent`.
                          Compiled by `scripts/stage-pi-harness-sidecar.sh` into a single Bun
                          executable at `src-tauri/binaries/claudette-pi-harness-<triple>` and
                          shipped via Tauri `bundle.externalBin`. Glue lives in
                          `src/agent/pi_sdk.rs`; only built/loaded when the
                          `pi-sdk` feature is on.
tests/                  — workspace-level Rust integration tests (e.g. `grants_enforcement.rs`
                          covering the community-plugin granted_capabilities flow).

Plugin system

A single sandboxed Lua runtime (src/plugin_runtime/) serves multiple plugin kinds declared via plugin.json's kind field (scm | env-provider, defaults to scm). Each kind has its own domain consumer (src/scm/, src/env_provider/) that dispatches operations on top of the shared runtime. Bundled plugins live in plugins/*/ and are seeded into the user's plugin dir on first run. Users can drop their own plugins into ~/.claudette/plugins/<name>/ (one plugin.json + one init.lua); discovery picks them up at startup.

Plugin settings (manifest.settings: Vec<PluginSettingField>) let a plugin declare typed user-configurable fields (boolean/text/select) that the Plugins settings section renders as a form. Values persist in app_settings as plugin:{name}:setting:{key} and are piped into the Lua host.config("<key>") surface at invocation time. Manifest defaults apply when no override is set. Global on/off state persists as plugin:{name}:enabled = "false" (absent key = enabled).

Settings UI separates two plugin concepts:

  • Plugins (src/ui/src/components/settings/sections/PluginsSettings.tsx) — Claudette's own Lua plugins (SCM + env-provider). Always visible. Shows status, per-plugin toggle, and the manifest-declared settings form.
  • Claude Code Plugins (ClaudeCodePluginsSettings.tsx, route key claude-code-plugins) — the Claude CLI marketplace integration from src/plugin.rs (marketplaces, channels, install/uninstall). Always visible in the Settings sidebar nav.

Guidelines for new code

  • Data types go in model/ — keep them free of UI and IO dependencies. All model types must derive Serialize.
  • Service/IO modules (db/, git.rs, diff.rs, agent/) live at src/ level in the claudette crate
  • Tauri commands go in src-tauri/src/commands/ — thin wrappers that call into claudette
  • React components go in src/ui/src/components/ — organized by feature area
  • State lives in the Zustand store (useAppStore) — UI state in React, agent sessions in Rust-side AppState
  • Streaming data (agent events, PTY output) flows via Tauri events, consumed by React hooks
  • Colors and styling use CSS custom properties defined in styles/theme.css — all colors must be var(--token-name) references; raw hex/rgba literals anywhere outside theme.css fail bun run lint:css (CI-blocking). Allowed exceptions: rgba(var(--*-rgb), <alpha>) for alpha layering, getPropertyValue(...) || "#..." safety fallbacks in theme.ts, and accentPreview swatches in CommandPalette that mirror existing theme hex values.

God files — keep diffs surgical

These files are already large enough that adding more responsibility makes them harder to reason about. When touching them, prefer extracting cohesive behavior into a focused helper / hook / slice / child component near the owning feature, then wire it through the existing entry point. The goal is to reduce or isolate complexity, not pile on another unrelated concern.

  • Rust: src/diff.rs, src/git.rs, src/plugin.rs, src/mcp.rs, src/mcp_supervisor.rs, src-tauri/src/ipc.rs, src-tauri/src/voice.rs, the largest siblings of the recently-split src-tauri/src/commands/agent_backends/ directory (gateway_translate.rs, runtime_dispatch.rs, discovery.rs, config.rs), and anything else in src-tauri/src/commands/* that's already a few hundred lines.
  • Frontend: src/ui/src/components/sidebar/Sidebar.tsx, src/ui/src/components/chat/ChatPanel.tsx, src/ui/src/components/chat/ChatInputArea.tsx, src/ui/src/components/terminal/TerminalPanel.tsx, src/ui/src/services/tauri.ts, large CSS modules (e.g. Settings.module.css).

If a god file grew because the right home for the new behavior didn't exist yet, build that home first and land it as a separate (or stacked) commit before adding the new responsibility.

Regression discipline

Treat regressions as the main risk before style or aesthetic concerns. Before changing behavior, identify the current contract: persisted DB fields, Tauri command payloads, CLI output, plugin manifests, settings keys, localized strings, terminal/session semantics, worktree behavior, and visible UI workflows. Do not remove, rename, or silently reinterpret any of these unless the task explicitly asks for that change.

If a behavior change is intentional, call it out plainly in the PR summary or review response and add or update tests that pin the new behavior. If it was incidental, preserve compatibility instead. Never fix a type / lint / test failure by deleting tests, removing UI controls, dropping state fields, weakening assertions, or broadening types — fix the underlying mismatch and keep existing user-visible capabilities intact.

Testing patterns

  • Rust: tests use tempfile::tempdir() to create ephemeral git repos — no fixtures or test databases. Async tests use #[tokio::test]. Test modules live at the bottom of each file (#[cfg(test)] mod tests).
  • TypeScript: vitest with describe/it/expect. Zustand tests reset state via useAppStore.setState() in beforeEach. No test database — frontend tests are pure state/logic tests. When constructing fixtures for store state, always read the actual type definition (e.g., TerminalTab in types/terminal.ts) — do not guess field names. Look for existing make* helpers in adjacent test files before creating new fixtures.

Dev launcher

  • Use ./scripts/dev.sh (not bare cargo tauri dev). It probes free Vite + debug-eval ports (bases 14253 / 19432), writes a per-PID discovery file at ${TMPDIR:-/tmp}/claudette-dev/<pid>.json so /claudette-debug can find the right instance, stages the CLI sidecar via scripts/stage-cli-sidecar.sh (which also chains to scripts/stage-pi-harness-sidecar.sh for the Pi SDK sidecar), and on macOS adds --runner scripts/macos-dev-app-runner.sh so the build is wrapped in a signed .app bundle (using src-tauri/Entitlements.plist) — required for TCC to grant mic/speech permissions to Claudette rather than the terminal. The runner also mirrors the staged sidecars and Pi package metadata into the dev .app's Contents/MacOS/ and Contents/Resources/binaries/pi/, so current_exe.parent() resolution behaves the same way it does in release. Default features used: devtools,server,voice,alternative-backends (override via $CARGO_TAURI_FEATURES; alternative-backends is always appended).
  • Bare cargo tauri dev still works for non-voice changes but does not invoke the macOS runner, probe ports, or write the discovery file.
  • Non-Nix contributors use mise (mise.toml pins rust = "1.94", bun = "latest", tauri-cli from npm). make setup runs mise install + bun install + cargo fetch; make run invokes cargo tauri dev --features devtools,server,alternative-backends. On macOS, mise.toml force-sets CC=/usr/bin/cc and per-target Cargo linkers so transitive -liconv/-lSystem links succeed under nix-darwin's ld — leave those env entries alone unless you're fixing them.

macOS privacy-prompt contract

Do not trigger CoreAudio or Speech.framework permission prompts at app launch — macOS attributes the TCC prompt to that moment, and an unmotivated launch-time prompt looks like spyware.

  • Voice setup uses PlatformSpeechEngine::availability() (prompt-safe; reports NeedsSpeechPermission etc.) for status display, and prepare() (triggers the TCC prompt) only inside start_recording_locked — i.e. when the user actually clicks the mic. There is no startup prewarm; cpal device enumeration is also avoided at launch.
  • mDNS discovery currently starts at app launch so the sidebar's Nearby list stays populated without extra UI. Changing that affects macOS Local Network prompt timing and should come with an intentional replacement UX.

Windows specifics

  • Windows builds use MSVC toolchain; [target.'cfg(windows)'.dependencies] in Cargo.toml pulls in Windows-only crates. Gate Windows-specific code with #[cfg(windows)] / #[cfg(not(windows))] rather than Unix-only paths.
  • Never spawn a subprocess with a raw Command::new. A Windows GUI process has no console, so every CreateProcessW child without the CREATE_NO_WINDOW flag pops a transient blank cmd.exe window. Construct commands via the claudette::process helper (or call .no_console_window() from CommandWindowExt on every std::process::Command / tokio::process::Command before .spawn()/.output()/.status()). The only exception is a terminal the user is intentionally opening (cmd/pwsh/wt) — use .new_console_window() there, and only there. This bug is invisible in dev/terminal-launched builds and only surfaces in shipped release builds, so guard at the spawn site. CI enforces this via clippy.toml's disallowed-methods; a raw Command::new will fail the build.
  • Cross-compilation (Nix devshell): build-win-arm64 / build-win-x64 via cargo xwin (requires XWIN_ACCEPT_LICENSE=1); deploy-win-arm64 / deploy-win-x64 build and push to the test VM.
  • Ephemeral Windows EC2 (Nix devshell): aws-win-spinup launches a Windows Server 2022 instance (state in .claudette/aws-win/, gitignored); aws-win-rdp opens RDP; aws-win-destroy terminates. Defaults: AWS_PROFILE=dev.urandom.io, AWS_REGION=us-west-2.
  • CI/nightly/release builds Windows as bare .exe (no installer), zipped for distribution — cargo xwin build --release --features tauri/custom-protocol.

Notification architecture

  • Notification sound and commands run on the Rust side (not in the webview) — macOS suspends webview JS when the window is hidden
  • tray.rs handles attention notifications (AskUserQuestion/ExitPlanMode); commands/chat/ handles agent-finished notifications
  • Both paths use the shared build_notification_command helper in commands/settings.rssh -c <cmd> on macOS/Linux, cmd.exe /S /C <cmd> on Windows
  • Native notification banners: mac-notification-sys on macOS (click-to-navigate), tauri-plugin-notification on Linux/Windows
  • Sound playback is platform-specific. macOS/Linux shell out to afplay / paplay / ffplay inline. Windows uses claudette::audio (src/audio.rs): PlaySoundW for built-in C:\Windows\Media system sounds (no decoder, no per-call volume) and rodio (cpal + Symphonia, gated to Windows-only deps) for OpenPeon sound packs that ship MP3/OGG/WAV. New Windows audio code belongs in audio.rs so the unsafe FFI and feature gating stay in one place.

Database conventions

  • rusqlite::Connection is not Send — open a fresh connection in each Tauri command via Database::open(&state.db_path)
  • UI-only state (collapsed sections, panel widths, selection) is NOT persisted — keep in Zustand

Schema migrations

  • Migrations live as .sql files under src/migrations/ and are registered in src/migrations/mod.rs as Migration entries in the MIGRATIONS const slice.
  • Adding a migration: create src/migrations/YYYYMMDDHHMMSS_snake_case_description.sql using the current UTC timestamp, then append a matching Migration { id, sql: include_str!(...), legacy_version: None } entry to MIGRATIONS. Migration IDs must be unique — a test enforces this.
  • Never edit the SQL of a released migration. The id is the tracked identity; rewriting or renaming applied migrations will desync databases in the field. Fix forward with a new migration.
  • Merging branches: each new migration is a distinct .sql file and a distinct MIGRATIONS entry, so parallel-branch migrations no longer collide on an integer version — both apply when merged. If two branches happen to choose the same timestamp (e.g. from date -u +%Y%m%d%H%M%S run at the same second), git will surface the clash as a merge conflict in mod.rs; bump one timestamp by a second and rename its file to resolve.
  • PRAGMA user_version is retained only to seed schema_migrations once on pre-redesign databases during the first run of the new runner. Do not read or write it in new code.
  • "Already exists" leniency: the runner treats SQLITE_ERROR failures whose message contains "already exists" or "duplicate column name" as benign — it logs [migrations] <id> skipped: ... to stderr, marks the migration applied, and continues. This makes hand-applied or out-of-order migrations on dev DBs survivable. It does not license writing migrations that depend on this: keep them strictly forward-only and additive, and prefer IF NOT EXISTS on new CREATE TABLE / CREATE INDEX statements.

CI/CD pipeline

  • PR CI (.github/workflows/ci.yml): Rust fmt + clippy + tests (coverage → Codecov), desktop Tauri compile check, TypeScript type-check + lint:css + build + vitest. (ESLint via bun run lint is available locally but not run in CI.)
  • Nightly (.github/workflows/nightly.yml): Every push to main (excluding docs/README). Computes version as <next-minor>-dev.<commit-count>.g<short-sha>, stamps all five Cargo.toml files (workspace root + src-tauri + src-server + src-cli + src-mobile), builds macOS (arm64 + x86_64), Linux (x86_64 + aarch64), Windows (x86_64 + arm64), promotes atomically from nightly-staging to nightly tag.
  • Release (.github/workflows/release-please.yml): Triggered by Conventional Commits via release-please. Auto-generates CHANGELOG, bumps workspace Cargo.toml (source of truth) and the workspace-member crates (src-tauri, src-server, src-cli, src-mobile), builds all platforms, uploads assets, posts to Discord (DISCORD_WEBHOOK_URL secret).
  • Windows builds in CI skip app bundling (bare .exe, zipped). macOS builds produce .dmg; Linux produces .AppImage + .deb.

Project context

  • See GitHub Issue #5 for the full MVP PRD
  • See GitHub Issue #11 for the Workspace Management TDD
  • P0 features: workspace management, agent chat, diff viewer, integrated terminal, checkpoints, git/GitHub integration, scripts, repo settings
  • Target platforms: macOS (Apple Silicon + Intel), Linux (x86_64 + aarch64, Wayland + X11), and Windows (x86_64 + ARM64)

Debugging (dev builds only)

A debug TCP eval server runs on 127.0.0.1 in dev builds (default port 19432, overridable via $CLAUDETTE_DEBUG_PORTscripts/dev.sh probes for a free port so multiple dev instances can coexist). It executes JS in the webview and returns results over TCP. Always use the /claudette-debug skill for debugging — it auto-discovers the right instance via ${TMPDIR:-/tmp}/claudette-dev/<pid>.json and has recipes for state inspection, store tracing, session monitoring, and UAT.

/claudette-debug state                    # Store overview
/claudette-debug state completedTurns     # Dump a specific slice
/claudette-debug eval 'return 1+1'        # Arbitrary JS
/claudette-debug monitor start            # Background session monitor → /tmp/claudette-debug/monitor.log
/claudette-debug monitor read             # Tail monitor log
/claudette-debug snapshot                 # Full store dump

Helper scripts (use relative paths from project root):

  • .claude/skills/claudette-debug/scripts/debug-eval.sh — single-shot JS eval
  • .claude/skills/claudette-debug/scripts/debug-monitor.sh — long-running session monitor

Key globals exposed in dev mode:

  • window.__CLAUDETTE_STORE__ — Zustand store (.getState() / .setState())
  • window.__CLAUDETTE_INVOKE__ — Tauri invoke function

Voice click→prompt latency

Every successful mic click emits one info-level event on the claudette::voice target with fields provider_id, total_ms (full voice_start_recording command duration), and stream_open_ms (just the cpal input-stream open). Failed starts (permission denied, model missing, recorder open failure, already-active recording) emit an error-level event on the same target via #[tracing::instrument(err)], so cold-start regressions show up regardless of outcome. Watch both in the dev-build console or in ~/.claudette/logs/claudette.<date>.log. Dev builds also emit a Tauri event voice://debug/start_latency (success path only) so the /claudette-debug skill can sample without log tailing. There is no cold/warm split — voice subsystems are intentionally not prewarmed at launch (see the macOS privacy-prompt contract above), so every click is effectively cold. Documented end-to-end under the public Diagnostics page (site/src/content/docs/features/diagnostics.mdx).

All debug code is gated behind #[cfg(debug_assertions)] / import.meta.env.DEV and excluded from release builds. See .claude/skills/claudette-debug/SKILL.md for full docs.

Dependencies

  • Add dependencies conservatively — binary size target is < 30 MB
  • Cold start target is < 2 seconds to interactive UI
  • When choosing crates, prefer well-maintained options with minimal transitive dependencies
  • Frontend: use bun as package manager, not npm