Repository navigation
feat(repo-ops): renumber-migration tool, gated on commands.migrationG… #178
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: validate | |
| # This workflow runs scripts from the checked-out ref, including on pull requests. | |
| # Least-privilege token, and no credentials left in .git/config for those scripts to reach. | |
| permissions: | |
| contents: read | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| jobs: | |
| validate: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false | |
| - name: Manifests are valid JSON, and match the marketplace | |
| run: | | |
| python3 - <<'EOF' | |
| import json, sys, pathlib | |
| mp = json.loads(pathlib.Path('.claude-plugin/marketplace.json').read_text()) | |
| fail = False | |
| for entry in mp['plugins']: | |
| src = entry['source'].lstrip('./') | |
| manifest = pathlib.Path(src) / '.claude-plugin' / 'plugin.json' | |
| if not manifest.exists(): | |
| print(f"FAIL {entry['name']}: no manifest at {manifest}"); fail = True; continue | |
| pj = json.loads(manifest.read_text()) | |
| if pj['name'] != entry['name']: | |
| print(f"FAIL {src}: manifest name {pj['name']!r} != marketplace {entry['name']!r}"); fail = True | |
| if pj.get('version') != entry.get('version'): | |
| print(f"FAIL {entry['name']}: version {pj.get('version')!r} != marketplace {entry.get('version')!r}"); fail = True | |
| sys.exit(1 if fail else 0) | |
| EOF | |
| - name: Agent Plugins v1.0.0 manifests match and stay within the closed schema | |
| run: | | |
| python3 - <<'EOF' | |
| import json, sys, pathlib | |
| ALLOWED = { | |
| '$schema', 'name', 'version', 'description', 'author', | |
| 'homepage', 'repository', 'license', 'keywords', 'extensions', | |
| } | |
| SCHEMA_URL = 'https://agent-plugins.org/schemas/1.0.0/plugin.schema.json' | |
| mp = json.loads(pathlib.Path('.claude-plugin/marketplace.json').read_text()) | |
| fail = False | |
| for entry in mp['plugins']: | |
| src = entry['source'].lstrip('./') | |
| manifest = pathlib.Path(src) / 'plugin.json' | |
| if not manifest.exists(): | |
| continue # optional: not every plugin has to opt into the open spec | |
| pj = json.loads(manifest.read_text()) | |
| extra = set(pj) - ALLOWED | |
| if extra: | |
| print(f"FAIL {manifest}: fields outside the closed schema: {sorted(extra)}"); fail = True | |
| if pj.get('$schema') != SCHEMA_URL: | |
| print(f"FAIL {manifest}: \\$schema is {pj.get('$schema')!r}, expected {SCHEMA_URL!r}"); fail = True | |
| if pj.get('name') != entry['name']: | |
| print(f"FAIL {manifest}: name {pj.get('name')!r} != marketplace {entry['name']!r}"); fail = True | |
| if pj.get('version') != entry.get('version'): | |
| print(f"FAIL {manifest}: version {pj.get('version')!r} != marketplace {entry.get('version')!r}"); fail = True | |
| sys.exit(1 if fail else 0) | |
| EOF | |
| - name: Every skill has frontmatter with name and description | |
| run: | | |
| python3 - <<'EOF' | |
| import sys, pathlib, re | |
| fail = False | |
| for skill in pathlib.Path('plugins').glob('*/skills/*/SKILL.md'): | |
| text = skill.read_text() | |
| m = re.match(r'^---\n(.*?)\n---\n', text, re.S) | |
| if not m: | |
| print(f"FAIL {skill}: no YAML frontmatter"); fail = True; continue | |
| fm = m.group(1) | |
| if fm != fm.strip() or re.search(r'(?m)^[ \t]*$', fm): | |
| print(f"FAIL {skill}: blank line inside frontmatter"); fail = True | |
| for field in ('name', 'description'): | |
| if not re.search(rf'^{field}:', fm, re.M): | |
| print(f"FAIL {skill}: frontmatter missing {field}"); fail = True | |
| sys.exit(1 if fail else 0) | |
| EOF | |
| - name: The coverage-ratchet shell helpers behave, and fail closed | |
| run: ./scripts/test-coverage.sh | |
| - name: The repo-profile helpers resolve and diff correctly, and fail closed | |
| run: ./scripts/test-profile.sh | |
| - name: The PR-template helper checks headings and resolves prTemplateSource correctly | |
| run: ./scripts/test-pr-template-check.sh | |
| - name: The repo-ops pr-template-guard and skip-label-race-guard hooks behave, and fail closed | |
| run: ./scripts/test-repo-ops-hooks.sh | |
| - name: The repo-ops wait-for-review and wait-for-checks tools report each outcome, and never act | |
| run: ./scripts/test-wait-tools.sh | |
| - name: The repo-ops renumber-migration tool re-parents onto the base head, idempotently, and refuses what it cannot do safely | |
| run: ./scripts/test-renumber-migration.sh | |
| - name: The research-radar harvester fails closed, windows from its mark, and dedupes | |
| run: python3 scripts/test-research-radar.py | |
| - name: The auto-dev tick's digest-skip filter drops planner digest issues | |
| run: ./scripts/test-auto-dev-digest-skip.sh | |
| - name: A documented `gh pr merge --auto` always pins the head commit | |
| run: python3 scripts/check-merge-arming.py | |
| - name: Vendored reference docs are in sync with the canonical | |
| run: ./scripts/sync-references.sh --check | |
| - name: Links resolve in both layouts; prose section references still exist | |
| run: python3 scripts/check-links.py |