From eeed780e51e0a56d80e3083b48080fe8b6092dc5 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Sun, 12 Apr 2026 12:29:05 -0500 Subject: [PATCH] chore(ai): modernize and unify agent tooling and instructions (#5087) --- .copilotignore | 27 ++ .gemini/settings.json | 5 + .../copilot-commit-message-instructions.md | 27 ++ .github/copilot-instructions.md | 8 +- .github/copilot-pull-request-instructions.md | 18 ++ .../android-source-set.instructions.md | 11 + .../instructions/build-logic.instructions.md | 10 + .../instructions/ci-workflows.instructions.md | 14 + .../instructions/kmp-common.instructions.md | 17 ++ .gitignore | 2 + .skills/code-review/SKILL.md | 67 +++++ .skills/compose-ui/SKILL.md | 31 +++ .skills/implement-feature/SKILL.md | 37 +++ .skills/kmp-architecture/SKILL.md | 55 ++++ .skills/navigation-and-di/SKILL.md | 37 +++ .skills/project-overview/SKILL.md | 76 ++++++ .skills/testing-ci/SKILL.md | 97 +++++++ AGENTS.md | 255 ++++-------------- CLAUDE.md | 9 + GEMINI.md | 8 +- SOUL.md | 2 +- docs/agent-playbooks/README.md | 52 ---- .../di-navigation3-anti-patterns-playbook.md | 58 ---- .../kmp-source-set-bridging-playbook.md | 45 ---- docs/agent-playbooks/task-playbooks.md | 113 -------- .../testing-and-ci-playbook.md | 88 ------ docs/kmp-status.md | 2 +- 27 files changed, 604 insertions(+), 567 deletions(-) create mode 100644 .copilotignore create mode 100644 .gemini/settings.json create mode 100644 .github/copilot-commit-message-instructions.md create mode 100644 .github/copilot-pull-request-instructions.md create mode 100644 .github/instructions/android-source-set.instructions.md create mode 100644 .github/instructions/build-logic.instructions.md create mode 100644 .github/instructions/ci-workflows.instructions.md create mode 100644 .github/instructions/kmp-common.instructions.md create mode 100644 .skills/code-review/SKILL.md create mode 100644 .skills/compose-ui/SKILL.md create mode 100644 .skills/implement-feature/SKILL.md create mode 100644 .skills/kmp-architecture/SKILL.md create mode 100644 .skills/navigation-and-di/SKILL.md create mode 100644 .skills/project-overview/SKILL.md create mode 100644 .skills/testing-ci/SKILL.md create mode 100644 CLAUDE.md delete mode 100644 docs/agent-playbooks/README.md delete mode 100644 docs/agent-playbooks/di-navigation3-anti-patterns-playbook.md delete mode 100644 docs/agent-playbooks/kmp-source-set-bridging-playbook.md delete mode 100644 docs/agent-playbooks/task-playbooks.md delete mode 100644 docs/agent-playbooks/testing-and-ci-playbook.md diff --git a/.copilotignore b/.copilotignore new file mode 100644 index 000000000..02ec3ad1d --- /dev/null +++ b/.copilotignore @@ -0,0 +1,27 @@ +# Ignore build artifacts and generated files from Copilot indexing +# This saves context window tokens and prevents Copilot from hallucinating off of minified code. + +# Build directories +**/build/** +.gradle/ +.idea/ + +# Android generated files +**/generated/** +.cxx/ +.externalNativeBuild/ + +# Git history & worktrees +.git/ +.worktrees/ + +# Protobuf (Prevents Copilot from suggesting raw protobuf byte buffers) +core/proto/ + +# Environment and secrets +local.properties +secrets.properties +*.jks + +# Agent References (Prevents pollution of project space with external code) +.agent_refs/ diff --git a/.gemini/settings.json b/.gemini/settings.json new file mode 100644 index 000000000..5e535b215 --- /dev/null +++ b/.gemini/settings.json @@ -0,0 +1,5 @@ +{ + "context": { + "fileName": ["AGENTS.md", "GEMINI.md"] + } +} diff --git a/.github/copilot-commit-message-instructions.md b/.github/copilot-commit-message-instructions.md new file mode 100644 index 000000000..93c242d16 --- /dev/null +++ b/.github/copilot-commit-message-instructions.md @@ -0,0 +1,27 @@ +# GitHub Copilot Commit Message Instructions + + +You are an expert Git maintainer enforcing Conventional Commits. + + + +1. **Format:** Use the Conventional Commits format: `(): ` (Replace angle brackets with actual text, do NOT output angle brackets). +2. **Types allowed:** + - `feat` (new feature for the user, not a new feature for build script) + - `fix` (bug fix for the user, not a fix to a build script) + - `docs` (changes to the documentation) + - `style` (formatting, missing semi colons, etc; no production code change) + - `refactor` (refactoring production code, e.g. KMP migration, extracting to commonMain) + - `test` (adding missing tests, refactoring tests; no production code change) + - `chore` (updating grunt tasks etc; no production code change) +3. **Scope:** Use the module or logical component as the scope (e.g., `ui`, `navigation`, `ble`, `firmware`, `deps`, `ai`). +4. **Subject line:** + - Use the imperative, present tense: "change" not "changed" nor "changes". + - Do not capitalize the first letter. + - Do not use a period (.) at the end. + - Keep it under 50 characters if possible. +5. **Body (Optional but recommended for large diffs):** + - Leave one blank line after the subject. + - Explain *why* the change was made, not just *what* changed. + - If migrating to KMP or extracting to `commonMain`, explicitly state "Decoupled from Android framework". + diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 2e60f3dff..e856cbe8f 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,6 +1,6 @@ -# Meshtastic Android - Agent Guide +# Meshtastic Android - GitHub Copilot Guide -**Canonical instructions live in [`AGENTS.md`](../AGENTS.md).** This file exists at `.github/copilot-instructions.md` so GitHub Copilot discovers it automatically. +> **Note:** The canonical instructions for all AI Agents have been deduplicated. -See [AGENTS.md](../AGENTS.md) for architecture, conventions, execution protocol, and coding standards. -See [docs/agent-playbooks/README.md](../docs/agent-playbooks/README.md) for version baselines and task recipes. +You MUST immediately read and internalize the unified instructions located at the root of the repository in `AGENTS.md`. +After reading `AGENTS.md`, consult the `.skills/` directory for task-specific playbooks. diff --git a/.github/copilot-pull-request-instructions.md b/.github/copilot-pull-request-instructions.md new file mode 100644 index 000000000..8e79d63d2 --- /dev/null +++ b/.github/copilot-pull-request-instructions.md @@ -0,0 +1,18 @@ +# GitHub Copilot Pull Request Instructions + + +You are an expert open-source maintainer. Your goal is to write clear, professional, and highly structured Pull Request descriptions based on the provided diffs. + + + +1. **Remove Boilerplate:** Always delete the "tips" section at the top of the `PULL_REQUEST_TEMPLATE.md` before generating your text. +2. **Context First:** Start with a clear, 1-2 sentence summary of *why* this change is being made. If the branch name or commits reference an issue (e.g., `fix-1234`), explicitly add `Fixes #1234` or `Resolves #1234`. +3. **Structured Changes:** Break down the code changes into bullet points categorized by: + - ๐ŸŒŸ **New Features** (UI, modules, logic) + - ๐Ÿ› ๏ธ **Refactoring & Architecture** (KMP migrations, Koin DI updates) + - ๐Ÿ› **Bug Fixes** + - ๐Ÿงน **Chores** (Dependencies, formatting, docs) +4. **Architecture Callouts:** If the diff includes moving files from `androidMain` to `commonMain`, or migrating from Android Views to Compose, highlight this as a "KMP Migration Milestone". +5. **Testing Callouts:** If the diff includes changes to `commonTest` or mentions tests, add a section called "Testing Performed" and list the tests that were added/modified. +6. **No "Magic" Text:** Do not invent URLs or insert fake image placeholders. Leave the HTML comment block for images intact so the user can manually add their screenshots. + diff --git a/.github/instructions/android-source-set.instructions.md b/.github/instructions/android-source-set.instructions.md new file mode 100644 index 000000000..6179bc61a --- /dev/null +++ b/.github/instructions/android-source-set.instructions.md @@ -0,0 +1,11 @@ +--- +applyTo: "**/androidMain/**/*.kt" +--- + +# Android Source-Set Rules + +- This is `androidMain` โ€” Android framework imports (`android.*`, `java.*`) are allowed here. +- Do NOT put business logic here. Business logic belongs in `commonMain`. +- If you find identical pure-Kotlin logic in both `androidMain` and `jvmMain`, extract it to `commonMain`. +- Use `expect`/`actual` only for small platform primitives. Prefer interfaces + DI. +- Keep `expect` declarations in `FileIo.kt` and shared helpers in `FileIoUtils.kt` to avoid JVM duplicate class errors. diff --git a/.github/instructions/build-logic.instructions.md b/.github/instructions/build-logic.instructions.md new file mode 100644 index 000000000..d61fa34b8 --- /dev/null +++ b/.github/instructions/build-logic.instructions.md @@ -0,0 +1,10 @@ +--- +applyTo: "build-logic/**/*.kt" +--- + +# Build-Logic Convention Plugin Rules + +- Prefer lazy Gradle configuration (`configureEach`, `withPlugin`, provider APIs). +- Avoid `afterEvaluate` unless there is no viable lazy alternative. +- Check `gradle/libs.versions.toml` for version catalog aliases before adding new ones. +- Convention plugins: `meshtastic.kmp.feature`, `meshtastic.kmp.library`, `meshtastic.kmp.jvm.android`, `meshtastic.koin`. diff --git a/.github/instructions/ci-workflows.instructions.md b/.github/instructions/ci-workflows.instructions.md new file mode 100644 index 000000000..55a72b328 --- /dev/null +++ b/.github/instructions/ci-workflows.instructions.md @@ -0,0 +1,14 @@ +--- +applyTo: "**/*.yml" +excludeAgent: "code-review" +--- + +# CI Workflow Rules + +- Prefer explicit Gradle task paths (`app:lintFdroidDebug`) over shorthand (`lintDebug`). +- CI uses `.github/ci-gradle.properties` โ€” don't assume local `gradle.properties` values. +- CI passes `-Pci=true` to enable full processor usage via `maxParallelForks`. +- Use `fetch-depth: 0` only where needed (spotless ratcheting, version code). Use `fetch-depth: 1` otherwise. +- Desktop build matrix: `macos-latest`, `windows-latest`, `ubuntu-24.04`, `ubuntu-24.04-arm`. +- Lightweight jobs (labelers, triage, stale): use `ubuntu-24.04-arm` runners. +- Gradle-heavy jobs: use `ubuntu-24.04` runners. diff --git a/.github/instructions/kmp-common.instructions.md b/.github/instructions/kmp-common.instructions.md new file mode 100644 index 000000000..235d5826d --- /dev/null +++ b/.github/instructions/kmp-common.instructions.md @@ -0,0 +1,17 @@ +--- +applyTo: "**/commonMain/**/*.kt" +--- + +# KMP commonMain Rules + +- NEVER import `java.*` or `android.*` in `commonMain`. +- Use `org.meshtastic.core.common.util.ioDispatcher` instead of `Dispatchers.IO`. +- Use Okio (`BufferedSource`/`BufferedSink`) instead of `java.io.*`. +- Use `kotlinx.coroutines.sync.Mutex` instead of `java.util.concurrent.locks.*`. +- Use `atomicfu` or Mutex-guarded `mutableMapOf()` instead of `ConcurrentHashMap`. +- Use `jetbrains-*` catalog aliases for lifecycle/navigation dependencies. +- Use `compose-multiplatform-*` catalog aliases for CMP dependencies. +- Never use plain `androidx.compose` dependencies in `commonMain`. +- Strings: use `stringResource(Res.string.key)` from `core:resources`. No hardcoded strings. +- CMP `stringResource` only supports `%N$s` and `%N$d` โ€” pre-format floats with `NumberFormatter.format()`. +- Check `gradle/libs.versions.toml` before adding dependencies. diff --git a/.gitignore b/.gitignore index 97dbb7b24..8447bc7f7 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,5 @@ wireless-install.sh .worktrees/ /firebase-debug.log.jdk/ firebase-debug.log +.agent_plans/ +.agent_refs/ diff --git a/.skills/code-review/SKILL.md b/.skills/code-review/SKILL.md new file mode 100644 index 000000000..08caa95be --- /dev/null +++ b/.skills/code-review/SKILL.md @@ -0,0 +1,67 @@ +# Skill: Code Review + +## Description +Perform comprehensive and precise code reviews for the `Meshtastic-Android` project. This skill ensures that incoming changes adhere strictly to the project's architecture guidelines, Kotlin Multiplatform (KMP) conventions, Modern Android Development (MAD) standards, and Jetpack Compose Multiplatform (CMP) best practices. + +## Context & Prerequisites +The `Meshtastic-Android` codebase is a highly modernized Kotlin Multiplatform (KMP) application designed for off-grid, decentralized mesh networks. +- **Language:** Kotlin (primary), JDK 21 required. +- **Architecture:** KMP core with Android and Desktop host shells. +- **UI:** Jetpack Compose Multiplatform (CMP) and Material 3 Adaptive. +- **Navigation:** JetBrains Navigation 3 (Scene-based). +- **DI:** Koin Annotations (with K2 compiler plugin). +- **Async & I/O:** Kotlin Coroutines, Flow, Okio, Ktor. + +## Code Review Checklist + +When reviewing code, meticulously verify the following categories. Flag any deviations and propose the canonical project pattern as a fix. + +### 1. KMP Architecture & Source Set Boundaries +- [ ] **No Platform Bleed:** Ensure absolutely no `java.*` or `android.*` imports exist in `commonMain` source sets. +- [ ] **KMP Native Alternatives:** Verify the use of KMP alternatives for standard JVM libraries: + - `java.util.concurrent.locks.*` -> `kotlinx.coroutines.sync.Mutex` + - `java.util.concurrent.ConcurrentHashMap` -> `atomicfu` or Mutex-guarded `mutableMapOf()` + - `java.io.*` -> `Okio` (`BufferedSource`/`BufferedSink`) + - `java.util.Locale` -> Kotlin `uppercase()`/`lowercase()` or `expect`/`actual` +- [ ] **Shared Helpers:** If `androidMain` and `jvmMain` contain identical pure-Kotlin logic, mandate extracting it to a shared function in `commonMain`. +- [ ] **File Naming Conflicts:** For `expect`/`actual` declarations, ensure files sharing the same package namespace have distinct names (e.g., keep `expect` in `LogExporter.kt` and shared helpers in `LogFormatter.kt`) to avoid duplicate class errors on the JVM target. +- [ ] **Interface & DI Over `expect`/`actual`:** Check that `expect`/`actual` is reserved for small platform primitives. Interfaces + DI should be preferred for larger capabilities. + +### 2. UI & Compose Multiplatform (CMP) +- [ ] **Compose Multiplatform Resources:** Ensure NO hardcoded strings. Must use `core:resources` (e.g., `stringResource(Res.string.key)` or asynchronous `getStringSuspend(Res.string.key)` for ViewModels/Coroutines). NEVER use blocking `getString()` in a coroutine. +- [ ] **String Formatting:** CMP only supports `%N$s` and `%N$d`. Flag any float formats (`%N$.1f`) in Compose string resources; they must be pre-formatted using `NumberFormatter.format()` from `core:common`. +- [ ] **Centralized Dialogs & Alerts:** Flag inline alert-rendering logic. Mandate the use of `AlertHost(alertManager)` or `SharedDialogs` from `core:ui/commonMain`. +- [ ] **Placeholders:** Require `PlaceholderScreen(name)` from `core:ui/commonMain` for unimplemented desktop/JVM features. No inline placeholders in feature modules. +- [ ] **Adaptive Layouts:** Verify use of `currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true)` to support desktop/tablet breakpoints (โ‰ฅ 1200dp). + +### 3. Navigation & State +- [ ] **Shared Navigation Graphs:** Feature navigation graphs must be defined as extension functions on `EntryProviderScope` in `commonMain` (e.g., `fun EntryProviderScope.settingsGraph(...)`). Flag any graphs defined in platform-specific source sets. +- [ ] **Navigation Host:** Ensure `MeshtasticNavDisplay` (from `core:ui/commonMain`) is used as the host instead of invoking `NavDisplay` directly. Host modules should not configure `entryDecorators` themselves. +- [ ] **ViewModel Scoping:** ViewModels obtained via `koinViewModel()` must be inside `entry` blocks to correctly tie to the backstack lifetime. + +### 4. Dependency Injection (Koin Annotations) +- [ ] **Annotation Usage:** Ensure Koin is configured via annotations (`@Single`, `@Factory`, `@KoinViewModel`). +- [ ] **Root Assembly:** Confirm that the root Koin DI graph is only assembled in host shells (`app` and `desktop`). + +### 5. Networking, DB & I/O +- [ ] **Ktor Strictly:** Check that Ktor is used for all HTTP networking. Flag and reject any usage of OkHttp. +- [ ] **Image Loading (Coil):** Coil must use `coil-network-ktor3` in host modules. Feature modules should ONLY depend on `libs.coil` (coil-compose) and never configure fetchers. +- [ ] **Room KMP:** Ensure `factory = { MeshtasticDatabaseConstructor.initialize() }` is used in `Room.databaseBuilder`. DAOs and Entities must reside in `commonMain`. +- [ ] **Bluetooth (BLE):** All Bluetooth communication must be routed through `core:ble` using Kable abstractions. + +### 6. Dependency Catalog Aliases +- [ ] **JetBrains vs. AndroidX:** + - In `commonMain`: Must use `jetbrains-*` aliases (e.g., `jetbrains-lifecycle-*`, `jetbrains-navigation3-ui`). + - In `androidMain`: Can use `androidx-*` or `jetbrains-*` as appropriate, but do not mix them up in `commonMain`. +- [ ] **Compose Multiplatform:** Ensure `compose-multiplatform-*` aliases are used instead of plain `androidx.compose` in all KMP modules. + +### 7. Testing +- [ ] **Shared Test Utilities:** Test fakes, doubles, and utilities should be placed in `core:testing`. +- [ ] **Libraries:** Verify usage of `Turbine` for Flow testing, `Kotest` for property-based testing, and `Mokkery` for mocking. +- [ ] **Robolectric Configuration:** Check that Compose UI tests running via Robolectric on JVM are pinned to `@Config(sdk = [34])` to prevent Java 21 / SDK 35 compatibility issues. + +## Review Output Guidelines +1. **Be Specific & Constructive:** Provide exact file references and code snippets illustrating the required project pattern. +2. **Reference the Docs:** Cite `AGENTS.md` and project architecture playbooks to justify change requests (e.g., "Per AGENTS.md, `java.io.*` cannot be used in `commonMain`; please migrate to Okio"). +3. **Enforce Build Health:** Remind authors to run `./gradlew test allTests` locally to verify changes, especially since KMP `test` tasks are ambiguous. +4. **Praise Good Patterns:** Acknowledge correct usage of complex architecture requirements, like proper Navigation 3 scene transitions or elegant `commonMain` helper extractions. diff --git a/.skills/compose-ui/SKILL.md b/.skills/compose-ui/SKILL.md new file mode 100644 index 000000000..d2e79c542 --- /dev/null +++ b/.skills/compose-ui/SKILL.md @@ -0,0 +1,31 @@ +# Skill: Compose Multiplatform (CMP) UI + +## Description +Guidelines for building shared UI, adaptive layouts, and handling strings/resources in Meshtastic-Android. The codebase uses Material 3 Adaptive. + +## 1. UI Components & Layouts +- **Material 3 / Adaptive:** Use `currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true)` to support Large (1200dp) and XL (1600dp) breakpoints. Investigate 3-pane "Power User" scenes using Navigation 3 Scenes and draggable dividers for desktop/tablets. +- **Dialogs & Alerts:** Use centralized components like `AlertHost(alertManager)` from `core:ui/commonMain`. Do NOT trigger alerts inline or duplicate alert logic. Use `SharedDialogs(uiViewModel)` for general popups. +- **Placeholders:** Use `PlaceholderScreen(name)` from `core:ui/commonMain` for unimplemented desktop/JVM features. +- **Theme Picker:** Use `ThemePickerDialog` from `feature:settings/commonMain`. +- **Platform Implementations:** Inject platform-specific behavior (e.g., Map providers) via `CompositionLocal` from the `app` or `desktop` shells. Do not tightly couple Google Maps/osmdroid dependencies to `commonMain`. + +## 2. Strings & Resources +- **Multiplatform Resources:** MUST use `core:resources` (e.g., `stringResource(Res.string.your_key)`). Never use hardcoded strings. +- **ViewModels/Coroutines:** Use the asynchronous `getStringSuspend(Res.string.your_key)`. NEVER use blocking `getString()` in a coroutine context. +- **Formatting Constraints:** CMP `stringResource` only supports `%N$s` (string) and `%N$d` (integer). + - **No Float formatting:** Formats like `%N$.1f` pass through unsubstituted. Pre-format in Kotlin using `NumberFormatter.format(value, decimalPlaces)` from `core:common` and pass as a string argument (`%N$s`). + - **Percent Literals:** Use bare `%` (not `%%`) for literal percent signs in CMP-consumed strings. +- **Workflow to Add a String:** + 1. Add to `core/resources/src/commonMain/composeResources/values/strings.xml`. + 2. Use the generated `org.meshtastic.core.resources.` symbol. + 3. Validate UI presentation. + +## 3. Tooling & Capabilities +- **Image Loading:** Use `libs.coil` (Coil Compose) in feature modules. Configuration/Networking for Coil (`coil-network-ktor3`) happens strictly in the `app` and `desktop` host modules. +- **QR Codes:** Use `rememberQrCodePainter` from `core:ui/commonMain` powered by `qrcode-kotlin`. No ZXing or Android Bitmap APIs in shared code. + +## Reference Anchors +- **Shared Strings:** `core/resources/src/commonMain/composeResources/values/strings.xml` +- **Platform abstraction contract:** `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/MapViewProvider.kt` +- **Provider wiring:** `app/src/main/kotlin/org/meshtastic/app/MainActivity.kt` diff --git a/.skills/implement-feature/SKILL.md b/.skills/implement-feature/SKILL.md new file mode 100644 index 000000000..1efa3caa0 --- /dev/null +++ b/.skills/implement-feature/SKILL.md @@ -0,0 +1,37 @@ +# Skill: Implement a Feature + +## Description +A step-by-step workflow for implementing a new feature in the Meshtastic-Android codebase, ensuring KMP compatibility and proper architecture. + +## Workflow + +### 1. Update Dependencies & Aliases +- Check `gradle/libs.versions.toml` before adding libraries. +- Use `jetbrains-*` aliases for lifecycle/navigation/adaptive dependencies in `commonMain`. +- Use `compose-multiplatform-*` aliases for CMP dependencies. + +### 2. Define the State & ViewModels +- Follow MVI/UDF patterns. +- Extend shared ViewModel logic in `feature//src/commonMain/kotlin/org/meshtastic/feature//ViewModel.kt`. +- Use `stateInWhileSubscribed` (from `core:ui`) for sharing state flows. +- Keep the ViewModel free of Android framework dependencies. + +### 3. Build the UI +- Use Jetpack Compose Multiplatform (CMP). +- Define strings in `core:resources` (see the `compose-ui` skill). +- Support adaptive layouts (Large/XL breakpoints). + +### 4. Wire Navigation & DI +- Define typed route objects in `core:navigation`. +- Export the navigation graph as an extension function on `EntryProviderScope` in `commonMain` (e.g., `fun EntryProviderScope.myFeatureGraph()`). +- Add the required DI bindings via Koin Annotations (`@Factory`, `@Single`, `@KoinViewModel`) in `commonMain`. +- **CRITICAL:** Ensure the module is registered in the app root graphs (`AppKoinModule.kt`, `DesktopKoinModule.kt`) and the navigation is injected into the root entry provider in the host shell. + +### 5. Validate Platform Separation +- If you need a platform-specific API (like camera or specific mapping SDK), define an interface in `commonMain`, implement it in the host shell, and inject it via `CompositionLocal` or Koin. + +### 6. Verify Locally +- Run the baseline checks (see `testing-ci` skill): + ```bash + ./gradlew spotlessCheck detekt assembleDebug test allTests + ``` diff --git a/.skills/kmp-architecture/SKILL.md b/.skills/kmp-architecture/SKILL.md new file mode 100644 index 000000000..805d9f2f9 --- /dev/null +++ b/.skills/kmp-architecture/SKILL.md @@ -0,0 +1,55 @@ +# Skill: KMP Architecture & Source-Set Bridging + +## Description +Guidelines on managing Kotlin Multiplatform (KMP) source-sets, expected abstractions, networking, database, and platform integration rules. + +## 1. Source-Set Boundaries +- **`commonMain`:** All business logic, DB entities, API network logic, ViewModels, and UI rendering. NO `java.*` or `android.*` imports. +- **`androidMain`:** Android framework integration (`Context`, system services, NFC hardware, BLE Android bindings). +- **`jvmMain` / `jvmAndroidMain`:** Shared JVM code between Android and Desktop. Uses the `meshtastic.kmp.jvm.android` convention plugin to bridge `jvm` and `android` source sets without manual `dependsOn` hacks. +- **`app` / `desktop`:** Host shells. Responsible for Koin DI root wiring, `MainKoinModule`, host-level UI themes, and running the `MeshtasticNavDisplay`. + +## 2. Bridging Strategies +- **Interface + DI (Preferred):** Expose an interface in `core:repository` or `core:ui` (e.g. `LocationRepository`, `MapViewProvider`), implement it in `androidMain` or the host `app`, and bind it via Koin or `CompositionLocal`. +- **`expect`/`actual` (Restricted):** Use only when a platform API cannot be abstracted cleanly (e.g. low-level File I/O mappings, `uppercase()` Locale helpers). Avoid deep class hierarchies using `expect`/`actual`. + - **Naming:** Keep `expect` in `FileIo.kt`, but put shared helpers in `FileIoUtils.kt` to prevent JVM duplicate class errors. +- **Shared Helpers:** Do not duplicate pure Kotlin logic between `androidMain` and `jvmMain`. Extract to a `commonMain` helper. + +## 3. Core Libraries & Constraints +- **Concurrency:** `kotlinx.coroutines`. Use `org.meshtastic.core.common.util.ioDispatcher` over `Dispatchers.IO` directly. +- **Standard Library Replacements:** + - `ConcurrentHashMap` -> `atomicfu` or Mutex-guarded `mutableMapOf()`. + - `java.util.concurrent.locks.*` -> `kotlinx.coroutines.sync.Mutex`. + - `java.io.*` -> `Okio` (`BufferedSource`/`BufferedSink`). +- **Networking:** Pure **Ktor**. No OkHttp. Ktor `Logging` plugin for debugging. +- **BLE:** Route through `core:ble` using **Kable**. +- **Room KMP:** Use `factory = { MeshtasticDatabaseConstructor.initialize() }` in `Room.databaseBuilder`. + +## 4. Hierarchy & Source-Set Conventions +- **Hierarchy template first:** Prefer Kotlin's default hierarchy template and convention plugins over manual `dependsOn(...)` graphs. Manual source-set wiring should be reserved for cases the template cannot model. +- **`expect`/`actual` restraint:** Prefer interfaces + DI for platform capabilities; use `expect`/`actual` for small unavoidable platform primitives. Avoid broad expect/actual class hierarchies when an interface-based boundary is sufficient. +- **Shared helpers over duplicated lambdas:** When `androidMain` and `jvmMain` contain identical pure-Kotlin logic (formatting, action dispatch, validation), extract to `commonMain`. Examples: `formatLogsTo()`, `handleNodeAction()`, `findNodeByNameSuffix()`, `MeshtasticAppShell`, `BaseRadioTransportFactory`. + +## 5. Dependency Catalog Aliases +- **JetBrains fork aliases:** Version catalog aliases for JetBrains-forked AndroidX artifacts use the `jetbrains-*` prefix (e.g., `jetbrains-lifecycle-runtime-compose`, `jetbrains-navigation3-ui`). Plain `androidx-*` aliases are true Google AndroidX artifacts. Never mix them up in `commonMain`. +- **Compose Multiplatform:** Version catalog aliases for Compose Multiplatform artifacts use the `compose-multiplatform-*` prefix (e.g., `compose-multiplatform-material3`, `compose-multiplatform-foundation`). Never use plain `androidx.compose` dependencies in `commonMain`. +- **Dependencies:** Always check `gradle/libs.versions.toml` before assuming a library is available. + +## 6. I/O & Serialization +- **Okio standard:** This project standardizes on Okio (`BufferedSource`/`BufferedSink`). JetBrains recommends `kotlinx-io` (built on Okio), but this project has not migrated. Do not introduce `kotlinx-io` without an explicit decision. +- **Room KMP:** Always use `factory = { MeshtasticDatabaseConstructor.initialize() }` in `Room.databaseBuilder` and `inMemoryDatabaseBuilder`. DAOs and Entities reside in `commonMain`. + +## 7. Build-Logic Conventions +- In `build-logic/convention`, prefer lazy Gradle configuration (`configureEach`, `withPlugin`, provider APIs). Avoid `afterEvaluate` in convention plugins unless there is no viable lazy alternative. + +## 8. Onboarding a New Target (Desktop/iOS) +1. Ensure all new logic compiles against the KMP core (`jvm()`, `iosArm64()`, etc.). +2. Do not use platform-specific constructs in `commonMain` or you break the iOS/Desktop builds. +3. Test using `kmpSmokeCompile` to verify cross-platform compilation. +4. For desktop wiring, copy the pattern in `desktop/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt` and use `NoopStubs.kt` to temporarily mock missing platform implementations. + +## Reference Anchors +- **Shared Okio I/O:** `core/domain/src/commonMain/kotlin/org/meshtastic/core/domain/usecase/settings/ImportProfileUseCase.kt` +- **Desktop DI Stubs:** `desktop/src/main/kotlin/org/meshtastic/desktop/stub/NoopStubs.kt` +- **Version Catalog:** `gradle/libs.versions.toml` +- **Convention Plugins:** `build-logic/convention/` diff --git a/.skills/navigation-and-di/SKILL.md b/.skills/navigation-and-di/SKILL.md new file mode 100644 index 000000000..557db4717 --- /dev/null +++ b/.skills/navigation-and-di/SKILL.md @@ -0,0 +1,37 @@ +# Skill: DI and Navigation 3 Architecture + +## Description +This skill covers dependency injection (Koin Annotations 4.2.x) and JetBrains Navigation 3 (1.1.x) architecture, constraints, and anti-patterns within the Meshtastic-Android KMP codebase. + +## Dependency Injection (Koin) + +### Guidelines +1. **Annotations First:** Use `@Module`, `@ComponentScan`, and `@KoinViewModel` annotations directly in `commonMain` shared modules to encapsulate dependency graphs per feature. +2. **App Root Assembly:** Don't assume feature/core `@Module` classes are active automatically. Ensure they are included by the app root module (`@Module(includes = [...])`) in `app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt` and `desktop/.../DesktopKoinModule.kt`. +3. **No Platform Bleed:** Don't put Android framework dependencies (`Context`, `Activity`, `Application`) into shared `commonMain` business logic. Inject interfaces instead. +4. **Resolution:** Resolve app-layer wrappers via `koinViewModel()` or injected bindings within Compose navigation graphs. + +### Anti-Patterns +- **A1 Module Compile Safety:** Do **not** enable A1 `compileSafety`. We rely on Koin's A3 full-graph validation (`startKoin` / `VerifyModule`) because of our decoupled Clean Architecture design (interfaces in one module, implemented in another). +- **Default Parameters:** Do **not** expect Koin to inject default parameters automatically. The K2 plugin's `skipDefaultValues = true` behavior skips parameters with default Kotlin values. + +## Navigation 3 + +### Guidelines +1. **Types:** Use Navigation 3 types consistently (`NavKey`, `NavBackStack`, `EntryProviderScope`). +2. **Typed Routes:** Keep route definitions in `core:navigation/src/commonMain/.../Routes.kt` as `@Serializable sealed interface` hierarchies. Don't use ad-hoc strings. +3. **Graph Assembly:** Define feature navigation graphs as extension functions on `EntryProviderScope` in `commonMain` (e.g., `fun EntryProviderScope.settingsGraph(backStack)`). +4. **Host Integration:** Use `MeshtasticNavDisplay` (from `core:ui/commonMain`) as the Navigation 3 host. Do not configure decorators manually inside feature modules. +5. **Back Handlers:** Use `NavigationBackHandler` from `androidx.navigationevent:navigationevent-compose` for back gestures in multiplatform code. Do not use Android's `BackHandler`. +6. **Deep Links:** Use `DeepLinkRouter.route()` in `core:navigation` to synthesize typed backstacks from RESTful paths. + +### Anti-Patterns +- **Single Backstack for Multiple Tabs:** Do **not** use a single `NavBackStack` list for multiple tabs. Use `MultiBackstack` (from `core:navigation`). +- **Decorator Reuse Across Tabs:** Do **not** reuse the same `NavEntryDecorator` instances across different backstacks. When rendering an active tab in `MeshtasticNavDisplay`, you **must** supply a fresh set of decorators (using `remember(backStack) { ... }`) bound to the active backstack instance to prevent permanent `ViewModelStore` destruction. +- **Custom Backstack Mutation:** Do **not** mutate back navigation with custom stacks disconnected from the app backstack. Mutate `NavBackStack` directly with `add(...)` and `removeLastOrNull()`. + +## Reference Anchors +- **App Startup / Koin Bootstrap:** `app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt` +- **DI App Wiring:** `app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt` +- **Shared Routes:** `core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/Routes.kt` +- **Desktop Nav Shell:** `desktop/src/main/kotlin/org/meshtastic/desktop/ui/DesktopMainScreen.kt` diff --git a/.skills/project-overview/SKILL.md b/.skills/project-overview/SKILL.md new file mode 100644 index 000000000..0ceade61a --- /dev/null +++ b/.skills/project-overview/SKILL.md @@ -0,0 +1,76 @@ +# Skill: Project Overview & Codebase Map + +## Description +High-level project context, module directory, namespacing conventions, environment setup, and troubleshooting for Meshtastic-Android. + +## 1. Project Vision & Architecture +Meshtastic-Android is a Kotlin Multiplatform (KMP) application for off-grid, decentralized mesh networks. The goal is to decouple business logic from the Android framework, enabling expansion to iOS and Desktop while maintaining a high-performance native Android experience. + +- **Language:** Kotlin (primary), AIDL. +- **Build System:** Gradle (Kotlin DSL). JDK 21 is REQUIRED. +- **Target SDK:** API 36. Min SDK: API 26 (Android 8.0). +- **Flavors:** + - `fdroid`: Open source only, no tracking/analytics. + - `google`: Includes Google Play Services (Maps) and DataDog analytics (RUM, Session Replay, Compose action tracking, custom `connect` RUM action). 100% sampling, Apple-parity environments ("Local"/"Production"). +- **KMP Modules:** Most `core:*` modules declare `jvm()`, `iosArm64()`, and `iosSimulatorArm64()` targets and compile clean across all. +- **Android-only Modules:** `core:api` (AIDL), `core:barcode` (CameraX + flavor-specific decoder). Shared contracts abstracted into `core:ui/commonMain`. + +## 2. Codebase Map + +| Directory | Description | +| :--- | :--- | +| `app/` | Main application module. Contains `MainActivity`, Koin DI modules, and app-level logic. Uses package `org.meshtastic.app`. | +| `build-logic/` | Convention plugins for shared build configuration (e.g., `meshtastic.kmp.feature`, `meshtastic.kmp.library`, `meshtastic.kmp.jvm.android`, `meshtastic.koin`). | +| `config/` | Detekt static analysis rules (`config/detekt/detekt.yml`) and Spotless formatting config (`config/spotless/.editorconfig`). | +| `docs/` | Architecture docs and agent playbooks. See `docs/kmp-status.md` and `docs/roadmap.md` for current status. | +| `core/model` | Domain models and common data structures. | +| `core:proto` | Protobuf definitions (Git submodule). | +| `core:common` | Low-level utilities, I/O abstractions (Okio), and common types. | +| `core:database` | Room KMP database implementation. | +| `core:datastore` | Multiplatform DataStore for preferences. | +| `core:repository` | High-level domain interfaces (e.g., `NodeRepository`, `LocationRepository`). | +| `core:domain` | Pure KMP business logic and UseCases. | +| `core:data` | Core manager implementations and data orchestration. | +| `core:network` | KMP networking layer using Ktor, MQTT abstractions, and shared transport (`StreamFrameCodec`, `TcpTransport`, `SerialTransport`, `BleRadioInterface`). | +| `core:di` | Common DI qualifiers and dispatchers. | +| `core:navigation` | Shared navigation keys/routes for Navigation 3 using `@Serializable sealed interface` hierarchies. `DeepLinkRouter` for typed backstack synthesis, and `MeshtasticNavSavedStateConfig` with `subclassesOfSealed()` for automatic polymorphic backstack persistence. | +| `core:ui` | Shared Compose UI components (`MeshtasticAppShell`, `MeshtasticNavDisplay`, `MeshtasticNavigationSuite`, `AlertHost`, `SharedDialogs`, `PlaceholderScreen`, `MainAppBar`, dialogs, preferences) and platform abstractions. | +| `core:service` | KMP service layer; Android bindings stay in `androidMain`. | +| `core:api` | Public AIDL/API integration module for external clients. | +| `core:prefs` | KMP preferences layer built on DataStore abstractions. | +| `core:barcode` | Barcode scanning (Android-only). | +| `core:nfc` | NFC abstractions (KMP). Android NFC hardware implementation in `androidMain`. | +| `core/ble/` | Bluetooth Low Energy stack using Kable. | +| `core/resources/` | Centralized string and image resources (Compose Multiplatform). | +| `core/testing/` | Shared test doubles, fakes, and utilities for `commonTest` across all KMP modules. | +| `feature/` | Feature modules (e.g., `settings`, `map`, `messaging`, `node`, `intro`, `connections`, `firmware`, `wifi-provision`, `widget`). All are KMP except `widget`. Use `meshtastic.kmp.feature` convention plugin. | +| `feature/wifi-provision` | KMP WiFi provisioning via BLE (Nymea protocol). Uses `core:ble` Kable abstractions. | +| `feature/firmware` | Fully KMP firmware update system: Unified OTA (BLE + WiFi), native Nordic Secure DFU protocol (pure KMP), USB/UF2 updates, and `FirmwareRetriever` with manifest-based resolution. Desktop is a first-class target. | +| `desktop/` | Compose Desktop application. Thin host shell relying on feature modules for shared UI. Full Koin DI graph, TCP, Serial/USB, and BLE transports. Versioning via `config.properties` + `GitVersionValueSource`. | +| `mesh_service_example/` | **DEPRECATED โ€” scheduled for removal.** Legacy sample app. See `core/api/README.md` for the current integration guide. | + +## 3. Namespacing +- **Standard:** Use the `org.meshtastic.*` namespace for all code. +- **Legacy:** Maintain the `com.geeksville.mesh` Application ID. + +## 4. Environment Setup +1. **JDK 21 MUST be used** to prevent Gradle sync/build failures. +2. **Secrets:** Copy `secrets.defaults.properties` to `local.properties`: + ```properties + MAPS_API_KEY=dummy_key + datadogApplicationId=dummy_id + datadogClientToken=dummy_token + ``` + +## 5. Troubleshooting +- **Build Failures:** Check `gradle/libs.versions.toml` for dependency conflicts. +- **Missing Secrets:** Check `local.properties` (see Environment Setup above). +- **JDK Version:** JDK 21 is required. +- **Configuration Cache:** Add `--no-configuration-cache` flag if cache-related issues persist. +- **Koin Injection Failures:** Verify the KMP component is included in `app` root module wiring (`AppKoinModule`). + +## Reference Anchors +- **KMP Migration Status:** `docs/kmp-status.md` +- **Roadmap:** `docs/roadmap.md` +- **Architecture Decision Records:** `docs/decisions/` +- **Version Catalog:** `gradle/libs.versions.toml` diff --git a/.skills/testing-ci/SKILL.md b/.skills/testing-ci/SKILL.md new file mode 100644 index 000000000..8342714de --- /dev/null +++ b/.skills/testing-ci/SKILL.md @@ -0,0 +1,97 @@ +# Skill: Testing and CI Verification + +## Description +Guidelines and commands for verifying code changes locally and understanding the Meshtastic-Android CI pipeline. Use this to determine which testing matrix is needed based on the change type. + +## 1) Baseline local verification order + +Run in this order for routine changes to ensure code formatting, analysis, and basic compilation: + +```bash +./gradlew clean +./gradlew spotlessCheck +./gradlew spotlessApply +./gradlew detekt +./gradlew assembleDebug +./gradlew test allTests +``` + +> **Why `test allTests` and not just `test`:** +> In KMP modules, the `test` task name is **ambiguous**. Gradle matches both `testAndroid` and +> `testAndroidHostTest` and refuses to run either, silently skipping KMP modules. +> `allTests` is the `KotlinTestReport` lifecycle task registered by the KMP plugin. +> Conversely, `allTests` does **not** cover pure-Android modules (`:app`, `:core:api`, etc.), which is why both `test` and `allTests` are needed. + +*Note: If testing Compose UI on the JVM (Robolectric) with Java 21, pin tests to `@Config(sdk = [34])` to avoid SDK 35 compatibility crashes.* + +## 2) Change-type verification matrix + +- `docs-only` changes: Usually no Gradle run required, but run `spotlessCheck` if practical. +- `UI text/resource` changes: `spotlessCheck`, `detekt`, `assembleDebug`. +- `feature/commonMain logic` changes: `spotlessCheck`, `detekt`, `test allTests`, `assembleDebug`. +- `navigation/DI wiring` changes: `spotlessCheck`, `detekt`, `assembleDebug`, `test allTests`, plus flavor unit tests if available. + - If touching any KMP module, also run `kmpSmokeCompile`. +- `worker/service/background` changes: Broad tests, targeted WorkManager checks. +- `BLE/networking/core repository`: `spotlessCheck`, `detekt`, `assembleDebug`, `test allTests`. + +## 3) Flavor and instrumentation checks + +Run these when relevant to map, provider, or flavor-specific behavior: + +```bash +./gradlew lintFdroidDebug lintGoogleDebug +./gradlew testFdroidDebug testGoogleDebug +./gradlew connectedAndroidTest +``` + +## 4) CI Pipeline Architecture + +CI is defined in `.github/workflows/reusable-check.yml` and structured as four parallel job groups: + +1. **`lint-check`** โ€” Runs spotless, detekt, Android lint, and KMP smoke compile in a single Gradle invocation (avoids 3x cold-start overhead). Uses `fetch-depth: 0` (full clone) for spotless ratcheting and version code calculation. Produces `cache_read_only` output and computed `version_code` for downstream jobs. +2. **`test-shards`** โ€” A 3-shard matrix that runs unit tests in parallel (depends on `lint-check`): + - `shard-core`: `allTests` for all `core:*` KMP modules. + - `shard-feature`: `allTests` for all `feature:*` KMP modules. + - `shard-app`: Explicit test tasks for pure-Android/JVM modules (`app`, `desktop`, `core:barcode`, `mesh_service_example`). + Each shard generates Kover XML coverage and uploads test results + coverage to Codecov with per-shard flags. + Downstream jobs use `fetch-depth: 1` and receive `VERSION_CODE` from lint-check via env var, enabling shallow clones. +3. **`android-check`** โ€” Builds APKs and runs instrumented tests (depends on `lint-check`). +4. **`build-desktop`** โ€” Multi-OS matrix (`macos-latest`, `windows-latest`, `ubuntu-24.04`, `ubuntu-24.04-arm`) that builds desktop distributions via `createDistributable` (depends on `lint-check`). + +### Runner Strategy (Three Tiers) +- **`ubuntu-24.04-arm`** โ€” Lightweight/utility jobs (status checks, labelers, triage, changelog, release metadata, stale, moderation). Benefits from ARM runners' shorter queue times. +- **`ubuntu-24.04`** โ€” Main Gradle-heavy jobs (CI `lint-check`/`test-shards`/`android-check`, release builds, Dokka, CodeQL, publish, dependency-submission). Pin for reproducibility. +- **Desktop runners:** Multi-OS matrix (`macos-latest`, `windows-latest`, `ubuntu-24.04`, `ubuntu-24.04-arm`) for the `build-desktop` job and release packaging. + +### CI Gradle Properties +`gradle.properties` is tuned for local dev (8g heap, 4g Kotlin daemon). CI uses `.github/ci-gradle.properties`, which the `gradle-setup` composite action copies to `~/.gradle/gradle.properties`. Key CI overrides: +- `org.gradle.daemon=false` (single-use runners) +- `kotlin.incremental=false` (fresh checkouts) +- `-Xmx4g` Gradle heap, `-Xmx2g` Kotlin daemon +- VFS watching disabled, workers capped at 4 +- `org.gradle.isolated-projects=true` for better parallelism +- Disables unused Android build features (`resvalues`, `shaders`) + +### CI Conventions +- **KMP Smoke Compile:** `./gradlew kmpSmokeCompile` is a lifecycle task (registered in `RootConventionPlugin`) that auto-discovers all KMP modules and depends on their `compileKotlinJvm` + `compileKotlinIosSimulatorArm64` tasks. +- **`maxParallelForks` CI logic:** `ProjectExtensions.kt` checks `project.findProperty("ci") == "true"` and uses full available processors in CI (4 forks on std runners) vs. half locally. All CI invocations pass `-Pci=true`. +- **Detekt report formats:** Detekt.kt checks `project.findProperty("ci") == "true"` and disables html, txt, md reports in CI; only xml + sarif are retained for GitHub annotations. +- **Robolectric SDK caching:** The `gradle-setup` composite action caches `~/.m2/repository/org/robolectric` to prevent flaky `SocketException` on SDK downloads. Cache key is `robolectric-{version}-sdk{level}` โ€” update when bumping version or SDK level. +- **`mavenLocal()` gated:** Disabled by default to prevent CI cache poisoning. Pass `-PuseMavenLocal` for local JitPack testing. +- **JUnit parallel execution:** Enabled project-wide with classes running sequentially (`junit.jupiter.execution.parallel.mode.classes.default=same_thread`) to avoid `Dispatchers.setMain()` races. Cross-module parallelism comes from Gradle forks (`maxParallelForks`). +- **`test-retry` plugin:** Applied to all module types (maxRetries=2, maxFailures=10). +- **`fail-fast: false`:** Test sharding does not cancel other shards on failure. +- **Explicit Gradle task paths:** Prefer `app:lintFdroidDebug` over shorthand `lintDebug` in CI. +- **Pull request CI:** Main-only (`.github/workflows/pull-request.yml` targets `main`). +- **Cache writes:** Trusted on `main` and merge queue runs; other refs use read-only cache. +- **Path filtering:** `check-changes` in `pull-request.yml` must include module dirs plus build/workflow entrypoints (`build-logic/**`, `gradle/**`, `.github/workflows/**`, `gradlew`, `settings.gradle.kts`, etc.). +- **AboutLibraries:** Runs in `offlineMode` by default (no GitHub/SPDX API calls). Release builds pass `-PaboutLibraries.release=true` via Fastlane/Gradle CLI to enable remote license fetching. Do NOT re-gate on `CI` or `GITHUB_TOKEN` alone. + +## 5) Shell & Tooling Conventions +- **Terminal Pagers:** When running shell commands like `git diff` or `git log`, ALWAYS use `--no-pager` (e.g., `git --no-pager diff`) to prevent getting stuck in an interactive prompt. +- **Text Search:** Prefer `rg` (ripgrep) over `grep` or `find` for fast text searching across the codebase. + +## 6) Agent/Developer Guidance +- Start with the smallest set that validates your touched area. +- If unable to run full validation locally, report exactly what ran and what remains. +- Keep documentation synced in `AGENTS.md` and `.skills/` directories. diff --git a/AGENTS.md b/AGENTS.md index b8fe03945..92009df61 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,208 +1,61 @@ -# Meshtastic Android - Agent Guide +# Meshtastic Android - Unified Agent & Developer Guide -This file serves as a comprehensive guide for AI agents and developers working on the `Meshtastic-Android` codebase. Use this as your primary reference for understanding the architecture, conventions, and strict rules of this project. + +You are an expert Android and Kotlin Multiplatform (KMP) engineer working on Meshtastic-Android, a decentralized mesh networking application. You must maintain strict architectural boundaries, use Modern Android Development (MAD) standards, and adhere to Compose Multiplatform and JetBrains Navigation 3 patterns. + -For execution-focused recipes, see `docs/agent-playbooks/README.md`. + +- **Project Goal:** Decouple business logic from the Android framework for seamless multi-platform execution (Android, Desktop, iOS) while maintaining a high-performance native Android experience. +- **Language & Tech:** Kotlin 2.3+ (JDK 21 REQUIRED), Gradle Kotlin DSL, Ktor, Okio, Room KMP. +- **Core Architecture:** + - `commonMain` is pure KMP. `androidMain` is strictly for Android framework bindings. + - App root DI and graph assembly live in the `app` and `desktop` host shells. +- **Skills Directory:** You **MUST** consult the relevant `.skills/` module before executing work: + - `.skills/project-overview/` - Codebase map, module directory, namespacing, environment setup, troubleshooting. + - `.skills/kmp-architecture/` - Bridging, expect/actual, source-sets, catalog aliases, build-logic conventions. + - `.skills/compose-ui/` - Adaptive UI, placeholders, string resources. + - `.skills/navigation-and-di/` - JetBrains Navigation 3 & Koin 4.2+ annotations. + - `.skills/testing-ci/` - Validation commands, CI pipeline architecture, CI Gradle properties. + - `.skills/implement-feature/` - Step-by-step feature workflow. + - `.skills/code-review/` - PR validation checklist. +- **Active Status:** Read `docs/kmp-status.md` and `docs/roadmap.md` to understand the current KMP migration epoch. + -## 1. Project Vision & Architecture -Meshtastic-Android is a Kotlin Multiplatform (KMP) application for off-grid, decentralized mesh networks. The goal is to decouple business logic from the Android framework, enabling future expansion to iOS and other platforms while maintaining a high-performance native Android experience. + +- **Think First:** Reason through the problem before writing code. For complex KMP tasks involving multiple modules or source sets, outline your approach step-by-step before executing. +- **Plan Before Execution:** Use the git-ignored `.agent_plans/` directory to write markdown implementation plans (`plan.md`) and Mermaid diagrams (`.mmd`) for complex refactors before modifying code. +- **Atomic Execution:** Follow your plan step-by-step. Do not jump ahead. Use TDD where feasible (write `commonTest` fakes first). +- **Baseline Verification:** Always instruct the user (or use your CLI tools) to run the baseline check before finishing: + `./gradlew clean spotlessCheck spotlessApply detekt assembleDebug test allTests` + -- **Language:** Kotlin (primary), AIDL. -- **Build System:** Gradle (Kotlin DSL). JDK 21 is REQUIRED. -- **Target SDK:** API 36. Min SDK: API 26 (Android 8.0). -- **Flavors:** - - `fdroid`: Open source only, no tracking/analytics. - - `google`: Includes Google Play Services (Maps) and DataDog analytics (RUM, Session Replay, Compose action tracking, custom `connect` RUM action). 100% sampling, Apple-parity environments ("Local"/"Production"). -- **Core Architecture:** Modern Android Development (MAD) with KMP core. - - **KMP Modules:** Most `core:*` modules. All declare `jvm()`, `iosArm64()`, and `iosSimulatorArm64()` targets and compile clean across all. - - **Android-only Modules:** `core:api` (AIDL), `core:barcode` (CameraX + flavor-specific decoder). Shared contracts abstracted into `core:ui/commonMain`. - - **UI:** Jetpack Compose Multiplatform (Material 3). - - **DI:** Koin Annotations with K2 compiler plugin. Root graph assembly is centralized in `app` and `desktop`. - - **Navigation:** JetBrains Navigation 3 (Scene-based architecture) with shared backstack state. Deep linking uses RESTful paths (e.g. `/nodes/1234`) parsed by `DeepLinkRouter` in `core:navigation`. - - **Lifecycle:** JetBrains multiplatform `lifecycle-viewmodel-compose` and `lifecycle-runtime-compose`. - - **Adaptive UI:** Material 3 Adaptive (v1.3+) with support for Large (1200dp) and Extra-large (1600dp) breakpoints. - - **Database:** Room KMP. + +- **Codebase Search:** Use whatever search and navigation tools your environment provides (file search, grep/ripgrep, symbol lookup, semantic search, etc.) to map out project boundaries before coding. Prefer `rg` (ripgrep) over `grep` or `find` for raw text search. +- **Terminal Pagers:** When running shell commands like `git diff` or `git log`, ALWAYS use `--no-pager` (e.g., `git --no-pager diff`) to prevent getting stuck in an interactive prompt. +- **Fetch Up-to-Date Docs:** If your environment supports web search, MCP servers, or documentation lookup tools, actively query them for the latest documentation on Koin 4.x, JetBrains Navigation 3, and Compose Multiplatform 1.11. +- **Clone Reference Repos:** If documentation is insufficient, use shell commands to clone bleeding-edge KMP dependency repositories into the local `.agent_refs/` directory (git-ignored) to inspect their source and test suites. Recommended: + - `https://github.com/JetBrains/kotlin-multiplatform-dev-docs` (Official Docs) + - `https://github.com/InsertKoinIO/koin` (Koin Annotations 4.x) + - `https://github.com/JetBrains/compose-multiplatform` (Navigation 3, Adaptive UI) + - `https://github.com/JuulLabs/kable` (BLE) + - `https://github.com/coil-kt/coil` (Coil 3 KMP) + - `https://github.com/ktorio/ktor` (Ktor Networking) +- **Formatting Hooks:** Always run `./gradlew spotlessApply` as an automatic formatting hook to fix style violations after editing. + -## 2. Codebase Map + +`AGENTS.md` is the single source of truth for agent instructions. Agent-specific files redirect here: +- `.github/copilot-instructions.md` โ€” Copilot redirect to `AGENTS.md`. +- `CLAUDE.md` โ€” Claude Code entry point; imports `AGENTS.md` via `@AGENTS.md` and adds Claude-specific instructions. +- `GEMINI.md` โ€” Gemini redirect to `AGENTS.md`. Gemini CLI also configured via `.gemini/settings.json` to read `AGENTS.md` directly. -| Directory | Description | -| :--- | :--- | -| `app/` | Main application module. Contains `MainActivity`, Koin DI modules, and app-level logic. Uses package `org.meshtastic.app`. | -| `build-logic/` | Convention plugins for shared build configuration (e.g., `meshtastic.kmp.feature`, `meshtastic.kmp.library`, `meshtastic.kmp.jvm.android`, `meshtastic.koin`). | -| `config/` | Detekt static analysis rules (`config/detekt/detekt.yml`) and Spotless formatting config (`config/spotless/.editorconfig`). | -| `docs/` | Architecture docs and agent playbooks. See `docs/agent-playbooks/README.md` for version baseline and task recipes. | -| `core/model` | Domain models and common data structures. | -| `core:proto` | Protobuf definitions (Git submodule). | -| `core:common` | Low-level utilities, I/O abstractions (Okio), and common types. | -| `core:database` | Room KMP database implementation. | -| `core:datastore` | Multiplatform DataStore for preferences. | -| `core:repository` | High-level domain interfaces (e.g., `NodeRepository`, `LocationRepository`). | -| `core:domain` | Pure KMP business logic and UseCases. | -| `core:data` | Core manager implementations and data orchestration. | -| `core:network` | KMP networking layer using Ktor, MQTT abstractions, and shared transport (`StreamFrameCodec`, `TcpTransport`, `SerialTransport`, `BleRadioInterface`). | -| `core:di` | Common DI qualifiers and dispatchers. | -| `core:navigation` | Shared navigation keys/routes for Navigation 3 using `@Serializable sealed interface` hierarchies per feature domain (e.g., `SettingsRoute`, `NodesRoute`). `DeepLinkRouter` for typed backstack synthesis, and `MeshtasticNavSavedStateConfig` with `subclassesOfSealed()` for automatic polymorphic backstack persistence โ€” new routes are registered at compile time. | -| `core:ui` | Shared Compose UI components (`MeshtasticAppShell`, `MeshtasticNavDisplay`, `MeshtasticNavigationSuite`, `AlertHost`, `SharedDialogs`, `PlaceholderScreen`, `MainAppBar`, dialogs, preferences) and platform abstractions. | -| `core:service` | KMP service layer; Android bindings stay in `androidMain`. | -| `core:api` | Public AIDL/API integration module for external clients. | -| `core:prefs` | KMP preferences layer built on DataStore abstractions. | -| `core:barcode` | Barcode scanning (Android-only). | -| `core:nfc` | NFC abstractions (KMP). Android NFC hardware implementation in `androidMain`. | -| `core/ble/` | Bluetooth Low Energy stack using Kable. | -| `core/resources/` | Centralized string and image resources (Compose Multiplatform). | -| `core/testing/` | **Shared test doubles, fakes, and utilities for `commonTest` across all KMP modules.** | -| `feature/` | Feature modules (e.g., `settings`, `map`, `messaging`, `node`, `intro`, `connections`, `firmware`, `wifi-provision`, `widget`). All are KMP with `jvm()` and `ios()` targets except `widget`. Use `meshtastic.kmp.feature` convention plugin. | -| `feature/wifi-provision` | KMP WiFi provisioning via BLE (Nymea protocol). Scans for provisioning devices, lists available networks, applies credentials. Uses `core:ble` Kable abstractions. | -| `feature/firmware` | Fully KMP firmware update system: Unified OTA (BLE + WiFi via Kable/Ktor), native Nordic Secure DFU protocol (pure KMP, no Nordic library), USB/UF2 updates, and `FirmwareRetriever` with manifest-based resolution. Desktop is a first-class target. | -| `desktop/` | Compose Desktop application โ€” first non-Android KMP target. Thin host shell relying entirely on feature modules for shared UI. Full Koin DI graph, TCP, Serial/USB, and BLE transports with `want_config` handshake. Versioning mirrors Android via `config.properties` + `GitVersionValueSource`; a `generateDesktopBuildConfig` task produces `DesktopBuildConfig.kt` at build time. | -| `mesh_service_example/` | **DEPRECATED โ€” scheduled for removal.** Legacy sample app showing `core:api` service integration. Do not add code here. See `core/api/README.md` for the current integration guide. | +Do NOT duplicate content into agent-specific files. When you modify architecture, module targets, CI tasks, validation commands, or agent workflow rules, update `AGENTS.md`, `.skills/`, `docs/kmp-status.md`, and `docs/decisions/architecture-review-2026-03.md` as needed. + -## 3. Development Guidelines & Coding Standards - -### A. UI Development (Jetpack Compose) -- **Material 3:** The app uses Material 3. -- **Strings:** MUST use the **Compose Multiplatform Resource** library in `core:resources` (`stringResource(Res.string.your_key)`). For ViewModels or non-composable Coroutines, use the asynchronous `getStringSuspend(Res.string.your_key)`. NEVER use hardcoded strings, and NEVER use the blocking `getString()` in a coroutine. -- **String formatting:** CMP's `stringResource(res, args)` / `getString(res, args)` only support `%N$s` (string) and `%N$d` (integer) positional specifiers. Float formats like `%N$.1f` are NOT supported โ€” they pass through unsubstituted. For float values, pre-format in Kotlin using `NumberFormatter.format(value, decimalPlaces)` from `core:common` and pass the result as a `%N$s` string arg. Use bare `%` (not `%%`) for literal percent signs in CMP-consumed strings, since CMP does not convert `%%` to `%`. For JVM-only code using `formatString()` (which wraps `String.format()`), full printf specifiers including `%N$.Nf` and `%%` are supported. -- **Dialogs:** Use centralized components in `core:ui` (e.g., `MeshtasticResourceDialog`). -- **Alerts:** Use `AlertHost(alertManager)` from `core:ui/commonMain` in each platform host shell (`Main.kt`, `DesktopMainScreen.kt`). For global responses like traceroute and firmware validation, use the specialized common handlers: `TracerouteAlertHandler(uiViewModel)` and `FirmwareVersionCheck(uiViewModel)`. Do NOT duplicate inline alert-rendering logic or trigger alerts directly during composition. For shared QR/contact dialogs, use the `SharedDialogs(uiViewModel)` composable. -- **Placeholders:** For desktop/JVM features not yet implemented, use `PlaceholderScreen(name)` from `core:ui/commonMain`. Do NOT define inline placeholder composables in feature modules. -- **Theme Picker:** Use `ThemePickerDialog` and `ThemeOption` from `feature:settings/commonMain`. Do NOT duplicate the theme dialog or enum in platform-specific source sets. -- **Adaptive Layouts:** Use `currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true)` to support the 2026 Desktop Experience breakpoints. Prioritize **higher information density** and mouse-precision interactions for Desktop and External Display (Android 16 QPR3) targets. **Investigate 3-pane "Power User" scenes** (e.g., Node List + Detail + Map/Charts) using Navigation 3 Scenes, `extraPane()`, and draggable dividers (`VerticalDragHandle` + `paneExpansionState`) for widths โ‰ฅ 1200dp. -- **Platform/Flavor UI:** Inject platform-specific behavior (e.g., map providers) via `CompositionLocal` from `app`. - -### B. Logic & Data Layer -- **KMP Focus:** All business logic must reside in `commonMain` of the respective `core` module. -- **Platform purity:** Never import `java.*` or `android.*` in `commonMain`. Use KMP alternatives: - - `java.util.Locale` โ†’ Kotlin `uppercase()` / `lowercase()` or `expect`/`actual`. - - `java.util.concurrent.ConcurrentHashMap` โ†’ `atomicfu` or `Mutex`-guarded `mutableMapOf()`. - - `java.util.concurrent.locks.*` โ†’ `kotlinx.coroutines.sync.Mutex`. - - `java.io.*` โ†’ Okio (`BufferedSource`/`BufferedSink`). Note: JetBrains now recommends `kotlinx-io` as the official Kotlin I/O library (built on Okio). This project standardizes on Okio directly; do not migrate without explicit decision. - - `kotlinx.coroutines.Dispatchers.IO` โ†’ `org.meshtastic.core.common.util.ioDispatcher` (expect/actual). Note: `Dispatchers.IO` is available in `commonMain` since kotlinx.coroutines 1.8.0, but this project uses the `ioDispatcher` wrapper for consistency. -- **Shared helpers over duplicated lambdas:** When `androidMain` and `jvmMain` contain identical pure-Kotlin logic (formatting, action dispatch, validation), extract it to a function in `commonMain`. Examples: `formatLogsTo()` in `feature:settings`, `handleNodeAction()` in `feature:node`, `findNodeByNameSuffix()` in `feature:connections`, `MeshtasticAppShell` in `core:ui/commonMain`, and `BaseRadioTransportFactory` in `core:network/commonMain`. -- **KMP file naming:** In KMP modules, `commonMain` and platform source sets (`androidMain`, `jvmMain`) share the same package namespace. If both contain a file with the same name (e.g., `LogExporter.kt`), the Kotlin/JVM compiler will produce a duplicate class error. Use distinct filenames: keep the `expect` declaration in `LogExporter.kt` and put shared helpers in a separate file like `LogFormatter.kt`. -- **`jvmAndroidMain` source set:** Modules that share JVM-specific code between Android and Desktop apply the `meshtastic.kmp.jvm.android` convention plugin. This creates a `jvmAndroidMain` source set via Kotlin's hierarchy template API. Used in `core:common`, `core:model`, `core:data`, `core:network`, and `core:ui`. -- **Hierarchy template first:** Prefer Kotlin's default hierarchy template and convention plugins over manual `dependsOn(...)` graphs. Manual source-set wiring should be reserved for cases the template cannot model. -- **`expect`/`actual` restraint:** Prefer interfaces + DI for platform capabilities; use `expect`/`actual` for small unavoidable platform primitives. Avoid broad expect/actual class hierarchies when an interface-based boundary is sufficient. -- **Feature navigation graphs:** Feature modules export Navigation 3 graph functions as extension functions on `EntryProviderScope` in `commonMain` (e.g., `fun EntryProviderScope.settingsGraph(backStack: NavBackStack)`). Host shells (`app`, `desktop`) assemble these into a single `entryProvider` block. Do NOT define navigation graphs in platform-specific source sets. -- **Concurrency:** Use Kotlin Coroutines and Flow. -- **Dependency Injection:** Use **Koin Annotations** with the K2 compiler plugin (`koin-plugin` in version catalog). The `koin-annotations` library version is unified with `koin-core` (both use `version.ref = "koin"`). The `KoinConventionPlugin` uses the typed `KoinGradleExtension` to configure the K2 plugin (e.g., `compileSafety.set(false)`). Keep root graph assembly in `app`. -- **ViewModels:** Follow the MVI/UDF pattern. Use the multiplatform `androidx.lifecycle.ViewModel` in `commonMain`. Both `app` and `desktop` use `MeshtasticNavDisplay` from `core:ui/commonMain`, which configures `ViewModelStoreNavEntryDecorator` + `SaveableStateHolderNavEntryDecorator`. ViewModels obtained via `koinViewModel()` inside `entry` blocks are scoped to the entry's backstack lifetime and cleared on pop. -- **Navigation host:** Use `MeshtasticNavDisplay` from `core:ui/commonMain` instead of calling `NavDisplay` directly. It provides entry-scoped ViewModel decoration, `DialogSceneStrategy` for dialog entries, and a shared 350 ms crossfade transition. Host modules (`app`, `desktop`) should NOT configure `entryDecorators`, `sceneStrategies`, or `transitionSpec` themselves. -- **BLE:** All Bluetooth communication must route through `core:ble` using Kable. -- **Networking:** Pure **Ktor** โ€” no OkHttp anywhere. Engines: `ktor-client-android` for Android, `ktor-client-java` for desktop/JVM. Use Ktor `Logging` plugin for HTTP debug logging (not OkHttp interceptors). `HttpClient` is provided via Koin in `app/di/NetworkModule` and `core:network/di/CoreNetworkAndroidModule`. -- **Image Loading (Coil):** Use `coil-network-ktor3` with `KtorNetworkFetcherFactory` on **all** platforms. `ImageLoader` is configured in host modules only (`app` via Koin `@Single`, `desktop` via `setSingletonImageLoaderFactory`). Feature modules depend only on `libs.coil` (coil-compose) for `AsyncImage` โ€” never add `coil-network-*` or `coil-svg` to feature modules. -- **Dependencies:** Check `gradle/libs.versions.toml` before assuming a library is available. -- **AboutLibraries:** Runs in `offlineMode` by default (no GitHub/SPDX API calls). Release builds pass `-PaboutLibraries.release=true` via Fastlane properties (Android) or Gradle CLI (desktop) to enable remote license/funding fetching. Do NOT re-gate on `CI` or `GITHUB_TOKEN` alone โ€” that burns API calls on every PR check. -- **JetBrains fork aliases:** Version catalog aliases for JetBrains-forked AndroidX artifacts use the `jetbrains-*` prefix (e.g., `jetbrains-lifecycle-runtime-compose`, `jetbrains-navigation3-ui`). Plain `androidx-*` aliases are true Google AndroidX artifacts. Never mix them up in `commonMain`. -- **Compose Multiplatform:** Version catalog aliases for Compose Multiplatform artifacts use the `compose-multiplatform-*` prefix (e.g., `compose-multiplatform-material3`, `compose-multiplatform-foundation`). Never use plain `androidx.compose` dependencies in common Main. -- **Room KMP:** Always use `factory = { MeshtasticDatabaseConstructor.initialize() }` in `Room.databaseBuilder` and `inMemoryDatabaseBuilder`. DAOs and Entities reside in `commonMain`. -- **QR Codes:** Use `rememberQrCodePainter` from `core:ui/commonMain` (powered by `qrcode-kotlin`) for generating QR codes. Do not use Android Bitmap or ZXing APIs in common code. -- **Testing:** Write ViewModel and business logic tests in `commonTest`. Use `Turbine` for Flow testing, `Kotest` for property-based testing, and `Mokkery` for mocking. Use `core:testing` shared fakes. -- **Build-logic conventions:** In `build-logic/convention`, prefer lazy Gradle configuration (`configureEach`, `withPlugin`, provider APIs). Avoid `afterEvaluate` in convention plugins unless there is no viable lazy alternative. - -### C. Namespacing -- **Standard:** Use the `org.meshtastic.*` namespace for all code. -- **Legacy:** Maintain the `com.geeksville.mesh` Application ID. - -## 4. Execution Protocol - -### A. Environment Setup -1. **JDK 21 MUST be used** to prevent Gradle sync/build failures. -2. **Secrets:** You must copy `secrets.defaults.properties` to `local.properties`: - ```properties - MAPS_API_KEY=dummy_key - datadogApplicationId=dummy_id - datadogClientToken=dummy_token - ``` - -### B. Strict Execution Commands -Always run commands in the following order to ensure reliability. Do not attempt to bypass `clean` if you are facing build issues. - -**Baseline (recommended order):** -```bash -./gradlew clean -./gradlew spotlessCheck -./gradlew spotlessApply -./gradlew detekt -./gradlew assembleDebug -./gradlew test allTests -``` - -**Testing:** -```bash -# Full host-side unit test run (required โ€” see note below): -./gradlew test allTests - -# Pure-Android / pure-JVM modules only (app, desktop, core:api, core:barcode, feature:widget, mesh_service_example): -./gradlew test - -# KMP modules only (all core:* KMP + all feature:* KMP modules โ€” jvmTest + testAndroidHostTest + iosSimulatorArm64Test): -./gradlew allTests - -# CI-aligned flavor-explicit Android unit tests: -./gradlew testFdroidDebugUnitTest testGoogleDebugUnitTest - -./gradlew connectedAndroidTest # Run instrumented tests -./gradlew testFdroidDebug testGoogleDebug # Flavor-specific unit tests -./gradlew lintFdroidDebug lintGoogleDebug # Flavor-specific lint checks -``` - -> **Why `test allTests` and not just `test`:** -> In KMP modules, the `test` task name is **ambiguous** โ€” Gradle matches both `testAndroid` and -> `testAndroidHostTest` and refuses to run either, silently skipping all 25 KMP modules. -> `allTests` is the `KotlinTestReport` lifecycle task registered by the KMP Gradle plugin for each -> KMP module. It runs `jvmTest`, `testAndroidHostTest` (where declared with `withHostTest {}`), and -> `iosSimulatorArm64Test` (disabled at execution โ€” iOS targets are compile-only). Conversely, -> `allTests` does **not** cover the pure-Android modules (`:app`, `:core:api`, `:core:barcode`, -> `:feature:widget`, `:mesh_service_example`, `:desktop`), which is why both are needed. - -*Note: If testing Compose UI on the JVM (Robolectric) with Java 21, pin your tests to `@Config(sdk = [34])` to avoid SDK 35 compatibility crashes.* - -**CI workflow conventions (GitHub Actions):** -- Reusable CI in `.github/workflows/reusable-check.yml` is structured as four parallel job groups: - 1. **`lint-check`** โ€” Runs spotless, detekt, Android lint, and KMP smoke compile in a single Gradle invocation. Uses `fetch-depth: 0` (full clone) for spotless ratcheting and version code calculation. Produces `cache_read_only` output and computed `version_code` for downstream jobs. - 2. **`test-shards`** โ€” A 3-shard matrix that runs unit tests in parallel (depends on `lint-check`): - - `shard-core`: `allTests` for all `core:*` KMP modules. - - `shard-feature`: `allTests` for all `feature:*` KMP modules. - - `shard-app`: Explicit test tasks for pure-Android/JVM modules (`app`, `desktop`, `core:barcode`, `mesh_service_example`). - Each shard generates its own Kover XML coverage and uploads test results + coverage to Codecov with per-shard flags. - Downstream jobs (test-shards, android-check, build-desktop) use `fetch-depth: 1` and receive `VERSION_CODE` from lint-check via env var, enabling shallow clones. - 3. **`android-check`** โ€” Builds APKs and runs instrumented tests (depends on `lint-check`). - 4. **`build-desktop`** โ€” Multi-OS matrix (`macos-latest`, `windows-latest`, `ubuntu-24.04`, `ubuntu-24.04-arm`) that builds runnable desktop distributions via `createDistributable` (depends on `lint-check`). The Kotlin/Native host-platform warning on `linux-aarch64` is non-fatal; only JVM targets are compiled for desktop. -- Test sharding uses `fail-fast: false` so a failure in one shard does not cancel the others. -- JUnit Platform parallel execution is enabled project-wide with classes running sequentially (`junit.jupiter.execution.parallel.mode.classes.default=same_thread`) to avoid `Dispatchers.setMain()` races (JVM-global singleton used by 19+ ViewModel test classes). Cross-module parallelism comes from Gradle forks (`maxParallelForks`). -- `test-retry` plugin (maxRetries=2, maxFailures=10) is applied to all module types: `AndroidApplicationConventionPlugin`, `AndroidLibraryConventionPlugin`, and `KmpLibraryConventionPlugin`. -- Android matrix job runs explicit assemble tasks for `app` and `mesh_service_example`; instrumentation is enabled by input and matrix API. -- Prefer explicit Gradle task paths in CI (for example `app:lintFdroidDebug`, `app:connectedGoogleDebugAndroidTest`) instead of shorthand tasks like `lintDebug`. -- Pull request CI is main-only (`.github/workflows/pull-request.yml` targets `main` branch). -- Gradle cache writes are trusted on `main` and merge queue runs (`merge_group` / `gh-readonly-queue/*`); other refs use read-only cache mode in reusable CI. -- PR `check-changes` path filtering lives in `.github/workflows/pull-request.yml` and must include module dirs plus build/workflow entrypoints (`build-logic/**`, `gradle/**`, `.github/workflows/**`, `gradlew`, `settings.gradle.kts`, etc.) so CI is not skipped for infra-only changes. -- **Runner strategy (three tiers):** - - **`ubuntu-24.04-arm`** โ€” Lightweight/utility jobs (status checks, labelers, triage, changelog, release metadata, stale, moderation). These run only shell scripts or GitHub API calls and benefit from ARM runners' shorter queue times. - - **`ubuntu-24.04`** โ€” Main Gradle-heavy jobs (CI `lint-check`/`test-shards`/`android-check`, release builds, Dokka, CodeQL, publish, dependency-submission). Pin where possible for reproducibility. - - **Desktop runners:** Reusable CI uses a multi-OS matrix (`macos-latest`, `windows-latest`, `ubuntu-24.04`, `ubuntu-24.04-arm`) for the `build-desktop` job in `.github/workflows/reusable-check.yml`; release packaging matrix remains `[macos-latest, windows-latest, ubuntu-24.04, ubuntu-24.04-arm]`. -- **CI Gradle properties:** `gradle.properties` is tuned for local dev (8g heap, 4g Kotlin daemon). CI uses `.github/ci-gradle.properties`, which the `gradle-setup` composite action copies to `~/.gradle/gradle.properties` before any Gradle invocation. Key CI overrides: `org.gradle.daemon=false` (single-use runners), `kotlin.incremental=false` (fresh checkouts), `-Xmx4g` Gradle heap, `-Xmx2g` Kotlin daemon, VFS watching disabled, workers capped at 4, `org.gradle.isolated-projects=true` for better parallelism. Disables unused Android build features (`resvalues`, `shaders`). This follows the nowinandroid `ci-gradle.properties` pattern. -- **CI optimization strategies (2026):** Applied comprehensive CI optimizations (P0-P3): - - **P0 (merged Gradle invocations):** `lint-check` merges spotlessCheck, detekt, android lint, and kmpSmokeCompile into a single Gradle invocation to avoid 3x cold-start overhead. Uses `filter: 'blob:none'` for blobless git clone. Switches submodules from `'recursive'` to boolean (saves overhead on nested submodule discovery). - - **P1 (reduced PR overhead):** Added `run_coverage` workflow input (default: true); PRs skip Kover reports via conditional tasks in test-shards matrix. Increased `maxParallelForks` in CI to use all available processors (4 on standard runners) when `ci=true` property is set, vs. half locally for system responsiveness. - - **P2 (build feature optimization):** Detekt disables non-essential report formats in CI (html, txt, md); retains only xml + sarif for GitHub annotations. Disables unused Android build features (resvalues, shaders) in `ci-gradle.properties`. - - **P3 (structural improvement):** Removed `verify-check-changes-filter` from `validate-and-build` dependencies; it now runs in parallel as a standalone required check instead of gating the main build. -- **`maxParallelForks` CI logic:** ProjectExtensions.kt line ~79 checks `project.findProperty("ci") == "true"` and uses full available processors in CI (4 forks on std runners) vs. half locally. All CI invocations pass `-Pci=true` to enable this. -- **Detekt report formats:** Detekt.kt line ~44 checks `project.findProperty("ci") == "true"` and disables html, txt, md reports in CI; only xml + sarif are required for GitHub reporting. -- **KMP Smoke Compile:** Use `./gradlew kmpSmokeCompile` instead of listing individual module compile tasks. The `kmpSmokeCompile` lifecycle task (registered in `RootConventionPlugin`) auto-discovers all KMP modules and depends on their `compileKotlinJvm` + `compileKotlinIosSimulatorArm64` tasks. -- **Robolectric SDK caching:** The `gradle-setup` composite action caches `~/.m2/repository/org/robolectric` to prevent flaky `SocketException` failures when Robolectric downloads instrumented SDK jars. The cache key is `robolectric-{version}-sdk{level}` โ€” update it when bumping the Robolectric version in `libs.versions.toml` or the SDK level in `robolectric.properties` / `@Config(sdk = ...)`. -- **`mavenLocal()` gated:** The `mavenLocal()` repository is disabled by default to prevent CI cache poisoning. For local JitPack testing, pass `-PuseMavenLocal` to Gradle. -- **Terminal Pagers:** When running shell commands like `git diff` or `git log`, ALWAYS use `--no-pager` (e.g., `git --no-pager diff`) to prevent the agent from getting stuck in an interactive prompt. -- **Text Search:** Prefer using `rg` (ripgrep) over `grep` or `find` for fast text searching across the codebase. - -### C. Documentation Sync -`AGENTS.md` is the single source of truth for agent instructions. `.github/copilot-instructions.md` and `GEMINI.md` are thin stubs that redirect here โ€” do NOT duplicate content into them. - -When you modify architecture, module targets, CI tasks, validation commands, or agent workflow rules, update `AGENTS.md`, `docs/agent-playbooks/*`, `docs/kmp-status.md`, and `docs/decisions/architecture-review-2026-03.md` as needed. - -## 5. Troubleshooting -- **Build Failures:** Check `gradle/libs.versions.toml` for dependency conflicts. -- **Missing Secrets:** Check `local.properties`. -- **JDK Version:** JDK 21 is required. -- **Configuration Cache:** Add `--no-configuration-cache` flag if cache-related issues persist. -- **Koin Injection Failures:** Verify the KMP component is included in `app` root module wiring (`AppKoinModule`). \ No newline at end of file + +- **No Lazy Coding:** DO NOT use placeholders like `// ... existing code ...`. Always provide complete, valid code blocks for the sections you modify to ensure correct diff application. +- **No Framework Bleed:** NEVER import `java.*` or `android.*` in `commonMain`. +- **Koin Annotations:** Use `@Single`, `@Factory`, and `@KoinViewModel` inside `commonMain` instead of manual constructor trees. Do not enable A1 module compile safety. +- **CMP Over Android:** Use `compose-multiplatform` constraints (e.g., no float formatting in `stringResource`). +- **Always Check Docs:** If unsure about an abstraction, search `core:ui/commonMain` or `core:navigation/commonMain` before assuming it doesn't exist. + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..39958ecd0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,9 @@ +# Meshtastic Android - Claude Code Guide + +@AGENTS.md + +## Claude-Specific Instructions + +- **Think First:** Always outline your step-by-step reasoning inside `` tags before writing code or shell commands. Claude models perform significantly better on complex KMP tasks when they "think out loud" first. +- **Skills:** The `.skills/` directory contains task-specific instruction modules. Load them as needed โ€” only the skill relevant to your current task. +- **Plan Mode:** Use plan mode for architectural changes spanning multiple modules. Write plans to `.agent_plans/` (git-ignored). diff --git a/GEMINI.md b/GEMINI.md index 9076b718e..72a350afb 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -1,6 +1,6 @@ -# Meshtastic Android - Agent Guide +# Meshtastic Android - Google Gemini Guide -**Canonical instructions live in [`AGENTS.md`](AGENTS.md).** This file exists as `GEMINI.md` so Google Gemini discovers it automatically. +> **Note:** The canonical instructions for all AI Agents have been deduplicated. -See [AGENTS.md](AGENTS.md) for architecture, conventions, execution protocol, and coding standards. -See [docs/agent-playbooks/README.md](docs/agent-playbooks/README.md) for version baselines and task recipes. +You MUST immediately read and internalize the unified instructions located at the root of the repository in `AGENTS.md`. +After reading `AGENTS.md`, consult the `.skills/` directory for task-specific playbooks. diff --git a/SOUL.md b/SOUL.md index 793387334..45924b40f 100644 --- a/SOUL.md +++ b/SOUL.md @@ -26,6 +26,6 @@ I am an **Android Architect**. My primary purpose is to evolve the Meshtastic-An I learn from the existing codebase. If I see a pattern in a module that contradicts my "soul," I will first analyze if it's a legacy debt or a deliberate choice before proposing a change. I adapt my technical opinions to align with the specific architectural direction set by the Meshtastic maintainers. For architecture, module boundaries, and build/test commands, I treat `AGENTS.md` as the source of truth. -For implementation recipes and verification scope, I use `docs/agent-playbooks/README.md`. +For implementation recipes and verification scope, I use `.skills/` directory. diff --git a/docs/agent-playbooks/README.md b/docs/agent-playbooks/README.md deleted file mode 100644 index 5d25a5509..000000000 --- a/docs/agent-playbooks/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Agent Playbooks - -These playbooks are execution-focused guidance for common changes in this repository. - -Use `AGENTS.md` as the source of truth for architecture boundaries and required conventions. If guidance conflicts, follow `AGENTS.md` and current code patterns. - -## Version baseline for external docs - -When checking upstream docs/examples, match these repository-pinned versions from `gradle/libs.versions.toml`: - -- Kotlin: `2.3.20` -- Koin: `4.2.0` (`koin-annotations` `4.2.0` โ€” uses same version as `koin-core`; compiler plugin `0.4.1`) -- JetBrains Navigation 3: `1.1.0-beta01` (`org.jetbrains.androidx.navigation3`) -- JetBrains Lifecycle (multiplatform): `2.11.0-alpha02` (`org.jetbrains.androidx.lifecycle`) -- AndroidX Lifecycle (Android-only): `2.10.0` (`androidx.lifecycle`) -- Kotlin Coroutines: `1.10.2` -- Compose Multiplatform: `1.11.0-beta01` -- JetBrains Material 3 Adaptive: `1.3.0-alpha06` (`org.jetbrains.compose.material3.adaptive`) - -Prefer versioned docs pages that match those versions (for example, Koin `4.2` docs rather than older `4.0/4.1` pages). - -## Dependency alias quick-reference - -Version catalog aliases split cleanly by fork provenance. **Use the right prefix for the right source set.** - -| Alias prefix | Coordinates | Use in | -|---|---|---| -| `jetbrains-lifecycle-*` | `org.jetbrains.androidx.lifecycle:*` | `commonMain`, `androidMain` | -| `jetbrains-navigation3-ui` | `org.jetbrains.androidx.navigation3:navigation3-ui` | `commonMain`, `androidMain` | -| `jetbrains-navigationevent-*` | `org.jetbrains.androidx.navigationevent:*` | `commonMain`, `androidMain` | -| `jetbrains-compose-material3-adaptive-*` | `org.jetbrains.compose.material3.adaptive:*` | `commonMain`, `androidMain` | -| `androidx-lifecycle-process` | `androidx.lifecycle:lifecycle-process` | `androidMain` only โ€” `ProcessLifecycleOwner` | -| `androidx-lifecycle-testing` | `androidx.lifecycle:lifecycle-runtime-testing` | `androidUnitTest` only | - -> **Note:** JetBrains does not publish a separate `navigation3-runtime` artifact โ€” `navigation3-ui` is the only artifact. The version catalog only defines `jetbrains-navigation3-ui`. The `lifecycle-runtime-ktx` and `lifecycle-viewmodel-ktx` KTX aliases were removed (extensions merged into base artifacts since Lifecycle 2.8.0). - -Quick references: - -- Koin annotations (4.2 docs): `https://insert-koin.io/docs/reference/koin-annotations/start` -- Koin KMP docs: `https://insert-koin.io/docs/reference/koin-annotations/kmp` -- AndroidX Navigation 3 release notes: `https://developer.android.com/jetpack/androidx/releases/navigation3` -- Kotlin release notes: `https://kotlinlang.org/docs/releases.html` - -## Playbooks - -- `docs/agent-playbooks/di-navigation3-anti-patterns-playbook.md` - DI and Navigation 3 mistakes to avoid. -- `docs/agent-playbooks/kmp-source-set-bridging-playbook.md` - when to use `expect`/`actual` vs interfaces + app wiring. -- `docs/agent-playbooks/task-playbooks.md` - step-by-step recipes for common implementation tasks, plus code anchor quick reference. -- `docs/agent-playbooks/testing-and-ci-playbook.md` - which Gradle tasks to run based on change type, plus CI parity. - - - diff --git a/docs/agent-playbooks/di-navigation3-anti-patterns-playbook.md b/docs/agent-playbooks/di-navigation3-anti-patterns-playbook.md deleted file mode 100644 index 550fd2079..000000000 --- a/docs/agent-playbooks/di-navigation3-anti-patterns-playbook.md +++ /dev/null @@ -1,58 +0,0 @@ -# DI and Navigation 3 Anti-Patterns Playbook - -This playbook is a fast guardrail for high-risk mistakes in dependency injection and navigation. - -Version note: align guidance with repository-pinned versions in `gradle/libs.versions.toml` (currently Koin `4.2.x` and Navigation 3 JetBrains fork `1.1.x`). - -## DI anti-patterns - -- Don't put Android framework dependencies (`Context`, `Activity`, `Application`) into shared `commonMain` business logic. -- Do use `@Module`, `@ComponentScan`, and `@KoinViewModel` annotations directly in `commonMain` shared modules. This provides compile-time safety and encapsulates dependency graphs per feature, which is the recommended 2026 KMP practice for Koin 4.x. -- Don't instantiate ViewModels or service dependencies manually in Compose or activities. -- Do resolve app-layer wrappers via Koin (`koinViewModel()` / injected bindings). -- Don't spread DI graph setup across unrelated modules without registration in app startup. -- Do ensure modules are reachable from app bootstrap in `app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt`. -- Don't assume feature/core `@Module` classes are active automatically. -- Do ensure they are included by the app root module (`@Module(includes = [...])`) in `app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt`. -- **Don't use Koin K2 Compiler Plugin's A1 Module Compile Safety checks for inverted dependencies.** -- **Do** leave A1 `compileSafety` disabled in `build-logic/convention/src/main/kotlin/KoinConventionPlugin.kt` (uses typed `KoinGradleExtension`). We rely on Koin's A3 full-graph validation (`startKoin` / `VerifyModule`) to handle our decoupled Clean Architecture design where interfaces are declared in one module and implemented in another. -- **Don't** expect Koin to inject default parameters automatically. The K2 plugin's `skipDefaultValues = true` (default behavior) will cause Koin to skip parameters that have default Kotlin values. - -### Current code anchors (DI) - -- App-level module scanning: `app/src/main/kotlin/org/meshtastic/app/MainKoinModule.kt` -- App startup + Koin init: `app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt` -- Shared ViewModel base: `feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt` -- Shared base UI ViewModel: `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/viewmodel/UIViewModel.kt` - -## Navigation 3 anti-patterns - -- Don't reintroduce controller-coupled navigation APIs for shared flow state. -- Do use Navigation 3 types (`NavKey`, `NavBackStack`, `EntryProviderScope`) consistently. -- Don't build route identifiers as ad-hoc strings in feature code when typed route keys already exist. -- Do keep route definitions in `core:navigation` and use typed route objects. -- Don't mutate back navigation with custom stacks disconnected from app backstack. -- Do mutate `NavBackStack` with `add(...)` and `removeLastOrNull()`. -- Don't use Android's `androidx.activity.compose.BackHandler` or custom `PredictiveBackHandler` in multiplatform UI. -- Do use the official KMP `NavigationBackHandler` from `androidx.navigationevent:navigationevent-compose` for back gestures. -- Don't parse deep links manually in platform code or push single routes without a backstack. -- Do use `DeepLinkRouter.route()` in `core:navigation` to synthesize the correct typed backstack from RESTful paths. -- **Don't use a single `NavBackStack` list for multiple tabs, nor reuse the same `NavEntryDecorator` instances across different backstacks.** -- **Do** use `MultiBackstack` (from `core:navigation`) to manage independent `NavBackStack` instances per tab. When rendering the active tab in `MeshtasticNavDisplay`, you **must** supply a fresh set of decorators (using `remember(backStack) { ... }`) bound to the active backstack instance. Failure to swap decorators when swapping backstacks causes Navigation 3 to perceive the inactive entries as "popped", permanently destroying their `ViewModelStore` and saved UI state. - -### Current code anchors (Navigation 3) - -- Typed routes: `core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/Routes.kt` -- Shared saved-state config: `core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/NavigationConfig.kt` -- App root backstack + `MeshtasticNavDisplay`: `app/src/main/kotlin/org/meshtastic/app/ui/Main.kt` -- Shared graph entry provider pattern: `feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt` -- Desktop Navigation 3 shell: `desktop/src/main/kotlin/org/meshtastic/desktop/ui/DesktopMainScreen.kt` -- Desktop nav graph assembly: `desktop/src/main/kotlin/org/meshtastic/desktop/navigation/DesktopNavigation.kt` - - -## Quick pre-PR checks for DI/navigation edits - -- Verify affected graph/module is registered and reachable from app startup. -- Verify no new Android framework type leaks into `commonMain`. -- Verify routes/backstack use typed keys and Navigation 3 primitives. -- Run targeted verification from `docs/agent-playbooks/testing-and-ci-playbook.md`. diff --git a/docs/agent-playbooks/kmp-source-set-bridging-playbook.md b/docs/agent-playbooks/kmp-source-set-bridging-playbook.md deleted file mode 100644 index 62753020a..000000000 --- a/docs/agent-playbooks/kmp-source-set-bridging-playbook.md +++ /dev/null @@ -1,45 +0,0 @@ -# KMP Source-Set Bridging Playbook - -Use this playbook when introducing platform-specific behavior into shared modules. - -## 1) Decide if `expect`/`actual` is needed - -Use `expect`/`actual` only when a platform API cannot be abstracted cleanly behind an interface passed from app wiring. - -- Prefer interface + DI when behavior is already app-owned. -- Prefer `expect`/`actual` for small platform primitives and utilities. - -Examples in current code: -- `core/common/src/commonMain/kotlin/org/meshtastic/core/common/util/CommonUri.kt` -- `core/common/src/androidMain/kotlin/org/meshtastic/core/common/util/CommonUri.android.kt` -- `core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/LocationRepository.kt` - -## 2) Keep source-set boundaries strict - -- `commonMain`: business logic, shared models, coroutine/Flow orchestration. -- `androidMain`: Android framework integration (`Context`, system services, Android SDK). -- `app`: app bootstrap, DI root inclusion, Activity/service wiring, flavor-specific providers. - -## 3) Resource and UI bridging rules - -- Shared strings/resources must come from `core:resources`. -- Platform/flavor UI implementations should be injected via `CompositionLocal` from app. - -Examples: -- Contract (main map): `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/MapViewProvider.kt` -- Contract (node tracks): `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalNodeTrackMapProvider.kt` -- Contract (traceroute): `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalTracerouteMapProvider.kt` -- Provider wiring: `app/src/main/kotlin/org/meshtastic/app/MainActivity.kt` - -## 4) DI and module activation checks - -- If a new feature/core module adds Koin annotations, verify it is included by app root module includes. -- App root includes are defined in `app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt`. - -## 5) Verification checklist - -- No Android-only imports in `commonMain`. -- `expect`/`actual` declarations compile across relevant source sets. -- Routing/DI still resolves from app startup (`MeshUtilApplication`). -- Run verification tasks from `docs/agent-playbooks/testing-and-ci-playbook.md` appropriate to touched modules. - diff --git a/docs/agent-playbooks/task-playbooks.md b/docs/agent-playbooks/task-playbooks.md deleted file mode 100644 index 25a856d9f..000000000 --- a/docs/agent-playbooks/task-playbooks.md +++ /dev/null @@ -1,113 +0,0 @@ -# Task Playbooks - -Use these as practical recipes. Keep edits minimal and aligned with existing module boundaries. - -For architecture rules and coding standards, see [`AGENTS.md`](../../AGENTS.md). - -## Code Anchor Quick Reference - -Key files for discovering established patterns: - -| Pattern | Reference File | -|---|---| -| App DI wiring | `app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt` | -| App startup / Koin bootstrap | `app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt` | -| Shared ViewModel | `feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt` | -| `CompositionLocal` platform injection | `app/src/main/kotlin/org/meshtastic/app/MainActivity.kt` | -| Platform abstraction contract | `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/MapViewProvider.kt` | -| Node track map provider contract | `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalNodeTrackMapProvider.kt` | -| Traceroute map provider contract | `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalTracerouteMapProvider.kt` | -| Shared strings resource | `core/resources/src/commonMain/composeResources/values/strings.xml` | -| Okio shared I/O | `core/domain/src/commonMain/kotlin/org/meshtastic/core/domain/usecase/settings/ImportProfileUseCase.kt` | -| `stateInWhileSubscribed` | `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/viewmodel/ViewModelExtensions.kt` | - -## Playbook A: Add or update a user-visible string - -1. Add/update key in `core/resources/src/commonMain/composeResources/values/strings.xml`. -2. Import generated resource symbol in UI code (`org.meshtastic.core.resources.`). -3. Use `stringResource(Res.string.)` in Compose. -4. If the string appears in a shared dialog, prefer `core:ui` dialog components. -5. Verify no hardcoded user-facing strings were introduced. - -Reference examples: -- `feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/list/NodeListScreen.kt` -- `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/AlertDialogs.kt` - -## Playbook B: Add shared ViewModel logic in a feature module - -1. Implement or extend base ViewModel logic in `feature//src/commonMain/...`. -2. Keep shared class free of Android framework dependencies. -3. Keep Android framework dependencies out of shared logic; if the module already uses Koin annotations in `commonMain`, keep patterns consistent and ensure app root inclusion. -4. Update navigation entry points in `feature/*/src/androidMain/kotlin/org/meshtastic/feature/*/navigation/...` to resolve ViewModels with `koinViewModel()`. - -Reference examples: -- Shared base: `feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt` -- Shared base UI ViewModel: `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/viewmodel/UIViewModel.kt` -- Navigation usage: `feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt` -- Desktop navigation usage: `desktop/src/main/kotlin/org/meshtastic/desktop/navigation/DesktopNavigation.kt` - -## Playbook C: Add a new dependency or service binding - -1. Check `gradle/libs.versions.toml` for existing library and version alias. -2. Add new dependency to version catalog first (if truly new). -3. Wire implementation in the owning module (`core:*`, `feature:*`, or `app`) following existing architecture. -4. Register bindings/modules in app Koin graph where needed. -5. For Android system integration (WorkManager, service bootstrapping), wire via `MeshUtilApplication` and app-layer modules. - -Reference examples: -- App startup and Koin bootstrap: `app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt` -- App module scan: `app/src/main/kotlin/org/meshtastic/app/MainKoinModule.kt` - -## Playbook D: Add or modify navigation flow - -1. Define/extend route keys in `core:navigation`. -2. Implement feature entry/content using Navigation 3 types (`NavKey`, `NavBackStack`, `EntryProviderScope`). -3. Add graph entries under the relevant feature module's `navigation` package (e.g., `feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/navigation`). -4. If the entry content depends on platform-specific UI (e.g. Activity context or specific desktop wrappers), use `expect`/`actual` declarations for the content composables. -5. Use backstack mutation (`add`, `removeLastOrNull`) instead of introducing controller-coupled APIs. -6. Verify deep-link behavior if route is externally reachable. - -Reference examples: -- Shared graph wiring: `feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt` -- Android specific content: `feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsMainScreen.kt` -- Desktop specific content: `feature/settings/src/jvmMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsMainScreen.kt` -- Feature intro graph pattern: `feature/intro/src/androidMain/kotlin/org/meshtastic/feature/intro/IntroNavGraph.kt` -- Desktop nav shell: `desktop/src/main/kotlin/org/meshtastic/desktop/ui/DesktopMainScreen.kt` -- Desktop nav graph assembly: `desktop/src/main/kotlin/org/meshtastic/desktop/navigation/DesktopNavigation.kt` - - -## Playbook E: Add flavor/platform-specific UI implementation - -1. Keep shared contracts in `core:ui` or feature shared code. -2. Inject flavor/platform implementation via `CompositionLocal` from `app`. -3. Avoid direct dependency from shared modules to Google Maps/osmdroid/other Android SDK-only APIs. -4. Keep adapter types narrow and stable (interfaces, DTO-like params). - -Reference examples: -- Contract (main map): `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/MapViewProvider.kt` -- Contract (node tracks): `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalNodeTrackMapProvider.kt` -- Contract (traceroute): `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalTracerouteMapProvider.kt` -- Provider wiring: `app/src/main/kotlin/org/meshtastic/app/MainActivity.kt` -- Consumer side: `feature/map/src/androidMain/kotlin/org/meshtastic/feature/map/MapScreen.kt` - -## Playbook F: Onboard a new platform target - -1. Create a platform application module (e.g., `desktop/`, `ios/`). -2. Copy `desktop/src/main/kotlin/org/meshtastic/desktop/stub/NoopStubs.kt` as the starting stub set. All repository interfaces have no-op implementations there. -3. Create a `KoinModule` that mirrors `desktop/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt` โ€” use stubs for unimplemented interfaces, real implementations where available. -4. Add `kotlinx-coroutines-swing` (JVM/Desktop) or the equivalent platform coroutines dispatcher module. Without it, `Dispatchers.Main` is unavailable and any code using `lifecycle.coroutineScope` will crash at runtime. -5. Progressively replace stubs with real implementations (e.g., serial transport for desktop, CoreBluetooth for iOS). -6. Add `()` target to feature modules as needed (all `core:*` modules already declare `jvm()`). -7. Update CI JVM smoke compile step in `.github/workflows/reusable-check.yml` to include new modules. -8. If `commonMain` code fails to compile for the new target, it's a KMP migration debt โ€” fix the shared code, not the target. - -Reference examples: -- Desktop stubs: `desktop/src/main/kotlin/org/meshtastic/desktop/stub/NoopStubs.kt` -- Desktop DI: `desktop/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt` -- Desktop Navigation 3 shell: `desktop/src/main/kotlin/org/meshtastic/desktop/ui/DesktopMainScreen.kt` -- Desktop nav graph entries: `desktop/src/main/kotlin/org/meshtastic/desktop/navigation/DesktopNavigation.kt` -- Desktop shared feature wiring: `feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt` -- Desktop-specific screen: `feature/settings/src/jvmMain/kotlin/org/meshtastic/feature/settings/DesktopSettingsScreen.kt` -- Roadmap: `docs/roadmap.md` - - diff --git a/docs/agent-playbooks/testing-and-ci-playbook.md b/docs/agent-playbooks/testing-and-ci-playbook.md deleted file mode 100644 index a7f0796df..000000000 --- a/docs/agent-playbooks/testing-and-ci-playbook.md +++ /dev/null @@ -1,88 +0,0 @@ -# Testing and CI Playbook - -Use this matrix to choose the right verification depth for a change. - -## 1) Baseline local verification order - -Run in this order for routine changes: - -```bash -./gradlew clean -./gradlew spotlessCheck -./gradlew spotlessApply -./gradlew detekt -./gradlew assembleDebug -./gradlew test -``` - -Notes: -- This order aligns with repository guidance in `AGENTS.md` and `.github/copilot-instructions.md`. -- CI runs host verification and Android build/device verification in separate jobs inside `.github/workflows/reusable-check.yml`. - -## 2) Change-type matrix - -- `docs-only` changes: - - Usually no Gradle run required. - - If you touched code examples or command docs, at least run `spotlessCheck` if practical. - - If you changed architecture, CI, validation commands, or agent workflow guidance, update the mirrored docs in `AGENTS.md`, `.github/copilot-instructions.md`, `GEMINI.md`, and `docs/kmp-status.md` in the same slice. -- `UI text/resource` changes: - - `spotlessCheck`, `detekt`, `assembleDebug`. -- `feature/commonMain logic` changes: - - `spotlessCheck`, `detekt`, `test`, `assembleDebug`. -- `navigation/DI wiring` changes (app graph, Koin module/wrapper changes): - - `spotlessCheck`, `detekt`, `assembleDebug`, `test`, plus `testFdroidDebugUnitTest` and `testGoogleDebugUnitTest` when available locally. - - If touching any KMP module, also run `kmpSmokeCompile`. -- `worker/service/background` changes: - - `spotlessCheck`, `detekt`, `assembleDebug`, `test`, and targeted tests around WorkManager/service behavior. -- `BLE/networking/core repository` changes: - - `spotlessCheck`, `detekt`, `assembleDebug`, `test`. - -## 3) Flavor and instrumentation checks - -Run these when relevant to map/provider/flavor-specific behavior: - -```bash -./gradlew lintFdroidDebug lintGoogleDebug -./gradlew testFdroidDebug -./gradlew testGoogleDebug -./gradlew connectedAndroidTest -``` - -## 4) CI parity checks - -Current reusable check workflow includes: - -- `spotlessCheck detekt` -- Android lint for all directly runnable Android modules: - `app:lintFdroidDebug app:lintGoogleDebug core:barcode:lintFdroidDebug core:barcode:lintGoogleDebug core:api:lintDebug mesh_service_example:lintDebug` - *(Note: `mesh_service_example:lintDebug` is temporary โ€” the module is deprecated and will be - removed along with its CI tasks in a future release.)* -- Host tests plus coverage aggregation: - `test koverXmlReport app:koverXmlReportFdroidDebug app:koverXmlReportGoogleDebug core:api:koverXmlReportDebug core:barcode:koverXmlReportFdroidDebug core:barcode:koverXmlReportGoogleDebug mesh_service_example:koverXmlReportDebug desktop:koverXmlReport` - *(Note: `mesh_service_example:koverXmlReportDebug` is temporary โ€” see above.)* -- KMP smoke compile lifecycle task (auto-discovers KMP modules and runs JVM + iOS simulator compile checks): - `kmpSmokeCompile` -- Android build tasks: - `app:assembleFdroidDebug app:assembleGoogleDebug mesh_service_example:assembleDebug` - *(Note: `mesh_service_example:assembleDebug` is temporary โ€” see above.)* -- Instrumented tests (when emulator tests are enabled): - `app:connectedFdroidDebugAndroidTest app:connectedGoogleDebugAndroidTest core:barcode:connectedFdroidDebugAndroidTest core:barcode:connectedGoogleDebugAndroidTest` -- Coverage uploads happen once from the host job; instrumented test results upload once from the first Android matrix API to avoid duplicate reporting. - -Reference: `.github/workflows/reusable-check.yml` - -PR workflow note: - -- `.github/workflows/pull-request.yml` ignores docs-only changes (`**/*.md`, `docs/**`), so doc-only PRs may skip Android CI by design. -- PR change detection includes workflow/build/config paths such as `.github/workflows/**`, `desktop/**`, `mesh_service_example/**` (deprecated, will be removed), `config/**`, `gradle/**`, `settings.gradle.kts`, and `test.gradle.kts`. -- Android CI on PRs runs with `run_instrumented_tests: false`; merge queue keeps the full emulator matrix on API 26 and 35. -- Gradle cache writes are enabled for trusted refs/events (`main`, `merge_group`, and `gh-readonly-queue/*`); other refs run in read-only cache mode. - -## 5) Practical guidance for agents - -- Start with the smallest set that validates your touched area. -- Keep documentation continuously in sync with architecture, CI, and workflow changes; do not defer doc fixes to a later PR. -- If modifying cross-module contracts (routes, repository interfaces, DI graph), run the broader baseline. -- If unable to run full validation locally, report exactly what ran and what remains. - - diff --git a/docs/kmp-status.md b/docs/kmp-status.md index fb9d74175..1f8ce1062 100644 --- a/docs/kmp-status.md +++ b/docs/kmp-status.md @@ -176,5 +176,5 @@ Remaining to be extracted from `:app` or unified in `commonMain`: - Roadmap: [`docs/roadmap.md`](./roadmap.md) - Agent guide: [`AGENTS.md`](../AGENTS.md) -- Playbooks: [`docs/agent-playbooks/`](./agent-playbooks/) +- Agent skills: [`.skills/`](../.skills/) - Decision records: [`docs/decisions/`](./decisions/)