mirror of
https://github.com/Monkopedia/rouse-context.git
synced 2026-10-19 10:33:18 +00:00
Cut auto-loaded context overhead (#425)
Three changes to reduce per-turn and per-agent-launch context cost: 1. New `.claude/agent-preamble.md` — single source for agent-launch boilerplate (don't touch .claude, use opus, branch/PR conventions, relay deploy method, device targeting). Agent prompts can now reference it instead of repeating ~15-20 lines per launch. 2. Trim `.claude/CLAUDE.md` from 73 to 51 lines — drop Architecture and Module Boundaries sections that now live (and are kept current) in `docs/design/overall.md` and `docs/design/android-app.md`. Keep build commands, workflow pointers, code style, signing keystore warning, domain. 3. New `docs/agent-quickstart.md` — distilled commands and paths agents reach for repeatedly: module->path map, build/test recipes, gh CLI patterns, adb-via-ssh patterns, gcloud relay ssh, common file locations, common gotchas. Pre-loading saves grep-then-discover cycles. Net: ~22 lines off the per-turn auto-load (CLAUDE.md), ~15-20 lines saved per agent launch (boilerplate consolidation), and the quickstart removes a class of repeated discovery work.
This commit is contained in:
parent
567e9ee9a7
commit
d586bbdd14
3 changed files with 244 additions and 79 deletions
|
|
@ -1,72 +1,51 @@
|
|||
# Rouse Context
|
||||
|
||||
Android app + relay server that turns a phone into a secure, on-demand MCP server.
|
||||
|
||||
## Architecture
|
||||
|
||||
Three distinct domains live in this repo:
|
||||
|
||||
- **Relay server** (`relay/`) — TLS passthrough via SNI routing, `/wake` endpoint, ACME cert orchestration, DNS-01 challenges via Cloudflare API. Never terminates TLS, never sees payloads.
|
||||
- **Tunnel client** (`core/tunnel/`) — Mux protocol, TLS client connecting back to relay, session lifecycle, CertificateStore interface. Bridges the relay protocol to on-device MCP.
|
||||
- **Android app** (`app/`, `core/mcp/`, `health/`, `api/`, `notifications/`, `work/`) — MCP SDK integration, Health Connect server, audit UI, foreground service. Pure Android/Kotlin.
|
||||
Android app + relay server that turns a phone into a secure, on-demand MCP server. For architecture, see `docs/design/overall.md` and `docs/design/android-app.md`.
|
||||
|
||||
## Build & Test
|
||||
|
||||
All Gradle commands require `JAVA_HOME=/usr/lib/jvm/java-21-openjdk`.
|
||||
`JAVA_HOME=/usr/lib/jvm/java-21-openjdk` for every Gradle command.
|
||||
|
||||
```bash
|
||||
# Build library modules
|
||||
./gradlew :core:mcp:assembleDebug :core:tunnel:assembleDebug :health:assembleDebug
|
||||
# Build a module
|
||||
./gradlew :core:mcp:assembleDebug
|
||||
|
||||
# Unit tests
|
||||
./gradlew :core:mcp:testDebugUnitTest
|
||||
|
||||
# Lint & static analysis
|
||||
# Lint & static analysis (whole tree)
|
||||
./gradlew ktlintCheck detekt
|
||||
|
||||
# Auto-fix formatting
|
||||
./gradlew ktlintFormat
|
||||
|
||||
# Relay (Rust, separate)
|
||||
cd relay && cargo test && cargo clippy --all-targets --all-features -- -D warnings
|
||||
```
|
||||
|
||||
For module-specific recipes and common commands, see `docs/agent-quickstart.md`.
|
||||
|
||||
## Workflow
|
||||
|
||||
MUST follow this sequence for any non-trivial feature:
|
||||
Non-trivial change: design → approve → test → implement → verify. See `.claude/rules/workflow.md`.
|
||||
|
||||
1. **Design** — Enter plan mode. Explore the codebase, understand existing patterns, design the approach. Present the plan for approval. Do NOT write code yet.
|
||||
2. **Approve** — Wait for explicit approval before proceeding.
|
||||
3. **Test** — Write tests that define the expected behavior. Verify they fail.
|
||||
4. **Implement** — Write the minimum code to pass the tests. Do not modify tests to match implementation.
|
||||
5. **Verify** — Run `./gradlew testDebugUnitTest ktlintCheck detekt`. Fix any failures.
|
||||
6. **Commit** — Stage specific files, write a clear commit message.
|
||||
UX-affecting changes (onboarding, permission/credential timing, error surfaces) require explicit user approval and a `docs/ux-decisions.md` entry. See `.claude/rules/ux-changes.md`.
|
||||
|
||||
Skip to step 4 for trivial changes (typos, single-line fixes, obvious bugs).
|
||||
|
||||
## Module Boundaries
|
||||
|
||||
- `:core:mcp` — MCP session orchestrator, HTTP routing, OAuth device code flow, token management. Contract layer.
|
||||
- `:core:tunnel` — Mux protocol, WebSocket client, TLS accept, CertificateStore interface. MUST NOT know about MCP.
|
||||
- `:api` — `McpIntegration` interface, `IntegrationStateStore`. Contract for integration modules.
|
||||
- `:health` — Health Connect MCP server. Depends on `:api` and `:core:mcp`.
|
||||
- `:notifications` — Notification state machine, audit persistence (Room). Depends on `:core:tunnel` and `:core:mcp`.
|
||||
- `:work` — Foreground service, FCM receiver, WorkManager, wakelock. Depends on `:core:tunnel` and `:notifications`.
|
||||
- `:app` — Wires everything together via Koin. The ONLY module that depends on all others.
|
||||
- `relay/` — Completely independent. Rust. No Android dependencies. Built with Cargo.
|
||||
When launching a sub-agent, point it at `.claude/agent-preamble.md` for the standard rules.
|
||||
|
||||
## Code Style
|
||||
|
||||
- ktlint with `android_studio` style (see `.editorconfig`)
|
||||
- detekt with coroutine rules enabled (see `config/detekt/detekt.yml`)
|
||||
- No wildcard imports
|
||||
- Structured concurrency — no `GlobalScope`, no unscoped `CoroutineScope()`, no `runBlocking` in production code
|
||||
- Structured concurrency — see `.claude/rules/coroutines.md`
|
||||
|
||||
## Signing Keystores
|
||||
|
||||
- Debug keystore: `.signing/debug.keystore`
|
||||
- Release keystore: `.signing/release.keystore`
|
||||
- Backups: `~/backups/rouse-context/`
|
||||
`.signing/{debug,release}.keystore`, backups at `~/backups/rouse-context/`.
|
||||
|
||||
NEVER regenerate these keystores. Regenerating causes signature mismatch, which forces an uninstall (losing all app data) and wastes ACME certificate quota on re-registration.
|
||||
NEVER regenerate. Regenerating forces an uninstall on every device (losing app data) and burns ACME quota. Critical.
|
||||
|
||||
## Domain: rousecontext.com
|
||||
## Domain
|
||||
|
||||
Registered at Squarespace, nameservers pointed to Cloudflare. Each device gets a unique subdomain (e.g. `abc123.rousecontext.com`). Cloudflare DNS API for automated ACME challenge TXT records.
|
||||
`rousecontext.com` registered at Squarespace, nameservers on Cloudflare. Each device gets a per-subdomain wildcard cert from GTS via DNS-01 (Cloudflare API). Per-integration secrets prefix the subdomain at the SNI layer (e.g. `<integration>-<rand>.<device>.rousecontext.com`).
|
||||
|
|
|
|||
65
.claude/agent-preamble.md
Normal file
65
.claude/agent-preamble.md
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# Agent preamble
|
||||
|
||||
Read this once when launched. Apply to every agent task in this repo.
|
||||
|
||||
## Tooling
|
||||
|
||||
- Use `Read`, `Grep`, `Glob`, `Edit`, `Write` over their `bash` equivalents (`cat`, `grep`, `find`, `sed`).
|
||||
- Spawn sub-agents with model `opus` for any code work.
|
||||
- Run agents in the background unless the user asks otherwise.
|
||||
|
||||
## What to NOT touch
|
||||
|
||||
- Anything under `.claude/` (rules, agent-preamble, project memory): triggers approval prompts and blocks autonomous work. If you genuinely need a rule change, surface it to the user first.
|
||||
- Signing keystores at `.signing/`: regenerating forces a forced uninstall on every device + burns ACME quota. Critical.
|
||||
- Build outputs (`build/`, `.gradle/`, etc).
|
||||
|
||||
## Workflow
|
||||
|
||||
For any non-trivial change: design → approve → test → implement → verify. See `.claude/rules/workflow.md`.
|
||||
|
||||
UX-affecting changes (onboarding, permission timing, error surfaces, credential timing, etc) need explicit user approval AND an entry in `docs/ux-decisions.md`. See `.claude/rules/ux-changes.md` for what counts.
|
||||
|
||||
## Build & verify
|
||||
|
||||
`JAVA_HOME=/usr/lib/jvm/java-21-openjdk` for every Gradle command. See `docs/agent-quickstart.md` for module-specific recipes.
|
||||
|
||||
Always before committing:
|
||||
- `./gradlew ktlintFormat` (formats Kotlin)
|
||||
- `./gradlew :module:testDebugUnitTest ktlintCheck detekt` for the modules you touched
|
||||
- For relay (Rust): `cargo fmt && cargo test && cargo clippy --all-targets --all-features -- -D warnings`
|
||||
|
||||
Match commit-message style — `git log --oneline -10` for recent examples.
|
||||
|
||||
## Branches and PRs
|
||||
|
||||
Branch name: `fix-NNN-short-description` for issue fixes.
|
||||
|
||||
PR lifecycle (must close out fully — no strays):
|
||||
1. Open PR with `gh pr create`. Body links the issue (`Closes #NNN`).
|
||||
2. Wait for CI green.
|
||||
3. `gh pr merge NNN --squash --delete-branch`.
|
||||
4. If local-branch deletion fails because another worktree has it checked out:
|
||||
```
|
||||
cd /home/jmonk/git/rouse-context && git checkout --detach origin/main && git branch -d <branch>
|
||||
```
|
||||
5. Comment on the issue with the PR number; remove the `in-progress` label (the PR's `Closes` line auto-closes the issue).
|
||||
|
||||
Don't leave open PRs or stale local branches behind.
|
||||
|
||||
## When something goes wrong
|
||||
|
||||
- If you can't make progress: file a NEW GitHub issue describing the blocker (don't bury it as a comment on the parent issue), then stop. Don't speculate-patch.
|
||||
- If you find a related bug while working: file as separate issue, link it, don't expand scope mid-PR unless the original task is bounded.
|
||||
|
||||
## Relay-specific
|
||||
|
||||
NEVER build the relay on the GCP VPS. CI deploys via GitHub Actions. To verify a deploy:
|
||||
```
|
||||
gcloud compute ssh relay --zone=us-central1-a --command='systemctl status rouse-relay | head -5'
|
||||
```
|
||||
|
||||
## Devices
|
||||
|
||||
- `adolin` Pixel 6 Pro (`ANDROID_SERIAL=1B151FDEE008EY`) — agent-controlled test device. Drive via `ssh adolin "ANDROID_SERIAL=... /opt/android-sdk/platform-tools/adb …"`.
|
||||
- The user's personal phone is NOT for autonomous testing. Always confirm before targeting any other device.
|
||||
|
|
@ -1,60 +1,35 @@
|
|||
# Agent Quickstart
|
||||
# Agent quickstart
|
||||
|
||||
A short reference for agents (and humans) writing code in this repo. For the
|
||||
full architecture and module layout, see `docs/workflow.md` and the
|
||||
project-root `CLAUDE.md`.
|
||||
Distilled commands and paths agents reach for repeatedly. Pre-loading saves grep-then-discover cycles.
|
||||
|
||||
## JSON construction
|
||||
## JSON construction (mandatory)
|
||||
|
||||
Tools that return structured data MUST use `@Serializable` data classes plus
|
||||
`Json.encodeToString` (or `Json.encodeToJsonElement` if a tree is preferable).
|
||||
Manual template literals — `"""{"field":"$value"}"""` — are NOT permitted in
|
||||
production code.
|
||||
Tools that return structured data MUST use `@Serializable` data classes plus `Json.encodeToString` (or `Json.encodeToJsonElement` if a tree is preferable). Manual template literals — `"""{"field":"$value"}"""` — are NOT permitted in production code.
|
||||
|
||||
### Why
|
||||
|
||||
Issue [#417](https://github.com/Monkopedia/rouse-context/issues/417) shipped
|
||||
malformed JSON to user testing because a template-literal error helper
|
||||
interpolated a raw exception message containing quotes, producing output the
|
||||
connector proxy refused to parse. The `@Serializable` + `encodeToString` path
|
||||
escapes embedded quotes/backslashes/control characters automatically; the
|
||||
manual path does not.
|
||||
`#417` shipped malformed JSON to user testing because a template-literal error helper interpolated a raw exception message containing quotes, producing output the connector proxy refused to parse. The `@Serializable` + `encodeToString` path escapes embedded quotes / backslashes / control characters automatically; the manual path does not.
|
||||
|
||||
### Enforcement
|
||||
### Two enforcement gates
|
||||
|
||||
Two gates run in CI:
|
||||
|
||||
1. **Test harness** (`McpToolTestHarness` in
|
||||
`integrations/src/test/kotlin/com/rousecontext/integrations/testing/`) —
|
||||
parse-asserts every `CallToolResult` body returned through the harness.
|
||||
Any tool whose result body fails `Json.parseToJsonElement` causes the
|
||||
test to fail with the offending tool name and body content.
|
||||
2. **Static check** (`scripts/check-no-manual-json.sh`) — runs in the
|
||||
`Android CI` workflow. Greps for `"""{` in `*/src/main/*.kt` and fails the
|
||||
build if any match isn't allowlisted.
|
||||
1. **Test harness** — `McpToolTestHarness` at `integrations/src/test/kotlin/com/rousecontext/integrations/testing/` parse-asserts every `CallToolResult` body returned through it. Any tool whose result body fails `Json.parseToJsonElement` causes the test to fail with the offending tool name and body content.
|
||||
2. **Static check** — `scripts/check-no-manual-json.sh` runs in the Android CI workflow. Greps for `"""{` in `*/src/main/*.kt` and fails the build on unallowlisted matches.
|
||||
|
||||
### The `// allow-manual-json` marker
|
||||
|
||||
For the rare case where a template literal is genuinely safe (e.g. a
|
||||
`@Preview`-only Compose fixture with hard-coded literals that never see
|
||||
runtime input), append a marker comment to the line:
|
||||
For the rare case where a template literal is genuinely safe (e.g. a `@Preview`-only Compose fixture with hard-coded literals that never see runtime input), append a marker comment to the line:
|
||||
|
||||
```kotlin
|
||||
val sample = """{"steps":7000}""" // allow-manual-json: @Preview fixture, no interpolation
|
||||
```
|
||||
|
||||
The reason text after the colon should describe **why this isn't a runtime
|
||||
risk** — typically because no user-controlled value flows into the template.
|
||||
A reviewer should treat the absence of a clear reason as a blocker.
|
||||
The reason text after the colon should describe **why this isn't a runtime risk** — typically because no user-controlled value flows into the template. A reviewer should treat the absence of a clear reason as a blocker.
|
||||
|
||||
`AuditDetailScreen.kt` is implicitly allowlisted in the script because its
|
||||
preview fixtures exist purely for IDE rendering and never reach runtime; new
|
||||
files should use the inline marker rather than expanding the file allowlist.
|
||||
`AuditDetailScreen.kt` is implicitly allowlisted in the script because its preview fixtures exist purely for IDE rendering; new files should use the inline marker rather than expanding the file allowlist.
|
||||
|
||||
## Test harness
|
||||
### Test harness usage
|
||||
|
||||
When writing or refactoring an `*McpProvider*Test`, use
|
||||
`McpToolTestHarness` rather than hand-rolling the mockk capture pattern:
|
||||
When writing or refactoring an `*McpProvider*Test`, use `McpToolTestHarness` rather than hand-rolling the mockk capture pattern:
|
||||
|
||||
```kotlin
|
||||
private val harness = McpToolTestHarness()
|
||||
|
|
@ -75,5 +50,151 @@ private val fakeConnection: ClientConnection = mockk(relaxed = true)
|
|||
}
|
||||
```
|
||||
|
||||
Pass `expectJsonBody = false` only for tools whose contract specifies a
|
||||
plain-text body (rare). Document the reason inline if you do.
|
||||
Pass `expectJsonBody = false` only for tools whose contract specifies a plain-text body (rare). Document the reason inline if you do.
|
||||
|
||||
## Module → path map
|
||||
|
||||
```
|
||||
:app app/
|
||||
:core:tunnel core/tunnel/
|
||||
:core:mcp core/mcp/
|
||||
:core:bridge core/bridge/
|
||||
:core:testfixtures core/testfixtures/
|
||||
:api api/
|
||||
:integrations integrations/
|
||||
:notifications notifications/
|
||||
:work work/
|
||||
:device-tests device-tests/
|
||||
:e2e e2e/
|
||||
relay (Rust) relay/
|
||||
```
|
||||
|
||||
Source-of-truth for the module list is `settings.gradle.kts:25-35`.
|
||||
|
||||
## Build & test recipes
|
||||
|
||||
All Gradle commands need `JAVA_HOME=/usr/lib/jvm/java-21-openjdk`.
|
||||
|
||||
```bash
|
||||
# Build a debug APK
|
||||
./gradlew :app:assembleDebug
|
||||
# → app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
# Build a release APK
|
||||
./gradlew :app:assembleRelease
|
||||
# → app/build/outputs/apk/release/app-release.apk
|
||||
|
||||
# Unit tests for one module
|
||||
./gradlew :core:mcp:jvmTest # KMP/JVM
|
||||
./gradlew :app:testDebugUnitTest # Android-only
|
||||
./gradlew :integrations:testDebugUnitTest # Android-only
|
||||
|
||||
# Tunnel integration tests (against real relay binary)
|
||||
./gradlew :core:tunnel:integrationTest --tests '*OAuthEndToEndTest*'
|
||||
|
||||
# E2E (against a real device — requires adb host + serial)
|
||||
./gradlew :e2e:e2eTest -Dadb.host=adolin -Dadb.serial=1B151FDEE008EY
|
||||
|
||||
# Lint & analysis (whole tree)
|
||||
./gradlew ktlintCheck detekt
|
||||
|
||||
# Format
|
||||
./gradlew ktlintFormat
|
||||
```
|
||||
|
||||
## Relay (Rust)
|
||||
|
||||
```bash
|
||||
cd relay
|
||||
cargo test
|
||||
cargo clippy --all-targets --all-features -- -D warnings
|
||||
cargo fmt
|
||||
```
|
||||
|
||||
NEVER build the relay on the GCP VPS. The PR-to-main triggers `.github/workflows/relay-deploy.yml` (or similar) which cross-compiles + deploys.
|
||||
|
||||
## GitHub CLI patterns
|
||||
|
||||
```bash
|
||||
# Concise CI status
|
||||
gh pr checks NNN --json name,state --jq '.[] | "\(.name)\t\(.state)"'
|
||||
|
||||
# Open PR
|
||||
gh pr create --title "..." --body "..."
|
||||
|
||||
# Merge + delete branch (squash)
|
||||
gh pr merge NNN --squash --delete-branch
|
||||
|
||||
# Issue management
|
||||
gh issue create --title "..." --body "..." --label in-progress
|
||||
gh issue edit NNN --add-label in-progress
|
||||
gh issue edit NNN --remove-label in-progress
|
||||
gh issue close NNN --comment "..."
|
||||
|
||||
# Just the body of a comment thread
|
||||
gh issue view NNN --comments --json comments --jq '.comments[].body'
|
||||
```
|
||||
|
||||
## Local-branch-deletion gotcha
|
||||
|
||||
If `gh pr merge --delete-branch` fails because another worktree has the branch checked out:
|
||||
|
||||
```bash
|
||||
cd /home/jmonk/git/rouse-context && git checkout --detach origin/main && git branch -d <branch>
|
||||
```
|
||||
|
||||
## adb via SSH (test device)
|
||||
|
||||
```bash
|
||||
# adolin Pixel 6 Pro (agent-controlled)
|
||||
ADB="ANDROID_SERIAL=1B151FDEE008EY /opt/android-sdk/platform-tools/adb"
|
||||
ssh adolin "$ADB <command>"
|
||||
|
||||
# Common operations
|
||||
ssh adolin "$ADB shell pidof com.rousecontext.debug"
|
||||
ssh adolin "$ADB shell pm clear com.rousecontext.debug"
|
||||
ssh adolin "$ADB shell am start -n com.rousecontext.debug/com.rousecontext.app.MainActivity"
|
||||
ssh adolin "$ADB shell run-as com.rousecontext.debug ls files/"
|
||||
ssh adolin "$ADB logcat -d -s 'Onboarding:*' 'TunnelClient:*' '*:S'" # filter at logcat level
|
||||
|
||||
# Kill app cleanly (NOT am force-stop — that disables FCM until manual relaunch).
|
||||
# Use cmd activity stop-app: kills the process including foreground services
|
||||
# but leaves FCM reachable. See #394.
|
||||
ssh adolin "$ADB shell cmd activity stop-app com.rousecontext.debug"
|
||||
|
||||
# Install
|
||||
scp app-debug.apk adolin:/tmp/
|
||||
ssh adolin "$ADB install -r /tmp/app-debug.apk"
|
||||
```
|
||||
|
||||
## UI automation on device
|
||||
|
||||
Drive Android UI via `uiautomator dump` XML, NOT screencap PNGs (resolution blows up context). See `/tmp/drive_onboard.sh` for the pattern (extract bounds via regex on `text="..."[^>]*bounds="[x1,y1][x2,y2]"`).
|
||||
|
||||
## Relay over gcloud
|
||||
|
||||
```bash
|
||||
gcloud compute ssh relay --zone=us-central1-a --command='sudo journalctl -u rouse-relay -f'
|
||||
gcloud compute ssh relay --zone=us-central1-a --command='systemctl status rouse-relay | head -5'
|
||||
```
|
||||
|
||||
## Common files to know
|
||||
|
||||
- `core/tunnel/src/jvmMain/.../OnboardingFlow.kt` — canonical onboarding sequence
|
||||
- `app/src/main/.../OnboardingViewModel.kt` — canonical VM (post-#392 single-VM invariant)
|
||||
- `app/src/main/.../navigation/AppNavigation.kt` + `Routes.kt` — nav graph + routes
|
||||
- `core/tunnel/src/jvmMain/.../TunnelClientImpl.kt` — tunnel client
|
||||
- `core/tunnel/src/jvmMain/.../MuxFrame.kt` / `MuxDemux.kt` — mux protocol
|
||||
- `relay/src/api/` — relay HTTP/WS endpoints
|
||||
- `relay/src/passthrough.rs` — TLS-passthrough decision + FCM wake
|
||||
- `relay/src/rate_limit.rs` — rate limiters (FcmWakeThrottle, etc.)
|
||||
- `docs/design/relay-api.md` — endpoint catalog (kept current as of #410)
|
||||
- `docs/design/android-app.md` — module map + dep graph (kept current as of #408)
|
||||
- `docs/design/overall.md` — system overview (kept current as of #409)
|
||||
- `docs/ux-decisions.md` — UX flow decisions log
|
||||
|
||||
## When in doubt
|
||||
|
||||
- Code is the source of truth over docs.
|
||||
- A `.claude/rules/*` rule is the source of truth over a memory entry.
|
||||
- The user's most recent in-session direction overrides everything else.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue