Merge pull request #1 from masa2146/feat/clone-app-re-subagent

Feat/clone app re subagent
This commit is contained in:
masa2146 2026-06-21 17:37:30 +03:00 committed by GitHub
commit adf93bacd1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 977 additions and 27 deletions

View File

@ -0,0 +1,543 @@
# Clone App — RE-via-Subagent + Payload Extraction Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Rewrite clone-app Phase 2 to run the `android-reverse-engineering` skill inside an isolated subagent (falling back to direct scripts), extract Tier-2 payloads for auth/payment/core flows, and persist a durable digest contract to `$WORK/`.
**Architecture:** Phase 2 becomes probe → dispatch → consume. A dispatched subagent runs RE in its own context and writes three files (`re-digest.md`, `payloads.json`, `re-summary.txt`) to `$WORK/`; the orchestrator ingests only the ≤40-line summary. A new reference doc `re-digest-contract.md` is the single source of truth for the schema. Both the skill branch and the script-fallback branch write the same files, so Phases 37 stay branch-agnostic. The work product is Markdown (SKILL.md prose + reference doc) plus structural bash tests — there is no compiled runtime, so "tests" assert document structure and wiring, matching the repo's existing offline test philosophy.
**Tech Stack:** Bash (`#!/usr/bin/env bash`, invoked via `bash <path>`), Python 3 stdlib (`json` for validity checks), Markdown skill/reference docs.
## Global Constraints
- **Upstream untouched:** No edits under `plugins/android-reverse-engineering/`. After every commit, `git status --porcelain plugins/android-reverse-engineering/` MUST print nothing.
- **All new/changed files live under `plugins/clone-app/`** (plus the design docs already committed).
- **Bash scripts use `#!/usr/bin/env bash`** and are run with `bash <path>` (shell is zsh; `sh` breaks them). Tests use `set -uo pipefail` (NOT `-e`) and aggregate failures into a `fail` var so every assertion runs.
- **Python is stdlib-only**`json`, no pip, no venv.
- **Effort is measured in "AI Sprints"** (one focused Claude session), never calendar time — preserve this wording in any SKILL.md edits.
- **Conventional Commits scoped to the plugin:** `feat(clone-app): …`, `test(clone-app): …`, `docs(clone-app): …`.
- **Two pause points in SKILL.md stay:** Phase 4 (choose stack) and Phase 7 (proceed to plan?). Do not add new mandatory pauses in Phase 2.
- **Working dir convention:** `WORK="./work/$PKG"` relative to the user's cwd; decompile output lands at `$WORK/output/` via `decompile.sh -o`.
---
### Task 1: Digest contract reference doc + schema fixtures + contract test
Establishes the single source of truth for what the subagent must produce, plus offline fixtures and a structural test that locks the schema. This task is independently reviewable: a reviewer can accept the contract/fixtures/test without yet seeing the SKILL.md rewrite.
**Files:**
- Create: `plugins/clone-app/skills/clone-app/references/re-digest-contract.md`
- Create: `plugins/clone-app/tests/fixtures/re-digest.sample.md`
- Create: `plugins/clone-app/tests/fixtures/payloads.sample.json`
- Test: `plugins/clone-app/tests/test-re-digest-contract.sh`
**Interfaces:**
- Produces (consumed by Task 2's SKILL.md prose and by the subagent at runtime):
- `re-digest.md` required section headings (exact strings): `## Framework & Stack`, `## Hosts`, `## Endpoint Inventory`, `## Key Flow Payloads`, `## BuildConfig Secrets`, `## Feature Signals`, `## RE Method`.
- `payloads.json` required top-level keys: `package`, `re_method`, `endpoints`, `buildconfig`. Each `endpoints[]` object has keys: `host`, `method`, `path`, `auth`, `source`, `request_body`, `response`, `headers`.
- `re-summary.txt`: plain text, ≤40 lines, fields — framework, host count, endpoint count, key-flow names, secrets-found count, RE method, blockers.
- `RE Method` allowed values: `re-skill`, `direct-scripts`, `limited: <framework>`.
- [ ] **Step 1: Write the failing test**
Create `plugins/clone-app/tests/test-re-digest-contract.sh`:
```bash
#!/usr/bin/env bash
set -uo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
P="$HERE/.." # plugins/clone-app
CONTRACT="$P/skills/clone-app/references/re-digest-contract.md"
DIGEST_FIX="$HERE/fixtures/re-digest.sample.md"
PAYLOAD_FIX="$HERE/fixtures/payloads.sample.json"
fail=0
has() { grep -qF "$2" "$1" && echo "PASS: $3" || { echo "FAIL: $3 — '$2' not in ${1##*/}"; fail=1; }; }
# Contract doc exists and documents every required re-digest.md section heading
for sec in "## Framework & Stack" "## Hosts" "## Endpoint Inventory" \
"## Key Flow Payloads" "## BuildConfig Secrets" "## Feature Signals" "## RE Method"; do
has "$CONTRACT" "$sec" "contract documents section $sec"
done
# Contract doc documents every required payloads.json key
for key in '"package"' '"re_method"' '"endpoints"' '"buildconfig"' \
'"request_body"' '"response"' '"headers"'; do
has "$CONTRACT" "$key" "contract documents json key $key"
done
# Contract names the three output files and the three RE Method values
for tok in "re-digest.md" "payloads.json" "re-summary.txt" \
"re-skill" "direct-scripts" "limited:"; do
has "$CONTRACT" "$tok" "contract names token $tok"
done
# Digest fixture has every required section heading
for sec in "## Framework & Stack" "## Hosts" "## Endpoint Inventory" \
"## Key Flow Payloads" "## BuildConfig Secrets" "## Feature Signals" "## RE Method"; do
has "$DIGEST_FIX" "$sec" "digest fixture has section $sec"
done
# Payload fixture is valid JSON and has the required shape
python3 -c "
import json,sys
d=json.load(open('$PAYLOAD_FIX'))
for k in ('package','re_method','endpoints','buildconfig'):
assert k in d, 'missing top key '+k
assert isinstance(d['endpoints'],list) and d['endpoints'], 'endpoints must be non-empty list'
for e in d['endpoints']:
for k in ('host','method','path','auth','source','request_body','response','headers'):
assert k in e, 'endpoint missing key '+k
print('PASS: payload fixture shape valid')
" || { echo "FAIL: payload fixture shape"; fail=1; }
exit $fail
```
- [ ] **Step 2: Run test to verify it fails**
Run: `bash plugins/clone-app/tests/test-re-digest-contract.sh`
Expected: FAIL — contract doc and fixtures do not exist yet (grep errors / `FAIL:` lines), exit non-zero.
- [ ] **Step 3: Create the contract reference doc**
Create `plugins/clone-app/skills/clone-app/references/re-digest-contract.md`:
````markdown
# RE Digest Contract
The Phase 2 subagent runs the reverse-engineering workflow in isolation and
MUST produce these three files under `$WORK/`, then return only the summary.
This file is the single source of truth for their schema. The subagent prompt
points here; do not duplicate the schema into SKILL.md prose.
## File 1 — `$WORK/re-digest.md` (human-readable, main artifact)
Required section headings, in this order:
```
# RE Digest — <pkg>
## Framework & Stack framework, HTTP lib, DI, serialization, obfuscation level
## Hosts first-party vs third-party (table)
## Endpoint Inventory Tier-1: host | method | path | auth | source file
## Key Flow Payloads Tier-2: auth, payment/checkout, 1-2 core flows
— request body / response shape / headers / params
## BuildConfig Secrets base URLs, API keys, feature flags, flavors
## Feature Signals screen count, SDKs, permissions, components
## RE Method re-skill | direct-scripts | limited: <framework>
```
## File 2 — `$WORK/payloads.json` (machine-readable, durable memory)
Required top-level keys: `package`, `re_method`, `endpoints`, `buildconfig`.
Each item in `endpoints` has: `host`, `method`, `path`, `auth`, `source`,
`request_body`, `response`, `headers`.
```json
{
"package": "com.example.app",
"re_method": "re-skill",
"endpoints": [
{
"host": "api.example.com",
"method": "POST",
"path": "/v1/auth/login",
"auth": "none",
"source": "com/example/api/AuthApi.java",
"request_body": { "email": "string", "password": "string" },
"response": { "token": "string", "user": {} },
"headers": { "Content-Type": "application/json" }
}
],
"buildconfig": { "BASE_URL": "https://api.example.com/v1" }
}
```
- `request_body` and `response` are `null` for Tier-1-only endpoints.
- Populate them ONLY for the key flows: **auth, payment/checkout, and the
12 core feature endpoints** (decision Q3=B). Everything else is a Tier-1
row with `null` payloads. Going Tier-2 on every endpoint is a non-goal —
it is token-expensive and the RE skill itself warns against it.
## File 3 — `$WORK/re-summary.txt` (≤40 lines, the only RE text returned)
Plain text. The subagent's return value = the contents of this file plus the
paths to the two files above. Fields:
- framework
- host count (first-party / third-party)
- endpoint count
- key-flow names found (auth / payment / core)
- secrets-found count (BuildConfig)
- RE method: `re-skill` | `direct-scripts` | `limited: <framework>`
- blockers / warnings (e.g. obfuscation, framework guard)
The subagent MUST NOT return raw decompiled sources.
## RE Method values
| Value | Meaning |
|---|---|
| `re-skill` | The `android-reverse-engineering` skill ran the workflow. |
| `direct-scripts` | The skill was absent; the sibling plugin's bash scripts ran. |
| `limited: <framework>` | Flutter / React Native / Cordova / Xamarin — Java decompile is shallow; payloads may be empty and the digest is partial. |
## Framework guard
If the fingerprint reports Flutter / React Native / Cordova / Xamarin, set
`RE Method: limited: <framework>`, write whatever partial signals are
available (manifest, strings, hardcoded URLs, SDK list), and leave payloads
empty where they cannot be recovered. Downstream phases widen the
uncertainty band accordingly.
````
- [ ] **Step 4: Create the digest fixture**
Create `plugins/clone-app/tests/fixtures/re-digest.sample.md`:
```markdown
# RE Digest — com.example.app
## Framework & Stack
Native Kotlin · Retrofit + OkHttp · Hilt · kotlinx.serialization · obfuscation: moderate
## Hosts
| Host | Party |
|------|-------|
| api.example.com | first |
| analytics.thirdparty.io | third |
## Endpoint Inventory
| Host | Method | Path | Auth | Source |
|------|--------|------|------|--------|
| api.example.com | POST | /v1/auth/login | none | com/example/api/AuthApi.java |
| api.example.com | GET | /v1/users/profile | Bearer | com/example/api/UserApi.java |
| api.example.com | POST | /v1/orders | Bearer | com/example/api/OrderApi.java |
## Key Flow Payloads
### POST /v1/auth/login (auth)
- request body: `{ "email": "string", "password": "string" }`
- response: `{ "token": "string", "user": {} }`
- headers: `Content-Type: application/json`
### POST /v1/orders (payment/core)
- request body: `{ "items": [], "total": "number" }`
- response: `{ "order_id": "string", "status": "string" }`
- headers: `Authorization: Bearer <token>`
## BuildConfig Secrets
- BASE_URL = https://api.example.com/v1
- ANALYTICS_KEY = <redacted-present>
## Feature Signals
~18 screens · SDKs: Firebase, AppsFlyer · permissions: INTERNET, ACCESS_NETWORK_STATE
## RE Method
re-skill
```
- [ ] **Step 5: Create the payloads fixture**
Create `plugins/clone-app/tests/fixtures/payloads.sample.json`:
```json
{
"package": "com.example.app",
"re_method": "re-skill",
"endpoints": [
{
"host": "api.example.com",
"method": "POST",
"path": "/v1/auth/login",
"auth": "none",
"source": "com/example/api/AuthApi.java",
"request_body": { "email": "string", "password": "string" },
"response": { "token": "string", "user": {} },
"headers": { "Content-Type": "application/json" }
},
{
"host": "api.example.com",
"method": "GET",
"path": "/v1/users/profile",
"auth": "Bearer",
"source": "com/example/api/UserApi.java",
"request_body": null,
"response": null,
"headers": { "Authorization": "Bearer <token>" }
}
],
"buildconfig": { "BASE_URL": "https://api.example.com/v1" }
}
```
- [ ] **Step 6: Run test to verify it passes**
Run: `bash plugins/clone-app/tests/test-re-digest-contract.sh`
Expected: all `PASS:` lines, exit 0.
- [ ] **Step 7: Verify upstream untouched**
Run: `git status --porcelain plugins/android-reverse-engineering/`
Expected: no output.
- [ ] **Step 8: Commit**
```bash
git add plugins/clone-app/skills/clone-app/references/re-digest-contract.md \
plugins/clone-app/tests/fixtures/re-digest.sample.md \
plugins/clone-app/tests/fixtures/payloads.sample.json \
plugins/clone-app/tests/test-re-digest-contract.sh
git commit -m "test(clone-app): add RE digest contract, fixtures, structural test"
```
---
### Task 2: Rewrite SKILL.md Phase 2 + update Phases 56 + error table
Rewrites Phase 2 to probe/dispatch/consume with the subagent and fallback, wires the digest into Phases 56, and extends the error table. The contract test from Task 1 is extended here to assert the SKILL.md wiring, so this task is independently testable.
**Files:**
- Modify: `plugins/clone-app/skills/clone-app/SKILL.md` (Phase 2 block lines 4371; Phase 5 ~9399; Phase 6 ~101112; error table ~122133)
- Modify: `plugins/clone-app/tests/test-re-digest-contract.sh` (append SKILL.md wiring assertions)
**Interfaces:**
- Consumes (from Task 1): the three output filenames (`re-digest.md`, `payloads.json`, `re-summary.txt`), the `references/re-digest-contract.md` path, and the `RE Method` values.
- Produces: Phase 2 prose that any later phase relies on — `$WORK/re-summary.txt` read in Phase 2c, `$WORK/payloads.json` read in Phase 5, `$WORK/re-digest.md` referenced in Phase 6.
- [ ] **Step 1: Append failing wiring assertions to the test**
Add to the end of `plugins/clone-app/tests/test-re-digest-contract.sh`, BEFORE the final `exit $fail` line (replace that final line):
```bash
# --- SKILL.md Phase 2 wiring (Task 2) ---
SKILL="$P/skills/clone-app/SKILL.md"
hasS() { grep -qF "$1" "$SKILL" && echo "PASS: SKILL $2" || { echo "FAIL: SKILL $2 — '$1' missing"; fail=1; }; }
hasReS() { grep -qE "$1" "$SKILL" && echo "PASS: SKILL $2" || { echo "FAIL: SKILL $2 — /$1/ missing"; fail=1; }; }
hasS "re-digest-contract.md" "Phase 2 points at the digest contract"
hasReS "subagent|Agent tool|dispatch" "Phase 2 dispatches a subagent"
hasS "android-reverse-engineering skill" "Phase 2 names the RE skill branch"
hasS "direct-scripts" "Phase 2 names the script-fallback branch"
hasS "re-summary.txt" "Phase 2c consumes the summary"
hasS "payloads.json" "Phase 5 consumes payloads.json"
hasS "Backend API Surface" "Phase 6 adds the Backend API Surface section"
hasS "RE subagent" "error table covers subagent failure"
exit $fail
```
- [ ] **Step 2: Run test to verify the new assertions fail**
Run: `bash plugins/clone-app/tests/test-re-digest-contract.sh`
Expected: Task-1 assertions still PASS; the new `SKILL …` assertions FAIL (SKILL.md not yet rewritten), exit non-zero.
- [ ] **Step 3: Replace the Phase 2 block in SKILL.md**
In `plugins/clone-app/skills/clone-app/SKILL.md`, replace the entire `## Phase 2: Reverse Engineering` section (from the `## Phase 2` heading through the line ending `...Keep this in context for later phases.`) with:
````markdown
## Phase 2: Reverse Engineering (probe → dispatch → consume)
RE runs inside an **isolated subagent** so the decompiled sources never flood
this orchestrator's context. The subagent prefers the
`android-reverse-engineering` **skill** and falls back to that plugin's bash
**scripts**. Either way it writes the same digest files to `$WORK/`, defined
in `${CLAUDE_PLUGIN_ROOT}/skills/clone-app/references/re-digest-contract.md`.
### Phase 2a — Probe
```bash
RE="$(bash ${CLAUDE_PLUGIN_ROOT}/skills/clone-app/scripts/resolve-re-scripts.sh 2>/tmp/re-err)"; RC=$?
```
- `RC == 0` → the RE **scripts** are on disk (fallback is available). If it
printed a `WARNING:` about bash version, the scripts need **bash 4+**
(macOS ships 3.2; `${VAR,,}` fails as "bad substitution") — install one with
`brew install bash` before the script-fallback branch can succeed.
- Check your own available-skills list for `android-reverse-engineering`
is the RE **skill** registered?
Pick the branch:
| RE skill registered | RE scripts on disk (`RC`) | Branch |
|---|---|---|
| yes | any | **re-skill** |
| no | 0 | **direct-scripts** |
| no | 1 | **stop** — show the `/tmp/re-err` resolver error and halt |
### Phase 2b — Dispatch the subagent
Dispatch one subagent (Agent tool, `general-purpose` type — it can both invoke
skills and run bash). Pass it: `$PKG`, `$APK`, `$WORK`, the chosen **branch**,
the resolved `$RE` scripts dir, and the path to `re-digest-contract.md`. Its
instructions:
1. **Run RE per branch.**
- **re-skill:** invoke the `android-reverse-engineering` skill on `$APK`,
output dir `$WORK/output` — run its full workflow (fingerprint, deps,
decompile, Kotlin-name recovery if Kotlin, API extraction incl. Tier-2).
- **direct-scripts:** run, in order, reading each output before the next:
`bash "$RE/fingerprint.sh" "$APK"`, `bash "$RE/check-deps.sh"`
(install required deps via `bash "$RE/install-dep.sh" <dep>`; ask before
optional vineflower/dex2jar), `bash "$RE/decompile.sh" -o "$WORK/output" "$APK"`
(add `--deobf` if obfuscation is heavy), `bash "$RE/recover-kotlin-names.sh"
"$WORK/output/sources" "$WORK/output/names/"` if Kotlin, then
`bash "$RE/find-api-calls.sh" "$WORK/output/sources"`.
2. **Framework guard:** if the fingerprint is Flutter / React Native / Cordova
/ Xamarin, Java decompile is shallow — produce a partial digest, set
`RE Method: limited: <framework>`, payloads may be empty.
3. **Extract** the Tier-1 endpoint inventory and Tier-2 payloads for **auth,
payment/checkout, and the 12 core feature endpoints** (not every endpoint).
4. **Write** `$WORK/re-digest.md`, `$WORK/payloads.json`, `$WORK/re-summary.txt`
exactly per `re-digest-contract.md`.
5. **Return** the contents of `$WORK/re-summary.txt` plus the two file paths —
**never** raw decompiled sources.
If the subagent fails, retry once; if it still fails and the **direct-scripts**
branch is available, re-dispatch on that branch; otherwise stop and report.
### Phase 2c — Consume
Read `$WORK/re-summary.txt` (the only RE text in this context). From it you have:
framework, HTTP stack, host counts, endpoint count, key-flow names, secrets
count, and the RE method. Read `$WORK/re-digest.md` or `$WORK/payloads.json`
**on demand** when a later phase needs detail. Keep the summary in context for
Phases 37.
````
- [ ] **Step 4: Update Phase 5 to read payloads.json**
In `plugins/clone-app/skills/clone-app/SKILL.md`, in `## Phase 5: Effort & Cost Estimation`, replace the line:
```
- the feature list → AI-Sprint effort table (min-max total, uncertainty band),
```
with:
```
- read `$WORK/payloads.json`; the endpoint count and the payload complexity of
the key flows size the backend work,
- the feature list + backend surface → AI-Sprint effort table (min-max total,
uncertainty band; widen the band when RE Method is `limited:`),
```
- [ ] **Step 5: Update Phase 6 to add the Backend API Surface section**
In `## Phase 6: Market Viability Report`, after the sentence ending
`...Fill every section from the data gathered.` add:
```
Include a **Backend API Surface** section: summarize the Tier-1 inventory from
`$WORK/re-digest.md` and the key-flow payloads from `$WORK/payloads.json` (host
list, endpoint count, auth model, and the auth/payment/core request+response
shapes). If RE Method was `limited:`, say so and note the reduced confidence.
```
- [ ] **Step 6: Extend the error table**
In `plugins/clone-app/skills/clone-app/SKILL.md`, in the `## Error Handling Summary` table, replace the row:
```
| RE plugin missing | show resolver error, stop |
```
with these rows:
```
| RE skill + scripts both missing | show resolver error, stop |
| RE subagent fails | retry once, then fall back to direct-scripts branch; else stop |
| Subagent returned no digest files | re-dispatch once; if still missing, stop and report |
```
- [ ] **Step 7: Run the contract test to verify it passes**
Run: `bash plugins/clone-app/tests/test-re-digest-contract.sh`
Expected: all `PASS:` (Task 1 + the new SKILL wiring assertions), exit 0.
- [ ] **Step 8: Verify upstream untouched**
Run: `git status --porcelain plugins/android-reverse-engineering/`
Expected: no output.
- [ ] **Step 9: Commit**
```bash
git add plugins/clone-app/skills/clone-app/SKILL.md \
plugins/clone-app/tests/test-re-digest-contract.sh
git commit -m "feat(clone-app): run RE via isolated subagent with script fallback + payloads"
```
---
### Task 3: Register the contract doc in the smoke test + full suite green
Adds the new reference file to the structural smoke check and confirms the whole suite passes. A reviewer can reject this independently if the smoke test or aggregate run regresses.
**Files:**
- Modify: `plugins/clone-app/tests/smoke-structure.sh:19-21` (the references loop)
**Interfaces:**
- Consumes (from Task 1): the reference filename `re-digest-contract`.
- Produces: nothing downstream — terminal verification task.
- [ ] **Step 1: Add the new reference to the smoke test's existence check**
In `plugins/clone-app/tests/smoke-structure.sh`, replace:
```bash
for r in stack-recommendation-guide effort-estimation-guide infra-cost-guide report-template; do
must_exist "$P/skills/clone-app/references/$r.md"
done
```
with:
```bash
for r in stack-recommendation-guide effort-estimation-guide infra-cost-guide report-template re-digest-contract; do
must_exist "$P/skills/clone-app/references/$r.md"
done
```
- [ ] **Step 2: Run the smoke test**
Run: `bash plugins/clone-app/tests/smoke-structure.sh`
Expected: all `PASS` lines including `PASS exists: plugins/clone-app/skills/clone-app/references/re-digest-contract.md`, exit 0.
- [ ] **Step 3: Run the full clone-app suite**
Run: `bash plugins/clone-app/tests/run-all.sh`
Expected: every suite runs; final line `ALL TESTS PASSED`, exit 0. (`run-all.sh` auto-discovers `test-re-digest-contract.sh` via its `test-*.sh` glob — no registration needed.)
- [ ] **Step 4: Verify upstream untouched**
Run: `git status --porcelain plugins/android-reverse-engineering/`
Expected: no output.
- [ ] **Step 5: Commit**
```bash
git add plugins/clone-app/tests/smoke-structure.sh
git commit -m "test(clone-app): register re-digest-contract in smoke structure check"
```
---
## Self-Review
**Spec coverage:**
- Phase 2 probe/dispatch/consume → Task 2 Step 3. ✓
- Subagent isolation (Agent tool, general-purpose) → Task 2 Step 3 Phase 2b. ✓
- Branch decision table (skill / scripts / stop) → Task 2 Step 3 Phase 2a. ✓
- Digest contract (3 files, schemas, RE Method values, framework guard) → Task 1 Step 3. ✓
- Tier-2 payloads for auth/payment/core only → contract doc + Task 2 Phase 2b step 3. ✓
- Durable memory in `$WORK/` (memory option 1) → files written by subagent, consumed on demand. ✓
- Phase 5 reads payloads.json → Task 2 Step 4. ✓
- Phase 6 Backend API Surface section → Task 2 Step 5. ✓
- Error table additions (subagent fail, no digest, both branches missing) → Task 2 Step 6. ✓
- New fixtures + structural tests, offline → Task 1. ✓
- smoke-structure.sh updated → Task 3 Step 1. ✓
- Upstream untouched check → every task's penultimate step + global constraint. ✓
- `resolve-re-scripts.sh` unchanged → not modified in any task. ✓
**Placeholder scan:** No TBD/TODO; every code/doc step shows full content. ✓
**Type consistency:** Filenames (`re-digest.md`, `payloads.json`, `re-summary.txt`), JSON keys (`package`, `re_method`, `endpoints`, `buildconfig`, `request_body`, `response`, `headers`), section headings, and RE Method values (`re-skill`, `direct-scripts`, `limited:`) are identical across the contract doc, fixtures, tests, and SKILL.md assertions. ✓

View File

@ -0,0 +1,148 @@
# Clone App — RE-via-Subagent + Payload Extraction Design Spec
**Date:** 2026-06-21
**Scope:** `plugins/clone-app/` only. `plugins/android-reverse-engineering/` stays byte-identical (upstream-untouched rule).
**Supersedes:** Phase 2 of the original `2026-06-21-clone-app-skill-design.md`. All other phases unchanged unless noted.
## Problem
Two gaps in the current clone-app skill:
1. **The RE skill never runs.** clone-app Phase 2 calls the `android-reverse-engineering` plugin's bash *scripts* directly (via `resolve-re-scripts.sh`). The RE *skill's* prose workflow — fingerprint triage, two-tier API documentation, BuildConfig secret hunting, call-flow tracing, cross-platform `.ps1` handling — is bypassed. clone-app re-implements a thin, lossy version of it inline. The user never observes the RE skill triggering because it genuinely does not.
2. **Payloads are not extracted.** Phase 2 assembles only an endpoint *list* (host/method/path/auth). Request bodies, response shapes, and headers — the RE skill's "Tier 2" detail — are never produced and never persisted. Nothing reaches memory or the feasibility report.
## Decisions (locked during brainstorming)
- **Q1 → C (hybrid w/ fallback):** Invoke the `android-reverse-engineering` **skill** for the heavy RE workflow when it is installed; **fall back to direct scripts** when it is not. Full workflow when available, graceful degradation otherwise.
- **Q2 → A (subagent isolation):** RE runs inside a dispatched subagent, in its own context. The orchestrator never ingests raw decompiled sources — only a concise summary plus on-demand file reads.
- **Q3 → B (targeted payloads):** Tier-2 payloads (request body, response shape, headers, params) for **auth, payment/checkout, and the 12 core feature endpoints**. Tier-1 flat inventory for everything else. Matches the RE skill's own "Tier-1 always, Tier-2 for ~10 high-value" guidance.
- **Memory → 1 (durable files):** Full detail written to files in `$WORK/`, off-context, re-readable. Only a ≤40-line summary enters orchestrator context.
## Architecture — Phase 2 rewrite (probe → dispatch → consume)
```
Phase 2a PROBE resolve-re-scripts.sh → are RE scripts on disk? (exit code)
orchestrator's skill list → is the RE skill registered?
Phase 2b DISPATCH dispatch a subagent (Agent tool, general-purpose) that runs RE
in isolation:
├─ RE skill registered → subagent invokes the
│ android-reverse-engineering skill
└─ skill absent, scripts present → subagent runs direct scripts
Subagent writes digest files to $WORK/, returns the summary text.
Phase 2c CONSUME orchestrator reads $WORK/re-summary.txt only; pulls
re-digest.md / payloads.json on demand in later phases.
```
**Invariant:** both branches write the **same digest files**, so Phases 37 are branch-agnostic — one consume path regardless of how RE ran.
### Branch decision table
| RE skill registered | RE scripts on disk | Action |
|---|---|---|
| ✓ | — | subagent runs **RE skill** (preferred) |
| ✗ | ✓ | subagent runs **direct scripts** (fallback) |
| ✓ | ✓ | subagent runs **RE skill** (skill wins) |
| ✗ | ✗ | **stop** — show `resolve-re-scripts.sh` error |
Two independent facts drive it: skill availability (orchestrator reads its own available-skills list) and script availability (`resolve-re-scripts.sh` exit code). `resolve-re-scripts.sh` is unchanged — it remains both the probe and the fallback locator.
**Why the subagent wraps the script-fallback branch too** (not only the skill branch): keeps decompile noise out of the orchestrator either way, and keeps Phases 37 on a single branch-agnostic consume path.
## Digest contract (subagent output)
Single source of truth: a new reference file `references/re-digest-contract.md`. The Phase-2 subagent prompt points at it. The subagent MUST write three files to `$WORK/` and return the summary.
### `$WORK/re-digest.md` — human-readable main artifact
```
# RE Digest — <pkg>
## Framework & Stack framework, HTTP lib, DI, serialization, obfuscation level
## Hosts first-party vs third-party (table)
## Endpoint Inventory Tier-1: host | method | path | auth | source file
## Key Flow Payloads Tier-2: auth, payment/checkout, 1-2 core
— request body / response shape / headers / params
## BuildConfig Secrets base URLs, API keys, feature flags, flavors
## Feature Signals screen count, SDKs, permissions, components
## RE Method "re-skill" | "direct-scripts" | "limited: <framework>"
```
### `$WORK/payloads.json` — machine-readable (memory requirement)
```json
{
"package": "com.example.app",
"re_method": "re-skill",
"endpoints": [
{
"host": "api.example.com",
"method": "POST",
"path": "/v1/auth/login",
"auth": "none",
"source": "com/example/api/AuthApi.java",
"request_body": { "email": "string", "password": "string" },
"response": { "token": "string", "user": {} },
"headers": { "Content-Type": "application/json" }
}
],
"buildconfig": { "BASE_URL": "https://api.example.com/v1" }
}
```
`request_body` / `response` are `null` for Tier-1-only endpoints. Populated only for auth/payment/core (Tier-2).
### `$WORK/re-summary.txt` — ≤40 lines, the only RE text entering orchestrator context
Contains: framework, host count, endpoint count, key-flow names, secrets-found count, RE method, blockers/warnings. The subagent's return value = contents of `re-summary.txt` + the two file paths. **The subagent must not dump raw decompiled sources into its return.**
### Contract rules
- **Tier-2 only for auth / payment / core** (decision Q3=B). All other endpoints → Tier-1 row, `null` payloads.
- **Framework guard:** if fingerprint reports Flutter / React Native / Cordova / Xamarin, the subagent writes a partial digest, sets `RE Method: limited: <framework>`, and payloads may be empty. Downstream widens the uncertainty band.
## Subagent dispatch
clone-app dispatches via the **Agent tool** (`general-purpose` type — can both invoke skills and run bash). The single prompt carries: `$PKG`, `$APK`, `$WORK`, the chosen branch, and the digest contract verbatim (or a pointer to `re-digest-contract.md`). Subagent steps:
1. Run RE — the skill or the direct scripts, per branch.
2. Extract Tier-1 inventory + Tier-2 payloads for auth/payment/core.
3. Write `re-digest.md`, `payloads.json`, `re-summary.txt`.
4. Return `re-summary.txt` contents + the two file paths. No raw sources.
## Downstream changes (Phases 37)
- **Phase 2c:** orchestrator reads `re-summary.txt` only; reads `re-digest.md` / `payloads.json` on demand if a later phase needs detail.
- **Phase 5 (effort/cost):** read `payloads.json`; endpoint count + payload complexity feed the AI-Sprint estimate (backend surface = real work) instead of a loose guess.
- **Phase 6 (report):** new optional report section **"Backend API Surface"** — Tier-1 table summary + key-flow payloads from the digest.
- **Error table additions:**
- RE subagent fails → retry once, then fall back to direct-scripts branch (if scripts present); else stop.
- Both branches unavailable → stop with the resolver error.
## Files changed (all under `plugins/clone-app/`)
| File | Change |
|---|---|
| `skills/clone-app/SKILL.md` | Rewrite Phase 2 (probe/dispatch/consume); tweak Phases 5 & 6; extend error table |
| `skills/clone-app/references/re-digest-contract.md` | **New** — digest schema, single source of truth for the subagent |
| `skills/clone-app/scripts/resolve-re-scripts.sh` | Unchanged (remains probe + fallback locator) |
| `tests/fixtures/re-digest.sample.md` | **New** — schema fixture |
| `tests/fixtures/payloads.sample.json` | **New** — schema fixture |
| `tests/test-re-digest-contract.sh` | **New** — structural assertions |
| `tests/smoke-structure.sh` | Add new reference file to present-files check |
`plugins/android-reverse-engineering/` is **untouched** — the subagent *invokes* it, never edits it. `git status --porcelain plugins/android-reverse-engineering/` must stay empty.
## Tests (offline, fixture-based — matches existing pattern)
- `test-re-digest-contract.sh` asserts:
- `re-digest-contract.md` lists every required `re-digest.md` section and every required `payloads.json` key.
- `SKILL.md` Phase 2 references the contract, names both branches, and dispatches a subagent.
- Sample fixtures parse (JSON valid; digest has all sections).
- `smoke-structure.sh` gains the new reference file in its existence check.
- **No live RE run in tests** — decompilation is too heavy and non-deterministic. Contract + wiring asserted structurally, same philosophy as the offline Python scraper tests.
## Non-goals
- Exhaustive Tier-2 payloads for every endpoint (Q3=C rejected — token-expensive, RE skill itself warns against it).
- Modifying any upstream RE file.
- Live decompilation inside the test suite.

View File

@ -40,35 +40,73 @@ a Cloudflare bot challenge), retries 3×, and prints the path (`app.apk` or
format changed — tell the user the download failed and ask for a local APK/XAPK
path; set `APK` to that path.
## Phase 2: Reverse Engineering
## Phase 2: Reverse Engineering (probe → dispatch → consume)
RE runs inside an **isolated subagent** so the decompiled sources never flood
this orchestrator's context. The subagent prefers the
`android-reverse-engineering` **skill** and falls back to that plugin's bash
**scripts**. Either way it writes the same digest files to `$WORK/`, defined
in `${CLAUDE_PLUGIN_ROOT}/skills/clone-app/references/re-digest-contract.md`.
### Phase 2a — Probe
Resolve the sibling RE scripts:
```bash
RE="$(bash ${CLAUDE_PLUGIN_ROOT}/skills/clone-app/scripts/resolve-re-scripts.sh)"
RE="$(bash ${CLAUDE_PLUGIN_ROOT}/skills/clone-app/scripts/resolve-re-scripts.sh 2>/tmp/re-err)"; RC=$?
```
If it exits non-zero, show its error (RE plugin not installed) and stop. If it
prints a `WARNING:` about bash version, the RE scripts need **bash 4+** (macOS
ships 3.2; their `${VAR,,}` syntax fails as "bad substitution" otherwise) —
install one with `brew install bash` before continuing, then re-run Phase 2.
- `RC == 0` → the RE **scripts** are on disk (fallback is available). If it
printed a `WARNING:` about bash version, the scripts need **bash 4+**
(macOS ships 3.2; `${VAR,,}` fails as "bad substitution") — install one with
`brew install bash` before the script-fallback branch can succeed.
- Check your own available-skills list for `android-reverse-engineering`
is the RE **skill** registered?
Run, in order, reading each output before the next:
1. `bash "$RE/fingerprint.sh" "$APK"` — framework, HTTP stack, obfuscation, SDKs, native libs.
- **If framework is Flutter / React Native / Cordova / Xamarin:** tell the user Java
decompilation is limited; proceed but rely on manifest + strings + hardcoded URLs +
the fingerprint SDK list. Skip steps 3-5's deep API extraction expectations.
2. `bash "$RE/check-deps.sh"` — parse `INSTALL_REQUIRED:` / `INSTALL_OPTIONAL:` lines.
Install required deps with `bash "$RE/install-dep.sh" <dep>`; re-run check-deps until clean.
Ask the user before installing optional deps (vineflower, dex2jar).
3. `bash "$RE/decompile.sh" -o "$WORK/output" "$APK"` (add `--deobf` if fingerprint showed heavy obfuscation: `bash "$RE/decompile.sh" -o "$WORK/output" --deobf "$APK"`).
Sources land at `$WORK/output/sources/`.
4. If the app is Kotlin: `bash "$RE/recover-kotlin-names.sh" "$WORK/output/sources" "$WORK/output/names/"`.
5. `bash "$RE/find-api-calls.sh" "$WORK/output/sources"` (full scan; add `--ktor`/`--apollo`/`--paths` as
the fingerprint suggests).
Pick the branch:
From these outputs assemble: framework, HTTP stack, **API endpoint list**,
first-party vs third-party hosts, AndroidManifest summary (permissions, components),
and a **feature list** (screen count, SDKs, backend signals). Keep this in context
for later phases.
| RE skill registered | RE scripts on disk (`RC`) | Branch |
|---|---|---|
| yes | any | **re-skill** |
| no | 0 | **direct-scripts** |
| no | 1 | **stop** — show the `/tmp/re-err` resolver error and halt |
### Phase 2b — Dispatch the subagent
Dispatch one subagent (Agent tool, `general-purpose` type — it can both invoke
skills and run bash). Pass it: `$PKG`, `$APK`, `$WORK`, the chosen **branch**,
the resolved `$RE` scripts dir, and the path to `re-digest-contract.md`. Its
instructions:
1. **Run RE per branch.**
- **re-skill:** invoke the android-reverse-engineering skill on `$APK`,
output dir `$WORK/output` — run its full workflow (fingerprint, deps,
decompile, Kotlin-name recovery if Kotlin, API extraction incl. Tier-2).
- **direct-scripts:** run, in order, reading each output before the next:
`bash "$RE/fingerprint.sh" "$APK"`, `bash "$RE/check-deps.sh"`
(install required deps via `bash "$RE/install-dep.sh" <dep>`; ask before
optional vineflower/dex2jar), `bash "$RE/decompile.sh" -o "$WORK/output" "$APK"`
(add `--deobf` if obfuscation is heavy), `bash "$RE/recover-kotlin-names.sh"
"$WORK/output/sources" "$WORK/output/names/"` if Kotlin, then
`bash "$RE/find-api-calls.sh" "$WORK/output/sources"`.
2. **Framework guard:** if the fingerprint is Flutter / React Native / Cordova
/ Xamarin, Java decompile is shallow — produce a partial digest, set
`RE Method: limited: <framework>`, payloads may be empty.
3. **Extract** the Tier-1 endpoint inventory and Tier-2 payloads for **auth,
payment/checkout, and the 12 core feature endpoints** (not every endpoint).
4. **Write** `$WORK/re-digest.md`, `$WORK/payloads.json`, `$WORK/re-summary.txt`
exactly per `re-digest-contract.md`.
5. **Return** the contents of `$WORK/re-summary.txt` plus the two file paths —
**never** raw decompiled sources.
If the subagent fails, retry once; if it still fails and the **direct-scripts**
branch is available (i.e. the Phase 2a probe returned `RC == 0`), re-dispatch on
that branch; otherwise stop and report.
### Phase 2c — Consume
Read `$WORK/re-summary.txt` (the only RE text in this context). From it you have:
framework, HTTP stack, host counts, endpoint count, key-flow names, secrets
count, and the RE method. Read `$WORK/re-digest.md` or `$WORK/payloads.json`
**on demand** when a later phase needs detail. Keep the summary in context for
Phases 37.
## Phase 3: Store Analysis
@ -94,7 +132,10 @@ the user to choose. **Wait for the user's choice before Phase 5.** Lock it.
Read `${CLAUDE_PLUGIN_ROOT}/skills/clone-app/references/effort-estimation-guide.md`
and `infra-cost-guide.md`. Build:
- the feature list → AI-Sprint effort table (min-max total, uncertainty band),
- read `$WORK/payloads.json`; the endpoint count and the payload complexity of
the key flows size the backend work,
- the feature list + backend surface → AI-Sprint effort table (min-max total,
uncertainty band; widen the band when RE Method is `limited:`),
- the MVP/Growth/Scale monthly infra cost table.
Base both on the **user-selected stack** from Phase 4.
@ -104,6 +145,10 @@ Read `${CLAUDE_PLUGIN_ROOT}/skills/clone-app/references/report-template.md`.
Fill every section from the data gathered. For market analysis (competitors,
market size), use web search as needed. Produce a GO / CONDITIONAL GO / NO GO
verdict tying effort + cost against market opportunity.
Include a **Backend API Surface** section: summarize the Tier-1 inventory from
`$WORK/re-digest.md` and the key-flow payloads from `$WORK/payloads.json` (host
list, endpoint count, auth model, and the auth/payment/core request+response
shapes). If RE Method was `limited:`, say so and note the reduced confidence.
Write the report:
```
@ -124,7 +169,9 @@ implementation plan?"
|---|---|
| Package not in URL | ask user for package name |
| Download fails 3× | ask for local APK path |
| RE plugin missing | show resolver error, stop |
| RE skill + scripts both missing | show resolver error, stop |
| RE subagent fails | retry once, then fall back to direct-scripts branch; else stop |
| Subagent returned no digest files | re-dispatch once; if still missing, stop and report |
| Flutter/RN/Cordova/Xamarin | warn, continue with limited RE |
| App Store not found | continue Google Play only |
| Play scrape returns nulls | web-search fallback, note source |

View File

@ -0,0 +1,85 @@
# RE Digest Contract
The Phase 2 subagent runs the reverse-engineering workflow in isolation and
MUST produce these three files under `$WORK/`, then return only the summary.
This file is the single source of truth for their schema. The subagent prompt
points here; do not duplicate the schema into SKILL.md prose.
## File 1 — `$WORK/re-digest.md` (human-readable, main artifact)
Required section headings, in this order:
```
# RE Digest — <pkg>
## Framework & Stack framework, HTTP lib, DI, serialization, obfuscation level
## Hosts first-party vs third-party (table)
## Endpoint Inventory Tier-1: host | method | path | auth | source file
## Key Flow Payloads Tier-2: auth, payment/checkout, 1-2 core flows
— request body / response shape / headers / params
## BuildConfig Secrets base URLs, API keys, feature flags, flavors
## Feature Signals screen count, SDKs, permissions, components
## RE Method re-skill | direct-scripts | limited: <framework>
```
## File 2 — `$WORK/payloads.json` (machine-readable, durable memory)
Required top-level keys: `package`, `re_method`, `endpoints`, `buildconfig`.
Each item in `endpoints` has: `host`, `method`, `path`, `auth`, `source`,
`request_body`, `response`, `headers`.
```json
{
"package": "com.example.app",
"re_method": "re-skill",
"endpoints": [
{
"host": "api.example.com",
"method": "POST",
"path": "/v1/auth/login",
"auth": "none",
"source": "com/example/api/AuthApi.java",
"request_body": { "email": "string", "password": "string" },
"response": { "token": "string", "user": {} },
"headers": { "Content-Type": "application/json" }
}
],
"buildconfig": { "BASE_URL": "https://api.example.com/v1" }
}
```
- `request_body` and `response` are `null` for Tier-1-only endpoints.
- Populate them ONLY for the key flows: **auth, payment/checkout, and the
12 core feature endpoints** (decision Q3=B). Everything else is a Tier-1
row with `null` payloads. Going Tier-2 on every endpoint is a non-goal —
it is token-expensive and the RE skill itself warns against it.
## File 3 — `$WORK/re-summary.txt` (≤40 lines, the only RE text returned)
Plain text. The subagent's return value = the contents of this file plus the
paths to the two files above. Fields:
- framework
- host count (first-party / third-party)
- endpoint count
- key-flow names found (auth / payment / core)
- secrets-found count (BuildConfig)
- RE method: `re-skill` | `direct-scripts` | `limited: <framework>`
- blockers / warnings (e.g. obfuscation, framework guard)
The subagent MUST NOT return raw decompiled sources.
## RE Method values
| Value | Meaning |
|---|---|
| `re-skill` | The `android-reverse-engineering` skill ran the workflow. |
| `direct-scripts` | The skill was absent; the sibling plugin's bash scripts ran. |
| `limited: <framework>` | Flutter / React Native / Cordova / Xamarin — Java decompile is shallow; payloads may be empty and the digest is partial. |
## Framework guard
If the fingerprint reports Flutter / React Native / Cordova / Xamarin, set
`RE Method: limited: <framework>`, write whatever partial signals are
available (manifest, strings, hardcoded URLs, SDK list), and leave payloads
empty where they cannot be recovered. Downstream phases widen the
uncertainty band accordingly.

View File

@ -0,0 +1,27 @@
{
"package": "com.example.app",
"re_method": "re-skill",
"endpoints": [
{
"host": "api.example.com",
"method": "POST",
"path": "/v1/auth/login",
"auth": "none",
"source": "com/example/api/AuthApi.java",
"request_body": { "email": "string", "password": "string" },
"response": { "token": "string", "user": {} },
"headers": { "Content-Type": "application/json" }
},
{
"host": "api.example.com",
"method": "GET",
"path": "/v1/users/profile",
"auth": "Bearer",
"source": "com/example/api/UserApi.java",
"request_body": null,
"response": null,
"headers": { "Authorization": "Bearer <token>" }
}
],
"buildconfig": { "BASE_URL": "https://api.example.com/v1" }
}

View File

@ -0,0 +1,38 @@
# RE Digest — com.example.app
## Framework & Stack
Native Kotlin · Retrofit + OkHttp · Hilt · kotlinx.serialization · obfuscation: moderate
## Hosts
| Host | Party |
|------|-------|
| api.example.com | first |
| analytics.thirdparty.io | third |
## Endpoint Inventory
| Host | Method | Path | Auth | Source |
|------|--------|------|------|--------|
| api.example.com | POST | /v1/auth/login | none | com/example/api/AuthApi.java |
| api.example.com | GET | /v1/users/profile | Bearer | com/example/api/UserApi.java |
| api.example.com | POST | /v1/orders | Bearer | com/example/api/OrderApi.java |
## Key Flow Payloads
### POST /v1/auth/login (auth)
- request body: `{ "email": "string", "password": "string" }`
- response: `{ "token": "string", "user": {} }`
- headers: `Content-Type: application/json`
### POST /v1/orders (payment/core)
- request body: `{ "items": [], "total": "number" }`
- response: `{ "order_id": "string", "status": "string" }`
- headers: `Authorization: Bearer <token>`
## BuildConfig Secrets
- BASE_URL = https://api.example.com/v1
- ANALYTICS_KEY = <redacted-present>
## Feature Signals
~18 screens · SDKs: Firebase, AppsFlyer · permissions: INTERNET, ACCESS_NETWORK_STATE
## RE Method
re-skill

View File

@ -16,7 +16,7 @@ done
for s in scrape-play-store.py check-appstore.py; do
must_exist "$P/skills/clone-app/scripts/$s"
done
for r in stack-recommendation-guide effort-estimation-guide infra-cost-guide report-template; do
for r in stack-recommendation-guide effort-estimation-guide infra-cost-guide report-template re-digest-contract; do
must_exist "$P/skills/clone-app/references/$r.md"
done

View File

@ -0,0 +1,62 @@
#!/usr/bin/env bash
set -uo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
P="$HERE/.." # plugins/clone-app
CONTRACT="$P/skills/clone-app/references/re-digest-contract.md"
DIGEST_FIX="$HERE/fixtures/re-digest.sample.md"
PAYLOAD_FIX="$HERE/fixtures/payloads.sample.json"
fail=0
has() { grep -qF "$2" "$1" && echo "PASS: $3" || { echo "FAIL: $3 — '$2' not in ${1##*/}"; fail=1; }; }
# Contract doc exists and documents every required re-digest.md section heading
for sec in "## Framework & Stack" "## Hosts" "## Endpoint Inventory" \
"## Key Flow Payloads" "## BuildConfig Secrets" "## Feature Signals" "## RE Method"; do
has "$CONTRACT" "$sec" "contract documents section $sec"
done
# Contract doc documents every required payloads.json key
for key in '"package"' '"re_method"' '"endpoints"' '"buildconfig"' \
'"request_body"' '"response"' '"headers"'; do
has "$CONTRACT" "$key" "contract documents json key $key"
done
# Contract names the three output files and the three RE Method values
for tok in "re-digest.md" "payloads.json" "re-summary.txt" \
"re-skill" "direct-scripts" "limited:"; do
has "$CONTRACT" "$tok" "contract names token $tok"
done
# Digest fixture has every required section heading
for sec in "## Framework & Stack" "## Hosts" "## Endpoint Inventory" \
"## Key Flow Payloads" "## BuildConfig Secrets" "## Feature Signals" "## RE Method"; do
has "$DIGEST_FIX" "$sec" "digest fixture has section $sec"
done
# Payload fixture is valid JSON and has the required shape
python3 -c "
import json,sys
d=json.load(open('$PAYLOAD_FIX'))
for k in ('package','re_method','endpoints','buildconfig'):
assert k in d, 'missing top key '+k
assert isinstance(d['endpoints'],list) and d['endpoints'], 'endpoints must be non-empty list'
for e in d['endpoints']:
for k in ('host','method','path','auth','source','request_body','response','headers'):
assert k in e, 'endpoint missing key '+k
print('PASS: payload fixture shape valid')
" || { echo "FAIL: payload fixture shape"; fail=1; }
# --- SKILL.md Phase 2 wiring (Task 2) ---
SKILL="$P/skills/clone-app/SKILL.md"
hasS() { grep -qF "$1" "$SKILL" && echo "PASS: SKILL $2" || { echo "FAIL: SKILL $2 — '$1' missing"; fail=1; }; }
hasReS() { grep -qE "$1" "$SKILL" && echo "PASS: SKILL $2" || { echo "FAIL: SKILL $2 — /$1/ missing"; fail=1; }; }
hasS "re-digest-contract.md" "Phase 2 points at the digest contract"
hasReS "subagent|Agent tool|dispatch" "Phase 2 dispatches a subagent"
hasS "android-reverse-engineering skill" "Phase 2 names the RE skill branch"
hasS "direct-scripts" "Phase 2 names the script-fallback branch"
hasS "re-summary.txt" "Phase 2c consumes the summary"
hasS "payloads.json" "Phase 5 consumes payloads.json"
hasS "Backend API Surface" "Phase 6 adds the Backend API Surface section"
hasS "RE subagent" "error table covers subagent failure"
exit $fail