mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
feat(codex): add Codex native plugin manifest and fix Claude plugin.json
- Add .codex-plugin/plugin.json — Codex-native plugin manifest with skills reference and MCP server config pointer - Add .codex-plugin/.mcp.json — standalone MCP server config bundle (github, context7, exa, memory, playwright, sequential-thinking) - Add .codex-plugin/README.md — installation guide and server reference - Fix .claude-plugin/plugin.json — add missing agents[] (28 explicit file paths per validator rules), skills[], and commands[] arrays; remove hooks field (auto-loaded by Claude Code v2.1+ convention) - Add tests/plugin-manifest.test.js — 16 CI tests enforcing PLUGIN_SCHEMA_NOTES.md rules (no hooks, arrays throughout, explicit agent paths, version required, .mcp.json structural checks) - Update package.json: add .codex-plugin/ to files[], add plugin manifest test to npm test chain Refs: .claude-plugin/PLUGIN_SCHEMA_NOTES.md
This commit is contained in:
committed by
Affaan Mustafa
parent
f07797533d
commit
d473cf87e6
20
.agents/plugins/marketplace.json
Normal file
20
.agents/plugins/marketplace.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "everything-claude-code",
|
||||||
|
"interface": {
|
||||||
|
"displayName": "Everything Claude Code"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "everything-claude-code",
|
||||||
|
"source": {
|
||||||
|
"source": "local",
|
||||||
|
"path": "./everything-claude-code"
|
||||||
|
},
|
||||||
|
"policy": {
|
||||||
|
"installation": "AVAILABLE",
|
||||||
|
"authentication": "ON_INSTALL"
|
||||||
|
},
|
||||||
|
"category": "Productivity"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -21,5 +21,37 @@
|
|||||||
"workflow",
|
"workflow",
|
||||||
"automation",
|
"automation",
|
||||||
"best-practices"
|
"best-practices"
|
||||||
]
|
],
|
||||||
|
"agents": [
|
||||||
|
"./agents/architect.md",
|
||||||
|
"./agents/build-error-resolver.md",
|
||||||
|
"./agents/chief-of-staff.md",
|
||||||
|
"./agents/code-reviewer.md",
|
||||||
|
"./agents/cpp-build-resolver.md",
|
||||||
|
"./agents/cpp-reviewer.md",
|
||||||
|
"./agents/database-reviewer.md",
|
||||||
|
"./agents/doc-updater.md",
|
||||||
|
"./agents/docs-lookup.md",
|
||||||
|
"./agents/e2e-runner.md",
|
||||||
|
"./agents/flutter-reviewer.md",
|
||||||
|
"./agents/go-build-resolver.md",
|
||||||
|
"./agents/go-reviewer.md",
|
||||||
|
"./agents/harness-optimizer.md",
|
||||||
|
"./agents/java-build-resolver.md",
|
||||||
|
"./agents/java-reviewer.md",
|
||||||
|
"./agents/kotlin-build-resolver.md",
|
||||||
|
"./agents/kotlin-reviewer.md",
|
||||||
|
"./agents/loop-operator.md",
|
||||||
|
"./agents/planner.md",
|
||||||
|
"./agents/python-reviewer.md",
|
||||||
|
"./agents/pytorch-build-resolver.md",
|
||||||
|
"./agents/refactor-cleaner.md",
|
||||||
|
"./agents/rust-build-resolver.md",
|
||||||
|
"./agents/rust-reviewer.md",
|
||||||
|
"./agents/security-reviewer.md",
|
||||||
|
"./agents/tdd-guide.md",
|
||||||
|
"./agents/typescript-reviewer.md"
|
||||||
|
],
|
||||||
|
"skills": ["./skills/"],
|
||||||
|
"commands": ["./commands/"]
|
||||||
}
|
}
|
||||||
|
|||||||
47
.codex-plugin/README.md
Normal file
47
.codex-plugin/README.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# .codex-plugin — Codex Native Plugin for ECC
|
||||||
|
|
||||||
|
This directory contains the **Codex plugin manifest** for Everything Claude Code.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
.codex-plugin/
|
||||||
|
└── plugin.json — Codex plugin manifest (name, version, skills ref, MCP ref)
|
||||||
|
.mcp.json — MCP server configurations at plugin root (NOT inside .codex-plugin/)
|
||||||
|
```
|
||||||
|
|
||||||
|
## What This Provides
|
||||||
|
|
||||||
|
- **125 skills** from `./skills/` — reusable Codex workflows for TDD, security,
|
||||||
|
code review, architecture, and more
|
||||||
|
- **6 MCP servers** — GitHub, Context7, Exa, Memory, Playwright, Sequential Thinking
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Codex plugin support is currently in preview. Once generally available:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install from Codex CLI
|
||||||
|
codex plugin install affaan-m/everything-claude-code
|
||||||
|
|
||||||
|
# Or reference locally during development
|
||||||
|
codex plugin install ./
|
||||||
|
```
|
||||||
|
|
||||||
|
## MCP Servers Included
|
||||||
|
|
||||||
|
| Server | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `github` | GitHub API access |
|
||||||
|
| `context7` | Live documentation lookup |
|
||||||
|
| `exa` | Neural web search |
|
||||||
|
| `memory` | Persistent memory across sessions |
|
||||||
|
| `playwright` | Browser automation & E2E testing |
|
||||||
|
| `sequential-thinking` | Step-by-step reasoning |
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The `skills/` directory at the repo root is shared between Claude Code (`.claude-plugin/`)
|
||||||
|
and Codex (`.codex-plugin/`) — same source of truth, no duplication
|
||||||
|
- MCP server credentials are inherited from the launching environment (env vars)
|
||||||
|
- This manifest does **not** override `~/.codex/config.toml` settings
|
||||||
30
.codex-plugin/plugin.json
Normal file
30
.codex-plugin/plugin.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "everything-claude-code",
|
||||||
|
"version": "1.9.0",
|
||||||
|
"description": "Battle-tested Codex workflows — 125 skills, production-ready MCP configs, and agent definitions for TDD, security scanning, code review, and autonomous development.",
|
||||||
|
"author": {
|
||||||
|
"name": "Affaan Mustafa",
|
||||||
|
"email": "me@affaanmustafa.com",
|
||||||
|
"url": "https://x.com/affaanmustafa"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/affaan-m/everything-claude-code",
|
||||||
|
"repository": "https://github.com/affaan-m/everything-claude-code",
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": ["codex", "agents", "skills", "tdd", "code-review", "security", "workflow", "automation"],
|
||||||
|
"skills": "./skills/",
|
||||||
|
"mcpServers": "./.mcp.json",
|
||||||
|
"interface": {
|
||||||
|
"displayName": "Everything Claude Code",
|
||||||
|
"shortDescription": "125 battle-tested skills for TDD, security, code review, and autonomous development.",
|
||||||
|
"longDescription": "Everything Claude Code (ECC) is a community-maintained collection of Codex skills and MCP configs evolved over 10+ months of intensive daily use. It covers TDD workflows, security scanning, code review, architecture decisions, and more — all in one installable plugin.",
|
||||||
|
"developerName": "Affaan Mustafa",
|
||||||
|
"category": "Productivity",
|
||||||
|
"capabilities": ["Read", "Write"],
|
||||||
|
"websiteURL": "https://github.com/affaan-m/everything-claude-code",
|
||||||
|
"defaultPrompt": [
|
||||||
|
"Use the tdd-workflow skill to write tests before implementation.",
|
||||||
|
"Use the security-review skill to scan for OWASP Top 10 vulnerabilities.",
|
||||||
|
"Use the code-review skill to review this PR for correctness and security."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
27
.mcp.json
Normal file
27
.mcp.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"github": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "@modelcontextprotocol/server-github"]
|
||||||
|
},
|
||||||
|
"context7": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "@upstash/context7-mcp@2.1.4"]
|
||||||
|
},
|
||||||
|
"exa": {
|
||||||
|
"url": "https://mcp.exa.ai/mcp"
|
||||||
|
},
|
||||||
|
"memory": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "@modelcontextprotocol/server-memory"]
|
||||||
|
},
|
||||||
|
"playwright": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "@playwright/mcp@0.0.68", "--extension"]
|
||||||
|
},
|
||||||
|
"sequential-thinking": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "@modelcontextprotocol/server-sequential-thinking"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
package-lock.json
generated
7
package-lock.json
generated
@@ -11,6 +11,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^2.2.5",
|
"@iarna/toml": "^2.2.5",
|
||||||
|
"ajv": "^8.18.0",
|
||||||
"sql.js": "^1.14.1"
|
"sql.js": "^1.14.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -19,7 +20,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^9.39.2",
|
||||||
"ajv": "^8.18.0",
|
|
||||||
"c8": "^10.1.2",
|
"c8": "^10.1.2",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.2",
|
||||||
"globals": "^17.1.0",
|
"globals": "^17.1.0",
|
||||||
@@ -449,7 +449,6 @@
|
|||||||
"version": "8.18.0",
|
"version": "8.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
|
||||||
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
|
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
@@ -1043,7 +1042,6 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fast-json-stable-stringify": {
|
"node_modules/fast-json-stable-stringify": {
|
||||||
@@ -1064,7 +1062,6 @@
|
|||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
|
||||||
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
|
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -1491,7 +1488,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/json-stable-stringify-without-jsonify": {
|
"node_modules/json-stable-stringify-without-jsonify": {
|
||||||
@@ -2512,7 +2508,6 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
|
|||||||
@@ -89,6 +89,9 @@
|
|||||||
"AGENTS.md",
|
"AGENTS.md",
|
||||||
".claude-plugin/plugin.json",
|
".claude-plugin/plugin.json",
|
||||||
".claude-plugin/README.md",
|
".claude-plugin/README.md",
|
||||||
|
".codex-plugin/plugin.json",
|
||||||
|
".codex-plugin/README.md",
|
||||||
|
".mcp.json",
|
||||||
"install.sh",
|
"install.sh",
|
||||||
"install.ps1",
|
"install.ps1",
|
||||||
"llms.txt"
|
"llms.txt"
|
||||||
|
|||||||
219
tests/plugin-manifest.test.js
Normal file
219
tests/plugin-manifest.test.js
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
/**
|
||||||
|
* Tests for plugin manifests:
|
||||||
|
* - .claude-plugin/plugin.json (Claude Code plugin)
|
||||||
|
* - .codex-plugin/plugin.json (Codex native plugin)
|
||||||
|
* - .mcp.json (MCP server config at plugin root)
|
||||||
|
* - .agents/plugins/marketplace.json (Codex marketplace discovery)
|
||||||
|
*
|
||||||
|
* Enforces rules from:
|
||||||
|
* - .claude-plugin/PLUGIN_SCHEMA_NOTES.md (Claude Code validator rules)
|
||||||
|
* - https://platform.openai.com/docs/codex/plugins (Codex official docs)
|
||||||
|
*
|
||||||
|
* Run with: node tests/plugin-manifest.test.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const repoRoot = path.join(__dirname, '..');
|
||||||
|
|
||||||
|
let passed = 0;
|
||||||
|
let failed = 0;
|
||||||
|
|
||||||
|
function test(name, fn) {
|
||||||
|
try {
|
||||||
|
fn();
|
||||||
|
console.log(` ✓ ${name}`);
|
||||||
|
passed++;
|
||||||
|
} catch (err) {
|
||||||
|
console.log(` ✗ ${name}`);
|
||||||
|
console.log(` Error: ${err.message}`);
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Claude plugin manifest ────────────────────────────────────────────────────
|
||||||
|
console.log('\n=== .claude-plugin/plugin.json ===\n');
|
||||||
|
|
||||||
|
const claudePluginPath = path.join(repoRoot, '.claude-plugin', 'plugin.json');
|
||||||
|
|
||||||
|
test('claude plugin.json exists', () => {
|
||||||
|
assert.ok(fs.existsSync(claudePluginPath), 'Expected .claude-plugin/plugin.json to exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
const claudePlugin = JSON.parse(fs.readFileSync(claudePluginPath, 'utf8'));
|
||||||
|
|
||||||
|
test('claude plugin.json has version field', () => {
|
||||||
|
assert.ok(claudePlugin.version, 'Expected version field');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('claude plugin.json agents is an array', () => {
|
||||||
|
assert.ok(Array.isArray(claudePlugin.agents), 'Expected agents to be an array (not a string/directory)');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('claude plugin.json agents uses explicit file paths (not directories)', () => {
|
||||||
|
for (const agentPath of claudePlugin.agents) {
|
||||||
|
assert.ok(
|
||||||
|
agentPath.endsWith('.md'),
|
||||||
|
`Expected explicit .md file path, got: ${agentPath}`,
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
!agentPath.endsWith('/'),
|
||||||
|
`Expected explicit file path, not directory, got: ${agentPath}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('claude plugin.json all agent files exist', () => {
|
||||||
|
for (const agentRelPath of claudePlugin.agents) {
|
||||||
|
const absolute = path.join(repoRoot, agentRelPath.replace(/^\.\//, ''));
|
||||||
|
assert.ok(
|
||||||
|
fs.existsSync(absolute),
|
||||||
|
`Agent file missing: ${agentRelPath}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('claude plugin.json skills is an array', () => {
|
||||||
|
assert.ok(Array.isArray(claudePlugin.skills), 'Expected skills to be an array');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('claude plugin.json commands is an array', () => {
|
||||||
|
assert.ok(Array.isArray(claudePlugin.commands), 'Expected commands to be an array');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('claude plugin.json does NOT have explicit hooks declaration', () => {
|
||||||
|
assert.ok(
|
||||||
|
!('hooks' in claudePlugin),
|
||||||
|
'hooks field must NOT be declared — Claude Code v2.1+ auto-loads hooks/hooks.json by convention',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Codex plugin manifest ─────────────────────────────────────────────────────
|
||||||
|
// Per official docs: https://platform.openai.com/docs/codex/plugins
|
||||||
|
// - .codex-plugin/plugin.json is the required manifest
|
||||||
|
// - skills, mcpServers, apps are STRING paths relative to plugin root (not arrays)
|
||||||
|
// - .mcp.json must be at plugin root (NOT inside .codex-plugin/)
|
||||||
|
console.log('\n=== .codex-plugin/plugin.json ===\n');
|
||||||
|
|
||||||
|
const codexPluginPath = path.join(repoRoot, '.codex-plugin', 'plugin.json');
|
||||||
|
|
||||||
|
test('codex plugin.json exists', () => {
|
||||||
|
assert.ok(fs.existsSync(codexPluginPath), 'Expected .codex-plugin/plugin.json to exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
const codexPlugin = JSON.parse(fs.readFileSync(codexPluginPath, 'utf8'));
|
||||||
|
|
||||||
|
test('codex plugin.json has name field', () => {
|
||||||
|
assert.ok(codexPlugin.name, 'Expected name field');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('codex plugin.json has version field', () => {
|
||||||
|
assert.ok(codexPlugin.version, 'Expected version field');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('codex plugin.json skills is a string (not array) per official spec', () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
typeof codexPlugin.skills,
|
||||||
|
'string',
|
||||||
|
'skills must be a string path per Codex official docs, not an array',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('codex plugin.json mcpServers is a string path (not array) per official spec', () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
typeof codexPlugin.mcpServers,
|
||||||
|
'string',
|
||||||
|
'mcpServers must be a string path per Codex official docs',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('codex plugin.json mcpServers exactly matches "./.mcp.json"', () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
codexPlugin.mcpServers,
|
||||||
|
'./.mcp.json',
|
||||||
|
'mcpServers must point exactly to "./.mcp.json" per official docs',
|
||||||
|
);
|
||||||
|
const mcpPath = path.join(repoRoot, codexPlugin.mcpServers.replace(/^\.\//, ''));
|
||||||
|
assert.ok(
|
||||||
|
fs.existsSync(mcpPath),
|
||||||
|
`mcpServers file missing at plugin root: ${codexPlugin.mcpServers}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('codex plugin.json has interface.displayName', () => {
|
||||||
|
assert.ok(
|
||||||
|
codexPlugin.interface && codexPlugin.interface.displayName,
|
||||||
|
'Expected interface.displayName for plugin directory presentation',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── .mcp.json at plugin root ──────────────────────────────────────────────────
|
||||||
|
// Per official docs: keep .mcp.json at plugin root, NOT inside .codex-plugin/
|
||||||
|
console.log('\n=== .mcp.json (plugin root) ===\n');
|
||||||
|
|
||||||
|
const mcpJsonPath = path.join(repoRoot, '.mcp.json');
|
||||||
|
|
||||||
|
test('.mcp.json exists at plugin root (not inside .codex-plugin/)', () => {
|
||||||
|
assert.ok(fs.existsSync(mcpJsonPath), 'Expected .mcp.json at repo root (plugin root)');
|
||||||
|
assert.ok(
|
||||||
|
!fs.existsSync(path.join(repoRoot, '.codex-plugin', '.mcp.json')),
|
||||||
|
'.mcp.json must NOT be inside .codex-plugin/ — only plugin.json belongs there',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const mcpConfig = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf8'));
|
||||||
|
|
||||||
|
test('.mcp.json has mcpServers object', () => {
|
||||||
|
assert.ok(
|
||||||
|
mcpConfig.mcpServers && typeof mcpConfig.mcpServers === 'object',
|
||||||
|
'Expected mcpServers object',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('.mcp.json includes at least github, context7, and exa servers', () => {
|
||||||
|
const servers = Object.keys(mcpConfig.mcpServers);
|
||||||
|
assert.ok(servers.includes('github'), 'Expected github MCP server');
|
||||||
|
assert.ok(servers.includes('context7'), 'Expected context7 MCP server');
|
||||||
|
assert.ok(servers.includes('exa'), 'Expected exa MCP server');
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Codex marketplace file ────────────────────────────────────────────────────
|
||||||
|
// Per official docs: repo marketplace lives at $REPO_ROOT/.agents/plugins/marketplace.json
|
||||||
|
console.log('\n=== .agents/plugins/marketplace.json ===\n');
|
||||||
|
|
||||||
|
const marketplacePath = path.join(repoRoot, '.agents', 'plugins', 'marketplace.json');
|
||||||
|
|
||||||
|
test('marketplace.json exists at .agents/plugins/', () => {
|
||||||
|
assert.ok(
|
||||||
|
fs.existsSync(marketplacePath),
|
||||||
|
'Expected .agents/plugins/marketplace.json for Codex repo marketplace discovery',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const marketplace = JSON.parse(fs.readFileSync(marketplacePath, 'utf8'));
|
||||||
|
|
||||||
|
test('marketplace.json has name field', () => {
|
||||||
|
assert.ok(marketplace.name, 'Expected name field');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('marketplace.json has plugins array with at least one entry', () => {
|
||||||
|
assert.ok(Array.isArray(marketplace.plugins) && marketplace.plugins.length > 0, 'Expected plugins array');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('marketplace.json plugin entries have required fields', () => {
|
||||||
|
for (const plugin of marketplace.plugins) {
|
||||||
|
assert.ok(plugin.name, `Plugin entry missing name`);
|
||||||
|
assert.ok(plugin.source && plugin.source.source, `Plugin "${plugin.name}" missing source.source`);
|
||||||
|
assert.ok(plugin.policy && plugin.policy.installation, `Plugin "${plugin.name}" missing policy.installation`);
|
||||||
|
assert.ok(plugin.category, `Plugin "${plugin.name}" missing category`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Summary ───────────────────────────────────────────────────────────────────
|
||||||
|
console.log(`\nPassed: ${passed}`);
|
||||||
|
console.log(`Failed: ${failed}`);
|
||||||
|
process.exit(failed > 0 ? 1 : 0);
|
||||||
Reference in New Issue
Block a user