Skip to content
Merged
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,24 @@ jobs:
if not text.strip():
raise ValueError(f'Empty markdown file: {path}')
PY
- name: Validate SKILL.md files (frontmatter + guardrail)
run: |
python3 - <<'PY'
from pathlib import Path
GUARDRAIL_PREFIX = '<!-- SECURITY GUARDRAIL:'
errors = []
skill_files = [f for f in Path('.').rglob('SKILL.md') if '.git' not in f.parts]
for skill_file in skill_files:
content = skill_file.read_text(encoding='utf-8')
if not content.startswith('---'):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate required frontmatter fields in SKILL checks

This check only verifies that SKILL.md starts with ---, so malformed or empty frontmatter still passes CI (for example, a file with just ---\n--- plus a guardrail comment). That means the workflow does not enforce the newly documented requirement that frontmatter include keys like name, description, and triggers, so invalid skill metadata can be merged unnoticed. Parse the frontmatter YAML block and assert required fields rather than using a prefix-only check.

Useful? React with 👍 / 👎.

errors.append(f'{skill_file}: missing YAML frontmatter')
elif GUARDRAIL_PREFIX not in content:
errors.append(f'{skill_file}: missing SECURITY GUARDRAIL comment')
Comment on lines +55 to +63
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new SKILL.md validator only checks that the file starts with --- and that '<!-- SECURITY GUARDRAIL:' appears somewhere in the file. This won’t catch malformed frontmatter (missing closing --- / invalid YAML), missing required keys (name/description/triggers), or a guardrail comment placed far away from the frontmatter—despite the docs implying those requirements. Consider parsing the frontmatter and validating required fields + ensuring the guardrail comment appears immediately after the frontmatter block (or adjust the docs/step name to match the looser checks).

Suggested change
GUARDRAIL_PREFIX = '<!-- SECURITY GUARDRAIL:'
errors = []
skill_files = [f for f in Path('.').rglob('SKILL.md') if '.git' not in f.parts]
for skill_file in skill_files:
content = skill_file.read_text(encoding='utf-8')
if not content.startswith('---'):
errors.append(f'{skill_file}: missing YAML frontmatter')
elif GUARDRAIL_PREFIX not in content:
errors.append(f'{skill_file}: missing SECURITY GUARDRAIL comment')
import yaml
GUARDRAIL_PREFIX = '<!-- SECURITY GUARDRAIL:'
REQUIRED_FRONTMATTER_KEYS = ('name', 'description', 'triggers')
errors = []
skill_files = [f for f in Path('.').rglob('SKILL.md') if '.git' not in f.parts]
for skill_file in skill_files:
content = skill_file.read_text(encoding='utf-8')
lines = content.splitlines()
if not lines or lines[0].strip() != '---':
errors.append(f'{skill_file}: missing YAML frontmatter opening delimiter')
continue
closing_index = None
for i in range(1, len(lines)):
if lines[i].strip() == '---':
closing_index = i
break
if closing_index is None:
errors.append(f'{skill_file}: missing YAML frontmatter closing delimiter')
continue
frontmatter_text = '\n'.join(lines[1:closing_index])
try:
frontmatter = yaml.safe_load(frontmatter_text)
except yaml.YAMLError as exc:
errors.append(f'{skill_file}: invalid YAML frontmatter ({exc})')
continue
if not isinstance(frontmatter, dict):
errors.append(f'{skill_file}: frontmatter must be a YAML mapping')
continue
missing_keys = [
key for key in REQUIRED_FRONTMATTER_KEYS
if key not in frontmatter or frontmatter[key] in (None, '', [])
]
if missing_keys:
errors.append(
f"{skill_file}: missing required frontmatter keys: {', '.join(missing_keys)}"
)
next_nonempty_line = None
for line in lines[closing_index + 1:]:
if line.strip():
next_nonempty_line = line
break
if next_nonempty_line is None:
errors.append(f'{skill_file}: missing SECURITY GUARDRAIL comment immediately after frontmatter')
elif not next_nonempty_line.startswith(GUARDRAIL_PREFIX):
errors.append(f'{skill_file}: SECURITY GUARDRAIL comment must appear immediately after frontmatter')

Copilot uses AI. Check for mistakes.
if errors:
for e in errors:
print(f'ERROR: {e}')
raise SystemExit(1)
print(f'All {len(skill_files)} SKILL.md files valid')
PY
- name: Check git diff hygiene
run: git diff --check
101 changes: 101 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Contributing to RTL-First Dev Kit

Thank you for improving the only production-grade RTL toolkit built for AI coding assistants.

## What lives in this repo

| Directory | Contents |
|-----------|----------|
| `tailwind/` | Tailwind 4.x logical-property mapping tables and usage patterns |
| `flutter/` | Flutter directional layout API patterns and migration notes |
| `nextjs/` | Next.js `dir` propagation, App Router, and i18n routing patterns |
| `biome/` | Biome lint policy rules that block physical-direction regressions |
| `ci/` | CI workflow snippets for RTL validation in real pipelines |
| `skills/` | Claude Code / Gemini CLI skill files that load this toolkit automatically |
| `cheatsheet/` | Quick-reference cards for the most common patterns |
Comment on lines +7 to +15
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The markdown table under “What lives in this repo” has an extra leading | on each row (e.g., || Directory | Contents |), which renders as an unintended empty column in GitHub-flavored Markdown. Update the table rows to use a single leading pipe (| Directory | Contents |, etc.).

Copilot uses AI. Check for mistakes.

---

## How to contribute

### 1. Adding a new mapping or pattern

1. Open the relevant file (`tailwind/mapping.md`, `flutter/patterns.md`, etc.).
2. Follow the existing table or code-block format exactly — no custom formatting.
3. Always include both the **physical** anti-pattern and the **logical** equivalent side-by-side.
4. If you add a Flutter pattern, test it in an actual `Directionality` context. If you add a Tailwind pattern, verify it against Tailwind 4.x (not v3).

### 2. Fixing an example

Open a pull request with the corrected snippet. In the PR description state:
- Which file and line changed
- Why the old example was wrong (e.g., "was using `ml-` which is physical")
- What it should be (e.g., "should use `ms-` which is logical")

### 3. Creating a new skill file

A skill file teaches AI coding assistants how to apply this toolkit automatically. Each skill lives at `skills/<skill-name>/SKILL.md`.

Every `SKILL.md` must:

1. **Start with YAML frontmatter** (`---` block) containing at least `name`, `description`, and `triggers`.
2. **Include the security guardrail comment** immediately after the frontmatter closing `---`:
```
<!-- SECURITY GUARDRAIL: ... -->
```
The comment text should describe what the skill does and does not do. See `skills/rtl-fix/SKILL.md` for a concrete example.
3. **List specific triggers** — phrases that should cause an AI assistant to load this skill automatically (e.g., `"rtl fix"`, `"code review"`, `"tsx file edit"`).

CI will fail if either requirement is missing.
Comment on lines +41 to +49
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The contribution guidelines say SKILL.md frontmatter must include specific keys and that the SECURITY GUARDRAIL comment must appear immediately after the closing ---, but the validation snippet below (and the CI step) only checks content.startswith('---') and that the guardrail prefix appears somewhere. Either tighten the validator to match these documented requirements (parse the frontmatter and verify keys + placement), or relax/clarify the docs so they match what CI actually enforces.

Copilot uses AI. Check for mistakes.

### 4. Improving CI or the Biome policy

- `ci/` contains workflow snippets, not the actual `.github/workflows/` files. Improvements to the repo's own CI go directly to `.github/workflows/ci.yml`.
- Biome rule changes go to `biome/policy.md` with an explanation of which physical-direction pattern they catch and why it cannot be auto-fixed.

---

## Coding conventions

- **No `ml-`, `mr-`, `pl-`, `pr-`, `left-`, `right-`** in any example unless you are explicitly showing the anti-pattern.
- All anti-pattern examples must be clearly marked with a `// BEFORE` or `BAD:` comment.
- All logical-property examples must be marked with `// AFTER` or `GOOD:`.
- Keep snippets short enough to copy directly into a project. If an example needs more than ~20 lines, split it.

---

## Pull request checklist

- [ ] CI passes locally (run `python3 .github/validate-skills.py` if available, or check the CI log)
- [ ] All new `SKILL.md` files have frontmatter and a `<!-- SECURITY GUARDRAIL:` comment
- [ ] No physical-direction classes appear in "correct" examples
- [ ] PR description explains the change in one sentence

---

## Running CI validation locally

```bash
python3 - <<'PY'
from pathlib import Path
GUARDRAIL_PREFIX = '<!-- SECURITY GUARDRAIL:'
errors = []
skill_files = [f for f in Path('.').rglob('SKILL.md') if '.git' not in f.parts]
for skill_file in skill_files:
content = skill_file.read_text(encoding='utf-8')
if not content.startswith('---'):
errors.append(f'{skill_file}: missing YAML frontmatter')
elif GUARDRAIL_PREFIX not in content:
errors.append(f'{skill_file}: missing SECURITY GUARDRAIL comment')
if errors:
for e in errors: print(f'ERROR: {e}')
raise SystemExit(1)
print(f'All {len(skill_files)} SKILL.md files valid')
PY
```

---

## License

By contributing you agree your changes are licensed under the [MIT License](LICENSE) already covering this repository.
Loading