|
| 1 | +--- |
| 2 | +name: skill-portability-checker |
| 3 | +version: "1.0" |
| 4 | +category: core |
| 5 | +description: Validates that a skill's companion scripts declare their OS and binary dependencies correctly, and checks whether those dependencies are actually present on the current machine. |
| 6 | +--- |
| 7 | + |
| 8 | +# Skill Portability Checker |
| 9 | + |
| 10 | +## What it does |
| 11 | + |
| 12 | +Skills with companion scripts (`.py`, `.sh`) can silently fail on machines where their dependencies aren't installed. A skill written on macOS may call `brew`, `pbcopy`, or use `/usr/local/bin` paths that don't exist on Linux. A Python script may `import pandas` on a system without it. |
| 13 | + |
| 14 | +Skill Portability Checker: |
| 15 | +1. Scans companion scripts for OS-specific patterns and external binary calls |
| 16 | +2. Checks whether those binaries are present on the current system (`PATH` lookup + `which`) |
| 17 | +3. Cross-checks against the skill's declared `os_filter:` frontmatter field (if any) |
| 18 | +4. Reports portability issues before the skill fails at runtime |
| 19 | + |
| 20 | +## Frontmatter field checked |
| 21 | + |
| 22 | +```yaml |
| 23 | +--- |
| 24 | +name: my-skill |
| 25 | +os_filter: [macos] # optional: ["macos", "linux", "windows"] |
| 26 | +--- |
| 27 | +``` |
| 28 | + |
| 29 | +If `os_filter:` is absent the skill is treated as cross-platform. The checker then warns if OS-specific calls are detected without a corresponding `os_filter:`. |
| 30 | + |
| 31 | +## Checks performed |
| 32 | + |
| 33 | +| Check | Description | |
| 34 | +|---|---| |
| 35 | +| OS_SPECIFIC_CALL | Script calls macOS/Linux/Windows-only binary without `os_filter:` | |
| 36 | +| MISSING_BINARY | Required binary not found on current system PATH | |
| 37 | +| BREW_ONLY | Script uses `brew` (macOS-only) but `os_filter:` includes non-macOS | |
| 38 | +| PYTHON_IMPORT | Script imports a non-stdlib module; checks if importable | |
| 39 | +| HARDCODED_PATH | Absolute path that doesn't exist on this machine (`/usr/local`, `C:\`) | |
| 40 | + |
| 41 | +## How to use |
| 42 | + |
| 43 | +```bash |
| 44 | +python3 check.py --check # Full portability scan |
| 45 | +python3 check.py --check --skill my-skill # Single skill |
| 46 | +python3 check.py --fix-hints my-skill # Print fix suggestions |
| 47 | +python3 check.py --format json |
| 48 | +``` |
| 49 | + |
| 50 | +## Procedure |
| 51 | + |
| 52 | +**Step 1 — Run the scan** |
| 53 | + |
| 54 | +```bash |
| 55 | +python3 check.py --check |
| 56 | +``` |
| 57 | + |
| 58 | +**Step 2 — Triage FAILs first** |
| 59 | + |
| 60 | +- **MISSING_BINARY**: The script calls a binary that isn't installed. Either install it or add a graceful fallback in the script. |
| 61 | +- **OS_SPECIFIC_CALL without os_filter**: Add `os_filter: [macos]` (or whichever OS applies) to the frontmatter so users on other platforms know the skill won't work. |
| 62 | + |
| 63 | +**Step 3 — Review WARNs** |
| 64 | + |
| 65 | +- **PYTHON_IMPORT**: Install the missing module or add a `try/except ImportError` with a graceful degradation path (like `HAS_MODULE = False`). |
| 66 | +- **HARDCODED_PATH**: Replace with `Path.home()` or environment-variable-based paths. |
| 67 | + |
| 68 | +**Step 4 — Add os_filter when needed** |
| 69 | + |
| 70 | +If a skill genuinely only works on one OS, declare it: |
| 71 | + |
| 72 | +```yaml |
| 73 | +os_filter: [macos] |
| 74 | +``` |
| 75 | +
|
| 76 | +This prevents the skill from being shown as broken on other platforms — it simply won't be loaded there. |
| 77 | +
|
| 78 | +## Output example |
| 79 | +
|
| 80 | +``` |
| 81 | +Skill Portability Report — linux (Python 3.11) |
| 82 | +──────────────────────────────────────────────── |
| 83 | +32 skills checked | 1 FAIL | 2 WARN |
| 84 | + |
| 85 | +FAIL obsidian-sync: MISSING_BINARY |
| 86 | + sync.py calls `osascript` — not found on this system |
| 87 | + Fix: add os_filter: [macos] to frontmatter (osascript is macOS-only) |
| 88 | + |
| 89 | +WARN morning-briefing: PYTHON_IMPORT |
| 90 | + run.py imports `pync` — not importable on this system |
| 91 | + Fix: wrap in try/except ImportError and degrade gracefully |
| 92 | +``` |
0 commit comments