Generated banner for Wp Project Triage
Generated logo for Wp Project Triage

Wp Project Triage

WordPressOfficialPublished by WordPress

  • Skill
  • Review and audit
  • Software development
  • GPL-2.0-or-later
  • Low risk
  • Has scripts

From the author

Use when you need a deterministic inspection of a WordPress repository (plugin/theme/block theme/WP core/Gutenberg/full site) including tooling/tests/version hints, and a structured JSON report to guide workflows and guardrails.
— the description by WordPress

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

WP Project Triage provides a systematic way to analyze WordPress repositories, including plugins, themes, block themes, and core files. By executing a detection script, the skill outputs a structured JSON report that identifies the project type, available tooling, testing configurations, and versioning hints. This data serves as a foundation for determining appropriate development workflows and establishing project guardrails.

What it does

  • Inspects WordPress repository structures to identify project types.
  • Detects installed PHP and Node.js tooling.
  • Identifies existing test suites and configurations.
  • Extracts versioning information and sources.
  • Generates a structured JSON report based on a defined schema.
  • Provides signals to guide subsequent development workflows.

When to use it

  • When you need to understand the structure of a WordPress repository before making changes.
  • When you need to identify the correct commands and conventions for a specific project.
  • When you need to verify the presence of specific tooling or test configurations.
  • When you need to establish guardrails for a WordPress development workflow.

Advantages

  • Provides deterministic insights into project composition.
  • Standardizes project analysis through a structured JSON output.
  • Helps prevent errors by clarifying project-specific requirements.
  • Facilitates informed decision-making for development workflows.

Usage examples

“Analyze this WordPress repository to determine its structure and tooling.”
The agent runs the detection script and returns a JSON report detailing the project kind, detected tools, and versioning information.
“What are the project guardrails for this theme repository?”
The agent inspects the repository and provides a report identifying the theme type and relevant build configurations to follow.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/wordpress/wp-project-triage/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip wp-project-triage.zip -d .agents/skills

Documentation by WordPress

WP Project Triage

When to use

Use this skill to quickly understand what kind of WordPress repo you’re in and what commands/conventions to follow before making changes.

Inputs required

  • Repo root (current working directory).

Procedure

  1. Run the detector (prints JSON to stdout):

    • node skills/wp-project-triage/scripts/detect_wp_project.mjs

  2. If you need the exact output contract, read:

    • skills/wp-project-triage/references/triage.schema.json

  3. Use the report to select workflow guardrails:

    • project kind(s)

    • PHP/Node tooling present

    • tests present

    • version hints and sources

  4. If the report is missing signals you need, update the detector rather than guessing.

Verification

  • The JSON should parse and include: project.kind, signals, and tooling.

  • Re-run after changes that affect structure/tooling (adding theme.json, block.json, build config).

Failure modes / debugging

  • If it reports unknown, check whether the repo root is correct.

  • If scanning is slow, add/extend ignore directories in the script.

Files

scripts/detect_wp_project.mjs

import fs from "node:fs";
import path from "node:path";
import process from "node:process";

const TOOL_VERSION = "0.1.0";

const DEFAULT_IGNORES = new Set([
  ".git",
  "node_modules",
  "vendor",
  "dist",
  "build",
  "coverage",
  ".next",
  ".turbo",
]);

function statSafe(p) {
  try {
    return fs.statSync(p);
  } catch {
    return null;
  }
}

function readFileSafe(p, maxBytes = 256 * 1024) {
  try {
    const buf = fs.readFileSync(p);
    if (buf.byteLength > maxBytes) return buf.subarray(0, maxBytes).toString("utf8");
    return buf.toString("utf8");
  } catch {
    return null;
  }
}

function scanForTokens(repoRoot, { tokens, exts, maxFiles = 2500, maxDepth = 8 }) {
  const loweredTokens = tokens.map((t) => t.toLowerCase());
  const matches = new Map();

  const { results: files, truncated } = findFilesRecursive(
    repoRoot,
    (p) => {
      const ext = path.extname(p).toLowerCase();
      return exts.includes(ext);
    },
    { maxFiles, maxDepth }
  );

  for (const filePath of files) {
    const contents = readFileSafe(filePath, 128 * 1024);
    if (!contents) continue;
    const haystack = contents.toLowerCase();

    for (let i = 0; i < loweredTokens.length; i += 1) {
      const token = loweredTokens[i];
      if (matches.has(token)) continue;
      if (haystack.includes(token)) matches.set(token, path.relative(repoRoot, filePath));
    }
    if (matches.size === loweredTokens.length) break;
  }

  return {
    truncated,
    matches: Object.fromEntries([...matches.entries()]),
  };
}

function existsFile(p) {
  const st = statSafe(p);
  return Boolean(st && st.isFile());
}

function existsDir(p) {
  const st = statSafe(p);
  return Boolean(st && st.isDirectory());
}

function detectPackageManager(repoRoot) {
  const hasPnpm = existsFile(path.join(repoRoot, "pnpm-lock.yaml"));
  const hasYarn = existsFile(path.join(repoRoot, "yarn.lock"));
  const hasNpm = existsFile(path.join(repoRoot, "package-lock.json"));
  const hasBun = existsFile(path.join(repoRoot, "bun.lockb")) || existsFile(path.join(repoRoot, "bun.lock"));
  if (hasPnpm) return "pnpm";
  if (hasYarn) return "yarn";
  if (hasBun) return "bun";
  if (hasNpm) return "npm";
  return null;
}

function findFilesRecursive(repoRoot, predicate, { maxFiles = 6000, maxDepth = 8 } = {}) {
  const results = [];
  const queue = [{ dir: repoRoot, depth: 0 }];
  let visited = 0;

  while (queue.length > 0) {
    const { dir, depth } = queue.shift();
    if (depth > maxDepth) continue;

    let entries;
    try {
      entries = fs.readdirSync(dir, { withFileTypes: true });
    } catch {
      continue;
    }

    for (const ent of entries) {
      const fullPath = path.join(dir, ent.name);
      if (ent.isDirectory()) {
        if (DEFAULT_IGNORES.has(ent.name)) continue;
        queue.push({ dir: fullPath, depth: depth + 1 });
        continue;
      }
      if (!ent.isFile()) continue;

      visited += 1;
      if (visited > maxFiles) return { results, truncated: true };
      if (predicate(fullPath)) results.push(fullPath);
    }
  }

  return { results, truncated: false };
}

function detectPluginHeaderFromPhpFile(filePath) {
  const contents = readFileSafe(filePath, 128 * 1024);
  if (!contents) return null;
  const headerMatch = contents.match(/^\s*\*?\s*Plugin Name:\s*(.+)\s*$/im);
  if (!headerMatch) return null;
  return headerMatch[1].trim();
}

function detectThemeHeaderFromStyleCss(filePath) {
  const contents = readFileSafe(filePath, 128 * 1024);
  if (!contents) return null;
  const headerMatch = contents.match(/^\s*\*?\s*Theme Name:\s*(.+)\s*$/im);
  if (!headerMatch) return null;
  return headerMatch[1].trim();
}

function guessWpCoreVersionFromCheckout(repoRoot) {
  const versionPhp = path.join(repoRoot, "wp-includes", "version.php");
  if (!existsFile(versionPhp)) return { value: null, source: null };
  const contents = readFileSafe(versionPhp, 64 * 1024);
  if (!contents) return { value: null, source: null };
  const match = contents.match(/\$wp_version\s*=\s*'([^']+)'/);
  if (!match) return { value: null, source: "wp-includes/version.php" };
  return { value: match[1], source: "wp-includes/version.php" };
}

function guessGutenbergVersion(repoRoot) {
  const gutenbergPackageJson = path.join(repoRoot, "packages", "plugins", "package.json");
  const rootPackageJson = path.join(repoRoot, "package.json");

  for (const candidate of [gutenbergPackageJson, rootPackageJson]) {
    if (!existsFile(candidate)) continue;
    const txt = readFileSafe(candidate);
    if (!txt) continue;
    try {
      const pkg = JSON.parse(txt);
      if (pkg?.name === "@wordpress/plugins" && typeof pkg?.version === "string") {
        return { value: pkg.version, source: path.relative(repoRoot, candidate) };
      }
      if (pkg?.name === "gutenberg" && typeof pkg?.version === "string") {
        return { value: pkg.version, source: path.relative(repoRoot, candidate) };
      }
    } catch {
      // ignore
    }
  }
  return { value: null, source: null };
}

function parsePackageJson(repoRoot) {
  const p = path.join(repoRoot, "package.json");
  if (!existsFile(p)) return null;
  const txt = readFileSafe(p);
  if (!txt) return null;
  try {
    return JSON.parse(txt);
  } catch {
    return null;
  }
}

function parseComposerJson(repoRoot) {
  const p = path.join(repoRoot, "composer.json");
  if (!existsFile(p)) return null;
  const txt = readFileSafe(p);
  if (!txt) return null;
  try {
    return JSON.parse(txt);
  } catch {
    return null;
  }
}

function detectConfigConstants(repoRoot) {
  const { results: configFiles } = findFilesRecursive(repoRoot, (p) => path.basename(p) === "wp-config.php", {
    maxFiles: 4000,
    maxDepth: 4,
  });
  const configPath = configFiles[0] ?? null;
  if (!configPath) {
    return { source: null, constants: {} };
  }

  const contents = readFileSafe(configPath, 256 * 1024);
  if (!contents) return { source: path.relative(repoRoot, configPath), constants: {} };

  const c = contents;
  const enabled = (name) =>
    new RegExp(`define\\(\\s*['"]${name}['"]\\s*,\\s*(true|1)\\s*\\)`, "i").test(c) ||
    new RegExp(`\\b${name}\\b\\s*=\\s*(true|1)`, "i").test(c);

  const mentioned = (name) => new RegExp(`\\b${name}\\b`, "i").test(c);

  return {
    source: path.relative(repoRoot, configPath),
    constants: {
      savequeriesMentioned: mentioned("SAVEQUERIES"),
      savequeriesEnabled: enabled("SAVEQUERIES"),
      wpDebugMentioned: mentioned("WP_DEBUG"),
      wpDebugEnabled: enabled("WP_DEBUG"),
      disableWpCronMentioned: mentioned("DISABLE_WP_CRON"),
      disableWpCronEnabled: enabled("DISABLE_WP_CRON"),
    },
  };
}

function detectKinds(repoRoot, signals) {
  const kinds = new Set();

  if (signals.isGutenbergRepo) kinds.add("gutenberg");
  if (signals.isWpCoreCheckout) kinds.add("wp-core");
  if (signals.hasWpContentDir) kinds.add("wp-site");
  if (signals.detectedThemeName) kinds.add(signals.isBlockTheme ? "wp-block-theme" : "wp-theme");
  if (signals.detectedPluginName) kinds.add(signals.isBlockPlugin ? "wp-block-plugin" : "wp-plugin");
  if (signals.hasMuPluginsDir) kinds.add("wp-mu-plugin");

  if (kinds.size === 0) kinds.add("unknown");

  const priority = [
    "gutenberg",
    "wp-core",
    "wp-site",
    "wp-block-theme",
    "wp-block-plugin",
    "wp-theme",
    "wp-mu-plugin",
    "wp-plugin",
    "unknown",
  ];
  let primary = "unknown";
  for (const k of priority) {
    if (kinds.has(k)) {
      primary = k;
      break;
    }
  }

  return { kind: [...kinds], primary };
}

function buildRecommendations({ repoRoot, primaryKind, packageManager, packageJson, composerJson, tooling, signals }) {
  const commands = [];
  const notes = [];

  if (tooling.node.hasPackageJson) {
    const pm = packageManager ?? "npm";
    const run = pm === "yarn" ? "yarn" : `${pm} run`;
    const hasScript = (name) => Boolean(packageJson?.scripts && Object.prototype.hasOwnProperty.call(packageJson.scripts, name));
    if (hasScript("lint")) commands.push(`${run} lint`);
    if (hasScript("test")) commands.push(`${run} test`);
    if (hasScript("build")) commands.push(`${run} build`);
    if (hasScript("start")) commands.push(`${run} start`);
    if (tooling.node.usesWordpressScripts) notes.push("Detected @wordpress/scripts usage; prefer its standard lint/build/test scripts.");
  }

  if (tooling.php.hasComposerJson) {
    commands.push("composer install");
    if (tooling.php.phpunitXml.length > 0) commands.push("vendor/bin/phpunit");
  }

  if (tooling.tests.hasWpEnv) notes.push("Detected wp-env; E2E workflows may rely on Docker.");
  if (signals.scanTruncated) notes.push("Scan truncated due to file limit; some signals may be missing.");
  if (primaryKind === "unknown") notes.push("Could not confidently classify repo; inspect root for plugin/theme headers or wp-content structure.");

  return { commands, notes };
}

function main() {
  const repoRoot = process.cwd();

  const wpContent = path.join(repoRoot, "wp-content");
  const pluginsDir = path.join(wpContent, "plugins");
  const muPluginsDir = path.join(wpContent, "mu-plugins");
  const themesDir = path.join(wpContent, "themes");

  const isWpCoreCheckout = existsFile(path.join(repoRoot, "wp-includes", "version.php"));
  const isGutenbergRepo =
    existsDir(path.join(repoRoot, "packages")) &&
    (existsDir(path.join(repoRoot, "packages", "block-editor")) || existsDir(path.join(repoRoot, "packages", "components")));

  const packageJson = parsePackageJson(repoRoot);
  const composerJson = parseComposerJson(repoRoot);
  const packageManager = detectPackageManager(repoRoot);

  const usesWordpressScripts = Boolean(
    packageJson?.devDependencies?.["@wordpress/scripts"] ||
      packageJson?.dependencies?.["@wordpress/scripts"] ||
      packageJson?.scripts?.build?.includes("wp-scripts") ||
      packageJson?.scripts?.start?.includes("wp-scripts") ||
      packageJson?.scripts?.test?.includes("wp-scripts") ||
      packageJson?.scripts?.lint?.includes("wp-scripts")
  );

  const pkgHasInteractivity = Boolean(
    packageJson?.devDependencies?.["@wordpress/interactivity"] || packageJson?.dependencies?.["@wordpress/interactivity"]
  );
  const pkgHasAbilities = Boolean(
    packageJson?.devDependencies?.["@wordpress/abilities"] || packageJson?.dependencies?.["@wordpress/abilities"]
  );

  const hasWpContentDir = existsDir(wpContent);
  const hasPluginsDir = existsDir(pluginsDir);
  const hasThemesDir = existsDir(themesDir);
  const hasMuPluginsDir = existsDir(muPluginsDir);

  const config = detectConfigConstants(repoRoot);

  const pluginCandidates = [];
  const themeCandidates = [];

  // Root-level plugin/theme detection (common when repo root is the plugin/theme).
  for (const entry of fs.readdirSync(repoRoot, { withFileTypes: true })) {
    if (!entry.isFile()) continue;
    if (entry.name.toLowerCase().endsWith(".php")) pluginCandidates.push(path.join(repoRoot, entry.name));
    if (entry.name === "style.css") themeCandidates.push(path.join(repoRoot, entry.name));
  }

  let detectedPluginName = null;
  for (const phpFile of pluginCandidates) {
    detectedPluginName = detectPluginHeaderFromPhpFile(phpFile);
    if (detectedPluginName) break;
  }

  let detectedThemeName = null;
  for (const styleCss of themeCandidates) {
    detectedThemeName = detectThemeHeaderFromStyleCss(styleCss);
    if (detectedThemeName) break;
  }

  const { results: blockJsonFiles, truncated: scanTruncated } = findFilesRecursive(
    repoRoot,
    (p) => path.basename(p) === "block.json",
    { maxFiles: 6000, maxDepth: 8 }
  );
  const { results: themeJsonFiles } = findFilesRecursive(repoRoot, (p) => path.basename(p) === "theme.json", {
    maxFiles: 6000,
    maxDepth: 8,
  });

  const templatesDirCandidates = [
    path.join(repoRoot, "templates"),
    path.join(repoRoot, "parts"),
    path.join(repoRoot, "patterns"),
  ];

  const isBlockTheme = themeJsonFiles.length > 0 && templatesDirCandidates.some((p) => existsDir(p));
  const isBlockPlugin = blockJsonFiles.length > 0;

  const interactivityScan = scanForTokens(repoRoot, {
    tokens: ["data-wp-interactive", "@wordpress/interactivity", "viewScriptModule"],
    exts: [".php", ".js", ".ts", ".tsx", ".json", ".html"],
    maxFiles: 2500,
    maxDepth: 8,
  });

  const abilitiesScan = scanForTokens(repoRoot, {
    tokens: [
      "wp_register_ability(",
      "wp_register_ability_category(",
      "wp_abilities_api_init",
      "wp_abilities_api_categories_init",
      "wp-abilities/v1",
      "@wordpress/abilities",
    ],
    exts: [".php", ".js", ".ts", ".tsx"],
    maxFiles: 2500,
    maxDepth: 8,
  });

  const innerBlocksScan = scanForTokens(repoRoot, {
    tokens: ["InnerBlocks", "useInnerBlocksProps", "InnerBlocks.Content"],
    exts: [".js", ".ts", ".tsx"],
    maxFiles: 2500,
    maxDepth: 8,
  });

  const wpCliConfigBasenames = new Set([
    "wp-cli.yml",
    "wp-cli.yaml",
    "wp-cli.local.yml",
    "wp-cli.local.yaml",
    ".wp-cli.yml",
    ".wp-cli.yaml",
  ]);
  const { results: wpCliConfigFiles, truncated: wpCliConfigTruncated } = findFilesRecursive(
    repoRoot,
    (p) => wpCliConfigBasenames.has(path.basename(p)),
    { maxFiles: 6000, maxDepth: 6 }
  );

  const composerRequire = composerJson?.require && typeof composerJson.require === "object" ? composerJson.require : {};
  const composerRequireDev =
    composerJson?.["require-dev"] && typeof composerJson["require-dev"] === "object" ? composerJson["require-dev"] : {};
  const composerHasWpCli = Boolean(
    composerRequire["wp-cli/wp-cli"] ||
      composerRequireDev["wp-cli/wp-cli"] ||
      composerRequire["wp-cli/wp-cli-bundle"] ||
      composerRequireDev["wp-cli/wp-cli-bundle"]
  );

  const wpCliTokenScan = scanForTokens(repoRoot, {
    tokens: [
      "wp search-replace",
      "wp db export",
      "wp db import",
      "wp cron event",
      "wp cache flush",
      "wp rewrite flush",
      "wp plugin update",
      "wp theme update",
    ],
    exts: [".sh", ".yml", ".yaml", ".js", ".ts", ".php", ".json"],
    maxFiles: 2500,
    maxDepth: 8,
  });

  const usesInteractivityApi = pkgHasInteractivity || Object.keys(interactivityScan.matches).length > 0;
  const usesAbilitiesApi = pkgHasAbilities || Object.keys(abilitiesScan.matches).length > 0;
  const usesInnerBlocks = Object.keys(innerBlocksScan.matches).length > 0;
  const usesWpCli = composerHasWpCli || wpCliConfigFiles.length > 0 || Object.keys(wpCliTokenScan.matches).length > 0;

  const wpContentRoot = path.join(repoRoot, "wp-content");
  const hasObjectCacheDropin = existsFile(path.join(wpContentRoot, "object-cache.php"));
  const hasAdvancedCacheDropin = existsFile(path.join(wpContentRoot, "advanced-cache.php"));
  const hasDbDropin = existsFile(path.join(wpContentRoot, "db.php"));
  const hasSunriseDropin = existsFile(path.join(wpContentRoot, "sunrise.php"));
  const hasQueryMonitorPlugin = existsDir(path.join(wpContentRoot, "plugins", "query-monitor"));
  const hasPerformanceLabPlugin = existsDir(path.join(wpContentRoot, "plugins", "performance-lab"));

  const phpunitXml = [];
  for (const candidate of ["phpunit.xml", "phpunit.xml.dist"]) {
    const full = path.join(repoRoot, candidate);
    if (existsFile(full)) phpunitXml.push(candidate);
  }

  const hasWpEnv =
    existsFile(path.join(repoRoot, ".wp-env.json")) ||
    existsFile(path.join(repoRoot, ".wp-env.override.json")) ||
    Boolean(packageJson?.devDependencies?.["@wordpress/env"] || packageJson?.dependencies?.["@wordpress/env"]);

  const hasPlaywright = Boolean(
    packageJson?.devDependencies?.["@playwright/test"] ||
      packageJson?.dependencies?.["@playwright/test"] ||
      packageJson?.devDependencies?.["@wordpress/e2e-test-utils-playwright"] ||
      packageJson?.dependencies?.["@wordpress/e2e-test-utils-playwright"]
  );

  const hasJest = Boolean(
    packageJson?.devDependencies?.jest ||
      packageJson?.dependencies?.jest ||
      packageJson?.devDependencies?.["@wordpress/jest-preset-default"] ||
      packageJson?.dependencies?.["@wordpress/jest-preset-default"]
  );

  const hasPhpUnit = phpunitXml.length > 0 || Boolean(composerJson?.requireDev?.phpunit || composerJson?.["require-dev"]?.phpunit);

  const signals = {
    paths: {
      repoRoot,
      wpContent: hasWpContentDir ? wpContent : null,
      pluginsDir: hasPluginsDir ? pluginsDir : null,
      themesDir: hasThemesDir ? themesDir : null,
      muPluginsDir: hasMuPluginsDir ? muPluginsDir : null,
    },
    isWpCoreCheckout,
    isGutenbergRepo,
    hasWpContentDir,
    hasPluginsDir,
    hasThemesDir,
    hasMuPluginsDir,
    detectedPluginName,
    detectedThemeName,
    isBlockPlugin,
    isBlockTheme,
    usesInteractivityApi,
    usesAbilitiesApi,
    usesInnerBlocks,
    usesWpCli,
    performanceHints: {
      wpConfig: config.source,
      constants: config.constants,
      dropins: {
        objectCache: hasObjectCacheDropin,
        advancedCache: hasAdvancedCacheDropin,
        db: hasDbDropin,
        sunrise: hasSunriseDropin,
      },
      plugins: {
        queryMonitor: hasQueryMonitorPlugin,
        performanceLab: hasPerformanceLabPlugin,
      },
    },
    interactivityHints: {
      packageJson: pkgHasInteractivity,
      matches: interactivityScan.matches,
      scanTruncated: interactivityScan.truncated,
    },
    abilitiesHints: {
      packageJson: pkgHasAbilities,
      matches: abilitiesScan.matches,
      scanTruncated: abilitiesScan.truncated,
    },
    innerBlocksHints: {
      matches: innerBlocksScan.matches,
      scanTruncated: innerBlocksScan.truncated,
    },
    wpCliHints: {
      configFiles: wpCliConfigFiles.map((p) => path.relative(repoRoot, p)).slice(0, 50),
      configScanTruncated: wpCliConfigTruncated,
      composerJson: composerHasWpCli,
      matches: wpCliTokenScan.matches,
      scanTruncated: wpCliTokenScan.truncated,
    },
    blockJsonFiles: blockJsonFiles.map((p) => path.relative(repoRoot, p)).slice(0, 50),
    themeJsonFiles: themeJsonFiles.map((p) => path.relative(repoRoot, p)).slice(0, 50),
    scanTruncated,
  };

  const { kind, primary } = detectKinds(repoRoot, signals);

  const versions = {
    wordpress: {
      core: guessWpCoreVersionFromCheckout(repoRoot),
    },
    gutenberg: guessGutenbergVersion(repoRoot),
  };

  const tooling = {
    php: {
      hasComposerJson: existsFile(path.join(repoRoot, "composer.json")),
      hasVendorDir: existsDir(path.join(repoRoot, "vendor")),
      phpunitXml,
    },
    node: {
      hasPackageJson: existsFile(path.join(repoRoot, "package.json")),
      packageManager,
      usesWordpressScripts,
    },
    tests: {
      hasPhpUnit,
      hasWpEnv,
      hasPlaywright,
      hasJest,
    },
  };

  const recommendations = buildRecommendations({
    repoRoot,
    primaryKind: primary,
    packageManager,
    packageJson,
    composerJson,
    tooling,
    signals,
  });

  const report = {
    tool: { name: "detect_wp_project", version: TOOL_VERSION },
    project: { kind, primary, notes: [] },
    signals,
    tooling,
    versions,
    recommendations,
  };

  process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
}

main();

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs Node.js

The author says: Targets WordPress 7.0+ (PHP 7.4.0+). Filesystem-based agent with bash + node. Some workflows require WP-CLI.

Security

What our scanner noticed· Low risk

  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
WordPress
Publisher
WordPress (Official)
Source
WordPress/agent-skills · skills/wp-project-triage · commit 7374e6f
Licence
GNU General Public License v2.0 or later (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by WordPress or WordPress.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download wp-project-triage.zip