From f55153354ec5cd347ac8faad547bd041505aca28 Mon Sep 17 00:00:00 2001 From: Eric Dvorsak Date: Thu, 11 Jun 2026 17:38:07 +0200 Subject: [PATCH] Add Codex plugin manifest --- .codex-plugin/plugin.json | 41 ++++++++++++++ README.md | 9 +++- scripts/test-skills.mjs | 110 +++++++++++++++++++++++++++++++++++++- 3 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 .codex-plugin/plugin.json diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json new file mode 100644 index 0000000..2a14fcc --- /dev/null +++ b/.codex-plugin/plugin.json @@ -0,0 +1,41 @@ +{ + "name": "allium", + "version": "3.3.0", + "description": "Velocity through clarity.", + "author": { + "name": "JUXT", + "url": "https://juxt.pro" + }, + "homepage": "https://juxt.github.io/allium/", + "repository": "https://github.com/juxt/allium", + "license": "MIT", + "keywords": [ + "specification", + "behaviour", + "behavior", + "domain-modelling", + "BDD", + "property-based-testing", + "generative-tests" + ], + "skills": "./skills/", + "interface": { + "displayName": "Allium", + "shortDescription": "Behavioural specifications for agentic engineering", + "longDescription": "Write, maintain, review and propagate Allium behavioural specifications alongside implementation work.", + "developerName": "JUXT", + "category": "Coding", + "capabilities": [ + "Read", + "Write", + "Interactive" + ], + "websiteURL": "https://juxt.github.io/allium/", + "defaultPrompt": [ + "Draft an Allium spec for this feature.", + "Review this .allium spec for gaps.", + "Generate test obligations from this spec." + ], + "screenshots": [] + } +} diff --git a/README.md b/README.md index 6f68dd7..e711824 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Allium is a skill for clarifying intent during agentic engineering. The LLM buil ## Get started -Allium works with Claude Code, Copilot, Cursor, Windsurf, Aider, Continue and 40+ other tools. How you install depends on your editor, but the skills are the same everywhere. +Allium works with Claude Code, Codex, Copilot, Cursor, Windsurf, Aider, Continue and 40+ other tools. How you install depends on your editor, but the skills are the same everywhere. **Claude Code** via the [JUXT plugin marketplace](https://github.com/juxt/claude-plugins): @@ -21,6 +21,13 @@ Allium works with Claude Code, Copilot, Cursor, Windsurf, Aider, Continue and 40 /plugin install allium ``` +**Codex** via the [JUXT plugin marketplace](https://github.com/juxt/claude-plugins): + +``` +codex plugin marketplace add juxt/claude-plugins +codex plugin add allium@juxt-plugins +``` + **Cursor, Windsurf, Aider, Continue and other skills-compatible tools:** ``` diff --git a/scripts/test-skills.mjs b/scripts/test-skills.mjs index 7ebd5eb..4a21587 100644 --- a/scripts/test-skills.mjs +++ b/scripts/test-skills.mjs @@ -10,9 +10,9 @@ * node scripts/test-skills.mjs structure # run one group * node scripts/test-skills.mjs portability links # run multiple groups * - * Groups: structure, portability, links, routing, generation, discovery, crosstalk + * Groups: structure, codex, portability, links, routing, generation, discovery, crosstalk * - * The first five groups are offline (free, fast). The last two require --live + * The first six groups are offline (free, fast). The last two require --live * and make Claude API calls. */ @@ -136,6 +136,7 @@ const skillNames = ["allium", "distill", "elicit", "propagate", "tend", "weed"]; const skillPaths = skillNames.map((n) => path.join(ROOT, "skills", n, "SKILL.md")); const agentPaths = ["tend", "weed"].map((n) => path.join(ROOT, "agents", `${n}.md`)); const vscodeAgentPaths = ["tend", "weed"].map((n) => path.join(ROOT, ".github", "agents", `${n}.agent.md`)); +const codexPluginPath = path.join(ROOT, ".codex-plugin", "plugin.json"); const portableSkillNames = ["tend", "weed"]; // Patterns that should not appear in portable artifacts @@ -151,6 +152,19 @@ function checkLeaks(body) { return CLAUDE_CODE_LEAKS.filter(([re]) => re.test(body)).map(([, name]) => name); } +function readJson(filePath) { + try { + return JSON.parse(readFileSync(filePath, "utf-8")); + } catch (e) { + fail(rel(filePath), `invalid JSON: ${e.message}`); + return null; + } +} + +function isObject(value) { + return value && typeof value === "object" && !Array.isArray(value); +} + // --------------------------------------------------------------------------- // Structure — frontmatter validity for all artifact types // --------------------------------------------------------------------------- @@ -210,6 +224,98 @@ if (shouldRun("structure")) { } } +// --------------------------------------------------------------------------- +// Codex — plugin manifest stays installable by Codex +// --------------------------------------------------------------------------- + +if (shouldRun("codex")) { + console.log("\n── codex: plugin manifest validation ──\n"); + + if (!existsSync(codexPluginPath)) { + fail(".codex-plugin/plugin.json", "file not found"); + } else { + const manifest = readJson(codexPluginPath); + + if (manifest) { + const requiredTopLevel = ["name", "version", "description", "author", "skills", "interface"]; + const missing = requiredTopLevel.filter((key) => !manifest[key]); + if (missing.length > 0) { + fail(".codex-plugin/plugin.json", `missing: ${missing.join(", ")}`); + } else { + pass(".codex-plugin/plugin.json required fields"); + } + + if (manifest.name === "allium") pass("codex plugin name"); + else fail("codex plugin name", `expected allium, got ${manifest.name}`); + + if (/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(manifest.version || "")) { + pass("codex plugin version"); + } else { + fail("codex plugin version", "must be strict semver"); + } + + if (manifest.skills === "./skills/") { + pass("codex skills path"); + } else { + fail("codex skills path", "must be ./skills/"); + } + + const skillsDir = path.join(ROOT, "skills"); + if (existsSync(skillsDir)) { + pass("codex skills directory exists"); + } else { + fail("codex skills directory", "skills/ not found"); + } + + const unsupported = ["agents", "hooks", "lspServers"].filter((key) => key in manifest); + if (unsupported.length > 0) { + fail(".codex-plugin/plugin.json", `unsupported fields: ${unsupported.join(", ")}`); + } else { + pass("codex manifest has no Claude-only fields"); + } + + if (isObject(manifest.interface)) { + const requiredInterface = [ + "displayName", + "shortDescription", + "longDescription", + "developerName", + "category", + "capabilities", + ]; + const missingInterface = requiredInterface.filter((key) => !manifest.interface[key]); + if (missingInterface.length > 0) { + fail("codex interface", `missing: ${missingInterface.join(", ")}`); + } else { + pass("codex interface required fields"); + } + + if ( + !manifest.interface.websiteURL || + /^https:\/\//.test(manifest.interface.websiteURL) + ) { + pass("codex interface websiteURL"); + } else { + fail("codex interface websiteURL", "must be an https URL"); + } + + const prompts = manifest.interface.defaultPrompt || []; + if ( + Array.isArray(prompts) && + prompts.length <= 3 && + prompts.every((p) => typeof p === "string" && p.length <= 128) + ) { + pass("codex default prompts"); + } else { + fail("codex default prompts", "must be at most 3 strings of 128 chars"); + } + } else { + fail("codex interface", "must be an object"); + } + } + } +} + // --------------------------------------------------------------------------- // Portability — no Claude Code references in portable artifacts // ---------------------------------------------------------------------------