docs: sync AGENTS.md, README.md, and CONTRIBUTING.md with current codebase (#644)
* docs: update AGENTS.md, README.md, and CONTRIBUTING.md Sync all three docs with current codebase state: Kotlin 2.4.10, Kotlinx Serialization, 28 screens, 6 theme presets, CI instrumented-tests job, color guard. * docs: missed control
This commit is contained in:
3 files changed
+90
-33
No files matched your search
@@ -5,14 +5,14 @@ This complements [README.md](README.md) (for humans) with agent-focused context.
|
|||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
**Hermes Control** is a Jetpack Compose Android app — a mobile control panel for
|
**Hermes Mobile** is a Jetpack Compose Android app — a mobile control panel for
|
||||||
[Hermes Agent](https://hermes-agent.nousresearch.com). It talks to the Hermes
|
[Hermes Agent](https://hermes-agent.nousresearch.com). It talks to the Hermes
|
||||||
dashboard's REST API and WebSocket TUI Gateway (JSON-RPC 2.0) over a trusted LAN.
|
dashboard's REST API and WebSocket TUI Gateway (JSON-RPC 2.0) over a trusted LAN.
|
||||||
|
|
||||||
- **Package:** `com.m57.hermescontrol`
|
- **Package:** `com.m57.hermescontrol`
|
||||||
- **Min SDK 26 / Target SDK 36 / Compile SDK 36**
|
- **Min SDK 26 / Target SDK 36 / Compile SDK 36**
|
||||||
- **Kotlin 2.3.20**, KSP 2.3.9 (standalone versioning, NOT `kotlinVersion-kspVersion`)
|
- **Kotlin 2.4.10**, KSP 2.3.10 (standalone versioning, NOT `kotlinVersion-kspVersion`)
|
||||||
- **Jetpack Compose**, Room 2.7.x, Navigation3, OkHttp WebSocket, Retrofit, Gson
|
- **Jetpack Compose**, Room 2.7.x, Navigation3, OkHttp WebSocket, Retrofit, Kotlinx Serialization
|
||||||
- **Auth:** `EncryptedSharedPreferences` (AES256-GCM), Bearer token (REST) + `?token=` (WS)
|
- **Auth:** `EncryptedSharedPreferences` (AES256-GCM), Bearer token (REST) + `?token=` (WS)
|
||||||
|
|
||||||
## Build & Test Commands
|
## Build & Test Commands
|
||||||
@@ -28,6 +28,7 @@ If you have a local Android SDK (`ANDROID_HOME` set), these work:
|
|||||||
```
|
```
|
||||||
|
|
||||||
**ktlint standalone** (no SDK needed):
|
**ktlint standalone** (no SDK needed):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Download the matching binary
|
# Download the matching binary
|
||||||
curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.2.1/ktlint
|
curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.2.1/ktlint
|
||||||
@@ -41,14 +42,17 @@ chmod +x ktlint
|
|||||||
### CI pipeline (`.github/workflows/android.yml`)
|
### CI pipeline (`.github/workflows/android.yml`)
|
||||||
|
|
||||||
| Job | Purpose |
|
| Job | Purpose |
|
||||||
|-----|---------|
|
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `ktlint` | ktlint 1.2.1 style check |
|
| `ktlint` | ktlint 1.2.1 style check + `checkColorLiterals` (hardcoded Color guard, issue #622) |
|
||||||
| `android-lint` | Android Lint |
|
| `android-lint` | Android Lint |
|
||||||
| `unit-tests` | JUnit |
|
| `unit-tests` | JUnit |
|
||||||
| `build` | assembleDebug (gated by the 3 above) |
|
| `build` | assembleDebug (gated by the 3 above) |
|
||||||
| `release-compile` | compileReleaseKotlin — catches release-variant compilation issues (e.g. debugImplementation-scoped deps referenced in main source) |
|
| `release-compile` | compileReleaseKotlin — catches release-variant compilation issues (e.g. debugImplementation-scoped deps referenced in main source) |
|
||||||
|
| `instrumented-tests` | Compose UI tests on Android emulator (API 34 ATD) |
|
||||||
| `ci-summary` | Aggregator (`if: always()`) — branch protection gates on THIS check |
|
| `ci-summary` | Aggregator (`if: always()`) — branch protection gates on THIS check |
|
||||||
|
|
||||||
|
Also: There is a separate workflow `merge-conflict-detector.yml` that auto-labels PRs with `has-conflicts`.
|
||||||
|
|
||||||
Every Gradle job validates `gradle-wrapper.jar` and uses the remote build cache
|
Every Gradle job validates `gradle-wrapper.jar` and uses the remote build cache
|
||||||
(`GRADLE_ENCRYPTED_KEY` secret). `concurrency.cancel-in-progress: true`.
|
(`GRADLE_ENCRYPTED_KEY` secret). `concurrency.cancel-in-progress: true`.
|
||||||
|
|
||||||
@@ -104,11 +108,13 @@ Some ViewModels (e.g. `AuthLoginViewModel`) are created at the Activity scope vi
|
|||||||
and cached state like `connectionSuccess` persists across screen entries.
|
and cached state like `connectionSuccess` persists across screen entries.
|
||||||
|
|
||||||
Always pair transient state flags with a `DisposableEffect` cleanup:
|
Always pair transient state flags with a `DisposableEffect` cleanup:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
onDispose { viewModel.clearTransientState() }
|
onDispose { viewModel.clearTransientState() }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The cleanup function should reset self-transitioning flags (`connectionSuccess`,
|
The cleanup function should reset self-transitioning flags (`connectionSuccess`,
|
||||||
`isLoading`, `errorMessage`) so the screen can re-enter cleanly.
|
`isLoading`, `errorMessage`) so the screen can re-enter cleanly.
|
||||||
|
|
||||||
@@ -118,7 +124,7 @@ The cleanup function should reset self-transitioning flags (`connectionSuccess`,
|
|||||||
explicitly disconnected on logout and reconnected after login:
|
explicitly disconnected on logout and reconnected after login:
|
||||||
|
|
||||||
| Event | Action |
|
| Event | Action |
|
||||||
|-------|--------|
|
| ------------- | ------------------------------------------------------ |
|
||||||
| Logout | `HermesWsClient.disconnect()` before clearing tokens |
|
| Logout | `HermesWsClient.disconnect()` before clearing tokens |
|
||||||
| Login success | `HermesWsClient.connect()` after `ApiClient.rebuild()` |
|
| Login success | `HermesWsClient.connect()` after `ApiClient.rebuild()` |
|
||||||
|
|
||||||
@@ -193,10 +199,18 @@ breakdown, edge cases, and timeline of previous occurrences.
|
|||||||
|
|
||||||
### Theme
|
### Theme
|
||||||
|
|
||||||
`Theme.kt` has `ENABLE_DYNAMIC_COLOR = true` (Material You on API 31+). Semantic
|
`Theme.kt` uses a preset-based theme system with 6 built-in presets: Default,
|
||||||
status colors (success/warning/error/info) are ALWAYS brand-defined via
|
Monochrome, Gruvbox, Catppuccin, AMOLED, and Neon Noir. Each preset provides both
|
||||||
`LocalHermesStatusColors`, never dynamic. Access them via
|
light and dark color schemes plus matching semantic status colors.
|
||||||
`LocalHermesStatusColors.current.success`, not `MaterialTheme.colorScheme`.
|
|
||||||
|
Dynamic color (Material You on API 31+) is controlled by a `useDynamicColors`
|
||||||
|
parameter on `HermesControlTheme`, not a compile-time constant. When dynamic
|
||||||
|
colors are active they override the preset's `ColorScheme`, but semantic status
|
||||||
|
colors (success/warning/error/info) are ALWAYS resolved from the active preset
|
||||||
|
via `LocalHermesStatusColors` — never from `MaterialTheme.colorScheme`.
|
||||||
|
|
||||||
|
Access status colors via `LocalHermesStatusColors.current.success`, not
|
||||||
|
`MaterialTheme.colorScheme`.
|
||||||
|
|
||||||
## Git Workflow
|
## Git Workflow
|
||||||
|
|
||||||
@@ -216,6 +230,7 @@ gh pr create --title "fix(#N): description" --body "Closes #N"
|
|||||||
### Commit Conventions — STRICT
|
### Commit Conventions — STRICT
|
||||||
|
|
||||||
**⚠ Agent-authored commits:** Do NOT override git authorship (`--author`) with the agent's identity. Use the default git config. No `Co-Authored-By` or `Generated with` trailers.
|
**⚠ Agent-authored commits:** Do NOT override git authorship (`--author`) with the agent's identity. Use the default git config. No `Co-Authored-By` or `Generated with` trailers.
|
||||||
|
|
||||||
- **Short commits:** subject line + max 2 lines of body. Split into atomic commits rather than writing long bodies.
|
- **Short commits:** subject line + max 2 lines of body. Split into atomic commits rather than writing long bodies.
|
||||||
- **Conventional Commits** types: `feat`, `fix`, `refactor`, `docs`, `test`, `ci`, `chore`.
|
- **Conventional Commits** types: `feat`, `fix`, `refactor`, `docs`, `test`, `ci`, `chore`.
|
||||||
- Bug fixes annotated inline with the tracking issue/regression ID.
|
- Bug fixes annotated inline with the tracking issue/regression ID.
|
||||||
@@ -244,7 +259,7 @@ gh pr create --title "fix(#N): description" --body "Closes #N"
|
|||||||
(`remember {}`, `buildAnnotatedString`, `LaunchedEffect`). Extract to a local
|
(`remember {}`, `buildAnnotatedString`, `LaunchedEffect`). Extract to a local
|
||||||
`val` at the composable scope first.
|
`val` at the composable scope first.
|
||||||
- Don't add new screens without checking if an existing one already covers the
|
- Don't add new screens without checking if an existing one already covers the
|
||||||
functionality (19+ screens exist). Extend rather than duplicate.
|
functionality (28+ screens exist). Extend rather than duplicate.
|
||||||
- **⚠ Never scope a dependency to `debugImplementation` if its import is used in
|
- **⚠ Never scope a dependency to `debugImplementation` if its import is used in
|
||||||
`main/` source code.** The CI `release-compile` job catches this, but save the
|
`main/` source code.** The CI `release-compile` job catches this, but save the
|
||||||
cycle. `okhttp3.logging.HttpLoggingInterceptor` is the classic example — it's
|
cycle. `okhttp3.logging.HttpLoggingInterceptor` is the classic example — it's
|
||||||
@@ -252,22 +267,48 @@ gh pr create --title "fix(#N): description" --body "Closes #N"
|
|||||||
`debugImplementation`. If you want it debug-only, wrap the usage in
|
`debugImplementation`. If you want it debug-only, wrap the usage in
|
||||||
`if (BuildConfig.DEBUG)` or extract it behind an interface.
|
`if (BuildConfig.DEBUG)` or extract it behind an interface.
|
||||||
|
|
||||||
|
### Hardcoded Color Guard (issue #622)
|
||||||
|
|
||||||
|
A custom Gradle task `checkColorLiterals` fails the build if `Color(0x...)` hex
|
||||||
|
literals or named `Color.White`/`Color.Black`/etc. appear outside the allowed
|
||||||
|
paths (`theme/`, `*Preview.kt`, `PairingScreen.kt`, `AuthLoginScreen.kt`).
|
||||||
|
|
||||||
|
Use `MaterialTheme.colorScheme.<token>`, `LocalHermesStatusColors.current.<semantic>`,
|
||||||
|
or `Color.Transparent` / `Color.Unspecified` instead. The guard runs in CI as part
|
||||||
|
of the `ktlint` job.
|
||||||
|
|
||||||
## Project Layout
|
## Project Layout
|
||||||
|
|
||||||
```
|
```
|
||||||
com.m57.hermescontrol/
|
com.m57.hermescontrol/
|
||||||
├── data/ local (AuthManager), remote (Retrofit), ws (WebSocket), model
|
├── data/
|
||||||
├── notification/ ChatNotificationService (foreground service)
|
│ ├── config/ ServerStore, ConnectionProfile, migrations
|
||||||
├── theme/ Color, Theme, Motion, Spacing, Shapes, Type
|
│ ├── local/ AuthManager, Room (ChatMessageEntity/Dao, HermesDatabase), AnalyticsCacheStore
|
||||||
├── ui/ chat, settings, common (HermesScaffold, StateViews), + 17 screens
|
│ ├── model/ 40+ data classes for API responses + requests
|
||||||
├── util/ CronExpressionFormatter, etc.
|
│ ├── remote/ ApiClient, Retrofit service, OkHttp provider, cookie management
|
||||||
|
│ ├── session/ ActiveSessionHolder
|
||||||
|
│ └── ws/ HermesWsClient, JSON-RPC models, WsEvent, BillingRepository
|
||||||
|
├── notification/ ChatNotificationService, NotificationReplyReceiver
|
||||||
|
├── theme/ Color, Theme, Motion, Spacing, Shapes, Type, HermesStatusColors
|
||||||
|
│ └── presets/ Default, Monochrome, Gruvbox, Catppuccin, AMOLED, NeonNoir
|
||||||
|
├── ui/
|
||||||
|
│ ├── common/ HermesScaffold, StateViews, SharedComponents, DetailDialog, DetailRows
|
||||||
|
│ └── 28 feature packages (achievements, analytics, authlogin, billing, channels,
|
||||||
|
│ chat, config, connect, cron, gateway, kanban, keys, landing, logs, mcp,
|
||||||
|
│ model, pairing, plugins, process, profiles, providers, sessions, settings,
|
||||||
|
│ skills, system, toolsets, webhooks)
|
||||||
|
├── util/ CronExpressionFormatter, LocaleContextWrapper
|
||||||
|
├── HermesControlApp.kt Application class
|
||||||
├── Navigation.kt Drawer + NavDisplay + entry wiring
|
├── Navigation.kt Drawer + NavDisplay + entry wiring
|
||||||
├── NavigationController.kt Central navigation guard (dedup)
|
├── NavigationController.kt Central navigation guard (dedup)
|
||||||
|
├── NavigationKeys.kt @Serializable NavKey data objects (28 screens + 6 settings sub-pages)
|
||||||
|
├── ScreenRegistry.kt entry<T> registrations for all NavKeys
|
||||||
└── MainActivity.kt
|
└── MainActivity.kt
|
||||||
```
|
```
|
||||||
|
|
||||||
## Further Reading
|
## Further Reading
|
||||||
|
|
||||||
- [README.md](README.md) — human-facing overview, features, screenshots, tech stack
|
- [README.md](README.md) — human-facing overview, features, screenshots, tech stack
|
||||||
- [TEST_INFRA.md](TEST_INFRA.md) / [TEST_READY.md](TEST_READY.md) — test infrastructure notes
|
- [CONTRIBUTING.md](CONTRIBUTING.md) — contributor workflow, PR checklist, code style
|
||||||
- [.github/workflows/android.yml](.github/workflows/android.yml) — CI pipeline source of truth
|
- [.github/workflows/android.yml](.github/workflows/android.yml) — CI pipeline source of truth
|
||||||
|
- [.github/workflows/merge-conflict-detector.yml](.github/workflows/merge-conflict-detector.yml) — auto-labels conflicting PRs
|
||||||
+10
-1
@@ -68,6 +68,13 @@ We enforce Kotlin coding conventions and Jetpack Compose best practices.
|
|||||||
### Compose Guidelines
|
### Compose Guidelines
|
||||||
- Standard screen structures must use `HermesScaffold` rather than raw Material3 `Scaffold`.
|
- Standard screen structures must use `HermesScaffold` rather than raw Material3 `Scaffold`.
|
||||||
- Composable parameters must follow the standard order: `modifier` first, then event callbacks, and finally children content.
|
- Composable parameters must follow the standard order: `modifier` first, then event callbacks, and finally children content.
|
||||||
|
- Do **not** apply `paddingValues` on inner content inside `HermesScaffold` — the scaffold already handles top bar padding. See `AGENTS.md` for the full breakdown of this recurring bug.
|
||||||
|
- Every data screen must implement `LoadingState`, `ErrorState`, and `EmptyState` branches in its `when { }` block.
|
||||||
|
|
||||||
|
### Color Usage
|
||||||
|
- **No hardcoded `Color(0x...)` literals** outside `theme/`, `*Preview.kt`, and auth screens. A Gradle task (`checkColorLiterals`) enforces this in CI.
|
||||||
|
- Use `MaterialTheme.colorScheme.<token>` or `LocalHermesStatusColors.current.<semantic>` instead.
|
||||||
|
- `Color.Transparent` and `Color.Unspecified` are allowed.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -77,7 +84,9 @@ Before submitting your PR, please verify:
|
|||||||
|
|
||||||
- [ ] Your branch is up-to-date with `main`.
|
- [ ] Your branch is up-to-date with `main`.
|
||||||
- [ ] Local build and `ktlint` checks pass successfully.
|
- [ ] Local build and `ktlint` checks pass successfully.
|
||||||
|
- [ ] `checkColorLiterals` passes (no hardcoded Color literals outside theme/).
|
||||||
- [ ] No unused imports, unused parameters, or dead code.
|
- [ ] No unused imports, unused parameters, or dead code.
|
||||||
- [ ] Every `Image` and `Icon` element has a descriptive `contentDescription` for accessibility.
|
- [ ] Every `Image` and `Icon` element has a descriptive `contentDescription` for accessibility.
|
||||||
- [ ] New components match the UI/UX style of similar existing screens.
|
- [ ] New screens use `HermesScaffold` and implement Loading/Error/Empty states.
|
||||||
|
- [ ] New components match the UI/UX style of similar existing screens (28+ screens for reference).
|
||||||
- [ ] Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) and are atomic (subject + ≤2 lines body).
|
- [ ] Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) and are atomic (subject + ≤2 lines body).
|
||||||
@@ -27,12 +27,14 @@
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Real-Time Chat:** Message your agent with Room-backed local database history.
|
- **Real-Time Chat:** Message your agent with Room-backed local database history and inline reply notifications.
|
||||||
- **System Config:** Manage active profiles, installed skills, plugins, and LLM model selections.
|
- **System Config:** Manage active profiles, installed skills, plugins, toolsets, and LLM model/provider selections.
|
||||||
- **Operations:** Stream and filter live logs, manage cron jobs, edit environment keys, and test webhooks.
|
- **Operations:** Stream and filter live logs, manage cron jobs, edit environment keys, test webhooks, and monitor processes.
|
||||||
- **Gateway Status:** Monitor WebSocket connection, MCP servers, and messaging channel status.
|
- **Gateway Status:** Monitor WebSocket connection, MCP servers, messaging channels, and OAuth providers.
|
||||||
- **Productivity:** View and manage tasks via integrated Kanban boards and track agent milestones.
|
- **Productivity:** View and manage tasks via integrated Kanban boards, track agent milestones, and browse session history.
|
||||||
- **Modern UX:** Native Material You design supporting dynamic color theming, scroll-aware TopBar, and pull-to-refresh.
|
- **Analytics & Billing:** Usage analytics dashboard and billing/subscription management.
|
||||||
|
- **Theming:** 6 built-in color presets (Default, Monochrome, Gruvbox, Catppuccin, AMOLED, Neon Noir) plus Material You dynamic colors on supported devices.
|
||||||
|
- **Modern UX:** Native Material 3 design with pull-to-refresh, scroll-aware TopBar, and customizable bottom navigation.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -108,23 +110,28 @@ The **Pairing** screen lets you approve or revoke agents and services that are t
|
|||||||
|
|
||||||
```
|
```
|
||||||
app/src/main/java/com/m57/hermescontrol/
|
app/src/main/java/com/m57/hermescontrol/
|
||||||
├── data/ # Local (Room, EncryptedSharedPreferences) & Remote (Retrofit, OkHttp WS)
|
├── data/ # Local (Room, AuthManager), Remote (Retrofit, OkHttp), WS (WebSocket), Models
|
||||||
├── notification/ # Foreground service for message notifications
|
├── notification/ # Foreground service + inline reply for chat notifications
|
||||||
├── theme/ # Material You design system, status colors, spacing, and typography
|
├── theme/ # Preset-based design system (6 themes), status colors, spacing, typography
|
||||||
└── ui/ # Compose screens (Chat, Settings, Profiles, Kanban, etc.) + Navigation
|
├── ui/ # 28 Compose feature screens + common components (HermesScaffold, StateViews)
|
||||||
|
├── util/ # CronExpressionFormatter, LocaleContextWrapper
|
||||||
|
└── Navigation*.kt # Navigation3 wiring, keys, screen registry, controller
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
- **Language:** Kotlin 2.4.0 with KSP compiler plugin
|
- **Language:** Kotlin 2.4.10 with KSP 2.3.10 compiler plugin
|
||||||
- **UI & Layout:** Jetpack Compose (BOM 2026.03.01) & Material 3 / Material You
|
- **UI & Layout:** Jetpack Compose (BOM 2026.03.01) & Material 3 / Material You
|
||||||
- **Navigation:** Navigation3 (Compose-first Routing)
|
- **Navigation:** Navigation3 (Compose-first Routing)
|
||||||
- **Networking:** Retrofit 3.0.0, OkHttp 5.4.0, Gson 2.14.0
|
- **Networking:** Retrofit 3.0.0, OkHttp 5.4.0, Kotlinx Serialization 1.11.0
|
||||||
- **Database:** Room 2.7.1 with SQLCipher encryption
|
- **Database:** Room 2.7.1 with SQLCipher 4.17.0 encryption
|
||||||
- **Security:** `EncryptedSharedPreferences` (AES256-GCM)
|
- **Security:** `EncryptedSharedPreferences` (AES256-GCM), DataStore
|
||||||
- **Formatting:** `ktlint` style rules (checked automatically in CI)
|
- **Theming:** 6 built-in presets (Default, Monochrome, Gruvbox, Catppuccin, AMOLED, Neon Noir) + Material You dynamic colors
|
||||||
|
- **Image Loading:** Coil 2.7.0
|
||||||
|
- **Testing:** JUnit 5, MockK, Turbine, Espresso, Compose UI testing
|
||||||
|
- **Formatting:** `ktlint` 1.2.1 style rules (checked automatically in CI)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user