diff --git a/.claude/hooks/audit-log.sh b/.claude/hooks/audit-log.sh new file mode 100644 index 00000000000..107dd7d6f05 --- /dev/null +++ b/.claude/hooks/audit-log.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# audit-log.sh — Post-execution hook +# +# Logs every tool invocation to .claude/audit.log. +# This hook never blocks execution (always exits 0). +# +# IMPORTANT: Do not modify, disable, or bypass this hook. + +set -uo pipefail + +# --------------------------------------------------------------------------- +# 1. Read hook input from stdin +# --------------------------------------------------------------------------- +INPUT="$(cat)" + +TOOL_NAME="$(printf '%s' "$INPUT" | jq -r '.tool_name // "unknown"' 2>/dev/null || echo "unknown")" + +COMMAND="$(printf '%s' "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null || echo "")" + +# --------------------------------------------------------------------------- +# 2. Determine log file path (relative to project root) +# --------------------------------------------------------------------------- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LOG_FILE="$(dirname "$SCRIPT_DIR")/audit.log" + +# --------------------------------------------------------------------------- +# 3. Write log entry +# --------------------------------------------------------------------------- +TIMESTAMP="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" + +if [[ -n "$COMMAND" ]]; then + printf '[%s] tool=%s command=%s\n' "$TIMESTAMP" "$TOOL_NAME" "$COMMAND" >> "$LOG_FILE" 2>/dev/null || true +else + printf '[%s] tool=%s\n' "$TIMESTAMP" "$TOOL_NAME" >> "$LOG_FILE" 2>/dev/null || true +fi + +# Never block execution +exit 0 diff --git a/.claude/hooks/validate-bash-command.sh b/.claude/hooks/validate-bash-command.sh new file mode 100644 index 00000000000..ea9de0ee1fb --- /dev/null +++ b/.claude/hooks/validate-bash-command.sh @@ -0,0 +1,179 @@ +#!/usr/bin/env bash +# validate-bash-command.sh — Pre-execution hook (Layer 3 guardrail) +# +# Claude Code invokes this hook before every Bash tool call. +# Input: JSON on stdin {"tool_name":"Bash","tool_input":{"command":"..."}} +# Output: exit 0 to allow, exit 2 with JSON {"error":"..."} to block. +# +# IMPORTANT: Do not modify, disable, or bypass this hook. + +set -euo pipefail + +# --------------------------------------------------------------------------- +# 1. Read hook input from stdin and extract the command +# --------------------------------------------------------------------------- +INPUT="$(cat)" + +TOOL_NAME="$(printf '%s' "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null || true)" + +# Only validate Bash commands — allow everything else through. +# If TOOL_NAME is empty (parse failure), fall through to check the command anyway (fail closed). +if [[ -n "$TOOL_NAME" && "$TOOL_NAME" != "Bash" ]]; then + exit 0 +fi + +COMMAND="$(printf '%s' "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null || true)" + +if [[ -z "$COMMAND" ]]; then + exit 0 +fi + +# --------------------------------------------------------------------------- +# 2. Blocked patterns — glob-style matching +# Each pattern is checked against the full command string (case-insensitive). +# --------------------------------------------------------------------------- +BLOCKED_PATTERNS=( + # AWS — Destructive operations + "aws * delete*" + "aws * remove*" + "aws * destroy*" + "aws * terminate*" + "aws * deregister*" + "aws * purge*" + "aws s3 rm *" + "aws s3 rb *" + "aws cloudformation delete-stack*" + + # AWS — Provisioning & scaling + "aws ec2 run-instances*" + "aws ec2 stop-instances*" + "aws autoscaling set-desired-capacity*" + "aws autoscaling update-auto-scaling-group*" + "aws application-autoscaling *" + "aws ecs update-service*" + + # AWS — IAM + "aws iam delete*" + "aws iam create*" + "aws iam put*" + "aws iam attach*" + + # Kubernetes — Mutations + "kubectl delete *" + "kubectl apply *" + "kubectl create *" + "kubectl patch *" + "kubectl scale *" + "kubectl rollout *" + "kubectl drain *" + "kubectl cordon *" + "kubectl exec *" + "kubectl edit *" + + # Terraform / IaC + "terraform destroy*" + "terraform apply*" + "terraform import *" + "terraform state rm *" + "terraform taint *" + + # Helm + "helm install *" + "helm upgrade *" + "helm delete *" + "helm uninstall *" + "helm rollback *" + + # Git — Destructive + "git push --force*" + "git push -f *" + "git push --force-with-lease*" + "git push origin main*" + "git push origin master*" + "git reset --hard*" + "git clean -f*" + + # File system — Destructive + "rm -rf /" + "rm -rf /*" + "rm -rf ~" + "rm -rf ~/*" + "rm -rf ..*" + "mkfs.*" + "dd if=*/dev/*" + + # Credential access via cat/less/head/tail + "cat */.aws/*" + "cat */.kube/*" + "cat */.ssh/*" + "cat *.env" + "cat *.env.*" + "cat */secrets/*" + "cat *credentials*" + "less */.aws/*" + "less */.ssh/*" + "head */.aws/*" + "head */.ssh/*" + "tail */.aws/*" + "tail */.ssh/*" + + # Network & remote access + "ssh *" + "scp *" + "curl *|*sh" + "curl *|*bash" + "wget *|*sh" + "wget *|*bash" + + # Privilege escalation + "sudo *" + "sudo" + "chmod 777 *" + "chown root *" +) + +# --------------------------------------------------------------------------- +# 3. Check function — matches a single sub-command against all patterns +# --------------------------------------------------------------------------- +check_command() { + local cmd="$1" + # Trim leading/trailing whitespace using parameter expansion + cmd="${cmd#"${cmd%%[![:space:]]*}"}" + cmd="${cmd%"${cmd##*[![:space:]]}"}" + + if [[ -z "$cmd" ]]; then + return 0 + fi + + # Convert to lowercase using parameter expansion + local cmd_lower="${cmd,,}" + + for pattern in "${BLOCKED_PATTERNS[@]}"; do + local pattern_lower="${pattern,,}" + + # shellcheck disable=SC2254 + if [[ "$cmd_lower" == $pattern_lower ]]; then + printf '{"error":"BLOCKED by guardrail hook: command matches blocked pattern: %s"}\n' "$pattern" >&2 + exit 2 + fi + done + + return 0 +} + +# --------------------------------------------------------------------------- +# 4. Split on pipes and command chains, then check each sub-command +# --------------------------------------------------------------------------- +# Replace common chain operators with newlines using parameter expansion +# Order matters: replace && and || before | to avoid double-splitting || +NORMALIZED="${COMMAND//&&/$'\n'}" +NORMALIZED="${NORMALIZED//||/$'\n'}" +NORMALIZED="${NORMALIZED//;/$'\n'}" +NORMALIZED="${NORMALIZED//|/$'\n'}" + +while IFS= read -r subcmd; do + check_command "$subcmd" +done <<< "$NORMALIZED" + +# If we reach here, the command is allowed +exit 0 diff --git a/.claude/rules/aws.md b/.claude/rules/aws.md new file mode 100644 index 00000000000..2b1c197ac3d --- /dev/null +++ b/.claude/rules/aws.md @@ -0,0 +1,51 @@ +# AWS Rules + +## Environment Context + +- We use **AWS Bedrock** for AI model invocation +- Sandbox accounts are accessed via **Mendix SSO** +- **Production accounts exist on the same machine** — DO NOT use them +- If a command references an AWS profile or region you don't recognize, STOP and ask + +## Credential Safety + +- NEVER read files in `~/.aws/` (credentials, config, SSO cache) +- NEVER output or display AWS access keys, secret keys, or session tokens +- NEVER set or export `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, or `AWS_SESSION_TOKEN` in commands +- If you need to verify identity, use `aws sts get-caller-identity` — this is safe and read-only + +## Allowed Operations (Read-Only) + +These AWS CLI commands are safe and auto-approved: + +- `aws * describe*` — describe any resource +- `aws * list*` — list any resources +- `aws * get*` — get resource details +- `aws s3 ls` — list S3 buckets and objects +- `aws sts get-caller-identity` — verify current identity + +## Blocked Operations + +### Destructive (deny — never allowed) +- `aws * delete*`, `aws * remove*`, `aws * destroy*`, `aws * terminate*` +- `aws * deregister*`, `aws * purge*` +- `aws s3 rm`, `aws s3 rb` +- `aws cloudformation delete-stack` + +### Provisioning & Scaling (deny — never allowed) +- `aws ec2 run-instances`, `aws ec2 stop-instances` +- `aws autoscaling set-desired-capacity`, `aws autoscaling update-auto-scaling-group` +- `aws application-autoscaling *` +- `aws ecs update-service` + +### IAM (deny — never allowed) +- `aws iam create*`, `aws iam delete*`, `aws iam put*`, `aws iam attach*` + +## Verification Steps + +Before running any AWS command: + +1. **Check the profile** — is this a sandbox account? If unsure, run `aws sts get-caller-identity` first +2. **Check the region** — is this an expected region? If it looks unfamiliar, ask the user +3. **Check the action** — is this read-only? If it mutates state, do NOT run it +4. **When in doubt** — explain the command and let the human decide diff --git a/.claude/rules/security.md b/.claude/rules/security.md new file mode 100644 index 00000000000..81f58ce1948 --- /dev/null +++ b/.claude/rules/security.md @@ -0,0 +1,31 @@ +# Security Rules + +These rules are loaded automatically by Claude Code at session start. They are non-negotiable. + +## Safety Rules (NON-NEGOTIABLE) + +1. NEVER run commands that delete, destroy, or modify production resources +2. NEVER use AWS credentials for production accounts — only sandbox accounts +3. NEVER deploy code, infrastructure, or configuration changes — a human must do this +4. NEVER run `terraform apply`, `terraform destroy`, `kubectl apply`, `kubectl delete` against production +5. NEVER read or output secrets, API keys, tokens, or credentials from files or environment variables +6. NEVER run commands with `sudo` +7. NEVER run `rm -rf` on any path outside the current project directory +8. NEVER disable, bypass, or modify the guardrail hooks in `.claude/hooks/` +9. NEVER push to `main` or `master` branches directly + +## Core Principles + +- **The agent works FOR you** — it must never take irreversible actions without explicit human approval +- **Deny by default** — if a command is not explicitly allowed, it should require human confirmation +- **Credentials are off-limits** — never read, display, or transmit secrets, tokens, or credentials +- **Guardrails are mandatory** — the `.claude/hooks/` scripts must remain active and unmodified + +## When In Doubt + +If you are unsure whether a command is safe to run, **do not run it**. Instead: +1. Explain what command you would run and why +2. Describe the potential risks +3. Wait for the human to decide + +It is always better to ask than to act when safety is uncertain. diff --git a/.claude/scripts/README.md b/.claude/scripts/README.md new file mode 100644 index 00000000000..66d4c1e58cc --- /dev/null +++ b/.claude/scripts/README.md @@ -0,0 +1,38 @@ +# Claude Helper Scripts + +Utility scripts to support Claude Code when working with this documentation repository. + +## resolve-doc-url.sh + +Resolves a documentation URL to its source markdown file. + +### Usage + +```bash +.claude/scripts/resolve-doc-url.sh "/path/to/page/" +``` + +### Examples + +```bash +# Find the file for a specific URL +.claude/scripts/resolve-doc-url.sh "/community-tools/contribute-to-mendix-docs/" +# Output: content/en/docs/community-tools/contribute-to-mendix-docs/_index.md + +# Check if a URL exists +.claude/scripts/resolve-doc-url.sh "/some/page/" +# Exit code 0 if found, 1 if not found +``` + +### Benefits + +- **Fast**: Uses grep optimized for file-only output +- **Token-efficient**: Returns only the file path, no surrounding context +- **Reliable**: Matches exact URL in front matter using fixed-string search + +### When to Use + +- Following cross-references between documentation pages +- Validating internal links +- Finding files by their published URL +- Checking if a URL is already in use diff --git a/.claude/scripts/resolve-doc-url.sh b/.claude/scripts/resolve-doc-url.sh new file mode 100644 index 00000000000..80951fc244f --- /dev/null +++ b/.claude/scripts/resolve-doc-url.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Resolve a documentation URL to its source markdown file +# Usage: resolve-doc-url.sh "/url/path/" + +if [ -z "$1" ]; then + echo "Usage: resolve-doc-url.sh " + echo "Example: resolve-doc-url.sh '/community-tools/contribute-to-mendix-docs/'" + exit 1 +fi + +# Search for the URL in front matter +# Using grep with -l (files only) and -F (fixed string) for speed +grep -rl --include="*.md" "^url: $1$" content/en/docs/ + +# Exit code 0 if found, 1 if not found diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000000..53603db57d7 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,190 @@ +{ + "env": { + "CLAUDE_CODE_ENABLE_TELEMETRY": "0", + "DISABLE_TELEMETRY": "1", + "OTEL_METRICS_EXPORTER": "otlp", + "AWS_PROFILE": "my-sandbox", + "AWS_REGION": "eu-central-1", + "CLAUDE_CODE_USE_BEDROCK": "1", + "ANTHROPIC_MODEL": "eu.anthropic.claude-sonnet-4-6", + "ANTHROPIC_SMALL_FAST_MODEL": "eu.anthropic.claude-haiku-4-5-20251001-v1:0", + "ANTHROPIC_DEFAULT_HAIKU_MODEL": "eu.anthropic.claude-haiku-4-5-20251001-v1:0", + "ANTHROPIC_DEFAULT_OPUS_MODEL": "eu.anthropic.claude-opus-4-8", + "ANTHROPIC_DEFAULT_SONNET_MODEL": "eu.anthropic.claude-sonnet-4-6", + "DISABLE_PROMPT_CACHING": "0", + "CLAUDE_CODE_MAX_OUTPUT_TOKENS": "10240", + "MAX_THINKING_TOKENS": "1024" + }, + "deny": [ + "Bash(aws * delete*)", + "Bash(aws * remove*)", + "Bash(aws * destroy*)", + "Bash(aws * terminate*)", + "Bash(aws * deregister*)", + "Bash(aws * purge*)", + "Bash(aws s3 rm *)", + "Bash(aws s3 rb *)", + "Bash(aws cloudformation delete-stack *)", + "Bash(aws ec2 run-instances *)", + "Bash(aws ec2 stop-instances *)", + "Bash(aws autoscaling set-desired-capacity *)", + "Bash(aws autoscaling update-auto-scaling-group *)", + "Bash(aws application-autoscaling *)", + "Bash(aws ecs update-service *)", + "Bash(aws iam delete*)", + "Bash(aws iam create*)", + "Bash(aws iam put*)", + "Bash(aws iam attach*)", + "Bash(kubectl delete *)", + "Bash(kubectl apply *)", + "Bash(kubectl create *)", + "Bash(kubectl patch *)", + "Bash(kubectl scale *)", + "Bash(kubectl rollout *)", + "Bash(kubectl drain *)", + "Bash(kubectl cordon *)", + "Bash(kubectl exec *)", + "Bash(kubectl edit *)", + "Bash(terraform destroy *)", + "Bash(terraform destroy)", + "Bash(terraform apply *)", + "Bash(terraform apply)", + "Bash(terraform import *)", + "Bash(terraform state rm *)", + "Bash(terraform taint *)", + "Bash(helm install *)", + "Bash(helm upgrade *)", + "Bash(helm delete *)", + "Bash(helm uninstall *)", + "Bash(helm rollback *)", + "Bash(git push --force *)", + "Bash(git push -f *)", + "Bash(git push --force-with-lease *)", + "Bash(git push origin main *)", + "Bash(git push origin main)", + "Bash(git push origin master *)", + "Bash(git push origin master)", + "Bash(git reset --hard *)", + "Bash(git clean -f *)", + "Bash(pnpm publish-packages)", + "Bash(rm -rf /)", + "Bash(rm -rf /*)", + "Bash(rm -rf ~)", + "Bash(rm -rf ~/*)", + "Bash(rm -rf ..*)", + "Bash(mkfs.*)", + "Bash(dd if=*/dev/*)", + "Bash(sudo *)", + "Bash(sudo)", + "Bash(chmod 777 *)", + "Bash(chown root *)", + "Bash(ssh *)", + "Bash(scp *)", + "Bash(curl * | sh)", + "Bash(curl * | bash)", + "Bash(wget * | sh)", + "Bash(wget * | bash)", + "Read(~/.aws/**)", + "Read(~/.kube/**)", + "Read(~/.ssh/**)", + "Read(.env)", + "Read(.env.*)", + "Read(**/secrets/**)", + "Read(**/*credentials*)", + "mcp__datadog-mcp__create_datadog_notebook", + "mcp__datadog-mcp__edit_datadog_notebook", + "mcp__atlassian__jira_delete_issue", + "mcp__atlassian__jira_remove_issue_link", + "mcp__atlassian__confluence_delete_page", + "mcp__atlassian__confluence_delete_attachment" + ], + "allow": [ + "Bash(git log *)", + "Bash(git status *)", + "Bash(git status)", + "Bash(git diff *)", + "Bash(git diff)", + "Bash(git branch *)", + "Bash(git branch)", + "Bash(git show *)", + "Bash(git rev-parse *)", + "Bash(git remote -v)", + "Bash(ls *)", + "Bash(ls)", + "Bash(pwd)", + "Bash(which *)", + "Bash(npm test *)", + "Bash(npm test)", + "Bash(npm run test *)", + "Bash(npm run lint *)", + "Bash(npx jest *)", + "Bash(npx tsc *)", + "Bash(pnpm test:compile)", + "Bash(pnpm test:browser)", + "Bash(pnpm test)", + "Bash(pnpm build)", + "Bash(pnpm install)", + "Bash(pnpm install:*)", + "Bash(pnpm lint)", + "Bash(pnpm lint:fix)", + "Bash(pnpx tsc *)", + "Bash(go test *)", + "Bash(mvn test *)", + "Bash(gradle test *)", + "Bash(python -m pytest *)", + "Bash(python3 -m pytest *)", + "Bash(aws * describe* *)", + "Bash(aws * list* *)", + "Bash(aws * get* *)", + "Bash(aws s3 ls *)", + "Bash(aws sts get-caller-identity *)", + "Bash(aws sts get-caller-identity)", + "Bash(kubectl get *)", + "Bash(kubectl describe *)", + "Bash(kubectl logs *)", + "Bash(terraform plan *)", + "Bash(terraform plan)", + "Bash(terraform validate *)", + "Bash(terraform validate)", + "Bash(terraform fmt *)", + "Bash(terraform fmt)", + "Read(./**)", + "Edit(./**)", + "mcp__datadog-mcp__analyze_datadog_logs", + "mcp__datadog-mcp__get_datadog_incident", + "mcp__datadog-mcp__get_datadog_metric", + "mcp__datadog-mcp__get_datadog_metric_context", + "mcp__datadog-mcp__get_datadog_notebook", + "mcp__datadog-mcp__get_datadog_trace", + "mcp__datadog-mcp__search_datadog_dashboards", + "mcp__datadog-mcp__search_datadog_events", + "mcp__datadog-mcp__search_datadog_hosts", + "mcp__datadog-mcp__search_datadog_incidents", + "mcp__datadog-mcp__search_datadog_logs", + "mcp__datadog-mcp__search_datadog_metrics", + "mcp__datadog-mcp__search_datadog_monitors", + "mcp__datadog-mcp__search_datadog_notebooks", + "mcp__datadog-mcp__search_datadog_rum_events", + "mcp__datadog-mcp__search_datadog_service_dependencies", + "mcp__datadog-mcp__search_datadog_services", + "mcp__datadog-mcp__search_datadog_spans" + ], + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [{"type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/validate-bash-command.sh"}] + } + ], + "PostToolUse": [ + { + "matcher": "*", + "hooks": [{"type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/audit-log.sh"}] + } + ] + }, + "statusLine": { + "type": "command", + "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/statusline.sh" + } +} diff --git a/.claude/skills/docs-add/SKILL.md b/.claude/skills/docs-add/SKILL.md new file mode 100644 index 00000000000..8a45edad32c --- /dev/null +++ b/.claude/skills/docs-add/SKILL.md @@ -0,0 +1,12 @@ +--- +name: docs-add +description: Adds new content to a single documentation page while preserving the original structure and meaning. Integrates new sections, paragraphs, or information smoothly with appropriate transitions. Use when the user wants to add, insert, include, or append new content to existing pages without rewriting what's already there. +user-invocable: true +disable-model-invocation: true +--- + +> **After adding content:** Consider running `/docs-polish` to improve clarity or `/docs-proofread` to check for errors in the final result. + +Ask the user for the new content to add. Determine a suitable place to smoothly integrate the new content into the existing content, with appropriate transitions and formatting, while preserving the original meaning and structure of the page. Don't make changes to existing content unless necessary for clarity or coherence when adding the new content. + +If the new content introduces redundancy or conflicts with existing content, flag these issues in the chat and suggest ways to resolve them without directly editing the existing content. diff --git a/.claude/skills/docs-alt-text/SKILL.md b/.claude/skills/docs-alt-text/SKILL.md new file mode 100644 index 00000000000..efe9b575706 --- /dev/null +++ b/.claude/skills/docs-alt-text/SKILL.md @@ -0,0 +1,71 @@ +--- +name: docs-alt-text +description: Generates W3C-compliant alt text for images in documentation pages. Analyzes each image's purpose and adds descriptive alt text for informative images or empty alt for decorative images, improving accessibility and SEO. +user-invocable: true +disable-model-invocation: false +--- + +> **Accessibility skill:** Generates alt text following W3C/WCAG 2.1 guidelines. Analyzes actual image content plus context to create concise, meaningful descriptions. + +## Workflow + +Follow this order for each image: + +1. **STEP 1 - View the image file** (REQUIRED) + - Extract image src path from figure shortcode + - Convert path: `src="/attachments/path/file.png"` → `static/attachments/path/file.png` + - Use Read tool to view the actual image + - Understand what the image shows BEFORE reading context + +2. **STEP 2 - Read surrounding context** + - Read the heading, preceding/following text, list item, or numbered step + - Understand the image's purpose within the documentation + - Consider if context + image together make the image informative or decorative + +3. **STEP 3 - Determine if informative or decorative** + - **Technical docs assumption:** Images are informative unless obviously decorative + - **Informative:** Images that convey information → write descriptive alt text + - **Decorative:** Images where the information is already given in adjacent text, or pure visual styling with no informational value → use `alt=""` + +4. **STEP 4 - Generate alt text** + - **If decorative, use `alt=""`.** Never omit the alt attribute entirely. + - **If informative, generate descriptive alt text:** + - Focus on the information the image communicates, not what it looks like + - Give the most concise description possible + - Maximum 30 words (flag complex images needing longer descriptions for body text) + - Don't include "screenshot of", "image of", or "picture of" (screen readers already announce it's an image) + - Use Mendix terminology + - Avoid redundancy with nearby text + - **Based on surrounding context:** + - In a procedure: emphasize the action/element relevant to the step (e.g., "Download button in Registration dialog") + - Showing UI elements: name the relevant elements (e.g., "Properties pane") + - Showing structure or relationships: describe what entities/components are connected (e.g., "Domain model with Customer and Order entities connected by one-to-many association") + - Showing logic or process flow: describe what the flow accomplishes (e.g., "Microflow that retrieves FileDocument list and updates encryption keys") + +5. **STEP 5 - Edit the figure shortcode** + - Use Edit tool to add/update only the `alt` attribute + - Preserve all other attributes: `class`, `width`, `max-width`, `link` + - Maintain exact indentation and spacing + +## Special Cases + +- **Images in numbered lists:** Common in procedures—describe the procedural step shown +- **Before/after sequences:** Describe what changed or the state shown +- **Existing alt text:** May update if it's empty, generic, or poor quality (e.g., `alt=""`, `alt="button"`, `alt="before"`) +- **File format icons:** Use format name (e.g., "PDF", "ZIP", "Word document") +- **Complex diagrams:** If needs >30 words, flag to user and suggest adding description to body text + +## What NOT to do + +- Don't modify `src` path or attributes other than `alt` +- Don't change surrounding text or document structure +- Don't process images outside the determined scope +- Don't generate alt text based solely on filename—always view the image first + +## After Processing + +Report summary: +- How many images processed +- How many updated + +**Always suggest user review:** Recommend that the user review the images themselves to confirm alt text accuracy, as AI-generated descriptions may miss important nuances or context-specific details. diff --git a/.claude/skills/docs-enhance/SKILL.md b/.claude/skills/docs-enhance/SKILL.md new file mode 100644 index 00000000000..bf6f395acf8 --- /dev/null +++ b/.claude/skills/docs-enhance/SKILL.md @@ -0,0 +1,10 @@ +--- +name: docs-enhance +description: Comprehensively edits a single documentation page including reorganization, restructuring, and rephrasing while preserving original meaning and intent. Improves flow, strengthens weak phrasing, and enhances overall quality. Use when documentation needs significant structural improvements, better organization, or when the user mentions reorganize, restructure, rewrite, or enhance. +user-invocable: true +disable-model-invocation: true +--- + +> **Skill progression:** This is the most intensive editing. If restructuring isn't needed, use `/docs-polish` for clarity improvements or `/docs-proofread` for basic fixes. + +Perform holistic improvements, including reorganization and stronger phrasing, while preserving original intent. This goes beyond basic proofreading and polishing to enhance the overall quality and impact of the text. Consider restructuring sentences, paragraphs, or sections for better flow, replacing weak words with stronger alternatives, and improving clarity and consistency while maintaining the original meaning. However, avoid making changes just for the sake of change; every edit should serve a clear purpose in enhancing the text. diff --git a/.claude/skills/docs-polish/SKILL.md b/.claude/skills/docs-polish/SKILL.md new file mode 100644 index 00000000000..e29220773bb --- /dev/null +++ b/.claude/skills/docs-polish/SKILL.md @@ -0,0 +1,45 @@ +--- +name: docs-polish +description: Applies style guide standards to a documentation page without changing meaning or reorganizing structure. This includes fixing grammar, improving clarity and readability, simplifying complex sentences, using active voice, and standardizing terminology and formatting. Use when the user wants to polish, check style guide compliance, improve language, or clean up documentation while preserving its structure. +user-invocable: true +disable-model-invocation: false +--- + +> **Skill progression:** This does everything `/docs-proofread` does plus style guide enforcement including clarity improvements. If only grammar and spelling fixes are needed, use `/docs-proofread`. For deeper reorganization, suggest `/docs-enhance`. If missing alt text is found, suggest `/docs-alt-text`. + +Improve clarity and readability without changing meaning, structure, or paragraph order: + +**docs-polish should**: +* Read Mendix style guides first (in parallel): `grammar-formatting.md`, `terminology.md`, and `product-naming-guide.md` from `/content/en/docs/community-tools/contribute-to-mendix-docs/style-guide/` +* Fix all spelling, grammar, and punctuation errors +* Check all figure shortcodes for missing alt text. If the alt text parameter is missing, insert `alt=""` as a placeholder. +* Ensure required front matter fields are present (title, url, description) and make descriptions concise and action-oriented +* Fix broken Markdown syntax +* Fix capitalization and terminology inconsistencies +* Break up long, complex sentences for better readability +* Simplify wordy or awkward phrasing +* Improve word choice (more precise or accessible terms) +* Change passive voice to active voice where appropriate +* Remove first-person plural (we, us, our, let's), except in release notes +* Remove bold and italics used for emphasis (reword or use alert shortcodes if needed) +* Apply Mendix style guide standards (overrides the Microsoft Writing Style Guide) +* Apply Microsoft Writing Style Guide standards, unless they conflict with the Mendix style guide standards + +**After completing edits**: +* Report what was changed in a concise summary +* If any images were found with missing or empty alt text, state "I found [N] image(s) with missing alt text. Consider running `/docs-alt-text` to generate alt text." + +**docs-polish should NOT**: +* Move paragraphs or restructure sections (that's `/docs-enhance`) +* Change technical meaning or accuracy +* Significantly increase document length +* Generate alt text for images +* Change command syntax, code identifiers, variable names, placeholders, or any other text that appears in code formatting (inline backticks or code blocks). Code-formatted text represents literal technical content that must remain unchanged. If you notice an issue with code-formatted text, flag it in the chat but don't edit it directly. + +Every edit should serve a clear purpose in making the text easier to read, scan, and understand. + +Priority order for determining scope: +1. If the user has selected text in a file (check for `ide_selection` tags), only polish the selected text in that file. Don't polish the entire document. +2. If there's one open file (check for `ide_opened_file` tags) and no selection, work on the entire file. +3. If there are multiple open files, list them and ask which to process. +4. If no files are open, ask for a file path. \ No newline at end of file diff --git a/.claude/skills/docs-pr-review/SKILL.md b/.claude/skills/docs-pr-review/SKILL.md new file mode 100644 index 00000000000..90a0bbcee7b --- /dev/null +++ b/.claude/skills/docs-pr-review/SKILL.md @@ -0,0 +1,16 @@ +--- +name: docs-pr-review +description: Analyzes all changes in a pull request and generates suggestions for improvements, clarifications, inconsistencies, and structural changes without making any edits. Use when the user asks to review, analyze, audit, or provide feedback on a pull request, or when they want suggestions before making changes. +user-invocable: true +disable-model-invocation: false +--- + +Compare changes between the current branch and the base branch (typically `development`). Read the complete final state of each modified file, not just the diff, to ensure changes don't introduce inconsistencies with unchanged content. + +Analyze all the changes in the current pull request or branch and return a list of suggestions or questions about any points to clarify, potential inconsistencies, and sections to restructure, add, or remove. Read the whole of each document modified in the pull request to ensure the changes do not make inconsistencies. + +Follow links to other documents in the repo to check for inconsistencies if they seem to provide important related information. + +Do not worry about possible invalid internal links to anchors in the repo as these will be picked up by a separate testing tool after the site has been built. + +Make no edits. diff --git a/.claude/skills/docs-proofread/SKILL.md b/.claude/skills/docs-proofread/SKILL.md new file mode 100644 index 00000000000..5782dc2b7f6 --- /dev/null +++ b/.claude/skills/docs-proofread/SKILL.md @@ -0,0 +1,33 @@ +--- +name: docs-proofread +description: Checks a single documentation page and fixes spelling, grammar, punctuation, basic Markdown formatting, and required front matter fields. Makes minimal corrections without rewording or restructuring. Use when the user asks to proofread, check for spelling and grammar errors, or fix typos. +user-invocable: true +disable-model-invocation: false +--- + +> **Skill progression:** This is the lightest touch. If more clarity and style guide work is needed, suggest `/docs-polish`. For deeper restructuring, suggest `/docs-enhance`. + +Do NOT rewrite, rephrase, or improve clarity. Proofread only: + +* **Spelling**: Fix typos and misspellings +* **Grammar**: Fix grammatical errors (subject-verb agreement, tense consistency, etc.) +* **Basic formatting checks**: + * Ensure required front matter fields are present (title, url, description) + * Fix broken Markdown syntax + * Fix any capitalization and terminology inconsistencies + +Do NOT: +* Rewrite sentences for clarity or conciseness +* Shorten or improve descriptions +* Change passive voice to active voice +* Simplify complex sentences +* Reorganize content +* Remove Markdown comments + +Priority order for determining scope: +1. If the user has selected text in a file (check for `ide_selection` tags), only proofread the selected text in that file. Don't proofread the entire document. +2. If there's one open file (check for `ide_opened_file` tags) and no selection, work on the entire file. +3. If there are multiple open files, list them and ask which to process. +4. If no files are open, ask for a file path. + +If you notice style or clarity issues that need improvement, finish proofreading first, then suggest that the user run `/docs-polish` for those improvements. diff --git a/.claude/skills/docs-review/SKILL.md b/.claude/skills/docs-review/SKILL.md new file mode 100644 index 00000000000..b8986ed2659 --- /dev/null +++ b/.claude/skills/docs-review/SKILL.md @@ -0,0 +1,8 @@ +--- +name: docs-review +description: Analyzes a single documentation page and generates suggestions for improvements, clarifications, inconsistencies, and structural changes without making any edits. Use when the user asks to review, analyze, audit, or provide feedback on a documentation page or section, or when they want suggestions before making changes. +user-invocable: true +disable-model-invocation: false +--- + +Analyze the page and return a list of suggestions or questions about any points to clarify, potential inconsistencies, and sections to restructure, add, or remove. Make no edits. diff --git a/.claude/statusline.sh b/.claude/statusline.sh new file mode 100755 index 00000000000..b3880ca8d0b --- /dev/null +++ b/.claude/statusline.sh @@ -0,0 +1,192 @@ +#!/bin/bash + +# Colors for terminal output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +PURPLE='\033[0;35m' +CYAN='\033[0;36m' +WHITE='\033[0;37m' +GRAY='\033[0;90m' +BOLD='\033[1m' +DIM='\033[2m' +RESET='\033[0m' + +# Read JSON input from stdin +input=$(cat) + +# Extract data from JSON +model_name=$(echo "$input" | jq -r '.model.display_name') +model_id=$(echo "$input" | jq -r '.model.id') +current_dir=$(echo "$input" | jq -r '.workspace.current_dir') +output_style=$(echo "$input" | jq -r '.output_style.name') +session_id=$(echo "$input" | jq -r '.session_id') +transcript_path=$(echo "$input" | jq -r '.transcript_path') + +# Get current time +current_time=$(date '+%H:%M:%S') + +# Function to get token usage from JSON or estimate from transcript +get_token_stats() { + # First try to get actual token usage from JSON input if available + actual_tokens=$(echo "$input" | jq -r '.token_usage.input_tokens // .conversation.token_count // empty' 2>/dev/null) + actual_messages=$(echo "$input" | jq -r '.conversation.message_count // empty' 2>/dev/null) + + if [[ -n "$actual_tokens" && "$actual_tokens" != "null" ]]; then + # Use actual token count from JSON + estimated_tokens="$actual_tokens" + message_count="${actual_messages:-0}" + echo "$estimated_tokens,$message_count" + elif [[ -f "$transcript_path" ]]; then + # Fallback: Count approximate tokens (rough estimate: 1 token ≈ 4 characters) + total_chars=$(wc -c <"$transcript_path" 2>/dev/null || echo "0") + estimated_tokens=$((total_chars / 4)) + + # Count messages + message_count=$(grep -c '"role":' "$transcript_path" 2>/dev/null || echo "0") + + echo "$estimated_tokens,$message_count" + else + echo "0,0" + fi +} + +# Function to estimate cost based on model and tokens +get_cost_estimate() { + local tokens=$1 + local model=$2 + local cost=0 + + # AWS Bedrock cost estimates per 1M tokens (input costs only, as of Feb 2026) + case "$model" in + *"sonnet-4-5"*) cost_per_1m=3.00 ;; # Claude Sonnet 4.5 - estimated + *"haiku-4-5"*) cost_per_1m=1.00 ;; # Claude Haiku 4.5 - estimated + *"opus-4-6"*) cost_per_1m=5.00 ;; # Claude Opus 4.6 - estimated + *) cost_per_1m=3.00 ;; # Default estimate + esac + + # Calculate cost in dollars + cost=$(echo "scale=4; $tokens * $cost_per_1m / 1000000" | bc 2>/dev/null || echo "0.0000") + printf "%.4f" "$cost" +} + +# Get token statistics +token_stats=$(get_token_stats) +estimated_tokens=$(echo "$token_stats" | cut -d',' -f1) +message_count=$(echo "$token_stats" | cut -d',' -f2) + +# Get cost estimate +cost_estimate=$(get_cost_estimate "$estimated_tokens" "$model_id") + +# Get git branch if in a git repo +git_info="" +if git rev-parse --git-dir >/dev/null 2>&1; then + branch=$(git branch --show-current 2>/dev/null) + if [[ -n "$branch" ]]; then + # Check for changes + if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then + git_info=" ${YELLOW}🔀 ${branch} ±${RESET}" + else + git_info=" ${GREEN}🌿 ${branch}${RESET}" + fi + fi +fi + +# Get kubectl context if available +kubectl_info="" +if command -v kubectl >/dev/null 2>&1; then + context=$(kubectl config current-context 2>/dev/null) + if [[ -n "$context" ]]; then + # Highlight production contexts + if [[ "$context" == *-prod* ]]; then + kubectl_info=" ${RED}⚠️ ⎈ ${context}${RESET}" + else + kubectl_info=" ${BLUE}⎈ $(basename "$context")${RESET}" + fi + fi +fi + +# Get AWS profile if set +aws_info="" +if [[ -n "$AWS_PROFILE" ]]; then + if [[ "$AWS_PROFILE" == *-prod* ]]; then + aws_info=" ${RED}⚠️ ☁️ ${AWS_PROFILE}${RESET}" + else + aws_info=" ${CYAN}☁️ ${AWS_PROFILE}${RESET}" + fi +fi + +# Build the status line with colors and emojis +status_line="" + +# Add directory (folder name only) +dir_display=$(basename "$current_dir") +status_line+=" ${BOLD}${BLUE}📁 $dir_display${RESET}" + +# Add git info +status_line+="$git_info" + +# Add kubectl info +status_line+="$kubectl_info" + +# Add AWS info +status_line+="$aws_info" + +# Add Claude-specific information +status_line+=" ${GRAY}|${RESET}" + +# Add model info with emoji +model_emoji="🤖" +case "$model_id" in + *"sonnet-4"*) model_emoji="🧠" ;; + *"sonnet-3-5"*) model_emoji="⚡" ;; + *"haiku"*) model_emoji="🌸" ;; + *"opus"*) model_emoji="👑" ;; +esac +status_line+=" ${PURPLE}${model_emoji} $model_name${RESET}" + +# Add output style if not default +if [[ "$output_style" != "null" && "$output_style" != "default" ]]; then + status_line+=" ${DIM}($output_style)${RESET}" +fi + +# Add token and cost information +if [[ "$estimated_tokens" -gt 0 ]]; then + # Format tokens with K/M suffixes + if [[ "$estimated_tokens" -gt 1000000 ]]; then + token_display=$(echo "scale=1; $estimated_tokens / 1000000" | bc 2>/dev/null || echo "0")M + elif [[ "$estimated_tokens" -gt 1000 ]]; then + token_display=$(echo "scale=1; $estimated_tokens / 1000" | bc 2>/dev/null || echo "0")K + else + token_display="$estimated_tokens" + fi + + # Color code based on token usage + if [[ "$estimated_tokens" -gt 50000 ]]; then + token_color="$RED" + elif [[ "$estimated_tokens" -gt 20000 ]]; then + token_color="$YELLOW" + else + token_color="$GREEN" + fi + + status_line+=" ${GRAY}|${RESET} ${token_color}🎯 ${token_display}t${RESET}" + + # Add cost if significant + if (($(echo "$cost_estimate > 0.01" | bc -l 2>/dev/null || echo "0"))); then + status_line+=" ${YELLOW}💰 \$${cost_estimate}${RESET}" + fi + + # Add message count + status_line+=" ${GRAY}💬 ${message_count}${RESET}" +fi + +# Add session info (shortened) +short_session=$(echo "$session_id" | cut -c1-8) +status_line+=" ${GRAY}|${RESET} ${DIM}🔗 $short_session${RESET}" + +# Add time +status_line+=" ${GRAY}|${RESET} ${WHITE}⏰ $current_time${RESET}" + +printf "$status_line" diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000000..23c98cc48b5 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,133 @@ +# Mendix Documentation Repository + + + +As an experienced technical editor agent, your primary responsibility is to proofread, edit, and review Markdown files under `content/en/docs` in accordance with the conventions below. Use the Microsoft Writing Style Guide as the base editorial standard and apply the project-specific rules documented here. + +## Instruction Precedence + +When instructions conflict, follow this order of precedence: + +1. The user's current request. +2. Task-specific prompt files in `.github/prompts/*.prompt.md` when explicitly referenced. +3. Overlay instruction files (for example, `.github/release-notes-instructions.md`) when path-scoped. +4. This file (`.github/copilot-instructions.md`). +5. Existing conventions in nearby pages within the same folder. +6. Microsoft Writing Style Guide. + +### Critical Constraints + +* MUST edit existing files in place unless the user explicitly asks to create new content. +* MUST preserve meaning and intent. +* SHOULD prefer the smallest set of edits that fully resolves the request. +* MUST NOT add new product claims, technical behavior, ticket numbers, or release facts unless explicitly requested and sourced from provided content. + +## Project Overview + +* **What** – This repository contains the source code for the Mendix documentation site, which describes the Mendix low‑code application development platform. Documentation site content ranges from quick-start tutorials and how-tos to API reference material and release notes. +* **Who** – Target readers are developers, business analysts, system administrators, and partners who consume the docs for learning, troubleshooting, and reference. Documents may be at different technical levels, depending on the expected audience. +* **Tech stack** – Hugo-based static website based on the Docsy theme. Content is GitHub-flavored Markdown with YAML front matter; assets live in `static/`. + +## Content Structure and Hierarchy + +The canonical tree is **`/content/en/docs`**. Top‑level directories correspond to major product areas (e.g. `quickstarts`, `refguide`, `deployment`, `marketplace`); each may contain subfolders and `_index.md` files that define section landing pages. + +Typical structure: + +``` +content/en/docs/ +├── _index.md +├── quickstarts/ +│   ├── _index.md +│   ├── hello-world.md +│   └── responsive-web-app.md +├── refguide/ +│   ├── _index.md +│   ├── modeling/ … +│   └── runtime/ … +… +``` + +* **Index files (`_index.md`)** define landing pages or categories. They often use `cascade` to pass metadata to children and may set `type`, `layout`, `no_list`, `description_list`, etc. +* Other `.md` files represent individual articles, how‑tos, reference topics, release notes, etc. File names must be simple, lowercase, and hyphen‑separated. + +Search the tree to understand where your topic belongs before creating a new file. + +## Style Standards + +* **Guiding manual** – Microsoft Writing Style Guide (https://learn.microsoft.com/style-guide/). Apply grammar, inclusive language, terminology, and formatting rules from that document. +* **Tone** – Clear, concise, active voice; use imperative mood for procedures; second person (you/your) when addressing readers. Keep a conversational, straightforward tone. Present tense. Use American English and write for a global audience. Prefer short, everyday words; avoid or explain jargon. Keep it simple—short sentences and fragments are easier to scan and read, and prune excess words. Avoid marketing language. +* **Terminology** – Capitalize product names (Mendix Portal, Studio Pro, Team Server); use “microflow”, “nanoflow”, etc. consistently. Never use e.g. or i.e. +* **Text formatting** – Reserve bold for UI labels, button names, menu items, or other interface text, or for introductions in list items. Don't use italics except to refer to titles and sections. Use wording or alert shortcodes for emphasis; don't use text formatting for emphasis. Use code font only to wrap literal code, filenames, paths, or command-line input. Use `` for keyboard shortcuts. +* **Headings** – H1 is generated from the front‑matter title. Subsequent headings increment by one level at a time. Don't use bold or italics as a replacement for headings. Use title case. Never start headings with numbers. +* **Lists and tables** – Bullet lists use asterisks; ordered lists use numbers followed by a period. If there are more than three data points per item, use a table instead. Use the same syntax and structure for all list items in a given list. Use complete sentences to introduce lists and tables, not partial sentences completed with the list items. +* **Indentation** – Use four spaces to indent content—for example, to create a sub-list or nest an image or code block in a list. Alerts are an exception: don't indent alert lines but do omit preceding blank line. +* **Links** – Use absolute paths starting with a leading slash (`/deployment/`). Use descriptive link text such as the page title, not “click here”. To link to a heading, add an anchor ID (`{#anchor-id}`) next to the heading and use that ID in the URL (for example, `[Section title](/path/to/page#anchor-id)` to link to a heading in another page or `[Section title](#anchor-id)` to link to a heading in the same page). +* **Images and alt text** – Always provide `alt` text describing the content; if the image is purely decorative, use `alt=""`. Use W3C guidelines to write alt text. Reference images with the `figure` shortcode (see below). +* **Code** – Use fenced code blocks with language specifier. + +Project‑specific preferences are documented in the templates and in `community-tools` example pages; consult them for tricky formatting cases. + +## Technical Implementation Details + +### Front Matter + +All Markdown files begin with YAML metadata. + +* `title` – Human‑readable page title. (Required) +* `url` – Page URL. Start and end with `/`, use only lowercase letters, numbers, and hyphens. Doesn't need to match the file path. (Required) +* `description` – Summary used for metadata, search snippets, and content lists. Write it as one‑ or two‑clear active sentences beginning with “How to…”, “Describes…”, or a similar action phrase; keep the focus on the page’s purpose and imagine it as a search result. (Required) +* `linktitle` – Short text shown in the left navigation pane; use when `title` is longer than 40 characters. (Optional) +* `aliases` – Redirect paths for moved or renamed pages. Add old URLs as alias entries. (Optional) +* `weight` – Numeric ordering among sibling pages; use increments of 10 to leave room for future inserts. (Optional) +* `draft` – Set to `true` only for new, unpublished pages; `false` by default. (Optional) +* `cascade` – Used in `_index.md` files to propagate metadata to child pages. Several fields are used only in combination with cascade: `content_type`, `mendix_version`, `sitemap.priority`, and `old_content`. (Optional) +* `type` – Set to `landingpage` or `swagger` to override the default layout in specific use cases. (Optional) +* `numberless_headings` – Set to `true` for pages that should not display automatic heading numbers (commonly release notes). (Optional) + +### Shortcodes + +Hugo shortcodes start with `{{`. Some common ones: + +* `figure` – Images. Attributes: `src` (required), `alt` (required), `class`, `max-width`, `link`. Always store assets under `static/attachments/...` and reference with `/attachments/...`. + + ```md + {{< figure src="/attachments/quickstarts/part1/3.login.png" alt="Sign in to Studio Pro" max-width="80%" >}} + ``` + +* `alert` – Callouts of type `info` and `warning`. + ```md + {{% alert color="warning" %}} + This action cannot be undone. + {{% /alert %}} + ``` + +* `button` – Link buttons with `color`, `href`, `text`, and optional `title`. +* `icon` – Inline SVG icons stored in `static/mx-icons` (`name` required, optional `color`) for use in UI descriptions. +* `youtube` / `vidyard` – Embed videos by ID. +* `swaggerui` / `swaggerui-disable-try-it-out` – Render OpenAPI specs on pages with `type:swagger`. +* `snippet` – Include external code or page content. +* `tabpane` / `tab` – Create tabbed code examples. +* `todo` – Internal draft notes; omitted in production. + +For comprehensive shortcode examples and edge cases, read `community-tools/contribute-to-mendix-docs/markdown-shortcodes.md`. + +## Editorial Workflow + +1. **Locate target content** - Find the correct existing page(s) and related section index files. +2. **Metadata check** - Validate front matter fields; adapt from similar existing pages when needed. +3. **Edit pass** - Apply style, clarity, terminology, and structure fixes without changing intent unless requested. +4. **Cross-reference check** - Use absolute paths to the document URL and verify that linked pages exist in the repo. Don't use the path to the Markdown file. +5. **Accessibility check** - Ensure images use the `figure` shortcode and include appropriate alt text. +6. **Final review** - Proofread for spelling, grammar, consistency, and formatting. + +## Standard Content Template + +If asked to create new content: + +1. Read the appropriate template from `templates/`: `how-to-template.md`, `reference-template.md`, `marketplace-component-page-template.md`, or `release-notes-template.md` +2. Create the new file based on the template +3. Remove comment lines (`#`) from the template +4. Follow the editorial workflow above + +For all pages, required sections are front matter and the introduction; other sections vary by content type. \ No newline at end of file diff --git a/.github/prompts/add.prompt.md b/.github/prompts/add.prompt.md new file mode 100644 index 00000000000..8e440116f91 --- /dev/null +++ b/.github/prompts/add.prompt.md @@ -0,0 +1,7 @@ +--- +description: Add new content to a page without rewriting existing content. +--- + +Ask the user for the new content to add. Determine a suitable place to smoothly integrate the new content into the existing content, with appropriate transitions and formatting, while preserving the original meaning and structure of the page. Don't make changes to existing content unless necessary for clarity or coherence when adding the new content. + +If the new content introduces redundancy or conflicts with existing content, flag these issues in the chat and suggest ways to resolve them without directly editing the existing content. \ No newline at end of file diff --git a/.github/prompts/enhance.prompt.md b/.github/prompts/enhance.prompt.md new file mode 100644 index 00000000000..6f56699495f --- /dev/null +++ b/.github/prompts/enhance.prompt.md @@ -0,0 +1,5 @@ +--- +description: Edit the text, including reorganization and rephrasing. +--- + +Perform holistic improvements, including reorganization and stronger phrasing, while preserving original intent. This goes beyond basic proofreading and polishing to enhance the overall quality and impact of the text. Consider restructuring sentences, paragraphs, or sections for better flow, replacing weak words with stronger alternatives, and improving clarity and consistency while maintaining the original meaning. However, avoid making changes just for the sake of change; every edit should serve a clear purpose in enhancing the text. \ No newline at end of file diff --git a/.github/prompts/polish.prompt.md b/.github/prompts/polish.prompt.md new file mode 100644 index 00000000000..4b88b3783d8 --- /dev/null +++ b/.github/prompts/polish.prompt.md @@ -0,0 +1,9 @@ +--- +description: Proofread and improve clarity without changing meaning or structure. +--- + +Follow the instructions in proofread.prompt.md. + +Then identify ways to further improve the clarity by making the document easier to read, scan, and understand, without moving paragraphs around, changing the meaning, or significantly increasing the length. This may include breaking up long sentences, adding subheadings, and improving word choice. Do not make changes just for the sake of change; every edit should serve a clear purpose in enhancing the text. + +Do not change any code samples directly, but flag any code issues or inconsistencies in the chat. \ No newline at end of file diff --git a/.github/prompts/proofread.prompt.md b/.github/prompts/proofread.prompt.md new file mode 100644 index 00000000000..b1f4347fd55 --- /dev/null +++ b/.github/prompts/proofread.prompt.md @@ -0,0 +1,10 @@ +--- +description: Check spelling, grammar, and basic style. +--- + +Proofread the document without altering meaning or structure: +* Check spelling, grammar, and basic style. + +Then further improve language and clarity without restructuring paragraphs or changing content order: +* Carefully go through each bullet point in the *Style standards* section of copilot-instructions.md and check if the guidance has been applied for each. +* Make sure that the front matter of the file adheres to the specified format and includes the required fields. \ No newline at end of file diff --git a/.github/prompts/review.prompt.md b/.github/prompts/review.prompt.md new file mode 100644 index 00000000000..ad49422b5ea --- /dev/null +++ b/.github/prompts/review.prompt.md @@ -0,0 +1,5 @@ +--- +description: Review the document and generate suggestions for improvement without any direct edits. +--- + +Analyze the page and return a list of suggestions or questions about any points to clarify, potential inconsistencies, and sections to restructure, add, or remove. Make no edits. \ No newline at end of file diff --git a/.github/workflows/auto-assign-pr.yml b/.github/workflows/auto-assign-pr.yml new file mode 100644 index 00000000000..5957bf8ebd3 --- /dev/null +++ b/.github/workflows/auto-assign-pr.yml @@ -0,0 +1,55 @@ +name: Auto-assign PR to Technical Writers + +on: + pull_request: + types: [opened] + +jobs: + auto-assign: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + - name: Assign PR to creator if on Technical Writers team + uses: actions/github-script@v9 + with: + script: | + const creator = context.payload.pull_request.user.login; + const prNumber = context.payload.pull_request.number; + const assignees = context.payload.pull_request.assignees; + + // Technical Writers team members + const techWriters = [ + 'ConnorLand', + 'Karuna-Mendix', + 'MariaShaposhnikova', + 'MarkvanMents', + 'NicoletaComan', + 'OlufunkeMoronfolu', + 'Yiyun333', + 'dbreseman', + 'katarzyna-koltun-mx', + 'quinntracy' + ]; + + console.log(`Processing PR #${prNumber} by ${creator}`); + console.log(`Current assignees: ${assignees.length > 0 ? assignees.map(a => a.login).join(', ') : 'none'}`); + + if (assignees.length > 0) { + console.log('PR already has assignees, skipping auto-assignment'); + return; + } + + if (techWriters.includes(creator)) { + console.log(`${creator} is on technical writers team, assigning PR...`); + await github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + assignees: [creator] + }); + console.log(`Successfully assigned PR #${prNumber} to ${creator}`); + } else { + console.log(`${creator} is not on technical writers team, skipping assignment`); + } diff --git a/.github/workflows/branch-deletion-pr-creation.yml b/.github/workflows/branch-deletion-pr-creation.yml new file mode 100644 index 00000000000..efd188e099b --- /dev/null +++ b/.github/workflows/branch-deletion-pr-creation.yml @@ -0,0 +1,142 @@ +name: Branch Deletion Phase One (PR Creation) +permissions: + contents: write + pull-requests: write +on: + schedule: + - cron: '00 22 1 * *' # 10PM on 1st of every month + workflow_dispatch: + inputs: + min_age_days: + description: "Minimum age in days since merge" + required: true + default: 27 + type: number + +jobs: + identify-branches: + if: github.repository_owner == 'mendix' + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + persist-credentials: true + + - name: Setup jq + run: sudo apt-get install -y jq + + - name: Create timestamp file + run: | + echo "Last scan for stale merged branches: $(TZ='Europe/Amsterdam' date +'%Y-%m-%d %H:%M:%S %Z (UTC%:z)')" > branch-cleanup-timestamp.txt + + - name: Fetch all branches + run: | + git fetch origin '+refs/heads/*:refs/remotes/origin/*' --prune + + - name: Process branches + id: branch-data + env: + MIN_AGE_DAYS: ${{ github.event.inputs.min_age_days || '27' }} + run: | + set -e + ALL_BRANCHES=$(git branch -r | grep -v "origin/HEAD" | sed 's/origin\///') + MIN_AGE_DAYS=${MIN_AGE_DAYS:-27} + + echo "MIN_AGE_DAYS=${MIN_AGE_DAYS}" + + PROTECTED_COUNT=0 + MERGED_COUNT=0 + UNMERGED_COUNT=0 + + PROTECTED_BRANCHES=() + MERGED_BRANCHES_TO_PROCESS=() + BRANCHES_TO_DELETE=() + BRANCHES_TOO_RECENT=() + UNMERGED_BRANCHES=() + + CURRENT_DATE=$(date +%Y%m%d) + echo "CURRENT_DATE=$CURRENT_DATE" >> $GITHUB_ENV + + for BRANCH in $ALL_BRANCHES; do + branch_lower=$(echo "$BRANCH" | tr '[:upper:]' '[:lower:]') + if [[ $branch_lower == *backup* || $branch_lower =~ ^(development|main|master|production)(-[0-9]+)?$ ]]; then + PROTECTED_BRANCHES+=("$BRANCH") + PROTECTED_COUNT=$((PROTECTED_COUNT+1)) + elif git branch -r --merged origin/development | grep -q "origin/$BRANCH"; then + MERGED_BRANCHES_TO_PROCESS+=("$BRANCH") + MERGED_COUNT=$((MERGED_COUNT+1)) + else + UNMERGED_BRANCHES+=("$BRANCH") + UNMERGED_COUNT=$((UNMERGED_COUNT+1)) + fi + done + + echo "branch totals: protected=${PROTECTED_COUNT} merged=${MERGED_COUNT} unmerged=${UNMERGED_COUNT}" + + for BRANCH in "${MERGED_BRANCHES_TO_PROCESS[@]}"; do + MERGE_HASH=$(git log --grep="Merge branch.*$BRANCH" origin/development -n 1 --pretty=format:"%H" || true) + if [ -z "$MERGE_HASH" ]; then + MERGE_HASH=$(git log -n 1 origin/$BRANCH --pretty=format:"%H" || true) + fi + + if [ -z "$MERGE_HASH" ]; then + echo "WARN: no merge commit found for $BRANCH — skipping" + UNMERGED_BRANCHES+=("$BRANCH (no-merge-hash)") + UNMERGED_COUNT=$((UNMERGED_COUNT+1)) + continue + fi + + MERGE_DATE_EPOCH=$(git show -s --format=%ct $MERGE_HASH 2>/dev/null || true) + if [ -z "$MERGE_DATE_EPOCH" ]; then + echo "WARN: could not read commit date for $BRANCH (hash=$MERGE_HASH) — skipping" + UNMERGED_BRANCHES+=("$BRANCH (no-merge-date)") + UNMERGED_COUNT=$((UNMERGED_COUNT+1)) + continue + fi + + DAYS_AGO=$(( ($(date +%s) - MERGE_DATE_EPOCH) / 86400 )) + + if [[ $DAYS_AGO -ge $MIN_AGE_DAYS ]]; then + BRANCHES_TO_DELETE+=("$BRANCH (${DAYS_AGO}d since merge)") + else + BRANCHES_TOO_RECENT+=("$BRANCH (${DAYS_AGO}d since merge)") + fi + echo "checked $BRANCH: hash=$MERGE_HASH date=$MERGE_DATE_EPOCH days=${DAYS_AGO}" + done + + echo "HAS_BRANCHES=$([ ${#BRANCHES_TO_DELETE[@]} -gt 0 ] && echo true || echo false)" >> $GITHUB_ENV + echo "BRANCHES_TO_DELETE=$(printf -- '- %s\n' "${BRANCHES_TO_DELETE[@]}" | jq -Rs .)" >> $GITHUB_ENV + echo "PROTECTED_BRANCHES=$(printf -- '- %s\n' "${PROTECTED_BRANCHES[@]}" | jq -Rs .)" >> $GITHUB_ENV + echo "BRANCHES_TOO_RECENT=$(printf -- '- %s\n' "${BRANCHES_TOO_RECENT[@]}" | jq -Rs .)" >> $GITHUB_ENV + echo "UNMERGED_BRANCHES=$(printf -- '- %s\n' "${UNMERGED_BRANCHES[@]}" | jq -Rs .)" >> $GITHUB_ENV + + - name: Create Deletion PR + if: env.HAS_BRANCHES == 'true' + uses: peter-evans/create-pull-request@v8 # NOTE: If you upgrade the version of this action, you must also update the GitHub Actions allowlist in mendix/docs > Settings > Actions > General to permit the new version tag, otherwise the workflow will fail. + with: + token: ${{ secrets.GITHUB_TOKEN }} + title: "[Auto] Branch Deletion Candidates - ${{ env.CURRENT_DATE }}" + body: | + ### Branches for Deletion + ${{ fromJSON(env.BRANCHES_TO_DELETE) }} + + ### Protected Branches + ${{ fromJSON(env.PROTECTED_BRANCHES) }} + + ### Recent Merges (too recent to delete) + ${{ fromJSON(env.BRANCHES_TOO_RECENT) }} + + ### Unmerged Branches + ${{ fromJSON(env.UNMERGED_BRANCHES) }} + base: development + labels: Internal WIP + assignees: MarkvanMents,OlufunkeMoronfolu + reviewers: MarkvanMents,OlufunkeMoronfolu + commit-message: "Add branch cleanup candidates for ${{ env.CURRENT_DATE }}" + add-paths: | + branch-cleanup-timestamp.txt + author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" + committer: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" \ No newline at end of file diff --git a/.github/workflows/branch-deletion-pr-processing.yml b/.github/workflows/branch-deletion-pr-processing.yml new file mode 100644 index 00000000000..f279c107443 --- /dev/null +++ b/.github/workflows/branch-deletion-pr-processing.yml @@ -0,0 +1,64 @@ +name: Branch Deletion Phase Two (PR Processing) +on: + pull_request: + types: [closed] + branches: [development] + paths: + - 'branch-cleanup-timestamp.txt' + +permissions: + contents: write + +jobs: + process-approved-deletion: + if: | + github.event.pull_request.merged == true && + startsWith(github.event.pull_request.title, '[Auto] Branch Deletion Candidates') && + contains(github.event.pull_request.labels.*.name, 'Internal WIP') + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + persist-credentials: true + + - name: Extract branches + id: extract-branches + env: + PR_BODY: ${{ github.event.pull_request.body }} + run: | + BRANCHES=$(echo "$PR_BODY" | awk ' + /### Branches for Deletion/ {flag=1; next} + /### Protected\/Recent Branches/ {flag=0} + flag && /^-/ {gsub(/^-[ \t]*/, ""); print} + ') + + CLEAN_BRANCHES=$(echo "$BRANCHES" | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + echo "branches=$(jq -nc '$ARGS.positional' --args $CLEAN_BRANCHES)" >> $GITHUB_OUTPUT + echo "Extracted branches: $BRANCHES" + + - name: Delete branches + env: + BRANCHES: ${{ steps.extract-branches.outputs.branches }} + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + + echo "$BRANCHES" | jq -r '.[]' | while read -r branch; do + branch_lower=$(echo "$branch" | tr '[:upper:]' '[:lower:]') + + if [[ $branch_lower =~ ^(backup|development|main|master|production)(-[0-9]+)?$ ]]; then + echo "Skipping protected branch: $branch" + continue + fi + + if git ls-remote --exit-code --heads origin "$branch" >/dev/null; then + echo "Deleting $branch" + git push origin --delete "$branch" + sleep 1 + else + echo "Branch $branch does not exist" + fi + done \ No newline at end of file diff --git a/.github/workflows/check-claude-settings.yml b/.github/workflows/check-claude-settings.yml new file mode 100644 index 00000000000..04c65c9c0fa --- /dev/null +++ b/.github/workflows/check-claude-settings.yml @@ -0,0 +1,44 @@ +name: Check Claude Configuration + +on: + pull_request: + paths: + - '.claude/**' + - 'CLAUDE.md' + +jobs: + check-claude-config: + runs-on: ubuntu-latest + steps: + - name: Check for Claude configuration changes + uses: actions/github-script@v9 + with: + script: | + const comment = `⚠️ **Claude Configuration Warning** + + You've modified Claude Code configuration files. These files contain the shared configuration for all contributors. Please revert this change if unintended. **ONLY modify these files if you need to change shared configurations that apply to everyone and have discussed this change with the TW team.** + + To override the Claude settings locally, use \`.claude/settings.local.json\` instead. This local file is gitignored and overrides the repo default. + + **For example, if you're changing AWS_PROFILE:** + The default profile name is \`my-sandbox\`. If your AWS profile has a different name, override it locally instead of changing the shared file. + + Create or edit \`.claude/settings.local.json\` in the repo root and include the following lines: + + \`\`\`json + { + "env": { + "AWS_PROFILE": "your-sandbox-name" + } + } + \`\`\` + `; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); + + core.setFailed('Claude configuration was modified - see comment for guidance'); diff --git a/.github/workflows/lint-action.yml b/.github/workflows/lint-action.yml index 1131ad4be78..af0ce84b234 100644 --- a/.github/workflows/lint-action.yml +++ b/.github/workflows/lint-action.yml @@ -20,12 +20,12 @@ jobs: steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - name: Checkout repo - uses: actions/checkout@v4 - # Installs NodeJS v20 in runner environment (upgraded from v16 in Feb 2024) + uses: actions/checkout@v6 + # Installs NodeJS v24 in runner environment (upgraded from v20 in Jun 2026) - name: Install NodeJS - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '20' + node-version: '24' # Installs linting tool via npm # Look at using the related GitHub action here: https://github.com/DavidAnson/markdownlint-cli2-action - name: Install markdown linter @@ -44,16 +44,17 @@ jobs: echo "VER<> $GITHUB_ENV echo "$VER" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - # Creates PR for linted content changes - needs version > 4 for NodeJS 20 + # Creates PR for linted content changes - needs version > 6 for NodeJS 24 - name: Create pull request - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v8 # NOTE: If you upgrade the version of this action, you must also update the GitHub Actions allowlist in mendix/docs > Settings > Actions > General to permit the new version tag, otherwise the workflow will fail. with: commit-message: Run markdownlint-cli2 on docs to find (and correct) linting errors. title: '[Auto] Lint docs' body: | ${{ env.VER }} branch: lint-docs - committer: MarkvanMents + author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" + committer: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" assignees: MarkvanMents,OlufunkeMoronfolu reviewers: MarkvanMents,OlufunkeMoronfolu labels: Internal WIP diff --git a/.github/workflows/remunusedattachments.yml b/.github/workflows/remunusedattachments.yml index c3a95e0ea30..541d59e9678 100644 --- a/.github/workflows/remunusedattachments.yml +++ b/.github/workflows/remunusedattachments.yml @@ -20,15 +20,15 @@ jobs: # Checks out your repository under $GITHUB_WORKSPACE, so your job can access it - name: Checkout repo - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version: '3.10' - run: python _scripts/removeUnusedAttachments.py # Creates pull request - name: Create Pull Request - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v8 # NOTE: If you upgrade the version of this action, you must also update the GitHub Actions allowlist in mendix/docs > Settings > Actions > General to permit the new version tag, otherwise the workflow will fail. with: commit-message: Run removeUnusedAttachments.py on docs title: '[Auto] Remove unused attachments' @@ -36,7 +36,8 @@ jobs: Pull Request to delete attachments that are no longer used. Check the htmltest output from the CI/CD pipeline to ensure that nothing has been removed accidentally. branch: rem-unused-attachments - committer: MarkvanMents + author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" + committer: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" assignees: MarkvanMents,OlufunkeMoronfolu reviewers: MarkvanMents,OlufunkeMoronfolu labels: Internal WIP diff --git a/.github/workflows/vale.yml b/.github/workflows/vale.yml new file mode 100644 index 00000000000..8626fd2ebe0 --- /dev/null +++ b/.github/workflows/vale.yml @@ -0,0 +1,56 @@ +name: Vale Linting + +# Trigger on PRs that change Markdown files (but skip run on draft PRs) +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + paths: + - 'content/en/docs/**/*.md' + +# Allow reading repo contents and posting PR comments +# Note: Fork PRs from org members run automatically but get read-only tokens, +# so reviewdog falls back to check annotations instead of review comments. +# External contributors require workflow approval per repo settings. +permissions: + contents: read + pull-requests: write + +jobs: + vale: + # Only run on Mendix repo, not forks, and not on draft PRs + if: github.repository_owner == 'mendix' && github.event.pull_request.draft == false + runs-on: ubuntu-latest + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + # Cancel old runs when new commits are pushed + concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + steps: + - name: Checkout repo + uses: actions/checkout@v6 + with: + sparse-checkout: | + .vale.ini + .vale + content/en/docs + sparse-checkout-cone-mode: false + + - name: Get changed files + id: changed-files + run: | + FILES=$(gh pr view ${{ github.event.pull_request.number }} --json files --jq '.files[].path' | grep '^content/en/docs/.*\.md$' | paste -sd, -) + echo "files=$FILES" >> $GITHUB_OUTPUT + env: + GH_TOKEN: ${{ github.token }} + + - name: Run Vale + if: steps.changed-files.outputs.files != '' + uses: vale-cli/vale-action@v2 + with: + files: ${{ steps.changed-files.outputs.files }} + separator: ',' + version: 3.14.2 + fail_on_error: false + reporter: github-pr-review # Uses filter_mode=added (default): only comment on modified lines + vale_flags: '--minAlertLevel=error' # Only flag errors, not warnings or suggestions diff --git a/.gitignore b/.gitignore index 08262bce2a6..7a90cada5d8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,10 @@ hugo.exe /htmltest.exe /.hugo_build.lock +/.claude/audit.log +/.claude/settings.local.json +/.mcp.json +/.vale/styles/Microsoft/ # For Mac users .DS_Store diff --git a/.travis.yml b/.travis.yml index b5eeb6baaef..83dc312b8a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - 20.9 # latest LTS version ('node' will give latest version, 'lts/*' will give LTS version ) + - 24.15 # latest LTS version ('node' will give latest version, 'lts/*' will give LTS version ) arch: amd64 # optional, this is default, routes to a full VM # arch: arm64 # this is the recommended LXD container - faster spin up but some limitations Not used as it seems very unstable. os: linux # optional, this is default @@ -37,21 +37,16 @@ branches: # - name: hugo # channel: extended/stable # needs extended to compile SCSS etc. Issue if snap is not updated correctly -# cache: -######## Skipped -# -# Is there anything worth cacheing that isn't currently cached? -# see https://docs.travis-ci.com/user/caching/#how-does-caching-work -# Probably NOT /static -# Docsy theme? -# Custom layouts? -# SASS/CSS -# +cache: +# Cache node_modules to avoid re-downloading on every build +# See https://docs.travis-ci.com/user/caching/ + directories: + - node_modules before_install: ############### # Hugo, Docsy and dependencies are installed via npm - - npm install # use npm rather than yarn - HUGO doesn't have that many dependencies + - travis_retry npm install # use npm rather than yarn - HUGO doesn't have that many dependencies # # Currently will use custom version of htmltest stored in /htmltest/htmltest # This version makes the html.Parse much smaller @@ -74,7 +69,8 @@ install: # Append output to hugo.log file to print at the end of the travis job # (see https://stackoverflow.com/questions/418896/how-to-redirect-output-to-a-file-and-stdout) - - ./node_modules/.bin/hugo --environment $TRAVIS_BRANCH 2>&1 | tee -a $TRAVIS_BUILD_DIR/hugo.log + # bash -o pipefail ensures Hugo's non-zero exit code is not masked by tee inside the subprocess + - travis_wait 15 bash -o pipefail -c './node_modules/.bin/hugo --environment $TRAVIS_BRANCH 2>&1 | tee -a $TRAVIS_BUILD_DIR/hugo.log' # normal htmltest takes too much memory - using a modified version which strips