Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
7 changes: 3 additions & 4 deletions .claude/lint-rules/conv003_page_naming_suffix.star
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@

RULE_ID = "CONV003"
RULE_NAME = "PageNamingSuffix"
DESCRIPTION = "Page names should end with a type suffix (_NewEdit, _View, _Overview, _Select, _Details, _Dashboard)"
VALID_SUFFIXES = ("_NewEdit", "_View", "_Overview", "_Select", "_Details", "_Dashboard", "_Edit", "_New")
DESCRIPTION = "Page names should end with a type suffix ({})".format(", ".join(VALID_SUFFIXES))
CATEGORY = "naming"
SEVERITY = "info"

VALID_SUFFIXES = ("_NewEdit", "_View", "_Overview", "_Select", "_Details", "_Dashboard")

def check():
violations = []

Expand All @@ -27,7 +26,7 @@ def check():

if not has_suffix:
violations.append(violation(
message="Page '{}' does not end with a recognized suffix (_NewEdit, _View, _Overview, _Select, _Details, _Dashboard)".format(name),
message="Page '{}' does not end with a recognized suffix ({})".format(name, ", ".join(VALID_SUFFIXES)),
location=location(
module=page.module_name,
document_type="Page",
Expand Down
10 changes: 5 additions & 5 deletions .claude/lint-rules/conv005_snippet_prefix.star
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
# CONV005: Snippet Prefix
#
# Snippet names should start with SNIPPET_ to distinguish them from pages
# Snippet names should start with SNIPPET_ or SNP_ to distinguish them from pages
# at a glance. Info severity - advisory only.

RULE_ID = "CONV005"
RULE_NAME = "SnippetPrefix"
DESCRIPTION = "Snippet names should start with SNIPPET_ prefix"
DESCRIPTION = "Snippet names should start with SNIPPET_ or SNP_ prefix"
CATEGORY = "naming"
SEVERITY = "info"

def check():
violations = []

for snippet in snippets():
if not snippet.name.startswith("SNIPPET_"):
if not (snippet.name.startswith("SNIPPET_") or snippet.name.startswith("SNP_")):
violations.append(violation(
message="Snippet '{}' does not start with SNIPPET_ prefix".format(snippet.name),
message="Snippet '{}' does not start with SNIPPET_ or SNP_ prefix".format(snippet.name),
location=location(
module=snippet.module_name,
document_type="Snippet",
document_name=snippet.qualified_name,
),
suggestion="Rename to 'SNIPPET_{}'".format(snippet.name),
suggestion="Rename to 'SNIPPET_{}' or 'SNP_{}'".format(snippet.name, snippet.name),
))

return violations
9 changes: 5 additions & 4 deletions .claude/lint-rules/conv009_max_microflow_objects.star
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,24 @@ DESCRIPTION = "Microflows should not exceed 15 activities (Mendix best practice)
CATEGORY = "quality"
SEVERITY = "info"

MAX_ACTIVITIES = 15
MAX_ACTIVITIES = 25
MIN_ACTIVITIES = 15

def check():
violations = []

for mf in microflows():
if mf.activity_count > MAX_ACTIVITIES:
if (mf.activity_count > MIN_ACTIVITIES and mf.activity_count <= MAX_ACTIVITIES):
violations.append(violation(
message="Microflow '{}' has {} activities (convention max: {}). Split into sub-microflows.".format(
mf.name, mf.activity_count, MAX_ACTIVITIES
mf.name, mf.activity_count, MIN_ACTIVITIES
),
location=location(
module=mf.module_name,
document_type="Microflow",
document_name=mf.qualified_name,
),
suggestion="Extract logical sections into SUB_ microflows to keep each under {} activities".format(MAX_ACTIVITIES),
suggestion="Extract logical sections into SUB_ microflows to keep each under {} activities".format(MIN_ACTIVITIES),
))

return violations
2 changes: 1 addition & 1 deletion .claude/lint-rules/conv010_act_microflow_content.star
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ RULE_ID = "CONV010"
RULE_NAME = "ACTMicroflowContent"
DESCRIPTION = "ACT_ microflows should only contain UI actions and sub-microflow calls"
CATEGORY = "architecture"
SEVERITY = "warning"
SEVERITY = "info"

# Allowed action types in ACT_ microflows
ALLOWED_ACTIONS = (
Expand Down
2 changes: 1 addition & 1 deletion .claude/lint-rules/conv017_no_calculated_attributes.star
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def check():
violations = []

for entity in entities():
if entity.is_external:
if entity.entity_type != "Persistent" or entity.is_external:
continue

for attr in attributes_for(entity.qualified_name):
Expand Down
59 changes: 59 additions & 0 deletions .claude/lint-rules/entity_attributes_info.star
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Starlark Lint Rule: Entity Attribute Count
#
# This rule checks that entities don't have too many attributes.
# Entities with many attributes often indicate:
# - Missing normalization (split into related entities)
# - God object anti-pattern
# - Need for entity inheritance/generalization
#
# Entity properties:
# .id - Document ID
# .name - Simple name (e.g., "Customer")
# .qualified_name - Full name (e.g., "MyModule.Customer")
# .module_name - Module name
# .entity_type - "Persistent", "NonPersistent", or "View"
# .description - Documentation
# .generalization - Parent entity (if any)
# .attribute_count - Number of attributes
# .access_rule_count - Number of access rules
# .is_external - True if entity is from an external service
#
# Full API reference: .claude/skills/mendix/write-lint-rules.md

RULE_ID = "DESIGN001"
RULE_NAME = "Entity Attribute Count"
DESCRIPTION = "Entities should not have more than 10 attributes"
CATEGORY = "design"
SEVERITY = "warning"

# Maximum allowed attributes per entity - customize as needed
MIN_ATTRIBUTES = 10
MAX_ATTRIBUTES = 20

def check():
"""
Check that entities don't have too many attributes.
Large entities are harder to maintain and may indicate design issues.
"""
violations = []

for entity in entities():
if (entity.attribute_count > MIN_ATTRIBUTES and entity.attribute_count <= MAX_ATTRIBUTES):
loc = location(
module=entity.module_name,
document_type="Entity",
document_name=entity.qualified_name
)
v = violation(
message="Entity '{}' has {} attributes (min {}, max: {}). Consider splitting into smaller entities.".format(
entity.name,
entity.attribute_count,
MIN_ATTRIBUTES,
MAX_ATTRIBUTES
),
location=loc,
suggestion="Extract related attributes into separate entities with associations, or use entity generalization."
)
violations.append(v)

return violations
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ CATEGORY = "design"
SEVERITY = "warning"

# Maximum allowed attributes per entity - customize as needed
MAX_ATTRIBUTES = 10
MAX_ATTRIBUTES = 20

def check():
"""
Expand Down
4 changes: 2 additions & 2 deletions .claude/lint-rules/entity_business_key.star
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ RULE_ID = "ARCH003"
RULE_NAME = "Entity Business Key"
DESCRIPTION = "Persistent entities should have a unique, not-null attribute as a business key"
CATEGORY = "architecture"
SEVERITY = "warning"
SEVERITY = "info"

# Attribute names that commonly indicate a business key
BUSINESS_KEY_PATTERNS = ["Code", "ExternalId", "ExternalID", "UUID", "Key", "Identifier", "Reference"]
BUSINESS_KEY_PATTERNS = ["Code", "ExternalId", "ExternalID", "UUID", "Key", "Identifier", "Reference", "_Id"]

def has_business_key(entity):
"""Check if entity has at least one attribute that is both unique and required."""
Expand Down
8 changes: 5 additions & 3 deletions .claude/lint-rules/example_microflow.star
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@
# .parameter_count - Number of parameters
# .activity_count - Number of activities


# Define valid prefixes - customize for your project
VALID_PREFIXES = ["ACT_", "SUB_", "DS_", "VAL_", "SCH_", "IVK_", "OCH_", "BCO_", "ACO_", "BCR_", "ACR_", "BDE_", "ADE_", "BRO_", "ARO_", "SCE_", "SE_", "DL_", "PWS_", "PRS_", "ASU_", "NAV_", "CRS_", "POST_", "GET_", "PATCH_", "PUT_", "DELETE_", "TEST_"]
RULE_ID = "CUSTOM002"
RULE_NAME = "Microflow Prefix Convention"
DESCRIPTION = "Microflows should have standard naming prefixes (ACT_, SUB_, DS_, VAL_, SCH_)"
DESCRIPTION = "Microflows should have standard naming prefixes ({})".format(", ".join(VALID_PREFIXES))
CATEGORY = "naming"
SEVERITY = "info"

# Define valid prefixes - customize for your project
VALID_PREFIXES = ["ACT_", "SUB_", "DS_", "VAL_", "SCH_", "IVK_", "OCH_"]


def check():
"""
Expand Down
63 changes: 63 additions & 0 deletions .claude/lint-rules/mccabe_complexity_info.star
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Starlark Lint Rule: McCabe Cyclomatic Complexity
#
# This rule checks that microflows don't exceed a McCabe complexity threshold.
# McCabe complexity measures the number of independent paths through code:
# - Base complexity is 1
# - Each decision point (IF, type check) adds 1
# - Each loop adds 1
# - Each error handler adds 1
#
# Thresholds:
# 1-10 : Simple, low risk
# 11-20 : Moderate complexity, moderate risk
# 21-50 : Complex, high risk
# 50+ : Untestable, very high risk
#
# Microflow properties:
# .id - Document ID
# .name - Simple name (e.g., "ProcessOrder")
# .qualified_name - Full name (e.g., "MyModule.ProcessOrder")
# .module_name - Module name
# .microflow_type - "MICROFLOW" or "NANOFLOW"
# .description - Documentation
# .return_type - Return type
# .parameter_count - Number of parameters
# .activity_count - Number of activities
# .complexity - McCabe cyclomatic complexity

RULE_ID = "QUAL001"
RULE_NAME = "McCabe Complexity"
DESCRIPTION = "Microflows should not exceed McCabe cyclomatic complexity of 10"
CATEGORY = "complexity"
SEVERITY = "info"

# Maximum allowed complexity - customize as needed
MIN_COMPLEXITY = 10
MAX_COMPLEXITY = 20

def check():
"""
Check that microflows don't exceed the McCabe complexity threshold.
High complexity indicates code that is hard to test and maintain.
"""
violations = []

for mf in microflows():
if (mf.complexity > MIN_COMPLEXITY and mf.complexity <= MAX_COMPLEXITY):
loc = location(
module=mf.module_name,
document_type="Microflow",
document_name=mf.qualified_name
)
v = violation(
message="Microflow '{}' has complexity {} (max: {}). Consider splitting into smaller microflows.".format(
mf.name,
mf.complexity,
MAX_COMPLEXITY
),
location=loc,
suggestion="Break down complex logic into sub-microflows. Extract decision branches into separate SUB_ microflows."
)
violations.append(v)

return violations
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@

RULE_ID = "QUAL001"
RULE_NAME = "McCabe Complexity"
DESCRIPTION = "Microflows should not exceed McCabe cyclomatic complexity of 10"
DESCRIPTION = "Microflows should not exceed McCabe cyclomatic complexity of 20"
CATEGORY = "complexity"
SEVERITY = "warning"

# Maximum allowed complexity - customize as needed
MAX_COMPLEXITY = 10
MAX_COMPLEXITY = 20

def check():
"""
Expand Down
7 changes: 5 additions & 2 deletions .claude/lint-rules/missing_documentation.star
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ def check():

# Check entities
for entity in entities():
if entity.entity_type != "Persistent" or entity.is_external:
continue

if not entity.description or entity.description.strip() == "":
loc = location(
module=entity.module_name,
Expand All @@ -46,8 +49,8 @@ def check():
if mf.microflow_type != "MICROFLOW":
continue

# Skip very simple microflows (1-2 activities)
if mf.activity_count <= 2:
# Skip simple microflows (1-4 activities)
if mf.activity_count <= 4:
continue

if not mf.description or mf.description.strip() == "":
Expand Down
2 changes: 1 addition & 1 deletion .claude/lint-rules/sec_strict_mode.star
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ RULE_ID = "SEC005"
RULE_NAME = "StrictModeDisabled"
DESCRIPTION = "Strict security mode is disabled - enables additional XPath constraint enforcement"
CATEGORY = "security"
SEVERITY = "warning"
SEVERITY = "info"

def check():
sec = project_security()
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,6 @@ grammardoc
snap-bson
.playwright-cli/
.claude/worktrees/

# temp implementation helper
implementation-helpers/
4 changes: 2 additions & 2 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
package api

import (
"github.com/mendixlabs/mxcli/model"
"github.com/mendixlabs/mxcli/sdk/mpr"
"github.com/JordtenBulte-OLC/mxcli/model"
"github.com/JordtenBulte-OLC/mxcli/sdk/mpr"
)

// ModelAPI is the main entry point for the high-level API.
Expand Down
2 changes: 1 addition & 1 deletion api/api_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"path/filepath"
"testing"

"github.com/mendixlabs/mxcli/sdk/mpr"
"github.com/JordtenBulte-OLC/mxcli/sdk/mpr"
)

// sourceProject is the pristine source project directory.
Expand Down
2 changes: 1 addition & 1 deletion api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package api
import (
"testing"

"github.com/mendixlabs/mxcli/model"
"github.com/JordtenBulte-OLC/mxcli/model"
)

func TestParseQualifiedName(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions api/domainmodels.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ package api
import (
"fmt"

"github.com/mendixlabs/mxcli/model"
"github.com/mendixlabs/mxcli/sdk/domainmodel"
"github.com/JordtenBulte-OLC/mxcli/model"
"github.com/JordtenBulte-OLC/mxcli/sdk/domainmodel"
)

// DomainModelsAPI provides methods for working with domain models.
Expand Down
2 changes: 1 addition & 1 deletion api/enumerations.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"fmt"
"slices"

"github.com/mendixlabs/mxcli/model"
"github.com/JordtenBulte-OLC/mxcli/model"
)

// EnumerationsAPI provides methods for working with enumerations.
Expand Down
4 changes: 2 additions & 2 deletions api/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ package api
import (
"strings"

"github.com/mendixlabs/mxcli/model"
"github.com/mendixlabs/mxcli/sdk/mpr"
"github.com/JordtenBulte-OLC/mxcli/model"
"github.com/JordtenBulte-OLC/mxcli/sdk/mpr"
)

// generateID creates a new unique ID for a model element.
Expand Down
4 changes: 2 additions & 2 deletions api/microflows.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ package api
import (
"fmt"

"github.com/mendixlabs/mxcli/model"
"github.com/mendixlabs/mxcli/sdk/microflows"
"github.com/JordtenBulte-OLC/mxcli/model"
"github.com/JordtenBulte-OLC/mxcli/sdk/microflows"
)

// MicroflowsAPI provides methods for working with microflows.
Expand Down
Loading
Loading