(file, content)
| 777 | } |
| 778 | |
| 779 | function checkRuleFrontmatter(file, content) { |
| 780 | const frontmatter = parseFrontmatter(content); |
| 781 | if (!frontmatter) { |
| 782 | addFailure({ |
| 783 | ruleId: "rule-frontmatter/required", |
| 784 | title: "Rule files must start with YAML frontmatter", |
| 785 | file, |
| 786 | problem: `${file} is missing YAML frontmatter. rules/*.mdc files must begin with YAML frontmatter that includes \`description\`, \`globs\`, and \`alwaysApply\` (true/false).`, |
| 787 | why: "Frontmatter gives Cursor and reviewers a predictable rule summary, scope, and apply behavior.", |
| 788 | fix: `Copy this example and adjust the values:\n${ruleFrontmatterExample}`, |
| 789 | }); |
| 790 | return; |
| 791 | } |
| 792 | |
| 793 | const fields = {}; |
| 794 | for (const field of requiredRuleFrontmatterFields) { |
| 795 | const value = readFrontmatterField(frontmatter, field); |
| 796 | if (value === null) { |
| 797 | addFailure({ |
| 798 | ruleId: "rule-frontmatter/field-required", |
| 799 | title: "Rule frontmatter must include all required fields", |
| 800 | file, |
| 801 | problem: `${file} is missing required YAML frontmatter field \`${field}\`. Required fields for rules/*.mdc: \`description\`, \`globs\`, \`alwaysApply\`.`, |
| 802 | why: "Missing frontmatter fields make rule scope and application behavior ambiguous for both maintainers and AI tooling.", |
| 803 | fix: "Add `description`, `globs`, and `alwaysApply`. Add `alwaysApply: false` for scoped rules, and `alwaysApply: true` only for rules that should always apply.", |
| 804 | }); |
| 805 | continue; |
| 806 | } |
| 807 | fields[field] = value; |
| 808 | } |
| 809 | |
| 810 | if (fields.description !== undefined && stripYamlQuotes(fields.description).trim().length === 0) { |
| 811 | addFailure({ |
| 812 | ruleId: "rule-frontmatter/description-required", |
| 813 | title: "Rule descriptions must be non-empty", |
| 814 | file, |
| 815 | problem: `${file} frontmatter field \`description\` is empty.`, |
| 816 | why: "The description is the quickest human and agent signal for what the rule is supposed to help Cursor do.", |
| 817 | fix: "Add a short explanation of what the rule helps Cursor do.", |
| 818 | }); |
| 819 | } |
| 820 | |
| 821 | if (fields.alwaysApply !== undefined && !["true", "false"].includes(fields.alwaysApply)) { |
| 822 | addFailure({ |
| 823 | ruleId: "rule-frontmatter/always-apply-boolean", |
| 824 | title: "Rule alwaysApply must be boolean text", |
| 825 | file, |
| 826 | problem: `${file} frontmatter field \`alwaysApply\` must be exactly \`true\` or \`false\`.`, |
| 827 | why: "Cursor rule application needs an unambiguous true/false value; values like `maybe` are not actionable.", |
| 828 | fix: "Use `alwaysApply: false` for scoped rules, or `alwaysApply: true` only for rules that should always apply.", |
| 829 | }); |
| 830 | } |
| 831 | |
| 832 | if ( |
| 833 | fields.globs !== undefined && |
| 834 | stripYamlQuotes(fields.globs).trim().length === 0 && |
| 835 | fields.alwaysApply !== "true" |
| 836 | ) { |
no test coverage detected