chore: add .rules and symlink agent config files (#820)

* chore: add .rules and symlink agent config files

* Update rules for testing and file modification

Clarify rules regarding testing commands and file modifications.

---------

Co-authored-by: thororen <78185467+thororen1234@users.noreply.github.com>
This commit is contained in:
prism 2026-02-21 00:21:23 +01:00 • committed by GitHub
parent 189ab429bb
commit 4caaef0027
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 214 additions and 241 deletions

211
.rules Normal file
View file

@ -0,0 +1,211 @@
# Equicord Development Guidelines
## Critical Rules
- **NEVER** edit existing license headers. New files use 2026.
- Push back when the user idea is wrong or not correct.
- **NEVER** git checkout files without explicit permission.
- Always verify components/functions arent deprecated before using.
- **NEVER** use `any` for Discord objects. Import proper types from `@vencord/discord-types`.
- Fix with smallest possible change. No refactoring unless explicitly asked.
- Solve the problem at hand only. No patterns for patterns sake.
- **NEVER** overengineer. No premature abstractions, no helpers for one-time operations, no building for hypothetical futures. Three similar lines is better than a premature abstraction.
- Write modern, robust, professional code. Use current language features, proper error handling, and clean structure.
- Write natural human text in errors, descriptions, messages. No dashes or robotic formatting. Write like "Module not found" not "Module - not found". Keep it simple and conversational.
## Code Rules
- **DELETE** dead code, dont comment it. Git preserves history.
- No hardcoded values, unused imports, magic numbers.
- Import `classNameFactory` from `@utils/css` (NOT `@api/Styles` which is deprecated). **ALWAYS** use `cl()` for class names, never hardcode strings like `"vc-plugin-name-class"`.
- When combining multiple class names use `classes()` from `@utils/misc`, not template strings like `` `${a} ${b}` ``.
- Use utilities from `@utils/`, `@api/`, `@components/`.
- Use `Logger` from `@utils/Logger`, not `console.log`.
- Descriptions: capital first letter, end with period.
- Less code with same functionality is **ALWAYS** better. If it can be done in fewer lines without losing clarity, do it.
- Keep logic flat. Avoid deep nesting, prefer early returns, guard clauses, and simple control flow.
- **NEVER** add comments unless explicitly asked.
- Keep existing comments, they contain important context.
- Use `Map`/`Set`, `.some`/`.find`, no spread in loops, `Promise.all`.
- KISS over clever, flat over nested, explicit over implicit.
## TypeScript
- **Prefer:** optional chaining (`?.`), nullish coalescing (`??`), `const`, arrow functions
- **Use:** destructuring, template literals, object shorthand, array methods
- **Style:** early returns, trust inference, inline single-use variables
## React
- Wrap complex components with `ErrorBoundary.wrap(Component, { noop: true })`.
- Return `null` for conditional rendering, not `undefined`.
- **Forbidden:** `React.cloneElement`, `React.isValidElement`, `React.memo()`, `React.lazy`, `React.Children`
- Always return cleanup functions in `useEffect`.
## Forbidden
### Code
- **No raw DOM manipulation.** No `document.querySelector`, no `MutationObserver`, no `element.style`. Always use webpack patches and React.
- Empty catch blocks.
- Hardcoded minified vars in patches (`e,t,n`).
- **NEVER** use `settings.use()` with arrays. Mutate then reassign.
### Plugins
- **No selfbot or API abuse.** Nothing that automates user actions, spoofs client state, or abuses Discord's API in any way.
- No plugins that only do something trivially achievable with existing built-in plugins or Discord features.
- No CSS-only plugins that only hide or redesign UI elements.
- No plugins targeting specific third-party bots (official Discord apps are fine).
- No untrusted third-party APIs. No plugins requiring users to supply their own API keys.
- Do not introduce new dependencies unless strictly necessary and well justified.
## Anti-Patterns
- `value !== null && value !== undefined` ⟹ `value`
- `array && array.length > 0` ⟹ `array.length`
- `settings?.store?.value` ⟹ `settings.store.value`
- `value || defaultValue` ⟹ `value ?? defaultValue`
## Patching
### Core Principles
- Minimum code touched. Stability over cleverness. One patch per concern.
- Try multiple approaches, keep simplifying until you find the cleanest solution.
- Remove any regex character that doesnt change the match result.
- Not overengineered, not overlooked, just clean and future-proof.
### Find Strings
- Use `#{intl::KEY}` in find when possible, most stable anchor.
- If find matches multiple modules, add nearby stable string like `"),icon:"` or function name.
- `"#{intl::PIN_MESSAGE}),icon:"` is better than generic strings.
### Match Patterns
- Match only what you need to replace, let find do the targeting.
- Can use `#{intl::KEY}` in match too, gets canonicalized to hash.
- `/#{intl::KEY}\)/` beats `/label:\i\.pinned\?.{0,60}#{intl::KEY}\)/`
- `$&` keeps original, append/prepend to it.
- Use bounded gaps: `.{0,50}` not `.+?` or `.*?`
- Only use capture groups if reusing in replace.
### NEVER Do
- **Hardcoded minified vars:** `e,t,n,r,i,o,s,l,c,u,$_,xx,eD,eH,eW` — use `\i` instead
- **Minified chains:** `\i\.\i` alone — surround with stable strings
- **Unbounded gaps:** `.+?` or `.*?` — use `.{0,N}`
- **Generic patterns:** `/className:\i/` alone — add anchor
- **Raw intl hashes:** `.aA4Vce` — use `#{intl::KEY_NAME}`
### Patch Examples
**Clean replacement:**
```js
find: "#{intl::PIN_MESSAGE}),icon:"
match: /#{intl::PIN_MESSAGE}\)/
replace: "$self.getPinLabel(arguments[0]))"
```
**Appending:**
```js
find: "#{intl::VIEW_AS_ROLES_MENTIONS_WARNING}"
match: /#{intl::VIEW_AS_ROLES_MENTIONS_WARNING}.{0,100}(?=])/
replace: "$&,$self.renderTooltip(arguments[0].guild)"
```
**Wrapping:**
```js
find: "#{intl::THREE_USERS_TYPING}"
match: /(?<="aria-atomic":!0,children:)\i/
replace: "$self.renderTypingUsers({ children: $& })"
```
## Plugin Interop
- `isPluginEnabled(name)` from `@api/PluginManager` takes a string, checks required/isDependency/enabled.
- Import the plugin directly from its path to access `.name` and functions.
- Dont use `Vencord.Plugins.plugins` or `plugin.started`. Dont use `"as unknown as"` casting.
```ts
import { isPluginEnabled } from "@api/PluginManager";
import otherPlugin from "@equicordplugins/otherPlugin";
if (!isPluginEnabled(otherPlugin.name)) return null;
otherPlugin.someFunction();
```
## Reference
### Types
`Channel`, `Guild`, `GuildMember`, `User`, `Role`, `Message` from `@vencord/discord-types`
### Components
`Paragraph`, `BaseText`, `Flex`, `Button`, `ErrorCard` from `@components/`
### Settings
Use `definePluginSettings` from `@api/Settings`, not inline settings object.
### Utilities
- **@utils/clipboard** — `copyToClipboard`
- **@utils/discord** — `insertTextIntoChatInputBox`, `getCurrentChannel`, `getCurrentGuild`, `getIntlMessage`, `openUserProfile`, `openPrivateChannel`, `sendMessage`, `copyWithToast`, `getUniqueUsername`, `fetchUserProfile`
- **@utils/css** — `classNameFactory`, `classNameToSelector`
- **@utils/misc** — `classes`, `sleep`, `isObject`, `isObjectEmpty`, `pluralise`, `parseUrl`, `identity`
- **@utils/text** — `formatDuration`, `formatDurationMs`, `humanFriendlyJoin`, `makeCodeblock`, `toInlineCode`, `escapeRegExp`
- **@utils/modal** — `openModal`, `closeModal`, `ModalRoot`, `ModalHeader`, `ModalContent`, `ModalCloseButton`
- **@utils/margins** — `Margins` (`Margins.top8`, `Margins.bottom16`, etc.)
- **@utils/guards** — `isTruthy`, `isNonNullish`
- **@utils/web** — `saveFile`, `chooseFile`
### Webpack Common
#### IconUtils
From `@webpack/common`. **NEVER** hardcode `cdn.discordapp.com` URLs.
- `IconUtils.getUserAvatarURL(user, canAnimate?, size?)`
- `IconUtils.getDefaultAvatarURL(id)`
- `IconUtils.getUserBannerURL({ id, banner, canAnimate?, size })`
- `IconUtils.getGuildIconURL({ id, icon, size?, canAnimate? })`
- `IconUtils.getGuildBannerURL(guild, canAnimate?)`
- `IconUtils.getChannelIconURL({ id, icon })`
- `IconUtils.getEmojiURL({ id, animated, size })`
- `IconUtils.getApplicationIconURL(data)`
- `IconUtils.getGameAssetURL(data)`
#### Stores
`UserStore`, `GuildStore`, `ChannelStore`, `GuildMemberStore`, `SelectedChannelStore`, `SelectedGuildStore`, `PresenceStore`, `RelationshipStore`, `MessageStore`, `EmojiStore`, `ThemeStore`, `PermissionStore`, `VoiceStateStore` from `@webpack/common`
#### Actions
`RestAPI`, `FluxDispatcher`, `MessageActions`, `NavigationRouter`, `ChannelRouter`, `ChannelActionCreators`, `SettingsRouter` from `@webpack/common`
#### Utils
`Constants` (`Constants.Endpoints`), `SnowflakeUtils`, `Parser`, `PermissionsBits`, `moment`, `lodash`, `ColorUtils`, `ImageUtils`, `DateUtils`, `UsernameUtils`, `DisplayProfileUtils` from `@webpack/common`
#### Components
`Tooltip`, `TextInput`, `TextArea`, `Select`, `Slider`, `Avatar`, `Menu`, `Popout`, `ScrollerThin`, `Timestamp` from `@webpack/common`
#### Toasts
`Toasts`, `showToast` from `@webpack/common`
### Imports
- **Lazy** from `@webpack` — `findByPropsLazy`, `findStoreLazy`, `findComponentByCodeLazy`, `findExportedComponentLazy`
- **Common** from `@webpack/common` — `useState`, `useEffect`, `useCallback`, `useStateFromStores`
### Never Hardcode
- `"cdn.discordapp.com/avatars/"` etc. ⟹ `IconUtils`
- `"/api/v9"`, `"/users/@me"` ⟹ `Constants.Endpoints` or `RestAPI`
- `console.log/warn/error` ⟹ `Logger` from `@utils/Logger`
- `` `${a} ${b}` `` for class names ⟹ `classes(a, b)` from `@utils/misc`
- `"/assets/*.png"` for default avatars ⟹ `IconUtils.getDefaultAvatarURL(id)`

240
AGENTS.md
View file

@ -1,240 +0,0 @@
# 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
AGENTS.md Symbolic link
View file

@ -0,0 +1 @@
.rules

View file

@ -1 +1 @@
AGENTS.md .rules

1
GEMINI.md Symbolic link
View file

@ -0,0 +1 @@
.rules