Claude-skill-registry add-feature
Scaffold a new toggleable feature with full structure, storage, API exposure, and bootstrap registration
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/add-feature-ariedam64-gemini" ~/.claude/skills/majiayu000-claude-skill-registry-add-feature && rm -rf "$T"
manifest:
skills/data/add-feature-ariedam64-gemini/SKILL.mdsource content
Add Feature Skill
Usage
/add-feature <featureName>
Step 1: Ask Questions
Before creating files, gather this information:
1. Game Data (CRITICAL)
Does this feature need game data? A) Static data → MGData B) Real-time state → Globals C) Both → MGData + Globals D) None
If A or C (Static data), specify which:
[ ] Plants - definitions, growth stages, harvest data [ ] Items - definitions, categories, properties [ ] Pets - definitions, abilities, stats [ ] Mutations - definitions, effects [ ] Shops - configurations (not stock) [ ] Recipes - crafting recipes [ ] Other: ___________
If B or C (Real-time), specify which:
[ ] currentTile - player's current tile [ ] myGarden - player's garden state [ ] myInventory - player's inventory [ ] myPets - player's pets [ ] players - other players in room [ ] shops - current shop stock [ ] weather - current weather [ ] gameMap - full map data [ ] Other: ___________
2. UI Type
A) Gemini HUD only - UI in Gemini overlay B) Game UI injection - Modifies existing game UI (src/ui/inject/qol/<name>/) C) Both - Feature logic + game UI injection D) No UI - Backend/logic only
3. Sprites (if UI selected)
Does the UI need game sprites? A) Yes → MGSprite.show() / MGSprite.toCanvas() B) No - Text/icons only If A, which types? [ ] Plant sprites [ ] Item sprites [ ] Pet sprites [ ] Cosmetic sprites [ ] Tile sprites [ ] Other: ___
4. UI Components (if UI selected)
Does the UI need existing components? → REUSE, never recreate! [ ] ArcadeButton, GeminiIconButton → Buttons [ ] Modal → Dialogs/popups [ ] ProgressBar → Progress indicators [ ] SegmentedControl → Tab-like selection [ ] Tab → Tabs [ ] SoundPicker → Audio selection [ ] Other from src/ui/components/
5. Module Dependencies
[ ] MGCalculators - XP, prices, growth time calculations [ ] MGCosmetic - Player cosmetic data [ ] MGTile - Map/tile utilities [ ] MGAudio - Sound effects, notifications [ ] MGShopActions - Buy/sell operations [ ] MGEnvironment - World/environment data [ ] None
6. WebSocket
A) Outgoing actions - Sends messages (api.ts) B) Incoming handlers - Reacts to server messages C) Middleware - Intercept/block/modify outgoing messages D) Multiple (specify which) E) None
If C (Middleware), specify:
[ ] Intercept specific message types [ ] Modify payload before sending [ ] Block messages based on conditions [ ] Log/track outgoing messages Which message types? ___________
7. Brief Description
One sentence for documentation:
Step 2: Create Structure
src/features/<featureName>/ ├── types.ts # Config, constants, types ├── state.ts # Storage operations ├── index.ts # Public API (MG<FeatureName>) ├── logic/ │ └── core.ts # Business logic ├── ui.ts # (if HUD UI) ├── handler.ts # (if incoming WS) └── middleware.ts # (if WS middleware)
Naming Conventions
- Folder:
→camelCasesrc/features/autoHarvest/ - Public API:
→MG<PascalCase>MGAutoHarvest - Storage key:
→SCREAMING_SNAKEAUTO_HARVEST - Storage value:
→feature:<camelCase>:configfeature:autoHarvest:config
File Responsibilities
| File | Purpose |
|---|---|
| Config interface with , , |
| , , |
| with , , , |
| , , array |
| HUD components (reuse existing!) |
| for incoming WS |
| , |
Read existing features for templates:
src/features/*/
Step 3: Register
A) Storage key → src/utils/storage.ts
src/utils/storage.tsexport const FEATURE_KEYS = { // ... existing <FEATURE_NAME>: 'feature:<featureName>:config', } as const;
B) Export → src/features/index.ts
src/features/index.tsexport { MG<FeatureName> } from './<featureName>'; export type { <FeatureName>Config } from './<featureName>';
C) API → src/api/index.ts
src/api/index.tsimport { MG<FeatureName> } from "../features/<featureName>"; Features: { <FeatureName>: MG<FeatureName>, }
D) Bootstrap → src/ui/loader/bootstrap.ts
src/ui/loader/bootstrap.tsimport { MG<FeatureName> } from "../../features/<featureName>"; { name: "<FeatureName>", init: () => MG<FeatureName>.init() }
E) Game UI injection (if applicable)
Create structure in
src/ui/inject/qol/<featureName>/:
├── index.ts # init(), destroy(), isEnabled() ├── inject.ts # DOM injection logic ├── styles.css.ts # Scoped styles └── state.ts # (optional)
Step 4: Key Patterns
Using Globals (reactive state)
import { getMyInventory } from '../../../globals/variables/myInventory'; const cleanups: (() => void)[] = []; function start(): void { const unsub = getMyInventory().subscribe((inventory) => { // React to changes onInventoryChange(inventory); }); cleanups.push(unsub); } function stop(): void { cleanups.forEach(fn => fn()); cleanups.length = 0; }
Using Middleware
import { registerMiddleware } from '../../websocket/middlewares/registry'; let unregister: (() => void) | null = null; export function registerFeatureMiddleware(): void { if (unregister) return; unregister = registerMiddleware((message) => { // Return message (pass), modified message, or null (block) return message; }); } export function unregisterFeatureMiddleware(): void { unregister?.(); unregister = null; }
UI with existing components
import { createArcadeButton, createProgressBar } from '../../ui/components'; function buildUI(container: HTMLElement): void { const button = createArcadeButton({ label: 'Action', onClick: handleClick }); const progress = createProgressBar({ value: 0, max: 100 }); container.appendChild(button.root); container.appendChild(progress.root); // Track for cleanup cleanups.push(() => { button.destroy(); progress.destroy(); }); }
Step 5: Validate
Structure
-
withtypes.ts
in configenabled: boolean -
exportsindex.tsMG<FeatureName> - Public API:
,init
,destroy
,isEnabledsetEnabled -
folder for business logiclogic/
Lifecycle
-
is idempotent (safe to call multiple times)init() -
cleans up ALL resourcesdestroy() - No side effects on import
- Uses
(notFEATURE_KEYS
)MODULE_KEYS
Game Data (NEVER hardcode!)
- Static data →
MGData.get('plants'/'items'/'pets'/...) - Sprites →
/MGSprite.show()MGSprite.toCanvas() - Calculations →
MGCalculators - Real-time → Globals with
+ unsubscribe in cleanup.subscribe()
UI (if applicable)
- Reuses existing components (never recreate Button, Modal, etc.)
- Child components'
called in cleanupdestroy() - Uses CSS variables (no hardcoded colors)
- If injection: fully removes DOM on destroy
WebSocket (if applicable)
- Actions via
onlywebsocket/api.ts - Handlers via
registerHandler() - Middleware returns
ormessage
, never throwsnull - Middleware unregisters on
destroy() - Message types from
enumsprotocol.ts
Registration
- Storage key in
src/utils/storage.ts - Export in
src/features/index.ts - Exposed in
src/api/index.ts - Initialized in
src/ui/loader/bootstrap.ts
References
- Rules:
,.claude/rules/features.md.claude/rules/core.md - Existing features:
src/features/*/ - Modules:
(MGData, MGSprite, MGCalculators, etc.)src/modules/*/ - Globals:
src/globals/variables/ - WebSocket:
(api.ts, middlewares/, handlers/)src/websocket/ - UI Components:
src/ui/components/ - UI Injection:
,src/ui/inject/qol/.claude/rules/ui/ui.inject.md