mirror of
https://github.com/trailofbits/skills
synced 2026-06-21 14:12:00 +00:00
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:
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
@@ -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 +0,0 @@
|
||||
../../plugins/testing-handbook-skills/skills/address-sanitizer
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/testing-handbook-skills/skills/aflpp
|
||||
@@ -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 +0,0 @@
|
||||
../../plugins/testing-handbook-skills/skills/atheris
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/trailmark/skills/audit-augmentation
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/audit-context-building/skills/audit-context-building
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/building-secure-contracts/skills/audit-prep-assistant
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/burpsuite-project-parser/skills/burpsuite-project-parser
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/c-review/skills/c-review
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/building-secure-contracts/skills/cairo-vulnerability-scanner
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/testing-handbook-skills/skills/cargo-fuzz
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/claude-in-chrome-troubleshooting/skills/claude-in-chrome-troubleshooting
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/building-secure-contracts/skills/code-maturity-assessor
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/static-analysis/skills/codeql
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/constant-time-analysis/skills/constant-time-analysis
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/testing-handbook-skills/skills/constant-time-testing
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/building-secure-contracts/skills/cosmos-vulnerability-scanner
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/testing-handbook-skills/skills/coverage-analysis
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/trailmark/skills/crypto-protocol-diagram
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/debug-buttercup/skills/debug-buttercup
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/workflow-skill-design/skills/designing-workflow-skills
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/devcontainer-setup/skills/devcontainer-setup
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/trailmark/skills/diagramming-code
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/differential-review/skills/differential-review
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/dimensional-analysis/skills/dimensional-analysis
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/dwarf-expert/skills/dwarf-expert
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/entry-point-analyzer/skills/entry-point-analyzer
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/firebase-apk-scanner/skills/firebase-apk-scanner
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/fp-check/skills/fp-check
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/testing-handbook-skills/skills/fuzzing-dictionary
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/testing-handbook-skills/skills/fuzzing-obstacles
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/trailmark/skills/genotoxic
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/git-cleanup/skills/git-cleanup
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/trailmark/skills/graph-evolution
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/building-secure-contracts/skills/guidelines-advisor
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/testing-handbook-skills/skills/harness-writing
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/insecure-defaults/skills/insecure-defaults
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/culture-index/skills/interpreting-culture-index
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/let-fate-decide/skills/let-fate-decide
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/testing-handbook-skills/skills/libafl
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/testing-handbook-skills/skills/libfuzzer
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/trailmark/skills/mermaid-to-proverif
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/modern-python/skills/modern-python
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/mutation-testing/skills/mutation-testing
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/testing-handbook-skills/skills/ossfuzz
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/property-based-testing/skills/property-based-testing
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/testing-handbook-skills/skills/ruzzy
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/static-analysis/skills/sarif-parsing
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/seatbelt-sandboxer/skills/seatbelt-sandboxer
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/second-opinion/skills/second-opinion
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/building-secure-contracts/skills/secure-workflow-guide
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/static-analysis/skills/semgrep
|
||||
@@ -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 +0,0 @@
|
||||
../../plugins/sharp-edges/skills/sharp-edges
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/skill-improver/skills/skill-improver
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/building-secure-contracts/skills/solana-vulnerability-scanner
|
||||
@@ -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 +0,0 @@
|
||||
../../plugins/supply-chain-risk-auditor/skills/supply-chain-risk-auditor
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/testing-handbook-skills/skills/testing-handbook-generator
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/building-secure-contracts/skills/token-integration-analyzer
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/building-secure-contracts/skills/ton-vulnerability-scanner
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/trailmark/skills/trailmark
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/trailmark/skills/trailmark-structural
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/trailmark/skills/trailmark-summary
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/variant-analysis/skills/variant-analysis
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/trailmark/skills/vector-forge
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/testing-handbook-skills/skills/wycheproof
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/yara-authoring/skills/yara-rule-authoring
|
||||
@@ -1 +0,0 @@
|
||||
../../plugins/zeroize-audit/skills/zeroize-audit
|
||||
Executable
+199
@@ -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())
|
||||
Executable
+284
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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
|
||||
|
||||
@@ -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 +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
|
||||
|
||||
@@ -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
-1
@@ -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,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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user