Files
aiturk-mobil/AGENTS.md
T
M57andM57 efba360d8a fix(ci): add release-compile safeguard to catch debugImplementation regressions (#336)
- Reverts okhttp.logging dependency from debugImplementation back to
  implementation so it's on the release classpath
- Adds compileReleaseKotlin CI job to catch release-variant compilation
  errors on every PR
- Fixes signing config to allow compileReleaseKotlin without keystore env
  vars (graceful fallback instead of hard require())
- Updates AGENTS.md with dependency scoping pitfall and CI docs

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
2026-06-23 05:46:11 +03:00

7.9 KiB

AGENTS.md

Guidance for AI coding agents working on this repository. This complements README.md (for humans) with agent-focused context.

Project Overview

Hermes Control is a Jetpack Compose Android app — a mobile control panel for Hermes Agent. It talks to the Hermes dashboard's REST API and WebSocket TUI Gateway (JSON-RPC 2.0) over a trusted LAN.

  • Package: com.m57.hermescontrol
  • Min SDK 26 / Target SDK 36 / Compile SDK 36
  • Kotlin 2.3.20, KSP 2.3.9 (standalone versioning, NOT kotlinVersion-kspVersion)
  • Jetpack Compose, Room 2.7.x, Navigation3, OkHttp WebSocket, Retrofit, Gson
  • Auth: EncryptedSharedPreferences (AES256-GCM), Bearer token (REST) + ?token= (WS)

Build & Test Commands

⚠ No local Android SDK — CI is the only compilation truth engine

./gradlew assembleDebug, ./gradlew test, ./gradlew ktlintCheck all fail locally (no Android SDK installed). CI verifies compilation, lint, and tests. Push small and watch CI.

What you CAN run locally

# Standalone ktlint binary (no SDK needed) — the PRIMARY local check
curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.2.1/ktlint
chmod +x ktlint
./ktlint <file>              # check one file
./ktlint --format <file>     # auto-fix import ordering, indent, trailing commas
find app/src -name '*.kt' | xargs ./ktlint --format   # fix all

CI pipeline (.github/workflows/android.yml)

Job Purpose
ktlint ktlint 1.2.1 style check
android-lint Android Lint
unit-tests JUnit
build assembleDebug (gated by the 3 above)
release-compile compileReleaseKotlin — catches release-variant compilation issues (e.g. debugImplementation-scoped deps referenced in main source)
ci-summary Aggregator (if: always()) — branch protection gates on THIS check

Every Gradle job validates gradle-wrapper.jar and uses the remote build cache (GRADLE_ENCRYPTED_KEY secret). concurrency.cancel-in-progress: true.

Releasing (.github/workflows/release.yml)

Triggers on git push tag v*. Release APK uses R8 minification + resource shrinking. Requires permissions: contents: write on the build-release job.

Code Style

  • ktlint 1.2.1 is enforced in CI. Run ./ktlint --format before pushing.
  • Import ordering is the #1 CI failure: ktlint enforces ASCII-lexicographic order (uppercase before lowercase: LaunchedEffect before collectAsState).
  • const val must use SCREAMING_SNAKE_CASE.
  • Trailing commas required. No trailing whitespace.
  • 120 char max line length.

Architecture Conventions

Navigation (Navigation3 — NOT Navigation Compose)

Uses androidx.navigation3 (NavKey, NavBackStack, NavDisplay, entry<T>).

⚠ Always route navigation through NavigationController.navigateTo(). Never call backStack.add(ScreenKey) directly from UI callbacks — the controller has a deduplication guard. Bypassing it stacks duplicate screen entries that compete for touch events and become unresponsive.

Shared Components

  • HermesScaffold — drawer-aware Scaffold + TopAppBar with refresh slot, pull-to-refresh, optional snackbar host. New screens MUST use this instead of raw Scaffold. API: title: @Composable () -> Unit (not String — wrap in { Text("...") }).
  • StateViews — LoadingState, ErrorState, EmptyState. Every data screen must implement all three branches in its when { } block.

Room Persistence

  • ChatMessageEntity / ChatMessageDao / HermesDatabase — chat messages survive app kills. getMessagesForSession() returns suspend fun ...: List<ChatMessageEntity> (not Flow — the caller controls the coroutine scope).
  • Room 2.7.x requires room { schemaDirectory("$projectDir/schemas") } DSL.
  • ChatViewModel extends AndroidViewModel (needs Application for DB access).

Theme

Theme.kt has ENABLE_DYNAMIC_COLOR = true (Material You on API 31+). Semantic status colors (success/warning/error/info) are ALWAYS brand-defined via LocalHermesStatusColors, never dynamic. Access them via LocalHermesStatusColors.current.success, not MaterialTheme.colorScheme.

Git Workflow

PR-Always (ENFORCED)

Every change goes through a PR. Never push directly to main.

git checkout main && git pull origin main
git checkout -b fix/issue-N-description    # or feat/...
# make changes, run ./ktlint --format
git commit -m "fix(#N): description"
git push -u origin HEAD
gh pr create --title "fix(#N): description" --body "Closes #N"

Commit Conventions — STRICT

🚫 NEVER add yourself as author or co-author. The agent must never appear in any commit metadata field — not as author, not as Co-Authored-By, not in Generated with, not anywhere.

  • No --author override. Use the default git config. Always.
  • 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.
  • Bug fixes annotated inline: // B7 (Jun 18 2026, kanban t_xxx): description.

Security Considerations

  • Cleartext HTTP/WS is intentional — trusted LAN only. usesCleartextTraffic="true" and http:///ws:// URLs are by design. Don't "fix" this unless adding HTTPS support.
  • HttpLoggingInterceptor.Level.BODY must stay gated on BuildConfig.DEBUG — it prints the Authorization header. (B1)
  • AuthManager token is ephemeral — it becomes invalid when the Hermes dashboard restarts. Symptom: 401 on every call. Fix: re-extract the token from the running gateway, don't assume the stored one is valid.
  • Release signing uses a keystore with env-var credentials (KEYSTORE_PATH, KEYSTORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD). Don't commit keystore files or hardcode passwords.

Things to Avoid

  • Don't run ./gradlew tasks locally expecting them to work — no Android SDK.
  • Don't remove @OptIn(ExperimentalMaterial3Api::class) from screens that use SegmentedButton, PullToRefreshBox, or other experimental M3 APIs.
  • Don't use remember { AuthManager.getBottomNavItems() } without a key — SharedPreferences is not Compose state; the value goes stale. Read it directly.
  • Don't access MaterialTheme.colorScheme.* inside non-@Composable lambdas (remember {}, buildAnnotatedString, LaunchedEffect). Extract to a local val at the composable scope first.
  • Don't add new screens without checking if an existing one already covers the functionality (19+ screens exist). Extend rather than duplicate.
  • ⚠ 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 cycle. okhttp3.logging.HttpLoggingInterceptor is the classic example — it's imported in ApiClient.kt (main source) so it must be implementation, not debugImplementation. If you want it debug-only, wrap the usage in if (BuildConfig.DEBUG) or extract it behind an interface.

Project Layout

com.m57.hermescontrol/
├── data/          local (AuthManager), remote (Retrofit), ws (WebSocket), model
├── notification/  ChatNotificationService (foreground service)
├── theme/         Color, Theme, Motion, Spacing, Shapes, Type
├── ui/            chat, settings, common (HermesScaffold, StateViews), + 17 screens
├── util/          CronExpressionFormatter, etc.
├── Navigation.kt          Drawer + NavDisplay + entry wiring
├── NavigationController.kt  Central navigation guard (dedup)
└── MainActivity.kt

Further Reading