feat: Migrate quick_check_skill.sh to JavaScript (validateSkillStructure)#631
Conversation
- New CommonJS module for validating skill directory structure - Checks for: SKILL.md with valid frontmatter, agents/openai.yaml presence - Validates name format (lowercase hyphen-case starting with letter) - Detects package noise (.DS_Store, __pycache__, node_modules, etc.) - Supports optional placeholder text detection (TODO, placeholder, Replace with) - 18 comprehensive Jest tests with full coverage - Replaces duplicate quick_check_skill.sh scripts All tests passing: Tests: 18 passed, 18 total https://claude.ai/code/session_01A1BeR5Rc2vNYaMQtgw6ERd
|
Warning Review limit reached
More reviews will be available in 36 minutes and 5 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Repository YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a new validation utility, validateSkillStructure.js, along with its corresponding test suite, to verify the directory structure, frontmatter, noise files, and placeholder text of a skill. The reviewer identified critical issues regarding security, portability, and performance due to the use of external shell commands (find and grep) via execSync. Additionally, they pointed out potential validation failures on Windows caused by CRLF line endings and provided a robust, pure Node.js alternative to resolve these concerns.
| const fs = require("fs"); | ||
| const path = require("path"); | ||
| const { execSync } = require("child_process"); | ||
|
|
||
| function validateSkillStructure(skillDir, options = {}) { | ||
| const { checkPlaceholders = true } = options; | ||
|
|
||
| if (!fs.existsSync(skillDir)) { | ||
| throw new Error("Skill directory not found"); | ||
| } | ||
|
|
||
| const skillMd = path.join(skillDir, "SKILL.md"); | ||
| if (!fs.existsSync(skillMd)) { | ||
| throw new Error("SKILL.md not found"); | ||
| } | ||
|
|
||
| const agentYaml = path.join(skillDir, "agents/openai.yaml"); | ||
| if (!fs.existsSync(agentYaml)) { | ||
| throw new Error("agents/openai.yaml not found"); | ||
| } | ||
|
|
||
| const skillContent = fs.readFileSync(skillMd, "utf8"); | ||
| const firstLine = skillContent.split("\n")[0]; | ||
| if (firstLine !== "---") { | ||
| throw new Error("SKILL.md must start with YAML frontmatter"); | ||
| } | ||
|
|
||
| const nameMatch = skillContent.match(/^name: [a-z][a-z0-9-]*$/m); | ||
| if (!nameMatch) { | ||
| throw new Error("frontmatter name must be lowercase hyphen-case"); | ||
| } | ||
|
|
||
| if (!skillContent.match(/^description: /m)) { | ||
| throw new Error("frontmatter description missing"); | ||
| } | ||
|
|
||
| const nameLineMatch = skillContent.match(/^name: (.+)$/m); | ||
| if (!nameLineMatch) { | ||
| throw new Error("Could not parse name from SKILL.md"); | ||
| } | ||
|
|
||
| const name = nameLineMatch[1].trim(); | ||
| const folderName = path.basename(skillDir); | ||
| if (name !== folderName) { | ||
| throw new Error("frontmatter name must match folder name"); | ||
| } | ||
|
|
||
| const noisePatterns = [ | ||
| "__MACOSX", | ||
| ".DS_Store", | ||
| "*.pyc", | ||
| "__pycache__", | ||
| "node_modules", | ||
| ]; | ||
|
|
||
| for (const pattern of noisePatterns) { | ||
| try { | ||
| const cmd = `find "${skillDir}" -name "${pattern}" 2>/dev/null | head -1`; | ||
| const result = execSync(cmd, { encoding: "utf8", stdio: "pipe" }).trim(); | ||
| if (result) { | ||
| throw new Error("package noise found"); | ||
| } | ||
| } catch (error) { | ||
| if (error.message === "package noise found") { | ||
| throw error; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const warnings = []; | ||
| if (checkPlaceholders) { | ||
| const excludePatterns = [ | ||
| "quick_check_skill.sh", | ||
| "validateSkillStructure.js", | ||
| ]; | ||
| try { | ||
| const excludeArgs = excludePatterns | ||
| .map((p) => `--exclude="${p}"`) | ||
| .join(" "); | ||
| const cmd = `grep -r "TODO\\|placeholder\\|Replace with" "${skillDir}" ${excludeArgs} 2>/dev/null || true`; | ||
| const hits = execSync(cmd, { | ||
| encoding: "utf8", | ||
| shell: "/bin/bash", | ||
| }).trim(); | ||
| if (hits) { | ||
| const lines = hits.split("\n").slice(0, 20); | ||
| warnings.push("possible placeholder text found:\n" + lines.join("\n")); | ||
| } | ||
| } catch { | ||
| // Ignore grep errors | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| valid: true, | ||
| name, | ||
| folderName, | ||
| warnings, | ||
| }; | ||
| } | ||
|
|
||
| module.exports = { validateSkillStructure }; |
There was a problem hiding this comment.
Security, Portability, and Robustness Improvements
- Security & Portability: Spawning external shell commands (
find,grep,/bin/bash) viaexecSyncintroduces potential command injection vulnerabilities ifskillDircontains shell metacharacters. Additionally, it makes the script non-portable, causing it to fail on Windows environments that lack these Unix utilities. - Performance: Spawning multiple shell processes is slow and resource-intensive compared to native Node.js file system operations.
- CRLF Line Endings: Reading
SKILL.mddirectly can cause validation failures on Windows because of CRLF (\r\n) line endings (e.g.,firstLine !== "---"or regex matching with$anchor).
This suggestion rewrites the module to use pure Node.js APIs for recursive directory traversal, noise checking, and placeholder detection, while also normalizing line endings to prevent Windows-specific failures.
const fs = require("fs");
const path = require("path");
function validateSkillStructure(skillDir, options = {}) {
const { checkPlaceholders = true } = options;
if (!fs.existsSync(skillDir)) {
throw new Error("Skill directory not found");
}
const skillMd = path.join(skillDir, "SKILL.md");
if (!fs.existsSync(skillMd)) {
throw new Error("SKILL.md not found");
}
const agentYaml = path.join(skillDir, "agents/openai.yaml");
if (!fs.existsSync(agentYaml)) {
throw new Error("agents/openai.yaml not found");
}
// Normalize line endings to avoid CRLF issues on Windows
const skillContent = fs.readFileSync(skillMd, "utf8").replace(/\r\n/g, "\n");
const firstLine = skillContent.split("\n")[0];
if (firstLine !== "---") {
throw new Error("SKILL.md must start with YAML frontmatter");
}
const nameMatch = skillContent.match(/^name: [a-z][a-z0-9-]*$/m);
if (!nameMatch) {
throw new Error("frontmatter name must be lowercase hyphen-case");
}
if (!skillContent.match(/^description: /m)) {
throw new Error("frontmatter description missing");
}
const nameLineMatch = skillContent.match(/^name: (.+)$/m);
if (!nameLineMatch) {
throw new Error("Could not parse name from SKILL.md");
}
const name = nameLineMatch[1].trim();
const folderName = path.basename(skillDir);
if (name !== folderName) {
throw new Error("frontmatter name must match folder name");
}
const warnings = [];
const excludePatterns = [
"quick_check_skill.sh",
"validateSkillStructure.js",
];
// Recursive directory traversal for noise and placeholder checks
function traverse(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
const entryName = entry.name;
// Check noise patterns
if (
entryName === "__MACOSX" ||
entryName === ".DS_Store" ||
entryName === "__pycache__" ||
entryName === "node_modules" ||
entryName.endsWith(".pyc")
) {
throw new Error("package noise found");
}
if (entry.isDirectory()) {
traverse(fullPath);
} else if (entry.isFile()) {
if (excludePatterns.includes(entryName)) {
continue;
}
if (checkPlaceholders) {
try {
const content = fs.readFileSync(fullPath, "utf8");
const lines = content.split(/\r?\n/);
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (
line.includes("TODO") ||
line.includes("placeholder") ||
line.includes("Replace with")
) {
warnings.push(fullPath + ":" + line.trim());
}
}
} catch {
// Ignore binary files or read errors
}
}
}
}
}
traverse(skillDir);
const formattedWarnings = [];
if (warnings.length > 0) {
const lines = warnings.slice(0, 20);
formattedWarnings.push("possible placeholder text found:\n" + lines.join("\n"));
}
return {
valid: true,
name,
folderName,
warnings: formattedWarnings,
};
}
module.exports = { validateSkillStructure };- Remove execSync calls to find and grep for better security and portability - Implement pure Node.js directory traversal instead of find command - Add CRLF line ending normalization for Windows compatibility - Manual placeholder detection replaces grep-based scanning - Gracefully handle binary files during placeholder detection Addresses security, portability, and Windows compatibility concerns: - No command injection vulnerabilities - Works on Windows without Unix utilities - Better performance with native file system APIs - Handles CRLF line endings properly https://claude.ai/code/session_01A1BeR5Rc2vNYaMQtgw6ERd
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Summary
Migrated
quick_check_skill.shto a reusable JavaScript modulevalidateSkillStructure.jswith comprehensive Jest test coverage. This is part of the broader initiative to eliminate all shell scripts.Changes
New Module:
scripts/skill-utils/validateSkillStructure.jsTest Suite:
scripts/skill-utils/__tests__/validateSkillStructure.test.jsFeatures Migrated
Test Results
Replaces
This module replaces two identical
quick_check_skill.shscripts:skills/design-md-agent/figma-wordpress-skill-creator/scripts/quick_check_skill.shskills/design-md-agent/wordpress-plugin-packaging-review/scripts/quick_check_skill.shRelated Issues
Closes #613 - Part of #616 (Epic: Migrate All Bash Scripts to JavaScript)
Test Plan
Generated by Claude Code