Skip to content
Open
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
5 changes: 3 additions & 2 deletions .agents/commands/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,13 @@ Print the path to the release notes file so the user can share it for review.
### 7. Build Mainnet Release

```bash
./gradlew assembleMainnetRelease
just release
```

Expected APK path: `app/build/outputs/apk/mainnet/release/bitkit-mainnet-release-{newVersionCode}-universal.apk`
Expected AAB path: `app/build/outputs/bundle/mainnetRelease/bitkit-mainnet-release-{newVersionCode}.aab`

Verify the file exists. If the build fails, stop and report the error to the user.
Verify both files exist. If the build fails, stop and report the error to the user.

### 8. Upload APK to Draft Release

Expand Down
2 changes: 1 addition & 1 deletion .cursor/rules/rules.main.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,6 @@ alwaysApply: true
---

## Rules for Android Unit tests and Instrumentation tests:
- run unit tests for specific files like this: `./gradlew :app:testDevDebugUnitTest --tests "to.bitkit.repositories.LightningRepoTest"`
- run unit tests for specific files like this: `just test file "to.bitkit.repositories.LightningRepoTest"`
- write unit tests in the same style and using same libraries as: `CurrencyRepoTest`, `LightningRepoTest`, `WalletRepoTest`
- in unit tests, use asserts from `kotlin.test` and mockito-kotlin for mocks
16 changes: 16 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copy to .env and uncomment only what you need.

# GITHUB_ACTOR=
# GITHUB_TOKEN=
# TX_TOKEN=

# KEYSTORE_FILE=
# KEYSTORE_PASSWORD=
# KEY_ALIAS=
# KEY_PASSWORD=

# E2E_BACKEND=local
# E2E_HOMEGATE_URL=http://127.0.0.1:6288

# TREZOR_BRIDGE=true
# TREZOR_BRIDGE_URL=http://10.0.2.2:21325
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ local.properties
# Secrets
google-services.json
.env
.env.*
!.env.example
*.keystore
!debug.keystore
keystore.*
Expand Down
31 changes: 17 additions & 14 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,46 @@ Durable shared agent command specs live in `.agents/commands/`. For PR creation,

```sh
# compile
./gradlew compileDevDebugKotlin
just compile

# Build, install, launch dev app on connected target
just run

# Build for dev
./gradlew assembleDevDebug
just build

# Run unit tests
./gradlew testDevDebugUnitTest
just test

# Run specific unit test file
./gradlew testDevDebugUnitTest --tests LightningRepoTest
just test file LightningRepoTest

# Run instrumented tests
./gradlew connectedDevDebugAndroidTest
just test android

# Build for E2E tests (UI hooks enabled, local Electrum by default)
E2E=true ./gradlew assembleDevRelease
just e2e

# Build for E2E tests with geoblocking disabled
GEO=false E2E=true ./gradlew assembleDevRelease
just e2e no geo

# Build for E2E tests using network Electrum (not local; staging/mainnet based on flavor)
E2E=true E2E_BACKEND=network ./gradlew assembleTnetRelease
just e2e network assembleTnetRelease

# Lint using detekt
./gradlew detekt
just lint

# Auto-format using detekt
./gradlew detekt --auto-correct
just format

# Update detekt baseline
./gradlew detektBaseline
just lint baseline

# Install dev build
./gradlew installDevDebug
just install

# Clean build artifacts
./gradlew clean
just clean
```

## Architecture Overview
Expand Down Expand Up @@ -162,7 +165,7 @@ suspend fun getData(): Result<Data> = withContext(Dispatchers.IO) {

- USE coding rules from `.cursor/default.rules.mdc`
- For multi-step changes, stacked PR surgery, and review follow-up with several small edits, batch validation instead of running the full build/check suite after every edit. Run the relevant Gradle checks once the coherent change set is ready, before updating a PR or pushing.
- Still run `./gradlew compileDevDebugKotlin`, `./gradlew testDevDebugUnitTest`, and `./gradlew detekt` before the final PR update/push for code changes, and fix failures before pushing.
- Still run `just compile`, `just test`, and `just lint` before the final PR update/push for code changes, and fix failures before pushing.
- After fixing validation failures, rerun the narrowest useful check that proves the fix. If only test files changed, prefer the targeted test task and a test-focused lint/detekt check when the project tooling supports it; otherwise use the standard detekt task before pushing.
- Use narrower checks earlier only when they answer an immediate risk, e.g. a single unit test after touching focused business logic or a Kotlin compile after a risky refactor.
- ALWAYS ask clarifying questions to ensure an optimal plan when encountering functional or technical uncertainties in requests
Expand Down
164 changes: 164 additions & 0 deletions Justfile
Comment thread
ovitrif marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
set dotenv-load
set dotenv-filename := ".env"
set windows-shell := ["sh", "-cu"]

gradle := "./gradlew"

default:
@just list

list:
@printf "%s\n" \
"list" \
"init" \
"compile" \
"run" \
"build [TASK]" \
"release" \
"install" \
"test" \
"test file PATTERN" \
"test android" \
"test lane LANE" \
"lint" \
"lint baseline" \
"format" \
"translations pull" \
"translations push source" \
"translations push all" \
"e2e [network|no geo|TASK] [TASK]" \
"changelog [all|next|hotfix]" \
"clean"

init:
#!/usr/bin/env sh
set -eu

if [ -e .env ]; then
echo ".env already exists"
exit 0
fi

cp .env.example .env
echo "Created .env"

compile:
{{ gradle }} compileDevDebugKotlin

run:
#!/usr/bin/env sh
set -eu

app_id="to.bitkit.dev"
app_dir="app/build/outputs/apk/dev/debug"

if ! command -v adb >/dev/null 2>&1; then
echo "adb is required to run the app." >&2
exit 1
fi

if [ -n "${ANDROID_SERIAL:-}" ]; then
device_id="$ANDROID_SERIAL"
else
echo "Looking for connected Android devices..."
device_id="$(
adb devices -l \
| awk 'NR > 1 && $2 == "device" && $1 !~ /^emulator-/ { print $1; exit }'
Comment thread
ovitrif marked this conversation as resolved.
)"

if [ -z "$device_id" ]; then
device_id="$(
adb devices -l \
| awk 'NR > 1 && $2 == "device" { print $1; exit }'
)"
fi
fi

if [ -z "$device_id" ]; then
echo "No connected Android device found." >&2
exit 1
fi

device_name="$(
adb -s "$device_id" shell getprop ro.product.model 2>/dev/null \
| tr -d '\r' \
|| true
)"

if [ -z "$device_name" ]; then
device_name="$device_id"
fi

echo "Using $device_name ($device_id)"
echo "Building Debug app..."
{{ gradle }} assembleDevDebug

app_path="$(
find "$app_dir" -maxdepth 1 -name '*-universal.apk' -type f \
| sort \
| tail -n 1
)"

if [ -z "$app_path" ]; then
app_path="$(
find "$app_dir" -maxdepth 1 -name '*.apk' -type f \
| sort \
| tail -n 1
)"
fi

if [ -z "$app_path" ]; then
echo "No APK found in $app_dir." >&2
exit 1
fi

echo "Installing $app_path..."
adb -s "$device_id" install -r "$app_path"

echo "Launching $app_id..."
adb -s "$device_id" shell am force-stop "$app_id"
adb -s "$device_id" shell monkey -p "$app_id" -c android.intent.category.LAUNCHER 1 >/dev/null

pid="$(
adb -s "$device_id" shell pidof -s "$app_id" 2>/dev/null \
| tr -d '\r' \
|| true
)"

if [ -z "$pid" ]; then
echo "Launched $app_id"
exit 0
fi

echo "Streaming logs for $app_id (pid $pid). Press Ctrl-C to stop."
adb -s "$device_id" logcat --pid "$pid"

build task="assembleDevDebug":
{{ gradle }} {{ task }}

release:
{{ gradle }} assembleMainnetRelease bundleMainnetRelease

install:
{{ gradle }} installDevDebug

test target="" value="":
{{ if target == "" { gradle + " testDevDebugUnitTest" } else if target == "android" { gradle + " connectedDevDebugAndroidTest" } else if target == "file" { if value == "" { error("usage: just test file PATTERN") } else { gradle + " testDevDebugUnitTest --tests '" + value + "'" } } else if target == "lane" { if value == "" { error("usage: just test lane LANE") } else { gradle + " connectedDevDebug" + value + "AndroidTest" } } else { error("usage: just test [file PATTERN|android|lane LANE]") } }}

lint target="":
{{ if target == "" { gradle + " detekt --rerun-tasks" } else if target == "baseline" { gradle + " detektBaseline --rerun-tasks" } else { error("usage: just lint [baseline]") } }}

format:
{{ gradle }} detekt --auto-correct --rerun-tasks

translations action value="":
{{ if action == "pull" { "./scripts/pull-translations.sh" } else if action == "push" { if value == "source" { "tx push --source" } else if value == "all" { "./scripts/push-translations.sh" } else { error("usage: just translations pull|push source|push all") } } else { error("usage: just translations pull|push source|push all") } }}

e2e mode="" value="" task="assembleDevRelease":
{{ if mode == "" { "E2E=true " + gradle + " " + task } else if mode == "network" { "E2E=true E2E_BACKEND=network " + gradle + " " + if value == "" { task } else { value } } else if mode == "no" { if value == "geo" { "GEO=false E2E=true " + gradle + " " + task } else { error("usage: just e2e no geo [TASK]") } } else { "E2E=true " + gradle + " " + mode } }}

changelog target="all":
./scripts/preview-changelog.sh --target {{ target }}

clean:
{{ gradle }} clean
Loading
Loading