# AGENTS.md Guidelines for AI agents (and developers) working on this codebase. ## Project Overview Goose2 is a Tauri 2 + React 19 desktop app. It uses TypeScript strict mode, Vite, and Tailwind CSS 4. The codebase follows a feature-sliced architecture organized under `src/app/`, `src/features/`, and `src/shared/`. ## First Steps Treat this repo as partially Hermit-managed. Do not assume `just`, `pnpm`, `node`, or `lefthook` are available globally. - In bash/zsh, run `source ./bin/activate-hermit` before using repo tools if the shell cannot find `just`, `pnpm`, or other managed binaries. - In fish, run `source ./bin/activate-hermit.fish`. - If PATH still looks wrong or you want to avoid shell assumptions, prefer repo-local binaries such as `./bin/just`, `./bin/pnpm`, and `./bin/lefthook`. - Biome is installed from `package.json` devDependencies, not from Hermit. Run it through `pnpm`, `pnpm exec biome`, or `npx biome` after `just setup`. - On a fresh clone, a newly created worktree, or after `just clean`, run `just setup` before relying on `pnpm`, Biome, or app-local tooling. - In new clones and worktrees, ensure git hooks are installed early with `lefthook install`. If `lefthook` is not on PATH, use `./bin/lefthook install`. - Agents starting in a fresh clone or worktree should do the setup and hook-install steps proactively rather than assuming the environment is already bootstrapped. - Use `just dev` for the normal desktop workflow. Use `just dev-frontend` only when you intentionally want the Vite app without Tauri. ## Common Commands - `just setup` installs frontend dependencies with `pnpm install` and builds the Rust backend once. - `just dev` starts the desktop app in dev mode and wires Tauri to the local Vite server. - `just check` runs Biome checks and file-size checks. - `just test` runs the Vitest suite. - `just tauri-check` runs `cargo check` in `src-tauri`. - `just ci` is the main local verification gate. - `just clean` removes Rust build artifacts, `dist`, and `node_modules`, so `just setup` is required again before `just dev`. ## Architecture ### The frontend → ACP → goose core path **All frontend ↔ backend communication in goose2 flows through a single path:** ``` React UI ──► features//api/ ──► @aaif/goose-sdk (TS) ──► goose-acp (WebSocket, ACP) ──► goose (core) ``` **YOU MUST TREAT THE CLIENT as a THIN CLIENT**** - The Tauri shell spawns a long-lived `goose serve` process and exposes its WebSocket URL via the `get_goose_serve_url` Tauri command. That is essentially the only Tauri command the frontend needs for backend work — it is how the renderer discovers the ACP endpoint. - The frontend opens a WebSocket to `goose serve` and talks to it using `@aaif/goose-sdk` (published from `ui/sdk/`). The SDK is generated from the ACP custom-method definitions in `crates/goose-sdk/src/custom_requests.rs`, so every backend method has a typed TypeScript client method. - The goose2 TypeScript code should only be UI - `goose-acp` (`crates/goose-acp/src/server.rs`) is the server side of the WebSocket. It implements handlers for the custom ACP methods and calls into the `goose` core crate to do the actual work (providers, config, sessions, dictation, etc.). - `goose` is the pure domain crate. It knows nothing about Tauri or WebSockets — it just exposes Rust APIs that `goose-acp` handlers invoke. **This is the pattern you must follow when adding any new backend-touching feature.** When you are vibecoding in this app, it is very tempting to reach for `invoke()` or add an HTTP fetch — don't. The rule is: if a feature needs to talk to `goose` core, it goes through the SDK → ACP → goose chain above. ### Don't build features entirely in the frontend If a feature involves data, persistence, secrets, provider config, sessions, filesystem access, network calls to external services, or anything else that could plausibly be reused by the CLI or another goose surface, **the logic belongs in the `goose` core crate, exposed via a typed ACP method.** Do not: - Stand up a feature whose business logic lives in a Zustand store, a React hook, or a `features//api/` adapter that calls `localStorage`, `fetch`, or filesystem APIs directly. - Reach for `invoke()` to add a new Tauri command that proxies into `goose` — add an ACP custom method instead. - Do not use `localStorage` except for things it's absolutely needed for (specifically outlined in other parts of this AGENTS.md) - Duplicate types between TS and Rust — let the SDK generation produce them from `crates/goose-sdk/src/custom_requests.rs` and use the generated types ALWAYS. Do not create a shadow of a type you could use from the generated types. The frontend's job is presentation, navigation, and orchestration of typed SDK calls. If you find a feature growing real logic on the React side, that's a signal to push it down into `goose-acp` + `goose`. ### File structure The directory layout is organized to reinforce the path above. Every feature that touches the backend has an `api/` module that wraps `GooseClient` calls — UI and stores never touch the SDK directly. ``` src/ app/ — App shell, entry point, top-level providers features/ — Feature modules (see Feature Organization below) / ui/ — React components (required) hooks/ — Custom React hooks for feature logic (when needed) stores/ — Zustand state management (frontend-only UI state; not a substitute for goose core) api/ — Thin wrappers around GooseClient SDK calls (the ONLY place ACP is touched) types.ts — Feature-specific type definitions (when needed) shared/ ui/ — Reusable UI components (button, etc.) lib/ — Utilities (cn.ts for class merging) theme/ — Theme provider, appearance settings styles/ — Global CSS, design tokens hooks/ — Shared hooks api/ — Shared GooseClient wrappers used by multiple features (e.g. dictation) constants/ — Shared constants context/ — Shared contexts ``` ### Feature Organization Not every feature needs every subdirectory. Use only what the feature requires. Note that anything beyond `ui/` only — i.e. anything with state or backend calls — should already have a corresponding ACP method on the goose side. | Pattern | Structure | Examples | Backend shape | |----------------------|----------------------------------|---------------------------------|------------------------------------------------| | **Full-featured** | `stores/` + `hooks/` + `ui/` + `api/` | agents, chat | Typed ACP methods drive the store | | **Data-driven** | `stores/` + `api/` + `ui/` | projects | CRUD via ACP, store caches results | | **API features** | `api/` + `ui/` | skills, providers | Pure pass-through to ACP | | **Simple features** | `ui/` only | home, settings, sidebar, status | No backend (pure presentation) | | **Tabs** | `ui/` + `types.ts` | tabs | No backend (frontend-only UI state) | If you're tempted to build a "Full-featured" or "Data-driven" feature without a corresponding `api/` module that calls a typed ACP method, stop and add the ACP method first. See "The canonical example" below. ### Import Rules for Features - Shared types live in `src/shared/types/` — this is the single source of truth for cross-feature types. - There should be NO root-level `src/stores/` or `src/types/` directories. - Feature stores use feature-relative imports (e.g., `../stores/featureStore`). - Cross-feature imports use `@/features/*/stores/` or `@/shared/types/`. - Only `features//api/` and `shared/api/` modules may import from `@aaif/goose-sdk` or call `getClient()`. UI components, hooks, and stores must go through those wrappers. ## Coding Conventions - Use `cn()` from `@/shared/lib/cn` for Tailwind class merging. - Import paths use the `@/` alias (maps to `./src`). - Components are controlled where possible (state lifted to parent). - Use `@tabler/icons-react` for icons (transitioning from `lucide-react`; existing `lucide-react` usage is fine until migrated). - All `