Agent skill · civitai
form-graph-port
Port an existing form (a data-graph graph, or a bespoke RHF+zod form like model training) to the form-graph library. Use when asked to move a form's field logic, branching, persistence, or server validation onto form-graph. Encodes the method proven by the generation-form port — oracle-first differential testing, scope mapping, staged cutover.
What it needs
About 4k tokens when loaded.
What this skill does
Porting a form to form-graph The method that took the generation form (~45 family graphs, 4 output types, 7 standalone workflows, ~12k differential cases) onto form-graph, distilled so the next port (e.g. model training) doesn't rediscover it. The worked example is src/shared/form-graph/generation/ + docs/form-graph-port-plan.md; read the plan doc's phase structure before starting anything sizable. 0. Read the lib's own guidance first C:\work\form-graph\CLAUDE.md carries the library's design invariants (one branch combinator, sync resolution, wire-named computedKeys, the prepack-after-every-edit rule for link: consumers). Don't design against an imagined API. 1. Identify the oracle, then build the harness FIRST Nothing else starts until parity is measurable. Oracle = whatever produces today's wire payload. For a data-graph form it's graph.safeParse. For a bespoke form (training: src/components/Training/Wizard + src/server/schema/training.schema.ts + the orchestrator validation) it's the submit payload builder — capture real input→payload fixtures if there's no parse function. Differential = byte-identical wire. assertDifferential pattern: port parse vs oracle parse over generated cases, plus the parse-fixpoint pin (re-parse the port's own state → identical data; this is what makes whatIf/cost preview trustworthy). Bound every generated-case driver — a fake that pages/loops must terminate on its own (see CLAUDE.md's microtask-loop warning; a hang is unreportable in vitest). Divergences found by the harness are findings to record, not always bugs — v1 does have dead paths and quirks. Pin the deliberate deltas in a comment or the plan doc. 2. Structure: declare-then-dispatch Discriminators are ordinary fields declared above the dispatch: .field('ecosystem', def) then .use(branch('ecosystem', [[keys, member], ...] as const)). Group related keys into one pair — arm count should equal family count, not key count. …
How to use it
Reference it in AdaL, Claude Code, Cursor or any coding agent — nothing to install:
@skills civitai/form-graph-port