diff --git a/apps/mobile/.kilo/MOBILE_WORKFLOW.md b/apps/mobile/.kilo/MOBILE_WORKFLOW.md index 9851ef07f5..b14089c971 100644 --- a/apps/mobile/.kilo/MOBILE_WORKFLOW.md +++ b/apps/mobile/.kilo/MOBILE_WORKFLOW.md @@ -40,6 +40,8 @@ kilo run \ While a role agent runs, the orchestrator checks on it about every 7 minutes and unsticks infrastructure failures: a wedged or crashed kilo CLI, a dead tmux window, or a hung service or simulator the agent cannot restart itself. Product, logic, or review problems are not stuck states — route those through the escalation ladder (Delegation and Escalation). When the agent's CLI process exits, react immediately: collect its result and continue the loop. The 7-minute cadence is only the ceiling for detecting a wedge, never a wait between dispatch and result. +Agent definitions allow every command and edit. The only remaining permission denial is `task`, kept because it removes the accidental dispatch path at zero cost — it is not airtight, since a shell `kilo run` can still dispatch; the workflow has exactly one dispatcher, the orchestrator, and role agents never dispatch agents by instruction. Every boundary — no dispatch, reviewers never modify the tree, the implementer never commits, pushes, or opens a PR — is enforced by instruction, not permission. Deny lists caused void review rounds (a reviewer whose blocked command made it exit with no verdict, which read as a pass) and takeover churn; the workflow trades enforcement for reliable rounds and accepts that a misbehaving agent can do what it was previously blocked from. + ### Delegation and Escalation The orchestrator is the expensive model driving cheap role agents. Its output is judgment — handoffs, steering, triage, verification — not diffs. @@ -61,6 +63,10 @@ Any step where an agent or LLM must respond — cloud-agent sessions, chat flows Hard ceilings: `mobile-plan-reviewer` 40, `mobile-implementer` 80, `mobile-reviewer` 50, `mobile-e2e-verifier` 100. Size every handoff below 75% of the role's limit; an implementation slice should fit in roughly 60 planned steps. Never raise a limit to fit an oversized task — split the task. +### Workflow Learnings + +[`WORKFLOW_LEARNINGS.md`](WORKFLOW_LEARNINGS.md) is a durable log of environment blockers — broken local stacks, credential and env-var traps, simulator quirks, tool wedges — and their fixes. Product bugs never go in it. Immediately after resolving such a blocker, record it in the section matching your role: symptom, cause, fix, a few lines each, reusable by a future run that hits the same wall. Read the file before writing; when an existing entry covers the blocker, update that entry instead of appending a duplicate. Only the planner and the orchestrator write the log; the orchestrator records blockers role agents hit. Kilo's edit tool rejects `.kilo/` paths — kilo sessions write entries through shell commands instead. Learnings written during a run are part of the run's deliverable: the orchestrator commits and pushes them with the run's PR so future runs can use them once merged. + ## Interaction Modes The planner's first message asks the user exactly one question: is this run `hands on` or `hands off`? The mode governs the planner, the orchestrator, and every later decision. @@ -93,7 +99,9 @@ Rules: ## Planning -1. Explore requirements in the selected mode. Inspect the affected repositories. Define acceptance criteria, the feature-state matrix, and non-goals. +Plan the simplest viable shape of every item — feature-wise as much as code-wise. When the request as stated is needlessly complex and a simpler shape delivers the same user value, challenge it: hands-on, raise it with the user before planning the complex shape; hands-off, decide with best judgment, plan the simpler shape, and record the decision and reasoning in the plan, the handoff, and the PR description. + +1. Read [`WORKFLOW_LEARNINGS.md`](WORKFLOW_LEARNINGS.md); re-read it before any environment-dependent phase, such as the bug reproduction gate. Explore requirements in the selected mode. Inspect the affected repositories. Define acceptance criteria, the feature-state matrix, and non-goals. 2. Create the dedicated worktrees. 3. For defect work, run the bug reproduction gate before writing the plan. 4. Write the complete draft plan. @@ -128,6 +136,7 @@ The handoff must contain: - The absolute path of the accepted plan and any approved design - The dedicated worktree path for every repository in scope, with each worktree's current branch, commit, and working-tree state - Acceptance criteria, feature-state matrix, execution ledger, non-goals, and unresolved risks +- Any simpler-shape decision and its reasoning (see Planning) - For defect work: the reported defect, the confirmed reproduction steps and failure classification, and the repro run's resource manifest - Existing changes and resources that must be preserved - The completion gate, with a direct instruction to continue until the PR is mergeable and conflict-free with all expected CI checks green on the latest head @@ -159,9 +168,9 @@ Everything the orchestrator does runs in the shared tmux session; inspect its wi | Agent | Responsibility | Repository edits | |---|---|---| -| `mobile-plan-reviewer` | Reviews a complete draft plan for ambiguity, unsupported claims, and missing execution detail | Denied | +| `mobile-plan-reviewer` | Reviews a complete draft plan for ambiguity, unsupported claims, and missing execution detail | None | | `mobile-implementer` | Implements one bounded task from the accepted plan and runs narrow checks | Where the task requires | -| `mobile-reviewer` | Independently reviews the full relevant diff and tests | Denied | +| `mobile-reviewer` | Independently reviews the full relevant diff and tests | None | | `mobile-e2e-verifier` | Exercises accepted behavior; in repro mode, reproduces a reported defect on the unmodified baseline | Temporary only | ## Local Tooling @@ -173,6 +182,8 @@ Start and inspect the local stack, simulator, login, and E2E flows per [e2e/AGEN Env sync between the app bundle, Metro, and this worktree is validated by `apps/mobile/e2e/preflight.sh` (run by `login.sh`). Trust its failure output instead of re-checking URLs by hand. +When several workflows run on one machine, device-bound work is capped by the slot semaphore [`e2e-slot.sh`](e2e-slot.sh) (default 3 slots, machine-global). Run `e2e-slot.sh acquire ` before any phase that drives a simulator, emulator, local backend stack, or native build — the repro gate, prewarm, and every E2E round — and `e2e-slot.sh release ` the moment that phase ends. Planning, implementation, review, checks, and CI waits are uncapped; never hold a slot through them. Slots are owned by tmux session name and reclaimed automatically when the session dies, so a crashed run cannot wedge the queue. `e2e-slot.sh status` shows holders. + ## Execution Ledger Split the accepted plan into the smallest behaviorally meaningful, independently testable slices. Before dispatching anything, record each slice: @@ -188,13 +199,13 @@ Two slices are parallel-safe only when all of these hold: their write sets do no ## Orchestration -1. Ingest the handoff. Split the work into ledger slices. +1. Ingest the handoff and read [`WORKFLOW_LEARNINGS.md`](WORKFLOW_LEARNINGS.md); re-read it before prewarm and before each E2E round. Split the work into ledger slices. 2. Dispatch ready independent slices in a wave of at most two or three concurrent `mobile-implementer`s. Each handoff lists the other active slices and their ownership boundaries. While a wave is active, run only ownership-safe narrow checks. 3. When the whole wave has returned, synchronize: inspect each result, ownership adherence, and the combined diff; resolve integration and architecture decisions yourself; run shared mutating commands and shared checks once. If one slice failed, preserve the successful ones. 4. Dispatch one fresh `mobile-reviewer` over the wave diff. Triage findings yourself: route valid findings through a bounded repair wave; record rejected findings with a short rationale. Loop repair wave → fresh reviewer, steering each round per the escalation ladder, until a fresh reviewer reports no valid actionable findings. Running this loop is the orchestrator's primary job, not a preamble to doing the work itself. 5. After checks and review pass, create the commits at the ledger's per-slice boundaries. If a slice exhausts its implementer budget, split it or re-dispatch it with a sharper handoff; take over only per the escalation ladder. 6. When device E2E is likely, prewarm concurrently with implementation: stable services, a claimed and labeled device, a baseline native build (only when unaffected by active slices), the exact Metro URL, and login state. Record a resource manifest for the final verifier. Do not judge acceptance behavior while implementation is still changing. -7. After review passes, perform the final full-diff review yourself, push the reviewed head, create the PR, and assign it to the requesting human — before starting E2E, so CI and Kilobot run concurrently with the E2E run. Do not read or triage any review comment yet (step 9). +7. After review passes, perform the final full-diff review yourself, push the reviewed head, create the PR — its description records any simpler-shape decision from the handoff — and assign it to the requesting human — before starting E2E, so CI and Kilobot run concurrently with the E2E run. Do not read or triage any review comment yet (step 9). 8. Dispatch a fresh final `mobile-e2e-verifier` with the resource manifest. Loop triage → repair wave → fresh reviewer → fresh verifier, steering each round per the escalation ladder, until a fresh verifier passes every applicable feature state. You may reproduce a failure once to triage it; the repair itself — with your diagnosis and acceptance criteria attached — goes through the implementer-reviewer loop. Never sit in an edit-run-verify loop yourself. Review the full diff of any E2E-driven repair, commit it at the right boundary, and push. ### Reviewer and CI Loop diff --git a/apps/mobile/.kilo/WORKFLOW_LEARNINGS.md b/apps/mobile/.kilo/WORKFLOW_LEARNINGS.md new file mode 100644 index 0000000000..9f1ec49fd2 --- /dev/null +++ b/apps/mobile/.kilo/WORKFLOW_LEARNINGS.md @@ -0,0 +1,7 @@ +# Workflow Learnings + +Environment blockers and their fixes, recorded by the planner or orchestrator for the role that hit them. The full contract — when to read, when to write, entry shape, deduplication — is in [MOBILE_WORKFLOW.md](MOBILE_WORKFLOW.md). + +## Planner + +## Orchestrator diff --git a/apps/mobile/.kilo/agent/mobile-e2e-verifier.md b/apps/mobile/.kilo/agent/mobile-e2e-verifier.md index acf8799874..ec3cb15c89 100644 --- a/apps/mobile/.kilo/agent/mobile-e2e-verifier.md +++ b/apps/mobile/.kilo/agent/mobile-e2e-verifier.md @@ -8,16 +8,8 @@ permission: edit: allow external_directory: allow task: deny - background_process: deny bash: "*": allow - "socat": deny - "socat *": deny - "git *commit*": deny - "git *push*": deny - "/usr/bin/git *commit*": deny - "/usr/bin/git *push*": deny - "gh pr*": deny maestro_*: allow --- @@ -40,7 +32,8 @@ During verification: - Retryable and empty states: a meaningful message plus a CTA that performs the expected recovery or next step. Non-retryable states: a meaningful message with no CTA at all. - Inspect backend, session-ingest, CLI, or other service logs when a flow crosses those boundaries. - Capture concise evidence: screenshots, exact visible state, and bounded log excerpts. Never credentials. -- Never create proxies, redirects, tunnels, NAT rules, or listeners to compensate for stale Expo state — with any tool, not just the denied `socat`. An unmanaged listener invalidates a `prewarm` handoff. +- Never create proxies, redirects, tunnels, NAT rules, or listeners to compensate for stale Expo state — with any tool, `socat` included. An unmanaged listener invalidates a `prewarm` handoff. +- Never dispatch agents, and never commit, push, or create or update a PR. Permissions restrict nothing except agent dispatch (`task`); this boundary is the instruction — the orchestrator owns all Git and PR actions. - Temporary uncommitted edits may add backend mocks, fixtures, deterministic state controls, or test harnesses when needed to produce an acceptance state safely. Use the smallest localized change and record every touched file. - Exception: LLM and agent responses are never mocked. Drive a real model call on `kilo-auto/efficient` — never `kilo-auto/free`, which is rate-limited; if an `efficient` call stalls, retry on `efficient`. Use the fake-llm server or any other LLM mock only when a real call cannot produce the required state (for example, a specific provider failure), and report each use with the mock named and justified. - Temporary edits must not change the behavior under test, bypass provenance or security checks, or fix or conceal a product failure. If producing a state would change the behavior being judged, report that state as blocked. diff --git a/apps/mobile/.kilo/agent/mobile-implementer.md b/apps/mobile/.kilo/agent/mobile-implementer.md index 93b5662c89..c4e3ff18a1 100644 --- a/apps/mobile/.kilo/agent/mobile-implementer.md +++ b/apps/mobile/.kilo/agent/mobile-implementer.md @@ -10,9 +10,6 @@ permission: task: deny bash: "*": allow - "git commit*": deny - "git push*": deny - "gh pr*": deny --- You implement one bounded task from an approved mobile-app plan. The task may require changes anywhere in the cloud monorepo or in a sibling repository such as `~/Projects/kilocode`: "mobile" is the product, not a directory boundary. @@ -35,7 +32,7 @@ While implementing: - Defer checks that need another active slice's unstable output to the orchestrator's synchronization barrier. - Run narrow format, type, lint, and test checks for the files you changed. - Work in small, independently reviewable slices. Finish and report one slice before starting the next. -- Never expand scope, dispatch subagents, commit, push, or create or update a PR. +- Never expand scope, dispatch subagents, commit, push, or create or update a PR. Permissions restrict nothing except agent dispatch (`task`); git and `gh` boundaries are this instruction — the orchestrator owns every commit, push, and PR. - Never claim the overall mobile task is complete. Review, E2E, and final verification belong to the orchestrator. Return: diff --git a/apps/mobile/.kilo/agent/mobile-plan-reviewer.md b/apps/mobile/.kilo/agent/mobile-plan-reviewer.md index bbc4007e34..cf806691a7 100644 --- a/apps/mobile/.kilo/agent/mobile-plan-reviewer.md +++ b/apps/mobile/.kilo/agent/mobile-plan-reviewer.md @@ -5,16 +5,14 @@ model: kilo/x-ai/grok-4.5 variant: high steps: 40 permission: - edit: deny + edit: allow external_directory: allow task: deny - background_process: deny bash: - "*": deny - "true": allow + "*": allow --- -You are an independent, read-only reviewer for a drafted mobile implementation plan. Read the plan and the relevant repository files. Do not run shell commands, edit files, decide product requirements, or fix findings yourself. +You are an independent, read-only reviewer for a drafted mobile implementation plan. Read the plan and the relevant repository files, and run read-only commands when they help you verify a claim. Permissions restrict nothing except agent dispatch (`task`); the read-only boundary is this instruction. Never dispatch agents, never modify any file or repository state, never commit, push, or create or update a PR, and never decide product requirements or fix findings yourself. Your 40-step limit is a hard ceiling. The handoff gives you the plan path, requirements, planning mode, repositories and worktrees in scope, priority order, minimum complete review, and a stopping rule. @@ -23,7 +21,7 @@ Report: - Unclear requirements, unsupported assumptions or claims, and missing or conflicting acceptance criteria - Missing feature states, non-goals, dependencies, ownership boundaries, or cross-repository contracts - Infeasible or ambiguous sequencing, unsafe parallel work, and underspecified verification or E2E coverage -- Steps that are not the simplest maintainable implementation, or that add unneeded scope or abstraction +- Steps that are not the simplest maintainable implementation, feature shapes needlessly more complex than what delivers the same user value, or unneeded scope or abstraction - Handoffs missing information an implementer, reviewer, verifier, or orchestrator needs to act without guessing Check repository files for claims that materially affect feasibility or correctness. Do not invent requirements beyond the request. A recorded, evidence-backed decision is not a defect just because uncertainty remains. diff --git a/apps/mobile/.kilo/agent/mobile-reviewer.md b/apps/mobile/.kilo/agent/mobile-reviewer.md index c1499474f7..228a3aa4eb 100644 --- a/apps/mobile/.kilo/agent/mobile-reviewer.md +++ b/apps/mobile/.kilo/agent/mobile-reviewer.md @@ -5,28 +5,14 @@ model: kilo/x-ai/grok-4.5 variant: high steps: 50 permission: - edit: deny + edit: allow external_directory: allow task: deny bash: - "*": deny - "git status*": allow - "git diff*": allow - "git log*": allow - "git show*": allow - "git merge-base*": allow - "git rev-parse*": allow - "git ls-files*": allow - "git branch --show-current*": allow - "pnpm typecheck*": allow - "pnpm lint*": allow - "pnpm test*": allow - "pnpm check:unused*": allow - "pnpm format:check*": allow - "pnpm exec oxfmt --list-different*": allow + "*": allow --- -You are an independent, read-only reviewer of an implementation for an approved mobile-app plan. Review every relevant change, including cloud backend, shared-package, and sibling-repository changes. Do not edit files or fix findings yourself. +You are an independent, read-only reviewer of an implementation for an approved mobile-app plan. Review every relevant change, including cloud backend, shared-package, and sibling-repository changes. Run any read-only commands you need, including in sibling repositories (for example `git -C diff`). Permissions restrict nothing except agent dispatch (`task`); the read-only boundary is this instruction. Never modify any file or repository state, never commit, push, or create or update a PR, and never fix findings yourself. Your 50-step limit is a hard ceiling. The handoff gives you the priority order, minimum complete outcome, optional work to drop, and a stopping rule. Review one coherent wave diff, not partial output from active implementers. @@ -44,7 +30,7 @@ For every new user-facing feature, also check its four states — happy, retryab - An explicit trigger or classification, message intent, CTA outcome or absence, and automated/E2E coverage for every state - Any `not applicable` state has an orchestrator-accepted rationale showing it is structurally impossible, not merely hard to test -Inspect the actual diff and surrounding code. Run narrow read-only checks when useful. Do not dispatch subagents, commit, push, or create or update a PR. Do not invent requirements beyond the accepted plan. +Inspect the actual diff and surrounding code. Run narrow read-only checks when useful. Do not dispatch subagents. Do not invent requirements beyond the accepted plan. Output findings first, ordered by severity. Each finding contains: diff --git a/apps/mobile/.kilo/e2e-slot.sh b/apps/mobile/.kilo/e2e-slot.sh new file mode 100755 index 0000000000..55eb8a4ab5 --- /dev/null +++ b/apps/mobile/.kilo/e2e-slot.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# Device/stack slot semaphore for parallel mobile workflows. +# +# Only work that needs a simulator, a local backend stack, or a native build is +# capped. Planning, implementation, review, and CI waits are unlimited. +# +# e2e-slot.sh acquire # blocks until a slot is free, then holds it +# e2e-slot.sh release # release as soon as the device phase ends +# e2e-slot.sh status # who holds what, and for how long +# +# A slot is owned by a tmux session name. If that tmux session no longer exists +# the slot is stale and is reclaimed automatically — no heartbeats to maintain. +# +# State is machine-global on purpose: every worktree's copy of this script must +# contend for the same slots, so the state dir never lives next to the script. +set -uo pipefail + +DIR="${E2E_SLOT_DIR:-$HOME/.cache/kilo-e2e-slots}" +TOTAL=${E2E_SLOTS:-3} +POLL=${E2E_POLL:-60} +mkdir -p "$DIR" + +reap() { + # If tmux cannot answer (missing binary, no server, socket error), liveness + # cannot be judged — keep every slot rather than wipe live ones. Acquirers + # always run inside tmux, so the server is up whenever reaping matters. + alive=$(tmux list-sessions -F '#{session_name}' 2>/dev/null) || return 0 + for s in "$DIR"/slot-*; do + [ -d "$s" ] || continue + owner=$(cat "$s/owner" 2>/dev/null || echo) + [ -n "$owner" ] || { rm -rf "$s"; continue; } + printf '%s\n' "$alive" | grep -qxF -- "$owner" || rm -rf "$s" + done +} + +case "${1:?usage: acquire|release|status }" in + acquire) + who=${2:?tmux session name required} + # already holding one? idempotent. + for s in "$DIR"/slot-*; do + [ -d "$s" ] && [ "$(cat "$s/owner" 2>/dev/null)" = "$who" ] && { echo "already holding $(basename "$s")"; exit 0; } + done + while :; do + reap + for i in $(seq 1 "$TOTAL"); do + if mkdir "$DIR/slot-$i" 2>/dev/null; then + printf '%s' "$who" > "$DIR/slot-$i/owner" + date -u +%Y-%m-%dT%H:%M:%SZ > "$DIR/slot-$i/since" + echo "acquired slot-$i" + exit 0 + fi + done + echo "all $TOTAL device slots busy; retrying in ${POLL}s: $(ls -1 "$DIR" 2>/dev/null | tr '\n' ' ')" >&2 + sleep "$POLL" + done + ;; + release) + who=${2:?tmux session name required} + for s in "$DIR"/slot-*; do + [ -d "$s" ] && [ "$(cat "$s/owner" 2>/dev/null)" = "$who" ] && rm -rf "$s" && echo "released $(basename "$s")" + done + exit 0 + ;; + status) + reap + n=0 + for s in "$DIR"/slot-*; do + [ -d "$s" ] || continue + n=$((n + 1)) + echo "$(basename "$s"): $(cat "$s/owner" 2>/dev/null) since $(cat "$s/since" 2>/dev/null)" + done + echo "$n/$TOTAL held" + ;; + *) echo "usage: $0 acquire|release|status " >&2; exit 1 ;; +esac diff --git a/apps/mobile/e2e/AGENTS.md b/apps/mobile/e2e/AGENTS.md index 9f7db57102..a2bb20c8ae 100644 --- a/apps/mobile/e2e/AGENTS.md +++ b/apps/mobile/e2e/AGENTS.md @@ -21,6 +21,8 @@ xcrun simctl list devices booted If a complete stack is already running for this worktree, reuse it. Never start a competing stack or stop an unrelated `kilo-dev-*` session. +When other workflows may be running on this machine, acquire a device slot before starting a stack, simulator, or native build, and release it when the device phase ends: `apps/mobile/.kilo/e2e-slot.sh acquire|release ` (see the Local Tooling section of [.kilo/MOBILE_WORKFLOW.md](../.kilo/MOBILE_WORKFLOW.md)). + If this worktree has no stack, start the complete mobile flow: ```bash @@ -190,22 +192,72 @@ Do not conclude Android is unavailable from `command -v adb` or the inherited `P pnpm dev:mobile:android doctor ``` -Use the wrappers for all Android tooling, including the Expo/Gradle build, so the resolved SDK/JDK environment is applied: +Use the wrappers for all Android tooling, including the Expo/Gradle build, so the resolved SDK/JDK environment is applied. Ordered glue: acquire e2e slot → launch emulator → bounded boot wait (serial discovered at visibility) → `claim` → `build` → `login.sh`. Never unbounded `adb wait-for-device`. Never put manual `adb reverse` or dev-client `am start` on the primary path — `login.sh` preflight does both. + +### Launch and GPU policy + +Two launch attempts total, then a test-environment blocker with the tail of `$EMULATOR_LOG`. Attempt 1 uses `-gpu host` (Mac GPU; fastest; software rendering competes for the CPU under parallel-workflow load). Keep `-no-snapshot-save -no-boot-anim` on every launch. Attempt 2 switches GPU only on an observed process-death signal (`pgrep -f "qemu.*"` empty, or the log shows the emulator exiting/erroring — the pane mirrors it live but dies with the session) → `-gpu swiftshader_indirect`. If the process is still alive but the boot envelope expired → repeat `-gpu host`. Never a third launch. ```bash +# After e2e-slot acquire (see Fresh Worktree Quickstart) ANDROID_SESSION="kilo-e2e-android-$(basename "$PWD")" +EMULATOR_LOG="/tmp/${ANDROID_SESSION}.log" +GPU_FLAG=host # attempt 2: swiftshader_indirect only after process death; else host again tmux new-session -d -s "$ANDROID_SESSION" -c "$PWD" \ - "pnpm dev:mobile:android emulator -avd -no-snapshot-save -no-boot-anim -gpu swiftshader_indirect" -pnpm dev:mobile:android adb wait-for-device + "pnpm dev:mobile:android emulator -avd -no-snapshot-save -no-boot-anim -gpu $GPU_FLAG 2>&1 | tee \"$EMULATOR_LOG\"" +``` + +### Bounded boot wait + +From the moment of launch, poll about every 15 s until the envelope expires. Cold boot on an idle host ≈ 1–3 minutes; under parallel-workflow load allow up to 8 minutes before declaring the attempt failed (relaunch-rule bounds, not SLAs). Each poll, check in order: + +1. **Liveness** — `pgrep -f "qemu.*"` still prints a PID. If empty, the attempt failed with the process-death signal. +2. **Visibility** — device appears with state `device` in `pnpm dev:mobile:android adb devices -l` (this is where the serial is discovered; a single local emulator is `emulator-5554`). +3. **Readiness** — once visible, `pnpm dev:mobile:android adb -s shell getprop sys.boot_completed` prints `1`. + +Device visibility is not readiness. Never gate on `adb devices` output alone, and never wait on any one stage without the liveness check. + +### Failed attempt → kill hard → relaunch once + +On failure: `tmux kill-session -t "$ANDROID_SESSION" 2>/dev/null` (session may already be gone if the emulator exited), then confirm the emulator process is actually gone — `pgrep -f "qemu.*"` prints nothing. If it survives: with a known serial, `pnpm dev:mobile:android adb -s emu kill`; with no serial yet (process died or never became visible to adb), `pkill -f "qemu.*"`. Re-check `pgrep` either way — a surviving emulator holds the AVD lock and dooms any relaunch. Then relaunch once per the GPU policy with the same envelope. If the second attempt also fails to boot, stop and return a test-environment blocker with the tail of `$EMULATOR_LOG` (survives session death; never a third launch, never an early give-up). + +### Claim, build, login + +```bash pnpm dev:mobile:android claim -pnpm dev:mobile:android adb reverse tcp: tcp: -pnpm dev:mobile:android adb reverse tcp: tcp: -pnpm dev:mobile:android build +pnpm dev:mobile:android build # validated cached APK only +apps/mobile/e2e/login.sh +``` + +`build` installs a validated cached APK when the Android native fingerprint and toolchain match. Never install an APK from another output path or invoke Gradle directly. Reinstall via `build ` only when the native fingerprint changed, never to reset app state. + +`login.sh` and `logout.sh` accept an iOS simulator UDID or an Android ADB serial. On Android, `login.sh`'s shared preflight verifies the claim, applies both `adb reverse` mappings (the `nextjs` service's API port and the `mobile` service's Metro port, both from `pnpm dev:status --json` — there is no service named `metro`), and opens the dev-client deep link itself. On the primary path no manual reverse or `am start` is needed. + +### Mid-test recovery + +`pnpm dev:mobile:android adb -s shell pm clear com.kilocode.kiloapp` resets app state and forgets the saved Metro URL. Afterwards, either rerun `apps/mobile/e2e/login.sh ` (restores claim check, both reverses, and the deep link), or manually restore both reverses and re-open the dev client: + +```bash +# Ports from pnpm dev:status --json: nextjs = API, mobile = Metro (not a service named metro) +pnpm dev:mobile:android adb -s reverse tcp: tcp: +pnpm dev:mobile:android adb -s reverse tcp: tcp: +pnpm dev:mobile:android adb -s shell am start -a android.intent.action.VIEW \ + -d "exp+kilo-app://expo-development-client/?url=" +``` + +Use the exact Metro URL from the `mobile` pane. Android's `localhost` is the emulator itself — reverses are required for host reachability. + +### App Links + +```bash +pnpm dev:mobile:android adb -s shell am start -a android.intent.action.VIEW -d "https:///" ``` -The build command installs a validated cached APK when the Android native fingerprint and toolchain match. Never install an APK from another output path or invoke Gradle directly. +Drives a real App Link through Android intent resolution. -ADB fallback commands (Maestro stays the primary driver): +### ADB fallback (Maestro stays primary) + +Derive tap coordinates from the current `uiautomator` bounds, never from screenshots or remembered positions. Re-dump after every navigation or prompt. ```bash pnpm dev:mobile:android adb devices -l @@ -217,13 +269,6 @@ pnpm dev:mobile:android adb -s shell input text '' pnpm dev:mobile:android adb -s shell input keyevent KEYCODE_BACK ``` -Android specifics: - -- Derive tap coordinates from the current `uiautomator` bounds, never from screenshots or remembered positions. Re-dump after every navigation or prompt. -- Android's `localhost` is the emulator itself. Restore both `adb reverse` mappings after clearing app data. -- The dev-client scheme is `exp+kilo-app`. `adb shell pm clear com.kilocode.kiloapp` also forgets the Metro URL; re-open the dev-client URL afterward with `adb shell am start`. -- `login.sh` and `logout.sh` accept an iOS simulator UDID or an Android ADB serial; their shared preflight applies platform-specific ownership checks and reconnects the dev client to this worktree. - ## Cleanup Clean up only resources you started. The remote CLI session and its disposable install belong to the orchestrator; never kill `kilo-e2e-cli-*` sessions or remove CLI scratch directories you did not create. @@ -231,10 +276,12 @@ Clean up only resources you started. The remote CLI session and its disposable i ```bash tmux kill-session -t "$ANDROID_SESSION" # if created rm -f "$LOGIN_LOG" # if created +rm -f "$EMULATOR_LOG" # if created pnpm dev:stop # only if you started this worktree's stack xcrun simctl shutdown # only if you booted it pnpm dev:mobile:simulator release # every simulator you claimed pnpm dev:mobile:android release # every Android device you claimed +apps/mobile/.kilo/e2e-slot.sh release # if you acquired a device slot ``` Also stop recorders, log followers, and emulator processes you created. Never use `tmux kill-server`, kill an unrelated `kilo-dev-*` session, shut down a simulator that was already booted, or use `pnpm dev:stop --force` while sibling worktrees are active.