Agent skill

Write Lint Rules

by mendixlabs in mendixlabs/mxcli

Write custom Starlark lint rules in .claude/lint-rules/ that run beside the built-ins under mxcli lint.

Apache-2.0Auto-check passedDevelopment

Install Write Lint Rules

skills CLI
$ npx skills add mendixlabs/mxcli --skill write-lint-rules -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install mendixlabs/mxcli write-lint-rules --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/mendixlabs/mxcli.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/mendix/write-lint-rules .claude/skills/write-lint-rules && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
write-lint-rules
GitHub stars
128
Token cost
~9.7k tokens
SKILL.md length
3,551 words
Files
2
Skills in repo
72
Repo updated
First seen
Licence
Apache-2.0

At a glance

Write custom Starlark lint rules in .claude/lint-rules/ that run beside the built-ins under mxcli lint.

  • A project convention should be enforced automatically
  • SKILL.md covers Rule File Structure, Available Query Functions and Object Properties
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md
  • Tasks that involve Linting and formatting

What it does

Write Lint Rules is an agent skill from mendixlabs/mxcli. Write custom Starlark lint rules in .claude/lint-rules/ that run beside the built-ins under mxcli lint. Use when a project convention should be enforced automatically.

Its SKILL.md is about 9.7k tokens, which your agent loads only when the skill is triggered. The skill folder holds 1 other file (for example `catalog-tables.md`).

It sits in Development, covering Linting and formatting. The repository describes itself as: Mendix cli tool, a headless way to work with Mendix projects. Enables Mendix projects for use with 3rd party agentic coding tools like Claude Code and Copilot. Includes a… The licence is Apache-2.0.

When your agent uses it

  • A project convention should be enforced automatically
  • Tasks that involve Linting and formatting

Example prompts

  • “/write-lint-rules”

Requirements

  • Python 3

What it can do on your machine

Read from SKILL.md and the folder at commit 20a6c89. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are python and bash).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Write Lint Rules loads about 9.7k tokens when it runs. Until then it costs about 47 tokens; SKILL.md has 3,551 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~47
When it runs · the whole SKILL.md, loaded when a task matches
~9.7k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from mendixlabs/mxcli at commit 20a6c89, republished under its Apache-2.0 licence (© mendixlabs). 3,551 words, ~9,715 tokens.

Download SKILL.mdSave it as .claude/skills/write-lint-rules/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.
name
write-lint-rules
description
Write custom Starlark lint rules in .claude/lint-rules/ that run beside the built-ins under `mxcli lint`. Use when a project convention should be enforced automatically.

Writing Custom Starlark Lint Rules

Custom lint rules are written in Starlark (a Python-like language) and placed in .claude/lint-rules/ as .star files. They run alongside the built-in rules when mxcli lint -p app.mpr is executed.

Rule File Structure

Every .star file must define metadata constants and a check() function:

python
RULE_ID = "CUSTOM001"          # unique identifier
RULE_NAME = "MyRule"           # Short display name
DESCRIPTION = "What it checks" # One-line description
CATEGORY = "security"          # Category: naming, quality, design, security, etc.
SEVERITY = "warning"           # hint, info, warning, error

def check():
    violations = []
    # ... iterate data, find issues, append violations ...
    return violations
Catalog data requirements (widgets, refs_to, cycles, …)

Some builtins need a deeper catalog than the default fast build:

  • refs_to, refs_from, widgets, xpath_expressions, activities_for, permissions, permissions_for, strings and the widget_count field of a page or snippet need REFRESH CATALOG FULL — the refs, widgets, xpath_expressions, activities, permissions and strings tables and the widget counts are only written by a full build.
  • The graph-analysis builtins (cycles, module_dependencies, community_of, layer_of, centrality, god_nodes, integration_surface) need REFRESH CATALOG COMMUNITIES (the graph_* tables).

You don't have to do anything: mxcli lint (and the LINT statement) auto-detect these builtins (a call name() and .widget_count in your rule's source and build the catalog at the required depth automatically. If the scan cannot see it — e.g. getattr(p, "widget_count"), or a builtin passed around by name — or you want to be explicit, declare it:

python
REQUIRES = ["full"]          # or ["communities"] — raises the auto-detected depth

Without this, a rule that reads a full-only table under a fast build gets [] / 0 with no warning and reports a clean pass (issue #721).

Available Query Functions

FunctionReturnsDescription
entities()list of entityAll non-system entities
microflows()list of microflowAll non-system microflows, nanoflows and rules — they share one catalog table. Name the document with document_noun_title, never a hardcoded "Microflow"
pages()list of pageAll non-system pages
enumerations()list of enumerationAll non-system enumerations
constants()list of constantAll non-system constants
widgets()list of widgetAll non-system page and snippet widgets (full catalog — auto-detected)
snippets()list of snippetAll non-system snippets
scheduled_events()list of scheduled_eventAll non-system scheduled events (requires MPR reader)
queues()list of queueAll non-system task queues
java_actions()list of java_actionAll non-system, non-marketplace Java actions, each carrying its parameters
database_connections()list of database_connectionAll non-system external database connections
documents()list of documentEvery App Explorer document outside System and Marketplace modules, as one uniform projection with folder — for rules about where a document lives
documentable_elements()list of documentableEvery element that can carry documentation, across all document types, with its description — for documentation sweeps. Leaves out microflows and Java actions; use microflows() / java_actions() for those
navigation_targets()list of navigation_targetEvery page a navigation profile routes to: profile home pages, role home pages and menu items. Login and not-found pages are excluded
rest_clients()list of rest_clientConsumed REST service documents (excluding platform modules)
rest_operations()list of rest_operationOperations on consumed REST services, including their timeout
attributes_for(entity_qualified_name)list of attributeAttributes for a specific entity
activities_for(microflow_qualified_name, nested = False)list of activityActivities of a microflow, nanoflow or rule, in flow order (full catalog — auto-detected). By default only the top level: a loop is one activity and its body is left out. nested = True adds every object inside a loop, at any depth, right after its loop, with parent_loop_id and loop_depth set
permissions()list of permissionAll permissions across all element types (full catalog — auto-detected)
permissions_for(entity_qualified_name)list of permissionAccess rules for a specific entity (full catalog — auto-detected)
refs_to(target_name)list of referenceCross-references to a target (full catalog — auto-detected)
refs_from(source_name)list of referenceCross-references from a source (outbound) (full catalog — auto-detected)
user_roles()list of user_roleUser roles from project security
module_roles()list of module_roleAll module roles (deduplicated from role mappings)
role_mappings()list of role_mappingUser role to module role assignments
project_security()project_security or NoneProject-level security settings (requires MPR reader)
xpath_expressions()list of xpath_expressionAll XPath constraint expressions in the catalog (access rules, retrieve actions, widgets) (full catalog — auto-detected)
modules()list of moduleThe user's modules (not System, not Marketplace), with their domain model's documentation
associations()list of associationAll non-system associations, same-module and cross-module, with the delete behaviour of both ends
entity_event_handlers()list of entity_event_handlerEvery before/after event handler on a non-system entity: which moment, which event, which microflow
navigation_menu_items()list of navigation_menu_itemEvery navigation menu item of every profile, at every depth. Navigation belongs to the project, so no module filter applies
jar_dependencies()list of jar_dependencyMaven dependencies declared by non-system, non-marketplace modules
strings(language = None)list of catalog_stringUser-facing and documentary text, one row per text and language; pass language ("nl_NL") to narrow. An untranslated language has no row. Needs a FULL catalog, which mxcli lint builds automatically for a rule that calls it
layouts()list of layoutAll non-system layouts
published_rest_operations()list of published_rest_operationOperations of published REST services, with the microflow behind each

The fields of module, association, entity_event_handler, navigation_menu_item, jar_dependency, catalog_string, layout and published_rest_operation are in catalog-tables.md.

Graph-analysis functions (architecture rules)

These expose the dependency-graph facts so you can enforce your own architecture policy (layering, allowed module dependencies, no cycles, coupling budgets). They require refresh catalog communities to have populated the graph tables; otherwise they return empty/None (the rule degrades gracefully — it does not fail). In a session, run refresh catalog communities before lint.

FunctionReturnsDescription
layer_of(asset)int or NoneTopological layer sequence number (no opinion on ordering)
community_of(asset)struct{id, label} or NoneThe asset's detected community (bounded context)
cycles()list of struct{id, size, members}Dependency cycles (SCCs > 1 node)
module_cycles()list of struct{id, size, members}Module-level dependency cycles over every reference kind; members are module names. Use this, not cycles(), for "no circular module dependencies" — modules can reference each other through documents that form no asset-level cycle
module_dependencies()list of struct{source_module, target_module, ref_kind, edges}Directed module→module edges
centrality(asset)struct{in, out, total, pagerank, betweenness} or NoneCentrality of an asset
god_nodes(metric="degree"|"pagerank"|"betweenness", min=N)list of struct{asset, object_type, module_name, degree, pagerank, betweenness}High-centrality assets above a threshold
integration_surface()list of struct{source_community, target_community, ref_kind, edges, mechanism}Cross-community contract edges (for app-splitting)

Example — a team enforcing its own strict layering (mxcli ships no such rule):

python
RULE_ID = "ARCH900"
RULE_NAME = "Layering"
DESCRIPTION = "A module may only depend on lower or equal layers"
CATEGORY = "architecture"
SEVERITY = "error"

def check():
    out = []
    for d in module_dependencies():
        if d.ref_kind in ("layout", "show_page"):  # ignore UI navigation
            continue
        ls, lt = layer_of(d.source_module + ".x"), layer_of(d.target_module + ".x")
        # (resolve a real asset per module in practice; shown simplified)
        if ls != None and lt != None and ls < lt:
            out.append(violation(message = "%s depends upward on %s" % (d.source_module, d.target_module)))
    return out

Another team bans a specific dependency:

python
def check():
    return [violation(message = "Payments must not depend on Reporting")
            for d in module_dependencies()
            if d.source_module == "Payments" and d.target_module == "Reporting"]

Object Properties

The example values below are the real ones — do not adapt their case or their spelling. A filter on a value the catalog never emits is silent: the rule compiles, runs, matches nothing and reports a clean pass. Two traps in particular:

  • Case is not cosmetic. Document and element kinds are upper-case ("MICROFLOW", "ENTITY", "READ"), attribute data types are TitleCase ("String", "DateTime"), and ref_kind is lower-case ("call", "show_page"). Guessing wrong matches zero rows.
  • action_type is the SDK name, never Mendix's BSON storage name. The catalog reports ShowPageAction / ClosePageAction / CreateObjectAction / CommitObjectsAction; the storage names ShowFormAction, CloseFormAction, CreateChangeAction and CommitAction that appear in .mpr documents never reach a rule. A rule that allow-lists the storage names flags every microflow that opens a page — the inversion measured at 49% false positives in mendixlabs/mxcli#1027. The one exception is widget.action_type, which is the raw stored type of a page action ("Forms$DeleteClientAction") — page actions have no SDK-name mapping in the catalog.

To check a value against your own project rather than trusting any list:

bash
sqlite3 .mxcli/catalog.db "SELECT DISTINCT ActionType FROM activities;"
sqlite3 .mxcli/catalog.db "SELECT DISTINCT SourceType, TargetType, RefKind FROM refs;"

Absence from your project means the construct is not used there; a value absent from the tables below is one the catalog never produces anywhere.

entity
PropertyTypeExample
idstringDocument UUID
namestring"Customer"
qualified_namestring"Sales.Customer"
module_namestring"Sales"
folderstring"DomainModel" — folder path within module
entity_typestringexactly "Persistent", "NonPersistent" or "View" — any other spelling matches nothing and the rule silently reports nothing
descriptionstringDocumentation text
generalizationstringParent entity qualified name
attribute_countintNumber of attributes
access_rule_countintNumber of access rules
validation_rule_countintNumber of validation rules
has_event_handlersboolTrue if entity has event handlers
is_externalboolTrue if entity is from an external service
has_created_dateboolTrue if the entity stores createdDate (an audit member, not counted in attribute_count)
has_changed_dateboolTrue if the entity stores changedDate
has_ownerboolTrue if the entity stores owner
has_changed_byboolTrue if the entity stores changedBy
microflow
PropertyTypeExample
idstringDocument UUID
namestring"ACT_Customer_Create"
qualified_namestring"Sales.ACT_Customer_Create"
module_namestring"Sales"
folderstring"microflows/Customer" — folder path within module
microflow_typestringexactly "MICROFLOW", "NANOFLOW" or "RULE" — upper-case, unlike entity_type. microflows() yields all three flavours, so a rule meant for microflows only must filter on "MICROFLOW"
descriptionstringDocumentation text
return_typestringReturn type
parameter_countintNumber of parameters
activity_countintNumber of activities at the top level of the flow, excluding start/end events and merges. A loop counts as one; its body is not counted
total_activity_countintactivity_count plus every activity inside a loop, at any depth — the size of the flow including loop bodies. Equal to activity_count for a flow without loops
complexityintMcCabe cyclomatic complexity
document_nounstring"microflow", "nanoflow" or "rule" — for mid-sentence use in a message
document_noun_titlestring"Microflow", "Nanoflow" or "Rule" — for document_type= and a message that opens with it
page
PropertyTypeExample
idstringDocument UUID
namestring"Customer_Overview"
qualified_namestring"Sales.Customer_Overview"
module_namestring"Sales"
folderstring"pages/Customer" — folder path within module
titlestringPage title in the project's default language (else en_US, else the lowest-sorted non-empty language); "" when the page has none
urlstringPage URL
descriptionstringDocumentation text
widget_countintNumber of widgets (full catalog — auto-detected)
enumeration
PropertyTypeExample
idstringDocument UUID
namestring"OrderStatus"
qualified_namestring"Sales.OrderStatus"
module_namestring"Sales"
folderstring"enumerations" — folder path within module
descriptionstringDocumentation text
value_countintNumber of enum values
constant
PropertyTypeExample
idstringDocument UUID
namestring"AppBaseUrl"
qualified_namestring"MyModule.AppBaseUrl"
module_namestring"MyModule"
folderstring"constants" — folder path within module
descriptionstringDocumentation text
default_valuestring"https://example.com"
exposed_to_clientbooltrue if constant is exposed to client

widget — the struct returned by widgets() — identity, references, tree position (parent_widget_id, depth), appearance (class_name, style) and primary action (action_type, has_confirmation) — is documented in catalog-tables.md, with an example rule.

snippet
PropertyTypeExample
idstringDocument UUID
namestring"SNIPPET_CustomerCard"
qualified_namestring"Sales.SNIPPET_CustomerCard"
module_namestring"Sales"
folderstring"snippets" — folder path within module
widget_countintNumber of widgets (full catalog — auto-detected)
scheduled_event
PropertyTypeExample
namestring"SE_NightlyCleanup"
qualified_namestring"MyModule.SE_NightlyCleanup"
module_namestring"MyModule"
microflow_namestring"MyModule.MF_NightlyCleanup" — resolved from catalog; raw UUID when catalog not built
interval_secondsint86400 — 0 for unrecognised interval type
repeatstringSchedule variant: "Minute", "Hour", "Day", "Week", "MonthDate", "MonthWeekday", "YearDate" or "YearWeekday"; "" when the event has no schedule
on_overlapstring"DelayNext" or "SkipNext" — what happens when a run is still going at the next start
time_zonestringTime zone the schedule is evaluated in
enabledboolTrue if the event is active
queue
PropertyTypeExample
namestring"ImportQueue"
qualified_namestring"Sales.ImportQueue"
module_namestring"Sales"
parallelismstring"3" — an expression, stored as a string; do not assume it parses as an integer
cluster_wideboolTrue if parallelism applies across the cluster rather than per node
java_action
PropertyTypeExample
idstringDocument UUID
namestring"JA_ParseJson"
qualified_namestring"Sales.JA_ParseJson"
module_namestring"Sales"
folderstringFolder path within module
documentationstringDocumentation text
descriptionstringSame as documentation, so a rule sweeping mixed document kinds can read one field name
export_levelstring"Hidden" or "API"
return_typestringReturn type
parameter_countintNumber of parameters
parameterslist of java_action_parameterThe action's parameters, in order
java_action_parameter (nested in java_action)
PropertyTypeExample
namestring"InputString"
descriptionstringParameter documentation
parameter_typestringParameter type
is_requiredboolTrue if the parameter is required
database_connection
PropertyTypeExample
idstringDocument UUID
namestring"LegacyDB"
qualified_namestring"Integration.LegacyDB"
module_namestring"Integration"
folderstringFolder path within module
database_typestringDatabase engine of the connection
query_countintNumber of queries defined on the connection
document

Returned by documents().

PropertyTypeExample
kindstringCatalog object type, upper-case: "MICROFLOW", "PAGE", "WORKFLOW", …
namestring"Customer_Overview"
qualified_namestring"Sales.Customer_Overview"
module_namestring"Sales"
folderstringFolder path within module; "" means directly in the module root
documentable

Returned by documentable_elements().

PropertyTypeExample
kindstringMendix term, TitleCase: "Page", "Enumeration", "Workflow", …
namestring"OrderStatus"
qualified_namestring"Sales.OrderStatus"
module_namestring"Sales"
descriptionstringDocumentation text, whichever of the element's Documentation/Description properties holds it
navigation_target

Returned by navigation_targets().

PropertyTypeExample
profilestringNavigation profile: "Responsive", "Phone", "Tablet", …
kindstring"home", "role_home" or "menu"
rolestringUser role, for a "role_home" target; "" otherwise
captionstringMenu item caption, for a "menu" target; "" otherwise
pagestringQualified name of the target page
xpath_expression

Returned by xpath_expressions(). Each row represents one XPath constraint used in a retrieve action, access rule, or widget data source.

PropertyTypeExample
idstringRow UUID
document_typestring"MICROFLOW", "NANOFLOW", "DOMAIN_MODEL", "PAGE", "SNIPPET"
document_idstringOwning document UUID
document_qualified_namestring"MyApp.GetActiveItems"
component_typestring"RETRIEVE_ACTION", "ACCESS_RULE", "WIDGET"
component_idstringComponent UUID
component_namestringActivity/rule name (may be empty)
xpath_expressionstringRaw XPath string, may include outer [ ]
target_entitystringQualified name of entity being queried, e.g. "MyApp.Order"
referenced_entitiesstringComma-separated qualified names of entities referenced by the XPath
is_parameterizedboolTrue when the XPath contains $variable references
usage_typestring"RETRIEVE", "SECURITY", "DATASOURCE"
module_namestring"MyApp"
Show full SKILL.md (1,414 more words)Show less
expr

Returned by parse_xpath(s). Every node has a kind field; additional fields depend on the kind.

kindAdditional fieldsDescription
"bin"op (string), left (expr), right (expr)Binary operator: =, !=, <, >, <=, >=, and, or
"unary"op (string), operand (expr)Unary operator: not, -
"call"name (string), args (list of expr)Function call, e.g. contains(…), length(…)
"string"value (string)String literal
"number"value (string)Numeric literal (kept as string to preserve precision)
"bool"value (bool)true or false
"empty"—Mendix empty keyword
"variable"name (string)$ParameterName
"attr_path"variable (string), path (list of string)$Obj/Association/Attribute
"qname"module (string), name (string), sub (string)Qualified name, e.g. MyApp.Status.Active
"paren"inner (expr)Parenthesised expression
"if"cond (expr), then (expr), else_ (expr)If-then-else expression
"constant"qname (string)Mendix constant reference, e.g. [%MyConst%]
"token"token (string), arg (string)Mendix token expression, e.g. [%CurrentUser%]
"recovered"source (string), reason (string)Parse failure — node carries the raw source fragment
"null"—Nil / missing node
"unknown"—Unrecognised AST node type

Walking an expr tree: check node.kind and recurse into child fields. Leaf kinds (no child nodes) are: string, number, bool, empty, variable, qname, constant, token, recovered, null, unknown.

Example — count not(…) calls in an XPath (using parse_xpath):

python
def count_not(node):
    if node.kind in ("null", "unknown", "recovered", "string", "number",
                     "bool", "empty", "variable", "qname", "constant", "token"):
        return 0
    if node.kind == "call" and node.name == "not":
        return 1 + sum([count_not(a) for a in node.args])
    if node.kind == "call":
        return sum([count_not(a) for a in node.args])
    if node.kind == "bin":
        return count_not(node.left) + count_not(node.right)
    if node.kind == "unary":
        return count_not(node.operand)
    if node.kind == "paren":
        return count_not(node.inner)
    if node.kind == "if":
        return count_not(node.cond) + count_not(node.then) + count_not(node.else_)
    if node.kind == "attr_path":
        return 0
    return 0
attribute
PropertyTypeExample
idstringAttribute UUID
namestring"Name"
entity_idstringParent entity UUID
entity_qualified_namestring"Sales.Customer"
module_namestring"Sales"
data_typestring"String", "Integer", "Long", "Decimal", "Boolean", "DateTime", "Date", "Enumeration", "AutoNumber", "Binary", "HashedString"
lengthintField length (for strings)
is_uniqueboolHas unique constraint
is_requiredboolIs required
default_valuestringDefault value
is_calculatedboolTrue if attribute is calculated (virtual)
descriptionstringDocumentation text
activity
PropertyTypeExample
idstringActivity UUID
namestringThe action_type for an action activity, otherwise the activity_type
captionstringThe stored caption: an activity's, a split's ("Is amount big?"), or an annotation's text. Empty for objects Mendix stores no caption for (start/end events, merges, loops). When auto_generate_caption is true this is Studio Pro's stored placeholder (typically "Activity"), not the caption Studio Pro shows
auto_generate_captionboolAction activity: whether Studio Pro generates the caption. False for other objects
descriptionstringThe documentation of an action activity, split or loop
activity_typestring"ActionActivity", "ExclusiveSplit", "ExclusiveMerge", "LoopedActivity", "InheritanceSplit", "StartEvent", "EndEvent", "Annotation"
action_typestringThe action inside an ActionActivity: "CreateObjectAction", "ChangeObjectAction", "CommitObjectsAction", "DeleteObjectAction", "RetrieveAction", "MicroflowCallAction", "ShowPageAction", "ClosePageAction", "LogMessageAction", "JavaActionCallAction", "RestCallAction", "WebServiceCallAction". Empty for an activity that is not an action
microflow_idstringParent microflow UUID
microflow_qualified_namestring"Sales.ACT_Customer_Create"
module_namestring"Sales"
entity_refstringEntity qualified name, for a create object and a database retrieve
service_refstringCalled service document (REST / web service / OData client); empty when the activity calls none
action_refstringOperation or action within that service; empty when the activity calls none
use_request_timeoutboolCall REST service or Call web service: whether "Use a timeout" is enabled. False for other action types
timeout_expressionstringCall REST service or Call web service: the timeout in seconds, stored as an expression, e.g. "300"
parent_loop_idstringid of the loop the activity is inside; empty at the top level. Only set with activities_for(…, nested = True)
loop_depthintNumber of loops around the activity: 0 at the top level, 1 directly inside a loop, 2 in a loop inside a loop
condition_expressionstringExclusive split: the condition expression, e.g. "$Order/Amount > 10". Empty for a rule-based split
condition_rulestringExclusive split calling a rule: the rule's qualified name, e.g. "Sales.IsValidOrder"
error_handling_typestringThe stored error handling of an action, loop or split: exactly "Rollback", "Custom", "CustomWithoutRollBack" (capital B), "Continue" or "Abort". Empty for objects without error handling
log_levelstringLog message: exactly "Trace", "Debug", "Info", "Warning", "Error" or "Critical"
log_node_expressionstringLog message: the log node as stored, an expression — "'MyNode'" (a quoted string literal) or "getKey(Sales.LogNodes.Orders)"
log_messagestringLog message: the message template, e.g. "Amount is {1}"
commit_typestringCreate or change object: exactly "Yes", "YesWithoutEvents" or "No". Empty for other actions
with_eventsboolTrue for a commit action with events, and for a create or change object with commit_type "Yes"
retrieve_sourcestringRetrieve: exactly "database" or "association". For "database", entity_ref is the retrieved entity
rest_client
PropertyTypeExample
idstringDocument UUID
namestring"CustomerApi"
qualified_namestring"Sales.CustomerApi"
module_namestring"Sales"
folderstringFolder path within module
base_urlstring"https://api.example.com/v1"
auth_schemestringAuthentication scheme, empty when none
operation_countintNumber of operations on the service
documentationstringDocumentation text
rest_operation
PropertyTypeExample
idstringOperation UUID
service_idstringOwning service UUID
service_qualified_namestring"Sales.CustomerApi"
namestring"GetCustomer"
http_methodstring"GET", "POST", …
pathstring"/customers/{id}"
parameter_countintNumber of parameters
has_bodyboolTrue when the request carries a body
response_typestringResponse type name
timeoutintConfigured timeout in milliseconds; 0 when none is set
module_namestring"Sales"
permission

Returned by permissions() (all types) or permissions_for() (entity-specific).

PropertyTypeExample
module_role_namestring"Admin"
element_typestring"ENTITY", "MICROFLOW", "PAGE", "ODATA_SERVICE" (from permissions() only)
element_namestring"Sales.Customer"
module_namestring"Sales"
entity_namestring"Sales.Customer" (from permissions_for() only)
access_typestring"CREATE", "READ", "WRITE", "DELETE" (entity), "EXECUTE" (microflow), "VIEW" (page), "ACCESS" (OData service), "MEMBER_READ", "MEMBER_WRITE"
member_namestringAttribute name (for MEMBER_READ/MEMBER_WRITE)
xpath_constraintstringXPath constraint or empty
is_constrainedboolTrue if XPath constraint is set
default_member_access_rightsstringThe rule's "default rights for new members": "None", "ReadOnly" or "ReadWrite". Empty for non-entity permissions
user_role
PropertyTypeExample
namestring"Administrator"
is_anonymousboolTrue if this is the anonymous/guest role
module_roleslist of string["Sales.Admin", "HR.Viewer"]
module_role
PropertyTypeExample
namestring"Sales.Admin" — qualified module role name
module_namestring"Sales"
descriptionstringModule role description
role_mapping
PropertyTypeExample
user_role_namestring"Administrator"
module_role_namestring"Sales.Admin"
module_namestring"Sales"
reference
PropertyTypeExample
source_typestringThe document the edge comes FROM, upper-case: "MICROFLOW", "NANOFLOW", "RULE", "PAGE", "SNIPPET", "ENTITY", "ASSOCIATION", "WORKFLOW", "NAVIGATION", "SCHEDULED_EVENT", "PUBLISHED_REST_OPERATION", "PROJECT_SETTINGS", "IMPORT_MAPPING", "EXPORT_MAPPING"
source_idstringSource UUID
source_namestring"Sales.ACT_Customer_Create"
target_typestringWhat it points AT, upper-case: "ENTITY", "ASSOCIATION", "MICROFLOW", "NANOFLOW", "RULE", "PAGE", "LAYOUT", "WORKFLOW", "WIDGET", "JAVA_ACTION", "REST_OPERATION", "REGULAR_EXPRESSION", "ATTRIBUTE", "ENUMERATION", "ENUMERATION_VALUE". LAYOUT, WIDGET, ATTRIBUTE, ENUMERATION and ENUMERATION_VALUE are only ever targets; SCHEDULED_EVENT and PROJECT_SETTINGS only ever sources
target_idstringTarget UUID
target_namestring"Sales.Customer"; three-part for an attribute or an enumeration value: "Sales.Order.Total", "Sales.OrderStatus.Open"
ref_kindstringHow it references: "call", "create", "retrieve", "change", "delete", "commit" (a commit action, or a create/change that commits — beside its "create"/"change" edge; a commit of a variable whose entity the flow cannot tell has no edge), "show_page", "datasource", "action", "layout", "parameter", "return", "generalize", "associate", "home_page", "login_page", "menu_item", "calculate", "schedule", "validate", "settings", "widget", "sync", "publish", "event", "member" (binds/reads/writes an attribute or navigates an association), "xpath" (an XPath constraint names it), "type" (typed as an enumeration), "value" (an expression names an enumeration value), "mapping" (an import/export mapping maps the entity) — lower-case, unlike the types above. Attribute names used only through a variable in a free-text expression ($Order/Total) have no edge
module_namestringSource module
project_security

Returned by project_security(). Returns none if no MPR reader is available.

PropertyTypeDescription
security_levelstring"CheckNothing" (Off), "CheckFormsAndMicroflows" (Prototype), "CheckEverything" (Production)
enable_demo_usersboolWhether demo users are enabled
enable_guest_accessboolWhether anonymous/guest access is enabled
check_securityboolWhether security checking is active
strict_modeboolStrict security mode
anonymous_user_rolestringName of the project's guest user role, the role anonymous users get. Read enable_guest_access too: the role name can stay set while guest access is off
admin_user_namestringName of the administrator account: "MxAdmin". Its password is deliberately not exposed
admin_user_rolestringThe administrator account's user role: "Administrator"
password_policystructNested password policy settings
password_policy (nested in project_security)
PropertyTypeDescription
min_lengthintMinimum password length
require_digitboolMust contain a digit
require_mixed_caseboolMust contain upper and lower case
require_symbolboolMust contain a symbol

Helper Functions

FunctionDescription
violation(message, location?, suggestion?)Create a violation to return
location(module, document_type, document_name, document_id?)Create a location for a violation
parse_xpath(s)Parse a raw XPath/expression string and return its AST as an expr struct tree. Outer [ ] are stripped automatically. Parse failures produce a recovered root node rather than raising.
is_pascal_case(s)Returns True if string is PascalCase
is_camel_case(s)Returns True if string is camelCase
matches(s, pattern)Returns True if string matches regex
get_option(key, default?)The rule's option key from the options: block under its rule ID in .claude/lint-config.yaml, or default (None if omitted) when unset
struct(**kwargs)Build an ad-hoc struct, e.g. struct(name="x", count=1), to group values inside a rule

Common Patterns

Pattern 1: Iterate entities and check a property
python
RULE_ID = "SEC001"
RULE_NAME = "NoEntityAccessRules"
description = "persistent entities should have access rules"
CATEGORY = "security"
SEVERITY = "warning"

def check():
    violations = []
    for e in entities():
        if e.entity_type == "Persistent" and not e.is_external and e.access_rule_count == 0:
            violations.append(violation(
                message="persistent entity '{}' has no access rules".format(e.qualified_name),
                location=location(module=e.module_name, document_type="entity", document_name=e.name),
                suggestion="grant <role> on {} (read *)".format(e.qualified_name),
            ))
    return violations
Pattern 2: Check project-level security settings
python
RULE_ID = "SEC002"
RULE_NAME = "WeakPasswordPolicy"
description = "password policy should require at least 8 characters"
CATEGORY = "security"
SEVERITY = "warning"

def check():
    sec = project_security()
    if sec == none:
        return []
    if sec.password_policy.min_length < 8:
        return [violation(
            message="password minimum length is {} (recommended: 8+)".format(sec.password_policy.min_length),
            location=location(module="", document_type="security", document_name="ProjectSecurity"),
            suggestion="alter app security password POLICY minimum length 8",
        )]
    return []
Pattern 3: Check cross-references
python
RULE_ID = "CUSTOM003"
RULE_NAME = "UnreferencedEntity"
description = "entities should be referenced by at least one microflow or page"
CATEGORY = "quality"
SEVERITY = "info"

def check():
    violations = []
    for e in entities():
        refs = refs_to(e.qualified_name)
        if len(refs) == 0:
            violations.append(violation(
                message="entity '{}' is not referenced anywhere".format(e.qualified_name),
                location=location(module=e.module_name, document_type="entity", document_name=e.name),
            ))
    return violations
Pattern 4: Check attributes of entities
python
RULE_ID = "CUSTOM004"
RULE_NAME = "RequiredStringLength"
description = "string attributes should have a length limit"
CATEGORY = "design"
SEVERITY = "warning"

def check():
    violations = []
    for e in entities():
        for attr in attributes_for(e.qualified_name):
            if attr.data_type == "string" and attr.length == 0:
                violations.append(violation(
                    message="string attribute '{}.{}' has unlimited length".format(e.name, attr.name),
                    location=location(module=e.module_name, document_type="entity", document_name=e.name),
                ))
    return violations

Validation

Test your rule by running the linter:

bash
mxcli lint -p app.mpr --list-rules   # Verify rule is loaded
mxcli lint -p app.mpr                 # run all rules including yours

If a .star file has syntax errors, a warning is printed and the rule is skipped.

© mendixlabs, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 1 other file in .claude/skills/mendix/write-lint-rules of mendixlabs/mxcli.

  • SKILL.md
  • catalog-tables.md

Open the folder on GitHubat commit 20a6c89

Compare with similar skills

Write Lint Rules next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Write Lint Rules compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Write Lint Rules this skillmendixlabs/mxcli128—~9.7kAutomated safety check: PassApache-2.0
Install Anti-Slop Oxlint Rulesdmmulroy/anti-slop5.3k1 repos~2.2kAutomated safety check: PassMIT
Summarise Ecosystem Resultsastral-sh/ruff50k1 repos~2.2kAutomated safety check: PassMIT
Minimizing Ty Ecosystem Changesastral-sh/ruff50k—~4.6kAutomated safety check: PassMIT
Babysit PR To Pass CIsgl-project/sglang37k2 repos~3kAutomated safety check: PassApache-2.0
Rust Best Practicesfarm-fe/farm5.6k3 repos~1.1kAutomated safety check: PassMIT

Similar skills

  • Installs, updates or migrates the vendored anti-slop Oxlint plugin in a repository, keeping local rule changes and the plugin's license and provenance files.

    5.3k GitHub starsUsed in 1 repo~2.2k tokens
    DevelopmentAuto-check passed
  • Official

    A skill your agent uses when a user says "summarise ecosystem results", "summarize this ty ecosystem report", "what changed in this ecosystem run?", or asks to summarise or summarize ty ecosystem…

    50k GitHub starsUsed in 1 repo~2.2k tokens
    DevelopmentAuto-check passed
  • Official

    A skill your agent uses when a user says "minimize this ty ecosystem change", "reproduce this ecosystem result", "investigate a primer difference", "investigate a mypyprimer difference"…

    50k GitHub stars~4.6k tokensUpdated today
    DevelopmentAuto-check passed
  • Babysit PR To Pass CI

    sgl-project/sglang

    Start and persistently pursue a goal to babysit an SGLang pull request until selected GitHub Actions workflows pass on the latest PR head.

    37k GitHub starsUsed in 2 repos~3k tokens
    DevelopmentAuto-check passed
  • Guide for writing idiomatic Rust code based on Apollo GraphQL's best practices handbook.

    5.6k GitHub starsUsed in 3 repos~1.1k tokens
    DevelopmentAuto-check passed
  • Go Pedantry

    chromedp/chromedp

    This skill should be used when the user is writing Go code and needs guidance on Go-specific pedantry: error wrapping with fmt.Errorf and %w, interface design (accept interfaces return structs)…

    13k GitHub stars~3.7k tokensUpdated 2 days ago
    DevelopmentAuto-check passed

More from mendixlabs/mxcli

All 72 skills in this repo
  • Mendix Odata Pushdown

    mendixlabs/mxcli

    Push OData query options into the SQL of a Mendix resource served by a read microflow, so $filter, $orderby, $top, $skip, $count and the key lookup reach the database instead of being silently…

    128 GitHub stars~2.5k tokensUpdated today
    Auto-check passed
  • Mendix Vega Charts

    mendixlabs/mxcli

    Chart a Mendix app with Vega-Lite through a pluggable widget that takes the specification and the data as separate properties, so the model emits rows and never assembles a chart payload.

    128 GitHub stars~3k tokensUpdated today
    Auto-check passed
  • Agents

    mendixlabs/mxcli

    Author Mendix AI agent documents in MDL — Model, Knowledge Base, Consumed MCP Service and Agent, with variables, tools and multi-line prompts.

    128 GitHub starsUsed in 1 repo~2.2k tokens
    Auto-check passed
  • Mendix Bulk Oql Dml

    mendixlabs/mxcli

    Run set-based INSERT, UPDATE and DELETE against Mendix entities through OQL statements, which the runtime supports and Studio Pro cannot author.

    128 GitHub stars~1.4k tokensUpdated today
    Auto-check passed
  • Business Events

    mendixlabs/mxcli

    Define event-driven APIs over Kafka with Mendix business event services — publish and subscribe contracts, CREATE/DROP/DESCRIBE.

    128 GitHub starsUsed in 1 repo~1.3k tokens
    Auto-check passed
  • Catalog Search

    mendixlabs/mxcli

    Search the Mendix Catalog platform service registry (catalog.mendix.com) from the CLI to find services published across an organisation.

    128 GitHub starsUsed in 1 repo~1.4k tokens
    Auto-check passed

Categories

Questions about Write Lint Rules

What does Write Lint Rules do?

Write custom Starlark lint rules in .claude/lint-rules/ that run beside the built-ins under mxcli lint. Write Lint Rules is an agent skill from mendixlabs/mxcli.claude/lint-rules/ that run beside the built-ins under mxcli lint.

When should I use Write Lint Rules?

Write Lint Rules fits situations like: A project convention should be enforced automatically; tasks that involve Linting and formatting.

How do I install Write Lint Rules in Claude Code?

Run `npx skills add mendixlabs/mxcli --skill write-lint-rules -a claude-code`. Or copy the skill folder (.claude/skills/mendix/write-lint-rules in mendixlabs/mxcli) into .claude/skills/write-lint-rules in your project. Claude Code loads it when a task matches its description.

How do I install Write Lint Rules in Codex?

Run `npx skills add mendixlabs/mxcli --skill write-lint-rules -a codex`. Or copy the skill folder (.claude/skills/mendix/write-lint-rules in mendixlabs/mxcli) into .agents/skills/write-lint-rules in your project. Codex loads it when a task matches its description.

Can I use Write Lint Rules in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add mendixlabs/mxcli --skill write-lint-rules -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/write-lint-rules, .gemini/skills/write-lint-rules, .github/skills/write-lint-rules and .opencode/skills/write-lint-rules in your project.

What does Write Lint Rules need to run?

SKILL.md names no scripts, command-line tools or credentials: Write Lint Rules is instructions for the agent only. Our summary lists: Python 3.

Does Write Lint Rules access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Write Lint Rules safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Write Lint Rules use?

Write Lint Rules is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Write Lint Rules use?

About 9.7k tokens (SKILL.md is roughly 39k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Write Lint Rules?

Skills that share tags, products or a category with Write Lint Rules: Install Anti-Slop Oxlint Rules (dmmulroy/anti-slop, 5.3k stars), Summarise Ecosystem Results (astral-sh/ruff, 50k stars), Minimizing Ty Ecosystem Changes (astral-sh/ruff, 50k stars) and Babysit PR To Pass CI (sgl-project/sglang, 37k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Write Lint Rules?

mendixlabs (a GitHub organization) maintains it in mendixlabs/mxcli, which has 128 GitHub stars. The repository holds 72 skills in this directory. The repository was last updated on October 7, 2026.

Source: mendixlabs/mxcli on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.