mirror of
https://github.com/nextlevelbuilder/ui-ux-pro-max-skill.git
synced 2026-09-20 19:16:14 +00:00
* fix(design): make workflows self-contained, add bundled-skill contract (#474 finding 2) The design skill's banner and social-photos workflows named skills the plugin does not ship (frontend-design, ai-artist, ai-multimodal, chrome-devtools, project-management, assets-organizing) and claudekit command namespaces (/ckm:brand, /ck:ui-ux-pro-max), so those steps fail or force the agent to improvise under a plugin or CLI install. Rewrite them after the pattern of #473: bundled skills stay referenced by name, optional tooling becomes an optional runtime capability, unbundled steps are implemented inline or dropped. The same references sat in references/social-photos-design.md, so the fix covers the skill directory, not just SKILL.md; the CLI copy is synced. check-asset-sync.yml gains a bundled-skill contract over both skill trees: no /ck: or /ckm: namespace, every "`name` skill" reference must be a directory under .claude/skills, and none of the claudekit-only names above in any spelling. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(design): address review - list-aware skill grep, case-insensitive denylist, banner intro, report naming - contract grep 2 walks comma/and/or lists so every member is checked, not only the last one, and accepts the sub-skill and bold forms; grep 3 matches case-insensitively; the step comment states what is not covered - banner intro no longer claims "no sibling skills" while step 2 names the bundled ui-ux-pro-max skill - social-photos report name stated inline instead of "naming pattern from session hooks" (a claudekit convention nothing in the bundle provides); "persistent" dropped from the task-list step; device-scale note scoped to tools that support it Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(ci): match wrapped skill lists - read each file as one record in the bundled-skill contract A Markdown list wrapped over two lines ("`playwright-mcp`,\n`brand`, and `ui-ux-pro-max` skills") passed the line-oriented allowlist grep. Read the file as one record (-z, separators as \s+) so wrapped lists are still lists; a hit now names the file and the name instead of the line. grep's own exit status is taken inside the substitution so an unreadable file still fails the step under plain bash -e, pipefail or not. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
157 lines
11 KiB
YAML
157 lines
11 KiB
YAML
name: Check asset sync
|
|
|
|
on:
|
|
pull_request:
|
|
paths:
|
|
- "src/ui-ux-pro-max/**"
|
|
- "cli/assets/**"
|
|
- ".claude/skills/**"
|
|
- "cli/scripts/sync-assets.mjs"
|
|
- "cli/package.json"
|
|
- "scripts/evaluate-relevance.py"
|
|
- "scripts/relevance_metrics.py"
|
|
- ".github/workflows/check-asset-sync.yml"
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
- "src/ui-ux-pro-max/**"
|
|
- "cli/assets/**"
|
|
- "cli/package.json"
|
|
- ".claude/skills/**"
|
|
- "cli/scripts/sync-assets.mjs"
|
|
- ".github/workflows/check-asset-sync.yml"
|
|
|
|
jobs:
|
|
check-assets:
|
|
name: cli/assets and .claude/skills/ui-ux-pro-max must match src/ui-ux-pro-max
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 20
|
|
# check:assets runs `node scripts/sync-assets.mjs --check`, which uses only
|
|
# node builtins (no npm install needed) and normalizes CRLF/LF before
|
|
# hashing, so it compares content rather than line endings. It checks
|
|
# both cli/assets/ AND .claude/skills/ui-ux-pro-max/{data,scripts} --
|
|
# the latter is what Claude Code actually loads when this repo is
|
|
# 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 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: |
|
|
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
|
|
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"
|
|
# Bundled-skill contract (#474 finding 2): the skill directories under .claude/skills are
|
|
# everything a plugin or CLI install ships, so a workflow step that names a skill outside
|
|
# that set ("frontend-design", "chrome-devtools", ...) or a claudekit command namespace
|
|
# ("/ckm:brand", "/ck:frontend-design") fails silently or leaves the agent improvising under
|
|
# either install. #473 made banner-design self-contained; this step keeps every shipped
|
|
# skill that way. Three greps over every shipped Markdown file in both trees:
|
|
# 1. no claudekit command namespace - "/ck:" or "/ckm:" not preceded by a word character
|
|
# or "/", so a URL or file path containing the letters is not a hit;
|
|
# 2. every backticked name followed by "skill", "skills" or "sub-skill" (the form these docs
|
|
# use to name a skill) must be a directory under .claude/skills - an allowlist, so a new
|
|
# unbundled name fails without editing this file. The lookahead walks a list ("`a`, `b`,
|
|
# and `c` skills", separators ", ", " and ", " or ", "/", " -> ") so every member is
|
|
# checked, not only the last one, and tolerates "**" around the name. The file is read as
|
|
# one record (-z, separators as \s+) so a list wrapped over several lines is still a
|
|
# list; a hit therefore names the file and the name, not the line;
|
|
# 3. none of the claudekit-only skill names the shipped docs used to reference, matched
|
|
# case-insensitively in their hyphenated spelling - "Related Skills: frontend-design, ..."
|
|
# carries neither backticks nor a "skill" suffix, so 2. cannot see it. "project-management"
|
|
# is also ordinary English: a hit in prose is a reword, not a workflow step.
|
|
# Not covered: a spaced or underscored spelling ("frontend design"); a bare name with no
|
|
# backticks and no "skill" suffix that is not on the list in 3.; mentions inside scripts
|
|
# (brand/scripts/extract-colors.cjs names ai-multimodal three times as a hint, one of them
|
|
# "if installed" - not a workflow step); cli/assets/templates, the CLI's rendered core skill,
|
|
# which neither this step nor the path contract scans (clean at the time of writing); and a
|
|
# bundled-set change - a skill directory added or removed under .claude/skills moves the
|
|
# allowlist with it, which is the intent.
|
|
# grep exit codes as above: for 1. and 3. only 1 (no hits) passes; for 2. hits are fine as
|
|
# long as every name is bundled, and 2 (error) never passes.
|
|
- name: Bundled-skill contract - no unbundled skill or claudekit command references
|
|
run: |
|
|
[ -d .claude/skills ] || { echo "::error::.claude/skills is missing - the bundled set cannot be derived"; exit 1; }
|
|
bundled=$(find .claude/skills -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | LC_ALL=C sort)
|
|
[ -n "$bundled" ] || { echo "::error::.claude/skills has no skill directories - the bundled set cannot be derived"; exit 1; }
|
|
status=0
|
|
rc=0; LC_ALL=C grep -rnIP --include='*.md' '(?<![\w/])/ckm?:' .claude/skills cli/assets/skills || rc=$?
|
|
if [ "$rc" -ne 1 ]; then
|
|
echo "::error::claudekit command namespaces (/ck:, /ckm:) are not shipped by this plugin - name the bundled skill instead (see #474 finding 2); grep rc=$rc"
|
|
status=1
|
|
fi
|
|
rc=0; hits=$(LC_ALL=C grep -rzoIP --include='*.md' '`[a-z0-9-]+`(?:\*\*)?(?=(?:(?:,\s+|\s+and\s+|,\s+and\s+|\s+or\s+|,\s+or\s+|/|\s+→\s+|\s+->\s+)(?:\*\*)?`[a-z0-9-]+`(?:\*\*)?)*\s+(?:sub-)?[Ss]kills?\b)' .claude/skills cli/assets/skills | tr '\0' '\n'; exit "${PIPESTATUS[0]}") || rc=$?
|
|
if [ "$rc" -eq 2 ]; then echo "::error::grep failed while scanning for skill references (rc=2)"; status=1; fi
|
|
unbundled=0
|
|
while IFS= read -r hit; do
|
|
[ -n "$hit" ] || continue
|
|
name=$(printf '%s\n' "$hit" | sed 's/.*`\([^`]*\)`.*/\1/')
|
|
if ! printf '%s\n' "$bundled" | grep -qxF -- "$name"; then
|
|
printf '%s\n' "$hit"
|
|
unbundled=1
|
|
fi
|
|
done <<< "$hits"
|
|
if [ "$unbundled" -ne 0 ]; then
|
|
echo "::error::skill references outside the bundled set ($(printf '%s' "$bundled" | tr '\n' ' ')) fail under a plugin or CLI install - implement the step inline, name a bundled skill, or drop the step (see #474 finding 2)"
|
|
status=1
|
|
fi
|
|
rc=0; LC_ALL=C grep -rniIP --include='*.md' '\b(frontend-design|ai-artist|ai-multimodal|chrome-devtools|assets-organizing|project-management)\b' .claude/skills cli/assets/skills || rc=$?
|
|
if [ "$rc" -ne 1 ]; then
|
|
echo "::error::claudekit-only skill names are not shipped by this plugin - implement the step inline, name a bundled skill, or drop the step (see #474 finding 2); grep rc=$rc"
|
|
status=1
|
|
fi
|
|
if [ "$status" -eq 0 ]; then echo "OK: shipped skill docs reference only bundled skills"; fi
|
|
exit "$status"
|