Align marketplace categories with Codex taxonomy

Constraint: Keep one EAPIL marketplace while using official Codex category metadata and stable plugin IDs.

Rejected: One marketplace per category | Codex groups the UI by marketplace source, which would require users to add multiple markets and change install selectors.

Confidence: high

Scope-risk: moderate

Directive: Keep localized plugins in the same functional category and distinguish them with a language suffix such as -zh.

Tested: Marketplace validation, all manifest/category consistency checks, KeyInfo market tests, frontend lint and production build.
This commit is contained in:
KeyInfo Bot
2026-07-14 10:48:25 +08:00
parent 59c6b9f6cb
commit 93eeb27a5c
37 changed files with 647 additions and 557 deletions
+40 -3
View File
@@ -22,6 +22,35 @@ MARKETPLACES = [
]
SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+([-.+][0-9A-Za-z.-]+)?$")
PLUGIN_NAME_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$")
OFFICIAL_CATEGORIES = {
"Productivity",
"Developer Tools",
"Engineering",
"Creativity",
"Data & Analytics",
"Education & Research",
"Communication",
"Finance",
"Business & Operations",
"Travel",
"Security",
"Other",
}
CATEGORY_ORDER = [
"Productivity",
"Developer Tools",
"Engineering",
"Creativity",
"Data & Analytics",
"Education & Research",
"Communication",
"Finance",
"Business & Operations",
"Travel",
"Security",
"Other",
]
CATEGORY_INDEX = {category: index for index, category in enumerate(CATEGORY_ORDER)}
def load_json(path: Path) -> object:
@@ -132,7 +161,7 @@ def validate_interface_asset(plugin_dir: Path, manifest_path: Path, value: objec
validate_manifest_path(plugin_dir, manifest_path, value, f"interface.{field}")
def validate_manifest(plugin_dir: Path, expected_name: str) -> None:
def validate_manifest(plugin_dir: Path, expected_name: str, expected_category: str) -> None:
manifest_path = plugin_dir / ".codex-plugin" / "plugin.json"
require(manifest_path.exists(), f"Missing manifest: {manifest_path}")
manifest = load_json(manifest_path)
@@ -160,6 +189,8 @@ def validate_manifest(plugin_dir: Path, expected_name: str) -> None:
require(isinstance(interface, dict), f"{manifest_path} missing interface object")
for key in ["displayName", "shortDescription", "longDescription", "developerName", "category"]:
require(bool(interface.get(key)), f"{manifest_path} missing interface.{key}")
require(interface.get("category") in OFFICIAL_CATEGORIES, f"{manifest_path} uses unsupported category")
require(interface.get("category") == expected_category, f"{manifest_path} category must match marketplace entry")
capabilities = interface.get("capabilities")
require(isinstance(capabilities, list), f"{manifest_path} interface.capabilities must be a list")
validate_interface_asset(plugin_dir, manifest_path, interface.get("logo"), "logo")
@@ -182,6 +213,11 @@ def validate_marketplace(marketplace_path: Path) -> None:
require(isinstance(marketplace, dict), f"Marketplace must be an object: {marketplace_path}")
require(bool(marketplace.get("name")), f"{marketplace_path} missing name")
require(isinstance(marketplace.get("plugins"), list), f"{marketplace_path} plugins must be a list")
expected_order = sorted(
marketplace["plugins"],
key=lambda entry: (CATEGORY_INDEX.get(str(entry.get("category") or ""), len(CATEGORY_INDEX)), str(entry.get("name") or "")),
)
require(marketplace["plugins"] == expected_order, f"{marketplace_path} plugins must be sorted by category and name")
seen: set[str] = set()
for entry in marketplace["plugins"]:
@@ -202,9 +238,10 @@ def validate_marketplace(marketplace_path: Path) -> None:
require(isinstance(policy, dict), f"{marketplace_path} {name} missing policy")
require(policy.get("installation") in {"AVAILABLE", "INSTALLED_BY_DEFAULT", "NOT_AVAILABLE"}, f"{marketplace_path} {name} invalid installation policy")
require(policy.get("authentication") in {"ON_INSTALL", "ON_USE"}, f"{marketplace_path} {name} invalid authentication policy")
require(bool(entry.get("category")), f"{marketplace_path} {name} missing category")
category = entry.get("category")
require(category in OFFICIAL_CATEGORIES, f"{marketplace_path} {name} uses unsupported category: {category}")
validate_manifest(plugin_dir, name)
validate_manifest(plugin_dir, name, str(category))
def main() -> int: