Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/skills/formio-form-builder
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-tdd
created: 2026-07-12
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Design: `formio-form-builder` skill

## Context

The library has two orchestrators today: `formio-application` (build a whole app) and, beneath it, `formio-resource-planner` (plan the data model). Single-form creation has no owner. The building blocks all exist — `formio-schema` authors form JSON, the MCP server's `form_create` persists it (and already instructs use of `formio-schema`), `formio-form` embeds a saved form — but no skill chains them into a pipeline, and no skill knows how to choose between the three Form.io form types (webform, wizard, PDF form).

Source material for the form-type reference (authoring inputs, not runtime dependencies):

- https://help.form.io/form-building/form-types — webform vs wizard vs PDF form
- https://help.form.io/form-building/pdf-forms — PDF form capabilities and when to use them
- https://help.form.io/how/nested-form-workflows/nested-wizard-workflow#create-child-wizard — child wizards / complex multi-page flows

Repo state that constrains this change:

- The `add-formio-form-skill` change is implemented but not archived, so the `formio-form-skill` capability spec lives in that change's `specs/` directory, not yet in `openspec/specs/`. This change's `formio-form-skill` delta layers on top of the pending version.
- `formio-resource-planner` has no capability spec at all; this change introduces a narrow one covering only the routing boundary being added.
- `plugin-build.test.ts` assertion 1.3 enumerates bundled skills; `add-formio-form-skill` hit a stale exclusion there. This change updates the assertion in the same change that adds the skill.
- The `formio-schema-skill` spec forbids the string `formio-form` in `formio-schema`'s description — and `formio-form-builder` contains that substring — so `formio-schema` gets NO reverse pointer; the reference is one-way.

## Goals / Non-Goals

**Goals:**

- One orchestrator skill, `plugin/skills/formio-form-builder/`, owning "build me a form" end to end: INTENT → SCHEMA → SAVE → EMBED (conditional).
- A crisp form-vs-resource routing boundary, stated in the description and enforced by reverse pointers on `formio-application`, `formio-resource-planner`, and `formio-form`.
- Zero duplication: form JSON shapes stay in `formio-schema`; embed guidance stays in `formio-form`; this skill references both by name.
- Skill ships in the `@formio/ai` plugin bundle with the packaging spec and build-test assertions updated in the same change.
- Structural tests guarantee the authoring contract (same pattern as `packages/skill-tests/src/formio-form/skill-structure.test.ts`).

**Non-Goals:**

- No MCP server changes — `form_create`, `form_get`, and `authenticate` already exist.
- No renderer behavior tests — the flow is prose/orchestration; there is no runtime surface to drive.
- No changes to `formio-schema` content or description.
- No eval harness (`evals/`) for this skill — can follow later per repo convention.
- No PDF-upload tooling — the form-types reference documents what PDF forms are and when to choose them; server-side PDF conversion/upload is out of scope.

## Decisions

### D1: Orchestrator tier, `formio-application` shape — root-level step docs, not `references/`

`formio-form-builder` mirrors `formio-application`'s layout: `SKILL.md` router plus root-level uppercase step docs (the INTENT.md/IMPORT.md pattern), NOT the `references/` layout used by reference skills (`formio-api`, `formio-form`). Rationale: this is a flow with approval gates, not a lookup library; each doc scripts one step. Files:

| Doc | Covers |
|---|---|
| `SKILL.md` | Router: three-clause description, stance, the four steps with links, SCHEMA delegation to `formio-schema`, MCP Tool Preference, URL terminology |
| `FORM_TYPES.md` | What each form type is (webform / wizard / PDF form), capabilities, when to choose it, how INTENT distinguishes them; wizard section covers nested/child wizard workflows |
| `INTENT.md` | The single batched `AskUserQuestion` interview script: form type (infer-and-confirm vs ask) + embed intent (embed handoff fires only on explicit yes) |
| `SAVE.md` | `form_create` invocation, the saved-form confirmation (path/URL), and error branches — auth failure routes through the `authenticate` portal-login flow |
| `EMBED.md` | Conditional handoff contract to `formio-form` (form URL, framework rules: unnamed framework → `formio-form`; Angular-explicit → `formio-angular`) |

No YAML frontmatter on the step docs (repo convention for non-SKILL.md docs).

SCHEMA gets no step doc: the step is "invoke `formio-schema` with the confirmed form type and the user's field requirements" — a delegation statement in `SKILL.md`, not a script. Writing a SCHEMA.md would invite duplicating component guidance that must live only in `formio-schema`.

### D2: INTENT is one batched interview capturing form type AND embed intent

Same pattern as `formio-application`'s INTENT step: one `AskUserQuestion` call, two questions.

- **Form type** — infer from phrasing when unambiguous ("multi-page form" ⇒ wizard, "pdf form" ⇒ pdf) and present the inference as the recommended option to confirm; ask open when ambiguous. `FORM_TYPES.md` holds the distinguishing signals.
- **Embed intent** — captured up front so a standalone "make me a survey" stays fast. The EMBED step fires ONLY on an explicit yes; "no" and "unsure/later" both mean the flow ends at SAVE with the form URL in hand (the user can invoke `formio-form` later).

Alternative — ask embed intent after SAVE — rejected: it adds a second interview round-trip to every run, and the answer changes nothing before EMBED anyway; batching keeps the fast path fast.

### D3: Description routing — the form-vs-resource boundary is stated verbatim

Three-clause template:

- **Capability clause**: orchestrator that builds a single Form.io form end to end — determines form type (webform, wizard, or PDF form), delegates schema authoring, saves via the MCP server, and optionally hands off to embedding.
- **Trigger clause** (`Use when the user asks to …`): "build a form", "create a form", "I would like a new form", "multi-page form", "build a wizard", "create a survey / contact form / intake form / registration form / questionnaire", "pdf form" — single-form creation intents.
- **Boundary rule**, stated explicitly in the description: "build a form to collect X" (standalone form) = this skill; "track X / manage X / build an app around X" (data model, CRUD, resources) = `formio-application` / `formio-resource-planner`.
- **Negative clause** (`Not for:`): embedding an EXISTING form (see `formio-form`); building a whole app, portal, or tracker (see `formio-application`); designing resources/data models/permissions (see `formio-resource-planner`); raw form JSON schema lookups without the build-and-save flow (see `formio-schema`); Form.io REST endpoint lookups (see `formio-api`).

Reverse pointers: `formio-application` and `formio-resource-planner` descriptions gain `Not for:` pointers at `formio-form-builder` for standalone single-form creation; `formio-form`'s description gains one for create-a-new-form requests (it stays embed-only). `formio-schema` gets none (spec-forbidden string, see Context).

### D4: Inbound handoffs are requirements on the siblings, not prose in this skill

Two siblings route TO `formio-form-builder`:

- `formio-application`: when the user asks for a standalone FORM (not a resource, not a data model, not an app) — an ADDED requirement on `formio-application-skill`.
- `formio-form`: when an embed request reveals the form does not exist yet, or the request needs form-type determination before embedding — an ADDED requirement on `formio-form-skill`.

Encoding these on the sibling capabilities (rather than only describing them in `formio-form-builder`'s docs) keeps each skill's spec the single source of truth for its own routing behavior, matching how the library's other reverse pointers are specified.

### D5: SAVE uses `form_create` with the canonical auth story

`SAVE.md` scripts: an approval gate showing the form title/path/type and target project before the call; the `form_create` invocation; on success, confirming the saved form path and full form URL (`{FORMIO_PROJECT_URL}/{formPath}`) back to the user; on auth error, routing through the `authenticate` portal-login flow (browser opens, JWT captured, attached as `x-jwt-token` via `formioFetch` — never PKCE, never API keys) and retrying. `SKILL.md` carries the `## MCP Tool Preference` section naming `form_create`, `form_get`, and `authenticate` with the canonical portal-login JWT paragraph. Terminology stays strict: `baseUrl` ⇒ `FORMIO_BASE_URL` only; `projectUrl` ⇒ `FORMIO_PROJECT_URL` only.

### D6: Structural tests must be substring-collision-safe

`formio-form-builder` contains the substring `formio-form`. Naive `toContain('formio-form')` assertions cannot distinguish a pointer at the embed skill from a pointer at this skill. The structural tests (and the sibling reverse-pointer assertions) therefore match backtick-delimited names — `` `formio-form` `` vs `` `formio-form-builder` `` — or an equivalent word-boundary regex. This also protects the existing `formio-form` structural test suite, whose sibling-pointer assertions would otherwise pass vacuously against `formio-form-builder` mentions.

Test file: `packages/skill-tests/src/formio-form-builder/skill-structure.test.ts`, following the `formio-form` pattern: frontmatter (`name: formio-form-builder`, non-empty description), three clauses with all five Not-for names, boundary rule present, the four step docs exist / non-empty / no frontmatter, MCP Tool Preference section (`form_create`, `form_get`, `authenticate`, `x-jwt-token`), dev symlink resolution, and sibling reverse-pointer assertions for `formio-application`, `formio-resource-planner`, and `formio-form`.

### D7: Plugin packaging updated in-change

`plugin-build.test.ts` assertion 1.3 gains `formio-form-builder` in its inclusion list, and the `claude-plugin-packaging` spec's "Plugin bundles the skills library" requirement adds the skill to the bundled set. The build script copies `plugin/skills/` wholesale, so no build-script change is expected — the test update is the guard. This is done in this change deliberately: `add-formio-form-skill` shipped its skill against a stale exclusion assertion and had to fix it after the fact.

## Risks / Trade-offs

- [Trigger overlap with `formio-application` on ambiguous phrasing ("I need something to collect maintenance requests")] → The boundary rule is stated verbatim in BOTH descriptions (this skill's positive form and the siblings' `Not for:` pointers); when genuinely ambiguous, the INTENT interview surfaces the distinction ("just a form, or an app around the data?") before any work happens.
- [Substring collision `formio-form` / `formio-form-builder` weakens description-based routing and tests] → Backtick-delimited name matching in all new assertions (D6); descriptions always write the full backticked skill name.
- [`formio-form-skill` base spec is in an unarchived change; archive order could conflict] → This change's delta copies the pending version's full requirement text and layers the addition on top; archiving `add-formio-form-skill` first keeps the merge clean. Noted in tasks.
- [PDF form creation may not be fully achievable via `form_create` alone (PDF upload is a hosted-server concern)] → `FORM_TYPES.md` documents the capability honestly: choosing "pdf" sets `display: "pdf"` and notes the PDF-document upload prerequisite; the skill never promises server-side PDF conversion.
- [help.form.io source docs drift after authoring] → Docs are cited as authoring sources in the change, not linked as living dependencies inside the skill; structural tests guard the contract that matters (layout, routing, tool preference).

## Open Questions

None — layout, routing boundaries, handoff contracts, and test scope are settled above.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Add `formio-form-builder` skill

## Why

The skills library has no orchestrator for the single most common Form.io request: "build me a form." Today a standalone form-creation intent ("create a survey", "I need a contact form", "build a multi-page wizard") has no owner — it mis-routes to `formio-application` (full app orchestration), `formio-resource-planner` (data-model planning), or `formio-schema` (a JSON reference, not a pipeline). Nothing determines the form type (webform vs wizard vs PDF form), authors the definition, persists it via `form_create`, and optionally hands off to embedding.

## What Changes

- New orchestrator skill at `plugin/skills/formio-form-builder/` — same tier and shape as `formio-application` — owning the full "build me a form" pipeline: INTENT → SCHEMA → SAVE → EMBED (conditional).
- **INTENT** — one batched `AskUserQuestion` interview (mirroring `formio-application`'s INTENT step) capturing (a) form type: `webform` (single-page), `wizard` (multi-page), or `pdf` form — inferred from phrasing when unambiguous and confirmed, asked when ambiguous; and (b) embed intent: embed in an application afterward, or just create in the Form.io project. Embed handoff fires ONLY on an explicit yes.
- **SCHEMA** — delegate component selection and full form JSON authoring to the existing `formio-schema` skill for the chosen form type. No component/schema documentation is duplicated; `formio-schema` is referenced by name only.
- **SAVE** — persist the definition via the MCP server's `form_create` tool (which already instructs use of `formio-schema`; no MCP tool changes). Auth errors route through the `authenticate` portal-login flow (`x-jwt-token`; never PKCE or API keys). Confirm the saved form path/URL back to the user.
- **EMBED** (conditional) — only on an explicit yes at INTENT: hand off to `formio-form` to embed the saved form by its form URL; Angular-explicit requests route through `formio-angular` per existing rules.
- Reference docs (no YAML frontmatter) mirroring `formio-application`'s INTENT.md/IMPORT.md pattern: a form-types reference authored from the official help.form.io docs (form types, PDF forms, nested wizard workflows), the INTENT interview script, the SAVE `form_create` invocation + error handling, and the EMBED handoff contract.
- Routing boundaries encoded in the three-clause description. Sharpest boundary — form vs resource/data model: "build a form to collect X" (standalone form) = `formio-form-builder`; "track X / manage X / app around X" (data model, CRUD, resources) = `formio-application` / `formio-resource-planner`. The description states this rule explicitly.
- Reverse `Not for:` pointers added to sibling descriptions: `formio-application`, `formio-resource-planner`, and `formio-form` each point standalone create-a-new-form requests at `formio-form-builder`. `formio-form` also gains the inbound handoff: when an embed request reveals the form does not exist yet, route to `formio-form-builder` first. `formio-schema`'s description is NOT touched — its spec forbids the string `formio-form` in its description; `formio-form-builder` references `formio-schema` one-way.
- The skill SHIPS in the `@formio/ai` plugin bundle — the `claude-plugin-packaging` spec's bundled-skills list and the `packages/mcp-server/src/__tests__/plugin-build.test.ts` inclusion assertions are updated in this change (the add-formio-form-skill change hit a stale exclusion here; not repeated).
- Dev symlink `.claude/skills/formio-form-builder` like the other orchestrators.
- Structural tests at `packages/skill-tests/src/formio-form-builder/` following the `formio-form` skill-structure.test.ts pattern (frontmatter, three-clause description with all Not-for names, reference docs present/no-frontmatter, MCP Tool Preference section, sibling reverse-pointer assertions). Flow behavior itself is prose/orchestration — no renderer behavior tests.

## Capabilities

### New Capabilities

- `formio-form-builder-skill`: The `plugin/skills/formio-form-builder/` orchestrator — directory layout and dev symlink, three-clause description with single-form-creation triggers and the form-vs-resource boundary rule, the four-step INTENT/SCHEMA/SAVE/EMBED flow with its reference docs, no-duplication constraints (defer to `formio-schema` and `formio-form` by name), MCP Tool Preference (`form_create` / `form_get` / `authenticate`), strict URL terminology, and structural tests.
- `formio-resource-planner-skill`: New (narrow) spec for the existing `formio-resource-planner` skill's routing boundary — its description gains a `Not for:` pointer at `formio-form-builder` for standalone single-form creation requests. (The planner has no spec today; this capability starts with the routing requirement only.)

### Modified Capabilities

- `formio-application-skill`: description's `Not for:` clauses additionally point standalone single-form creation requests ("build/create a form", surveys, contact forms) at `formio-form-builder`; the orchestrator hands off to `formio-form-builder` when the user asks to create a standalone form rather than a resource/data model or app.
- `formio-form-skill`: description gains a `Not for:` pointer at `formio-form-builder` for create-a-new-form requests (`formio-form` stays embed-only); the skill routes to `formio-form-builder` when the form to embed does not exist yet or the request needs form-type determination. (Base spec currently lives in the unarchived `add-formio-form-skill` change; this delta layers on top of it.)
- `claude-plugin-packaging`: the plugin bundle SHALL include the `formio-form-builder` skill; build/smoke test inclusion assertions updated accordingly.

## Impact

- New: `plugin/skills/formio-form-builder/SKILL.md` + reference docs; `.claude/skills/formio-form-builder` symlink.
- New: `packages/skill-tests/src/formio-form-builder/skill-structure.test.ts`.
- Modified: `plugin/skills/formio-application/SKILL.md`, `plugin/skills/formio-resource-planner/SKILL.md`, `plugin/skills/formio-form/SKILL.md` (description clauses + handoff prose only); `packages/mcp-server/src/__tests__/plugin-build.test.ts` (inclusion assertion).
- No MCP server changes — `form_create`, `form_get`, and `authenticate` already exist and `form_create` already instructs use of `formio-schema`.
- Source material (authoring inputs, not runtime dependencies): help.form.io Form Types, PDF Forms, and Nested Wizard Workflow docs.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## MODIFIED Requirements

### Requirement: Plugin bundles the skills library

The plugin source tree SHALL include, and the build SHALL copy to `dist/plugin/skills/`, the full `formio-api` router skill, every `formio-api-<group>` capability-group skill, and the `formio-schema`, `formio-resource-planner`, `formio-form`, and `formio-form-builder` skills. (`formio-form` is the `@formio/js` embed skill; `formio-form-builder` is the build-a-form orchestrator introduced by the `formio-form-builder-skill` capability.) The plugin build test's skill-inclusion assertions (`packages/mcp-server/src/__tests__/plugin-build.test.ts`) SHALL assert `formio-form-builder` is present in the bundled `skills/` directory.

#### Scenario: Installed plugin exposes all skills

- **WHEN** a user installs `@formio/ai` in a Claude Code project
- **THEN** Claude Code discovers every `formio-api`, `formio-schema`, `formio-resource-planner`, `formio-form`, and `formio-form-builder` skill from the plugin's `skills/` directory

#### Scenario: Bundled formio-form-builder is the orchestrator skill

- **WHEN** the bundled `skills/formio-form-builder/SKILL.md` is inspected
- **THEN** its frontmatter `name` is `formio-form-builder` and its description claims single-form creation triggers

#### Scenario: Build test asserts inclusion, not a stale exclusion

- **WHEN** the plugin build test suite runs against a built `dist/plugin/`
- **THEN** it asserts `formio-form-builder` is present in `dist/plugin/skills/`
- **AND** no assertion excludes `formio-form-builder` from the bundle
Loading
Loading