* fix: stabilize validation and tests on Windows * test: add Windows smoke coverage for skill activation * refactor: make setup_web script CommonJS * fix: repair aegisops-ai frontmatter * docs: add when-to-use guidance to core skills * docs: add when-to-use guidance to Apify skills * docs: add when-to-use guidance to Google and Expo skills * docs: add when-to-use guidance to Makepad skills * docs: add when-to-use guidance to git workflow skills * docs: add when-to-use guidance to fp-ts skills * docs: add when-to-use guidance to Three.js skills * docs: add when-to-use guidance to n8n skills * docs: add when-to-use guidance to health analysis skills * docs: add when-to-use guidance to writing and review skills * meta: sync generated catalog metadata * docs: add when-to-use guidance to Robius skills * docs: add when-to-use guidance to review and workflow skills * docs: add when-to-use guidance to science and data skills * docs: add when-to-use guidance to tooling and automation skills * docs: add when-to-use guidance to remaining skills * fix: gate bundle helper execution in Windows activation * chore: drop generated artifacts from contributor PR * docs(maintenance): Record PR 457 sweep Document the open issue triage, PR supersedence decision, local verification, and source-only cleanup that prepared PR #457 for re-running CI. --------- Co-authored-by: sickn33 <sickn33@users.noreply.github.com>
86 lines
3.4 KiB
JavaScript
86 lines
3.4 KiB
JavaScript
const assert = require("assert");
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const { findProjectRoot } = require("../../lib/project-root");
|
|
|
|
function normalizeRelativePath(value) {
|
|
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
}
|
|
|
|
const projectRoot = findProjectRoot(__dirname);
|
|
const pluginsRoot = path.join(projectRoot, "plugins");
|
|
const claudeMarketplace = JSON.parse(
|
|
fs.readFileSync(path.join(projectRoot, ".claude-plugin", "marketplace.json"), "utf8"),
|
|
);
|
|
const codexMarketplace = JSON.parse(
|
|
fs.readFileSync(path.join(projectRoot, ".agents", "plugins", "marketplace.json"), "utf8"),
|
|
);
|
|
|
|
const claudePluginPaths = new Set(
|
|
claudeMarketplace.plugins.map((plugin) => normalizeRelativePath(plugin.source)),
|
|
);
|
|
const codexPluginPaths = new Set(
|
|
codexMarketplace.plugins.map((plugin) => normalizeRelativePath(plugin.source.path)),
|
|
);
|
|
const knownPluginPaths = new Set([...claudePluginPaths, ...codexPluginPaths]);
|
|
|
|
for (const relativePluginPath of knownPluginPaths) {
|
|
const pluginDir = path.join(projectRoot, relativePluginPath);
|
|
assert.ok(fs.existsSync(pluginDir), `plugin directory must exist: ${relativePluginPath}`);
|
|
assert.ok(fs.statSync(pluginDir).isDirectory(), `plugin path must be a directory: ${relativePluginPath}`);
|
|
|
|
const skillsDir = path.join(pluginDir, "skills");
|
|
assert.ok(fs.existsSync(skillsDir), `plugin skills dir must exist: ${relativePluginPath}`);
|
|
assert.ok(fs.statSync(skillsDir).isDirectory(), `plugin skills dir must be a directory: ${relativePluginPath}`);
|
|
|
|
const skillMarkdownFiles = [];
|
|
const stack = [skillsDir];
|
|
while (stack.length > 0) {
|
|
const currentDir = stack.pop();
|
|
for (const child of fs.readdirSync(currentDir, { withFileTypes: true })) {
|
|
const childPath = path.join(currentDir, child.name);
|
|
if (child.isDirectory()) {
|
|
stack.push(childPath);
|
|
} else if (child.isFile() && child.name === "SKILL.md") {
|
|
skillMarkdownFiles.push(childPath);
|
|
}
|
|
}
|
|
}
|
|
assert.ok(skillMarkdownFiles.length > 0, `plugin must contain at least one skill: ${relativePluginPath}`);
|
|
|
|
const codexManifestPath = path.join(pluginDir, ".codex-plugin", "plugin.json");
|
|
if (fs.existsSync(codexManifestPath)) {
|
|
const codexManifest = JSON.parse(fs.readFileSync(codexManifestPath, "utf8"));
|
|
assert.strictEqual(codexManifest.skills, "./skills/");
|
|
assert.ok(
|
|
codexMarketplace.plugins.some((plugin) => plugin.name === codexManifest.name),
|
|
`Codex marketplace should expose ${codexManifest.name}`,
|
|
);
|
|
}
|
|
|
|
const claudeManifestPath = path.join(pluginDir, ".claude-plugin", "plugin.json");
|
|
if (fs.existsSync(claudeManifestPath)) {
|
|
const claudeManifest = JSON.parse(fs.readFileSync(claudeManifestPath, "utf8"));
|
|
assert.ok(
|
|
claudeMarketplace.plugins.some((plugin) => plugin.name === claudeManifest.name),
|
|
`Claude marketplace should expose ${claudeManifest.name}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
for (const entry of fs.readdirSync(pluginsRoot, { withFileTypes: true })) {
|
|
if (!entry.isDirectory()) {
|
|
continue;
|
|
}
|
|
|
|
const relativePluginPath = normalizeRelativePath(path.join("plugins", entry.name));
|
|
if (entry.name.startsWith("antigravity-bundle-") || entry.name === "antigravity-awesome-skills" || entry.name === "antigravity-awesome-skills-claude") {
|
|
assert.ok(
|
|
knownPluginPaths.has(relativePluginPath),
|
|
`generated plugin directory should be represented in a marketplace: ${relativePluginPath}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
console.log("ok");
|