Skip to content
Agentmart
Submit a tool

analytics-and-consent

Gates analytics on opt-in consent: no SDK before a yes, typed events without personal data, ATT.

Skill by zakariaf

IndexedMIT★ 11
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/zakariaf-analytics-and-consent

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Gates analytics on opt-in consent: no SDK before a yes, typed events without personal data, ATT. Use when adding analytics, events, screen views or a usage-data toggle. Not for crashes (crash-reporting-and-observability).

Use cases: data

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 26, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found
Runs as
Node.js package

Network hosts it talks to 4

  • analytics.example.test · found in code
  • codeload.github.com · found in install command
  • eu.i.posthog.com · found in code
  • us.i.posthog.com · found in code

Files it touches

  • ~/.claude/skills/analytics-and-consent

What's inside

Skill name
analytics-and-consent
Description
Gates analytics on opt-in consent: no SDK before a yes, typed events without personal data, ATT. Use when adding analytics, events, screen views or a usage-data toggle. Not for crashes (crash-reporting-and-observability).
License
MIT
Instructions
232 lines in SKILL.md · view on GitHub

Before installing

  • Ships executable scripts

Files (82)

  • SKILL.md
  • analytics-facts.json · assets/
  • shared.json · assets/
  • app-config-analytics.ts · examples/
  • create-root-kits.ts · examples/
  • posthog-analytics-adapter.ts · examples/
  • usage-data-prompt.tsx · examples/
  • use-tasks-model.ts · examples/
  • use-usage-data-model.ts · examples/
  • att-and-tracking.md · references/
  • consent-gate.md · references/
  • event-catalog.md · references/
  • modes-keys-and-testing.md · references/
  • store-declarations.md · references/
  • vendors-and-sdk-facts.md · references/
  • check-analytics-behaviour.mjs · scripts/
  • check-analytics.mjs · scripts/
  • check-lib.mjs · scripts/
  • analytics-repo.mjs · scripts/lib/
  • analytics-rules.mjs · scripts/lib/
  • analytics-scenarios.mjs · scripts/lib/
  • app-modules.mjs · scripts/lib/
  • source-scan.mjs · scripts/lib/
  • ts-literal.mjs · scripts/lib/
  • selftest.mjs · scripts/
  • expo-tracking-transparency.ts · templates/__mocks__/
  • analytics.json · templates/contracts/
  • analytics.schema.json · templates/contracts/
  • analytics-manifest.test.ts · templates/scripts/analytics/
  • analytics-manifest.ts · templates/scripts/analytics/
  • analytics-config.test.ts · templates/src/config/
  • analytics-config.ts · templates/src/config/
  • app-variant.test.ts · templates/src/config/
  • app-variant.ts · templates/src/config/
  • result.test.ts · templates/src/core/
  • result.ts · templates/src/core/
  • use-screen-views.test.tsx · templates/src/hooks/
  • use-screen-views.ts · templates/src/hooks/
  • ar.json · templates/src/i18n/catalogs/
  • en.json · templates/src/i18n/catalogs/
  • analytics-consent-store-port.ts · templates/src/services/analytics/
  • analytics-consent.test.ts · templates/src/services/analytics/
  • analytics-consent.ts · templates/src/services/analytics/
  • analytics-context.test.tsx · templates/src/services/analytics/
  • analytics-context.tsx · templates/src/services/analytics/
  • analytics-events.test.ts · templates/src/services/analytics/
  • analytics-events.ts · templates/src/services/analytics/
  • analytics-port.ts · templates/src/services/analytics/
  • analytics-root-kit.test.tsx · templates/src/services/analytics/
  • analytics-root-kit.tsx · templates/src/services/analytics/
  • analytics-sanitize.test.ts · templates/src/services/analytics/
  • analytics-sanitize.ts · templates/src/services/analytics/
  • create-analytics.test.ts · templates/src/services/analytics/
  • create-analytics.ts · templates/src/services/analytics/
  • fake-analytics.test.ts · templates/src/services/analytics/
  • fake-analytics.ts · templates/src/services/analytics/
  • kv-analytics-consent-adapter.test.ts · templates/src/services/analytics/
  • kv-analytics-consent-adapter.ts · templates/src/services/analytics/
  • screen-name.test.ts · templates/src/services/analytics/
  • screen-name.ts · templates/src/services/analytics/
  • …and 22 more

SKILL.md (first lines)

# Analytics and consent

Makes product analytics in an Expo app something a reviewer can trust: nothing leaves the device
until the user said yes, the vendor SDK exists only after that yes, every event comes from a typed
catalog that cannot carry personal data, analytics never tracks unless the app deliberately opts into
Apple's ATT, and the App Store and Google Play answers are generated from the same contract the code
is checked against. Two zero-dependency checkers prove it on the real code.

## Rules that must hold

1. **Opt-in before any analytics SDK exists.** The consent state starts `'unknown'`; `createAnalytics`
   drops every event until `setConsent('granted')` answers the current question, and calls `createSdk`
   (the vendor adapter) only then, in a build whose mode is not `off`. *Why:* Apple 5.1.1(ii) requires
   consent for usage data even when anonymous, ePrivacy requires it for an install id, and some SDKs
   contact their servers when constructed, opted out or not (PostHog 4.79.1 loads remote config).
2. **Withdrawing is as easy as giving, and stops and forgets.** A settings row calls `setConsent` at any
   time; a no runs `reset()` (anonymous and device ids and the unsent queue dropped) then `optOut()`, in
   that order, or `clearSdkStorage` when no SDK runs in the session, and `forget()` also clears the
   saved answer (account deletion). *Why:* 5.1.1(ii) and the
   GDPR require an easy withdrawal; PostHog's `reset()` clears a saved opt-out (the reverse order opts
   the user back in) and by default keeps the device id and the queue, which a flush still sends; an
   earlier session's ids stay on the device until something deletes them.
3. **A changed question asks again.** Raise `ANALYTICS_CONSENT_VERSION` whenever `collects`, `sdk` or
   `endpoints` in `contracts/analytics.json` change; an older answer reads as `'unknown'`. The contract's
   `consent: { version, covers }` records the fingerprint of what that version covers (`consent-covers`).
   *Why:* consent covers the purposes the user was shown, not the ones added later.
4. **Ads consent is not analytics consent, and analytics does not track by default.** Never derive the
   analytics answer from UMP's `canRequestAds`; the default contract says `tracking: false`, the adapter
   reads no advertising id, and `createAnalytics` gets no `tracking` option. The tracking profile passes
   the canonical `createExpoTrackingConsentAdapter` (never a second ATT import) and the adapter learns
   `isTrackingAllowed` only from an `'authorized'` answer. *Why:* UMP asks for Google's ad vendors, not
   the app's analytics vendor; tracking without ATT breaks 5.1.2(i).
5. **One vendor, one importer, created lazily in the root-kit registry.** `contracts/analytics.json`
   `sdk` is the only analytics package installed; only `src/services/analytics/<vendor>-analytics-adapter.ts`
   imports it; the adapter is created only inside `createAnalytics({ createSdk })` in
   `src/services/create-root-kits.ts` (app-startup-and-bootstrap's registry, part of the composition
   root), which registers `createAnalyticsKit`: features reach the port through `useAnalytics()`, never
   through an edited root file; the client is constructed inside the adapter's factory,
   never at module level; tests use the fakes. Auto-start SDKs (expo-insights; expo-observe unless the
   app writes `dispatchingEnabled: false`) and session replay are not allowed. *Why:* one seam to audit and upgrade, and a structural guarantee
   that nothing runs before consent.
6. **The SDK's privacy options are written out.** PostHog: `defaultOptIn: false`, `personProfiles:
   'never'` (anonymous), `disableGeoip: true` or a declared coarse location, no lifecycle events, no
   autocapture, no session replay, no `PostHogProvider`, error autocapture off, an explicit `host`,
   `disableRemoteFeatureFlags: true`, and a reset that is `reset([])` plus clearing the queue.
   *Why:* every default above either collects more than the catalog or survives a withdrawal.
7. **Every event is in the typed catalog, with a purpose, sent by a literal name.** `ANALYTICS_EVENTS`
   is plain data; names are snake_case, at most 40 characters, never vendor-reserved; `track()` is called
   from a model hook's handler as `track({ name: '<event>', props })`. *Why:* tsc and a text search then
   show every event and where it is sent; an event without a decision behind it is collection without a
   purpose.
8. **Events carry no personal data.** Properties are `enum`, `boolean`, bucketed `count` or `route`,
   never free text; names that mean ids, text, contact or location fail; `sanitizeEvent` drops anything
   that does not match at run time. *Why:* free strings are where titles, search terms and emails leak,
   usually by accident.
9. **Screens are route patterns.** `useScreenViews` sends `screenNameFromSegments(useSegments())`
   (`tasks/[id]`); nothing sends `usePathname()`, search params or URLs. *Why:* a concrete path holds
   record ids and whatever a deep link carried.
10. **Anonymous by default.** No identify, alias or person properties unless the contract says
    `identifies: true`, and then a linked `user-id` is declared. *Why:* the declarations and the consent
    text must match what is sent.
11. **Builds decide the project.** Development and preview are `off` (or `test`), production is `live`
    (or `off`); key and host come from the build's environment through `analyticsExtra()` into
    `expo.extra`, read back with `readAnalyticsExtra(extra, applicationId)`; never a key in code, never
    `EXPO_PUBLIC_ANALYTICS_*`. *Why:* testing builds must not pollute production data, E2E and audit
    builds must send nothing, and an EAS Update made for another variant must not redirect events.
12. **Store declarations come from the contract.** `collects` covers what the catalog, the SDK and the
    identity setting imply; `app.config.ts` writes `privacyManifests: { ...PRIVACY_MANIFESTS,
    NSPrivacyCollectedDataTypes: [...analyticsCollectedDataTypes(projectRoot)] }`; `contracts/privacy.json`
    `sdks["<sdk>"].collects` holds the same rows; the developer enters them in App Store Connect and
    Play's Data safety form. *Why:* a JavaScript SDK ships no privacy manifest, replacing the manifest
    drops its required-reason APIs (ITMS-91053), and answers that differ from the code get apps rejected.
13. **Apps for children ship no third-party analytics.** `audience: 'child-directed'` means `sdk:
    'none'`, no tracking, no identify; stop and ask before any analytics there. *Why:* App Review 1.3
    and 5.1.4 and Play Families forbid device information to third parties.
14. **Analytics is silent and never breaks a feature.** No port method throws or rejects; failures go
    to the error log; at most 100 events wait while the SDK starts. *Why:* analytics is never what the
    user came for, and an escaped rejection is lost in release builds.

## Workflow

1. **Decide with the developer** (stop and ask; never invent): the vendor (default PostHog), the region
   (`endpoints`), the audience, whether the app identifies users, and whether any SDK tracks. Read
   [references/vendors-and-sdk-facts.md](references/vendors-and-sdk-facts.md) and
   [references/att-and-tracking.md](references/att-and-tracking.md). The vendor project, its data
   retention and its data processing agreement are the developer's to set up.
2. **Install**: `npm install -E posthog-react-native@4.79.1` (outside the Expo map, pinned exactly) and
   `npx expo install expo-application` (`readAnalyticsExtra` compares the binary's application id);
   classify it in `contracts/architecture.json` (`"vendorSdks": { "posthog-react-native": "AnalyticsPort" }`,
   plus `vendor('posthog-react-native', 'AnalyticsPort')` in the ESLint config) and in the dependency policy.
3. **Copy the templates** at the paths their first lines name: `templates/src/services/analytics/`,
   `templates/src/config/analytics-config.ts` (+ test), `templates/src/hooks/use-screen-views.ts` (+ test),
   `templates/scripts/analytics/` (+ test), `templates/contracts/analytics.json` (+ schema; fill it). The
   synced canonical files (`src/services/consent/*`, `src/config/app-variant.ts`, `src/core/result.ts`, the
   tracking mock) are copied only when the app lacks them.
4. **Write the catalog** from the app's real questions: read
   [references/event-catalog.md](references/event-catalog.md); one event per decision, closed property kinds.
5. **Write the vendor adapter** after [examples/posthog-analytics-adapter.ts](examples/posthog-analytics-adapter.ts)
   and its test (mock the package; the only test that imports it).
6. **Register the kit** in app-startup-and-bootstrap's registry `src/services/create-root-kits.ts`
   ([examples/create-root-kits.ts](examples/create-root-kits.ts)): `createAnalytics` with the adapter call
   inside `createSdk`, then `createAnalyticsKit({ analytics, errorLog: data.errorLog })` in the list. The
   kit provides the port (`useAnalytics()`), sends screen views beside the root Stack and flushes on the
   way to the background; the root files stay unchanged. Wire `app.config.ts` ([examples/app-config-analytics.ts](examples/app-config-analytics.ts))
   and the EAS environments ([references/modes-keys-and-testing.md](references/modes-keys-and-testing.md)).
7. **Ask and let the user change the answer**: read [references/consent-gate.md](references/consent-gate.md);
   the prompt after onboarding and the settings row ([examples/use-usage-data-model.ts](examples/use-usage-data-model.ts),
   [examples/usage-data-prompt.tsx](examples/usage-data-prompt.tsx)), its keys merged into every catalog
   (`templates/src/i18n/catalogs/`); `forget()` from account deletion.
8. **Send events** from model handlers through `useAnalytics()` ([examples/use-tasks-model.ts](examples/use-tasks-model.ts));
   screen views and the background flush come with the kit (`analytics-root-kit.tsx`).
9. **Declare**: read [references/store-declarations.md](references/store-declarations.md); fill `collects`
   (the one source), copy it into `contracts/privacy.json` `sdks["<sdk>"].collects` when that contract
   exists, give the developer the App Privacy and Data safety rows; hand the manifest and network audit
   to privacy-and-store-declarations.
10. **Run the tests**: `npx jest src/services/analytics src/config/analytics-config.test.ts src/hooks/use-screen-views.test.tsx scripts/analytics --ci --maxWorkers=2`.
    An app with the port and catalog but no vendor yet sets `sdk: "none"` (every build off, nothing
    collected): check-analytics then notes that `manifest-wiring` and `consent-ui` wait for the vendor.
11. **Run the checkers**: `node ${CLAUDE_SKILL_DIR}/scripts/check-analytics.mjs .` and

From zakariaf/ReactNative-Skills, MIT.

More from zakariaf/ReactNative-Skills

Signals

Stars
11
Forks
2
Open issues
0
Last commit (this folder)
Oct 17, 2026 (12 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from zakariaf

Cite this page

analytics-and-consent skill. Agent Mart. https://agentmart.dev/skills/zakariaf-analytics-and-consent (last updated 2026-10-24)