mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
## Summary - Activate lefthook pre-commit and pre-push hooks that have existed in `lefthook.yml` since the initial desktop app but were never wired up. - Add `just hooks` recipe: sets `core.hooksPath = .hooks` and runs `lefthook install --force` to generate hook scripts. - Wire hook installation into `scripts/dev-setup.sh` so hooks activate automatically on `just setup`. - Enable `parallel: true` for pre-commit hooks (pre-push already had it) — all 5 format/lint checks run simultaneously. - Add `just mobile-fmt` recipe (`dart format .`) and `just fmt-all` recipe (Rust root + Tauri Rust + Dart) as one-shot formatters. - `.hooks/` is gitignored since lefthook generates machine-specific scripts. - Update `AGENTS.md`: document pre-commit/pre-push hooks in Quality Gates, add `just fmt-all` and `just hooks` usage, upgrade worktree fmt gotcha from CI note to commit blocker, add `just mobile-fmt` to mobile commands; also backfill CLI-first updates (`SPROUT_AUTH_TAG`, complete exit codes, `--format compact` flag position, two new gotchas). All hook commands delegate to `just` recipes as the single source of truth.
285 lines
13 KiB
Markdown
285 lines
13 KiB
Markdown
# AGENTS.md — AI Agent Contributor Guide
|
|
|
|
This guide is for AI agents contributing to the Sprout codebase. It covers
|
|
agent-specific context and conventions. For general contributor info (setup,
|
|
code style, PR process, architecture), see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
|
|
---
|
|
|
|
## Repo Structure
|
|
|
|
```
|
|
crates/
|
|
# Relay + core
|
|
sprout-relay # WebSocket relay server — main entry point; also hosts git + huddle audio
|
|
sprout-core # Core types, event verification, filter matching, kind registry
|
|
sprout-db # Postgres event store and data access layer
|
|
sprout-auth # Authentication and authorization
|
|
sprout-pubsub # Redis pub/sub fan-out, presence, typing indicators
|
|
sprout-search # Typesense-backed full-text search
|
|
sprout-audit # Hash-chain audit log
|
|
sprout-media # Blossom/S3 media storage
|
|
# Agent surface
|
|
sprout-mcp # MCP server providing AI agent tools (being phased out in favor of the CLI)
|
|
sprout-acp # ACP harness bridging Sprout events to AI agents
|
|
sprout-agent # Minimal ACP-compliant agent (non-streaming, tool-calls-as-output)
|
|
sprout-dev-mcp # Developer MCP server — shell + file-edit tools
|
|
sprout-persona # Agent persona packs
|
|
sprout-workflow # YAML-as-code workflow engine (evalexpr conditions)
|
|
# Clients + interop
|
|
sprout-proxy # Nostr client compatibility proxy (NIP-28)
|
|
sprout-pair-relay # Ephemeral sidecar relay for NIP-AB device pairing
|
|
sprout-pairing-cli # CLI for NIP-AB device pairing interop testing
|
|
git-sign-nostr # Sign git objects with a Nostr key
|
|
git-credential-nostr # Git credential helper for Nostr-authed push/fetch
|
|
# Tooling + shared
|
|
sprout-cli # Agent-first CLI
|
|
sprout-sdk # Typed Nostr event builders
|
|
sprout-admin # Operator CLI for relay administration
|
|
sprout-test-client # Integration test client and E2E test suite
|
|
sprig # All-in-one harness bundling ACP, agent, and dev MCP
|
|
|
|
desktop/ # Tauri 2 + React 19 desktop app
|
|
web/ # Browser web client (repo browser, served by the relay)
|
|
mobile/ # Flutter mobile app
|
|
migrations/ # SQL migrations (auto-applied on relay startup)
|
|
scripts/ # Dev tooling
|
|
.env.example # Config template — copy to .env before running
|
|
```
|
|
|
|
---
|
|
|
|
## Getting Started
|
|
|
|
```bash
|
|
. ./bin/activate-hermit # activate hermit toolchain (Rust, Node, etc.)
|
|
cp .env.example .env # configure local environment
|
|
just setup # install deps, run migrations
|
|
just relay # start relay at ws://localhost:3000
|
|
just ci # run before any PR
|
|
```
|
|
|
|
See CONTRIBUTING.md for full setup details and dependency requirements.
|
|
|
|
---
|
|
|
|
## Quality Gates
|
|
|
|
Run `just ci` before every PR — it runs `fmt` + `clippy` + desktop lint +
|
|
unit tests + builds. Clippy passing does not mean fmt passes; run both.
|
|
|
|
Run `just test` for integration tests if you touched `sprout-relay`,
|
|
`sprout-db`, or `sprout-auth` — these require a running Postgres and Redis.
|
|
|
|
**Pre-commit and pre-push hooks** are installed automatically by `just setup`.
|
|
Pre-commit runs 5 checks in parallel on every `git commit` (Rust fmt, Tauri Rust
|
|
fmt, desktop lint, web lint, mobile fmt) — a commit will fail if any are dirty.
|
|
Pre-push runs the full CI gate: all pre-commit checks plus clippy, unit tests,
|
|
desktop build, Tauri check, web build, and mobile tests (~minutes). Run
|
|
`just fmt-all` before committing to auto-fix all formatting in one shot. Run
|
|
`just hooks` to re-install hooks after env changes.
|
|
|
|
Additional rules:
|
|
- No `unsafe` code
|
|
- Do not introduce new `unwrap()` or `expect()` in production paths — use `?` and proper error types
|
|
- New public API must have doc comments
|
|
|
|
---
|
|
|
|
## Key Patterns
|
|
|
|
**Dual API surface**: Sprout exposes both a REST API and a NIP-29 WebSocket
|
|
relay. Both paths converge on shared DB functions in `sprout-db`. When adding
|
|
a feature, implement the shared DB logic first, then wire up both surfaces.
|
|
|
|
**Prefer Nostr events over new REST endpoints**: For new feature work, model
|
|
the operation as a Nostr event (new kind in `sprout-core/src/kind.rs`, handler
|
|
in `sprout-relay`) rather than adding a new REST endpoint. REST is reserved
|
|
for things that genuinely need an HTTP-only surface: media upload/download
|
|
(Blossom), OAuth callbacks, health checks, and the existing read endpoints
|
|
that proxy DB queries. Two helpful endpoints already exist and rarely need
|
|
to be duplicated:
|
|
|
|
- `POST /events` — submit any signed event (same path the WebSocket uses).
|
|
- `POST /query` — Nostr REQ filters over HTTP. NIP-50 `search` filters
|
|
are routed to `sprout-search` (Typesense-backed) automatically.
|
|
- `POST /count` — Nostr COUNT filters over HTTP.
|
|
|
|
If you find yourself reaching for a new REST endpoint, first check whether
|
|
an event kind would do the job — it usually will, and you get realtime
|
|
fan-out, NIP-29 scoping, and the existing auth pipeline for free.
|
|
|
|
Reference https://github.com/nostr-protocol/nips
|
|
|
|
**Event kinds**: All event kind integers are defined in
|
|
`sprout-core/src/kind.rs`. New features get new kind integers — add them here
|
|
first, then implement handling in the relay.
|
|
|
|
**Channel scoping**: Channels use `h` tags (NIP-29 group tag), not `e` tags.
|
|
Filters and queries must scope to `h` tags when operating within a channel.
|
|
|
|
**Agent-facing operations go in `sprout-cli`, not `sprout-mcp`**: `sprout-mcp`
|
|
is being phased out. New agent-facing features belong in `sprout-cli` — add a
|
|
subcommand there first, then wire the REST/WebSocket call in `client.rs`. Do
|
|
not add new tools to `sprout-mcp` unless specifically required for backward
|
|
compatibility. `sprout-dev-mcp` (shell + file tools for `sprout-agent`) is
|
|
separate and not being phased out.
|
|
|
|
**Workflow conditions**: `sprout-workflow` uses
|
|
[evalexpr](https://docs.rs/evalexpr) for condition evaluation. Keep expressions
|
|
simple and testable.
|
|
|
|
**Thread counters**: `reply_count` and `descendant_count` are materialized on
|
|
thread root events. Any code that inserts replies must update these counters —
|
|
check existing reply handlers for the pattern.
|
|
|
|
---
|
|
|
|
## Agent CLI (`sprout-cli`)
|
|
|
|
`sprout` is the agent-first CLI replacing `sprout-mcp`. Auth env vars
|
|
(`SPROUT_RELAY_URL`, `SPROUT_PRIVATE_KEY`, `SPROUT_AUTH_TAG`) are auto-injected
|
|
by the ACP harness into managed agent subprocesses.
|
|
|
|
All reads return sig-stripped JSON arrays; all writes return
|
|
`{event_id, accepted, message}`; creates add the entity ID. Exit codes:
|
|
0=ok, 1=input error, 2=network/relay, 3=auth, 4=other, 5=write conflict (NIP-33 LWW).
|
|
|
|
`--format compact` is a **global** flag — it goes before the subcommand:
|
|
`sprout --format compact channels list`, NOT `sprout channels list --format compact`.
|
|
|
|
See `crates/sprout-cli/TESTING.md` for the full live-testing runbook.
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
```bash
|
|
just test-unit # unit tests, no infrastructure needed
|
|
just test # full integration suite (requires Postgres + Redis)
|
|
```
|
|
|
|
E2E tests live in `crates/sprout-test-client/tests/`:
|
|
- `e2e_relay.rs` — WebSocket relay protocol
|
|
- `e2e_rest_api.rs` — REST endpoint coverage
|
|
- `e2e_mcp.rs` — MCP tool surface
|
|
- `e2e_tokens.rs` — auth token flows
|
|
- `e2e_workflows.rs` — workflow engine
|
|
- `e2e_media.rs` — media upload/download (Blossom)
|
|
- `e2e_media_extended.rs` — extended media scenarios
|
|
- `e2e_nostr_interop.rs` — Nostr interop (NIP-50 search, NIP-10 threads, NIP-17 gift wraps)
|
|
|
|
Desktop E2E: `cd desktop && pnpm exec playwright test`
|
|
|
|
See [TESTING.md](TESTING.md) for the full multi-agent E2E guide.
|
|
|
|
---
|
|
|
|
## Common Gotchas
|
|
|
|
1. **Kind `39000` for channel metadata, not `41`** — kind 41 is NIP-01 (unused). All kinds defined in `sprout-core/src/kind.rs`.
|
|
2. **Relay queries must specify `kinds`** — omitting `kinds` triggers the p-gate (403). Always include explicit kind filters.
|
|
3. **`messages search` must include `--kinds`** — an open-ended search (no kinds) hits the relay p-gate and returns 403. Pass at least `--kinds 9,45001,45003` to scope the query.
|
|
4. **Worktrees: `cd` in the same command** — shell CWD doesn't persist between tool calls. Use `cd /path && cargo build` as one command.
|
|
5. **Desktop crate excluded from root workspace** — `cargo test` at repo root does NOT run desktop tests. Use `cargo test --manifest-path desktop/src-tauri/Cargo.toml` explicitly.
|
|
6. **Desktop fmt check fails in worktrees and blocks commits** — the pre-commit hook runs `just desktop-tauri-fmt-check`, which fails in git worktrees because `cargo fmt` resolves workspace paths relative to the worktree root. Run `just desktop-tauri-fmt` from the main checkout to apply the fix, then re-stage and commit. CI is unaffected.
|
|
|
|
---
|
|
|
|
## Desktop App
|
|
|
|
The desktop app is Tauri 2 + React 19 + Vite + Tailwind CSS. Features are
|
|
organized under `desktop/src/features/`. Biome handles linting and formatting.
|
|
|
|
```bash
|
|
just desktop-dev # web-only dev server (faster iteration)
|
|
just desktop-app # full Tauri app with native shell
|
|
```
|
|
|
|
### Workspace Switching
|
|
|
|
The desktop app supports multiple workspaces (each backed by a different relay).
|
|
Switching workspaces does **not** reload the page — it uses React key-based
|
|
remounting. `<AppReady key={workspaceKey} />` in `App.tsx` forces the entire
|
|
workspace-scoped subtree to unmount and remount with fresh state.
|
|
|
|
**Module-level singletons must be explicitly reset.** React remounting only
|
|
clears React state (useState, useRef, context). Module-level variables (Maps,
|
|
class instances, cached promises) survive across remounts. Every workspace-scoped
|
|
singleton needs a reset function wired into `resetWorkspaceState()` in
|
|
`desktop/src/features/workspaces/useWorkspaceInit.ts`.
|
|
|
|
Current singletons that are reset on workspace switch:
|
|
- `relayClient.disconnect()` — WebSocket teardown + promise rejection
|
|
- `resetMediaCaches()` — proxy port and relay origin caches
|
|
- `clearSearchHitEventCache()` — search result event cache
|
|
- `clearAllDrafts()` — message draft cache
|
|
|
|
**If you add a new module-level cache, Map, or class instance that holds
|
|
workspace-scoped data, you must add its reset to `resetWorkspaceState()`.**
|
|
Failure to do so causes data from the old workspace to leak into the new one.
|
|
|
|
Key files:
|
|
- `desktop/src/app/App.tsx` — workspace key, init gate, remount boundary
|
|
- `desktop/src/features/workspaces/useWorkspaceInit.ts` — `resetWorkspaceState()`, applies config to Tauri backend
|
|
- `desktop/src/features/workspaces/useWorkspaces.tsx` — `WorkspacesProvider` context (shared state for App + AppShell)
|
|
- `desktop/src/main.tsx` — provider hierarchy (`QueryClientProvider` > `WorkspacesProvider` > `App`)
|
|
|
|
---
|
|
|
|
## Mobile App (Flutter)
|
|
|
|
The mobile app lives in `mobile/` — a Flutter app using Riverpod + Hooks.
|
|
|
|
### Architecture
|
|
|
|
- **State management:** Riverpod + `flutter_hooks` (`HookConsumerWidget`)
|
|
- **Theme:** Catppuccin Latte (light) / Macchiato (dark) — matches desktop
|
|
- **Features:** Isolated under `lib/features/`, shared code in `lib/shared/`
|
|
- **Nostr models:** `lib/shared/relay/nostr_models.dart` — event kinds must
|
|
stay in sync with `desktop/src/shared/constants/kinds.ts`
|
|
|
|
### Rules
|
|
|
|
- **NEVER use `StatefulWidget`** — always use `HookConsumerWidget` or
|
|
`ConsumerWidget` with `flutter_hooks` for local state.
|
|
- **NEVER run `flutter run`, `flutter build`, `flutter clean`, or
|
|
`flutter upgrade`** — only `flutter test`, `flutter analyze`, and
|
|
`dart format` are safe for agents to run.
|
|
- **Do NOT use `print()`** — use `debugPrint()` or structured logging.
|
|
- Prefer `context.colors` and `context.textTheme` (via theme extensions)
|
|
over raw `Theme.of(context)` calls.
|
|
- Keep widgets small and composable.
|
|
- Feature modules must not import from other feature modules — only from
|
|
`shared/`.
|
|
- Use `Grid` tokens for spacing, `Radii` for border radius.
|
|
|
|
### Quality Checks
|
|
|
|
```bash
|
|
cd mobile
|
|
dart format --output=none --set-exit-if-changed .
|
|
flutter analyze
|
|
flutter test
|
|
```
|
|
|
|
Or from repo root: `just mobile-fmt` (auto-fix), `just mobile-check` (lint + fmt check), `just mobile-test` (tests).
|
|
|
|
### Testing Conventions
|
|
|
|
- Prefer **widget tests** over unit tests for UI components — test the
|
|
whole widget tree, not individual methods.
|
|
- Use `ProviderScope(overrides: [...])` to inject fake notifiers.
|
|
- Fake notifiers should extend the real notifier class and override `build()`.
|
|
- Use the `WidgetHelpers.testable()` wrapper for simple widget tests or
|
|
build a custom `ProviderScope` + `MaterialApp` when you need specific overrides.
|
|
|
|
---
|
|
|
|
## See Also
|
|
|
|
- [CONTRIBUTING.md](CONTRIBUTING.md) — setup, code style, PR process, how to add event kinds / CLI subcommands / API endpoints
|
|
- [TESTING.md](TESTING.md) — multi-agent E2E test guide
|
|
- [ARCHITECTURE.md](ARCHITECTURE.md) — system design and component relationships
|
|
- [README.md](README.md) — project overview and quick start
|