mirror of
https://github.com/alirezarezvani/claude-skills.git
synced 2026-10-07 02:58:13 +00:00
Same root cause as #587/#591 — Claude Code's runtime loader rejects array-form skills paths like ["./content-production", "./ai-seo", ...] even when each entry is a valid subdirectory containing SKILL.md. `claude plugin validate` accepts them but the loader does not. The proven canonical layout (used by self-improving-agent in #536): <plugin>/ ├── .claude-plugin/plugin.json skills: "./skills" └── skills/ ├── <skill-1>/SKILL.md ├── <skill-2>/SKILL.md └── ... Restructured 9 multi-skill domain plugins: - business-growth (4 skills moved) - c-level-advisor (28) - engineering (36) - engineering-team (32) - finance (2) - marketing-skill (43) - product-team (12) - project-management (8) - ra-qm-team (13) Also fixed standalone plugins that had root SKILL.md + ./skills/ subdir (agenthub, autoresearch-agent, executive-mentor, playwright-pro). The loader rejected them despite skills="./skills" because of the conflicting root SKILL.md (compare self-improving-agent which works because PR #536 moved its root SKILL.md). Moved each root SKILL.md into ./skills/<name>/. Restored standalone plugin folders to their original paths after the multi-skill restructure swept them into parent skills/ directories (marketplace.json source paths require original locations). Removed 7 orphaned marketplace entries that pointed to skill folders without their own plugin.json (content-creator, demand-gen, fullstack-engineer, aws-architect, product-manager, scrum-master, skill-security-auditor) — these were already non-functional. Bumped patch versions on every changed plugin and synced marketplace.json. Marketplace now lists 29 working plugins (down from 36). After merge: users run `/plugin marketplace update claude-code-skills` followed by `/plugin update --all` to pick up the working layout.
165 lines
5.2 KiB
JavaScript
165 lines
5.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* 2.5D Layer Validator
|
|
* Usage: node scripts/validate-layers.js path/to/your/index.html
|
|
*
|
|
* Checks:
|
|
* 1. Every animated element has a data-depth attribute
|
|
* 2. Decorative elements have aria-hidden="true"
|
|
* 3. prefers-reduced-motion is implemented in CSS
|
|
* 4. Product images have alt text
|
|
* 5. SplitText elements have aria-label
|
|
* 6. No more than 80 animated elements (performance)
|
|
* 7. Will-change is not applied globally
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const filePath = process.argv[2];
|
|
|
|
if (!filePath) {
|
|
console.error('\n❌ Usage: node validate-layers.js path/to/index.html\n');
|
|
process.exit(1);
|
|
}
|
|
|
|
const html = fs.readFileSync(path.resolve(filePath), 'utf8');
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
const results = [];
|
|
|
|
function check(label, condition, suggestion) {
|
|
if (condition) {
|
|
passed++;
|
|
results.push({ status: '✅', label });
|
|
} else {
|
|
failed++;
|
|
results.push({ status: '❌', label, suggestion });
|
|
}
|
|
}
|
|
|
|
function warn(label, condition, suggestion) {
|
|
if (!condition) {
|
|
results.push({ status: '⚠️ ', label, suggestion });
|
|
}
|
|
}
|
|
|
|
// --- CHECKS ---
|
|
|
|
// 1. Scene elements present
|
|
check(
|
|
'Scene elements found (.scene)',
|
|
html.includes('class="scene') || html.includes("class='scene"),
|
|
'Wrap each major section in <section class="scene"> for the depth system to work.'
|
|
);
|
|
|
|
// 2. Depth layers present
|
|
const depthMatches = html.match(/data-depth=["']\d["']/g) || [];
|
|
check(
|
|
`Depth attributes found (${depthMatches.length} elements)`,
|
|
depthMatches.length >= 3,
|
|
'Each scene needs at least 3 elements with data-depth="0" through data-depth="5".'
|
|
);
|
|
|
|
// 3. prefers-reduced-motion in linked CSS
|
|
const hasReducedMotionInline = html.includes('prefers-reduced-motion');
|
|
check(
|
|
'prefers-reduced-motion implemented',
|
|
hasReducedMotionInline || html.includes('hero-section.css'),
|
|
'Add @media (prefers-reduced-motion: reduce) { } block. See references/accessibility.md.'
|
|
);
|
|
|
|
// 4. Decorative elements have aria-hidden
|
|
const decorativeElements = (html.match(/class="[^"]*(?:depth-0|depth-1|depth-5|glow-blob|particle|deco)[^"]*"/g) || []).length;
|
|
const ariaHiddenCount = (html.match(/aria-hidden="true"/g) || []).length;
|
|
check(
|
|
`Decorative elements have aria-hidden (found ${ariaHiddenCount})`,
|
|
ariaHiddenCount >= 1,
|
|
'Add aria-hidden="true" to all decorative layers (depth-0, depth-1, particles, glows).'
|
|
);
|
|
|
|
// 5. Images have alt text
|
|
const imgTags = html.match(/<img[^>]*>/g) || [];
|
|
const imgsWithoutAlt = imgTags.filter(tag => !tag.includes('alt=')).length;
|
|
check(
|
|
`All images have alt attributes (${imgTags.length} images found)`,
|
|
imgsWithoutAlt === 0,
|
|
`${imgsWithoutAlt} image(s) missing alt attribute. Decorative images use alt="", meaningful images need descriptive alt text.`
|
|
);
|
|
|
|
// 6. Skip link present
|
|
check(
|
|
'Skip-to-content link present',
|
|
html.includes('skip-link') || html.includes('Skip to'),
|
|
'Add <a href="#main-content" class="skip-link">Skip to main content</a> as first element in <body>.'
|
|
);
|
|
|
|
// 7. GSAP script loaded
|
|
check(
|
|
'GSAP script included',
|
|
html.includes('gsap') || html.includes('gsap.min.js'),
|
|
'Include GSAP from CDN: <script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>'
|
|
);
|
|
|
|
// 8. ScrollTrigger plugin loaded
|
|
warn(
|
|
'ScrollTrigger plugin loaded',
|
|
html.includes('ScrollTrigger'),
|
|
'Add ScrollTrigger plugin for scroll animations: <script src=".../ScrollTrigger.min.js"></script>'
|
|
);
|
|
|
|
// 9. Performance: too many animated elements
|
|
const animatedElements = (html.match(/data-animate=/g) || []).length + depthMatches.length;
|
|
check(
|
|
`Animated element count acceptable (${animatedElements} total)`,
|
|
animatedElements <= 80,
|
|
`${animatedElements} animated elements found. Target is under 80 for smooth 60fps performance.`
|
|
);
|
|
|
|
// 10. Main landmark present
|
|
check(
|
|
'<main> landmark present',
|
|
html.includes('<main'),
|
|
'Wrap page content in <main id="main-content"> for accessibility and skip link target.'
|
|
);
|
|
|
|
// 11. Heading hierarchy
|
|
const h1Count = (html.match(/<h1[\s>]/g) || []).length;
|
|
check(
|
|
`Single <h1> present (found ${h1Count})`,
|
|
h1Count === 1,
|
|
h1Count === 0
|
|
? 'Add one <h1> element as the main page heading.'
|
|
: `Multiple <h1> elements found (${h1Count}). Each page should have exactly one <h1>.`
|
|
);
|
|
|
|
// 12. lang attribute on html
|
|
check(
|
|
'<html lang=""> attribute present',
|
|
html.includes('lang='),
|
|
'Add lang="en" (or your language) to the <html> element: <html lang="en">'
|
|
);
|
|
|
|
// --- REPORT ---
|
|
|
|
console.log('\n📋 2.5D Layer Validator Report');
|
|
console.log('═══════════════════════════════════════');
|
|
console.log(`File: ${filePath}\n`);
|
|
|
|
results.forEach(r => {
|
|
console.log(`${r.status} ${r.label}`);
|
|
if (r.suggestion) {
|
|
console.log(` → ${r.suggestion}`);
|
|
}
|
|
});
|
|
|
|
console.log('\n═══════════════════════════════════════');
|
|
console.log(`Passed: ${passed} | Failed: ${failed}`);
|
|
|
|
if (failed === 0) {
|
|
console.log('\n🎉 All checks passed! Your 2.5D site is ready.\n');
|
|
} else {
|
|
console.log(`\n🔧 Fix the ${failed} issue(s) above before shipping.\n`);
|
|
process.exit(1);
|
|
}
|