The previous pass renamed the record directory but left two concrete identifiers in
the transcripts: a package name plus device serial in TOOL-VERDICTS.md, and a
logcat-truncated tag in FINDINGS.md. Both are now <PKG> / <DEVICE>, matching the
convention the extension-pass records already use. No claim, command or conclusion
changes -- only the identifiers.
The docstring promised "a smali tree or a directory of dex files" and delivered neither: iter_files() yielded only .smali, a direct .dex was read as UTF-8 text and regex-matched against smali syntax, and a dex-only directory yielded nothing at all. A real dex, a dex directory and a typo path all printed [total refs] 0 and a note blaming the needle, so the one tool the skill recommends for judging blast radius before patching returned a confident false zero.
A .dex is now decoded through dexutil (no baksmali needed), a directory is walked for .smali and .dex alike, an archive is opened for its classes*.dex, input that cannot be read is refused with exit 2 and a reason, and every run prints [scanned] N file(s) so the two cases can never print identically again. Also corrects the recorded exit code for this defect: it was 1, not 0, which moves it from "no signal at all" to "the signal was there and the message contradicted it".
Verified against a fixture whose call graph is known by construction (docs/tool-verification/fixtures/make_fixture_dex.py, committed as the evidence): the .dex and the equivalent smali tree both return 3/2/2, matching what was written. And against the real target: Ljava/lang/String;->length gives 356 refs in 2.3 s where it previously gave 0; a five-dex directory gives 9,472; a typo path exits 2.
Docs updated with it: F9 records the opcode-table finding the fix exposed, F2/F3 and TOOL-VERDICTS.md are corrected, and dex-patching.md now tells the reader to look at the [scanned] line before the count.
Capability-first tooling guidance, plus removal of target identity from the repo.
Toolchain (references/toolchain.md):
- New ASC (droidasc) section + README Requirements row. An agent should reach for an
indexer before any full decompile. Covers install, query subcommands, the division
of labour with ddc (ASC locates, ddc reads), and its measured limits -- including the
unverified-negative trap when a non-ASCII needle goes through a shell, and the
mangled-class case where ASC is the only route.
- ddc: two measured limits -- a renamed class is unreachable via -c, and a decompiled
body can be semantically wrong rather than merely ugly.
- New "Closing a capability gap": installing the missing tool IS the task, with measured
install costs set against the cost of the workaround.
- New "Using the kit's scripts instead of writing your own", with two-tools-disagreeing
as the bug detector it is.
- Ghidra section for the aarch64 case: headless invocation, the flattening caveat, and
the "a decompiler cannot go where the data is not" boundary.
- HaE placed in Tier 3 as traffic triage, naming the three situations where it is the
wrong next step.
Observation ordering:
- SKILL.md: new Tooling section with four obligations; G2 now requires a written
capability inventory; symptom index gains four rows (silent feature loss with no
request at all, telling your patch apart from the target's own behaviour, spawn keeps
the Activity stack down, the device server being killed by the ROM).
- dynamic-frida.md: the patch-then-detach ordering, and device-server survival notes.
- New scripts/spawn_patch_detach.py and scripts/hook_patch_only.js.
Neutrality:
- docs/verification-jiongnew/ -> docs/tool-verification/. Target-specific transcripts
dropped; all target identity removed. The record keeps the measurements and loses
the specimen.
- No target identity remains anywhere in the tree.
check_repo.py: 0 problems. check_refs.py: 182 reachable, 0 dangling, 0 warnings.