@dev-workflow/skill 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +80 -0
- package/bin/cli.mjs +9 -0
- package/package.json +39 -0
- package/skills/artifact/README.md +23 -0
- package/skills/execution/devFlow/SKILL.md +341 -0
- package/skills/execution/devFlow/agents/openai.yaml +7 -0
- package/skills/execution/devFlow/domains/backend/references/api-tech.md +173 -0
- package/skills/execution/devFlow/domains/backend/scripts/check_api_tech_doc.mjs +170 -0
- package/skills/execution/devFlow/domains/backend/scripts/check_api_tech_doc.test.mjs +89 -0
- package/skills/execution/devFlow/domains/backend/templates/api-tech.md +164 -0
- package/skills/execution/devFlow/domains/frontend/references/contract-check.md +270 -0
- package/skills/execution/devFlow/domains/frontend/references/foundation-freeze.md +92 -0
- package/skills/execution/devFlow/domains/frontend/references/lightweight-flow.md +55 -0
- package/skills/execution/devFlow/domains/frontend/references/page-build.md +268 -0
- package/skills/execution/devFlow/domains/frontend/references/page-tech.md +414 -0
- package/skills/execution/devFlow/domains/frontend/references/product-design-specs/field-common.md +69 -0
- package/skills/execution/devFlow/domains/frontend/references/product-design-specs/fields-complex.md +105 -0
- package/skills/execution/devFlow/domains/frontend/references/product-design-specs/fields-id-code.md +44 -0
- package/skills/execution/devFlow/domains/frontend/references/product-design-specs/fields-number-date.md +106 -0
- package/skills/execution/devFlow/domains/frontend/references/product-design-specs/fields-relation.md +144 -0
- package/skills/execution/devFlow/domains/frontend/references/product-design-specs/fields-selectors.md +102 -0
- package/skills/execution/devFlow/domains/frontend/references/product-design-specs/fields-text.md +130 -0
- package/skills/execution/devFlow/domains/frontend/references/product-design-specs/index.md +42 -0
- package/skills/execution/devFlow/domains/frontend/references/product-design-specs/interaction.md +61 -0
- package/skills/execution/devFlow/domains/frontend/scripts/check_page_tech_doc.mjs +82 -0
- package/skills/execution/devFlow/domains/frontend/scripts/contract_check_static.mjs +145 -0
- package/skills/execution/devFlow/domains/frontend/scripts/generate_foundation_summary.mjs +224 -0
- package/skills/execution/devFlow/domains/frontend/templates/contract-report.md +66 -0
- package/skills/execution/devFlow/domains/frontend/templates/mini-plan-l0.md +7 -0
- package/skills/execution/devFlow/domains/frontend/templates/mini-plan-l1.md +9 -0
- package/skills/execution/devFlow/domains/frontend/templates/page-build/component.tsx.tpl +8 -0
- package/skills/execution/devFlow/domains/frontend/templates/page-build/constants.ts.tpl +5 -0
- package/skills/execution/devFlow/domains/frontend/templates/page-build/index.ts.tpl +1 -0
- package/skills/execution/devFlow/domains/frontend/templates/page-build/route.tsx.tpl +11 -0
- package/skills/execution/devFlow/domains/frontend/templates/page-build/service.ts.tpl +17 -0
- package/skills/execution/devFlow/domains/frontend/templates/page-build/types.ts.tpl +11 -0
- package/skills/execution/devFlow/domains/frontend/templates/page-tech.md +103 -0
- package/skills/execution/devFlow/domains/requirement/references/prd-review.md +129 -0
- package/skills/execution/devFlow/domains/requirement/templates/prd-review.md +57 -0
- package/skills/execution/figmaSync/SKILL.md +349 -0
- package/skills/execution/figmaSync/chapters/apply.md +177 -0
- package/skills/execution/figmaSync/chapters/plan.md +359 -0
- package/skills/execution/figmaSync/chapters/prepare.md +47 -0
- package/skills/execution/figmaSync/examples/native-css.md +129 -0
- package/skills/execution/figmaSync/examples/plan-doc-sample.md +39 -0
- package/skills/execution/figmaSync/scripts/_shared/session-log.mjs +82 -0
- package/skills/execution/figmaSync/scripts/_shared/token-resolver.mjs +132 -0
- package/skills/execution/figmaSync/scripts/_shared/token-source.mjs +145 -0
- package/skills/execution/figmaSync/scripts/figma-sync-report.mjs +448 -0
- package/skills/execution/figmaSync/scripts/icon-inventory.mjs +165 -0
- package/skills/execution/figmaSync/scripts/lookup-var.mjs +184 -0
- package/skills/execution/figmaSync/scripts/match-token.mjs +210 -0
- package/skills/execution/figmaSync/scripts/prepare-check.mjs +290 -0
- package/skills/execution/figmaSync/scripts/verify-plan.mjs +458 -0
- package/skills/execution/figmaSync/snapshots/README.md +13 -0
- package/skills/execution/figmaSync/snapshots/last-sync.md +1538 -0
- package/skills/execution/figmaSync/templates/PLAN.md.tpl +227 -0
- package/skills/review/consistency-checker/SKILL.md +112 -0
- package/skills/review/consistency-checker/rules/README.md +16 -0
- package/src/cli/agents.mjs +16 -0
- package/src/cli/copy.mjs +95 -0
- package/src/cli/install.mjs +131 -0
- package/src/cli/prompts.mjs +66 -0
- package/tools/lark/README.md +78 -0
- package/tools/lark/references/lark-doc.md +156 -0
- package/tools/lark/references/lark-read.md +235 -0
- package/tools/lark/references/prepare.md +147 -0
- package/tools/lark/scripts/lark_api.mjs +404 -0
- package/tools/lark/scripts/lark_api.test.mjs +63 -0
- package/tools/lark/scripts/lark_check_permissions.mjs +52 -0
- package/tools/lark/scripts/lark_publish_doc.mjs +299 -0
- package/tools/lark/scripts/lark_read_docx.mjs +328 -0
- package/tools/lark/scripts/markdown_to_lark_blocks.mjs +397 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
---
|
|
2
|
+
figmaUrl: <粘贴用户提供的 Figma URL>
|
|
3
|
+
nodeId: <从 URL 提取的 node-id,例如 123:456>
|
|
4
|
+
fileKey: <从 URL 提取的 fileKey>
|
|
5
|
+
targetFile: <计划落地的源码主文件,例如 src/routes/welcome/route.tsx>
|
|
6
|
+
foundationSummaryPath: ./foundation-summary.md
|
|
7
|
+
|
|
8
|
+
# 失效锚点(apply 阶段校验是否过期)
|
|
9
|
+
figmaMetadataHash: <get_metadata 返回 JSON 的 sha256>
|
|
10
|
+
figmaSnapshotPath: ./figma-snapshot.png
|
|
11
|
+
apexUiVersion: <node_modules/@frontend/apex-ui--react/package.json 的 version>
|
|
12
|
+
themeHash: <theme/dark/primitives.css + theme/dark/aliases.css 的 sha256 摘要>
|
|
13
|
+
cssDraftPath: ./figma-plan.css
|
|
14
|
+
cssDraftHash: <figma-plan.css 的 sha256>
|
|
15
|
+
|
|
16
|
+
# 报告引用
|
|
17
|
+
planReport: ./session-report-plan.md
|
|
18
|
+
applyReport: null
|
|
19
|
+
|
|
20
|
+
plannedAt: <ISO 时间戳>
|
|
21
|
+
status: planned # planned | applied | superseded
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# PLAN: <页面/特性名>
|
|
25
|
+
|
|
26
|
+
> 本文档由 `figmaSync plan` 生成。plan 阶段同时生成 `figma-plan.css` 作为审核草案。
|
|
27
|
+
> 该 CSS 草案不在 plan 阶段接入业务代码;apply 阶段需在用户确认后迁移为正式 CSS。
|
|
28
|
+
|
|
29
|
+
## 1. 视觉结构
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
<节点层级树,按 get_metadata 输出整理;保留 nodeId 便于追溯>
|
|
35
|
+
- WelcomePage (1:1)
|
|
36
|
+
├── HeroBanner (1:2)
|
|
37
|
+
├── FeatureGrid (1:5)
|
|
38
|
+
└── Footer (1:9)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 2. 组件映射决策
|
|
42
|
+
|
|
43
|
+
| Figma 节点 | nodeId | 目标实现 | 复用类型 | 样式归属 | 是否需要 CSS | 备注 |
|
|
44
|
+
| ----------- | ------ | ----------------------------------------- | ---------------- | ------------- | ------------ | ------------------ |
|
|
45
|
+
| CTA Button | 1:3 | `<Button type="primary" size="medium" />` | Apex UI | Apex UI props | 否 | props 可覆盖视觉 |
|
|
46
|
+
| FilterPanel | 1:4 | `TableFilter` | common component | 业务组件 CSS | 少量 | 只补布局 class |
|
|
47
|
+
| HeroBanner | 1:2 | 自定义 section | 无 | 页面 CSS | 是 | 页面私有布局 |
|
|
48
|
+
|
|
49
|
+
> verify-plan 会校验本表里所有 `<Component prop=...>` 字面量是否存在于 Apex UI llm
|
|
50
|
+
> 文档;引用未注册的组件 / prop 会 fail 或 warning。
|
|
51
|
+
>
|
|
52
|
+
> 样式归属必须先判定再写 CSS。`Apex UI props` 表示颜色、边框、圆角、字号、状态样式由
|
|
53
|
+
> 组件 prop 或内置状态控制,不得写入 `figma-plan.css`;`业务组件 CSS` 和 `页面 CSS`
|
|
54
|
+
> 才允许进入 CSS 草案;`待确认` 必须进入待确认清单。
|
|
55
|
+
|
|
56
|
+
## 2.5 Icon 资产匹配
|
|
57
|
+
|
|
58
|
+
> 本章节由 `figmaSync plan` 根据 Figma 节点语义与仓库 icon inventory 生成。若仓库没有
|
|
59
|
+
> `assets/`、`icons/`、`public/`、`src/**/assets/`、`src/**/icons/` 中的可用 icon,
|
|
60
|
+
> 必须在表格中写明,并让用户确认其他 icon 导入方案或确认不关心 icon。
|
|
61
|
+
|
|
62
|
+
Icon inventory:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
<node .agent/skills/figmaSync/scripts/icon-inventory.mjs --pretty 的输出摘要>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
| Figma 节点 | nodeId | 语义 | 匹配仓库 icon | 匹配依据 | 状态 | 用户确认 |
|
|
69
|
+
| ---------- | ------ | -------- | -------------------------- | ----------------------- | ---- | -------- |
|
|
70
|
+
| SearchIcon | 1:20 | search | `src/icons/SearchIcon.tsx` | 文件名精确匹配 `search` | 可用 | 已确认 |
|
|
71
|
+
| ExportIcon | 1:21 | download | - | 仓库未发现 download icon | 缺失 | 待确认 |
|
|
72
|
+
|
|
73
|
+
状态取值:
|
|
74
|
+
|
|
75
|
+
- `可用` — apply 阶段必须使用匹配到的仓库 icon。
|
|
76
|
+
- `缺失` — plan 阶段必须让用户确认处理方式;未确认时 verify-plan 失败。
|
|
77
|
+
- `忽略` — 用户明确表示不关心 icon 或接受无 icon 实现。
|
|
78
|
+
|
|
79
|
+
## 3. 组件与模块拆分规划
|
|
80
|
+
|
|
81
|
+
> 根据设计稿复杂度和目标目录的基建情况,制定合理的组件与样式拆分策略。
|
|
82
|
+
|
|
83
|
+
- **拆分策略**:[基于现有基建组件拆分 / Human 确认的轻量视觉拆分]
|
|
84
|
+
- **基建事实来源**:
|
|
85
|
+
- route:
|
|
86
|
+
- components:
|
|
87
|
+
- service:
|
|
88
|
+
- types:
|
|
89
|
+
- constants:
|
|
90
|
+
- foundation-summary:
|
|
91
|
+
- **figmaSync 允许修改范围**:
|
|
92
|
+
- 布局。
|
|
93
|
+
- CSS。
|
|
94
|
+
- Apex UI 组件选型和 props。
|
|
95
|
+
- 局部展示结构。
|
|
96
|
+
- **figmaSync 禁止修改范围**:
|
|
97
|
+
- 路由路径。
|
|
98
|
+
- service 契约。
|
|
99
|
+
- API 请求基础封装。
|
|
100
|
+
- 类型语义。
|
|
101
|
+
- 页面核心状态模型。
|
|
102
|
+
- 业务组件边界。
|
|
103
|
+
- 权限逻辑。
|
|
104
|
+
- Wujie bridge。
|
|
105
|
+
- src/routeTree.gen.ts。
|
|
106
|
+
- **拆分明细**:
|
|
107
|
+
- **模块/组件 A** (对应节点 `nodeId`)
|
|
108
|
+
- 文件路径:`src/.../xxx.tsx`
|
|
109
|
+
- CSS 拆分:`src/.../xxx.css` (包含主要的局部 class)
|
|
110
|
+
- 职责/内容:...
|
|
111
|
+
- **模块/组件 B** ...
|
|
112
|
+
|
|
113
|
+
## 4. CSS 变量映射表
|
|
114
|
+
|
|
115
|
+
| 来源(Figma) | 类型 | CSS variable | /theme 路径 | 原始值 | 命中方式 | nodeId | designContextStatus |
|
|
116
|
+
| -------------------------------------- | ------ | ------------------------- | ---------------------------------- | ---------- | ------------------------- | ------ | ------------------- |
|
|
117
|
+
| `var(--fill-neutral-primary, #090A0B)` | color | `--fill-neutral-primary` | `foundation.fill.neutral.primary` | `{grey.14}` | Fast Path 0 (variables) | 1:2 | OK |
|
|
118
|
+
| `var(--spacing-24, 24px)` | number | `--spacing-24` | `baseValue.spacing.24` | `24` | Fast Path 0 (variables) | 1:2 | OK |
|
|
119
|
+
| `#202226` | color | `--fill-neutral-tertiary` | `foundation.fill.neutral.tertiary` | `{grey.12}` | Slow Path (value-match) | 1:6 | OK |
|
|
120
|
+
| `--xxx-not-mapped` | - | - | - | - | 未命中 | 1:8 | OK |
|
|
121
|
+
|
|
122
|
+
命中方式取值约定:
|
|
123
|
+
|
|
124
|
+
- `Fast Path 0 (variables)` — 从 `get_variable_defs` 字典直接命中。
|
|
125
|
+
- `Fast Path 1 (lookup-var)` — `lookup-var.mjs` 命中。
|
|
126
|
+
- `Slow Path (value-match)` — `match-token.mjs` 命中。
|
|
127
|
+
- `未命中` — 写入 CSS 草案 TODO 注释和 §5 待确认清单。
|
|
128
|
+
|
|
129
|
+
designContextStatus 取值约定:
|
|
130
|
+
|
|
131
|
+
- `OK` — `get_design_context` 成功返回。
|
|
132
|
+
- `RECURSED` — 因 too-large 递归到子节点拿到的。
|
|
133
|
+
- `TOO_LARGE_SKIPPED` — 递归 2 层仍 too-large,apply 阶段必须补调。
|
|
134
|
+
|
|
135
|
+
## 5. CSS 草案审核
|
|
136
|
+
|
|
137
|
+
CSS 草案文件:`./figma-plan.css`
|
|
138
|
+
|
|
139
|
+
审核重点:
|
|
140
|
+
|
|
141
|
+
- 是否只使用原生 CSS。
|
|
142
|
+
- 是否优先使用 `/theme` CSS variables。
|
|
143
|
+
- 是否避免覆盖 Apex UI props 已能表达的样式。
|
|
144
|
+
- 是否已剔除 Apex UI 组件自管的状态色、背景、边框、圆角、字号、hover/active/disabled/selected 状态。
|
|
145
|
+
- 是否只包含归属为 `业务组件 CSS` 或 `页面 CSS` 的样式。
|
|
146
|
+
- 未命中变量是否均有 TODO 注释和待确认记录。
|
|
147
|
+
|
|
148
|
+
```css
|
|
149
|
+
<figma-plan.css 关键片段,保留完整文件在同目录>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## 6. 样式落地清单
|
|
153
|
+
|
|
154
|
+
### Level 1(组件局部 CSS)
|
|
155
|
+
|
|
156
|
+
- HeroBanner: `.welcome-page__hero`,使用 `--fill-neutral-primary` 与 `--spacing-24`。
|
|
157
|
+
|
|
158
|
+
### Level 2(扩展 common component)
|
|
159
|
+
|
|
160
|
+
- (无)
|
|
161
|
+
|
|
162
|
+
### Level 3(建议新增 /theme 变量)
|
|
163
|
+
|
|
164
|
+
- (无)
|
|
165
|
+
|
|
166
|
+
### 未命中待确认
|
|
167
|
+
|
|
168
|
+
- `#123456` from node `1:8`:现有 `/theme` 未命中,需要用户确认是否新增变量或接受一次性 CSS 值。
|
|
169
|
+
|
|
170
|
+
## 7. 实施步骤
|
|
171
|
+
|
|
172
|
+
| # | 子任务 | 产物 | 验证标准 | 独立 commit |
|
|
173
|
+
| --- | ----------------------------------------------- | ----------------------- | --------------------- | ----------- |
|
|
174
|
+
| 1 | 新建/更新 `<targetFile>` 骨架 | route.tsx | typecheck pass | ❌ |
|
|
175
|
+
| 2 | 将 `figma-plan.css` 迁移为正式 CSS 文件 | route.css / styles.css | format:check pass | ✅ |
|
|
176
|
+
| 3 | 拼装 Apex UI 与 common component | route.tsx | dev 视觉 OK | ✅ |
|
|
177
|
+
| 4 | 拼装自定义区块并绑定 className | route.tsx + CSS | dev 视觉 OK | ✅ |
|
|
178
|
+
| 5 | 收尾跑 `pnpm figma:report --command=apply ...` | session-report-apply.md | 硬编码待确认项已解释 | — |
|
|
179
|
+
|
|
180
|
+
> 「独立 commit = ✅」的行做完后,AI 必须停下等用户测试再进入下一行。
|
|
181
|
+
|
|
182
|
+
## 8. CSS 草案内容
|
|
183
|
+
|
|
184
|
+
```css
|
|
185
|
+
/* figma-plan.css */
|
|
186
|
+
.welcome-page {
|
|
187
|
+
min-height: 100%;
|
|
188
|
+
background: var(--fill-neutral-primary);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.welcome-page__hero {
|
|
192
|
+
display: flex;
|
|
193
|
+
gap: var(--spacing-24);
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## 9. 实施偏离记录
|
|
198
|
+
|
|
199
|
+
> 由 `figmaSync apply` 在执行过程中追加。如无偏离,保留本章节但留空。
|
|
200
|
+
|
|
201
|
+
- <ISO 时间戳> · <章节> · <原决策> → <新决策> · 原因:<一句话>
|
|
202
|
+
|
|
203
|
+
## 10. 盲区与待补调
|
|
204
|
+
|
|
205
|
+
记录 plan 阶段无法解析的节点(多为 too-large),apply 阶段必须补调:
|
|
206
|
+
|
|
207
|
+
| nodeId | 节点名 | 原因 | apply 补调状态 |
|
|
208
|
+
| ------ | --------- | ------------------------------------------ | -------------- |
|
|
209
|
+
| 1:42 | DataTable | get_design_context too-large 递归 2 层失败 | 待补调 |
|
|
210
|
+
|
|
211
|
+
## 11. 参考链接
|
|
212
|
+
|
|
213
|
+
- Figma URL: <同 frontmatter>
|
|
214
|
+
- Plan session report: <同 frontmatter.planReport>
|
|
215
|
+
- Apply session report: <apply 阶段写入 frontmatter.applyReport>
|
|
216
|
+
|
|
217
|
+
## 12. 反向校验
|
|
218
|
+
|
|
219
|
+
> 由 `pnpm figma:verify-plan` 跑出后,AI 把结果摘要写到这里。warning 项必须
|
|
220
|
+
> 在这里说明意图,否则 review 时无法判断是否可接受。
|
|
221
|
+
|
|
222
|
+
- CSS variable 校验:✅ N 行已检查
|
|
223
|
+
- CSS 草案校验:✅ figma-plan.css 已检查
|
|
224
|
+
- Apex UI 校验:✅ M 个组件已校验
|
|
225
|
+
- Icon 校验:✅ 可用 N · 缺失 M · 忽略 K
|
|
226
|
+
- warning:
|
|
227
|
+
- <如有>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: consistency-checker
|
|
3
|
+
description: PM↔UI 一致性检查器。检查 PRD 全文与 Figma UI 稿之间的一致性,输出结构化差异清单,用于 UI 评审会前 12-24 小时前置发现问题。适用于用户提供 PRD 和 Figma 链接并要求检查一致性、评审前查漏、生成一致性 checklist 时。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 一致性检查器
|
|
7
|
+
|
|
8
|
+
## 对齐边诊断
|
|
9
|
+
|
|
10
|
+
| 项 | 值 |
|
|
11
|
+
|---|---|
|
|
12
|
+
| 服务对齐边 | PM↔UI |
|
|
13
|
+
| 现状分级 | 主痛 |
|
|
14
|
+
| AI 形态 | 补契约工具 |
|
|
15
|
+
| 主战场 | UI 评审前 12-24 小时(次战场:自测联调阶段兜底) |
|
|
16
|
+
|
|
17
|
+
## 这个 skill 解决什么问题
|
|
18
|
+
|
|
19
|
+
在 UI 评审会前 12-24 小时前置检查 PRD 与 UI 稿之间的一致性,把「评审过快 + 无 PRD 对照」导致的主痛遗漏,前移到会前发现。
|
|
20
|
+
|
|
21
|
+
## 什么时候用
|
|
22
|
+
|
|
23
|
+
- 用户显式输入 `$consistency-checker <prd> <figma-url>`
|
|
24
|
+
- 用户话术:"帮我检查一下 PRD 和 UI 稿有没有对不上的地方 / UI 评审前查一下 / 生成一致性 checklist"
|
|
25
|
+
- 用户提供 PRD 全文 + Figma URL
|
|
26
|
+
|
|
27
|
+
## 前置产物
|
|
28
|
+
|
|
29
|
+
| 产物 | 来源 | 是否必需 |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| PRD 全文(md / txt / URL) | 人工提供 / lark-read | 必需,**不支持 PDF** |
|
|
32
|
+
| Figma URL | 人工提供 | 必需,**强制走 Figma MCP,不降级到 OCR / 截图** |
|
|
33
|
+
| 历史校准数据 | `skills/artifact/calibration/` | 可选 |
|
|
34
|
+
| 项目业务词表 | `rules/` | 可选 |
|
|
35
|
+
| 历史类似需求 checklist | `rules/` | 可选 |
|
|
36
|
+
|
|
37
|
+
## 输出产物
|
|
38
|
+
|
|
39
|
+
| 产物 | 位置 | 下游消费者 |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `consistency-report.json` | 项目文档目录 | 人工评审会 / 未闭环清单 |
|
|
42
|
+
|
|
43
|
+
结构(每条):
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
id · 唯一标识
|
|
47
|
+
group · PRD 未见 / UI 越界 / 状态覆盖 / 语义一致
|
|
48
|
+
priority · P0 / P1 / P2(P0 硬性 ≤ 5 条)
|
|
49
|
+
content · 问题描述(≥30 字)
|
|
50
|
+
references · { PRD 章节引用 · Figma 节点引用 }
|
|
51
|
+
suggested_speech · AI 生成的举手话术
|
|
52
|
+
rule_id · 触发的规则 ID
|
|
53
|
+
confidence · high / medium / low
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 下一步
|
|
57
|
+
|
|
58
|
+
- 输出 P0 项 → UI 评审会当场质询
|
|
59
|
+
- 会中未认领的 P0/P1 项 → 写入 `skills/artifact/open-issues/`(未闭环清单)
|
|
60
|
+
- 自测阶段发现遗漏 → 反查 `consistency-report.json`,归因写入 `skills/artifact/calibration/`(校准清单)
|
|
61
|
+
|
|
62
|
+
## 明确不做
|
|
63
|
+
|
|
64
|
+
- **不处理 PRD 隐含约定** —— PM 侧问题,AI 无解
|
|
65
|
+
- **不做 UI 层动画 / 悬浮态细节判断** —— Figma 静态元数据难以覆盖
|
|
66
|
+
- **不越界判断"UI 越界是设计还是遗漏"** —— 标为"待 PM 澄清",不下结论
|
|
67
|
+
- **不接受 PDF PRD** —— 结构化提取不可靠
|
|
68
|
+
- **不降级到 OCR / 截图** —— Figma URL 必须能被 Figma MCP 访问,不能就报错
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 详细规则
|
|
73
|
+
|
|
74
|
+
### 四类检查
|
|
75
|
+
|
|
76
|
+
**第一类 · PRD 提到但 UI 未见**
|
|
77
|
+
- 操作类关键词(增/删/改/查 · 导入/导出 · 批量)→ 检查 UI 是否有对应入口
|
|
78
|
+
- 权限描述(角色 X 可 Y)→ 检查 UI 是否有对应角色的入口 / 隐藏
|
|
79
|
+
- 异常态描述(超时 / 失败 / 无权限)→ 检查 UI 是否有对应状态
|
|
80
|
+
|
|
81
|
+
**第二类 · UI 有但 PRD 未提(可能越界)**
|
|
82
|
+
- UI 稿上的按钮 / 入口 → 反查 PRD 是否有对应功能描述
|
|
83
|
+
- UI 上的字段展示 → 反查 PRD 是否有对应字段定义
|
|
84
|
+
|
|
85
|
+
**第三类 · 状态覆盖**
|
|
86
|
+
- 列表 / 详情页 → 检查空态 / 加载态 / 错误态是否已画
|
|
87
|
+
- 输入字段 → 检查极限值 / 空值 / 特殊字符
|
|
88
|
+
- 多语言场景 → 检查文案溢出
|
|
89
|
+
|
|
90
|
+
**第四类 · 语义一致性**
|
|
91
|
+
- PRD 措辞 vs UI 文案 → 检查关键词对应
|
|
92
|
+
- PRD 枚举值 vs UI 展示 → 检查值域一致
|
|
93
|
+
|
|
94
|
+
### 规则库外置
|
|
95
|
+
|
|
96
|
+
规则文件放 `rules/*.json`,可直接改文件不改 skill。
|
|
97
|
+
|
|
98
|
+
### 输出约束
|
|
99
|
+
|
|
100
|
+
- **P0 硬性 ≤ 5 条**(超过自动降 P1)—— 防止扫读容量过载
|
|
101
|
+
- 无 references 的项自动 discard
|
|
102
|
+
- suggested_speech 用于降低人的发言门槛(配合"降门槛"策略)
|
|
103
|
+
|
|
104
|
+
### 待补充实现
|
|
105
|
+
|
|
106
|
+
- [ ] `rules/` 规则库初始 json 骨架
|
|
107
|
+
- [ ] PRD 加载器(考虑抽 `tools/prd-loader/`)
|
|
108
|
+
- [ ] Figma MCP 调用封装(考虑抽 `tools/figma-mcp/`)
|
|
109
|
+
- [ ] 四类检查的 JS 实现
|
|
110
|
+
- [ ] 输出格式化脚本
|
|
111
|
+
|
|
112
|
+
**当前状态:规范骨架已就绪,实现待启动。**
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# 规则库
|
|
2
|
+
|
|
3
|
+
一致性检查器的规则外置目录。改规则不改 skill 代码。
|
|
4
|
+
|
|
5
|
+
## 计划文件
|
|
6
|
+
|
|
7
|
+
- `operation-keywords.json` — 操作类关键词表(增/删/改/查 · 导入/导出 · 批量)
|
|
8
|
+
- `permission-patterns.json` — 权限描述提取正则
|
|
9
|
+
- `exception-patterns.json` — 异常态描述提取正则
|
|
10
|
+
- `state-coverage.json` — 状态覆盖检查项(空态 / 加载 / 错误 / 权限拒绝 / 超时)
|
|
11
|
+
- `boundary-values.json` — 边界值检查项(极限 / 空值 / 特殊字符 / 多语言溢出)
|
|
12
|
+
- `semantic-glossary.json` — 语义词表(同名不同义 / 措辞对应)
|
|
13
|
+
|
|
14
|
+
## Schema 约定(TBD)
|
|
15
|
+
|
|
16
|
+
每个规则文件的 JSON schema 在 skill 实现开始时定义。当前仅占位。
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// agent 名 → 目标目录映射
|
|
2
|
+
// 目录路径均以「用户仓库根目录」为基准,且以斜杠结尾便于拼接。
|
|
3
|
+
export const AGENTS = {
|
|
4
|
+
claude: {
|
|
5
|
+
id: 'claude',
|
|
6
|
+
label: 'Claude Code',
|
|
7
|
+
targetDir: '.claude/'
|
|
8
|
+
},
|
|
9
|
+
codex: {
|
|
10
|
+
id: 'codex',
|
|
11
|
+
label: 'Codex',
|
|
12
|
+
targetDir: '.agents/'
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const AGENT_ORDER = ['claude', 'codex']
|
package/src/cli/copy.mjs
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// 递归复制目录:镜像仓库层级,逐文件冲突处理。
|
|
2
|
+
import fs from 'node:fs/promises'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
export const CONFLICT = {
|
|
6
|
+
SKIP: 'skip',
|
|
7
|
+
OVERWRITE: 'overwrite',
|
|
8
|
+
PROMPT: 'prompt'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// 单文件冲突时的处理结果码(供 onConflict 回调返回)
|
|
12
|
+
export const CONFLICT_DECISION = {
|
|
13
|
+
SKIP_ONE: 'skip_one',
|
|
14
|
+
OVERWRITE_ONE: 'overwrite_one',
|
|
15
|
+
SKIP_REST: 'skip_rest',
|
|
16
|
+
OVERWRITE_REST: 'overwrite_rest'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function pathExists(p) {
|
|
20
|
+
try {
|
|
21
|
+
await fs.access(p)
|
|
22
|
+
return true
|
|
23
|
+
} catch {
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function walkFiles(srcRoot) {
|
|
29
|
+
const out = []
|
|
30
|
+
async function walk(dir) {
|
|
31
|
+
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
const full = path.join(dir, entry.name)
|
|
34
|
+
if (entry.isDirectory()) {
|
|
35
|
+
await walk(full)
|
|
36
|
+
} else if (entry.isFile()) {
|
|
37
|
+
out.push(path.relative(srcRoot, full))
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
await walk(srcRoot)
|
|
42
|
+
return out
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function writeFile(from, to) {
|
|
46
|
+
await fs.mkdir(path.dirname(to), { recursive: true })
|
|
47
|
+
await fs.copyFile(from, to)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 复制单个源目录到目标目录,返回统计。
|
|
51
|
+
// state 让「本次全部这样」的决定跨目录延续;调用方在整个安装流程内共享一个 state 对象。
|
|
52
|
+
export async function copyDir(srcAbs, destAbs, { conflict = CONFLICT.SKIP, onConflict, state = {} } = {}) {
|
|
53
|
+
const stats = { copied: 0, skipped: 0, overwritten: 0 }
|
|
54
|
+
if (!(await pathExists(srcAbs))) {
|
|
55
|
+
throw new Error(`源目录不存在:${srcAbs}`)
|
|
56
|
+
}
|
|
57
|
+
const files = await walkFiles(srcAbs)
|
|
58
|
+
for (const rel of files) {
|
|
59
|
+
const from = path.join(srcAbs, rel)
|
|
60
|
+
const to = path.join(destAbs, rel)
|
|
61
|
+
const exists = await pathExists(to)
|
|
62
|
+
if (!exists) {
|
|
63
|
+
await writeFile(from, to)
|
|
64
|
+
stats.copied++
|
|
65
|
+
continue
|
|
66
|
+
}
|
|
67
|
+
// 冲突分派
|
|
68
|
+
let mode = conflict
|
|
69
|
+
if (mode === CONFLICT.PROMPT) {
|
|
70
|
+
if (state.restOverride) {
|
|
71
|
+
mode = state.restOverride
|
|
72
|
+
} else {
|
|
73
|
+
const decision = await onConflict(to)
|
|
74
|
+
if (decision === CONFLICT_DECISION.SKIP_REST) {
|
|
75
|
+
state.restOverride = CONFLICT.SKIP
|
|
76
|
+
mode = CONFLICT.SKIP
|
|
77
|
+
} else if (decision === CONFLICT_DECISION.OVERWRITE_REST) {
|
|
78
|
+
state.restOverride = CONFLICT.OVERWRITE
|
|
79
|
+
mode = CONFLICT.OVERWRITE
|
|
80
|
+
} else if (decision === CONFLICT_DECISION.OVERWRITE_ONE) {
|
|
81
|
+
mode = CONFLICT.OVERWRITE
|
|
82
|
+
} else {
|
|
83
|
+
mode = CONFLICT.SKIP
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (mode === CONFLICT.OVERWRITE) {
|
|
88
|
+
await writeFile(from, to)
|
|
89
|
+
stats.overwritten++
|
|
90
|
+
} else {
|
|
91
|
+
stats.skipped++
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return stats
|
|
95
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// 主流程:选 agent → 选冲突策略 → 展示计划 → 确认 → 复制 → 后置检查。
|
|
2
|
+
import fs from 'node:fs/promises'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { AGENTS, AGENT_ORDER } from './agents.mjs'
|
|
6
|
+
import { copyDir, CONFLICT, CONFLICT_DECISION } from './copy.mjs'
|
|
7
|
+
import { select, confirm, closePrompts } from './prompts.mjs'
|
|
8
|
+
|
|
9
|
+
const COPY_PLAN = [
|
|
10
|
+
{ srcRel: 'skills/execution/devFlow', destRel: 'skills/execution/devFlow' },
|
|
11
|
+
{ srcRel: 'skills/execution/figmaSync', destRel: 'skills/execution/figmaSync' },
|
|
12
|
+
{ srcRel: 'skills/review/consistency-checker', destRel: 'skills/review/consistency-checker' },
|
|
13
|
+
{ srcRel: 'skills/artifact', destRel: 'skills/artifact' },
|
|
14
|
+
{ srcRel: 'tools/lark', destRel: 'tools/lark' }
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
// 提示消费者:SKILL.md 里引用了根目录 HUMAN_AGENT_WORKFLOW.md,用户仓库根缺这个文件时打印警告。
|
|
18
|
+
const HAW_FILENAME = 'HUMAN_AGENT_WORKFLOW.md'
|
|
19
|
+
|
|
20
|
+
function packageRoot() {
|
|
21
|
+
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
22
|
+
return path.resolve(here, '..', '..')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function pathExists(p) {
|
|
26
|
+
try {
|
|
27
|
+
await fs.access(p)
|
|
28
|
+
return true
|
|
29
|
+
} catch {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function run() {
|
|
35
|
+
try {
|
|
36
|
+
await runInner()
|
|
37
|
+
} finally {
|
|
38
|
+
closePrompts()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function runInner() {
|
|
43
|
+
console.log('')
|
|
44
|
+
console.log('📦 @dev-workflow/skill installer')
|
|
45
|
+
|
|
46
|
+
const agentId = await select(
|
|
47
|
+
'? 目标 agent:',
|
|
48
|
+
AGENT_ORDER.map(id => ({
|
|
49
|
+
value: id,
|
|
50
|
+
label: AGENTS[id].label,
|
|
51
|
+
hint: `→ ${AGENTS[id].targetDir}`
|
|
52
|
+
})),
|
|
53
|
+
{ defaultIndex: 0 }
|
|
54
|
+
)
|
|
55
|
+
const agent = AGENTS[agentId]
|
|
56
|
+
const cwd = process.cwd()
|
|
57
|
+
const targetAbs = path.resolve(cwd, agent.targetDir)
|
|
58
|
+
|
|
59
|
+
const conflictMode = await select(
|
|
60
|
+
'? 冲突策略(目标文件已存在时):',
|
|
61
|
+
[
|
|
62
|
+
{ value: CONFLICT.SKIP, label: '跳过已存在文件', hint: '(保留目标仓库现有版本)' },
|
|
63
|
+
{ value: CONFLICT.OVERWRITE, label: '全部覆盖', hint: '(用包内版本覆盖)' },
|
|
64
|
+
{ value: CONFLICT.PROMPT, label: '逐个决定', hint: '(首次冲突时问,可选择本次剩余全部)' }
|
|
65
|
+
],
|
|
66
|
+
{ defaultIndex: 0 }
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
console.log('')
|
|
70
|
+
console.log(`📂 将复制以下内容到 ${agent.targetDir}:`)
|
|
71
|
+
for (const item of COPY_PLAN) {
|
|
72
|
+
console.log(` ${item.srcRel}/ → ${agent.targetDir}${item.destRel}/`)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const ok = await confirm('? 确认开始?', { defaultYes: false })
|
|
76
|
+
if (!ok) {
|
|
77
|
+
console.log('\n已取消,未做任何改动。')
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const pkgRoot = packageRoot()
|
|
82
|
+
const total = { copied: 0, skipped: 0, overwritten: 0 }
|
|
83
|
+
const state = {}
|
|
84
|
+
for (const item of COPY_PLAN) {
|
|
85
|
+
const src = path.join(pkgRoot, item.srcRel)
|
|
86
|
+
const dest = path.join(targetAbs, item.destRel)
|
|
87
|
+
process.stdout.write(` ↳ ${item.destRel} ... `)
|
|
88
|
+
const stats = await copyDir(src, dest, {
|
|
89
|
+
conflict: conflictMode,
|
|
90
|
+
state,
|
|
91
|
+
onConflict: filePath => askConflict(filePath)
|
|
92
|
+
})
|
|
93
|
+
total.copied += stats.copied
|
|
94
|
+
total.skipped += stats.skipped
|
|
95
|
+
total.overwritten += stats.overwritten
|
|
96
|
+
console.log(`copied=${stats.copied}, skipped=${stats.skipped}, overwritten=${stats.overwritten}`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log('')
|
|
100
|
+
console.log(`✅ 已安装到 ${agent.targetDir}`)
|
|
101
|
+
console.log(` 合计:新增 ${total.copied},跳过 ${total.skipped},覆盖 ${total.overwritten}`)
|
|
102
|
+
|
|
103
|
+
// 后置检查:目标仓库根是否有 HUMAN_AGENT_WORKFLOW.md
|
|
104
|
+
const hawPath = path.resolve(cwd, HAW_FILENAME)
|
|
105
|
+
const hasHaw = await pathExists(hawPath)
|
|
106
|
+
if (!hasHaw) {
|
|
107
|
+
console.log('')
|
|
108
|
+
console.log(`⚠️ 未在仓库根目录发现 ${HAW_FILENAME}`)
|
|
109
|
+
console.log(` devFlow / prd-review 等子命令依赖它判断 L0/L1/L2/L3 分档`)
|
|
110
|
+
console.log(` 请从 dev-workflow-skill 源仓库获取并放到项目根目录`)
|
|
111
|
+
console.log(` https://github.com/Wenyari/dev-workflow-skill/blob/main/${HAW_FILENAME}`)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log('')
|
|
115
|
+
console.log(`建议:git add ${agent.targetDir} && git commit -m "chore: install @dev-workflow/skill"`)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function askConflict(filePath) {
|
|
119
|
+
console.log('')
|
|
120
|
+
console.log(`⚠️ 冲突:${filePath} 已存在`)
|
|
121
|
+
return select(
|
|
122
|
+
'? 处理方式:',
|
|
123
|
+
[
|
|
124
|
+
{ value: CONFLICT_DECISION.SKIP_ONE, label: '跳过本文件' },
|
|
125
|
+
{ value: CONFLICT_DECISION.OVERWRITE_ONE, label: '覆盖本文件' },
|
|
126
|
+
{ value: CONFLICT_DECISION.SKIP_REST, label: '此后剩余冲突全部跳过' },
|
|
127
|
+
{ value: CONFLICT_DECISION.OVERWRITE_REST, label: '此后剩余冲突全部覆盖' }
|
|
128
|
+
],
|
|
129
|
+
{ defaultIndex: 2 }
|
|
130
|
+
)
|
|
131
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// 纯 Node readline 交互,不引入第三方依赖。
|
|
2
|
+
// 用 module-level 单例 rl,避免多次 close/open 关闭 stdin 后无法再读。
|
|
3
|
+
// 测试模式:设 DWF_TEST_ANSWERS="1|y" 时按顺序消费答案,绕过 readline。
|
|
4
|
+
import readline from 'node:readline'
|
|
5
|
+
|
|
6
|
+
let rlInstance = null
|
|
7
|
+
let testAnswers = null
|
|
8
|
+
|
|
9
|
+
function loadTestAnswers() {
|
|
10
|
+
if (testAnswers !== null) return testAnswers
|
|
11
|
+
const raw = process.env.DWF_TEST_ANSWERS
|
|
12
|
+
testAnswers = raw ? raw.split('|') : []
|
|
13
|
+
return testAnswers
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getRl() {
|
|
17
|
+
if (!rlInstance) {
|
|
18
|
+
rlInstance = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
19
|
+
}
|
|
20
|
+
return rlInstance
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function ask(question) {
|
|
24
|
+
const answers = loadTestAnswers()
|
|
25
|
+
if (answers.length > 0) {
|
|
26
|
+
const next = answers.shift()
|
|
27
|
+
process.stdout.write(`${question}${next}\n`)
|
|
28
|
+
return Promise.resolve(next)
|
|
29
|
+
}
|
|
30
|
+
return new Promise(resolve => getRl().question(question, answer => resolve(answer)))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 显式关闭:主流程结束时调用一次。
|
|
34
|
+
export function closePrompts() {
|
|
35
|
+
if (rlInstance) {
|
|
36
|
+
rlInstance.close()
|
|
37
|
+
rlInstance = null
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 单选,返回被选中项的 value。
|
|
42
|
+
// options: [{ value, label, hint? }]
|
|
43
|
+
export async function select(message, options, { defaultIndex = 0 } = {}) {
|
|
44
|
+
console.log('')
|
|
45
|
+
console.log(message)
|
|
46
|
+
options.forEach((opt, i) => {
|
|
47
|
+
const marker = i === defaultIndex ? '❯' : ' '
|
|
48
|
+
const hint = opt.hint ? ` ${opt.hint}` : ''
|
|
49
|
+
console.log(` ${marker} ${i + 1}) ${opt.label}${hint}`)
|
|
50
|
+
})
|
|
51
|
+
while (true) {
|
|
52
|
+
const raw = (await ask(`\n请输入编号 [1-${options.length}] (默认 ${defaultIndex + 1}):`)).trim()
|
|
53
|
+
if (raw === '') return options[defaultIndex].value
|
|
54
|
+
const n = Number(raw)
|
|
55
|
+
if (Number.isInteger(n) && n >= 1 && n <= options.length) return options[n - 1].value
|
|
56
|
+
console.log(` ⚠️ 请输入 1 - ${options.length} 之间的整数`)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// y/N 确认,默认 no。
|
|
61
|
+
export async function confirm(message, { defaultYes = false } = {}) {
|
|
62
|
+
const suffix = defaultYes ? '(Y/n)' : '(y/N)'
|
|
63
|
+
const raw = (await ask(`\n${message} ${suffix}:`)).trim().toLowerCase()
|
|
64
|
+
if (raw === '') return defaultYes
|
|
65
|
+
return raw === 'y' || raw === 'yes'
|
|
66
|
+
}
|