Skills generator
`@mandujs/skills` auto-generates a per-project skill manifest — the list of domain-specific actions an agent can perform in this repo. Pure, deterministic, safe to run in CI.
On this page
Skills generator
@mandujs/skills auto-generates a per-project skill manifest —
the list of domain-specific actions an agent can perform in this
repo. It scans your source for exported functions matching skill
conventions, writes a deterministic manifest, and surfaces it to
agents via the mandu.ai.brief MCP tool.
# Generate / refresh the manifest
bunx @mandujs/cli skills generate
# Or via MCP (when the tool surface is registered)
# Tool: mandu.ai.brief → output.skills array
Static vs generated skills
| Kind | Path | Who writes it |
|---|---|---|
| Static | skills/<id>/index.ts |
Human-written, versioned, reviewed |
| Generated | .mandu/skills/generated/<id>.ts |
Auto-emitted by the generator from heuristic source scans |
The manifest at .mandu/skills/manifest.json lists both with a
source discriminator:
{
"version": 1,
"generatedAt": "2026-04-19T10:00:00.000Z",
"skills": [
{
"id": "send-verification-email",
"source": "static",
"path": "skills/send-verification-email/index.ts",
"description": "Send the verification email for a freshly-created user."
},
{
"id": "scaffold-resource",
"source": "generated",
"path": ".mandu/skills/generated/scaffold-resource.ts",
"description": "Generate a resource scaffold (derived from mandu generate resource)."
}
]
}
What a skill is
A skill is an exported async function with a known shape:
// skills/send-verification-email/index.ts
import type { Skill } from "@mandujs/skills";
export const skill: Skill<{ email: string }, { sent: boolean }> = {
id: "send-verification-email",
description: "Send the verification email for a freshly-created user.",
input: {
type: "object",
properties: {
email: { type: "string", format: "email" },
},
required: ["email"],
},
output: {
type: "object",
properties: { sent: { type: "boolean" } },
},
async run({ email }, ctx) {
const mail = ctx.mail ?? ctx.resolve("mail");
await mail.send({ to: email, subject: "Verify", body: "..." });
return { sent: true };
},
};
export default skill;
id must be unique across the manifest. input/output are
JSON-Schema-lite shapes — enough for an agent to build a correct
call without guessing.
Generator conventions
The generator currently emits skills for:
mandu scaffold/mandu newshortcuts —generate-resource,generate-page, etc. Each becomes a skill whoserun()delegates to the underlying command.- Contracts with a single endpoint — each
shared/contracts/*.contract.tsyields a skillcall-<contract-id>wrapping the typed fetch. - Seed runners — each
spec/seeds/*.seed.tsyields a skillseed-<file>(gated on env, matches CLI semantics).
Heuristics are conservative — a function that "might be" a skill is skipped unless it matches a clear shape. The generator's output is idempotent; re-running it produces byte-identical files given the same source.
Purity
The generator is pure in the same sense as the loop-closure framework:
- Only reads from the filesystem (under the project root).
- No network, no
spawn, no env-var reads. - No timestamps in the generated source (only in the manifest
generatedAtfield). - Byte-stable output given the same input source tree.
This makes it safe to run in CI as a non-destructive gate:
- run: bunx @mandujs/cli skills generate
- run: git diff --exit-code .mandu/skills/ # fails if generator drifted
Invocation
# Refresh the manifest
bunx @mandujs/cli skills generate
# Print the manifest to stdout (no write)
bunx @mandujs/cli skills generate --stdout
# List every known skill (static + generated)
bunx @mandujs/cli skills list
# Show one skill's spec
bunx @mandujs/cli skills show send-verification-email
Using skills at runtime
Skills are callable from your server code (not the agent — the agent calls them over MCP):
import { runSkill } from "@mandujs/skills";
import manifest from "../.mandu/skills/manifest.json";
export async function handleSignup(email: string) {
const result = await runSkill(manifest, "send-verification-email", {
email,
}, { /* ctx */ });
return { ok: result.sent };
}
runSkill() validates input against the JSON-Schema-lite shape,
then dispatches to the resolved module.
Skills vs MCP tools
They're related but distinct:
Skill (@mandujs/skills) |
MCP tool (@mandujs/mcp) |
|
|---|---|---|
| Scope | Project-specific actions | Framework-wide, cross-project |
| Written by | Users + generator | Framework authors |
| Lives in | skills/ + .mandu/skills/generated/ |
packages/mcp/src/tools/ |
| Surfaced to agent via | mandu.ai.brief → skills[] |
MCP list_tools response |
The generated skill list is exactly what mandu.ai.brief returns,
so a fresh agent can enumerate the repo's own actions without
reading source.
Regeneration in watch mode
A dev-mode watcher is planned but not yet shipped — for now, run
bunx @mandujs/cli skills generate manually after adding new
contracts or resources. The output is deterministic, so CI diffs
pick up drift automatically.
Common errors
SKILL_E001: duplicate skill id 'x' — the generator and a static
skill both declare the same id. Rename the static skill or add it
to the generator's exclude list.
SKILL_E002: manifest stale — a static skill file changed but
the manifest wasn't regenerated. Run bunx @mandujs/cli skills generate.
SKILL_E003: input validation failed at runtime — a caller
passed input that doesn't match the skill's input schema. Check
the error's field pointer.
🤖 Agent Prompt
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/ai/skills-generator to my project.
Summary of the page:
Skills generator: scans project source for exported functions matching skill conventions, builds a manifest at `.mandu/skills/manifest.json` with { id, source: 'generated'|'static', path?, description? }. Static skills are human-written under `skills/`. Both surfaced via `mandu.ai.brief` MCP tool. Generator is pure — no network, no spawn.
Required invariants — must hold after your changes:
- Static skills live under `skills/<id>/index.ts` (human-written, versioned)
- Generated skills live under `.mandu/skills/generated/<id>.ts` (machine-emitted, regenerable)
- Manifest is deterministic — identical source tree produces byte-identical `.mandu/skills/manifest.json`
- Generator is pure — no network, no spawn, only fs reads under project root
- Skills surface in `mandu.ai.brief` MCP tool output under `skills: Array<{ id, source, path? }>`
Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.
Related
- AI — MCP tools —
mandu.ai.briefsurfacesskills[]in its output. - AI — Loop closure — another pure
framework in
@mandujs/skills. - Build with Agents — MCP setup — wire the agent that will consume the skills.
For Agents
{
"schema": "mandu.skills/v0.25",
"package": "@mandujs/skills",
"manifest_path": ".mandu/skills/manifest.json",
"static_path": "skills/<id>/index.ts",
"generated_path": ".mandu/skills/generated/<id>.ts",
"determinism": "byte-stable output given the same source tree",
"surfaced_via": "mandu.ai.brief → skills[]",
"rules": [
"Skill ids are globally unique across static + generated",
"Generator is pure — no network, no spawn, no env reads",
"Use `git diff --exit-code .mandu/skills/` as a CI gate for drift"
]
}For Agents
Skills generator: scans project source for exported functions matching skill conventions, builds a manifest at `.mandu/skills/manifest.json` with { id, source: 'generated'|'static', path?, description? }. Static skills are human-written under `skills/`. Both surfaced via `mandu.ai.brief` MCP tool. Generator is pure — no network, no spawn.
- Static skills live under `skills/<id>/index.ts` (human-written, versioned)
- Generated skills live under `.mandu/skills/generated/<id>.ts` (machine-emitted, regenerable)
- Manifest is deterministic — identical source tree produces byte-identical `.mandu/skills/manifest.json`
- Generator is pure — no network, no spawn, only fs reads under project root
- Skills surface in `mandu.ai.brief` MCP tool output under `skills: Array<{ id, source, path? }>`