Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .agents/rules/antigravity-rtk-rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# RTK - Rust Token Killer (Google Antigravity)

**Usage**: Token-optimized CLI proxy for shell commands.

## Rule

Always prefix shell commands with `rtk` to minimize token consumption.

Examples:

```bash
rtk git status
rtk cargo test
rtk ls src/
rtk grep "pattern" src/
rtk find "*.rs" .
rtk docker ps
rtk gh pr list
```

## Meta Commands

```bash
rtk gain # Show token savings
rtk gain --history # Command history with savings
rtk discover # Find missed RTK opportunities
rtk proxy <cmd> # Run raw (no filtering, for debugging)
```

## Why

RTK filters and compresses command output before it reaches the LLM context, saving 60-90% tokens on common operations. Always use `rtk <cmd>` instead of raw commands.
60 changes: 60 additions & 0 deletions .github/workflows/android-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Android regression checks

on:
push:
branches: [main, Fix-issue]
pull_request:
branches: [main]
workflow_dispatch:

permissions:
contents: read

jobs:
publication-safety:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
persist-credentials: false
- name: Reject bundled SDKs, keys, recordings and generated files
env:
PUBLICATION_BASE: ${{ github.event.pull_request.base.sha || (github.ref == 'refs/heads/main' && github.event.before) || '' }}
run: |
python3 -m unittest discover -s scripts -p 'test_*.py'
if [ -n "$PUBLICATION_BASE" ] && [ "$PUBLICATION_BASE" != "0000000000000000000000000000000000000000" ]; then
python3 scripts/check_publication.py --base "$PUBLICATION_BASE"
else
python3 scripts/check_publication.py --base origin/main
fi
- name: Verify Gitleaks and scan full reachable history with redaction
run: |
curl --fail --silent --show-error --location --output "$RUNNER_TEMP/gitleaks.tar.gz" https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz
echo "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb $RUNNER_TEMP/gitleaks.tar.gz" | sha256sum --check --strict
tar -xzf "$RUNNER_TEMP/gitleaks.tar.gz" -C "$RUNNER_TEMP" gitleaks
"$RUNNER_TEMP/gitleaks" git . --log-opts='HEAD --full-history --diff-merges=first-parent' --redact --no-banner

phone-and-shared:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4
with:
distribution: temurin
java-version: '17'
- uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3
with:
packages: platform-tools
log-accepted-android-sdk-licenses: false
- name: Install public Android build components
run: sdkmanager 'platforms;android-34' 'build-tools;34.0.0'
- name: Test sensor contracts and phone; run Android lint and APK build
run: |
chmod +x gradlew
./gradlew :shared:test :mobile:testDebugUnitTest :mobile:lintDebug :mobile:assembleDebug --no-daemon --console=plain
# Full Wear checks run locally with the private Samsung AAR. Never upload SDKs or recordings.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@ key.properties
/captures/
.externalNativeBuild/
.cxx/
__pycache__/
*.pyc
*.aab
*.p12
*.pfx
*.pem
146 changes: 83 additions & 63 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,63 +1,83 @@
# AI Agent Instructions for HeartRateMonitor Project

You are acting as a **Senior Android Developer** and **Architect** assisting with the `HeartRateMonitor` multi-module Android application. Your primary goal is to produce high-quality, production-ready code that is robust, maintainable, and highly readable.

When interacting with this repository, you must strictly adhere to the following guidelines and best practices.

---

## 1. Core Engineering Principles

### SOLID Principles
Every architectural decision and code modification must respect SOLID principles:
- **Single Responsibility Principle (SRP):** Classes and functions should have one reason to change. Example: Separate UI rendering (`Activity`/`Fragment`) from permission handling (`PermissionManager`) and sensor data collection (`Listeners`).
- **Open/Closed Principle (OCP):** Software entities should be open for extension but closed for modification. Example: Use interfaces and abstract classes for message routing so new message types can be added without modifying existing router logic.
- **Liskov Substitution Principle (LSP):** Subtypes must be substitutable for their base types. Ensure proper inheritance, especially when dealing with SDK listener callbacks.
- **Interface Segregation Principle (ISP):** Keep interfaces small and focused (e.g., `PermissionCallback`, `TrackerDataObserver`). Do not force classes to implement methods they do not use.
- **Dependency Inversion Principle (DIP):** High-level modules should not depend on low-level modules; both should depend on abstractions. Inject dependencies where possible.

### DRY (Don't Repeat Yourself)
- Identify duplicated logic across the codebase and extract it into reusable helper functions, base classes, or extension functions.
- If code is shared between the `mobile` and `wear` modules, place it in the `shared` module to ensure consistency and single-source-of-truth.

### Clean Code
- **Naming:** Use clear, descriptive, and unambiguous names for variables, functions, and classes. Avoid abbreviations.
- **Null Safety:** Leverage Kotlin's null safety. Avoid `lateinit` unless strictly necessary and guaranteed to be initialized. Prefer nullable types (`?`) and safe calls (`?.`) to prevent `NullPointerException` (NPE) crashes.
- **Immutability:** Prefer `val` over `var`. Use read-only collections (`List` instead of `MutableList`) for public API surfaces.
- **Comments & Documentation:** Code should be self-documenting. Use KDoc to explain the *why* behind complex logic, not the *what*. Document API surfaces, especially for SDK integrations.

---

## 2. Project Architecture & Context

This is a multi-module project consisting of:
- **`:mobile`**: The companion smartphone app.
- **`:wear`**: The Wear OS smartwatch app (handles raw sensor data collection).
- **`:shared`**: Common data models and constants (e.g., `Message`, `ActivityCode`) shared between mobile and wear.

### Android & Wear OS Specifics
- **Wearable Data Layer:** Use modern asynchronous Google Play Services APIs (`MessageClient`, `DataClient`, `NodeClient`) with Kotlin Coroutines (`.await()`). Do *not* use the deprecated `GoogleApiClient` or `Wearable.MessageApi`.
- **Sensor SDKs:** The project relies on external SDKs like the **Samsung Health Sensor SDK** and **Polar SDK**. Ensure proper lifecycle management of these sensors to prevent memory leaks and battery drain.
- **Permissions:** Be hyper-aware of Android permission changes. For example, on Android 13+ (API 33), `BODY_SENSORS_BACKGROUND` must be requested sequentially *after* `BODY_SENSORS` has been granted. Do not batch incompatible permission requests.

---

## 3. Execution Workflow & Testing

### Refactoring & Implementation
1. **Analyze First:** Before making changes, analyze the current state of the file and its dependencies. Understand the blast radius of your changes.
2. **Step-by-Step:** Apply changes iteratively. Do not attempt to rewrite massive files in a single pass unless absolutely necessary.
3. **Coroutine Usage:** Offload heavy lifting (database, network, IPC) to background threads using Kotlin Coroutines (`lifecycleScope`, `viewModelScope`, `Dispatchers.IO`). Never block the main thread.

### Testing Standard
- **Mandatory Unit Tests:** For every class or public function refactored or created, you must generate or update the corresponding Unit Tests.
- Use `JUnit4` and `Mockito` for testing.
- Test edge cases, null states, lifecycle changes, and error handling, not just the "happy path."

---

## 4. Communication & Planning

- When asked to implement a complex feature or major refactor, **always propose a structured plan first** and wait for user approval.
- Clearly list out the files that will be modified, created, or deleted.
- Explain *why* the changes are being made in the context of the principles outlined in this document.
# AGENTS.md — application contract and maintenance

## Scope and authoring

- Modules: `mobile` hub, `wear` Galaxy Watch acquisition, `shared` protocols/buffers/clocks/control. Both APKs: **1.3.1 / versionCode 5**.
- Apply existing RTK (`C:\Users\HP OMEN\.codex\RTK.md`), Caveman and Ponytail guidance; authoring only, no app dependencies or unavailable executable claims.
- Preserve dirty files, security/accessibility controls and scoped delivery. Do not fabricate samples, metrics or device receipts; deterministic test fixtures are not measurements.
- Before changing behavior read this file, [sensor spec](openspec/specs/01-watch-accelerometer-and-vitals.md) and [session spec](openspec/specs/02-session-control-and-polar-recovery.md). `PRD.md`/`DESIGN.md` are absent here.
- Use [session repair plan/checklist](SESSION_CONTROL_FIX_PLAN.md); historic v1.3.0 evidence remains in [SENSOR_FIX_PLAN.md](SENSOR_FIX_PLAN.md).
- Adapt [OpenSpec](https://github.com/Fission-AI/OpenSpec) proposal/spec/tasks and [BMAD](https://github.com/bmad-code-org/BMAD-METHOD) context/spec/build/review. Write BDD behavior, implement the smallest fix, verify, review and attach receipts; these CLIs are not installed dependencies.

## Complete feature inventory

| Feature | Implementation / contract |
| --- | --- |
| Watch acceleration | Native `Sensor.TYPE_ACCELEROMETER`; X/Y/Z in m/s², requested 25 Hz; 100-sample batches. Honor registration result; no Samsung connection requirement. |
| Watch PPG Green/IR/Red | One Samsung `PPG_CONTINUOUS` tracker with all three PPG types, **25 Hz each**, 300-sample batches. Only normal SDK status samples are accepted. |
| PPG channel controls | Phone status controls include/exclude individual channels from acquisition buffers within the active session. The shared hardware tracker remains active while any channel is enabled. |
| Polar connection | Manual device ID, Bluetooth permission/setup, connected/disconnected state, device name, firmware and battery information. |
| Polar ECG | ECG-ready H10 at 130 Hz; one owned pending/running subscription, timeout, two transient retries. Cancel pending start by disconnecting; wait for asynchronous stop before Resume. |
| Polar HR/RR | Record SDK HR independently of ECG. Optional `rrsMs` already uses milliseconds; unavailable RR stays empty. Verity Sense must not be described as an ECG source. |
| Clock conversion | Watch PPG retains SDK Unix milliseconds. Native acceleration maps elapsed-realtime event timestamps to Unix milliseconds from a captured clock anchor. |
| ECG timing | Preserve per-sample Polar nanoseconds and gaps. Anchor the last sample of the first packet to phone receipt time once per subscription. Clear the anchor on resume/reconnect. |
| HR/RR timing | HR rows use notification receipt time. RR plot intervals are back-calculated from that receipt and the SDK millisecond intervals; never add an epoch timestamp as a time correction. |
| QRS analysis | Existing derivative/adaptive-threshold detector, R-peak markers and derived HR/RR. Peak indices continue to address the complete ECG recording after resume. No measured clinical accuracy claim. |
| Start/Pause/Resume/End | Ordered revisions, idempotent requests, fresh peer lookup, ACK/retry. Pause/End confirm successful sensor release; Resume keeps identity and buffers; End is terminal. |
| Watch transport | Session ID, unique channel/session/batch paths, serialized uploads with retry, copied callback payloads, shape validation and duplicate suppression. Final counts cover Green/IR/Red/acceleration. |
| Transfer completion | Stop receipt and received counts must match before a Watch transfer is complete. Late flushes remain acceptable after pause/stop. Explicit partial export remains marked incomplete. |
| CSV export | SAF-selected directory and filename; ECG, each PPG channel, acceleration, Polar HR/RR, All PPG and All. Empty channels have zero rows, not padded samples. |
| Plotting | Separate ECG, Green/IR/Red, QRS and HR/RR graphs; pan while stopped, visibility/analysis preferences. Repeated redraw requests are coalesced through Choreographer. No measured 60 FPS claim. |
| Foreground lifecycle | Private phone/Watch services and ongoing notifications. Active acquisition holds CPU wake locks; Pause/End release them. Watch stops service after final uploads drain. |
| Reconfiguration | Plot setup refresh preserves samples. Polar API restart/device ID change ends an active session before replacing the connection, retaining received recordings for export. |
| Settings/help | Device ID, plot/analysis visibility, configured preferences, SAF directory selection, device information and actionable in-app help. |
| Distribution | Matching versioned debug APKs, immutable release tags, direct phone/Watch download links, preserved historical releases; Samsung AAR stays local/ignored. |
| Lost controller | Phone heartbeat every 3 s; Watch pauses sensors after 15 s without a valid command. Stop-release failure is retried, never acknowledged as off. Last control state persists to reject a delayed Start after Activity recreation. |

## Invariants and limits

- Values/timestamps stay paired. Validate size/shape/type/order/finite XYZ; reject bad packets and duplicates without synthetic zero rows. Accept late current-session application-buffer tails after Pause/End.
- Serialize control/acquisition on the main looper. Bind Samsung callbacks to registration generations; keep ownership after failed unregistration and retry release. Cancel pending/running ECG and obsolete API callbacks on lifecycle changes.
- End for a session is terminal. New Start needs a newer ID; Resume only follows Pause. Do not mark Watch off before matching revision and `sensorsStopped=true`; final counts must also match before transfer completion.
- Continuous Green/IR/Red uses one `PPG_CONTINUOUS`, **25 Hz each**. Legacy IR/Red 100 Hz trackers are on-demand; never substitute them for continuous acquisition.
- H10 supplies ECG; Verity Sense does not. Supported HR is independent of ECG. `rrsMs` is already milliseconds; no second scaling or epoch doubling.
- Preserve real Polar per-sample nanoseconds, dropped samples and pause gaps; reset receipt anchors only for a new subscription. Do not replace sensor time with sample-index clocks or require `setLocalTime` before ECG.
- CPU wake locks exist only for active recording. Watch lease expiration pauses acquisition; reconnection can resume the same in-memory session. Activity destruction ends Watch acquisition; a new session is required after recreation.
- Receipt anchoring approximates absolute alignment and includes BLE delay/clock drift. Requested accelerometer rate is not guaranteed; retain actual event times.
- Session samples/unsent queues are in memory; process-death recovery/durable recording is absent. Pause/End drain application buffers, not an independently proven Samsung SDK-internal tail. Save before closing; physical completeness needs hardware receipts.
- Unsupported capability/permission/policy, registration failure and final ECG failure stay visible. Never promote build/test/CI success to physiological, clinical, measured frame-rate or hardware acceptance.
- Legacy ECG image helper has no active export menu; do not advertise inactive helpers as delivered features.

## Executable BDD mapping

| Contract | Regression |
| --- | --- |
| Ordered commands, terminal End, lease/heartbeat, ACK and restored End | `shared/.../WatchControlTest.kt` |
| Failed/partial registration, retry ownership, callback invalidation | `shared/.../TrackerLifecycleTest.kt` |
| Pending/synchronous ECG startup, disposal, timeout, duplicate start, retry budget | `mobile/.../EcgStreamControllerTest.kt` |
| Clock anchoring, 130 Hz span/gaps, RR/native event times | `shared/.../TimestampHelperTest.kt` |
| Full/partial batches, no off-by-one drain or malformed data | `shared/.../RecordingBufferTest.kt`, `SensorBatchValidationTest.kt` |
| Late tails, duplicate suppression and final four-channel counts | `shared/.../WatchSessionTest.kt` |
| Missing Samsung readiness and continuous rate metadata | `wear/.../ListenerReadinessTest.kt`, `RecordingBufferTest.kt` |
| Phone controls/service/wake-lock release and frame scheduling | `mobile/src/androidTest/.../RecordingLifecycleTest.kt` |

## Build and release gates

- Gradle 8.6 / AGP 8.4.0 / Kotlin 2.1.0 / JDK 17 via `JAVA_HOME`; no absolute JDK path in `gradle.properties`.
- Samsung Health Sensor SDK 1.4.1 AAR belongs only in ignored `wear/libs/`. Public CI checks shared/mobile; full Wear gates must run locally with the private SDK.
- Focused regressions: `:shared:test --tests '*WatchControlTest'` or `:mobile:testDebugUnitTest --tests '*EcgStreamControllerTest'`; Android lint has module/task scope.
- Full required gates before release:

```powershell
.\gradlew.bat :shared:test :mobile:testDebugUnitTest :wear:testDebugUnitTest :mobile:lintDebug :wear:lintDebug :mobile:assembleDebug :wear:assembleDebug :mobile:assembleDebugAndroidTest --no-daemon --console=plain
.\gradlew.bat :mobile:connectedDebugAndroidTest --no-daemon --console=plain
git diff --check
```

- Record exact test/error/warning counts and APK hashes in the plan. Explicitly allowlist staged paths, scan publication content, deliver only to the authorized fork/branches; update `main` through a tested PR and merge commit when requested. Preserve upstream and existing history/tags.
- Run `python -m unittest discover -s scripts -p 'test_*.py'` and `python scripts/check_publication.py --base origin/main` before publishing. Scan staged/current files and reachable history with redacted Gitleaks; CI repeats both gates on `main`, `Fix-issue`, and PRs. Keep action SHAs, tool/distribution checksums and read-only workflow permissions.
- Backup/device transfer must remain disabled; settings/services private. Do not log raw sensor values, device IDs or received message contents. SDK 34 Wear health service requires Body Sensors or granted Activity Recognition; denial stays visible.
- Existing Polar AAR is retained only in pre-existing Git history. No AAR may be added to current/incoming source; history cleanup requires explicit rewrite authorization. Merge evidence: [MAIN_MERGE_AUDIT.md](MAIN_MERGE_AUDIT.md).
- Keep AARs, real session CSVs, APKs, credentials and keystores out of commits. Publish only matching phone/Watch debug APK assets; preserve old releases and direct download links in README.
- AI commits include `Co-Authored-By: Codex (GPT-6) <noreply@openai.com>`.
- Physical Watch/H10 acceptance remains **unchecked** without the devices: record actual model/firmware/permissions, rates/counts/CSV hashes, timestamp gaps, repeated rapid controls, LEDs off, reconnect and absence/error behavior.
Loading
Loading