test(clone-app): add RE digest contract, fixtures, structural test
This commit is contained in:
parent
73b3b3d8c5
commit
b6eca91d1d
|
|
@ -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
|
||||
1–2 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.
|
||||
|
|
@ -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" }
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
#!/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
|
||||
Loading…
Reference in New Issue