Imported from mcmire/dotfiles (
src/config/opencode/AGENTS.md). Install upstream withnpx skills add mcmire/dotfiles --skill opencode. Copyright stays with the author.
Personal Rules
Writing English
- Choose alternatives for the following words and phrases, which are uncommon:
- "watermark"
- "carries" (replace with "has", e.g., "A mutation in the
errorstate always carries a non-nullerror." -> "A mutation in theerrorstate always has a non-nullerror.") - "carrying" (replace with "containing")
- "load-bearing"
Writing code
General development workflow
- Always test your work to make sure that it passes the user's intentions. Unit tests are preferred, but any kinds of tests will do.
- If you find yourself creating scratch tests, stop and consider adding real tests so that the behavior being added or the fix being made won't break in the future.
- Always write tests first, watch them fail, then implement the code to make them pass. (See "General testing guidelines" for more.)
- When you complete a task and reach a point where you would print a summary of changes to the user, create a commit.
- When creating a commit, make sure to not only supply a subject line, but also supply a commit body. Please include the rationale for the change in the commit body: the problems encountered and the solutions implemented. There is no need to give a TODO list of changes, but if there are any changes which required multiple attempts or are particularly difficult to understand, feel free to call them out.
General code guidelines
- If you find that you need to use a comment to explain a section of code, that is a code smell and you probably want to split that code off into a separate function.
- Don't allow functions to grow out of control and do too many things. Same goes for React components.
- Before implementing a task you think will be complex, search the web to see if anyone else has solved the presented problems before and come up with any existing solutions. Search for existing packages, GitHub gists, blog posts, etc.
- Name functions verbs or verb phrases, never nouns or noun phrases (e.g., use
replaceBracketsinstead ofbracketReplacement). - When writing documentation blocks for symbols (e.g. JSDoc, PyDoc, etc.), use the first line to summarize what the symbol represents or does, but then use the remaining paragraphs (short of tags) to explain the reason that the symbol exists and how it's used. Don't describe implementation details in a JSDoc block; that's what inline comments are for.
- When naming variables, never use a past tense verb (e.g.
collected) unless the variable represents a boolean. - When naming variables, recognize and reuse full names of concepts instead of abbreviating them when possible. For instance:
- Don't shorten
messengerClientIdentificationtoidentification - Don't shorten
networkConfigurationtonetworkConfigorconfig - Don't shorten
contexttoctx - Don't shorten
transactiontotxThe only cases in which it's acceptable to use an abbrevation isiforindex, but only do so if it's the only argument to a function.
- Don't shorten
General testing guidelines
- When writing tests, if you need to define test helpers in the same file, place them below all tests. (But if you need to define types, place them at the top of the file.)
- When writing a test, mentally break it up into three stages: "arrange", "act", and "assert". Always use empty lines to divide them.
- Prefer testing one clear behavior per test. This doesn't necessarily mean making one assertion per test. Use the test name as a guide; if you find you are saying "it does this thing AND it does that" or "it does this thing AND NOT that", then divide the test into two.
- If a function isn't exported, don't export it just so you can test it. Test it indirectly through something else that's already exported.
- Don't test constants or variables that return static values (strings, numbers, etc.). Only test logic, which would only be contained in functions or methods.
- If data that is set up in a test matters to the test — i.e., if it's referenced directly or indirectly in an assertion — then state it explicitly in the test itself, do not define it in a test helper which is shared by other tests.
// ❌ BAD it("parses wallet-library migration rows", () => { const metrics = parseMetricsData(createPersistedMetrics()); expect(metrics.walletLibraryMigrations[0]).toStrictEqual({ id: "MetaMask/metamask-extension:AccountsController:2026-01-01", repository: "MetaMask/metamask-extension", createdAt: new Date("2026-01-01T00:00:00.000Z"), occurredAt: new Date("2026-01-01T00:00:00.000Z"), messengerClientName: "AccountsController", isPresentInWalletLibrary: true, commitSha: "abc123", walletLibraryVersion: "10.0.0", }); }); function createPersistedMetrics(): PersistedMetricsFixture { return { // ... walletLibraryMigrations: [ { id: "MetaMask/metamask-extension:AccountsController:2026-01-01", repository: "MetaMask/metamask-extension", createdAt: "2026-01-01T00:00:00.000Z", occurredAt: "2026-01-01T00:00:00.000Z", messengerClientName: "AccountsController", isPresentInWalletLibrary: true, commitSha: "abc123", walletLibraryVersion: "10.0.0", }, ], }; } // ✅ GOOD it("parses wallet-library migration rows", () => { const metrics = parseMetricsData( createPersistedMetrics({ id: "MetaMask/metamask-extension:AccountsController:2026-01-01", repository: "MetaMask/metamask-extension", createdAt: "2026-01-01T00:00:00.000Z", occurredAt: "2026-01-01T00:00:00.000Z", messengerClientName: "AccountsController", isPresentInWalletLibrary: true, commitSha: "abc123", walletLibraryVersion: "10.0.0", }), ); expect(metrics.walletLibraryMigrations[0]).toStrictEqual({ id: "MetaMask/metamask-extension:AccountsController:2026-01-01", repository: "MetaMask/metamask-extension", createdAt: new Date("2026-01-01T00:00:00.000Z"), occurredAt: new Date("2026-01-01T00:00:00.000Z"), messengerClientName: "AccountsController", isPresentInWalletLibrary: true, commitSha: "abc123", walletLibraryVersion: "10.0.0", }); }); function createPersistedMetrics( overrides?: Partial<PersistedMetricsFixture> = {}, ): PersistedMetricsFixture { const defaults = { // ... walletLibraryMigrations: [], }; return { ...defaults, ...overrides }; } - When writing tests for a file, prefer to not mock functions or methods that are imported from another file. Only choose to mock imported functions/methods if they make requests or execute shell commands, or if it would be too complex not to mock them.
GitHub Actions
- When using a public action (e.g.
actions/checkout,actions/setup-node, etc.) make sure to use the latest version, unless there is a good reason not to do so (e.g. organization requires that certain actions be pinned).
JavaScript/TypeScript
- Use braces to surround the body of an
ifstatement, even if it could fit on one line. - Always define constants and types at the top of the file (in that order).
- When defining named functions, especially those at the top level, always use
function, never useconst+ arrow functions. (Use arrow functions for methods likemap,reduce, etc.) - When writing scripts:
- Always define the
mainfunction first, and then define supporting functions in reverse order below that (for instance, ifmaincallsfoo, andfoocallsbar, then the order would bemain,foo,bar). - If the file is executable, add a shebang at the top (
#!/usr/bin/env yarn tsxif it is present inpackage.json,#!/usr/bin/env nodeotherwise) and include this above the main function:// Run the script. main().catch((error) => { error.exitCode = 1; });- Always use
yargsto parse command-line options, never parse them by hand. Set up Yargs such that--help(or-h) works. Add a brief summary for each option and one or two examples.
- Always use
- Always define the
- Import Node's
pathmodule using a wildcard import (import * as ..., notimport { ... } as ...). - Provide JSDoc when defining types, interfaces, properties of types/interfaces, functions, classes, and top-level variables (constants). Use the first line to summarize what the symbol represents or does, but then use the remaining paragraphs (short of tags) to explain the reason that the symbol exists and how it's used.
- Use the "long" block comment when providing JSDoc, not the "short" block comment. This particularly applies to properties of types/interfaces. In other words:
not this:type Foo = { // ✅ GOOD /** * The bar. */ bar: number; };type Foo = { // ❌ BAD /** The bar. */ bar: number; }; - Prefer using arrow functions (
() => { ... }) over function expressions (function () { ... }), particularly when passing functions to other functions (e.g.it/testin test files). Only usefunction () { ... }if you need a function whosethisneeds to be rebound. - When adding or modifying a function or method so that it takes more than three arguments, convert the arguments to an options bag rather than use positional arguments.
- When using Vitest, prefer
itovertest. - When adding a dependency to a project, make sure to add the latest version of the package, unless there is a specific reason not to do so (e.g. project requires CommonJS but package is only ESM-compatible).
- When writing or updating tests, check to see if the test harness has been configured to automatically reset mocks, and if so, then do not call
jest.resetAllMocks/jest.restoreAllMocksorvi.clearAllMocks/vi.restoreAllMocks(or some variation of this) inbeforeEach/afterEachhooks. - Don't use
awaitto call an asynchronous function/method and then act on the result within the same statement. Make the asynchronous call in one statement and then act on the result in another. - When calling an asynchronous function/method but not using
awaitbut rather capturing the promise, call the variablepromiseFor${nameOfAction(e.g.promiseForScanning,promiseForFetching, etc.). - When making assertions in tests, don't use
?.to look up properties on values that may or may not be present; useassertfirst to narrow the type.
This provides a better error message if the object we're looking through isn't what we expect in practice.// ❌ BAD expect(foo?.bar).toStrictEqual('baz') // ✅ GOOD assert(foo); expect(foo.bar).toStrictEqual('baz') - TypeScript: Don't use type assertions (
as ...) or non-null assertions (foo!) unless absolutely necessary. If you do need to use either, add a comment above the line such asType assertion: <Reason>orNon-null assertion: <Reason>. - TypeScript: Instead of using type annotations, have TypeScript infer the type as much as possible. Use
as constfor statically defined data. If you really need to use a type annotation, try usingsatisfiesinstead.- The only exception to this rule is return types on functions/methods — type annotations are acceptable there (and even required for some projects).
- TypeScript: Don't extract function/method argument types or return types by default; wait until we get to a point where we need to use the type in more than one place.
- TypeScript: Don't use the
privatekeyword, use ES private fields instead.
Shell Scripting
- Always run
shellcheckafter updating a Bash script. - Use
ifstatements for conditional logic, not&&or||shorthand.
Memory Index
How to use memories
When a task relates to a topic below, call memory_read_topic with the filename and namespace to load the full detail. After a non-trivial task (debugging, a workaround, a non-obvious decision), suggest /remember.
Known topic files
- [[messenger-adapter-minimal-namespace-support]] (procedural): Typing a function/class that needs a MetaMask Messenger to minimally support certain namespaces while allowing extras (BaseController capability-check pattern, not structural adapter)
- [[typescript-subclass-structural-checking]] (semantic): Why a subclass of a generic base can fail an
extends BaseClass<...wide...>constraint the base itself passes (structural private-field check); fix by inferring type args instead of constraining - [[typescript-deferred-any-circular-imports]] (procedural): Detecting a "deferred"
anyfrom circular imports (union collapsing toany), why structuralIsAnyfails on it, and the[T] extends [Brand]fix - [[sqlite-in-git-for-metrics]] (procedural): Storing a small structured dataset as SQLite committed to Git, feeding a browser dashboard
- [[version-accurate-historical-dependency-scanning]] (procedural): Scanning a dependency's API across git history version-accurately without per-commit installs
- [[typescript-generics-deferred-conditionals-and-narrowing]] (semantic): Why generic params are unresolved in a function body, why conditional return types over them are deferred (forcing
as unknown as), and why value-narrowing can't narrow a type parameter