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:
@@ -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 };
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
123
tools/scripts/tests/activate_skills_batch_smoke.test.js
Normal file
123
tools/scripts/tests/activate_skills_batch_smoke.test.js
Normal 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 });
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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")],
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user