← Files Realize PluginARCHIVED FILE

.github/workflows/validate.yml

6.63 KB · Nov 12, 2026 · 12:09 UTC

↓ Download file

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