fix: sync upstream main with Windows validation and skill guidance cleanup (#457)

* 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>
This commit is contained in:
Al-Garadi
2026-04-05 22:04:39 +03:00
committed by GitHub
parent f585040a39
commit ef285b5c97
152 changed files with 993 additions and 62 deletions

View File

@@ -1,11 +1,6 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { createRequire } from 'module';
const fs = require('fs');
const path = require('path');
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const require = createRequire(import.meta.url);
const { findProjectRoot } = require('../lib/project-root');
const { resolveSafeRealPath } = require('../lib/symlink-safety');
@@ -73,8 +68,8 @@ function main() {
console.log('✅ Web app assets setup complete!');
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
if (require.main === module) {
main();
}
export { copyFolderSync, copyIndexFiles, main };
module.exports = { copyFolderSync, copyIndexFiles, main };

View File

@@ -10,7 +10,7 @@ import shutil
import subprocess
import tempfile
import json
from pathlib import Path
from pathlib import Path, PurePosixPath
MS_REPO = "https://github.com/microsoft/skills.git"
REPO_ROOT = Path(__file__).parent.parent
@@ -182,7 +182,7 @@ def find_skills_in_directory(source_dir: Path):
continue
try:
relative_path = item.relative_to(skills_source)
relative_path = PurePosixPath(item.relative_to(skills_source).as_posix())
except ValueError:
continue
@@ -212,7 +212,7 @@ def find_plugin_skills(source_dir: Path, already_synced_names: set):
if skill_name not in already_synced_names:
results.append({
"relative_path": Path("plugins") / skill_name,
"relative_path": PurePosixPath("plugins") / skill_name,
"skill_md": skill_file,
"source_dir": skill_dir,
})
@@ -238,7 +238,7 @@ def find_github_skills(source_dir: Path, already_synced_names: set):
if skill_dir.name not in already_synced_names:
results.append({
"relative_path": Path(".github/skills") / skill_dir.name,
"relative_path": PurePosixPath(".github/skills") / skill_dir.name,
"skill_md": skill_md,
"source_dir": skill_dir,
})

View File

@@ -116,7 +116,7 @@ def sync_github_about(
runner(topic_command, dry_run=dry_run)
if not dry_run:
print(f" Synced GitHub About settings for {repo}")
print(f"[ok] Synced GitHub About settings for {repo}")
def replace_if_present(content: str, pattern: re.Pattern[str], replacement: str) -> tuple[str, bool]:
@@ -241,7 +241,7 @@ def update_text_file(path: Path, transform, metadata: dict, dry_run: bool) -> bo
return True
path.write_text(updated, encoding="utf-8", newline="\n")
print(f" Updated {path}")
print(f"[ok] Updated {path}")
return True
@@ -353,7 +353,7 @@ def update_package_description(base_dir: str, metadata: dict, dry_run: bool) ->
with open(package_path, "w", encoding="utf-8", newline="\n") as file:
file.write(updated_content)
print(f" Updated package description in {package_path}")
print(f"[ok] Updated package description in {package_path}")
return True

View File

@@ -0,0 +1,123 @@
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const { spawnSync } = require("child_process");
if (process.platform !== "win32") {
console.log("Skipping activate-skills.bat smoke test outside Windows.");
process.exit(0);
}
const repoRoot = path.resolve(__dirname, "../..", "..");
const scriptPath = path.join("scripts", "activate-skills.bat");
const root = fs.mkdtempSync(path.join(repoRoot, ".tmp-activate-skills-batch-"));
const baseDir = path.join(root, "antigravity");
const repoSkills = path.join(root, "repo-skills");
function makeSkill(skillId) {
const skillDir = path.join(repoSkills, skillId);
fs.mkdirSync(skillDir, { recursive: true });
fs.writeFileSync(
path.join(skillDir, "SKILL.md"),
`---\nname: ${skillId}\ndescription: test skill\ncategory: testing\nrisk: safe\nsource: community\ndate_added: "2026-04-05"\n---\n`,
"utf8",
);
}
try {
makeSkill("brainstorming");
makeSkill("custom-skill");
fs.mkdirSync(path.join(baseDir, "skills", "legacy-skill"), { recursive: true });
fs.writeFileSync(path.join(baseDir, "skills", "legacy-skill", "SKILL.md"), "legacy", "utf8");
const result = spawnSync(
"cmd.exe",
["/d", "/c", `${scriptPath} --clear brainstorming custom-skill`],
{
cwd: repoRoot,
env: {
...process.env,
AG_BASE_DIR: baseDir,
AG_REPO_SKILLS_DIR: repoSkills,
AG_PYTHON_BIN: "__missing_python__",
AG_NO_PAUSE: "1",
},
encoding: "utf8",
timeout: 20000,
maxBuffer: 1024 * 1024 * 20,
},
);
if (result.error) {
if (result.error.code === "EPERM" || result.error.code === "EACCES") {
console.log("Skipping activate-skills.bat smoke test; this sandbox blocks cmd.exe child processes.");
process.exit(0);
}
throw result.error;
}
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
assert.ok(
fs.existsSync(path.join(baseDir, "skills", "brainstorming", "SKILL.md")),
"brainstorming should be activated into the live skills directory",
);
assert.ok(
fs.existsSync(path.join(baseDir, "skills", "custom-skill", "SKILL.md")),
"custom-skill should be activated into the live skills directory",
);
assert.ok(
fs.existsSync(path.join(baseDir, "skills_library", "brainstorming", "SKILL.md")),
"repo skills should be synced into the backing library",
);
const archives = fs.readdirSync(baseDir).filter((entry) => entry.startsWith("skills_archive_"));
assert.ok(archives.length > 0, "--clear should archive the previously active skills directory");
assert.ok(
fs.existsSync(path.join(baseDir, archives[0], "legacy-skill", "SKILL.md")),
"legacy active skills should move into a timestamped archive",
);
assert.match(
result.stdout,
/Done! Antigravity skills are now activated\./,
"script should report successful activation",
);
const missingHelperBaseDir = path.join(root, "antigravity-missing-helper");
const missingHelperResult = spawnSync(
"cmd.exe",
["/d", "/c", `${scriptPath} --clear custom-skill`],
{
cwd: repoRoot,
env: {
...process.env,
AG_BASE_DIR: missingHelperBaseDir,
AG_REPO_SKILLS_DIR: repoSkills,
AG_BUNDLE_HELPER: path.join(root, "missing-bundle-helper.py"),
AG_PYTHON_BIN: "__missing_python__",
AG_NO_PAUSE: "1",
},
encoding: "utf8",
timeout: 20000,
maxBuffer: 1024 * 1024 * 20,
},
);
if (missingHelperResult.error) {
if (missingHelperResult.error.code === "EPERM" || missingHelperResult.error.code === "EACCES") {
console.log("Skipping activate-skills.bat smoke test; this sandbox blocks cmd.exe child processes.");
process.exit(0);
}
throw missingHelperResult.error;
}
assert.strictEqual(
missingHelperResult.status,
0,
missingHelperResult.stderr || missingHelperResult.stdout,
);
assert.ok(
fs.existsSync(path.join(missingHelperBaseDir, "skills", "custom-skill", "SKILL.md")),
"explicit skill args should still activate when the bundle helper path is missing",
);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}

View File

@@ -1,13 +1,17 @@
const assert = require("assert");
const fs = require("fs");
const os = require("os");
const path = require("path");
const { spawnSync } = require("child_process");
const repoRoot = path.resolve(__dirname, "../..", "..");
const scriptPath = path.join(repoRoot, "scripts", "activate-skills.sh");
if (process.platform === "win32") {
console.log("Skipping activate-skills.sh smoke test on Windows; use the batch-script coverage instead.");
process.exit(0);
}
const root = fs.mkdtempSync(path.join(os.tmpdir(), "activate-skills-shell-"));
const repoRoot = path.resolve(__dirname, "../..", "..");
const scriptPath = path.posix.join("scripts", "activate-skills.sh");
const root = fs.mkdtempSync(path.join(repoRoot, ".tmp-activate-skills-shell-"));
const baseDir = path.join(root, "antigravity");
const repoSkills = path.join(root, "repo-skills");
const outsideDir = path.join(root, "outside-skill-root");
@@ -37,8 +41,8 @@ try {
cwd: repoRoot,
env: {
...process.env,
AG_BASE_DIR: baseDir,
AG_REPO_SKILLS_DIR: repoSkills,
AG_BASE_DIR: path.relative(repoRoot, baseDir).split(path.sep).join("/"),
AG_REPO_SKILLS_DIR: path.relative(repoRoot, repoSkills).split(path.sep).join("/"),
AG_PYTHON_BIN: "python3",
},
encoding: "utf8",

View File

@@ -10,6 +10,7 @@ function runNpmPackDryRunJson() {
const result = spawnSync(npmCommand, ["pack", "--dry-run", "--json"], {
cwd: repoRoot,
encoding: "utf8",
shell: process.platform === "win32",
});
if (result.error) {

View File

@@ -3,6 +3,10 @@ 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(
@@ -13,10 +17,10 @@ const codexMarketplace = JSON.parse(
);
const claudePluginPaths = new Set(
claudeMarketplace.plugins.map((plugin) => plugin.source.replace(/^\.\//, "")),
claudeMarketplace.plugins.map((plugin) => normalizeRelativePath(plugin.source)),
);
const codexPluginPaths = new Set(
codexMarketplace.plugins.map((plugin) => plugin.source.path.replace(/^\.\//, "")),
codexMarketplace.plugins.map((plugin) => normalizeRelativePath(plugin.source.path)),
);
const knownPluginPaths = new Set([...claudePluginPaths, ...codexPluginPaths]);
@@ -69,7 +73,7 @@ for (const entry of fs.readdirSync(pluginsRoot, { withFileTypes: true })) {
continue;
}
const relativePluginPath = path.join("plugins", entry.name);
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),

View File

@@ -9,6 +9,7 @@ const TOOL_SCRIPTS = path.join("tools", "scripts");
const TOOL_TESTS = path.join(TOOL_SCRIPTS, "tests");
const LOCAL_TEST_COMMANDS = [
[path.join(TOOL_TESTS, "activate_skills_shell.test.js")],
[path.join(TOOL_TESTS, "activate_skills_batch_smoke.test.js")],
[path.join(TOOL_TESTS, "activate_skills_batch_security.test.js")],
[path.join(TOOL_TESTS, "automation_workflows.test.js")],
[path.join(TOOL_TESTS, "apply_skill_optimization_security.test.js")],

View File

@@ -4,7 +4,7 @@ const os = require("os");
const path = require("path");
async function main() {
const { copyIndexFiles } = await import("../../scripts/setup_web.js");
const { copyIndexFiles } = require("../../scripts/setup_web.js");
const root = fs.mkdtempSync(path.join(os.tmpdir(), "setup-web-sync-"));
try {
@@ -30,7 +30,4 @@ async function main() {
}
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
main();

View File

@@ -33,6 +33,10 @@ get_bundle_skills = load_module(
)
def relative_posix(path: pathlib.Path, root: pathlib.Path) -> str:
return path.relative_to(root).as_posix()
class EditorialBundlesTests(unittest.TestCase):
def setUp(self):
self.manifest_bundles = editorial_bundles.load_editorial_bundles(REPO_ROOT)
@@ -79,7 +83,7 @@ class EditorialBundlesTests(unittest.TestCase):
for skill in next(bundle for bundle in self.manifest_bundles if bundle["id"] == "essentials")["skills"]
}
actual_ids = {
str(path.relative_to(essentials_plugin))
relative_posix(path, essentials_plugin)
for path in essentials_plugin.rglob("SKILL.md")
}
self.assertEqual(actual_ids, {f"{skill_id}/SKILL.md" for skill_id in expected_ids})
@@ -140,12 +144,12 @@ class EditorialBundlesTests(unittest.TestCase):
self.assertTrue(copied_dir.is_dir(), f'copied skill dir missing for {skill["id"]}')
source_files = sorted(
str(path.relative_to(source_dir))
relative_posix(path, source_dir)
for path in source_dir.rglob("*")
if path.is_file()
)
copied_files = sorted(
str(path.relative_to(copied_dir))
relative_posix(path, copied_dir)
for path in copied_dir.rglob("*")
if path.is_file()
)

View File

@@ -133,11 +133,16 @@ class PluginCompatibilityTests(unittest.TestCase):
report = plugin_compatibility.build_report(REPO_ROOT / "skills")
entries = plugin_compatibility.compatibility_by_skill_id(report)
for skill_id in ("project-skill-audit", "molykit", "claude-code-expert"):
for skill_id in ("molykit", "claude-code-expert"):
self.assertEqual(entries[skill_id]["targets"]["codex"], "blocked")
self.assertEqual(entries[skill_id]["targets"]["claude"], "blocked")
self.assertIn("absolute_host_path", entries[skill_id]["reasons"])
self.assertEqual(entries["project-skill-audit"]["targets"]["codex"], "supported")
self.assertEqual(entries["project-skill-audit"]["targets"]["claude"], "blocked")
self.assertNotIn("absolute_host_path", entries["project-skill-audit"]["reasons"])
self.assertIn("target_specific_home_path", entries["project-skill-audit"]["blocked_reasons"]["claude"])
self.assertEqual(entries["playwright-skill"]["targets"]["codex"], "supported")
self.assertEqual(entries["playwright-skill"]["targets"]["claude"], "supported")
self.assertEqual(entries["playwright-skill"]["setup"]["type"], "manual")

View File

@@ -19,7 +19,7 @@ def collect_skill_ids(skills_dir):
for root, dirs, files in os.walk(skills_dir):
dirs[:] = [d for d in dirs if not d.startswith(".")]
if "SKILL.md" in files:
rel = os.path.relpath(root, skills_dir)
rel = os.path.relpath(root, skills_dir).replace(os.sep, "/")
ids.add(rel)
return ids