module-dev
Skill1 starsChanged 4 months agoApache-2.0View on GitHub
Priority: HIGH — Self-contained reference. This file and its reference docs contain ALL necessary patterns for module development. DO NOT fetch source code from GitHub. Weedbox module/package development guide with Uber Fx dependency injection. Use when: creating weedbox module, developing weedbox package, building custom module, implementing dependency injection, managing module lifecycle (OnStart/OnStop), configuring module with Viper, injecting database/logger/NATS/Redis dependencies, building connector-style modules with swappable interface implementations. Covers: Module() function, Params struct, fx.In/fx.Out, weedbox.Module generic, fxmodule.InterfaceModule helper for swappable interfaces, configuration management, error handling, event publishing. Keywords: weedbox module, weedbox package, uber fx, dependency injection, module development, OnStart, OnStop, Params, fx.Invoke, fx.Provide, lifecycle hooks, create module, new module, custom module, Go module, weedbox generic, module skills, fxmodule, InterfaceModule, connector module, swappable implementation, named instance, multi-instance, stateless module, idempotent handler, horizontal scaling, replicas.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- yktsnet/dotfiles-publictheir own version, first seen Nov 5, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- yktsnet/dotfiles-public★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository6 other files it gives its agents.
- common-modulesSkill
- crud-api-devSkill
- project-devSkill
- weedboxSkill
- user-modulesSkill
- workqueue-modulesSkill
The file
We split this skill into the rules it gives an agent
7 rules. About 0 are common, 3 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 65
Before creating a new module, you MUST:
- Very specific: line 71
Verify index naming: - When creating GORM models with named indexes, use idx_<tablename>_<columns> format (SQLite index names are global)
- Very specific: line 235
When two implementations of the same interface are loaded into the same fx.App (e.g.
- Very specific: line 376
Scope naming: Use consistent naming (mymodule, mymodule_apis)
- Very specific: line 377
Logging: Use structured logging with zap.String(), zap.Error()
- Distinctive: line 378
Configuration: Always set defaults, use scoped config paths
- Distinctive: line 381
Errors: Use module-specific error code ranges
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Weedbox Module Development
This skill helps develop modules using the weedbox framework with Uber FX dependency injection.
Contents
- Critical Rules / Common Mistakes / Pre-Development Checklist
- Module Structure
- Three Methods for Creating Modules and Quick Comparison
- Injection Rules
- User Modules Reference / Common Modules Reference
- Configuration Management
- Database Models
- Error Handling / Event Handling
- Multi-Instance Safety
- Registering Module / Best Practices / Module Checklist
- Module Skills
- Related Documentation
Framework Overview
Plasma backend uses:
- weedbox/weedbox - Base module framework
- weedbox/common-modules - Common reusable modules
- go.uber.org/fx - Dependency injection
⚠️ Critical Rules
Source of Truth — Do NOT Fetch from GitHub
This file and its reference docs (references/*.md) are the complete reference for weedbox module development. They contain all necessary patterns, code examples, and best practices. DO NOT browse GitHub repositories to look up weedbox module source code.
⚠️ Common Mistakes (DO NOT)
| Wrong | Correct | Reason |
m.GetParams() | m.Params() | weedbox.Module uses Params() method |
*sqlite_connector.SQLiteConnector | database.DatabaseConnector | Use interface types, not concrete implementations |
*postgres_connector.PostgresConnector | database.DatabaseConnector | Same as above, always use interface for database |
*http_server.HTTPServer \name:"..."`` | *http_server.HTTPServer | common-modules do NOT need name tag |
gorm:"uniqueIndex:idx_foo_bar" | gorm:"uniqueIndex:idx_tablename_foo_bar" | SQLite index names are GLOBAL - must include table name prefix |
📋 Pre-Development Checklist
Before creating a new module, you MUST:
- Read existing modules - Find an existing module in the project (e.g.,
customer/module.go) and read the complete code as a template - Verify Params types - Dependencies must use interface types (e.g.,
database.DatabaseConnector), not concrete implementations - Verify method names - weedbox modules use
m.Params()to access parameters - Verify name tag rules - Method 2 modules require
nametag, common-modules do NOT - Verify index naming - When creating GORM models with named indexes, use
idx_<tablename>_<columns>format (SQLite index names are global)
Module Structure
pkg/mymodule/
├── module.go # Module definition and FX setup
├── <feature>.go # Business logic (named by feature, e.g., job.go, schedule.go)
├── models/ # Data models subdirectory
│ ├── model1.go
│ └── model2.go
├── errors.go # Error definitions
├── config.go # Configuration structs
├── codec.go # Serialization helpers
├── utils.go # Utility functions
└── *_test.go # TestsNote: Business logic files are named by their feature/responsibility (e.g., job.go, blueprint.go, schedule.go), not generically as core.go.
Three Methods for Creating Modules
Method 1: Manual FX Module
Full control over FX dependency injection with manual lifecycle hook registration.
type Params struct {
fx.In
Logger *zap.Logger
Database database.DatabaseConnector
}
type MyModule struct {
params Params
logger *zap.Logger
scope string
}
func Module(scope string) fx.Option {
return fx.Module(scope,
fx.Provide(func(p Params) *MyModule { ... }),
fx.Invoke(func(lc fx.Lifecycle, m *MyModule) {
lc.Append(fx.Hook{OnStart: m.onStart, OnStop: m.onStop})
}),
)
}When to use: Complex FX annotations, fine-grained control, multiple lifecycle hooks.
For complete documentation, see ./references/METHOD1_MANUAL_FX.md.
Method 2: Weedbox Generic Module (Recommended for business modules)
Cleaner code using weedbox.Module[P] generics with built-in lifecycle management.
type Params struct {
weedbox.Params
Database database.DatabaseConnector
}
type MyModule struct {
weedbox.Module[*Params]
}
func Module(scope string) fx.Option {
m := new(MyModule)
return fx.Module(scope,
fx.Supply(fx.Annotated{Name: scope, Target: m}),
fx.Invoke(func(p Params) { weedbox.InitModule(scope, &p, m) }),
)
}
func (m *MyModule) OnStart(ctx context.Context) error { ... }
func (m *MyModule) OnStop(ctx context.Context) error { ... }
func (m *MyModule) InitDefaultConfigs() { ... }When to use: New business-logic modules, simple dependencies, less boilerplate.
For complete documentation, see ./references/METHOD2_WEEDBOX_GENERIC.md.
Method 3: Interface Module (fxmodule.InterfaceModule) — for swappable implementations
For connector-style modules — those that implement a shared interface (database connectors, cache backends, message brokers, etc.) and may be loaded side-by-side with other implementations of the same interface.
import (
"github.com/weedbox/common-modules/database"
"github.com/weedbox/weedbox/fxmodule"
)
type Params struct {
fx.In
Lifecycle fx.Lifecycle
Logger *zap.Logger
}
func Module(scope string) fx.Option {
return fxmodule.InterfaceModule[database.DatabaseConnector](
scope,
func(p Params) database.DatabaseConnector {
c := &MyConnector{ /* ... */ }
p.Lifecycle.Append(fx.Hook{OnStart: c.onStart, OnStop: c.onStop})
return c
},
)
}InterfaceModule[I] registers the constructor as name:"<scope>" returning I, and the first call across the process also exposes the same instance as the unnamed default of I. Multiple implementations can be loaded into the same fx.App — consumers disambiguate via name:"<scope>", and existing single-load callers that inject I without a tag keep working unchanged.
When to use: The module is one implementation of a swappable interface (database.DatabaseConnector, cache.Cache, etc.); multiple implementations may need to coexist in the same app.
Don't use for plain business-logic modules that expose a concrete struct — use Method 1 or Method 2 for those.
For complete documentation, see ./references/METHOD3_INTERFACE_MODULE.md.
Quick Comparison
| Aspect | Method 1 (Manual FX) | Method 2 (Weedbox Generic) | Method 3 (InterfaceModule) |
| Module shape | Concrete struct | Concrete struct + base class | Constructor returning an interface |
| Base struct | Custom struct | weedbox.Module[*Params] | None (constructor function) |
| Params | fx.In struct | Embed weedbox.Params | fx.In struct |
| Logger | m.logger (manual) | m.Logger() (built-in) | Manual (in the constructor) |
| Config path | m.getConfigPath() | m.GetConfigPath() | Manual |
| Lifecycle | lc.Append(fx.Hook{}) | OnStart() / OnStop() | lc.Append(fx.Hook{}) inside ctor |
| Side-by-side loading | Not supported | Not supported | Supported — multiple impls in one app |
| Injection (single-load) | No name tag | Requires name tag | No name tag (unnamed default) |
| Injection (multi-load) | N/A | N/A | name:"<scope>" on each consumer |
Injection Rules
type Params struct {
weedbox.Params
// Method 2 modules - REQUIRE `name` tag
PkgManager *pkg_manager.PkgManager `name:"pkg_manager"`
// User modules - REQUIRE `name` tag (all use Method 2)
User *user.UserManager `name:"user"`
RBAC *rbac.RBACManager `name:"rbac"`
Auth *auth.AuthManager `name:"auth"`
// Method 1 modules - NO `name` tag needed
ViewManager *view_manager.ViewManager
// Common modules (single-load) - NO `name` tag
Database database.DatabaseConnector
}Multi-load exception (Method 3 / InterfaceModule)
When two implementations of the same interface are loaded into the same fx.App (e.g. sqlite_connector.Module("cache") + postgres_connector.Module("main")), consumers MUST disambiguate via the name tag:
type Params struct {
fx.In
Cache database.DatabaseConnector `name:"cache"` // sqlite
Main database.DatabaseConnector `name:"main"` // postgres
}The first connector loaded in the process also claims the unnamed default slot, so existing code that injects the interface without a tag continues to work — but if load order is brittle, always inject by named tag.
Test caveat: tests building multiple fx.Apps in one process must call fxmodule.ResetClaim[I]() between apps. See METHOD3 reference for details.
User Modules Reference
Available from github.com/weedbox/user-modules:
| Module | Import | Purpose |
user | user.Module("user") | User CRUD, bcrypt, UUID v7 |
rbac | rbac.Module("rbac") | RBAC with privy |
auth | auth.Module("auth") | JWT auth, middleware |
permissions | (plain package) | Permission definitions |
user_apis | user_apis.Module("user_apis") | User REST endpoints |
auth_apis | auth_apis.Module("auth_apis") | Auth REST endpoints |
role_apis | role_apis.Module("role_apis") | Role/resource REST endpoints |
http_token_validator | http_token_validator.Module("scope") | Global JWT middleware |
See user-modules skill for detailed documentation.
Common Modules Reference
Available from github.com/weedbox/common-modules:
| Module | Import | Purpose |
logger | logger.Module() | Zap logging |
http_server | http_server.Module("scope") | Gin HTTP server |
sqlite_connector | sqlite_connector.Module("scope") | SQLite connection |
postgres_connector | postgres_connector.Module("scope") | PostgreSQL connection |
nats_connector | nats_connector.Module("scope") | NATS client |
database | database.DatabaseConnector | Database interface |
daemon | daemon.Module("scope") | Daemon lifecycle |
lifecycle | lifecycle.Module("scope") | PostStart/PreStop hooks (after all modules start / before any stops) |
healthcheck_apis | healthcheck_apis.Module("scope") | Health endpoints |
configs | configs.NewConfig("PREFIX") | Configuration |
Configuration Management
Using Viper
// Set defaults
viper.SetDefault(m.GetConfigPath("timeout"), "30s")
// Read values
timeout := viper.GetDuration(m.GetConfigPath("timeout"))Config File Format
[mymodule]
timeout = "30s"
enabled = trueEnvironment Variables
export PLASMA_MYMODULE_TIMEOUT=60sDatabase Models
Models are placed in a models/ subdirectory of the module (e.g., pkg/mymodule/models/).
Load-bearing rule: SQLite index names are global across all tables — always name explicit indexes idx_<table>_<columns> (e.g., idx_task_status), never generic names like idx_status. Unnamed indexes (gorm:"index") are auto-named by GORM and safe.
For GORM index configuration, naming examples, common index patterns, and auto migration, see ./references/DATABASE_MODELS.md.
Error Handling
const (
ErrCodeGeneral = 400000
ErrCodeNotFound = 400001
ErrCodeInvalidData = 400002
)Event Handling
Publishing Events
func (m *MyModule) publishEvent(ctx context.Context, subject string, data interface{}) error {
payload, _ := json.Marshal(data)
return m.Params().EventManager.Publish(ctx, subject, payload)
}Subscribing to Events
consumer := event_manager.NewWorkQueueConsumer(
m.Params().NATSConnector,
"mymodule.events",
"mymodule-consumer",
m.handleEvent,
)Multi-Instance Safety
Assume every module runs as N replicas in production (see the root skill rule). When writing a module:
- No in-process source of truth — locks, counters, dedup sets, and sessions live in the database, Redis, or NATS; process memory is at most a cache.
- No bare
time.Ticker/time.AfterFuncfor business schedules — every replica would fire independently. Use theschedulermodule; itspostgresandnatsmodes deliver each tick to exactly one replica. - Distribute background work via
workqueue.WorkQueue— competing consumers across replicas — instead of a goroutine polling a table that every replica would race on. - Write idempotent handlers — scheduler and workqueue deliveries are at-least-once; a redelivered task must be harmless (guard with a unique key or a state check in the database).
- In-memory caches need an invalidation story — a write on replica A must not leave stale reads on replica B indefinitely; prefer
redis_connectoror short TTLs.
Per-instance concerns (HTTP handlers, connection pools, logging, health status) need no coordination.
Registering Module
// modules.go
func loadModules() ([]fx.Option, error) {
modules := []fx.Option{
mymodule.Module("mymodule"),
}
return modules, nil
}Best Practices
- Scope naming: Use consistent naming (
mymodule,mymodule_apis) - Logging: Use structured logging with
zap.String(),zap.Error() - Configuration: Always set defaults, use scoped config paths
- Lifecycle: Initialize in
OnStart, cleanup inOnStop - Dependencies: Declare all dependencies in
Paramsstruct - Errors: Use module-specific error code ranges
- Multi-instance: Assume N replicas — see Multi-Instance Safety
Module Checklist
- Choose a method: Method 2 (default for business modules), Method 1 (manual FX control), or Method 3 (swappable interface/connector)
- Create
module.gowithModule()function - Define
Paramsstruct with dependencies - Implement lifecycle hooks
- Set default configurations with Viper
- Define data models if needed
- Add error codes in appropriate range
- Verify multi-instance safety: no in-process coordination state, no bare timers for business schedules, idempotent handlers
- Register in
modules.go - Add tests
Module Skills
Module Skills are .skills/*.md documentation files placed inside each module (e.g., pkg/mymodule/.skills/mymodule-development.md). Create one after completing a module, especially for complex modules or API modules that need an endpoint reference.
For directory structure, naming conventions, content templates, and checklists, see ./references/MODULE_SKILLS.md.
Related Documentation
- ./references/METHOD1_MANUAL_FX.md - Manual FX module details
- ./references/METHOD2_WEEDBOX_GENERIC.md - Weedbox generic module details
- ./references/METHOD3_INTERFACE_MODULE.md - Interface/connector module details
- ./references/DATABASE_MODELS.md - GORM models, indexes, and migration
- ./references/MODULE_SKILLS.md - Module skills guide
- ../user-modules/SKILL.md - User management, auth, and RBAC modules
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

