agentleFS
weedboxweedbox/skills/module-dev/SKILL.md

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

  • Other versions1 project uses a version of this.

    Word for word · 0

    None found.

    Edited · 0

    None found.

    Wrote their own version · 1

    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.

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
  1. Distinctive:

    Before creating a new module, you MUST:

    line 65
  2. Very specific:

    Verify index naming: - When creating GORM models with named indexes, use idx_<tablename>_<columns> format (SQLite index names are global)

    line 71
  3. Very specific:

    When two implementations of the same interface are loaded into the same fx.App (e.g.

    line 235
  4. Very specific:

    Scope naming: Use consistent naming (mymodule, mymodule_apis)

    line 376
  5. Very specific:

    Logging: Use structured logging with zap.String(), zap.Error()

    line 377
  6. Distinctive:

    Configuration: Always set defaults, use scoped config paths

    line 378
  7. Distinctive:

    Errors: Use module-specific error code ranges

    line 381

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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)

WrongCorrectReason
m.GetParams()m.Params()weedbox.Module uses Params() method
*sqlite_connector.SQLiteConnectordatabase.DatabaseConnectorUse interface types, not concrete implementations
*postgres_connector.PostgresConnectordatabase.DatabaseConnectorSame as above, always use interface for database
*http_server.HTTPServer \name:"..."``*http_server.HTTPServercommon-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:

  1. Read existing modules - Find an existing module in the project (e.g., customer/module.go) and read the complete code as a template
  2. Verify Params types - Dependencies must use interface types (e.g., database.DatabaseConnector), not concrete implementations
  3. Verify method names - weedbox modules use m.Params() to access parameters
  4. Verify name tag rules - Method 2 modules require name tag, common-modules do NOT
  5. 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           # Tests

Note: 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

AspectMethod 1 (Manual FX)Method 2 (Weedbox Generic)Method 3 (InterfaceModule)
Module shapeConcrete structConcrete struct + base classConstructor returning an interface
Base structCustom structweedbox.Module[*Params]None (constructor function)
Paramsfx.In structEmbed weedbox.Paramsfx.In struct
Loggerm.logger (manual)m.Logger() (built-in)Manual (in the constructor)
Config pathm.getConfigPath()m.GetConfigPath()Manual
Lifecyclelc.Append(fx.Hook{})OnStart() / OnStop()lc.Append(fx.Hook{}) inside ctor
Side-by-side loadingNot supportedNot supportedSupported — multiple impls in one app
Injection (single-load)No name tagRequires name tagNo name tag (unnamed default)
Injection (multi-load)N/AN/Aname:"<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:

ModuleImportPurpose
useruser.Module("user")User CRUD, bcrypt, UUID v7
rbacrbac.Module("rbac")RBAC with privy
authauth.Module("auth")JWT auth, middleware
permissions(plain package)Permission definitions
user_apisuser_apis.Module("user_apis")User REST endpoints
auth_apisauth_apis.Module("auth_apis")Auth REST endpoints
role_apisrole_apis.Module("role_apis")Role/resource REST endpoints
http_token_validatorhttp_token_validator.Module("scope")Global JWT middleware

See user-modules skill for detailed documentation.

Common Modules Reference

Available from github.com/weedbox/common-modules:

ModuleImportPurpose
loggerlogger.Module()Zap logging
http_serverhttp_server.Module("scope")Gin HTTP server
sqlite_connectorsqlite_connector.Module("scope")SQLite connection
postgres_connectorpostgres_connector.Module("scope")PostgreSQL connection
nats_connectornats_connector.Module("scope")NATS client
databasedatabase.DatabaseConnectorDatabase interface
daemondaemon.Module("scope")Daemon lifecycle
lifecyclelifecycle.Module("scope")PostStart/PreStop hooks (after all modules start / before any stops)
healthcheck_apishealthcheck_apis.Module("scope")Health endpoints
configsconfigs.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 = true

Environment Variables

export PLASMA_MYMODULE_TIMEOUT=60s

Database 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:

  1. 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.
  2. No bare time.Ticker / time.AfterFunc for business schedules — every replica would fire independently. Use the scheduler module; its postgres and nats modes deliver each tick to exactly one replica.
  3. Distribute background work via workqueue.WorkQueue — competing consumers across replicas — instead of a goroutine polling a table that every replica would race on.
  4. 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).
  5. In-memory caches need an invalidation story — a write on replica A must not leave stale reads on replica B indefinitely; prefer redis_connector or 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

  1. Scope naming: Use consistent naming (mymodule, mymodule_apis)
  2. Logging: Use structured logging with zap.String(), zap.Error()
  3. Configuration: Always set defaults, use scoped config paths
  4. Lifecycle: Initialize in OnStart, cleanup in OnStop
  5. Dependencies: Declare all dependencies in Params struct
  6. Errors: Use module-specific error code ranges
  7. 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.go with Module() function
  • Define Params struct 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

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.