From c4011e497f07e2ca045f397de9423662bd178b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Willem=20Ross=C3=A9e?= Date: Fri, 20 Mar 2026 15:15:01 +0100 Subject: [PATCH] feat(skillpack): add Warp build/install support Add Warp skillpack build/install targets using .agents/skills (project) and ~/.agents/skills (global). Update README and packaging docs, CI smoke checks, and eval scenario coverage. Co-Authored-By: Oz --- .github/workflows/ci.yml | 7 ++-- README.md | 17 +++++++--- docs/packaging.md | 3 +- .../skillpack-build-and-install.json | 8 ++--- shared/scripts/skillpack-build.mjs | 10 +++--- shared/scripts/skillpack-install.mjs | 32 ++++++++++++------- 6 files changed, 50 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 568ecc0..7f8c1b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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: diff --git a/README.md b/README.md index 13273a7..a7d1268 100644 --- a/README.md +++ b/README.md @@ -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/)) @@ -66,7 +66,7 @@ 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: @@ -74,6 +74,7 @@ This copies skills into: - `.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 @@ -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 @@ -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 diff --git a/docs/packaging.md b/docs/packaging.md index 9a629af..53794fa 100644 --- a/docs/packaging.md +++ b/docs/packaging.md @@ -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). diff --git a/eval/scenarios/skillpack-build-and-install.json b/eval/scenarios/skillpack-build-and-install.json index 653e1b2..07c63e5 100644 --- a/eval/scenarios/skillpack-build-and-install.json +++ b/eval/scenarios/skillpack-build-and-install.json @@ -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= --targets=codex,vscode,claude,cursor", + "Step 3: Run install command: node shared/scripts/skillpack-install.mjs --from=dist --dest= --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)" ], @@ -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" ] } diff --git a/shared/scripts/skillpack-build.mjs b/shared/scripts/skillpack-build.mjs index a5841de..0f4e6c5 100644 --- a/shared/scripts/skillpack-build.mjs +++ b/shared/scripts/skillpack-build.mjs @@ -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:", " - /codex/.codex/skills//SKILL.md", " - /vscode/.github/skills//SKILL.md", " - /claude/.claude/skills//SKILL.md", " - /cursor/.cursor/skills//SKILL.md", + " - /warp/.agents/skills//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", "", @@ -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; @@ -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}`); @@ -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)); diff --git a/shared/scripts/skillpack-install.mjs b/shared/scripts/skillpack-install.mjs index e9411aa..e050c62 100644 --- a/shared/scripts/skillpack-install.mjs +++ b/shared/scripts/skillpack-install.mjs @@ -11,7 +11,7 @@ function usage() { "Options:", " --dest= Destination repo root (required, unless using --global)", " --from= Source directory (default: dist)", - " --targets= Comma-separated targets: codex, vscode, claude, claude-global, cursor, cursor-global (default: codex,vscode)", + " --targets= Comma-separated targets: codex, vscode, claude, claude-global, cursor, cursor-global, warp, warp-global (default: codex,vscode)", " --skills= Comma-separated skill names to install (default: all)", " --mode= 'replace' (default) or 'merge'", " --global Shorthand for --targets=claude-global (installs to ~/.claude/skills)", @@ -25,11 +25,13 @@ function usage() { " claude-global Install to ~/.claude/skills/ (user-level, ignores --dest)", " cursor Install to /.cursor/skills/", " cursor-global Install to ~/.cursor/skills/ (user-level, ignores --dest)", + " warp Install to /.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", @@ -37,11 +39,14 @@ function usage() { " # 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") ); @@ -133,31 +138,35 @@ 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 = { @@ -165,6 +174,7 @@ function getDestDir(destRepoRoot, target) { 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]; } @@ -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)); @@ -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();