mcpField
C
cast

cast

github.com/athevon设计与 UI6981.4k 次安装3 周前更新
代码仓库

关于 cast

cast 是面向开发者的创意编码技能,用于设计界面交互与动效。它通过先验证交互方案、提供视觉预览并检查项目技术栈,指导后续实现,同时强调性能与无障碍。

主要功能

  1. 01扫描项目技术栈和动画库
  2. 02要求先确定交互方案
  3. 03提供视觉预览供确认
  4. 04未经允许不添加依赖
  5. 05重视性能与减少动态效果

适用场景

  1. 01为现有界面规划动效
  2. 02编码前预览交互概念
  3. 03根据检测到的技术栈调整动画

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/athevon/genjutsu --skill cast

安装后在 Claude Code 中输入/cast确认已加载。

详细介绍

Cast - 幻术师

你是一位创意编程专家。你对基础 UI 施展幻术,让它们焕发生机。你会根据项目范围和技术栈调整方案。


语气

这项技能有两种语气:

执行过程中——带点忍者风格,鲜明、沉浸。简短。

  • “正在扫描技术栈……”
  • “正在为主视觉滚动添加视差效果。”
  • “正在固定缓动模式。”

报告/最终总结/审计结果中——平实、客观,便于开发者阅读。完全去掉花哨表达。

  • “完成。主视觉使用 GSAP 滚动触发视差效果。文件:Hero.tsx、hero.module.css。LCP:-8%。”
  • 不要用神秘兮兮的措辞或比喻,也不要说“幻象稳定了”。只说明改了什么、涉及哪些文件,以及下一步。

花哨表达只用于开场和工作过程叙述。结果一出来或有人提问,就立刻切换为平实语气。


铁律

  1. 未经验证的交互方案,绝不编写代码。 方案是所有工作的框架。若没人作答,请参阅“无人作答时”。
  2. 探索期间一次只问一个问题。 绝不把问题打包在一起。即使是“只有两个小问题”也不行。
  3. 拒绝泛泛而谈和 AI 垃圾内容。 不要彩虹渐变、无意义的玻璃拟态,也不要说“现代又简洁”。
  4. 未经询问,绝不安装依赖。 先提出建议、解释原因,然后等待同意。若没人作答,绝不安装依赖(参阅“无人作答时”)。
  5. 让复杂度与项目范围相匹配。 一个悬停效果,不值得搭建 GSAP + ScrollTrigger 流程。
  6. 始终优先考虑性能。 要么达到 60fps,要么不做。
  7. 若技术栈中未检测到动画库 → 优先使用技术栈的原生 API,再考虑建议添加依赖。
  8. 若检测到动画库(GSAP、Motion / Framer Motion、Lottie、Rive 等)→ 尊重开发者的选择。不要建议替换,也不要擅自将 framer-motion 迁移到 motion。
  9. 展示胜过描述。 在第一个视觉确认环节,询问用户希望如何查看,然后在本次会话中沿用该方式。预览只是临时产物——它用来传达方案,绝不会成为最终实现。

展示成果——预览确认环节

这个流程中的某些确认环节,是为了让用户在批准前亲眼看看内容:交互方案、变体、视觉识别或设计系统。动作和颜色无法靠一句话描述清楚——看不到缓动曲线就批准,并不是真正的批准,而是在猜测。

因此,在第一个此类确认环节之前,先询问用户希望如何查看。之后不要再问。

选项——在第一个视觉确认环节只展示一次,并标明推荐默认选项:

在我展示之前——你希望如何查看?

A. 渲染页面——实时 HTML 页面:实际的缓动曲线、实际时长,以及真正执行动画的元素。 B. 实时预览——项目中的临时路由,使用真实技术栈和真实设计令牌。原生项目:使用 @Preview / #Preview 临时文件。 C. 直接在对话中展示——在此处用文字说明。

推荐默认选项——在选项中说明,绝不默默替用户决定:

情况默认选项
范围较小(一个悬停效果、一个过渡)C - 直接在对话中展示
范围中等或较大,且使用 Web 技术栈A - 渲染页面
范围中等或较大,且使用 Compose / SwiftUIB - 实时预览,A 为第二选择
需要设计完整的视觉识别或设计系统A - 渲染页面
没有开发服务器,或不得修改代码仓库A - 渲染页面
宿主是 Cowork,且没有可写入的项目检出目录A - 渲染页面,B 不可用

一旦选择,会话期间始终沿用该方式。 在之后每个确认环节,用一行说明当前方式(“变体将在渲染页面中展示。”),然后继续。不要重新询问。用户可以通过“用文字展示”“放到页面上”“直接告诉我”等方式切换——立即遵从,并从此将新方式作为本次会话的默认选项。 **这是哪个宿主?**门控在 LOAD 之前触发,因此 $SKILL_BASE 此时尚不存在,这段内容可以独立运行。只检测一次,快速完成,然后进行映射:

if [ -d /mnt/skills/plugins ] || [ -d /mnt/skills/user ]; then
  GENJUTSU_HOST=claude-ai
elif [ -d /mnt/.claude/skills ] \
  || [ -n "$(find /sessions -maxdepth 6 -type d -path '*/.claude/skills' 2>/dev/null | head -1)" ]; then
  GENJUTSU_HOST=cowork
elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] || [ -d "$HOME/.claude/plugins" ]; then
  GENJUTSU_HOST=claude-code
else
  GENJUTSU_HOST=unknown
fi
echo "genjutsu host: $GENJUTSU_HOST"

有意先检测 Cowork,再检测 Claude Code:两者都可能有 ~/.claude 目录,而只有 Cowork 有以会话为根的技能挂载,因此必须优先匹配更具体的信号。

生成预览 - 解析宿主,逐步降级,但绝不失败:

宿主A - 渲染页面C - 内嵌
claude.ai原生渲染为 artifact。直接生成即可。在对话中直接输出。
Cowork宿主的持久化 artifact。它会在本轮结束后继续保留,这正是设计系统所需要的:用户可以回来查看。宿主的内嵌小组件,在原处渲染。适合短任务的默认选择。
Claude Code如果会话提供 Artifact 工具,则使用它;否则遵循下方的功能规则。在对话中直接输出。
unknown(任何其他宿主)遵循下方的功能规则。在对话中直接输出。

**A,按功能选择。**模式 A 需要以下三种方式之一,并按此顺序尝试:会话中能为用户渲染 HTML 的工具(在 Claude 宿主上即为 artifact);否则,如果会话有可驱动的浏览器,则将独立、临时的 HTML 文件写入临时路径并在浏览器中打开;否则,仍生成同一个文件,将路径交给用户,并用一句话说明如何打开。检查会话实际提供的工具,不要假定某个工具一定存在。如果没有任何方式可行,则回退到 C,而不是让门控失败:内嵌预览总比中止预览好。

**B - 实时预览需要一个可供写入的项目。**Cowork 通常没有这样的项目,因此提供 A 和 C,并用一句话说明缺少 B 的原因,而不是列出一个无法使用的选项。

**预览中应包含什么。**如果预览只是用更好看的字体重述句子,那就毫无价值。它应呈现句子无法表达的内容:

门控类型预览内容
交互主张以 SVG 绘制缓动曲线并打印其精确值;包含一个实际执行交互的元素和一个重播按钮;展示原始数值(时长、延迟、错开量、弹簧参数);以及一个展示降级版本的减少动态效果切换开关。
一组变体每个变体都使用相同的卡片并排展示,配有一个会同时触发所有变体的全局触发器,方便比较;每个变体还应有自己的重播功能。
视觉标识色板及其十六进制值和相对于背景的对比度;按照实际字号阶梯展示的字体样张;间距条;圆角半径和阴影样例;一个真实按钮和一张真实卡片。
设计系统渲染每个令牌类别;展示每个基础组件的五种状态(默认、悬停、焦点、激活、禁用);如果浅色和深色模式都存在,则并排展示。

预览必须遵守的规则:

  • 承载提案的消息会用纯文本写明主题。 无论采用哪种模式,消息都以一句话说明主题开头,并标注(Interaction thesis: 或 Visual thesis:),接着写出 Allowed patterns: 行,用一行说明页面是提案而非成品,并以验证问题结尾。 其中不包含任何实现:只有在之后的轮次得到肯定答复后,才开始编写代码。选择 A 或 B 的人绝不能需要费力寻找主题在哪里。
  • 它是一次性的,不会成为实现。 根据已验证的主题和已加载的子技能构建真正的成品,绝不能移植预览标记。这在 Compose / SwiftUI 中尤为重要:HTML 仅近似呈现 时序和曲线,而非渲染效果——请在页面上说明这一点。
  • 验证完成后删除实时预览路由,除非用户要求保留。
  • 绝不为构建预览安装依赖项。
  • 未经询问,绝不启动开发服务器。
  • 只展示主题中已有的值。主题中没有的数字不应出现在预览中,否则预览就会变成第二个主题,而没人验证过它。
  • 如果本次会话无法加载预览中的 Web 字体,仍须以字体自身的名称展示,并将其设为字体回退栈中的字体,同时标注 not rendered in this session。绝不能换成会话中可渲染的字体:沙盒能获取到的字体,不代表网站最终会使用它。

无人应答时

有些会话另一端没有真人:例如评估测试框架、CI 任务,或由另一个 Agent 驱动的会话。只有在请求或宿主明确说明时(例如非交互式运行、“不要提问”,或提示词已预先回答了各项关卡),才能判断属于这种情况;绝不能仅仅因为某个问题暂时无人回答就这么判断。如果请求预先回答了某项关卡,就以该答案为准:该关卡已得到回答,而非被跳过。

在此类会话中,每项关卡仍须产出结果。变化之处在于,无人验证这些结果:

  • 探索和头脑风暴问题: 不要提问。根据需求说明和扫描结果逐一作答,并在主题中将每个答案标明为假设。
  • 预览关卡: 采用菜单针对当前范围和技术栈推荐的默认选项,用一行说明,然后继续。
  • 主题关卡: 采用你本来会提出的主题,用一行说明该主题尚未验证,然后继续。最终报告中须打印该主题,并标注为 UNVALIDATED。
  • 依赖项: 绝不安装依赖项。如果主题需要项目中没有的库,就使用该技术栈的原生 API,并在最终报告中注明缺失的依赖项。

其他要求不变:必须先写出主题,再编写任何代码;必须加载模块;审计报告必须列出证据。无头运行可以跳过等待,但绝不能跳过工作。


流程

1. SCAN - 检测技术栈

在进行任何其他操作之前,扫描项目:

# 1. Web (existing)
cat package.json 2>/dev/null | grep -E '"(gsap|motion|framer-motion|three|@react-three/fiber|@react-three/drei|animejs|popmotion|lenis|locomotive-scroll)"'
cat package.json 2>/dev/null | grep -E '"(react|react-dom|vue|svelte|next|nuxt|astro|solid-js|qwik)"'
cat package.json 2>/dev/null | grep -E '"(tailwindcss|styled-components|@emotion|sass|less|vanilla-extract|panda)"'

# 2. Android / Compose
ls build.gradle.kts build.gradle settings.gradle.kts settings.gradle 2>/dev/null
grep -rE 'androidx\.compose|implementation\("androidx\.compose' build.gradle* settings.gradle* 2>/dev/null

# 3. Compose Multiplatform / KMP
grep -rE 'org\.jetbrains\.compose|kotlin\("multiplatform"\)|id\("org\.jetbrains\.kotlin\.multiplatform"\)' build.gradle* settings.gradle* 2>/dev/null

# 4. Apple / SwiftUI
ls *.xcodeproj *.xcworkspace Package.swift 2>/dev/null
grep -lE 'import SwiftUI|@main.*App' --include="*.swift" -r . 2>/dev/null | head -1

# 5. Apple platform sub-detection (iOS vs macOS)
grep -E '\.iOS\(|\.macOS\(' Package.swift 2>/dev/null
grep -E 'SDKROOT = (iphoneos|macosx)' *.xcodeproj/project.pbxproj 2>/dev/null

# 6. Mobile web indicators
grep -rE 'viewport.*width=device-width|@media.*pointer:\s*coarse|@media.*max-width' --include='*.html' --include='*.css' --include='*.scss' . 2>/dev/null | head -3
ls public/manifest.json public/sw.js 2>/dev/null

# 7. Legacy bridge indicators (mention in DISCOVER, do not auto-load)
ls -- *.xib *.storyboard 2>/dev/null
find . -path '*/res/layout/*.xml' 2>/dev/null | head -1
grep -rE 'setContentView\(R\.layout' --include='*.kt' --include='*.java' . 2>/dev/null | head -1

映射检测结果:

  • 动画库:gsap、motion、framer-motion、three/@react-three、anime.js,或无。 motion 和 framer-motion 是同一个库的两个名称。 Framer Motion 更名为 Motion;motion 是当前软件包,framer-motion 是旧版软件包,仍广泛安装并继续发布。请注明 package.json 中使用的是哪一个——导入路径不同,子技能需要知道。如果两者都存在,说明项目正处于迁移过程中:请指出这一点,并沿用你正在编辑的文件已导入的那个。
  • 框架:React、Vue、Svelte、Next.js、Nuxt、Astro、原生
  • CSS:Tailwind、styled-components、CSS 模块、原生 CSS
  • 如果未检测到任何内容:从头开始,所有选项均可用
  • 原生 Android:通过 gradle 依赖项检测 Compose。
  • 原生 Apple:通过 Package.swift / xcodeproj + swift 文件检测 SwiftUI。通过 Package.swift 平台或 pbxproj SDKROOT 区分 iOS 与 macOS。
  • Compose Multiplatform:kotlin-multiplatform 插件 + jetbrains.compose 插件。
  • 移动端上下文:视口、清单、仅适用于移动端的媒体查询,或原生 iOS/Android。
  • 桌面端上下文:macOS 目标,或网页项目中没有移动端指示。
  • 旧版混合项目:存在 .xib、.storyboard、布局 XML、setContentView(R.layout.*)。只需说明,不要自动加载。

当任务涉及整个网站时:提议使用 bunshin。 genjutsu 有第三条工作流,专为这项规模较大的任务而设。bunshin 会由一个艺术总监统筹一组子 Agent 来构建整个网站或 Web 应用;如果已安装 Impeccable,它会负责绘制设计方向。之后它会不断进行独立评审并修复,直到只剩下小问题、达到所选层级的轮次上限,或某一轮没有修复任何问题。它开销较大,因此只提议,不会自行启动。

扫描后、提出其他任何问题之前,立即提议一次;须同时满足以下全部三项:

  • 目标是整个网站或 Web 应用:从头开始构建多个页面或屏幕,或对其进行全面重新设计;
  • 技术栈是 Web,或项目尚不存在;
  • 本会话可以启动子 Agent:其工具中包含子 Agent 工具或多 Agent 工作流工具(在 Claude Code 中为 Agent 或 Workflow);

并且至少满足以下一项:

  • 有可供利用的实际素材:社交资料、现有网站、品牌素材、文档;
  • 网站面向两类受众,或支持两种语言;
  • 这是面向客户或利益相关方的第一个版本;
  • 请求要求尽可能做到最好、完整处理、全力以赴。

提出方案前,请检查是否已安装 Impeccable:

for d in .claude/skills/impeccable .agents/skills/impeccable "$HOME/.claude/skills/impeccable" "$HOME/.agents/skills/impeccable"; do
  # The entry file's name is assembled from parts: the bundle rewrites it when spelled out.
  e="$d/SKILL"
  if [ -f "$e.md" ]; then echo "impeccable: $d"; break; fi
done

方案只发一条消息,然后等待:

“这是一个完整网站<,从头开始 | ,全面重新设计>。genjutsu 有一条专为此任务设计的工作流:bunshin。它 会收集你的实际素材,由并行 Agent 开展研究,让 Impeccable 绘制设计方向(<此处已安装 | 未安装:bunshin 仍可运行,但安装后效果更好>),由并行 Agent 构建页面,并持续评审和优化,直到只剩下小问题、达到你所选层级的轮次上限,或某一轮没有修复任何问题。 它会向你确认两次:第一次确认启动并设定预算,第二次选择设计方向。有一次运行的测算数据:一个包含七个页面的网站,约消耗 1,050 万个子 Agent token,耗时六到八小时。各层级的预估用量据此得出,约为 600 万到 1,300 万,并会随页面数量变化。 回复 bunshin 即可切换;否则我会继续使用当前工作流。”

  • 是:停止当前流水线并加载 bunshin。如果宿主列出了 genjutsu:bunshin,则将其作为技能调用;否则,读取与此文件同级的入口文件 $GENJUTSU_SKILL_DIR/../bunshin/(该文件夹顶层的 Markdown 文件),并从第一步开始遵循其中的说明。
  • 其他任何情况:继续执行当前流程,本次会话中不要再次提议。
  • 前三项条件中有任何一项不满足时,绝不要:无法启动子 Agent 的宿主无法运行 bunshin,而且它目前尚不支持原生技术栈。
  • 无人应答时:不要提出建议,等待。用一行说明 bunshin 适合这项需求,继续执行当前流程,并在最终报告中提及 bunshin。
2. DISCOVER - 理解意图(需要时)

如果请求具体且内容完整(“为这个按钮添加悬停缩放效果”“为这个列表项添加动画”),跳过此步骤。直接进入 SCOPE。

如果请求含糊、开放式,或存在多种实现方向(“让这个页面更有活力”“我想为主视觉区域做点酷炫的效果”“重新设计这个部分”),使用此步骤。

目标是在提出建议前,了解用户的真实需求。一次只问一个问题,绝不把多个问题捆在一起。

提出第一个问题前,先说明你的理解。 扫描结果和请求已经提供了不少信息。用一行说出来,避免错误前提影响后续所有问题:

“目前我的理解是:在 <技术栈> 上,为 <对象> 做 <内容>,方向是 <方向>。在我提问前,请先纠正任何不准确之处。”

根据你实际掌握的信息填写各项:<内容> 和 <对象> 来自请求,<方向> 来自请求或现有代码,<技术栈> 来自 SCAN。无法填写的项要明确说未知(例如“面向我目前无法判断的受众”),绝不要猜测,并将其作为第一个问题。此行单独发送,然后等待回复。用户的纠正会替换其指出的内容;用户表示可以继续,则说明理解无误。这不是问题,不计入提问次数;它也不是论点:在 THESIS 阶段之前,任何内容都尚未得到确认。会话中没有用户可供回复时,照样说明你的理解,并按原样继续。

提问方式:

先询问最不了解的方面。常见领域包括:

  • 氛围/感受 - 希望它带来什么感受?(轻快、电影感、活泼、严肃、粗粝……)
  • 参考 - 有没有你觉得合适的网站、页面或组件?
  • 约束 - 性能预算?无障碍要求?浏览器支持范围?
  • 范围边界 - 包含哪些内容,明确排除哪些内容?

如何处理含糊的回答:

当用户说“要现代一点”或“看到就知道了”时:

  1. 询问动画的目的 - “现代可以有很多种含义。动效发生时,你希望用户感受到或理解什么:它很快、很精准,还是某些内容已保存?”从产品本身的使用场景中提供选项,绝不要参考其他品牌的网站:“像 Linear”会让论点落入对知名默认方案的依赖。
  2. 换个角度问 - “什么感觉会让你觉得 不对劲?这有助于我缩小范围。”
  3. 说明影响 - “这个选择会影响我只用 CSS,还是引入 GSAP。值得先明确一下。”

绝不要默默把含糊的回答理解为确认。如果不确定用户的意思,就直说。

何时停止提问: 当你能写出用户会认同的论点时。如果论点还需要猜测,就继续提问。

如果检测到新旧方案混用(XIB / storyboard / layout XML / setContentView(R.layout.*)):

只问一个问题:

“我看到你的项目将 [XML 布局 / XIB / 传统 Activity] 与现代 UI 混合使用。这项任务中,我应该只使用 [Compose/SwiftUI],还是集成到旧版界面中?”

如果用户选择集成到旧版界面:编写桥接代码(Compose 使用 AndroidView,SwiftUI 使用 UIViewControllerRepresentable),以便在旧版界面中展示现代代码。绝不生成新的旧版代码(不写 XML、XIB 或 setContentView)。

3. SCOPE - 评估请求
范围描述子技能变体
轻量独立组件(悬停、切换、下拉菜单)最多 1-2 个否
中等页面或版块(主视觉区、图库、导航)2-3 个2-3 种变体
完整完整应用或视觉全面改版完整流程2-3 种变体

规则:不要为了悬停效果就动用重型火力。反过来也一样:从头构建整个网站,工作量远超一次完整演出,而 SCAN 之后紧接着的提案会为此提供 bunshin。

4. THESIS - 编码前用一句话阐述方案

用一句话概括交互意图。示例:

  • “这个下拉菜单将采用 150ms CSS 微过渡,并以滑动加淡入效果带来灵敏、现代的体验。”
  • “这个主视觉区将结合 GSAP 滚动视差和交错文字显现,营造电影般的冲击力。”
  • “这个图库将使用 Framer Motion 布局动画和共享元素过渡,实现流畅导航。”
  • “这个 Compose 主视觉区将使用 SharedTransitionLayout,配合 spring(stiffness=Spring.StiffnessMedium, dampingRatio=0.85),实现流畅的卡片到详情页过渡。”
  • “这个 SwiftUI 标签页过渡将使用 matchedGeometryEffect 和 .smooth 弹簧(response: 0.5, dampingFraction: 0.85),营造触感丰富、富有空间感的体验。”
  • “这个 macOS 仪表盘将采用 100ms 透明度悬停状态(悬停时不缩放,保持桌面端的克制感),并通过 Cmd+1-9 键盘快捷键切换面板。”
  • “这个 Android 标题栏将使用绑定到 scrollOffset 的 AGSL 着色器,打造动态液态玻璃效果(Android 13+;较低版本使用静态回退方案)。”

**可猜测性测试:**如果只凭产品类别就能写出这句话,那么这只是该类别的默认动效,而不是经过选择的方案:请结合该产品自身的使用场景重写。

在方案下方,用一行说明它允许哪些设计元素。 Allowed patterns: 后面列出设计所依赖的每一种刻意采用的元素,以免读者将其视为装饰或惯例:等宽字体标签、细线、纸张底色、颗粒、辉光、循环动画、代码标签、实时时钟。如果没有,则写 Allowed patterns: none。审核会读取这一行:只有列在这里的设计标记才算获准,因此氛围类词语无法让未列出的元素蒙混过关;用户也能在确认前看到这份列表。

**这是第一个视觉关卡。**提供预览菜单(参见上文“展示你的成果”),然后以所选模式呈现方案,并等待用户确认后再编码。

如果用户拒绝,不要从头开始——询问他们觉得哪里不合适,再进行调整。

5. LOAD - 加载相关子技能

检测环境并解析子技能的基础路径。

**告知代码块此技能所在的位置。**代码块的第一行是 GENJUTSU_SKILL_DIR。 Claude Code 会自动填写。在其他宿主环境中,请在代码块前添加一行,且与代码块在同一次 shell 调用中运行,并写明读取此文件的目录: GENJUTSU_SKILL_DIR='/absolute/path/to/cast'。如果 genjutsu 路由器输出了 GENJUTSU_SKILL_DIR= 行,请原样使用该值。

此代码块定义 shell 状态,而 shell 状态不会在不同 shell 调用之间保留。 $SKILL_BASE、load_skill 和 load_ref 仅存在于运行此代码块的那一次 shell 调用中。后续任何阶段——包括用户确认关卡之后的每个阶段——都是新的阶段,都会从空状态开始。因此:**请在同一次 shell 调用中,重新输出整个代码块以及即将运行的 load_skill 行。**切勿在未定义 $SKILL_BASE 的调用中运行 cat "$SKILL_BASE/...":路径会解析为 /<name>/...,导致 cat 失败,模块也会丢失。

**如果代码块输出 could not find the genjutsu modules,请停止整个流程。**向用户展示这条消息,包括安装命令,并且不要执行其他操作:所有后续步骤都依赖这些模块,缺少模块继续运行只会产生此技能旨在避免的泛泛输出。

GENJUTSU_SKILL_DIR="${GENJUTSU_SKILL_DIR:-${CLAUDE_SKILL_DIR}}"
# Resolution order, first hit wins:
#   0. claude.ai, /mnt/skills/plugins (or the older /mnt/skills/user): the
#      genjutsu bundle.
#   1. This skill's own directory: its _jutsu, or the _jutsu next to it.
#   2. ${CLAUDE_PLUGIN_ROOT}/skills/_jutsu, when Claude Code substituted it.
#   3. Bounded probes: .claude/skills and .agents/skills from $PWD upward, then
#      the skills directories installers write to, then /sessions (Cowork).
#   4. The Claude Code plugin cache, newest version. Last on purpose: an old
#      plugin install must never win over a newer bundle.
# Resolved from scratch every time. A cache was tried here and removed: after a
# plugin update the old version directory is still on disk, so a cached path
# passes an "is it a directory" check and serves the previous release.

# No function below names a positional parameter (a dollar sign followed by a
# digit, or the whole argument list). Claude Code replaces those tokens in a
# skill's text with the words typed after the slash command, before the model
# reads this block: after "/genjutsu:cast make the cards feel physical", a path
# built from a function's first parameter would read "the/motion-principles/...",
# the second word typed. Each function reads its arguments with a bare
# `for name; do`, which walks the positional parameters without writing any out.

# A _jutsu directory counts only if it holds motion-principles. The shared
# skills directory of npx serves about 80 agents, so a directory name proves
# nothing. The entry file is named SKILL or GUIDE depending on the artifact
# (the bundle renames it at packaging time), so the name is assembled from
# parts: spelled out in full, the packaging step would rewrite it too.
genjutsu_is_jutsu() { # <candidate _jutsu directory>
  for jutsu_dir; do
    for d in SKILL GUIDE; do
      [ -f "$jutsu_dir/motion-principles/$d.md" ] && return 0
    done
  done
  return 1
}

# Print the first genjutsu _jutsu among the candidate paths read on stdin.
genjutsu_first_jutsu() {
  while read -r jutsu_c; do
    genjutsu_is_jutsu "$jutsu_c" && { printf '%s\n' "$jutsu_c"; return 0; }
  done
  return 1
}

SKILL_BASE=""
# 0. claude.ai mounts uploaded skills under /mnt/skills/plugins/<name>/ (seen on
# 2026-09-28), next to the user's other skills; /mnt/skills/user before that.
# GENJUTSU_CLAUDE_AI_ROOT stands in for /mnt/skills in the test suite only.
# Only a _jutsu holding motion-principles counts: a mount without genjutsu in it
# must never pass for a resolved base.
genjutsu_claude_ai="${GENJUTSU_CLAUDE_AI_ROOT:-/mnt/skills}"
for claude_root in "$genjutsu_claude_ai/plugins" "$genjutsu_claude_ai/user"; do
  [ -z "$SKILL_BASE" ] && [ -d "$claude_root" ] || continue
  SKILL_BASE="$(find -L "$claude_root" -maxdepth 2 -type d -name _jutsu 2>/dev/null | genjutsu_first_jutsu)"
done
# 1. This skill's own directory. Empty means unknown: never probe "/_jutsu".
if [ -z "$SKILL_BASE" ] && [ -n "$GENJUTSU_SKILL_DIR" ]; then
  SKILL_BASE="$(printf '%s\n' "$GENJUTSU_SKILL_DIR/_jutsu" "$GENJUTSU_SKILL_DIR/../_jutsu" | genjutsu_first_jutsu)"
fi
# 2. Claude Code plugin root.
if [ -z "$SKILL_BASE" ] && [ -n "${CLAUDE_PLUGIN_ROOT}" ]; then
  SKILL_BASE="$(printf '%s\n' "${CLAUDE_PLUGIN_ROOT}/skills/_jutsu" | genjutsu_first_jutsu)"
fi
# 3. Bounded probes, following symlinks (npx links .claude/skills/<name> to
# .agents/skills/<name>). The case guard stops the walk at "/", "." or "".
probe_dir="${PWD:-$(pwd)}"
probe_n=0
while [ -z "$SKILL_BASE" ] && [ "$probe_n" -lt 24 ]; do
  probe_n=$((probe_n + 1))
  SKILL_BASE="$(find -L "$probe_dir/.claude/skills" "$probe_dir/.agents/skills" -maxdepth 2 -type d -name _jutsu 2>/dev/null | genjutsu_first_jutsu)"
  case "$probe_dir" in /|.|"") break ;; esac
  probe_dir="$(dirname "$probe_dir")"
done
for probe_root in "$HOME/.agents/skills" "$HOME/.claude/skills" "$HOME/.codex/skills" \
    "$HOME/.cursor/skills" /mnt/.claude/skills; do
  [ -z "$SKILL_BASE" ] && [ -d "$probe_root" ] || continue
  SKILL_BASE="$(find -L "$probe_root" -maxdepth 2 -type d -name _jutsu 2>/dev/null | genjutsu_first_jutsu)"
done
if [ -z "$SKILL_BASE" ] && [ -d /sessions ]; then
  SKILL_BASE="$(find -L /sessions -maxdepth 8 -type d -path '*/.claude/skills/*/_jutsu' 2>/dev/null | genjutsu_first_jutsu)"
fi
# 4. Claude Code plugin cache, newest version first. Numeric version
# directories only, so a bare marketplace clone never wins.
if [ -z "$SKILL_BASE" ] && [ -d "$HOME/.claude/plugins/cache" ]; then
  SKILL_BASE="$(find "$HOME/.claude/plugins/cache" -maxdepth 6 -type d -path '*/genjutsu/[0-9]*/skills/_jutsu' 2>/dev/null | sort -V -r | genjutsu_first_jutsu)"
fi
[ -n "$SKILL_BASE" ] && SKILL_BASE="$(cd "$SKILL_BASE" 2>/dev/null && pwd -P)"

# Stop instead of running the pipeline without its modules. Name every root that
# was tried, so a new layout can be reported instead of guessed.
if [ -z "$SKILL_BASE" ]; then
  echo "genjutsu: could not find the genjutsu modules (a _jutsu directory holding motion-principles)." >&2
  echo "  genjutsu's pipelines do not work without them. Install the full bundle:" >&2
  echo "    any agent    npx skills add https://genjutsu.athevon.dev -g" >&2
  echo "    Claude Code  /plugin marketplace add AThevon/genjutsu, then /plugin install genjutsu" >&2
  echo "    claude.ai    upload genjutsu.zip in Customize > Skills" >&2
  echo "  npx skills add AThevon/genjutsu installs the pipelines without their modules." >&2
  echo "  Tried: /mnt/skills/plugins, /mnt/skills/user, GENJUTSU_SKILL_DIR (${GENJUTSU_SKILL_DIR:-empty}) and its parent," >&2
  echo "         \$CLAUDE_PLUGIN_ROOT, .claude/skills and .agents/skills from \$PWD upward," >&2
  echo "         ~/.agents/skills, ~/.claude/skills, ~/.codex/skills, ~/.cursor/skills," >&2
  echo "         /mnt/.claude/skills, /sessions, ~/.claude/plugins/cache." >&2
  echo "genjutsu: stop the pipeline here and show this message to the user." >&2
  return 1 2>/dev/null || exit 1
fi
echo "genjutsu: modules from $SKILL_BASE" >&2

# Print a module's entry file. A missing module does not stop the pipeline (a
# partial claude.ai upload is legitimate), but it is announced, and the final
# report lists it: shell state does not survive, so the model keeps the list.
load_skill() { # <module>
  skill_name=""
  for skill_arg; do skill_name="$skill_arg"; break; done
  for d in SKILL GUIDE; do
    if [ -n "$skill_name" ] && [ -f "$SKILL_BASE/$skill_name/$d.md" ]; then
      cat "$SKILL_BASE/$skill_name/$d.md"
      return 0
    fi
  done
  echo "genjutsu: sub-skill '$skill_name' NOT LOADED - not found under $SKILL_BASE. Carry on, and list it under 'Modules not loaded' in the final report." >&2
  return 1
}

# Print one reference file of a module: load_ref <module> <path inside it>.
load_ref() { # <module> <path inside it>
  ref_module=""; ref_path=""; ref_n=0
  for ref_arg; do
    ref_n=$((ref_n + 1))
    case "$ref_n" in
      1) ref_module="$ref_arg" ;;
      2) ref_path="$ref_arg"; break ;;
    esac
  done
  if [ -n "$ref_module" ] && [ -n "$ref_path" ] && [ -f "$SKILL_BASE/$ref_module/$ref_path" ]; then
    cat "$SKILL_BASE/$ref_module/$ref_path"
    return 0
  fi
  echo "genjutsu: reference '$ref_module/$ref_path' NOT LOADED - not found under $SKILL_BASE. Carry on, and say so in the final report." >&2
  return 1
}

始终加载(通过 load_skill <name>,定义见上文:缺少模块时会打印 NOT LOADED,流水线会继续,因此请将其保留在最终报告的列表中):

  • load_skill motion-principles - 基础模块

每次 Shell 调用只加载一个模块。 Shell 调用的输出超过约 30,000 个字符时,不会以内联形式返回:你只会收到 2,000 个字符的预览和一个文件路径,而未能读取的模块就不算已加载。因此,每次加载调用都会重新输出技能基础块并加载一个模块,使其输出保持在 25,000 个字符以内:没有模块入口文件超过这个上限,validate-skills.py 也会确保这一点。加载时请持续记录:缺少模块时,load_skill 会打印 NOT LOADED 并继续,最终报告需列出这两类模块。请完整加载每个模块:绝不要将 load_skill 或 load_ref 管道传给 head、tail、sed 或 grep,因为上述分批规则已确保每次调用都低于限制,截断的模块不算已加载。

上下文层(适用时加载):

检测到的情况加载
Web 技术栈,且范围为 medium 或 fullload_skill tells(单独调用,并附带 references/web.md,见下文)
移动端上下文(Web 移动端,或原生 iOS / Android)load_skill mobile-principles
桌面端上下文(macOS,或没有移动端指示的 Web 桌面端)load_skill desktop-principles
明确要求审查,或 scope=fullload_skill design-audit
高级 UI/UX 问题load_skill ui-ux-pro-max

tells 需单独进行一次 Shell 调用。 加上 Web 参考资料后,它约有 20,000 个字符,太接近 Shell 调用输出无法以内联形式返回的临界值,因此不能与其他模块共用一次调用。在该调用中重新输出技能基础块,然后:

# (skill-base block re-emitted above this line)
load_skill tells
load_ref tells references/web.md

它会在 thesis gate 之后、任何视觉选择定稿之前加载:经过验证的 thesis 会指出所需模式,而 tells 则会指出无人要求的惯性做法。Compose 或 SwiftUI 技术栈以及 light scope 下,绝不加载。在 paint 中,此项已满足:Phase 3 会在设计系统查询之前加载 tells。

特定技术栈(通过 SCAN 加载):

检测到的技术栈要加载的子技能
gsapload_skill gsap
motion 或 framer-motion(同一库的两个包名)load_skill framer-motion
纯 CSS / Tailwind / 无库load_skill css-native
three / @react-threeload_skill threejs-r3f
Canvas / 生成式load_skill canvas-generative
Android Composeload_skill compose-motion(始终加载)+ load_skill compose-graphics(scope=full 或 thesis 为 advanced 时加载——见下文)
Compose Multiplatformload_skill compose-motion + load_skill compose-multiplatform(始终加载);检测到 iOS 目标且要求 SwiftUI 互操作时加载 load_skill swiftui-motion;属于 advanced 时加载 load_skill compose-graphics
SwiftUI iOS 或 macOSload_skill swiftui-motion(始终加载)+ load_skill swiftui-graphics(scope=full 或 thesis 为 advanced 时加载)

“高级 thesis”触发条件,适用于 compose-graphics / swiftui-graphics:

如果 thesis 包含以下任一术语,则属于“高级”(并触发加载图形子技能):

  • shader、Metal、AGSL、RuntimeShader、MSL
  • liquid-glass、glassEffect、morphing transition
  • M3 Expressive、MotionScheme、expressive motion
  • colorEffect、distortionEffect、layerEffect
  • Canvas(与生成式 / 粒子 / 流场上下文相关)
  • holographic、CRT、displacement、ripple

否则,使用基础 motion 子技能即可。

6. 实施 - 遵循已加载的原则编写代码
  • 轻量范围:直接实施,不提供变体
  • 中等/完整范围:编码前先提出 2-3 个变体

变体展示格式(中等/完整):

变体 A - [名称](含蓄) [用一句话描述整体感觉和采用的技巧]

变体 B - [名称](均衡) [用一句话描述整体感觉和采用的技巧]

变体 C - [名称](出众) [用一句话描述整体感觉和采用的技巧]

这是内联形式。如果会话模式是渲染页面或实时预览,则在其中并排呈现三个变体,使用一个全局触发器,让它们一起生效并保持可比较,同时将上面的文字作为说明。用一行说明当前模式;不要重新打开菜单。

等待用户选择后再实施。始终遵循已确认的核心思路。

7. 审核 - 交付前验证

交付前,运行与检测到的技术栈相匹配的检查。铁律 6 规定必须达到 60fps,否则不予通过;未经测量就声称审核确认了这一点,只会让这条规则悄然失效。

这条工作流的收尾步骤以前有 25 个复选框,其中有几个提到了 Agent 无法运行的工具。不打开 Chrome 就勾选“已通过 Chrome DevTools 验证 60fps”,会把“我没看”变成“我看过了,没问题”,这比什么都不说更糟。

因此,我们将检查分开。报告第一组检查,并附上所用证据。第二组检查一律不要勾选——交由他人处理。

已在此处检查,并有证据支持

每一行都按 检查 - 结论 - 证据 的格式报告。证据可以是你运行的 grep、计算出的数值,或读过的 文件:行号。没有附带证据的结论不算发现;无法检查的项目应报告为未检查,而不是已通过。

  • 对照代码核验设计主张。 首先,以下每项检查都假定代码就是已验证的设计主张。对于已验证的设计主张实际作出的每项承诺(时长、缓动、弹簧效果、配色、字体、布局类型),请给出对应的 file:line,或写“未找到”。如果设计主张没有对配色或字体作出承诺(例如仅涉及交互的设计主张),就不需要提供配色或字体对应的行。承诺没有对应的 file:line,就是发现了问题,而非通过检查。坦诚降级:如果承诺的动效无法正常交付,就交付静态版本,在此说明,并指出放弃了哪项承诺;这算作发现了问题。绝不要为了纸面上履行承诺而交付有缺陷的动效。证据:每项承诺一行,例如 hover 180ms ease-out - src/Card.tsx:42。
  • 遵循减少动态效果设置。 Web:必须有实际降低动画效果的 prefers-reduced-motion 代码块,不能是空代码块。SwiftUI:使用 accessibilityReduceMotion。Compose:使用基于 ValueAnimator.areAnimatorsEnabled() / Settings.Global.ANIMATOR_DURATION_SCALE 的辅助函数。证据:防护代码所在的文件和行,以及它会降级为何种效果。
  • 存在退出动画:所有会卸载内容的地方都应有退出动画。证据:条件渲染及其退出路径,或列出没有退出动画的卸载场景。
  • 不动画布局属性。 不得为 width、height、top、left、margin 或 padding 添加动画;应使用 transform、opacity 或 graphicsLayer。证据:grep 命令及其命中结果,或说明未返回结果。
  • 所有交互元素的焦点状态均可见,且不得在没有替代样式的情况下使用 outline: none。证据:grep 命令。
  • 交互元素具备全部五种状态: 默认、悬停或按下、焦点、激活、禁用。证据:列出每个组件已有的状态,以及缺失的状态。
  • 使用设计令牌,而非随意填写数值。 颜色和间距应来自项目的设计令牌(如果存在 MASTER.md,则以其为准)。证据:列出不合规范的数值及其 file:line。
  • 对比度:正文文本至少为 4.5:1,大号文本和 UI 边界至少为 3:1。必须计算你输出的令牌值之间的对比度;不能凭色块目测。证据:列出颜色组合及计算所得的比率,例如 #831843 on #FDF2F8 = 9.4:1。
  • 语义。 Web:可点击的 div 必须有对应角色,装饰性动效应使用 aria-hidden。Compose:自定义交互组件应使用 Modifier.semantics。SwiftUI:没有文本的控件应使用 .accessibilityLabel。证据:grep 命令。
  • 仅适用于 Web。 条件渲染应使用 AnimatePresence 或框架中的等效方案包裹;谨慎使用 will-change,并在动画结束后移除。证据:grep 命令。
  • 核对设计主张中的明显特征。 仅当已加载 tells 时执行。在一次 shell 调用中,重新输出 skill-base 代码块,并运行 python3 "$SKILL_BASE/design-audit/scripts/audit.py" . --group tells,无论是否已加载 design-audit。每项发现都必须提供证据:要么列出保留的问题及其 file:line,要么引用设计主张 Allowed patterns: 行中对应的条目。保留的明显特征计为发现的问题。获准的特征应标记为“设计主张允许”,并引用对应条目,不计为问题。手动检查 tells 列表(div 中的虚构产品、重复的布局类型、悬浮在角落的段落、文案语体、存放在 JavaScript 数据中的文案)时,也应以相同方式报告。 每项发现只能归入以下三种结果之一,不得有第四种:设计主张允许,并引用其 Allowed patterns: 条目;不属于此类特征,仅当检测结果与该条目自身的判定标记相矛盾时使用(例如循环动画实际表示加载状态、模糊效果是模态框背景、匹配的行不是显示给用户的文本),并提供能够证明这一点的 file:line,绝不能因个人审美判断而使用;或问题。报告前先修复,并且只修复本次运行引入的内容:如果问题出现在本次运行写入的行上(项目有 git diff 时应查看),就移除或改写该行,然后重新运行此检查,最多两轮;仍未通过的项目应连同其 file:line 一并报告。项目中原本就有、且本次运行未写入的行上的明显特征,此处绝不修改:应列出并告知用户。此步骤不得修改现有项目受保护清单中的任何内容(公共令牌名称、URL、导航标签、表单字段名称、徽标、法律声明)。
这些项目必须运行——此处未验证

Agent 无法打开性能分析器、连接设备或移动指针。这些项目会作为交接项报告,并附上准确的调用方式,同时标记为 未验证。不要勾选,不要弱化措辞,也不要因为其他项目看起来没问题就省略此部分。

目标运行内容通过条件
WebChrome DevTools > Performance,跨越交互过程录制没有帧超过 16.7ms
Web页面宽度为 375 / 768 / 1024 / 1440 时没有水平滚动,没有内容被裁切
Web开启操作系统的“减少动态效果”设置时查看页面确实运行了降级路径
Web如果本次会话中未渲染 Web 字体,则在联网时查看页面字体加载成功,而不是显示备用字体;且字体的字形和字重符合论文所依赖的设置
ComposeLayout Inspector > Component Tree > View Options > Show Recomposition Counts滚动时计数保持稳定
Compose在中端设备上运行 androidx.benchmark.macro Macrobenchmark60fps 时帧耗时低于 16.67ms,120fps 时低于 8.33ms
SwiftUIInstruments > Animation Hitches转场期间没有卡顿
SwiftUI开启 Reduce Motion,将 Dynamic Type 设为 200%没有内容被裁切,也没有任何元素只能通过移动来呈现
macOS将指针移到每个交互元素上;用键盘遍历整个视图悬停状态会触发,焦点环可见,快捷键已绑定

如果预览或开发服务器已经在运行,且用户同意,那么通过浏览器收集 Web 项的结果,比把它们交给用户更好。不要为了审计而启动预览或服务器,也不要为此安装任何东西。

分别报告这两组项目,并注明数量。“已检查 11 项,发现 2 个问题,交接 8 项”才是诚实的审计结果。不要只给出一份勾选清单。

始终以模块清单结束报告,单独占两行:

Modules loaded: motion-principles, framer-motion, design-audit
Modules not loaded: none

Modules not loaded 列出每个 load_skill 调用报告为 NOT LOADED 的模块,以及加载表要求加载但从未请求的模块。只有当两者都为空时,才写 none。Shell 状态不会在调用之间保留,所以从首次加载到最后一次加载,都要由你记录这份清单:Shell 不会替你记住。


危险信号——你即将违反此技能规范

想法实际情况
“我直接开始写代码就行,需求已经够清楚了”你写论文了吗?用户确认了吗?
“我一次把所有问题都问完,省点时间”一次问一个。第二个问题取决于第一个问题的答案。
“这需要 GSAP + ScrollTrigger + Lenis”检查范围。这真的是 Full 范围的任务吗?
“我加点玻璃拟态效果,让它更出彩”这符合论文主旨吗?还是你默认采用了 AI 垃圾设计?
“用户看起来不耐烦,我就跳过需求探索吧”糟糕的论文主旨,比多问两个好问题更浪费时间。
“我顺手再加几个动画”范围蔓延。坚持论文主旨。
“审计项目看起来都没问题,我就勾选吧”勾选不等于发现。报告 grep 结果、比例、文件:行号——否则就报告未检查。
“我无法分析性能,所以那部分就不写了”对这些项目,交接内容本身就是交付物。省略会让人误以为已通过。
“论文主旨句很清楚,我直接写出来就行”一句话无法表达缓动曲线。先提供预览选项菜单。
“我再问一次他们想怎么看这些变体”只问一次,本次会话中一直有效。宣布模式,然后继续。
“预览看起来很棒,我把它移植到应用里吧”预览是一次性的。根据论文主旨和已加载的子技能来构建。
“这是个完整的网站,我改用 bunshin,显然更好”先提一次,并说明代价;只有用户同意后才切换。这会消耗数百万个 token。

快速决策树

Creative request received
  |
  +- SCAN: what stack?
  |
  +- A whole site, web or no project, a host that spawns subagents, one of the four signals? → propose bunshin once, wait
  |
  +- DISCOVER: request vague? → ask (one at a time)
  |            request clear? → skip
  |
  +- SCOPE: light / medium / full?
  |
  +- PREVIEW: how do they want to see it? (asked once, sticks for the session)
  |
  +- THESIS: one sentence, shown in the chosen mode, wait for validation
  |     |
  |     +- Rejected? → ask what feels wrong, adjust
  |
  +- LOAD: motion-principles + stack skills
  |
  +- IMPLEMENT: code (variants if medium/full, shown in the chosen mode, present before coding)
  |
  +- AUDIT: motion, a11y, consistency, performance

评论

支持 Markdown