[Feature Request] Preprocessor conditionals + replacement variables in .mdc rules and .md skills (like VSCode's predefined variables)

Feature request for product/service

Cursor IDE

Describe the request

Problem

Cursor rules (.mdc files) and agent skills (.md files) are static text. There’s no way to:

  1. Conditionally include content based on the runtime environment — e.g., show PowerShell syntax on Windows, bash syntax on macOS/Linux. Today we either duplicate rules per platform or use install-time code generation, both of which drift and add maintenance burden.

  2. Reference file-relative paths — e.g., a skill at .agents/skills/github/pr-review/SKILL.md that references an adjacent script at ./scripts/post-review.mjs must hardcode the full path. Moving/nesting the skill breaks all references. This becomes increasingly painful as skill hierarchies deepen (the natural evolution for reducing context bloat).

  3. Validate rule files in the editor — there’s no schema checking that flags invalid variable references or malformed conditionals before they silently produce broken context at inference time. Today the only way to debug a broken rule is to ask the agent to ingest it and observe the failure.

Proposed Solution

1. Conditional Blocks (Preprocessor Directives)

Allow conditional sections in rules that are resolved before the content enters the LLM context window. The agent never sees the conditionals — only the resolved output for the current environment.

---
alwaysApply: true
---

<if os="windows">
Use PowerShell syntax: `2>$null`, `$env:VAR = "value"`, `Remove-Item -Recurse`.
Do not assume bash or CMD conventions.
</if>

<if os="linux,darwin">
Use POSIX shell syntax: `2>/dev/null`, `export VAR=value`, `rm -rf`.
</if>

Key design points:

  • Resolution happens in Cursor’s rule ingestion pipeline, not at the model level — the LLM receives clean, unambiguous text
  • Authors see the full picture (all variants co-located in one file) — easy to review, keeps variants in sync
  • The model gets zero branching overhead — just the resolved block for the current environment
  • Variables are sourced from the same runtime context Cursor already injects into the system prompt (OS, shell, workspace path, etc.)

Available condition variables (these are already collected — just expose them for rules):

  • oswindows, linux, darwin
  • shellpowershell, bash, zsh, fish, etc.
  • workspace — workspace name or path pattern matching
  • Custom user-defined variables (via settings.json or .cursor/variables.json)

2. Replacement Variables (Predefined + Custom)

Adopt the same predefined variable pattern that VSCode already uses in tasks.json, launch.json, and settings.json — but for rule/skill files:

Variable Resolves To Use Case
${fileDir} Directory of the current rule/skill file Relative script references
${workspaceFolder} Workspace root path Absolute workspace references
${workspaceName} Workspace folder name Display/naming
${os} windows, linux, darwin Inline conditionals
${shell} Current shell name Shell-specific instructions
${userHome} User home directory Global tool paths

Real example — today vs. proposed:

Today (fragile, breaks on move):

Run the review script:
`node .agents/skills/github-pr-review-comments/scripts/post-review.mjs`

Proposed (move-safe):

Run the review script:
`node ${fileDir}/scripts/post-review.mjs`

This is critical for skill nesting. As projects grow, skills naturally form hierarchies (e.g., github/ parent skill with pr-review/, issues/, backlog/ children). Every nesting or reorganization currently requires updating all hardcoded paths. ${fileDir} eliminates this entirely.

3. In-Editor Schema Validation

Provide IDE-level diagnostics for rule files:

  • Flag unknown variables${doesNotExist} gets a yellow squiggle with “Unknown replacement variable”
  • Flag malformed conditionals — unclosed <if> blocks, unknown condition keys
  • Flag unreachable blocks<if os="commodore64"> flagged as “No matching runtime value”
  • Hover previews — hover over ${fileDir} to see the resolved value for the current file
  • Resolved preview — a command/panel that shows the final resolved content as the LLM will see it (like a “compiled” view)

This gives authors the same confidence they have editing tasks.json — you see problems before they silently break agent behavior.

Why This Matters

  • Multi-platform teams — the single biggest friction point for shared rules today. One team member on macOS, another on Windows, both need the same rule set. Currently requires install-time generation scripts or “just trust the model to figure it out.”
  • Skill evolution — as agent context matures, skills will nest deeper and reference more adjacent files (scripts, templates, configs). Without ${fileDir}, every reorg is a find-and-replace across all skills.
  • Debugging cost — today the only way to know a rule is broken is to observe the agent misbehaving, debug context and then manually trace it back. Schema validation catches issues at authoring time.
  • Zero model overhead — preprocessing means the LLM never wastes tokens parsing conditionals. It’s strictly better than putting <if> blocks in the prompt and asking the model to interpret them.

Prior Art

  • VSCode predefined variables: ${workspaceFolder}, ${file}, ${fileBasename}, etc. — battle-tested, familiar to the entire user base
  • C preprocessor #ifdef: conditional compilation resolved before the compiler sees the code — same principle
  • Terraform/CDK conditionals: infrastructure templates that resolve environment-specific values at synth time

Summary

Capability Status Today Proposed
Platform-specific rule content Manual duplication or install-time generation <if os="..."> resolved at ingestion
File-relative paths in rules Hardcoded absolute paths ${fileDir} replacement variable
Validation of rule syntax None (silent failures) IDE squiggles + resolved preview
Custom variables Not supported settings.json or .cursor/variables.json
4 Likes

So I’m starting to notice that using git (git grep, git ls-files, git ls-tree) and something like node or python can solve the cross platform issue and gives testing capacity.

Pre-processor directives are powerful however, but they should not be used for scripting. They would be good for variable toggling.

For example, let’s say I want to turn on a feature in my prompts, kind of like enabling voice mode on the grok prompts with the if statement. That context is injected when that item exists. This becomes powerful since plugins and skills can write to other skills / context conditionally or we can feature-flag pieces of a prompt. I fore-see the most mature skill building architectures will eventually require this.

One thing I would like, is that the .cursorignore also ignores within the skill folder if I put a reference there… so that I can control what is loaded in my repository, and automatically remove nested detection if I want to with just one glob in the .cursorignore.