Install the "write-lint-rules" agent skill from https://github.com/mendixlabs/mxcli/tree/main/.claude/skills/mendix/write-lint-rules into .claude/skills/write-lint-rules/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "write-lint-rules", then confirm the skill loads.
Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
Type this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
skills CLI
$ npx skills add mendixlabs/mxcli --skill write-lint-rules -a codex
Project install goes to .agents/skills/; add -g for ~/.codex/skills/.
Install the "write-lint-rules" agent skill from https://github.com/mendixlabs/mxcli/tree/main/.claude/skills/mendix/write-lint-rules into .agents/skills/write-lint-rules/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "write-lint-rules", then confirm the skill loads.
Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
skills CLI
$ npx skills add mendixlabs/mxcli --skill write-lint-rules -a cursor
Project install goes to .agents/skills/; add -g for ~/.cursor/skills/.
Install the "write-lint-rules" agent skill from https://github.com/mendixlabs/mxcli/tree/main/.claude/skills/mendix/write-lint-rules into .cursor/skills/write-lint-rules/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "write-lint-rules", then confirm the skill loads.
Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
skills CLI
$ npx skills add mendixlabs/mxcli --skill write-lint-rules -a gemini-cli
Project install goes to .agents/skills/; add -g for ~/.gemini/skills/.
Install the "write-lint-rules" agent skill from https://github.com/mendixlabs/mxcli/tree/main/.claude/skills/mendix/write-lint-rules into .gemini/skills/write-lint-rules/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "write-lint-rules", then confirm the skill loads.
Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
Installs for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
skills CLI
$ npx skills add mendixlabs/mxcli --skill write-lint-rules -a github-copilot
Project install goes to .agents/skills/; add -g for ~/.copilot/skills/.
Install the "write-lint-rules" agent skill from https://github.com/mendixlabs/mxcli/tree/main/.claude/skills/mendix/write-lint-rules into .github/skills/write-lint-rules/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "write-lint-rules", then confirm the skill loads.
GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
skills CLI
$ npx skills add mendixlabs/mxcli --skill write-lint-rules -a opencode
OpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
Install the "write-lint-rules" agent skill from https://github.com/mendixlabs/mxcli/tree/main/.claude/skills/mendix/write-lint-rules into .opencode/skills/write-lint-rules/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "write-lint-rules", then confirm the skill loads.
OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
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.
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:
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
Function
Returns
Description
entities()
list of entity
All non-system entities
microflows()
list of microflow
All 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 page
All non-system pages
enumerations()
list of enumeration
All non-system enumerations
constants()
list of constant
All non-system constants
widgets()
list of widget
All non-system page and snippet widgets (full catalog — auto-detected)
snippets()
list of snippet
All non-system snippets
scheduled_events()
list of scheduled_event
All non-system scheduled events (requires MPR reader)
queues()
list of queue
All non-system task queues
java_actions()
list of java_action
All non-system, non-marketplace Java actions, each carrying its parameters
database_connections()
list of database_connection
All non-system external database connections
documents()
list of document
Every 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 documentable
Every 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_target
Every 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_client
Consumed REST service documents (excluding platform modules)
rest_operations()
list of rest_operation
Operations on consumed REST services, including their timeout
Activities 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 permission
All permissions across all element types (full catalog — auto-detected)
permissions_for(entity_qualified_name)
list of permission
Access rules for a specific entity (full catalog — auto-detected)
refs_to(target_name)
list of reference
Cross-references to a target (full catalog — auto-detected)
refs_from(source_name)
list of reference
Cross-references from a source (outbound) (full catalog — auto-detected)
user_roles()
list of user_role
User roles from project security
module_roles()
list of module_role
All module roles (deduplicated from role mappings)
All XPath constraint expressions in the catalog (access rules, retrieve actions, widgets) (full catalog — auto-detected)
modules()
list of module
The user's modules (not System, not Marketplace), with their domain model's documentation
associations()
list of association
All non-system associations, same-module and cross-module, with the delete behaviour of both ends
entity_event_handlers()
list of entity_event_handler
Every before/after event handler on a non-system entity: which moment, which event, which microflow
navigation_menu_items()
list of navigation_menu_item
Every navigation menu item of every profile, at every depth. Navigation belongs to the project, so no module filter applies
jar_dependencies()
list of jar_dependency
Maven dependencies declared by non-system, non-marketplace modules
strings(language = None)
list of catalog_string
User-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 layout
All non-system layouts
published_rest_operations()
list of published_rest_operation
Operations 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.
Function
Returns
Description
layer_of(asset)
int or None
Topological layer sequence number (no opinion on ordering)
community_of(asset)
struct{id, label} or None
The 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 None
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
Property
Type
Example
id
string
Document UUID
name
string
"Customer"
qualified_name
string
"Sales.Customer"
module_name
string
"Sales"
folder
string
"DomainModel" — folder path within module
entity_type
string
exactly "Persistent", "NonPersistent" or "View" — any other spelling matches nothing and the rule silently reports nothing
description
string
Documentation text
generalization
string
Parent entity qualified name
attribute_count
int
Number of attributes
access_rule_count
int
Number of access rules
validation_rule_count
int
Number of validation rules
has_event_handlers
bool
True if entity has event handlers
is_external
bool
True if entity is from an external service
has_created_date
bool
True if the entity stores createdDate (an audit member, not counted in attribute_count)
has_changed_date
bool
True if the entity stores changedDate
has_owner
bool
True if the entity stores owner
has_changed_by
bool
True if the entity stores changedBy
microflow
Property
Type
Example
id
string
Document UUID
name
string
"ACT_Customer_Create"
qualified_name
string
"Sales.ACT_Customer_Create"
module_name
string
"Sales"
folder
string
"microflows/Customer" — folder path within module
microflow_type
string
exactly "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"
description
string
Documentation text
return_type
string
Return type
parameter_count
int
Number of parameters
activity_count
int
Number 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_count
int
activity_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
complexity
int
McCabe cyclomatic complexity
document_noun
string
"microflow", "nanoflow" or "rule" — for mid-sentence use in a message
document_noun_title
string
"Microflow", "Nanoflow" or "Rule" — for document_type= and a message that opens with it
page
Property
Type
Example
id
string
Document UUID
name
string
"Customer_Overview"
qualified_name
string
"Sales.Customer_Overview"
module_name
string
"Sales"
folder
string
"pages/Customer" — folder path within module
title
string
Page title in the project's default language (else en_US, else the lowest-sorted non-empty language); "" when the page has none
url
string
Page URL
description
string
Documentation text
widget_count
int
Number of widgets (full catalog — auto-detected)
enumeration
Property
Type
Example
id
string
Document UUID
name
string
"OrderStatus"
qualified_name
string
"Sales.OrderStatus"
module_name
string
"Sales"
folder
string
"enumerations" — folder path within module
description
string
Documentation text
value_count
int
Number of enum values
constant
Property
Type
Example
id
string
Document UUID
name
string
"AppBaseUrl"
qualified_name
string
"MyModule.AppBaseUrl"
module_name
string
"MyModule"
folder
string
"constants" — folder path within module
description
string
Documentation text
default_value
string
"https://example.com"
exposed_to_client
bool
true 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
Property
Type
Example
id
string
Document UUID
name
string
"SNIPPET_CustomerCard"
qualified_name
string
"Sales.SNIPPET_CustomerCard"
module_name
string
"Sales"
folder
string
"snippets" — folder path within module
widget_count
int
Number of widgets (full catalog — auto-detected)
scheduled_event
Property
Type
Example
name
string
"SE_NightlyCleanup"
qualified_name
string
"MyModule.SE_NightlyCleanup"
module_name
string
"MyModule"
microflow_name
string
"MyModule.MF_NightlyCleanup" — resolved from catalog; raw UUID when catalog not built
interval_seconds
int
86400 — 0 for unrecognised interval type
repeat
string
Schedule variant: "Minute", "Hour", "Day", "Week", "MonthDate", "MonthWeekday", "YearDate" or "YearWeekday"; "" when the event has no schedule
on_overlap
string
"DelayNext" or "SkipNext" — what happens when a run is still going at the next start
time_zone
string
Time zone the schedule is evaluated in
enabled
bool
True if the event is active
queue
Property
Type
Example
name
string
"ImportQueue"
qualified_name
string
"Sales.ImportQueue"
module_name
string
"Sales"
parallelism
string
"3" — an expression, stored as a string; do not assume it parses as an integer
cluster_wide
bool
True if parallelism applies across the cluster rather than per node
java_action
Property
Type
Example
id
string
Document UUID
name
string
"JA_ParseJson"
qualified_name
string
"Sales.JA_ParseJson"
module_name
string
"Sales"
folder
string
Folder path within module
documentation
string
Documentation text
description
string
Same as documentation, so a rule sweeping mixed document kinds can read one field name
The action_type for an action activity, otherwise the activity_type
caption
string
The 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_caption
bool
Action activity: whether Studio Pro generates the caption. False for other objects
description
string
The documentation of an action activity, split or loop
The 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_id
string
Parent microflow UUID
microflow_qualified_name
string
"Sales.ACT_Customer_Create"
module_name
string
"Sales"
entity_ref
string
Entity qualified name, for a create object and a database retrieve
service_ref
string
Called service document (REST / web service / OData client); empty when the activity calls none
action_ref
string
Operation or action within that service; empty when the activity calls none
use_request_timeout
bool
Call REST service or Call web service: whether "Use a timeout" is enabled. False for other action types
timeout_expression
string
Call REST service or Call web service: the timeout in seconds, stored as an expression, e.g. "300"
parent_loop_id
string
id of the loop the activity is inside; empty at the top level. Only set with activities_for(…, nested = True)
loop_depth
int
Number of loops around the activity: 0 at the top level, 1 directly inside a loop, 2 in a loop inside a loop
condition_expression
string
Exclusive split: the condition expression, e.g. "$Order/Amount > 10". Empty for a rule-based split
condition_rule
string
Exclusive split calling a rule: the rule's qualified name, e.g. "Sales.IsValidOrder"
error_handling_type
string
The 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_level
string
Log message: exactly "Trace", "Debug", "Info", "Warning", "Error" or "Critical"
log_node_expression
string
Log message: the log node as stored, an expression — "'MyNode'" (a quoted string literal) or "getKey(Sales.LogNodes.Orders)"
log_message
string
Log message: the message template, e.g. "Amount is {1}"
commit_type
string
Create or change object: exactly "Yes", "YesWithoutEvents" or "No". Empty for other actions
with_events
bool
True for a commit action with events, and for a create or change object with commit_type"Yes"
retrieve_source
string
Retrieve: exactly "database" or "association". For "database", entity_ref is the retrieved entity
rest_client
Property
Type
Example
id
string
Document UUID
name
string
"CustomerApi"
qualified_name
string
"Sales.CustomerApi"
module_name
string
"Sales"
folder
string
Folder path within module
base_url
string
"https://api.example.com/v1"
auth_scheme
string
Authentication scheme, empty when none
operation_count
int
Number of operations on the service
documentation
string
Documentation text
rest_operation
Property
Type
Example
id
string
Operation UUID
service_id
string
Owning service UUID
service_qualified_name
string
"Sales.CustomerApi"
name
string
"GetCustomer"
http_method
string
"GET", "POST", …
path
string
"/customers/{id}"
parameter_count
int
Number of parameters
has_body
bool
True when the request carries a body
response_type
string
Response type name
timeout
int
Configured timeout in milliseconds; 0 when none is set
module_name
string
"Sales"
permission
Returned by permissions() (all types) or permissions_for() (entity-specific).
The rule's "default rights for new members": "None", "ReadOnly" or "ReadWrite". Empty for non-entity permissions
user_role
Property
Type
Example
name
string
"Administrator"
is_anonymous
bool
True if this is the anonymous/guest role
module_roles
list of string
["Sales.Admin", "HR.Viewer"]
module_role
Property
Type
Example
name
string
"Sales.Admin" — qualified module role name
module_name
string
"Sales"
description
string
Module role description
role_mapping
Property
Type
Example
user_role_name
string
"Administrator"
module_role_name
string
"Sales.Admin"
module_name
string
"Sales"
reference
Property
Type
Example
source_type
string
The 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_id
string
Source UUID
source_name
string
"Sales.ACT_Customer_Create"
target_type
string
What 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_id
string
Target UUID
target_name
string
"Sales.Customer"; three-part for an attribute or an enumeration value: "Sales.Order.Total", "Sales.OrderStatus.Open"
ref_kind
string
How 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_name
string
Source module
project_security
Returned by project_security(). Returns none if no MPR reader is available.
Name 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_name
string
Name of the administrator account: "MxAdmin". Its password is deliberately not exposed
admin_user_role
string
The administrator account's user role: "Administrator"
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.
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.
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.
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…
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"…
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)…
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…
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.
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.