EGB-677 stage-1 structural cleanups, no behavior change for the v2 happy path:
1. cmd_which reuses _json_external_entries (the push/pull extractor) instead of
its own duplicated jq @tsv projection, so `which` applies the same
properties->gradle-properties normalization + skip-with-warning rules the
sync path does and can't drift from it.
2. The two external-manifest read guards are factored into _json_readable
(plain regular file, silent) / _legacy_readable (warn+skip a symlinked
.secrets-files), shared by _external_entries_for_push/_pull.
3. The legacy (manifest-less) pull path now warns when nested <project>/<relpath>.age
blobs exist that its non-recursive globs can't see (external/ excluded —
pull_external_files handles those), so it never silently under-restores.
Tests: +4 in test/manifest.bats (normalized which display, malformed external
skipped by which, nested-blob warning fires, external-only no false warning).
Full suite green (286/286).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>