重开仓库内容:改推五个技能(browser-harness / humanizer / humanizer-zh / product-planning / session-mechanism)
按授权清空原有内容后重新提交(原 oil-ui-pro 一并移出,可从历史恢复)。 browser-harness 剔除 .venv 等运行环境;根 .gitignore 补记 .venv/ 与 node_modules/。
This commit is contained in:
1 parent
9a152a1952
commit
777f7fe5d0
240 files changed
+52518
-4409
No files matched your search
@@ -32,10 +32,10 @@ description: 产品规划总入口(唯一入口),四段式调度:① 产
|
||||
|
||||
> ⚠️ **2026-10-06 交接口口径反转**:②→③ 原写的是「⛔ 不许给页面结构」,**已废**。
|
||||
> **新口径:②段钉骨架,③段做皮肉。** 用户原话:「3 是负责设计和交互,**页面关系和布局必须在第二步确认清楚,不然第三步没有方向 一会一个样子**」。
|
||||
> ⇒ **②段必须给**:有哪几页、每页几个板块、板块怎么排、跨页关系。**③段⛔ 不许增删移动板块**,缺板块回②段改 PRD。
|
||||
> ⇒ **②段必须给**:有哪几页、每页几个板块、板块怎么排、跨页关系。**③段⛔ 不许增删移动板块**,缺板块回②段改 ②段。
|
||||
> ⛔ **仍归③段**:配色 / 字体 / 间距 / 组件样式 / 动效,以及每个板块的视觉与交互实现。
|
||||
|
||||
> ⚠️ **②→③ 仍是本流水线最容易被搞坏的一环**,但病灶换了位置:旧病灶是「PRD 说"必须能**读到**",③段读成"必须**常驻主屏**"」→ 五类信息全铺一屏 → 信息墙。
|
||||
> ⚠️ **②→③ 仍是本流水线最容易被搞坏的一环**,但病灶换了位置:旧病灶是「②段 说"必须能**读到**",③段读成"必须**常驻主屏**"」→ 五类信息全铺一屏 → 信息墙。
|
||||
> **分档表就是为这句歧义而生的**:它把"读到"明确成"一次点击内可达"这一档。**该纪律照旧有效**,只是现在**骨架不由③段独立推导,而是②段给定、③段在骨架内兑现分档**。
|
||||
|
||||
> 💡 **③段为什么在骨架给定后仍然不会"退化成照抄"**:它做的仍是**另一类工作** —— 骨架是灰块(有哪些块、什么顺序),③段要给的是**视觉与交互实现**(怎么排版好看、点哪有什么反馈、空态怎么呈现、响应式怎么变)。**两者不是同一样东西的粗细两版,是两个维度。**
|
||||
@@ -103,7 +103,7 @@ description: 产品规划总入口(唯一入口),四段式调度:① 产
|
||||
|---|---|---|
|
||||
| 页面设计全链(规范 → 原型 → 审查) | ③ 段 `stage-delivery` 自己的 D0→D5 流水线(判据与素材由两个页面设计技能供给) | 不要另找风格库、也不要自己手搓一套 token 与门禁 |
|
||||
| 评审用的可点原型 / 生产级前端页面 | ③ 段 3b(按 D2 构建,单文件 HTML 交付;**先判页型**:营销页取 `open-design` 技能的 `design-templates/web-prototype/`,**工具型页面取 ③段 `references/layouts-tooling.md`**) | ⛔ **不要不判页型就套 `web-prototype` 种子** —— 那是营销落地页骨架,会把后台做成营销页 |
|
||||
| 设计规范文件 | ③ 段 3a 生成 `DESIGN.md`(令牌表由 `open-design` 技能选定套的 `tokens.css` 产出 + 该项目 PRD 的界面需求) | 不要凭空编一套 token |
|
||||
| 设计规范文件 | ③ 段 3a 生成 `DESIGN.md`(令牌表由 `open-design` 技能选定套的 `tokens.css` 产出 + 该项目②段的界面需求) | 不要凭空编一套 token |
|
||||
| 架构图 / 流程图 / 时序图 | `diagram-design` | **不要手写 Mermaid** |
|
||||
| 状态迁移的守卫/副作用/并发 | `state-machine` | diagram-design 不覆盖这些,别指望它 |
|
||||
| 审美方向 / 风格定调 | `open-design` 技能里**选一套**(读候选套 `DESIGN.md` 第 1 章,按页面类型挑) | 不要用"米白+衬线+陶土色"这类默认审美,也不要凭感觉编 |
|
||||
@@ -120,6 +120,20 @@ description: 产品规划总入口(唯一入口),四段式调度:① 产
|
||||
|
||||
**DESIGN.md 说明**:`@google/design.md` 提供 `lint` / `diff` / `export` / `spec` 四个子命令,目前是 alpha(v0.4.0)。在非 TTY 的管道里可能不回显输出,需在真实终端里确认结果。**本机实测该命令完全不可用,不要拿它当校验关卡**;改由 D3 收口与 D5 终检**按真实像素人工复测**对比度(判据见 `open-design` 技能的 `craft/color.md`)。⚠️ 原两把门禁脚本(静态文案检查 / 渲染机检)**已随旧主干从磁盘移除**,当前**没有机器复现** ⇒ 结论只能记「人工判定」,不得写成「脚本已通过」。
|
||||
|
||||
## 可调用工具(自然语言触发)
|
||||
|
||||
> 两个「随段携带」的独立工具,装在 ③段 `references/stage-delivery/assets/` 下;按用户说法直接触发,产物落项目目录。它们**不是**新的段,只是叫得动的工具。
|
||||
|
||||
| 用户说 | 触发工具 | 做什么 | 产物 |
|
||||
|---|---|---|---|
|
||||
| 「**分析 XXX 视频**」「把这个视频抽帧」「拆一下这条视频的画面」 | `stage-delivery/assets/video-capture/` | 取视频(本地文件 / 直链 URL)+ 抽帧(双因素 + 首尾 + 补帧) | 关键帧 `frames/` + `frame_times.json`(供 ①竞品分析 / ③视觉参考) |
|
||||
| 「**获取 XXX 网站的设计风格**」「抓这个网站的风格/组件/配色」 | `stage-delivery/assets/design-capture/` | 抓网页设计系统(色彩 / 排版 / 组件)→ 生成样式模板 | 一套 `design-system-<名>/`(与 `design-system-tiaoyue` 同构,可直接被 ③段 D1 选用) |
|
||||
|
||||
- 两者都**独立、可单跑**:`video-capture` 只依赖 `ffmpeg`;`design-capture` 只依赖本机 Chrome(CDP)+ `vendor/` 里搬运的抽取脚本,**⛔ 不依赖 open-design daemon**。
|
||||
- 用法与边界见各自 `SKILL.md`;来源与署名见各自 `ATTRIBUTION.md`。
|
||||
- 触发时机:①②段拆竞品视频 → `video-capture`;③段要新建样式或拆参考站 → `design-capture`。
|
||||
- ⛔ 两工具都**只做合规最小集**(不抓平台页内视频、⛔ 不调付费接口);抽取结果须先目视比对再采用。
|
||||
|
||||
## 进入方式
|
||||
|
||||
| 你说什么 | 模式 | 从哪开始 |
|
||||
@@ -127,7 +141,7 @@ description: 产品规划总入口(唯一入口),四段式调度:① 产
|
||||
| "规划 XX" / "从零做 XX" | **全流程(自动处理)** | `stage-discovery` → `stage-requirements` → `stage-delivery` → `stage-proto-doc`,**一路跑完,段间不停下来问**(含 ① 段末) |
|
||||
| "先确认方案" / "跑完方案停下等我" / "规划 XX,先给方案" | **方案确认** | 同全流程、同一套四段,**唯一差别是 ① 段末停下等你确认方案**,确认后才进 ②③④ |
|
||||
| "只做产品需求" / "只做产品功能" / "只做界面交互" / "只做原型说明文档" | **单段** | **只调对应那一个 stage skill**,不展开其他段,也不催促回补前段 |
|
||||
| 已有 PRD,要出原型 | 单段 | 直接调 `stage-delivery`,从它的 3a 起 |
|
||||
| 已有 ②段,要出原型 | 单段 | 直接调 `stage-delivery`,从它的 3a 起 |
|
||||
| 已有原型,要补说明 | 单段 | 直接调 `stage-proto-doc`,从它的 4a 起 |
|
||||
| 只问某个单点 | 单段 | 不跑任何段,在对应 stage skill 内点名子步,或直接说要看哪份方法论 |
|
||||
|
||||
@@ -196,7 +210,7 @@ description: 产品规划总入口(唯一入口),四段式调度:① 产
|
||||
- 把估算数据写成确凿事实
|
||||
- 跳过 ③ 段 D0 的 Gate-1(未定死令牌表与设计契约)就开写页面代码
|
||||
- 在 `grill-me` 提问模式下仍套用"提问上限 3 个",把提问做成走过场
|
||||
- **只跑一次 `grill-me`**:① 没定义需求就去取证,或 ② 拿①段的需求定义代替功能压测直接写 PRD
|
||||
- **只跑一次 `grill-me`**:① 没定义需求就去取证,或 ② 拿①段的需求定义代替功能压测直接写 ②段
|
||||
- **把自动补全当默认动作**:用户没说话就替用户拍板,还没标 `【假设】`、没进待复核清单
|
||||
- 把 ④ 的说明区写进产品功能范围,或让 ③ 顺手把说明文档一起写了(该调 `stage-proto-doc` 就调)
|
||||
- 手写 Mermaid 代替 diagram-design,手写 token 表代替 DESIGN.md
|
||||
@@ -231,7 +245,11 @@ python scripts/check_naming.py
|
||||
> 例如「②段到底该不该给页面结构」这种反转,**闸门一个字都报不出来**(两版写法都合法)。
|
||||
> ⇒ **口径类改动要靠人工回读**,且**必须把反转理由与用户原话写进文档留痕**。
|
||||
⚠️ 脚本输出里的 `[提示]` 项(旧名出现)**不计入失败**,但要人工看一眼是不是落在「废止理由块」里 —— 落在里面是正当留痕,散落在正文里就是真残留。
|
||||
⚠️ **副本要一起查**(主副本 `product-planning/` + 副副本 `pm-skills/skills/` + 装入副本 `.workbuddy/skills/product-planning/`),否则改主副本忘同步,脚本会直接把差异报出来。
|
||||
🔴 **2026-10-07 起本技能只在全局一份**:`E:/ProgramData/.workbuddy/skills/product-planning/` ——
|
||||
**它就是本体**,⛔ 不再有「主 / 副 / 装入」三副本,也⛔ 不再需要「三处 md5 复核」
|
||||
(那只在有副本时才有意义)。改完**只跑一次**:`check_naming.py --ws <工作区>`。
|
||||
> 用户原话:「**把产品规划复制到 workbuddy 全局skills中 后续维护和使用全局技能**」。
|
||||
> ⚠️ 工作区原先那三份已归档到 `归档/技能迁全局-20261007/`(未直删,可随时取回)。
|
||||
- **把两种模式混用**:在自动处理模式下自作主张停下问方案,或在"方案确认"模式下确认前就开跑 ②③④
|
||||
- **在"方案确认"模式下把 ① 段的方案当成已确认就往下跑**:该模式下 ① 段末必须停下等用户确认(含"不做什么")
|
||||
- **下游擅自裁剪上游已拍板项**:②③④ 发现要砍 ① 段或决策表里已拍板的东西时,不许自行裁掉或改口径,必须列成"与已定决策的差异"交用户显式确认
|
||||
|
||||
@@ -18,8 +18,9 @@ description: 产品规划第③段,界面交互。设计工程供给来自 ope
|
||||
|
||||
| 技能 | 它是什么 | 本段取它什么 |
|
||||
|---|---|---|
|
||||
| **`open-design`** | 网页 App 设计**判据供给库**:153 套设计系统(定调与令牌)+ 13 份工艺判据 + 35 个版式骨架 | **定调 + 数值判据 + 版式骨架**(本段默认主线) |
|
||||
| **`oil-ui-pro`** | 界面设计**方法论**:五步流程(探索方向 → 建结构 → 取实证 → 独立评审打分 → 验证交付)+ 风格对比页与截图工具 | **方向探索 + 对比页 + 独立评审打分**(做多方向比选或要独立评审时) |
|
||||
| **`open-design`**(⚠️ **工作区级**,全局没有) | 网页 App 设计**判据供给库**:153 套设计系统(定调与令牌)+ 13 份工艺判据 + 35 个版式骨架 | **定调 + 数值判据 + 版式骨架**(本段默认主线) |
|
||||
| **`oil-ui-pro`**(✅ **全局级**) | 界面设计**方法论**:五步流程(探索方向 → 建结构 → 取实证 → 独立评审打分 → 验证交付)+ 风格对比页与截图工具 | **方向探索 + 对比页 + 独立评审打分**(做多方向比选或要独立评审时;`open-design` 缺位时也由它顶) |
|
||||
| **本段自带样式库**(✅ **随段携带**) | `assets/design-systems/<选定一套>/` —— **样式风格库,可续加**;当前 1 套:`design-system-tiaoyue`(跳跃视界规范 + 277 个自有组件族) | **定调 + 令牌 + 组件形态** —— 与 `open-design` 那 153 套**同权**,⛔ 二者每次只选一套 |
|
||||
|
||||
> ⭐ **两个技能是「平行同层级」的,可分别独立使用** —— 本段**不把它们缝成一条流水线**,而是按任务取用:多数页面走 `open-design` 主线(快、判据硬);需要「先拉开几个方向让用户挑」或「要一个没看过制作过程的评审打分」时,取 `oil-ui-pro` 的方法与工具。
|
||||
> ⛔ **判据正文仍只在本段**(`references/execution-runbook.md` 与 `references/layouts-tooling.md`)。两个技能都是**素材与方法**,不是规范权威 ⇒ **同一条判据两处都有且口径不同时,以本段为准**,并在 runbook §3.4 标注「本项目追加」。
|
||||
@@ -34,9 +35,28 @@ description: 产品规划第③段,界面交互。设计工程供给来自 ope
|
||||
| **工艺规约**(正向判据 · 数值) | `open-design/craft/` | 字号刻度 / 配色配额 / 状态齐备 / 动效纪律 / 反 AI 味 / 无障碍 | 3a 判据、3d 终检 |
|
||||
| **原型模板**(正向形态 · 版式) | `open-design/design-templates/web-prototype/` | 种子 `assets/template.html` + 8 个版式骨架 + 自查清单 | 3b 构建 |
|
||||
|
||||
> ⚠️ **路径怎么找(2026-10-05 更新,⛔ 不许写死层数)**:两个设计技能都装在**技能仓库 `.workbuddy/skills/`** 下(`open-design/`、`oil-ui-pro/`),③段**按技能名引用它们**,⛔ 不按相对路径爬。
|
||||
> ✅ **正解 = 在技能仓库里找 `open-design/design-systems/` 与 `oil-ui-pro/SKILL.md`**,找到即用。技能仓库是统一的技能加载根,技能之间的引用以它为基准。
|
||||
> ⛔ **反例(实测已踩)**:靠写死的 `../` 层数去爬 —— 本技能有**三份副本、目录深度各不相同**(主副本 `product-planning/`/副副本 `pm-skills/skills/`/装入副本 `.workbuddy/skills/product-planning/`),**只有装入副本与两个设计技能同级**,另两份旁边根本没有 ⇒ 写死层数必然两份落空。
|
||||
**本段自带样式库的那一套(`design-system-tiaoyue`)怎么读**(⚠️ 套内命名与 open-design **不同形**,别按 `DESIGN.md` 去找):
|
||||
|
||||
| 要什么 | 套内路径 |
|
||||
|---|---|
|
||||
| 入口(规范总纲、生成时的参考范围) | `assets/design-systems/design-system-tiaoyue/SKILL.md` |
|
||||
| 令牌(含暗色一套) | 同目录 `assets/tokens.css` + `assets/tokens-dark.css`(色值原始记录 `assets/brand.json`) |
|
||||
| 组件形态 | 同目录 `references/components.md` + `references/design-system.html`(组件族清单 `references/library.json`) |
|
||||
| 已知冲突 / 取舍记录 | 同目录 `references/known-conflicts.md` + `references/capture-notes.md` |
|
||||
|
||||
> ⚠️ **路径怎么找(2026-10-07 更新)**:一律**按技能名引用**(宿主按名加载,与物理位置无关),
|
||||
> ⛔ **不按相对路径爬、⛔ 不写死 `../` 层数**。
|
||||
> **2026-10-07 起两个技能不再同处**:本技能在**全局技能根**(`E:/ProgramData/.workbuddy/skills/`);
|
||||
> `open-design` 现在**只装在工作区**(`<工作区>/.workbuddy/skills/open-design/`)、**全局里没有**;
|
||||
> `oil-ui-pro` 在**全局**。⇒ 更没得爬,只能按名找。
|
||||
>
|
||||
> 🔴 **`open-design` 不可得时的回落**(2026-10-07 用户定案「**先用 oil-ui-pro 试试效果**」):
|
||||
> 用 `oil-ui-pro` 顶「方向探索 / 对比页 / 独立评审打分」三块。**但必须明白它顶不了什么** ——
|
||||
> `open-design` 独有的 **153 套设计系统(定调与令牌)** 与 **13 份工艺判据**
|
||||
> (字号刻度 / 配色配额 / 状态齐备 / 动效纪律 / 反 AI 味 / 无障碍),`oil-ui-pro` **没有**。
|
||||
> ⇒ 缺这块时只有两条路:① 把 `open-design` 装回来(它是可得的技能,只是现在装在工作区级);
|
||||
> ② 明说「本次定调与工艺数值判据**无供给来源**」。
|
||||
> ⛔ **不许凭感觉编一套顶上** —— 那正是本项目反复踩的坑。
|
||||
|
||||
**两者都不依赖付费云 / MCP / API Key 才能用于设计** —— `open-design` 去掉那些之后剩下的全是静态 md / css / html;`oil-ui-pro` 的设计方法本身宿主中立、离线可用(联网仅用于版本检查)。`open-design` 的 daemon、云模型服务、`packages/components` 的 tsx **未收录**。
|
||||
|
||||
@@ -49,7 +69,7 @@ description: 产品规划第③段,界面交互。设计工程供给来自 ope
|
||||
> **旧口径(已废)**:「页面结构归本段,唯一落点是 3a 的 D0 设计契约」。②段只给"必须在界面上发生什么",本段**独立推导**"几个页面、每页放什么"。
|
||||
> **新口径**:**②段钉骨架,③段做皮肉。**
|
||||
>
|
||||
> | 归②段 PRD(本段⛔ 不许动) | 归本段(②段⛔ 不许写) |
|
||||
> | 归②段(本段⛔ 不许动) | 归本段(②段⛔ 不许写) |
|
||||
> |---|---|
|
||||
> | 有哪几页 | 配色 / 字体 / 间距 / 组件样式 / 动效 |
|
||||
> | 每页几个板块、板块怎么排 | 每个板块的**视觉实现**(怎么好看、怎么对齐、什么层次) |
|
||||
@@ -60,13 +80,13 @@ description: 产品规划第③段,界面交互。设计工程供给来自 ope
|
||||
> **用户原话**:「3 是负责设计和交互,**页面关系和布局必须在第二步确认清楚,不然第三步没有方向 一会一个样子**」。
|
||||
>
|
||||
> 🔴 **旧口径为什么站不住**:它把"有哪几页"留在本段 ⇒ **本段做一版一个样,②段手里没有任何东西能判断本段改得对不对**。用户看到的现象就是"一会一个样子"。
|
||||
> ⭐ **本段拿到的骨架是冻结的**:⛔ **不许新增 / 移动 / 删除板块**。**缺板块要回②段改 PRD**(见「硬约束」的对应禁令)。
|
||||
> ⭐ **本段拿到的骨架是冻结的**:⛔ **不许新增 / 移动 / 删除板块**。**缺板块要回②段改 ②段**(见「硬约束」的对应禁令)。
|
||||
>
|
||||
> **仍然保留的两个字段**(它们在**给定骨架之内**决定"怎么摆得好看"):
|
||||
> - **`必要内容`(减法)**:⚠️ **口径已调整** —— 它不再是"决定哪些内容上屏"(那是②段分档表的活),而是**核对**②段给的信息是否都在骨架里有落点;⛔ **本段不得以它为由增删信息档位**。
|
||||
> - **`结构骨架`(加法)**:⚠️ **口径已调整** —— 它不再是"独立决定几个区块",而是把②段给的**板块清单**翻译成**具体的骨架档 / 列宽 / 密度目标**(骨架档选哪个、密度多少,仍是本段的活)。
|
||||
>
|
||||
> ⛔ **本段不许回头改 PRD 的骨架**(那是②段)。⛔ **也不许无视 PRD 骨架自己另起一套** —— 判据:D0 里的页面清单与每页板块,必须与 ②段《界面布局》**逐条对得上**;对不上就停下来问②段要哪一版。
|
||||
> ⛔ **本段不许回头改②段的骨架**(那是②段)。⛔ **也不许无视 ②段骨架自己另起一套** —— 判据:D0 里的页面清单与每页板块,必须与 ②段《界面布局》**逐条对得上**;对不上就停下来问②段要哪一版。
|
||||
|
||||
**顺序 3a → 3b → 3c → 3d。先有规范再出原型,不许反过来;原型能点通后先会诊、再收口打磨。**
|
||||
|
||||
@@ -109,12 +129,19 @@ description: 产品规划第③段,界面交互。设计工程供给来自 ope
|
||||
| 3d 独立评审(**按需**) | 要「一个没看过制作过程的评审打分」时,取 `oil-ui-pro` 的 `references/visual-review.md`(协议)+ `references/tools.md`(截图与对比取证) |
|
||||
| 3d 终检 | `open-design/design-templates/web-prototype/references/checklist.md` + `craft/state-coverage.md` |
|
||||
|
||||
> **定调来源唯一**:`design-systems/` 里**只选一套**作本项目调性来源,套名与选择理由写进 `DESIGN.md`;**不许混用两套**(混用 = 没有调性)。既有项目中凡引用旧风格来源(`minimalist-ui` / 皮肤族 / `ui-ux-pro-max` 检索结果)的 `DESIGN.md` 行,重跑 3a 时一律改写为选定套 + 留痕对照。
|
||||
> 本段 `references/` 只放**流程规则**,不放风格 —— 风格全部来自选定套,避免第二真相源。
|
||||
> **定调来源唯一(每次只选一套)**:可选套来自**两处**,⛔ 每次设计**只选其中一套**、**不许混用两套**(混用 = 没有调性):
|
||||
> ① `open-design/design-systems/`(⚠️ **工作区级**,153 套);
|
||||
> ② 🔴 **本段自带样式库 `assets/design-systems/`**(✅ **随段携带、可续加**;当前 1 套:`design-system-tiaoyue`)。
|
||||
> 套名与选择理由写进 `DESIGN.md`。既有项目中凡引用旧风格来源(`minimalist-ui` / 皮肤族 / `ui-ux-pro-max` 检索结果)的 `DESIGN.md` 行,重跑 3a 时一律改写为选定套 + 留痕对照。
|
||||
> 本段 `references/` 只放**流程规则**,⛔ 不放风格;**随段携带的样式风格一律放 `assets/design-systems/<套>/`**
|
||||
> (🔴 2026-10-07 用户令两条:「把 `design-system-tiaoyue` 整合到 `product-planning` 作为一种样式风格」+
|
||||
> 「**后续可以添加多种风格,每次只能选择一种进行设计**」⇒ 只把"不放风格"这条**放宽到 `assets/`**,`references/` 仍不放)。
|
||||
> ⚠️ **续加新风格时的硬要求**:目录名与 open-design 同形(`assets/design-systems/<套名>/`),
|
||||
> 套内自带入口文件(`SKILL.md` 或 `DESIGN.md`);⛔ 不许改已入套的文件内容 —— 原样搬入、只在本文件登记去向。
|
||||
|
||||
## 3a 视觉规范(定契约 + 定调 + Gate-1)
|
||||
|
||||
**第 0 步 · 供给核对(每轮必做,防漂移)**:确认技能仓库里的两个页面设计技能仍在位(`open-design/` 下 `design-systems/<选定套>/` 存在、`craft/` 存在、`design-templates/web-prototype/` 存在;`oil-ui-pro/` 入口 `SKILL.md` 存在),并把**选定套名**与 `DESIGN.md` 里记录的上次套名比对。
|
||||
**第 0 步 · 供给核对(每轮必做,防漂移)**:确认技能仓库里的供给仍在位(`open-design/` 下 `design-systems/<选定套>/` 存在、`craft/` 存在、`design-templates/web-prototype/` 存在;`oil-ui-pro/` 入口 `SKILL.md` 存在;🔴 **若上轮选的是自带库那套,另核 `assets/design-systems/<套>/SKILL.md` 仍在位**),并把**选定套名**与 `DESIGN.md` 里记录的上次套名比对。
|
||||
|
||||
- 一致 → 直接定契约。
|
||||
- **不一致或供给缺失 → 先停下报告**,⛔ 不许拿别的套顶上、也不许凭记忆写风格。把差异列成"供给变更清单",**在会话里向用户呈现并确认**后,再改 `DESIGN.md`,把新套名与清单写进修订记录。
|
||||
@@ -128,10 +155,12 @@ description: 产品规划第③段,界面交互。设计工程供给来自 ope
|
||||
> **为什么是契约字段而不是新子步**:交互一旦单独成文,就会与 `必要内容`、`状态覆盖` 三处互相矛盾 —— 这正是记忆里「同一事实多处落点必然打架」的病。**收进契约 = 一个字段、一处裁决、一个门禁。**
|
||||
> **判定"漏没漏"**:页面里**每一个能点的东西**都必须在这张表里出现。表里没有 ⇒ 它不该可点(改成静态文本,或回D0 补进 `必要内容` 再点)。
|
||||
|
||||
**D1 定调**:从 `open-design/design-systems/` **选一套**,不凭感觉编、也不全读:
|
||||
**D1 定调**:从 `open-design/design-systems/` **或本段自带 `assets/design-systems/`** 里**选一套**(⛔ 每次只选一套),不凭感觉编、也不全读:
|
||||
|
||||
- **怎么选**:按本项目的**页面类型**挑 —— 读候选套 `DESIGN.md` 的第 1 章 `Visual Theme & Atmosphere`,认准写明该类的那一句(工具型数据密集界面 ⇒ 认 `data-dense, enterprise` 这类描述)。153 套分两代模板(A 组 / B 组章节名不同),**按自己那套的章节读,不必统一**。
|
||||
- ⚠️ **选了自带库那套(`design-system-tiaoyue`)时**:套内**没有 `DESIGN.md`** ⇒ 读它自己的 `SKILL.md`(规范总纲 + 生成时的参考范围)+ `references/known-conflicts.md`(已知取舍);**入口 / 令牌 / 组件形态按上面那张「怎么读」表取**,⛔ 别按 open-design 的三件套去找。
|
||||
- **选定后读三件**:`DESIGN.md`(调性 + 色值 + 字号刻度 + 间距刻度 + 动效基调)→ `tokens.css`(**圆角与阴影只有这里**)→ `components.html`(组件实际长相)。
|
||||
- **要换风格** ⇒ 下一轮重跑 D1 重选一套,**旧套留痕**(用户 2026-10-07:「后续可以添加多种风格,**每次只能选择一种进行设计**」)。
|
||||
- **没找到匹配的套** ⇒ 换一个类型词再挑一次;仍不行就直说"未找到匹配",**不许拿泛化结果当结论落盘**。
|
||||
- ⚠️ **工具型页面的版式不在模板里**:`web-prototype/references/layouts.md` 那 8 个骨架是**落地页 / 营销页**向(hero / features / stats / quote / cta / log / pricing)。工具型页面只适用其中 **Layout 7 日志列表**与 **Layout 8 对比表**;其余套上去会把后台做成营销页。**工具型页面的版式由 D0 的 `结构骨架` 字段决定,不从模板抄。**
|
||||
|
||||
@@ -221,7 +250,7 @@ description: 产品规划第③段,界面交互。设计工程供给来自 ope
|
||||
|
||||
| # | 模型建议(原文摘句) | 判定 | 理由 | 落地位置 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | ... | 采纳 / 不采纳 / 待议 | 对照 `DESIGN.md` 与 PRD 说清 | 文件 + 选择器 |
|
||||
| 1 | ... | 采纳 / 不采纳 / 待议 | 对照 `DESIGN.md` 与②段说清 | 文件 + 选择器 |
|
||||
|
||||
**功能作用**:引入一个不在本项目上下文里的外部视角,补自己看不见的盲区。
|
||||
|
||||
@@ -229,7 +258,7 @@ description: 产品规划第③段,界面交互。设计工程供给来自 ope
|
||||
|
||||
**异常情况**:登录失效 / 人机验证 / 网络不通 / 回答被截断时,**不要假装跑过**。退路是把提问模板和原型文件路径交给用户,由用户在自己浏览器里问,再把回答粘回来落盘。仍然取不到时,在 `3c-GPT会诊.md` 里写明"本轮未取得外部审查",并在本段收尾时明示——**未取得不等于已通过**。
|
||||
|
||||
**处置纪律**:模型建议不是命令。采纳要给理由,与 `DESIGN.md` 或 PRD 冲突的写清为什么不采纳。采纳项**在 3d 之前改完**,并回到 3b 的实测清单把这些改动再点一遍。
|
||||
**处置纪律**:模型建议不是命令。采纳要给理由,与 `DESIGN.md` 或②段冲突的写清为什么不采纳。采纳项**在 3d 之前改完**,并回到 3b 的实测清单把这些改动再点一遍。
|
||||
|
||||
## 3d 审查与打磨(收口 + 动效 + 终检 + Gate-2/3)
|
||||
|
||||
@@ -287,12 +316,14 @@ description: 产品规划第③段,界面交互。设计工程供给来自 ope
|
||||
|
||||
## 硬约束
|
||||
|
||||
- ⭐⭐ **骨架冻结,不许动板块(2026-10-06 用户定案)**:页面清单、每页板块、板块排列、跨页关系**由②段 ②段《界面布局》定死**。
|
||||
⛔ **本段不许新增 / 移动 / 删除 / 合并板块**。**做不下来 / 觉得缺板块 ⇒ 回②段改 PRD**,⛔ **不许就地加一块** —— 就地加会让 PRD 与原型不一致,下一个读原型的人拿到的是错的骨架。
|
||||
- ⭐⭐ **骨架冻结,不许动板块(2026-10-06 用户定案)**:页面清单、每页板块、板块排列、跨页关系**由②段《界面布局》定死**。
|
||||
⛔ **本段不许新增 / 移动 / 删除 / 合并板块**。**做不下来 / 觉得缺板块 ⇒ 回②段改《界面布局》**,⛔ **不许就地加一块** —— 就地加会让②段与原型不一致,下一个读原型的人拿到的是错的骨架。
|
||||
**允许动的**:板块内的**视觉与交互实现**(怎么排版好看、点哪有什么反馈、空态怎么呈现、响应式怎么变)。
|
||||
⛔ **不许无视 PRD 骨架另起一套** —— D0 的页面清单与每页板块必须与 ②段《界面布局》**逐条对得上**;对不上就停下来问②段要哪一版。
|
||||
⛔ **不许无视②段骨架另起一套** —— D0 的页面清单与每页板块必须与 ②段《界面布局》**逐条对得上**;对不上就停下来问②段要哪一版。
|
||||
- **声明制**:开工前、每进一个子步、每处偏离,都要声明(当前子步 / 依据文件 + 小节号 / 产出落点)。**不许静默偏离**。段末必须附**偏离单**,没有偏离单不得报完成。
|
||||
- **设计工程供给是 `open-design`**:三处(设计系统 / 工艺规约 / 原型模板)按需取用;**冲突处以本段与 `DESIGN.md` 为准**。不许跳过 Gate-1 或 Gate-3。
|
||||
- **设计工程供给默认是 `open-design`**(三处:设计系统 / 工艺规约 / 原型模板,按需取用);**冲突处以本段与 `DESIGN.md` 为准**。不许跳过 Gate-1 或 Gate-3。
|
||||
⚠️ **`open-design` 不可得时**(它只在工作区级,别的工作区可能没有)⇒ 见上方「不可得时的回落」:由 `oil-ui-pro` 顶方向探索与评审,
|
||||
但**定调与工艺数值判据会没有来源**,⛔ 不许凭感觉编。
|
||||
- **页型先判再动手**(2026-10-05 新增):3a 的 D0 必须写出「营销页 / 工具型页面 + 依据」。**工具型页面零 `.eyebrow` / 零 `.lead` / 零 `.hero`** —— 三者已从种子删除或降级为 `body.is-marketing` 专用。⛔ **判不出页型不许进 3b**。
|
||||
- **`open-design` 的 `web-prototype` 种子是营销落地页骨架**:⛔ 工具型页面**不许**直接套用其版式节奏与标题区结构;版式走本技能 `references/layouts-tooling.md`。**这是一处"照供给做反而错"的例外,必须显式绕开。**
|
||||
- **先定死,再写码**:设计契约与令牌表未落笔,不许写任何组件代码。
|
||||
@@ -330,8 +361,8 @@ description: 产品规划第③段,界面交互。设计工程供给来自 ope
|
||||
|
||||
## 反面清单
|
||||
|
||||
- ⛔ **就地增删板块**(2026-10-06 新增):觉得骨架不合适就自己加一块 / 挪一块 / 删一块 —— **骨架归②段 PRD**,要改回②段改。就地改会让 PRD 与原型不一致
|
||||
- ⛔ **无视 PRD 骨架另起一套**(2026-10-06 新增):D0 的页面清单与每页板块必须与 ②段《界面布局》逐条对得上
|
||||
- ⛔ **就地增删板块**(2026-10-06 新增):觉得骨架不合适就自己加一块 / 挪一块 / 删一块 —— **骨架归②段**,要改回②段改。就地改会让②段与原型不一致
|
||||
- ⛔ **无视 ②段骨架另起一套**(2026-10-06 新增):D0 的页面清单与每页板块必须与 ②段《界面布局》逐条对得上
|
||||
- 先出原型再补规范(违反铁律 1)
|
||||
- **违反限载**:一次把全部 `design-systems/` 或 `craft/` 原文读进上下文(一次只许 1~2 份)
|
||||
- **不判页型就照 `web-prototype` 种子做** —— 会把后台做成营销页(标题上下各一行小字);或拿 `layouts.md` 的营销骨架顶替工具型版式
|
||||
@@ -346,8 +377,8 @@ description: 产品规划第③段,界面交互。设计工程供给来自 ope
|
||||
- **把模型建议当命令照抄**,改坏与 `DESIGN.md` 的一致性;或伪造"GPT 说……"却拿不出回答原文
|
||||
- 把"未取得外部审查"当已通过
|
||||
- 工具型页面做入场编排;用 `linear`/`ease` 做过渡曲线;用 `@keyframes` 做交互过渡
|
||||
- ⛔ **就地增删板块**(2026-10-06):骨架归②段 PRD,本段只能动板块内的视觉与交互实现
|
||||
- 越界回头改 PRD 的**功能取舍**(那是第②段)—— ⚠️ **但"骨架缺板块"是例外:必须回②段改,⛔ 不许就地在原型里加**
|
||||
- ⛔ **就地增删板块**(2026-10-06):骨架归②段,本段只能动板块内的视觉与交互实现
|
||||
- 越界回头改②段的**功能取舍**(那是第②段)—— ⚠️ **但"骨架缺板块"是例外:必须回②段改,⛔ 不许就地在原型里加**
|
||||
- 顺手把原型说明文档 / 演示引导写了(那是第④段 `stage-proto-doc`)
|
||||
|
||||
## 待补(2026-10-02 登记 · 2026-10-06 更新)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# 归属与许可(ATTRIBUTION)
|
||||
|
||||
本工具(`design-capture`)的抓取脚本派生自外部项目,按其许可使用并署名如下。
|
||||
|
||||
## 来源
|
||||
|
||||
- 上游项目:**open-design** —— 网页设计系统采集插件 **OD Clipper**
|
||||
- 上游文件:`open-design/clipper/brand-capture.js`
|
||||
- 上游许可:**Apache License 2.0**(见上游仓库根 `LICENSE`)
|
||||
- 本仓文件:`vendor/brand-capture.js`
|
||||
|
||||
## 本仓改造(Apache-2.0 允许派生,此处如实列出改动)
|
||||
|
||||
- 唯一改动:`window.__odBrandCapture` 的**返回值**增加一个 `data` 字段
|
||||
(原版只回 `{html, resources, title, url, summary}`,其中 `summary` 仅为计数;结构化数据原本只内嵌在 `html` 里)。
|
||||
改造后调用方可直接拿到结构化 `data`(palette/typography/assets/components/layout),供模板生成器消费。
|
||||
- 改动位置:`brand-capture.js` 中 `window.__odBrandCapture = function (opts) { … }` 的 `return { … }`。
|
||||
- 其余逻辑未改动。
|
||||
|
||||
## 说明
|
||||
|
||||
- 抽取逻辑本身是**纯浏览器端 JS(light-DOM 读取)**,不依赖 open-design 的 daemon 或服务端;本工具因此可脱离 open-design 独立运行(只连本机 Chrome 的 CDP)。
|
||||
- 上游客服端(open-design daemon / Library)不在本工具依赖范围内。
|
||||
- 版权与许可归原作者所有;如上游许可条款与本仓使用方式有出入,以 Apache-2.0 为准。
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
name: design-capture
|
||||
description: ③段独立工具 —— 输入一个网站 URL,抓取它的设计风格与组件样板,生成一套可直接选用的样式模板(design-system-<名>/,与 design-system-tiaoyue 同构)。当用户说「获取 XXX 网站的设计风格」「抓一下这个网站的风格/配色/字体/组件」「按某网站风格新建一套样式」「把某个网页变成设计系统」时调用。
|
||||
agent_created: true
|
||||
---
|
||||
|
||||
# design-capture · 网站设计风格抓取(③段独立工具)
|
||||
|
||||
把「一个网址」变成「一套可选样式模板」:抓源站 → 落原始设计系统 → 生成 `design-system-<名>/` 模板包(与 `assets/design-systems/design-system-tiaoyue/` 同构),随后可被 D1 定调当普通样式套用。
|
||||
|
||||
## 一、什么时候用
|
||||
|
||||
- 用户说「**获取 XXX 网站的设计风格**」「抓这个网站的配色/字号/组件」「按这个网站新建一套样式」。
|
||||
- ③段 D1 定调时,现有 `assets/design-systems/` 里没有合适的套 → 从某个标杆网站**现场新建一套**。
|
||||
|
||||
## 二、怎么用(两步)
|
||||
|
||||
第一步 · 抓取(本机 Chrome + CDP,零其他依赖)
|
||||
```
|
||||
node scripts/capture_design_system.mjs --url https://example.com --out raw.json
|
||||
```
|
||||
- 前置:本机有可连的 Chrome 远程调试端口;没有就先起一个:
|
||||
`chrome.exe --remote-debugging-port=9333 --user-data-dir=<临时目录> about:blank`
|
||||
- 产出 `raw.json`:含 `html`(设计系统 HTML)、`data`(结构化:palette/typography/assets/components/layout)、`resources`。
|
||||
|
||||
第二步 · 生成样式模板包
|
||||
```
|
||||
python scripts/build_style_package.py --raw raw.json --out ./design-system-<名> --name "<样式名>"
|
||||
```
|
||||
- 可选暗色:`--dark-raw dark.json`(另跑一次暗色采集再并入)。
|
||||
- 产出:`SKILL.md` + `assets/tokens.css`(+`tokens-dark.css`)+ `assets/brand.json` + `references/{components.md,library.json,design-system.html,capture-notes.md}`。
|
||||
|
||||
第三步 · 装进样式库(供 D1 选用)
|
||||
把生成目录整体放进 `assets/design-systems/design-system-<名>/`,即成为可选样式;③段 D1 定调时与 open-design 的套**同权,每次只选一套**。
|
||||
|
||||
## 三、边界(如实登记)
|
||||
|
||||
- ⛔ 不依赖 open-design daemon:抽取脚本已搬进 `vendor/`,只连本机 Chrome。
|
||||
- ⛔ 不抓登录墙后 / 需交互才出现的内容:抓的是「页面上可见的样式」。
|
||||
- ⛔ 跨域样式表若禁止读取,其规则不进统计。
|
||||
- 组件是「每类一个代表性样本」(button/input/card/nav),不是全站枚举。
|
||||
- 暗色:默认只抓亮色;要暗色须对暗色形态另跑一次(`--dark-raw`)。
|
||||
- 抓取质量取决于源站;生成后应**目视比对**再采用。
|
||||
|
||||
## 四、归属
|
||||
|
||||
`vendor/brand-capture.js` 搬运自 open-design clipper(Apache-2.0),细节与改造见 `ATTRIBUTION.md`。
|
||||
+328
@@ -0,0 +1,328 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
design-capture · 设计系统模板生成器(③段工具)
|
||||
|
||||
做什么:把抓取 raw JSON(capture_design_system.mjs 的产出)转成一套「可选样式」模板包,
|
||||
与 skills/.../assets/design-systems/design-system-tiaoyue/ 同构,可直接被 D1 定调选用。
|
||||
|
||||
输出(默认写入 --out 目录):
|
||||
SKILL.md 规范总纲(含七语义色、排版、几何、组件用法、生成步骤)
|
||||
assets/tokens.css 亮色令牌 :root{}
|
||||
assets/tokens-dark.css 暗色令牌(仅当提供 --dark-raw 时生成)
|
||||
assets/brand.json 品牌画像(色/字/几何/来源)
|
||||
references/components.md 组件族人类可读目录
|
||||
references/library.json 机器可读全量(原始 data)
|
||||
references/design-system.html 抓取出的设计系统原始 HTML
|
||||
references/capture-notes.md 抓取记录与已知缺口
|
||||
|
||||
依赖:仅 Python 3 标准库。
|
||||
用法:
|
||||
python build_style_package.py --raw raw.json --out ./design-system-xxx [--name "跳跃视界"] [--dark-raw dark.json]
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
||||
ROLE_TOKEN = {
|
||||
"background": "--background",
|
||||
"surface": "--surface",
|
||||
"foreground": "--foreground",
|
||||
"muted": "--muted",
|
||||
"faint": "--faint",
|
||||
"border": "--border",
|
||||
"accent": "--accent",
|
||||
"accent-secondary": "--accent-secondary",
|
||||
"highlight": "--accent-2",
|
||||
}
|
||||
|
||||
# 角色 → 常用别名(同义归并,便于生成器/原型直接引用)
|
||||
ROLE_ALIAS = {
|
||||
"background": "--c-bg",
|
||||
"surface": "--c-surface",
|
||||
"foreground": "--c-text",
|
||||
"muted": "--c-text-2",
|
||||
"border": "--c-border",
|
||||
"accent": "--c-accent",
|
||||
}
|
||||
|
||||
|
||||
def digits_only(s):
|
||||
return re.sub(r"[^0-9a-zA-Z]+", "-", str(s or "")).strip("-").lower()
|
||||
|
||||
|
||||
def slugify(name):
|
||||
s = digits_only(name)
|
||||
return s or "captured-style"
|
||||
|
||||
|
||||
def hex_to_rgb(h):
|
||||
h = str(h or "").strip().lstrip("#")
|
||||
if len(h) == 3:
|
||||
h = "".join(c * 2 for c in h)
|
||||
if len(h) != 6:
|
||||
return None
|
||||
try:
|
||||
return tuple(int(h[i:i + 2], 16) for i in (0, 2, 4))
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def rel_luminance(hex_color):
|
||||
rgb = hex_to_rgb(hex_color)
|
||||
if not rgb:
|
||||
return 0.5
|
||||
def f(c):
|
||||
c = c / 255.0
|
||||
return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
|
||||
r, g, b = (f(c) for c in rgb)
|
||||
return 0.2126 * r + 0.7152 * g + 0.0722 * b
|
||||
|
||||
|
||||
def contrast_text(hex_color):
|
||||
return "#FFFFFF" if rel_luminance(hex_color) < 0.5 else "#1A1A18"
|
||||
|
||||
|
||||
def tokens_from_colors(colors, layout):
|
||||
"""colors: [{role, hex}] → ordered token lines."""
|
||||
lines = []
|
||||
slots = []
|
||||
for c in colors or []:
|
||||
role = (c or {}).get("role")
|
||||
hexv = (c or {}).get("hex")
|
||||
if role and hexv:
|
||||
slots.append((role, hexv))
|
||||
out = {}
|
||||
for role, hexv in slots:
|
||||
tok = ROLE_TOKEN.get(role, "--" + digits_only(role))
|
||||
out.setdefault(tok, hexv)
|
||||
# 兜底:确保核心角色存在
|
||||
out.setdefault("--background", "#FFFFFF")
|
||||
out.setdefault("--surface", "#FFFFFF")
|
||||
out.setdefault("--foreground", "#1A1A18")
|
||||
out.setdefault("--muted", "#57564F")
|
||||
out.setdefault("--border", "#E7E5DC")
|
||||
out.setdefault("--accent", "#C96442")
|
||||
out["--on-accent"] = contrast_text(out["--accent"])
|
||||
out.setdefault("--ok", "#3D7A4F")
|
||||
out.setdefault("--err", "#B4453A")
|
||||
if layout and layout.get("radius") and layout["radius"] != "—":
|
||||
out["--radius"] = layout["radius"]
|
||||
# 别名
|
||||
for role, tok in ROLE_ALIAS.items():
|
||||
src = ROLE_TOKEN.get(role)
|
||||
if src and src in out:
|
||||
out[("alias:" + tok)] = out[src]
|
||||
return out
|
||||
|
||||
|
||||
def write_tokens(path, tokens):
|
||||
alias = [(k.split(":", 1)[1], v) for k, v in tokens.items() if k.startswith("alias:")]
|
||||
base = [(k, v) for k, v in tokens.items() if not k.startswith("alias:")]
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write(":root{\n")
|
||||
f.write(" /* primitive / 语义令牌(由 design-capture 从源站实测生成) */\n")
|
||||
for k, v in base:
|
||||
f.write(f" {k}:{v};\n")
|
||||
if alias:
|
||||
f.write(" /* 常用别名 */\n")
|
||||
for k, v in alias:
|
||||
f.write(f" {k}:{v};\n")
|
||||
f.write("}\n")
|
||||
|
||||
|
||||
def md_components(components, typography):
|
||||
lines = ["# 组件目录(抓取)", ""]
|
||||
order = ["button", "input", "card", "nav"]
|
||||
labels = {"button": "按钮", "input": "输入框", "card": "卡片", "nav": "导航/头部"}
|
||||
for key in order:
|
||||
prof = (components or {}).get(key) or {}
|
||||
lines.append(f"## {labels.get(key, key)}(`{key}`)")
|
||||
if not prof:
|
||||
lines.append("- (未抓到该组件样本)")
|
||||
lines.append("")
|
||||
continue
|
||||
for pk, pv in prof.items():
|
||||
if pv in (None, "", "none"):
|
||||
continue
|
||||
lines.append(f"- `{pk}`: {pv}")
|
||||
lines.append("")
|
||||
fams = (typography or {}).get("families") or []
|
||||
specs = (typography or {}).get("specs") or []
|
||||
lines.append("## 排版")
|
||||
lines.append(f"- 字体族:{', '.join(fams) if fams else '(未抓到)'}")
|
||||
for s in specs:
|
||||
if not isinstance(s, dict):
|
||||
continue
|
||||
lines.append(
|
||||
f"- `{s.get('selector','')}` → {s.get('family','')} / {s.get('weight','')} / {s.get('size','')} / lh {s.get('lineHeight','')}"
|
||||
)
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def md_capture_notes(meta, data, has_dark):
|
||||
content = (data or {}).get("content") or {}
|
||||
lines = [
|
||||
"# 抓取记录(design-capture)",
|
||||
"",
|
||||
f"- 源站:{meta.get('requestedUrl') or content.get('url') or '—'}",
|
||||
f"- 抓取时间:{meta.get('capturedAt') or '—'}",
|
||||
f"- 工具:design-capture v{meta.get('toolVersion','1.0')}(抽取脚本搬运自 open-design clipper,Apache-2.0)",
|
||||
f"- CDP 端口:{meta.get('cdpPort','—')}",
|
||||
f"- 页面标题:{content.get('title','—')}",
|
||||
"",
|
||||
"## 已知缺口(如实登记,⛔ 不要当已覆盖)",
|
||||
"",
|
||||
f"- 暗色令牌:{'已随 --dark-raw 一并生成' if has_dark else '未抓取(源站亮色为准;要暗色需另跑带 dark 的采集)'}",
|
||||
"- 组件为「页面代表性样本」而非全站枚举:抓取只取每类一个最优样本(button/input/card/nav)。",
|
||||
"- 动态类名/运行时样式:源站若用 Vue scoped 或运行时 JS 决定样式,快照只反映抓取时刻。",
|
||||
"- 跨域样式表受限:跨域 CSS 若禁止读取,其规则不会进入组件/字体统计。",
|
||||
"",
|
||||
]
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def build_skill_md(name, data, tokens):
|
||||
content = (data or {}).get("content") or {}
|
||||
brand = (data or {}).get("brand") or {}
|
||||
layout = (data or {}).get("layout") or {}
|
||||
colors = brand.get("colors") or []
|
||||
color_rows = "\n".join(
|
||||
f"- `{c.get('role','')}` = `{c.get('hex','')}`" for c in colors if isinstance(c, dict)
|
||||
)
|
||||
slug = slugify(name)
|
||||
desc = (
|
||||
f"{name} 的设计规范与组件库 —— 按此规范生成「{name} 风格」的界面时使用。"
|
||||
f"含语义色令牌、排版、几何,以及从源站实测提取的组件样本。"
|
||||
f"当用户说「按 {name} 的风格做一页」「用 {slug} 的设计系统生成」时触发。"
|
||||
)
|
||||
return f"""---
|
||||
name: design-system-{slug}
|
||||
description: {desc}
|
||||
agent_created: true
|
||||
source: design-capture(源站实测,脚本搬运自 open-design clipper · Apache-2.0)
|
||||
---
|
||||
|
||||
# {name} 设计规范
|
||||
|
||||
从 `{content.get('url') or brand.get('sourceUrl') or '源站'}` **真实页面实测提取**的设计系统 + 组件样本,用于生成同风格界面。
|
||||
|
||||
**核心纪律**:本规范每个值都来自**实测**,⛔ 不要凭印象改色值/圆角/命名,⛔ 不要用通用默认值顶替。
|
||||
|
||||
## 一、覆盖范围
|
||||
|
||||
- **设计令牌**:语义色 + 排版 + 几何 → `assets/tokens.css`(亮色)
|
||||
{('| **`assets/tokens-dark.css`**(暗色,随 --dark-raw 生成)' if False else '')}
|
||||
- **机器可读全量**:`references/library.json`(原始结构化 `data`)
|
||||
- **人可读组件目录**:`references/components.md`
|
||||
- **设计系统原始产出**:`references/design-system.html`
|
||||
- **抓取记录与缺口**:`references/capture-notes.md`
|
||||
|
||||
## 二、语义色(实测值)
|
||||
|
||||
{color_rows or '- (未抓到色板)'}
|
||||
|
||||
- `--on-accent` = `{tokens.get('--on-accent','')}`(强调色上的文字,按对比度自动取黑/白)
|
||||
|
||||
## 三、几何与排版
|
||||
|
||||
- 圆角:`{layout.get('radius','—')}`
|
||||
- 姿态:{(';'.join(layout.get('postureRules') or []) or '—')}
|
||||
- 排版:见 `references/components.md`
|
||||
|
||||
## 四、组件怎么用
|
||||
|
||||
优先级:① 先用 `references/components.md` 里的样本组件 → ② 没有才自创,且沿用同一套令牌。
|
||||
组件来自源站代表性样本(button/input/card/nav),非全站枚举。
|
||||
|
||||
## 五、生成一个页面的步骤
|
||||
|
||||
1. 读 `assets/tokens.css`,把 `:root` 令牌整段纳入(⛔ 不要手改 hex)。
|
||||
2. 读 `references/components.md`,挑出该页面需要的组件。
|
||||
3. 按源站命名/形态复用;状态用语义修饰。
|
||||
4. 自查:色值是否全部来自令牌?圆角/组件形态是否一致?
|
||||
|
||||
## 六、已知缺口
|
||||
|
||||
见 `references/capture-notes.md`。
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--raw", required=True, help="capture_design_system.mjs 产出的 raw JSON")
|
||||
ap.add_argument("--out", required=True, help="输出目录(将生成为一套 design-system-<slug>/)")
|
||||
ap.add_argument("--name", default="", help="样式名(缺省取页面标题)")
|
||||
ap.add_argument("--dark-raw", default="", help="可选:另一份暗色采集的 raw JSON")
|
||||
args = ap.parse_args()
|
||||
|
||||
with open(args.raw, "r", encoding="utf-8") as f:
|
||||
raw = json.load(f)
|
||||
data = raw.get("data") or {}
|
||||
content = data.get("content") or {}
|
||||
brand = data.get("brand") or {}
|
||||
name = args.name or content.get("title") or brand.get("name") or "Captured Style"
|
||||
slug = slugify(name)
|
||||
|
||||
out = args.out
|
||||
os.makedirs(os.path.join(out, "assets"), exist_ok=True)
|
||||
os.makedirs(os.path.join(out, "references"), exist_ok=True)
|
||||
|
||||
tokens = tokens_from_colors(brand.get("colors"), data.get("layout"))
|
||||
write_tokens(os.path.join(out, "assets", "tokens.css"), tokens)
|
||||
|
||||
# brand.json
|
||||
brand_json = {
|
||||
"name": name,
|
||||
"tagline": brand.get("tagline") or content.get("description") or "",
|
||||
"sourceUrl": brand.get("sourceUrl") or content.get("url") or raw.get("url") or "",
|
||||
"accent": brand.get("accent") or tokens.get("--accent"),
|
||||
"colors": brand.get("colors") or [],
|
||||
"typography": data.get("typography") or {},
|
||||
"layout": data.get("layout") or {},
|
||||
}
|
||||
with open(os.path.join(out, "assets", "brand.json"), "w", encoding="utf-8") as f:
|
||||
json.dump(brand_json, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# 暗色(可选)
|
||||
has_dark = False
|
||||
if args.dark_raw and os.path.exists(args.dark_raw):
|
||||
with open(args.dark_raw, "r", encoding="utf-8") as f:
|
||||
dark_raw = json.load(f)
|
||||
dark_data = dark_raw.get("data") or {}
|
||||
dark_brand = dark_data.get("brand") or {}
|
||||
dark_tokens = tokens_from_colors(dark_brand.get("colors"), dark_data.get("layout"))
|
||||
write_tokens(os.path.join(out, "assets", "tokens-dark.css"), dark_tokens)
|
||||
has_dark = True
|
||||
|
||||
# references
|
||||
with open(os.path.join(out, "references", "design-system.html"), "w", encoding="utf-8") as f:
|
||||
f.write(raw.get("html") or "")
|
||||
with open(os.path.join(out, "references", "library.json"), "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
with open(os.path.join(out, "references", "components.md"), "w", encoding="utf-8") as f:
|
||||
f.write(md_components(data.get("components"), data.get("typography")))
|
||||
with open(os.path.join(out, "references", "capture-notes.md"), "w", encoding="utf-8") as f:
|
||||
f.write(md_capture_notes(raw.get("_meta") or {}, data, has_dark))
|
||||
|
||||
# SKILL.md
|
||||
with open(os.path.join(out, "SKILL.md"), "w", encoding="utf-8") as f:
|
||||
f.write(build_skill_md(name, data, tokens))
|
||||
|
||||
print(json.dumps({
|
||||
"ok": True,
|
||||
"package": os.path.abspath(out),
|
||||
"name": name,
|
||||
"slug": slug,
|
||||
"colors": len(brand.get("colors") or []),
|
||||
"hasDark": has_dark,
|
||||
}, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* design-capture · 独立「抓取网站设计系统」驱动(①③段工具)
|
||||
*
|
||||
* 做什么:连本机 Chrome(CDP)→ 打开目标 URL → 注入 vendor/brand-capture.js →
|
||||
* 调 window.__odBrandCapture() → 落一份 raw JSON(含结构化 data + 设计系统 html)。
|
||||
* 依赖:本机 Chrome(已开 --remote-debugging-port)+ Node ≥ 21(内置 fetch + 全局 WebSocket)。
|
||||
* 边界:⛔ 不依赖 open-design daemon;抽取脚本搬运自 open-design clipper(Apache-2.0,见 ../ATTRIBUTION.md)。
|
||||
*
|
||||
* 用法:
|
||||
* node capture_design_system.mjs --url https://example.com --out raw.json [--ports 9222,9223,9333] [--settle 3500]
|
||||
* 若没有可连的 Chrome,先起一个:
|
||||
* chrome.exe --remote-debugging-port=9333 --user-data-dir=<临时目录> about:blank
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
||||
const VENDOR = path.join(HERE, '..', 'vendor', 'brand-capture.js');
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
function parseArgs(argv) {
|
||||
const a = {};
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const t = argv[i];
|
||||
if (!t.startsWith('--')) continue;
|
||||
const k = t.slice(2);
|
||||
const next = argv[i + 1];
|
||||
a[k] = next && !next.startsWith('--') ? argv[++i] : true;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
const TARGET_URL = args.url;
|
||||
const OUT = args.out || 'design-capture-raw.json';
|
||||
const PORTS = String(args.ports || '9222,9223,9224,9333').split(',').map((s) => s.trim()).filter(Boolean);
|
||||
const SETTLE = Number(args.settle || 3500);
|
||||
|
||||
if (!TARGET_URL) {
|
||||
console.error('用法:node capture_design_system.mjs --url <网址> --out <raw.json> [--ports 9222,9223] [--settle 3500]');
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
async function probePorts() {
|
||||
for (const p of PORTS) {
|
||||
for (const host of ['127.0.0.1', '[::1]']) {
|
||||
try {
|
||||
const r = await fetch(`http://${host}:${p}/json/version`, { signal: AbortSignal.timeout(800) });
|
||||
if (r.ok) return { port: p, host };
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function openTab(host, port, url) {
|
||||
for (const method of ['PUT', 'GET']) {
|
||||
try {
|
||||
const r = await fetch(`http://${host}:${port}/json/new?${encodeURIComponent(url)}`, { method });
|
||||
if (r.ok) return await r.json();
|
||||
} catch {}
|
||||
}
|
||||
throw new Error('无法新建标签页(/json/new 失败)');
|
||||
}
|
||||
|
||||
class CDP {
|
||||
constructor(wsUrl) {
|
||||
this.wsUrl = wsUrl;
|
||||
this.id = 0;
|
||||
this.pending = new Map();
|
||||
}
|
||||
connect() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.ws = new WebSocket(this.wsUrl);
|
||||
this.ws.addEventListener('open', () => resolve());
|
||||
this.ws.addEventListener('error', (e) => reject(new Error('WebSocket 连接失败:' + (e.message || e.type))));
|
||||
this.ws.addEventListener('message', (ev) => {
|
||||
let msg;
|
||||
try { msg = JSON.parse(ev.data); } catch { return; }
|
||||
if (msg.id && this.pending.has(msg.id)) {
|
||||
const { resolve: res, reject: rej } = this.pending.get(msg.id);
|
||||
this.pending.delete(msg.id);
|
||||
msg.error ? rej(new Error(msg.error.message)) : res(msg.result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
send(method, params = {}) {
|
||||
const id = ++this.id;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pending.set(id, { resolve, reject });
|
||||
this.ws.send(JSON.stringify({ id, method, params }));
|
||||
setTimeout(() => {
|
||||
if (this.pending.has(id)) { this.pending.delete(id); reject(new Error(`CDP 超时:${method}`)); }
|
||||
}, 30000);
|
||||
});
|
||||
}
|
||||
async eval(expression, returnByValue = true) {
|
||||
const r = await this.send('Runtime.evaluate', { expression, returnByValue, awaitPromise: true });
|
||||
if (r.exceptionDetails) {
|
||||
throw new Error('页面脚本异常:' + (r.exceptionDetails.exception?.description || r.exceptionDetails.text));
|
||||
}
|
||||
return r.result ? r.result.value : undefined;
|
||||
}
|
||||
close() { try { this.ws.close(); } catch {} }
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const target = await probePorts();
|
||||
if (!target) {
|
||||
console.error('未发现可用的 Chrome 远程调试端口(试过 ' + PORTS.join('/') + ')。');
|
||||
console.error('请先启动:chrome.exe --remote-debugging-port=9333 --user-data-dir=<临时目录> about:blank');
|
||||
process.exit(3);
|
||||
}
|
||||
|
||||
const tab = await openTab(target.host, target.port, TARGET_URL);
|
||||
const cdp = new CDP(tab.webSocketDebuggerUrl);
|
||||
await cdp.connect();
|
||||
await cdp.send('Page.enable');
|
||||
await cdp.send('Runtime.enable');
|
||||
|
||||
const deadline = Date.now() + 45000;
|
||||
let ready = '';
|
||||
while (Date.now() < deadline) {
|
||||
try { ready = await cdp.eval('document.readyState'); } catch {}
|
||||
if (ready === 'complete') break;
|
||||
await sleep(500);
|
||||
}
|
||||
await sleep(SETTLE);
|
||||
|
||||
const src = fs.readFileSync(VENDOR, 'utf8');
|
||||
await cdp.eval(src, false);
|
||||
const kind = await cdp.eval('typeof window.__odBrandCapture');
|
||||
if (kind !== 'function') throw new Error('注入后未找到 window.__odBrandCapture(页面可能禁止脚本)');
|
||||
|
||||
const cap = await cdp.eval('window.__odBrandCapture({})');
|
||||
if (!cap || !cap.html) throw new Error('抓取失败:未返回 html');
|
||||
|
||||
cap._meta = {
|
||||
tool: 'design-capture',
|
||||
toolVersion: '1.0',
|
||||
capturedAt: new Date().toISOString(),
|
||||
requestedUrl: TARGET_URL,
|
||||
cdpPort: target.port,
|
||||
};
|
||||
fs.writeFileSync(OUT, JSON.stringify(cap));
|
||||
console.log(JSON.stringify({
|
||||
ok: true,
|
||||
out: path.resolve(OUT),
|
||||
title: cap.title,
|
||||
url: cap.url,
|
||||
summary: cap.summary,
|
||||
htmlBytes: cap.html.length,
|
||||
hasStructuredData: !!cap.data,
|
||||
}, null, 2));
|
||||
|
||||
cdp.close();
|
||||
try { await fetch(`http://${target.host}:${target.port}/json/close/${tab.id}`); } catch {}
|
||||
}
|
||||
|
||||
main().catch((e) => { console.error('抓取失败:' + e.message); process.exit(1); });
|
||||
+1806
File diff suppressed because it is too large.
Load diff
+144
@@ -0,0 +1,144 @@
|
||||
---
|
||||
name: design-system-tiaoyue
|
||||
description: 跳跃视界(tiaoyue.com)的设计规范与组件库 —— 按此规范生成或改造「跳跃视界风格」的界面时使用。含七语义色令牌、排版、几何,以及从 9 个真实页面实测提取的 277 个自有组件族(72 个全局族)+ Element Plus 基座 + Tailwind 工具类约定。生成页面时只参考个人可操作页面,避开团队创建页。当用户说「按跳跃视界的风格做一页」「用 tiaoyue 的设计系统生成」「参考跳跃视界的组件」「做成跳跃视界那样的界面」时触发。
|
||||
agent_created: true
|
||||
---
|
||||
|
||||
# 跳跃视界(tiaoyue.com)设计规范
|
||||
|
||||
从 `https://www.tiaoyue.com/` **真实页面实测提取**的设计系统 + 组件库,用于生成同风格的界面。
|
||||
|
||||
**核心纪律**:这套规范里的每个值都来自**实测**(clipper 扩展从活页面 + 9 页 DOM 快照读出),
|
||||
⛔ 不要凭印象改动色值/圆角/命名,⛔ 不要用「Inter + 靛蓝 + 紫渐变」那套通用默认值顶替。
|
||||
|
||||
## 〇、生成页面时的参考范围(用户 2026-10-05 定案)
|
||||
|
||||
- ✅ **只参考个人可操作页面**:首页 / 影视 / 短视频 / 转绘 / 画布 / 剧本 / 资产 / 空间(8 页)
|
||||
- ⛔ **避开「团队创建」页**(`team` 页 · 路由 `/team/onboarding`)
|
||||
- 组件库里**仍保留** team 页数据(保完整性),但标记了 `teamOnly: true` 的族**生成时不要参考**:
|
||||
|
||||
1、`onboarding-divider`
|
||||
2、`onboarding-feature`
|
||||
3、`team-scope-content`
|
||||
4、`team-onboarding-card`
|
||||
|
||||
## 一、这套规范覆盖什么
|
||||
|
||||
- **设计令牌**:七语义色角色 + 排版 + 几何 → `assets/tokens.css`(亮色)
|
||||
|**`assets/tokens-dark.css`(暗色 · 2026-10-05 补抓)**
|
||||
- **机器可读全量**:`references/library.json`(277 个自有组件族 + 52 个 Element Plus 类 + 659 个 Tailwind 类;`skipOnGenerate` 字段标出应避开的页)
|
||||
- **人可读组件目录**:`references/components.md`(按族分组、标注来源页与用量)
|
||||
- **设计系统原始产出**:`references/design-system.html`(clipper 产出的令牌 + 组件样例)
|
||||
- **⚠️ 口径冲突 C-01 · 已裁决「不混搭」部分**:`references/known-conflicts.md`(⛔ 生成双主题页前必读)
|
||||
|
||||
## 二、七语义色角色(实测值)
|
||||
|
||||
### 2.1 亮色基线(`assets/tokens.css`,clipper 从 9 个登录后页面提取)
|
||||
|
||||
- `background` = `#faf9f5` —— 页面底(暖白纸感)
|
||||
- `surface` = `#ffffff` —— 卡片/面板底
|
||||
- `foreground` = `#1a1a18` —— 主文字
|
||||
- `muted` = `#57564f` —— 次级文字
|
||||
- `faint` = `#8a887f` —— 三级文字/占位
|
||||
- `border` = `#e7e5dc` —— 描边
|
||||
- `border-soft` = `#efeee7` —— 弱描边/分隔
|
||||
- **`accent` = `#9E4CFF`** —— **主强调色(紫)**
|
||||
- `on-accent` = `#FFFFFF` —— 强调色上的文字
|
||||
- `ok` = `#3d7a4f` —— 成功态
|
||||
- `err` = `#b4453a` —— 错误态
|
||||
|
||||
### 2.2 暗色基线(`assets/tokens-dark.css`,2026-10-05 DOM 实测补抓)
|
||||
|
||||
- `background` = `#0d0d12`|`surface` = `#27272a`|`surface-soft` = `#2b2b33`|`field` = `#18161b`
|
||||
- `foreground` = `#ffffff`|`muted` = `#a1a1aa`
|
||||
- `border` = `rgba(255,255,255,.06)`(叠加白,⛔ 不是硬 hex)
|
||||
- **`accent` = `#9e4cff` —— ⭐ 与亮色同值,品牌紫不随主题翻转**
|
||||
- `ok` = `#34d399`|`err` = `#f87171`|`warn` = `#fb923c`
|
||||
|
||||
**主题机制**:`html[data-theme="dark"|"light"]` 显式属性,⛔ **不是** `prefers-color-scheme`(9 页快照零命中)。
|
||||
|
||||
#### ⚠️ 暗色下三条与亮色**相反**的规则(⛔ 别照搬亮色经验)
|
||||
|
||||
1. **`accent` 当文字色要「提亮」,不是「降档」。**
|
||||
`craft/color.md` 的「文本降一档」是**亮底**的降法。暗底上 `#9e4cff` 直接当文字只有
|
||||
**bg 3.79:1 / surface 2.91:1** ⇒ 必须沿明度轴**提亮**到 `--accent-text: #b270ff`
|
||||
(bg 6.14 / surface 4.72 ✅,色相 H=267.5° 不变)。亮色侧同样给出 `--accent-text: #963dff`。
|
||||
2. **暗色只有两级可用文字灰,不是三级。**
|
||||
`--muted-weak` #71717a 对 bg 4.01 / surface 3.08 / surface-soft 2.90 ⇒ **⛔ 不许写正文**。
|
||||
提到 #A0A0A7 可过三底,但它与 `--muted` #a1a1aa 肉眼几乎不可分⇒ 等于造假层级。
|
||||
⇒ 文字只用 `ink` + `muted`;第三级仅用于分隔线 / 图标 / 禁用态。
|
||||
3. **暗色的「面」靠半透明白叠加,不是硬 hex。**
|
||||
站内 `--figma-surface-card` 实为 `rgba(255,255,255,.06)`;`--surface` 的 `#27272a` 是**强面**。
|
||||
⇒ 叠加式卡片用 `--overlay-card` / `--overlay-card-hover`,⛔ 别拿 `#27272a` 硬顶(会失去 hover 态)。
|
||||
|
||||
#### 🟡 口径冲突 C-01 · 已裁决「不混搭」部分(生成双主题页前必读)
|
||||
|
||||
同一次暗色实测读到的**该页亮色值,与上面 2.1 的亮色基线不同**(bg #f2f2f2 vs #faf9f5、
|
||||
ink #000000 vs #1a1a18、muted #52525b vs #57564f、faint #71717a vs #8a887f、line #f4f4f5 vs #e7e5dc)。
|
||||
两套取色来源不同(`--figma-*` 家族 vs clipper),页面也不同。
|
||||
|
||||
**已裁决(2026-10-05,棒2 独立复核 `_c01_recheck.py`:6 角色 5 条不同、色温相反 A 暖 5/5 vs B 冷 3/5、
|
||||
跨路混搭对比度全达标故须结构约定防住)**:
|
||||
⇒ **暗色与亮色是两套独立基线,不共用令牌。**
|
||||
⇒⛔ **要整套切**(暗色连同其配套亮色),⛔ 不要与 2.1 亮色混搭上生产。
|
||||
|
||||
⚠️ **仍⛔ 未裁决**:**哪一套亮色才是 tiaoyue 主线的正解(A 路 clipper 9 页 / B 路 CDP 活动页)**——
|
||||
两套亮色基线**都是合法的**,本条⛔ **不判哪套"更对"**;要定它得**在主线页面**(⛔ 非 team 页)
|
||||
同批抓一次 light+dark 才能裁。
|
||||
详见 `references/known-conflicts.md`。
|
||||
|
||||
## 三、几何与排版
|
||||
|
||||
- 圆角:`--radius: 14px`|`--ui-radius: 16px`|**按钮 `--btn-radius: 26px`(药丸形)**
|
||||
- 按钮:`--btn-px: 12px`/`--btn-py: 6px`/`--btn-bw: 1px`/`--btn-weight: 400`
|
||||
- 排版:**走系统字体栈**(源站 `@font-face` 为 0 条,未内嵌 webfont)
|
||||
- 布局:**左侧栏(`app-sidebar`,宽 `w-16`)+ 顶栏(`layout-header`)** 的应用型外壳
|
||||
|
||||
## 四、组件库怎么用
|
||||
|
||||
**优先级**:① 先用「全局组件」(72 个,跨 8+ 页复用)→ ② 再用「单页专属组件」→ ③ 都没有才自创,
|
||||
且自创必须**沿用同一套令牌与 BEM 命名**。
|
||||
⛔ 全过程**不要参考 `teamOnly: true` 的族**(团队创建页专属,用户已明确避开)。
|
||||
|
||||
**核心全局组件**(详见 `references/components.md`):
|
||||
|
||||
- **导航**:`nav-item` / `nav-icon` / `nav-tip` / `nav-tips`、`menu-item` / `menu-icon`
|
||||
- **按钮**:`maiya-btn`(主按钮)、`header-action-btn`(`--text` / `--icon` 两变体)、`auth-submit-btn`
|
||||
- **卡片**:`double-festival-card`(活动卡)、`featured-card`(内容卡,首页 240 次)、`asset-card`(资产卡)
|
||||
- **表单/认证**:`auth-field` / `auth-agreement-link` / `auth-tab` / `auth-dialog-overlay`
|
||||
- **提示/弹层**:`beta-code-panel` / `beta-code-popper` / `notice-tabs` / `deployment-update-notice`
|
||||
- **内容**:`course-entry`、`points-capsule`(点数胶囊)
|
||||
- **状态修饰**:`is-active` / `is-inactive` / `is-light` / `is-align-center`(⚠️ 修饰类不参与分族)
|
||||
|
||||
**命名约定**:BEM —— `块__元素` / `块--修饰`;状态用 `is-*`。
|
||||
|
||||
**基座**:源站用 **Element Plus**(52 个 `el-*` 类)+ **Tailwind 工具类**(659 个)。
|
||||
⇒ 生成时若引入 Element Plus,命名可直接对上;若不引,用 Tailwind 表达同样的布局意图。
|
||||
|
||||
## 五、生成一个页面的步骤
|
||||
|
||||
1. 读 `assets/tokens.css`,把 `:root` 令牌整段纳入(⛔ 不要手改 hex)
|
||||
—— 若页面要暗色,另读 `assets/tokens-dark.css` 并挂 `data-theme` 开关
|
||||
(⛔ 见 §2.2 冲突 C-01:**整套切**,⛔ 不要与亮色基线混搭)
|
||||
2. 读 `references/components.md`,挑出该页面需要的组件族(⛔ 跳过 `teamOnly: true` 的族)
|
||||
3. 搭外壳:`app-sidebar`(左)+ `layout-header`(顶)+ 主内容区
|
||||
4. 按 BEM 命名复用组件;状态用 `is-*`
|
||||
5. 自查:色值是否全部来自令牌?圆角/按钮形态是否一致?命名是否同族?
|
||||
**暗色是否被误当推导值(现在已是实测值)?有没有误用团队创建页的组件?**
|
||||
**暗色下 `accent` 当文字是否已换成 `--accent-text`?有没有拿 `--muted-weak` 写正文?**
|
||||
|
||||
## 六、已知缺口
|
||||
|
||||
- 🟡 **暗色与亮色的口径冲突 C-01 · 已裁决「不混搭」部分**(见 §2.2 与 `references/known-conflicts.md`)——
|
||||
两套亮色值并存,⛔ 不要混搭(**要暗色 ⇒ 整套切**)。
|
||||
⚠️ **仍⛔ 未裁决**:哪一套亮色才是主线正解(A 路 clipper / B 路 CDP 活动页)。
|
||||
- ⚠️ **暗色只覆盖 1 个页面**(`/team/onboarding`)。令牌是 `:root` 级、理论上全站共用,
|
||||
但「令牌级共用」≠「页面级实现」⇒ 要声称"全站暗色 OK"需逐页复测。
|
||||
- ⚠️ **暗色只到「角色层」,没到「状态层」**:`--figma-*` 家族里数百个 `-hover`/`-pressed`/`-disabled`
|
||||
档未逐条回填。`tokens-dark.css` 的 overlay 段只补了 4 个实测到的叠加值。
|
||||
- ⚠️ **暗色的切换入口未验证**:只证了机制(`data-theme` 属性)与值,⛔ 不知道 UI 上长什么样。
|
||||
- **画布页(`/aigc/canvas/canvasList`)与剧本页(`/aiScript`)是**编辑器型密集界面**,
|
||||
其组件族偏专属,复用度低
|
||||
- `tencent-captcha-dy`(720 次)是**腾讯验证码**注入的 DOM,⛔ 不是源站自有组件,**不要参考**
|
||||
- 源站的部分类名相同但**样式由运行时 JS 决定**(Vue scoped + 动态 class),
|
||||
快照只能反映抓取时刻的状态
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
{
|
||||
"source": {
|
||||
"url": "https://www.tiaoyue.com/",
|
||||
"title": "跳跃视界 设计系统",
|
||||
"product": "跳跃视界(AI短剧/漫剧/短视频创作平台)",
|
||||
"company": "北京跳跃视界智能科技有限公司",
|
||||
"extractedBy": "open-design clipper (Extract design system)",
|
||||
"extractedAt": "2026-10-05"
|
||||
},
|
||||
"semanticColors": {
|
||||
"background": "#faf9f5",
|
||||
"surface": "#ffffff",
|
||||
"foreground": "#1a1a18",
|
||||
"muted": "#57564f",
|
||||
"faint": "#8a887f",
|
||||
"border": "#e7e5dc",
|
||||
"borderSoft": "#efeee7",
|
||||
"accent": "#9E4CFF",
|
||||
"accentInk": "#9E4CFF",
|
||||
"onAccent": "#FFFFFF",
|
||||
"ok": "#3d7a4f",
|
||||
"err": "#b4453a",
|
||||
"radius": "14px",
|
||||
"uiRadius": "16px"
|
||||
},
|
||||
"typography": {
|
||||
"families": [
|
||||
{
|
||||
"value": """,
|
||||
"count": 18
|
||||
},
|
||||
{
|
||||
"value": "inherit",
|
||||
"count": 9
|
||||
},
|
||||
{
|
||||
"value": "var(--font-display)",
|
||||
"count": 4
|
||||
},
|
||||
{
|
||||
"value": "var(--font-body)",
|
||||
"count": 2
|
||||
},
|
||||
{
|
||||
"value": "var(--font-mono)",
|
||||
"count": 2
|
||||
}
|
||||
],
|
||||
"fontFaceRules": 0
|
||||
},
|
||||
"assets": {
|
||||
"images": 11,
|
||||
"logos": 8,
|
||||
"inlineDataUris": 31
|
||||
},
|
||||
"tokenCount": 43,
|
||||
"tokens": [
|
||||
{
|
||||
"name": "paper",
|
||||
"value": "#faf9f5"
|
||||
},
|
||||
{
|
||||
"name": "surface",
|
||||
"value": "#ffffff"
|
||||
},
|
||||
{
|
||||
"name": "ink",
|
||||
"value": "#1a1a18"
|
||||
},
|
||||
{
|
||||
"name": "ink-mute",
|
||||
"value": "#57564f"
|
||||
},
|
||||
{
|
||||
"name": "ink-faint",
|
||||
"value": "#8a887f"
|
||||
},
|
||||
{
|
||||
"name": "line",
|
||||
"value": "#e7e5dc"
|
||||
},
|
||||
{
|
||||
"name": "line-soft",
|
||||
"value": "#efeee7"
|
||||
},
|
||||
{
|
||||
"name": "asset-surface",
|
||||
"value": "#FFFFFF"
|
||||
},
|
||||
{
|
||||
"name": "accent",
|
||||
"value": "#9E4CFF"
|
||||
},
|
||||
{
|
||||
"name": "accent-ink",
|
||||
"value": "#9E4CFF"
|
||||
},
|
||||
{
|
||||
"name": "on-accent",
|
||||
"value": "#FFFFFF"
|
||||
},
|
||||
{
|
||||
"name": "ok",
|
||||
"value": "#3d7a4f"
|
||||
},
|
||||
{
|
||||
"name": "err",
|
||||
"value": "#b4453a"
|
||||
},
|
||||
{
|
||||
"name": "radius",
|
||||
"value": "14px"
|
||||
},
|
||||
{
|
||||
"name": "ui-radius",
|
||||
"value": "16px"
|
||||
},
|
||||
{
|
||||
"name": "btn-bg",
|
||||
"value": "#9E4CFF"
|
||||
},
|
||||
{
|
||||
"name": "btn-fg",
|
||||
"value": "#FFFFFF"
|
||||
},
|
||||
{
|
||||
"name": "btn-bd",
|
||||
"value": "#9E4CFF"
|
||||
},
|
||||
{
|
||||
"name": "btn-bw",
|
||||
"value": "1px"
|
||||
},
|
||||
{
|
||||
"name": "btn-radius",
|
||||
"value": "26px"
|
||||
},
|
||||
{
|
||||
"name": "btn-px",
|
||||
"value": "12px"
|
||||
},
|
||||
{
|
||||
"name": "btn-py",
|
||||
"value": "6px"
|
||||
},
|
||||
{
|
||||
"name": "btn-weight",
|
||||
"value": "400"
|
||||
},
|
||||
{
|
||||
"name": "btn-transform",
|
||||
"value": "none"
|
||||
},
|
||||
{
|
||||
"name": "btn-ls",
|
||||
"value": "normal"
|
||||
},
|
||||
{
|
||||
"name": "btn-shadow",
|
||||
"value": "none"
|
||||
},
|
||||
{
|
||||
"name": "inp-bg",
|
||||
"value": "#FFFFFF"
|
||||
},
|
||||
{
|
||||
"name": "inp-fg",
|
||||
"value": "#000000"
|
||||
},
|
||||
{
|
||||
"name": "inp-bd",
|
||||
"value": "#E5E7EB"
|
||||
},
|
||||
{
|
||||
"name": "inp-bw",
|
||||
"value": "1px"
|
||||
},
|
||||
{
|
||||
"name": "inp-radius",
|
||||
"value": "9px"
|
||||
},
|
||||
{
|
||||
"name": "inp-px",
|
||||
"value": "12px"
|
||||
},
|
||||
{
|
||||
"name": "inp-py",
|
||||
"value": "10px"
|
||||
},
|
||||
{
|
||||
"name": "inp-shadow",
|
||||
"value": "none"
|
||||
},
|
||||
{
|
||||
"name": "card-bg",
|
||||
"value": "#FFFFFF"
|
||||
},
|
||||
{
|
||||
"name": "card-bd",
|
||||
"value": "#FFFFFF"
|
||||
},
|
||||
{
|
||||
"name": "card-bw",
|
||||
"value": "0px"
|
||||
},
|
||||
{
|
||||
"name": "card-radius",
|
||||
"value": "24px"
|
||||
},
|
||||
{
|
||||
"name": "card-shadow",
|
||||
"value": "rgba(0, 0, 0, 0) 0px 18px 40px -18px"
|
||||
},
|
||||
{
|
||||
"name": "card-pad",
|
||||
"value": "28px"
|
||||
},
|
||||
{
|
||||
"name": "font-display",
|
||||
"value": "\"PingFang SC\", -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif"
|
||||
},
|
||||
{
|
||||
"name": "font-body",
|
||||
"value": "\"PingFang SC\", -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif"
|
||||
},
|
||||
{
|
||||
"name": "font-mono",
|
||||
"value": "\"PingFang SC\", -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif"
|
||||
}
|
||||
]
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
/* tiaoyue.com —— 暗色主题设计令牌(design-system-tiaoyue 技能资产)
|
||||
*
|
||||
* ⛔⛔⛔ 规则(2026-10-05 裁决,非建议):暗色与亮色是**两套独立基线**。
|
||||
* ---------------------------------------------------------------------------
|
||||
* 本文件暗色值实测自https://www.tiaoyue.com/team/onboarding 活动页
|
||||
*(CDP Runtime.evaluate + getComputedStyle(document.documentElement))。
|
||||
* ⛔ 但**同一次实测读到的该页亮色值,与本技能 assets/tokens.css 的亮色基线不同**:
|
||||
*
|
||||
* 角色 本文件配套的亮色(实测) tokens.css 亮色基线(9页 clipper)
|
||||
* bg #f2f2f2 #faf9f5
|
||||
* ink #000000 #1a1a18
|
||||
* muted #52525b #57564f
|
||||
* faint #71717a #8a887f
|
||||
* line #f4f4f5 #e7e5dc
|
||||
*
|
||||
* · ⛔ 不得把本文件暗色与 assets/tokens.css 亮色混搭成一套上生产。
|
||||
* · ⛔ 不得声称二者「同源」—— 逐条比对 6 角色 5 条不同,色温相反(暖 5/5 vs 冷 3/5)。
|
||||
* · ✅ 要暗色 ⇒ 整套切:本文件 dark + 本文件附0 的 light(同源于一次 CDP 实测)。
|
||||
* · ⭐ 唯一可跨基线共用的值:--accent = #9e4cff。
|
||||
* · ⚠️ 为什么这条不能靠对比度兜底:实测跨路混搭的对比度**全部达标**
|
||||
* (A 的 muted 压 B 的 bg = 6.58:1)⇒ 混搭在量测上**看不出来**,
|
||||
* 只能靠"令牌文件分离 + 整套切"这条结构约定防住。
|
||||
* 冲突登记见 references/known-conflicts.md(条目 C-01,已裁决「不混搭」部分)。
|
||||
*
|
||||
* ⭐ 唯一跨两条链路恒定的值:`--accent = #9e4cff`(品牌紫不随主题翻转)。
|
||||
*
|
||||
* 站点主题机制:`html[data-theme="dark"|"light"]` 显式属性。
|
||||
* ⛔ 不是 prefers-color-scheme(9 页快照全文检索零命中)。
|
||||
* 默认 dark(首页首帧内联脚本,key = VITE_APP_STORAGE_PREFIX + '-app')。
|
||||
*
|
||||
* 抓取脚本/ 原始读数 / 路线判定:见 [暗色主题补抓]/(probe_dark_tokens.py、_probe_raw.json、取证报告.md)
|
||||
* 逐条对比度复算:_verify_contrast.py(本技能内同目录tools/)
|
||||
*/
|
||||
|
||||
/* ============ 七语义色角色(暗色) ============ */
|
||||
:root[data-theme="dark"] {
|
||||
/* —— 底/面 ——
|
||||
* ⭐ 暗色的"面"主要靠**半透明白叠加**,不是硬 hex。下列 hex 取的是「强面」
|
||||
* (card-strong / modal-surface)。做叠加式卡片请用末尾 overlay 那一支。 */
|
||||
--bg: #0d0d12; /* 页面底 */
|
||||
--surface: #27272a; /* 卡片面(= card-strong) */
|
||||
--surface-soft: #2b2b33; /* 次级面 */
|
||||
--field: #18161b; /* 输入槽底 */
|
||||
--modal-surface: #27272a; /* 浮层底 */
|
||||
|
||||
/* —— 文字 ——
|
||||
* ⚠️ 暗色只有**两级**可用文字灰,不是三级。理由见下方 --muted-weak 处方。 */
|
||||
--ink: #ffffff; /* 主文字 19.38:1 (bg) / 14.89:1 (surface) */
|
||||
--ink-rgb: 255 255 255; /* 供 rgb(var(--ink-rgb)/α) 用 */
|
||||
--muted: #a1a1aa; /* 次文字7.56:1 (bg) / 5.81:1 (surface) ✅ AA */
|
||||
|
||||
/* ⛔ --muted-weak 在暗色下**不是文字色**,降级为「非文字角色」。
|
||||
* 实测:#71717a 对 bg 4.01 / surface 3.08 / surface-soft 2.90 ⇒ 卡内正文全不过。
|
||||
* 沿明度轴提到 #A0A0A7 可过三底(7.46/5.73/5.40),但它与 --muted #a1a1aa
|
||||
* 视觉上几乎不可区分 ⇒ 拿它当"第三级灰"等于造一个假层级。
|
||||
* ⇒ 处方:暗色下文字只用 ink + muted 两级;第三级用 --muted-weak 仅做
|
||||
* 分隔线 / 图标 / 禁用态。⛔ 不许拿它写正文。 */
|
||||
--muted-weak: #71717a; /* ⛔ 仅非文字用途 */
|
||||
|
||||
/* —— 线 —— */
|
||||
--line: rgba(255, 255, 255, .06); /* 分隔线(=叠加白,见 overlay 段) */
|
||||
--field-border: rgba(255, 255, 255, .06); /* 控件边 */
|
||||
--modal-border: #3f3f46; /* 浮层边(实测值,强于叠加) */
|
||||
|
||||
/* —— 主色(品牌) —— */
|
||||
--color-primary: #9e4cff; /* ⭐ 与亮色同值,绝不翻转 */
|
||||
--primary-rgb: 158 76 255;
|
||||
--text-on-primary: #ffffff; /* ⚠️ 对 --color-primary 只有 4.26:1,见下 */
|
||||
|
||||
/* —— 状态 —— */
|
||||
--color-success: #34d399; /* 10.08:1 (bg) */
|
||||
--color-danger: #f87171; /* 7.01:1 (bg) */
|
||||
--color-warning: #fb923c; /* 8.56:1 (bg) */
|
||||
|
||||
/* —— 底分量 —— */
|
||||
--bg-rgb: 13 13 18;
|
||||
}
|
||||
|
||||
/* ============ 附0:与暗色「同源」的亮色基线(B 路 · 关键) ============ */
|
||||
/* ⛔⛔ 为什么需要这个块:暗色与 assets/tokens.css 的亮色**不是同一套**(见头部 C-01)。
|
||||
* 要用暗色,就必须连同**B 路亮色**一起切,否则就是跨体系混搭。
|
||||
* ⇒ 「整套切」= 本块的 light + 上面的 dark,两值同源于一次 CDP 实测。
|
||||
*
|
||||
* ⚠️ 本块⛔ 不是 clipper 那套(#faf9f5 / #1a1a18 / #57564f / #8a887f / #e7e5dc)。
|
||||
* 两者差异见头部表格;⛔ 不要把它们混着用。
|
||||
* ⚠️ B 路 ink 是 **#000000 纯黑**:craft/color.md 明确劝阻纯黑(振动/眼疲劳)。
|
||||
* ⛔ 本块照抄实测值不改(改了就又不是实测值了);若要缓解,
|
||||
* **先裁决 C-01**再统一处理,⛔ 不许单点手调。
|
||||
*/
|
||||
:root[data-theme="light"] {
|
||||
--bg: #f2f2f2; /* 实测值(⛔ 非 #faf9f5,见 C-01) */
|
||||
--surface: #ffffff;
|
||||
--surface-soft: #f0f2f5;
|
||||
--field: #ffffff;
|
||||
--modal-surface: #ffffff;
|
||||
|
||||
--ink: #000000; /* ⚠️ 纯黑,见上方警告 */
|
||||
--ink-rgb: 24 24 27; /* ⚠️ 与 --ink 不自洽,见 known-conflicts */
|
||||
--muted: #52525b; /* 6.90:1 (bg) */
|
||||
--muted-weak: #71717a; /* 4.32:1 (bg) · ⛔ 仅大字/非文字 */
|
||||
|
||||
--line: #f4f4f5;
|
||||
--field-border: #f4f4f5;
|
||||
--modal-border: #e4e4e7;
|
||||
|
||||
--color-primary: #9e4cff; /* ⭐ 恒定 */
|
||||
--primary-rgb: 158 76 255;
|
||||
--text-on-primary: #ffffff; /* 4.26:1 · ⛔ 见 known-conflicts C-02 */
|
||||
|
||||
--color-success: #059669;
|
||||
--color-danger: #ef4444;
|
||||
--color-warning: #f97316;
|
||||
|
||||
--bg-rgb: 242 242 242;
|
||||
|
||||
/* 亮色侧 accent 文本档(#9e4cff 直接当文字仅 4.04/4.26 ⇒ 降到 4.52/4.76) */
|
||||
--accent-text: #963dff;
|
||||
|
||||
--radius-base: 12px;
|
||||
--radius-l: 16px;
|
||||
--radius-full: 999px;
|
||||
}
|
||||
|
||||
/* ============ 附1:accent 文本档(craft/color.md 处方 · 本项目实测推导) ============ */
|
||||
/* ⭐⭐ 处方在暗色下**方向相反**:craft/color.md 说「文本用色降一档,fill 留brand 亮色」,
|
||||
* 那是**亮底**的降法。暗底上 accent #9e4cff 直接当文字只有
|
||||
* bg 3.79:1 / surface 2.91:1 ⇒ ⛔ 须**提亮**,不是降档。
|
||||
* 推导:沿 HSL 明度轴搜临界档,⛔ 色相不变(H=267.5°、S=1.000 全程锁定)。
|
||||
*/
|
||||
:root[data-theme="dark"] {
|
||||
--accent-text: #b270ff; /* L=0.720 · bg 6.14:1 / surface 4.72:1 ✅ 双底过 4.5 */
|
||||
}
|
||||
|
||||
/* ============ 附2:暗色"面"的构成方式(站内实际写法) ============ */
|
||||
/* ⭐ 关键:站内 --figma-surface-card 实为 rgba(255,255,255,.06),不是 hex。
|
||||
* 若做叠加式卡片,用下列 overlay 令牌,⛔ 别用 --surface 的 #27272a 硬顶
|
||||
* (会失去与底色的透叠关系,且 hover 态无处可去)。
|
||||
* 实测合成后(算得,非目测):
|
||||
* 白 .06 叠 bg => #1C1C20 vs ink 16.99 / vs muted 6.63
|
||||
* 白 .10 叠 bg => #25252A vs ink 15.25 / vs muted 5.95 ← hover 用这档
|
||||
* 白 .06 叠 surface => #343437 vs ink 12.41 / vs muted 4.84
|
||||
*/
|
||||
:root[data-theme="dark"] {
|
||||
--overlay-card: rgba(255, 255, 255, .06);
|
||||
--overlay-card-hover: rgba(255, 255, 255, .10);
|
||||
--overlay-secondary: rgba(255, 255, 255, .08);
|
||||
--overlay-input:rgba(255, 255, 255, .06);
|
||||
}
|
||||
|
||||
/* ============ 附3:几何(两形态同值,取自同一 DOM) ============ */
|
||||
:root[data-theme="dark"] {
|
||||
--radius-base: 12px;
|
||||
--radius-l: 16px;
|
||||
--radius-full: 999px;
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/* tiaoyue.com design tokens — 由 open-design clipper 提取 */
|
||||
:root {
|
||||
--paper: #faf9f5;
|
||||
--surface: #ffffff;
|
||||
--ink: #1a1a18;
|
||||
--ink-mute: #57564f;
|
||||
--ink-faint: #8a887f;
|
||||
--line: #e7e5dc;
|
||||
--line-soft: #efeee7;
|
||||
--asset-surface: #FFFFFF;
|
||||
--accent: #9E4CFF;
|
||||
--accent-ink: #9E4CFF;
|
||||
--on-accent: #FFFFFF;
|
||||
--ok: #3d7a4f;
|
||||
--err: #b4453a;
|
||||
--radius: 14px;
|
||||
--ui-radius: 16px;
|
||||
--btn-bg: #9E4CFF;
|
||||
--btn-fg: #FFFFFF;
|
||||
--btn-bd: #9E4CFF;
|
||||
--btn-bw: 1px;
|
||||
--btn-radius: 26px;
|
||||
--btn-px: 12px;
|
||||
--btn-py: 6px;
|
||||
--btn-weight: 400;
|
||||
--btn-transform: none;
|
||||
--btn-ls: normal;
|
||||
--btn-shadow: none;
|
||||
--inp-bg: #FFFFFF;
|
||||
--inp-fg: #000000;
|
||||
--inp-bd: #E5E7EB;
|
||||
--inp-bw: 1px;
|
||||
--inp-radius: 9px;
|
||||
--inp-px: 12px;
|
||||
--inp-py: 10px;
|
||||
--inp-shadow: none;
|
||||
--card-bg: #FFFFFF;
|
||||
--card-bd: #FFFFFF;
|
||||
--card-bw: 0px;
|
||||
--card-radius: 24px;
|
||||
--card-shadow: rgba(0, 0, 0, 0) 0px 18px 40px -18px;
|
||||
--card-pad: 28px;
|
||||
--font-display: "PingFang SC", -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", "Noto Sans SC", sans-serif;
|
||||
--font-body: "PingFang SC", -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", "Noto Sans SC", sans-serif;
|
||||
--font-mono: "PingFang SC", -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", "Noto Sans SC", sans-serif;
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
# tiaoyue 设计系统提取记录
|
||||
|
||||
## 结论
|
||||
|
||||
按 A 案(open-design clipper 扩展)提取 `https://www.tiaoyue.com/` 的设计风格与组件样式,**已达成**。
|
||||
|
||||
## 产出
|
||||
|
||||
- `design-system.html`(1,427,632 B)—— clipper 原始产出,自包含 HTML,含令牌 + 组件样例
|
||||
- `tokens.css`(43 个令牌)
|
||||
- `brand.json`(七语义色角色 + 排版 + 资产清单)
|
||||
- `_asset_raw.json`(daemon 资产的元数据快照)
|
||||
|
||||
## 提取到的设计系统
|
||||
|
||||
**七语义色角色**(源站是**亮色主题**为基准):
|
||||
|
||||
- background:`#faf9f5`(暖白纸感)
|
||||
- surface:`#ffffff`
|
||||
- foreground:`#1a1a18`
|
||||
- muted:`#57564f`
|
||||
- faint:`#8a887f`
|
||||
- border:`#e7e5dc` / border-soft:`#efeee7`
|
||||
- accent:`#9E4CFF`(紫,源站主强调色)
|
||||
- on-accent:`#FFFFFF`
|
||||
- ok:`#3d7a4f` / err:`#b4453a`
|
||||
|
||||
**排版**:35 处 `font-family` 声明,`@font-face` 0 条(源站字体走系统栈,未内嵌 webfont)
|
||||
|
||||
**几何**:`--radius: 14px`、`--ui-radius: 16px`、`--btn-radius: 26px`(药丸形按钮)、`--btn-bw: 1px`
|
||||
|
||||
**资产**:8 个 logo 候选、15 张代表图、31 个内联 data URI
|
||||
|
||||
**组件**:53 个按钮 / 5 个卡片 / 导航 —— 均为**用提取出的令牌渲染**的样例
|
||||
|
||||
## 技术链路(以便复现)
|
||||
|
||||
1. open-design 依赖安装:`pnpm install --frozen-lockfile --ignore-scripts`(**必须 `--ignore-scripts`**,见坑)
|
||||
2. 原生包补构建:better-sqlite3 用 `prebuild-install`、sharp 用 `install/check.js`、protobufjs 用 `scripts/postinstall`
|
||||
3. 构建:`pnpm -r --filter "@open-design/daemon..." run build`
|
||||
4. 起 daemon:`node apps/daemon/dist/cli.js --no-open` → `127.0.0.1:7456`
|
||||
5. 起独立 Chrome(9223)+ `--enable-unsafe-extension-debugging`
|
||||
6. **装扩展不用 `--load-extension`**(Chrome 137+ 已移除)⇒ 用 CDP `Extensions.loadUnpacked`
|
||||
7. 唤醒 MV3 SW(开 `popup.html`)→ 激活目标页 → 在 popup 上下文 `chrome.runtime.sendMessage({type:'captureDesignSystemToLibrary'})`
|
||||
8. 取回:`GET /api/library/assets/<id>/raw`
|
||||
|
||||
## 未验证 / 缺口
|
||||
|
||||
- **源站是明暗双主题**(首页默认 dark `#0b0b10`),本次抓到的是**登录后的亮色形态**;暗色令牌未单独抓取
|
||||
- `@font-face` 为 0 —— 源站可能用 CDN 字体或系统栈,未进一步追
|
||||
- **未跑 `brand-extract` skill**(B 案)做交叉校验
|
||||
+499
@@ -0,0 +1,499 @@
|
||||
# 跳跃视界(tiaoyue.com)组件库
|
||||
|
||||
从 9 个页面快照里实测提取。自有组件族 **277** 个,Element Plus 类 52 个,Tailwind 工具类 659 个。
|
||||
|
||||
> ⚠️ **生成页面时的参考范围**:用户 2026-10-05 定案 —— **避开「团队创建」页(`team` / `/team/onboarding`)**,
|
||||
> 只参考个人可操作页面。库里仍保留 team 页数据(保完整性),但标了 `teamOnly: true` 的族**生成时不要参考**。
|
||||
|
||||
## 一、全局组件(跨 9 页复用)
|
||||
|
||||
- **double-festival-card**(99 次 / 9 页)
|
||||
- `double-festival-card` ×9
|
||||
- `double-festival-card__hero` ×9
|
||||
- `double-festival-card__content` ×9
|
||||
- `double-festival-card__logo` ×9
|
||||
- `double-festival-card__heading` ×9
|
||||
- `double-festival-card__eyebrow` ×9
|
||||
- **nav-tips**(90 次 / 9 页)
|
||||
- `nav-tips` ×9
|
||||
- `nav-tips__row` ×81
|
||||
- **nav-item**(81 次 / 9 页)
|
||||
- `nav-item` ×81
|
||||
- **nav-icon**(81 次 / 9 页)
|
||||
- `nav-icon` ×81
|
||||
- **nav-tip**(81 次 / 9 页)
|
||||
- `nav-tip` ×81
|
||||
- **menu-item**(81 次 / 9 页)
|
||||
- `menu-item__label` ×72
|
||||
- `menu-item__badge` ×9
|
||||
- **maiya-btn**(78 次 / 9 页)
|
||||
- `maiya-btn--secondaryWeak` ×9
|
||||
- `maiya-btn--size-md` ×21
|
||||
- `maiya-btn__label` ×26
|
||||
- `maiya-btn--primary` ×11
|
||||
- `maiya-btn--tertiary` ×2
|
||||
- `maiya-btn--size-space` ×5
|
||||
- **is-inactive**(72 次 / 9 页)
|
||||
- `is-inactive` ×72
|
||||
- **is-light**(69 次 / 9 页)
|
||||
- `is-light` ×69
|
||||
- **deployment-update-notice**(54 次 / 9 页)
|
||||
- `deployment-update-notice` ×9
|
||||
- `deployment-update-notice__panel` ×9
|
||||
- `deployment-update-notice__icon` ×9
|
||||
- `deployment-update-notice__badge` ×9
|
||||
- `deployment-update-notice__close` ×9
|
||||
- `deployment-update-notice__actions` ×9
|
||||
- **auth-field**(54 次 / 9 页)
|
||||
- `auth-field` ×54
|
||||
- **auth-agreement-link**(54 次 / 9 页)
|
||||
- `auth-agreement-link` ×54
|
||||
- **header-action-btn**(37 次 / 9 页)
|
||||
- `header-action-btn--text` ×27
|
||||
- `header-action-btn--icon` ×10
|
||||
- **course-entry**(36 次 / 9 页)
|
||||
- `course-entry` ×9
|
||||
- `course-entry__hover-bg` ×9
|
||||
- `course-entry__details` ×9
|
||||
- `course-entry__badge` ×9
|
||||
- **points-capsule**(36 次 / 9 页)
|
||||
- `points-capsule--single` ×9
|
||||
- `points-capsule__section` ×9
|
||||
- `points-capsule__interactive` ×9
|
||||
- `points-capsule__points` ×9
|
||||
- **beta-code-panel**(27 次 / 9 页)
|
||||
- `beta-code-panel` ×18
|
||||
- `beta-code-panel--white` ×9
|
||||
- **notice-tabs**(27 次 / 9 页)
|
||||
- `notice-tabs__item` ×18
|
||||
- `notice-tabs__badge` ×9
|
||||
- **is-active**(23 次 / 9 页)
|
||||
- `is-active` ×23
|
||||
- **avatar-personal**(18 次 / 9 页)
|
||||
- `avatar-personal` ×18
|
||||
- **beta-code-popper**(18 次 / 9 页)
|
||||
- `beta-code-popper` ×18
|
||||
- **auth-dialog-overlay**(18 次 / 9 页)
|
||||
- `auth-dialog-overlay` ×18
|
||||
- **is-align-center**(18 次 / 9 页)
|
||||
- `is-align-center` ×18
|
||||
- **auth-tab**(18 次 / 9 页)
|
||||
- `auth-tab` ×18
|
||||
- **auth-submit-btn**(18 次 / 9 页)
|
||||
- `auth-submit-btn` ×18
|
||||
- **auth-agreement-text**(18 次 / 9 页)
|
||||
- `auth-agreement-text` ×18
|
||||
- **double-festival-shell**(18 次 / 9 页)
|
||||
- `double-festival-shell` ×9
|
||||
- `double-festival-shell__close` ×9
|
||||
- **app-bg**(13 次 / 9 页)
|
||||
- `app-bg` ×9
|
||||
- `app-bg--home` ×1
|
||||
- `app-bg--creative-workbench` ×3
|
||||
- **layout-main**(10 次 / 9 页)
|
||||
- `layout-main` ×9
|
||||
- `layout-main--no-scrollbar` ×1
|
||||
- **is-disabled**(10 次 / 9 页)
|
||||
- `is-disabled` ×10
|
||||
- **-bottom-px**(10 次 / 9 页)
|
||||
- `-bottom-px` ×10
|
||||
- **vjs-styles-defaults**(9 次 / 9 页)
|
||||
- `vjs-styles-defaults` ×9
|
||||
- **-z-10**(9 次 / 9 页)
|
||||
- `-z-10` ×9
|
||||
- **sidebar-shell**(9 次 / 9 页)
|
||||
- `sidebar-shell` ×9
|
||||
- **app-sidebar**(9 次 / 9 页)
|
||||
- `app-sidebar` ×9
|
||||
- **nav-tips-layer**(9 次 / 9 页)
|
||||
- `nav-tips-layer` ×9
|
||||
- **nav-tips-viewport**(9 次 / 9 页)
|
||||
- `nav-tips-viewport` ×9
|
||||
- **is-visible**(9 次 / 9 页)
|
||||
- `is-visible` ×9
|
||||
- **is-expand-right**(9 次 / 9 页)
|
||||
- `is-expand-right` ×9
|
||||
- **-top-1.5**(9 次 / 9 页)
|
||||
- `-top-1.5` ×9
|
||||
- **layout-header**(9 次 / 9 页)
|
||||
- `layout-header` ×9
|
||||
- **-mr-10**(9 次 / 9 页)
|
||||
- `-mr-10` ×9
|
||||
- **router-link-active**(9 次 / 9 页)
|
||||
- `router-link-active` ×9
|
||||
- **notification-trigger**(9 次 / 9 页)
|
||||
- `notification-trigger__badge` ×9
|
||||
- **is-fixed**(9 次 / 9 页)
|
||||
- `is-fixed` ×9
|
||||
- **is-dot**(9 次 / 9 页)
|
||||
- `is-dot` ×9
|
||||
- **header-avatar-btn**(9 次 / 9 页)
|
||||
- `header-avatar-btn__fallback` ×9
|
||||
- **course-qr-panel**(9 次 / 9 页)
|
||||
- `course-qr-panel` ×9
|
||||
- **points-balance-popover**(9 次 / 9 页)
|
||||
- `points-balance-popover__total` ×9
|
||||
- **user-menu-popper**(9 次 / 9 页)
|
||||
- `user-menu-popper` ×9
|
||||
- **user-panel**(9 次 / 9 页)
|
||||
- `user-panel` ×9
|
||||
- **switch-identity-btn**(9 次 / 9 页)
|
||||
- `switch-identity-btn` ×9
|
||||
- **auth-dialog**(9 次 / 9 页)
|
||||
- `auth-dialog` ×9
|
||||
- **auth-card**(9 次 / 9 页)
|
||||
- `auth-card` ×9
|
||||
- **auth-code-btn**(9 次 / 9 页)
|
||||
- `auth-code-btn` ×9
|
||||
- **auth-agreement-check**(9 次 / 9 页)
|
||||
- `auth-agreement-check` ×9
|
||||
- **create-team-modal**(9 次 / 9 页)
|
||||
- `create-team-modal` ×9
|
||||
- **referral-modal**(9 次 / 9 页)
|
||||
- `referral-modal` ×9
|
||||
- **referral-qr-modal**(9 次 / 9 页)
|
||||
- `referral-qr-modal` ×9
|
||||
- **feedback-modal**(9 次 / 9 页)
|
||||
- `feedback-modal` ×9
|
||||
- **is-drawer**(9 次 / 9 页)
|
||||
- `is-drawer` ×9
|
||||
- **account-settings-modal**(9 次 / 9 页)
|
||||
- `account-settings-modal` ×9
|
||||
- **account-settings-drawer**(9 次 / 9 页)
|
||||
- `account-settings-drawer` ×9
|
||||
- **rtl**(9 次 / 9 页)
|
||||
- `rtl` ×9
|
||||
- **switch-identity-modal**(9 次 / 9 页)
|
||||
- `switch-identity-modal` ×9
|
||||
- **contact-modal**(9 次 / 9 页)
|
||||
- `contact-modal` ×9
|
||||
- **business-cooperation-modal**(9 次 / 9 页)
|
||||
- `business-cooperation-modal` ×9
|
||||
- **double-festival-modal**(9 次 / 9 页)
|
||||
- `double-festival-modal` ×9
|
||||
- **double-festival-dialog**(9 次 / 9 页)
|
||||
- `double-festival-dialog` ×9
|
||||
- **tenant-switch-modal**(9 次 / 9 页)
|
||||
- `tenant-switch-modal` ×9
|
||||
- **asset-picker-sdk-overlay**(9 次 / 9 页)
|
||||
- `asset-picker-sdk-overlay` ×9
|
||||
- **asset-picker-sdk-frame**(9 次 / 9 页)
|
||||
- `asset-picker-sdk-frame` ×9
|
||||
- **is-ready**(9 次 / 9 页)
|
||||
- `is-ready` ×9
|
||||
|
||||
## 二、单页专属组件
|
||||
|
||||
### 首页(home)— 26 个专属族
|
||||
- **featured-card**(260 次)
|
||||
- `featured-card` ×20
|
||||
- `featured-card--landscape` ×20
|
||||
- `featured-card__media` ×20
|
||||
- `featured-card__badge` ×20
|
||||
- `featured-card__like` ×20
|
||||
- **quick-card**(40 次)
|
||||
- `quick-card` ×4
|
||||
- `quick-card--split` ×1
|
||||
- `quick-card__text` ×4
|
||||
- `quick-card__title` ×4
|
||||
- `quick-card__desc` ×4
|
||||
- **asset-track-filter**(23 次)
|
||||
- `asset-track-filter` ×1
|
||||
- `asset-track-filter__tab` ×7
|
||||
- `asset-track-filter__tab--active` ×1
|
||||
- `asset-track-filter__label` ×7
|
||||
- `asset-track-filter__tab--inactive` ×6
|
||||
- **home-hero-carousel**(20 次)
|
||||
- `home-hero-carousel__stage` ×1
|
||||
- `home-hero-carousel__card` ×3
|
||||
- `home-hero-carousel__card-inner` ×3
|
||||
- `home-hero-carousel__overlay` ×3
|
||||
- `home-hero-carousel__nav` ×2
|
||||
- **featured-card-wrap**(20 次)
|
||||
- `featured-card-wrap` ×20
|
||||
- **quick-split**(19 次)
|
||||
- `quick-split` ×1
|
||||
- `quick-split__item` ×2
|
||||
- `quick-split__item--left` ×1
|
||||
- `quick-split__panel` ×2
|
||||
- `quick-split__icon` ×2
|
||||
- **app-footer**(14 次)
|
||||
- `app-footer` ×1
|
||||
- `app-footer__link` ×6
|
||||
- `app-footer__sep` ×4
|
||||
- `app-footer__icp` ×1
|
||||
- `app-footer__company` ×1
|
||||
- **home-carousel-card-media**(4 次)
|
||||
- `home-carousel-card-media__layer` ×4
|
||||
- **is-side**(2 次)
|
||||
- `is-side` ×2
|
||||
- **featured-artist-tab**(2 次)
|
||||
- `featured-artist-tab` ×1
|
||||
- `featured-artist-tab__badge` ×1
|
||||
- **asset-recommend-subfilters**(2 次)
|
||||
- `asset-recommend-subfilters` ×1
|
||||
- `asset-recommend-subfilters__tools` ×1
|
||||
- **featured-asset-player-overlay**(2 次)
|
||||
- `featured-asset-player-overlay` ×2
|
||||
- **app-aurora**(1 次)
|
||||
- `app-aurora` ×1
|
||||
- **router-link-exact-active**(1 次)
|
||||
- `router-link-exact-active` ×1
|
||||
- **is-center**(1 次)
|
||||
- `is-center` ×1
|
||||
|
||||
### 短视频(shortvideo)— 40 个专属族
|
||||
- **creative-skill-card**(54 次)
|
||||
- `creative-skill-card` ×9
|
||||
- `creative-skill-card__cover` ×9
|
||||
- `creative-skill-card__body` ×9
|
||||
- `creative-skill-card__title` ×9
|
||||
- `creative-skill-card__desc` ×9
|
||||
- **agent-run-mode-item**(11 次)
|
||||
- `agent-run-mode-item` ×2
|
||||
- `agent-run-mode-item__icon` ×2
|
||||
- `agent-run-mode-item__text` ×2
|
||||
- `agent-run-mode-item__label` ×2
|
||||
- `agent-run-mode-item__desc` ×2
|
||||
- **creative-start-card**(9 次)
|
||||
- `creative-start-card` ×1
|
||||
- `creative-start-card__ghost` ×1
|
||||
- `creative-start-card__ghost-cover` ×1
|
||||
- `creative-start-card__ghost-info` ×1
|
||||
- `creative-start-card__ghost-line` ×2
|
||||
- **cutm-item**(9 次)
|
||||
- `cutm-item` ×3
|
||||
- `cutm-item__icon` ×2
|
||||
- `cutm-item__label` ×3
|
||||
- `cutm-item__img` ×1
|
||||
- **cib**(3 次)
|
||||
- `cib` ×1
|
||||
- `cib--home` ×1
|
||||
- `cib--expanded` ×1
|
||||
- **css-tab**(3 次)
|
||||
- `css-tab` ×3
|
||||
- **cars-trigger**(2 次)
|
||||
- `cars-trigger` ×1
|
||||
- `cars-trigger--pill` ×1
|
||||
- **section-header**(2 次)
|
||||
- `section-header__view-all` ×2
|
||||
- **creativeHomeComposer**(1 次)
|
||||
- `creativeHomeComposer` ×1
|
||||
- **creativeHomeHeroTitle**(1 次)
|
||||
- `creativeHomeHeroTitle` ×1
|
||||
- **cmi**(1 次)
|
||||
- `cmi` ×1
|
||||
- **is-focused**(1 次)
|
||||
- `is-focused` ×1
|
||||
- **cib-textarea**(1 次)
|
||||
- `cib-textarea` ×1
|
||||
- **cmi-content**(1 次)
|
||||
- `cmi-content` ×1
|
||||
- **tiptap**(1 次)
|
||||
- `tiptap` ×1
|
||||
|
||||
### 转绘(redraw)— 4 个专属族
|
||||
- **dramaHomeSkeletonRedraw**(1 次)
|
||||
- `dramaHomeSkeletonRedraw` ×1
|
||||
- **dhsRedrawCardLeft**(1 次)
|
||||
- `dhsRedrawCardLeft` ×1
|
||||
- **dhsRedrawCardRight**(1 次)
|
||||
- `dhsRedrawCardRight` ×1
|
||||
- **dhsRedrawCardCenter**(1 次)
|
||||
- `dhsRedrawCardCenter` ×1
|
||||
|
||||
### 剧本(script)— 15 个专属族
|
||||
- **index-feature-card**(18 次)
|
||||
- `index-feature-card` ×4
|
||||
- `index-feature-card--wide` ×1
|
||||
- `index-feature-card__icon` ×4
|
||||
- `index-feature-card__deco` ×1
|
||||
- `index-feature-card__action` ×4
|
||||
- **script-square-filter-popper**(8 次)
|
||||
- `script-square-filter-popper` ×5
|
||||
- `script-square-filter-popper--multi` ×3
|
||||
- **feature-entry-hover-content**(4 次)
|
||||
- `feature-entry-hover-content` ×4
|
||||
- **index-hit-hero**(2 次)
|
||||
- `index-hit-hero` ×1
|
||||
- `index-hit-hero__inner` ×1
|
||||
- **index-hit-hero-glow**(1 次)
|
||||
- `index-hit-hero-glow` ×1
|
||||
- **script-list-card-more**(1 次)
|
||||
- `script-list-card-more` ×1
|
||||
- **-translate-x-1/2**(1 次)
|
||||
- `-translate-x-1/2` ×1
|
||||
- **script-square-section**(1 次)
|
||||
- `script-square-section` ×1
|
||||
- **script-square-tabs**(1 次)
|
||||
- `script-square-tabs` ×1
|
||||
- **script-square-card-scroll**(1 次)
|
||||
- `script-square-card-scroll` ×1
|
||||
- **script-list-card-dropdown**(1 次)
|
||||
- `script-list-card-dropdown` ×1
|
||||
- **script-list-menu-item**(1 次)
|
||||
- `script-list-menu-item--danger` ×1
|
||||
- **script-square-purchase-modal**(1 次)
|
||||
- `script-square-purchase-modal` ×1
|
||||
- **script-square-detail-modal**(1 次)
|
||||
- `script-square-detail-modal` ×1
|
||||
- **script-feature-gate-modal**(1 次)
|
||||
- `script-feature-gate-modal` ×1
|
||||
|
||||
### 资产(asset)— 19 个专属族
|
||||
- **asset-multi-select-filter**(266 次)
|
||||
- `asset-multi-select-filter` ×4
|
||||
- `asset-multi-select-filter__text` ×4
|
||||
- `asset-multi-select-filter__chevron` ×4
|
||||
- `asset-multi-select-filter__popper` ×4
|
||||
- `asset-multi-select-filter__panel` ×4
|
||||
- **asset-card**(180 次)
|
||||
- `asset-card` ×20
|
||||
- `asset-card--clickable` ×20
|
||||
- `asset-card--meta` ×20
|
||||
- `asset-card__cover` ×20
|
||||
- `asset-card__meta-tags` ×20
|
||||
- **asset-card-official-meta**(160 次)
|
||||
- `asset-card-official-meta` ×20
|
||||
- `asset-card-official-meta__body` ×20
|
||||
- `asset-card-official-meta__title` ×20
|
||||
- `asset-card-official-meta__creator` ×20
|
||||
- `asset-card-official-meta__creator-name` ×20
|
||||
- **asset-media-cover**(80 次)
|
||||
- `asset-media-cover` ×20
|
||||
- `asset-media-cover--ratio` ×20
|
||||
- `asset-media-cover__media` ×20
|
||||
- `asset-media-cover__media--cover` ×20
|
||||
- **asset-default-avatar**(60 次)
|
||||
- `asset-default-avatar` ×20
|
||||
- `asset-default-avatar--official-placeholder` ×20
|
||||
- `asset-default-avatar__official-icon` ×20
|
||||
- **asset-main-tabs**(24 次)
|
||||
- `asset-main-tabs` ×1
|
||||
- `asset-main-tabs__item` ×4
|
||||
- `asset-main-tabs__item--active` ×1
|
||||
- `asset-main-tabs__icon` ×3
|
||||
- `asset-main-tabs__icon-img` ×3
|
||||
- **waterfall**(21 次)
|
||||
- `waterfall__grid` ×1
|
||||
- `waterfall__item` ×20
|
||||
- **asset-tag**(20 次)
|
||||
- `asset-tag` ×20
|
||||
- **asset-track-pill-bar**(14 次)
|
||||
- `asset-track-pill-bar` ×1
|
||||
- `asset-track-pill-bar__tracks` ×1
|
||||
- `asset-track-pill-bar__pill` ×6
|
||||
- `asset-track-pill-bar__pill--active` ×1
|
||||
- `asset-track-pill-bar__pill--inactive` ×5
|
||||
- **asset-search-field**(11 次)
|
||||
- `asset-search-field` ×1
|
||||
- `asset-search-field--with-btn` ×1
|
||||
- `asset-search-field--official` ×1
|
||||
- `asset-search-field__input` ×1
|
||||
- `asset-search-field__divider` ×1
|
||||
- **asset-copyright-tip**(3 次)
|
||||
- `asset-copyright-tip` ×1
|
||||
- `asset-copyright-tip__trigger` ×1
|
||||
- `asset-copyright-tip__icon` ×1
|
||||
- **official-asset-banner**(3 次)
|
||||
- `official-asset-banner` ×1
|
||||
- `official-asset-banner__title` ×1
|
||||
- `official-asset-banner__desc` ×1
|
||||
- **asset-page**(2 次)
|
||||
- `asset-page` ×1
|
||||
- `asset-page__header` ×1
|
||||
- **asset-field-input**(2 次)
|
||||
- `asset-field-input` ×1
|
||||
- `asset-field-input--plain` ×1
|
||||
- **asset-official-panel**(1 次)
|
||||
- `asset-official-panel` ×1
|
||||
|
||||
### 空间(space)— 15 个专属族
|
||||
- **project-folder-design-v2**(9 次)
|
||||
- `project-folder-design-v2__stage` ×1
|
||||
- `project-folder-design-v2__back-assembly` ×1
|
||||
- `project-folder-design-v2__back` ×1
|
||||
- `project-folder-design-v2__front-assembly` ×1
|
||||
- `project-folder-design-v2__sparkles` ×1
|
||||
- **space-last-viewed-hint**(8 次)
|
||||
- `space-last-viewed-hint--card` ×1
|
||||
- `space-last-viewed-hint--rim` ×1
|
||||
- `space-last-viewed-hint__glow` ×1
|
||||
- `space-last-viewed-hint__rim` ×1
|
||||
- `space-last-viewed-hint__rim-spin` ×1
|
||||
- **space-create-button**(5 次)
|
||||
- `space-create-button__icon` ×4
|
||||
- `space-create-button__icon--lg` ×1
|
||||
- **is-plain**(5 次)
|
||||
- `is-plain` ×5
|
||||
- **space-quota-progress**(4 次)
|
||||
- `space-quota-progress--team-home` ×1
|
||||
- `space-quota-progress__divider` ×1
|
||||
- `space-quota-progress__badge` ×1
|
||||
- `space-quota-progress__track` ×1
|
||||
- **is-circle**(3 次)
|
||||
- `is-circle` ×3
|
||||
- **space-view-icon**(2 次)
|
||||
- `space-view-icon--grid` ×1
|
||||
- `space-view-icon--list` ×1
|
||||
- **space-layout**(1 次)
|
||||
- `space-layout__project-header` ×1
|
||||
- **space-page-header**(1 次)
|
||||
- `space-page-header__quota-actions` ×1
|
||||
- **is-round**(1 次)
|
||||
- `is-round` ×1
|
||||
- **space-team-search-button**(1 次)
|
||||
- `space-team-search-button__icon` ×1
|
||||
- **space-view-switch**(1 次)
|
||||
- `space-view-switch--team-home` ×1
|
||||
- **space-more-menu**(1 次)
|
||||
- `space-more-menu--card-overlay` ×1
|
||||
- **space-more-button**(1 次)
|
||||
- `space-more-button--card-overlay` ×1
|
||||
- **space-more-dropdown**(1 次)
|
||||
- `space-more-dropdown--card` ×1
|
||||
|
||||
### 团队(team)— 4 个专属族 ⚠️ **生成时避开**(用户定案)
|
||||
- **onboarding-divider**(4 次)
|
||||
- `onboarding-divider__line` ×2
|
||||
- `onboarding-divider__line--left` ×1
|
||||
- `onboarding-divider__line--right` ×1
|
||||
- **onboarding-feature**(3 次)
|
||||
- `onboarding-feature__title` ×3
|
||||
- **team-scope-content**(1 次)
|
||||
- `team-scope-content` ×1
|
||||
- **team-onboarding-card**(1 次)
|
||||
- `team-onboarding-card` ×1
|
||||
|
||||
## 三、Element Plus 组件(源站 UI 基座)
|
||||
|
||||
- `el-tooltip__trigger` ×358
|
||||
- `el-dropdown-menu__item` ×294
|
||||
- `el-overlay` ×218
|
||||
- `el-modal-dialog` ×209
|
||||
- `el-overlay-dialog` ×209
|
||||
- `el-icon` ×102
|
||||
- `el-popper` ×69
|
||||
- `el-tooltip` ×69
|
||||
- `el-popover` ×56
|
||||
- `el-input` ×56
|
||||
- `el-input__wrapper` ×56
|
||||
- `el-input__inner` ×56
|
||||
- `el-input--large` ×54
|
||||
- `el-scrollbar__bar` ×26
|
||||
- `el-scrollbar__thumb` ×26
|
||||
- `el-input--suffix` ×20
|
||||
- `el-input__suffix` ×20
|
||||
- `el-input__suffix-inner` ×20
|
||||
- `el-dialog` ×18
|
||||
- `el-dialog__header` ×18
|
||||
- `el-dialog__title` ×18
|
||||
- `el-dialog__body` ×18
|
||||
- `el-dropdown` ×13
|
||||
- `el-dropdown__popper` ×13
|
||||
- `el-scrollbar` ×13
|
||||
- `el-scrollbar__wrap` ×13
|
||||
- `el-scrollbar__wrap--hidden-default` ×13
|
||||
- `el-scrollbar__view` ×13
|
||||
- `el-dropdown__list` ×13
|
||||
- `el-dropdown-menu` ×13
|
||||
+1052
File diff suppressed because it is too large.
Load diff
+82
@@ -0,0 +1,82 @@
|
||||
# 已知冲突登记(design-system-tiaoyue)
|
||||
|
||||
> ⛔ **本文件只登记"挖出来但未裁决"的冲突**,每条必须写全:现象 / 两条独立证据 /
|
||||
> 为什么现在不裁决 / 裁决前⛔ 怎么用。
|
||||
> ⛔ **禁止**在本文件里"顺手 harmonize"—— 冲突没裁决就合并,等于把不确定变成看起来确定。
|
||||
>
|
||||
> 登记时间:2026-10-05(暗色补抓并入时)
|
||||
|
||||
---
|
||||
|
||||
## C-01 · 亮色基线存在**两套**,暗色与亮色是否同源未证
|
||||
|
||||
**状态**:🟡 **已裁决「不混搭」部分**(2026-10-05,棒2 独立复核 `_c01_recheck.py`:
|
||||
6 角色 5 条不同、色温相反 A 暖 5/5 vs B 冷 3/5、跨路混搭对比度全达标故须结构约定防住)
|
||||
**裁决内容**:暗色与亮色是两套独立基线,不共用令牌;要暗色就整套切。
|
||||
⛔ 仍**未裁决**:哪一套亮色才是 tiaoyue 主线的正解(A 路 or B 路)——
|
||||
这一条要**在主线页面**(⛔ 非 team 页)同批抓一次 light+dark 才能定,
|
||||
需要动技能之外的抓取动作,棒2 ⛔ 未做(域外 + 需批准)。
|
||||
|
||||
### 现象
|
||||
|
||||
同一个站(tiaoyue.com)的亮色形态,两次独立抓取给出**两套不同的值**:
|
||||
|
||||
| 角色 | A 路:clipper 9 页(= `assets/tokens.css`) | B 路:CDP 活动页 DOM(暗色补抓同批读出) | 是否相同 |
|
||||
|---|---|---|---|
|
||||
| background | `#faf9f5`(暖白纸感) | `#f2f2f2`(冷灰) | ❌ |
|
||||
| foreground | `#1a1a18` | `#000000` | ❌ |
|
||||
| muted | `#57564f` | `#52525b` | ❌ |
|
||||
| faint | `#8a887f` | `#71717a` | ❌ |
|
||||
| border | `#e7e5dc`(暖调) | `#f4f4f5`(冷调) | ❌ |
|
||||
| surface | `#ffffff` | `#ffffff` | ✅ |
|
||||
| **accent** | `#9E4CFF` | `#9e4cff` | ✅ **唯一恒定** |
|
||||
|
||||
⚠️ 注意 A 路整体偏**暖**(`#faf9f5`/`#e7e5dc`/`#57564f`),B 路整体偏**冷**(`#f2f2f2`/`#f4f4f5`/`#52525b`)。
|
||||
这不是零散漂移,是**两套色温不同的体系**。
|
||||
|
||||
### 两条独立证据(为什么确信"真的有两套",不是某一次读错)
|
||||
|
||||
1. **A 路**:`assets/brand.json` + `assets/tokens.css`,来源 `open-design clipper`,`extractedAt: 2026-10-05`,
|
||||
覆盖 9 个登录后页面。
|
||||
2. **B 路**:`_probe_raw.json` + `tokens-dark.css`,来源 CDP `getComputedStyle(document.documentElement)`,
|
||||
覆盖 1 个页面(`/team/onboarding`)。**同一次请求里同时切了 dark/light 两种属性**,
|
||||
变异对照有效(`data-theme` 回读值随请求变)⇒ B 路的 light/dark 读数本身可信。
|
||||
|
||||
⚠️ **但两条证据各自都有可疑之处,这正是不能立刻裁决的原因**:
|
||||
- A 路的 clipper 抓的是**渲染态**;源站默认 `dark`(首页首帧脚本自证),若clipper 抓到的其实是
|
||||
暗色渲染结果再"反推"成亮色,整套值就不可信。
|
||||
- B 路只覆盖**活动页**一个页面,而该页正是 `SKILL.md` §0 明令**生成时避开**的 team 页。
|
||||
⛔ 用一个"该避开的页面"的值去定义全站亮色,本身就可能不代表主线页面。
|
||||
|
||||
### 为什么现在不裁决
|
||||
|
||||
裁决需要**在主线页面上同批抓一次 light+dark**(一次会话、一个 profile、同一批 `getComputedStyle`),
|
||||
才能得到真正可比的 A/B。本棒⛔ 没有做(且改技能应先于重跑页面,不能在未定的判据上跑页面)。
|
||||
|
||||
### 裁决前 ⛔ 怎么用(硬规则)
|
||||
|
||||
1. **要暗色 ⇒ 整套切**:`tokens-dark.css` + B 路亮色值,⛔ 不要与 `tokens.css` 混搭。
|
||||
2. **⛔ 不要**把 `tokens.css` 亮色 + `tokens-dark.css` 暗色合成一套上生产 —— 那是本冲突的产物,
|
||||
不是解法。
|
||||
3. 本文件里 `tokens-dark.css` 的暗色值**本身可信**(实测 + 两条链路互证),
|
||||
可单独使用;⛔ 不可信的只是"它与 `tokens.css` 亮色配对"这个假设。
|
||||
|
||||
### 裁决它需要做的最小动作
|
||||
|
||||
- 在**主线页面**(如首页/ 资产页,⛔ 非 team 页)用同一探针切`light`/`dark` 各读一次,
|
||||
与现有两套值并排比对;
|
||||
- 顺带查清clipper 那次抓的到底是亮色还是暗色渲染态(决定 A 路要不要整体作废)。
|
||||
|
||||
---
|
||||
|
||||
## C-02 · `on-accent` 对 `accent` 只有 4.26:1(两形态一致)
|
||||
|
||||
**状态**:🟡 已登记,处置方式已定
|
||||
|
||||
**现象**:`#FFFFFF` / `#9e4cff` = **4.26:1**,低于正文 4.5:1,**亮暗两形态完全相同**
|
||||
⇒ 这是品牌紫的固有属性,**⛔ 不是暗色引入的新问题**,调暗色解决不了它。
|
||||
|
||||
**已定处置**(实测过候选,见下):
|
||||
- 主按钮文字用 **≥16px + bold** ⇒ 走大字线 **3:1**,4.26 ✅ 通过;
|
||||
- ⛔ 若要小字(<16px 常规字重)压在主色上,⛔ 本规范**不提供**合规解
|
||||
(试过 `#1a0a2e` = 4.38,⛔ 仍不过 4.5)⇒ 这种情况不许出现。
|
||||
+4113
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,19 @@
|
||||
# 归属与来源(ATTRIBUTION)
|
||||
|
||||
本工具(`video-capture`)的抽帧算法与铁律来自同工作区技能,非第三方项目。
|
||||
|
||||
## 来源
|
||||
|
||||
- 上游技能:`aigc-idea-impression`(`E:/ProgramData/AIProject/agent-product/.workbuddy/skills/`)
|
||||
- 上游文件:`references/操作规范/视频抽帧与字幕规范.md`
|
||||
- 算法:「双因素(画面 scene + 字幕区 crop scene)+ 首尾帧 + 长间隔补帧」;逐点 `-ss` 抽帧;CRLF 防护。
|
||||
|
||||
## 本工具改动
|
||||
|
||||
- 用 Python 标准库 + ffmpeg/ffprobe 重写为独立脚本(`scripts/extract_frames.py`),不再依赖上游技能的其它上下文。
|
||||
- **不改**抽帧判据与阈值(0.3 / 0.1 / 0.05、>5s 补中点、尾帧 时长-0.3)。
|
||||
- 「取视频」只保留最小合规集(本地文件 / 直链 URL);上游的平台页内抓取与付费接口(红狐等)**未纳入**(符合本项目收录标准)。
|
||||
|
||||
## 说明
|
||||
|
||||
- 版权归原技能作者所有;如需字幕 OCR/转录,按上游《视频抽帧与字幕规范》另做(逐帧 OCR,禁只对代表帧 OCR)。
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
name: video-capture
|
||||
description: 独立工具 —— 取一个视频并抽帧(双因素 + 首尾 + 补帧),产出关键帧与时间线,供分析/参考。当用户说「分析 XXX 视频」「把这个视频抽帧」「拆一下这条视频的画面」「提取视频关键帧」时调用。
|
||||
agent_created: true
|
||||
---
|
||||
|
||||
# video-capture · 取视频 + 抽帧(独立工具)
|
||||
|
||||
把「一个视频」变成「一批有信息量的关键帧 + 时间线」,用于产品分析 / 竞品拆解 / 视觉参考。
|
||||
|
||||
## 一、什么时候用
|
||||
|
||||
- 用户说「**分析 XXX 视频**」「把这个视频抽帧」「拆一下这条视频的画面」「提取视频关键帧」。
|
||||
- ①段竞品分析要拆竞品 demo、③段要拿参考视频的画面。
|
||||
|
||||
## 二、怎么用(两步)
|
||||
|
||||
第一步 · 取视频(最小、合规)
|
||||
```
|
||||
python scripts/fetch_video.py --file D:/a.mp4 --out ./video
|
||||
# 或直链媒体 URL:
|
||||
python scripts/fetch_video.py --url https://host/x.mp4 --out ./video
|
||||
```
|
||||
- ⛔ 不抓平台页内视频(抖音/B站/YouTube 页面解析)+ ⛔ 不调付费接口:需平台视频请先用浏览器工具下到本地,再 `--file` 传入。
|
||||
|
||||
第二步 · 抽帧(双因素 + 首尾 + 补帧)
|
||||
```
|
||||
python scripts/extract_frames.py --video ./video/a.mp4 --out ./frames
|
||||
```
|
||||
- 产 `frames/c_XXXX_<时间>s.jpg` + `frames/frame_times.json`(含时长/分辨率/字幕裁剪/阈值/帧表)。
|
||||
- 可选:`--crop "720:240:0:720"` 指定字幕区;`--max-gap 5` 长间隔补帧阈值;`--thresholds 0.3,0.1,0.05`。
|
||||
|
||||
## 三、抽帧算法(铁律,来自《视频抽帧与字幕规范》)
|
||||
|
||||
1. 画面 scene 0.3;2. **字幕区 crop 后 scene 0.1**(漏这条 = 画面静止但字幕换句的帧全丢);
|
||||
3. 低阈值 scene 0.05 补漏;4. 首帧(0) + 尾帧(时长-0.3);5. 相邻帧间隔 >5s 补中点;逐点 `-ss` 抽帧(禁 `eq(t)`)。
|
||||
|
||||
## 四、边界
|
||||
|
||||
- 字幕识别不在此工具内(本工具只出帧);要字幕稿需另对帧做逐帧 OCR/视觉读取。
|
||||
- 抽帧是「有信息量的帧」而非固定节奏:帧数随内容变化。
|
||||
- ⛔ 内容合规:只对用户有权分析的素材使用。
|
||||
|
||||
## 五、归属
|
||||
|
||||
抽帧算法搬运自 `aigc-idea-impression` 技能的《视频抽帧与字幕规范》(用户自有技能),见 `ATTRIBUTION.md`。
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
video-capture · 抽帧(双因素 + 首尾 + 补帧)
|
||||
|
||||
铁律来源:aigc-idea-impression 的《视频抽帧与字幕规范》(2026-08-30 实测沉淀)。
|
||||
算法:① 画面 scene 0.3 ② 字幕区 crop 后 scene 0.1(漏这条 = 画面静止但字幕换句的帧全丢)
|
||||
③ 低阈值 scene 0.05 ④ 首帧/尾帧 ⑤ 相邻帧间隔 >5s 补中点;逐点 -ss 抽帧(禁 eq(t))。
|
||||
|
||||
依赖:ffmpeg / ffprobe(本机已就绪)。仅 Python 标准库。
|
||||
用法:
|
||||
python extract_frames.py --video a.mp4 --out frames/ [--max-gap 5] [--crop "720:240:0:720"] [--thresholds 0.3,0.1,0.05]
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def run(cmd):
|
||||
return subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace")
|
||||
|
||||
|
||||
def ffprobe_meta(video):
|
||||
r = run([
|
||||
"ffprobe", "-v", "error",
|
||||
"-show_entries", "format=duration",
|
||||
"-show_entries", "stream=codec_type,width,height",
|
||||
"-of", "json", video,
|
||||
])
|
||||
info = {"duration": 0.0, "width": 0, "height": 0}
|
||||
try:
|
||||
j = json.loads(r.stdout or "{}")
|
||||
info["duration"] = float((j.get("format") or {}).get("duration") or 0.0)
|
||||
for s in (j.get("streams") or []):
|
||||
if s.get("codec_type") == "video":
|
||||
info["width"] = int(s.get("width") or 0)
|
||||
info["height"] = int(s.get("height") or 0)
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
return info
|
||||
|
||||
|
||||
def scene_times(video, vf, label):
|
||||
"""跑一次 scene 检测,返回时间点列表。"""
|
||||
r = run([
|
||||
"ffmpeg", "-hide_banner", "-i", video,
|
||||
"-vf", vf, "-vsync", "vfr", "-f", "null", "-",
|
||||
])
|
||||
out = (r.stderr or "") + (r.stdout or "")
|
||||
times = [float(t) for t in re.findall(r"pts_time:([0-9.]+)", out)]
|
||||
times = sorted(set(t for t in times if t >= 0))
|
||||
return times
|
||||
|
||||
|
||||
def default_subtitle_crop(w, h):
|
||||
"""字幕区默认取底部 15%。"""
|
||||
if w <= 0 or h <= 0:
|
||||
return None
|
||||
ch = max(1, int(h * 0.15))
|
||||
y = h - ch
|
||||
return f"crop={w}:{ch}:0:{y}"
|
||||
|
||||
|
||||
def extract(video, out_dir, max_gap, crop, thresholds):
|
||||
meta = ffprobe_meta(video)
|
||||
dur = meta["duration"]
|
||||
if dur <= 0:
|
||||
print(json.dumps({"ok": False, "error": "无法读取视频时长(文件损坏或非视频)"}), file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
t_pic, t_sub, t_low = thresholds
|
||||
times = set()
|
||||
|
||||
# ① 画面变化
|
||||
times.update(scene_times(video, f"select='gt(scene,{t_pic})',showinfo", "pic"))
|
||||
# ② 字幕区变化(crop 后检测)—— 漏这条会丢"画面静止但字幕换句"
|
||||
sub_crop = crop or default_subtitle_crop(meta["width"], meta["height"])
|
||||
if sub_crop:
|
||||
times.update(scene_times(video, f"{sub_crop},select='gt(scene,{t_sub})',showinfo", "sub"))
|
||||
# ③ 低阈值补漏
|
||||
times.update(scene_times(video, f"select='gt(scene,{t_low})',showinfo", "low"))
|
||||
# ④ 首尾帧
|
||||
times.add(0.0)
|
||||
times.add(max(0.0, dur - 0.3))
|
||||
|
||||
ordered = sorted(times)
|
||||
# ⑤ 长间隔补中点
|
||||
with_gap = []
|
||||
prev = None
|
||||
for t in ordered:
|
||||
if prev is not None and (t - prev) > max_gap:
|
||||
with_gap.append((t + prev) / 2.0)
|
||||
with_gap.append(t)
|
||||
prev = t
|
||||
ordered = sorted(set(round(t, 3) for t in (ordered + with_gap)))
|
||||
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
frames = []
|
||||
for i, t in enumerate(ordered, 1):
|
||||
t = min(t, max(0.0, dur - 0.05))
|
||||
name = f"c_{i:04d}_{t}s.jpg"
|
||||
path = os.path.join(out_dir, name)
|
||||
run([
|
||||
"ffmpeg", "-hide_banner", "-loglevel", "error", "-y",
|
||||
"-ss", f"{t}", "-i", video, "-frames:v", "1", "-q:v", "2", path,
|
||||
])
|
||||
frames.append({"index": i, "time": t, "file": name})
|
||||
|
||||
manifest = {
|
||||
"video": os.path.abspath(video),
|
||||
"duration": round(dur, 3),
|
||||
"resolution": f'{meta["width"]}x{meta["height"]}',
|
||||
"subtitleCrop": sub_crop,
|
||||
"thresholds": {"picture": t_pic, "subtitle": t_sub, "low": t_low},
|
||||
"maxGap": max_gap,
|
||||
"frameCount": len(frames),
|
||||
"frames": frames,
|
||||
}
|
||||
with open(os.path.join(out_dir, "frame_times.json"), "w", encoding="utf-8", newline="\n") as f:
|
||||
json.dump(manifest, f, ensure_ascii=False, indent=2)
|
||||
return manifest
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--video", required=True)
|
||||
ap.add_argument("--out", default="frames")
|
||||
ap.add_argument("--max-gap", type=float, default=5.0)
|
||||
ap.add_argument("--crop", default="", help='字幕区裁剪,如 "720:240:0:720";缺省按分辨率底部 15%')
|
||||
ap.add_argument("--thresholds", default="0.3,0.1,0.05", help="画面,字幕,低阈值")
|
||||
args = ap.parse_args()
|
||||
|
||||
if not os.path.exists(args.video):
|
||||
print(json.dumps({"ok": False, "error": f"找不到视频:{args.video}"}), file=sys.stderr)
|
||||
sys.exit(2)
|
||||
try:
|
||||
t = [float(x) for x in args.thresholds.split(",")]
|
||||
assert len(t) == 3
|
||||
except Exception:
|
||||
print("thresholds 需为 3 个数值,如 0.3,0.1,0.05", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
m = extract(args.video, args.out, args.max_gap, args.crop.strip(), t)
|
||||
print(json.dumps({
|
||||
"ok": True, "out": os.path.abspath(args.out),
|
||||
"frameCount": m["frameCount"], "duration": m["duration"],
|
||||
"resolution": m["resolution"], "subtitleCrop": m["subtitleCrop"],
|
||||
}, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
video-capture · 取视频(最小、合规集)
|
||||
|
||||
只做两件事,用于把「一个视频」落到本地待抽帧:
|
||||
1) 本地文件:直接登记(--file)
|
||||
2) 直链媒体 URL:下载(--url,需 .mp4/.mov/.webm/.m4v 等直链)
|
||||
⛔ 不做:平台页内抓取(抖音/B站/YouTube 等页面解析)、⛔ 不调任何付费接口。
|
||||
—— 如需平台视频,请先用浏览器工具把视频下到本地,再用 --file 传入(本工具不代抓)。
|
||||
|
||||
依赖:ffprobe(校验)+ Python 标准库。可选 curl(更快)。
|
||||
用法:
|
||||
python fetch_video.py --file D:/a.mp4 --out ./video
|
||||
python fetch_video.py --url https://host/x.mp4 --out ./video
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.request
|
||||
|
||||
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/126.0 Safari/537.36"
|
||||
|
||||
|
||||
def probe(path):
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["ffprobe", "-v", "error", "-show_entries", "format=duration",
|
||||
"-show_entries", "stream=codec_type,width,height", "-of", "json", path],
|
||||
capture_output=True, text=True, encoding="utf-8", errors="replace")
|
||||
j = json.loads(r.stdout or "{}")
|
||||
dur = float((j.get("format") or {}).get("duration") or 0)
|
||||
w = h = 0
|
||||
for s in (j.get("streams") or []):
|
||||
if s.get("codec_type") == "video":
|
||||
w, h = int(s.get("width") or 0), int(s.get("height") or 0)
|
||||
break
|
||||
return {"duration": round(dur, 3), "resolution": f"{w}x{h}"}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def download(url, dest):
|
||||
req = urllib.request.Request(url, headers={"User-Agent": UA, "Referer": url})
|
||||
with urllib.request.urlopen(req, timeout=90) as r, open(dest, "wb") as f:
|
||||
shutil.copyfileobj(r, f)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
g = ap.add_mutually_exclusive_group(required=True)
|
||||
g.add_argument("--file", help="本地视频路径")
|
||||
g.add_argument("--url", help="直链媒体 URL(.mp4/.mov/.webm/.m4v)")
|
||||
ap.add_argument("--out", default="video", help="输出目录")
|
||||
args = ap.parse_args()
|
||||
|
||||
os.makedirs(args.out, exist_ok=True)
|
||||
|
||||
if args.file:
|
||||
if not os.path.exists(args.file):
|
||||
print(json.dumps({"ok": False, "error": f"找不到文件:{args.file}"}), file=sys.stderr)
|
||||
sys.exit(2)
|
||||
dest = os.path.join(args.out, os.path.basename(args.file))
|
||||
if os.path.abspath(dest) != os.path.abspath(args.file):
|
||||
shutil.copy2(args.file, dest)
|
||||
else:
|
||||
if not os.path.splitext(args.url.split("?")[0])[1].lower() in (".mp4", ".mov", ".webm", ".m4v", ".mkv"):
|
||||
print(json.dumps({"ok": False, "error": "仅支持直链媒体 URL(.mp4/.mov/.webm/.m4v/.mkv);平台页内抓取不在本工具范围"}), file=sys.stderr)
|
||||
sys.exit(2)
|
||||
dest = os.path.join(args.out, "video" + os.path.splitext(args.url.split("?")[0])[1].lower())
|
||||
try:
|
||||
download(args.url, dest)
|
||||
except Exception as e:
|
||||
print(json.dumps({"ok": False, "error": f"下载失败:{e}"}), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
meta = probe(dest)
|
||||
print(json.dumps({"ok": True, "video": os.path.abspath(dest), **meta}, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -3,7 +3,7 @@
|
||||
`SKILL.md` 写规则,本文件写**照着走的顺序与验收**。跑 ③ 段时按本文件逐步走;**任何一步偏离,必须当场声明并记入第 6 节偏离单**。
|
||||
|
||||
> **本手册为什么存在**:本项目真实踩过的坑——③ 段 3b 交付了原型,却**没走当时的入口主流程、没读依据文件、执行时也没声明**,用户直到追问"基于哪个 skill"才知道。所以本手册第一条是声明制,第二条是"每步都写明依据哪个文件的哪一节"。
|
||||
> ③ 段的**设计工程供给为 `open-design`**(`../../../open-design/`,本仓内离线资产,只读引用);**流程规则与契约字段留在本手册与 `SKILL.md`** —— 两者分工见第 1 节末的「⛔ 三样必须留在本项目」。
|
||||
> ③ 段的**设计工程供给为 `open-design`**(**按技能名引用** `open-design` 技能;⛔ 不写死相对路径 —— 技能位置一变它就会失效且不报错);**流程规则与契约字段留在本手册与 `SKILL.md`** —— 两者分工见第 1 节末的「⛔ 三样必须留在本项目」。
|
||||
|
||||
---
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
## 1. 必须加载的文件(按阶段加载,用完释放)
|
||||
|
||||
> ⚠️ **2026-10-02 已重接必读源**:原必读表指向 `ui-page-design/references/02_~05_*.md` 与 `vendor/*`,**该目录实测已不在磁盘上**(工作区与全局技能根两处都查过)→ 照旧表走 D0 会**读不到任何一份必读文档**。现改为直接引 `open-design`(`../../../open-design/`,本仓内离线资产)。
|
||||
> ⚠️ **2026-10-02 已重接必读源**:原必读表指向 `ui-page-design/references/02_~05_*.md` 与 `vendor/*`,**该目录实测已不在磁盘上**(工作区与全局技能根两处都查过)→ 照旧表走 D0 会**读不到任何一份必读文档**。现改为直接引 **`open-design` 技能**(按技能名引用,⛔ 不写死相对路径)。
|
||||
> **改动原则**:判据类全部交出去,**只保留 craft 没有对应的东西**。
|
||||
|
||||
**开工前必读**:两个页面设计技能的位置与分工 —— `open-design/craft/README.md`(读一份就知道有哪些规约、怎么按需取)+ 本段 `SKILL.md` 的两技能分工表。
|
||||
@@ -87,13 +87,13 @@
|
||||
### 2.1 D0 锚需求 → 设计契约
|
||||
|
||||
> ⭐⭐ **2026-10-06 口径反转(用户定案):骨架归②段,本段做皮肉。**
|
||||
> 本节原文写的是「先把 PRD 的界面描述降级为功能约束,那份视图清单本身不许当结构用」——**这条已废**。
|
||||
> **新口径**:②段 ②段《界面布局》里的**页面清单 / 每页板块 / 板块排列 / 跨页关系**就是**骨架定稿**,
|
||||
> 本段⛔ **不许增删移动板块**,**要改回②段改 PRD**。本段在给定骨架内决定**视觉与交互实现**。
|
||||
> 本节原文写的是「先把②段的界面描述降级为功能约束,那份视图清单本身不许当结构用」——**这条已废**。
|
||||
> **新口径**:②段《界面布局》里的**页面清单 / 每页板块 / 板块排列 / 跨页关系**就是**骨架定稿**,
|
||||
> 本段⛔ **不许增删移动板块**,**要改回②段改 ②段**。本段在给定骨架内决定**视觉与交互实现**。
|
||||
> **理由(用户原话)**:「页面关系和布局必须在第二步确认清楚,不然第三步没有方向 一会一个样子」。
|
||||
> ⛔ **仍归本段的**:配色 / 字体 / 间距 / 组件样式 / 动效 / 板块内的排版与交互行为 / 响应式。
|
||||
|
||||
**读 PRD 时取四样**:**骨架(页面清单 + 每页板块 + 排列 + 跨页关系)**、必须在界面上发生什么、红线、禁用条件与状态流转。
|
||||
**读②段时取四样**:**骨架(页面清单 + 每页板块 + 排列 + 跨页关系)**、必须在界面上发生什么、红线、禁用条件与状态流转。
|
||||
**骨架是冻结输入**;其余三样是功能约束。
|
||||
|
||||
按本手册 §2.1 的清单产出**设计契约**(**十二个字段**):页面职责("让<谁>在<多久>内<完成什么>")、主用户、**主操作(全页唯一,写了两个就说明该拆页)**、必要内容(**核对面**)、**重复项的信息量(行式列表 / 卡片网格时逐条列出:每个重复项除"主标识 + 动作"外必须带 ≥1 条次级信息)**、**结构骨架(翻译面:把②段板块清单翻成骨架档 + 列宽 + 密度目标)**、**交互清单(加法,2026-10-02 新增:逐个可点元素一行,四列「元素 / 触发 / 反馈 / 何时不可点」;⛔ 动效参数归3d、视觉样式归令牌表、"这个状态有没有"归下一字段状态覆盖)**、**状态覆盖六项逐条**、组件来源(优先沿用既有)、响应式行为(**写布局怎么变**,不写"自适应")、明确拒绝的模式(≥3 条可判定)、验收标准(可观察可判定)。
|
||||
@@ -101,11 +101,11 @@
|
||||
**两个字段的分工(2026-10-06 调整):`必要内容` 是核对面,`结构骨架` 是翻译面。**
|
||||
|
||||
> **`结构骨架`(翻译面)怎么落**(⛔ **规则留在本手册,craft 无对应** —— 实测 `craft/` 只有零散 layout 提及,不构成骨架规则):
|
||||
> ⭐ **输入是②段 PRD 的板块清单**,本字段做的是**翻译**,不是**发明**。
|
||||
> ⭐ **输入是②段的板块清单**,本字段做的是**翻译**,不是**发明**。
|
||||
> ① **骨架档**:单列 / 主区+侧区 / 主区+侧区+辅助区,选一档并写清理由。**有 ≥3 个并列目的地且宽屏 ≥1024 时优先侧栏**——但侧栏是项目级取舍,**不许写死成"必须没有"**。
|
||||
> ② **板块落位**:把②段给的板块按顺序放到骨架档里,每块给一个**文字标题**(页面 ≥2 个带文字标题的板块)。
|
||||
> ③ **密度目标**:宽松(24–96)/ 标准(16–64)/ 密集(8–32),同一页面不得混用两档。
|
||||
> ⛔ **不许在②段板块清单之外加块** —— 缺板块回②段改 PRD。
|
||||
> ⛔ **不许在②段板块清单之外加块** —— 缺板块回②段改 ②段。
|
||||
> **板块先分、内容后放**——顺序反了就只能得到一张平铺的长条。
|
||||
|
||||
> **`必要内容`(核对面)的判据不变,但用途变了。**
|
||||
@@ -179,8 +179,8 @@
|
||||
|---|---|---|
|
||||
| 1 | 设计契约 | **十二个字段**全部填写,无一为空或"待定";有重复项时**重复项的信息量**逐条列出次级信息;**交互清单四列齐全、且表外的可点元素为 0** |
|
||||
| 1b | **页型判定**(本项目附加,2026-10-05 新增) | **已写出**「营销页 / 工具型页面 + 依据」;工具型页面的 D0 里**零 `.eyebrow` / 零 `.lead` / 零 `.hero`** |
|
||||
| 2 | **结构骨架**(翻译面,2026-10-06 口径调整) | 骨架档 / 板块落位 / 密度目标三者已写;板块 ≥2 个且每个有文字标题;**板块清单来自②段 PRD、逐条对得上、未新增**;②段分档表的每条信息都有落位 |
|
||||
| 2b | **骨架未漂**(本项目附加,2026-10-06 新增) | D0 的**页面清单与每页板块**与②段 ②段《界面布局》**逐条对得上**;⛔ 本段未增删移动任何板块 |
|
||||
| 2 | **结构骨架**(翻译面,2026-10-06 口径调整) | 骨架档 / 板块落位 / 密度目标三者已写;板块 ≥2 个且每个有文字标题;**板块清单来自②段、逐条对得上、未新增**;②段分档表的每条信息都有落位 |
|
||||
| 2b | **骨架未漂**(本项目附加,2026-10-06 新增) | D0 的**页面清单与每页板块**与②段《界面布局》**逐条对得上**;⛔ 本段未增删移动任何板块 |
|
||||
| 3 | 状态覆盖 | 六个状态逐项有结论(勾选,或"不适用 + 理由") |
|
||||
| 4 | 令牌表 | 色值 / 字体 / **间距两档且适用面已分开写** / 圆角 / 阴影全为**具体值** |
|
||||
| 4b | **字阶定值表** | 七档每档一个 px 定值(不是区间);相邻档差 ≥2px;每档只许一个定值 |
|
||||
@@ -260,7 +260,7 @@
|
||||
|
||||
**取不到时**:不许假装跑过。落盘写明"本轮未取得外部审查",并在段末明示——**未取得不等于已通过**。
|
||||
|
||||
**处置**:采纳要给理由,与 `DESIGN.md` 或 PRD 冲突的写清为什么不采纳。采纳项**在 3d 之前改完**,并回到 3b 的实测清单把这些改动再点一遍。
|
||||
**处置**:采纳要给理由,与 `DESIGN.md` 或②段冲突的写清为什么不采纳。采纳项**在 3d 之前改完**,并回到 3b 的实测清单把这些改动再点一遍。
|
||||
|
||||
---
|
||||
|
||||
@@ -379,7 +379,7 @@
|
||||
- [ ] 第 1 节必读表已按阶段取用;同一时刻只读 1~2 份,未一次性全读(限载)
|
||||
- [ ] **供给核对做过**:三处供给在位、选定套名与 `DESIGN.md` 记录一致;不一致时供给变更清单已获用户确认并写入修订记录
|
||||
- [ ] `docs/pm/<项目>/DESIGN.md` 存在,含**设计契约**(**十二个字段**,含**结构骨架**、**交互清单**与**重复项的信息量**)与**令牌表**(三层 token 结构、含**组件规格 + 字阶定值表 + 高度层级 + 布局脚手架(含容器与对齐锚点)+ 浮层只许一个可见关闭出口**、全为具体值)
|
||||
- [ ] **骨架未漂**(2026-10-06 口径调整):D0 的页面清单与每页板块与②段 ②段《界面布局》**逐条对得上**;⛔ 本段未增删移动任何板块
|
||||
- [ ] **骨架未漂**(2026-10-06 口径调整):D0 的页面清单与每页板块与②段《界面布局》**逐条对得上**;⛔ 本段未增删移动任何板块
|
||||
- [ ] **分档已兑现**:②段分档表每条信息的"主屏常驻 / 可点入"在实现里兑现;⛔ 档位未被本段改动
|
||||
- [ ] **`结构骨架` 已落**:骨架档给了理由;板块 ≥2 个且每个有文字标题;**板块清单来自②段**;密度目标只取一档
|
||||
- [ ] **页型已判**(营销页 / 工具型,给了依据);工具型页面**零 `.eyebrow` / 零 `.lead` / 零 `.hero`**,且 `layouts-tooling.md` §5.5 五条逐条过完(含"标题区零从属小字"的 DOM 取值)
|
||||
|
||||
@@ -63,11 +63,11 @@ description: 产品规划第①段,产品需求。五个子步依次产出五
|
||||
| 节 | 火力点 | 原归属 |
|
||||
|---|---|---|
|
||||
| **A 节** | 要做什么、为谁、边界在哪 | ①段 |
|
||||
| **B 节** | 做成什么样、哪些情况不成立、状态怎么流转 | ②段 |
|
||||
| **B 节** | 做成什么样、哪些情况不成立、状态怎么流转 |②段|
|
||||
|
||||
> ⛔ **不许再加回②段一份**。②段从 2026-10-06 起**只细化功能**,不再反问需求 —— 反问在这里一次性做完。
|
||||
> ⚠️ **合并的代价要说清**:两个火力点合到一处,提问量必然变大。**用"分节提问"解决**,⛔ 不许因为量大就省略 B 节 —— 跳了 B 节等于把功能压测整个删掉。
|
||||
> ⭐ **B 节只给粗粒度形态**(有哪几页、大概几个板块),⛔ **不把页面结构写死**(精确板块清单与布局归②段 ②段的《界面布局》)。
|
||||
> ⭐ **B 节只给粗粒度形态**(有哪几页、大概几个板块),⛔ **不把页面结构写死**(精确板块清单与布局归②段的《界面布局》)。
|
||||
|
||||
## 1b 竞品分析(外部取证)
|
||||
|
||||
@@ -110,7 +110,7 @@ description: 产品规划第①段,产品需求。五个子步依次产出五
|
||||
```
|
||||
|
||||
四个槽位缺一不算完成;⛔ **「我要办成什么」里不许出现功能名**(出现即越界到②段)。
|
||||
它是②段 PRD 功能清单的**直接推导依据** —— ②段每列一项功能,都要能追回本份的某一条场景;②段**按使用场景梳理功能**(一条场景可跨多个页面)。
|
||||
它是②段功能清单的**直接推导依据** —— ②段每列一项功能,都要能追回本份的某一条场景;②段**按使用场景梳理功能**(一条场景可跨多个页面)。
|
||||
⛔ **不用另写一份"用户故事"**:原②段的 `user-stories.md` 已删除,其内容归入本份。
|
||||
⭐ **旧三段式(处境 / 现在怎么凑合 / 不用会用什么)改挂到四槽位**:处境 → 「当…」;凑合与替代 → 「这样…」的反面与对照。映射表见 `references/usage-scenario.md` §5。
|
||||
|
||||
@@ -130,8 +130,8 @@ description: 产品规划第①段,产品需求。五个子步依次产出五
|
||||
| `prd/2a-产品功能.md` | **②段** | 它是**功能清单**(做哪些 / 不做哪些 / 优先级 + 每条的状态流转) |
|
||||
| `prd/2b-界面布局.md` | **②段** | 它是**页面骨架**(页面关系 + 页面内板块布局 —— 有哪几页、每页几个板块) |
|
||||
| ~~`prd/user-stories.md`~~ | **已删(2026-10-06)** | 用户故事归①段《使用场景》(`1e-使用场景.md`) |
|
||||
| ~~`ux/state-machine.md`~~ | **已删(2026-10-06)** | 状态机**整合进 PRD 功能清单** |
|
||||
| ~~`ux/diagrams/*`~~ | **已删(2026-10-06)** | 图示改名「**界面布局**」并**整合进 PRD 同一文档** |
|
||||
| ~~`ux/state-machine.md`~~ | **已删(2026-10-06)** | 状态机**整合进 ②段功能清单** |
|
||||
| ~~`ux/diagrams/*`~~ | **已删(2026-10-06)** | 图示改名「**界面布局**」并**整合进 ②段(独立成 `prd/2b-界面布局.md`)** |
|
||||
| ~~`00-问题定义.md`~~ | **已废(2026-10-06)** | 并入 `1a-需求文档.md` |
|
||||
| ~~`01-需求澄清.md`~~ | **已废(2026-10-06)** | 并入 `1a-需求文档.md` |
|
||||
|
||||
@@ -172,8 +172,8 @@ description: 产品规划第①段,产品需求。五个子步依次产出五
|
||||
- **把自动补全当默认动作**:用户没说话就替他拍板,还没标 `【假设】`、没进待复核清单
|
||||
- 拿本段的需求文档代替第②段的功能压测(那是两件事 —— ②段已无压测,压测在 1a 的 B 节)
|
||||
- **在本段 1a 写功能清单**(那是 2a 的活;1a 只定问题与边界、形态与状态)
|
||||
- ⛔ **只做 A 节、跳过 B 节**(2026-10-06 新增):B 节即原②段的功能压测,跳过它等于把压测整个删掉,接口会带着没压测过的需求直接进 PRD 与原型
|
||||
- ⛔ **在 B 节把页面结构写死**(2026-10-06 新增):B 节只给粗粒度形态(有哪几页、大概几个板块);精确板块清单与布局图归②段 ②段的《界面布局》
|
||||
- ⛔ **只做 A 节、跳过 B 节**(2026-10-06 新增):B 节即原②段的功能压测,跳过它等于把压测整个删掉,接口会带着没压测过的需求直接进②段与原型
|
||||
- ⛔ **在 B 节把页面结构写死**(2026-10-06 新增):B 节只给粗粒度形态(有哪几页、大概几个板块);精确板块清单与布局图归②段的《界面布局》
|
||||
- ⛔ **产出白名单外的第 6 份文档**(2026-10-06 新增):⛔ 不许因为"用户点名了某个参考产品/某份素材"就另立文档 —— 写进 `1b-竞品分析.md` 里
|
||||
- 没有取证就直接写策略
|
||||
- 把 11 个子步全跑一遍充数
|
||||
@@ -193,7 +193,7 @@ description: 产品规划第①段,产品需求。五个子步依次产出五
|
||||
- 全文零外链、零【事实】、仅 1 处【假设】。
|
||||
- 它的 Importance / Satisfaction 分数(0.90 / 0.10 / Score 0.81…)**没有任何数据来源**,是纯主观填数。
|
||||
- 它自己首段就写着「**无真实数据,全部为用研判断**」—— 诚实标注了,但**标注不等于成立**:一份自己都承认没数据的文档,占着「1b 取证」的名额产出。
|
||||
- 它的下游产物(`strategy/31`、`strategy/30`、PRD 的 `priorities.md`)大量引用这些分数 ⇒ **编的数会一路传到功能优先级**。
|
||||
- 它的下游产物(`strategy/31`、`strategy/30`、②段的 `priorities.md`)大量引用这些分数 ⇒ **编的数会一路传到功能优先级**。
|
||||
|
||||
**替代**:⛔ 不产出任何替代文档。**它的真实内容("我改错了回不去""文档互相踩")已经由 `1a-需求文档.md` 的用户原话承载** —— 用户自己说的问题不需要打分。
|
||||
|
||||
@@ -206,13 +206,13 @@ description: 产品规划第①段,产品需求。五个子步依次产出五
|
||||
|
||||
**两次改的理由**:
|
||||
|
||||
**第一次(价值主张 → 使用场景)**:原文档是 Who / Why / What Before / How / What After / Alternatives 六段,其中 **Who 与 What After 在②段 PRD §7.1.1 的 S1–S5 场景表里已经写过一遍**(谁、什么时候、要办成什么、落在主线哪一步)。⇒ 两份文档描述同一批人、同一条链,**必然打架**,而且价值主张里那套 What After 是"改成什么样"的想象,不是可核的交付。
|
||||
**第一次(价值主张 → 使用场景)**:原文档是 Who / Why / What Before / How / What After / Alternatives 六段,其中 **Who 与 What After 在旧稿(已废的 `PRD.md`)§7.1.1 的 S1–S5 场景表里已经写过一遍**(谁、什么时候、要办成什么、落在主线哪一步)。⇒ 两份文档描述同一批人、同一条链,**必然打架**,而且价值主张里那套 What After 是"改成什么样"的想象,不是可核的交付。
|
||||
|
||||
**第二次(三段式 → 用户故事格式,2026-10-06)**:改称使用场景后,文档写成了「处境 / 现在怎么凑合 / 不用会用什么」三段 —— **是分析者视角,不是用户视角**,读起来像一份论证,不像用户的话。用户定案改成**用户故事格式**(作为…当…我要…这样…),并明确它就是用户故事。
|
||||
|
||||
**改成什么**:`references/usage-scenario.md` —— **唯一句式** `作为【谁】,当【什么处境】,我要【办成什么】,这样【得到什么结果】`,四槽位缺一不算完成。
|
||||
**旧三段式的残留价值不丢**:处境 → 「当…」槽位;凑合与替代 → 「这样…」的反面与对照(映射表见该文件 §5)。
|
||||
**边界(2026-10-06 重划)**:场景的**归口是①段本份**(②段**按它梳理功能**,⛔ 不重写场景,重写必打架);⛔ **不许在①段写"每页放哪几块"那种界面话**(那是②段《界面布局》与③段 D0 的活)。
|
||||
**与②段的接口**:**《使用场景》是②段 PRD 功能清单的直接推导依据** —— ②段每列一项功能都要能追回某条场景;追不回的该砍。
|
||||
**与②段的接口**:**《使用场景》是②段功能清单的直接推导依据** —— ②段每列一项功能都要能追回某条场景;追不回的该砍。
|
||||
- **把推演出来的用户画像当用研结论进 1c** —— 三个 JTBD 若都追不到一句用户原话,那是自证不是用研
|
||||
- 越界去写 PRD、出原型或写原型说明文档(那是第②③④段)
|
||||
- 越界去写 ②段、出原型或写原型说明文档(那是第②③④段)
|
||||
@@ -19,7 +19,7 @@ PM 链路的**①段 1a 需求文档**,在**任何一项调研之前**先做
|
||||
| 节 | 火力点 | 原归属 | 不做的后果 |
|
||||
|---|---|---|---|
|
||||
| **A 节** | **要做什么、为谁、边界在哪** | ①段 | 不澄清就没法取证 —— 查回来的答的不是用户的问题 |
|
||||
| **B 节** | **做成什么样、哪些情况不成立、状态怎么流转** | ②段 | 不压测就没法写 PRD —— AI 带着理解错的需求一路做到原型 |
|
||||
| **B 节** | **做成什么样、哪些情况不成立、状态怎么流转** |②段| 不压测就没法写②段—— AI 带着理解错的需求一路做到原型 |
|
||||
|
||||
> ⚠️ **合并的代价要说清**:两个火力点合到一处,**提问量必然变大**。
|
||||
> ✅ **用"分节提问"解决**:先问完 A 节,再开 B 节;每节内部照旧一轮问完整个 frontier。
|
||||
@@ -118,12 +118,12 @@ frontier 为空 = 决策树每个分支都走过、没有静默假设,且**待
|
||||
## B 节必须覆盖的分支(做成什么样、哪些情况不成立、状态怎么流转)
|
||||
|
||||
> 这一节是 **2026-10-06 从②段合并进来**的。原②段的写法是「在 `create-prd` **之前**做一次功能压测」——
|
||||
> 目的是**把"需求澄清"从可跳过的步骤变成强制环节**,避免 AI 带着理解错的需求把 PRD 和原型一路做完。
|
||||
> 目的是**把"需求澄清"从可跳过的步骤变成强制环节**,避免 AI 带着理解错的需求把②段和原型一路做完。
|
||||
> 合并后**这个目的不变**,只是位置前移到这里。
|
||||
|
||||
| 分支 | 要问清的 |
|
||||
|---|---|
|
||||
| 形态与骨架 | 有哪几页、每页大致几个板块、板块怎么排、页与页怎么连(**粗粒度即可**,细的归②段 ②段的《界面布局》) |
|
||||
| 形态与骨架 | 有哪几页、每页大致几个板块、板块怎么排、页与页怎么连(**粗粒度即可**,细的归②段的《界面布局》) |
|
||||
| 不成立条件 | 什么情况下这套东西**不适用 / 用不起来**(不是"异常",是"这个做法本身失效") |
|
||||
| 状态流转 | 有哪些状态、按什么次序触发、彼此怎么迁移(起始 / 中间 / 终态) |
|
||||
| 失败与恢复 | 失败怎么办、能否重试、谁兜底 |
|
||||
@@ -133,7 +133,7 @@ frontier 为空 = 决策树每个分支都走过、没有静默假设,且**待
|
||||
|
||||
> ⭐ **B 节的产出必须落进 `1a-需求文档.md`**(⛔ 不另开 `grill-decisions.md`)—— 该文件已删除(2026-10-06)。
|
||||
> ⭐ **B 节问的是「做成什么样」,⛔ 不问「做成什么样好看」**:配色 / 字体 / 间距 / 动效一律不在这里问(那是③段)。
|
||||
> ⭐ **B 节的"形态与骨架"是粗粒度**:本轮只确认"有哪几页、大概几个板块",**精确的板块清单与布局图归②段 PRD**。
|
||||
> ⭐ **B 节的"形态与骨架"是粗粒度**:本轮只确认"有哪几页、大概几个板块",**精确的板块清单与布局图归②段**。
|
||||
> ⛔ **不许在 B 节就把页面结构写死** —— 那会让②段的《界面布局》变成照抄。
|
||||
|
||||
## 必须落盘
|
||||
@@ -146,7 +146,7 @@ frontier 为空 = 决策树每个分支都走过、没有静默假设,且**待
|
||||
|
||||
「来源」列是本表的关键:一眼看出哪些是用户拍的板、哪些是模型补的。**1c 的每条结论都要能追到本表的某一行**(或追到 1b 的某份调研文件)。
|
||||
|
||||
这张表是 1b(调研什么)与 1c(方案怎么定)的共同输入,**也是②段写 PRD 的输入**(⛔ 不许与已定决策冲突)。
|
||||
这张表是 1b(调研什么)与 1c(方案怎么定)的共同输入,**也是②段写那两份文档的输入**(⛔ 不许与已定决策冲突)。
|
||||
|
||||
## 反面清单
|
||||
|
||||
@@ -159,6 +159,6 @@ frontier 为空 = 决策树每个分支都走过、没有静默假设,且**待
|
||||
- **把自动补全当默认动作**,用户没说话就替他决定,还不标 `【假设】`
|
||||
- 标了 `【假设】` 却没进待复核清单,静默流进策略结论
|
||||
- **漏问「交付形态」这一支**(2026-09-29 新增):问完了"做什么、为谁、边界",却没问"**应该有几个界面、价值在哪一层**"——后续 ②③ 段会在无形态依据的情况下自行发明结构,做出来的东西被判定为"产品框图"。**该支为 A 节必问项**
|
||||
- ⛔ **只做 A 节、跳过 B 节**(2026-10-06 新增):B 节是从②段合并来的,**跳过它等于把功能压测整个删掉** —— 接口会带着没压测过的需求直接进 PRD 与原型
|
||||
- ⛔ **在 B 节把页面结构写死**(2026-10-06 新增):B 节只给粗粒度形态;精确板块清单与布局归②段 PRD
|
||||
- ⛔ **只做 A 节、跳过 B 节**(2026-10-06 新增):B 节是从②段合并来的,**跳过它等于把功能压测整个删掉** —— 接口会带着没压测过的需求直接进②段与原型
|
||||
- ⛔ **在 B 节把页面结构写死**(2026-10-06 新增):B 节只给粗粒度形态;精确板块清单与布局归②段
|
||||
- ⛔ **状态行只写"frontier 为空",把未经确认的模型补全藏在正文**(2026-10-02 新增)
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
## 二、它跟②段的关系(本份存在的理由)
|
||||
|
||||
⭐⭐ **《使用场景》是②段 PRD 功能清单的直接推导依据。**
|
||||
⭐⭐ **《使用场景》是②段功能清单的直接推导依据。**
|
||||
|
||||
- ②段每列一项功能,**都要能追回本份的某一条场景**。追不回的 ⇒ 那条功能没由来,②段该砍。
|
||||
- ②段**按使用场景梳理功能**(一条场景下的功能可能**跨好几个页面**),并标优先级。
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
Importance / Satisfaction / Score / 优先级分数,**没有数据源就不许出现**。
|
||||
|
||||
> 来源:本项目 2026-10-02 实测 —— 机会树的 0.90/0.10/0.81 全是主观填数,且一路传到 PRD 的 `priorities.md`。**一份自己都写"无真实数据"的文档不该占着"取证产出"的名额。**
|
||||
> 来源:本项目 2026-10-02 实测 —— 机会树的 0.90/0.10/0.81 全是主观填数,且一路传到②段的 `priorities.md`。**一份自己都写"无真实数据"的文档不该占着"取证产出"的名额。**
|
||||
> ✅ 正确写法:`【判断】我改错了回不去(依据:`1a-需求文档.md` Q1 用户原话)` —— 把来源标出来,不假装是数。
|
||||
|
||||
### ⛔ 2. 不许写"改成什么样"
|
||||
@@ -67,7 +67,7 @@ Importance / Satisfaction / Score / 优先级分数,**没有数据源就不许
|
||||
|
||||
- **场景的归口是①段本份**;②段**按它梳理功能**,⛔ **不重写场景**(重写必打架)。
|
||||
- **⛔ 不写功能清单**(那是②段 2a)。
|
||||
- **⛔ 不写界面结构 / 板块布局**(那是②段 ②段的《界面布局》与③段 D0)。
|
||||
- **⛔ 不写界面结构 / 板块布局**(那是②段的《界面布局》与③段 D0)。
|
||||
|
||||
## 五、旧三段式的残留价值(别丢)
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ description: 产品规划第④段,原型说明文档。在已交付的原型
|
||||
- **说明区是非产品功能**:它是原型的自解释注解层,交付时要明说,不能让人误以为是产品里真有一个说明页。
|
||||
- **只有原型上有的事实才许写**:每一段说明都要能在原型里点到对应元素。写不出 `target` 的步骤,要么承认是概念步骤,要么回去改原型。
|
||||
- **不得把自动结论表述为人工认可**(红线,本项目实测抓过):机器判定只能说"进入队列 / 待人工逐条查看",不许写成"通过 / 已认可"。
|
||||
- **文案口径跟 PRD 走**:同一个概念在说明区与 PRD 里必须同名,冲突时改说明区。
|
||||
- **文案口径跟②段走**:同一个概念在说明区与②段里必须同名,冲突时改说明区。
|
||||
- **4c 交付前必须浏览器实测**:逐步骤真点一遍,走完全流程再退出。未跑实测不得报完成。
|
||||
- **段内子步之间不反问**,做完直接进下一个子步。只有三种情况停:信息不足且查不到、破坏性/不可逆动作、用户显式要求(见 `product-planning` 的「执行规则」)。
|
||||
- **`<项目>` 按 `product-planning` 的「项目与路径约定」定**;定不出来就停下问用户,不许猜。
|
||||
|
||||
@@ -75,4 +75,4 @@
|
||||
- [ ] 每步三段齐全,且各自能通过上面的合格判据
|
||||
- [ ] 全文没有"通过 / 已认可"这类把机器结论说成人审的词
|
||||
- [ ] 每个禁用态都给了原因,每个异常态都给了恢复路径
|
||||
- [ ] 说明区文案与 PRD 术语逐词一致
|
||||
- [ ] 说明区文案与②段术语逐词一致
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: stage-requirements
|
||||
description: 产品规划第②段,产品功能。只做一件事:把①段《使用场景》(`1e-使用场景.md`)细化成功能清单(做哪些 / 不做哪些 / 优先级),并钉死页面骨架(《界面布局》:有哪几页、每页几个板块、板块怎么排、跨页关系)。当用户要写 PRD、列产品功能、定优先级、梳理功能、定页面与板块布局,或说"只做产品功能"时调用。⛔ 不含需求澄清与 grill 反问(那已全部归①段),不含视觉规范 / 交互 / 动效(那归③段)。
|
||||
description: 产品规划第②段,产品功能。只做一件事:把①段《使用场景》(`1e-使用场景.md`)细化成功能清单(做哪些 / 不做哪些 / 优先级),并钉死页面骨架(《界面布局》:有哪几页、每页几个板块、板块怎么排、跨页关系)。当用户要写②段两份、列产品功能、定优先级、梳理功能、定页面与板块布局,或说"只做产品功能"时调用。⛔ 不含需求澄清与 grill 反问(那已全部归①段),不含视觉规范 / 交互 / 动效(那归③段)。
|
||||
---
|
||||
|
||||
# ② 产品功能
|
||||
@@ -28,10 +28,10 @@ description: 产品规划第②段,产品功能。只做一件事:把①段
|
||||
> ⚠️ 原 `prd/PRD.md` **已废**(它把两件事塞在一份里)。
|
||||
>
|
||||
> **融合去哪了**:
|
||||
> - **状态机** ⇒ 整合进 PRD 的功能清单(每个功能条目带自己的状态流转,⛔ 不再单独成文)
|
||||
> - **状态机** ⇒ 整合进②段的功能清单(每个功能条目带自己的状态流转,⛔ 不再单独成文)
|
||||
> - **用户故事** ⇒ 归①段《使用场景》(`1e-使用场景.md`)(它就是用户故事)
|
||||
> - **grill / 压测** ⇒ 归①段 1a(全场唯一一份 `grill-me`)
|
||||
> - **图示** ⇒ 改名「**界面布局**」,整合进 PRD 同一文档
|
||||
> - **图示** ⇒ 改名「**界面布局**」,整合进 ②段(独立成 `prd/2b-界面布局.md`)
|
||||
> - **优先级** ⇒ 进功能条目本身(每条标优先级,⛔ 不再单独成文)
|
||||
> - **事前验尸** ⇒ 删除(风险由①段《产品策略》的「假设与风险」承载)
|
||||
|
||||
@@ -52,7 +52,7 @@ description: 产品规划第②段,产品功能。只做一件事:把①段
|
||||
|
||||
> **用户原话**:「3 是负责设计和交互,**页面关系和布局必须在第二步确认清楚,不然第三步没有方向 一会一个样子**」。
|
||||
> ⭐ **旧口径为什么站不住**:它把"有哪几页"留在③段 ⇒ ③段做一版一个样,②段没有任何东西可用来判断③段改得对不对。**骨架是判断基准,必须在更上游定死。**
|
||||
> ⛔ **交给③段时骨架是冻结的**:③段**不许新增 / 移动 / 删除板块**,缺板块要回②段改 PRD(见 `stage-delivery` 的对应禁令)。
|
||||
> ⛔ **交给③段时骨架是冻结的**:③段**不许新增 / 移动 / 删除板块**,缺板块要回②段改 ②段(见 `stage-delivery` 的对应禁令)。
|
||||
|
||||
## 本段职责(2026-10-06 按「只细化功能」重划)
|
||||
|
||||
@@ -63,7 +63,7 @@ description: 产品规划第②段,产品功能。只做一件事:把①段
|
||||
| **有哪些功能、优先级如何、什么不做** | 从每条使用场景推出功能条目;每条带优先级、状态流转、验收要点 | `2a-产品功能.md` |
|
||||
| **这些功能长成什么骨架** | 有哪几页、每页几个板块、板块怎么排、跨页关系 | `2b-界面布局.md` |
|
||||
|
||||
> ⭐ **本段产物的主体是「功能条目的有无与优先级」**,这正是「产品功能」这个名字的依据。实测本项目 26 条功能定义**全在 PRD、一条不在 research**,这就是归属证据。
|
||||
> ⭐ **本段产物的主体是「功能条目的有无与优先级」**,这正是「产品功能」这个名字的依据。实测本项目 26 条功能定义**全在 ②段、一条不在 research**,这就是归属证据。
|
||||
> ⛔ **本段不许重开需求**:需求澄清、grill 反问、取证**全部在①段做完了**。本段**假定需求已成立**,只做功能取舍。
|
||||
> ⭐ **功能的组织方式是「按使用场景」**(2026-10-06 用户定案):一条使用场景下可能挂**跨多个页面**的功能。
|
||||
> **用户原话**:「功能可以分使用场景梳理(可能是跨页面的)」。⛔ **不按页面组织功能**(按页面组织会把跨页场景切碎,且与①段《使用场景》(`1e-使用场景.md`)对不上)。
|
||||
@@ -128,16 +128,16 @@ description: 产品规划第②段,产品功能。只做一件事:把①段
|
||||
## 硬约束
|
||||
|
||||
- ⭐⭐ **本段不再是压测入口(2026-10-06)**:`grill-me` **全场只有①段一份**,火力点已扩到覆盖"做成什么样、状态怎么流转"。⛔ **本段不许再开一轮反问** —— ①段 `1a-需求文档.md` 的决策表就是本段的压测输入。**用户始终不回应时①段怎么处理,本段照旧继承,但不再自己去问。**
|
||||
- ⭐ **假设继承照旧,但账在①段**:`1a-需求文档.md` 带进来的每条 `【假设】`,在 PRD 落盘时**逐条**给结论,只允许三种归宿 —— ① 已确认(写明确认人与时间)|② 待实现且已排定确认时机(写明"在第几步落地前必须问")|③ `【已按假设落地】`(写清影响面 + 回退口径)。⛔ **第四种(既未确认、已进实现、又无落地说明)不许带进本段**。
|
||||
- ⭐ **假设继承照旧,但账在①段**:`1a-需求文档.md` 带进来的每条 `【假设】`,在②段落盘时**逐条**给结论,只允许三种归宿 —— ① 已确认(写明确认人与时间)|② 待实现且已排定确认时机(写明"在第几步落地前必须问")|③ `【已按假设落地】`(写清影响面 + 回退口径)。⛔ **第四种(既未确认、已进实现、又无落地说明)不许带进本段**。
|
||||
- ⭐⭐ **引用假设必须带状态**:本段任何一条功能 / 状态流转 / 分档,**只要依据是某条假设**,就在括号里带上该假设的当前状态(`(依据 Q7 · 已确认)`、`(依据 Q10 · 【已按假设落地 · 影响面:每版全量复制 → 版本目录体积】)`)。**下游③段据此判断这条能不能当硬约束用。**
|
||||
- ⭐ **假设与实测要回头对账**:凡靠假设得出的结论,**一旦下游跑出实测结果,必须回填并改状态**。实测踩坑:`PRD.md:132` 假设 3「单文件 HTML 在 `file://` 下能双击打开(③段需实测)」标着待实测,而 ③段**早已实测了 16 版**,事实成立却没人回填。
|
||||
- ⭐ **待确认 / 暂缓项必须带回收三字段(2026-10-02 新增)**:本段任何"待确认 / 暂缓 / 待定"条目必须同时写 **复核人 + 复核时机 + 结论落点(哪个文件哪一节)**,三字段缺一不许落盘。⛔ 只写"暂缓"等于无限期悬挂。
|
||||
- **「不做 / 移出」必须逐条核对,不许自行裁剪**:写 PRD 前、落盘后各做一次——把 PRD 里每一条"不做 / 移出 / 暂缓"与 `1a-需求文档.md` 的**已拍板项**逐条对照。凡涉及已拍板项,**一律不得自行裁剪或改口径**,必须列成「与已定决策的差异」表(原条目 / 原口径 / 拟改口径 / 理由)交用户显式确认。**只写"已核对无冲突"不算核对**,要给出对照过的条目数。
|
||||
- **「不做 / 移出」必须逐条核对,不许自行裁剪**:写②段前、落盘后各做一次——把②段里每一条"不做 / 移出 / 暂缓"与 `1a-需求文档.md` 的**已拍板项**逐条对照。凡涉及已拍板项,**一律不得自行裁剪或改口径**,必须列成「与已定决策的差异」表(原条目 / 原口径 / 拟改口径 / 理由)交用户显式确认。**只写"已核对无冲突"不算核对**,要给出对照过的条目数。
|
||||
- ⭐⭐ **每条功能必须能追回①段《使用场景》(`1e-使用场景.md`)**:追不回的功能 ⇒ 要么补一条场景,要么砍掉。**这是本段唯一的"功能有无"判据**,代替原来的凭空打分。
|
||||
- ⭐⭐ **状态流转写在功能条目里,⛔ 不再单独成文(2026-10-06)**:每个功能条目自带"状态流转"字段。**六项治理照旧全覆盖**(失败恢复路径、持久化范围、并发冲突、幂等、超时迁移目标、不可逆操作二次确认)—— 只是**落点从独立文档移进功能条目**。
|
||||
- ⭐ **状态挂不到任何功能上的中间态,要么删掉,要么补一个功能条目让它服务。**
|
||||
- ⭐⭐ **骨架必须给全(2026-10-06 新增)**:②段的《界面布局》必须给出 —— **页面清单 + 页面流转 + 每页板块清单与排列 + 每页主操作 + 每条信息在"主屏常驻 / 可点入"哪一档**。⛔ **缺任何一样,③段就没有方向**(这正是旧口径的病灶)。
|
||||
- **信息承载分档的判据**:「删掉它,主操作还做得成吗」——答"做得成"⇒ 只能进"可点入"。⛔ **「能读到」= 可点入,不等于主屏常驻**;⛔ 不许以"PRD 要求能读到"为由把信息留在主屏。
|
||||
- **信息承载分档的判据**:「删掉它,主操作还做得成吗」——答"做得成"⇒ 只能进"可点入"。⛔ **「能读到」= 可点入,不等于主屏常驻**;⛔ 不许以"②段 要求能读到"为由把信息留在主屏。
|
||||
- ⛔ **本段(含布局)不许写视觉**:配色 / 字体 / 间距 / 组件样式 / 动效,一个字都不许出现。
|
||||
- **段内不反问**,做完直接往下走。只有四种情况停:信息不足且查不到、破坏性/不可逆动作、用户显式要求、① 段末的方案确认(**仅"方案确认"模式**,见 `product-planning` 的「执行规则」)。
|
||||
- **`<项目>` 按 `product-planning` 的「项目与路径约定」定**;定不出来就停下问用户,不许猜。
|
||||
@@ -145,7 +145,7 @@ description: 产品规划第②段,产品功能。只做一件事:把①段
|
||||
## 完成标准
|
||||
|
||||
- `2a-产品功能.md` 与 `2b-界面布局.md` **两份都在,且只有这两份**(⛔ 不许再产出 `grill-decisions` / `user-stories` / `priorities` / `pre-mortem` / `state-machine` / `diagrams` 六份中的任何一份;⛔ 也不许再退回单份 `PRD.md`)
|
||||
- PRD 的「不做 / 移出」清单**逐条对照过**①段已拍板项,差异行为 0 或每一行都已获用户显式确认(**附对照条目数**,不得只写"无冲突")
|
||||
-②段的「不做 / 移出」清单**逐条对照过**①段已拍板项,差异行为 0 或每一行都已获用户显式确认(**附对照条目数**,不得只写"无冲突")
|
||||
- **功能清单**:每条功能带齐「功能名 / 来自哪条场景 / 优先级 / 状态流转 / 验收要点 / 跨哪些页面」六字段;⛔ **追不回①段场景的条目为 0**
|
||||
- **状态流转**六项治理全覆盖(失败恢复 / 持久化 / 并发 / 幂等 / 超时 / 不可逆二次确认),且落在功能条目内
|
||||
- **《界面布局》**给全五样:页面清单 + 页面流转 + 每页板块与排列 + 每页主操作 + 每条信息的分档(含判据回答)
|
||||
@@ -169,7 +169,7 @@ description: 产品规划第②段,产品功能。只做一件事:把①段
|
||||
- **状态流转只画正常路径**,不管失败与并发;或状态挂不到任何功能条目上(凭空中间态)
|
||||
- ⛔ **在本段写视觉**(配色 / 字体 / 间距 / 组件样式 / 动效)—— 那是③段
|
||||
- 越界去出原型或写说明文档(那是第③④段)
|
||||
- **把 `【假设】` 原样带进 PRD 当既定事实**;或假设已被实现却从不回填状态
|
||||
- **把 `【假设】` 原样带进②段当既定事实**;或假设已被实现却从不回填状态
|
||||
- **只写"暂缓"而不给复核人 / 复核时机 / 结论落点** —— 等于让这条永远挂着
|
||||
|
||||
## 已删除的产出(2026-10-06 用户定案 · 防加回来)
|
||||
@@ -182,12 +182,12 @@ description: 产品规划第②段,产品功能。只做一件事:把①段
|
||||
| `prd/user-stories.md` | **归①段《使用场景》(`1e-使用场景.md`)** —— 它就是用户故事 |
|
||||
| `prd/priorities.md` | **进功能条目本身** —— 每条标 P0/P1/P2 |
|
||||
| `prd/pre-mortem.md` | **删除** —— 风险由①段《产品策略》的「假设与风险」承载 |
|
||||
| `ux/state-machine.md` | **整合进 PRD 功能清单** —— 每个功能条目自带状态流转 |
|
||||
| `ux/diagrams/*` | **改名「界面布局」并整合进 PRD 同一文档** |
|
||||
| `ux/state-machine.md` | **整合进 ②段功能清单** —— 每个功能条目自带状态流转 |
|
||||
| `ux/diagrams/*` | **改名「界面布局」并整合进 ②段(独立成 `prd/2b-界面布局.md`)** |
|
||||
|
||||
> ⚠️ **同时删掉的 4 份方法论**:`references/grill-me.md`(并入①段那份)、`references/user-stories.md`、`references/prioritization-frameworks.md`、`references/pre-mortem.md`。
|
||||
> **保留的 2 份**:`references/create-prd.md`(功能清单的方法论)、`references/state-machine.md`(**六项治理清单仍要读**,只是落点变成功能条目内)、`assets/diagram-design/`(整树保留,供画布局图)。
|
||||
>
|
||||
> ⭐ **为什么删的是这几份**:它们的**内容全部有归宿**(见上表),但**每一份独立成文都会与另一份打架** ——
|
||||
> `priorities.md` 与 PRD 的功能条目抢优先级、`user-stories.md` 与①段《使用场景》(`1e-使用场景.md`)抢同一批人、`state-machine.md` 与功能清单抢"状态挂在哪"。
|
||||
> `priorities.md` 与②段的功能条目抢优先级、`user-stories.md` 与①段《使用场景》(`1e-使用场景.md`)抢同一批人、`state-machine.md` 与功能清单抢"状态挂在哪"。
|
||||
> ⇒ **本项目反复踩的坑就是"同一件事写两遍",这次是一次性收口。**
|
||||
@@ -9,7 +9,7 @@
|
||||
> 它是给"面向市场、要售卖、有干系人"的产品写的;本项目与同类**单机自用 / 工具型**产品没有这些内容,硬填就是编。
|
||||
> 保留的部分见下。
|
||||
|
||||
## 本 PRD 写两件事
|
||||
## 本②段写两件事
|
||||
|
||||
| # | 写什么 | 判据 |
|
||||
|---|---|---|
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
### 「不做」清单(必须逐条对照,不许自行裁剪)
|
||||
|
||||
写 PRD 前、落盘后各做一次:把每一条"不做 / 移出 / 暂缓"与 ①段 `1a-需求文档.md` 的**已拍板项**逐条对照。
|
||||
写②段前、落盘后各做一次:把每一条"不做 / 移出 / 暂缓"与 ①段 `1a-需求文档.md` 的**已拍板项**逐条对照。
|
||||
|
||||
凡涉及已拍板项,**一律不得自行裁剪或改口径**,必须列成「与已定决策的差异」表:
|
||||
|
||||
@@ -93,10 +93,10 @@
|
||||
|
||||
**唯一判据**:「**删掉它,主操作还做得成吗**」—— 答"**做得成**" ⇒ 只能进"可点入"。
|
||||
|
||||
> ⛔ **「能读到」= 可点入,不等于主屏常驻。** 不得以"PRD 要求能读到"为理由把信息留在主屏。
|
||||
> ⛔ **「能读到」= 可点入,不等于主屏常驻。** 不得以"②段 要求能读到"为理由把信息留在主屏。
|
||||
> 🔴 **四种禁用免死金牌**(理由里只要出现,一律判"不上屏",哪怕后面跟着"但是"):
|
||||
> ① 「但 PRD 要求能读到」② 「留(产品级约束)」③ 「但不完整/不专业」④ 「将来可能需要」。
|
||||
> **实战教训**:本项目首版 PRD 只说"不写结构",③段把"必须能读到"全理解为主屏常驻 ⇒ 一屏铺 5 类信息 ⇒ 页面成"信息墙",被判定为"最多算一个产品框图"。
|
||||
> ① 「但②段要求能读到」② 「留(产品级约束)」③ 「但不完整/不专业」④ 「将来可能需要」。
|
||||
> **实战教训**:本项目首版②段只说"不写结构",③段把"必须能读到"全理解为主屏常驻 ⇒ 一屏铺 5 类信息 ⇒ 页面成"信息墙",被判定为"最多算一个产品框图"。
|
||||
|
||||
### 页型一句(仍需写)
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
| Contacts | ⛔ **删** —— 单机自用产品没有干系人名单 |
|
||||
| Market Segment | ⛔ **删** —— 归①段《用户画像》 |
|
||||
| Value Proposition | ⛔ **删** —— 归①段 |
|
||||
| Solution → Key Features | ✅ **保留** —— 就是本 PRD 的「功能清单」 |
|
||||
| Solution → Key Features | ✅ **保留** —— 就是本②段的「功能清单」 |
|
||||
| Solution → UX/Prototypes | ⚠️ **改为「界面布局」** —— 只给灰块骨架,⛔ 不出视觉稿 |
|
||||
| Solution → Assumptions | ✅ **保留** —— 假设台账与归宿(口径见 SKILL.md 硬约束) |
|
||||
| Release | ⚠️ **可选** —— 第一版做什么 vs 后面做什么;⛔ 不写具体日期 |
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
> **Trae 用法**:本文件由模型按需加载,没有斜杠命令。
|
||||
>
|
||||
> ⭐⭐ **2026-10-06 变更:本份不再产出独立文档。**
|
||||
> 状态流转**整合进 PRD 的功能清单** —— 每个功能条目自带一个「状态流转」字段。
|
||||
> 状态流转**整合进②段的功能清单** —— 每个功能条目自带一个「状态流转」字段。
|
||||
> 本份保留的原因是它给出的**六项治理清单**(失败恢复 / 持久化 / 并发 / 幂等 / 超时 / 不可逆二次确认)**仍要逐项覆盖**,
|
||||
> 只是**落点从 `ux/state-machine.md` 移进 `prd/2a-产品功能.md` 的功能条目内**。
|
||||
> ⛔ **旧产出 `ux/state-machine.md` 已删除,不许再加回来。**
|
||||
|
||||
@@ -13,13 +13,14 @@
|
||||
这三处**没有任何门禁会报错** —— 只能靠回读,而回读会漏。
|
||||
|
||||
用法:
|
||||
python scripts/check_naming.py # 查 product-planning/ 与 pm-skills/skills/
|
||||
python scripts/check_naming.py --ws <工作区> # 查技能包自身 + 该工作区的产出白名单
|
||||
python scripts/check_naming.py --root <某个技能根> # 只查一份
|
||||
退出码:0 = 全过;1 = 有 FAIL。
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
@@ -279,16 +280,33 @@ def check_whitelist(ws, label):
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--root", action="append", default=None,
|
||||
help="技能根目录,可重复;默认查本技能包与 pm-skills/skills/")
|
||||
help="技能根目录,可重复;默认只查本技能包自身")
|
||||
ap.add_argument("--ws", default=None,
|
||||
help="工作区根(含 docs/pm 的那一层);默认从 cwd 向上找")
|
||||
args = ap.parse_args()
|
||||
|
||||
# 本文件位于 <技能包>/scripts/ ⇒ 上溯 2 层 = 技能包(整合后 SKILL.md 就在这层),3 层 = 仓库根
|
||||
# 本文件位于 <技能包>/scripts/ ⇒ 上溯 2 层 = 技能包(SKILL.md 就在这层)
|
||||
skill_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
ws = os.path.dirname(skill_root)
|
||||
roots = args.root or [
|
||||
skill_root,
|
||||
os.path.join(ws, "pm-skills", "skills"),
|
||||
]
|
||||
# 🔴🔴 2026-10-07 技能迁全局后的**必须修正**:
|
||||
# 旧写法 `ws = os.path.dirname(skill_root)` 是按「技能包在工作区根」写的。
|
||||
# 技能搬到 `E:/ProgramData/.workbuddy/skills/product-planning/` 之后,
|
||||
# `ws` 会算成 `E:/ProgramData/.workbuddy/skills` ⇒ **`docs/pm/*` 一个都扫不到**
|
||||
# ⇒ `check_whitelist()` 静默扫零个目录、**恒绿通过**(假绿比报红更危险)。
|
||||
# ⇒ 工作区一律**显式给**:`--ws <工作区>`;没给就向上找「含 docs/pm 的那一层」,
|
||||
# 再找不到就**明确报 FAIL**,⛔ 绝不静默跳过。
|
||||
roots = args.root or [skill_root]
|
||||
ws = args.ws
|
||||
if not ws:
|
||||
_p = pathlib.Path.cwd()
|
||||
for _ in range(6):
|
||||
if (_p / "docs" / "pm").is_dir():
|
||||
ws = str(_p)
|
||||
break
|
||||
_p = _p.parent
|
||||
if not ws or not os.path.isdir(os.path.join(ws, "docs", "pm")):
|
||||
add("FAIL", "找得到工作区(`docs/pm` 那一层)",
|
||||
"⛔ 未找到 ⇒ 产出白名单**没被真正检查**。请显式传 `--ws <工作区>`。")
|
||||
ws = ws or os.getcwd()
|
||||
|
||||
for r in roots:
|
||||
check_root(r, os.path.basename(os.path.dirname(r.rstrip("/")) if r.endswith("skills") else r))
|
||||
|
||||
Reference in new issue
Block a user