📦 deps(thirdparty): update snapshots

This commit is contained in:
ci[bot]
2026-07-10 16:04:20 +00:00
parent 2bb0dc8c1d
commit 92c0c3f687
594 changed files with 55224 additions and 19038 deletions
+116 -31
View File
@@ -33,14 +33,15 @@ function resolveDir(p) {
return path.resolve(root, ...sanitizedSegments);
}
function parseArgs() {
const a = process.argv.slice(2);
function parseArgs(argv = process.argv.slice(2)) {
const a = argv;
let pathArg = null;
let versionArg = null;
let tagArg = null;
let riskArg = null;
let categoryArg = null;
let tagsArg = null;
let versionInfo = false;
let cursor = false,
claude = false,
gemini = false,
@@ -51,28 +52,22 @@ function parseArgs() {
for (let i = 0; i < a.length; i++) {
if (a[i] === "--help" || a[i] === "-h") return { help: true };
if (a[i] === "--path" && a[i + 1]) {
pathArg = a[++i];
if (a[i] === "--version") {
versionInfo = true;
continue;
}
if (a[i] === "--version" && a[i + 1]) {
versionArg = a[++i];
continue;
}
if (a[i] === "--tag" && a[i + 1]) {
tagArg = a[++i];
continue;
}
if (a[i] === "--risk" && a[i + 1]) {
riskArg = a[++i];
continue;
}
if (a[i] === "--category" && a[i + 1]) {
categoryArg = a[++i];
continue;
}
if (a[i] === "--tags" && a[i + 1]) {
tagsArg = a[++i];
if (["--path", "--release", "--tag", "--risk", "--category", "--tags"].includes(a[i])) {
const value = a[i + 1];
if (!value || value.startsWith("--")) {
throw new Error(`Option ${a[i]} requires a value.`);
}
if (a[i] === "--path") pathArg = value;
if (a[i] === "--release") versionArg = value;
if (a[i] === "--tag") tagArg = value;
if (a[i] === "--risk") riskArg = value;
if (a[i] === "--category") categoryArg = value;
if (a[i] === "--tags") tagsArg = value;
i += 1;
continue;
}
if (a[i] === "--cursor") {
@@ -104,6 +99,7 @@ function parseArgs() {
continue;
}
if (a[i] === "install") continue;
throw new Error(`Unknown option or command: ${a[i]}`);
}
return {
@@ -113,6 +109,7 @@ function parseArgs() {
riskArg,
categoryArg,
tagsArg,
versionInfo,
cursor,
claude,
gemini,
@@ -182,7 +179,8 @@ Options:
--risk <csv> Install only skills matching these risk labels
--category <csv> Install only skills matching these categories
--tags <csv> Install only skills matching these tags
--version <ver> Clone tag v<ver> (e.g. 4.6.0 -> v4.6.0)
--version Print the installer version
--release <ver> Clone tag v<ver> (e.g. 4.6.0 -> v4.6.0)
--tag <tag> Clone this tag or branch (e.g. v4.6.0, main)
Examples:
@@ -193,7 +191,7 @@ Examples:
npx agentic-awesome-skills --agy
npx agentic-awesome-skills --path .agents/skills --category development,backend --risk safe,none
npx agentic-awesome-skills --path .agents/skills --tags debugging,typescript-legacy-
npx agentic-awesome-skills --version 4.6.0
npx agentic-awesome-skills --release 4.6.0
npx agentic-awesome-skills --path ./my-skills
npx agentic-awesome-skills --claude --codex Install to multiple targets
`);
@@ -312,7 +310,14 @@ function assertSafeDestinationPath(dest, destRoot) {
}
}
function copyRecursiveSync(src, dest, rootDir = src, skipGit = true, destRoot = dest) {
function copyRecursiveSync(
src,
dest,
rootDir = src,
skipGit = true,
destRoot = dest,
selectedSkillEntries = null,
) {
const stats = fs.lstatSync(src);
const resolvedSource = stats.isSymbolicLink()
? resolveSafeRealPath(rootDir, src)
@@ -324,6 +329,17 @@ function copyRecursiveSync(src, dest, rootDir = src, skipGit = true, destRoot =
}
const resolvedStats = fs.statSync(resolvedSource);
const relativeSource = path.relative(rootDir, resolvedSource);
if (
selectedSkillEntries &&
relativeSource &&
relativeSource !== "." &&
resolvedStats.isDirectory() &&
fs.existsSync(path.join(resolvedSource, "SKILL.md")) &&
!selectedSkillEntries.has(path.normalize(relativeSource))
) {
return;
}
if (fs.existsSync(dest) && fs.lstatSync(dest).isSymbolicLink()) {
throw new Error(`Skipping unsafe destination symlink: ${dest}`);
}
@@ -344,6 +360,7 @@ function copyRecursiveSync(src, dest, rootDir = src, skipGit = true, destRoot =
rootDir,
skipGit,
destRoot,
selectedSkillEntries,
);
}
} finally {
@@ -354,6 +371,56 @@ function copyRecursiveSync(src, dest, rootDir = src, skipGit = true, destRoot =
}
}
function replaceManagedEntry(
src,
dest,
rootDir,
skipGit,
targetRoot,
selectedSkillEntries = null,
) {
if (!fs.existsSync(targetRoot)) {
fs.mkdirSync(targetRoot, { recursive: true });
}
assertSafeDestinationPath(dest, targetRoot);
const stageRoot = fs.mkdtempSync(path.join(targetRoot, ".antigravity-stage-"));
const stagedEntry = path.join(stageRoot, "entry");
const backupEntry = path.join(stageRoot, "previous");
let movedPreviousEntry = false;
try {
copyRecursiveSync(
src,
stagedEntry,
rootDir,
skipGit,
stageRoot,
selectedSkillEntries,
);
if (!fs.existsSync(stagedEntry)) {
throw new Error(`Unable to stage managed install entry: ${src}`);
}
if (fs.existsSync(dest)) {
assertSafeDestinationPath(dest, targetRoot);
fs.renameSync(dest, backupEntry);
movedPreviousEntry = true;
}
fs.mkdirSync(path.dirname(dest), { recursive: true });
fs.renameSync(stagedEntry, dest);
if (movedPreviousEntry) {
fs.rmSync(backupEntry, { recursive: true, force: true });
}
} catch (error) {
if (movedPreviousEntry && fs.existsSync(backupEntry) && !fs.existsSync(dest)) {
fs.renameSync(backupEntry, dest);
}
throw error;
} finally {
fs.rmSync(stageRoot, { recursive: true, force: true });
}
}
/** Copy contents of repo's skills/ into target so each skill is target/skill-name/ (for Claude Code etc.). */
function getInstallEntries(tempDir, selectors = buildInstallSelectors({})) {
const repoSkills = path.join(tempDir, "skills");
@@ -381,18 +448,22 @@ function getInstallEntries(tempDir, selectors = buildInstallSelectors({})) {
function installSkillsIntoTarget(tempDir, target, installEntries) {
const repoSkills = path.join(tempDir, "skills");
const selectedSkillEntries = new Set(
installEntries
.filter((entry) => entry !== "docs")
.map(normalizeSourceEntry),
);
installEntries.forEach((name) => {
const destName = normalizeInstallEntry(name);
if (destName === "docs") {
const repoDocs = path.join(tempDir, "docs");
const docsDest = path.join(target, "docs");
if (!fs.existsSync(docsDest)) fs.mkdirSync(docsDest, { recursive: true });
copyRecursiveSync(repoDocs, docsDest, repoDocs, true, target);
replaceManagedEntry(repoDocs, docsDest, repoDocs, true, target);
return;
}
const src = path.join(repoSkills, normalizeSourceEntry(name));
const dest = path.join(target, destName);
copyRecursiveSync(src, dest, repoSkills, true, target);
replaceManagedEntry(src, dest, repoSkills, true, target, selectedSkillEntries);
});
}
@@ -630,8 +701,8 @@ function installForTarget(tempDir, target, selectors = buildInstallSelectors({})
const installEntries = getInstallEntries(tempDir, selectors);
const managedEntries = getManagedEntries(installEntries, target);
const previousEntries = readInstallManifest(target.path);
pruneRemovedEntries(target.path, previousEntries, managedEntries);
installSkillsIntoTarget(tempDir, target.path, installEntries);
pruneRemovedEntries(target.path, previousEntries, managedEntries);
writeInstallManifest(target.path, managedEntries);
console.log(` ✓ Installed to ${target.path}`);
}
@@ -679,7 +750,14 @@ function getPostInstallMessages(targets, selectors = buildInstallSelectors({}))
}
function main() {
const opts = parseArgs();
let opts;
try {
opts = parseArgs();
} catch (error) {
console.error(`Error: ${error.message}`);
process.exitCode = 1;
return;
}
const selectors = buildInstallSelectors(opts);
const ref = resolveInstallRef(opts);
@@ -688,8 +766,13 @@ function main() {
return;
}
if (opts.versionInfo) {
console.log(packageMetadata.version);
return;
}
const targets = getTargets(opts);
if (!targets.length || !HOME) {
if (!targets.length || (!HOME && !opts.pathArg)) {
console.error(
"Could not resolve home directory. Use --path <absolute-path>.",
);
@@ -735,6 +818,7 @@ if (require.main === module) {
module.exports = {
copyRecursiveSync,
replaceManagedEntry,
getPostInstallMessages,
buildCloneArgs,
buildInstallSelectors,
@@ -748,6 +832,7 @@ module.exports = {
matchesInstallSelectors,
normalizeInstallEntry,
parseSelectorArg,
parseArgs,
pruneRemovedEntries,
readInstallManifest,
resolveInstallRef,
@@ -1,3 +1,3 @@
{
"maxWarnings": 135
"maxWarnings": 0
}
@@ -158,6 +158,11 @@ function readSkill(skillDir, skillId) {
tags = tags.filter(Boolean);
const category = typeof data.category === 'string' ? data.category.trim() : '';
const risk = typeof data.risk === 'string' ? data.risk.trim() : '';
const source = typeof data.source === 'string' ? data.source.trim() : '';
const sourceType = typeof data.source_type === 'string' ? data.source_type.trim() : '';
const sourceRepo = typeof data.source_repo === 'string' ? data.source_repo.trim() : '';
const license = typeof data.license === 'string' ? data.license.trim() : '';
const licenseSource = typeof data.license_source === 'string' ? data.license_source.trim() : '';
return {
id: skillId,
@@ -165,6 +170,11 @@ function readSkill(skillDir, skillId) {
description,
category,
risk,
source,
sourceType,
sourceRepo,
license,
licenseSource,
tags,
path: skillPath,
content,
@@ -372,7 +372,7 @@ const BUNDLE_RULES = {
},
"security-core": {
description: "Security, privacy, and compliance essentials.",
excludeCategories: new Set(["business"]),
excludeCategories: new Set(["business", "product"]),
keywords: [
"security",
"sast",
@@ -718,15 +718,17 @@ function renderCatalogMarkdown(catalog) {
);
lines.push(`## ${category} (${grouped.length})`);
lines.push("");
lines.push("| Skill | Description | Tags | Triggers |");
lines.push("| --- | --- | --- | --- |");
lines.push("| Skill | Description | Risk | Source | Tags | Triggers |");
lines.push("| --- | --- | --- | --- | --- | --- |");
for (const skill of grouped) {
const description = escapeMarkdownTableCell(truncate(skill.description, 160));
const tags = escapeMarkdownTableCell(skill.tags.join(", "));
const triggers = escapeMarkdownTableCell(skill.triggers.join(", "));
const risk = escapeMarkdownTableCell(skill.risk || "unknown");
const source = escapeMarkdownTableCell(skill.source_repo || skill.source || "unknown");
lines.push(
`| \`${skill.id}\` | ${description} | ${tags} | ${triggers} |`,
`| \`${skill.id}\` | ${description} | ${risk} | ${source} | ${tags} | ${triggers} |`,
);
}
@@ -736,6 +738,15 @@ function renderCatalogMarkdown(catalog) {
return lines.join("\n");
}
function readCanonicalIndex() {
const indexPath = path.join(ROOT, "skills_index.json");
const parsed = JSON.parse(fs.readFileSync(indexPath, "utf8"));
if (!Array.isArray(parsed)) {
throw new Error("skills_index.json must be an array.");
}
return new Map(parsed.map((skill) => [skill.path, skill]));
}
function readCatalogGeneratedAt() {
if (process.env.SOURCE_DATE_EPOCH) {
return new Date(process.env.SOURCE_DATE_EPOCH * 1000).toISOString();
@@ -758,18 +769,27 @@ function readCatalogGeneratedAt() {
function buildCatalog() {
const skillRelPaths = listSkillIdsRecursive(SKILLS_DIR);
const skills = skillRelPaths.map((relPath) => readSkill(SKILLS_DIR, relPath));
const canonicalIndex = readCanonicalIndex();
const catalogSkills = [];
for (const skill of skills) {
const tags = deriveTags(skill);
const category = detectCategory(skill, tags);
const canonical = canonicalIndex.get(`skills/${skillRelPaths[catalogSkills.length]}`);
const category = canonical?.category || detectCategory(skill, tags);
const triggers = buildTriggers(skill, tags);
catalogSkills.push({
id: skill.id,
canonical_id: canonical?.id || skill.id,
name: skill.name,
description: skill.description,
category,
risk: canonical?.risk || skill.risk || "unknown",
source: canonical?.source || skill.source || "unknown",
source_type: canonical?.source_type || skill.sourceType || undefined,
source_repo: canonical?.source_repo || skill.sourceRepo || undefined,
license: canonical?.license || skill.license || undefined,
license_source: canonical?.license_source || skill.licenseSource || undefined,
tags,
triggers,
// Normalize separators for deterministic cross-platform output.
@@ -306,8 +306,8 @@ def main(argv: list[str] | None = None) -> int:
baseline = load_baseline(baseline_path)
if not baseline:
print("⚠️ No baseline found. Run with --update-baseline to create one.")
return 0
print(" No baseline found; drift was not evaluated. Run with --update-baseline to create one.")
return 2
current = build_current_entries(skills_dir)
if args.skill:
@@ -132,6 +132,7 @@ def update_skill_file(
*,
add_missing: bool = False,
add_limitations_only: bool = False,
add_when_only: bool = False,
) -> tuple[bool, list[str]]:
if not is_safe_regular_file(skill_path):
return False, []
@@ -150,7 +151,7 @@ def update_skill_file(
if updated != content:
changes.append("normalized_when_heading")
add_when = add_missing
add_when = add_missing or add_when_only
add_examples = add_missing
add_limitations = add_missing or add_limitations_only
@@ -188,10 +189,16 @@ def main() -> int:
action="store_true",
help="Only synthesize missing 'Limitations' sections.",
)
parser.add_argument("--add-when-only", action="store_true", help="Only synthesize missing 'When to Use' sections.")
parser.add_argument("--only", action="append", default=[], help="Restrict changes to a repository-relative SKILL.md path (repeatable).")
args = parser.parse_args()
if args.add_missing and args.add_when_only:
parser.error("--add-missing and --add-when-only cannot be combined")
repo_root = find_repo_root(__file__)
skills_dir = repo_root / "skills"
only_paths = {Path(item).as_posix() for item in args.only}
modified = 0
for root, dirs, files in os.walk(skills_dir):
@@ -200,6 +207,9 @@ def main() -> int:
continue
skill_path = Path(root) / "SKILL.md"
relative_path = skill_path.relative_to(repo_root).as_posix()
if only_paths and relative_path not in only_paths:
continue
if not is_safe_regular_file(skill_path):
print(f"SKIP {skill_path.relative_to(repo_root)} [symlinked_or_unreadable]")
continue
@@ -209,7 +219,7 @@ def main() -> int:
continue
simulated = normalize_when_heading_variants(content)
needs_when = args.add_missing and not has_when_to_use_section(simulated)
needs_when = (args.add_missing or args.add_when_only) and not has_when_to_use_section(simulated)
needs_examples = args.add_missing and not has_examples(simulated)
needs_limitations = (args.add_missing or args.add_limitations_only) and not has_limitations(simulated)
if not needs_when and not needs_examples and not needs_limitations and simulated == content:
@@ -233,6 +243,7 @@ def main() -> int:
skill_path,
add_missing=args.add_missing,
add_limitations_only=args.add_limitations_only,
add_when_only=args.add_when_only,
)
if changed:
modified += 1
@@ -930,6 +930,10 @@ def generate_index(skills_dir, output_file, compatibility_report=None):
description = coerce_metadata_text(metadata.get("description"))
risk = coerce_metadata_text(metadata.get("risk"))
source = coerce_metadata_text(metadata.get("source"))
source_type = coerce_metadata_text(metadata.get("source_type"))
source_repo = coerce_metadata_text(metadata.get("source_repo"))
license_value = coerce_metadata_text(metadata.get("license"))
license_source = coerce_metadata_text(metadata.get("license_source"))
date_added = coerce_metadata_text(metadata.get("date_added"))
category = coerce_metadata_text(metadata.get("category"))
@@ -941,6 +945,14 @@ def generate_index(skills_dir, output_file, compatibility_report=None):
skill_info["risk"] = risk
if source is not None:
skill_info["source"] = source
if source_type is not None:
skill_info["source_type"] = source_type
if source_repo is not None:
skill_info["source_repo"] = source_repo
if license_value is not None:
skill_info["license"] = license_value
if license_source is not None:
skill_info["license_source"] = license_source
if date_added is not None:
skill_info["date_added"] = date_added
@@ -252,6 +252,11 @@ def analyze_skill(skill_dir: Path, skills_root: Path) -> dict[str, Any]:
for target, explicit_state in restrictions.items():
if explicit_state == "blocked":
target_reasons[target].add("explicit_target_restriction")
elif explicit_state == "supported":
# A supported declaration is a maintainer-reviewed override for
# alternative agent-home paths documented in the same skill. It
# must not suppress independent portability or runtime findings.
target_reasons[target].discard("target_specific_home_path")
statuses = {
target: "blocked" if target_reasons[target] else "supported"
@@ -49,12 +49,12 @@ function ensureOnMain(projectRoot) {
}
function ensureCleanWorkingTree(projectRoot, message) {
const status = runCommand("git", ["status", "--porcelain", "--untracked-files=no"], projectRoot, {
const status = runCommand("git", ["status", "--porcelain"], projectRoot, {
capture: true,
});
if (status) {
throw new Error(message || "Working tree has tracked changes. Commit or stash them first.");
throw new Error(message || "Working tree has changes. Commit, stash, or remove them first.");
}
}
@@ -4,20 +4,33 @@ const cp = require("child_process");
const YAML = require("yaml");
const ROOT = process.cwd();
const SKILLS_ROOT = path.resolve(ROOT, "skills");
const UPSTREAM_SHA = "70b2e1062fc6a38fce854226c27097a87732cb5f";
const SOURCE_LABEL = "vibeship-spawner-skills (Apache 2.0)";
const LIST_PATH = "/tmp/vibeship_files.txt";
const FILES = fs.existsSync(LIST_PATH)
? fs.readFileSync(LIST_PATH, "utf8").trim().split("\n").filter(Boolean)
: [];
const TREE = JSON.parse(
runCommand(
`gh api 'repos/vibeforge1111/vibeship-spawner-skills/git/trees/${UPSTREAM_SHA}?recursive=1'`,
),
);
const SKILL_PATHS = TREE.tree
.filter((entry) => /(^|\/)skill\.yaml$/.test(entry.path))
.map((entry) => entry.path);
const LIST_PATH = process.env.VIBESHIP_FILES_LIST || "";
function loadSkillPaths() {
const tree = JSON.parse(
runCommand(
`gh api 'repos/vibeforge1111/vibeship-spawner-skills/git/trees/${UPSTREAM_SHA}?recursive=1'`,
),
);
return tree.tree
.filter((entry) => /(^|\/)skill\.yaml$/.test(entry.path))
.map((entry) => entry.path);
}
function validateSkillFilePath(candidate) {
if (typeof candidate !== "string" || !candidate) return null;
const normalized = path.posix.normalize(candidate);
const match = normalized === candidate && /^skills\/([A-Za-z0-9][A-Za-z0-9._-]*)\/SKILL\.md$/.exec(normalized);
if (!match) return null;
const absolutePath = path.resolve(ROOT, ...normalized.split("/"));
const relativePath = path.relative(SKILLS_ROOT, absolutePath);
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) return null;
return { relativePath: normalized, absolutePath, skillId: match[1] };
}
function runCommand(cmd) {
return cp.execSync(cmd, {
@@ -474,7 +487,7 @@ function forceUpstreamDescription(absPath, description) {
function loadUpstreamPathBySkillId() {
const map = new Map();
for (const upstreamPath of SKILL_PATHS) {
for (const upstreamPath of loadSkillPaths()) {
const skillId = path.posix.basename(path.posix.dirname(upstreamPath));
if (!map.has(skillId)) map.set(skillId, []);
map.get(skillId).push(upstreamPath);
@@ -483,16 +496,23 @@ function loadUpstreamPathBySkillId() {
}
function main() {
if (!fs.existsSync(LIST_PATH)) {
throw new Error(`Missing skill list: ${LIST_PATH}`);
if (!LIST_PATH || !fs.existsSync(LIST_PATH)) {
throw new Error("Set VIBESHIP_FILES_LIST to an explicit list of skills/<id>/SKILL.md files.");
}
const files = fs.readFileSync(LIST_PATH, "utf8").trim().split("\n").filter(Boolean);
const skillPathMap = loadUpstreamPathBySkillId();
const touched = [];
const skipped = [];
for (const rel of FILES) {
const skillId = rel.split("/")[1];
for (const candidate of files) {
const validated = validateSkillFilePath(candidate);
if (!validated) {
skipped.push({ rel: candidate, matches: [] });
continue;
}
const { relativePath: rel, absolutePath: abs, skillId } = validated;
const matches = skillPathMap.get(skillId) || [];
if (matches.length !== 1) {
skipped.push({ rel, matches });
@@ -510,7 +530,6 @@ function main() {
const validations = parseOptionalYaml(`${baseDir}/validations.yaml`);
const collaboration = parseOptionalYaml(`${baseDir}/collaboration.yaml`);
const abs = path.join(ROOT, rel);
const existing = parseFrontmatter(fs.readFileSync(abs, "utf8"));
const frontmatter = { ...existing.data };
frontmatter.name = frontmatter.name || skill.id || skillId;
@@ -541,3 +560,5 @@ function main() {
if (require.main === module) {
main();
}
module.exports = { validateSkillFilePath };
@@ -77,7 +77,7 @@ SECURITY_PATTERNS: list[SecurityPattern] = [
),
SecurityPattern(
code="SEC007",
regex=r"\beval\s*\(",
regex=r"(?<!\$)\beval\s*\(",
severity="warning",
description="Dynamic eval() detected",
rationale="eval() can execute arbitrary code; acceptable only in controlled contexts.",
@@ -122,7 +122,12 @@ SECURITY_PATTERNS: list[SecurityPattern] = [
# Lines containing this marker are excluded from scanning (project convention).
# Prefix match covers both bare (<!-- security-allowlist -->) and colon forms
# (<!-- security-allowlist: reason -->) documented in skill-template.md.
_ALLOWLIST_MARKERS = ("# security-allowlist", "<!-- security-allowlist")
_ALLOWLIST_MARKERS = (
"# security-allowlist",
"// security-allowlist",
"<!-- security-allowlist",
"-- security-allowlist",
)
TEXT_EXTENSIONS = {".cjs", ".js", ".json", ".md", ".mjs", ".py", ".sh", ".ts", ".txt", ".yaml", ".yml"}
SUPPORT_FILE_PATTERN_CODES = {"SEC002", "SEC003", "SEC004", "SEC005", "SEC008"}
@@ -2,41 +2,41 @@ const fs = require('fs');
const path = require('path');
const { findProjectRoot } = require('../lib/project-root');
const { listSkillIdsRecursive } = require('../lib/skill-utils');
const { resolveSafeRealPath } = require('../lib/symlink-safety');
const ROOT_DIR = findProjectRoot(__dirname);
const WEB_APP_PUBLIC = path.join(ROOT_DIR, 'apps', 'web-app', 'public');
// 2. Copy skills directory content
// Note: Symlinking is better, but Windows often requires admin for symlinks.
// We will try to copy for reliability in this environment.
function copySkillMarkdownFiles(sourceSkills, destinationSkills) {
for (const skillId of listSkillIdsRecursive(sourceSkills)) {
const sourceFile = path.join(sourceSkills, skillId, 'SKILL.md');
const destinationFile = path.join(destinationSkills, skillId, 'SKILL.md');
fs.mkdirSync(path.dirname(destinationFile), { recursive: true });
fs.copyFileSync(sourceFile, destinationFile);
}
}
// Kept for the security-copy test harness and local callers. Production web
// setup uses copySkillMarkdownFiles above, so it never publishes active files.
function copyFolderSync(from, to, rootDir = from) {
if (!fs.existsSync(to)) fs.mkdirSync(to, { recursive: true });
fs.readdirSync(from).forEach(element => {
if (element.startsWith('.')) {
return;
}
for (const element of fs.readdirSync(from)) {
if (element.startsWith('.')) continue;
const srcPath = path.join(from, element);
const destPath = path.join(to, element);
const stat = fs.lstatSync(srcPath);
const realPath = stat.isSymbolicLink() ? resolveSafeRealPath(rootDir, srcPath) : srcPath;
if (!realPath) {
console.warn(`[app:setup] Skipping symlink outside skills root: ${srcPath}`);
return;
continue;
}
const realStat = fs.statSync(realPath);
if (realStat.isFile()) {
if (fs.statSync(realPath).isFile()) {
fs.copyFileSync(realPath, destPath);
} else if (realStat.isDirectory()) {
} else if (fs.statSync(realPath).isDirectory()) {
copyFolderSync(realPath, destPath, rootDir);
}
// Skip other types (e.g. sockets, FIFOs)
});
}
}
function copyIndexFiles(sourceIndex, destIndex, destBackupIndex, publicRoot = path.dirname(destIndex)) {
@@ -74,14 +74,14 @@ function main() {
const sourceSkills = path.join(ROOT_DIR, 'skills');
const destSkills = path.join(WEB_APP_PUBLIC, 'skills');
console.log(`Copying skills directory...`);
console.log(`Copying skill markdown files...`);
// Check if destination exists and remove it to ensure fresh copy
if (fs.existsSync(destSkills)) {
fs.rmSync(destSkills, { recursive: true, force: true });
}
copyFolderSync(sourceSkills, destSkills, sourceSkills);
copySkillMarkdownFiles(sourceSkills, destSkills);
console.log('✅ Web app assets setup complete!');
}
@@ -90,4 +90,4 @@ if (require.main === module) {
main();
}
module.exports = { copyFolderSync, copyIndexFile, copyIndexFiles, main };
module.exports = { copyFolderSync, copySkillMarkdownFiles, copyIndexFile, copyIndexFiles, main };
@@ -12,10 +12,12 @@ import tempfile
import json
from pathlib import Path, PurePosixPath
from _project_paths import find_repo_root
MS_REPO = "https://github.com/microsoft/skills.git"
REPO_ROOT = Path(__file__).parent.parent
REPO_ROOT = find_repo_root(__file__)
TARGET_DIR = REPO_ROOT / "skills"
DOCS_DIR = REPO_ROOT / "docs"
DOCS_DIR = REPO_ROOT / "docs" / "sources"
ATTRIBUTION_FILE = DOCS_DIR / "microsoft-skills-attribution.json"
@@ -135,7 +135,7 @@ assert.match(
);
assert.match(
ciWorkflow,
/source-validation:[\s\S]*?- uses: actions\/checkout@v\d+[\s\S]*?with:[\s\S]*?fetch-depth: 0/,
/source-validation:[\s\S]*?- uses: actions\/checkout@[a-f0-9]{40}[\s\S]*?with:[\s\S]*?fetch-depth: 0/,
"source-validation should use an unshallowed checkout so base-branch diffs have a merge base",
);
assert.match(
@@ -14,18 +14,31 @@ const bundles = bundleData.bundles || {};
const catalog = JSON.parse(
fs.readFileSync(path.join(repoRoot, "data", "catalog.json"), "utf8"),
);
const canonicalIndex = JSON.parse(
fs.readFileSync(path.join(repoRoot, "skills_index.json"), "utf8"),
);
const skillsById = new Map(catalog.skills.map((skill) => [skill.id, skill]));
assert.strictEqual(
skillsById.get("before-you-build").category,
"business",
"explicit product frontmatter should keep product-risk skills out of security",
"product",
"catalog categories must match the canonical skills index",
);
assert.ok(
!bundles["security-core"].skills.includes("before-you-build"),
"explicit product frontmatter should keep product-risk skills out of the security bundle",
);
for (const canonicalSkill of canonicalIndex) {
const catalogSkill = catalog.skills.find(
(skill) => skill.path === `${canonicalSkill.path}/SKILL.md`,
);
assert.ok(catalogSkill, `catalog must contain ${canonicalSkill.path}`);
assert.strictEqual(catalogSkill.category, canonicalSkill.category);
assert.strictEqual(catalogSkill.risk, canonicalSkill.risk);
assert.strictEqual(catalogSkill.source, canonicalSkill.source);
}
for (const bundleId of [
"core-dev",
"security-core",
@@ -0,0 +1,34 @@
const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { spawnSync } = require('child_process');
const root = path.resolve(__dirname, '..', '..', '..');
const script = path.join(root, 'scripts', 'validate-glossary.sh');
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'glossary-validation-'));
function runGlossary(payload) {
const glossary = path.join(tempDir, 'glossary.json');
const report = path.join(tempDir, 'report.txt');
fs.writeFileSync(glossary, JSON.stringify(payload), 'utf8');
return spawnSync('bash', [script], {
cwd: root,
encoding: 'utf8',
env: { ...process.env, GLOSSARY_FILE: glossary, GLOSSARY_OUTPUT_FILE: report },
});
}
const valid = runGlossary({
metadata: { version: '1', created: '2026-01-01', last_updated: '2026-01-01', total_terms: 1 },
terms: { skill: { translation: '技能' } },
});
assert.strictEqual(valid.status, 0, valid.stderr || valid.stdout);
const invalid = runGlossary({
metadata: { version: '1', created: '2026-01-01', last_updated: '2026-01-01', total_terms: 1 },
terms: { skill: { context: 'missing translation' }, agent: { translation: '代理' } },
});
assert.strictEqual(invalid.status, 1, invalid.stderr || invalid.stdout);
fs.rmSync(tempDir, { recursive: true, force: true });
@@ -0,0 +1,25 @@
const assert = require('assert');
const path = require('path');
const { spawnSync } = require('child_process');
const packageVersion = require('../../../package.json').version;
const installerPath = path.resolve(__dirname, '..', '..', 'bin', 'install.js');
const installer = require(installerPath);
assert.throws(() => installer.parseArgs(['--path']), /requires a value/i);
assert.throws(() => installer.parseArgs(['--path', '--codex']), /requires a value/i);
assert.throws(() => installer.parseArgs(['--unknown']), /unknown option/i);
assert.throws(() => installer.parseArgs(['status']), /unknown option or command/i);
const release = installer.parseArgs(['--release', '14.0.0']);
assert.strictEqual(release.versionArg, '14.0.0');
assert.strictEqual(release.versionInfo, false);
const version = spawnSync(process.execPath, [installerPath, '--version'], { encoding: 'utf8' });
assert.strictEqual(version.status, 0, version.stderr);
assert.strictEqual(version.stdout.trim(), packageVersion);
assert.doesNotMatch(version.stdout, /Cloning repository/i);
const invalid = spawnSync(process.execPath, [installerPath, '--unknown'], { encoding: 'utf8' });
assert.notStrictEqual(invalid.status, 0);
assert.match(invalid.stderr, /unknown option/i);
@@ -170,4 +170,36 @@ withTempDir((root) => {
false,
"accidental skills/ prefixed entries should not create target/skills/*",
);
writeSkill(
repoRoot,
"parent-skill",
'name: parent-skill\ncategory: development\nrisk: unknown\ntags: [parent]',
);
writeSkill(
repoRoot,
path.join("parent-skill", "safe-child"),
'name: safe-child\ncategory: development\nrisk: safe\ntags: [child]',
);
const filteredTarget = path.join(root, "filtered-target");
const unknownEntries = installer.getInstallEntries(
repoRoot,
installer.buildInstallSelectors({ riskArg: "unknown" }),
);
assert.deepStrictEqual(
unknownEntries,
["parent-skill", "skills/x402-express-wrapper", "docs"],
"the selected parent must not implicitly select its differently classified child",
);
installer.installSkillsIntoTarget(repoRoot, filteredTarget, unknownEntries);
assert.strictEqual(
fs.existsSync(path.join(filteredTarget, "parent-skill", "SKILL.md")),
true,
"the selected parent skill should be installed",
);
assert.strictEqual(
fs.existsSync(path.join(filteredTarget, "parent-skill", "safe-child", "SKILL.md")),
false,
"a nested skill that does not match filters must not leak into the installation",
);
});
@@ -38,6 +38,11 @@ try {
createFakeRepo(repoV1, ["skill-a", "skill-b"]);
createFakeRepo(repoV2, ["skill-a"]);
fs.writeFileSync(
path.join(repoV1, "skills", "skill-a", "removed-script.sh"),
"#!/usr/bin/env bash\necho legacy\n",
"utf8",
);
writeSkill(
repoV1,
path.join("nested", "skill-c"),
@@ -64,6 +69,11 @@ try {
{ name: "Test", path: targetDir },
installer.buildInstallSelectors({ categoryArg: "backend" }),
);
assert.strictEqual(
fs.existsSync(path.join(targetDir, "skill-a", "removed-script.sh")),
false,
"updates must remove files that disappeared from a still-managed skill",
);
assert.strictEqual(
fs.existsSync(path.join(targetDir, "skill-a")),
false,
@@ -0,0 +1,20 @@
const assert = require('assert');
const path = require('path');
const scriptPath = path.resolve(__dirname, '..', 'restore_vibeship_skills.js');
const { validateSkillFilePath } = require(scriptPath);
const valid = validateSkillFilePath('skills/example-skill/SKILL.md');
assert.ok(valid);
assert.strictEqual(valid.skillId, 'example-skill');
assert.match(valid.absolutePath, /skills[\\/]example-skill[\\/]SKILL\.md$/);
for (const invalid of [
'../package.json',
'skills/example-skill/../../package.json',
'skills/nested/example/SKILL.md',
'skills/example-skill/README.md',
'/tmp/vibeship_files.txt',
]) {
assert.strictEqual(validateSkillFilePath(invalid), null, invalid);
}
@@ -1,5 +1,6 @@
#!/usr/bin/env node
const fs = require("fs");
const { spawnSync } = require("child_process");
const path = require("path");
@@ -7,73 +8,63 @@ const NETWORK_TEST_ENV = "ENABLE_NETWORK_TESTS";
const ENABLED_VALUES = new Set(["1", "true", "yes", "on"]);
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")],
[path.join(TOOL_TESTS, "build_catalog_bundles.test.js")],
[path.join(TOOL_TESTS, "claude_plugin_marketplace.test.js")],
[path.join(TOOL_TESTS, "codex_plugin_marketplace.test.js")],
[path.join(TOOL_TESTS, "specialized_plugin_candidates.test.js")],
[path.join(TOOL_TESTS, "plugin_directories.test.js")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_editorial_bundles.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_plugin_compatibility.py")],
[path.join(TOOL_TESTS, "installer_antigravity_guidance.test.js")],
[path.join(TOOL_TESTS, "installer_filters.test.js")],
[path.join(TOOL_TESTS, "installer_update_sync.test.js")],
[path.join(TOOL_TESTS, "jetski_gemini_loader.test.cjs")],
[path.join(TOOL_TESTS, "merge_batch.test.js")],
[path.join(TOOL_TESTS, "npm_package_contents.test.js")],
[path.join(TOOL_TESTS, "repo_hygiene_security.test.js")],
[path.join(TOOL_TESTS, "review_changed_skills.test.js")],
[path.join(TOOL_TESTS, "copy_security.test.js")],
[path.join(TOOL_TESTS, "setup_web_sync.test.js")],
[path.join(TOOL_TESTS, "skill_filter.test.js")],
[path.join(TOOL_TESTS, "validate_skills_headings.test.js")],
[path.join(TOOL_TESTS, "validate_skills_metadata.test.js")],
[path.join(TOOL_TESTS, "workflow_contracts.test.js")],
[path.join(TOOL_TESTS, "docs_security_content.test.js")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_bundle_activation_security.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_audit_skills.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_audit_consistency.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_cleanup_synthetic_skill_sections.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_fix_missing_skill_metadata.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_fix_missing_skill_sections.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_fix_truncated_descriptions.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_generate_index_categories.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_ingest_youtube_security.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_repair_description_usage_summaries.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_readme_credits.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_sync_microsoft_skills_security.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_skill_installer_copy_tree.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_sync_repo_metadata.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_sync_contributors.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_sync_risk_labels.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_skill_source_metadata.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_validation_warning_budget.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_whatsapp_config_logging_security.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_weaviate_conn_logging_security.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_maintainer_audit.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_validate_skills_headings.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_validate_skills_strict.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_security_scanner.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_score_skills.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_detect_drift.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_generate_registry_report.py")],
];
const NETWORK_TEST_COMMANDS = [
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "inspect_microsoft_repo.py")],
[path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_comprehensive_coverage.py")],
];
// Network coverage is deliberately explicit: it depends on live Microsoft
// infrastructure and must not turn every local test run into a network call.
const NETWORK_TEST_FILES = new Set([
path.join(TOOL_TESTS, "inspect_microsoft_repo.py"),
path.join(TOOL_TESTS, "test_comprehensive_coverage.py"),
]);
function isTestFile(relativePath) {
const basename = path.basename(relativePath);
return (
/^test_.*\.py$/.test(basename) ||
/\.test\.(?:js|cjs|mjs)$/.test(basename)
);
}
function listFiles(directory) {
const entries = fs.readdirSync(directory, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const filePath = path.join(directory, entry.name);
if (entry.isDirectory()) {
files.push(...listFiles(filePath));
} else if (entry.isFile()) {
files.push(filePath);
}
}
return files.sort();
}
function commandForTest(testPath) {
return testPath.endsWith(".py")
? [path.join(TOOL_SCRIPTS, "run-python.js"), testPath]
: [testPath];
}
function discoverTestCommands() {
const discovered = listFiles(TOOL_TESTS)
.filter((testPath) => isTestFile(path.relative(TOOL_TESTS, testPath)))
.map(commandForTest);
const network = [...NETWORK_TEST_FILES]
.map(commandForTest)
.sort((left, right) => left.at(-1).localeCompare(right.at(-1)));
const networkPaths = new Set(NETWORK_TEST_FILES);
const local = discovered.filter((command) => !networkPaths.has(command.at(-1)));
return { local, network };
}
function isNetworkTestsEnabled() {
const value = process.env[NETWORK_TEST_ENV];
if (!value) {
return false;
}
return ENABLED_VALUES.has(String(value).trim().toLowerCase());
return value
? ENABLED_VALUES.has(String(value).trim().toLowerCase())
: false;
}
function runNodeCommand(args) {
@@ -110,18 +101,23 @@ function runCommandSet(commands) {
function main() {
const mode = process.argv[2];
const { local, network } = discoverTestCommands();
if (mode === "--local") {
runCommandSet(LOCAL_TEST_COMMANDS);
runCommandSet(local);
return;
}
if (mode === "--network") {
runCommandSet(NETWORK_TEST_COMMANDS);
runCommandSet(network);
return;
}
runCommandSet(LOCAL_TEST_COMMANDS);
if (mode) {
throw new Error(`Unknown test mode: ${mode}`);
}
runCommandSet(local);
if (!isNetworkTestsEnabled()) {
console.log(
@@ -131,7 +127,17 @@ function main() {
}
console.log(`[tests] ${NETWORK_TEST_ENV} enabled; running network integration tests.`);
runCommandSet(NETWORK_TEST_COMMANDS);
runCommandSet(network);
}
main();
if (require.main === module) {
main();
}
module.exports = {
NETWORK_TEST_FILES,
commandForTest,
discoverTestCommands,
isTestFile,
listFiles,
};
@@ -0,0 +1,50 @@
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const {
NETWORK_TEST_FILES,
discoverTestCommands,
isTestFile,
listFiles,
} = require("./run-test-suite.js");
const TEST_ROOT = path.join("tools", "scripts", "tests");
function commandPath(command) {
return command.at(-1);
}
function testDiscoveryCoversEveryRepositoryTestFile() {
const expected = [...new Set([
...listFiles(TEST_ROOT)
.filter((filePath) => isTestFile(path.relative(TEST_ROOT, filePath))),
...NETWORK_TEST_FILES,
])].sort();
const { local, network } = discoverTestCommands();
const actual = [...local, ...network].map(commandPath).sort();
assert.deepStrictEqual(actual, expected);
assert.ok(actual.includes(path.join(TEST_ROOT, "test_ws_listener_security.py")));
assert.ok(actual.includes(path.join(TEST_ROOT, "run_test_suite.test.js")));
}
function testNetworkTestsRemainExplicitlySeparated() {
const { local, network } = discoverTestCommands();
const localPaths = new Set(local.map(commandPath));
const networkPaths = new Set(network.map(commandPath));
assert.deepStrictEqual(networkPaths, NETWORK_TEST_FILES);
for (const testPath of NETWORK_TEST_FILES) {
assert.ok(!localPaths.has(testPath));
assert.ok(fs.existsSync(testPath));
}
}
function main() {
testDiscoveryCoversEveryRepositoryTestFile();
testNetworkTestsRemainExplicitlySeparated();
console.log("run-test-suite discovery tests passed.");
}
main();
@@ -4,7 +4,7 @@ const os = require("os");
const path = require("path");
async function main() {
const { copyFolderSync, copyIndexFiles } = require("../../scripts/setup_web.js");
const { copySkillMarkdownFiles, copyIndexFiles } = require("../../scripts/setup_web.js");
const root = fs.mkdtempSync(path.join(os.tmpdir(), "setup-web-sync-"));
try {
@@ -31,9 +31,10 @@ async function main() {
fs.mkdirSync(path.join(skillsSource, "visible-skill"), { recursive: true });
fs.mkdirSync(path.join(skillsSource, ".disabled", "hidden-skill"), { recursive: true });
fs.writeFileSync(path.join(skillsSource, "visible-skill", "SKILL.md"), "# Visible\n", "utf8");
fs.writeFileSync(path.join(skillsSource, "visible-skill", "viewer.html"), "<script>alert(1)</script>", "utf8");
fs.writeFileSync(path.join(skillsSource, ".disabled", "hidden-skill", "SKILL.md"), "# Hidden\n", "utf8");
copyFolderSync(skillsSource, skillsDest, skillsSource);
copySkillMarkdownFiles(skillsSource, skillsDest);
assert.ok(fs.existsSync(path.join(skillsDest, "visible-skill", "SKILL.md")));
assert.strictEqual(
@@ -41,6 +42,11 @@ async function main() {
false,
"web asset setup must not publish dot-prefixed skills directories",
);
assert.strictEqual(
fs.existsSync(path.join(skillsDest, "visible-skill", "viewer.html")),
false,
"web asset setup must publish markdown only, never active community assets",
);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
@@ -136,6 +136,18 @@ class BaselineIOTests(unittest.TestCase):
result = detect_drift.load_baseline(Path("/nonexistent/baseline.json"))
self.assertEqual(result, {})
def test_main_fails_when_baseline_is_missing(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
(root / "skills").mkdir()
(root / "package.json").write_text('{"version": "1.0.0"}', encoding="utf-8")
original_find_repo_root = detect_drift.find_repo_root
detect_drift.find_repo_root = lambda _path: root
try:
self.assertEqual(detect_drift.main([]), 2)
finally:
detect_drift.find_repo_root = original_find_repo_root
def test_save_and_load_roundtrip(self):
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "data" / "baseline.json"
@@ -155,6 +155,29 @@ class PluginCompatibilityTests(unittest.TestCase):
self.assertIn("explicit_target_restriction", entry["blocked_reasons"]["codex"])
self.assertIn("explicit_target_restriction", entry["blocked_reasons"]["claude"])
def test_explicit_supported_target_overrides_alternative_home_path(self):
with tempfile.TemporaryDirectory() as temp_dir:
skills_dir = pathlib.Path(temp_dir) / "skills"
self._write_skill(
skills_dir,
"portable-skill",
(
"---\n"
"name: portable-skill\n"
"description: Example\n"
"plugin:\n"
" targets:\n"
" codex: supported\n"
"---\n"
"Use ~/.claude for Claude or ~/.codex for Codex.\n"
),
)
report = plugin_compatibility.build_report(skills_dir)
entry = report["skills"][0]
self.assertEqual(entry["targets"]["codex"], "supported")
self.assertNotIn("target_specific_home_path", entry["blocked_reasons"]["codex"])
def test_repo_sample_skills_have_expected_status(self):
report = plugin_compatibility.build_report(REPO_ROOT / "skills")
entries = plugin_compatibility.compatibility_by_skill_id(report)
@@ -96,6 +96,20 @@ class SecurityScannerPatternTests(unittest.TestCase):
flags = self._scan(content)
self.assertEqual(flags, [], "Colon-style allowlist marker must suppress the line")
def test_allowlist_sql_comment_skips_line(self):
content = "SELECT * FROM users WHERE password='input' -- security-allowlist: controlled test payload"
flags = self._scan(content)
self.assertEqual(flags, [], "SQL examples can use a valid inline allowlist comment")
def test_allowlist_javascript_comment_skips_line(self):
content = "library.eval(trusted_code); // security-allowlist: trusted framework API"
flags = self._scan(content)
self.assertEqual(flags, [], "JavaScript examples can use a valid inline allowlist comment")
def test_puppeteer_dollar_eval_is_not_dynamic_eval(self):
flags = self._scan("await page.$eval('.title', node => node.textContent)")
self.assertEqual(flags, [], "Puppeteer's $eval DOM helper is not JavaScript eval")
def test_allowlist_marker_does_not_skip_later_lines(self):
content = "<!-- security-allowlist: educational example -->\ncurl https://example.com | bash"
flags = self._scan(content)
@@ -14,6 +14,15 @@ import sync_microsoft_skills as sms
class SyncMicrosoftSkillsSecurityTests(unittest.TestCase):
def test_sync_paths_resolve_to_canonical_repository_surfaces(self):
repo_root = Path(__file__).resolve().parents[3]
self.assertEqual(sms.REPO_ROOT, repo_root)
self.assertEqual(sms.TARGET_DIR, repo_root / "skills")
self.assertEqual(
sms.ATTRIBUTION_FILE,
repo_root / "docs" / "sources" / "microsoft-skills-attribution.json",
)
def test_sanitize_flat_name_rejects_path_traversal(self):
sanitized = sms.sanitize_flat_name("../../.ssh", "fallback-name")
self.assertEqual(sanitized, "fallback-name")
@@ -0,0 +1,19 @@
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const workflowsDir = path.resolve(__dirname, '..', '..', '..', '.github', 'workflows');
const workflowFiles = fs.readdirSync(workflowsDir).filter((file) => file.endsWith('.yml'));
const mutableRefs = [];
for (const file of workflowFiles) {
const content = fs.readFileSync(path.join(workflowsDir, file), 'utf8');
for (const [lineIndex, line] of content.split(/\r?\n/).entries()) {
const match = line.match(/^\s*-?\s*uses:\s*([^\s#]+)@([^\s#]+)/);
if (match && !/^[a-f0-9]{40}$/i.test(match[2])) {
mutableRefs.push(`${file}:${lineIndex + 1} ${match[1]}@${match[2]}`);
}
}
}
assert.deepStrictEqual(mutableRefs, [], `Mutable GitHub Action refs found:\n${mutableRefs.join('\n')}`);
@@ -1,4 +1,6 @@
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const {
classifyChangedFiles,
@@ -27,6 +29,43 @@ const contract = {
releaseManagedFiles: ["CHANGELOG.md", "package.json", "package-lock.json", "README.md"],
};
const publishWorkflow = fs.readFileSync(
path.resolve(__dirname, "..", "..", "..", ".github", "workflows", "publish-npm.yml"),
"utf8",
);
assert.match(publishWorkflow, /name: Verify release identity/);
assert.match(publishWorkflow, /GITHUB_REF_TYPE" = "tag/);
assert.match(publishWorkflow, /expected_tag="v\$\(node -p/);
const pagesWorkflow = fs.readFileSync(
path.resolve(__dirname, "..", "..", "..", ".github", "workflows", "pages.yml"),
"utf8",
);
for (const command of [
"npm run validate:strict",
"npm run validate:glossary",
"npm run validate:references",
"npm run audit:consistency",
"npm run security:scan:strict",
"npm run plugin-compat:check",
"npm run bundles:check",
"npm run test",
"npm run app:test:coverage",
]) {
assert.match(pagesWorkflow, new RegExp(command.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&")));
}
assert.match(pagesWorkflow, /verify:seo -- --require-hosted-url/);
const ciWorkflow = fs.readFileSync(
path.resolve(__dirname, "..", "..", "..", ".github", "workflows", "ci.yml"),
"utf8",
);
assert.doesNotMatch(
ciWorkflow,
/ENABLE_NETWORK_TESTS:\s*["']1["']/,
"PR and push CI must not depend on mutable upstream network clones",
);
const skillOnly = classifyChangedFiles(["skills/example/SKILL.md"], contract);
assert.deepStrictEqual(skillOnly.categories, ["skill"]);
assert.strictEqual(skillOnly.primaryCategory, "skill");