Files
Daniel DimitrovandGitHub 2d80cf413e chore: split AGENTS.md across the monorepo to fix size warning (#7785)
* chore: split AGENTS.md across the monorepo to fix size warning

Root AGENTS.md was 41.7k chars, exceeding agent's 40k performance
threshold. Per the agents.md spec for monorepos, split into nested files
that agents auto-load from each subtree:

- apps/web/AGENTS.md (new): feature architecture, Storybook, web testing,
  web-specific pitfalls, debugging tips
- apps/mobile/AGENTS.md (new): Expo + Tamagui guidance
- packages/AGENTS.md (new): shared-package conventions, dual env vars
- apps/web/docs/code-style.md: appended Code Complexity Guidelines
- apps/mobile/docs/code-style.md: cross-reference to complexity rules
- root AGENTS.md: trimmed to cross-cutting rules + Nested guidance index

Root drops 41.7k -> 23.8k chars (no warning, ~16k headroom). All content
preserved in the new layout; existing nested files (cypress, .storybook)
unchanged. README, CLAUDE.md, PR template, and specs/** still reference
the (still-existing) root file.

* chore: gitignore docs/brainstorms/ like docs/plans/

Brainstorms are local working artifacts, same as plans. Remove the
accidentally-committed brainstorm file and prevent future leaks.
2026-04-30 14:35:12 +02:00

23 KiB
Raw Permalink Blame History

AI Contributor Guidelines

This repository is the Safe{Wallet} monorepo, containing both web and mobile applications for Safe (formerly Gnosis Safe), a multi-signature smart contract wallet on Ethereum and other EVM chains. The repository uses a Yarn 4 workspace-based monorepo structure. Follow these rules when proposing changes via an AI agent.

Nested guidance

This monorepo uses nested AGENTS.md files. Agents working in a subtree automatically load the nearest one. Start at root for cross-cutting rules, then drop into the relevant subtree:

Subtree File Covers
apps/web/ apps/web/AGENTS.md Feature architecture, Storybook, web testing, web pitfalls
apps/web/cypress/ apps/web/cypress/AGENTS.md Cypress E2E patterns
apps/web/.storybook/ apps/web/.storybook/AGENTS.md Storybook fixtures and provider patterns
apps/mobile/ apps/mobile/AGENTS.md Expo + Tamagui
packages/ packages/AGENTS.md Shared packages, dual env vars

When adding new guidance, place it in the most-specific subtree it applies to.

Quick Start

Common commands for getting started:

# Install dependencies (uses Yarn 4 via corepack)
yarn install

# Run web app in development mode
yarn workspace @safe-global/web dev

# Run mobile app in development mode
yarn workspace @safe-global/mobile start

# Run tests for web
yarn workspace @safe-global/web test

# Run Storybook for web
yarn workspace @safe-global/web storybook

Turborepo

Root-level lint, type-check, and test run through Turborepo. Tasks are cached by input hash and re-used on subsequent runs — locally and in CI.

yarn type-check                                   # all workspaces (cached)
yarn turbo run type-check --filter=@safe-global/web    # scoped
yarn turbo run test --filter=@safe-global/utils... # package + dependents

Cache directory is .turbo/ (gitignored). Task definitions live in turbo.json.

Remote cache

CI reads TURBO_TOKEN (repo secret) and TURBO_TEAM (repo variable) via .github/actions/yarn. These must be configured once per Vercel team:

  1. Create or pick a Vercel team; copy the team slug → set repo variable TURBO_TEAM.
  2. Create a Vercel personal access token with access to that team → set repo secret TURBO_TOKEN.
  3. Locally: yarn turbo login && yarn turbo link to enable remote cache in development.

Self-hosted cache (e.g. ducktors/turborepo-remote-cache) can be wired by setting TURBO_API, TURBO_TOKEN, TURBO_TEAM — the same env vars the Vercel backend uses.

Architecture Overview

  • apps/web - Next.js web application
  • apps/mobile - Expo/React Native mobile application
  • packages/ - Shared libraries (store, utils, etc.) used by both platforms
  • config/ - Shared configuration files

The monorepo uses Yarn 4 workspaces to manage dependencies and enables sharing code between web and mobile applications.

Key Entry Points

Stable architectural landmarks for fast orientation:

Area Path Purpose
Web app entry apps/web/src/pages/_app.tsx Next.js app bootstrap, providers, InitApp
Redux store apps/web/src/store/index.ts makeStore(), middleware, RTK Query APIs
RTK Query APIs apps/web/src/store/api/gateway/ CGW API endpoints (balances, transactions, etc.)
Feature system apps/web/src/features/__core__/ createFeatureHandle, useLoadFeature, proxy stubs
Page layout apps/web/src/components/common/PageLayout/ Main app layout, sidebar, header
Safe info hook apps/web/src/hooks/useSafeInfo.ts Current Safe address, owners, threshold
Chain config packages/store/src/gateway/chains/ RTK Query chains endpoint with retry logic
Theme package packages/theme/src/ Palettes, spacing, typography tokens
Mobile entry apps/mobile/src/app/_layout.tsx Expo Router root layout

If ast-grep (aka sg) is installed, prefer it over text-based grep for structural code searches. It understands TypeScript/TSX syntax so it won't match inside comments or strings.

# Find all components using useAppSelector
sg -p 'useAppSelector($$$)' --lang tsx apps/web/src/

# Find all createSlice calls
sg -p 'createSlice({ name: $NAME, $$$})' --lang ts apps/web/src/

# Find all default exports of a function component
sg -p 'export default function $NAME($$$) { $$$}' --lang tsx apps/web/src/

# Find useMemo with specific dependency
sg -p 'useMemo(() => $$$, [$$$, chainId, $$$])' --lang tsx apps/web/src/

Install: brew install ast-grep or npm install -g @ast-grep/cli

TypeScript LSP (symbol-aware navigation)

When available in your agent environment, the LSP tool exposes the TypeScript language server and indexes the entire monorepo (apps/ + packages/, ~40k+ symbols). Use it for any question about what a symbol is or who uses it — it follows imports, re-exports, and module resolution, and ignores matches in comments and strings. This is strictly more accurate than grep for symbol-level questions, and complements ast-grep (which is best for structural pattern matching).

When to reach for LSP (strongly preferred over grep):

  • "Who consumes this hook / component / selector / slice / endpoint / type?" → findReferences
  • "Where is this symbol defined?" → goToDefinition
  • "What are all implementations of this interface?" → goToImplementation
  • "What's the exported API of this file?" → documentSymbol
  • "Does a symbol named X exist anywhere, and where?" → workspaceSymbol
  • "Who calls this function, and whom does it call?" → prepareCallHierarchy + incomingCalls / outgoingCalls
  • "What type is this expression?" → hover

When to reach for ast-grep instead:

  • Structural patterns, not symbol identity. E.g. "every useMemo whose deps array contains chainId", "every call to createSlice with a given shape".

When plain grep is still fine:

  • Searching strings, comments, config, copy/UI text, file names, or anything that isn't a TS/TSX identifier.

Gotcha — default exports: For files that use export default, target the identifier on the export default line, not the local const/function binding, or you will miss all the importing consumers. Example: for apps/web/src/hooks/useSafeInfo.ts, aiming findReferences at the local const useSafeInfo binding returns ~2 refs; aiming at export default useSafeInfo returns 500+ refs across the codebase. For named exports this does not matter.

All operations take: filePath, line (1-based), character (1-based), operation.

# Examples
operation=documentSymbol  filePath=apps/web/src/hooks/useSafeInfo.ts  line=1 character=1
operation=findReferences  filePath=apps/web/src/hooks/useSafeInfo.ts  line=29 character=16  # the "default" identifier
operation=goToDefinition  filePath=apps/web/src/hooks/useSafeInfo.ts  line=4  character=10  # selectSafeInfo import
operation=workspaceSymbol filePath=<any .ts file>                     line=1  character=1   # index-wide symbol search

Cost note: workspaceSymbol with an empty/broad query returns tens of thousands of entries (1.7 MB+) and will be truncated to a persisted file — use it with a specific query, or prefer findReferences / goToDefinition starting from a known site.

Unified Theme System

The project uses @safe-global/theme package as a single source of truth for all design tokens (colors, spacing, typography, radius) across web and mobile.

Key Features

  • Unified Palettes: Light and dark mode color palettes shared between platforms
  • Dual Spacing Systems: 4px base for mobile, 8px base for web (with overlapping values using same names)
  • Platform Generators: Automatic generation of MUI themes (web) and Tamagui tokens (mobile)
  • Static Colors: Theme-independent brand colors available to both platforms

Usage

Web (MUI):

import { generateMuiTheme } from '@safe-global/theme'

const theme = generateMuiTheme('light') // or 'dark'

Mobile (Tamagui):

import { generateTamaguiTokens, generateTamaguiThemes } from '@safe-global/theme'

const tokens = generateTamaguiTokens()
const themes = generateTamaguiThemes()

Direct Token Access:

import { lightPalette, darkPalette, spacingMobile, spacingWeb, typography } from '@safe-global/theme'

Modifying Theme

To add or modify colors/tokens:

  1. Edit files in packages/theme/src/palettes/ or packages/theme/src/tokens/
  2. Run type-check to ensure consistency: yarn workspace @safe-global/theme type-check
  3. Regenerate CSS vars for web: yarn workspace @safe-global/web css-vars

Important Notes

  • Never edit apps/web/src/styles/vars.css directly - it's auto-generated
  • Always use theme tokens instead of hard-coded colors
  • Both light and dark modes must be updated together for consistency

General Principles

  • Follow the DRY principle avoid code duplication by extracting reusable functions, hooks, and components
  • Prefer functional code over imperative use pure functions, avoid side effects, leverage map/filter/reduce instead of loops
  • Use declarative and reactive patterns prefer React hooks, derived state, and data transformations over manual state synchronization
  • Always cover new logic, services, and hooks with unit tests
  • Run type-check, lint, prettier and unit tests before each commit
  • Never use the any type!
  • Treat code comments as tech debt! Add them only when really necessary & the code at hand is hard to understand.
  • Use sentence case for UI text Buttons, headings, labels, warnings, and other UI copy should use sentence case (e.g., "Add new owner") not Title Case (e.g., "Add New Owner")

Web-specific principles (feature architecture, MUI theme, vars.css, feature flags, Storybook story requirement) live in apps/web/AGENTS.md. Mobile-specific principles live in apps/mobile/AGENTS.md.

Testing Requirements

Every code change must include tests. See apps/web/docs/TESTING.md for conventions, templates, and mock patterns.

Workflow

Fast Feedback Loop

The repo provides automated verification:

  1. Automatic: A Claude Code Stop hook runs verify:changed once at the end of each agent turn. It early-exits (no-op) when no .ts/.tsx/.js/.jsx files have been modified. When it does run, type-check runs on the full project (TSC requires this), while lint, prettier, and tests are scoped to changed files only. The workspace (web/mobile) is auto-detected from the changed file paths. Set SKIP_VERIFY=1 to disable. Fix any errors before moving on.

  2. Manual: Run yarn verify:changed:web anytime to check your work. Run yarn verify:web for a full check before committing.

  3. Test scaffolding: Run yarn test:scaffold <file> to generate a test skeleton with the correct imports, mocks, and structure. See the Test Decision Matrix in the Testing Guidelines section for which files need tests.

Rules for agents:

  • Fix all verify:changed errors before proceeding to the next task
  • If verify:changed reports a missing test, write one before committing
  • Do NOT run type-check, lint, prettier, and test separately — use verify
  • Do NOT commit without a clean verify:changed pass

Pre-implementation regression checklist (REQUIRED)

Before writing code for any non-trivial change (anything beyond a typo, doc tweak, or single-line local fix), you MUST produce a regression checklist and include it in your response to the user. Optimise for impact analysis, not diff completion: a change to a shared hook, selector, component, slice, or API endpoint touches many user journeys, and plain text search is not enough to find them.

Build the checklist in this order:

  1. Map the surface. Identify what you are touching: the primary file(s), plus any shared hooks, components, selectors, Redux slices, RTK Query endpoints, feature flags, routes, or persisted state involved.
  2. Find consumers with symbol-aware search. For "who uses this symbol?" questions, prefer the LSP tool's findReferences (see "TypeScript LSP" above) — it follows imports, re-exports, and module resolution across the whole monorepo. Use ast-grep for structural pattern questions (e.g. "every useMemo with chainId in deps"). Fall back to plain grep only for strings, comments, config, or UI copy. Plain text search misses structural usages, matches inside comments/strings, and does not follow re-exports.
  3. Translate consumers into flows. For each consumer, name the user journey it belongs to (create / edit / delete / retry / empty / error / offline / permission / feature-flag-off / mobile variant).
  4. List tests to add or run. Happy path, each neighbouring flow, regression-sensitive paths, and invariant properties. Prefer targeted tests around shared contracts over broad E2E sweeps.
  5. State what you will NOT verify. Be explicit. This exposes false confidence.

Required checklist format (paste into your response before implementing):

### Regression checklist

**Primary flow changed:** <one sentence>

**Surfaces touched:**
- <shared hook / component / selector / slice / endpoint / flag / route>

**Neighbouring flows to verify:**
- <flow A> — <why it could be affected>
- <flow B> — <why it could be affected>

**Tests to add/run:**
- <test name or description>

**Not verified (risks):**
- <what you are skipping and why>

Rules:

  • Do NOT start editing code until this checklist exists in the conversation. For small, strictly local changes, a one-line "local change, no shared surfaces touched" note is sufficient.
  • When you open the PR, carry the relevant lines into the "Affected flows", "Blast radius", and "Risks / not checked" fields of the PR template.
  • If the checklist reveals that a shared abstraction has many unknown consumers, slow down and investigate before coding — that is the signal this process is designed to surface.
  1. Install dependencies: yarn install (from the repository root).

    • Uses Yarn 4 (managed via corepack)
    • Automatically runs yarn after-install for the web workspace, which generates TypeScript types from contract ABIs
  2. Pre-commit hooks: The repository uses Husky for git hooks:

    • pre-commit: Automatically runs lint-staged (prettier) and type-check on staged TypeScript files
    • pre-push: Runs linting before pushing
    • These hooks ensure code quality before commits reach the repository
    • If hooks fail: Fix the reported issues and try committing again. Common issues:
      • Type errors: Run yarn workspace @safe-global/web type-check to see all errors
      • Formatting: Run yarn prettier:fix to auto-fix
      • Linting: Run yarn workspace @safe-global/web lint:fix to auto-fix where possible
  3. Formatting (CRITICAL): ALWAYS run yarn prettier:fix before staging and committing. Do NOT rely on lint-staged alone — it can miss formatting issues due to stash/restore edge cases. Run it explicitly:

    yarn prettier:fix
    

    Then verify with yarn workspace @safe-global/web prettier (the check-only command). CI will reject unformatted code.

  4. Linting and tests: when you change any source code under apps/ or packages/, execute, for web:

    yarn workspace @safe-global/web type-check
    yarn workspace @safe-global/web lint
    yarn workspace @safe-global/web prettier   # verify formatting (CI runs this)
    yarn workspace @safe-global/web test
    

    For mobile:

    yarn workspace @safe-global/mobile type-check
    yarn workspace @safe-global/mobile lint
    yarn workspace @safe-global/mobile prettier
    yarn workspace @safe-global/mobile test
    
  5. Commit messages: use semantic commit messages as described in CONTRIBUTING.md.

    • Examples: feat: add transaction history, fix: resolve wallet connection bug, refactor: simplify address validation
    • CI/CD changes: Always use chore: prefix for CI, workflows, build configs (NEVER feat: or fix:)
    • Test changes: Always use tests: prefix for changes in unit or e2e tests (NEVER feat: or fix:)
  6. Code style: follow the guidelines in:

    • apps/web/docs/code-style.md for the web app.
    • apps/mobile/docs/code-style.md for the mobile app.
  7. Pull requests: fill out the PR template and ensure all checks pass.

  8. PR poem: Include a short technical poem at the very top of each PR description that acts as a concise summary of what the PR actually changes. The poem should prioritize clarity over artistry — a reader should understand the gist of the PR from the poem alone. Use a randomly chosen short form (e.g., haiku, limerick, free verse, tanka) and keep it to 24 lines. Wrap in a blockquote:

    > Strip Sentry SDK and config,
    > no more error tracking calls,
    > bundle shrinks, tests pass clean.
    
  9. PR description: Always use the GitHub PR template (.github/PULL_REQUEST_TEMPLATE.md). Fill out all sections — "What it solves", "How this PR fixes it", "How to test it", and the checklist.

  10. PR visual summary (required): Every PR must include a visual in the ## Visual summary section. This is mandatory, not optional.

    • Architecture/logic changes → Mermaid diagram (flowchart, sequence, or class diagram) showing what changed
    • UI changes → Screenshot of the result (use Chrome DevTools MCP if the app is running, or describe how to capture manually)
    • Both if the PR includes UI + logic changes

    Mermaid diagrams are rendered natively by GitHub. Example:

    ```mermaid
    flowchart LR
      A[useSafeInfo hook] --> B[New validation logic]
      B --> C{Is owner?}
      C -->|Yes| D[Show actions]
      C -->|No| E[Show read-only]
    ```
    

    For refactors, use a before/after diagram:

    ```mermaid
    flowchart TB
      subgraph Before
        A1[Component A] --> B1[Inline logic]
        A1 --> C1[Inline logic]
      end
      subgraph After
        A2[Component A] --> H[useSharedHook]
        H --> B2[Extracted service]
      end
    ```
    

Environment Variables Web apps use NEXT_PUBLIC_* prefix, mobile apps use EXPO_PUBLIC_* prefix for environment variables. In shared packages, check for both prefixes.

Testing Guidelines

Unit Tests

  • When writing Redux tests, verify resulting state changes rather than checking that specific actions were dispatched.

  • Avoid any type assertions Create properly typed test helpers instead of using as any. For example, when testing Redux slices with a minimal store, create a helper function that properly types the state:

    // Good: Properly typed helper
    type TestRootState = ReturnType<ReturnType<typeof createTestStore>['getState']>
    const getSafeState = (state: TestRootState, chainId: string, safeAddress: string) => {
      return state[sliceName][`${chainId}:${safeAddress}`]
    }
    
    // Bad: Using 'any'
    const state = store.getState() as any
    
  • Use Mock Service Worker (MSW) for tests involving network requests instead of mocking fetch. Use MSW for mocking blockchain RPC calls instead of mocking ethers.js directly

  • Create test data with helpers using faker

  • Ensure shared package tests work for both web and mobile environments

  • Test files should be colocated with source files using the *.test.ts(x) naming convention

Platform-specific testing

Security & Safe Wallet Patterns

Safe (formerly Gnosis Safe) is a multi-signature smart contract wallet that requires multiple signatures to execute transactions.

Key Concepts

  • Safe Account A smart contract wallet requiring M-of-N signatures to execute transactions
  • Owners Addresses that can sign transactions for a Safe
  • Threshold Minimum number of signatures required to execute a transaction
  • Transaction Building Follow Safe SDK patterns for building multi-signature transactions using @safe-global/protocol-kit

Best Practices

  • Safe Address Validation Always validate Ethereum addresses using established utilities (ethers.js isAddress)
  • Chain-Specific Safes Safe addresses are unique per chain; always include chainId when referencing a Safe
  • Transaction Building Use the Safe SDK (@safe-global/protocol-kit, @safe-global/api-kit) for transaction creation
  • Wallet Provider Integration Follow established patterns for wallet connection and Web3 provider setup (Web3-Onboard)
  • Never hardcode private keys or sensitive data Use environment variables and secure key management

Environment Configuration

  • Local Development Points to staging backend by default
  • Environment Branches PRs get deployed automatically for testing
  • RPC Configuration Infura integration for Web3 RPC calls (requires INFURA_TOKEN)
  • Chain Configuration Chain configs are managed through the Safe Config Service

Common Pitfalls

Cross-cutting mistakes to avoid. Web-specific pitfalls live in apps/web/AGENTS.md; mobile-specific ones in apps/mobile/AGENTS.md.

  1. Using any type Always properly type your code, create interfaces/types as needed.
  2. Forgetting to run tests Always run tests before committing.
  3. Breaking mobile when changing shared code Shared packages (packages/**) affect both web and mobile.
  4. Modifying generated files Never manually edit auto-generated files in packages/utils/src/types/contracts/ or packages/store/src/gateway/AUTO_GENERATED/. CI fails if they don't match the schema. Run yarn workspace @safe-global/store build:dev to regenerate the store types.
  5. Not handling chain-specific logic Always consider multi-chain scenarios.
  6. Incomplete error handling Always handle loading, error, and empty states in UI components.