marko-php/marko/create-module

Claude Code and agent skill by marko-php/marko

Trust tier 2
  • community
  • ext:[{"type":"hook","description":"Pre/post lifecycle hooks"}]
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Scaffold a new Marko module — a self-contained Composer package with composer.json, namespaced src/, and Pest tests. **Use this skill whenever the user asks to create, add, or scaffold a new Marko module or package.** Concrete triggers: 'create a module named payment', 'scaffold an acme/blog package', 'add a new module for X', 'break this code out into its own package', 'I need a new module', 'make me a Marko module'. Marko makes no distinction between core and third-party modules: layout is identical.

Install

npx vskill@latest install marko-php/marko/create-module

Semi-extensibleMentions customization, but needs skill-specific knowledge. Read the extensible skills standard

SKILL.md

Create a Marko module

A Marko module is a Composer package that the framework auto-discovers via the extra.marko.module flag. Modules can live anywhere — app/{name}/ for application code, modules/{vendor}/{name}/ for a distributable package dropped into a host project, packages/{name}/ in the framework monorepo, or vendor/{vendor}/{name}/ once installed from Packagist. The layout is identical in every case. Directory and Composer-name segments are always lowercase; only the PHP namespace is StudlyCase.

This skill is the canonical specification for a Marko module. Do not inspect existing modules in this project to infer layout — siblings may have drifted from spec. Copy the templates from `assets/` verbatim, substitute placeholders, and stop.

Step 1 — Confirm the name, then pick a location

If the user did not give the module a name, ask for one before scaffolding anything. Do not invent a name, reuse a vendor as the name, or proceed with a placeholder.

Casing rule — read this before creating any directory

A module's identity has two forms that must stay in sync:

  • Directory and Composer name → always lowercase. They are identical segment-for-segment, so a module can move between tiers (e.g. modules/ → vendor/) or be symlinked with zero path rewrites.
  • PHP namespace → always StudlyCase. blog → Blog, my-payment → MyPayment.

So the name blog yields directory blog/, Composer name …/blog, and namespace …\Blog. Lowercase module directories are correct — do not capitalize them. The casing only flips to StudlyCase in the PHP namespace and the psr-4 autoload key. (If you have seen "lowercase module names" before and assumed it was a bug, it was not: the directory is supposed to be lowercase; only the namespace is StudlyCase.)

Pick the tier by where the module lives
TierDirectoryComposer nameNamespaceWhen
App-localapp/{name}/app/{name}App\{Name}Application code, not distributed. The vendor segment is always the literal app — there is no project-derived vendor.
Distributablemodules/{vendor}/{name}/{vendor}/{name}{Vendor}\{Name}A package you intend to publish or share, dropped into a host project. Two-segment, vendor-scoped.
Framework monorepopackages/{name}/marko/{name}Marko\{Name}Only when working inside the Marko monorepo itself (root contains packages/core/).

Never author into `vendor/` — it is ephemeral and populated by Composer. The modules/ tier is the manual-install mirror of vendor/: same two-segment {vendor}/{name} shape, so a module can later move to vendor/ (or be symlinked) without changes.

Default tier: if the user does not specify, assume an app-local module (app/{name}/, namespace App\{Name}). Only use modules/{vendor}/{name}/ when the user signals the module is meant to be distributed/shared.

Choosing `{vendor}` (distributable modules only — app-local modules have no vendor): derive it from the host project's root directory name (a project in ~/Sites/acme → vendor acme). Do not read it from the project's composer.json name: a project scaffolded from marko/skeleton still carries marko/skeleton there, so the directory name is the reliable signal. Never use `marko` as the vendor for an application or third-party module — that vendor is reserved for packages inside the framework monorepo.

Step 2 — Write composer.json

Copy the appropriate template to <module-root>/composer.json and substitute all placeholders.

Required keys: name, type: marko-module, require.marko/core, psr-4 autoload, and extra.marko.module: true to flag it for the code indexer. Never set a `version` field — let Composer infer it from the branch.

Deterministic constraint selection rule

Resolve {{marko_constraint}} and choose the template using this ordered decision tree:

  1. Inside the Marko monorepo (root directory contains packages/core/) → use assets/composer.json.monorepo.tmpl. Set {{marko_constraint}} to self.version. This ensures all packages in the monorepo resolve against each other at the same version without manual pinning.
  1. Otherwise (host project / standalone module) → use assets/composer.json.tmpl. Determine {{marko_constraint}} by reading the host project's root composer.json:
  • Find the first marko/* constraint that is already declared (e.g. "marko/framework": "*" → constraint is *; "marko/framework": "dev-develop" → constraint is dev-develop; "marko/framework": "^1.2" → constraint is ^1.2).
  • If no marko/* constraint is present in the host composer.json, default to *.

Using * (or matching the host's existing constraint) ensures the scaffolded module resolves against whatever version of Marko the host project is consuming — whether that is a tagged release, a dev-develop branch alias, or a Composer path symlink. Never default to `^1.0`: that constraint is incompatible with any host consuming dev-develop or *, which is the most common case during active development (e.g. marko/skeleton requires marko/framework: *).

Step 3 — Create the directory layout

{module-root}/
composer.json
src/ # PSR-4 source
tests/
Pest.php # Pest bootstrap
Unit/
Feature/
README.md # Slim pointer per docs/DOCS-STANDARDS.md

Copy assets/Pest.php.tmpl to tests/Pest.php. No placeholder substitution needed.

Step 4 — Decide whether you need module.php

module.php is optional. Only create it if the module needs explicit DI bindings (interface → concrete class wiring), singleton declarations, or boot callbacks for lifecycle hooks.

If the module is just classes that auto-resolve, omit `module.php` entirely. Do not create an empty manifest.

When you do need it, copy assets/module.php.tmpl to <module-root>/module.php and substitute {{Vendor}} and {{Name}} placeholders.

Step 5 — Add a slim README

Copy assets/README.md.tmpl to <module-root>/README.md and substitute {{vendor}} and {{name}} placeholders.

Per docs/DOCS-STANDARDS.md, package READMEs are slim pointers — title, install command, one quick example, and a link to the full docs page. Substantive documentation belongs in docs/src/content/docs/packages/{name}.md, not the README.

Step 6 — Verify the module is discovered

The module exists because you wrote it. The runtime discovers and autoloads it live on the next request — no composer dump-autoload, no index rebuild, no extra registration step. The MCP/LSP index self-refreshes (scoped to app/ and modules/), so list_modules and validate_module will reflect freshly created code automatically.

If you want to double-check structure, confirm:

  • composer.json has extra.marko.module: true
  • The module's psr-4 namespace resolves correctly

You may call list_modules or validate_module as an optional cross-check, but these are not gates — the module is live as soon as the files are in place.

Verification

After writing files, expect LSP diagnostics from marko-lsp to surface in the same turn. Resolve all diagnostics before declaring the module complete — diagnostics are the verification gate, not optional warnings. Calling list_modules is an optional cross-check to confirm the MCP index reflects the new module; it is not required for the module to be active.

Conventions to enforce

  • Every PHP file: declare(strict_types=1);
  • Constructor property promotion always
  • Type declarations on every parameter, return, and property
  • No final classes (blocks Preferences extensibility)
  • No magic methods — be explicit
  • Use readonly where immutability is appropriate, not as a blanket rule

What this skill does not cover

  • Authoring plugins for the new module — see the marko-create-plugin skill
  • Adding routes, observers, commands — see the relevant Marko docs pages
  • Database migrations — see the marko/database package docs

See also

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
383
Repository forks
37
7-day trend score
30

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Extension points

  • hookPre/post lifecycle hooks

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/marko-php/marko/create-module/badge)](https://verified-skill.com/skills/marko-php/marko/create-module)

Something wrong with this skill or its page? Tell us.