Scaffold TypeScript or JavaScript Classes from a Diagram
diagrams_scaffoldDraft bounded empty exported TypeScript or JavaScript classes from entity-name candidates in a stored PlantUML or Mermaid diagram, returning source text to review and then save yourself. TypeScript is the default language.
Instructions
Draft bounded empty exported TypeScript or JavaScript classes from entity-name candidates in a stored PlantUML or Mermaid diagram, returning source text to review and then save yourself. TypeScript is the default language.
This reads heuristic entity-name candidates through the existing extractor unchanged, so candidates may include aliases, namespaces, components, participants, and call names such as charge. By default (all opt-in flags false) TypeScript mode does NOT recover class kinds, members, field types, relations, or runtime behavior. Every result says so with confidence "heuristic" plus a heuristic_warning; review the draft before saving it in the selected language yourself.
Set language="javascript" to emit empty exported JavaScript class shells only. It uses the same candidate filtering, entity cap, and diagram source byte limit as the default TypeScript shells, returns no member, type, or relation metadata, and rejects include_members/include_types/include_relations when any is true.
With include_members=true in TypeScript mode, simple top-level multiline class NAME { blocks can add member stubs. Names must be bare ASCII identifiers; quoted, aliased, generic, nested, or decorated classes are outside this subset. The first substantive line must be @startuml (PlantUML) or classDiagram (Mermaid).
Supported members: optional +, -, #, or ~ visibility; fields name: Type, Type name, or bare name; methods name(args) with an optional single-token return label, or PlantUML Type name(args). Without include_types, fields become unknown; methods accept ..._args: unknown[], return unknown, and throw the literal "Not implemented". Argument names, types, modifiers, and bodies are not recovered. Member names use the same ASCII identifier grammar and length cap as class names; constructor and prototype are excluded. Duplicate names keep their first occurrence across field/method collisions and repeated class blocks.
Unsupported syntax is skipped, including constructors, initializers, implementation bodies, and external Class : member notation. Whole-line comments, PlantUML block comments, and note bodies are ignored. Each emitted class receives at most 30 members; members_capped reports an additional recognized unique name beyond that limit. This is a heuristic subset, not a full parser.
With include_types=true in TypeScript mode (which implies include_members=true), accepted field labels resolve to a strict case-sensitive subset only: string, number, boolean, bigint, or exactly an emitted class name, optionally with one [] suffix, optionally paired with exactly one null arm in either order (for example, string | null or null | User[]); nullable labels canonicalize to Atom | null. Whitespace around arms and the separator is allowed, but not inside an atom. Bare null, undefined as a type atom, any, unknown, void, extra or duplicate arms, parentheses, other generics, objects, functions, qualified names, and nested arrays are unsupported. Classes past max_entities, skipped candidates, and nonexistent classes fall back to unknown. Accepted fields emit as name!: Type (definite assignment for strict TypeScript); every other emitted field keeps the member-mode name: unknown;. Methods stay literal throwing stubs with no recovered params or returns. Class references are heuristic labels only, with no supplied runtime value or relations claimed. typed_fields counts accepted emitted fields and unresolved_fields counts emitted fields falling back to unknown (never methods or skipped/over-cap members).
Array and ReadonlyArray accept exactly one supported primitive or emitted-class base and emit T[] or readonly T[] respectively. Literal readonly T[] is also accepted in name: Type notation for one supported base, optionally with one null arm in either order. Readonly scalars, nested arrays, and other generic forms are unsupported. Either wrapper may have one null arm. No whitespace is allowed inside wrappers and nested wrappers or array arguments or array suffixes on wrappers remain unsupported. Wrapper and nullable labels require name: Type field notation. Trimmed labels are retained up to 82 characters including whitespace around the separator. Type identifiers remain limited to 60 characters. An overlong label falls back to unknown even if removing extra whitespace would make it fit.
With include_relations=true in TypeScript mode, typed members are enabled even if include_types/include_members are false. Body field labels use the same nullable grammar as include_types, including literal readonly T[] in colon notation. Top-level Owner --> Target : role links become role!: Target;. Optional target multiplicity before Target (quoted , 0.., or 1..*) becomes role!: Target[];. Both endpoints must be selected emitted classes and independently declared plain top-level class NAME, class NAME { multiline blocks, or class NAME {} empty blocks. No endpoint classes are invented. Alias/quoted/generic/nested/implicit declarations and other arrows, multiplicities or free-text/unlabeled links are outside this subset.
Role names use the member identifier rules. Body fields/methods win collisions regardless of edge position, then the first accepted role wins. Relation fields follow body members. Comments, note bodies and nested blocks are ignored. At most 300 relationship fields are appended, within the existing class member cap. Recognized arrow lines not added (missing endpoints, invalid role, conflicts, duplicates or limits) increment relations_skipped. Unsupported grammar does not count. A novel candidate refused by either cap sets relations_capped; a class cap also sets members_capped. These are heuristic proposed fields, not recovered ownership, inheritance, runtime values or enforced cardinality.
Relation mode additionally returns include_relations:true, relations_included, relations_skipped, relations_capped, relation_limit. Type/member counts include appended fields. False or omitted include_relations preserves the three existing source and response modes.
It never writes, renders, fetches, or executes: one traversal-protected store read, pure filtering and emission, no new dependencies. Nothing lands outside the response until you save it with an explicit write. Still read-only with include_members=true or include_types=true.
Args:
relative_path (string, required): Path to the diagram, relative to the diagrams root
language ("typescript" | "javascript", default "typescript"): Output language; JavaScript is shell-only
max_entities (integer 1-60, default 30): Hard cap on emitted classes
include_members (boolean, default false): TypeScript-only heuristic member stubs plus members metadata
include_types (boolean, default false): TypeScript-only heuristic field types; true implies include_members=true plus typed metadata
include_relations (boolean, default false): TypeScript-only named-arrow fields; implies include_types and include_members
Returns: JSON with schema: { "relative_path": string, // POSIX form "language": "typescript" | "javascript", // defaults to "typescript" "source": string, // the scaffold; identical to the text block ("" when no emittable names) "entities": string[], // emitted class names in candidate order, capped "entities_included": number, // length of "entities" "entities_available": number, // valid candidates before the cap "entities_capped": boolean, // true when valid names were dropped by the cap "entity_limit": number, // the applied cap (max_entities, at most 60) "skipped_entities": string[], // invalid or reserved candidates, independent of the cap "skipped_count": number, // length of "skipped_entities" "confidence": "heuristic", "heuristic_warning": string, // selected language and mode limitations "written": false, // always false; save the source yourself // include_members=true only: // "include_members": true, // "members_included": number, // total emitted field+method stubs across emitted classes // "members_capped": boolean, // true when a 31st unique supported member was found // "member_limit": 30 // fixed per-class member cap // include_types=true only (implies the member keys above): // "include_types": true, // "typed_fields": number, // emitted fields with an accepted type label (name!: Type) // "unresolved_fields": number // emitted fields falling back to name: unknown }
Names are emitted only when they match /^[A-Za-z_][A-Za-z0-9_]*$/ at most 60 characters and avoid reserved and TypeScript-forbidden class names. Other candidates are listed in skipped_entities without further renaming. An empty candidate set returns empty source.
Examples:
Use when: "Draft class shells for the names in this diagram" -> relative_path="models/order.puml"
Use when: "Give me TypeScript stubs for the Mermaid model" -> relative_path="models/order.mmd", max_entities=10
Use when: "Draft JavaScript class shells from this model" -> relative_path="models/order.mmd", language="javascript"
Use when: "Draft shells with heuristic member stubs" -> relative_path="models/order.puml", include_members=true
Use when: "Draft stubs with heuristic field types" -> relative_path="models/order.puml", include_types=true
Use when: "Draft fields from named arrows between my declared classes" -> relative_path="models/order.mmd", include_relations=true
Don't use when: You want full members, types, or relations (out of scope; JavaScript emits empty shells only; TypeScript opt-in emits heuristic stubs and a strict field-type subset with unknown fallback)
Don't use when: You want a diagram from code (use diagrams_generate)
Error Handling:
Returns "Error: No diagram found at ''" if the diagram does not exist, before any extraction
Returns "Error: Refused to access path outside the diagrams root" if relative_path attempts to escape the diagrams directory
Returns "Error: ... does not have a recognized diagram extension ..." when the path has no diagram extension
Returns "Error: Diagram source is bytes, larger than the 1000000-byte limit ..." when the stored source exceeds the byte cap; nothing partial is returned
Returns a clear error when language="javascript" is combined with include_members/include_types/include_relations; this check runs before the stored diagram is read
An empty candidate set is not an error: source is "" with entities [] and the heuristic warning still explains the limitation
Unexpected internal failures return a generic "Error: Unexpected internal error ..." with isError:true and are logged to stderr without source, paths, or secrets
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| language | No | Output language (default: typescript). JavaScript emits empty exported class shells only and cannot be combined with include_members, include_types, or include_relations. | typescript |
| max_entities | No | Hard cap on emitted class shells, an integer between 1 and 60 (default: 30). The applied cap is reported as entity_limit; anything past it is counted in entities_available and flagged by entities_capped. | |
| include_types | No | TypeScript-only opt-in heuristic field types for member stubs (default: false). True implies include_members=true; with relations disabled, false adds no new keys. True resolves field labels to string, number, boolean, bigint, or exactly an emitted class name, optionally with one [] suffix; a field may also have exactly one null arm in either order (canonicalized as Atom | null). Array<T> and ReadonlyArray<T> also accept a supported primitive or emitted-class base and emit T[] or readonly T[]. Literal readonly T[] is accepted in colon notation only. Readonly scalars and nested arrays are unsupported. Whitespace around arms and the separator is allowed, not inside an atom. Accepted fields emit as name!: Type; all others emit as name: unknown. | |
| relative_path | Yes | Path to the diagram to scaffold from, relative to the diagrams root. | |
| include_members | No | TypeScript-only opt-in heuristic member stubs from plain class NAME { ... } blocks (default: false). With other opt-ins disabled, false keeps empty classes with no new keys; true adds throwing stubs plus members metadata. | |
| include_relations | No | TypeScript-only opt-in named association fields (default: false). Implies include_types and include_members. Only labeled --> relations between explicitly declared, emitted plain classes are used; no inheritance or runtime values. |