mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 02:29:55 +02:00
Make agent instructions imperative and command-first, add nested crates/vnidrop and shared guides, and keep compose-skill as the UI source of truth with VniDrop-specific overrides.
309 lines
11 KiB
Markdown
309 lines
11 KiB
Markdown
# AGENTS.md
|
||
|
||
Operational instructions for coding agents working in this repository.
|
||
Humans: see `README.md` for product overview and run configs.
|
||
Agents: read this file (and the nearest nested `AGENTS.md`) before editing.
|
||
|
||
Nested guides take precedence when editing under those trees:
|
||
|
||
- [`crates/vnidrop/AGENTS.md`](crates/vnidrop/AGENTS.md) — Rust core
|
||
- [`shared/AGENTS.md`](shared/AGENTS.md) — Compose Multiplatform UI / KMP
|
||
|
||
---
|
||
|
||
## Project overview
|
||
|
||
VniDrop is a cross-platform **local P2P file transfer** app (Android, iOS, Desktop).
|
||
|
||
| Layer | Path | Responsibility |
|
||
|-------|------|----------------|
|
||
| Rust core | `crates/vnidrop/` | Iroh endpoint, blobs, SQLite, tickets, approval, streaming |
|
||
| Shared KMP | `shared/` | Compose UI, ViewModels, expect/actual platform bridges |
|
||
| Hosts | `androidApp/`, `iosApp/`, `desktopApp/` | Thin app shells |
|
||
|
||
**Invariant:** UI/platform opens files and handles pickers; **Rust streams bytes**.
|
||
Do not design features that move transfer payloads through Kotlin heap by default.
|
||
|
||
Domain docs (reference, do not paste into PRs):
|
||
|
||
- [`crates/vnidrop/CORE_FLOW.md`](crates/vnidrop/CORE_FLOW.md)
|
||
- [`crates/vnidrop/tests/README.md`](crates/vnidrop/tests/README.md)
|
||
|
||
---
|
||
|
||
## Absolute rules
|
||
|
||
1. Prefer PRs into `master`. Do not merge to `master` locally unless the user asks.
|
||
2. Do not `git push`, force-push, or open a PR unless the user asks.
|
||
3. If `commit.gpgsign` is enabled, create **signed** commits only. If signing fails
|
||
(empty `ssh-add -l`), stop and tell the user to unlock the key. Never switch to
|
||
unsigned commits to “unblock” yourself.
|
||
4. Change only files required for the task. No drive-by refactors, dependency bumps,
|
||
or repo-wide formatting.
|
||
5. Do not force architecture migrations (MVI, Hilt, Nav3, etc.) unless requested.
|
||
6. Never commit secrets, key material, or passphrases.
|
||
7. Destructive git (`reset --hard`, `push --force`, dropping DBs) only with explicit
|
||
user approval.
|
||
8. **Every bug fix includes a regression test** at the lowest layer that catches it.
|
||
9. After code changes, run the **relevant** checks in [Build and test](#build-and-test)
|
||
and fix failures before finishing.
|
||
|
||
---
|
||
|
||
## Build and test
|
||
|
||
Install prerequisites when missing: Rust stable + rustfmt + clippy, JDK 17,
|
||
Android NDK/SDK only if building Android, Xcode only for iOS.
|
||
|
||
### Rust core (`crates/vnidrop` or workspace root)
|
||
|
||
Run from the **repo root** (Cargo workspace):
|
||
|
||
```bash
|
||
cargo fmt --all -- --check
|
||
cargo clippy --workspace --all-targets -- -D warnings
|
||
cargo test --workspace --all-targets
|
||
```
|
||
|
||
Focused:
|
||
|
||
```bash
|
||
cargo test -p vnidrop
|
||
cargo test -p vnidrop --test output_sink
|
||
cargo test -p vnidrop --test transfer
|
||
cargo test -p vnidrop --test approval
|
||
cargo test -p vnidrop --test lifecycle
|
||
```
|
||
|
||
After finishing Rust edits, format:
|
||
|
||
```bash
|
||
cargo fmt --all
|
||
```
|
||
|
||
CI also runs `cargo doc --workspace --no-deps` with `RUSTDOCFLAGS=-D warnings`
|
||
(see `.github/workflows/rust-core.yml`). Run it before large Rust public-API changes.
|
||
|
||
### Shared KMP / Compose (`shared/`)
|
||
|
||
```bash
|
||
./gradlew :shared:jvmTest
|
||
./gradlew :shared:compileKotlinJvm
|
||
```
|
||
|
||
Other targets (slower / machine-dependent):
|
||
|
||
```bash
|
||
./gradlew :shared:testAndroidHostTest
|
||
./gradlew :shared:iosSimulatorArm64Test # macOS + Xcode
|
||
./gradlew :androidApp:assembleDebug
|
||
./gradlew :desktopApp:run
|
||
```
|
||
|
||
**Note:** `jvmTest` CI runs on **macOS** because Gobley host cargo is enabled for
|
||
the current Gobley host; Linux JVM cargo may be disabled in
|
||
`shared/build.gradle.kts`. Prefer macOS for local parity with CI.
|
||
|
||
### What to run before finishing
|
||
|
||
| You changed… | Minimum verification |
|
||
|--------------|----------------------|
|
||
| `crates/vnidrop/**` only | `cargo fmt`, `cargo clippy … -D warnings`, `cargo test -p vnidrop` |
|
||
| Cancel / export / sinks | Above + `cargo test -p vnidrop --test output_sink` |
|
||
| `shared/**` only | `./gradlew :shared:jvmTest` |
|
||
| Both | Rust suite + `:shared:jvmTest` |
|
||
| Docs only | No suite required; verify links/paths |
|
||
|
||
Do not kill long `cargo` / Gradle runs mid-flight unless they hang past several
|
||
minutes with no output; first builds are slow.
|
||
|
||
---
|
||
|
||
## Repository map (edit here)
|
||
|
||
### Rust runtime (keep split; do not re-merge into one file)
|
||
|
||
```
|
||
crates/vnidrop/src/runtime/
|
||
mod.rs # CoreInner, startup recovery, emit helpers
|
||
facade.rs # UniFFI VnidropCore + block_on
|
||
share.rs # import / share
|
||
receive.rs # receive, download, export, output sinks
|
||
lifecycle.rs # cancel, delete, status, access mode, shutdown
|
||
provider.rs # provider events, per-connection send progress
|
||
```
|
||
|
||
Other core modules: `filesystem.rs`, `repository.rs`, `approval.rs`,
|
||
`handshake.rs`, `ticket.rs`, `access_policy.rs`, `event_hub.rs`, `api.rs`.
|
||
|
||
### Shared app
|
||
|
||
```
|
||
shared/src/commonMain/kotlin/com/vnidrop/app/
|
||
core/ # CoreGateway, models, pickers interfaces
|
||
feature/send|receive|approvals|settings|app/
|
||
ui/theme|components|navigation|feedback|state/
|
||
androidMain|iosMain|jvmMain/ # expect/actual implementations
|
||
```
|
||
|
||
### Platform file rules (do not violate)
|
||
|
||
- Desktop / path-based iOS: paths; directory walk in Rust when `is_directory`.
|
||
- Android **share**: ParcelFileDescriptor **file** FDs only — never a directory FD.
|
||
Folder share expands SAF trees in Kotlin to per-file FDs + relative names.
|
||
- Android **receive** default: MediaStore Downloads sink; custom trees via SAF write.
|
||
- Receive publish: no-overwrite temp + hard link / exclusive rename
|
||
(see `CORE_FLOW.md`).
|
||
|
||
---
|
||
|
||
## Code style
|
||
|
||
### General
|
||
|
||
- Match surrounding code (naming, imports, error handling).
|
||
- Prefer small, reviewable diffs. Avoid files growing past ~800 LoC without
|
||
splitting when adding substantial logic.
|
||
- Do not add one-off helpers used only once if an inline block is clearer.
|
||
- Prefer exhaustive `when` / `match`; avoid wildcards that hide new cases.
|
||
|
||
### Comments (strict)
|
||
|
||
Comment **why**, invariants, and platform/concurrency traps only.
|
||
|
||
- Do comment: cancel-before-await ordering, SAF/FD limits, security-scoped
|
||
leases, “exactly one finish/abort after start_file”, durability rules.
|
||
- Do **not** comment: restating the next line, tutorial narration, section
|
||
banners that repeat the function name, pasted docs from this file.
|
||
|
||
### Rust
|
||
|
||
- Follow Clippy with `-D warnings` (CI fails otherwise).
|
||
- Do not hold `std::sync::MutexGuard` or other guards across `.await`.
|
||
- Prefer `Handle::block_on` via existing `VnidropCore::block_on` for concurrent
|
||
API entry; cancel signals active transfers **synchronously** before async work.
|
||
- Prefer private modules; export only what UniFFI / other crates need.
|
||
- New public traits/types: short docs when the role is non-obvious.
|
||
|
||
### Kotlin / Compose
|
||
|
||
For UI and presentation work, **load and follow** the in-repo skill:
|
||
|
||
```text
|
||
.codex/skills/compose-skill/SKILL.md
|
||
```
|
||
|
||
- Open at most one `references/*.md` file when the skill’s Quick Routing requires it.
|
||
- Do not invent a second Compose style guide.
|
||
- VniDrop uses **MVVM-style** ViewModels (`*State` + `StateFlow` + named methods),
|
||
not a forced MVI `onEvent` base — adapt, do not rewrite.
|
||
- Theme via `LocalVniDropColors` / `VniDropThemeTokens` only.
|
||
Brand primary (light): HSL `271, 91%, 65%` ≈ `#A855F7`.
|
||
- Strings: CMP `Res.string.*` / composeResources — not Android `R` in `commonMain`.
|
||
- Verify multiplatform target support before adding AndroidX/Jetpack deps to
|
||
`commonMain`.
|
||
|
||
Details: [`shared/AGENTS.md`](shared/AGENTS.md).
|
||
|
||
---
|
||
|
||
## Testing instructions
|
||
|
||
- Prefer deterministic tests (gates, fixed sizes, public API fixtures).
|
||
- Avoid long sleeps; if polling is required: short interval + hard timeout +
|
||
clear assertion message.
|
||
- Rust integration tests use **public** UniFFI API + `tests/support/` only.
|
||
- Failure paths: assert durable status and/or events when applicable, not only
|
||
the error string.
|
||
- Do not add tests for pure static constants.
|
||
- Do not add negative tests for code you deleted.
|
||
- Prefer comparing whole objects when equality is meaningful.
|
||
|
||
Layout:
|
||
|
||
| Layer | Location |
|
||
|-------|----------|
|
||
| Rust unit / private | `crates/vnidrop/src/tests/` |
|
||
| Rust integration | `crates/vnidrop/tests/` |
|
||
| Shared logic | `shared/src/commonTest/` |
|
||
| Shared Compose/JVM | `shared/src/jvmTest/` |
|
||
|
||
---
|
||
|
||
## Git and PR instructions
|
||
|
||
### Branches
|
||
|
||
Name the change, not a roadmap step:
|
||
|
||
- Good: `feat/folder-share`, `fix/cancel-export-hang`, `docs/agents-md`
|
||
- Bad: `feat/step3-remaining`, `wip`, `temp`
|
||
|
||
After a PR merges: delete the feature branch **locally and on `origin`**, then
|
||
branch from updated `master`.
|
||
|
||
### Commits
|
||
|
||
- Style in history: `feat(scope):`, `fix(scope):`, `refactor(scope):`, `docs:`, `ci:`.
|
||
- Subject = outcome; body only when needed.
|
||
- Signed when repo requires it.
|
||
|
||
### Pull requests
|
||
|
||
- Title matches the main change.
|
||
- Summary: short bullets of what/why.
|
||
- **Test plan must be executable for this PR**:
|
||
- exact commands, and/or
|
||
- 1–2 concrete scenarios that would catch a regression.
|
||
- No filler plans (“everything works”, “CI green”) without commands or scenarios.
|
||
|
||
---
|
||
|
||
## Security considerations
|
||
|
||
- Treat tickets and endpoint IDs as sensitive enough not to log full blobs in
|
||
production paths.
|
||
- Do not weaken approval/access checks for convenience.
|
||
- Do not store secrets in the repo; app data dirs and key files stay out of git.
|
||
- Be careful with file publish races (no-clobber rename/link policy exists for a reason).
|
||
|
||
---
|
||
|
||
## Common tasks → start files
|
||
|
||
| Task | Start here |
|
||
|------|------------|
|
||
| Share / multi-file / folders | `runtime/share.rs`, `filesystem.rs`, platform `FileSystemService.*` |
|
||
| Receive / export / sinks | `runtime/receive.rs` |
|
||
| Cancel / delete / stop share | `runtime/lifecycle.rs`, `facade.rs` |
|
||
| Per-receiver send progress | `runtime/provider.rs`, `ui/state/AppUiModels.kt` |
|
||
| Approvals | `feature/approvals/`, `approval.rs` |
|
||
| QR / NFC invitations | `TransferShareActions.*`, `ReceiveInvitationActions.*` |
|
||
| Theme / brand | `ui/theme/VniDropTheme.kt` |
|
||
| Compose skill | `.codex/skills/compose-skill/SKILL.md` |
|
||
|
||
---
|
||
|
||
## Anti-patterns (never)
|
||
|
||
- Streaming multi-MB transfer data through Kotlin as the primary design
|
||
- Passing Android **directory** FDs into Rust
|
||
- Nested exclusive `Runtime::block_on` that deadlocks cancel during receive
|
||
- Holding locks across `.await`
|
||
- Rebuilding a monolithic `runtime.rs`
|
||
- Flaky multi-minute sleeps in tests
|
||
- Unsigned commits when signing is required
|
||
- Force-push or secret commits without explicit user direction
|
||
|
||
---
|
||
|
||
## Implementation checklist
|
||
|
||
1. Read this file + nearest nested `AGENTS.md`.
|
||
2. For Compose/UI: load `compose-skill`.
|
||
3. Smallest correct change; tests for bugs/behavior changes.
|
||
4. Run relevant build/test commands; fix failures.
|
||
5. Sparse comments only where non-obvious.
|
||
6. Commit (signed) / push / PR only as the user requests.
|
||
7. Summarize what changed and what you ran.
|