Skip to content
Merged
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
64 changes: 64 additions & 0 deletions .claude/skills/build-lookup/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
name: build-lookup
description: >
Look up the git commit and GitHub Actions run for a Flipcash versionCode.
Usage: /build-lookup <versionCode>
user-invocable: true
argument-hint: "<versionCode>"
allowed-tools:
- Bash
---

# Build Lookup: versionCode to commit + CI run

Resolve a Flipcash versionCode to its git commit and the associated GitHub
Actions build run.

## Background

The Flipcash versionCode is the output of `git rev-list --count HEAD` at build
time (see `apps/flipcash/app/build.gradle.kts:20-25`). This means
**versionCode N = the Nth commit from the root of the repository**.

When someone says "commit 3797" or "build 3797" they mean versionCode 3797.

## Usage

Parse `$ARGUMENTS` for a versionCode (integer). If not provided, ask the user.

Run the helper script:

```bash
bash .claude/skills/build-lookup/scripts/build-lookup.sh <versionCode>
```

The script emits JSON:

```json
{
"version_code": 3797,
"commit": {
"sha": "637722b9c...",
"short": "637722b9c",
"message": "the commit message",
"date": "2026-06-03T..."
},
"total_commits": 3810,
"github_actions": {
"run_id": 26846060877,
"url": "https://github.com/code-payments/code-android-app/actions/runs/26846060877",
"status": "completed",
"conclusion": "success"
}
}
```

`github_actions` is `null` if no matching workflow run was found.

## Output

Present the results clearly:

- **versionCode**: N
- **Commit**: `<short_sha>` — `<message>` (date)
- **GitHub Actions**: [Run #ID](url) — status/conclusion
104 changes: 104 additions & 0 deletions .claude/skills/build-lookup/scripts/build-lookup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env bash
# build-lookup.sh — Resolve a versionCode to its git commit and GitHub Action run.
#
# The Flipcash versionCode is derived from `git rev-list --count HEAD` (see
# apps/flipcash/app/build.gradle.kts:20-25). versionCode N = the Nth commit
# from the root of the repo.
#
# Usage:
# ./build-lookup.sh <versionCode>
# ./build-lookup.sh 3797
#
# Output: JSON with commit_sha, commit_short, commit_message, and (if found)
# the GitHub Actions run ID and URL for the Flipcash2 workflow.

set -euo pipefail

if [[ $# -lt 1 ]]; then
echo "Usage: build-lookup.sh <versionCode>" >&2
exit 1
fi

VERSION_CODE="$1"
WORKFLOW_ID="229420296" # "Flipcash2 Build and Deploy"

# ── Resolve versionCode to commit ───────────────────────────────────
# versionCode = git rev-list --count HEAD at the time of build.
# The Nth commit (1-indexed from root) is at position N in `git rev-list --reverse HEAD`.
# Equivalently: git rev-list HEAD | sed -n '<offset>p' where offset = total - N + 1.

TOTAL=$(git rev-list --count HEAD)

if (( VERSION_CODE > TOTAL )); then
echo "ERROR: versionCode $VERSION_CODE exceeds current commit count ($TOTAL)" >&2
exit 1
fi

if (( VERSION_CODE < 1 )); then
echo "ERROR: versionCode must be >= 1" >&2
exit 1
fi

OFFSET=$(( TOTAL - VERSION_CODE + 1 ))
COMMIT_SHA=$(git rev-list HEAD | sed -n "${OFFSET}p")
COMMIT_SHORT="${COMMIT_SHA:0:9}"
COMMIT_MESSAGE=$(git log -1 --format='%s' "$COMMIT_SHA")
COMMIT_DATE=$(git log -1 --format='%aI' "$COMMIT_SHA")

# ── Find GitHub Actions run for this commit ─────────────────────────
RUN_JSON=""
RUN_ID=""
RUN_URL=""
RUN_STATUS=""
RUN_CONCLUSION=""

if command -v gh &>/dev/null; then
# gh run list --commit requires a full SHA match, so fetch recent runs and
# filter by headSha prefix instead. We pull the last 100 runs to cover ~2
# weeks of builds.
ALL_RUNS=$(gh run list \
--workflow="$WORKFLOW_ID" \
--json databaseId,url,status,conclusion,headSha \
--limit 100 2>/dev/null || true)

if [[ -n "$ALL_RUNS" ]]; then
MATCH=$(echo "$ALL_RUNS" | jq --arg sha "$COMMIT_SHA" '[.[] | select(.headSha == $sha)] | .[0] // empty')
if [[ -n "$MATCH" ]]; then
RUN_ID=$(echo "$MATCH" | jq -r '.databaseId')
RUN_URL=$(echo "$MATCH" | jq -r '.url')
RUN_STATUS=$(echo "$MATCH" | jq -r '.status')
RUN_CONCLUSION=$(echo "$MATCH" | jq -r '.conclusion')
fi
fi
fi

# ── Emit result ─────────────────────────────────────────────────────
jq -n \
--arg version_code "$VERSION_CODE" \
--arg commit_sha "$COMMIT_SHA" \
--arg commit_short "$COMMIT_SHORT" \
--arg commit_message "$COMMIT_MESSAGE" \
--arg commit_date "$COMMIT_DATE" \
--arg total_commits "$TOTAL" \
--arg run_id "${RUN_ID:-}" \
--arg run_url "${RUN_URL:-}" \
--arg run_status "${RUN_STATUS:-}" \
--arg run_conclusion "${RUN_CONCLUSION:-}" \
'{
version_code: ($version_code | tonumber),
commit: {
sha: $commit_sha,
short: $commit_short,
message: $commit_message,
date: $commit_date
},
total_commits: ($total_commits | tonumber),
github_actions: (
if $run_id != "" then {
run_id: ($run_id | tonumber),
url: $run_url,
status: $run_status,
conclusion: $run_conclusion
} else null end
)
}'
97 changes: 97 additions & 0 deletions .claude/skills/r8-mapping/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
name: r8-mapping
description: >
Download and use the R8 mapping file for a Flipcash release build to
deobfuscate stack traces. Usage: /r8-mapping <versionCode>
user-invocable: true
argument-hint: "<versionCode>"
allowed-tools:
- Bash
- Read
- Grep
---

# R8 Mapping: download and deobfuscate

Download the R8 `mapping.txt` for a Flipcash release build and use it to
deobfuscate stack traces.

## Background

Release builds are minified/obfuscated by R8. The mapping file is uploaded as
part of the `release-artifacts` artifact in the "Flipcash2 Build and Deploy"
GitHub Actions workflow (ID `229420296`). The mapping lives at
`mapping/release/mapping.txt` inside the artifact.

**R8 class merging**: R8 may merge multiple unrelated classes into one
obfuscated class. Use line numbers from the stack trace to disambiguate which
original class a frame belongs to.

## Step 1 — Download the mapping

Parse `$ARGUMENTS` for a versionCode (integer). If not provided, ask the user.

```bash
bash .claude/skills/r8-mapping/scripts/r8-mapping.sh <versionCode>
```

The script emits JSON:

```json
{
"mapping_path": "/tmp/r8-mapping-3797/mapping/release/mapping.txt",
"run_id": 26846060877,
"version_code": 3797,
"line_count": 1211075
}
```

## Step 2 — Deobfuscate classes

To find what an obfuscated class name maps to:

```bash
grep " -> <obfuscated_class>:" <mapping_path>
```

This returns lines like:
```
com.original.ClassName -> ag3:
com.other.MergedClass -> ag3:
```

Multiple results means R8 merged those classes. Use stack trace line numbers
to disambiguate — each class section in the mapping contains line-number
ranges for its methods.

## Step 3 — Deobfuscate methods

After finding the class section, look for method mappings within it:

```bash
# Find the class section and its method mappings
grep -A 200 "^com.original.ClassName -> <obfuscated>:" <mapping_path> | head -200
```

Method lines look like:
```
1:5:void run():123:127 -> run
6:10:void otherMethod():45:49 -> a
```

The format is:
```
<obfuscated_line_start>:<obfuscated_line_end>:<return_type> <original_method>(<params>):<original_line_start>:<original_line_end> -> <obfuscated_method>
```

Match the line number from the stack trace against `obfuscated_line_start:obfuscated_line_end`
to find the original method and its original line numbers.

## Output

Present deobfuscated stack frames clearly:

```
Original: com.original.ClassName.methodName (ClassName.kt:127)
Obfuscated: ag3.run (SourceFile:3)
```
94 changes: 94 additions & 0 deletions .claude/skills/r8-mapping/scripts/r8-mapping.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env bash
# r8-mapping.sh — Download the R8 mapping file for a Flipcash release build.
#
# Downloads the `release-artifacts` artifact from the GitHub Actions run
# associated with a given versionCode, then extracts the R8 mapping.txt.
#
# Usage:
# ./r8-mapping.sh <versionCode>
# ./r8-mapping.sh --run-id <github_actions_run_id>
#
# Output: JSON with the path to the downloaded mapping file and metadata.
# The mapping file is saved to /tmp/r8-mapping-<versionCode>/mapping/release/mapping.txt

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
BUILD_LOOKUP="$REPO_ROOT/.claude/skills/build-lookup/scripts/build-lookup.sh"
WORKFLOW_ID="229420296"

# ── Argument parsing ────────────────────────────────────────────────
RUN_ID=""
VERSION_CODE=""

while [[ $# -gt 0 ]]; do
case "$1" in
--run-id) RUN_ID="$2"; shift 2 ;;
*) VERSION_CODE="$1"; shift ;;
esac
done

# ── Resolve run ID if only versionCode given ────────────────────────
if [[ -z "$RUN_ID" && -n "$VERSION_CODE" ]]; then
LOOKUP_JSON=$(bash "$BUILD_LOOKUP" "$VERSION_CODE")

RUN_ID=$(echo "$LOOKUP_JSON" | jq -r '.github_actions.run_id // empty')
COMMIT_SHA=$(echo "$LOOKUP_JSON" | jq -r '.commit.sha')

if [[ -z "$RUN_ID" ]]; then
echo "ERROR: No GitHub Actions run found for versionCode $VERSION_CODE (commit $COMMIT_SHA)" >&2
echo "Try: gh run list --workflow=$WORKFLOW_ID --commit=${COMMIT_SHA:0:9} --limit=10" >&2
exit 1
fi
elif [[ -z "$RUN_ID" ]]; then
echo "Usage: r8-mapping.sh <versionCode>" >&2
echo " r8-mapping.sh --run-id <run_id>" >&2
exit 1
fi

# ── Download artifact ───────────────────────────────────────────────
DEST_DIR="/tmp/r8-mapping-${VERSION_CODE:-$RUN_ID}"
MAPPING_PATH="$DEST_DIR/mapping/release/mapping.txt"

if [[ -f "$MAPPING_PATH" ]]; then
# Already downloaded, skip
LINE_COUNT=$(wc -l < "$MAPPING_PATH" | tr -d ' ')
else
rm -rf "$DEST_DIR"
mkdir -p "$DEST_DIR"

echo "Downloading release-artifacts from run $RUN_ID..." >&2
gh run download "$RUN_ID" \
-n release-artifacts \
-D "$DEST_DIR" 2>&1 >&2

if [[ ! -f "$MAPPING_PATH" ]]; then
# Try alternate path — artifact may have different structure
ALT=$(find "$DEST_DIR" -name "mapping.txt" -type f 2>/dev/null | head -1)
if [[ -n "$ALT" ]]; then
MAPPING_PATH="$ALT"
else
echo "ERROR: mapping.txt not found in downloaded artifacts" >&2
echo "Contents of $DEST_DIR:" >&2
find "$DEST_DIR" -type f >&2
exit 1
fi
fi

LINE_COUNT=$(wc -l < "$MAPPING_PATH" | tr -d ' ')
echo "Downloaded mapping.txt ($LINE_COUNT lines)" >&2
fi

# ── Emit result ─────────────────────────────────────────────────────
jq -n \
--arg mapping_path "$MAPPING_PATH" \
--arg run_id "$RUN_ID" \
--arg version_code "${VERSION_CODE:-unknown}" \
--arg line_count "$LINE_COUNT" \
'{
mapping_path: $mapping_path,
run_id: ($run_id | tonumber),
version_code: (if $version_code != "unknown" then ($version_code | tonumber) else null end),
line_count: ($line_count | tonumber)
}'
Loading
Loading