AI 前端开发总偏题?用 XMind 骨架与 OpenSpec 串起需求流水线

作者:袖梨 2026-09-12

把一段模糊需求直接交给 AI,代码通常很快就能生成,但接口路径、字段命名、枚举取值和项目目录只要有一处靠猜,联调阶段就可能付出成倍的返工成本。要减少这种偏差,关键不是继续修饰提示词,而是先由开发者明确需求骨架,再让 AI 在确定的契约和边界内完成实现。

AI 写前端总跑偏?我用「xmind 骨架 + OpenSpec」把前端需求拆解做成了流水线

把「拆需求」这件事从 AI 手里拿回来,交给它一套它看得懂的骨架。


一、缘起:AI 不是不会写,是「需求没拆开」

先说个你可能也遇到过的场景。

我用 AI 写一个移动端 H5 的新页面,需求不复杂:一个列表页,点击进详情,顶上加个状态标签。听起来十分钟的活。

我把需求一段话丢给 AI,它刷刷刷生成了一堆代码。跑起来一看——

  • 接口路径是它的(/api/v1/order/list,后端实际叫 /order/queryList);
  • 枚举码值也是它的(状态 1 是什么、2 是什么,全靠它脑补);
  • 文件名、目录结构跟项目里其他页面对不上(项目用 useStore.js,它给你整了个 store.ts);
  • 最要命的是契约对不上:接口返参字段名一个字母之差,联调时后端说「我返的是 orderStatus 不是 status」,整段逻辑推倒重来。

几次下来我悟了:AI 不是不会写代码,是「需求没拆开」的时候它只能猜。你给它一个模糊的段落,它就用幻觉把空白填满——而幻觉的代价,是你在联调、验收、返工里加倍还回去。

于是我逼着自己回答一个问题:

到底是谁该对「需求拆解」负责?

答案不是 AI。拆解是人的事,因为拆解的本质是「拍板」,而拍板需要业务上下文——这个枚举码值到底是几、这个页面该放哪个目录、这次改动碰不碰老功能的关键路径。这些 AI 不知道,也不该由它替你决定。

想清楚这点之后,我做了一套流程,把人机分工钉死,让 AI 只干它擅长的「填充」,不干它不擅长的「拍板」。


二、核心洞察:一句话分工

整套方法论可以压缩成一句话:

人做「主体骨架设计」(把需求拆成 5 层需求骨架),AI 做「细节落地」(把骨架补成可实现的四件套和代码)。

整条链路的交接点是一串 skill 入口,用文字树表示:

需求分析(人)
└── xmind 分解(人)—— 拆出 5 层需求骨架
    └── [xmind-extract] 提取分支 → 结构化 md
        └── [skeleton-dev] 骨架自检(软引导)
            ├── 缺层 → 回补骨架(回到「xmind 分解」)
            └── 齐备 / 豁免 → [opsx:explore] → propose → apply → archive

demo(列表页 · 新功能),它喂进流程的骨架长这样(5 层全展开):

列表页(新页面、新功能)
├── 1 未提供产物声明
│   ├── figma:未出 → 以原型截图为基准
│   ├── 接口:只有路径,缺样例 → mock 短路兜底
│   └── 枚举:状态映射缺 → 待确认
├── 2 契约层
│   ├── 枚举:orderStatus <值:语义>
│   │   └── 1 待支付 / 2 已支付 / 3 已关闭(码值待确认)
│   └── 接口:列表查询 /v1/order/queryList
│       ├── 入参:pageNo(数字,必填) / pageSize(数字,必填) / status(枚举,可选)
│       ├── 返参:list(数组) / orderStatus(枚举) / total(数字)
│       └── 注意点:orderStatus 需映射为展示文案(待确认)
├── 3 文件层
│   ├── 页面入口:src/business/<模块>/<页面>/index.jsx
│   ├── 状态文件:src/business/<模块>/<页面>/useStore.js
│   ├── 样式文件:src/business/<模块>/<页面>/index.scss
│   └── 路由:<模块>/<页面>(path 待确认)
├── 4 数据层
│   ├── 字段:list(数组) / orderStatus(枚举) / isLoading(布尔)
│   ├── 方法:initPage() / queryList() / clearAll()
│   └── 初始化:进入页面 → initPage() → queryList()
└── 5 UI 层
    ├── 区块:Header → 列表 → 详情入口
    ├── 事件:进入初始化 / 列表点击 / 页面销毁清理
    └── 状态:加载中 / 空态 / 错误态

这条链路背后是一条更基本的分工原则:

干什么为什么
需求分析 → 拆骨架 → 拍板(缺什么补什么、什么可以豁免)需要业务上下文,需要「拍板」
AI从骨架出发,读现有代码、补细节、出工件、写代码擅长填充与检索,不擅长凭空决策

别小看这个原则。95% 的 AI 翻车,根源都是让 AI 去干了它不擅长的「脑补」——你以为省了拆解的时间,其实只是把成本推后到了返工。


三、骨架长什么样:5 层要素

「骨架」具体是什么?我把它做成一棵固定 5 层的树。无论新页面还是老页面改造,都以这 5 层为母版(老页面「不动视图」场景可削减为薄骨架,见第五节):

名称回答什么问题
1未提供产物声明哪些东西还没给我?(figma / 接口样例 / 枚举)
2契约层接口长什么样?枚举码值是什么?有哪些公用方法?
3文件层代码放哪个目录?改动落在哪个文件?
4数据层有哪些状态字段?有哪些方法?怎么初始化?
5UI 层页面有哪些区块?有哪些事件?

这 5 层的顺序不是随便排的,它是从「外部契约」到「内部实现」

1 未提供产物声明2 契约3 文件4 数据5 UI
产物基准对外接口代码落位状态视图

按照这个顺序拆、按这个顺序做,会自然带来两个好处。

好处一:契约前置,幻觉无处可藏

第 2 层「契约层」是整个骨架的重心。接口路径、字段名、枚举码值,这些是「跨系统的外部契约」——它们对下游可见,一旦错了就是连锁返工。

所以我在流程里定了一条硬规矩:

接口路径 / 字段名 / 枚举码值属于外部契约,不得以「避免实现细节」为由省略

这一条把 AI 最容易幻觉的地方,提前钉死在了人手里。接口没样例?那就先写字段、标注「待确认」;枚举不全?那就先标问号,进 explore 补。宁可显式留白,也不让 AI 自己编。

好处二:产物声明,把「不知道」摆上台面

第 1 层「未提供产物声明」听着朴素,其实是整套流程的兜底开关

设计稿没出?写「figma 未出 → 以原型截图为基准」。 接口没样例?写「只有路径,缺样例 → mock 短路兜底」。 枚举不全?写「缺 → 待确认,进 explore 补」。

主动声明「我缺什么」,AI 就知道该走 mock、该标待确认、该拿原型图当基准,而不是自己去猜去编。

这里有个反直觉的细节:「缺」要写出来,留空反而会被判缺层。因为「没给」和「没写在骨架里」在 AI 眼里是两回事——留空 = 未声明 = 必判缺。主动写明「缺」,才换得来「用 mock 兜底」的豁免。


四、完整流水线:三步走

有了骨架,整条流水线就三步:

第 1 步 · 入口:xmind 写需求 → 提取成 md

为什么用 xmind 当输入层,而不是直接写 prompt?

因为 xmind 有三个 prompt 给不了的好处:

  1. 可视化——需求分几个模块、每层写了什么,一眼看清,比一坨 Markdown 好审;
  2. 可协作——产品、需求、前端都能在同一个 xmind 上补分支,不用抢一份文档;
  3. 结构天然——树形结构天然对应「模块 → 5 层」,写成节点标题就是结构化的。

在要提取的模块分支下,直接把 5 层层名当子节点标题写:

我的列表页面(新页面、新功能)
├── 1 未提供产物声明
│   ├── figma:未出 → 以原型截图为基准
│   ├── 接口:只有路径,缺样例 → mock 短路兜底
│   └── 枚举:状态映射缺 → 待确认
├── 2 契约层
│   ├── 枚举:orderStatus <值:语义>
│   └── 接口:列表查询 /v1/order/queryList
│       ├── 入参:<字段/类型/可选>
│       ├── 返参:<字段/类型>
│       └── 注意点:<枚举映射 / 依赖 / 待确认>
├── 3 文件层
│   └── src/business/<模块>/<页面>/ + 路由
├── 4 数据层
│   ├── 变量:<变量>:<类型>,<说明>
│   └── 方法:initPage() / <方法>()
└── 5 UI 层
    ├── 主页面 Header → 列表 → 详情
    └── 事件:进入初始化 → 页面销毁

然后触发 xmind-extract,把某个分支转成结构化 md(落到 tmp/ 里,不污染提交)。它在参数收集齐之后调一个零 npm 依赖的解析脚本(Node ≥ 18 + 系统 unzip 就够,不用装任何东西),支持按标题或序号路径定位、多个同名候选会列出来让你选。

我特意把这个解析器写成零依赖的纯脚本,而不是 MCP 或一堆 npm 包——流程工具越轻,越容易在团队里活下去

第 2 步 · 关口:骨架自检(软引导,不硬拦)

提取出来的 md 交给下一个 skill:skeleton-dev,做骨架自检——逐层核对 5 层要素「有没有」,输出一张表(下例为新页面):

状态核对情况建议
1 未提供产物声明figma 未出 → 原型截图已声明
2 契约层新增展示字段取值映射未给枚举口径回来源补枚举,或豁免带缺进 explore 敲定
3 文件层页面存放地址 + 路由已声明
4 数据层状态字段 + 方法已列(含初始化)
5 UI 层原型截图的区块与事件已列

这里有一个我认为是整套设计里最关键的决定

自检是「软引导」,不是「硬门禁」。

缺层了怎么办?默认引导二选一——回去补,或者明确豁免(「我知道缺,进 explore 敲定」)。拍板的是人,不是流程。

为什么不做成硬门禁?因为我试过。硬拦的结果是:为了过门禁,大家开始凑要素——明明不需要的东西为了填格子硬写上去,流程变成官僚主义,最后没人用。

而软引导的哲学是:模板是给 explore 的探索骨架,不是不放行就不许进的关卡。AI 只负责告诉你「哪儿缺、建议怎么补」,补不补、豁不豁免,你自己定。人主导设计,AI 辅助核对。

自检之外,还有一步「兜底补流程」

skeleton-dev 的实际执行序列是固定的六步:输入核对(定 add / modify)→ 读取需求 md + 骨架规则 → 逐层自检出表 → 人拍板(补全 or 豁免)→ 实施流程兜底补充 → 组装进 explore

其中「实施流程兜底补充」这一步容易被忽略:需求 md 如果没写具体的实施流程,就按 add / modify 补一份 6 阶段序列进去,补齐再进 explore。补的就是这张表:

阶段做什么依赖
1 产物声明声明未提供的产物(figma / 接口样例 / 枚举)+ 兜底策略
2 契约层枚举 + 接口 + 公用方法 + Service 短路 mock1
3 文件层页面结构 + 存放地址 + 路由注册无(可与 4 并行)
4 数据层状态字段 + 方法名 + 接口调用1 + 2
5 UI 层DOM + 样式 + 接入 Service + 事件3 + 4
6 联调 / 回归(可选)mock 拆除 + 真实返参校验 + figma 精调 / 老路径回归5

我把这张表特意和骨架对齐编号阶段 1-5 与骨架层 1-5 一一对应(阶段2 契约层 = 层2 契约层,…,阶段5 UI 层 = 层5 UI 层),阶段 6 是可选收尾、不落骨架层。这样「骨架自检 → 实施流程 → tasks 按阶段排」是一条直线,中间不用换脑子。

阶段 6 该不该省,用三个客观条件判定——无 mock / figma 已出无需精调 / 无接口依赖 → 省略,且 tasks 不生成空阶段。省得有人为了「流程完整」硬凑一个假阶段。

还有一个我踩过、值得抄走的防呆:兜底补充时判断「需求 md 有没有实施流程」,要检测「阶段1…阶段N」这种阶段级序列,不能用骨架层名(契约层 / 文件层…)去判断。因为按 5 层规范写出来的需求 md 必然含层名——拿层名当特征,会恒判「已有流程」,兜底直接失效。

第 3 步 · 落地:OpenSpec explore → propose → apply → archive

骨架自检通过(或豁免放行)后,进入 OpenSpec 的流程。OpenSpec 是一套「规范驱动」的变更管理工具,核心是让 AI 读着规范写代码,而不是读着一段话写代码。

一个 change 的完整流程(阶段 0 为前置配置,阶段 1~7 为主流程):

阶段 0  config.yaml(项目配置:技术栈 + 约束,注入每个工件)
   ↓
阶段 1  创建变更        openspec new change
   ↓
阶段 2  探索澄清        /opsx:explore       ← xmind 骨架喂进来
   ↓
阶段 3  规划提案        /opsx:propose       → proposal → {spec, design} → tasks
   ↓
阶段 4  审查更新        /opsx:update        (需求的规划工件变了就回来改)
   ↓
阶段 5  实现任务        /opsx:apply         → 照 tasks 施工,逐条打勾
   ↓
阶段 6  规格同步        /opsx:sync          → delta 并入主 specs
   ↓
阶段 7  归档变更        /opsx:archive       → 闭环

阶段 1~7 是命令的执行顺序;真正约束 AI「先写什么、后写什么」的,是 schema 里的工件依赖图openspec/schemas/spec-driven/schema.yaml):

                 proposal(why + 范围 + 能力清单)
                        │
         ┌──────────────┴──────────────┐
         ▼                             ▼
 specs/**/spec.md               design.md
 (what:行为契约 / delta)       (how:技术方案)
         └──────────────┬──────────────┘
                        ▼
              tasks.md(可勾选清单)
                        │
                        ▼
                   apply(照 tasks 施工)

注意 specs 和 design 都只依赖 proposal——两者可并行;tasks 才依赖两者。而 proposal 里的 Capabilities 一节是整张图的枢纽:它列出本次新增 / 修改哪些能力,每个能力对应一份 specs/<能力>/spec.md,proposal 与 specs 的契约就在这一节咬合。

其中「四件套」是:

工件回答对应骨架的哪层
proposal.md为什么(why)+ 范围1 产物声明 / 验收边界
specs/**/spec.md做什么(what,行为契约)2 契约层 + 5 UI 事件
design.md怎么做(how,技术方案)2 契约 / 3 文件 / 4 数据 / 5 UI
tasks.md按什么顺序做(可勾选)实施流程阶段

关键在 spec 用 delta 格式——## ADDED / ## MODIFIED / ## REMOVED / ## RENAMED,描述「相对主规格改了哪些」。这样每个 change 归档时,契约会自动并入公共需求库 openspec/specs/,下一个 change 一读就知道现状。

回到开头那个「接口路径被编」的问题:现在接口路径写在 spec 里、归档进主 specs,AI 想编也没有空间编——它读的是规格,不是幻觉。

先看 proposal.md:7 节定范围

proposal.md 是整条链的起点,schema 给它钉了 7 节:

Why(为什么做)/ What Changes(改什么)/ Out of Scope(明确不做)/ Capabilities(新增 / 修改哪些能力)/ Impact(影响面)/ Risks(风险 + 缓解)/ Acceptance Criteria(可独立验证的验收项)

前面说的「Capabilities 是枢纽」就落在这里——它列出的每个能力,都要长出对应的 specs/<能力>/spec.md能力清单写不清,spec 就没法拆。schema 为此加了一道校验:一个 change 要么至少声明一个能力,要么在 .openspec.yaml 里显式 skip_specs: true(纯重构 / 工具 / 文档才用),否则 openspec validate 直接拒收;但它同时叮嘱 AI「不要为了过校验硬造一个需求」——一头堵死空转、一头防造需求。

另外三节也各有分工:Out of Scope 划边界、防范围蔓延,Risks 要求逐条「风险 / 影响 / 缓解」(真没有就写「无」+ 原因),Acceptance Criteria 是一串 checkbox,直接定义「什么叫做完」。

再看 design.md:13 节固定图纸

design.md 在这套 schema 里不是自由发挥的文档,而是被钉死的 13 节骨架(schema 里明写 Do not add sections outside this skeleton):

架构概述 / 实施节奏 / 页面文件结构 / 数据流 / Store 设计 / 组件设计 / Service 设计 / 路由设计 / 枚举与状态映射 / 数据 Mock 设计 / 关键决策 / 专项约定 / 待确认清单

为什么值得把模板改这么死?因为其中几节正是骨架 2 / 3 / 4 / 5 层的「可施工化」——骨架只说「有接口、有字段」,design 把每个字段的口径、类型、来源都落到表里:

骨架层design 对应节
2 契约层7 Service 设计(接口契约逐字段表)+ 9 枚举与状态映射
3 文件层3 页面文件结构(完整目录树,辅助文件标「联调后删除」)
4 数据层5 Store 设计(字段 / 方法 / 接口调用三I张表)
5 UI 层6 组件设计(逐组件 props 契约 + 列表逐字段表)

第 13 节「待确认清单」是不得省略的硬约束(无未决项就写「无」)——它把「还没和用户敲定的东西」集中摆上台面,生成 tasks 前先过一遍,避免把没确认的假设悄悄焊进任务清单

几条「写不对就静默失效」的硬规则
  • Scenario 必须 4 个井号#### Scenario: xxx)——用 3 个或列表项,openspec validate静默放过,但归档时场景丢失;
  • 每个 Requirement 至少一个 Scenario;需求描述用 SHALL / MUST,别用 should / may;
  • 新增能力要写 ## Purpose(50 字以上),否则归档后主规格留一行 TBD ... Update Purpose after archive 等你手补;
  • 修改(MODIFIED)需求必须整块复制原需求(### Requirement: 到所有 Scenario)再改——只贴片段,归档时会丢细节;
  • tasks 一律 - [ ] X.Y 描述——apply 阶段靠 checkbox 格式追踪进度,格式不对的任务不会被追踪。

五、几个我想清楚的设计决策

写方法论容易写成「我做了 A、B、C」。但真正有价值的是「为什么是 A 不是 B」。这一节讲讲我踩过的分叉路口。

1. 为什么自检要「软引导」而不是「硬拦」

上面说过了,这里补一句:我一度也想要硬门禁,觉得「不让缺层的需求进流程」才严谨。但实践下来,门禁只会让人学会绕过门禁。软引导 + 人拍板,才让流程活下来——因为它把「判断」留给了最该判断的人。

2. 新页面 vs 老页面,是两套骨架

这是我做完一个真实改造需求后才补上的。新页面是创建式(从零垒),老页面改造是定位式 / 增量式 / 回归式——每层职责全变了:

新页面(add)老页面改造(modify)
1 产物声明声明缺的产物声明产物 + 给「锚点」(改动点)
2 契约层定义新契约复用为主 + 少量新增 + 兼容性检查
3 文件层建文件(入口 + 按需增减)定位式:改哪个文件哪个函数 + 「不动」清单
4 数据层新建状态文件按页面既有状态风格改,不发明不夹带
5 UI 层搭组件分叉:动视图 / 不动视图

改造最核心的两条铁律:

  1. 最小改动——只做增量,不顺手重构老代码;
  2. 按需回归——涉及老功能关键路径 / 有 mock / 有 figma 待处理,就必做回归验证。

还有一条我认为比「写改动方案」更重要的提醒:

改老页面,先把「不动清单」写清楚,比写「改动方案」更重要。

因为「不动」是「最小改动」的落地产物,也是回归的对象——最容易被写漏,也最容易出事。AI 有了「不动清单」,就知道哪些区块是碰都不能碰的。

3. 场景判定只看一件事:动不动视图

老页面改造分两个场景:

  • 场景 A · 动视图(加标签 / 改布局 / 换展示)→ 走全 5 层,重点做视图 diff(新增 / 修改 / 不动区块);
  • 场景 B · 不动视图(改口径 / 跳转带参 / 换数据源)→ 走薄骨架(1 + 3 + 5),2/4 层有改动才写。

判定标准我只留一条:这次动不动视图。简单、可判定,不给人留纠结空间。

场景 A 的视图工作也不是「照设计稿整页重建」,而是「做一次原型截图 ↔ 现状代码的区块 diff,只动有差异的部分」。一次 diff 同时产出改动清单(喂给文件层定位)和回归清单(喂给回归验证)——一份工作,两处收益。

4. 为什么骨架要用「框架无关抽象词」

骨架规则里,我从头到尾用的是抽象词:「页面入口文件」「状态文件」「接口定义」,而不是具体的 React / MobX 名词。

为什么?因为这些规则要能复用到任何技术栈。落地映射单独抽了一个「profile」文件:

抽象要素本项目落地(React)
页面入口文件index.jsx
页面状态文件useStore.js
接口定义文件service/interface
业务目录src/business/<模块>/<页面>/

换一个前端框架,只换这份 profile 的映射表,骨架规则一个字都不用改。这是我做过的最划算的一笔抽象。

5. mock 短路:让前后端解耦

实施流程里我埋了三条机制:

  • mock 短路——接口没就绪?mock 开关一开直接返回假数据,前端先把结构、数据流、UI 跑通;
  • 回退——联调卡在接口未就绪?回退到契约层用 mock 占位,流程不卡死;
  • 并行——文件层和数据层没有强依赖,可并行。

说到底就一句话:前端不依赖后端进度,也能先动工


六、收益

说点实在的。这套流程跑下来,我感受到的变化:

  1. 幻觉显著减少。契约(接口 / 字段 / 枚举)在骨架里就钉死了,AI 没有脑补空间;编不出来,只能照着写。
  2. 返工大幅下降。以前返工都发生在联调(字段名对不上、枚举码值猜错);现在这些问题在「拆骨架」阶段就被逼出来了。
  3. 协作有共同语言。产品、需求、前端都在 xmind 上按 5 层写,契约是同一份,不再各说各话。
  4. AI 的输出变得可预期。因为输入是结构化的骨架,不是一段自由的描述——输入的确定性,决定了输出的确定性

如果用一句话总结我最大的感受:

AI 的上限,取决于你喂给它的输入的确定性。


七、局限与还没解决的

不做标题党,说说这套方法现在的短板:

  • 骨架拆得好不好,依赖人的水平。这套流程不能替你想清楚业务,它只是逼你把「想不清楚」显式暴露出来。想不清楚的地方,它标 △ / ✗,但你得自己去补。
  • xmind 输入层有门槛。团队得先接受「按 5 层写 xmind」这个规范,否则每次提取都是「5 层全缺 → 软引导劝补」,对话往返翻倍。
  • 轻量脚本靠约定,不靠机制。解析器零依赖很轻,但没有强校验——你写错分支结构,它不会拦你,只会忠实提取。
  • OpenSpec 本身还在演进。它的 CLI 版本、工件格式都在变,配置格式写错会静默失效。用它得接受「工具在动」这件事。

八、小结

把这篇的核心再收一遍:

  1. 拆需求是人的事,不是 AI 的事——因为拆解的本质是「拍板」,拍板需要业务上下文。
  2. 骨架固定 5 层:产物声明 → 契约 → 文件 → 数据 → UI,从外部契约到内部实现。
  3. 契约前置:接口 / 字段 / 枚举属于外部契约,宁可显式留白,也不让 AI 编。
  4. 自检是软引导,不是硬门禁:缺层默认引导「补全 or 豁免」,人拍板
  5. 老页面改造是新范式:定为+ 增量 + 回归,「不动清单」比「改动方案」更重要。
  6. 抽象词 + profile:骨架规则跨技术栈复用,换框架只换一张映射表。

最后一句,是我做完整套流程后最想说的:

别指望一个更聪明的模型来拯救糟糕的输入。 把需求拆成 AI 看得懂的骨架,比换十个更强的模型都管用。


如果这套思路对你有用,欢迎在评论区聊聊你是怎么拆需求的。工具本身不复杂,难的是「把人该干的事,老老实实交回给人」。

相关文章

精彩推荐