* Add AGENTS.md guide for coding agents Adds a comprehensive AGENTS.md guide for AI coding agents working on Equicord, based on the agents.md standard format. Key sections included: - Project overview and technology stack - Setup commands (install, build, dev, test) - Project structure with path aliases - Code style, patterns, and philosophy - Plugin structure with clear directory placement (src/equicordplugins/ for Equicord, src/plugins/ for Vencord) - Forbidden patterns table with fixes - Discord types and plugin interop - Patch quality guidelines - Memory leaks and cleanup - Built-in utilities reference - Plugin acceptance rules - Git workflow (feature branches from fork's dev, PR to upstream dev) - Platform-specific plugins (.desktop, .web, .discordDesktop) Also adds CLAUDE.md symlink for Claude compatibility. Sources: - Merged content from .gemini/styleguide.md - Follows agents.md standard format * Improve AGENTS.md based on Vercel agent evals research - Add retrieval-led reasoning directive at top - Add Quick Reference and Key Files Index for fast navigation - Add explicit workflow ordering (explore first, then act) - Compact from 518 to 240 lines (54% reduction) - Convert verbose lists to tables - Merge related sections - Remove redundant examples --------- Co-authored-by: Mishal <91066601+mishalhossin@users.noreply.github.com>
10 KiB
AGENTS.md
IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. When uncertain about APIs, patterns, or existing implementations, read the relevant source files rather than relying on assumptions.
Quick Reference
| Task | Command/Location |
|---|---|
| Install | pnpm install --frozen-lockfile |
| Build | pnpm build |
| Dev/Watch | pnpm dev |
| Lint | pnpm lint |
| Type check | pnpm testTsc |
| Full test | pnpm test |
| New Equicord plugin | src/equicordplugins/PluginName/index.ts |
| New Vencord plugin | src/plugins/PluginName/index.ts |
Key Files Index:
| Need | File |
|---|---|
| Plugin types | src/utils/types.ts |
| Settings API | src/api/Settings.ts |
| Constants/devs | src/utils/constants.ts |
| CSS utilities | src/utils/css.ts |
| Webpack common exports | src/webpack/common/*.ts |
| Example plugins | src/plugins/*/index.ts or src/equicordplugins/*/index.ts |
| Discord types | @vencord/discord-types (external) |
Project Overview
Equicord is a Discord client mod forked from Vencord. It modifies the Discord web client by patching webpack modules at runtime to inject custom functionality.
Technology Stack: TypeScript (strict), React, Webpack patching Package Manager: pnpm | License: GPL-3.0-or-later
Workflow
Before writing code:
- Explore relevant source files to understand existing patterns
- Check the Key Files Index for API/type references
- Look at similar plugins for examples
- Then proceed with implementation
This "explore first, then act" approach produces better results than jumping straight to implementation.
Setup Commands
| Command | Description |
|---|---|
pnpm install --frozen-lockfile |
Install dependencies |
pnpm build |
Build project |
pnpm buildWeb |
Build for web |
pnpm dev / pnpm watch |
Start dev server (watch mode) |
pnpm watchWeb |
Watch web build |
pnpm lint |
Lint code |
pnpm lint:fix |
Fix lint issues |
pnpm lint-styles |
Lint CSS |
pnpm testTsc |
Type check |
pnpm test |
Full test suite (builds, type checks, lints, generates plugin JSON) |
pnpm generatePluginJson |
Generate plugin list |
pnpm generateEquicordPluginJson |
Generate Equicord plugin list |
Requirements: Node.js >= 18, pnpm (see package.json for version)
Code Style
TypeScript: Strict mode. Use inference - don't annotate obvious types.
Formatting: Double quotes, semicolons required, single quotes only for escaping.
Patterns: ?. optional chaining, ?? nullish coalescing (never || for defaults), const over let, arrow functions, destructuring, template literals, early returns, flat code.
Philosophy: Less code is better. No comments unless requested. Delete dead code, never comment it out. No overengineering or premature abstractions. Trust TypeScript inference.
Text: Plugin/setting descriptions: capital first letter, end with period. Error messages: natural human text, no robotic formatting.
Performance: Map/Set for lookups, .find()/.some() not .filter()[0], no spread in loops, Promise.all() for parallel async.
Hygiene: Delete dead code. No unused imports. No logging (console.log, Logger). No empty catch blocks.
Plugin Structure
Place Equicord plugins in src/equicordplugins/PluginName/, upstream/Vencord plugins in src/plugins/PluginName/.
import definePlugin from "@utils/types";
import { EquicordDevs } from "@utils/constants";
export default definePlugin({
name: "PluginName", // PascalCase, matches directory name
description: "Does something.", // Capital first, period at end
authors: [EquicordDevs.Name], // EquicordDevs for new, Devs for upstream
});
Settings: Use definePluginSettings from @api/Settings. Reject inline options objects.
Prefer declarative APIs: flux, contextMenus, chatBarButton, messagePopoverButton, managedStyle instead of manual registration.
Reject deprecated fields: renderChatBarButton, renderMessagePopoverButton, options
Lifecycle: Anything registered in start() must be cleaned up in stop().
Forbidden Patterns
| Bad | Good | Reason |
|---|---|---|
value !== null && value !== undefined |
value or isNonNullish(value) |
Verbose |
array && array.length > 0 |
array.length |
Redundant |
settings?.store?.value |
settings.store.value |
Store always defined |
value || defaultValue |
value ?? defaultValue |
|| falsifies 0, "", false |
`${classA} ${classB}` |
classes(classA, classB) |
Handles null/false |
"vc-plugin-class" hardcoded |
cl("class") via classNameFactory |
Typo-proof |
console.log/warn/error |
Remove it | No logging |
cdn.discordapp.com/... |
IconUtils.* methods |
Handles animated, sizing |
/api/v9/... |
Constants.Endpoints.* |
Endpoints change |
@api/Styles |
@utils/css |
Deprecated |
any for Discord objects |
Import from @vencord/discord-types |
Type safety |
as unknown as |
Find correct type | Unsafe |
React.memo() |
Remove it | Not needed |
React.cloneElement/isValidElement/Children |
Find another approach | Forbidden |
React.lazy(() => import(...)) |
LazyComponent |
Framework-integrated |
Empty catch {} |
Handle error | Silent failures |
| CSS-only plugins | Must have logic/patches | Not allowed |
document.querySelector(...) |
Use webpack patches | DOM forbidden |
Vencord.Plugins.plugins["X"] |
isPluginEnabled + import |
Proper interop |
plugin.started |
isPluginEnabled(plugin.name) |
Proper interop |
| Unexplained magic numbers | Named constants | Readability |
| Unused imports | Remove them | Cleanliness |
CSS, React, Settings
CSS: Use classNameFactory from @utils/css: const cl = classNameFactory("vc-my-plugin-"); Combine with classes() from @utils/misc.
React: Return null for conditional rendering, never undefined. Wrap complex components with ErrorBoundary.wrap(). useEffect must return cleanup for subscriptions/timers.
Settings: settings.store.key (reactive), settings.plain.key (non-reactive). Never settings.use() with arrays in variables.
Patch Quality
Patches modify Discord's minified webpack modules. Stability is paramount.
- One patch per concern. Each replacement does one thing.
- Surgical. Match only what needs replacing, let
findtarget the module. - No hardcoded minified vars. Use
\i, nevere,t,n,r,i,eD,eH. - Bounded gaps.
.{0,50}not.+?or.*?. - No generic patterns. Add stable anchors.
- No raw intl hashes. Use
#{intl::KEY_NAME}. $&for append/prepend,$selffor plugin method calls.
Memory Leaks
Every start() must have matching stop(). Common leaks: event listeners, intervals/timeouts, MutationObservers, context menu patches, chat bar buttons, styles, Flux subscriptions.
Prefer declarative APIs that handle cleanup automatically: flux, contextMenus, chatBarButton, messagePopoverButton, managedStyle.
useEffect must return cleanup for subscriptions, timers, observers, or any persistent resource.
Plugin Interop
@equicordplugins/pluginNamefor Equicord plugins@plugins/pluginNamefor Vencord/upstream plugins
Use isPluginEnabled(plugin.name) to check if enabled. Never access Vencord.Plugins.plugins["X"] directly.
Platform-Specific Plugins
Directory suffixes: pluginName.desktop/, pluginName.web/, pluginName.discordDesktop/
Built-in Utilities
@utils/misc: classes, sleep, isObject, isObjectEmpty, parseUrl, pluralise, identity
@utils/guards: isTruthy, isNonNullish
@utils/text: formatDuration, formatDurationMs, humanFriendlyJoin, makeCodeblock, toInlineCode, escapeRegExp
@utils/discord: getCurrentChannel, getCurrentGuild, getIntlMessage, openPrivateChannel, insertTextIntoChatInputBox, sendMessage, copyWithToast, openUserProfile, fetchUserProfile, getUniqueUsername, openInviteModal
@utils/css: classNameFactory, classNameToSelector
@utils/clipboard: copyToClipboard
@utils/modal: openModal, closeModal, ModalRoot, ModalHeader, ModalContent, ModalFooter, ModalCloseButton
@utils/margins: Margins.top8, .top16, .bottom8, .bottom16 etc.
@utils/web: saveFile, chooseFile
@utils/lazy: proxyLazy, makeLazy
@utils/lazyReact: LazyComponent
@utils/react: useAwaiter, useForceUpdater, useTimer
@api/DataStore: get, set, del (IndexedDB, async)
@api/Commands: sendBotMessage, findOption
@webpack/common: Stores (UserStore, GuildStore, ChannelStore, 30+ more), Actions (RestAPI, FluxDispatcher, MessageActions, NavigationRouter), Utils (Constants.Endpoints, SnowflakeUtils, Parser, PermissionsBits, moment, lodash, IconUtils), Components (Tooltip, TextInput, Select, Avatar, Menu, Popout), Toasts (Toasts, showToast)
@webpack finders: findByPropsLazy, findByCodeLazy, findStoreLazy, findComponentByCodeLazy, findExportedComponentLazy
@components/: ErrorBoundary, Flex, Button, Paragraph, Heading, BaseText, Span, ErrorCard, Link, CodeBlock, FormSwitch
Ethical Guidelines & Acceptance Rules
Not accepted: Fake deafen/mute, trolling plugins, selfbot/API abuse (auto-replies, animated statuses, message pruning, Nitro snipers).
Instant reject: Simple slash-commands, text replacement, DOM manipulation, UI-only hide/redesign, third-party bot targeting, untrusted APIs, user-provided API keys, new dependencies without justification.
Git Workflow
- Never commit to
main(protected) - Never commit to fork's
devbranch - Create feature branch from
dev:feature/plugin-nameorfix/plugin-name - Keep commits atomic
- Squash commits before merging if addressing same issue
- Run
pnpm lintandpnpm buildbefore committing
PR title format: [PluginName] Description