Scaffold a new SeldonFrame block from a natural-language intent. Use when the builder says "build me a block that ...", "scaffold a block that ...", or similar. Produces a real, validated block skeleton in packages/crm/src/blocks/ — BLOCK.md with composition contract, tools.ts with Zod schemas, subscription handler stubs (when reactive), and test.todo stubs per tool. Does NOT commit, does NOT install into any workspace. Builder owns review + git.
Scanned 9/1/2026
Install to Claude Code
npx -y skills add seldonframe/seldonframe --skill block-creation --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Block Creation?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/seldonframe-block-creation)More formats (shields.io, HTML) on the badges page.
---
name: block-creation
description: Scaffold a new SeldonFrame block from a natural-language intent. Use when the builder says "build me a block that ...", "scaffold a block that ...", or similar. Produces a real, validated block skeleton in packages/crm/src/blocks/ — BLOCK.md with composition contract, tools.ts with Zod schemas, subscription handler stubs (when reactive), and test.todo stubs per tool. Does NOT commit, does NOT install into any workspace. Builder owns review + git.
---
# block-creation — Skill instructions
## When to invoke
Invoke this skill when the builder expresses intent to create a new
block. Trigger phrases include:
- "build me a block that ..."
- "scaffold a block for ..."
- "create a new block named ..."
- "I need a block that does ..."
Do NOT invoke when the builder is asking to:
- Install an existing block into a workspace (that's the SeldonFrame
MCP's `install_*` tools).
- Modify an existing block (refer to direct file edits).
- Generate UI components or pages (out-of-scope for this slice;
future SLICE 4 scope).
## Framing — this is code-authoring, not workspace-admin
The scaffold writes files to the builder's repo working tree. It
does NOT:
- commit to git
- push to a remote
- install the block into a running workspace
- modify any workspace's database
Those are the builder's follow-up actions (`git add` / `git commit` /
`git push`, then `install_block` against a workspace). Claude Code
is a code-gen assistant here, not a platform admin.
## Workflow
### 1. Intent → BlockSpec
Claude Code reads the builder's natural-language intent and
constructs a `BlockSpec` JSON object matching the shape below.
**Before constructing the spec, Claude should Read:**
1. Reference anatomies — the composition-contract shape of two
existing blocks. These show the canonical layout for produces /
consumes / tools / subscriptions:
- `packages/crm/src/blocks/notes.block.md` (simple tool-only
block — the PR 1 C7 smoke-test)
- `packages/crm/src/blocks/crm.block.md` (real-world anatomy
with `## Subscriptions` declaring a reactive handler)
2. Canonical NL → BlockSpec examples (these are hand-curated
translation lessons in code form):
- `packages/crm/src/lib/scaffolding/nl/example-specs.ts` —
EXAMPLE_SPECS export. Two examples: tool-only (contact-notes)
+ reactive-with-subscription (auto-activity-log).
3. Hard constraints on BlockSpec shape (the schema Claude must
honor):
- `packages/crm/src/lib/scaffolding/spec.ts` — BlockSpecSchema
+ field patterns (slug, tool name, handler name, event name,
subscription event).
Reading order: anatomies first → schema → examples. Each reads in
under 30 seconds; together they give Claude the concrete pattern
needed to translate NL confidently.
**Required fields:**
- `slug`: kebab-case lowercase, e.g. `"notes"`, `"client-satisfaction"`. Cannot collide with reserved core blocks: `crm`, `caldiy-booking`, `email`, `sms`, `payments`, `formbricks-intake`, `landing-pages`.
- `title`: human-readable title, e.g. `"Notes"`, `"Client Satisfaction"`.
- `description`: one-line builder-facing description.
- `triggerPhrases`: 2-5 natural-language phrases that would activate this block.
- `frameworks`: array of framework strings, e.g. `["universal"]`.
**Optional fields (defaulted if absent):**
- `produces`: array of `{ name, fields }` event declarations.
- `consumes`: array of `{ kind, ... }` discriminated-union entries (`kind: "event" | "soul_field" | "trigger_payload"`).
- `tools`: array of tool definitions: `{ name, description, args: [...], returns: [...], emits: [...] }`.
- `subscriptions`: array of `{ event, handlerName, description, idempotencyKey }` reactive handlers. `event` is fully-qualified: `"<source-block>:<event.name>"`.
### 2. Pre-classify the intent (deterministic safety check)
Before translating NL → BlockSpec, call the deterministic
classifier once to sanity-check the intent:
```ts
import { classifyIntent } from "@/lib/scaffolding/nl/intent-classifier";
const classification = classifyIntent(nlIntent);
// { tier: 1 | 2 | 3, issues: string[], suggestedAction: string }
```
Behavior by tier:
- **Tier 3** — the intent is destructive / modifies an existing
core block. Refuse by default. If the builder explicitly
confirms (e.g., "yes, this is an admin-only tool with
safeguards"), the scaffold may proceed; otherwise relay the
specific risk and stop.
- **Tier 1** — the intent is empty, trivially short, or
self-contradictory. Ask ONE focused clarifying question
(`suggestedAction` points at what to ask). Wait for a reply.
Re-classify after the reply; proceed only when classification
is no longer tier 1.
- **Tier 2** — the common case. Proceed with scaffold generation.
Fill sensible defaults for under-specified fields; mark each
default with `TODO (scaffold-default)` comments the builder
can grep post-scaffold.
The classifier's heuristics are loose — false-negative tier-2
intents are acceptable (downstream BlockSpec validation catches
structural problems). False-positive tier-3 decisions are
preferred over false-negative — better to refuse once and require
confirmation than scaffold a destructive tool silently.
### 3. Clarify rarely (three-tier policy per audit G-4)
- **Tier 1 — Ask once** only when the intent is genuinely meaningless
(zero description, or internally contradictory type declarations).
Ask ONE focused question; proceed.
- **Tier 2 — Default with TODO markers** for everything else. When
the intent doesn't name tools, scaffold `create_<entity>` /
`list_<entity>s` / `get_<entity>` as defaults. Every scaffolded
default lands with `TODO (scaffold-default)` markers the builder
can grep.
- **Tier 3 — Fail on dangerous output.** Refuse to scaffold:
- Destructive tools without explicit confirmation.
- Modifications to existing blocks (scope limits to NEW blocks).
### 4. Run the scaffold
Once the BlockSpec is constructed, write it to a temporary file and
invoke the scaffold CLI:
```bash
# Claude writes /tmp/spec.json with the BlockSpec JSON
pnpm scaffold:block --spec /tmp/spec.json
```
The scaffold will:
1. Validate the BlockSpec against the Zod schema.
2. Render BLOCK.md, tools.ts, subscription handler stubs (if any), and test stubs.
3. Write all files to `packages/crm/src/blocks/<slug>.*` and `packages/crm/tests/unit/blocks/<slug>.spec.ts`.
4. Run the validation gate:
- `parseBlockMd` round-trip on the new BLOCK.md
- `tsc --noEmit` across the CRM package
- `pnpm emit:blocks:check` (after the builder adds the block to the emit TARGETS registry on next step)
5. On success, print the created-files list and next-step hints.
6. On failure, print the orphan report with `git clean` recovery commands.
### 5. Relay results
After the scaffold succeeds, tell the builder:
- Files created (from the scaffold's stdout).
- Next-step checklist (also from the scaffold's stdout):
1. Review TODO (scaffold-default) markers — they're the fill-in points.
2. Add the block to `scripts/emit-block-tools.impl.ts` TARGETS list so emit:blocks:check covers it.
3. Run `pnpm emit:blocks` to populate the TOOLS block in the BLOCK.md.
4. Run `pnpm test:unit` — new test stubs appear as todos.
5. `git diff` / `git add` / `git commit` when satisfied.
On failure, relay the orphan report verbatim so the builder has the
exact recovery commands.
## BlockSpec example — "notes" block
```json
{
"slug": "notes",
"title": "Notes",
"description": "Simple note-taking on contacts.",
"triggerPhrases": [
"Add a notes block",
"Install notes",
"Let me jot notes on contacts"
],
"frameworks": ["universal"],
"produces": [
{
"name": "note.created",
"fields": [
{ "name": "noteId", "type": "string", "nullable": false },
{ "name": "contactId", "type": "string", "nullable": false }
]
}
],
"consumes": [],
"tools": [
{
"name": "create_note",
"description": "Create a note on a contact.",
"args": [
{ "name": "contactId", "type": "string", "nullable": false, "required": true },
{ "name": "body", "type": "string", "nullable": false, "required": true }
],
"returns": [
{ "name": "noteId", "type": "string", "nullable": false, "required": true }
],
"emits": ["note.created"]
}
],
"subscriptions": []
}
```
## Dry-run mode
Pass `--dry-run` to see what would be created without writing:
```bash
pnpm scaffold:block --spec /tmp/spec.json --dry-run
```
Useful for previewing before committing to the scaffold.
## Error recovery
If the scaffold fails mid-pipeline:
- Files that landed before the failure remain on disk (by design —
orphan detection, not transactional rollback).
- The error message lists every orphan + concrete `git clean`
commands to remove them.
- Fix the underlying issue and re-run. The scaffold refuses to
overwrite existing files, so the builder must remove orphans
first (or pick a different slug).
## Out of scope for this skill
- NL intent parsing without an explicit BlockSpec construction step
(future PR will layer this on; this PR requires Claude Code to
translate intent → BlockSpec JSON inline).
- UI scaffolding (pages, Puck components) — future SLICE 4.
- Database schema generation (builder authors Drizzle schemas
manually if their block needs persistence).
- Marketplace publishing (out of slice).
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!