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/<skill>/scripts/...) in
  brand/, slides/ and design/ references -> scripts/... (own skill) or
  ../<skill>/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 <daniel@buckenmaier.xyz>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
notbucki 2026-09-03 10:14:02 +02:00 committed by GitHub
parent d9062e3bc2
commit 91c193ac05
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 477 additions and 122 deletions

View File

@ -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/<file>` is this skill's own `scripts/` folder, and `../<skill>/scripts/<file>` 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:**

View File

@ -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 <asset-path>`
Run: `node scripts/validate-asset.cjs <asset-path>`
## Archival

View File

@ -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

View File

@ -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",
],

View File

@ -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!');

View File

@ -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.

View File

@ -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/<file>` is this skill's own `scripts/` folder, and `../<skill>/scripts/<file>` 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:**

View File

@ -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/<file>` is this skill's own `scripts/` folder, and `../<skill>/scripts/<file>` 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

View File

@ -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

View File

@ -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 <path>
node ../brand/scripts/inject-brand-context.cjs
node ../brand/scripts/validate-asset.cjs <path>
```
**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:**

View File

@ -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

View File

@ -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`

View File

@ -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

View File

@ -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
```

View File

@ -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

View File

@ -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 <generated_logo.png>")
sys.exit(0)
elif action == 'exit':

View File

@ -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/<file>` is this skill's own `scripts/` folder, and `../<skill>/scripts/<file>` 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 |

View File

@ -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

View File

@ -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
```

View File

@ -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

View File

@ -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/<file>` is this skill's own `scripts/` folder, and `../<skill>/scripts/<file>` 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

View File

@ -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/<skill>/
for the plugin, cli/assets/skills/<skill>/ 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/<file>` for the skill's own scripts and `../<skill>/scripts/<file>` for a
sibling sub-skill (the sub-skills are always installed side by side).
This test extracts every `python|python3|node|bash <path>` 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'(?<![\w/.-])(?:python3?|node|bash)\s+"?([^\s"`\']+\.(?:py|cjs|js|mjs|sh))')
PLUGIN_ROOT = "${CLAUDE_PLUGIN_ROOT}/"
def shipped_invocations():
for tree in SKILL_TREES:
for skill_dir in sorted((REPO / tree).iterdir()):
if not skill_dir.is_dir():
continue
for md in sorted(skill_dir.rglob("*.md")):
for lineno, line in enumerate(md.read_text(encoding="utf-8").splitlines(), 1):
for match in INVOCATION.finditer(line):
yield skill_dir, md, lineno, match.group(1)
def resolve(skill_dir, md, path):
"""Return (target, None) for a skill-relative path, or (None, reason)."""
if path.startswith(PLUGIN_ROOT):
if md.relative_to(REPO) != PLUGIN_ONLY_FILE:
return None, "the ${CLAUDE_PLUGIN_ROOT} form is only valid in the plugin-only core SKILL.md"
return REPO / path[len(PLUGIN_ROOT):], None
if path.startswith("scripts/"):
return skill_dir / path, None
if path.startswith("../"):
parts = path.split("/")
if len(parts) > 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 ../<skill>/scripts/<file> and the sibling must ship"
return None, "not skill-relative (expected scripts/<file> or ../<skill>/scripts/<file>)"
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()

View File

@ -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/<file>" for the skill's own, "../<skill>/scripts/<file>"
# 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/<skill>"), project-rooted (".claude/skills/<skill>")
# 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/<name>"), 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/<skill>/..." 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/<skill>) 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 '(?<![/\w])\.claude/skills/[A-Za-z0-9_-]+\b|(?<!~)(?<!\$CLAUDE_PLUGIN_ROOT)(?<!\$\{CLAUDE_PLUGIN_ROOT\})/\.claude/skills/[A-Za-z0-9_-]+\b|\$\{?CLAUDE_PLUGIN_ROOT\}?/\.claude/skills/(?!ui-ux-pro-max\b)[A-Za-z0-9_-]+\b' .claude/skills cli/assets/skills || rc=$?
if [ "$rc" -ne 1 ]; then
echo "::error::project- or variable-rooted skill paths (.claude/skills/<skill>, \$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"

View File

@ -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/<skill>/
for the plugin, cli/assets/skills/<skill>/ 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/<file>` for the skill's own scripts and `../<skill>/scripts/<file>` for a
sibling sub-skill (the sub-skills are always installed side by side).
This test extracts every `python|python3|node|bash <path>` 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'(?<![\w/.-])(?:python3?|node|bash)\s+"?([^\s"`\']+\.(?:py|cjs|js|mjs|sh))')
PLUGIN_ROOT = "${CLAUDE_PLUGIN_ROOT}/"
def shipped_invocations():
for tree in SKILL_TREES:
for skill_dir in sorted((REPO / tree).iterdir()):
if not skill_dir.is_dir():
continue
for md in sorted(skill_dir.rglob("*.md")):
for lineno, line in enumerate(md.read_text(encoding="utf-8").splitlines(), 1):
for match in INVOCATION.finditer(line):
yield skill_dir, md, lineno, match.group(1)
def resolve(skill_dir, md, path):
"""Return (target, None) for a skill-relative path, or (None, reason)."""
if path.startswith(PLUGIN_ROOT):
if md.relative_to(REPO) != PLUGIN_ONLY_FILE:
return None, "the ${CLAUDE_PLUGIN_ROOT} form is only valid in the plugin-only core SKILL.md"
return REPO / path[len(PLUGIN_ROOT):], None
if path.startswith("scripts/"):
return skill_dir / path, None
if path.startswith("../"):
parts = path.split("/")
if len(parts) > 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 ../<skill>/scripts/<file> and the sibling must ship"
return None, "not skill-relative (expected scripts/<file> or ../<skill>/scripts/<file>)"
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()

View File

@ -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/<file>` is this skill's own `scripts/` folder, and `../<skill>/scripts/<file>` 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:**

View File

@ -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 <asset-path>`
Run: `node scripts/validate-asset.cjs <asset-path>`
## Archival

View File

@ -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

View File

@ -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",
],

View File

@ -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!');

View File

@ -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.

View File

@ -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/<file>` is this skill's own `scripts/` folder, and `../<skill>/scripts/<file>` 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:**

View File

@ -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/<file>` is this skill's own `scripts/` folder, and `../<skill>/scripts/<file>` 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

View File

@ -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

View File

@ -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 <path>
node ../brand/scripts/inject-brand-context.cjs
node ../brand/scripts/validate-asset.cjs <path>
```
**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:**

View File

@ -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

View File

@ -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`

View File

@ -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

View File

@ -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
```

View File

@ -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

View File

@ -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 <generated_logo.png>")
sys.exit(0)
elif action == 'exit':

View File

@ -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/<file>` is this skill's own `scripts/` folder, and `../<skill>/scripts/<file>` 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 |

View File

@ -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

View File

@ -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
```

View File

@ -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

View File

@ -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/<file>` is this skill's own `scripts/` folder, and `../<skill>/scripts/<file>` 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

View File

@ -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/<skill>/
for the plugin, cli/assets/skills/<skill>/ 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/<file>` for the skill's own scripts and `../<skill>/scripts/<file>` for a
sibling sub-skill (the sub-skills are always installed side by side).
This test extracts every `python|python3|node|bash <path>` 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'(?<![\w/.-])(?:python3?|node|bash)\s+"?([^\s"`\']+\.(?:py|cjs|js|mjs|sh))')
PLUGIN_ROOT = "${CLAUDE_PLUGIN_ROOT}/"
def shipped_invocations():
for tree in SKILL_TREES:
for skill_dir in sorted((REPO / tree).iterdir()):
if not skill_dir.is_dir():
continue
for md in sorted(skill_dir.rglob("*.md")):
for lineno, line in enumerate(md.read_text(encoding="utf-8").splitlines(), 1):
for match in INVOCATION.finditer(line):
yield skill_dir, md, lineno, match.group(1)
def resolve(skill_dir, md, path):
"""Return (target, None) for a skill-relative path, or (None, reason)."""
if path.startswith(PLUGIN_ROOT):
if md.relative_to(REPO) != PLUGIN_ONLY_FILE:
return None, "the ${CLAUDE_PLUGIN_ROOT} form is only valid in the plugin-only core SKILL.md"
return REPO / path[len(PLUGIN_ROOT):], None
if path.startswith("scripts/"):
return skill_dir / path, None
if path.startswith("../"):
parts = path.split("/")
if len(parts) > 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 ../<skill>/scripts/<file> and the sibling must ship"
return None, "not skill-relative (expected scripts/<file> or ../<skill>/scripts/<file>)"
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()