Skip to content

Commit e0c7a1d

Browse files
authored
Merge branch 'master' into fix/cjit-channel-false-match
2 parents 9172263 + 1f40617 commit e0c7a1d

107 files changed

Lines changed: 5323 additions & 574 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/commands/pr.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ When the user provides custom instructions after `--`:
136136
#### Automated Checks
137137
```
138138
- Keep local verification commands, Gradle tasks, detekt, lint, unit tests, build passes, cargo test, cargo clippy, npm test, typecheck, CI coverage, or similar automated checks out of `#### Manual Tests`; summarize them under `#### Automated Checks` when they add useful context.
139-
- Use `#### Automated Checks` to summarize automated verification evidence, prioritizing coverage added, modified, or removed with file paths and a short explanation.
139+
- Use `#### Automated Checks` to summarize automated verification evidence, prioritizing coverage added, modified, or removed, each with the test file name and a short explanation.
140+
- Reference test files by bare file name only (e.g. `HwWalletRepoTest.kt`), never the full path. Only when two referenced test files share the same name, prefix the shortest leading path segment(s) that disambiguate them (e.g. `repositories/FooTest.kt` vs `viewmodels/FooTest.kt`).
140141
- For removed automated coverage, state why it was removed.
141142
- Do not list standard CI or PR bot commands as checkbox items just because they run for every PR. If standard CI coverage is worth mentioning, summarize it in one sentence.
142143
- List raw commands only when they were run locally, are non-standard, use special flags or environment values, validate workflow behavior, or explain a meaningful verification gap.
@@ -184,9 +185,9 @@ Concrete style target:
184185
- [ ] **5b.** back: returns to Connections List.
185186
- [ ] **6.** `regression:` Channel Detail → tap Close Connection: works.
186187
#### Automated Checks
187-
- Unit tests added: cover invoice timeout handling in `app/src/test/.../SendInvoiceTest.kt`.
188-
- Unit tests modified: update channel navigation assertions in `app/src/test/.../ChannelDetailTest.kt`.
189-
- Test coverage removed: delete stale mock-only assertions from `app/src/test/.../OldFlowTest.kt` because the flow no longer exists.
188+
- Unit tests added: cover invoice timeout handling in `SendInvoiceTest.kt`.
189+
- Unit tests modified: update channel navigation assertions in `ChannelDetailTest.kt`.
190+
- Test coverage removed: delete stale mock-only assertions from `OldFlowTest.kt` because the flow no longer exists.
190191
- CI: standard compile, unit test, and detekt checks run by the PR bot.
191192
```
192193

.agents/commands/release.md

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
description: "Create a new release: bump version, create PR, build mainnet, tag, draft release"
2+
description: "Create a new release: bump version, create PR, run release workflow, tag, draft release"
33
allowed_tools: Bash, Read, Edit, Write, Glob, Grep, AskUserQuestion, mcp__github__create_pull_request, mcp__github__list_pull_requests, mcp__github__pull_request_read, mcp__github__get_file_contents, mcp__github__update_pull_request
44
---
55

@@ -77,6 +77,8 @@ Cherry-pick the commits you need onto this branch now, then continue.
7777
```
7878
Wait for the user to confirm they are done cherry-picking before proceeding.
7979

80+
If the base is a tag that predates the release workflow changes, port the current release workflow support onto the release branch before proceeding. At minimum, the release branch/tag must contain the updated artifact naming in `.github/workflows/release.yml`, otherwise Step 7 will dispatch an old workflow and then look for an artifact name it cannot produce.
81+
8082
Finalize changelog after the release branch contains all release commits:
8183

8284
```bash
@@ -202,22 +204,64 @@ gh release edit v{newVersionName} --notes-file /tmp/release-notes.md
202204

203205
Print the path to the release notes file so the user can share it for review.
204206

205-
### 7. Build Mainnet Release
207+
### 7. Run Store Release Workflow
206208

207209
```bash
208-
just release
210+
release_ref="v{newVersionName}"
211+
release_artifact_dir=".ai/release-artifacts-{newVersionName}"
212+
if ! git show "$release_ref:.github/workflows/release.yml" | grep -q 'bitkit-release-$build_number-$GITHUB_RUN_NUMBER'; then
213+
echo "Release ref $release_ref does not contain the current release artifact naming." >&2
214+
echo "Port the release workflow changes onto the release branch, retag, then rerun /release." >&2
215+
exit 1
216+
fi
217+
dispatch_started_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
218+
gh workflow run release.yml --ref "$release_ref"
219+
run_id=""
220+
for attempt in {1..30}; do
221+
run_id="$(gh run list \
222+
--workflow release.yml \
223+
--branch "$release_ref" \
224+
--event workflow_dispatch \
225+
--created ">=$dispatch_started_at" \
226+
--limit 1 \
227+
--json databaseId \
228+
--jq '.[0].databaseId // empty')"
229+
if [ -n "$run_id" ]; then
230+
break
231+
fi
232+
sleep 5
233+
done
234+
if [ -z "$run_id" ]; then
235+
echo "Failed to find release workflow run for $release_ref" >&2
236+
exit 1
237+
fi
238+
gh run watch "$run_id" --exit-status
239+
workflow_run_url="$(gh run view "$run_id" --json url --jq .url)"
240+
run_number="$(gh run view "$run_id" --json number --jq .number)"
241+
rm -rf "$release_artifact_dir"
242+
mkdir -p "$release_artifact_dir"
243+
gh run download "$run_id" \
244+
--name "bitkit-release-{newVersionCode}-${run_number}" \
245+
--dir "$release_artifact_dir"
246+
if command -v sha256sum >/dev/null; then
247+
(cd "$release_artifact_dir" && sha256sum -c SHA256SUMS.txt)
248+
else
249+
(cd "$release_artifact_dir" && shasum -a 256 -c SHA256SUMS.txt)
250+
fi
209251
```
210252

211-
Expected APK path: `app/build/outputs/apk/mainnet/release/bitkit-mainnet-release-{newVersionCode}-universal.apk`
212-
Expected AAB path: `app/build/outputs/bundle/mainnetRelease/bitkit-mainnet-release-{newVersionCode}.aab`
253+
Expected APK path: `.ai/release-artifacts-{newVersionName}/bitkit-mainnet-release-{newVersionCode}-universal.apk`
254+
Expected AAB path: `.ai/release-artifacts-{newVersionName}/bitkit-mainnet-release-{newVersionCode}.aab`
255+
256+
Verify both files exist. If the workflow fails or the artifact checksum verification fails, stop and report the error to the user.
213257

214-
Verify both files exist. If the build fails, stop and report the error to the user.
258+
Store `workflow_run_url` for the summary.
215259

216-
### 8. Upload APK to Draft Release
260+
### 8. Upload Workflow APK to Draft Release
217261

218262
```bash
219263
gh release upload v{newVersionName} \
220-
app/build/outputs/apk/mainnet/release/bitkit-mainnet-release-{newVersionCode}-universal.apk
264+
.ai/release-artifacts-{newVersionName}/bitkit-mainnet-release-{newVersionCode}-universal.apk
221265
```
222266

223267
### 9. Return to Master
@@ -235,14 +279,16 @@ Version bump PR: {PR URL}
235279
Release branch: release-{newVersionName}
236280
Tag: v{newVersionName}
237281
Draft release: {release URL}
282+
Release workflow: {workflow run URL}
283+
Artifacts: .ai/release-artifacts-{newVersionName}
238284
APK uploaded: bitkit-mainnet-release-{newVersionCode}-universal.apk
239285
Store release notes: .ai/release-notes-{newVersionName}.md
240286
241287
Next steps:
242288
- Share release notes with Jacobo for review
243-
- QA the APK
244-
- If patching the release branch: increment only versionCode, re-tag, rebuild, and re-upload
245-
- Submit to Play Store when QA passes
289+
- QA the workflow-built APK
290+
- Submit the workflow-built AAB to Play Store when QA passes
291+
- If patching the release branch: increment only versionCode, re-tag, rerun the release workflow, and re-upload
246292
- Publish the draft release on GitHub after store release
247293
- Merge release branch PR into master
248294
```

.github/workflows/release-internal.yml

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ concurrency:
1010
env:
1111
TERM: xterm-256color
1212
FORCE_COLOR: 1
13+
NDK_VERSION: 28.1.13356709
1314

1415
jobs:
1516
build-internal:
16-
if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')
1717
runs-on: ubuntu-latest
1818
timeout-minutes: 45
1919
environment: release-internal
@@ -35,6 +35,16 @@ jobs:
3535
- name: Setup Gradle
3636
uses: gradle/actions/setup-gradle@v5
3737

38+
- name: Setup Android NDK
39+
run: |
40+
set -euo pipefail
41+
android_sdk_root="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
42+
test -n "$android_sdk_root"
43+
sdkmanager_path="$android_sdk_root/cmdline-tools/latest/bin/sdkmanager"
44+
test -x "$sdkmanager_path"
45+
yes | "$sdkmanager_path" --licenses >/dev/null || true
46+
"$sdkmanager_path" --install "ndk;$NDK_VERSION"
47+
3848
- name: Decode mainnet release google-services.json
3949
env:
4050
MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64 }}
@@ -65,6 +75,33 @@ jobs:
6575
KEY_PASSWORD: ${{ secrets.INTERNAL_KEY_PASSWORD }}
6676
run: ./gradlew assembleMainnetRelease --no-daemon --stacktrace
6777

78+
- name: Verify native libraries are stripped
79+
run: |
80+
set -euo pipefail
81+
android_sdk_root="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
82+
test -n "$android_sdk_root"
83+
llvm_readelf_candidates=("$android_sdk_root/ndk/$NDK_VERSION"/toolchains/llvm/prebuilt/*/bin/llvm-readelf)
84+
llvm_readelf_path="${llvm_readelf_candidates[0]}"
85+
test -x "$llvm_readelf_path"
86+
87+
native_count=0
88+
extract_root="$(mktemp -d)"
89+
trap 'rm -rf "$extract_root"' EXIT
90+
while IFS= read -r -d '' artifact_path; do
91+
extract_dir="$extract_root/$(basename "$artifact_path")"
92+
mkdir -p "$extract_dir"
93+
unzip -q "$artifact_path" 'lib/*/*.so' -d "$extract_dir" 2>/dev/null || true
94+
while IFS= read -r -d '' native_path; do
95+
native_count=$((native_count + 1))
96+
if "$llvm_readelf_path" -S "$native_path" | grep -q '\.debug_'; then
97+
native_entry="${native_path#"$extract_dir"/}"
98+
echo "Native library contains debug sections: $artifact_path:$native_entry"
99+
exit 1
100+
fi
101+
done < <(find "$extract_dir" -name '*.so' -print0)
102+
done < <(find app/build/outputs/apk/mainnet/release -type f -name '*.apk' -print0)
103+
test "$native_count" -gt 0
104+
68105
- name: Verify internal release signature
69106
run: |
70107
set -euo pipefail
@@ -86,14 +123,25 @@ jobs:
86123
set -euo pipefail
87124
artifact_dir="$RUNNER_TEMP/internal-release"
88125
mkdir -p "$artifact_dir"
89-
find app/build/outputs/apk/mainnet/release -name 'bitkit-mainnet-release-*.apk' -print0 |
90-
xargs -0 -I {} cp {} "$artifact_dir/"
91-
(cd "$artifact_dir" && sha256sum *.apk > SHA256SUMS.txt)
126+
mapfile -d '' apk_paths < <(find app/build/outputs/apk/mainnet/release -name 'bitkit-mainnet-release-*.apk' -print0)
127+
test "${#apk_paths[@]}" -gt 0
128+
129+
for apk_path in "${apk_paths[@]}"; do
130+
cp "$apk_path" "$artifact_dir/"
131+
done
132+
133+
apk_file="$(basename "${apk_paths[0]}")"
134+
build_number="${apk_file#bitkit-mainnet-release-}"
135+
build_number="${build_number%%-*}"
136+
[[ "$build_number" =~ ^[0-9]+$ ]]
137+
138+
(cd "$artifact_dir" && sha256sum -- *.apk > SHA256SUMS.txt)
139+
echo "artifact_name=bitkit-release-internal-$build_number-$GITHUB_RUN_NUMBER" >> "$GITHUB_OUTPUT"
92140
echo "artifact_dir=$artifact_dir" >> "$GITHUB_OUTPUT"
93141
94142
- name: Upload internal artifacts
95143
uses: actions/upload-artifact@v6
96144
with:
97-
name: bitkit-internal-release-${{ github.run_number }}
145+
name: ${{ steps.artifacts.outputs.artifact_name }}
98146
path: ${{ steps.artifacts.outputs.artifact_dir }}
99147
retention-days: 30

.github/workflows/release.yml

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ concurrency:
1010
env:
1111
TERM: xterm-256color
1212
FORCE_COLOR: 1
13+
NDK_VERSION: 28.1.13356709
1314

1415
jobs:
1516
build-release:
16-
if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')
17+
if: startsWith(github.ref, 'refs/tags/v')
1718
runs-on: ubuntu-latest
1819
timeout-minutes: 45
1920
environment: release
@@ -35,6 +36,16 @@ jobs:
3536
- name: Setup Gradle
3637
uses: gradle/actions/setup-gradle@v5
3738

39+
- name: Setup Android NDK
40+
run: |
41+
set -euo pipefail
42+
android_sdk_root="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
43+
test -n "$android_sdk_root"
44+
sdkmanager_path="$android_sdk_root/cmdline-tools/latest/bin/sdkmanager"
45+
test -x "$sdkmanager_path"
46+
yes | "$sdkmanager_path" --licenses >/dev/null || true
47+
"$sdkmanager_path" --install "ndk;$NDK_VERSION"
48+
3849
- name: Decode mainnet release google-services.json
3950
env:
4051
MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64 }}
@@ -65,6 +76,33 @@ jobs:
6576
KEY_PASSWORD: ${{ secrets.BITKIT_KEY_PASSWORD }}
6677
run: ./gradlew assembleMainnetRelease bundleMainnetRelease --no-daemon --stacktrace
6778

79+
- name: Verify native libraries are stripped
80+
run: |
81+
set -euo pipefail
82+
android_sdk_root="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
83+
test -n "$android_sdk_root"
84+
llvm_readelf_candidates=("$android_sdk_root/ndk/$NDK_VERSION"/toolchains/llvm/prebuilt/*/bin/llvm-readelf)
85+
llvm_readelf_path="${llvm_readelf_candidates[0]}"
86+
test -x "$llvm_readelf_path"
87+
88+
native_count=0
89+
extract_root="$(mktemp -d)"
90+
trap 'rm -rf "$extract_root"' EXIT
91+
while IFS= read -r -d '' artifact_path; do
92+
extract_dir="$extract_root/$(basename "$artifact_path")"
93+
mkdir -p "$extract_dir"
94+
unzip -q "$artifact_path" 'lib/*/*.so' 'base/lib/*/*.so' -d "$extract_dir" 2>/dev/null || true
95+
while IFS= read -r -d '' native_path; do
96+
native_count=$((native_count + 1))
97+
if "$llvm_readelf_path" -S "$native_path" | grep -q '\.debug_'; then
98+
native_entry="${native_path#"$extract_dir"/}"
99+
echo "Native library contains debug sections: $artifact_path:$native_entry"
100+
exit 1
101+
fi
102+
done < <(find "$extract_dir" -name '*.so' -print0)
103+
done < <(find app/build/outputs/apk/mainnet/release app/build/outputs/bundle/mainnetRelease -type f \( -name '*.apk' -o -name '*.aab' \) -print0)
104+
test "$native_count" -gt 0
105+
68106
- name: Verify release signatures
69107
run: |
70108
set -euo pipefail
@@ -108,16 +146,30 @@ jobs:
108146
set -euo pipefail
109147
artifact_dir="$RUNNER_TEMP/release"
110148
mkdir -p "$artifact_dir"
111-
find app/build/outputs/bundle/mainnetRelease -name 'bitkit-mainnet-release-*.aab' -print0 |
112-
xargs -0 -I {} cp {} "$artifact_dir/"
113-
find app/build/outputs/apk/mainnet/release -name 'bitkit-mainnet-release-*.apk' -print0 |
114-
xargs -0 -I {} cp {} "$artifact_dir/"
115-
(cd "$artifact_dir" && sha256sum *.aab *.apk > SHA256SUMS.txt)
149+
mapfile -d '' bundle_paths < <(find app/build/outputs/bundle/mainnetRelease -name 'bitkit-mainnet-release-*.aab' -print0)
150+
mapfile -d '' apk_paths < <(find app/build/outputs/apk/mainnet/release -name 'bitkit-mainnet-release-*.apk' -print0)
151+
test "${#bundle_paths[@]}" -gt 0
152+
test "${#apk_paths[@]}" -gt 0
153+
154+
for bundle_path in "${bundle_paths[@]}"; do
155+
cp "$bundle_path" "$artifact_dir/"
156+
done
157+
for apk_path in "${apk_paths[@]}"; do
158+
cp "$apk_path" "$artifact_dir/"
159+
done
160+
161+
apk_file="$(basename "${apk_paths[0]}")"
162+
build_number="${apk_file#bitkit-mainnet-release-}"
163+
build_number="${build_number%%-*}"
164+
[[ "$build_number" =~ ^[0-9]+$ ]]
165+
166+
(cd "$artifact_dir" && sha256sum -- *.aab *.apk > SHA256SUMS.txt)
167+
echo "artifact_name=bitkit-release-$build_number-$GITHUB_RUN_NUMBER" >> "$GITHUB_OUTPUT"
116168
echo "artifact_dir=$artifact_dir" >> "$GITHUB_OUTPUT"
117169
118170
- name: Upload release artifacts
119171
uses: actions/upload-artifact@v6
120172
with:
121-
name: bitkit-release-${{ github.run_number }}
173+
name: ${{ steps.artifacts.outputs.artifact_name }}
122174
path: ${{ steps.artifacts.outputs.artifact_dir }}
123175
retention-days: 30

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ suspend fun getData(): Result<Data> = withContext(Dispatchers.IO) {
230230
- ALWAYS add new localizable string resources in alphabetical order in `strings.xml`
231231
- NEVER add string resources for strings used only in dev settings screens and previews and never localize acronyms
232232
- ALWAYS use template in `.github/pull_request_template.md` for PR descriptions
233+
- ALWAYS reference test files in PR descriptions/QA Notes by bare file name only (e.g. `HwWalletRepoTest.kt`), NEVER the full path; only when two referenced test files share the same name, prefix the shortest leading path segment(s) that disambiguate them (e.g. `repositories/FooTest.kt` vs `viewmodels/FooTest.kt`)
233234
- ALWAYS wrap `ULong` numbers with `USat` in arithmetic operations, to guard against overflows
234235
- PREFER to use one-liners with `run {}` when applicable, e.g. `override fun someCall(value: String) = run { this.value = value }`
235236
- ALWAYS add imports instead of inline fully-qualified names

Justfile

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ set dotenv-filename := ".env"
33
set windows-shell := ["sh", "-cu"]
44

55
gradle := "./gradlew"
6+
ndk_ver := "28.1.13356709"
67

78
default:
89
@just list
@@ -45,16 +46,29 @@ init:
4546
compile:
4647
{{ gradle }} compileDevDebugKotlin
4748

48-
run mode="":
49+
run mode="" logs="":
4950
#!/usr/bin/env sh
5051
set -eu
5152
5253
app_id="to.bitkit.dev"
5354
app_dir="app/build/outputs/apk/dev/debug"
5455
mode="{{ mode }}"
56+
logs="{{ logs }}"
57+
attach_logs=false
5558
59+
if [ "$mode" = "logs" ]; then
60+
attach_logs=true
61+
mode=""
62+
fi
63+
if [ -n "$logs" ]; then
64+
if [ "$logs" != "logs" ]; then
65+
echo "usage: just run [docker] [logs]" >&2
66+
exit 1
67+
fi
68+
attach_logs=true
69+
fi
5670
if [ -n "$mode" ] && [ "$mode" != "docker" ]; then
57-
echo "usage: just run [docker]" >&2
71+
echo "usage: just run [docker] [logs]" >&2
5872
exit 1
5973
fi
6074
@@ -136,6 +150,11 @@ run mode="":
136150
adb -s "$device_id" shell am force-stop "$app_id"
137151
adb -s "$device_id" shell monkey -p "$app_id" -c android.intent.category.LAUNCHER 1 >/dev/null
138152
153+
if [ "$attach_logs" != "true" ]; then
154+
echo "Launched $app_id"
155+
exit 0
156+
fi
157+
139158
pid="$(
140159
adb -s "$device_id" shell pidof -s "$app_id" 2>/dev/null \
141160
| tr -d '\r' \
@@ -154,7 +173,7 @@ build task="assembleDevDebug":
154173
{{ gradle }} {{ task }}
155174

156175
release:
157-
{{ gradle }} assembleMainnetRelease bundleMainnetRelease
176+
NDK_VERSION={{ ndk_ver }} {{ gradle }} assembleMainnetRelease bundleMainnetRelease
158177

159178
install:
160179
{{ gradle }} installDevDebug

0 commit comments

Comments
 (0)