← Files Realize PluginARCHIVED FILE
.github/workflows/validate.yml
6.63 KB · Nov 12, 2026 · 12:09 UTC
name: validate
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate JSON files
run: |
set -euo pipefail
for f in .claude-plugin/plugin.json .claude-plugin/marketplace.json .codex-plugin/plugin.json .mcp.json; do
echo "Checking $f"
python3 -m json.tool "$f" > /dev/null
done
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install PyYAML
run: pip install pyyaml
- name: Validate YAML frontmatter on agents and skills
run: |
python3 - <<'PY'
import os, sys, yaml
# Required key -> expected type (None means any type)
AGENT_REQUIRED = {
"name": str,
"description": str,
"model": str,
"tools": list,
}
SKILL_REQUIRED = {
"name": str,
"description": str,
"allowed-tools": list,
}
errors = []
def check(path, required):
with open(path, "r", encoding="utf-8") as fh:
text = fh.read()
if not text.startswith("---"):
errors.append(f"{path}: missing YAML frontmatter")
return
parts = text.split("---", 2)
if len(parts) < 3:
errors.append(f"{path}: unterminated frontmatter")
return
try:
meta = yaml.safe_load(parts[1]) or {}
except yaml.YAMLError as e:
errors.append(f"{path}: invalid YAML ({e})")
return
for key, expected_type in required.items():
if key not in meta:
errors.append(f"{path}: missing required key '{key}'")
elif expected_type is not None and not isinstance(meta[key], expected_type):
errors.append(
f"{path}: key '{key}' must be {expected_type.__name__}, "
f"got {type(meta[key]).__name__}"
)
for root, _, files in os.walk("agents"):
for f in files:
if f.endswith(".md"):
check(os.path.join(root, f), AGENT_REQUIRED)
for root, _, files in os.walk("skills"):
for f in files:
if f == "SKILL.md":
check(os.path.join(root, f), SKILL_REQUIRED)
if errors:
print("\n".join(errors))
sys.exit(1)
print("All frontmatter valid.")
PY
- name: Support-bundle unit tests
run: node skills/support/scripts/test-build-bundle.js
- name: Pixel page-fetch unit tests
run: node skills/diagnose-tracking/scripts/test-fetch-page.js
- name: Brand and guardrail lint
run: bash scripts/brand-check.sh
# The Anthropic directory check blocked v0.5.1 on these. Everything in the
# repo ships, so they are checked repo-wide. See .claude/CLAUDE.md,
# "Shipped files must pass the Anthropic directory check".
- name: Directory-check guards
run: |
set -euo pipefail
fail=0
# ROOT_CLAUDE_MD: contributor notes live in .claude/CLAUDE.md.
if [ -f CLAUDE.md ]; then
echo "FAIL: CLAUDE.md at the plugin root (ROOT_CLAUDE_MD). Keep contributor notes in .claude/CLAUDE.md."
fail=1
fi
# SECRET_IN_SCRIPT: vendor-prefixed keys, and a credential key name
# next to a literal value. Test fixtures must be assembled at runtime.
prefixes='sk-ant-[A-Za-z0-9]|ya29\.[A-Za-z0-9]|eyJ[A-Za-z0-9_-]{10,}\.eyJ|AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{20}|xox[abps]-[A-Za-z0-9]|-----BEGIN [A-Z ]*PRIVATE KEY'
keyed='(password|passwd|client_secret|api_?key|access_token|refresh_token)["'"'"']?[[:space:]]*[:=][[:space:]]*["'"'"']?[A-Za-z0-9._-]{6,}'
if git grep -nIE "$prefixes|$keyed" -- . ':!*.png'; then
echo "FAIL: credential-shaped literal(s) above (SECRET_IN_SCRIPT). Build test fixtures at runtime."
fail=1
fi
# ALLOWED_TOOLS_BROAD / unscoped fetch: a skill's allowed-tools is a
# pre-approval (no prompt), so it is an allowlist, not a denylist:
# anything not matched below fails. Bash only for the skill's own
# script, in the exact quoted form the skill tells the model to run.
# Agents' `tools:` is not checked: it lists what an agent may call,
# and permission prompts still apply to it.
python3 - <<'PY' || fail=1
import glob, os, re, sys, yaml
PLAIN = {"Read", "Grep", "Glob", "AskUserQuestion", "WebSearch"}
# The help-center fallback is the only sanctioned fetch target.
WEBFETCH = {"WebFetch(domain:realize.com)", "WebFetch(domain:www.realize.com)"}
bad = []
for p in sorted(glob.glob("skills/*/SKILL.md")):
skill = os.path.basename(os.path.dirname(p))
meta = yaml.safe_load(open(p, encoding="utf-8").read().split("---", 2)[1]) or {}
# The skill's own script plus the exact arguments the skill runs it
# with. Each argument is a flag or a plain value; a value may hold
# one `*` inside a filename (complaint-*.txt), never a bare `*`,
# `:*`, `..`, or the write-anywhere flags.
own_script = re.compile(
r'Bash\(node "\$\{CLAUDE_PLUGIN_ROOT\}/skills/' + re.escape(skill)
+ r'/scripts/[A-Za-z0-9_-]+\.js"((?: [A-Za-z0-9_~./-]+(?:\*[A-Za-z0-9_./-]+)?)*)\)')
def own_script_ok(t):
m = own_script.fullmatch(t)
if not m:
return False
args = m.group(1).split()
return ".." not in m.group(1) and not any(
a in ("--out", "--force", "--allow-git") for a in args)
for t in meta.get("allowed-tools", []):
ok = (
t in PLAIN
or t in WEBFETCH
or t == "Edit(~/.realize-plugin/tmp/**)"
or own_script_ok(t)
)
if not ok:
bad.append(f"{p}: {t}")
if bad:
sys.exit("FAIL: allowed-tools entries outside the allowlist (ALLOWED_TOOLS_BROAD):\n " + "\n ".join(bad))
print("allowed-tools OK")
PY
exit "$fail"
SHA-256: d0abe17b36377c6d3e0e752230c13d1845611fee32fd682170de297b4f42d112