Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@ jobs:
- name: Run local eval harness
run: node eval/harness/run.mjs

- name: Build skillpacks (Codex + VS Code)
run: node shared/scripts/skillpack-build.mjs --clean --out=dist --targets=codex,vscode
- name: Build skillpacks (Codex + VS Code + Warp)
run: node shared/scripts/skillpack-build.mjs --clean --out=dist --targets=codex,vscode,warp

- name: Install skillpacks (smoke)
run: |
rm -rf /tmp/skillpack-install-test
mkdir -p /tmp/skillpack-install-test
node shared/scripts/skillpack-install.mjs --from=dist --dest=/tmp/skillpack-install-test --targets=codex,vscode
node shared/scripts/skillpack-install.mjs --from=dist --dest=/tmp/skillpack-install-test --targets=codex,vscode,warp
test -f /tmp/skillpack-install-test/.codex/skills/wordpress-router/SKILL.md
test -f /tmp/skillpack-install-test/.github/skills/wordpress-router/SKILL.md
test -f /tmp/skillpack-install-test/.agents/skills/wordpress-router/SKILL.md

- uses: actions/setup-python@v5
with:
Expand Down
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**Teach AI coding assistants how to build WordPress the right way.**

Agent Skills are portable bundles of instructions, checklists, and scripts that help AI assistants (Claude, Copilot, Codex, Cursor, etc.) understand WordPress development patterns, avoid common mistakes, and follow best practices.
Agent Skills are portable bundles of instructions, checklists, and scripts that help AI assistants (Claude, Copilot, Codex, Cursor, Warp, etc.) understand WordPress development patterns, avoid common mistakes, and follow best practices.

> **AI Authorship Disclosure:** These skills were generated using GPT-5.2 Codex (High Reasoning) from official Gutenberg and WordPress documentation, then reviewed and edited by WordPress contributors. We tested skills with AI assistants and iterated based on results. This is v1, and skills will improve as the community uses them and contributes fixes. See [docs/ai-authorship.md](docs/ai-authorship.md) for details. ([WordPress AI Guidelines](https://make.wordpress.org/ai/handbook/ai-guidelines/))

Expand Down Expand Up @@ -66,14 +66,15 @@ cd agent-skills
node shared/scripts/skillpack-build.mjs --clean

# Install into your WordPress project
node shared/scripts/skillpack-install.mjs --dest=../your-wp-project --targets=codex,vscode,claude,cursor
node shared/scripts/skillpack-install.mjs --dest=../your-wp-project --targets=codex,vscode,claude,cursor,warp
```

This copies skills into:
- `.codex/skills/` for OpenAI Codex
- `.github/skills/` for VS Code / GitHub Copilot
- `.claude/skills/` for Claude Code (project-level)
- `.cursor/skills/` for Cursor (project-level)
- `.agents/skills/` for Warp (project-level, recommended)

### Install globally for Cursor

Expand All @@ -83,6 +84,14 @@ node shared/scripts/skillpack-install.mjs --targets=cursor-global

This installs skills to `~/.cursor/skills/` where Cursor will discover them.

### Install globally for Warp

```bash
node shared/scripts/skillpack-install.mjs --targets=warp-global
```

This installs skills to `~/.agents/skills/` where Warp will discover them.

### Available options

```bash
Expand All @@ -92,8 +101,8 @@ node shared/scripts/skillpack-install.mjs --list
# Dry run (preview without installing)
node shared/scripts/skillpack-install.mjs --global --dry-run

# Install specific skills to a project (e.g. Claude + Cursor)
node shared/scripts/skillpack-install.mjs --dest=../my-repo --targets=claude,cursor --skills=wp-wpcli-and-ops
# Install specific skills to a project (e.g. Claude + Cursor + Warp)
node shared/scripts/skillpack-install.mjs --dest=../my-repo --targets=claude,cursor,warp --skills=wp-wpcli-and-ops
```

### Manual installation
Expand Down
3 changes: 2 additions & 1 deletion docs/packaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ Outputs:
- `dist/vscode/.github/skills/*` (VS Code / Copilot repo layout)
- `dist/claude/.claude/skills/*` (Claude Code repo layout)
- `dist/cursor/.cursor/skills/*` (Cursor repo layout)
- `dist/warp/.agents/skills/*` (Warp repo layout)

## Install into another repo

1. Build dist (above).
2. Install into a destination repo:

- `node shared/scripts/skillpack-install.mjs --dest=../some-repo --targets=codex,vscode,claude,cursor`
- `node shared/scripts/skillpack-install.mjs --dest=../some-repo --targets=codex,vscode,claude,cursor,warp`

By default, install mode is `replace` (it replaces only the skill directories it installs).

8 changes: 4 additions & 4 deletions eval/scenarios/skillpack-build-and-install.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"name": "Build and install skillpacks",
"skills": [],
"query": "Package this repo's skills for Codex, VS Code, Claude, and Cursor and install them into another repository.",
"query": "Package this repo's skills for Codex, VS Code, Claude, Cursor, and Warp and install them into another repository.",
"expected_behavior": [
"Step 1: Run build command: node shared/scripts/skillpack-build.mjs --clean --targets=codex,vscode,claude,cursor",
"Step 1: Run build command: node shared/scripts/skillpack-build.mjs --clean --targets=codex,vscode,claude,cursor,warp",
"Step 2: Verify build output in dist/ directory",
"Step 3: Run install command: node shared/scripts/skillpack-install.mjs --from=dist --dest=<repo-root> --targets=codex,vscode,claude,cursor",
"Step 3: Run install command: node shared/scripts/skillpack-install.mjs --from=dist --dest=<repo-root> --targets=codex,vscode,claude,cursor,warp",
"Step 4: Verify skills installed in target repository",
"Step 5: Confirm no symlinks in installed skills (files are copied)"
],
Expand All @@ -14,6 +14,6 @@
"Output created in dist/ directory",
"Install command copies to target repository",
"No symlinks in installed skills",
"Codex, vscode, claude, and cursor targets supported"
"Codex, vscode, claude, cursor, and warp targets supported"
]
}
10 changes: 6 additions & 4 deletions shared/scripts/skillpack-build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ function usage() {
process.stderr.write(
[
"Usage:",
" node shared/scripts/skillpack-build.mjs [--out=dist] [--targets=codex,vscode,claude,cursor] [--skills=skill1,skill2] [--clean]",
" node shared/scripts/skillpack-build.mjs [--out=dist] [--targets=codex,vscode,claude,cursor,warp] [--skills=skill1,skill2] [--clean]",
"",
"Outputs:",
" - <out>/codex/.codex/skills/<skill>/SKILL.md",
" - <out>/vscode/.github/skills/<skill>/SKILL.md",
" - <out>/claude/.claude/skills/<skill>/SKILL.md",
" - <out>/cursor/.cursor/skills/<skill>/SKILL.md",
" - <out>/warp/.agents/skills/<skill>/SKILL.md",
"",
"Options:",
" --targets Comma-separated list of targets (codex, vscode, claude, cursor). Default: codex,vscode,claude,cursor",
" --targets Comma-separated list of targets (codex, vscode, claude, cursor, warp). Default: codex,vscode,claude,cursor,warp",
" --skills Comma-separated list of skill names to build. Default: all skills",
" --clean Remove target directories before building",
"",
Expand All @@ -26,7 +27,7 @@ function usage() {
}

function parseArgs(argv) {
const args = { out: "dist", targets: ["codex", "vscode", "claude", "cursor"], skills: [], clean: false };
const args = { out: "dist", targets: ["codex", "vscode", "claude", "cursor", "warp"], skills: [], clean: false };
for (const a of argv) {
if (a === "--help" || a === "-h") args.help = true;
else if (a === "--clean") args.clean = true;
Expand Down Expand Up @@ -101,6 +102,7 @@ function buildTarget({ repoRoot, outDir, target, skillDirs }) {
vscode: path.join(outDir, "vscode", ".github", "skills"),
claude: path.join(outDir, "claude", ".claude", "skills"),
cursor: path.join(outDir, "cursor", ".cursor", "skills"),
warp: path.join(outDir, "warp", ".agents", "skills"),
};
const destSkillsRoot = rootByTarget[target];
assert(destSkillsRoot, `Unknown target: ${target}`);
Expand All @@ -117,7 +119,7 @@ function buildTarget({ repoRoot, outDir, target, skillDirs }) {
process.stdout.write(`OK: built ${target} skillpack at ${rel}\n`);
}

const VALID_TARGETS = ["codex", "vscode", "claude", "cursor"];
const VALID_TARGETS = ["codex", "vscode", "claude", "cursor", "warp"];

function main() {
const args = parseArgs(process.argv.slice(2));
Expand Down
32 changes: 21 additions & 11 deletions shared/scripts/skillpack-install.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function usage() {
"Options:",
" --dest=<path> Destination repo root (required, unless using --global)",
" --from=<path> Source directory (default: dist)",
" --targets=<list> Comma-separated targets: codex, vscode, claude, claude-global, cursor, cursor-global (default: codex,vscode)",
" --targets=<list> Comma-separated targets: codex, vscode, claude, claude-global, cursor, cursor-global, warp, warp-global (default: codex,vscode)",
" --skills=<list> Comma-separated skill names to install (default: all)",
" --mode=<mode> 'replace' (default) or 'merge'",
" --global Shorthand for --targets=claude-global (installs to ~/.claude/skills)",
Expand All @@ -25,23 +25,28 @@ function usage() {
" claude-global Install to ~/.claude/skills/ (user-level, ignores --dest)",
" cursor Install to <dest>/.cursor/skills/",
" cursor-global Install to ~/.cursor/skills/ (user-level, ignores --dest)",
" warp Install to <dest>/.agents/skills/ (project-level, recommended for Warp)",
" warp-global Install to ~/.agents/skills/ (user-level, ignores --dest)",
"",
"Examples:",
" # Build and install to a WordPress project",
" node shared/scripts/skillpack-build.mjs --clean",
" node shared/scripts/skillpack-install.mjs --dest=../my-wp-repo --targets=codex,vscode,claude,cursor",
" node shared/scripts/skillpack-install.mjs --dest=../my-wp-repo --targets=codex,vscode,claude,cursor,warp",
"",
" # Install globally for Claude Code (all skills)",
" node shared/scripts/skillpack-install.mjs --global",
"",
" # Install globally for Cursor (all skills)",
" node shared/scripts/skillpack-install.mjs --targets=cursor-global",
"",
" # Install globally for Warp (all skills)",
" node shared/scripts/skillpack-install.mjs --targets=warp-global",
"",
" # Install specific skills globally",
" node shared/scripts/skillpack-install.mjs --global --skills=wp-playground,wp-block-development",
"",
" # Install to project with specific skills",
" node shared/scripts/skillpack-install.mjs --dest=../my-repo --targets=claude,cursor --skills=wp-wpcli-and-ops",
" node shared/scripts/skillpack-install.mjs --dest=../my-repo --targets=claude,cursor,warp --skills=wp-wpcli-and-ops",
"",
].join("\n")
);
Expand Down Expand Up @@ -133,38 +138,43 @@ function listSkillDirs(skillsRoot) {
.filter((d) => fs.existsSync(path.join(d, "SKILL.md")));
}

const VALID_TARGETS = ["codex", "vscode", "claude", "claude-global", "cursor", "cursor-global"];
const VALID_TARGETS = ["codex", "vscode", "claude", "claude-global", "cursor", "cursor-global", "warp", "warp-global"];

// Map target to source subdirectory in dist
function getSourceDir(fromDir, target) {
// claude-global uses the same source as claude; cursor-global uses the same as cursor
// claude-global uses the same source as claude; cursor-global uses the same as cursor; warp-global uses the same as warp
const sourceTarget =
target === "claude-global" ? "claude" : target === "cursor-global" ? "cursor" : target;
target === "claude-global" ? "claude" : target === "cursor-global" ? "cursor" : target === "warp-global" ? "warp" : target;
const targetDirMap = {
codex: path.join(fromDir, "codex", ".codex", "skills"),
vscode: path.join(fromDir, "vscode", ".github", "skills"),
claude: path.join(fromDir, "claude", ".claude", "skills"),
cursor: path.join(fromDir, "cursor", ".cursor", "skills"),
warp: path.join(fromDir, "warp", ".agents", "skills"),
};
return targetDirMap[sourceTarget];
}

// Map target to destination directory
function getDestDir(destRepoRoot, target) {
// claude-global and cursor-global don't need destRepoRoot
// claude-global, cursor-global, and warp-global don't need destRepoRoot
if (target === "claude-global") {
return path.join(os.homedir(), ".claude", "skills");
}
if (target === "cursor-global") {
return path.join(os.homedir(), ".cursor", "skills");
}
if (target === "warp-global") {
return path.join(os.homedir(), ".agents", "skills");
}

// Other targets require destRepoRoot
const destDirMap = {
codex: path.join(destRepoRoot, ".codex", "skills"),
vscode: path.join(destRepoRoot, ".github", "skills"),
claude: path.join(destRepoRoot, ".claude", "skills"),
cursor: path.join(destRepoRoot, ".cursor", "skills"),
warp: path.join(destRepoRoot, ".agents", "skills"),
};
return destDirMap[target];
}
Expand Down Expand Up @@ -213,14 +223,14 @@ function installTarget({ fromDir, destRepoRoot, target, skillsFilter, mode, dryR
copyDir({ srcDir: srcSkillDir, destDir: destSkillDir });
}

const isGlobal = target === "claude-global" || target === "cursor-global";
const isGlobal = target === "claude-global" || target === "cursor-global" || target === "warp-global";
const location = isGlobal ? destSkillsRoot : path.relative(destRepoRoot, destSkillsRoot) || ".";
process.stdout.write(`OK: installed ${skillDirs.length} skill(s) to ${location}\n`);
}

function listAvailableSkills(fromDir) {
// Check all possible target sources
const sources = ["codex", "vscode", "claude", "cursor"]
const sources = ["codex", "vscode", "claude", "cursor", "warp"]
.map((t) => getSourceDir(fromDir, t))
.filter((p) => fs.existsSync(p));

Expand Down Expand Up @@ -258,8 +268,8 @@ function main() {
assert(VALID_TARGETS.includes(t), `Invalid target: ${t}. Valid targets: ${VALID_TARGETS.join(", ")}`);
}

// --dest is required unless only using global targets (claude-global, cursor-global)
const needsDest = targets.some((t) => t !== "claude-global" && t !== "cursor-global");
// --dest is required unless only using global targets (claude-global, cursor-global, warp-global)
const needsDest = targets.some((t) => t !== "claude-global" && t !== "cursor-global" && t !== "warp-global");
if (needsDest && !args.dest) {
process.stderr.write("Error: --dest is required for non-global targets.\n\n");
usage();
Expand Down
Loading