chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
name: Claude Code Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, ready_for_review, reopened]
|
||||
|
||||
jobs:
|
||||
claude-review:
|
||||
if: |
|
||||
github.event.pull_request.draft == false &&
|
||||
github.actor != 'dependabot[bot]' &&
|
||||
github.event.pull_request.user.login != 'dependabot[bot]' &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run Claude Code Review
|
||||
id: claude-review
|
||||
uses: anthropics/claude-code-action@4481e6d3c7bbb88db2a928ca3444c536f589c7c1 # v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
track_progress: true
|
||||
|
||||
prompt: |
|
||||
REPO: ${{ github.repository }}
|
||||
PR NUMBER: ${{ github.event.pull_request.number }}
|
||||
|
||||
You are reviewing a PR for claude-agents — a multi-harness
|
||||
agentic plugin marketplace (Claude Code, Codex CLI, Cursor,
|
||||
OpenCode, Gemini CLI) with 82+ plugins, 191+ agents, 155+
|
||||
skills, and 102+ commands. The source of truth is Markdown
|
||||
under `plugins/`; per-harness artifacts under `.codex/`,
|
||||
`.cursor-plugin/`, `.cursor/rules/`, `.opencode/`, and the
|
||||
top-level `skills/`/`agents/`/`commands/` directories are
|
||||
generated by `make generate HARNESS=<name>` and are
|
||||
gitignored.
|
||||
|
||||
Read `AGENTS.md` at the repo root (canonical context) before
|
||||
starting. Consult `docs/authoring.md` for plugin / agent /
|
||||
skill frontmatter shapes, `docs/harnesses.md` for per-harness
|
||||
capability deltas, and `docs/plugins.md` for the catalog.
|
||||
|
||||
## Review checklist
|
||||
|
||||
1. **Source-of-truth invariant** — Only `plugins/`,
|
||||
`.claude-plugin/marketplace.json`, `docs/`, `tools/`, and
|
||||
top-level Markdown (`AGENTS.md`, `CLAUDE.md`, `GEMINI.md`,
|
||||
`README.md`, `ARCHITECTURE.md`, `CONTRIBUTING.md`) should
|
||||
be hand-edited. Flag any change under `.codex/`,
|
||||
`.cursor-plugin/`, `.cursor/rules/`, `.opencode/`, or
|
||||
top-level `skills/`/`agents/`/`commands/` at the extension
|
||||
root — those are generated artifacts and must not be
|
||||
committed by hand.
|
||||
|
||||
2. **Plugin / agent / skill frontmatter** — Every agent under
|
||||
`plugins/*/agents/*.md` needs `name:`, `description:`, and
|
||||
a `model:` tier. Every skill under
|
||||
`plugins/*/skills/*/SKILL.md` needs `name:` and
|
||||
`description:`. Plugin directory names must be lowercase,
|
||||
hyphen-separated, and must NOT contain `__` (that is the
|
||||
adapter namespace separator — see `docs/authoring.md`).
|
||||
|
||||
3. **Cross-harness portability** — Content should work across
|
||||
all five harnesses unless explicitly marked Claude-Code-
|
||||
only in `CLAUDE.md`. Watch for hard dependencies on
|
||||
Claude-Code-only primitives (`TodoWrite`, the `Task` /
|
||||
`Agent` spawn tool, per-agent `tools:` frontmatter) without
|
||||
a documented fallback. Locked agents (`tools: []`) get
|
||||
special-cased by the OpenCode adapter — preserve that
|
||||
contract.
|
||||
|
||||
4. **Codex 8 KB skill body cap** — Skill bodies in
|
||||
`plugins/*/skills/*/SKILL.md` should fit under ~8 KB after
|
||||
adapter transpilation; overflow belongs in
|
||||
`references/details.md`. `make garden` flags oversize
|
||||
skills; if a new or edited skill is borderline, suggest
|
||||
splitting before merge.
|
||||
|
||||
5. **Canonical context sync** — `AGENTS.md` is the single
|
||||
source of truth; `CLAUDE.md` imports it via `@AGENTS.md`
|
||||
and `GEMINI.md` mirrors it. If any of the three diverge in
|
||||
this PR, call it out. Per OpenAI's harness-engineering
|
||||
practice, `AGENTS.md` must stay under ~150 lines — detail
|
||||
belongs in `docs/`.
|
||||
|
||||
6. **Quality gates** — Will this break
|
||||
`make validate STRICT=1`, `make garden`, `make test`, or
|
||||
`make smoke-test`? Reference the relevant gate by name in
|
||||
your finding so the author knows what to run.
|
||||
|
||||
7. **Catalog drift** — New, removed, or renamed plugins,
|
||||
agents, skills, or commands should be reflected in
|
||||
`docs/plugins.md`, `docs/agents.md`, and
|
||||
`docs/agent-skills.md` counts and lists. The plugin total
|
||||
in `AGENTS.md` and `README.md` should also stay in sync.
|
||||
|
||||
8. **Python tooling correctness** — Code under `tools/` uses
|
||||
uv + ruff + ty (NOT pip / mypy / black). Flag any
|
||||
reintroduction of `pip`, `requirements.txt`, `mypy`, or
|
||||
`black`. Watch for unhandled errors in adapter code,
|
||||
broken JSON in `plugin.json` or `marketplace.json`, and
|
||||
missing tests under `tools/tests/`.
|
||||
|
||||
9. **Security** — No secrets in code or workflow files; no
|
||||
destructive git in scripts (`push --force`,
|
||||
`reset --hard`, `branch -D`); no shell injection in
|
||||
`Bash(...)` allowlists or hook scripts; pinned action SHAs
|
||||
and `persist-credentials: false` on any new GitHub
|
||||
workflow (match the style in
|
||||
`.github/workflows/validate.yml`).
|
||||
|
||||
## Output rules
|
||||
|
||||
- Use inline comments
|
||||
(`mcp__github_inline_comment__create_inline_comment`) for
|
||||
specific issues tied to a line of code.
|
||||
- Use `gh pr comment` for a short summary (≤ 10 lines) at
|
||||
the end.
|
||||
- If no critical issues are found, post a single one-line
|
||||
✅ summary via `gh pr comment` and stop — do NOT attempt to
|
||||
submit a formal review approval (no review-submission tool
|
||||
is exposed).
|
||||
- Do NOT comment on formatting, import order, or naming
|
||||
style — `ruff` and `ty` handle that.
|
||||
- Do NOT suggest documentation or comment additions unless a
|
||||
public-facing doc count or catalog entry is wrong.
|
||||
- Do NOT re-raise issues already flagged by `coderabbitai`
|
||||
or `chatgpt-codex-connector` in this PR's existing
|
||||
comments — read them first via
|
||||
`gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/comments`.
|
||||
- Be concise: one sentence per finding is enough. Prefer
|
||||
actionable suggestions (diff blocks) over prose.
|
||||
|
||||
claude_args: |
|
||||
--model claude-opus-4-7
|
||||
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh api repos/*/pulls/*/comments:*)"
|
||||
@@ -0,0 +1,79 @@
|
||||
name: Claude Code
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
issues:
|
||||
# `edited` (not `assigned`) is the actionable event here: the `if:`
|
||||
# block below checks `github.event.issue.body` / `.title` for
|
||||
# `@claude`, which only changes on edit. Reassignment would fire
|
||||
# the workflow without any matching body/title change.
|
||||
types: [opened, edited]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
claude:
|
||||
# Gate on author_association so only repo owners / members /
|
||||
# collaborators (who already have write access) can trigger this
|
||||
# workflow — required because the job is granted write scopes below.
|
||||
if: |
|
||||
(
|
||||
github.event_name == 'issue_comment' &&
|
||||
contains(github.event.comment.body, '@claude') &&
|
||||
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
|
||||
) ||
|
||||
(
|
||||
github.event_name == 'pull_request_review_comment' &&
|
||||
contains(github.event.comment.body, '@claude') &&
|
||||
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
|
||||
) ||
|
||||
(
|
||||
github.event_name == 'pull_request_review' &&
|
||||
contains(github.event.review.body, '@claude') &&
|
||||
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association)
|
||||
) ||
|
||||
(
|
||||
github.event_name == 'issues' &&
|
||||
(contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) &&
|
||||
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association)
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
issues: write
|
||||
id-token: write
|
||||
actions: read # Required for Claude to read CI results on PRs
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@4481e6d3c7bbb88db2a928ca3444c536f589c7c1 # v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
|
||||
# This is an optional setting that allows Claude to read CI results on PRs
|
||||
additional_permissions: |
|
||||
actions: read
|
||||
|
||||
# Tools tailored to this repo's uv-native Python toolchain.
|
||||
# See AGENTS.md for the canonical command reference.
|
||||
#
|
||||
# Both `gh` and `git` are enumerated to safe subcommands only.
|
||||
# Writes that need a commit/branch are handled by the action
|
||||
# itself via the GitHub API (see action.yml `branch_prefix` /
|
||||
# `branch_name_template` inputs) — local `git add/commit/push`
|
||||
# is not in the allowlist, so destructive variants
|
||||
# (`push --force`, `reset --hard`, `branch -D`, `tag -d`,
|
||||
# `checkout -- .`, `clean -fd`) are unreachable.
|
||||
claude_args: |
|
||||
--model claude-opus-4-7
|
||||
--allowedTools "Edit,Write,Read,Bash(uv:*),Bash(make:*),Bash(python:*),Bash(python3:*),Bash(ruff:*),Bash(ty:*),Bash(pytest:*),Bash(gh pr view:*),Bash(gh pr diff:*),Bash(gh pr list:*),Bash(gh pr comment:*),Bash(gh pr edit:*),Bash(gh pr checks:*),Bash(gh issue view:*),Bash(gh issue list:*),Bash(gh issue comment:*),Bash(gh issue edit:*),Bash(gh api repos/*/pulls/*/comments:*),Bash(gh api repos/*/issues/*/comments:*),Bash(gh api repos/*/issues/*/timeline:*),Bash(gh run view:*),Bash(gh workflow view:*),Bash(gh search:*),Bash(git status:*),Bash(git diff:*),Bash(git log:*),Bash(git show:*),Bash(git rev-parse:*),Bash(git ls-files:*),mcp__github_inline_comment__create_inline_comment"
|
||||
@@ -0,0 +1,162 @@
|
||||
name: Code Quality
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
# Read-only lint workflow — no writes back to the repo, no deployments.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
python-lint:
|
||||
name: Python (ruff + ty)
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: plugins/plugin-eval
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- name: Set up Python
|
||||
run: uv python install
|
||||
|
||||
- name: Sync plugin-eval dev dependencies
|
||||
run: uv sync --all-extras
|
||||
|
||||
- name: ruff check (lint)
|
||||
# Scope: adapter framework + plugin-eval. yt-design-extractor.py is legacy
|
||||
# and out of scope for the multi-harness work; not gated.
|
||||
run: |
|
||||
uv run ruff check \
|
||||
../../tools/adapters/ \
|
||||
../../tools/generate.py \
|
||||
../../tools/validate_generated.py \
|
||||
../../tools/doc_gardener.py \
|
||||
../../tools/tests/ \
|
||||
src/plugin_eval/
|
||||
|
||||
- name: ruff format --check
|
||||
run: |
|
||||
uv run ruff format --check \
|
||||
../../tools/adapters/ \
|
||||
../../tools/generate.py \
|
||||
../../tools/validate_generated.py \
|
||||
../../tools/doc_gardener.py \
|
||||
../../tools/tests/ \
|
||||
src/plugin_eval/
|
||||
|
||||
- name: ty type-check
|
||||
run: |
|
||||
uv run ty check \
|
||||
../../tools/adapters/ \
|
||||
../../tools/generate.py \
|
||||
../../tools/validate_generated.py \
|
||||
../../tools/doc_gardener.py \
|
||||
../../tools/tests/ \
|
||||
src/plugin_eval/
|
||||
|
||||
markdown-lint:
|
||||
name: Markdown (markdownlint-cli2)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: markdownlint
|
||||
# Lints top-level guides (README, AGENTS, ARCHITECTURE, CLAUDE, per-harness
|
||||
# setup, CONTRIBUTING) and our authored docs/. Per-plugin READMEs are owned
|
||||
# by their plugin authors and not lint-gated here — they should be authored
|
||||
# to spec, but lint enforcement happens at the plugin-author layer, not at
|
||||
# the framework PR layer.
|
||||
# Config in .markdownlint.json keeps the ruleset narrow and pragmatic.
|
||||
uses: DavidAnson/markdownlint-cli2-action@eb5ca3ab411449c66620fe7f1b3c9e10547144b0 # v18
|
||||
with:
|
||||
globs: |
|
||||
*.md
|
||||
docs/*.md
|
||||
config: .markdownlint.json
|
||||
|
||||
json-lint:
|
||||
name: JSON / TOML / YAML syntax
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- name: Set up Python
|
||||
run: uv python install 3.12
|
||||
|
||||
- name: Validate every JSON file
|
||||
run: |
|
||||
set -e
|
||||
shopt -s globstar nullglob
|
||||
failed=0
|
||||
for f in **/*.json; do
|
||||
# Skip generated trees and node_modules
|
||||
case "$f" in
|
||||
.codex/*|.cursor/*|.cursor-plugin/*|.opencode/*|node_modules/*|skills/*|agents/*|commands/*) continue ;;
|
||||
esac
|
||||
if ! uv run python -m json.tool "$f" > /dev/null 2>&1; then
|
||||
echo "::error file=$f::invalid JSON"
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
exit $failed
|
||||
|
||||
- name: Validate every TOML file
|
||||
run: |
|
||||
set -e
|
||||
shopt -s globstar nullglob
|
||||
failed=0
|
||||
for f in **/*.toml; do
|
||||
case "$f" in
|
||||
.codex/*|.cursor/*|.cursor-plugin/*|.opencode/*|node_modules/*|commands/*) continue ;;
|
||||
esac
|
||||
if ! uv run python -c "import tomllib, sys; tomllib.loads(open(sys.argv[1]).read())" "$f" 2>/dev/null; then
|
||||
echo "::error file=$f::invalid TOML"
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
exit $failed
|
||||
|
||||
- name: Validate every YAML file
|
||||
run: |
|
||||
set -e
|
||||
shopt -s globstar nullglob
|
||||
failed=0
|
||||
for f in **/*.yml **/*.yaml; do
|
||||
case "$f" in
|
||||
.codex/*|.cursor/*|.cursor-plugin/*|.opencode/*|node_modules/*) continue ;;
|
||||
esac
|
||||
# Use safe_load_all to handle multi-document YAML (e.g. Kubernetes manifests
|
||||
# with `---` document separators — valid YAML, but safe_load only reads the
|
||||
# first doc and would mis-flag the file as invalid).
|
||||
if ! uv run --with pyyaml python -c "import yaml, sys; list(yaml.safe_load_all(open(sys.argv[1]).read()))" "$f" 2>/dev/null; then
|
||||
echo "::error file=$f::invalid YAML"
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
exit $failed
|
||||
@@ -0,0 +1,73 @@
|
||||
name: Plugin Eval Report
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
depth:
|
||||
description: 'Evaluation depth (quick = static only, standard = +LLM judge, deep = +Monte Carlo)'
|
||||
required: true
|
||||
default: 'quick'
|
||||
type: choice
|
||||
options:
|
||||
- quick
|
||||
- standard
|
||||
- deep
|
||||
only_changed:
|
||||
description: 'Comma-separated plugin names to evaluate (blank = all)'
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
schedule:
|
||||
# Weekly full static sweep, Mondays at 06:00 UTC
|
||||
- cron: '0 6 * * 1'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
eval:
|
||||
name: Evaluate plugins (${{ inputs.depth || 'quick' }})
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 180
|
||||
env:
|
||||
DEPTH: ${{ inputs.depth || 'quick' }}
|
||||
ONLY_CHANGED: ${{ inputs.only_changed || '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- name: Sync plugin-eval dependencies
|
||||
working-directory: plugins/plugin-eval
|
||||
run: uv sync --all-extras
|
||||
|
||||
- name: Run eval sweep
|
||||
working-directory: plugins/plugin-eval
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
run: |
|
||||
args=( --depth "$DEPTH" --output-dir "$GITHUB_WORKSPACE/eval-reports" --concurrency 4 )
|
||||
if [ -n "$ONLY_CHANGED" ]; then
|
||||
args+=( --only-changed "$ONLY_CHANGED" )
|
||||
fi
|
||||
uv run python scripts/eval_all.py "${args[@]}"
|
||||
|
||||
- name: Post report to job summary
|
||||
if: always()
|
||||
run: |
|
||||
if [ -f eval-reports/summary.md ]; then
|
||||
cat eval-reports/summary.md >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "No summary produced." >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
- name: Upload reports artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: eval-reports-${{ env.DEPTH }}-${{ github.run_id }}
|
||||
path: eval-reports/
|
||||
retention-days: 30
|
||||
@@ -0,0 +1,272 @@
|
||||
name: Validate
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validate-json:
|
||||
name: Validate JSON files
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Validate marketplace.json
|
||||
run: python3 -m json.tool .claude-plugin/marketplace.json > /dev/null
|
||||
|
||||
- name: Validate every plugin.json
|
||||
run: |
|
||||
set -e
|
||||
shopt -s globstar nullglob
|
||||
failed=0
|
||||
for f in plugins/*/.claude-plugin/plugin.json; do
|
||||
if ! python3 -m json.tool "$f" > /dev/null 2>&1; then
|
||||
echo "::error file=$f::invalid JSON"
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
exit $failed
|
||||
|
||||
- name: Validate hooks.json files
|
||||
run: |
|
||||
set -e
|
||||
shopt -s globstar nullglob
|
||||
failed=0
|
||||
for f in plugins/*/hooks/*.json; do
|
||||
if ! python3 -m json.tool "$f" > /dev/null 2>&1; then
|
||||
echo "::error file=$f::invalid JSON"
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
exit $failed
|
||||
|
||||
- name: Validate marketplace entries resolve to plugin dirs
|
||||
run: |
|
||||
python3 - <<'PY'
|
||||
import json, os, posixpath, sys
|
||||
from urllib.parse import urlparse
|
||||
with open('.claude-plugin/marketplace.json') as f:
|
||||
mp = json.load(f)
|
||||
errors = []
|
||||
for p in mp.get('plugins', []):
|
||||
src = p.get('source')
|
||||
if isinstance(src, str) and src.startswith('./plugins/'):
|
||||
path = src.lstrip('./')
|
||||
if not os.path.isdir(path):
|
||||
errors.append(f"{p['name']}: source {src} does not exist")
|
||||
elif not os.path.isfile(os.path.join(path, '.claude-plugin', 'plugin.json')):
|
||||
errors.append(f"{p['name']}: missing .claude-plugin/plugin.json in {src}")
|
||||
elif isinstance(src, dict):
|
||||
if src.get('source') != 'git-subdir':
|
||||
errors.append(f"{p['name']}: unsupported source object {src.get('source')!r}")
|
||||
continue
|
||||
|
||||
extra_keys = set(src) - {'source', 'url', 'path'}
|
||||
if extra_keys:
|
||||
errors.append(f"{p['name']}: git-subdir entry has unsupported keys: {sorted(extra_keys)}")
|
||||
|
||||
url = src.get('url')
|
||||
path = src.get('path')
|
||||
|
||||
if not url:
|
||||
errors.append(f"{p['name']}: git-subdir entry missing url")
|
||||
else:
|
||||
parsed = urlparse(url)
|
||||
if parsed.scheme != 'https' or parsed.netloc != 'github.com' or not parsed.path.endswith('.git'):
|
||||
errors.append(f"{p['name']}: git-subdir url must be an https://github.com/*.git URL")
|
||||
|
||||
if not path:
|
||||
errors.append(f"{p['name']}: git-subdir entry missing path")
|
||||
elif path != '.':
|
||||
normalized = posixpath.normpath(path)
|
||||
if (
|
||||
path.startswith('/')
|
||||
or '\\' in path
|
||||
or normalized != path
|
||||
or normalized == '..'
|
||||
or normalized.startswith('../')
|
||||
):
|
||||
errors.append(f"{p['name']}: git-subdir path must be a normalized relative path")
|
||||
if errors:
|
||||
for e in errors:
|
||||
print(f"::error::{e}")
|
||||
sys.exit(1)
|
||||
print(f"OK: {len(mp.get('plugins', []))} marketplace entries validated")
|
||||
PY
|
||||
|
||||
- name: Check agent name uniqueness
|
||||
run: python3 tools/check_agent_name_collisions.py --fail-on-duplicates
|
||||
|
||||
plugin-eval-tests:
|
||||
name: plugin-eval pytest
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: plugins/plugin-eval
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- name: Set up Python
|
||||
run: uv python install
|
||||
|
||||
- name: Sync dependencies
|
||||
run: uv sync --all-extras
|
||||
|
||||
- name: Run tests
|
||||
run: uv run pytest
|
||||
|
||||
tools-tests:
|
||||
name: tools pytest (adapters + validators + gardener)
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: plugins/plugin-eval
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- name: Set up Python
|
||||
run: uv python install
|
||||
|
||||
- name: Sync dependencies
|
||||
run: uv sync --all-extras
|
||||
|
||||
- name: Run tools test suite
|
||||
run: uv run pytest -q ../../tools/tests/
|
||||
|
||||
multi-harness-generate:
|
||||
name: Cross-harness generation + validation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- name: Set up Python
|
||||
run: uv python install 3.12
|
||||
|
||||
- name: Sync plugin-eval venv (provides pyyaml + adapter imports)
|
||||
working-directory: plugins/plugin-eval
|
||||
run: uv sync --all-extras
|
||||
|
||||
- name: Generate all harness artifacts
|
||||
run: make generate-all
|
||||
|
||||
- name: Verify committed artifacts are in sync with sources
|
||||
# Native-install artifacts are committed so the repo installs from a clone.
|
||||
# Regeneration must produce no diff — if it does, a source change was committed
|
||||
# without running `make generate-all`. Run it locally and commit the result.
|
||||
run: |
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "::error::Generated artifacts drifted from the committed tree. Run 'make generate-all' and commit the result."
|
||||
git status --short
|
||||
git --no-pager diff --stat
|
||||
exit 1
|
||||
fi
|
||||
echo "OK: committed harness artifacts match a fresh 'make generate-all'."
|
||||
|
||||
- name: Structural validation (strict)
|
||||
run: make validate STRICT=1
|
||||
|
||||
- name: Doc-gardener (no errors allowed)
|
||||
# Errors fail this step; warnings (e.g. oversized source skills with no
|
||||
# references/) are surfaced but don't block. Use STRICT=1 to gate on warnings too.
|
||||
run: make garden
|
||||
|
||||
- name: Upload generated artifacts for inspection
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: multi-harness-output
|
||||
path: |
|
||||
.codex/
|
||||
.cursor/
|
||||
.cursor-plugin/
|
||||
.opencode/
|
||||
opencode.json
|
||||
commands/
|
||||
agents/
|
||||
skills/
|
||||
AGENTS.md
|
||||
retention-days: 7
|
||||
|
||||
cli-smoke-test:
|
||||
name: Real-CLI smoke test (OpenCode + Gemini)
|
||||
runs-on: ubuntu-latest
|
||||
# Real-CLI subprocess tests: invokes the actual harness binaries against our
|
||||
# generated artifacts to catch issues that pure-Python parsing misses (CLI
|
||||
# version drift, schema-loader surprises, plugin discovery bugs).
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node.js (for Gemini CLI)
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Set up Bun (for OpenCode CLI)
|
||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install OpenCode CLI
|
||||
run: |
|
||||
curl -fsSL https://opencode.ai/install | bash
|
||||
echo "$HOME/.opencode/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Install Gemini CLI
|
||||
# Pinned to the released line we developed against; bump alongside any
|
||||
# gemini-extension.json schema changes.
|
||||
run: npm install -g @google/gemini-cli@latest
|
||||
|
||||
- name: Verify CLI versions
|
||||
run: |
|
||||
opencode --version
|
||||
gemini --version
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- name: Set up Python
|
||||
run: uv python install 3.12
|
||||
|
||||
- name: Sync plugin-eval dependencies
|
||||
working-directory: plugins/plugin-eval
|
||||
run: uv sync --all-extras
|
||||
|
||||
- name: Generate all harness artifacts
|
||||
run: make generate-all
|
||||
|
||||
- name: Run real-CLI smoke tests
|
||||
working-directory: plugins/plugin-eval
|
||||
run: uv run pytest -v ../../tools/tests/test_cli_smoke.py
|
||||
Reference in New Issue
Block a user