Remove legacy codex compatiblity scripts/shims. (#173)

* Remove legacy codex compatiblity scripts/shims.

Codex supports claude plugins so this shouldn't be necessary.
Add a script to test the plugin loadablility in both claude and codex

* fix: resolve code review findings for PR #173

Review findings addressed (4 reviewers: pr-review-toolkit agents,
Codex gpt-5.3-codex, direct diff review):

P2 fixed:
- Bump versions for the 5 substantively changed plugins in both
  plugin.json and marketplace.json (gh-cli 1.5.0 new skill,
  claude-in-chrome-troubleshooting 1.1.0 skill rename,
  modern-python 1.5.1 / skill-improver 1.0.3 hooks change,
  zeroize-audit 0.1.1 MCP config relocation) so clients pick up
  the changes
- README Codex install: replace unpasteable /plugins slash-command
  block with verified CLI syntax (codex plugin marketplace add)
- check_claude_loadability: parse_json_output now fails fast with
  command context on empty CLI output instead of returning None
- check_codex_loadability: surface skipped RPC error messages in
  timeout failures instead of a bare TimeoutError

P3 fixed:
- Both checkers: error out when marketplace.json lists no plugins
  instead of passing vacuously

Dismissed:
- @latest CLI installs in validate.yml: deliberate; the check
  validates against the clients users actually run
- select.select portability: CI-only script on ubuntu-latest
- Divergent mcpServers validation between checkers: intentional;
  the Codex checker enforces the repo's .mcp.json convention

Verified: ruff, prek, validate_plugin_metadata.py, and both
loadability checks pass end-to-end (39 plugins, 74 skills, 2 MCP
servers load in Claude Code and Codex)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Dan Guido <dan@trailofbits.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
William Tan
2026-06-05 12:18:09 -04:00
committed by GitHub
parent c94841be3d
commit f09e5c729a
94 changed files with 771 additions and 509 deletions
+5 -5
View File
@@ -49,7 +49,7 @@
},
{
"name": "claude-in-chrome-troubleshooting",
"version": "1.0.0",
"version": "1.1.0",
"description": "Diagnose and fix Claude in Chrome MCP extension connectivity issues",
"author": {
"name": "Dan Guido"
@@ -115,7 +115,7 @@
},
{
"name": "gh-cli",
"version": "1.4.1",
"version": "1.5.0",
"description": "Intercepts GitHub URL fetches and curl/wget commands, redirecting to the authenticated gh CLI.",
"author": {
"name": "William Tan",
@@ -253,7 +253,7 @@
},
{
"name": "modern-python",
"version": "1.5.0",
"version": "1.5.1",
"description": "Modern Python best practices. Use when creating new Python projects, and writing Python scripts, or migrating existing projects from legacy tools.",
"author": {
"name": "William Tan",
@@ -335,7 +335,7 @@
},
{
"name": "zeroize-audit",
"version": "0.1.0",
"version": "0.1.1",
"description": "Detects missing or compiler-optimized zeroization of sensitive data with assembly and control-flow analysis",
"author": {
"name": "Trail of Bits",
@@ -365,7 +365,7 @@
},
{
"name": "skill-improver",
"version": "1.0.2",
"version": "1.0.3",
"description": "Automatically reviews and fixes Claude Code skills through iterative refinement until they meet quality standards. Requires plugin-dev plugin.",
"author": {
"name": "Paweł Płatek",
-29
View File
@@ -1,29 +0,0 @@
# Installing Trail of Bits Skills for Codex
This repository primarily targets Claude plugin discovery, but it also exposes a Codex-native skill tree under `.codex/skills/`.
## Install
1. Clone the repository:
```sh
git clone https://github.com/trailofbits/skills.git ~/.codex/trailofbits-skills
```
2. Link the Codex-native skill directories into your Codex skills directory:
```sh
~/.codex/trailofbits-skills/.codex/scripts/install-for-codex.sh
```
3. Restart Codex so it discovers the new skills.
## Verify
```sh
ls -la ~/.codex/skills | grep trailofbits-
```
## Notes
- Existing Claude plugin support remains unchanged under `plugins/`.
- Codex uses the `.codex/skills/` view, which reuses the existing skill content where possible.
- The `gh-cli` plugin does not expose a Claude `skills/` directory, so Codex gets a wrapper skill describing the intended authenticated GitHub CLI workflow.
-30
View File
@@ -1,30 +0,0 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
SOURCE_DIR="${REPO_ROOT}/.codex/skills"
TARGET_DIR="${HOME}/.codex/skills"
mkdir -p "${TARGET_DIR}"
if [ ! -d "${SOURCE_DIR}" ]; then
echo "ERROR: Source directory does not exist: ${SOURCE_DIR}" >&2
echo "Ensure .codex/skills/ is present in the repository." >&2
exit 1
fi
count=0
for skill_dir in "${SOURCE_DIR}"/*; do
[ -e "${skill_dir}" ] || continue
skill_name="$(basename "${skill_dir}")"
target_link="${TARGET_DIR}/trailofbits-${skill_name}"
ln -sfn "${skill_dir}" "${target_link}"
count=$((count + 1))
done
if [ "${count}" -eq 0 ]; then
echo "WARNING: No skills found in ${SOURCE_DIR}" >&2
exit 1
fi
echo "Installed ${count} Trail of Bits Codex skills into ${TARGET_DIR}"
-1
View File
@@ -1 +0,0 @@
../../plugins/testing-handbook-skills/skills/address-sanitizer
-1
View File
@@ -1 +0,0 @@
../../plugins/testing-handbook-skills/skills/aflpp
-1
View File
@@ -1 +0,0 @@
../../plugins/agentic-actions-auditor/skills/agentic-actions-auditor
@@ -1 +0,0 @@
../../plugins/building-secure-contracts/skills/algorand-vulnerability-scanner
@@ -1 +0,0 @@
../../plugins/ask-questions-if-underspecified/skills/ask-questions-if-underspecified
-1
View File
@@ -1 +0,0 @@
../../plugins/testing-handbook-skills/skills/atheris
-1
View File
@@ -1 +0,0 @@
../../plugins/trailmark/skills/audit-augmentation
-1
View File
@@ -1 +0,0 @@
../../plugins/audit-context-building/skills/audit-context-building
-1
View File
@@ -1 +0,0 @@
../../plugins/building-secure-contracts/skills/audit-prep-assistant
-1
View File
@@ -1 +0,0 @@
../../plugins/burpsuite-project-parser/skills/burpsuite-project-parser
-1
View File
@@ -1 +0,0 @@
../../plugins/c-review/skills/c-review
@@ -1 +0,0 @@
../../plugins/building-secure-contracts/skills/cairo-vulnerability-scanner
-1
View File
@@ -1 +0,0 @@
../../plugins/testing-handbook-skills/skills/cargo-fuzz
@@ -1 +0,0 @@
../../plugins/claude-in-chrome-troubleshooting/skills/claude-in-chrome-troubleshooting
-1
View File
@@ -1 +0,0 @@
../../plugins/building-secure-contracts/skills/code-maturity-assessor
-1
View File
@@ -1 +0,0 @@
../../plugins/static-analysis/skills/codeql
-1
View File
@@ -1 +0,0 @@
../../plugins/constant-time-analysis/skills/constant-time-analysis
-1
View File
@@ -1 +0,0 @@
../../plugins/testing-handbook-skills/skills/constant-time-testing
@@ -1 +0,0 @@
../../plugins/building-secure-contracts/skills/cosmos-vulnerability-scanner
-1
View File
@@ -1 +0,0 @@
../../plugins/testing-handbook-skills/skills/coverage-analysis
-1
View File
@@ -1 +0,0 @@
../../plugins/trailmark/skills/crypto-protocol-diagram
-1
View File
@@ -1 +0,0 @@
../../plugins/debug-buttercup/skills/debug-buttercup
-1
View File
@@ -1 +0,0 @@
../../plugins/workflow-skill-design/skills/designing-workflow-skills
-1
View File
@@ -1 +0,0 @@
../../plugins/devcontainer-setup/skills/devcontainer-setup
-1
View File
@@ -1 +0,0 @@
../../plugins/trailmark/skills/diagramming-code
-1
View File
@@ -1 +0,0 @@
../../plugins/differential-review/skills/differential-review
-1
View File
@@ -1 +0,0 @@
../../plugins/dimensional-analysis/skills/dimensional-analysis
-1
View File
@@ -1 +0,0 @@
../../plugins/dwarf-expert/skills/dwarf-expert
-1
View File
@@ -1 +0,0 @@
../../plugins/entry-point-analyzer/skills/entry-point-analyzer
-1
View File
@@ -1 +0,0 @@
../../plugins/firebase-apk-scanner/skills/firebase-apk-scanner
-1
View File
@@ -1 +0,0 @@
../../plugins/fp-check/skills/fp-check
-1
View File
@@ -1 +0,0 @@
../../plugins/testing-handbook-skills/skills/fuzzing-dictionary
-1
View File
@@ -1 +0,0 @@
../../plugins/testing-handbook-skills/skills/fuzzing-obstacles
-1
View File
@@ -1 +0,0 @@
../../plugins/trailmark/skills/genotoxic
-1
View File
@@ -1 +0,0 @@
../../plugins/git-cleanup/skills/git-cleanup
-1
View File
@@ -1 +0,0 @@
../../plugins/trailmark/skills/graph-evolution
-1
View File
@@ -1 +0,0 @@
../../plugins/building-secure-contracts/skills/guidelines-advisor
-1
View File
@@ -1 +0,0 @@
../../plugins/testing-handbook-skills/skills/harness-writing
-1
View File
@@ -1 +0,0 @@
../../plugins/insecure-defaults/skills/insecure-defaults
-1
View File
@@ -1 +0,0 @@
../../plugins/culture-index/skills/interpreting-culture-index
-1
View File
@@ -1 +0,0 @@
../../plugins/let-fate-decide/skills/let-fate-decide
-1
View File
@@ -1 +0,0 @@
../../plugins/testing-handbook-skills/skills/libafl
-1
View File
@@ -1 +0,0 @@
../../plugins/testing-handbook-skills/skills/libfuzzer
-1
View File
@@ -1 +0,0 @@
../../plugins/trailmark/skills/mermaid-to-proverif
-1
View File
@@ -1 +0,0 @@
../../plugins/modern-python/skills/modern-python
-1
View File
@@ -1 +0,0 @@
../../plugins/mutation-testing/skills/mutation-testing
-1
View File
@@ -1 +0,0 @@
../../plugins/testing-handbook-skills/skills/ossfuzz
-1
View File
@@ -1 +0,0 @@
../../plugins/property-based-testing/skills/property-based-testing
-1
View File
@@ -1 +0,0 @@
../../plugins/testing-handbook-skills/skills/ruzzy
-1
View File
@@ -1 +0,0 @@
../../plugins/static-analysis/skills/sarif-parsing
-1
View File
@@ -1 +0,0 @@
../../plugins/seatbelt-sandboxer/skills/seatbelt-sandboxer
-1
View File
@@ -1 +0,0 @@
../../plugins/second-opinion/skills/second-opinion
-1
View File
@@ -1 +0,0 @@
../../plugins/building-secure-contracts/skills/secure-workflow-guide
-1
View File
@@ -1 +0,0 @@
../../plugins/static-analysis/skills/semgrep
-1
View File
@@ -1 +0,0 @@
../../plugins/semgrep-rule-creator/skills/semgrep-rule-creator
@@ -1 +0,0 @@
../../plugins/semgrep-rule-variant-creator/skills/semgrep-rule-variant-creator
-1
View File
@@ -1 +0,0 @@
../../plugins/sharp-edges/skills/sharp-edges
-1
View File
@@ -1 +0,0 @@
../../plugins/skill-improver/skills/skill-improver
@@ -1 +0,0 @@
../../plugins/building-secure-contracts/skills/solana-vulnerability-scanner
-1
View File
@@ -1 +0,0 @@
../../plugins/spec-to-code-compliance/skills/spec-to-code-compliance
@@ -1 +0,0 @@
../../plugins/building-secure-contracts/skills/substrate-vulnerability-scanner
-1
View File
@@ -1 +0,0 @@
../../plugins/supply-chain-risk-auditor/skills/supply-chain-risk-auditor
-1
View File
@@ -1 +0,0 @@
../../plugins/testing-handbook-skills/skills/testing-handbook-generator
-1
View File
@@ -1 +0,0 @@
../../plugins/building-secure-contracts/skills/token-integration-analyzer
-1
View File
@@ -1 +0,0 @@
../../plugins/building-secure-contracts/skills/ton-vulnerability-scanner
-1
View File
@@ -1 +0,0 @@
../../plugins/trailmark/skills/trailmark
-1
View File
@@ -1 +0,0 @@
../../plugins/trailmark/skills/trailmark-structural
-1
View File
@@ -1 +0,0 @@
../../plugins/trailmark/skills/trailmark-summary
-1
View File
@@ -1 +0,0 @@
../../plugins/variant-analysis/skills/variant-analysis
-1
View File
@@ -1 +0,0 @@
../../plugins/trailmark/skills/vector-forge
-1
View File
@@ -1 +0,0 @@
../../plugins/testing-handbook-skills/skills/wycheproof
-1
View File
@@ -1 +0,0 @@
../../plugins/yara-authoring/skills/yara-rule-authoring
-1
View File
@@ -1 +0,0 @@
../../plugins/zeroize-audit/skills/zeroize-audit
+199
View File
@@ -0,0 +1,199 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///
"""Check that this marketplace installs and loads through Claude Code."""
from __future__ import annotations
import argparse
import json
import os
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import Any
MARKETPLACE = Path(".claude-plugin/marketplace.json")
MANIFEST = Path(".claude-plugin/plugin.json")
DEFAULT_MCP = Path(".mcp.json")
def run_claude(claude_bin: str, repo: Path, env: dict[str, str], args: list[str]) -> str:
result = subprocess.run(
[claude_bin, *args],
cwd=repo,
env=env,
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
sys.stderr.write(result.stdout)
sys.stderr.write(result.stderr)
raise RuntimeError(f"claude {' '.join(args)} failed with {result.returncode}")
return result.stdout
def parse_json_output(output: str, context: str) -> Any:
text = output.strip()
if not text:
raise RuntimeError(f"{context}: expected JSON output, got empty response")
try:
return json.loads(text)
except json.JSONDecodeError:
starts = [index for index in (text.find("["), text.find("{")) if index != -1]
if not starts:
raise
return json.loads(text[min(starts) :])
def expected_mcp_servers(plugin_root: Path, errors: list[str]) -> set[str]:
expected: set[str] = set()
default_mcp = plugin_root / DEFAULT_MCP
if default_mcp.is_file():
try:
data = json.loads(default_mcp.read_text(encoding="utf-8"))
servers = data.get("mcpServers", data) if isinstance(data, dict) else {}
if isinstance(servers, dict):
expected.update(servers)
else:
errors.append(f"{plugin_root.name}: {default_mcp} mcpServers must be an object")
except (OSError, json.JSONDecodeError) as exc:
errors.append(f"{plugin_root.name}: cannot read {default_mcp}: {exc}")
manifest_path = plugin_root / MANIFEST
try:
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
errors.append(f"{plugin_root.name}: cannot read {manifest_path}: {exc}")
return expected
if not isinstance(manifest, dict) or "mcpServers" not in manifest:
return expected
configured = manifest["mcpServers"]
specs = configured if isinstance(configured, list) else [configured]
for spec in specs:
if isinstance(spec, str):
if Path(spec).is_absolute():
errors.append(f"{plugin_root.name}: manifest mcpServers path must be relative")
continue
try:
data = json.loads((plugin_root / spec).read_text(encoding="utf-8"))
servers = data.get("mcpServers", data) if isinstance(data, dict) else {}
if isinstance(servers, dict):
expected.update(servers)
else:
errors.append(f"{plugin_root.name}: {spec} mcpServers must be an object")
except (OSError, json.JSONDecodeError) as exc:
errors.append(f"{plugin_root.name}: cannot read manifest mcpServers {spec}: {exc}")
elif isinstance(spec, dict):
expected.update(spec)
else:
errors.append(f"{plugin_root.name}: unsupported manifest mcpServers entry for CI check")
return expected
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("repo", nargs="?", type=Path, default=Path(__file__).resolve().parents[2])
parser.add_argument("--claude-bin", default=os.environ.get("CLAUDE_BIN", "claude"))
args = parser.parse_args()
repo = args.repo.resolve()
claude_bin = shutil.which(args.claude_bin) if os.sep not in args.claude_bin else args.claude_bin
if claude_bin is None:
print(f"ERROR: claude binary not found: {args.claude_bin}", file=sys.stderr)
return 1
marketplace = json.loads((repo / MARKETPLACE).read_text(encoding="utf-8"))
marketplace_name = marketplace["name"]
plugin_names = [plugin["name"] for plugin in marketplace["plugins"]]
if not plugin_names:
print(f"ERROR: no plugins listed in {MARKETPLACE}", file=sys.stderr)
return 1
errors: list[str] = []
with tempfile.TemporaryDirectory(prefix="claude-load-check-") as tmp:
temp_root = Path(tmp)
home = temp_root / "home"
config_dir = temp_root / "claude"
home.mkdir()
config_dir.mkdir()
env = os.environ.copy()
env.update({"HOME": str(home), "CLAUDE_CONFIG_DIR": str(config_dir)})
run_claude(
claude_bin,
repo,
env,
["plugin", "validate", "--strict", str(repo / MARKETPLACE)],
)
run_claude(claude_bin, repo, env, ["plugin", "marketplace", "add", str(repo)])
available = parse_json_output(
run_claude(claude_bin, repo, env, ["plugin", "list", "--available", "--json"]),
"claude plugin list --available --json",
)
available_ids = {plugin["pluginId"] for plugin in available.get("available", [])}
expected_ids = {f"{name}@{marketplace_name}" for name in plugin_names}
missing_available = sorted(expected_ids - available_ids)
if missing_available:
errors.append("missing available plugins: " + ", ".join(missing_available))
for plugin_name in plugin_names:
run_claude(
claude_bin,
repo,
env,
["plugin", "validate", "--strict", str(repo / "plugins" / plugin_name / MANIFEST)],
)
run_claude(
claude_bin,
repo,
env,
["plugin", "install", f"{plugin_name}@{marketplace_name}"],
)
installed = parse_json_output(
run_claude(claude_bin, repo, env, ["plugin", "list", "--json"]),
"claude plugin list --json",
)
installed_by_id = {plugin["id"]: plugin for plugin in installed}
missing_installed = sorted(expected_ids - set(installed_by_id))
if missing_installed:
errors.append("missing installed plugins: " + ", ".join(missing_installed))
mcp_count = 0
for plugin_name in plugin_names:
plugin_id = f"{plugin_name}@{marketplace_name}"
plugin = installed_by_id.get(plugin_id, {})
for error in plugin.get("errors", []) or []:
errors.append(f"{plugin_id}: {error}")
expected_mcp = expected_mcp_servers(repo / "plugins" / plugin_name, errors)
loaded_mcp = set((plugin.get("mcpServers") or {}).keys())
mcp_count += len(loaded_mcp)
if loaded_mcp != expected_mcp:
errors.append(
f"{plugin_id}: loaded MCP servers {sorted(loaded_mcp)}, "
f"expected {sorted(expected_mcp)}"
)
print(
f"loaded Claude marketplace {marketplace_name} with "
f"{len(plugin_names) - len(missing_installed)} plugins and {mcp_count} MCP servers"
)
if errors:
print(f"\nFound {len(errors)} Claude loadability error(s):", file=sys.stderr)
for error in errors:
print(f" - {error}", file=sys.stderr)
return 1
print("All Claude loadability checks passed")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+284
View File
@@ -0,0 +1,284 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///
"""Check that this Claude plugin marketplace loads through Codex."""
from __future__ import annotations
import argparse
import json
import os
import select
import shutil
import subprocess
import sys
import tempfile
import time
from pathlib import Path
from typing import Any
MARKETPLACE = Path(".claude-plugin/marketplace.json")
MANIFEST = Path(".claude-plugin/plugin.json")
DEFAULT_MCP = Path(".mcp.json")
def read_rpc_message(proc: subprocess.Popen[str], log_path: Path, timeout: float) -> dict[str, Any]:
if proc.stdout is None:
raise RuntimeError("codex app-server stdout is unavailable")
deadline = time.monotonic() + timeout
while True:
log_tail = "\n".join(
log_path.read_text(encoding="utf-8", errors="replace").splitlines()[-80:]
)
if proc.poll() is not None:
raise RuntimeError(f"codex app-server exited with {proc.returncode}\n{log_tail}")
remaining = deadline - time.monotonic()
if remaining <= 0:
raise TimeoutError(f"timed out waiting for codex app-server\n{log_tail}")
ready, _, _ = select.select([proc.stdout], [], [], min(remaining, 0.2))
if not ready:
continue
line = proc.stdout.readline().strip()
if not line.startswith("{"):
continue
try:
message = json.loads(line)
except json.JSONDecodeError:
continue
if isinstance(message, dict):
return message
def expected_mcp_servers(plugin_root: Path, errors: list[str]) -> set[str]:
manifest_path = plugin_root / MANIFEST
try:
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
errors.append(f"{plugin_root.name}: cannot read {manifest_path}: {exc}")
return set()
mcp_path = plugin_root / DEFAULT_MCP
if isinstance(manifest, dict) and "mcpServers" in manifest:
configured = manifest["mcpServers"]
if not isinstance(configured, str) or not configured:
errors.append(
f"{plugin_root.name}: manifest mcpServers must be a non-empty string path"
)
return set()
if Path(configured).is_absolute():
errors.append(f"{plugin_root.name}: manifest mcpServers must be relative")
return set()
mcp_path = plugin_root / configured
elif not mcp_path.exists():
return set()
try:
data = json.loads(mcp_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
errors.append(f"{plugin_root.name}: cannot read {mcp_path}: {exc}")
return set()
if not isinstance(data, dict):
errors.append(f"{plugin_root.name}: {mcp_path} must be a JSON object")
return set()
servers = data.get("mcpServers", data)
if not isinstance(servers, dict):
errors.append(f"{plugin_root.name}: {mcp_path} mcpServers must be an object")
return set()
return set(servers)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("repo", nargs="?", type=Path, default=Path(__file__).resolve().parents[2])
parser.add_argument("--codex-bin", default=os.environ.get("CODEX_BIN", "codex"))
parser.add_argument("--timeout", type=float, default=60.0)
args = parser.parse_args()
repo = args.repo.resolve()
codex_bin = shutil.which(args.codex_bin) if os.sep not in args.codex_bin else args.codex_bin
if codex_bin is None:
print(f"ERROR: codex binary not found: {args.codex_bin}", file=sys.stderr)
return 1
marketplace_path = repo / MARKETPLACE
marketplace = json.loads(marketplace_path.read_text(encoding="utf-8"))
marketplace_name = marketplace["name"]
plugin_names = [plugin["name"] for plugin in marketplace["plugins"]]
if not plugin_names:
print(f"ERROR: no plugins listed in {MARKETPLACE}", file=sys.stderr)
return 1
errors: list[str] = []
loaded_plugin_count = 0
loaded_skill_count = 0
loaded_mcp_count = 0
request_id = 0
tmp = tempfile.TemporaryDirectory(prefix="codex-load-check-")
temp_root = Path(tmp.name)
home = temp_root / "home"
codex_home = temp_root / "codex-home"
home.mkdir()
codex_home.mkdir()
(codex_home / "config.toml").write_text(
"[features]\nplugins = true\nplugin_hooks = true\n",
encoding="utf-8",
)
log_path = temp_root / "app-server.stderr.log"
log_file = log_path.open("w+", encoding="utf-8")
env = os.environ.copy()
env.update(
{
"HOME": str(home),
"USERPROFILE": str(home),
"CODEX_HOME": str(codex_home),
"CODEX_APP_SERVER_DISABLE_MANAGED_CONFIG": "1",
"RUST_LOG": env.get("RUST_LOG", "warn"),
}
)
proc = subprocess.Popen(
[
codex_bin,
"app-server",
"--listen",
"stdio://",
"--enable",
"plugins",
"--enable",
"plugin_hooks",
],
cwd=str(repo),
env=env,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=log_file,
text=True,
bufsize=1,
)
def write(message: dict[str, Any]) -> None:
if proc.stdin is None:
raise RuntimeError("codex app-server stdin is unavailable")
proc.stdin.write(json.dumps(message, separators=(",", ":")) + "\n")
proc.stdin.flush()
def request(method: str, params: dict[str, Any]) -> Any:
nonlocal request_id
request_id += 1
write({"jsonrpc": "2.0", "id": request_id, "method": method, "params": params})
skipped_errors: list[str] = []
while True:
try:
message = read_rpc_message(proc, log_path, args.timeout)
except TimeoutError as exc:
if skipped_errors:
detail = "\n".join(skipped_errors[-3:])
raise TimeoutError(f"{exc}\nskipped error messages:\n{detail}") from exc
raise
if message.get("id") != request_id:
if "error" in message:
skipped_errors.append(json.dumps(message)[:500])
continue
if "error" in message:
raise RuntimeError(f"{method} failed: {message['error']}")
return message.get("result")
try:
request(
"initialize",
{
"clientInfo": {
"name": "codex-load-check",
"title": "Codex Load Check",
"version": "0",
},
"capabilities": {
"experimentalApi": True,
"requestAttestation": False,
"optOutNotificationMethods": [],
},
},
)
write({"jsonrpc": "2.0", "method": "initialized"})
listed = request("plugin/list", {"cwds": [str(repo)], "marketplaceKinds": ["local"]})
for error in listed.get("marketplaceLoadErrors", []):
errors.append(f"plugin/list: {error.get('marketplacePath')}: {error.get('message')}")
matching = [
item for item in listed.get("marketplaces", []) if item.get("name") == marketplace_name
]
if not matching:
errors.append(f"plugin/list: missing marketplace {marketplace_name!r}")
else:
listed_plugins = {
plugin.get("name") for item in matching for plugin in item.get("plugins", [])
}
missing = sorted(set(plugin_names) - listed_plugins)
if missing:
errors.append("plugin/list: missing plugins: " + ", ".join(missing))
for plugin_name in plugin_names:
plugin_root = repo / "plugins" / plugin_name
skill_paths = list((plugin_root / "skills").rglob("SKILL.md"))
expected_skills = len(skill_paths)
expected_mcp = expected_mcp_servers(plugin_root, errors)
try:
plugin = request(
"plugin/read",
{
"marketplacePath": str(marketplace_path),
"remoteMarketplaceName": None,
"pluginName": plugin_name,
},
).get("plugin", {})
except Exception as exc: # noqa: BLE001 - CI should show RPC failures.
errors.append(f"{plugin_name}: {exc}")
continue
loaded_skills = len(plugin.get("skills", []))
loaded_mcp_value = plugin.get("mcpServers", {})
loaded_mcp = (
set(loaded_mcp_value) if isinstance(loaded_mcp_value, (dict, list)) else set()
)
if loaded_skills != expected_skills:
errors.append(
f"{plugin_name}: loaded {loaded_skills} skills, expected {expected_skills}"
)
if loaded_mcp != expected_mcp:
errors.append(
f"{plugin_name}: loaded MCP servers {sorted(loaded_mcp)}, "
f"expected {sorted(expected_mcp)}"
)
loaded_plugin_count += 1
loaded_skill_count += loaded_skills
loaded_mcp_count += len(loaded_mcp)
finally:
if proc.poll() is None:
proc.terminate()
try:
proc.wait(timeout=3)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait(timeout=3)
log_file.close()
tmp.cleanup()
print(
f"loaded marketplace {marketplace_name} with {loaded_plugin_count} plugins, "
f"{loaded_skill_count} skills, and {loaded_mcp_count} MCP servers"
)
if errors:
print(f"\nFound {len(errors)} Codex loadability error(s):", file=sys.stderr)
for error in errors:
print(f" - {error}", file=sys.stderr)
return 1
print("All Codex loadability checks passed")
return 0
if __name__ == "__main__":
raise SystemExit(main())
-119
View File
@@ -1,119 +0,0 @@
#!/usr/bin/env python3
from __future__ import annotations
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
PLUGINS_DIR = REPO_ROOT / "plugins"
CODEX_SKILLS_DIR = REPO_ROOT / ".codex" / "skills"
def plugin_skill_dirs() -> dict[str, Path]:
if not PLUGINS_DIR.is_dir():
raise SystemExit(
f"Plugins directory not found: {PLUGINS_DIR}. "
f"Is this script running from the repository root?"
)
mapping: dict[str, Path] = {}
for skill_md in sorted(PLUGINS_DIR.glob("*/skills/*/SKILL.md")):
skill_dir = skill_md.parent
name = skill_dir.name
if name in mapping:
raise SystemExit(
f"Duplicate plugin skill name '{name}' at {mapping[name]} and {skill_dir}"
)
mapping[name] = skill_dir
return mapping
def codex_skill_entries() -> dict[str, Path]:
mapping: dict[str, Path] = {}
if not CODEX_SKILLS_DIR.exists():
return mapping
for entry in sorted(CODEX_SKILLS_DIR.iterdir()):
mapping[entry.name] = entry
return mapping
def rel(path: Path) -> str:
try:
return str(path.relative_to(REPO_ROOT))
except ValueError:
return str(path)
def main() -> int:
plugin_skills = plugin_skill_dirs()
codex_entries = codex_skill_entries()
errors: list[str] = []
if not CODEX_SKILLS_DIR.exists():
print(
"Codex skills directory .codex/skills/ does not exist.",
file=sys.stderr,
)
return 1
for name, skill_dir in plugin_skills.items():
codex_entry = codex_entries.get(name)
if codex_entry is None:
errors.append(
f"Missing Codex mapping for plugin skill '{name}'. "
f"Create with: ln -sfn "
f"../../{rel(skill_dir)} .codex/skills/{name}"
)
continue
if codex_entry.is_symlink():
resolved = codex_entry.resolve()
if not resolved.exists():
errors.append(
f"Dangling symlink for '{name}': "
f"{rel(codex_entry)} -> {resolved} "
f"(target does not exist). "
f"Expected: {skill_dir.resolve()}"
)
elif resolved != skill_dir.resolve():
errors.append(
f"Mismatched Codex symlink for '{name}': "
f"{rel(codex_entry)} -> {resolved}, "
f"expected {skill_dir.resolve()}"
)
else:
skill_md = codex_entry / "SKILL.md"
if not skill_md.exists():
errors.append(
f"Codex entry '{name}' is not a symlink and has no SKILL.md at {rel(skill_md)}"
)
for name, codex_entry in codex_entries.items():
if name in plugin_skills:
continue
skill_md = codex_entry / "SKILL.md"
if not skill_md.exists():
errors.append(f"Codex-only entry '{name}' must contain SKILL.md at {rel(skill_md)}")
if errors:
print(
"Codex skill validation failed:\n",
file=sys.stderr,
)
for error in errors:
print(f"- {error}", file=sys.stderr)
print(
"\nFix by adding the missing symlinks to .codex/skills/ and committing the changes.",
file=sys.stderr,
)
return 1
print(
f"Validated {len(plugin_skills)} plugin skills "
f"against {len(codex_entries)} Codex entries "
f"successfully."
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+17 -2
View File
@@ -90,5 +90,20 @@ jobs:
- name: Validate plugin metadata
run: python3 .github/scripts/validate_plugin_metadata.py
- name: Validate Codex skill mappings
run: python3 .github/scripts/validate_codex_skills.py
- name: Install Claude Code CLI
run: |
npm install --global --prefix "$RUNNER_TEMP/claude-cli" @anthropic-ai/claude-code@latest
echo "$RUNNER_TEMP/claude-cli/bin" >> "$GITHUB_PATH"
"$RUNNER_TEMP/claude-cli/bin/claude" --version
- name: Check Claude loadability
run: python3 .github/scripts/check_claude_loadability.py
- name: Install Codex CLI
run: |
npm install --global --prefix "$RUNNER_TEMP/codex-cli" @openai/codex@latest
echo "$RUNNER_TEMP/codex-cli/bin" >> "$GITHUB_PATH"
"$RUNNER_TEMP/codex-cli/bin/codex" --version
- name: Check Codex loadability
run: python3 .github/scripts/check_codex_loadability.py
+226
View File
@@ -0,0 +1,226 @@
# Contributing Skills
## Resources
**Official Anthropic documentation (always check these first):**
- [Claude Code Plugins](https://code.claude.com/docs/en/plugins)
- [Agent Skills](https://code.claude.com/docs/en/skills)
- [Best Practices](https://code.claude.com/docs/en/skills#best-practices)
- [Skill Authoring Best Practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices) — progressive disclosure, degrees of freedom, workflow checklists
- [The Complete Guide to Building Skills](https://resources.anthropic.com/hubfs/The-Complete-Guide-to-Building-Skill-for-Claude.pdf) ([text](https://gist.github.com/liskl/269ae33835ab4bfdd6140f0beb909873)) — evaluation-driven development, iterative testing
**Reference skills** - learn by example at different complexity levels:
| Complexity | Skill | What It Demonstrates |
|------------|-------|---------------------|
| **Basic** | [ask-questions-if-underspecified](plugins/ask-questions-if-underspecified/) | Minimal frontmatter, simple guidance |
| **Intermediate** | [constant-time-analysis](plugins/constant-time-analysis/) | Python package, references/, language-specific docs |
| **Advanced** | [culture-index](plugins/culture-index/) | Scripts, workflows/, templates/, PDF extraction, multiple entry points |
**When in doubt, copy one of these and adapt it.**
**Deep dives on skill authoring:**
- [Claude Skills Deep Dive](https://leehanchung.github.io/blogs/2025/10/26/claude-skills-deep-dive/) - Comprehensive analysis of skill architecture
**Example plugins worth studying:**
- [superpowers](https://github.com/obra/superpowers) - Advanced workflow patterns, TDD enforcement, multi-skill orchestration
- [compound-engineering-plugin](https://github.com/EveryInc/compound-engineering-plugin) - Production plugin structure
- [getsentry/skills](https://github.com/getsentry/skills) — Production Sentry skills; `security-review` is a standout routing + progressive disclosure example
**For Claude:** Use the `claude-code-guide` subagent for plugin/skill questions - it has access to official documentation.
## Technical Reference
### Codex Compatibility
This repository uses Claude plugin marketplace metadata as the canonical source for both Claude Code and Codex. Codex supports `.claude-plugin/marketplace.json` and `plugins/<name>/.claude-plugin/plugin.json` directly, so do not add duplicate Codex-only sidecar metadata.
Rules:
- Do not add `.agents/plugins/marketplace.json`, `.codex/`, or `plugins/<name>/.codex-plugin/`.
- Keep plugin components at the plugin root using Codex-compatible default paths: `skills/`, `hooks/hooks.json`, `.mcp.json`, and `.app.json`.
- If a plugin needs MCP configuration, put it in `.mcp.json` at the plugin root rather than embedding an object in `.claude-plugin/plugin.json`.
- Before submitting, run:
```sh
python3 .github/scripts/check_claude_loadability.py
python3 .github/scripts/check_codex_loadability.py
```
- If this check fails in CI, update the canonical Claude marketplace or plugin root components so Codex can load them through Claude marketplace compatibility.
### Plugin Structure
```
plugins/
<plugin-name>/
.claude-plugin/
plugin.json # Plugin metadata (name, version, description, author)
commands/ # Optional: slash commands
agents/ # Optional: autonomous agents
skills/ # Optional: knowledge/guidance
<skill-name>/
SKILL.md # Entry point with frontmatter
references/ # Optional: detailed docs
workflows/ # Optional: step-by-step guides
scripts/ # Optional: utility scripts
hooks/ # Optional: event hooks
README.md # Plugin documentation
```
**Important**: Component directories (`skills/`, `commands/`, `agents/`, `hooks/`) must be at the plugin root, NOT inside `.claude-plugin/`. Only `plugin.json` belongs in `.claude-plugin/`.
### Frontmatter
```yaml
---
name: skill-name # kebab-case, max 64 chars
description: "Third-person description of what it does and when to use it"
allowed-tools: # Optional: restrict to needed tools only
- Read
- Grep
---
```
### Naming Conventions
- **kebab-case**: `constant-time-analysis`, not `constantTimeAnalysis`
- **Gerund form preferred**: `analyzing-contracts`, `processing-pdfs` (not `contract-analyzer`, `pdf-processor`)
- **Avoid vague names**: `helper`, `utils`, `tools`, `misc`
- **Avoid reserved words**: `anthropic`, `claude`
### Path Handling
- Use `{baseDir}` for paths, **never hardcode** absolute paths
- Use forward slashes (`/`) even on Windows
### Python Scripts
When skills include Python scripts with dependencies:
1. **Use PEP 723 inline metadata** - Declare dependencies in the script header:
```python
# /// script
# requires-python = ">=3.11"
# dependencies = ["requests>=2.28", "pydantic>=2.0"]
# ///
```
2. **Use `uv run`** - Enables automatic dependency resolution:
```bash
uv run {baseDir}/scripts/process.py input.pdf
```
3. **Include `pyproject.toml`** - Keep in `scripts/` for development tooling (ruff, etc.)
4. **Document system dependencies** - List non-Python deps (poppler, tesseract) in workflows with platform-specific install commands
### Hooks
PreToolUse hooks run on every Bash command—performance is critical:
- **Prefer shell + jq** over Python—interpreter startup (Python + tree-sitter) adds noticeable latency
- **Fast-fail early** - exit 0 immediately for non-matching commands so most invocations are instant
- **Favor regex over AST parsing** - accept rare false positives if performance gain is significant and Claude can rephrase
- **Anticipate false positive patterns** - diagnostic commands (`which python`), search tools (`grep python`), and filenames (`cat python.txt`) shouldn't trigger interception
- **Document tradeoffs** in PR descriptions so reviewers understand deliberate design choices
## Quality Standards
These are Trail of Bits house standards on top of Anthropic's requirements.
### Description Quality
Your skill competes with 100+ others. The description must trigger correctly.
- **Third-person voice**: "Analyzes X" not "I help with X"
- **Include triggers**: "Use when auditing Solidity" not just "Smart contract tool"
- **Be specific**: "Detects reentrancy vulnerabilities" not "Helps with security"
### Value-Add
Skills should provide guidance Claude doesn't already have, not duplicate reference material.
- **Behavioral guidance over reference dumps** - Don't paste entire specs; teach when and how to look things up
- **Explain WHY, not just WHAT** - Include trade-offs, decision criteria, judgment calls
- **Document anti-patterns WITH explanations** - Say why something is wrong, not just that it's wrong
**Example**: The DWARF skill doesn't include the full DWARF spec. It teaches Claude how to use `dwarfdump`, `readelf`, and `pyelftools` to look up what it needs, plus judgment about when each tool is appropriate.
### Scope Boundaries
Prescriptiveness should match task risk:
- **Strict for fragile tasks** - Security audits, crypto implementations, compliance checks need rigid step-by-step enforcement
- **Flexible for variable tasks** - Code exploration, documentation, refactoring can offer options and judgment calls
### Required Sections
Every SKILL.md must include:
```markdown
## When to Use
[Specific scenarios where this skill applies]
## When NOT to Use
[Scenarios where another approach is better]
```
### Security Skills
For audit/security skills, also include:
```markdown
## Rationalizations to Reject
[Common shortcuts or rationalizations that lead to missed findings]
```
### Content Organization
- Keep SKILL.md **under 500 lines** - split into `references/`, `workflows/`
- Use **progressive disclosure** - quick start first, details in linked files
- **One level deep** - SKILL.md links to files, files don't chain to more files
Note: Directory depth is fine (`references/guides/topic.md`). Reference *chains* are not (`SKILL.md → file1.md → file2.md` where file1 references file2). The problem is chained references, not nested folders.
### Progressive Disclosure Pattern
```markdown
## Quick Start
[Core instructions here]
## Advanced Usage
See [ADVANCED.md](references/ADVANCED.md) for detailed patterns.
## API Reference
See [API.md](references/API.md) for complete method documentation.
```
## PR Checklist
Before submitting:
**Technical (CI validates these):**
- [ ] Valid YAML frontmatter with `name` and `description`
- [ ] Name is kebab-case, ≤64 characters
- [ ] All referenced files exist
- [ ] No hardcoded paths (`/Users/...`, `/home/...`)
- [ ] `python3 .github/scripts/check_claude_loadability.py` passes
- [ ] `python3 .github/scripts/check_codex_loadability.py` passes
**Quality (reviewers check these):**
- [ ] Description triggers correctly (third-person, specific)
- [ ] "When to use" and "When NOT to use" sections present
- [ ] Examples are concrete (input → output)
- [ ] Explains WHY, not just WHAT
**Documentation:**
- [ ] Plugin has README.md
- [ ] Added to root README.md table
- [ ] Registered in root `.claude-plugin/marketplace.json` (repo-level, not the plugin's own `.claude-plugin/`)
- [ ] Added to CODEOWNERS with plugin-specific ownership (`/plugins/<name>/ @gh-username @dguido`)
- To find the GitHub username: run `gh api user --jq .login` (most reliable — uses authenticated GitHub identity)
**Version updates (for existing plugins):**
- [ ] Increment version in both `plugins/<name>/.claude-plugin/plugin.json` and the root `.claude-plugin/marketplace.json` when making substantive changes (clients only update plugins when the version number increases)
- [ ] Ensure version numbers match between the plugin's `plugin.json` and its entry in the root `.claude-plugin/marketplace.json`
+1 -221
View File
@@ -1,221 +1 @@
# Contributing Skills
## Resources
**Official Anthropic documentation (always check these first):**
- [Claude Code Plugins](https://code.claude.com/docs/en/plugins)
- [Agent Skills](https://code.claude.com/docs/en/skills)
- [Best Practices](https://code.claude.com/docs/en/skills#best-practices)
- [Skill Authoring Best Practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices) — progressive disclosure, degrees of freedom, workflow checklists
- [The Complete Guide to Building Skills](https://resources.anthropic.com/hubfs/The-Complete-Guide-to-Building-Skill-for-Claude.pdf) ([text](https://gist.github.com/liskl/269ae33835ab4bfdd6140f0beb909873)) — evaluation-driven development, iterative testing
**Reference skills** - learn by example at different complexity levels:
| Complexity | Skill | What It Demonstrates |
|------------|-------|---------------------|
| **Basic** | [ask-questions-if-underspecified](plugins/ask-questions-if-underspecified/) | Minimal frontmatter, simple guidance |
| **Intermediate** | [constant-time-analysis](plugins/constant-time-analysis/) | Python package, references/, language-specific docs |
| **Advanced** | [culture-index](plugins/culture-index/) | Scripts, workflows/, templates/, PDF extraction, multiple entry points |
**When in doubt, copy one of these and adapt it.**
**Deep dives on skill authoring:**
- [Claude Skills Deep Dive](https://leehanchung.github.io/blogs/2025/10/26/claude-skills-deep-dive/) - Comprehensive analysis of skill architecture
**Example plugins worth studying:**
- [superpowers](https://github.com/obra/superpowers) - Advanced workflow patterns, TDD enforcement, multi-skill orchestration
- [compound-engineering-plugin](https://github.com/EveryInc/compound-engineering-plugin) - Production plugin structure
- [getsentry/skills](https://github.com/getsentry/skills) — Production Sentry skills; `security-review` is a standout routing + progressive disclosure example
**For Claude:** Use the `claude-code-guide` subagent for plugin/skill questions - it has access to official documentation.
## Technical Reference
### Codex Compatibility
This repository now supports both Claude plugin discovery and Codex-native skill discovery.
Rules:
- If a plugin adds `skills/<name>/SKILL.md`, it must also remain reachable through `.codex/skills/<name>`.
- If a plugin is command/hook/agent-only and has no `skills/` directory, add an explicit Codex wrapper skill under `.codex/skills/<plugin-name>/SKILL.md` or document why no Codex equivalent is intended.
- Before submitting, run:
```sh
python3 .github/scripts/validate_codex_skills.py
```
- If this check fails in CI, the remediation path should be local and mechanical: run the installer or update the `.codex/skills/` mapping, then commit the resulting changes.
### Plugin Structure
```
plugins/
<plugin-name>/
.claude-plugin/
plugin.json # Plugin metadata (name, version, description, author)
commands/ # Optional: slash commands
agents/ # Optional: autonomous agents
skills/ # Optional: knowledge/guidance
<skill-name>/
SKILL.md # Entry point with frontmatter
references/ # Optional: detailed docs
workflows/ # Optional: step-by-step guides
scripts/ # Optional: utility scripts
hooks/ # Optional: event hooks
README.md # Plugin documentation
```
**Important**: Component directories (`skills/`, `commands/`, `agents/`, `hooks/`) must be at the plugin root, NOT inside `.claude-plugin/`. Only `plugin.json` belongs in `.claude-plugin/`.
### Frontmatter
```yaml
---
name: skill-name # kebab-case, max 64 chars
description: "Third-person description of what it does and when to use it"
allowed-tools: Read Grep # Optional: restrict to needed tools only
---
```
### Naming Conventions
- **kebab-case**: `constant-time-analysis`, not `constantTimeAnalysis`
- **Gerund form preferred**: `analyzing-contracts`, `processing-pdfs` (not `contract-analyzer`, `pdf-processor`)
- **Avoid vague names**: `helper`, `utils`, `tools`, `misc`
- **Avoid reserved words**: `anthropic`, `claude`
### Path Handling
- Use `{baseDir}` for paths, **never hardcode** absolute paths
- Use forward slashes (`/`) even on Windows
### Python Scripts
When skills include Python scripts with dependencies:
1. **Use PEP 723 inline metadata** - Declare dependencies in the script header:
```python
# /// script
# requires-python = ">=3.11"
# dependencies = ["requests>=2.28", "pydantic>=2.0"]
# ///
```
2. **Use `uv run`** - Enables automatic dependency resolution:
```bash
uv run {baseDir}/scripts/process.py input.pdf
```
3. **Include `pyproject.toml`** - Keep in `scripts/` for development tooling (ruff, etc.)
4. **Document system dependencies** - List non-Python deps (poppler, tesseract) in workflows with platform-specific install commands
### Hooks
PreToolUse hooks run on every Bash command—performance is critical:
- **Prefer shell + jq** over Python—interpreter startup (Python + tree-sitter) adds noticeable latency
- **Fast-fail early** - exit 0 immediately for non-matching commands so most invocations are instant
- **Favor regex over AST parsing** - accept rare false positives if performance gain is significant and Claude can rephrase
- **Anticipate false positive patterns** - diagnostic commands (`which python`), search tools (`grep python`), and filenames (`cat python.txt`) shouldn't trigger interception
- **Document tradeoffs** in PR descriptions so reviewers understand deliberate design choices
## Quality Standards
These are Trail of Bits house standards on top of Anthropic's requirements.
### Description Quality
Your skill competes with 100+ others. The description must trigger correctly.
- **Third-person voice**: "Analyzes X" not "I help with X"
- **Include triggers**: "Use when auditing Solidity" not just "Smart contract tool"
- **Be specific**: "Detects reentrancy vulnerabilities" not "Helps with security"
### Value-Add
Skills should provide guidance Claude doesn't already have, not duplicate reference material.
- **Behavioral guidance over reference dumps** - Don't paste entire specs; teach when and how to look things up
- **Explain WHY, not just WHAT** - Include trade-offs, decision criteria, judgment calls
- **Document anti-patterns WITH explanations** - Say why something is wrong, not just that it's wrong
**Example**: The DWARF skill doesn't include the full DWARF spec. It teaches Claude how to use `dwarfdump`, `readelf`, and `pyelftools` to look up what it needs, plus judgment about when each tool is appropriate.
### Scope Boundaries
Prescriptiveness should match task risk:
- **Strict for fragile tasks** - Security audits, crypto implementations, compliance checks need rigid step-by-step enforcement
- **Flexible for variable tasks** - Code exploration, documentation, refactoring can offer options and judgment calls
### Required Sections
Every SKILL.md must include:
```markdown
## When to Use
[Specific scenarios where this skill applies]
## When NOT to Use
[Scenarios where another approach is better]
```
### Security Skills
For audit/security skills, also include:
```markdown
## Rationalizations to Reject
[Common shortcuts or rationalizations that lead to missed findings]
```
### Content Organization
- Keep SKILL.md **under 500 lines** - split into `references/`, `workflows/`
- Use **progressive disclosure** - quick start first, details in linked files
- **One level deep** - SKILL.md links to files, files don't chain to more files
Note: Directory depth is fine (`references/guides/topic.md`). Reference *chains* are not (`SKILL.md → file1.md → file2.md` where file1 references file2). The problem is chained references, not nested folders.
### Progressive Disclosure Pattern
```markdown
## Quick Start
[Core instructions here]
## Advanced Usage
See [ADVANCED.md](references/ADVANCED.md) for detailed patterns.
## API Reference
See [API.md](references/API.md) for complete method documentation.
```
## PR Checklist
Before submitting:
**Technical (CI validates these):**
- [ ] Valid YAML frontmatter with `name` and `description`
- [ ] Name is kebab-case, ≤64 characters
- [ ] All referenced files exist
- [ ] No hardcoded paths (`/Users/...`, `/home/...`)
- [ ] `python3 .github/scripts/validate_codex_skills.py` passes
**Quality (reviewers check these):**
- [ ] Description triggers correctly (third-person, specific)
- [ ] "When to use" and "When NOT to use" sections present
- [ ] Examples are concrete (input → output)
- [ ] Explains WHY, not just WHAT
**Documentation:**
- [ ] Plugin has README.md
- [ ] Added to root README.md table
- [ ] Registered in root `.claude-plugin/marketplace.json` (repo-level, not the plugin's own `.claude-plugin/`)
- [ ] Added to CODEOWNERS with plugin-specific ownership (`/plugins/<name>/ @gh-username @dguido`)
- To find the GitHub username: run `gh api user --jq .login` (most reliable — uses authenticated GitHub identity)
**Version updates (for existing plugins):**
- [ ] Increment version in both `plugins/<name>/.claude-plugin/plugin.json` and the root `.claude-plugin/marketplace.json` when making substantive changes (clients only update plugins when the version number increases)
- [ ] Ensure version numbers match between the plugin's `plugin.json` and its entry in the root `.claude-plugin/marketplace.json`
@AGENTS.md
+6 -7
View File
@@ -1,6 +1,6 @@
# Trail of Bits Skills Marketplace
A Claude Code plugin marketplace from Trail of Bits providing skills to enhance AI-assisted security analysis, testing, and development workflows.
A Claude Code plugin marketplace from Trail of Bits providing skills to enhance AI-assisted security analysis, testing, and development workflows. Codex can load this marketplace through its Claude marketplace compatibility.
> Also see: [claude-code-config](https://github.com/trailofbits/claude-code-config) · [skills-curated](https://github.com/trailofbits/skills-curated) · [claude-code-devcontainer](https://github.com/trailofbits/claude-code-devcontainer) · [dropkit](https://github.com/trailofbits/dropkit)
@@ -20,17 +20,16 @@ A Claude Code plugin marketplace from Trail of Bits providing skills to enhance
### Codex
Codex-native skill discovery is supported via the sidecar `.codex/skills/` tree in this repository.
Codex supports Claude plugin marketplaces directly, so this repository does not need Codex-specific sidecar metadata.
Install with:
Install the marketplace with:
```sh
git clone https://github.com/trailofbits/skills.git ~/.codex/trailofbits-skills
~/.codex/trailofbits-skills/.codex/scripts/install-for-codex.sh
codex plugin marketplace add trailofbits/skills
codex plugin list
codex plugin add <plugin-name>@trailofbits
```
See [`.codex/INSTALL.md`](.codex/INSTALL.md) for additional details.
### Local Development
To add the marketplace locally (e.g., for testing or development), navigate to the **parent directory** of this repository:
@@ -1,6 +1,6 @@
{
"name": "claude-in-chrome-troubleshooting",
"version": "1.0.0",
"version": "1.1.0",
"description": "Diagnose and fix Claude in Chrome MCP extension connectivity issues",
"author": {
"name": "Dan Guido"
@@ -1,5 +1,5 @@
---
name: claude-in-chrome-troubleshooting
name: chrome-mcp-troubleshooting
description: Diagnose and fix Claude in Chrome MCP extension connectivity issues. Use when mcp__claude-in-chrome__* tools fail, return "Browser extension is not connected", or behave erratically.
---
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "gh-cli",
"version": "1.4.1",
"version": "1.5.0",
"description": "Intercepts GitHub URL fetches and curl/wget commands, redirecting to the authenticated gh CLI.",
"author": {
"name": "William Tan",
+5 -5
View File
@@ -7,7 +7,7 @@
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/persist-session-id.sh\""
"command": "bash \"${CLAUDE_PLUGIN_ROOT:-.}/hooks/persist-session-id.sh\""
}
]
},
@@ -16,7 +16,7 @@
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/setup-shims.sh\""
"command": "bash \"${CLAUDE_PLUGIN_ROOT:-.}/hooks/setup-shims.sh\""
}
]
}
@@ -27,7 +27,7 @@
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/intercept-github-fetch.sh\""
"command": "bash \"${CLAUDE_PLUGIN_ROOT:-.}/hooks/intercept-github-fetch.sh\""
}
]
},
@@ -36,7 +36,7 @@
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/intercept-github-curl.sh\""
"command": "bash \"${CLAUDE_PLUGIN_ROOT:-.}/hooks/intercept-github-curl.sh\""
}
]
}
@@ -47,7 +47,7 @@
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/cleanup-clones.sh\""
"command": "bash \"${CLAUDE_PLUGIN_ROOT:-.}/hooks/cleanup-clones.sh\""
}
]
}
@@ -32,6 +32,6 @@ gh pr view 123 --repo owner/repo
gh api repos/owner/repo/pulls
```
For the original Claude plugin implementation, see:
For the hook implementation, see:
- `plugins/gh-cli/README.md`
- `plugins/gh-cli/hooks/`
@@ -1,6 +1,6 @@
{
"name": "modern-python",
"version": "1.5.0",
"version": "1.5.1",
"description": "Modern Python best practices. Use when creating new Python projects, and writing Python scripts, or migrating existing projects from legacy tools.",
"author": {
"name": "William Tan",
+1 -1
View File
@@ -7,7 +7,7 @@
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/setup-shims.sh\""
"command": "bash \"${CLAUDE_PLUGIN_ROOT:-.}/hooks/setup-shims.sh\""
}
]
}
@@ -1,6 +1,6 @@
{
"name": "skill-improver",
"version": "1.0.2",
"version": "1.0.3",
"description": "Automatically reviews and fixes Claude Code skills through iterative refinement until they meet quality standards. Requires plugin-dev plugin.",
"author": {
"name": "Paweł Płatek",
+1 -1
View File
@@ -6,7 +6,7 @@
"hooks": [
{
"type": "command",
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.sh\"",
"command": "\"${CLAUDE_PLUGIN_ROOT:-.}/hooks/stop-hook.sh\"",
"timeout": 30
}
]
@@ -1,19 +1,10 @@
{
"name": "zeroize-audit",
"version": "0.1.0",
"version": "0.1.1",
"description": "Detects missing or compiler-optimized zeroization of sensitive data with assembly and control-flow analysis",
"author": {
"name": "Trail of Bits",
"email": "opensource@trailofbits.com",
"url": "https://github.com/trailofbits"
},
"mcpServers": {
"serena": {
"type": "stdio",
"command": "uvx",
"args": ["--from", "git+https://github.com/oraios/serena", "serena", "start-mcp-server", "--context", "claude-code", "--project-from-cwd"],
"_docs": "Serena wraps language servers (clangd for C/C++) and exposes semantic analysis as MCP tools. It auto-discovers compile_commands.json from the project root. See skills/zeroize-audit/references/mcp-analysis.md.",
"env": {}
}
}
}
+19
View File
@@ -0,0 +1,19 @@
{
"mcpServers": {
"serena": {
"type": "stdio",
"command": "uvx",
"args": [
"--from",
"git+https://github.com/oraios/serena",
"serena",
"start-mcp-server",
"--context",
"claude-code",
"--project-from-cwd"
],
"_docs": "Serena wraps language servers (clangd for C/C++) and exposes semantic analysis as MCP tools. It auto-discovers compile_commands.json from the project root. See skills/zeroize-audit/references/mcp-analysis.md.",
"env": {}
}
}
}