From design-is-code
Transform a precise design (UML sequence diagrams and decision tables) into working code using the DisC methodology
How this skill is triggered — by the user, by Claude, or both
Slash command
/design-is-code:discThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are executing the DisC (Design is Code) methodology. Transform the provided precise design (UML sequence diagrams and decision tables) into working code: first generate tests from the design, then derive implementation from the tests.
You are executing the DisC (Design is Code) methodology. Transform the provided precise design (UML sequence diagrams and decision tables) into working code: first generate tests from the design, then derive implementation from the tests.
In DisC, the design is the source. Tests are derived from the design. Implementation is derived from the tests. Code is never written directly — it is the last link in a deterministic chain that begins with a human-authored design.
Two kinds of design feed this chain. Sequence diagrams specify how orchestrators call collaborators — the structure of behaviour. Decision tables specify what pure functions return — the result of behaviour. Each kind has its own deterministic transformation, but both obey the same rule: every element of the design produces exactly one test, and the implementation is whatever makes those tests pass.
What DisC controls: DisC pins the contract — call order, arguments, and data flow for orchestrators; input-output behaviour for pure functions with a filled decision table.
The two invariants — every other rule derives from these:
Design element = Test. Every call_arrow in a sequence diagram becomes exactly one verify_test. Every row in a decision_table_file becomes exactly one leaf test. The count of design elements must equal the count of generated tests.
Implement from tests, not design. The transformation has two phases separated by a wall.
Every later reference uses the exact snake_case name defined here.
What the human authors. The first artifacts in the chain.
call_arrow — An arrow from caller to callee representing a method invocation. Labeled with a method call: A -> B: method(arg). May appear as solid (->) or plain arrow. Identified by its label format: a method name followed by parentheses.
return_arrow — An arrow from callee back to caller representing a returned value. Labeled with a value: B --> A: value,A <-- B: value or B --> A: value : Type. May appear as dashed (-->) or plain arrow. Identified by direction (back to the original caller) and label format (a value name, no parentheses).
participant — A named box in the diagram. By default, the participant name stands for the abstraction. When the name is separated by colon, the left is the implementation name and the right is the abstraction's name. When a participant_target stereotype is attached (defined in the Composition block below), the name becomes a local handle and the class identity comes from the stereotype (FQN for REUSE/UPDATE, sibling-folder path for DEFER). See entity_declaration for the data-side parallel — entities are passed through participants, not called by them.
loop_block — A loop / end fragment wrapping one or more arrows.
branch_block — An alt / else / end fragment with multiple paths.
throw_arrow — A self-arrow (from a participant to itself) tagged as a throw declaration.
system_caller — The fixed boundary marker for the entry into the system under test. Stands in for the test harness in DisC tests and for the framework (HTTP, message queue, scheduled trigger) in production. It is not a participant: it has no abstraction, no implementation, no class to generate, and it is never instantiated, mocked, or placed in a constructor. It exists in the design only to be the caller of the entry interaction (and, optionally, the callee of the final return). Exactly one system_caller per diagram. The notation used to write the system_caller is owned by the language_profile.
Distinguishing call_arrow from return_arrow — when arrow styles are identical, use three signals:
call_arrow has parentheses in its label: method(arg). A return_arrow has a value name with no parentheses: result or result : Type.call_arrow goes forward (caller to new callee). A return_arrow goes back to the original caller.return_arrow always follows a call_arrow to the same callee and returns to the same caller.decision_table_file — A <Participant>.decision.md file (sibling of the .puml that uses it) with YAML frontmatter (target, input, output, a target_placement declaration, optional config) and a markdown table of rows. Specifies the input/output behaviour of a pure function leaf. Frontmatter and config: keys are documented in the language_profile.
variant_decision_table — A decision_table_file whose target: Variant.method resolves to a permit record's override of a sealed_family behavior. Distinct pairing rule from the standard decision_table_file → pure function leaf pairing (see Step 2.8): the target resolves to an entity permit, not a leaf participant. When attached, the variant's override body is filled from rows; when absent, the override is generated in skeleton mode (throws a marker exception; a skeleton test is emitted).
A parallel taxonomy to participant. Entities are data nodes (passed through data_pipes); participants are behavioral nodes (called via call_arrows). Entities are never mocked at the SUT level and never appear in any constructor as a collaborator — they show up in tests only as data_mocks the orchestrator passes between collaborator interactions.
entity_declaration — A line in the .puml prelude declaring a domain type the participants pass through data_pipes. Five kinds, one form per line: record, enum, class, interface, sealed-interface. The syntactic form is owned by the language_profile.
entity_prelude — The block of entity_declarations between the target_placement header and the participant prelude, preceded by a marker the language_profile defines. Optional — when absent, every type referenced in a method signature that is neither a language_profile-recognised standard type nor a participant is generated as a plain class under the language profile's entity package. When present, declared entities win over inferred ones for the same name.
Per-entity configuration (entity_target) and derived/composite entity concepts (sealed_family) live in the Composition section below, alongside their participant-side counterparts.
target_placement — A declaration on each design file (both .puml and decision_table_file) stating where its generated code should live. Every design file carries one; the language_profile defines the form. This is a required_decision.language_profile — A static reference document at skills/disc/<language>.md that owns every language-specific rule the pipeline depends on. SKILL.md is language-neutral; the language_profile defines the form of target_placement, the recognised config: keys, the test/implementation/decision-table templates, file path patterns, naming conventions, UPDATE-mode rules, the build command, and the documented defaults for every optional_decision. Step 3a selects which language_profile to load based on project signals.Each participant plays one of two roles. The role determines instantiation, mocking, and constructor membership.
component_under_test — The subject of the test. The participant that the system_caller calls. Instantiated in test setup via constructor injection of all collaborators. Not mocked.
collaborator — Every other participant. Mocked in tests. Injected into the component_under_test via its constructor.
Classifies each participant by its relationship to the call graph. Determines test style.
orchestrator — A participant that has outgoing call_arrows to other participants. DisC dictates its implementation structure: call order, arguments, and data flow are all fixed by tests.
leaf_node — A participant with no outgoing call_arrows. Its internal algorithm is unconstrained. DisC verifies its correctness via input/output examples, not its structure. Sub-classified as pure function (output depends only on inputs), side effect (touches external systems), or factory (pass-through to a constructor). The full sub-classification table — including DisC's action per sub-kind — is in the leaf_node transformation rule.
Derived facts about the design, computed before tests are generated.
interaction — One call_arrow paired with its optional return_arrow. The atomic unit of DisC. The interaction whose caller is the system_caller is the entry interaction — it declares the public method-under-test and produces no verify_test (the test harness's call into the SUT is not something to verify). Every other interaction is a collaborator interaction and produces a verify_test.
data_pipe — A relationship between two consecutive interactions in which the return_arrow value of the first becomes an argument of the next.
participant_target — A declaration on a participant that tells DisC whether to CREATE it as a new abstraction, REUSE an existing type as-is, UPDATE an existing type by adding methods, or DEFER its design to a separate .puml. Absent by default (meaning create). Four mutually-exclusive forms:
create (default when no stereotype is declared on the participant): generate the interface, implementation, and tests for this participant under the file's target_placement.existing:<fqn>: this participant is already implemented at the fully-qualified name <fqn>. DisC does not generate files for it; it appears only as a collaborator mock and constructor parameter. A participant declared as existing must have no outgoing call_arrows (REUSE means as-is — no behavioural change).extend:<fqn>:+method1,+method2,...: this participant exists at <fqn> but the design adds the listed methods. DisC opens the existing files (interface, implementation, test) in UPDATE mode and adds only the listed signatures.defer:<relative_puml_path>: this participant is called by the SUT but its internals have not yet been designed; design them later in their own .puml at the given path. DisC generates the interface plus a throwing stub-implementation now, with no test class and no decision table. The actual implementation will come from running DisC on the child .puml. Like existing:, a defer: participant must have no outgoing call_arrows in this diagram — its outgoing calls live in its own .puml. The path is optional in the stereotype; absent, the profile defaults to a sibling-folder convention (see language_profile).Exactly one form per participant. The syntactic form of the stereotype is owned by the language_profile. The abstract concept defines what each declaration means; the profile defines how it looks in the diagram.
One-hop mocking invariant. The SUT's test always mocks its direct collaborators as units, regardless of their participant_target. A collaborator's own dependencies (its grandchildren in the call tree) never bubble up to the SUT's test. This is true for create, existing:, extend:, and defer: alike — each collaborator is one mock at the SUT's level.
Direction of flow. DisC is outside-in for interfaces and inside-out for implementations. A participant's interface is pinned by its callers' call_arrows — the leaf does not author its own contract. Implementation flows the other way: each leaf is implemented from its own tests in isolation, and the orchestrator's implementation composes them. In a multi-level design (defer: participants), this convention spans .puml files: each child's interface is locked by the parent's call signatures (validated by the host's contractHash), and host tools build the tree bottom-up so an orchestrator is built only after its leaves' real implementations exist.
entity_target — Per-entity analog of participant_target. Two forms:
create (default, no stereotype): generate the entity's file under the language_profile's entity-package convention.existing:<fqn>: the entity already exists at <fqn>. DisC generates no file; it reads the existing source for its shape (fields for a record, permits for a sealed-interface) so downstream codegen can reference it. A REUSE entity carries the FQN binding only — declaring body content on a REUSE entity refuses at Step 1.extend and defer from participant_target do not apply to entities.
sealed_family — A sealed-interface entity_declaration paired with a permits list. Permits each resolve to a record or class entity in the same entity_prelude. Sealed families host sealed polymorphism variance: the parent declares one or more behaviors, and each permit produces an override. A sealed family with < 2 permits refuses at Step 1 (the sealed-interface kind models closed disjoint unions; with one permit there is no choice — declare the variant directly as a record or class).
What is generated in Phase 1 (design → tests).
Note: decision_table is the artifact generated for a pure function leaf. decision_table_file is the input file that may attach to one. They are different concepts on opposite ends of the pipeline.
verify_test — A test asserting that a collaborator method was called with expected arguments. One verify_test per call_arrow.
result_test — A test asserting that the component_under_test's return value equals an expected value. One result_test per final return_arrow (the return_arrow back to the system_caller). Absent when the method-under-test returns void.
stub — Configuring what a collaborator returns when called. One stub per return_arrow. Wired in test setup before execution.
data_mock — A mock representing a return value or input data. It is NOT a collaborator. It does NOT appear in the constructor. It exists only to carry identity through the data_pipe.
test_group — A scoped collection of tests sharing setup. One test_group per method when a component_under_test has multiple methods, or one test_group per branch when a branch_block is present.
decision_table — A set of input/output examples for a leaf_node. Human-designed, not AI-generated. Each row becomes one test with a direct assertion on the return value.
Each rule describes how a UML element transforms into test and implementation constructs, using only the concepts defined above.
participant to roleThe participant called by the system_caller is the component_under_test. It is instantiated in test setup via constructor injection of all collaborators.
Every other participant is a collaborator. Each collaborator becomes a mock in the test and a constructor parameter of the component_under_test.
The interaction whose caller is the system_caller is the entry interaction. It declares the method-under-test:
call_arrow's label is the method name and parameter list invoked on the component_under_test.data_pipe sources available to subsequent interactions.return_arrow back to the system_caller is the explicit final return (see "Final return_arrow").The entry interaction produces no verify_test. There is nothing to verify — the call is the test's invocation of the SUT, not an outbound call from the SUT.
How the method name, parameter types, and return type are derived from the entry interaction's label is owned by the language_profile. SKILL.md establishes the role and the structural rule; the profile owns the syntactic and type-resolution details.
interaction with return_arrowAn interaction that has a return_arrow produces:
stub in setup: configure the collaborator to return a data_mock when called.verify_test: assert the collaborator was called with the expected argument.The return_arrow label determines the data_mock name and type:
value : Type — Split on :. Left side is the variable name. Right side is the type.value — The variable name is the label. The type is inferred by capitalizing the first letter of each word (e.g., product becomes type Product).savedOrder : Order is clear, while inferring a type name from savedOrder alone is not).interaction without return_arrow (void)A call_arrow with no following return_arrow produces:
stub (nothing to configure).verify_test: assert the collaborator was called with the expected argument.return_arrowA return_arrow from the component_under_test back to the system_caller produces:
result_test: assert the captured result equals the expected data_mock.In implementation, this becomes the method's return statement.
When this return_arrow is absent, the method-under-test returns void: no result_test, and the implementation method declares a void return.
data_pipeWhen a return_arrow labels its value x, and a subsequent call_arrow uses x as an argument, the data_mock named x flows from the stub of the first interaction into the verify_test of the second interaction.
In implementation, this means the return value of one method call is passed as the argument to the next method call.
loop_blockA loop_block wrapping one or more arrows transforms as follows:
call_arrow inside the loop_block still produces one verify_test, verified for the single element.loop_block becomes an iteration construct over the collection.branch_blockAn alt / else / end fragment with multiple paths transforms as follows:
test_group with its own setup.test_group configures stubs that drive execution down that specific branch.verify_tests matching only that branch's call_arrows.test_group setup independently.branch_block becomes a conditional.throw_arrowA throw_arrow (self-arrow tagged as a throw declaration) produces two test_groups governed by the throw_placement rule:
Happy path test_group:
stubs are configured to avoid the exception condition.verify_tests assert collaborator calls happened.Exception path test_group:
stubs are configured to trigger the exception condition.When the UML specifies a message template in the throw_arrow:
leaf_nodeA participant with no outgoing call_arrows is a leaf_node. Leaves are classified by what kind of work they do at their boundary. DisC recognizes three sub-kinds:
| Sub-kind | Identified by | DisC action |
|---|---|---|
| pure function | Output depends only on inputs. Deterministic. | If a decision_table_file is attached, generate tests from its filled rows. Otherwise generate a decision_table skeleton. Human fills in test cases. |
| side effect | Touches external systems (DB, network, clock, queue, filesystem). | Mocked in consumer tests only. No standalone DisC test. Tested via integration, not DisC. |
| factory | Name ends in Factory. Assumed pass-through to a constructor. | No standalone test. Correctness is transitive through its consumer. |
For pure function leaves:
verify_tests.decision_table is valid.decision_table_file is attached, each row becomes one test with declared-type arguments and assertions. No TODO markers. Exception rows become exception-assertion tests (the assertion construct is owned by the language_profile).Dual testing rule (pure functions only): In the consumer's test, a pure function leaf is still a mocked collaborator with verify_tests. It also gets its own standalone decision_table test. These serve different purposes: the consumer's test verifies orchestration wiring; the decision_table verifies computational correctness. side effect and factory leaves have no standalone DisC test, so dual testing does not apply.
When AI generates both test cases and implementation for a leaf_node, it can produce matching pairs that pass but do not reflect actual requirements. AI writes: test expects X, implementation returns X. But the human needed Y.
Prevention: humans design decision_table test cases. AI implements only.
A pure function leaf appears in two places:
collaborator in its consumer's verify_tests (testing orchestration).decision_table test (testing correctness).Both are necessary. Neither substitutes for the other. side effect and factory leaves have no standalone DisC test, so this rule applies only to pure function leaves.
Each test contains exactly one verify_test OR one result_test. Never both. Never multiple. When a test fails, you know exactly which interaction broke.
Tests contain no conditionals, no loops, no branching. Tests are declarative: setup executes, then each test verifies one thing.
Prefer the explicit : Type format over inferring the type from the variable name. Explicit typing eliminates ambiguity and makes the data_mock type unambiguous to any reader of the diagram.
When a throw_arrow is present, the method-under-test is called at different places depending on the path:
A pure function leaf has decisions about behaviour that the rows may or may not pin down.
required_decision is one DisC will not silently default. If the rows do not demonstrate it AND config: does not pin it, Step 1 refuses.optional_decision is one where DisC applies a documented default silently when the rows are silent.The lists of which specific decisions are required_decision vs optional_decision — and the default values for each optional_decision — are language-specific and enumerated in the language_profile. Defaults are documented once in the language_profile; they are not reported per run. Any default can be overridden via config:.
The design provided by the user:
$ARGUMENTS
Execute these eight steps in order. Each step must be complete before the next begins. Report each step using its ### Step N: <name> heading from below as the section label in your response.
When $ARGUMENTS contains the token --plan, DisC executes Steps 1 through 6 as planning only and emits a single JSON object to stdout in place of Steps 7 and 8. No files are written. No file-writing tool is invoked. Plan mode lets host tools (e.g., DisC Studio) render a preview-before-apply panel.
The JSON envelope:
{
"actions": [
{
"type": "CREATE" | "UPDATE" | "REUSE",
"path": "<language-profile-path>/X.<ext>",
"participant": "X",
"reason": "string explanation",
"addedMethods": ["m1", "m2"]
}
],
"warnings": [
"<CollaboratorName>.<methodName> signature mismatch — using catalog form"
],
"summary": {
"create": 3, "update": 1, "reuse": 2,
"verifyTests": 4, "resultTests": 1, "decisionTables": 0
}
}
type: "CREATE" — file does not exist (or participant_target is create); plan would Write it.type: "UPDATE" — file exists (or participant_target is extend:...); plan would Edit it. addedMethods lists what would be added.type: "REUSE" — participant_target is existing:<fqn> or the participant resolves to an already-correct file with no additions. Plan would not touch this file. Include the row so the host can show "we'll use your existing X as-is".The envelope is the only thing emitted on stdout in plan mode. Steps 1–6 are reasoned about internally; no per-step narration is printed. The exit code is 0 on success and non-zero with a JSON error envelope ({"error": "..."}) on refusal.
When --plan is absent, the pipeline runs normally and produces Steps 1–8 narration plus written files.
When $ARGUMENTS contains the token --validate-only, DisC executes Step 1 only (the refusal-grade contract checks) and exits. No files are written. No file-writing tool is invoked. No tests, no implementation, no plan envelope — only the verdict on whether the design is shaped well enough to feed into Steps 2–6. Validate mode lets host tools (e.g., DisC Studio) preflight the design at authoring time before the user commits to a full run.
Strict-output rule — exactly as in plan mode, Step 1 is reasoned about internally; no per-step narration, no markdown headings, no "Validation Results" prose, no checklist of which rules passed. The stdout contract is one of exactly two shapes:
{"ok": true} — nothing before it, nothing after it. Exit code 0.#### REFUSAL — STOP markdown block exactly as defined in the Step 1 refusal protocol below — same wording, same EXPLAIN + SUGGEST sections, same exit code (non-zero). Host tools render this verbatim.Any other output (progress narration, partial summaries, "let me check…" preambles) is a contract violation that host tools cannot parse. Run the checks silently; emit only the verdict.
When --validate-only is absent and --plan is absent, the pipeline runs normally. When both flags are present, --validate-only wins (it is a strict subset of --plan).
The input set contains at least one .puml (UML sequence diagram) and may also contain one or more decision_table_files (<Participant>.decision.md). The plugin reads decision-table files from the same folder as the .puml being processed — not from a project-wide design/ root. This lets nested designs (a parent .puml with sibling-folder children) keep their decision tables locally scoped to their level of the call tree.
For each .puml: parse the diagram. For each element, confirm it matches a concept defined in the Concepts section above:
participant · call_arrow · return_arrow · loop_block · branch_block · throw_arrow
Use the disambiguation rules ("Distinguishing call_arrow from return_arrow") when arrow styles are identical. Confirm target_placement is declared on the design file.
For each decision_table_file: parse the YAML frontmatter and the markdown table. Confirm:
target: is present and well-formed (Class.method).input: is present and declares a type for every input column used in the table.output: is present and declares the method's return type.target_placement is declared.input.* key or an expected.* output field.throws: <ExceptionType> (optionally : "<message>").Refusal protocol — if any element is unsupported or ambiguous:
Refuse when:
UML structure:
system_caller. Every .puml must declare exactly one system_caller (the caller of the entry interaction).system_caller. One .puml = one method-under-test = one entry interaction.branch_block, loop_block, or other fragment. The entry interaction lives at top level.Decision-table well-formedness:
decision_table_file has missing or malformed frontmatter, or zero rows.decision_table_file's target: does not resolve to a pure function leaf in any UML in the input set (see Step 2 pairing).decision_table_file leaves a required_decision unspecified AND config: does not pin it. The refusal message names the decision and instructs the human to either (a) add a row that demonstrates the choice, or (b) add the corresponding config: key (see the language_profile for the recognized key for each decision).Participant stereotype:
participant_target stereotype is malformed: empty FQN (<<@class:>>), or a +method listed in an extend: form whose name does not appear as a call_arrow callee method on this participant in any UML in the input set.participant_target = existing:<fqn> has any outgoing call_arrow. Reuse-as-is means no behavioural change; if the design needs to call methods on this participant, the participant must be a different role (typically extend:) or the design must be restructured.+method listed in an extend:<fqn>:+method,... does not appear as a call_arrow callee on this participant. Every listed method must be exercised by the design.participant_target = defer:<path> is the target of the entry interaction. Its own .puml defines that — refuse, and direct the human to invoke DisC on the child .puml instead.participant_target = defer:<path> has any outgoing call_arrow in this diagram. Deferral means the internals are designed elsewhere; declaring them here is a contradiction.participant_target stereotype. Pick exactly one form. The four forms (create, existing:, extend:, defer:) are mutually exclusive.Entity prelude:
entity_declaration's kind is not one of record, enum, class, interface, sealed-interface.sealed_family declares fewer than 2 permits. The sealed-interface kind models closed disjoint unions; with one permit there is no choice — declare the variant directly as a record or class.sealed_family permit name does not resolve to a record or class entity_declaration in the same entity_prelude.entity_target = existing:<fqn> carries body content (fields, values, behaviors, or permits). REUSE means as-is — FQN binding only. The shape is read from the existing source.variant_decision_table's target permit does not implement the sealed parent it claims to override, or names a behavior that does not exist on the parent.sealed_family permit name collides with a participant name in the same input set. An identifier cannot be both an entity and a participant — rename one.Identify which concepts apply:
system_caller. The participant it calls is the component_under_test.participant as a collaborator.component_under_test and collaborator participants, sub-classify by call-graph role: orchestrator (has outgoing call_arrows) or leaf_node (no outgoing call_arrows).call_arrows → each is an interaction. The entry interaction (caller = system_caller) is counted but produces no verify_test.loop_blocks, branch_blocks, throw_arrows.leaf_node by asking: does its output depend only on inputs, does it touch the world, or is it a pass-through factory?| Sub-kind | Identified by | DisC action |
|---|---|---|
| pure function | Output depends only on inputs | decision_table skeleton (human fills in) — or filled rows when a decision_table_file is attached |
| side effect | Touches external systems (DB, network, clock, queue, etc.) | Mocked in consumer only — no standalone test |
| factory | Name ends in Factory | No standalone test — assumed pass-through constructor |
6.5. Read the participant_target declared on each participant. Parse the stereotype using the language_profile's notation. Record each participant's participant_target as one of:
create (no stereotype, or explicit create) — the default; DisC will generate this participant's interface, implementation, and test in Step 3 onwards.existing:<fqn> — DisC will not generate files; the participant is referenced only as a collaborator.extend:<fqn>:+method1,+method2,... — DisC will open the existing files at <fqn> in UPDATE mode and add the listed +method signatures.defer:<relative_puml_path> — DisC will generate the interface and a throwing stub-implementation (per the language_profile), but no test class and no decision table. The actual implementation comes from a later DisC run on the child .puml at the given path.Refusals for malformed stereotypes, existing or defer participants with outgoing arrows, entry-interaction targets that are deferred, multiple stereotypes on one participant, and unmatched +method lists belong to Step 1 — by this point they have already been ruled out.
Pair each decision_table_file with its target pure function leaf:
target: Class.method frontmatter field.Class across all UMLs in the run. It must be a leaf_node sub-classified as pure function.call_arrow to that participant with method name method.decision_table_file.Class does not match a participant, fall through to step 8 (it may be a sealed_family permit). If Class matches a participant that is a side effect or factory, refuse per Step 1's refusal protocol.Parse the entity_prelude (when present) and build the entities map:
entity_declaration, record {name, kind, fields, values, behaviors, permits, existingFqn, target}.sealed_family, link each permit name to its declaration in the same map; the permit's kind must be record or class.entity_target = existing:<fqn> entity, record the FQN binding; Step 3g will read the existing source.participant in the same input set, a primitive, a language standard-library type (per the language_profile's Domain Type Rule), or a boundary carrier (*Request/*Response/*DTO).decision_table_file (unpaired in step 7) as a variant_decision_table candidate:
target: Class.method.Class in the entities map. It must be a permit of some sealed_family..method on the parent sealed-interface's behaviors[]. It must exist.variant_decision_table.Class is not a permit, or .method is not a parent behavior, refuse per Step 1's refusal protocol.entity_prelude is absent, step 8 short-circuits to no-op. Type-token resolution does not apply; signature inference runs in Step 3 instead.3a. Detect language/framework — Determine which language_profile to load:
language_profile, use that.| Signal files | Language profile |
|---|---|
build.gradle, pom.xml, *.java | java_spring.md |
Load the matched language_profile. All subsequent steps use its conventions.
3b. Read target_placement per design file — For each design file, read its declared placement using the language_profile's form. Placement is per-file and authored by the human.
3c. Derive all target file paths — Use the language_profile's naming conventions, package placement rules, and file path patterns. Each file's target_placement anchors the paths derived from its participants and domain types; suffix-based sub-packaging (per the language_profile) applies relative to that placement.
3d. Check file existence — Glob all target paths.
3e. For each existing file: read it, identify what's already there (mocks, test groups, methods, signatures).
3f. Set mode per file from participant_target:
For each participant, derive the mode from its declared participant_target (recorded in Step 2.6.5):
participant_target = create → mode is CREATE for the participant's interface, implementation, and test. File paths come from the file's target_placement + the language_profile's naming and package-placement conventions.participant_target = existing:<fqn> → mode is REUSE. No interface, implementation, or test is generated for this participant. It is only referenced as a collaborator mock and constructor parameter in the component_under_test's test and implementation. The participant's existing signatures are read from source to populate mock field types where needed.participant_target = extend:<fqn>:+method,... → mode is UPDATE for all three files (interface, implementation, test). The FQN parses to the file path per the language_profile. Only the listed +method signatures and their corresponding test groups are added; everything else in the existing files is sacred.participant_target = defer:<path> → mode is STUB. CREATE the interface and a throwing stub-implementation (the profile owns the stub template and the Pending<Name> naming). No test class. No decision table — defer: participants are not leaves and have no decision_table_file paired. The SUT still mocks the participant as a collaborator at its own test level (one-hop mocking invariant). The deferred child's own internals are designed and implemented by a future DisC run on <path>.Fallback for participants with no participant_target declared: glob the conventional file path per the language_profile. NEW → CREATE, EXISTS → UPDATE.
3g. Set mode per entity from entity_target (when entity_prelude is present):
For each entity in the entities map (built in Step 2.8), derive the mode from its declared entity_target:
entity_target = create → mode is CREATE. The file path comes from the language_profile's entity-file convention; the body is the entity's kind plus its fields/values/behaviors/permits, expressed in the profile's per-kind template. For sealed_family parents, the body is the parent declaration (sealed-interface form per the profile) plus the parent's behaviors as abstract method signatures. For each permit, a separate CREATE is emitted: the permit's own record file, with the parent-implementation declaration (form per the profile) and one override body per parent behavior (filled or skeleton — see Step 6).entity_target = existing:<fqn> → mode is REUSE. No file is generated. Read the existing source via the language_profile: for a record, capture field names and types; for a sealed-interface, capture the permits clause. Refuse here if a REUSE sealed_family's design-declared permits don't match the existing source's permits clause exactly — REUSE is FQN binding only; permit drift is a design error. (To add a new permit to an existing sealed type, declare the family as create in the design.) The captured shape is used in Step 5 (cross-check) and in Step 4 codegen wherever the entity appears as a data_mock or method parameter.Fallback when entity_prelude is absent: every type referenced in a method signature that does not match a participant is generated as a plain class under the entity package, with no fields.
For each classified element, apply its transformation rule from the Transformation Rules section above:
| Element | Rule | Produces |
|---|---|---|
participant | "participant to role" | Mocks, constructor wiring |
Entry interaction | "Entry interaction" | Method-under-test signature; test method invocation; data_pipe sources |
Collaborator interaction + return_arrow | "interaction with return_arrow" | stub + verify_test |
Collaborator interaction (void) | "interaction without return_arrow" | verify_test only |
Final return_arrow | "Final return_arrow" | result_test |
data_pipe | "data_pipe" | Return value → next argument |
loop_block | "loop_block" | Single-element collection, iteration |
branch_block | "branch_block" | Separate test_group per branch |
throw_arrow | "throw_arrow" | Two test_groups with throw_placement |
leaf_node (pure function), no file attached | "leaf_node" | decision_table skeleton |
leaf_node (pure function), decision_table_file attached | "leaf_node" | Filled tests, one per row |
Participant with participant_target = defer:<path> | "STUB mode" | Interface + throwing stub-impl only. No test class. No decision table. |
entity_declaration (entity_target = create) | "entity to file" (language profile owns the template per kind) | File (per the language_profile's entity-file convention) for one of: record, enum, class, interface, or sealed-interface family parent |
entity_declaration (entity_target = existing:<fqn>) | "REUSE entity" | No file. Source shape recorded in Step 3g is used wherever the entity appears downstream. |
sealed_family permit (each one) | "variant impl" | Each permit emits its record file with the parent-implementation declaration and one override body per parent behavior. Body is filled from a paired variant_decision_table's rows, or skeleton (throwing the language profile's variant marker exception) when no table is paired. A test file accompanies each permit. |
Use the language_profile's test class template and naming conventions.
Generation order: declared entities (parents before permits) → inferred domain types (fallback when no entity_prelude) → participant interfaces → tests (orchestrator mockist tests, pure-function leaf tests, variant tests) → decision_table skeletons (only for unpaired pure function leaves; sealed_family permits without paired variant_decision_tables emit skeleton variant impls inline rather than as a separate decision-table file).
Before writing anything, pass every check. Fix generated code if any check fails.
Self-reflection protocol: Iterate your output until you rate it 10/10 against an internal rubric before proceeding. Do not infer patterns not defined in this methodology.
Six critical checks:
Arrow parity — verify_test count == count of collaborator interactions (interactions whose caller is the component_under_test). The entry interaction is excluded — it produces no verify_test. Each stub has a corresponding return_arrow. The result_test matches the value labeled on the return_arrow back to the system_caller; if no such arrow is present, the method-under-test is void and there is no result_test.
Data flow integrity — Each data_pipe connects correctly. Implementation call order matches verify_test order. Variable names match data_mock names.
File mode correctness — Step 3 discovery complete. CREATE → Write tool. UPDATE → Edit tool. STUB → Write tool for interface and Pending<Name> stub-impl only; no test file. No existing content modified, moved, or deleted. No duplicate mock fields or test groups.
Type resolution (only when entity_prelude is present) — Every type referenced in any participant method or sealed-interface behavior resolves to an entity_declaration in the entities map, a participant in the same input set, a primitive, a language standard-library type (per the language_profile's Domain Type Rule), or a boundary carrier. Any unresolved token refuses here — the design references a type DisC cannot place.
Sealed-family override completeness — Every sealed_family permit produces an override for every parent behavior. Each override is either filled (from a paired variant_decision_table's rows) or skeleton-throwing (with the language profile's variant marker exception). REUSE sealed-family permits clauses match the existing source's permits clause exactly — any drift should have been refused at Step 3g; this is a belt-and-braces re-check.
Pattern rules:
collaborator has a mock field; constructor includes all collaborators and only collaboratorsdata_mock has a mock field (or real value for primitives and types the language_profile flags as non-mockable)throw_placement correct (exception path calls method inside assertion, not in setup)leaf_nodes classified as pure function, side effect, or factory; standalone tests (pure functions only) use direct assertions, not verify_testsdecision_table skeletons marked TODO for human review (only when no decision_table_file is attached)decision_table_file, every required_decision is either demonstrated by rows or pinned by config:. (If it isn't, Step 1 should have refused — this is a belt-and-braces check.)pure function leaves both mocked in consumer AND get standalone tests (dual testing); side effect and factory leaves have no standalone testbranch_block has one test_group per branch with branch-specific stub setuploop_block test data uses single-element collectionRe-read the test file. Do NOT reference the UML diagram.
Derive implementation entirely from the tests:
verify_test → one method call in implementation, in orderstub chain → capture return value, pass through data_piperesult_test expectsUse the language_profile's implementation template and conventions.
This enforces Invariant 2: implementation matches what tests demand, not what UML shows.
For pure function leaves with a decision_table_file attached:
The implementation is a deterministic function of three inputs: the rows, the config: block, and the documented optional_decision values from the language_profile.
config:. It pins the value for any decision the rows do not demonstrate.optional_decision the rows and config: are silent on, apply the language_profile's documented default.required_decision. If one is unspecified at this point, Step 1 failed to refuse — stop and re-check Step 1, do not paper over it here.Write the implementation using these values. There is no per-run audit log; the rules are fixed by the methodology and the language_profile.
For participants with participant_target = defer:<path> (STUB mode):
The stub-implementation is canonical: every method on the interface throws the language_profile's stub-marker exception, whose message names the deferred participant and the expected sub-design path. The exact template (annotation, naming, message string) is owned by the language_profile. The stub compiles and lets the application framework wire the SUT's dependency; only actual execution of the deferred behaviour fails, at runtime, with a clear DisC-tagged message that CI can grep for to block production deploys.
For sealed_family permits:
Each permit record carries one override method body per parent behavior. The body source depends on whether a variant_decision_table is paired:
variant_decision_table attached): apply the existing filled-mode generator (same rule as "pure function leaves with decision_table_file attached" above), but route the generated body into the permit's override method instead of a standalone consumer-default impl file. One test per row in the permit's accompanying test file.variant_decision_table paired): the override throws the language profile's variant marker exception (named after the deferred-design marker so CI greppable). The accompanying test file is a skeleton with one test placeholder per parent behavior, marked TODO for the human to fill.The sealed parent itself has no implementation file beyond its parent declaration (sealed-interface form per the language_profile) — the parent's behaviors are abstract; the permits own the bodies. No standalone parent-test exists; the variants' tests cover correctness, and the orchestrator's test covers the dispatch.
CREATE mode: Write tool — complete file. UPDATE mode: Read tool first, then Edit tool — add only, never modify existing.
Never use the Write tool on an existing file.
Critical rule: Existing content is sacred.
Use the language_profile's UPDATE mode rules per file type.
Summary:
Entry interaction: present (caller = system_caller, target = <SUT>.<method>)
Interactions: [E] entry + [N] collaborator = [total]
Orchestrators: [N] participants with outgoing arrows
Leaf nodes: [M] total ([P] pure function, [S] side effect, [F] factory)
Deferred: [D] participants stubbed; child .puml paths: [paths]
Entities: [E_total] total ([R] record, [S] sealed-family, [N] enum, [I] interface, [C] class; [X] REUSE)
Sealed-family variants: [V] permits ([VF] filled from variant_decision_table, [VS] skeleton)
Decision tables: [K] filled from decision_table_file, [Q] skeletons for humans to fill
Tests: [N] verify_tests + [R] result_tests = [total] total
Files: [CREATE/UPDATE/STUB labels per file]
Human verification checklist:
verify_tests in test. Must match.verify_test argument matches its UML arrow's argument.stub matches a return_arrow.target_placement declared on its source design file.sealed_family, every permit's record file has the parent-implementation declaration and one override per parent behavior.Final steps:
language_profile's build commandProvides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
npx claudepluginhub mossgreen/design-is-code-plugin --plugin design-is-code