LangENKO

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.

since v0.25
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:

  1. mandu scaffold / mandu new shortcutsgenerate-resource, generate-page, etc. Each becomes a skill whose run() delegates to the underlying command.
  2. Contracts with a single endpoint — each shared/contracts/*.contract.ts yields a skill call-<contract-id> wrapping the typed fetch.
  3. Seed runners — each spec/seeds/*.seed.ts yields a skill seed-<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 generatedAt field).
  • 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.briefskills[] 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

🤖 Agent Prompt — Skills generator
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.

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

AI hint

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.

Invariants
  • 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? }>`
Guard scope
skills