# Block Creation

> 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.

- Skill: `seldonframe/block-creation` (Agent Skill)
- Install (CLI): `npx skillmds@latest add seldonframe/block-creation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/seldonframe/block-creation/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: seldonframe (https://skillmd.com/u/seldonframe)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/seldonframe/block-creation

---


# 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).

