From f003c0333e52e4cea4bd0a16ec1954c50291aa64 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 May 2026 23:10:44 +0000 Subject: [PATCH 1/6] Add PR validation workflow for plugin registry guidelines Agent-Logs-Url: https://github.com/AV-Lab/avlite-community-plugins/sessions/a94537f5-da17-4305-b323-f3e3d449ab69 Co-authored-by: majid-khonji <4955888+majid-khonji@users.noreply.github.com> --- .github/scripts/validate_plugins.py | 417 ++++++++++++++++++++++++++++ .github/workflows/validate-pr.yml | 42 +++ 2 files changed, 459 insertions(+) create mode 100644 .github/scripts/validate_plugins.py create mode 100644 .github/workflows/validate-pr.yml diff --git a/.github/scripts/validate_plugins.py b/.github/scripts/validate_plugins.py new file mode 100644 index 0000000..2ab0a51 --- /dev/null +++ b/.github/scripts/validate_plugins.py @@ -0,0 +1,417 @@ +#!/usr/bin/env python3 +"""Validate `plugins.yaml` against the registry guidelines documented in README.md. + +This script enforces the contribution guidelines described in the project README: + +* `plugins.yaml` is valid YAML and has the expected top-level structure. +* Every plugin entry has the required fields with the correct types. +* `name` is unique, uses snake_case (no spaces). +* `category` is one of the standard categories listed in the README. +* `description` is short (<= 100 characters as recommended by the guidelines). +* `repository` is a valid public Git URL (http(s) or git@). +* `version` is either `latest` or looks like a (semver-ish) tag. +* Entries are sorted alphabetically by `name`. +* The `Available Plugins` table in `README.md` stays in sync with `plugins.yaml`. +* Optionally (when `--check-remote` is given and `GITHUB_TOKEN` is available), + each referenced GitHub repository is reachable, public, has a README, + has a LICENSE, and — when `version` is not `latest` — exposes a tag/release + matching the declared version. + +Exits with status 0 when all checks pass, 1 otherwise. All discovered problems +are printed to stdout to make the GitHub Actions log easy to read. +""" + +from __future__ import annotations + +import argparse +import json +import os +import re +import sys +import urllib.error +import urllib.request +from pathlib import Path +from typing import Any + +import yaml + + +REQUIRED_FIELDS: dict[str, type] = { + "name": str, + "description": str, + "repository": str, + "version": str, + "author": str, + "category": str, +} +OPTIONAL_FIELDS: dict[str, type] = { + "tags": list, +} + +ALLOWED_CATEGORIES = { + "perception", + "planning", + "control", + "localization", + "simulation", + "visualization", + "utility", +} + +NAME_RE = re.compile(r"^[A-Za-z0-9]+(?:_[A-Za-z0-9]+)*$") +# The README asks for snake_case names with no spaces. Existing entries +# (e.g. `ORBit_perception`) mix cases, so we accept letters and digits +# separated by underscores rather than enforcing strict lowercase. +VERSION_RE = re.compile(r"^(latest|v?\d+\.\d+(?:\.\d+)?(?:[-+][0-9A-Za-z.-]+)?)$") +URL_RE = re.compile(r"^(https?://|git@)[\w.@:/\-~]+?(?:\.git)?/?$") +GITHUB_URL_RE = re.compile( + r"^https?://github\.com/(?P[\w.\-]+)/(?P[\w.\-]+?)(?:\.git)?/?$" +) +DESCRIPTION_MAX_LEN = 100 + +REPO_ROOT = Path(__file__).resolve().parents[2] +PLUGINS_YAML = REPO_ROOT / "plugins.yaml" +README_MD = REPO_ROOT / "README.md" + + +class Problems: + def __init__(self) -> None: + self.errors: list[str] = [] + self.warnings: list[str] = [] + + def error(self, msg: str) -> None: + self.errors.append(msg) + + def warn(self, msg: str) -> None: + self.warnings.append(msg) + + def report(self) -> int: + for w in self.warnings: + print(f"::warning::{w}") + for e in self.errors: + print(f"::error::{e}") + if self.errors: + print(f"\nValidation failed with {len(self.errors)} error(s) " + f"and {len(self.warnings)} warning(s).") + return 1 + print(f"Validation passed ({len(self.warnings)} warning(s)).") + return 0 + + +def load_plugins(problems: Problems) -> list[dict[str, Any]]: + if not PLUGINS_YAML.is_file(): + problems.error(f"{PLUGINS_YAML} does not exist") + return [] + try: + data = yaml.safe_load(PLUGINS_YAML.read_text()) + except yaml.YAMLError as exc: + problems.error(f"plugins.yaml is not valid YAML: {exc}") + return [] + if not isinstance(data, dict) or "plugins" not in data: + problems.error("plugins.yaml must be a mapping with a top-level `plugins` key") + return [] + plugins = data["plugins"] + if not isinstance(plugins, list): + problems.error("`plugins` must be a list") + return [] + return plugins + + +def validate_entry(idx: int, entry: Any, problems: Problems) -> None: + label = f"plugins[{idx}]" + if not isinstance(entry, dict): + problems.error(f"{label} must be a mapping, got {type(entry).__name__}") + return + + name = entry.get("name", f"") + label = f"plugin `{name}`" + + # Unknown fields + known = set(REQUIRED_FIELDS) | set(OPTIONAL_FIELDS) + for key in entry: + if key not in known: + problems.warn(f"{label}: unknown field `{key}`") + + # Required fields presence + type + for field, expected in REQUIRED_FIELDS.items(): + if field not in entry: + problems.error(f"{label}: missing required field `{field}`") + continue + value = entry[field] + if not isinstance(value, expected) or (isinstance(value, str) and not value.strip()): + problems.error(f"{label}: field `{field}` must be a non-empty {expected.__name__}") + + # Optional fields type + for field, expected in OPTIONAL_FIELDS.items(): + if field in entry and not isinstance(entry[field], expected): + problems.error(f"{label}: field `{field}` must be a {expected.__name__}") + + if "tags" in entry and isinstance(entry["tags"], list): + for i, tag in enumerate(entry["tags"]): + if not isinstance(tag, str) or not tag.strip(): + problems.error(f"{label}: tags[{i}] must be a non-empty string") + + # Name format + if isinstance(entry.get("name"), str): + if " " in entry["name"]: + problems.error(f"{label}: `name` must not contain spaces") + elif not NAME_RE.match(entry["name"]): + problems.error( + f"{label}: `name` must be snake_case " + "(letters/digits separated by underscores)" + ) + + # Description length + desc = entry.get("description") + if isinstance(desc, str) and len(desc) > DESCRIPTION_MAX_LEN: + problems.warn( + f"{label}: `description` is {len(desc)} characters, " + f"keep it under {DESCRIPTION_MAX_LEN}" + ) + + # Category + cat = entry.get("category") + if isinstance(cat, str) and cat not in ALLOWED_CATEGORIES: + problems.error( + f"{label}: category `{cat}` is not one of " + f"{sorted(ALLOWED_CATEGORIES)}" + ) + + # Repository URL + repo = entry.get("repository") + if isinstance(repo, str) and not URL_RE.match(repo): + problems.error(f"{label}: `repository` is not a valid URL: {repo!r}") + + # Version format + version = entry.get("version") + if isinstance(version, str) and not VERSION_RE.match(version): + problems.warn( + f"{label}: `version` {version!r} is not `latest` and does not look " + "like a semver tag (e.g. `1.2.0` or `v1.2.0`)" + ) + + +def validate_collection(plugins: list[dict[str, Any]], problems: Problems) -> None: + names = [p.get("name") for p in plugins if isinstance(p, dict) and isinstance(p.get("name"), str)] + + # Uniqueness + seen: dict[str, int] = {} + for n in names: + seen[n] = seen.get(n, 0) + 1 + for n, count in seen.items(): + if count > 1: + problems.error(f"Duplicate plugin name `{n}` appears {count} times") + + # Alphabetical sort (case-insensitive, as the README asks for sorted entries) + sorted_names = sorted(names, key=str.lower) + if names != sorted_names: + out_of_order = [ + f"{a!r} should come after {b!r}" + for a, b in zip(names, sorted_names) + if a != b + ] + problems.error( + "plugins.yaml entries must be sorted alphabetically by `name`. " + f"First mismatch: {out_of_order[0] if out_of_order else 'unknown'}" + ) + + +def parse_readme_table(problems: Problems) -> list[dict[str, str]] | None: + if not README_MD.is_file(): + problems.error("README.md not found") + return None + text = README_MD.read_text().splitlines() + try: + header_idx = next( + i for i, line in enumerate(text) + if line.strip().lower().startswith("## available plugins") + ) + except StopIteration: + problems.error("README.md is missing the `## Available Plugins` section") + return None + + rows: list[dict[str, str]] = [] + # Skip to the table header row + i = header_idx + 1 + while i < len(text) and not text[i].lstrip().startswith("|"): + i += 1 + if i >= len(text): + problems.error("`Available Plugins` section has no markdown table") + return None + header = [c.strip() for c in text[i].strip().strip("|").split("|")] + i += 2 # skip the |---|---| separator + while i < len(text) and text[i].lstrip().startswith("|"): + cells = [c.strip() for c in text[i].strip().strip("|").split("|")] + if len(cells) == len(header): + rows.append(dict(zip(header, cells))) + i += 1 + return rows + + +def validate_readme_in_sync( + plugins: list[dict[str, Any]], problems: Problems +) -> None: + rows = parse_readme_table(problems) + if rows is None: + return + + plugins_by_name = { + p["name"]: p for p in plugins if isinstance(p, dict) and isinstance(p.get("name"), str) + } + table_names = {row.get("Name", "").strip() for row in rows} + + missing_in_table = set(plugins_by_name) - table_names + extra_in_table = table_names - set(plugins_by_name) + for n in sorted(missing_in_table): + problems.error( + f"README `Available Plugins` table is missing an entry for `{n}` " + "(present in plugins.yaml)" + ) + for n in sorted(extra_in_table): + problems.error( + f"README `Available Plugins` table lists `{n}` " + "but it is not in plugins.yaml" + ) + + for row in rows: + name = row.get("Name", "").strip() + plugin = plugins_by_name.get(name) + if not plugin: + continue + for col, field in (("Category", "category"), + ("Description", "description"), + ("Repository", "repository")): + expected = str(plugin.get(field, "")).strip() + actual = row.get(col, "").strip() + if expected and actual != expected: + problems.error( + f"README table for `{name}`: column `{col}` is " + f"{actual!r} but plugins.yaml has {expected!r}" + ) + + +# --------------------------------------------------------------------------- +# Optional remote checks (GitHub API) +# --------------------------------------------------------------------------- + +def _gh_get(path: str, token: str | None) -> tuple[int, Any]: + url = f"https://api.github.com{path}" + req = urllib.request.Request(url, headers={ + "Accept": "application/vnd.github+json", + "User-Agent": "avlite-plugins-validator", + }) + if token: + req.add_header("Authorization", f"Bearer {token}") + try: + with urllib.request.urlopen(req, timeout=15) as resp: + return resp.status, json.loads(resp.read() or b"null") + except urllib.error.HTTPError as exc: + body: Any = None + try: + body = json.loads(exc.read() or b"null") + except Exception: + body = None + return exc.code, body + except (urllib.error.URLError, TimeoutError) as exc: + return 0, str(exc) + + +def validate_remote(plugins: list[dict[str, Any]], problems: Problems) -> None: + token = os.environ.get("GITHUB_TOKEN") + if not token: + problems.warn( + "GITHUB_TOKEN is not set; remote repository checks will be unauthenticated " + "and may be rate-limited." + ) + + for entry in plugins: + if not isinstance(entry, dict): + continue + name = entry.get("name", "") + repo_url = entry.get("repository", "") + version = entry.get("version", "") + if not isinstance(repo_url, str): + continue + m = GITHUB_URL_RE.match(repo_url) + if not m: + problems.warn( + f"plugin `{name}`: repository {repo_url!r} is not a github.com URL; " + "skipping remote checks" + ) + continue + owner, repo = m.group("owner"), m.group("repo") + + status, payload = _gh_get(f"/repos/{owner}/{repo}", token) + if status == 0: + problems.warn(f"plugin `{name}`: could not reach GitHub ({payload})") + continue + if status == 404: + problems.error( + f"plugin `{name}`: repository {repo_url} is not accessible (404). " + "It must be public." + ) + continue + if status >= 400 or not isinstance(payload, dict): + problems.warn( + f"plugin `{name}`: GitHub API returned {status} for {repo_url}" + ) + continue + if payload.get("private"): + problems.error(f"plugin `{name}`: repository {repo_url} is private") + if not payload.get("license"): + problems.error( + f"plugin `{name}`: repository {repo_url} has no detected LICENSE" + ) + + # README presence + status, _ = _gh_get(f"/repos/{owner}/{repo}/readme", token) + if status == 404: + problems.error( + f"plugin `{name}`: repository {repo_url} has no README" + ) + elif status >= 400 and status != 0: + problems.warn( + f"plugin `{name}`: could not verify README (HTTP {status})" + ) + + # Tag matches version + if isinstance(version, str) and version and version != "latest": + status, _ = _gh_get(f"/repos/{owner}/{repo}/git/ref/tags/{version}", token) + if status == 404: + # try with a leading 'v' + alt = version if version.startswith("v") else f"v{version}" + status2, _ = _gh_get( + f"/repos/{owner}/{repo}/git/ref/tags/{alt}", token + ) + if status2 == 404: + problems.error( + f"plugin `{name}`: no tag matching version `{version}` " + f"found in {repo_url}" + ) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--check-remote", + action="store_true", + help="Also verify that referenced GitHub repositories exist, are public, " + "have a LICENSE/README, and expose the declared version tag.", + ) + args = parser.parse_args() + + problems = Problems() + plugins = load_plugins(problems) + if plugins: + for i, entry in enumerate(plugins): + validate_entry(i, entry, problems) + validate_collection(plugins, problems) + validate_readme_in_sync(plugins, problems) + if args.check_remote: + validate_remote(plugins, problems) + + return problems.report() + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml new file mode 100644 index 0000000..bb507f6 --- /dev/null +++ b/.github/workflows/validate-pr.yml @@ -0,0 +1,42 @@ +name: Validate plugin registry PR + +on: + pull_request: + branches: [main] + paths: + - "plugins.yaml" + - "README.md" + - ".github/scripts/validate_plugins.py" + - ".github/workflows/validate-pr.yml" + push: + branches: [main] + paths: + - "plugins.yaml" + - "README.md" + - ".github/scripts/validate_plugins.py" + - ".github/workflows/validate-pr.yml" + workflow_dispatch: + +permissions: + contents: read + +jobs: + validate: + name: Validate plugins.yaml against README guidelines + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: python -m pip install --upgrade pip pyyaml + + - name: Validate plugins.yaml and README + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: python .github/scripts/validate_plugins.py --check-remote From 6696e9ab5cd8cedc53484f8db011b0f19e723a42 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 00:09:53 +0000 Subject: [PATCH 2/6] Add sample-avlite-plugin to registry Agent-Logs-Url: https://github.com/AV-Lab/avlite-community-plugins/sessions/5b75d71a-935f-41af-bc15-96afa78c3d3d Co-authored-by: majid-khonji <4955888+majid-khonji@users.noreply.github.com> --- README.md | 1 + plugins.yaml | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/README.md b/README.md index 8f5c7c0..d9f98e3 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ plugins: | Name | Category | Description | Repository | | ---- | -------- | ----------- | ---------- | | ORBit_perception | perception | ORBit perception plugin for AVLite | https://github.com/AV-Lab/ORBit_perception | +| sample_avlite_plugin | perception | Sample AVLite plugin demonstrating the plugin interface | https://github.com/AV-Lab/sample-avlite-plugin | ## Contributing diff --git a/plugins.yaml b/plugins.yaml index eb65e95..0dbdf38 100644 --- a/plugins.yaml +++ b/plugins.yaml @@ -12,3 +12,12 @@ plugins: tags: - perception - computer-vision + - name: sample_avlite_plugin + description: Sample AVLite plugin demonstrating the plugin interface + repository: https://github.com/AV-Lab/sample-avlite-plugin + version: latest + author: AV-Lab + category: perception + tags: + - sample + - example From 9575939bc675a7f64882be47ae70fd07b9a6c895 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 00:12:19 +0000 Subject: [PATCH 3/6] Remove optional tags from sample_avlite_plugin entry Agent-Logs-Url: https://github.com/AV-Lab/avlite-community-plugins/sessions/b3e65f6d-6d15-4408-9c43-6e0dfe785ba6 Co-authored-by: majid-khonji <4955888+majid-khonji@users.noreply.github.com> --- plugins.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins.yaml b/plugins.yaml index 0dbdf38..656706d 100644 --- a/plugins.yaml +++ b/plugins.yaml @@ -18,6 +18,3 @@ plugins: version: latest author: AV-Lab category: perception - tags: - - sample - - example From bd82fa9cabb42a40ee7c90c402943f3fdb031761 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 00:17:48 +0000 Subject: [PATCH 4/6] Update categories to new strategy-based set Agent-Logs-Url: https://github.com/AV-Lab/avlite-community-plugins/sessions/322d4e07-43c1-4d3b-a6c2-14075fcd4a70 Co-authored-by: majid-khonji <4955888+majid-khonji@users.noreply.github.com> --- .github/scripts/validate_plugins.py | 14 +++++++------- README.md | 20 ++++++++++---------- plugins.yaml | 4 ++-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/scripts/validate_plugins.py b/.github/scripts/validate_plugins.py index 2ab0a51..93213bd 100644 --- a/.github/scripts/validate_plugins.py +++ b/.github/scripts/validate_plugins.py @@ -49,13 +49,13 @@ } ALLOWED_CATEGORIES = { - "perception", - "planning", - "control", - "localization", - "simulation", - "visualization", - "utility", + "PerceptionStrategy", + "LocalizationStrategy", + "MappingStrategy", + "PlanningStrategy", + "ControlStrategy", + "Executer", + "WorldBridge", } NAME_RE = re.compile(r"^[A-Za-z0-9]+(?:_[A-Za-z0-9]+)*$") diff --git a/README.md b/README.md index d9f98e3..be797d1 100644 --- a/README.md +++ b/README.md @@ -20,13 +20,13 @@ A central registry of community-maintained plugins for [AVLite](https://github.c Use one of the following standard categories for `category`. If your plugin doesn't fit, open an issue to propose a new one rather than inventing one ad hoc: -- `perception` — sensing, detection, tracking, segmentation, fusion -- `planning` — global/local planners, behavior planning, decision-making -- `control` — vehicle controllers, actuation -- `localization` — mapping, SLAM, pose estimation -- `simulation` — simulators, scenario generation, synthetic data -- `visualization` — UIs, dashboards, debug tooling -- `utility` — shared libraries, helpers, integrations +- `PerceptionStrategy` — sensing, detection, tracking, segmentation, fusion +- `LocalizationStrategy` — pose estimation, SLAM-based localization +- `MappingStrategy` — map building, SLAM mapping, environment representation +- `PlanningStrategy` — global/local planners, behavior planning, decision-making +- `ControlStrategy` — vehicle controllers, actuation +- `Executer` — runtime execution, scheduling, orchestration +- `WorldBridge` — bridges to simulators, middleware, or external world interfaces ### Example Entry @@ -37,7 +37,7 @@ plugins: repository: https://github.com/AV-Lab/ORBit_perception version: latest author: AV-Lab - category: perception + category: PerceptionStrategy tags: - perception - computer-vision @@ -47,8 +47,8 @@ plugins: | Name | Category | Description | Repository | | ---- | -------- | ----------- | ---------- | -| ORBit_perception | perception | ORBit perception plugin for AVLite | https://github.com/AV-Lab/ORBit_perception | -| sample_avlite_plugin | perception | Sample AVLite plugin demonstrating the plugin interface | https://github.com/AV-Lab/sample-avlite-plugin | +| ORBit_perception | PerceptionStrategy | ORBit perception plugin for AVLite | https://github.com/AV-Lab/ORBit_perception | +| sample_avlite_plugin | PerceptionStrategy | Sample AVLite plugin demonstrating the plugin interface | https://github.com/AV-Lab/sample-avlite-plugin | ## Contributing diff --git a/plugins.yaml b/plugins.yaml index 656706d..2a7c49e 100644 --- a/plugins.yaml +++ b/plugins.yaml @@ -8,7 +8,7 @@ plugins: repository: https://github.com/AV-Lab/ORBit_perception version: latest author: AV-Lab - category: perception + category: PerceptionStrategy tags: - perception - computer-vision @@ -17,4 +17,4 @@ plugins: repository: https://github.com/AV-Lab/sample-avlite-plugin version: latest author: AV-Lab - category: perception + category: PerceptionStrategy From 1243fbff6a6f56e90b69a24aec7a2acfe6311208 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 00:20:37 +0000 Subject: [PATCH 5/6] Remove plugin names from README Agent-Logs-Url: https://github.com/AV-Lab/avlite-community-plugins/sessions/9b6d9fed-964d-4735-a276-1c03994c9ed5 Co-authored-by: majid-khonji <4955888+majid-khonji@users.noreply.github.com> --- .github/scripts/validate_plugins.py | 73 ++--------------------------- README.md | 22 ++++----- 2 files changed, 13 insertions(+), 82 deletions(-) diff --git a/.github/scripts/validate_plugins.py b/.github/scripts/validate_plugins.py index 93213bd..4f5eb44 100644 --- a/.github/scripts/validate_plugins.py +++ b/.github/scripts/validate_plugins.py @@ -71,7 +71,6 @@ REPO_ROOT = Path(__file__).resolve().parents[2] PLUGINS_YAML = REPO_ROOT / "plugins.yaml" -README_MD = REPO_ROOT / "README.md" class Problems: @@ -217,77 +216,16 @@ def validate_collection(plugins: list[dict[str, Any]], problems: Problems) -> No def parse_readme_table(problems: Problems) -> list[dict[str, str]] | None: - if not README_MD.is_file(): - problems.error("README.md not found") - return None - text = README_MD.read_text().splitlines() - try: - header_idx = next( - i for i, line in enumerate(text) - if line.strip().lower().startswith("## available plugins") - ) - except StopIteration: - problems.error("README.md is missing the `## Available Plugins` section") - return None - - rows: list[dict[str, str]] = [] - # Skip to the table header row - i = header_idx + 1 - while i < len(text) and not text[i].lstrip().startswith("|"): - i += 1 - if i >= len(text): - problems.error("`Available Plugins` section has no markdown table") - return None - header = [c.strip() for c in text[i].strip().strip("|").split("|")] - i += 2 # skip the |---|---| separator - while i < len(text) and text[i].lstrip().startswith("|"): - cells = [c.strip() for c in text[i].strip().strip("|").split("|")] - if len(cells) == len(header): - rows.append(dict(zip(header, cells))) - i += 1 - return rows + # Deprecated: the README no longer mirrors plugins.yaml in a table. + return None def validate_readme_in_sync( plugins: list[dict[str, Any]], problems: Problems ) -> None: - rows = parse_readme_table(problems) - if rows is None: - return - - plugins_by_name = { - p["name"]: p for p in plugins if isinstance(p, dict) and isinstance(p.get("name"), str) - } - table_names = {row.get("Name", "").strip() for row in rows} - - missing_in_table = set(plugins_by_name) - table_names - extra_in_table = table_names - set(plugins_by_name) - for n in sorted(missing_in_table): - problems.error( - f"README `Available Plugins` table is missing an entry for `{n}` " - "(present in plugins.yaml)" - ) - for n in sorted(extra_in_table): - problems.error( - f"README `Available Plugins` table lists `{n}` " - "but it is not in plugins.yaml" - ) - - for row in rows: - name = row.get("Name", "").strip() - plugin = plugins_by_name.get(name) - if not plugin: - continue - for col, field in (("Category", "category"), - ("Description", "description"), - ("Repository", "repository")): - expected = str(plugin.get(field, "")).strip() - actual = row.get(col, "").strip() - if expected and actual != expected: - problems.error( - f"README table for `{name}`: column `{col}` is " - f"{actual!r} but plugins.yaml has {expected!r}" - ) + # Deprecated: README no longer lists individual plugins. Kept as a no-op + # to preserve the public function surface. + return # --------------------------------------------------------------------------- @@ -406,7 +344,6 @@ def main() -> int: for i, entry in enumerate(plugins): validate_entry(i, entry, problems) validate_collection(plugins, problems) - validate_readme_in_sync(plugins, problems) if args.check_remote: validate_remote(plugins, problems) diff --git a/README.md b/README.md index be797d1..0ae38de 100644 --- a/README.md +++ b/README.md @@ -32,23 +32,18 @@ Use one of the following standard categories for `category`. If your plugin does ```yaml plugins: - - name: ORBit_perception - description: ORBit perception plugin for AVLite - repository: https://github.com/AV-Lab/ORBit_perception + - name: my_perception_plugin + description: One-line summary of what the plugin does + repository: https://github.com/your-org/your-plugin-repo version: latest - author: AV-Lab + author: your-org category: PerceptionStrategy tags: - perception - computer-vision ``` -## Available Plugins - -| Name | Category | Description | Repository | -| ---- | -------- | ----------- | ---------- | -| ORBit_perception | PerceptionStrategy | ORBit perception plugin for AVLite | https://github.com/AV-Lab/ORBit_perception | -| sample_avlite_plugin | PerceptionStrategy | Sample AVLite plugin demonstrating the plugin interface | https://github.com/AV-Lab/sample-avlite-plugin | +The authoritative list of registered plugins lives in [`plugins.yaml`](plugins.yaml). Tools and the AVLite runtime consume that file directly. ## Contributing @@ -56,9 +51,8 @@ To add or update a plugin in this registry: 1. **Fork** this repository and create a feature branch. 2. **Edit `plugins.yaml`** and append (or update) your plugin entry following the [schema](#plugin-registry-schema) above. Keep entries alphabetically sorted by `name` to minimize merge conflicts. -3. **Update the [Available Plugins](#available-plugins) table** in this README so the human-readable listing stays in sync with `plugins.yaml`. -4. **Verify your plugin repository** is public, has a clear `README`, a valid `LICENSE`, and a tagged release matching the `version` you list (unless you intentionally use `latest`). -5. **Open a pull request** with a short description of the plugin and a link to its repository. A maintainer will review and merge. +3. **Verify your plugin repository** is public, has a clear `README`, a valid `LICENSE`, and a tagged release matching the `version` you list (unless you intentionally use `latest`). +4. **Open a pull request** with a short description of the plugin and a link to its repository. A maintainer will review and merge. ### Guidelines @@ -69,7 +63,7 @@ To add or update a plugin in this registry: ### Removing or Renaming a Plugin -If a plugin is no longer maintained or is being renamed, open a PR that updates or removes the corresponding entry in `plugins.yaml` and the [Available Plugins](#available-plugins) table, and explain the reason in the PR description. +If a plugin is no longer maintained or is being renamed, open a PR that updates or removes the corresponding entry in `plugins.yaml`, and explain the reason in the PR description. ## License From 085f9bf7643ee0b0dcabc70ef69385775169f4eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 00:29:45 +0000 Subject: [PATCH 6/6] Remove inaccessible ORBit_perception entry from plugins.yaml Agent-Logs-Url: https://github.com/AV-Lab/avlite-community-plugins/sessions/3d8eb088-b83b-4026-9def-d5d2e9022da3 Co-authored-by: majid-khonji <4955888+majid-khonji@users.noreply.github.com> --- plugins.yaml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/plugins.yaml b/plugins.yaml index 2a7c49e..1c8377a 100644 --- a/plugins.yaml +++ b/plugins.yaml @@ -3,15 +3,6 @@ # Each plugin entry should follow the structure below plugins: - - name: ORBit_perception - description: ORBit perception plugin for AVLite - repository: https://github.com/AV-Lab/ORBit_perception - version: latest - author: AV-Lab - category: PerceptionStrategy - tags: - - perception - - computer-vision - name: sample_avlite_plugin description: Sample AVLite plugin demonstrating the plugin interface repository: https://github.com/AV-Lab/sample-avlite-plugin