This skill should be used when the user asks to "sync domain model", "update Qlerify", "push changes to Qlerify", "sync schemas", "sync entities", or after implementing features that add or change...
Reconcile a local codebase with its Qlerify workflow so the two stay in agreement. Drift happens on both sides: a developer adds a field or endpoint in code, or someone edits the domain model on the Qlerify board. This skill detects drift on either side and routes each direction to the right place.
code-generation skill, which is the model β code executor.This skill pairs with two others: workflow-creation builds the model (and reverse-engineers code into a model in its Phase 0); code-generation writes code from the model. Sync is the ongoing reconciler that keeps them aligned after both have run.
code-generation, where the model is authoritative for producing code β that's a different action. Authority depends on what's being done: sync writes codeβmodel drift itself, and hands modelβcode drift back to code-generation, which treats the model as the source of truth.modelHash recorded at the last sync/generation is the pivot. Without it, sync can see that code and model differ but not which side moved β so it must ask rather than assume.version column, or an FK index is an implementation choice, not a missing/extra domain element. Read persistenceDecisions from the anchor and do not flag them..qlerify/codegen.jsonSync is precise only when it knows the model state as of the last reconciliation. That state lives in .qlerify/codegen.json (written by code-generation, and maintained by this skill):
workflowId + workflowName β which workflow this code corresponds tomodelHash β content hash of the canonical spec at last sync/generation (the drift pivot)stack, aggregates β platform and progress, used by code-generationpersistenceDecisions β non-derivable storage choices to ignore as driftgeneratedAt β ISO timestamp written by code-generationsyncedAt β ISO timestamp written/updated by sync on each reconciliation (absent until the first sync)Sync maintains this anchor. If a project has a model but no anchor (hand-written or model-first code that was never generated), sync writes one on its first successful run so every later sync becomes precise. From then on the project is "anchored" regardless of whether code-generation ever ran.
modelHash is the drift pivot, so sync and code-generation must compute it identically β if they diverge, every sync falsely reports "model moved." Until an authoritative model_hash MCP tool exists, both skills use this exact recipe:
specification object returned by get_workflow.color, group, and follows. Stripping these means board-only edits (recolor, regroup, drag-reorder events) do not read as model drift. What remains is the domain-semantic model: entities, value objects, commands, read models, domain event schemas, bounded contexts, roles, aggregate-root links, and acceptance criteria.modelHash is the SHA-256 hex digest of that string.Reference implementation (bash + jq, run on the specification JSON):
jq -cS 'walk(if type == "object" then del(.color, .group, .follows) else . end)' \
| shasum -a 256 | cut -d" " -f1
The scope is domain semantics only β two models that differ only in board cosmetics or event ordering must produce the same hash. Follow this definition exactly; a future model_hash MCP tool would make it authoritative and remove the hand-rolled step.
.qlerify/codegen.json (and the cached spec at .qlerify/workflow.json).download skill (much faster than MCP for big specs); otherwise call get_workflow once and cache it. The field types, values, and relationships you need are already in the spec payload itself β read them from there (and from the cached .qlerify/workflow.json) rather than fetching the $schema URL.workflowId from the anchor.list_workflows, match by project/name, or ask the user. For a brownfield codebase with unclear aggregate boundaries, isolate one aggregate at a time first β defer to workflow-creation Phase 0 for the extraction.modelHash. If they differ, the model moved since last sync β that side's drift is known up front.Read the code as a domain model, in the same DDD terms the other skills use β not as raw files.
id) and lifecycle; a value object is defined by its attributes and replaced as a whole. Classify by identity/lifecycle semantics, not by whether the ORM gave it an id column.*Id fields pointing at another bounded context's aggregate. These stay opaque strings; do not pull in the external aggregate's internals.Search hints: src/domain/, src/modules/, src/entities|models/, command/handler/query folders, src/routes|api|controllers/, prisma/schema.prisma, **/schema.graphql, **/migrations/. Use git diff / git log to find recently changed schema files for field-level deltas.
Build a reconciliation diff per category (entities, value objects, commands, read models, domain event schemas, fields, bounded contexts). For each element decide which case it is:
| Case | Meaning | Action |
|---|---|---|
| In code, not in model | Code is ahead | Apply code β model (Phase 3) |
| In model, not in code | Model is ahead | Suggest code-generation (Phase 4) |
| In both, differing | Conflict or one-sided edit | Anchored: direction from the hash. Unanchored: ask the user |
Differs but is a persistenceDecision |
Storage choice, not drift | Ignore |
In anchored mode, the hash comparison from Phase 0 tells you whether the model moved; combine that with the code scan to attribute each difference to the correct side automatically. In unanchored mode, present each difference and let the user say which side is right.
Renames are ambiguous β never auto-apply them. Entities, commands, and fields are keyed by name, so a rename (note β customerNote) looks structurally identical to a delete of note plus an add of customerNote. Before treating a name as deleted on one side and a new name as added on the other, check whether they are actually the same element renamed: correlate against the anchored baseline (the element that existed at last sync) and compare shape β same type/dataType, same relatedEntity/cardinality, same position among siblings, same surrounding fields. If they plausibly match, it is a rename, not a delete+add β flag it as a conflict (or a one-sided rename) and ask the user rather than deleting one name and creating the other, which would drop data and history. When there is no baseline (unanchored) the correlation is weaker, so lean even harder on asking.
Write code-side drift into Qlerify with the MCP tools, following the model's own authoring rules.
id for entities, no id for VOs, boundedContext, link aggregateRootFor), then create/adjust commands and read models, then fill entity fields last with update_entities β including the cross-entity relatedEntity references. The bulk create tools resolve relatedEntity refs against the workflow state at the start of the call, so a forward reference to an entity created in the same call resolves to null. The stubs-then-fields order avoids that.create_entities, create_commands, create_read_models, update_entities, β¦) and batch related changes into one atomic write.cardinality on related fields, computed: true on calculated read-model fields, isFilter: true on query parameters (at any nesting level), isRequired for non-nullable fields, and 3 realistic exampleData values per entity field.id; value objects never do and are replaced as a whole.relatedEntity (+ cardinality) and is named after the entity (shippingAddress, orderItems). A reference to an external-BC aggregate is a plain string named {entity}Id (customerId) β never combine an Id suffix with relatedEntity.isFilter: true for query parameters and computed: true for runtime-calculated fields, at any nesting level.exampleData values (["Object", "Object", "Object"] for relatedEntity fields).workflow-creation skill is installed alongside this one, consult its references/ (entity, command, read-model, domain-event generation rules, naming/character limits, and the relatedEntity usage table). Don't depend on it being present β the essentials above stand alone.When the model has elements the code lacks (or the hash shows the model moved), sync does not edit code. Instead:
code-generation skill to apply the delta as a targeted patch β it already knows how to add an aggregate or apply a model change to existing code from the anchor.validate_domain_model on the affected bounded context(s). Treat it as a judgment loop, not a one-shot: fix genuine structural problems, leave legitimate domain patterns (e.g. a read-model filter field that isn't on the entity) as-is. Re-run until every remaining issue is consciously accepted..qlerify/codegen.json as modelHash, with a fresh syncedAt. Create the anchor here if the project was unanchored, recording workflowId, workflowName, and the hash so the next sync is precise. Preserve stack, aggregates, and persistenceDecisions if present.| Code type | Qlerify field |
|---|---|
string, varchar, text, char, uuid |
string |
number, int, float, decimal, bigint |
number |
boolean, bool |
boolean |
Nested object, JSON, jsonb, embedded type |
object (+ relatedEntity + cardinality when it's an owned entity/VO) |
| Foreign key / relation to a same-BC entity | relatedEntity ($ref) + cardinality |
| Foreign key to an external-BC aggregate | plain string field named {entity}Id β no relatedEntity |
| Enum / union of literals | string with the allowed values captured as an invariant in the description |
workflow-creation Phase 0.code-generation. Sync detects modelβcode drift and hands off.persistenceDecisions and leaves them alone.