From 91c193ac059b487d13ce535c7015b021eb71c841 Mon Sep 17 00:00:00 2001 From: notbucki <103531753+notbucki@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:14:02 +0200 Subject: [PATCH] fix(skills): skill-relative paths below SKILL.md, widen path contract (#482) Refs #474 (finding 1 follow-up to #476, which rewrote design/SKILL.md and added a contract that grepped only */SKILL.md; the same defect one level down survived). - 29 home-rooted paths (~/.claude/skills/design/scripts/...) in design/references/{cip,icon,logo}-design.md -> scripts/... (27 at review time, two more added by #470); the printed hint in design/scripts/cip/generate.py now derives the absolute path from __file__ - 19 project-rooted invocations (.claude/skills//scripts/...) in brand/, slides/ and design/ references -> scripts/... (own skill) or ..//scripts/... (sibling sub-skill; sub-skills are installed side by side in every layout) - brand/scripts/sync-brand-to-tokens.cjs resolved its sibling script from process.cwd(), silently skipping CSS regeneration under plugin and --global installs; now resolved from __dirname, with a warning when the sibling skill is missing; regression test asserts the regeneration - brand/scripts/extract-colors.cjs: tool-neutral hint instead of a project-rooted path into a skill this plugin does not ship - "Script Paths" section in the five sub-skills that invoke scripts: script path from the skill directory, working directory at the project root - new test_skill_script_paths.py (src, mirrored to both scripts/tests copies): every python/node/bash invocation in every shipped skill markdown must be skill-relative and name a file that ships - check-asset-sync.yml: contract covers every file under both skill trees and home-/project-/variable-rooted forms; ${CLAUDE_PLUGIN_ROOT} allowed only in the plugin-only core SKILL.md; LC_ALL=C + -I for the tracked .coverage binary; grep errors fail instead of passing; push filter includes the workflow and sync-assets.mjs - CLI copy regenerated via sync-assets.mjs Co-authored-by: notbucki Co-authored-by: Claude Fable 5.1 --- .claude/skills/brand/SKILL.md | 4 + .../brand/references/approval-checklist.md | 2 +- .claude/skills/brand/references/update.md | 4 +- .../skills/brand/scripts/extract-colors.cjs | 6 +- .../brand/scripts/sync-brand-to-tokens.cjs | 9 +- .../tests/test_sync_brand_to_tokens.py | 8 ++ .claude/skills/design-system/SKILL.md | 4 + .claude/skills/design/SKILL.md | 4 + .../skills/design/references/cip-design.md | 24 +++--- .../design/references/design-routing.md | 8 +- .../skills/design/references/icon-design.md | 16 ++-- .../skills/design/references/logo-design.md | 18 ++-- .../references/slides-copywriting-formulas.md | 4 +- .../references/slides-layout-patterns.md | 4 +- .../design/references/slides-strategies.md | 4 +- .claude/skills/design/scripts/cip/generate.py | 5 +- .claude/skills/slides/SKILL.md | 4 + .../slides/references/copywriting-formulas.md | 4 +- .../slides/references/layout-patterns.md | 4 +- .../slides/references/slide-strategies.md | 4 +- .claude/skills/ui-styling/SKILL.md | 4 + .../scripts/tests/test_skill_script_paths.py | 82 +++++++++++++++++++ .github/workflows/check-asset-sync.yml | 65 ++++++++++++--- .../scripts/tests/test_skill_script_paths.py | 82 +++++++++++++++++++ cli/assets/skills/brand/SKILL.md | 4 + .../brand/references/approval-checklist.md | 2 +- cli/assets/skills/brand/references/update.md | 4 +- .../skills/brand/scripts/extract-colors.cjs | 6 +- .../brand/scripts/sync-brand-to-tokens.cjs | 9 +- .../tests/test_sync_brand_to_tokens.py | 8 ++ cli/assets/skills/design-system/SKILL.md | 4 + cli/assets/skills/design/SKILL.md | 4 + .../skills/design/references/cip-design.md | 24 +++--- .../design/references/design-routing.md | 8 +- .../skills/design/references/icon-design.md | 16 ++-- .../skills/design/references/logo-design.md | 18 ++-- .../references/slides-copywriting-formulas.md | 4 +- .../references/slides-layout-patterns.md | 4 +- .../design/references/slides-strategies.md | 4 +- .../skills/design/scripts/cip/generate.py | 5 +- cli/assets/skills/slides/SKILL.md | 4 + .../slides/references/copywriting-formulas.md | 4 +- .../slides/references/layout-patterns.md | 4 +- .../slides/references/slide-strategies.md | 4 +- cli/assets/skills/ui-styling/SKILL.md | 4 + .../scripts/tests/test_skill_script_paths.py | 82 +++++++++++++++++++ 46 files changed, 477 insertions(+), 122 deletions(-) create mode 100644 .claude/skills/ui-ux-pro-max/scripts/tests/test_skill_script_paths.py create mode 100644 cli/assets/scripts/tests/test_skill_script_paths.py create mode 100644 src/ui-ux-pro-max/scripts/tests/test_skill_script_paths.py diff --git a/.claude/skills/brand/SKILL.md b/.claude/skills/brand/SKILL.md index 336e8ef..48d912f 100644 --- a/.claude/skills/brand/SKILL.md +++ b/.claude/skills/brand/SKILL.md @@ -20,6 +20,10 @@ Brand identity, voice, messaging, asset management, and consistency frameworks. - Asset organization, naming, and approval - Color palette management and typography specs +## Script Paths + +Script paths in this skill and its `references/` are relative to the directory that contains this SKILL.md, not to the project: `scripts/` is this skill's own `scripts/` folder, and `..//scripts/` is a sibling sub-skill installed alongside it. Build the full path from that directory (Claude Code reports it as the skill's base directory when the skill loads) and keep the working directory at the project root — the scripts read and write project files such as `docs/brand-guidelines.md`, `assets/design-tokens.json` or `src/` relative to it. + ## Quick Start **Inject brand context into prompts:** diff --git a/.claude/skills/brand/references/approval-checklist.md b/.claude/skills/brand/references/approval-checklist.md index ff05bac..0ce4bb6 100644 --- a/.claude/skills/brand/references/approval-checklist.md +++ b/.claude/skills/brand/references/approval-checklist.md @@ -157,7 +157,7 @@ The `validate-asset.cjs` script can auto-check: - Naming convention - Basic metadata -Run: `node .claude/skills/brand/scripts/validate-asset.cjs ` +Run: `node scripts/validate-asset.cjs ` ## Archival diff --git a/.claude/skills/brand/references/update.md b/.claude/skills/brand/references/update.md index 4a92438..25ed7f3 100644 --- a/.claude/skills/brand/references/update.md +++ b/.claude/skills/brand/references/update.md @@ -46,7 +46,7 @@ Edit `docs/brand-guidelines.md`: Run the sync script: ```bash -node .claude/skills/brand/scripts/sync-brand-to-tokens.cjs +node scripts/sync-brand-to-tokens.cjs ``` This will: @@ -58,7 +58,7 @@ This will: Confirm all files are updated: ```bash # Check brand context extraction -node .claude/skills/brand/scripts/inject-brand-context.cjs --json | head -30 +node scripts/inject-brand-context.cjs --json | head -30 # Check CSS variables grep "primary" assets/design-tokens.css | head -5 diff --git a/.claude/skills/brand/scripts/extract-colors.cjs b/.claude/skills/brand/scripts/extract-colors.cjs index a2ec2b4..73aa6d4 100755 --- a/.claude/skills/brand/scripts/extract-colors.cjs +++ b/.claude/skills/brand/scripts/extract-colors.cjs @@ -287,11 +287,7 @@ function main() { "1. Run the ImageMagick command to extract colors:", ` ${generateImageMagickCommand(resolvedPath)}`, "", - "2. Or use the ai-multimodal skill:", - ` python .claude/skills/ai-multimodal/scripts/gemini_batch_process.py \\`, - ` --files "${resolvedPath}" \\`, - ` --task analyze \\`, - ` --prompt "Extract the 10 most dominant colors as hex values"`, + "2. Or use an image-analysis skill (e.g. ai-multimodal, if installed) to extract the 10 most dominant colors as hex values", "", "3. Then compare extracted colors against brand palette", ], diff --git a/.claude/skills/brand/scripts/sync-brand-to-tokens.cjs b/.claude/skills/brand/scripts/sync-brand-to-tokens.cjs index 013fa6f..1e3b8ce 100644 --- a/.claude/skills/brand/scripts/sync-brand-to-tokens.cjs +++ b/.claude/skills/brand/scripts/sync-brand-to-tokens.cjs @@ -17,7 +17,10 @@ const { execFileSync } = require('child_process'); const BRAND_GUIDELINES = 'docs/brand-guidelines.md'; const DESIGN_TOKENS_JSON = 'assets/design-tokens.json'; const DESIGN_TOKENS_CSS = 'assets/design-tokens.css'; -const GENERATE_TOKENS_SCRIPT = '.claude/skills/design-system/scripts/generate-tokens.cjs'; +// Sibling sub-skill, resolved from this file's location so it works in every +// install context (plugin cache, project or --global CLI install), not only +// when the process runs from a project root that contains .claude/skills/. +const GENERATE_TOKENS_SCRIPT = path.resolve(__dirname, '..', '..', 'design-system', 'scripts', 'generate-tokens.cjs'); /** * Extract color info from brand guidelines markdown @@ -229,7 +232,7 @@ function main() { console.log(`✅ Updated: ${DESIGN_TOKENS_JSON}`); // Regenerate CSS - const generateScript = path.resolve(process.cwd(), GENERATE_TOKENS_SCRIPT); + const generateScript = GENERATE_TOKENS_SCRIPT; if (fs.existsSync(generateScript)) { try { execFileSync('node', [generateScript, '--config', DESIGN_TOKENS_JSON, '-o', DESIGN_TOKENS_CSS], { @@ -240,6 +243,8 @@ function main() { } catch (e) { console.error('⚠️ Failed to regenerate CSS:', e.message); } + } else { + console.warn(`⚠️ design-system sub-skill not found at ${generateScript}; ${DESIGN_TOKENS_CSS} not regenerated`); } console.log('\n✨ Brand sync complete!'); diff --git a/.claude/skills/brand/scripts/tests/test_sync_brand_to_tokens.py b/.claude/skills/brand/scripts/tests/test_sync_brand_to_tokens.py index 5e6b1e4..53c5437 100644 --- a/.claude/skills/brand/scripts/tests/test_sync_brand_to_tokens.py +++ b/.claude/skills/brand/scripts/tests/test_sync_brand_to_tokens.py @@ -62,6 +62,14 @@ def test_sync_parses_bundled_starter_template(tmp_path): assert primitive["secondary"]["500"]["$value"] == "#8B5CF6" assert primitive["accent"]["500"]["$value"] == "#10B981" + # #474: the sibling design-system script is resolved from this skill's own + # location, so the CSS regeneration must run even though tmp_path has no + # .claude/skills/ tree. Before the fix it was resolved from the working + # directory and silently skipped in every layout but a project install. + assert "Regenerated" in result.stdout, result.stdout + css = tmp_path / "assets" / "design-tokens.css" + assert css.exists() and css.stat().st_size > 0 + def test_reports_missing_guidelines_without_breaking_the_harness(tmp_path): """The missing-guidelines path is the one that breaks a locale-decoded pipe. diff --git a/.claude/skills/design-system/SKILL.md b/.claude/skills/design-system/SKILL.md index 4397c7f..804e553 100644 --- a/.claude/skills/design-system/SKILL.md +++ b/.claude/skills/design-system/SKILL.md @@ -48,6 +48,10 @@ Component (component-specific) --button-bg: var(--color-primary); ``` +## Script Paths + +Script paths in this skill and its `references/` are relative to the directory that contains this SKILL.md, not to the project: `scripts/` is this skill's own `scripts/` folder, and `..//scripts/` is a sibling sub-skill installed alongside it. Build the full path from that directory (Claude Code reports it as the skill's base directory when the skill loads) and keep the working directory at the project root — the scripts read and write project files such as `docs/brand-guidelines.md`, `assets/design-tokens.json` or `src/` relative to it. + ## Quick Start **Generate tokens:** diff --git a/.claude/skills/design/SKILL.md b/.claude/skills/design/SKILL.md index 4200f14..16256ad 100644 --- a/.claude/skills/design/SKILL.md +++ b/.claude/skills/design/SKILL.md @@ -37,6 +37,10 @@ Unified design skill: brand, tokens, UI, logo, CIP, slides, banners, social phot | Social media images/photos | Social Photos (built-in) | `references/social-photos-design.md` | | SVG icons, icon sets | Icon (built-in) | `references/icon-design.md` | +## Script Paths + +Script paths in this skill and its `references/` are relative to the directory that contains this SKILL.md, not to the project: `scripts/` is this skill's own `scripts/` folder, and `..//scripts/` is a sibling sub-skill installed alongside it. Build the full path from that directory (Claude Code reports it as the skill's base directory when the skill loads) and keep the working directory at the project root — the scripts read and write project files such as `docs/brand-guidelines.md`, `assets/design-tokens.json` or `src/` relative to it. + ## Logo Design (Built-in) 55+ styles, 30 color palettes, 25 industry guides. Gemini Nano Banana, Atlas diff --git a/.claude/skills/design/references/cip-design.md b/.claude/skills/design/references/cip-design.md index 81829ed..4037769 100644 --- a/.claude/skills/design/references/cip-design.md +++ b/.claude/skills/design/references/cip-design.md @@ -16,49 +16,49 @@ Corporate Identity Program design with 50+ deliverables, 20 styles, 20 industrie ### CIP Brief (Start Here) ```bash -python3 ~/.claude/skills/design/scripts/cip/search.py "tech startup" --cip-brief -b "BrandName" +python3 scripts/cip/search.py "tech startup" --cip-brief -b "BrandName" ``` ### Search Domains ```bash # Deliverables -python3 ~/.claude/skills/design/scripts/cip/search.py "business card letterhead" --domain deliverable +python3 scripts/cip/search.py "business card letterhead" --domain deliverable # Design styles -python3 ~/.claude/skills/design/scripts/cip/search.py "luxury premium elegant" --domain style +python3 scripts/cip/search.py "luxury premium elegant" --domain style # Industry guidelines -python3 ~/.claude/skills/design/scripts/cip/search.py "hospitality hotel" --domain industry +python3 scripts/cip/search.py "hospitality hotel" --domain industry # Mockup contexts -python3 ~/.claude/skills/design/scripts/cip/search.py "office reception" --domain mockup +python3 scripts/cip/search.py "office reception" --domain mockup ``` ### Generate Mockups ```bash # With logo (RECOMMENDED - uses image editing) -python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TopGroup" --logo /path/to/logo.png --deliverable "business card" --industry "consulting" +python3 scripts/cip/generate.py --brand "TopGroup" --logo /path/to/logo.png --deliverable "business card" --industry "consulting" # Full CIP set with logo -python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TopGroup" --logo /path/to/logo.png --industry "consulting" --set +python3 scripts/cip/generate.py --brand "TopGroup" --logo /path/to/logo.png --industry "consulting" --set # Pro model for 4K text rendering -python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TopGroup" --logo logo.png --deliverable "business card" --model pro +python3 scripts/cip/generate.py --brand "TopGroup" --logo logo.png --deliverable "business card" --model pro # Custom deliverables with aspect ratio -python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "GreenLeaf" --logo logo.png --industry "organic food" --deliverables "letterhead,packaging,vehicle" --ratio 16:9 +python3 scripts/cip/generate.py --brand "GreenLeaf" --logo logo.png --industry "organic food" --deliverables "letterhead,packaging,vehicle" --ratio 16:9 # Without logo (AI generates interpretation) -python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TechFlow" --deliverable "business card" --no-logo-prompt +python3 scripts/cip/generate.py --brand "TechFlow" --deliverable "business card" --no-logo-prompt ``` ### Render HTML Presentation ```bash -python3 ~/.claude/skills/design/scripts/cip/render-html.py --brand "TopGroup" --industry "consulting" --images /path/to/cip-output -python3 ~/.claude/skills/design/scripts/cip/render-html.py --brand "TopGroup" --industry "consulting" --images ./topgroup-cip --output presentation.html +python3 scripts/cip/render-html.py --brand "TopGroup" --industry "consulting" --images /path/to/cip-output +python3 scripts/cip/render-html.py --brand "TopGroup" --industry "consulting" --images ./topgroup-cip --output presentation.html ``` ## Models diff --git a/.claude/skills/design/references/design-routing.md b/.claude/skills/design/references/design-routing.md index 78f5587..4d745bd 100644 --- a/.claude/skills/design/references/design-routing.md +++ b/.claude/skills/design/references/design-routing.md @@ -164,14 +164,14 @@ Application Code **Brand:** ```bash -node .claude/skills/brand/scripts/inject-brand-context.cjs -node .claude/skills/brand/scripts/validate-asset.cjs +node ../brand/scripts/inject-brand-context.cjs +node ../brand/scripts/validate-asset.cjs ``` **Tokens:** ```bash -node .claude/skills/design-system/scripts/generate-tokens.cjs -c tokens.json -node .claude/skills/design-system/scripts/validate-tokens.cjs -d src/ +node ../design-system/scripts/generate-tokens.cjs -c tokens.json +node ../design-system/scripts/validate-tokens.cjs -d src/ ``` **Components:** diff --git a/.claude/skills/design/references/icon-design.md b/.claude/skills/design/references/icon-design.md index db6db01..961cdac 100644 --- a/.claude/skills/design/references/icon-design.md +++ b/.claude/skills/design/references/icon-design.md @@ -13,29 +13,29 @@ AI-powered SVG icon generation using Gemini 3.1 Pro Preview. 15 styles, 12 categ ### Generate Single Icon ```bash -python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "settings gear" --style outlined -python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "shopping cart" --style filled --color "#6366F1" -python3 ~/.claude/skills/design/scripts/icon/generate.py --name "dashboard" --category navigation --style duotone +python3 scripts/icon/generate.py --prompt "settings gear" --style outlined +python3 scripts/icon/generate.py --prompt "shopping cart" --style filled --color "#6366F1" +python3 scripts/icon/generate.py --name "dashboard" --category navigation --style duotone ``` ### Generate Batch Variations ```bash -python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "cloud upload" --batch 4 --output-dir ./icons -python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "notification bell" --batch 6 --style outlined --output-dir ./icons +python3 scripts/icon/generate.py --prompt "cloud upload" --batch 4 --output-dir ./icons +python3 scripts/icon/generate.py --prompt "notification bell" --batch 6 --style outlined --output-dir ./icons ``` ### Generate Multiple Sizes ```bash -python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "user profile" --sizes "16,24,32,48" --output-dir ./icons +python3 scripts/icon/generate.py --prompt "user profile" --sizes "16,24,32,48" --output-dir ./icons ``` ### List Styles/Categories ```bash -python3 ~/.claude/skills/design/scripts/icon/generate.py --list-styles -python3 ~/.claude/skills/design/scripts/icon/generate.py --list-categories +python3 scripts/icon/generate.py --list-styles +python3 scripts/icon/generate.py --list-categories ``` ## CLI Options diff --git a/.claude/skills/design/references/logo-design.md b/.claude/skills/design/references/logo-design.md index a240e96..258ae77 100644 --- a/.claude/skills/design/references/logo-design.md +++ b/.claude/skills/design/references/logo-design.md @@ -15,20 +15,20 @@ AI-powered logo design with 55+ styles, 30 color palettes, 25 industry guides. G ### Design Brief (Start Here) ```bash -python3 ~/.claude/skills/design/scripts/logo/search.py "tech startup modern" --design-brief -p "BrandName" +python3 scripts/logo/search.py "tech startup modern" --design-brief -p "BrandName" ``` ### Search Domains ```bash # Styles -python3 ~/.claude/skills/design/scripts/logo/search.py "minimalist clean" --domain style +python3 scripts/logo/search.py "minimalist clean" --domain style # Color palettes -python3 ~/.claude/skills/design/scripts/logo/search.py "tech professional" --domain color +python3 scripts/logo/search.py "tech professional" --domain color # Industry guidelines -python3 ~/.claude/skills/design/scripts/logo/search.py "healthcare medical" --domain industry +python3 scripts/logo/search.py "healthcare medical" --domain industry ``` ### Generate Logo @@ -36,11 +36,11 @@ python3 ~/.claude/skills/design/scripts/logo/search.py "healthcare medical" --do **ALWAYS** use white background for output logos. ```bash -python3 ~/.claude/skills/design/scripts/logo/generate.py --brand "TechFlow" --style minimalist --industry tech -python3 ~/.claude/skills/design/scripts/logo/generate.py --prompt "coffee shop vintage badge" --style vintage -python3 ~/.claude/skills/design/scripts/logo/generate.py --brand "TechFlow" --provider atlas -python3 ~/.claude/skills/design/scripts/logo/generate.py --brand "TechFlow" --provider muapi -python3 ~/.claude/skills/design/scripts/logo/generate.py --brand "TechFlow" --provider muapi --muapi-model nano-banana-pro +python3 scripts/logo/generate.py --brand "TechFlow" --style minimalist --industry tech +python3 scripts/logo/generate.py --prompt "coffee shop vintage badge" --style vintage +python3 scripts/logo/generate.py --brand "TechFlow" --provider atlas +python3 scripts/logo/generate.py --brand "TechFlow" --provider muapi +python3 scripts/logo/generate.py --brand "TechFlow" --provider muapi --muapi-model nano-banana-pro ``` Options: `--style`, `--industry`, `--prompt`, `--provider`, `--atlas-model`, `--muapi-model` diff --git a/.claude/skills/design/references/slides-copywriting-formulas.md b/.claude/skills/design/references/slides-copywriting-formulas.md index ecf2875..87352fc 100644 --- a/.claude/skills/design/references/slides-copywriting-formulas.md +++ b/.claude/skills/design/references/slides-copywriting-formulas.md @@ -66,10 +66,10 @@ ```bash # Find formula for slide type -python .claude/skills/design-system/scripts/search-slides.py "problem agitation" -d copy +python ../design-system/scripts/search-slides.py "problem agitation" -d copy # Get emotion-appropriate formula -python .claude/skills/design-system/scripts/search-slides.py "urgency cta" -d copy +python ../design-system/scripts/search-slides.py "urgency cta" -d copy ``` ## Quick Reference diff --git a/.claude/skills/design/references/slides-layout-patterns.md b/.claude/skills/design/references/slides-layout-patterns.md index e2b3849..0949ab0 100644 --- a/.claude/skills/design/references/slides-layout-patterns.md +++ b/.claude/skills/design/references/slides-layout-patterns.md @@ -113,10 +113,10 @@ ```bash # Find layout for specific use -python .claude/skills/design-system/scripts/search-slides.py "metrics dashboard" -d layout +python ../design-system/scripts/search-slides.py "metrics dashboard" -d layout # Contextual recommendation -python .claude/skills/design-system/scripts/search-slides.py "traction slide" \ +python ../design-system/scripts/search-slides.py "traction slide" \ --context --position 4 --total 10 ``` diff --git a/.claude/skills/design/references/slides-strategies.md b/.claude/skills/design/references/slides-strategies.md index e004fe1..eae5b13 100644 --- a/.claude/skills/design/references/slides-strategies.md +++ b/.claude/skills/design/references/slides-strategies.md @@ -76,10 +76,10 @@ Pattern breaks at 1/3 and 2/3 positions create engagement peaks. ```bash # Find strategy by goal -python .claude/skills/design-system/scripts/search-slides.py "investor pitch" -d strategy +python ../design-system/scripts/search-slides.py "investor pitch" -d strategy # Get emotion arc -python .claude/skills/design-system/scripts/search-slides.py "series a funding" -d strategy --json +python ../design-system/scripts/search-slides.py "series a funding" -d strategy --json ``` ## Matching Strategy to Context diff --git a/.claude/skills/design/scripts/cip/generate.py b/.claude/skills/design/scripts/cip/generate.py index 0be632f..e92b2e1 100644 --- a/.claude/skills/design/scripts/cip/generate.py +++ b/.claude/skills/design/scripts/cip/generate.py @@ -427,7 +427,10 @@ Image Editing Mode: action = check_logo_required(args.brand, skip_prompt=args.no_logo_prompt) if action == 'generate': print("\n💡 To generate a logo, use the logo-design skill:") - print(f" python ~/.claude/skills/design/scripts/logo/generate.py --brand \"{args.brand}\" --industry \"{args.industry}\"") + # Resolved from this file so the hint is correct from any cwd and in + # every install layout (plugin cache, project or --global install). + logo_script = Path(__file__).resolve().parents[1] / "logo" / "generate.py" + print(f" python \"{logo_script}\" --brand \"{args.brand}\" --industry \"{args.industry}\"") print("\n Then re-run this command with --logo ") sys.exit(0) elif action == 'exit': diff --git a/.claude/skills/slides/SKILL.md b/.claude/skills/slides/SKILL.md index 38750ff..52d345d 100644 --- a/.claude/skills/slides/SKILL.md +++ b/.claude/skills/slides/SKILL.md @@ -24,6 +24,10 @@ Strategic HTML presentation design with data visualization. |------------|-------------|-----------| | `create` | Create strategic presentation slides | `references/create.md` | +## Script Paths + +Script paths in this skill and its `references/` are relative to the directory that contains this SKILL.md, not to the project: `scripts/` is this skill's own `scripts/` folder, and `..//scripts/` is a sibling sub-skill installed alongside it. Build the full path from that directory (Claude Code reports it as the skill's base directory when the skill loads) and keep the working directory at the project root — the scripts read and write project files such as `docs/brand-guidelines.md`, `assets/design-tokens.json` or `src/` relative to it. + ## References (Knowledge Base) | Topic | File | diff --git a/.claude/skills/slides/references/copywriting-formulas.md b/.claude/skills/slides/references/copywriting-formulas.md index ecf2875..87352fc 100644 --- a/.claude/skills/slides/references/copywriting-formulas.md +++ b/.claude/skills/slides/references/copywriting-formulas.md @@ -66,10 +66,10 @@ ```bash # Find formula for slide type -python .claude/skills/design-system/scripts/search-slides.py "problem agitation" -d copy +python ../design-system/scripts/search-slides.py "problem agitation" -d copy # Get emotion-appropriate formula -python .claude/skills/design-system/scripts/search-slides.py "urgency cta" -d copy +python ../design-system/scripts/search-slides.py "urgency cta" -d copy ``` ## Quick Reference diff --git a/.claude/skills/slides/references/layout-patterns.md b/.claude/skills/slides/references/layout-patterns.md index e2b3849..0949ab0 100644 --- a/.claude/skills/slides/references/layout-patterns.md +++ b/.claude/skills/slides/references/layout-patterns.md @@ -113,10 +113,10 @@ ```bash # Find layout for specific use -python .claude/skills/design-system/scripts/search-slides.py "metrics dashboard" -d layout +python ../design-system/scripts/search-slides.py "metrics dashboard" -d layout # Contextual recommendation -python .claude/skills/design-system/scripts/search-slides.py "traction slide" \ +python ../design-system/scripts/search-slides.py "traction slide" \ --context --position 4 --total 10 ``` diff --git a/.claude/skills/slides/references/slide-strategies.md b/.claude/skills/slides/references/slide-strategies.md index e004fe1..eae5b13 100644 --- a/.claude/skills/slides/references/slide-strategies.md +++ b/.claude/skills/slides/references/slide-strategies.md @@ -76,10 +76,10 @@ Pattern breaks at 1/3 and 2/3 positions create engagement peaks. ```bash # Find strategy by goal -python .claude/skills/design-system/scripts/search-slides.py "investor pitch" -d strategy +python ../design-system/scripts/search-slides.py "investor pitch" -d strategy # Get emotion arc -python .claude/skills/design-system/scripts/search-slides.py "series a funding" -d strategy --json +python ../design-system/scripts/search-slides.py "series a funding" -d strategy --json ``` ## Matching Strategy to Context diff --git a/.claude/skills/ui-styling/SKILL.md b/.claude/skills/ui-styling/SKILL.md index 5824efe..3f86abc 100644 --- a/.claude/skills/ui-styling/SKILL.md +++ b/.claude/skills/ui-styling/SKILL.md @@ -53,6 +53,10 @@ Use when: - Minimal text, maximum visual impact - Systematic patterns and refined aesthetics +## Script Paths + +Script paths in this skill and its `references/` are relative to the directory that contains this SKILL.md, not to the project: `scripts/` is this skill's own `scripts/` folder, and `..//scripts/` is a sibling sub-skill installed alongside it. Build the full path from that directory (Claude Code reports it as the skill's base directory when the skill loads) and keep the working directory at the project root — the scripts read and write project files such as `docs/brand-guidelines.md`, `assets/design-tokens.json` or `src/` relative to it. + ## Quick Start ### Component + Styling Setup diff --git a/.claude/skills/ui-ux-pro-max/scripts/tests/test_skill_script_paths.py b/.claude/skills/ui-ux-pro-max/scripts/tests/test_skill_script_paths.py new file mode 100644 index 0000000..3651496 --- /dev/null +++ b/.claude/skills/ui-ux-pro-max/scripts/tests/test_skill_script_paths.py @@ -0,0 +1,82 @@ +"""Every script invocation in the shipped skill markdown resolves from the skill directory. + +Regression test for #474. The sub-skills ship in two copies (.claude/skills// +for the plugin, cli/assets/skills// for CLI installs) and land in layouts where +neither the project root nor ~/.claude/skills/ is a valid anchor: the plugin cache, a +project's .claude/skills/, ~/.claude/skills/ (--global), or a manual copy. The one anchor +that exists in all of them is the skill's own directory, so documented commands use +`scripts/` for the skill's own scripts and `..//scripts/` for a +sibling sub-skill (the sub-skills are always installed side by side). + +This test extracts every `python|python3|node|bash ` invocation from every +markdown file under both trees and asserts that the path is skill-relative and names a +file that ships. The core skill's `${CLAUDE_PLUGIN_ROOT}/.claude/skills/...` form is +resolved against the repository root, which is what that variable denotes under a +plugin install - and accepted only in that file, because the sub-skills also ship +through the CLI, where the variable does not exist. The grep-based path contract in check-asset-sync.yml is the negative +side (no home-, project- or variable-rooted paths anywhere, code included); this is +the positive side (every documented invocation points at a real file). +""" + +import re +import unittest +from pathlib import Path + +REPO = next( + parent for parent in Path(__file__).resolve().parents + if (parent / "scripts" / "generate-catalog-summary.py").is_file() +) +SKILL_TREES = ("cli/assets/skills", ".claude/skills") +# The only file that may use the plugin-root form: hand-authored for the plugin install +# and not shipped by the CLI (sync-assets.mjs mirrors data/ and scripts/, never SKILL.md). +# (Built from segments: the path contract in check-asset-sync.yml scans this file too.) +PLUGIN_ONLY_FILE = Path(".claude") / "skills" / "ui-ux-pro-max" / "SKILL.md" +INVOCATION = re.compile(r'(? 3 and parts[2] == "scripts" and (skill_dir.parent / parts[1]).is_dir(): + return skill_dir.parent / parts[1] / "/".join(parts[2:]), None + return None, "a sibling invocation must be ..//scripts/ and the sibling must ship" + return None, "not skill-relative (expected scripts/ or ..//scripts/)" + + +class SkillScriptPathsTest(unittest.TestCase): + def test_every_shipped_markdown_invocation_resolves_from_the_skill_directory(self): + problems, seen = [], 0 + for skill_dir, md, lineno, path in shipped_invocations(): + seen += 1 + target, reason = resolve(skill_dir, md, path) + if reason is None and not target.is_file(): + reason = f"no such file: {target}" + if reason: + problems.append(f"{md.relative_to(REPO)}:{lineno}: {path} -- {reason}") + # Guard against a silently broken extractor: the two trees carry well over + # a hundred documented invocations between them. + self.assertGreater(seen, 100, f"extractor found only {seen} invocations") + self.assertEqual(problems, [], "\n" + "\n".join(problems)) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/workflows/check-asset-sync.yml b/.github/workflows/check-asset-sync.yml index 0823454..8c80a43 100644 --- a/.github/workflows/check-asset-sync.yml +++ b/.github/workflows/check-asset-sync.yml @@ -18,6 +18,8 @@ on: - "cli/assets/**" - "cli/package.json" - ".claude/skills/**" + - "cli/scripts/sync-assets.mjs" + - ".github/workflows/check-asset-sync.yml" jobs: check-assets: @@ -36,15 +38,58 @@ jobs: # installed as a plugin, and previously had no sync check at all. - name: Check assets are in sync with source of truth run: npm --prefix cli run check:assets - # Path contract (#474): skill instructions must invoke their scripts via - # skill-relative paths (like the brand/design-system sub-skills do). A - # user-level "~/.claude/skills/..." path only works in one install - # context: it breaks under a marketplace/plugin install (skills live in - # the plugin cache) and under project-level CLI installs. - - name: Path contract - no hard-coded user-level skill paths + # Path contract (#474): skill instructions and scripts reach their scripts + # via skill-relative paths ("scripts/" for the skill's own, "..//scripts/" + # for a sibling sub-skill) so they resolve in every install context: marketplace/ + # plugin cache, project-level CLI install, CLI --global install, manual copy. + # Home-rooted ("~/.claude/skills/"), project-rooted (".claude/skills/") + # and variable-rooted ("$HOME/...", "${PWD}/...") forms each work in only one of them. + # Every file under both skill trees is checked, not just SKILL.md - the first + # version of this step looked only at SKILL.md and missed 27 home-rooted paths + # one directory down in references/. The one allowed absolute form is + # "${CLAUDE_PLUGIN_ROOT}/.claude/skills/ui-ux-pro-max/..." (braced or bare variable, + # directly followed by "/"): the core skill's SKILL.md is hand-authored for the + # plugin install only and that variable anchors it there. The same variable into a + # sub-skill is flagged, because sub-skills are also installed by the CLI where it is + # unset - and a third check pins the token itself to that one file (plus the checker + # that names it), so a sub-skill cannot borrow the core form either: sub-skills ship + # through the CLI too, where the variable does not exist. Both patterns require a path INTO a named skill ("skills/"), so a bare + # mention of the directory in prose or a code comment ("~/.claude/skills/, or ...") + # is not a hit - naming a skill after "skills/" in prose is. + # LC_ALL=C so that only NUL-containing files count as binary and an offending line + # with a stray non-UTF-8 byte is printed instead of suppressed as improperly encoded + # (the verdict is the same in both locales; the diagnostic is not); -I then skips + # .claude/skills/ui-styling/scripts/.coverage, a tracked SQLite database whose + # recorded absolute paths contain "/.claude/skills/ui-styling/". + # Not covered: backslash-separated Windows spellings and the platform-root-relative + # "skills//..." form - the docs are bash-fenced and skill-relative, so neither + # appears; the positive side (every documented invocation names a file that ships) + # is src/ui-ux-pro-max/scripts/tests/test_skill_script_paths.py. + # grep exit codes: 0 = hits (violation), 1 = clean, 2 = error - only 1 passes, so an + # unreadable file can never turn into a green run (a missing tree is caught above). + - name: Path contract - no install-specific skill paths run: | - if grep -rn '~/\.claude/skills/' .claude/skills/*/SKILL.md cli/assets/skills/*/SKILL.md; then - echo "::error::SKILL.md files must use skill-relative script paths, not ~/.claude/skills/... (see #474)" - exit 1 + for d in .claude/skills cli/assets/skills; do + [ -d "$d" ] || { echo "::error::$d is missing - the path contract has nothing to scan"; exit 1; } + done + status=0 + rc=0; LC_ALL=C grep -rnIP '~/\.claude/skills/[A-Za-z0-9_-]+\b' .claude/skills cli/assets/skills || rc=$? + if [ "$rc" -ne 1 ]; then + echo "::error::home-rooted skill paths (~/.claude/skills/) only resolve for one install layout - use skill-relative paths (see #474); grep rc=$rc" + status=1 fi - echo "OK: no hard-coded user-level skill paths" + rc=0; LC_ALL=C grep -rnIP '(?, \$HOME/..., \${CLAUDE_PLUGIN_ROOT}/... outside the core skill) only resolve for one install layout - use skill-relative paths (see #474); grep rc=$rc" + status=1 + fi + rc=0; found=$(LC_ALL=C grep -rlIF 'CLAUDE_PLUGIN_ROOT' .claude/skills cli/assets/skills) || rc=$? + if [ "$rc" -eq 2 ]; then echo "::error::grep failed while scanning for CLAUDE_PLUGIN_ROOT (rc=2)"; status=1; fi + offenders=$(printf '%s\n' "$found" | grep -vxF -e '.claude/skills/ui-ux-pro-max/SKILL.md' -e '.claude/skills/ui-ux-pro-max/scripts/tests/test_skill_script_paths.py' | grep -v '^$' || true) + if [ -n "$offenders" ]; then + printf '%s\n' "$offenders" + echo "::error::CLAUDE_PLUGIN_ROOT is only defined under a plugin install; only the plugin-only core SKILL.md may use it - sub-skills ship through the CLI too (see #474)" + status=1 + fi + if [ "$status" -eq 0 ]; then echo "OK: all skill paths are skill-relative"; fi + exit "$status" diff --git a/cli/assets/scripts/tests/test_skill_script_paths.py b/cli/assets/scripts/tests/test_skill_script_paths.py new file mode 100644 index 0000000..3651496 --- /dev/null +++ b/cli/assets/scripts/tests/test_skill_script_paths.py @@ -0,0 +1,82 @@ +"""Every script invocation in the shipped skill markdown resolves from the skill directory. + +Regression test for #474. The sub-skills ship in two copies (.claude/skills// +for the plugin, cli/assets/skills// for CLI installs) and land in layouts where +neither the project root nor ~/.claude/skills/ is a valid anchor: the plugin cache, a +project's .claude/skills/, ~/.claude/skills/ (--global), or a manual copy. The one anchor +that exists in all of them is the skill's own directory, so documented commands use +`scripts/` for the skill's own scripts and `..//scripts/` for a +sibling sub-skill (the sub-skills are always installed side by side). + +This test extracts every `python|python3|node|bash ` invocation from every +markdown file under both trees and asserts that the path is skill-relative and names a +file that ships. The core skill's `${CLAUDE_PLUGIN_ROOT}/.claude/skills/...` form is +resolved against the repository root, which is what that variable denotes under a +plugin install - and accepted only in that file, because the sub-skills also ship +through the CLI, where the variable does not exist. The grep-based path contract in check-asset-sync.yml is the negative +side (no home-, project- or variable-rooted paths anywhere, code included); this is +the positive side (every documented invocation points at a real file). +""" + +import re +import unittest +from pathlib import Path + +REPO = next( + parent for parent in Path(__file__).resolve().parents + if (parent / "scripts" / "generate-catalog-summary.py").is_file() +) +SKILL_TREES = ("cli/assets/skills", ".claude/skills") +# The only file that may use the plugin-root form: hand-authored for the plugin install +# and not shipped by the CLI (sync-assets.mjs mirrors data/ and scripts/, never SKILL.md). +# (Built from segments: the path contract in check-asset-sync.yml scans this file too.) +PLUGIN_ONLY_FILE = Path(".claude") / "skills" / "ui-ux-pro-max" / "SKILL.md" +INVOCATION = re.compile(r'(? 3 and parts[2] == "scripts" and (skill_dir.parent / parts[1]).is_dir(): + return skill_dir.parent / parts[1] / "/".join(parts[2:]), None + return None, "a sibling invocation must be ..//scripts/ and the sibling must ship" + return None, "not skill-relative (expected scripts/ or ..//scripts/)" + + +class SkillScriptPathsTest(unittest.TestCase): + def test_every_shipped_markdown_invocation_resolves_from_the_skill_directory(self): + problems, seen = [], 0 + for skill_dir, md, lineno, path in shipped_invocations(): + seen += 1 + target, reason = resolve(skill_dir, md, path) + if reason is None and not target.is_file(): + reason = f"no such file: {target}" + if reason: + problems.append(f"{md.relative_to(REPO)}:{lineno}: {path} -- {reason}") + # Guard against a silently broken extractor: the two trees carry well over + # a hundred documented invocations between them. + self.assertGreater(seen, 100, f"extractor found only {seen} invocations") + self.assertEqual(problems, [], "\n" + "\n".join(problems)) + + +if __name__ == "__main__": + unittest.main() diff --git a/cli/assets/skills/brand/SKILL.md b/cli/assets/skills/brand/SKILL.md index 336e8ef..48d912f 100644 --- a/cli/assets/skills/brand/SKILL.md +++ b/cli/assets/skills/brand/SKILL.md @@ -20,6 +20,10 @@ Brand identity, voice, messaging, asset management, and consistency frameworks. - Asset organization, naming, and approval - Color palette management and typography specs +## Script Paths + +Script paths in this skill and its `references/` are relative to the directory that contains this SKILL.md, not to the project: `scripts/` is this skill's own `scripts/` folder, and `..//scripts/` is a sibling sub-skill installed alongside it. Build the full path from that directory (Claude Code reports it as the skill's base directory when the skill loads) and keep the working directory at the project root — the scripts read and write project files such as `docs/brand-guidelines.md`, `assets/design-tokens.json` or `src/` relative to it. + ## Quick Start **Inject brand context into prompts:** diff --git a/cli/assets/skills/brand/references/approval-checklist.md b/cli/assets/skills/brand/references/approval-checklist.md index ff05bac..0ce4bb6 100644 --- a/cli/assets/skills/brand/references/approval-checklist.md +++ b/cli/assets/skills/brand/references/approval-checklist.md @@ -157,7 +157,7 @@ The `validate-asset.cjs` script can auto-check: - Naming convention - Basic metadata -Run: `node .claude/skills/brand/scripts/validate-asset.cjs ` +Run: `node scripts/validate-asset.cjs ` ## Archival diff --git a/cli/assets/skills/brand/references/update.md b/cli/assets/skills/brand/references/update.md index 4a92438..25ed7f3 100644 --- a/cli/assets/skills/brand/references/update.md +++ b/cli/assets/skills/brand/references/update.md @@ -46,7 +46,7 @@ Edit `docs/brand-guidelines.md`: Run the sync script: ```bash -node .claude/skills/brand/scripts/sync-brand-to-tokens.cjs +node scripts/sync-brand-to-tokens.cjs ``` This will: @@ -58,7 +58,7 @@ This will: Confirm all files are updated: ```bash # Check brand context extraction -node .claude/skills/brand/scripts/inject-brand-context.cjs --json | head -30 +node scripts/inject-brand-context.cjs --json | head -30 # Check CSS variables grep "primary" assets/design-tokens.css | head -5 diff --git a/cli/assets/skills/brand/scripts/extract-colors.cjs b/cli/assets/skills/brand/scripts/extract-colors.cjs index a2ec2b4..73aa6d4 100644 --- a/cli/assets/skills/brand/scripts/extract-colors.cjs +++ b/cli/assets/skills/brand/scripts/extract-colors.cjs @@ -287,11 +287,7 @@ function main() { "1. Run the ImageMagick command to extract colors:", ` ${generateImageMagickCommand(resolvedPath)}`, "", - "2. Or use the ai-multimodal skill:", - ` python .claude/skills/ai-multimodal/scripts/gemini_batch_process.py \\`, - ` --files "${resolvedPath}" \\`, - ` --task analyze \\`, - ` --prompt "Extract the 10 most dominant colors as hex values"`, + "2. Or use an image-analysis skill (e.g. ai-multimodal, if installed) to extract the 10 most dominant colors as hex values", "", "3. Then compare extracted colors against brand palette", ], diff --git a/cli/assets/skills/brand/scripts/sync-brand-to-tokens.cjs b/cli/assets/skills/brand/scripts/sync-brand-to-tokens.cjs index 013fa6f..1e3b8ce 100644 --- a/cli/assets/skills/brand/scripts/sync-brand-to-tokens.cjs +++ b/cli/assets/skills/brand/scripts/sync-brand-to-tokens.cjs @@ -17,7 +17,10 @@ const { execFileSync } = require('child_process'); const BRAND_GUIDELINES = 'docs/brand-guidelines.md'; const DESIGN_TOKENS_JSON = 'assets/design-tokens.json'; const DESIGN_TOKENS_CSS = 'assets/design-tokens.css'; -const GENERATE_TOKENS_SCRIPT = '.claude/skills/design-system/scripts/generate-tokens.cjs'; +// Sibling sub-skill, resolved from this file's location so it works in every +// install context (plugin cache, project or --global CLI install), not only +// when the process runs from a project root that contains .claude/skills/. +const GENERATE_TOKENS_SCRIPT = path.resolve(__dirname, '..', '..', 'design-system', 'scripts', 'generate-tokens.cjs'); /** * Extract color info from brand guidelines markdown @@ -229,7 +232,7 @@ function main() { console.log(`✅ Updated: ${DESIGN_TOKENS_JSON}`); // Regenerate CSS - const generateScript = path.resolve(process.cwd(), GENERATE_TOKENS_SCRIPT); + const generateScript = GENERATE_TOKENS_SCRIPT; if (fs.existsSync(generateScript)) { try { execFileSync('node', [generateScript, '--config', DESIGN_TOKENS_JSON, '-o', DESIGN_TOKENS_CSS], { @@ -240,6 +243,8 @@ function main() { } catch (e) { console.error('⚠️ Failed to regenerate CSS:', e.message); } + } else { + console.warn(`⚠️ design-system sub-skill not found at ${generateScript}; ${DESIGN_TOKENS_CSS} not regenerated`); } console.log('\n✨ Brand sync complete!'); diff --git a/cli/assets/skills/brand/scripts/tests/test_sync_brand_to_tokens.py b/cli/assets/skills/brand/scripts/tests/test_sync_brand_to_tokens.py index 5e6b1e4..53c5437 100644 --- a/cli/assets/skills/brand/scripts/tests/test_sync_brand_to_tokens.py +++ b/cli/assets/skills/brand/scripts/tests/test_sync_brand_to_tokens.py @@ -62,6 +62,14 @@ def test_sync_parses_bundled_starter_template(tmp_path): assert primitive["secondary"]["500"]["$value"] == "#8B5CF6" assert primitive["accent"]["500"]["$value"] == "#10B981" + # #474: the sibling design-system script is resolved from this skill's own + # location, so the CSS regeneration must run even though tmp_path has no + # .claude/skills/ tree. Before the fix it was resolved from the working + # directory and silently skipped in every layout but a project install. + assert "Regenerated" in result.stdout, result.stdout + css = tmp_path / "assets" / "design-tokens.css" + assert css.exists() and css.stat().st_size > 0 + def test_reports_missing_guidelines_without_breaking_the_harness(tmp_path): """The missing-guidelines path is the one that breaks a locale-decoded pipe. diff --git a/cli/assets/skills/design-system/SKILL.md b/cli/assets/skills/design-system/SKILL.md index 4397c7f..804e553 100644 --- a/cli/assets/skills/design-system/SKILL.md +++ b/cli/assets/skills/design-system/SKILL.md @@ -48,6 +48,10 @@ Component (component-specific) --button-bg: var(--color-primary); ``` +## Script Paths + +Script paths in this skill and its `references/` are relative to the directory that contains this SKILL.md, not to the project: `scripts/` is this skill's own `scripts/` folder, and `..//scripts/` is a sibling sub-skill installed alongside it. Build the full path from that directory (Claude Code reports it as the skill's base directory when the skill loads) and keep the working directory at the project root — the scripts read and write project files such as `docs/brand-guidelines.md`, `assets/design-tokens.json` or `src/` relative to it. + ## Quick Start **Generate tokens:** diff --git a/cli/assets/skills/design/SKILL.md b/cli/assets/skills/design/SKILL.md index 4200f14..16256ad 100644 --- a/cli/assets/skills/design/SKILL.md +++ b/cli/assets/skills/design/SKILL.md @@ -37,6 +37,10 @@ Unified design skill: brand, tokens, UI, logo, CIP, slides, banners, social phot | Social media images/photos | Social Photos (built-in) | `references/social-photos-design.md` | | SVG icons, icon sets | Icon (built-in) | `references/icon-design.md` | +## Script Paths + +Script paths in this skill and its `references/` are relative to the directory that contains this SKILL.md, not to the project: `scripts/` is this skill's own `scripts/` folder, and `..//scripts/` is a sibling sub-skill installed alongside it. Build the full path from that directory (Claude Code reports it as the skill's base directory when the skill loads) and keep the working directory at the project root — the scripts read and write project files such as `docs/brand-guidelines.md`, `assets/design-tokens.json` or `src/` relative to it. + ## Logo Design (Built-in) 55+ styles, 30 color palettes, 25 industry guides. Gemini Nano Banana, Atlas diff --git a/cli/assets/skills/design/references/cip-design.md b/cli/assets/skills/design/references/cip-design.md index 81829ed..4037769 100644 --- a/cli/assets/skills/design/references/cip-design.md +++ b/cli/assets/skills/design/references/cip-design.md @@ -16,49 +16,49 @@ Corporate Identity Program design with 50+ deliverables, 20 styles, 20 industrie ### CIP Brief (Start Here) ```bash -python3 ~/.claude/skills/design/scripts/cip/search.py "tech startup" --cip-brief -b "BrandName" +python3 scripts/cip/search.py "tech startup" --cip-brief -b "BrandName" ``` ### Search Domains ```bash # Deliverables -python3 ~/.claude/skills/design/scripts/cip/search.py "business card letterhead" --domain deliverable +python3 scripts/cip/search.py "business card letterhead" --domain deliverable # Design styles -python3 ~/.claude/skills/design/scripts/cip/search.py "luxury premium elegant" --domain style +python3 scripts/cip/search.py "luxury premium elegant" --domain style # Industry guidelines -python3 ~/.claude/skills/design/scripts/cip/search.py "hospitality hotel" --domain industry +python3 scripts/cip/search.py "hospitality hotel" --domain industry # Mockup contexts -python3 ~/.claude/skills/design/scripts/cip/search.py "office reception" --domain mockup +python3 scripts/cip/search.py "office reception" --domain mockup ``` ### Generate Mockups ```bash # With logo (RECOMMENDED - uses image editing) -python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TopGroup" --logo /path/to/logo.png --deliverable "business card" --industry "consulting" +python3 scripts/cip/generate.py --brand "TopGroup" --logo /path/to/logo.png --deliverable "business card" --industry "consulting" # Full CIP set with logo -python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TopGroup" --logo /path/to/logo.png --industry "consulting" --set +python3 scripts/cip/generate.py --brand "TopGroup" --logo /path/to/logo.png --industry "consulting" --set # Pro model for 4K text rendering -python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TopGroup" --logo logo.png --deliverable "business card" --model pro +python3 scripts/cip/generate.py --brand "TopGroup" --logo logo.png --deliverable "business card" --model pro # Custom deliverables with aspect ratio -python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "GreenLeaf" --logo logo.png --industry "organic food" --deliverables "letterhead,packaging,vehicle" --ratio 16:9 +python3 scripts/cip/generate.py --brand "GreenLeaf" --logo logo.png --industry "organic food" --deliverables "letterhead,packaging,vehicle" --ratio 16:9 # Without logo (AI generates interpretation) -python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TechFlow" --deliverable "business card" --no-logo-prompt +python3 scripts/cip/generate.py --brand "TechFlow" --deliverable "business card" --no-logo-prompt ``` ### Render HTML Presentation ```bash -python3 ~/.claude/skills/design/scripts/cip/render-html.py --brand "TopGroup" --industry "consulting" --images /path/to/cip-output -python3 ~/.claude/skills/design/scripts/cip/render-html.py --brand "TopGroup" --industry "consulting" --images ./topgroup-cip --output presentation.html +python3 scripts/cip/render-html.py --brand "TopGroup" --industry "consulting" --images /path/to/cip-output +python3 scripts/cip/render-html.py --brand "TopGroup" --industry "consulting" --images ./topgroup-cip --output presentation.html ``` ## Models diff --git a/cli/assets/skills/design/references/design-routing.md b/cli/assets/skills/design/references/design-routing.md index 78f5587..4d745bd 100644 --- a/cli/assets/skills/design/references/design-routing.md +++ b/cli/assets/skills/design/references/design-routing.md @@ -164,14 +164,14 @@ Application Code **Brand:** ```bash -node .claude/skills/brand/scripts/inject-brand-context.cjs -node .claude/skills/brand/scripts/validate-asset.cjs +node ../brand/scripts/inject-brand-context.cjs +node ../brand/scripts/validate-asset.cjs ``` **Tokens:** ```bash -node .claude/skills/design-system/scripts/generate-tokens.cjs -c tokens.json -node .claude/skills/design-system/scripts/validate-tokens.cjs -d src/ +node ../design-system/scripts/generate-tokens.cjs -c tokens.json +node ../design-system/scripts/validate-tokens.cjs -d src/ ``` **Components:** diff --git a/cli/assets/skills/design/references/icon-design.md b/cli/assets/skills/design/references/icon-design.md index db6db01..961cdac 100644 --- a/cli/assets/skills/design/references/icon-design.md +++ b/cli/assets/skills/design/references/icon-design.md @@ -13,29 +13,29 @@ AI-powered SVG icon generation using Gemini 3.1 Pro Preview. 15 styles, 12 categ ### Generate Single Icon ```bash -python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "settings gear" --style outlined -python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "shopping cart" --style filled --color "#6366F1" -python3 ~/.claude/skills/design/scripts/icon/generate.py --name "dashboard" --category navigation --style duotone +python3 scripts/icon/generate.py --prompt "settings gear" --style outlined +python3 scripts/icon/generate.py --prompt "shopping cart" --style filled --color "#6366F1" +python3 scripts/icon/generate.py --name "dashboard" --category navigation --style duotone ``` ### Generate Batch Variations ```bash -python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "cloud upload" --batch 4 --output-dir ./icons -python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "notification bell" --batch 6 --style outlined --output-dir ./icons +python3 scripts/icon/generate.py --prompt "cloud upload" --batch 4 --output-dir ./icons +python3 scripts/icon/generate.py --prompt "notification bell" --batch 6 --style outlined --output-dir ./icons ``` ### Generate Multiple Sizes ```bash -python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "user profile" --sizes "16,24,32,48" --output-dir ./icons +python3 scripts/icon/generate.py --prompt "user profile" --sizes "16,24,32,48" --output-dir ./icons ``` ### List Styles/Categories ```bash -python3 ~/.claude/skills/design/scripts/icon/generate.py --list-styles -python3 ~/.claude/skills/design/scripts/icon/generate.py --list-categories +python3 scripts/icon/generate.py --list-styles +python3 scripts/icon/generate.py --list-categories ``` ## CLI Options diff --git a/cli/assets/skills/design/references/logo-design.md b/cli/assets/skills/design/references/logo-design.md index a240e96..258ae77 100644 --- a/cli/assets/skills/design/references/logo-design.md +++ b/cli/assets/skills/design/references/logo-design.md @@ -15,20 +15,20 @@ AI-powered logo design with 55+ styles, 30 color palettes, 25 industry guides. G ### Design Brief (Start Here) ```bash -python3 ~/.claude/skills/design/scripts/logo/search.py "tech startup modern" --design-brief -p "BrandName" +python3 scripts/logo/search.py "tech startup modern" --design-brief -p "BrandName" ``` ### Search Domains ```bash # Styles -python3 ~/.claude/skills/design/scripts/logo/search.py "minimalist clean" --domain style +python3 scripts/logo/search.py "minimalist clean" --domain style # Color palettes -python3 ~/.claude/skills/design/scripts/logo/search.py "tech professional" --domain color +python3 scripts/logo/search.py "tech professional" --domain color # Industry guidelines -python3 ~/.claude/skills/design/scripts/logo/search.py "healthcare medical" --domain industry +python3 scripts/logo/search.py "healthcare medical" --domain industry ``` ### Generate Logo @@ -36,11 +36,11 @@ python3 ~/.claude/skills/design/scripts/logo/search.py "healthcare medical" --do **ALWAYS** use white background for output logos. ```bash -python3 ~/.claude/skills/design/scripts/logo/generate.py --brand "TechFlow" --style minimalist --industry tech -python3 ~/.claude/skills/design/scripts/logo/generate.py --prompt "coffee shop vintage badge" --style vintage -python3 ~/.claude/skills/design/scripts/logo/generate.py --brand "TechFlow" --provider atlas -python3 ~/.claude/skills/design/scripts/logo/generate.py --brand "TechFlow" --provider muapi -python3 ~/.claude/skills/design/scripts/logo/generate.py --brand "TechFlow" --provider muapi --muapi-model nano-banana-pro +python3 scripts/logo/generate.py --brand "TechFlow" --style minimalist --industry tech +python3 scripts/logo/generate.py --prompt "coffee shop vintage badge" --style vintage +python3 scripts/logo/generate.py --brand "TechFlow" --provider atlas +python3 scripts/logo/generate.py --brand "TechFlow" --provider muapi +python3 scripts/logo/generate.py --brand "TechFlow" --provider muapi --muapi-model nano-banana-pro ``` Options: `--style`, `--industry`, `--prompt`, `--provider`, `--atlas-model`, `--muapi-model` diff --git a/cli/assets/skills/design/references/slides-copywriting-formulas.md b/cli/assets/skills/design/references/slides-copywriting-formulas.md index ecf2875..87352fc 100644 --- a/cli/assets/skills/design/references/slides-copywriting-formulas.md +++ b/cli/assets/skills/design/references/slides-copywriting-formulas.md @@ -66,10 +66,10 @@ ```bash # Find formula for slide type -python .claude/skills/design-system/scripts/search-slides.py "problem agitation" -d copy +python ../design-system/scripts/search-slides.py "problem agitation" -d copy # Get emotion-appropriate formula -python .claude/skills/design-system/scripts/search-slides.py "urgency cta" -d copy +python ../design-system/scripts/search-slides.py "urgency cta" -d copy ``` ## Quick Reference diff --git a/cli/assets/skills/design/references/slides-layout-patterns.md b/cli/assets/skills/design/references/slides-layout-patterns.md index e2b3849..0949ab0 100644 --- a/cli/assets/skills/design/references/slides-layout-patterns.md +++ b/cli/assets/skills/design/references/slides-layout-patterns.md @@ -113,10 +113,10 @@ ```bash # Find layout for specific use -python .claude/skills/design-system/scripts/search-slides.py "metrics dashboard" -d layout +python ../design-system/scripts/search-slides.py "metrics dashboard" -d layout # Contextual recommendation -python .claude/skills/design-system/scripts/search-slides.py "traction slide" \ +python ../design-system/scripts/search-slides.py "traction slide" \ --context --position 4 --total 10 ``` diff --git a/cli/assets/skills/design/references/slides-strategies.md b/cli/assets/skills/design/references/slides-strategies.md index e004fe1..eae5b13 100644 --- a/cli/assets/skills/design/references/slides-strategies.md +++ b/cli/assets/skills/design/references/slides-strategies.md @@ -76,10 +76,10 @@ Pattern breaks at 1/3 and 2/3 positions create engagement peaks. ```bash # Find strategy by goal -python .claude/skills/design-system/scripts/search-slides.py "investor pitch" -d strategy +python ../design-system/scripts/search-slides.py "investor pitch" -d strategy # Get emotion arc -python .claude/skills/design-system/scripts/search-slides.py "series a funding" -d strategy --json +python ../design-system/scripts/search-slides.py "series a funding" -d strategy --json ``` ## Matching Strategy to Context diff --git a/cli/assets/skills/design/scripts/cip/generate.py b/cli/assets/skills/design/scripts/cip/generate.py index 0be632f..e92b2e1 100644 --- a/cli/assets/skills/design/scripts/cip/generate.py +++ b/cli/assets/skills/design/scripts/cip/generate.py @@ -427,7 +427,10 @@ Image Editing Mode: action = check_logo_required(args.brand, skip_prompt=args.no_logo_prompt) if action == 'generate': print("\n💡 To generate a logo, use the logo-design skill:") - print(f" python ~/.claude/skills/design/scripts/logo/generate.py --brand \"{args.brand}\" --industry \"{args.industry}\"") + # Resolved from this file so the hint is correct from any cwd and in + # every install layout (plugin cache, project or --global install). + logo_script = Path(__file__).resolve().parents[1] / "logo" / "generate.py" + print(f" python \"{logo_script}\" --brand \"{args.brand}\" --industry \"{args.industry}\"") print("\n Then re-run this command with --logo ") sys.exit(0) elif action == 'exit': diff --git a/cli/assets/skills/slides/SKILL.md b/cli/assets/skills/slides/SKILL.md index 38750ff..52d345d 100644 --- a/cli/assets/skills/slides/SKILL.md +++ b/cli/assets/skills/slides/SKILL.md @@ -24,6 +24,10 @@ Strategic HTML presentation design with data visualization. |------------|-------------|-----------| | `create` | Create strategic presentation slides | `references/create.md` | +## Script Paths + +Script paths in this skill and its `references/` are relative to the directory that contains this SKILL.md, not to the project: `scripts/` is this skill's own `scripts/` folder, and `..//scripts/` is a sibling sub-skill installed alongside it. Build the full path from that directory (Claude Code reports it as the skill's base directory when the skill loads) and keep the working directory at the project root — the scripts read and write project files such as `docs/brand-guidelines.md`, `assets/design-tokens.json` or `src/` relative to it. + ## References (Knowledge Base) | Topic | File | diff --git a/cli/assets/skills/slides/references/copywriting-formulas.md b/cli/assets/skills/slides/references/copywriting-formulas.md index ecf2875..87352fc 100644 --- a/cli/assets/skills/slides/references/copywriting-formulas.md +++ b/cli/assets/skills/slides/references/copywriting-formulas.md @@ -66,10 +66,10 @@ ```bash # Find formula for slide type -python .claude/skills/design-system/scripts/search-slides.py "problem agitation" -d copy +python ../design-system/scripts/search-slides.py "problem agitation" -d copy # Get emotion-appropriate formula -python .claude/skills/design-system/scripts/search-slides.py "urgency cta" -d copy +python ../design-system/scripts/search-slides.py "urgency cta" -d copy ``` ## Quick Reference diff --git a/cli/assets/skills/slides/references/layout-patterns.md b/cli/assets/skills/slides/references/layout-patterns.md index e2b3849..0949ab0 100644 --- a/cli/assets/skills/slides/references/layout-patterns.md +++ b/cli/assets/skills/slides/references/layout-patterns.md @@ -113,10 +113,10 @@ ```bash # Find layout for specific use -python .claude/skills/design-system/scripts/search-slides.py "metrics dashboard" -d layout +python ../design-system/scripts/search-slides.py "metrics dashboard" -d layout # Contextual recommendation -python .claude/skills/design-system/scripts/search-slides.py "traction slide" \ +python ../design-system/scripts/search-slides.py "traction slide" \ --context --position 4 --total 10 ``` diff --git a/cli/assets/skills/slides/references/slide-strategies.md b/cli/assets/skills/slides/references/slide-strategies.md index e004fe1..eae5b13 100644 --- a/cli/assets/skills/slides/references/slide-strategies.md +++ b/cli/assets/skills/slides/references/slide-strategies.md @@ -76,10 +76,10 @@ Pattern breaks at 1/3 and 2/3 positions create engagement peaks. ```bash # Find strategy by goal -python .claude/skills/design-system/scripts/search-slides.py "investor pitch" -d strategy +python ../design-system/scripts/search-slides.py "investor pitch" -d strategy # Get emotion arc -python .claude/skills/design-system/scripts/search-slides.py "series a funding" -d strategy --json +python ../design-system/scripts/search-slides.py "series a funding" -d strategy --json ``` ## Matching Strategy to Context diff --git a/cli/assets/skills/ui-styling/SKILL.md b/cli/assets/skills/ui-styling/SKILL.md index 5824efe..3f86abc 100644 --- a/cli/assets/skills/ui-styling/SKILL.md +++ b/cli/assets/skills/ui-styling/SKILL.md @@ -53,6 +53,10 @@ Use when: - Minimal text, maximum visual impact - Systematic patterns and refined aesthetics +## Script Paths + +Script paths in this skill and its `references/` are relative to the directory that contains this SKILL.md, not to the project: `scripts/` is this skill's own `scripts/` folder, and `..//scripts/` is a sibling sub-skill installed alongside it. Build the full path from that directory (Claude Code reports it as the skill's base directory when the skill loads) and keep the working directory at the project root — the scripts read and write project files such as `docs/brand-guidelines.md`, `assets/design-tokens.json` or `src/` relative to it. + ## Quick Start ### Component + Styling Setup diff --git a/src/ui-ux-pro-max/scripts/tests/test_skill_script_paths.py b/src/ui-ux-pro-max/scripts/tests/test_skill_script_paths.py new file mode 100644 index 0000000..3651496 --- /dev/null +++ b/src/ui-ux-pro-max/scripts/tests/test_skill_script_paths.py @@ -0,0 +1,82 @@ +"""Every script invocation in the shipped skill markdown resolves from the skill directory. + +Regression test for #474. The sub-skills ship in two copies (.claude/skills// +for the plugin, cli/assets/skills// for CLI installs) and land in layouts where +neither the project root nor ~/.claude/skills/ is a valid anchor: the plugin cache, a +project's .claude/skills/, ~/.claude/skills/ (--global), or a manual copy. The one anchor +that exists in all of them is the skill's own directory, so documented commands use +`scripts/` for the skill's own scripts and `..//scripts/` for a +sibling sub-skill (the sub-skills are always installed side by side). + +This test extracts every `python|python3|node|bash ` invocation from every +markdown file under both trees and asserts that the path is skill-relative and names a +file that ships. The core skill's `${CLAUDE_PLUGIN_ROOT}/.claude/skills/...` form is +resolved against the repository root, which is what that variable denotes under a +plugin install - and accepted only in that file, because the sub-skills also ship +through the CLI, where the variable does not exist. The grep-based path contract in check-asset-sync.yml is the negative +side (no home-, project- or variable-rooted paths anywhere, code included); this is +the positive side (every documented invocation points at a real file). +""" + +import re +import unittest +from pathlib import Path + +REPO = next( + parent for parent in Path(__file__).resolve().parents + if (parent / "scripts" / "generate-catalog-summary.py").is_file() +) +SKILL_TREES = ("cli/assets/skills", ".claude/skills") +# The only file that may use the plugin-root form: hand-authored for the plugin install +# and not shipped by the CLI (sync-assets.mjs mirrors data/ and scripts/, never SKILL.md). +# (Built from segments: the path contract in check-asset-sync.yml scans this file too.) +PLUGIN_ONLY_FILE = Path(".claude") / "skills" / "ui-ux-pro-max" / "SKILL.md" +INVOCATION = re.compile(r'(? 3 and parts[2] == "scripts" and (skill_dir.parent / parts[1]).is_dir(): + return skill_dir.parent / parts[1] / "/".join(parts[2:]), None + return None, "a sibling invocation must be ..//scripts/ and the sibling must ship" + return None, "not skill-relative (expected scripts/ or ..//scripts/)" + + +class SkillScriptPathsTest(unittest.TestCase): + def test_every_shipped_markdown_invocation_resolves_from_the_skill_directory(self): + problems, seen = [], 0 + for skill_dir, md, lineno, path in shipped_invocations(): + seen += 1 + target, reason = resolve(skill_dir, md, path) + if reason is None and not target.is_file(): + reason = f"no such file: {target}" + if reason: + problems.append(f"{md.relative_to(REPO)}:{lineno}: {path} -- {reason}") + # Guard against a silently broken extractor: the two trees carry well over + # a hundred documented invocations between them. + self.assertGreater(seen, 100, f"extractor found only {seen} invocations") + self.assertEqual(problems, [], "\n" + "\n".join(problems)) + + +if __name__ == "__main__": + unittest.main()