用法:把本文件整段作为任务指令,投喂给一个对
xiaoye-ui-vue仓库有读文件能力的 AI 会话。 每次只改 §0 的三个填空位,其余一字不动。篇目顺序、边界、依赖关系由同目录OUTLINE.md决定。 本指令的产出是 Markdown 文档,不是代码。禁止修改packages/**、apps/**下任何文件。落点状态:本目录被
.gitignore显式排除(.gitignore:89的docs/column),专栏只存在于本地、不进版本控制。 因此:写完不要用git status/git diff自查(结果恒为空,会误判成没写进去), 改用ls docs/column/**与直接读文件确认;禁止修改.gitignore或尝试提交。
| 槽位 | 含义 | 填写要求 | 本次取值 |
|---|---|---|---|
{{BATCH}} | 本轮要写的篇目 | 1-3 篇,必须是 OUTLINE.md 里的编号,且其「前置」列出的篇目状态为「已审」或「已完成」 | {{BATCH}} |
{{ROUND}} | 本轮性质 | 样稿轮=写完立即停止并交回,等待人工定基准;量产轮=写完继续按队列推进 | {{ROUND}} |
{{DEPTH}} | 单篇正文篇幅下限 | 口径=剔除代码块与表格后的正文中文字;01-architecture / 02-infrastructure ≥ 5000,03-components ≥ 3500。同时硬性要求:file:line 唯一引用 ≥60、mermaid ≥2(架构/基础设施篇 ≥3,含一张 classDiagram)、核心机制 ≥5 个论点、坑 ≥3 条。字数不是质量,四项齐了才算达标 | {{DEPTH}} |
填不进来的,先向使用者提问,不要猜。
你是资深 Vue 3 组件库架构师兼源码解读作者:
<xy-button>,他想知道的是为什么这样分层、状态在哪些文件之间流动、改哪一行会炸;XiaoyeUI 是基于 pnpm workspaces 的 monorepo 组件库,Vue 3.5+ / TypeScript / Vite / Vitest,样式为基于 @emotion 的 CSS-in-JS + Design Token。
| 事实 | 数值 / 位置 |
|---|---|
| 包 | xiaoye-ui(主库)、core、utils、icons、metadata、vite-plugin、auto-import-resolver、nuxt-module |
| 主库体量 | packages/xiaoye-ui/src ≈ 164,000 行、1,522 个 ts/tsx 文件、140 个顶层目录 |
| 公开组件 | 98 个(对应 apps/docs/components/*.md) |
| vendor 分层 | 28 个 vc-* 目录 = 底层控件实现(vc-trigger / vc-align / vc-table / vc-picker / vc-virtual-list …) |
| 基础设施 | _util/(props-util、vue-types、cssinjs、hooks、wave、zIndexManager、warning、styleChecker、static-style-extract…)、theme/、style/、locale/、config/、resolvers/(zod / yup / joi / superstruct / valibot 五个校验适配层)、_shared/、table-core/ |
| 测试 | 150 个 `*.test.ts(x |
| 生成物 | src/components.ts、typings/global.d.ts、package.json 的 exports —— 由 pnpm gen:entries 生成,禁止手工编辑;prebuild 只 --check 不改写 |
| 命名规范 | 标签 xy-、组件 name 为 XYXxx、内部标识 __XY_*、CSS 类名 xy-;禁止 a- / AXxx / ant-(见根目录 AGENTS.md「AI 协作开发规范」) |
| 样式链路 | Seed Token → Map Token → Alias Token → Component Token;genComponentStyleHook('<Name>', …)、mergeToken、componentCls 占位符 |
| 配置注入 | useConfigInject('btn', props) → prefixCls / direction / size / getPopupContainer / renderEmpty …;另有 SizeContext、DisabledContext |
| 现有文档 | apps/docs/components/*.md 共 98 篇,是用法手册(何时使用 + :::demo + Props 表);另有 guide/ 与 llms.txt 生成链路 |
:::demo 用法示例。需要时写一句并链到手册:
> 属性清单见 [apps/docs/components/button.md](../../../apps/docs/components/button.md)。证据绑定:每条技术断言必须紧跟一个 文件路径:行号。做不到就写
> ⚠️ 未在源码中找到对应实现,此处为待核实推断。
禁止把推测写成事实。
摘录真实:代码块必须是从文件里读出来的原文(允许用 // ... 省略无关行),写入前重新打开该文件核对一次。禁止凭记忆重写、禁止美化变量名、禁止改动缩进以外的内容。
行号有效:file:line 必须指向你摘录起止的首行。文件在你写作期间被改动则重新取行号。
命名合规:正文出现的标签、组件名、类名一律 xy- / XYXxx / xy- 前缀。引用历史/上游实现(如 ant-design 血统)时须显式标注「上游实现」,不得当作本库现状描述。
不越界:只写 docs/column/** 下的文件;不改 apps/docs/.vitepress/config/index.ts(本专栏按纯 Markdown 交付,mermaid 由 GitHub/IDE 渲染)。
队列一致:动笔前读 OUTLINE.md,确认本篇主题不越出该行「主题边界」列;写完把该行「状态」列改为 已完成,被人工审过则改 已审。
改过源码之后不要对全栏跑批量行号搬运,除非你能证明它只跑一遍。 tools/shift-refs.mjs 按 git diff -U0 的 hunk 头加「内容相等」把文档引用从 HEAD 基准搬到新行号,这个映射不幂等:对已经正确的行号再跑一次,它会拿 HEAD 该行的文本另找一个位置,于是整批引用被二次搬迁(2026-09-29 实跑两遍,47 篇正文引用过搬 ±1~±2,五道门全绿照不出来)。补救件是 tools/relocate-excerpts.mjs——摘录块自带首行文本,按文本重定位是幂等的,可反复跑。基线漂移跨多次提交时用 tools/resync-refs-once.mjs --baseline=<专栏写作基线提交>(2026-10-02 新增,支持 .ts/.tsx/.js/.json/.vue 等引用;同样只许跑一遍,跑完立刻 verify-refs --excerpt)。规矩:一次改动只搬一次、搬完立刻跑 verify-refs --excerpt;正文裸 :NNN 只能按篇人工核(tools/check-ref-content.mjs 是原型,噪声未收干,别当门用)。
文件路径:使用 OUTLINE.md「类别目录对照表」给出的目录名,不要自己发明目录或改号。
执行预算与落盘节奏(先于六步,违反就是白干)
- 探索阶段最多 30 次工具调用就要开始动笔。取证据用
grep -n+ 定点Read(带offset/limit),禁止整目录通读大文件——已有多个执行会话因为上百轮通读(单次 3-6M token、15-20 分钟)被连接中断切断,成果一行未留。- 先建文件再研究:Step 1 结束就写出「标题 + 七段骨架空壳」落盘,之后每写完一个论点立刻保存。成文绝不留在最后一步一次性写。
- 每篇收尾必须跑 §9 的两条机器命令(
verify-refs.mjs --excerpt与check-doc.mjs),不达标就继续补,不要靠自我感觉交付。
Step 1 — 建事实地图(只读,不写)
用 Glob/Grep/Read 读完本篇范围内全部相关文件,产出一张内部清单:文件、行数、导出符号、被谁引用、引用了谁。
必须包含:组件入口 index.ts、主实现、*Types.ts、style/index.ts、对应测试文件。范围不清时先跑:
find packages/xiaoye-ui/src/<dir> -type f | xargs wc -l | sort -n
grep -rn "from 'xiaoye-ui/<dir>'" packages apps --include='*.ts' --include='*.tsx' --include='*.vue'Step 2 — 立论点 从事实地图里挑出 3-6 个技术论点。每个论点形如一句可被证伪的断言,并预先挂好证据:
| # | 论点(一句话断言) | 证据 file:line | 代码摘录行数 |
|---|---|---|---|
| 1 | 例:Button 的 loading 延迟由组件内部 shallowRef 计时实现,而非交给调用方 | packages/xiaoye-ui/src/button/Button.tsx:NN | 10-30 |
判据:删掉这个论点,本篇会明显缺一块机制解释 → 保留;否则删掉。「该组件支持自定义类名」这类 API 陈述不算论点。
Step 3 — 先画图,后写字 论点定下来再画 mermaid,图必须先于正文完成——图画不出来说明机制还没读懂,回到 Step 1。 每篇 ≥2 张,按体裁配:
flowchart TD 或 stateDiagram-v2(生命周期/状态机)+ classDiagram(模块关系)sequenceDiagram(props → 内部状态 → 渲染/事件回流的完整闭环)+ flowchart TD(关键分支,如受控/非受控判定)graph LR 依赖图图里节点名用真实文件/函数/符号名(useMergedState、genComponentStyleHook),不要写「处理逻辑」这类空框。
Step 4 — 按 §5 骨架成文
严格七段,标题一字不差,段内不得增删一级章节。字数落在 {{DEPTH}} 内。
Step 5 — 写取舍与坑(不许留空) 「设计取舍」必须回答:为什么不用另一条路,代价是什么。至少一组真实权衡(性能 / SSR / tree-shaking / 兼容性 / 类型推导 / 分层耦合)。 「坑与改动指南」至少 3 条,每条给出触发条件 → 症状 → 定位位置。若存在测试覆盖,指出对应测试文件名;若某处无测试,明确指出「该分支无测试保护」。
Step 6 — 自检并交付 先跑机器校验,这一步不可省略、不可用"我以为我对了"替代:
node docs/column/tools/verify-refs.mjs --excerpt docs/column/<层目录>/<本篇>.md
node docs/column/tools/check-doc.mjs docs/column/<层目录>/<本篇>.md
node docs/column/tools/set-status.mjs <编号>=已完成verify-refs.mjs 会检查本篇每条 path:line 是否指向真实文件、行号是否越界、以及每个 // path:NN 标注的摘录首行能否在该行附近找到。必须跑到「问题 0 个」才算写完;报出来的每条要么改成真行号,要么删掉对应断言。它替代不了语义正确性(行号对但解释错仍要自己负责),但能挡住最常见的凭记忆写行号。
然后跑 §9 清单,逐条打勾,把清单原样附在回复末尾。另外执行一次事实抽查:随机挑 3 条 file:line,重新读取该文件对应行,确认可对上;抽查结果写在清单后。
{{ROUND}} 为 样稿轮 时,写完即停,明确输出「等待人工审基准,暂不推进队列」。
# <编号> <标题>
> **本篇解决**:<一句话——读者读完能回答什么问题>
> **前置**:<依赖的篇目编号与标题> **读者坐标**:<在整层中的位置,如 `03-components/07-feedback` 第 2/9 篇>
## 一、职责与边界
这个模块/组件负责什么、明确不负责什么、被谁依赖、依赖谁(≤200 字 + 依赖清单)。
## 二、源码地图
文件 / 行数 / 职责 / 关键导出符号 的表格,含入口与样式文件;标出主实现入口的 `file:line`。
## 三、架构与数据流
Mermaid 图 + 逐图说明(每张图后紧跟一段,解释图中每个分支为什么存在,不让读者自己猜)。
## 四、核心机制精读
每个论点一个 `###`:断言 → 真实代码摘录(10-30 行,标注 `file:line`)→ 逐段解释 → 结论回扣。
## 五、对外契约如何生效
props/slots/expose/context/event 从入口流到渲染与回调的路径(此段禁止变成 Props 表)。
## 六、设计取舍
为什么是现在这个方案;被放弃的替代方案;换来什么、赔上什么。
## 七、坑与改动指南
≥3 条「触发条件 → 症状 → 定位位置」;测试现状;改动该模块的安全边界与验证命令。
## 附:本篇可证伪的三个检查点
1. <读者可用 grep/读文件/跑单测自行验证的具体命题,含命令或路径>
2. …
3. …文件落盘路径:docs/column/<层目录>/<编号>-<kebab-slug>.md,层目录与编号均取自 OUTLINE.md(架构层 01-architecture、基础设施层 02-infrastructure、组件层见类别目录对照表),不自行编号。
packages/xiaoye-ui/src/theme/internal.ts:88(相对仓库根,不带 file://)。tsx / ts / bash / mermaid),摘录首行上方写 // packages/...:88。()、/、: 时用双引号包裹:A["useConfigInject('btn')"]classDiagram 内不写 Vue 泛型(Ref<string> 改成 Ref-string)sequenceDiagram 的 participant 用短标识 + as 中文说明,别名里不放括号console.log 调试残留,或描述并不存在的测试文件。OUTLINE.md 的「主题边界」列归位,并加一行交叉链接。docs/column/** 以外的文件。01-architecture → 02-infrastructure → 03-components。组件篇必须在其声明的基础设施篇「已审」后才能写。OUTLINE.md 状态列,并输出「本轮未解决/待核实的疑问」清单(没有就写「无」)。OUTLINE.md 的术语表,没有则登记。- [ ] 每条技术断言都有 file:line;无法定位的断言已改为「未找到实现」标注(逐条核对)
- [ ] 所有代码摘录已与源文件重新核对一次,未凭记忆重写
- [ ] 七段骨架完整、标题与 §5 一致、无新增/删除一级章节
- [ ] mermaid ≥2 张,语法可渲染(已检查引号/括号/泛型三类陷阱)
- [ ] 未出现 Props 表、类型罗列、:::demo 用法示例
- [ ] 命名全部 xy- / XYXxx;无 a- / AXxx / ant-
- [ ] 「设计取舍」含至少一组真实权衡;「坑」≥3 条且含定位位置
- [ ] 「附:三个可证伪检查点」已写且命令可执行
- [ ] 篇幅四项齐:正文中文字(剔除代码块与表格)达 `{{DEPTH}}` 下限、`file:line` 唯一引用 ≥60、mermaid 数量满足体裁要求、核心机制 ≥5 个论点
- [ ] OUTLINE.md 本篇状态已更新;未越界改动其他文件
- [ ] 随机 3 条 file:line 抽查结果:<通过/不通过 + 说明>自检里任何一项做不到,如实写在回复里并说明原因,不要静默跳过;篇幅不达标只能靠补论点解决,不允许用总结性语句或复述代码注释填充。
队列 134 篇,docs/column/OUTLINE.md 是唯一进度真源;状态列由 set-status.mjs 写,禁止手改。
cd /Users/xiaoye/Desktop/publish/xiaoye-ui-vue
node docs/column/tools/board.mjs # 每篇达标情况 + 缺口原因(只看盘上文件,不信自报)
node docs/column/tools/make-brief.mjs --ready # 可开工清单(自动剔除已被认领的)
node docs/column/tools/make-brief.mjs --claims # 谁正在写:45 分钟内的认领派一篇 = make-brief.mjs <编号> [slug](续写未完成篇用 --resume <编号>),把输出原样交给一个后台执行会话即可;它会自己落盘并跑四道门。
必须知道的七条运维事实:
同时在飞的写作会话上限 20,超出的派工不会报错,而是排队约 1 小时后以 Queuing failed 死掉且颗粒无收。所以只在收到完成/失败通知、确认腾出槽位后再补派,别「多派几个以防万一」。
认领机制:make-brief 每出一份任务书就写 docs/column/tools/.claims/<编号>.json,--ready 会剔除被认领的篇目;协调者用 --claim(补记,不拦派工)/ --release(回收)。同一篇派两次会互相覆盖整份文件——I08 与 C11-04 真撞过。防重复派工的唯一有效落点是协调者:派工成功后立即 --claim(--ready 当场剔除),不要指望 make-brief 的拦截分支——它写入的足迹是 note: 'fetched',与 --claim 的 note: 'backfill' 一样都只作可见性、不拦第二次取任务书(写作会话重复取书是常态,且每次调用都是新的 node 进程,用 pid 判「自撞」无效;2026-09-29 曾因这个拦截让 C02-01 误判「他人认领」而差点停工)。
收尾四道门,缺一不算写完:verify-refs.mjs --excerpt(0 问题)、check-doc.mjs(达标)、check-xref.mjs(0 个具体编号引用错误)、set-status.mjs <编号>=已完成。不要跑 prettier:docs/column 在 .gitignore 里,prettier 默认跳过(空操作),而 --ignore-path /dev/null 会把骨架的 blockquote 与「坑」的四行结构压平,破坏正文。
OUTLINE.md 的表格单元格里不能出现裸竖线 |(比如把 grep -r "a\|b" 抄进「底层」列)。board.mjs 与 set-status.mjs 都按竖线切列,一个未转义的 | 会让整行从队列里静默消失——2026-09-29 队列因此从 144 掉到 142,且没有任何报错。判形命令:
node -e "const fs=require('fs');const t=fs.readFileSync('docs/column/OUTLINE.md','utf8');
const ok=new Set([...t.matchAll(/^\|\s*((?:A|I)\d\d|C\d\d-\d\d)\s*((?:\|[^|\n]*){4})\|\s*([^|\n]*?)\s*\|\s*$/gm)].map(m=>m[1]));
const all=[...t.matchAll(/^\|\s*((?:A|I)\d\d|C\d\d-\d\d)\s*\|/gm)].map(m=>m[1]);
console.log('总行',all.length,'可解析',ok.size,'掉行',all.filter(i=>!ok.has(i)).join(' '));"掉行的修法是把命令改写成「与」或 -e a -e b 形式(C02-06 的写手就是这么自救的)。set-status 报「命中 0 处」有两种含义:状态本来已是已完成(无害),或该行掉出了正则——用上面的命令区分。
派工单不要再抄长规则:共则已落在 docs/column/tools/DISPATCH.md(骨架先行、证据纪律、本仓缺陷族清单、未证实断言的降级写法、禁止事项、四道门、回报格式)。派一篇只需让会话先读该文件、再跑 make-brief.mjs <编号>,外加本篇专属的一两条提示(例如「某结论已由 C0x-xx 论证,直接引用」「某依赖已核实为登记错误」)。
导览层与 134 篇深读是两套门。每个目录另有一篇 导览.md(根目录是 总导览.md,共 15 篇),它不写源码机制,只做跨篇路由(选型表 + 坑索引 + 改动前最小阅读集),合同在 docs/column/tools/GUIDE.md,由第 5 道门 check-guide.mjs 校验(≥1500 字、表格外叙述 ≥700、选型表 ≥6 行、坑索引按类 8/层 10/根 12 条且 ≥80% 带真实编号、禁占位、禁裸 |)。因为四段结构与七段骨架不同,check-doc.mjs、check-xref.mjs、board.mjs、make-brief.mjs、gen-index.mjs 的枚举里都加了 !/导览\.md$/ 排除——新增任何「不按七段骨架写」的元文档时,这五处枚举要同步排除,否则会被篇门判成不达标或被当成队列行解析。verify-refs.mjs 不排除导览:它的 file:line 与篇间引用照旧要证。文件名口径:134 篇深读是 NN-<英文 slug>-<中文标题>.md(中英双语,历史路径记忆与全文检索都能命中),导览固定叫 导览.md/总导览.md。
对账(写手被中断时状态会落后于盘上文件,输出「已过门槛但未回填」的编号):
node -e '
const fs=require("fs"),cp=require("child_process");
const st=new Map(); for(const m of fs.readFileSync("docs/column/OUTLINE.md","utf8").matchAll(/^\|\s*((?:A|I)\d\d|C\d\d-\d\d)\s*((?:\|[^|\n]*){4})\|\s*([^|\n]*?)\s*\|\s*$/gm)) st.set(m[1],m[3]);
const files=cp.execSync("find docs/column -name \\"*.md\\"").toString().split("\n").filter(f=>f&&!/(README|PROMPT|OUTLINE)\.md$/.test(f));
const idof=f=>{const b=f.split("/").pop();let m=b.match(/^(C\d\d-\d\d)/); if(m)return m[1]; m=f.match(/\/(01-architecture|02-infrastructure)\/0?(\d+)-/); return m?(m[1][1]==="1"?"A":"I")+m[2].padStart(2,"0"):null;};
let out=""; try{out=cp.execSync("node docs/column/tools/check-doc.mjs "+files.join(" ")).toString();}catch(e){out=e.stdout.toString();}
const good=new Set([...out.matchAll(/✓ (\S+\.md)/g)].map(m=>m[1]));
console.log([...good].map(idof).filter(i=>i&&!/已完成|已审/.test(st.get(i)||"")).join(" ")||"无");'
node docs/column/tools/gen-index.mjs # 重建 README 目录(口径=盘上真实文件数)OUTLINE.md 末尾的「已知待办」登记了跨篇复用的结论与「API 面存活、实现死亡」清单——动笔前先读它,能省掉一整轮重复取证。
C03-08《Dropdown》曾对 A07 的坑 1/坑 2 留白(「不裁决哪张 builtinPlacements 表生效」)。2026-09-29 复裁结论:A07 无误,悬念由源码关闭,C03-08 论点 5 末段已改写为完整链条——厂商层在 vc-dropdown/Dropdown.tsx:127 于 ...otherProps 之后硬设六键表(字面顺序,非运行时猜测)→ 业务层十二键表(_util/placements.ts:55/:59 的裸 top/bottom)在 Dropdown 族从未生效 → 键查不到则 align 无 points → PopupInner.tsx:137-141 的 alignDisabled 为真 → Align 的 disabled prop(vc-align/Align.tsx:39、:161-166)使 onAlign 从不触发 → getClassNameFromAlign(只在 PopupInner.tsx:79 被调)从不执行 → 弹层拿不到任何 -placement-* 类名。仲裁时踩过的一个坑要记下:曾考虑用 isPointsEq 的 points 反查(alignUtil.ts:3-8)论证「top 会被译回 topCenter 类名」,该推断是错的——反查读的是 align.points,而这条链上 points 恰恰为空,反查根本进不去。连带结论:arrow={{ pointAtCenter: true }} 在 Dropdown 族是零效果开关,dropdown/style/index.ts:146-152、:177-183 的 -placement-top/-placement-bottom 箭头居中规则是死 CSS,apps/docs/examples/dropdown/arrow-center.vue:24 演示的正是失效档位。测试面(两种方法交叉核过):只有 dropdown/dropdown-button.test.js(3 个 it、2 次 expect(,不碰 placement)与 split-button.test.tsx:116-121(只断言 'topLeft' 被转发);注意本仓测试文件同时有 .js 与 .tsx 后缀,只 find -name "*.test.tsx" 会得出「dropdown 没有任何测试」的错误结论。