Add AGENTS.md guide for coding agents (#815)

* 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>
This commit is contained in:
M 2026-02-21 02:57:08 +06:00 • committed by GitHub
parent f6a9580197
commit 77c40bdd9d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 241 additions and 0 deletions

240
AGENTS.md Normal file
View file

@ -0,0 +1,240 @@
# 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](https://github.com/Vendicated/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:**
1. Explore relevant source files to understand existing patterns
2. Check the Key Files Index for API/type references
3. Look at similar plugins for examples
4. 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/`.**
```typescript
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 `find` target the module.
- **No hardcoded minified vars.** Use `\i`, never `e`, `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, **`$self`** for 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/pluginName` for Equicord plugins
- `@plugins/pluginName` for 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 `dev` branch
- Create feature branch from `dev`: `feature/plugin-name` or `fix/plugin-name`
- Keep commits atomic
- Squash commits before merging if addressing same issue
- Run `pnpm lint` and `pnpm build` before committing
**PR title format:** `[PluginName] Description`
---

1
CLAUDE.md Symbolic link
View file

@ -0,0 +1 @@
AGENTS.md