Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b987327
fix(run-local): verify the web client bundle after the boot, not befo…
claude Aug 8, 2026
4551a4e
fix(describe): put back the page-parameter mapping mxcli stores impli…
claude Aug 8, 2026
28ce821
fix(odata): stop deleting a published service's role grants on modify
claude Aug 8, 2026
01ef224
fix(init): keep .ai-context/skills/ in step with the binary that serv…
claude Aug 8, 2026
054f780
fix(odata): publish the query-option opt-out instead of always claimi…
claude Aug 9, 2026
18795da
feat(check): flag a read microflow that cannot keep its resource's pr…
claude Aug 9, 2026
6aa7c33
feat(log): drive the runtime's per-node log levels
claude Aug 9, 2026
5b62444
docs(log): correct the published-OData node — it exists, per app
claude Aug 9, 2026
ff97412
fix(pages): qualify an association datasource's name, which wrote an …
claude Aug 9, 2026
f1fa02b
fix(screenshot): declare the real scheme so an https-root app can be …
claude Aug 9, 2026
cd2ac98
fix(visitor): keep MDL comments out of the Mendix expressions they si…
claude Aug 9, 2026
416b4ee
fix(check): stop recommending a remedy Mendix rejects (CE6585)
claude Aug 9, 2026
6195c52
Merge pull request #119 from ako/claude/mxcli-issues-ovfoxk
ako Aug 9, 2026
11211da
Merge branch 'main' into claude/mxbuild-diagnostics-spike-emta6h
ako Aug 9, 2026
b4a825e
Merge pull request #120 from ako/claude/mxbuild-diagnostics-spike-emta6h
ako Aug 9, 2026
1ab84da
docs(check): correct a test comment that outlived the CE6585 finding
claude Aug 9, 2026
10ba2e1
feat(navigation): MENU ITEM ... ICON, against a Studio Pro reference
claude Aug 9, 2026
9364d43
refactor(navigation): ICON is a qualified name, not a string
claude Aug 9, 2026
dc780ec
fix(odata): the modelsdk writer never wrote AllowedModuleRoles
claude Aug 9, 2026
109a55c
feat(odata): name the custom-authentication microflow from MDL
claude Aug 9, 2026
414a209
Merge pull request #121 from ako/claude/mxbuild-diagnostics-spike-emta6h
ako Aug 9, 2026
ededab1
feat(odata): publish a microflow as an OData action
claude Aug 9, 2026
3509f2a
feat(odata): DESCRIBE round-trips a published microflow
claude Aug 9, 2026
27ea1da
fix(odata): generated external entities follow the contract's $top/$skip
claude Aug 9, 2026
70a169b
fix(odata): MDL-ODATA03 checks that paging is used, not that it could be
claude Aug 9, 2026
715bac5
Merge pull request #122 from ako/claude/mxbuild-diagnostics-spike-emta6h
ako Aug 9, 2026
4c78200
fix(run): name the process holding the port instead of guessing
claude Aug 9, 2026
66c938b
fix(odata): honour the whole-set form of Filter/Sort restrictions
claude Aug 9, 2026
5a7780f
feat(lint): QUAL002 covers Java actions and their parameters
claude Aug 9, 2026
fbb1609
feat(lint): QUAL002 sweeps every document type; stop reporting System
claude Aug 9, 2026
4ccf8e5
fix(lint): exclude the System module from every rule, not just QUAL002
claude Aug 9, 2026
7a37cde
fix(lint): add modules.Id to the rules-package test doubles
claude Aug 9, 2026
c9245e8
fix(lint): report failed catalog queries instead of swallowing them
claude Aug 9, 2026
b872086
Merge pull request #123 from ako/claude/mxbuild-diagnostics-spike-emta6h
ako Aug 9, 2026
7a1bc0f
fix(odata): stop create-external-entities duplicating suffixed associ…
claude Aug 9, 2026
38a1137
Merge pull request #124 from ako/claude/mxbuild-diagnostics-spike-emta6h
ako Aug 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
275 changes: 229 additions & 46 deletions .claude/lint-rules/missing_documentation.star
Original file line number Diff line number Diff line change
@@ -1,66 +1,249 @@
# Starlark Lint Rule: Missing Documentation
#
# This rule checks that entities and microflows have documentation.
# Good documentation helps with maintainability and onboarding new developers.
# Undocumented model elements are invisible to `mxcli check` and to the build —
# nothing fails, so nothing reminds you. This rule is the reminder, and it
# covers every document type a user authors, not just the domain model.
#
# Checks:
# - Entities should have a description explaining their purpose
# - Microflows should have a description explaining what they do
# Documents swept generically (one option each, all default True):
# Module, Entity, Page, Snippet, BuildingBlock, Layout, Enumeration,
# JavaScriptAction, ImageCollection, DataTransformer, Workflow,
# BusinessEventService, RestClient, PublishedRestService, Constant,
# JsonStructure, ImportMapping, ExportMapping
#
# Entity properties:
# .description - Documentation text
# Handled separately, because they carry exemptions or children a uniform
# sweep cannot express:
# Microflow .description (nanoflows and trivial flows exempt)
# JavaAction .documentation
# JavaAction params .description <- the one Studio Pro shows a CALLER
#
# Microflow properties:
# .description - Documentation text
# Members, off by default purely because of volume:
# Attribute .description
# Association .description
#
# Why Java action parameters default ON while attributes default OFF: an action
# has a handful of parameters and Studio Pro renders each description in the
# dialog where someone wires up the call — an undocumented parameter is a blank
# field next to a name like `pInput` at exactly the moment a caller has to
# decide what to pass. A domain model has hundreds of attributes and
# associations, so the same check there is a wall of text rather than a signal.
#
# Every kind is individually switchable; see the table in _DOC_KINDS and the
# options listed under docs-site/src/tools/starlark-rules.md.

RULE_ID = "QUAL002"
RULE_NAME = "Missing Documentation"
DESCRIPTION = "Entities and microflows should have documentation describing their purpose"
DESCRIPTION = "Model elements should have documentation describing their purpose"
CATEGORY = "quality"
SEVERITY = "info"

# kind (as emitted by documentable_elements) -> (option, noun, suggestion)
#
# A new Mendix document type is covered by adding a row in Go's
# documentableSources and a row here — not by writing another loop.
_DOC_KINDS = {
"Module": (
"check_modules",
"Module",
"Document what the module is for: it is the first thing a newcomer opens.",
),
"Entity": (
"check_entities",
"Entity",
"Add a description explaining the entity's purpose and what data it represents.",
),
"Page": (
"check_pages",
"Page",
"Describe what the page shows and who reaches it.",
),
"Snippet": (
"check_snippets",
"Snippet",
"Describe what the snippet renders and what context it expects, since it is reused across pages.",
),
"BuildingBlock": (
"check_building_blocks",
"Building block",
"Describe what the building block is for: it exists to be dropped in by someone who did not write it.",
),
"Layout": (
"check_layouts",
"Layout",
"Describe the layout's intended use and its placeholders.",
),
"Enumeration": (
"check_enumerations",
"Enumeration",
"Describe what the enumeration models, especially where the values map to something external.",
),
"JavaScriptAction": (
"check_javascript_actions",
"JavaScript action",
"Document what the action does and what it returns. Like a Java action, its body is code the model cannot show a reader.",
),
"ImageCollection": (
"check_image_collections",
"Image collection",
"Describe what the collection is for and where its images are used.",
),
"DataTransformer": (
"check_data_transformers",
"Data transformer",
"Describe the transformation applied and the shape it expects.",
),
"Workflow": (
"check_workflows",
"Workflow",
"Describe the process the workflow models and who its user tasks are for.",
),
"BusinessEventService": (
"check_business_event_services",
"Business event service",
"Document the events published or consumed, since other applications depend on them.",
),
"RestClient": (
"check_rest_clients",
"REST client",
"Document which external service is consumed and what it is used for.",
),
"PublishedRestService": (
"check_published_rest_services",
"Published REST service",
"Document the contract: this is the description external consumers read.",
),
"Constant": (
"check_constants",
"Constant",
"Describe what the constant configures and what a valid value looks like — it is set per environment by someone who cannot see the code.",
),
"JsonStructure": (
"check_json_structures",
"JSON structure",
"Note which payload the structure was captured from.",
),
"ImportMapping": (
"check_import_mappings",
"Import mapping",
"Describe the source payload and what it maps onto.",
),
"ExportMapping": (
"check_export_mappings",
"Export mapping",
"Describe the target payload and what it is produced for.",
),
"Association": (
# Off by default with attributes: a real domain model has as many
# associations as entities, and none of them are documented.
"check_associations",
"Association",
"Add a description, or switch this off with `check_associations: false` if the names are self-describing here.",
),
}

# Kinds whose option defaults to False. Everything else defaults to True.
_OFF_BY_DEFAULT = {"check_associations": True}

def _blank(text):
"""True when a documentation field is absent or whitespace-only."""
return not text or text.strip() == ""

def _flag(violations, module, doc_type, doc_name, message, suggestion):
violations.append(violation(
message = message,
location = location(
module = module,
document_type = doc_type,
document_name = doc_name,
),
suggestion = suggestion,
))

def check():
"""
Check that entities and microflows have documentation.
"""
violations = []

# Check entities
for entity in entities():
if not entity.description or entity.description.strip() == "":
loc = location(
module=entity.module_name,
document_type="Entity",
document_name=entity.qualified_name
)
v = violation(
message="Entity '{}' has no documentation.".format(entity.name),
location=loc,
suggestion="Add a description explaining the entity's purpose and what data it represents."
# ---- every document type, one sweep -------------------------------------
for el in documentable_elements():
entry = _DOC_KINDS.get(el.kind)
if entry == None:
# A kind Go knows about but this table does not. Staying silent is
# right: a rule inventing a message for an element it cannot
# describe is worse than not reporting it.
continue
option, noun, suggestion = entry
if not get_option(option, not _OFF_BY_DEFAULT.get(option, False)):
continue
if _blank(el.description):
_flag(
violations,
el.module_name,
el.kind,
el.qualified_name,
"{} '{}' has no documentation.".format(noun, el.name),
suggestion,
)
violations.append(v)

# Check microflows (skip nanoflows as they're often simple)
for mf in microflows():
# Only check microflows, not nanoflows
if mf.microflow_type != "MICROFLOW":
continue
# ---- microflows: exempt nanoflows and trivial flows ---------------------
if get_option("check_microflows", True):
# Nanoflows are excluded: they are usually a couple of client-side steps
# whose name says everything a description would.
min_activities = get_option("min_activities", 3)
for mf in microflows():
if mf.microflow_type != "MICROFLOW":
continue
if mf.activity_count < min_activities:
continue
if _blank(mf.description):
_flag(
violations,
mf.module_name,
"Microflow",
mf.qualified_name,
"Microflow '{}' has no documentation.".format(mf.name),
"Add a description explaining what this microflow does and when it should be called.",
)

# Skip very simple microflows (1-2 activities)
if mf.activity_count <= 2:
continue
# ---- Java actions and their parameters ----------------------------------
check_actions = get_option("check_java_actions", True)
check_params = get_option("check_java_action_params", True)
if check_actions or check_params:
for ja in java_actions():
if check_actions and _blank(ja.documentation):
_flag(
violations,
ja.module_name,
"JavaAction",
ja.qualified_name,
"Java action '{}' has no documentation.".format(ja.name),
"Add documentation explaining what the action does, and what it returns. " +
"Unlike a microflow, its body is Java that the model cannot show a reader.",
)
if not check_params:
continue
for p in ja.parameters:
if _blank(p.description):
_flag(
violations,
ja.module_name,
"JavaAction",
ja.qualified_name,
"Java action parameter '{}.{}' has no description.".format(ja.name, p.name),
"Add a description: Studio Pro shows it to whoever wires up the call, " +
"where the parameter name is all they otherwise have to go on.",
)

if not mf.description or mf.description.strip() == "":
loc = location(
module=mf.module_name,
document_type="Microflow",
document_name=mf.qualified_name
)
v = violation(
message="Microflow '{}' has no documentation.".format(mf.name),
location=loc,
suggestion="Add a description explaining what this microflow does and when it should be called."
)
violations.append(v)
# ---- entity attributes (off by default: high volume) --------------------
if get_option("check_attributes", False):
for entity in entities():
for attr in attributes_for(entity.qualified_name):
if _blank(attr.description):
_flag(
violations,
entity.module_name,
"Entity",
entity.qualified_name,
"Attribute '{}.{}' has no documentation.".format(entity.name, attr.name),
"Add a description, or switch this off with `check_attributes: false` if " +
"attribute names are self-describing in this project.",
)

return violations
Loading
Loading