Skip to content

Commit 413290c

Browse files
chore: Add AGENTS.md (#3823)
Add AGENTS.md file with instructions for AI agents that operate on the repository. The goal is to outline our requirements for feature development and any quirks in using the repo. Mostly written by Claude as an experiment, but with edits and guidance from myself. Also with inspiration from MetaMask/core#7765 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: documentation-only addition plus a small `lint-staged` pattern tweak that only affects formatting behavior for `CLAUDE.md`. No runtime or package code changes. > > **Overview** > Adds `AGENTS.md`, a repository guide for AI coding agents covering stack/tooling, monorepo conventions, build/test/lint commands, changelog requirements, and high-level Snaps platform architecture/naming guidelines. > > Adds `CLAUDE.md` pointing to `AGENTS.md`, and updates `lint-staged` in `package.json` to exclude `CLAUDE.md` (alongside `CHANGELOG.md`) from Prettier formatting. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 4ade350. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Maarten Zuidhoorn <maarten@zuidhoorn.com>
1 parent 200cd08 commit 413290c

File tree

3 files changed

+298
-1
lines changed

3 files changed

+298
-1
lines changed

AGENTS.md

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
# AGENTS.md
2+
3+
This file provides guidance to AI coding agents when working with code in this repository.
4+
5+
## Stack
6+
7+
- **Yarn 4** for managing the monorepo
8+
- **TypeScript 5** for type-safe code
9+
- **Jest** with **SWC** for unit tests; **Vitest** for browser tests
10+
- **ESLint 9** and **Prettier** for linting and formatting
11+
- **ts-bridge** for building packages (produces both ESM and CJS)
12+
- **LavaMoat** for secure builds of execution environments
13+
- **`@metamask/auto-changelog`** for changelog management
14+
15+
## Package Structure
16+
17+
Packages live in `packages/`. Each package follows this structure:
18+
19+
- `src/` — Source files that get built and published
20+
- `package.json` — Package metadata, dependencies, and scripts
21+
- `CHANGELOG.md` — Historical changes following Keep a Changelog format
22+
- `README.md` — Package documentation
23+
- `jest.config.js` — Jest configuration extending the base config
24+
- `tsconfig.json` — TypeScript config for development/IDE
25+
- `tsconfig.build.json` — TypeScript config for production builds
26+
27+
## Development Workflow
28+
29+
Follow test-driven development when making changes:
30+
31+
1. **Update tests first**: Before modifying implementation, update or write tests that describe the desired behavior. Aim for 100% coverage.
32+
2. **Watch tests fail**: Run tests to verify they fail with the current implementation.
33+
3. **Make tests pass**: Implement changes to make the tests pass.
34+
4. **Run all checks**: Ensure lint, types, tests, and changelog validation all pass.
35+
36+
## Build Commands
37+
38+
```bash
39+
# Install dependencies (requires Node.js 20+ and Yarn 4)
40+
yarn install
41+
42+
# Build all packages
43+
yarn build
44+
45+
# Build a single package
46+
yarn workspace @metamask/snaps-controllers build
47+
48+
# Build execution environments exclusively (special LavaMoat build, run optionally)
49+
yarn build:execution-environments
50+
```
51+
52+
## Testing
53+
54+
```bash
55+
# Run all tests across all packages
56+
yarn test
57+
58+
# Run tests for a specific package
59+
yarn workspace @metamask/snaps-controllers test
60+
61+
# Run a single test file within a package (without coverage)
62+
yarn workspace @metamask/snaps-controllers jest --no-coverage src/snaps/SnapController.test.ts
63+
64+
# Run tests in watch mode
65+
yarn workspace @metamask/snaps-controllers test:watch
66+
67+
# Run browser tests (some packages have vitest browser tests)
68+
yarn workspace @metamask/snaps-controllers test:browser
69+
```
70+
71+
## Linting
72+
73+
```bash
74+
# Run all linting
75+
yarn lint
76+
77+
# Fix auto-fixable issues
78+
yarn lint:fix
79+
80+
# Run just ESLint
81+
yarn lint:eslint
82+
83+
# Validate changelogs
84+
yarn changelog:validate
85+
```
86+
87+
## Updating Changelogs
88+
89+
Each consumer-facing change should have a changelog entry. Follow ["Keep a Changelog"](https://keepachangelog.com/):
90+
91+
- Maintain an `## [Unreleased]` section at the top
92+
- Use these categories in order: Added, Changed, Deprecated, Removed, Fixed, Security
93+
- Prefix breaking changes with `**BREAKING:**` and list them first in their category
94+
- Link to PRs: `([#123](link-to-pr))`
95+
- Describe API changes precisely, not just PR titles
96+
- Omit internal/non-consumer-facing changes
97+
- Run `yarn changelog:validate` after updates
98+
99+
## Architecture
100+
101+
This is a Yarn workspaces monorepo for Snaps Platform infrastructure. Key packages:
102+
103+
- **snaps-sdk**: Core library for building Snaps. Provides types, JSX runtime, and APIs that Snap developers use. Foundation dependency for most other packages.
104+
105+
- **snaps-utils**: Shared utilities used across the Snaps ecosystem. Includes manifest validation, permissions handling, and common helpers.
106+
107+
- **snaps-controllers**: Controllers that manage Snap lifecycle within MetaMask. Handles installation, execution, permissions, and state management. Used by MetaMask clients (extension/mobile).
108+
109+
- **snaps-execution-environments**: Sandboxed environments where Snaps run with limited access to globals. Uses SES (Secure EcmaScript) for isolation. Builds special bundles via LavaMoat for security.
110+
111+
- **snaps-rpc-methods**: JSON-RPC method implementations for Snap APIs (e.g., `snap_dialog`, `snap_manageState`).
112+
113+
- **snaps-simulation**: Headless testing framework for Snaps. Powers the Jest preset.
114+
115+
- **snaps-jest**: Jest preset and matchers for end-to-end Snap testing. Uses snaps-simulation under the hood.
116+
117+
- **snaps-cli**: CLI tool for building and serving Snaps during development.
118+
119+
- **create-snap**: Scaffolding tool (`yarn create @metamask/snap`) to bootstrap new Snap projects.
120+
121+
- **snaps-webpack-plugin** / **snaps-rollup-plugin**: Bundler plugins for building Snaps.
122+
123+
## Dependency Graph
124+
125+
```
126+
snaps-sdk (foundation)
127+
128+
snaps-utils
129+
130+
snaps-rpc-methods
131+
132+
snaps-controllers ←── snaps-execution-environments (peer dep)
133+
134+
snaps-simulation
135+
136+
snaps-jest
137+
```
138+
139+
## Key Architectural Concepts
140+
141+
### SES and Compartments
142+
143+
Snaps run in [SES (Secure EcmaScript)](https://github.com/endojs/endo/tree/master/packages/ses) compartments for isolation. The compartment limits access to globals.
144+
145+
### Permission System
146+
147+
The platform uses `PermissionController` from `@metamask/permission-controller` extensively:
148+
149+
- **Subjects**: Websites, Snaps, or extensions that request permissions
150+
- **Targets**: JSON-RPC methods or endowments being permissioned
151+
- **Restricted methods**: JSON-RPC methods that require permission to call (e.g., `snap_getBip32Entropy`)
152+
- **Permitted methods**: JSON-RPC methods available without special permission (e.g., `wallet_requestSnaps`). These generally do not pass through the `PermissionController`.
153+
- **Endowment**: Capability granted to a Snap (e.g., `endowment:network-access`). May grant access to JavaScript globals.
154+
- **Caveats**: Constraints that attenuate what a permission grants (e.g., limiting derivation paths)
155+
- **Caveat mappers**: Functions that convert manifest values to caveat objects
156+
157+
### JSON-RPC Flow
158+
159+
Requests flow through a middleware stack in `json-rpc-engine`. Each connection (dapp or Snap) gets its own engine instance. The permission middleware checks authorization before allowing restricted method calls.
160+
161+
### Execution Flow
162+
163+
1. **SnapController** receives a request and starts the Snap if needed
164+
2. **ExecutionService** creates an execution environment (iframe, worker, etc.)
165+
3. **ExecutionEnvironment** sets up SES compartment with allowed endowments
166+
4. Snap code is evaluated in the compartment
167+
5. Exported handlers are registered and can receive requests
168+
6. Responses are validated as JSON-serializable before returning
169+
170+
## Naming Conventions
171+
172+
### File Naming
173+
174+
| Type | Pattern | Example |
175+
| ---------------- | ------------------------ | ------------------------------------ |
176+
| Controller class | `PascalCase.ts` | `SnapController.ts` |
177+
| Utility files | `lowercase.ts` | `utils.ts`, `validation.ts` |
178+
| Test files | `[Name].test.ts` | `SnapController.test.ts` |
179+
| Browser tests | `[Name].test.browser.ts` | `IFrameSnapExecutor.test.browser.ts` |
180+
| JSX tests | `[Name].test.tsx` | `SnapController.test.tsx` |
181+
182+
### Class and Type Naming
183+
184+
| Element | Pattern | Example |
185+
| ---------------- | ------------------------- | ------------------------------------------ |
186+
| Controller | `[Domain]Controller` | `SnapController`, `CronjobController` |
187+
| Abstract service | `Abstract[Domain]Service` | `AbstractExecutionService` |
188+
| Executor | `[Platform]SnapExecutor` | `IFrameSnapExecutor`, `ThreadSnapExecutor` |
189+
| Struct validator | `[Type]Struct` | `CronjobSpecificationStruct` |
190+
| Props type | `[Component]Props` | `TextProps`, `ButtonProps` |
191+
| Element type | `[Component]Element` | `TextElement`, `ButtonElement` |
192+
193+
### Function Naming
194+
195+
| Purpose | Pattern | Example |
196+
| ---------- | ----------------- | ------------------------------ |
197+
| Factory | `get[Type]Object` | `getPersistedSnapObject()` |
198+
| Type guard | `is[Type]` | `isCronjobSpecification()` |
199+
| Creator | `create[Thing]` | `createSnapComponent()` |
200+
| Handler | `on[Action]` | `onTransaction`, `onSignature` |
201+
202+
### Test Utilities
203+
204+
| Type | Pattern | Example |
205+
| -------------- | ----------------------------- | --------------------------------------------- |
206+
| Mock constant | `MOCK_[DESCRIPTOR]` | `MOCK_SNAP_ID`, `MOCK_ORIGIN` |
207+
| Factory helper | `get[Type]Object()` | `getSnapObject()`, `getPersistedSnapObject()` |
208+
| Directory | `__mocks__/`, `__fixtures__/` | `__mocks__/fs.ts` |
209+
210+
## Adding New Platform Features
211+
212+
When implementing a new Snaps Platform feature (e.g., a new permission, endowment, or RPC method), include:
213+
214+
1. **An example Snap** in `packages/examples/packages/` demonstrating the feature
215+
2. **A test-snaps UI** in `packages/test-snaps/` for manual testing with MetaMask
216+
3. **Simulation support** in `snaps-simulation` and/or `snaps-jest` if needed for the example's E2E tests to pass
217+
218+
New RPC methods, permissions, or platform APIs often require corresponding mock implementations or handlers in the simulation layer before the example Snap's tests can function correctly.
219+
220+
### Example Snap Structure
221+
222+
Create a new directory in `packages/examples/packages/<feature-name>/`:
223+
224+
```
225+
packages/examples/packages/<feature-name>/
226+
├── src/
227+
│ ├── index.ts # Snap entry point with handler exports
228+
│ └── index.test.ts # Tests using @metamask/snaps-jest
229+
├── snap.manifest.json # Snap manifest with required permissions
230+
├── package.json # Package with @metamask/snaps-jest devDependency
231+
└── jest.config.js
232+
```
233+
234+
The example Snap should:
235+
236+
- Export the relevant handler (e.g., `onRpcRequest`, `onTransaction`)
237+
- Request only the permissions needed for the feature
238+
- Include tests using `installSnap()` from `@metamask/snaps-jest`
239+
240+
### Test-Snaps Integration
241+
242+
Add a feature directory in `packages/test-snaps/src/features/snaps/<feature-name>/`:
243+
244+
```
245+
packages/test-snaps/src/features/snaps/<feature-name>/
246+
├── constants.ts # SNAP_ID, SNAP_PORT, VERSION exports
247+
├── index.ts # Re-exports the React component
248+
└── <FeatureName>.tsx # React component with UI to test the Snap
249+
```
250+
251+
Then:
252+
253+
1. Add the example Snap as a `devDependency` in `packages/test-snaps/package.json`
254+
2. Export the feature component from `packages/test-snaps/src/features/snaps/index.ts`
255+
256+
See `packages/examples/packages/multichain-provider/` for a complete example.
257+
258+
## Code Guidelines
259+
260+
### General
261+
262+
- Packages use `workspace:^` for internal dependencies
263+
- Build uses `ts-bridge` (not tsc directly) to produce ESM and CJS outputs
264+
- Tests use Jest with SWC for transformation; some packages also have Vitest browser tests
265+
- JSX components use `@metamask/snaps-sdk/jsx` with automatic runtime
266+
- Coverage threshold is 100% by default (some packages override this)
267+
- Workspace package names use `@metamask/` scope (e.g., `@metamask/snaps-controllers`, not the directory name `snaps-controllers`)
268+
- Use uppercase "Snap" (not "snap") in comments and documentation when referring to MetaMask Snaps
269+
- Document all functions, classes, and types with JSDoc
270+
- Use `@metamask/superstruct` for runtime validation of data structures, RPC parameters, and API inputs/outputs
271+
- Define a `[Type]Struct` and infer the TypeScript type from it: `type MyType = Infer<typeof MyTypeStruct>`
272+
- Validate early at system boundaries (RPC handlers, external data) rather than deep in business logic
273+
274+
### Controllers
275+
276+
- Controllers are generally used whenever we need to store state in the MetaMask clients
277+
- Controller classes should extend `BaseController`
278+
- Controllers must have state; stateless logic belongs in services
279+
- Define public messenger types with actions and events
280+
- Include `:getState` action and `:stateChange` event at minimum
281+
- Constructor takes `messenger` and `state` options
282+
283+
### Exports
284+
285+
- Each package has an `index.ts` that defines all public exports
286+
- Some packages have platform-specific entry points (e.g., `@metamask/snaps-utils/node`, `@metamask/snaps-controllers/node`, `@metamask/snaps-controllers/react-native`) for platform-specific APIs that shouldn't be bundled for other environments
287+
288+
## Further Reading
289+
290+
See `docs/` for detailed platform internals:
291+
292+
- `docs/internals/architecture.md` — System overview with sequence diagrams
293+
- `docs/internals/permissions.md` — Permission system deep dive
294+
- `docs/internals/execution.md` — SES sandboxing and endowments
295+
- `docs/internals/json-rpc.md` — JSON-RPC middleware stack
296+
- `docs/internals/platform/` — Individual component documentation

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AGENTS.md

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"*.{js,ts,tsx,jsx}": [
5151
"eslint --fix"
5252
],
53-
"!(CHANGELOG).{json,yml,md}": [
53+
"!(CHANGELOG|CLAUDE).{json,yml,md}": [
5454
"prettier --write"
5555
],
5656
"yarn.lock": [

0 commit comments

Comments
 (0)