Files
vnidrop/AGENTS.md
Hammed Abass ff8e046cf7 docs: rewrite AGENTS.md to agents.md / Codex style
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.
2026-07-12 20:26:31 +02:00

11 KiB
Raw Blame History

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:


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):


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 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):

cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace --all-targets

Focused:

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:

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/)

./gradlew :shared:jvmTest
./gradlew :shared:compileKotlinJvm

Other targets (slower / machine-dependent):

./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:

.codex/skills/compose-skill/SKILL.md
  • Open at most one references/*.md file when the skills 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.


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
    • 12 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.