After cloning, install the project git hooks:
bash scripts/install-hooks.shThis installs a pre-commit hook that reminds you to tag a release when:
versionNameis bumped inapp/build.gradle.kts— prints the exact tag command to run after merging- 10+ commits have accumulated since the last release tag — suggests tagging
The hook is advisory only (never blocks a commit).
Always work on a branch off main. Name branches by type:
feat/short-description # new feature
fix/short-description # bug fix
refactor/short-description # refactor, no behaviour change
review/short-description # design or code review changes
chore/short-description # build, config, tooling
git checkout main
git pull origin main
git checkout -b feat/my-featureThe project uses a multi-module structure. Follow this rule when deciding where code lives:
| Layer | Module | Can import |
|---|---|---|
| Domain models & interfaces | :core:domain |
:core:common only |
| Room DB, DAOs, entities | :core:database |
:core:domain |
| Repository impls, mappers | :core:data |
:core:domain + :core:database |
| Theme, formatters, shared UI | :core:ui |
— |
| Feature screens & ViewModels | :feature:* |
:core:domain + :core:ui |
| DI wiring, NavGraph | :app |
all modules |
Hard rule: Feature modules must never import :core:data or :core:database. If a feature needs data, it goes through a :core:domain interface. Only :app wires the implementation via DataModule.
Follow Conventional Commits:
<type>: <short description>
<optional body>
| Type | When to use |
|---|---|
feat |
New feature |
fix |
Bug fix |
refactor |
Code change with no behaviour change |
chore |
Build, config, tooling, version bump |
docs |
Documentation only |
test |
Adding or fixing tests |
perf |
Performance improvement |
ci |
CI/CD workflow changes |
Examples:
feat: add recurring expense support
fix: show currency code instead of hardcoded dollar sign
chore: bump versionName to 1.2.0
ci: decode keystore with printf to avoid CRLF issues
-
Push your branch and open a PR against
main:git push -u origin feat/my-feature gh pr create --title "feat: ..." --body "..."
-
The CI Build workflow runs automatically on every PR — it must pass before merging.
-
Merge using squash merge to keep
mainhistory clean:gh pr merge <number> --squash --delete-branch
-
Pull
mainlocally after merge:git checkout main && git pull origin main
Unit tests live in :core:domain — pure Kotlin, no emulator required.
# Run domain use-case tests only (~3 s)
.\gradlew :core:domain:test
# Run all unit tests across all modules
.\gradlew testTest stack: JUnit 4 + MockK + kotlinx-coroutines-test (declared in gradle/libs.versions.toml).
Convention: every new use case class in :core:domain must ship with a matching test file in core/domain/src/test/. Every bug fix in the domain layer adds a regression test. CI blocks the PR if any test fails.
Adding tests for Android ViewModels and Compose screens (Robolectric or instrumented) is tracked as tech debt item #8 in backlog/improvements.md.
Trigger: push to main or any pull request targeting main.
Skipped for: commits that only change .md files or docs/.
Steps:
- Checkout code
- Set up JDK 17 (Temurin)
- Restore Gradle cache
- Decode keystore from
KEYSTORE_BASE64secret - Write
keystore.propertiesfrom secrets ./gradlew test— unit tests (fails build if any test fails; results uploaded as artifact)./gradlew assembleDebug- Upload
Kaasu-debug.apkas a build artifact
A green CI run is required before merging any PR.
Trigger: pushing a v* tag to main.
git tag v1.2.0
git push origin v1.2.0Steps:
- Same setup as CI (JDK, Gradle, keystore)
./gradlew assembleRelease— builds a signed release APK- Creates a GitHub Release at the tag with:
Kaasu-release.apkattached- Auto-generated release notes from merged PRs
Before tagging a release:
- Bump
versionNamein app/build.gradle.kts - Ensure all intended PRs are merged to
main - Confirm CI is green on
main
Release and debug builds are both signed with the shared keystore. Credentials are never stored in the repo — they live in:
- Locally:
keystore.properties(git-ignored) — create this file manually, see format below - CI/CD: four GitHub Actions secrets (
KEYSTORE_BASE64,KEYSTORE_STORE_PASSWORD,KEYSTORE_KEY_ALIAS,KEYSTORE_KEY_PASSWORD)
keystore.properties format:
storeFile=kaasu.keystore
storePassword=<password>
keyAlias=kaasu
keyPassword=<password>The keystore file (
app/kaasu.keystore) is git-ignored. To build locally on a new machine, obtain the keystore file andkeystore.propertiesfrom a team member.