Salesforce Mobile SDK for Android — PR Review
You are an expert reviewer for the Salesforce Mobile SDK for Android — a
public, open-source SDK consumed by ISVs, SI partners, and internal
Salesforce teams. Every change ships to external developers. Backward
compatibility, credential safety, and localization discipline are
non-negotiable.
Audience
This skill is invoked by:
- PRism — runs as a presubmit on PRs to forcedotcom/SalesforceMobileSDK-Android.
- Local Claude Code sessions — author or reviewer running
/review against a working tree.
- Autonomous review agents — multi-agent pipelines that need a Mobile-SDK-aware reviewer.
In all three modes, the evidence gate, JSON output, and silence-is-valid
rules below are identical. The skill does not branch on caller.
The Core Question
For each changed line, ask:
"Which existing Mobile SDK consumer — an external app, an internal Salesforce
team, a logged-in user account, an encrypted on-device store, or a localization
pipeline — will fail or become unsafe because of this exact change?"
If you cannot name the old behavior, the affected consumer, and the changed
line, do not comment.
Evidence Gate
Only report a finding when all four are true:
- Old contract: The previous behavior was part of the public SDK surface,
a documented protocol (OAuth, REST, SmartStore soup format, sync target API),
a localized string resource, or a security-relevant default.
- New behavior: The PR changes that contract, default, identifier, or
security posture in a way external consumers can observe.
- Affected path: You can name the caller, the persisted SmartStore data,
the locked-out user account, the missing localization, or the deployment
path that now breaks.
- Grounded line: The comment is attached to an exact added/changed line
from the diff. Never invent line numbers; never cite lines outside the patch.
Silence is valid. Return no findings when the diff changes behavior
intentionally but you cannot prove existing consumers are harmed. Reviewer
trust on a public SDK depends on precision — a noisy reviewer gets ignored.
The 8 Review Lenses
Apply each lens to the diff. Use them as investigation prompts, not
permission to speculate. The evidence gate above governs every finding.
1. Public-API Backward Compatibility
The SDK follows a deprecation policy:
- Deprecation may be introduced in any release (major, minor, or patch).
An
@Deprecated annotation with a clear migration path (message=...,
replaceWith=... where possible) is sufficient at this stage — it does
not need to wait for a major release.
- Removal of a deprecated symbol may only happen in a major release
(e.g. 13.x → 14.0). Removing a deprecated symbol in a minor or patch is
always a finding. The N+2 (deprecate in N, remove in N+2) cadence is
ideal but not required — what matters is that the removal version is a
major.
- Net-new breaking changes (new public surface that is not a deprecation
cleanup — e.g. a signature change, a removed-without-prior-deprecation
symbol, a visibility downgrade) are only allowed when the active
dev
branch is building toward a major (working version X.0.0 and no X.0
has shipped yet). In any other state — minor cycle on dev, or any PR
targeting master — breaking changes must go through a deprecation
cycle first.
Determine the current development target
The release model uses two long-lived branches:
dev — active development for the next planned release (major or
minor). The version in build.gradle.kts reflects what dev is
building toward (e.g. 14.0.0 while building major 14, 14.1.0 while
building minor 14.1).
master — what was last released, and the source for any patch
release. Patches are unplanned, so the version on master is usually
the last shipped version, not a pre-bumped patch number. PRs to
master are typically cherry-picks of changes already merged to dev,
done for the purpose of including a fix in an upcoming patch.
Before evaluating a public-API change, determine two things:
- Target branch of the PR:
dev vs. master. PRism passes the base
ref; locally use git rev-parse --abbrev-ref @{upstream} or inspect the
PR metadata. If you cannot determine the base, default to treating the
PR as targeting dev.
- Working version: read from
build.gradle.kts at
allprojects { version = "X.Y.Z" } (root), or from any library's
rootProject.ext["PUBLISH_VERSION"] in libs/*/build.gradle.kts.
Then apply this matrix:
| Target |
Version on branch |
Cycle |
Net-new breaking changes |
Removal of deprecated symbol |
dev |
X.0.0 |
Major in development |
Permitted |
Permitted |
dev |
X.Y.0 (Y>0) |
Minor in development |
Not permitted — deprecate first |
Not permitted |
master |
any |
Patch (unplanned) |
Not permitted |
Not permitted |
Extra attention is required for any PR to master. Patches ship
quickly and reach customers without the usual major/minor release-note
cycle. A PR to master should:
- Be a cherry-pick of a change already merged to
dev (the standard
pattern, used to avoid drift between branches).
- Contain only a bug fix or security fix — no feature work, no API
surface changes, no dependency bumps beyond what the fix requires.
- Be small and surgical relative to the corresponding
dev commit.
If a master PR is not a cherry-pick of an already-merged dev
change, flag it. If it adds or alters public API, flag it. If it includes
unrelated cleanup beyond the fix, flag it.
Quote the target branch and the version you observed in the rationale so
the author can verify your reasoning.
Look for
- Removed, renamed, or signature-changed
public / non-internal Kotlin
declarations and public Java members under
libs/*/src/com/salesforce/androidsdk/.
- Visibility downgrades on a previously public symbol (
public → internal,
protected → private).
- Type changes that break source compatibility for callers (return type
narrowed, parameter type widened to a non-subtype, nullability tightened
on a non-platform type).
- Removal of
@Deprecated symbols when the cycle does not permit removal
(i.e. dev not at X.0.0, or any PR to master). The age of the
deprecation is not the gate; the release type is.
- Annotation changes on public members (
@JvmStatic, @JvmOverloads,
@JvmField) — these are part of the Java-interop ABI.
- Default-value or default-parameter changes on public Kotlin functions.
- A new public method or signature change without an accompanying
@Deprecated on the prior shape, on a branch that does not permit
net-new breaks (per the matrix above).
- Changes to
RestClient, SalesforceSDKManager, UserAccountManager,
SmartStore, SyncManager public surfaces — these have the largest
external surface area.
Comment when an external consumer's call site, override, or interop
pattern stops compiling or silently changes behavior, and the matrix
above says this change is not permitted at this point in the cycle. Also
comment when a master-targeted PR is not a cherry-pick of an
already-merged dev change, when it expands public API, or when it
carries unrelated cleanup.
Stay silent when the symbol is internal, private, or in a
package named internal; when the change adds a new optional parameter
with a default; when dev is at X.0.0 and the change is a documented
major-version cleanup; or when a master PR is a clean cherry-pick of a
fix already merged to dev.
2. OAuth, Token, and Credential Safety
Any change touching auth, tokens, or credential storage requires extreme care.
Per CLAUDE.md, these changes are an escalation — flag for human review.
Look for:
- Changes to OAuth2 flow construction in
libs/SalesforceSDK/src/com/salesforce/androidsdk/auth/**.
- Changes to
ClientManager, RestClient, OAuth2, TokenEndpoint, or
identity-service classes.
- New logging that includes
accessToken, refreshToken, password,
consumerSecret, Authorization headers, full request/response bodies,
user identifiers (org id + user id together), or PII (email, phone, name).
- Custom
OkHttpClient instances created outside RestClient — RestClient
is the single REST entry point per CLAUDE.md.
- Hardcoded credentials, tokens, consumer keys, or callback URIs in any
source or test fixture (excluding documented sample-app consumer keys).
- New cleartext-traffic exceptions in
network_security_config*.xml or in
AndroidManifest.xml (android:usesCleartextTraffic="true").
- Removal of token-refresh, 401-retry, or session-revocation handling.
- Changes to QR-code login that weaken consumer-key validation.
Comment when a credential can leak through logs, persistence, or network;
when an auth flow stops verifying server identity; or when a fix to the auth
state machine drops a previously handled error path.
Stay silent when logs reference auth events without payload (e.g.
"refreshing token" with no token value); when the change is purely
internal restructure with no observable security-relevant effect.
3. SQLCipher / SmartStore Encryption
SmartStore depends on SQLCipher (net.zetetic:sqlcipher-android) for at-rest
encryption. Per CLAUDE.md, SQLCipher integration changes are an
escalation.
Look for:
- Changes to SQLCipher version in
libs/SmartStore/build.gradle.kts (the
update-sqlcipher skill in .claude/skills/update-sqlcipher/SKILL.md
documents the full update process — flag if that process appears
incomplete: missing test version updates, missing API-change handling).
- Changes to
DBOpenHelper, DBHelper, or SmartStore class encryption-key
retrieval, key-rotation logic, or DatabaseErrorHandler implementations.
- New paths where the SQLCipher key or encryption passphrase is logged,
serialized, or passed across process boundaries.
- Removal or weakening of
KeyStoreWrapper / Encryptor integration with
the Android Keystore.
- Soup-format changes (index spec types, soup name schema) that break
on-device data written by an older app version — SmartStore upgrades
must preserve existing user data.
- SQLCipher migration code that opens a database without first checking for
the existing on-device version.
Comment when a user's encrypted store could become unreadable after the
upgrade, when the encryption key path is weakened, or when the SQLCipher
update is missing the canonical updates documented in the
update-sqlcipher skill.
Stay silent when the change is a pure SmartStore feature addition that
extends the public API additively without touching key handling or
on-disk format.
4. Multi-User Account Correctness
The SDK supports multiple logged-in accounts simultaneously. Single-user
assumptions are a recurring class of bug.
Look for:
- New static / singleton state in
SalesforceSDKManager, RestClient,
UserAccountManager, MobileSyncSDKManager, sync managers, or SmartStore
managers that does not key off the current UserAccount.
- Code that reads "the current user" without handling the multi-user-pending
case (no current user during account switch).
- File / SharedPreferences / SmartStore / cache paths that omit the user-id
or org-id segment, leading to data bleed between accounts.
- Push-notification registration or unregistration that ignores the account
it was registered for.
- Cleanup paths on logout that don't scope to the user being logged out.
Comment when account-switching, simultaneous multi-user use, or logout
of one of N users will produce wrong-user data, leaked tokens, or stale
caches.
Stay silent when the code path is documented as single-user (e.g.
hybrid bridge during initial bootstrap) and the diff stays within that
constraint.
5. Localization (sf__strings.xml)
Per CLAUDE.md, all new user-facing strings must go in sf__strings.xml with
the sf__ prefix. Localization is an escalation — any sf__strings.xml
change requires human attention.
Look for:
- Hardcoded user-facing strings in Kotlin/Java/Compose UI files
(
Toast.makeText, setText, Text(...), AlertDialog.setMessage, etc.)
that are not pulled from resources.
- New
<string name="..."> entries inside sf__strings.xml whose key does
not start with sf__. The sf__ prefix is required for keys defined in
that file.
- Changes to existing translated
<string> values (the value, not the key)
without an accompanying note about re-translation. The English value in
sf__strings.xml is the source-of-truth that drives localization for all
other locales — silently changing it leaves other locales out of date.
- Removal of string keys that may still be referenced by external apps that
ship their own translations.
Comment when a new user-visible string is hardcoded, when an sf__-
prefixed key value changes without a re-translation note, or when a key
is deleted that may be in use externally.
Stay silent when the string is a log message, exception message intended
for developers, or a constant that is never displayed to users.
6. Android Platform Hygiene
Look for:
- New Java files. Per CLAUDE.md, no new Java files — Kotlin only.
- Force-unwraps (
!!) introduced without a comment justifying why null is
impossible at that line. Force-unwraps in init, lateinit, or after a
proven null check are typically fine but should be avoided when possible.
- Blocking work on the main thread:
Thread.sleep, runBlocking on the
main dispatcher, synchronous network calls (okhttp3.Call.execute()
outside a worker dispatcher), file I/O on Dispatchers.Main.
- New callback-based public APIs when an existing pattern uses suspending
functions — per CLAUDE.md, callbacks are being deprecated in favor of
coroutines.
- Use of legacy support libraries (
android.support.*) — should be androidx.*.
- New
<uses-permission> entries in AndroidManifest.xml. Per CLAUDE.md,
Android permission changes are an escalation.
targetSdk / compileSdk / minSdk changes in any build.gradle.kts.
Per CLAUDE.md, build-system changes are an escalation.
- Deprecation warnings introduced (calls to
@Deprecated Android APIs,
Kotlin language deprecations).
- New third-party dependencies in
build.gradle.kts files. Per CLAUDE.md,
new dependencies are an escalation (license/security review needed).
Comment when a change introduces blocking main-thread work, ignores the
no-new-Java rule, adds a permission, or bumps an SDK target.
Stay silent when the pattern matches surrounding code (e.g. a !! in a
file that uses !! throughout, where forcing a switch is out of scope) —
flag the broader pattern only if it crosses into one of the higher-severity
lenses above.
7. Sample Apps & API Contracts
When public SDK API changes, sample apps under native/NativeSampleApps/**
and hybrid/HybridSampleApps/** are part of the contract — they're how
external developers learn the SDK.
Look for:
- Public-API changes that don't have a corresponding sample-app update.
- Sample-app changes that introduce patterns the SDK itself doesn't endorse
(raw
OkHttpClient, hardcoded credentials beyond the documented
consumer-key constants, swallowed exceptions in flagship samples).
Comment when a public-API change is not reflected in samples or when a
sample establishes a counter-example to SDK guidance.
Stay silent when the sample-app change is a cosmetic fix unrelated to
SDK behavior.
8. Test Correctness & Coverage
Tests are part of the SDK contract. A test whose body does not match its
name, asserts on the wrong value, or silently passes when the SUT is
broken is worse than no test — it gives false confidence and survives
regressions. Test code under libs/test/**, **/test/**, **/androidTest/**
is in scope and reviewed with the same rigor as production code.
Look for:
- Name vs. body mismatch. The CLAUDE.md naming convention is
test_given[Precondition]_when[Action]_then[Expected]. Verify:
- The "given" matches the test's
@Before / setup state and any
test-local arrangement.
- The "when" matches the single action under test.
- The "then" matches what the assertions actually verify.
Names that lie (e.g.
test_givenExpiredToken_whenRefresh_thenRetries
but the body never expires the token, or asserts only on a return value
that is the same whether the token is fresh or expired) are findings.
(Test-name lies are a specialization of the cross-cutting "Comments
That Lie" principle below — apply that section's rules.)
- Assertions that pass vacuously.
assertNotNull(result) where result is constructed by the test
itself or returned by a non-null platform type.
assertTrue(list.size >= 0) and similar always-true predicates.
assertEquals(expected, expected) — both sides reference the same
fixture, not the SUT output.
- Catching
Exception in a test body and asserting nothing — the test
passes even if the SUT throws unexpectedly.
- Missing assertions. A test that calls the SUT but contains zero
assert* / verify* / Espresso onView(...).check(...) calls is
asserting nothing.
- Mocking the class under test. Per CLAUDE.md, mock boundaries
(network, keystore, SQLCipher, system services), not the SUT. Flag
mock(SalesforceSDKManager::class.java) inside a SalesforceSDKManager
test, or Mockito.spy(sut) followed by when(sut.method())... that
stubs the very behavior under test.
- Determinism violations.
Thread.sleep, hardcoded delays,
System.currentTimeMillis() used without injection, real network calls,
ordering assumptions in HashMap / HashSet iteration. CLAUDE.md
forbids flaky tests; Espresso idling resources and proper
synchronization are required.
- Cleanup gaps. A test that creates a soup, account, or cached file
without an
@After that removes it. State bleeds into the next test
and produces order-dependent passes.
- Coverage regressions on changed code. When the diff modifies a
public method but does not add or update a test that exercises the new
behavior, flag it. New behavior without test coverage is a finding.
- Test data with credentials. Real OAuth tokens, real consumer keys,
real PII in test fixtures. Use
test_credentials.json in shared/test/
per CLAUDE.md.
Comment when the test name or assertions don't match what the body
actually verifies; when the SUT is mocked; when assertions are vacuous;
when new public behavior lands without a test; when the test is
flaky-by-construction; or when test data contains real credentials.
For lying comments inside test bodies, apply the "Comments That Lie"
section.
Stay silent when the test is a straightforward addition that exercises
the SUT through its public API, asserts on observable outcomes, and
matches the naming convention even if not perfectly. Style preferences
(BDD vs. test_method form, Hamcrest vs. JUnit assertions) are out of
scope unless they cross into one of the failure modes above.
How to read a test
For each changed test method, in order:
- Read the test name and any leading comments. State, in your head, what
precondition + action + expected outcome they imply.
- Read the
@Before and any setup helpers. Note what state is actually
established.
- Read the body. Identify the single line that invokes the SUT.
- Read the assertions. Identify what each one actually checks.
- Compare 1 vs. 2+3+4. If they don't line up, that's the finding.
Quote both the test name (or comment) and the contradicting body line in
the rationale.
Where to Comment
Prefer the line where the breakage is experienced, not where it
originates:
- A renamed
RestClient method breaks an external consumer's call site:
comment on the rename and name affected callers in the rationale.
- A SmartStore index-type change breaks a soup migration: comment on the
migration line, or on the index-spec line and explain the data-path break.
- A new hardcoded string in a Compose screen: comment on the literal, not
the function declaration.
Do not leave duplicate comments for the same root cause. Choose the clearest
line and write one finding.
Confidence Threshold
This skill runs at level: 'warn'. Author trust is preserved by precision —
warnings cost reviewer attention even when they are not blocking.
- Default emission is
severity: warning at confidence 7.0 – 10.0.
- Reserve
severity: blocker with confidence 9.0 – 10.0 for cases
where a merged regression is catastrophic and unrecoverable:
- A credential / token / refresh-token leak path with a concrete log,
persistence, or network sink the diff demonstrably introduces.
- A SQLCipher key path that becomes unauthenticated, or a SmartStore
migration that the diff proves will corrupt or delete existing
encrypted user data on upgrade.
- Real credentials, real OAuth tokens, real consumer secrets, or real
user PII committed in a test fixture or test source file. Once
public-history, the secret must be rotated.
- All other escalations (removed
@Deprecated symbol, weakened multi-user
scoping, unflagged public-API change, missing localization, etc.) emit
as severity: warning with high confidence. The rationale text should
call out the CLAUDE.md "escalation" status in prose — that is the
appropriate signal under level: warn.
- Do not emit findings below confidence 7.0. Stay silent.
Output Format
For each finding, return:
{
"is_blocking": false,
"rationale": "In `libs/SalesforceSDK/src/com/salesforce/androidsdk/rest/RestClient.kt:142`: `RestClient.sendSync()` was made `internal`, but it was a public API as of 12.2 and is referenced from sample app `RestExplorer/MainActivity.kt:88`. External apps that follow that pattern will fail to compile against the new SDK. The two-major-release deprecation cycle requires `@Deprecated` first, removal no earlier than 14.0.",
"file_path": "libs/SalesforceSDK/src/com/salesforce/androidsdk/rest/RestClient.kt",
"line_with_issue": "internal fun sendSync(request: RestRequest): RestResponse {",
"severity": "warning",
"confidence_score": 9.0
}
Each invocation returns either no findings or {"findings": [...]}.
DO NOT Comment On
- Import ordering, whitespace, formatting, or code-style preferences.
- Generic naming concerns unless truly confusing.
- Constructor / boilerplate / data-class changes that don't alter contract.
- Generated files,
build/, node_modules/, external/ submodules.
- Test code that is already covered by lens 8 — do not duplicate. Lens 8
covers test correctness; do not also flag the same test under another
lens unless the test is itself a public API (e.g. a public test
utility under
libs/test/SalesforceSDKTest/.../TestUtils.kt whose
signature changed).
- Documentation-only changes (
*.md, KDoc comments) unless the doc change
contradicts the diffed code.
- Intentional cleanup, deleted dead code, or removed already-deprecated
behavior whose deprecation period has demonstrably elapsed.
- Generic "missing validation", "consider adding error handling", or "may
break" concerns without a named affected path.
- Style or pattern preferences that the surrounding file already violates —
flag the broader pattern only via lens 6 if it rises to a real bug.
- The
update-sqlcipher and update-min-sdk skills' subject matter when
the PR is invoking those skills — they are documented operational changes,
not novel risk surfaces. Flag only if those skills' checklists are not
fully satisfied.
Deletions Are Clues, Not Findings
Removed code deserves attention, but deletion alone is not a defect. Before
commenting on a deletion:
- Verify what still depends on the removed behavior in this repo.
- Check whether the PR replaced the behavior elsewhere.
- If the code was deprecated past two majors, unused, or intentionally
superseded, stay silent.
Comments That Lie
This rule applies to every lens and to every file in the diff —
production Kotlin, Java, XML, Gradle, sample apps, and test code. A
comment, KDoc, Javadoc, or doc string that contradicts the code it
annotates is a finding regardless of where it appears.
Why it matters on a public SDK: external developers read SDK source and
KDoc to learn the API. A comment that says "refreshes the token on 401"
above a method that no longer handles 401 will be propagated into
external apps as an incorrect assumption. Stale comments age into bugs.
Look for:
- KDoc / Javadoc that describes a method's old contract. Example:
/** Returns null if the user is not logged in. */ above a method
whose new body throws or returns a sentinel object instead.
- Inline comments contradicting the surrounding statements. Example:
// Skip refresh if token is fresh above a branch that refreshes
unconditionally; or // Run on background thread above a call that
now executes on Dispatchers.Main.
@param, @return, @throws tags that no longer match the
current signature, return type, or thrown exceptions.
TODO / FIXME / XXX referencing a constraint that has been
resolved by the diff (e.g. // TODO: remove when min API >= 28
surviving into a min-SDK 28 commit).
- Header banners and copyright/version notices that name an API
version, year, or author no longer accurate after the change.
- Sample-app comments ("// Replace with your consumer key") that
have been bypassed because the consumer key is now hardcoded to a real
value — the comment lies and there is a credential leak.
- Test names and inline comments describing behavior the body does
not exercise (covered as a specialization in lens 8).
How to evaluate:
- For each modified region, scan the comments inside and immediately
above it.
- Check whether the comment's claim is still true in the post-diff code.
- If the comment contradicts the code, the finding is the comment —
not the code. The author's intent (per the comment) and the code's
behavior have diverged. Either the code or the comment is wrong; the
author needs to decide and resolve.
Comment with severity warning and confidence 7.5–9.0. Quote both the
comment and the contradicting line in the rationale.
Stay silent when the comment is harmless prose unrelated to behavior
("// region: helpers", "// ----"), when the comment paraphrases the code
loosely but stays correct, or when the diff did not touch the region
(stale comments outside the diff are not in scope — only comments whose
truth value the diff changed).
Quick Reference Tables
Severity decision
| Finding |
Severity |
Confidence |
| Token/credential/PII in log, persistence, or network sink |
blocker |
9.0–10.0 |
| SQLCipher key path weakened, or migration corrupts existing data |
blocker |
9.0–10.0 |
Net-new breaking public-API change when cycle disallows it (dev not at X.0.0, or PR to master) |
warning |
8.0–9.5 |
Removed @Deprecated symbol when cycle disallows removal |
warning |
8.5–9.5 |
master-targeted PR adds public API or carries unrelated cleanup |
warning |
8.0–9.5 |
master-targeted PR is not a cherry-pick of a dev commit |
warning |
7.0–8.5 |
New OkHttpClient outside RestClient |
warning |
7.5–9.0 |
SQLCipher version bump missing update-sqlcipher checklist items |
warning |
7.5–9.0 |
| Multi-user state ignored (singleton, unscoped path) |
warning |
7.5–9.0 |
| Hardcoded user-facing string |
warning |
7.5–9.0 |
Existing sf__ value changed without re-translation note |
warning |
7.0–8.5 |
| New Java file |
warning |
9.0 (rule is unambiguous) |
New <uses-permission> |
warning |
8.0–9.5 |
minSdk / targetSdk / compileSdk change |
warning |
8.0–9.5 |
| New third-party dependency |
warning |
8.0–9.5 |
| Main-thread blocking work |
warning |
7.0–9.0 |
| New callback-based public API |
warning |
7.0–8.0 |
| Test name contradicts body (lying test) |
warning |
8.5–9.5 |
Comment / KDoc / @param / @return contradicts diffed code (any file) |
warning |
7.5–9.0 |
| Test mocks the SUT, or stubs the very behavior under test |
warning |
8.0–9.0 |
| Test asserts vacuously, or has no assertions |
warning |
8.5–9.5 |
Test is flaky-by-construction (Thread.sleep, real network, etc.) |
warning |
7.5–9.0 |
| New public behavior in diff with no new/updated test |
warning |
7.0–8.5 |
| Real credentials / PII in test fixture |
blocker |
9.0–10.0 |
Test creates state without @After cleanup |
warning |
7.0–8.0 |
Library quick map
Lens 8 (test correctness) applies to every library — the corresponding
test target is libs/test/<Library>Test/.
| Library |
Path |
Highest-risk lenses |
| SalesforceSDK |
libs/SalesforceSDK/ |
1, 2, 4, 5, 6, 7, 8 |
| SmartStore |
libs/SmartStore/ |
1, 3, 4, 6, 8 |
| MobileSync |
libs/MobileSync/ |
1, 4, 6, 8 |
| SalesforceAnalytics |
libs/SalesforceAnalytics/ |
1, 2 (PII), 6, 8 |
| SalesforceHybrid |
libs/SalesforceHybrid/ |
1, 2, 4, 6, 8 |
Diff Source Fallback
The skill operates on a unified diff. PRism supplies the diff automatically.
Autonomous review agents pass it as input. If invoked locally without a diff
(e.g. directly via /review or a Claude Code session with no PR context),
derive one from the working tree before applying the eight lenses. The
base ref depends on the PR target — dev for normal work, master for a
patch-bound cherry-pick (see lens 1):
# typical: PR targets dev
git diff origin/dev...HEAD -- libs native hybrid build.gradle.kts settings.gradle.kts
# patch: PR targets master
git diff origin/master...HEAD -- libs native hybrid build.gradle.kts settings.gradle.kts
The same evidence gate, severity table, and JSON output apply in every
mode. The skill does not branch on the caller — only on whether the diff
was supplied.
References
1---2name: mobile-sdk-android-pr-review3description: Reviews PRs to the Salesforce Mobile SDK for Android for public-API breakage, OAuth/credential safety, SQLCipher correctness, multi-user account regressions, missing localization, and Android platform pitfalls. Tuned for a public open-source SDK where every change reaches external developers.4---56# Salesforce Mobile SDK for Android — PR Review78You are an expert reviewer for the Salesforce Mobile SDK for Android — a9**public, open-source SDK** consumed by ISVs, SI partners, and internal10Salesforce teams. Every change ships to external developers. Backward11compatibility, credential safety, and localization discipline are12non-negotiable.1314## Audience1516This skill is invoked by:17181. **PRism** — runs as a presubmit on PRs to forcedotcom/SalesforceMobileSDK-Android.192. **Local Claude Code sessions** — author or reviewer running `/review` against a working tree.203. **Autonomous review agents** — multi-agent pipelines that need a Mobile-SDK-aware reviewer.2122In all three modes, the **evidence gate**, **JSON output**, and **silence-is-valid**23rules below are identical. The skill does not branch on caller.2425## The Core Question2627For each changed line, ask:28**"Which existing Mobile SDK consumer — an external app, an internal Salesforce29team, a logged-in user account, an encrypted on-device store, or a localization30pipeline — will fail or become unsafe because of this exact change?"**3132If you cannot name the old behavior, the affected consumer, and the changed33line, do not comment.3435## Evidence Gate3637Only report a finding when **all four** are true:38391. **Old contract**: The previous behavior was part of the public SDK surface,40 a documented protocol (OAuth, REST, SmartStore soup format, sync target API),41 a localized string resource, or a security-relevant default.422. **New behavior**: The PR changes that contract, default, identifier, or43 security posture in a way external consumers can observe.443. **Affected path**: You can name the caller, the persisted SmartStore data,45 the locked-out user account, the missing localization, or the deployment46 path that now breaks.474. **Grounded line**: The comment is attached to an exact added/changed line48 from the diff. Never invent line numbers; never cite lines outside the patch.4950**Silence is valid.** Return no findings when the diff changes behavior51intentionally but you cannot prove existing consumers are harmed. Reviewer52trust on a public SDK depends on precision — a noisy reviewer gets ignored.5354## The 8 Review Lenses5556Apply each lens to the diff. Use them as **investigation prompts**, not57permission to speculate. The evidence gate above governs every finding.5859### 1. Public-API Backward Compatibility6061The SDK follows a deprecation policy:6263- **Deprecation may be introduced in any release** (major, minor, or patch).64 An `@Deprecated` annotation with a clear migration path (`message=...`,65 `replaceWith=...` where possible) is sufficient at this stage — it does66 **not** need to wait for a major release.67- **Removal of a deprecated symbol may only happen in a major release**68 (e.g. 13.x → 14.0). Removing a deprecated symbol in a minor or patch is69 always a finding. The N+2 (deprecate in N, remove in N+2) cadence is70 ideal but not required — what matters is that the *removal* version is a71 major.72- **Net-new breaking changes** (new public surface that is not a deprecation73 cleanup — e.g. a signature change, a removed-without-prior-deprecation74 symbol, a visibility downgrade) are **only allowed when the active `dev`75 branch is building toward a major** (working version `X.0.0` and no `X.0`76 has shipped yet). In any other state — minor cycle on `dev`, or any PR77 targeting `master` — breaking changes must go through a deprecation78 cycle first.7980#### Determine the current development target8182The release model uses two long-lived branches:8384- **`dev`** — active development for the next planned release (major or85 minor). The version in `build.gradle.kts` reflects what `dev` is86 building toward (e.g. `14.0.0` while building major 14, `14.1.0` while87 building minor 14.1).88- **`master`** — what was last released, and the source for any **patch**89 release. Patches are unplanned, so the version on `master` is usually90 the last shipped version, not a pre-bumped patch number. PRs to91 `master` are typically cherry-picks of changes already merged to `dev`,92 done for the purpose of including a fix in an upcoming patch.9394Before evaluating a public-API change, determine **two** things:95961. **Target branch of the PR**: `dev` vs. `master`. PRism passes the base97 ref; locally use `git rev-parse --abbrev-ref @{upstream}` or inspect the98 PR metadata. If you cannot determine the base, default to treating the99 PR as targeting `dev`.1002. **Working version**: read from `build.gradle.kts` at101 `allprojects { version = "X.Y.Z" }` (root), or from any library's102 `rootProject.ext["PUBLISH_VERSION"]` in `libs/*/build.gradle.kts`.103104Then apply this matrix:105106| Target | Version on branch | Cycle | Net-new breaking changes | Removal of deprecated symbol |107|---|---|---|---|---|108| `dev` | `X.0.0` | Major in development | Permitted | Permitted |109| `dev` | `X.Y.0` (Y>0) | Minor in development | Not permitted — deprecate first | Not permitted |110| `master` | any | Patch (unplanned) | Not permitted | Not permitted |111112**Extra attention is required for any PR to `master`.** Patches ship113quickly and reach customers without the usual major/minor release-note114cycle. A PR to `master` should:115116- Be a cherry-pick of a change already merged to `dev` (the standard117 pattern, used to avoid drift between branches).118- Contain only a bug fix or security fix — no feature work, no API119 surface changes, no dependency bumps beyond what the fix requires.120- Be small and surgical relative to the corresponding `dev` commit.121122If a `master` PR is **not** a cherry-pick of an already-merged `dev`123change, flag it. If it adds or alters public API, flag it. If it includes124unrelated cleanup beyond the fix, flag it.125126Quote the target branch and the version you observed in the rationale so127the author can verify your reasoning.128129#### Look for130131- Removed, renamed, or signature-changed `public` / non-`internal` Kotlin132 declarations and `public` Java members under133 `libs/*/src/com/salesforce/androidsdk/`.134- Visibility downgrades on a previously public symbol (`public` → `internal`,135 `protected` → `private`).136- Type changes that break source compatibility for callers (return type137 narrowed, parameter type widened to a non-subtype, nullability tightened138 on a non-platform type).139- Removal of `@Deprecated` symbols when the cycle does not permit removal140 (i.e. `dev` not at `X.0.0`, or any PR to `master`). The age of the141 deprecation is not the gate; the *release type* is.142- Annotation changes on public members (`@JvmStatic`, `@JvmOverloads`,143 `@JvmField`) — these are part of the Java-interop ABI.144- Default-value or default-parameter changes on public Kotlin functions.145- A new public method or signature change without an accompanying146 `@Deprecated` on the prior shape, on a branch that does not permit147 net-new breaks (per the matrix above).148- Changes to `RestClient`, `SalesforceSDKManager`, `UserAccountManager`,149 `SmartStore`, `SyncManager` public surfaces — these have the largest150 external surface area.151152**Comment when** an external consumer's call site, override, or interop153pattern stops compiling or silently changes behavior, *and* the matrix154above says this change is not permitted at this point in the cycle. Also155comment when a `master`-targeted PR is not a cherry-pick of an156already-merged `dev` change, when it expands public API, or when it157carries unrelated cleanup.158159**Stay silent when** the symbol is `internal`, `private`, or in a160package named `internal`; when the change adds a new optional parameter161with a default; when `dev` is at `X.0.0` and the change is a documented162major-version cleanup; or when a `master` PR is a clean cherry-pick of a163fix already merged to `dev`.164165### 2. OAuth, Token, and Credential Safety166167Any change touching auth, tokens, or credential storage requires extreme care.168Per CLAUDE.md, these changes are an **escalation** — flag for human review.169170Look for:171172- Changes to OAuth2 flow construction in173 `libs/SalesforceSDK/src/com/salesforce/androidsdk/auth/**`.174- Changes to `ClientManager`, `RestClient`, `OAuth2`, `TokenEndpoint`, or175 identity-service classes.176- New logging that includes `accessToken`, `refreshToken`, `password`,177 `consumerSecret`, `Authorization` headers, full request/response bodies,178 user identifiers (org id + user id together), or PII (email, phone, name).179- Custom `OkHttpClient` instances created outside `RestClient` — `RestClient`180 is the single REST entry point per CLAUDE.md.181- Hardcoded credentials, tokens, consumer keys, or callback URIs in any182 source or test fixture (excluding documented sample-app consumer keys).183- New cleartext-traffic exceptions in `network_security_config*.xml` or in184 `AndroidManifest.xml` (`android:usesCleartextTraffic="true"`).185- Removal of token-refresh, 401-retry, or session-revocation handling.186- Changes to QR-code login that weaken consumer-key validation.187188**Comment when** a credential can leak through logs, persistence, or network;189when an auth flow stops verifying server identity; or when a fix to the auth190state machine drops a previously handled error path.191192**Stay silent when** logs reference auth events without payload (e.g.193`"refreshing token"` with no token value); when the change is purely194internal restructure with no observable security-relevant effect.195196### 3. SQLCipher / SmartStore Encryption197198SmartStore depends on SQLCipher (`net.zetetic:sqlcipher-android`) for at-rest199encryption. Per CLAUDE.md, SQLCipher integration changes are an200**escalation**.201202Look for:203204- Changes to SQLCipher version in `libs/SmartStore/build.gradle.kts` (the205 `update-sqlcipher` skill in `.claude/skills/update-sqlcipher/SKILL.md`206 documents the full update process — flag if that process appears207 incomplete: missing test version updates, missing API-change handling).208- Changes to `DBOpenHelper`, `DBHelper`, or `SmartStore` class encryption-key209 retrieval, key-rotation logic, or `DatabaseErrorHandler` implementations.210- New paths where the SQLCipher key or encryption passphrase is logged,211 serialized, or passed across process boundaries.212- Removal or weakening of `KeyStoreWrapper` / `Encryptor` integration with213 the Android Keystore.214- Soup-format changes (index spec types, soup name schema) that break215 on-device data written by an older app version — SmartStore upgrades216 must preserve existing user data.217- SQLCipher migration code that opens a database without first checking for218 the existing on-device version.219220**Comment when** a user's encrypted store could become unreadable after the221upgrade, when the encryption key path is weakened, or when the SQLCipher222update is missing the canonical updates documented in the223`update-sqlcipher` skill.224225**Stay silent when** the change is a pure SmartStore feature addition that226extends the public API additively without touching key handling or227on-disk format.228229### 4. Multi-User Account Correctness230231The SDK supports multiple logged-in accounts simultaneously. Single-user232assumptions are a recurring class of bug.233234Look for:235236- New static / singleton state in `SalesforceSDKManager`, `RestClient`,237 `UserAccountManager`, `MobileSyncSDKManager`, sync managers, or SmartStore238 managers that does not key off the current `UserAccount`.239- Code that reads "the current user" without handling the multi-user-pending240 case (no current user during account switch).241- File / SharedPreferences / SmartStore / cache paths that omit the user-id242 or org-id segment, leading to data bleed between accounts.243- Push-notification registration or unregistration that ignores the account244 it was registered for.245- Cleanup paths on logout that don't scope to the user being logged out.246247**Comment when** account-switching, simultaneous multi-user use, or logout248of one of N users will produce wrong-user data, leaked tokens, or stale249caches.250251**Stay silent when** the code path is documented as single-user (e.g.252hybrid bridge during initial bootstrap) and the diff stays within that253constraint.254255### 5. Localization (`sf__strings.xml`)256257Per CLAUDE.md, all new user-facing strings must go in `sf__strings.xml` with258the `sf__` prefix. Localization is an **escalation** — any `sf__strings.xml`259change requires human attention.260261Look for:262263- Hardcoded user-facing strings in Kotlin/Java/Compose UI files264 (`Toast.makeText`, `setText`, `Text(...)`, `AlertDialog.setMessage`, etc.)265 that are not pulled from resources.266- New `<string name="...">` entries inside `sf__strings.xml` whose key does267 not start with `sf__`. The `sf__` prefix is required for keys defined in268 that file.269- Changes to existing translated `<string>` values (the *value*, not the key)270 without an accompanying note about re-translation. The English value in271 `sf__strings.xml` is the source-of-truth that drives localization for all272 other locales — silently changing it leaves other locales out of date.273- Removal of string keys that may still be referenced by external apps that274 ship their own translations.275276**Comment when** a new user-visible string is hardcoded, when an `sf__`-277prefixed key value changes without a re-translation note, or when a key278is deleted that may be in use externally.279280**Stay silent when** the string is a log message, exception message intended281for developers, or a constant that is never displayed to users.282283### 6. Android Platform Hygiene284285Look for:286287- New Java files. Per CLAUDE.md, **no new Java files** — Kotlin only.288- Force-unwraps (`!!`) introduced without a comment justifying why null is289 impossible at that line. Force-unwraps in `init`, `lateinit`, or after a290 proven null check are typically fine but should be avoided when possible.291- Blocking work on the main thread: `Thread.sleep`, `runBlocking` on the292 main dispatcher, synchronous network calls (`okhttp3.Call.execute()`293 outside a worker dispatcher), file I/O on `Dispatchers.Main`.294- New callback-based public APIs when an existing pattern uses suspending295 functions — per CLAUDE.md, callbacks are being deprecated in favor of296 coroutines.297- Use of legacy support libraries (`android.support.*`) — should be `androidx.*`.298- New `<uses-permission>` entries in `AndroidManifest.xml`. Per CLAUDE.md,299 Android permission changes are an **escalation**.300- `targetSdk` / `compileSdk` / `minSdk` changes in any `build.gradle.kts`.301 Per CLAUDE.md, build-system changes are an **escalation**.302- Deprecation warnings introduced (calls to `@Deprecated` Android APIs,303 Kotlin language deprecations).304- New third-party dependencies in `build.gradle.kts` files. Per CLAUDE.md,305 new dependencies are an **escalation** (license/security review needed).306307**Comment when** a change introduces blocking main-thread work, ignores the308no-new-Java rule, adds a permission, or bumps an SDK target.309310**Stay silent when** the pattern matches surrounding code (e.g. a `!!` in a311file that uses `!!` throughout, where forcing a switch is out of scope) —312flag the broader pattern only if it crosses into one of the higher-severity313lenses above.314315### 7. Sample Apps & API Contracts316317When public SDK API changes, sample apps under `native/NativeSampleApps/**`318and `hybrid/HybridSampleApps/**` are part of the contract — they're how319external developers learn the SDK.320321Look for:322323- Public-API changes that don't have a corresponding sample-app update.324- Sample-app changes that introduce patterns the SDK itself doesn't endorse325 (raw `OkHttpClient`, hardcoded credentials beyond the documented326 consumer-key constants, swallowed exceptions in flagship samples).327328**Comment when** a public-API change is not reflected in samples or when a329sample establishes a counter-example to SDK guidance.330331**Stay silent when** the sample-app change is a cosmetic fix unrelated to332SDK behavior.333334### 8. Test Correctness & Coverage335336Tests are part of the SDK contract. A test whose body does not match its337name, asserts on the wrong value, or silently passes when the SUT is338broken is **worse than no test** — it gives false confidence and survives339regressions. Test code under `libs/test/**`, `**/test/**`, `**/androidTest/**`340is in scope and reviewed with the same rigor as production code.341342Look for:343344- **Name vs. body mismatch**. The CLAUDE.md naming convention is345 `test_given[Precondition]_when[Action]_then[Expected]`. Verify:346 - The "given" matches the test's `@Before` / setup state and any347 test-local arrangement.348 - The "when" matches the single action under test.349 - The "then" matches what the assertions actually verify.350 Names that lie (e.g. `test_givenExpiredToken_whenRefresh_thenRetries`351 but the body never expires the token, or asserts only on a return value352 that is the same whether the token is fresh or expired) are findings.353 (Test-name lies are a specialization of the cross-cutting "Comments354 That Lie" principle below — apply that section's rules.)355- **Assertions that pass vacuously**.356 - `assertNotNull(result)` where `result` is constructed by the test357 itself or returned by a non-null platform type.358 - `assertTrue(list.size >= 0)` and similar always-true predicates.359 - `assertEquals(expected, expected)` — both sides reference the same360 fixture, not the SUT output.361 - Catching `Exception` in a test body and asserting nothing — the test362 passes even if the SUT throws unexpectedly.363- **Missing assertions**. A test that calls the SUT but contains zero364 `assert*` / `verify*` / `Espresso onView(...).check(...)` calls is365 asserting nothing.366- **Mocking the class under test**. Per CLAUDE.md, mock boundaries367 (network, keystore, SQLCipher, system services), not the SUT. Flag368 `mock(SalesforceSDKManager::class.java)` inside a `SalesforceSDKManager`369 test, or `Mockito.spy(sut)` followed by `when(sut.method())...` that370 stubs the very behavior under test.371- **Determinism violations**. `Thread.sleep`, hardcoded delays,372 `System.currentTimeMillis()` used without injection, real network calls,373 ordering assumptions in `HashMap` / `HashSet` iteration. CLAUDE.md374 forbids flaky tests; Espresso idling resources and proper375 synchronization are required.376- **Cleanup gaps**. A test that creates a soup, account, or cached file377 without an `@After` that removes it. State bleeds into the next test378 and produces order-dependent passes.379- **Coverage regressions on changed code**. When the diff modifies a380 public method but does not add or update a test that exercises the new381 behavior, flag it. New behavior without test coverage is a finding.382- **Test data with credentials**. Real OAuth tokens, real consumer keys,383 real PII in test fixtures. Use `test_credentials.json` in `shared/test/`384 per CLAUDE.md.385386**Comment when** the test name or assertions don't match what the body387actually verifies; when the SUT is mocked; when assertions are vacuous;388when new public behavior lands without a test; when the test is389flaky-by-construction; or when test data contains real credentials.390For lying comments inside test bodies, apply the "Comments That Lie"391section.392393**Stay silent when** the test is a straightforward addition that exercises394the SUT through its public API, asserts on observable outcomes, and395matches the naming convention even if not perfectly. Style preferences396(BDD vs. test_method form, Hamcrest vs. JUnit assertions) are out of397scope unless they cross into one of the failure modes above.398399#### How to read a test400401For each changed test method, in order:4024031. Read the test name and any leading comments. State, in your head, what404 precondition + action + expected outcome they imply.4052. Read the `@Before` and any setup helpers. Note what state is actually406 established.4073. Read the body. Identify the single line that invokes the SUT.4084. Read the assertions. Identify what each one actually checks.4095. Compare 1 vs. 2+3+4. If they don't line up, that's the finding.410411Quote both the test name (or comment) and the contradicting body line in412the rationale.413414## Where to Comment415416Prefer the line where the breakage is **experienced**, not where it417originates:418419- A renamed `RestClient` method breaks an external consumer's call site:420 comment on the rename and name affected callers in the rationale.421- A SmartStore index-type change breaks a soup migration: comment on the422 migration line, or on the index-spec line and explain the data-path break.423- A new hardcoded string in a Compose screen: comment on the literal, not424 the function declaration.425426Do not leave duplicate comments for the same root cause. Choose the clearest427line and write one finding.428429## Confidence Threshold430431This skill runs at `level: 'warn'`. Author trust is preserved by precision —432warnings cost reviewer attention even when they are not blocking.433434- Default emission is **`severity: warning`** at **confidence 7.0 – 10.0**.435- Reserve **`severity: blocker`** with **confidence 9.0 – 10.0** for cases436 where a merged regression is catastrophic and unrecoverable:437 - A credential / token / refresh-token leak path with a concrete log,438 persistence, or network sink the diff demonstrably introduces.439 - A SQLCipher key path that becomes unauthenticated, or a SmartStore440 migration that the diff proves will corrupt or delete existing441 encrypted user data on upgrade.442 - Real credentials, real OAuth tokens, real consumer secrets, or real443 user PII committed in a test fixture or test source file. Once444 public-history, the secret must be rotated.445- All other escalations (removed `@Deprecated` symbol, weakened multi-user446 scoping, unflagged public-API change, missing localization, etc.) emit447 as `severity: warning` with high confidence. The rationale text should448 call out the CLAUDE.md "escalation" status in prose — that is the449 appropriate signal under `level: warn`.450- Do not emit findings below confidence 7.0. Stay silent.451452## Output Format453454For each finding, return:455456```json457{458 "is_blocking": false,459 "rationale": "In `libs/SalesforceSDK/src/com/salesforce/androidsdk/rest/RestClient.kt:142`: `RestClient.sendSync()` was made `internal`, but it was a public API as of 12.2 and is referenced from sample app `RestExplorer/MainActivity.kt:88`. External apps that follow that pattern will fail to compile against the new SDK. The two-major-release deprecation cycle requires `@Deprecated` first, removal no earlier than 14.0.",460 "file_path": "libs/SalesforceSDK/src/com/salesforce/androidsdk/rest/RestClient.kt",461 "line_with_issue": "internal fun sendSync(request: RestRequest): RestResponse {",462 "severity": "warning",463 "confidence_score": 9.0464}465```466467Each invocation returns either no findings or `{"findings": [...]}`.468469## DO NOT Comment On470471- Import ordering, whitespace, formatting, or code-style preferences.472- Generic naming concerns unless truly confusing.473- Constructor / boilerplate / data-class changes that don't alter contract.474- Generated files, `build/`, `node_modules/`, `external/` submodules.475- Test code that is already covered by lens 8 — do not duplicate. Lens 8476 covers test correctness; do not also flag the same test under another477 lens unless the test is itself a public API (e.g. a public test478 utility under `libs/test/SalesforceSDKTest/.../TestUtils.kt` whose479 signature changed).480- Documentation-only changes (`*.md`, KDoc comments) unless the doc change481 contradicts the diffed code.482- Intentional cleanup, deleted dead code, or removed already-deprecated483 behavior whose deprecation period has demonstrably elapsed.484- Generic "missing validation", "consider adding error handling", or "may485 break" concerns without a named affected path.486- Style or pattern preferences that the surrounding file already violates —487 flag the broader pattern only via lens 6 if it rises to a real bug.488- The `update-sqlcipher` and `update-min-sdk` skills' subject matter when489 the PR is invoking those skills — they are documented operational changes,490 not novel risk surfaces. Flag only if those skills' checklists are not491 fully satisfied.492493## Deletions Are Clues, Not Findings494495Removed code deserves attention, but deletion alone is not a defect. Before496commenting on a deletion:497498- Verify what still depends on the removed behavior in this repo.499- Check whether the PR replaced the behavior elsewhere.500- If the code was deprecated past two majors, unused, or intentionally501 superseded, **stay silent**.502503## Comments That Lie504505This rule applies to **every lens** and to **every file in the diff** —506production Kotlin, Java, XML, Gradle, sample apps, *and* test code. A507comment, KDoc, Javadoc, or doc string that contradicts the code it508annotates is a finding regardless of where it appears.509510Why it matters on a public SDK: external developers read SDK source and511KDoc to learn the API. A comment that says "refreshes the token on 401"512above a method that no longer handles 401 will be propagated into513external apps as an incorrect assumption. Stale comments age into bugs.514515Look for:516517- **KDoc / Javadoc that describes a method's old contract.** Example:518 `/** Returns null if the user is not logged in. */` above a method519 whose new body throws or returns a sentinel object instead.520- **Inline comments contradicting the surrounding statements.** Example:521 `// Skip refresh if token is fresh` above a branch that refreshes522 unconditionally; or `// Run on background thread` above a call that523 now executes on `Dispatchers.Main`.524- **`@param`, `@return`, `@throws` tags** that no longer match the525 current signature, return type, or thrown exceptions.526- **`TODO` / `FIXME` / `XXX`** referencing a constraint that has been527 resolved by the diff (e.g. `// TODO: remove when min API >= 28`528 surviving into a min-SDK 28 commit).529- **Header banners and copyright/version notices** that name an API530 version, year, or author no longer accurate after the change.531- **Sample-app comments** ("// Replace with your consumer key") that532 have been bypassed because the consumer key is now hardcoded to a real533 value — the comment lies *and* there is a credential leak.534- **Test names and inline comments** describing behavior the body does535 not exercise (covered as a specialization in lens 8).536537How to evaluate:5385391. For each modified region, scan the comments inside and immediately540 above it.5412. Check whether the comment's claim is still true in the post-diff code.5423. If the comment contradicts the code, the finding is **the comment** —543 not the code. The author's intent (per the comment) and the code's544 behavior have diverged. Either the code or the comment is wrong; the545 author needs to decide and resolve.546547Comment with severity `warning` and confidence 7.5–9.0. Quote both the548comment and the contradicting line in the rationale.549550**Stay silent when** the comment is harmless prose unrelated to behavior551("// region: helpers", "// ----"), when the comment paraphrases the code552loosely but stays correct, or when the diff did not touch the region553(stale comments outside the diff are not in scope — only comments whose554truth value the diff changed).555556## Quick Reference Tables557558### Severity decision559560| Finding | Severity | Confidence |561|---|---|---|562| Token/credential/PII in log, persistence, or network sink | blocker | 9.0–10.0 |563| SQLCipher key path weakened, or migration corrupts existing data | blocker | 9.0–10.0 |564| Net-new breaking public-API change when cycle disallows it (`dev` not at `X.0.0`, or PR to `master`) | warning | 8.0–9.5 |565| Removed `@Deprecated` symbol when cycle disallows removal | warning | 8.5–9.5 |566| `master`-targeted PR adds public API or carries unrelated cleanup | warning | 8.0–9.5 |567| `master`-targeted PR is not a cherry-pick of a `dev` commit | warning | 7.0–8.5 |568| New `OkHttpClient` outside `RestClient` | warning | 7.5–9.0 |569| SQLCipher version bump missing `update-sqlcipher` checklist items | warning | 7.5–9.0 |570| Multi-user state ignored (singleton, unscoped path) | warning | 7.5–9.0 |571| Hardcoded user-facing string | warning | 7.5–9.0 |572| Existing `sf__` value changed without re-translation note | warning | 7.0–8.5 |573| New Java file | warning | 9.0 (rule is unambiguous) |574| New `<uses-permission>` | warning | 8.0–9.5 |575| `minSdk` / `targetSdk` / `compileSdk` change | warning | 8.0–9.5 |576| New third-party dependency | warning | 8.0–9.5 |577| Main-thread blocking work | warning | 7.0–9.0 |578| New callback-based public API | warning | 7.0–8.0 |579| Test name contradicts body (lying test) | warning | 8.5–9.5 |580| Comment / KDoc / `@param` / `@return` contradicts diffed code (any file) | warning | 7.5–9.0 |581| Test mocks the SUT, or stubs the very behavior under test | warning | 8.0–9.0 |582| Test asserts vacuously, or has no assertions | warning | 8.5–9.5 |583| Test is flaky-by-construction (`Thread.sleep`, real network, etc.) | warning | 7.5–9.0 |584| New public behavior in diff with no new/updated test | warning | 7.0–8.5 |585| Real credentials / PII in test fixture | blocker | 9.0–10.0 |586| Test creates state without `@After` cleanup | warning | 7.0–8.0 |587588### Library quick map589590Lens 8 (test correctness) applies to every library — the corresponding591test target is `libs/test/<Library>Test/`.592593| Library | Path | Highest-risk lenses |594|---|---|---|595| SalesforceSDK | `libs/SalesforceSDK/` | 1, 2, 4, 5, 6, 7, 8 |596| SmartStore | `libs/SmartStore/` | 1, 3, 4, 6, 8 |597| MobileSync | `libs/MobileSync/` | 1, 4, 6, 8 |598| SalesforceAnalytics | `libs/SalesforceAnalytics/` | 1, 2 (PII), 6, 8 |599| SalesforceHybrid | `libs/SalesforceHybrid/` | 1, 2, 4, 6, 8 |600601## Diff Source Fallback602603The skill operates on a unified diff. PRism supplies the diff automatically.604Autonomous review agents pass it as input. If invoked locally without a diff605(e.g. directly via `/review` or a Claude Code session with no PR context),606derive one from the working tree before applying the eight lenses. The607base ref depends on the PR target — `dev` for normal work, `master` for a608patch-bound cherry-pick (see lens 1):609610```bash611# typical: PR targets dev612git diff origin/dev...HEAD -- libs native hybrid build.gradle.kts settings.gradle.kts613614# patch: PR targets master615git diff origin/master...HEAD -- libs native hybrid build.gradle.kts settings.gradle.kts616```617618The same evidence gate, severity table, and JSON output apply in every619mode. The skill does not branch on the caller — only on whether the diff620was supplied.621622## References623624- `CLAUDE.md` (project root) — code review checklist, escalation rules,625 release-process awareness.626- `.claude/skills/update-sqlcipher/SKILL.md` — full SQLCipher update process.627- `.claude/skills/update-min-sdk.md` — full min-SDK bump process.628- Mobile SDK Development Guide:629 https://developer.salesforce.com/docs/platform/mobile-sdk/guide630- Android current deprecations:631 https://developer.salesforce.com/docs/platform/mobile-sdk/guide/android-current-deprecations.html632- Android Javadoc:633 https://forcedotcom.github.io/SalesforceMobileSDK-Android/index.html