- name
- kickjs-add-module
- description
- Use when the user asks to add a new feature module (controller + service + repo + DTOs).
<!-- Generated by `kick g agents` for kick-fullstack. Edits are overwritten on the next refresh; keep customisation in a SKILL.local.md alongside. -->
**Trigger phrases**: "add a users module", "scaffold tasks", "new feature for X".
**Steps**:
1. Run `kick g module <name>` (use plural form if the project pluralizes — check `kick.config.ts`).
2. Verify the new folder under `src/modules/<name>/` contains `<name>.module.ts` (filename suffix is mandatory for Vite HMR).
3. Confirm the module appears in `src/modules/index.ts` exports — generator does this automatically; verify if you bypassed it.
4. Open `<name>.dto.ts` and tighten the Zod schemas to real fields (the generator emits placeholders).
5. Run `kick typecheck` and `pnpm run test` before claiming done.
**Canonical module shape** — `defineModule` factory, never `class implements AppModule`:
```ts
export const TodosModule = defineModule({
name: 'TodosModule',
build: () => ({
register(container) {
container.registerFactory(TODO_REPO, () => createTodoRepository())
},
routes() {
return { path: '/todos', controller: TodosController }
},
}),
})
```
The module file MUST include `import.meta.glob([...], { eager: true })` for every `@Controller` / `@Service` / `@Repository` / `@Component` class — without it, decorators never fire and DI silently resolves to `undefined` (or routes vanish). Use **recursive** patterns (`./**/*.controller.ts`) so the glob keeps working when you nest files into sub-folders (`controllers/`, `presentation/`, …). If you reorganise and a class stops loading, `kick typegen` flags it as orphaned and `kick typegen --fix` patches the glob for you.
**Multiple route sets / versioning** — `routes()` may return an array with per-entry `version` override:
```ts
routes() {
return [
{ path: '/todos', controller: TodosController }, // /api/v1/todos
{ path: '/todos', version: 2, controller: TodosV2Controller }, // /api/v2/todos
]
}
```
**Conditional / per-tenant mounting** — use `bootstrap({ setup(registry) { registry.mount(...) } })`, not the static `modules` array.
**Composition** — `defineModules().mount(TodosModule()).mount(UsersModule())` (fluent) or `AppModuleEntry[]` (array form).
**Red flags** (stop and ask):
- File created as `<name>.ts` instead of `<name>.module.ts` — Vite plugin's `*.module.[tj]sx?` glob doesn't pick it up; every save becomes a full restart.
- `@Controller('/path')` with a path argument combined with module `routes().path` — duplicates the prefix. The decorator path is OpenAPI metadata only.
- `TodosModule` instead of `TodosModule()` for a module that takes config — refused, since the bare name would silently select the defaults. A module with no config accepts either, but prefer the invoked form for consistency.
- `routes()` returning `router: …` when a `controller:` would do — controller form is required for OpenAPI/Swagger introspection.
- Module not registered in `src/modules/index.ts`.
Ver no GitHub