Compare commits

...
12 Commits
Author SHA1 Message Date
leroy b47dcf2ff2 le-spec v1.0.0:Le SPEC 智能需求分析与 SPEC 生成总控 skill——真实集成 grilling/grill-with-docs(domain-modeling) + think + openspec(CLI 1.16.1 + 7 个 openspec-* skills,均经 skills.sh 装入 ~/.agents/skills 并软链四 runtime);七阶段工作流(识别/十类分拣/Grill 访谈/Think 挑战/反向验收/OpenSpec 生成/四视角审查)复杂度三档自适应;状态 state.md + 调用账本 ledger.jsonl 双持久化支撑中断恢复与真实调用核验;依赖检查 deps_check.py 分级报缺+精确安装命令,ledger.py append/check/tail;四类回合终止停点(批准前不编码为硬约束);测试 A-I 九项全过:两场景 fresh-context 子代理多轮实测(4 轮访谈+变更+中断恢复+strict validate 双项目通过),账本 28 条真实调用(grilling×9/think×2/openspec CLI×14),独立子代理审查实测抓出自审遗漏的 2 blocking;已知限制 7 项如实入库(reports/test-report.md) 2026-11-08 12:00:02 +08:00
leroy 4ec81e253f le-app-design v1.2.1:Codex 运行时新增 image_gen 插画通道——内建 image_gen 可产出吉祥物/场景插图/封面图(prompt 必须携带 design-plan 色板 hex,产物随停点 1.5 首屏过目复核);图标系统与精确几何仍走 SVG(craft-floor:icons are drawn);image_gen 属宿主能力不进调用账本,非 Codex runtime 不引入依赖;适配矩阵 Codex 行同步 2026-11-07 20:50:40 +08:00
leroy f2723b8c5d 移除误入库的 trigger-eval.json(上轮 git add -A 收进来的生成物;目录已 gitignore) 2026-11-07 20:23:28 +08:00
leroy f1ea057eb4 gitignore 忽略 le-app-design/reports/:trigger_eval.py 的生成物目录(验证形态已定为每运行再生,静态输出不入库) 2026-11-07 20:23:10 +08:00
leroy 44a2215940 le-app-design 移除 reports/ 目录:静态证据示例对本 skill 是仪式性负担——验证由每次运行的依赖门/调用账本/验收门现场再生,历史由 git 提交承担(243bbf1/0ad31c7/bc66afd);README 注明对 rigorous 惯例的这处有意偏离(与 Leroy 确认);SpeakPark 双轮结论并入根 README 验证段 2026-11-07 20:21:40 +08:00
leroy a473075f4d reports/e2e-habit 精简:移除 v1.0 过渡形态的截图(丑且代表旧顺序),保留 calls.jsonl/审查报告/design-plan/BRIEF 文字与账本证据——「真实调用」的背书是账本不是截图;reports/README 更新政策与历史注记(SpeakPark 两轮账本随项目目录清理失存,结论以测试报告附录为准,不重建不伪造) 2026-11-07 20:19:15 +08:00
leroy fdc54547f5 gitignore 补 .zcode/(本地 runtime 会话目录,与 .agents/ 同款处理) 2026-11-07 19:29:53 +08:00
leroy 94fa94baef le-app-design reports/ 归档政策:精选证据(测试报告+一份完整 E2E 证据包)进 git 支撑已验证主张;真实任务运行产物留项目目录不进仓;绝对路径 WARN 有意接受 2026-11-07 17:15:33 +08:00
leroy bc66afda33 le-app-design v1.2.0:确认环节与一致性修复(冷读评审驱动)。①盲盒阀门:S4 拆 a/b,新增停点 1.5 首屏确认(截图必发,豁免停顿不豁免发图),停点 2 附修复前后截图;②修复三处自相矛盾:轻量路由 vs 验收门硬冲突(evidence_report 新增 --require 支持子集门与履职前缀,四象限回归通过)、S3 降级 vs S7 必验 MASTER(改条件 artifact)、截图档位两口径(统一 390 主档+375 兼容,移动 Web 免桌面);③补占位符定义:项目代号进 BRIEF、slug 来源、S5 双 sub-agent 派发模板要点、note 命名规范表、回弹降级三条操作化判据、轻量路由 S2快速版/S4′/S5′ 定义、保持现状=跳 S6 直 S7 2026-11-07 17:15:33 +08:00
leroy 9ec5bf1c52 le-app-design pipeline S4 补渲染事实断言规则:显隐/布局验证用 computed display 与 boundingRect,不信 hidden 属性(v1.1 重跑实测:#id 的 display 压过 [hidden],两页堆叠渲染而属性检查全绿);编排器无法亲验截图时交 S5 Assessment A 首个看图 2026-11-07 17:04:21 +08:00
leroy 0ad31c7a52 le-app-design v1.1.0:阶段顺序修正——frontend-design 先定方向,ui-ux-pro-max 后编系统(方向由题材与 POV 决定,不由品类目录决定)。依据四条:①pro-max 自身 Step 1 契约即期望风格关键词作为输入;②impeccable craft-floor 原文 direction settled 后才施工;③两次实跑实证系统先行产出品类默认脸(cream 命中 detect tell / Tailwind 默认靛蓝被评审点名),frontend-design 两遍法被迫做防守性裁决;④专业流程方向先于系统。新增两补丁:硬约束否决权(改值不改方向)与目录回弹降级(51 风格目录匹配失败时用 design-plan token 表 + pro-max 逐项校验);S2 可选调研模式(--domain 检索当情绪板,不做决定);停点2 补 impeccable critique 的 Questions skipped 字面行硬规则(上次审计发现的契约遗漏) 2026-11-07 16:42:18 +08:00
leroy 243bbf1db1 le-app-design v1.0.0:统一 App 设计工坊——真实集成并调用三上游的编排器。ui-ux-pro-max(MIT,search.py 设计系统/规范检索)+ frontend-design(Apache-2.0,Skill 工具装载的视觉 design lead)+ impeccable(Apache-2.0,CLI 检测器+playbook 审查);三机制保证真集成可验证:check_deps 硬依赖门(缺即终止)、log_call 证据账本(调用落 calls.jsonl 含装载 sha256)、evidence_report 验收门(三方各 ≥1 成功调用才 PASS);S0–S7 流水线含两个回合终止停点与轻量任务路由;E2E 已真跑(习惯打卡 App 双屏原型):7 条真实调用留账、双独立 sub-agent 评审 26/36 + detect 2 findings、修复 3×P1 后字阶清零、交互复测 4/4、验收门 PASS;上游三件补装 ~/.codex/skills,仓库 README 同步 2026-11-07 16:18:01 +08:00
22 changed files with 1753 additions and 6 deletions
+8
View File
@@ -3,6 +3,7 @@
# 本地 runtime 发现入口(软链到 skills/le-meta-skill,属于个人环境配置) # 本地 runtime 发现入口(软链到 skills/le-meta-skill,属于个人环境配置)
.agents/ .agents/
.zcode/
# macOS # macOS
.DS_Store .DS_Store
@@ -20,3 +21,10 @@ __pycache__/
# 环境与密钥(skill 零密钥底线) # 环境与密钥(skill 零密钥底线)
.env .env
.env.* .env.*
# le-app-design:trigger_eval.py 每次运行会重建 reports/trigger-eval.json(生成物,按需重跑即得,不入库)
skills/le-app-design/reports/
# le-spec:同上,trigger-eval.json 为生成物不入库;test-report.md 为交付证据入库
skills/le-spec/reports/trigger-eval.json
+26 -6
View File
@@ -2,7 +2,7 @@
> *「把重复出现的方法,变成可触发、可验证、可维护的 Skill。」* > *「把重复出现的方法,变成可触发、可验证、可维护的 Skill。」*
MetaSkill 是 Leroy 的个人 Skill 仓库:**le-meta-skill**(Skill 工坊)与其实战技能 **le-wechat-article**(公众号写作工坊)、**le-gpt-image**(无 Key GPT Image 生图工坊)。ZCode / Claude Code / Codex 通用。 MetaSkill 是 Leroy 的个人 Skill 仓库:**le-meta-skill**(Skill 工坊)与其实战技能 **le-wechat-article**(公众号写作工坊)、**le-gpt-image**(无 Key GPT Image 生图工坊)、**le-app-design**(统一 App 设计工坊)。ZCode / Claude Code / Codex 通用。
## 仓库结构 ## 仓库结构
@@ -19,10 +19,15 @@ skills/
│ ├── references/ # 6 篇:五类型路由·排版规则·审读手册(含合规)·视觉生产·作者画像·常用事实卡 │ ├── references/ # 6 篇:五类型路由·排版规则·审读手册(含合规)·视觉生产·作者画像·常用事实卡
│ ├── scripts/ # check_article(硬规则自检)/ md2wechat(3 骨架×4 色板转微信 HTML)/ publish_draft(草稿箱同步,零依赖) │ ├── scripts/ # check_article(硬规则自检)/ md2wechat(3 骨架×4 色板转微信 HTML)/ publish_draft(草稿箱同步,零依赖)
│ └── evals/ reports/ │ └── evals/ reports/
└── le-gpt-image/ # 无 Key GPT Image 生图工坊 ├── le-gpt-image/ # 无 Key GPT Image 生图工坊
├── SKILL.md # 工作流:接单→停点确认→生成交付;能力边界 / 尺寸质量速查 / 故障排查 │ ├── SKILL.md # 工作流:接单→停点确认→生成交付;能力边界 / 尺寸质量速查 / 故障排查
├── references/ # prompt-craft:结构公式·文字渲染·摄影/插画/信息图·中文东方风格·改图指令·反模式 │ ├── references/ # prompt-craft:结构公式·文字渲染·摄影/插画/信息图·中文东方风格·改图指令·反模式
└── scripts/ # gpt_image.py(零依赖:Codex 凭证直连内部图像通道,token 自动刷新) │ └── scripts/ # gpt_image.py(零依赖:Codex 凭证直连内部图像通道,token 自动刷新)
└── le-app-design/ # 统一 App 设计工坊(编排器)
├── SKILL.md # S0–S7 流水线:依赖门→产品理解→视觉探索→设计规范→构建→审查→迭代→验收;六铁律 + 岗位裁决表
├── references/ # pipeline(七阶段细则)· runtimes(四 runtime 适配矩阵)
├── scripts/ # check_deps(硬依赖门)/ log_call(调用证据账本)/ evidence_report(验收门),纯标准库
└── evals/ # 触发用例(可回归的词面冒烟)
``` ```
## le-meta-skill —— Skill 工坊 ## le-meta-skill —— Skill 工坊
@@ -85,6 +90,21 @@ skills/
安全边界:凭证只读不打印不外发;内部接口无稳定性契约(native 失效可切 responses 备用通道);内容政策拒绝即停,不换措辞绕过。 安全边界:凭证只读不打印不外发;内部接口无稳定性契约(native 失效可切 responses 备用通道);内容政策拒绝即停,不换措辞绕过。
## le-app-design —— 统一 App 设计工坊
一个入口(「帮我设计这个 App」)跑完产品理解 → 视觉探索 → 设计规范 → Web 高保真原型 → 设计审查 → 优化迭代 → 交付。它不是借鉴了三个开源 Skill 的新 Skill,而是**真正集成并调用**三个上游的编排器(v1.1 起 frontend-design 先定方向、ui-ux-pro-max 后编系统——方向由题材决定,不由品类目录决定):
- **ui-ux-pro-max**(MIT):`search.py` CLI 真跑 `--design-system --persist` 生成 MASTER.md,UX/可访问性/触控规范走数据检索——设计规范的硬约束来源
- **frontend-design**(Apache-2.0, Anthropic):经宿主 Skill 工具装载(或 Read 原文全文,一等路径)——视觉方向与品牌差异化的 design lead
- **impeccable**(Apache-2.0, pbakaus):`impeccable` CLI(context / detect 62 规则 / critique-storage)+ playbook 装载照做——审查、精修与质量底线
- **三个机制保证「真集成」可验证**:`check_deps.py` 硬依赖门(缺任何一个即终止并报安装指引)、`log_call.py` 证据账本(每次调用落 calls.jsonl,含装载文件的 sha256)、`evidence_report.py` 验收门(按 `--require` 口径判参与的上游各有 ≥1 条履职成功调用才许宣称完成)。验证不靠静态示例——**每次真实运行现场再生证据**;创建当日已三跑全过验收门(E2E + 同题双轮顺序对比 21/36→24/36、品类默认特征 2→0),过程记录见 git 提交 243bbf1 / 0ad31c7 / bc66afd。
触发方式:「帮我设计一个 App」「做一个高保真原型」「把首页和详情页这些界面设计出来」。点名单个上游(「用 impeccable 审查」)不触发,直连对应 Skill。
依赖安装:三个上游需装到 `~/.agents/skills/`(Codex 侧另链 `~/.codex/skills/`),详见 [skills/le-app-design/README.md](skills/le-app-design/README.md)。不 vendoring、不修改上游(归属见其 NOTICE)。
安全边界:上游目录只读;停点 1(设计方向)与停点 2(修复范围)为回合终止点,「一口气跑完」仅豁免停点、不豁免铁律与验收门。
## 安装 ## 安装
三个 runtime 读的都是同一套 SKILL.md 开放格式,脚本只用 python3 标准库。把要装的 skill 软链到对应位置即可: 三个 runtime 读的都是同一套 SKILL.md 开放格式,脚本只用 python3 标准库。把要装的 skill 软链到对应位置即可:
@@ -99,7 +119,7 @@ mkdir -p ~/.claude/skills && ln -s <克隆路径>/skills/le-wechat-article ~/.cl
mkdir -p ~/.codex/skills && ln -s <克隆路径>/skills/le-wechat-article ~/.codex/skills/le-wechat-article mkdir -p ~/.codex/skills && ln -s <克隆路径>/skills/le-wechat-article ~/.codex/skills/le-wechat-article
``` ```
装 le-meta-skill、le-gpt-image 同理,把路径换成对应 skill 目录。 装 le-meta-skill、le-gpt-image 同理,把路径换成对应 skill 目录。装 le-app-design 还需先装三个上游依赖(ui-ux-pro-max / frontend-design / impeccable),并用其 `scripts/check_deps.py` 验证三方全绿。
## License ## License
+27
View File
@@ -0,0 +1,27 @@
# NOTICE — 第三方依赖归属
le-app-design 是一个编排器 Skill:运行时按各自原生机制调用以下三个上游
Skill(CLI 执行 / 原文装载),不分发、不内嵌、不修改它们的任何内容。
本仓库遵循各上游许可证条款,特此归属:
## ui-ux-pro-max
- 上游: https://github.com/nextlevelbuilder/ui-ux-pro-max-skill
- 许可证: MIT © nextlevelbuilder
- 调用方式: 执行其 scripts/search.py(设计系统生成与域检索),读取其 data/*.csv 经由该 CLI
- 本仓库不包含其源码副本
## frontend-design
- 上游: https://github.com/anthropics/skills (skills/frontend-design)
- 许可证: Apache-2.0 © Anthropic PBC(其 SKILL.md 附 LICENSE.txt 全文)
- 调用方式: 宿主 Skill 工具装载,或 Read 其 SKILL.md 原文全文进执行上下文
- 本仓库不包含其源码副本
## impeccable
- 上游: https://github.com/pbakaus/impeccable
- 许可证: Apache-2.0 © Patrick Bakaus
- 调用方式: 执行其 scripts/impeccable CLI(context / detect / critique-storage…),
并按其 SKILL.md 路由装载 reference/*.md playbook 照做
- 本仓库不包含其源码副本
---
le-app-design 自身以仓库根 LICENSE(MIT, © 2026 Leroy)发布。
+60
View File
@@ -0,0 +1,60 @@
# le-app-design
> 统一 App 设计工坊:一个入口完成产品理解 → 视觉探索 → 设计规范 → Web 高保真原型 → 设计审查 → 优化迭代 → 交付。底层**真实集成并调用**三个上游 Skill,本 Skill 只做编排、协调与质量控制。
**状态**:rigorous 档(一处有意偏离:不设 reports/ 静态证据目录——本 skill 的验证形态是**每次运行现场再生**(依赖门 + 调用账本 + 验收门),静态示例会腐烂且冗余,历史记录由 git 提交承担。2026-11-07 与 Leroy 确认)。ZCode / Claude Code / Codex / OpenCode 通用。
## 它解决什么问题
你想要一个 App 从想法变成可交互的高保真原型,只需要说「帮我设计这个 App」。le-app-design 负责检测依赖、给三个上游分派岗位、串起全流程、留下可检查的调用证据:frontend-design 先定有品牌差异性的视觉方向,ui-ux-pro-max 把方向编撰成设计系统并对可访问性/触控等硬约束校验,impeccable 做设计审查与反模式检测并盯着修复。你不需要知道三个上游各自怎么调。
## 三个上游依赖(必需,非可选)
| 上游 | 岗位 | 许可证 | 仓库 |
|---|---|---|---|
| ui-ux-pro-max | 设计规范生成 / UX 与可访问性数据检索 | MIT | github.com/nextlevelbuilder/ui-ux-pro-max-skill |
| frontend-design | 视觉创意 / 品牌差异化(Anthropic) | Apache-2.0 | github.com/anthropics/skills |
| impeccable | 设计审查 / 62 规则反模式检测 / 精修 | Apache-2.0 | github.com/pbakaus/impeccable |
本 Skill 不 vendoring、不修改、不复制三个上游的任何内容,只按其原生机制调用(CLI 执行 / 原文装载)。归属详见 [NOTICE](NOTICE)。
## 依赖安装
三份都装到 `~/.agents/skills/`(首选,impeccable 自引用该路径):
```bash
# 示例:ui-ux-pro-max
git clone --depth 1 https://github.com/nextlevelbuilder/ui-ux-pro-max-skill /tmp/uiux
ln -s /tmp/uiux/.claude/skills/ui-ux-pro-max ~/.agents/skills/ui-ux-pro-max
# frontend-design: git clone anthropics/skills → skills/frontend-design
# impeccable: git clone pbakaus/impeccable → 整目录链接
```
Codex 侧还需链接到 `~/.codex/skills/`。装完验证:
```bash
python3 <本目录>/scripts/check_deps.py
```
三方全绿才可使用;缺任何一个,触发本 Skill 时会明确报缺并终止,不会假装集成成功。
## 触发方式
- 帮我设计一个习惯打卡 App,要高保真原型
- 把这个产品想法做成可交互的 UI 原型
- 给我的工具类产品做一轮完整的设计 + 审查 + 改进
- design an app prototype for …
不触发:点名单个上游(「用 impeccable 审查」)、公众号文章/海报排版、只要一张配图、纯功能开发。
## 核心机制(为什么这是真集成)
- **硬依赖门** `scripts/check_deps.py`:按依赖各自形态做活性检查——pro-max 真跑一次检索、impeccable 跑二进制 `--version`、frontend-design 校验原文完整可读(含 sha256)。缺则终止。
- **证据账本** `scripts/log_call.py`:每次对上游的调用(CLI 执行与原文装载)都落 `calls.jsonl`——时间、命令、退出码、stdout 摘要、装载文件的 sha256。没进账本的调用视为没发生。
- **验收门** `scripts/evidence_report.py`:三方各有 ≥1 条成功调用才判 PASS;否则 FAIL 并列出缺口,禁止把未完成的流程标记为成功。
## 安全边界
- 三个上游目录只读;本 Skill 的任何流程都不修改它们。
- 停点 1(设计方向确认)与停点 2(修复范围确认)会停下来等你;说「一口气跑完」可豁免停点,但豁免不了铁律与验收门。
- 交付产物全部落盘(运行目录 `le-app-design-runs/<id>/`),包括审查报告与集成证据,可复查。
+140
View File
@@ -0,0 +1,140 @@
---
name: le-app-design
description: |
Le App Design —— 统一 App 设计工坊:只叫这一个名字,自动完成「产品理解 → 视觉探索 → 设计规范 → Web 高保真原型 → 设计审查 → 优化迭代 → 交付」。底层真实集成并调用三个上游 Skill——frontend-design(视觉创意与品牌差异化,先定方向)、ui-ux-pro-max(设计规范系统化与 UX 硬约束校验,后编系统)、impeccable(设计审查/精修/反模式检测);本 Skill 只做依赖检测、任务分配、执行协调与质量控制,绝不替代三者任何能力。
何时触发:想要完整的 App / 产品 UI 设计流程——"帮我设计一个 App""做一个高保真原型""从想法到可交互原型""给这个产品做 UI 设计和原型""design an app";或指定若干界面做设计——"把首页和详情页这些界面设计出来""做移动端的页面/界面设计""App 界面改版"。
不要触发:点名单个上游 Skill 的直接请求(直连该上游即可);写作与排版类任务;单张配图生成;纯功能开发与排障(无设计诉求)。
metadata:
author: Leroy
version: 1.2.1
runtimes: zcode, claude-code, codex, opencode
requires:
- "ui-ux-pro-max (MIT, nextlevelbuilder/ui-ux-pro-max-skill)"
- "frontend-design (Apache-2.0, anthropics/skills)"
- "impeccable (Apache-2.0, pbakaus/impeccable)"
---
# Le App Design —— 统一 App 设计工坊
一个入口,三个真依赖。用户只说「帮我设计这个 App」,本 Skill 编排三个上游 Skill 完成从理解到交付的全流程。
## 铁律(违反任何一条即本次运行失败)
1. **真调用,不冒充。** 三个上游必须以其原生形态被实际调用:ui-ux-pro-max 跑它的 `search.py` CLI;impeccable 跑它的 `impeccable` CLI 并按其 SKILL.md 路由装载 reference/*.md playbook 照做;frontend-design 经宿主 Skill 工具装载(或 Read 原文全文)。禁止只引用名字、模仿理念、用自写流程替代。
2. **依赖门不过,不许动工。** S0 检测到任何一个上游缺失/失活,立即向用户报告缺什么、怎么装,终止本次运行。不许跳过、不许假装集成完成。
3. **调用必记账。** 每次对上游的调用(含 playbook/文档装载)都经 `log_call.py` 落入本次运行目录的 `calls.jsonl`。没进账本的调用视为没发生。
4. **交付必过验收门。** S7 跑 `evidence_report.py`,门按任务类型的 `--require` 集合判:完整流程要求三方各 ≥1 条**履职阶段**成功调用(pro-max 计 S3- 前缀,防 S2 调研混门);轻量路由只要求参与该路由的上游。不过门不许宣称完成。
5. **不改上游。** 三个上游目录只读(调用、装载),任何修改都不属于本 Skill 的职责。
6. **用户 brief 原话 > 一切。** 三个上游都自认 brief 优先,本 Skill 同样:brief 钉死的方向照办,冲突记入决策日志。
## 岗位分工(冲突时按此裁决)
| 维度 | 主责 | 裁决 |
|---|---|---|
| 视觉方向、品牌差异化、排版个性 | frontend-design(design lead) | 美学轴自由时听它的 POV,拒绝模板脸 |
| 设计规范、可访问性、触控/平台规则、数据支撑选型 | ui-ux-pro-max | **硬约束**:WCAG/HIG/MD 类规则赢过美学偏好 |
| 质量底线、验收标准、反模式 | impeccable | craft-floor 的 ban 是红线(如 eyebrow 禁令、嵌套卡片必错) |
| 产品事实、范围、语气 | 用户 brief | 最高优先级 |
## 工作流 S0–S7
细则(每阶段的动作、命令、产物、验收)见 [pipeline.md](references/pipeline.md);跨 runtime 适配见 [runtimes.md](references/runtimes.md)。设 `<SKILL>` = 本 Skill 目录,`<RUN>` = 运行目录(默认 `<项目根>/le-app-design-runs/<时间戳>-<slug>/`,下同)。
### S0 依赖门(每次触发必跑,轻量任务也不例外)
```bash
python3 <SKILL>/scripts/check_deps.py --out <RUN>/deps.json
```
三方解析 + 活性检查(pro-max 真跑一次检索、impeccable 跑 `--version`、frontend-design 校验原文完整可读)。非零退出 → 停止并报告缺失项与安装指引,本次运行终止。
### S1 产品理解
产出 `<RUN>/BRIEF.md`:**项目代号**(供 S3 的 `-p` 与目录命名)、产品是什么、给谁、核心任务、平台与形态、impeccable 四模式归属(Persuade / Operate / Read / Experience)。brief 信息不足且影响方向 → 问用户;足够 → 写明假设直接做。
### S2 视觉探索 — frontend-design(方向先行)
1. 宿主有 Skill 工具(ZCode / Claude Code)→ 用它装载 `frontend-design`;没有 → Read `<DEP_FD>/SKILL.md` 全文进上下文(一等路径,不是降级)。
2. 装载后立即记账:`python3 <SKILL>/scripts/log_call.py frontend-design --run-dir <RUN> --load <DEP_FD>/SKILL.md`
3. 按其「两遍法」产出 `<RUN>/design-plan.md`:从题材世界出发定艺术方向——色板(4–6 个具名 hex)、字体与角色、布局概念(一句话 + ASCII)、设计原则;对照它的 AI 味特征清单逐条自检,修订写明改了什么、为什么。**此时不看任何品类匹配结果——方向由题材与 POV 决定,不由目录里有什么决定。**
4. (可选调研模式)方向成形前可用 pro-max 的 `--domain style|color|typography` 检索选项当情绪板(经 log_call 记账、note 标 S2-research)——只供参考,不做决定,不生成 MASTER。
### S3 设计规范 — ui-ux-pro-max(系统后行)
方向已定,本阶段把它**编撰**成可执行的规范并对硬约束校验:
```bash
# 用已定方向的关键词驱动系统生成(不是品类词),dials 按方向调
python3 <SKILL>/scripts/log_call.py ui-ux-pro-max --run-dir <RUN> --note S3-design-system -- \
python3 <DEP_UIUX>/scripts/search.py "<方向关键词,如 exercise-book hand-drawn sticker>" \
--design-system --persist -p "<项目名>" --output-dir <项目根>
```
两条规则:
- **硬约束否决权**:frontend-design 定的 hex/字号若对比度、触控、平台规范不达标,以 pro-max 数据(WCAG/HIG/MD)为准**改值不改方向**;逐项校验(`--domain ux`、`--domain typography` 等)全部经 log_call。
- **目录回弹降级**:若 pro-max 风格目录(51 个活跃风格)对已定方向匹配失败——生成结果明显回弹到品类默认——则**不采用该 MASTER**,降级为「design-plan 的 token 表即规范 + pro-max 逐项校验补硬约束」,在 design-plan 决策记录里写明降级与理由。方向的独特性优先于系统的便利性。
### 停点 1(回合终止点)
向用户展示 BRIEF + design-plan(设计方向)+ MASTER 要点或降级说明,等确认或调整后再进构建。用户已明确说「一口气跑完」→ 豁免本停点,继续。**本停点必须终止回合等待用户,不得自答自继续。**
### S4 高保真构建(a 首屏 → 停点 1.5 → b 全量)
1. Read `<DEP_IMPECCABLE>/reference/craft-floor.md`(装载即调用)并记账。
2. **S4a 首屏**:先只构建主界面一屏(真实内容 + 规范 token + design-plan 个性),390×844 截图。
3. **停点 1.5(首屏确认,回合终止点)**:把首屏截图发到对话,请用户确认「方向落地长这样」再继续——这一站消除盲盒:成品第一次见面不再是 S5 之后。豁免时跳过停顿,**但截图仍必须发出**。
4. **S4b 全量**:其余屏 + 完整交互(390 主档 + 375 兼容档截图;移动 Web 任务免桌面档)。显隐/布局断言用渲染事实(computed display / boundingRect),不信 hidden 属性。
### S5 设计审查 — impeccable
1. `log_call.py impeccable -- sh <DEP_IMPECCABLE>/scripts/impeccable context --target <主文件>`,按其指令行事。
2. 按其 SKILL.md 路由装载 `reference/critique.md`(+ 需要的 audit/craft-floor 等)并照做——critique 要求**双独立 sub-agent**(Assessment A 设计评审 / Assessment B detect+浏览器证据):ZCode/Claude Code 用 Agent 工具并行派两个;Codex 按 critique.md 的 Codex 门问询;无 sub-agent 的 runtime 按其降级规则出横幅。playbook 装载逐一记账。
3. Assessment B 真跑:`log_call.py impeccable -- sh <DEP_IMPECCABLE>/scripts/impeccable detect --json <文件>`;可浏览器时按 playbook 走 overlay/截图证据。
4. 按 critique.md 完整出报告(启发式评分表、persona 红旗、P0–P3、快照持久化 + trend)。
### 停点 2(回合终止点)
展示审查报告**并附修复前后关键截图**(不是纯文字报告——用户确认修复范围时应已见过成品),四选一:全部 / Top 3 / 仅 P0-P1 / 保持现状(现状=跳过 S6 直接 S7,报告照出)。「一口气跑完」豁免同上;豁免时默认修 P0+P1,且审查报告末尾必须补字面行 `Questions skipped: 停点2经用户豁免(默认修复 P0+P1)`——这是 impeccable critique 契约的收尾要求,漏了即该次 critique 不完整。
### S6 优化迭代
按确认的范围修复:P0/P1 必修;修完复跑 `detect --json` 至 0 findings(或记明剩余项与理由);复截图核对视觉。涉及大改方向的回到 S3 重走(罕见)。
### S7 交付 + 验收门
```bash
# 完整流程(默认三方门,pro-max 计履职前缀)
python3 <SKILL>/scripts/evidence_report.py --run-dir <RUN> --out <RUN>/INTEGRATION-REPORT.md \
--require "ui-ux-pro-max:S3-,frontend-design:S2-,impeccable" \
--artifact <原型主文件> --artifact <RUN>/shot-*.png --artifact <RUN>/design-plan.md
# 系统路径(非降级)另加 --artifact design-system/<slug>/MASTER.md(slug 跑完 S3 后 ls design-system/ 确认)
```
交付物 = 原型(可打开可交互)+ BRIEF/design-plan/MASTER(或降级说明)+ 审查报告与快照 + INTEGRATION-REPORT。报告 FAIL(门或产物缺失)→ 不许宣称完成,如实说明缺什么。
## 轻量任务路由(含各自的验收门口径)
- **仅审查/体检**(「帮我审一下这个原型的设计」):S0 → S5(完整 critique:装载 critique.md + 双 sub-agent + detect + 快照)→ 停点 2 →(S6 按所选范围)→ S7。验收门 `--require "impeccable"`。
- **局部修改**(「把这个页面间距调好」):S0 → S2 快速版(涉及视觉决策时:装载 frontend-design + 记账,方向口头确认即可,无 design-plan 全文档)→ S4′(craft-floor 装载 + 就地修改 + 渲染事实断言 + 复截图)→ S5′(detect --json + 复截对比,不走完整 critique)→ S7。验收门 `--require "impeccable"`(走了 S2 快速版则加 `,frontend-design`)。
- 三上游经 check_deps 解析后始终可调用;轻量路由是省阶段,不是换实现——省掉的阶段不要求调用,但**不许伪造调用记录凑门**。
## 失败模式与停手点
- 依赖缺失/失活:S0 即停,报缺失项与安装指引(见 README「依赖安装」)。
- 上游 CLI 中途失败:重试一次;仍失败 → 记账(账本保留失败记录)、报告用户、终止或按用户指示降级到其余上游可覆盖的范围,并在交付物中明确标注缺口。
- Chrome/浏览器不可用:截图核对改为声明 fallback(craft-floor 逐条人工核对),在报告中注明。
- 用户点名单一上游:不触发本 Skill(负触发已在 description 声明)。
## 反例黑名单
- 不要在账本外「顺手」直接调上游——没记账的调用等于没发生。
- 不要把三个上游的文档内容抄进本 Skill 或其 references 来「省一次装载」。
- 不要因为某上游慢/临时失败就用自己的设计判断顶替其岗位并照常宣称完成。
- 不要在停点自问自答继续跑;豁免只能来自用户明确授权。
- 不要检测到依赖缺失还继续产出「设计」——那是没有集成的假成功。
## Reference Map
- 七阶段细则与验收:[pipeline.md](references/pipeline.md)
- 跨 runtime 适配与已知限制:[runtimes.md](references/runtimes.md)
- 依赖安装与许可证:[README.md](README.md)、[NOTICE](NOTICE)
@@ -0,0 +1,23 @@
{
"_说明": "le-app-design 触发用例。family 标注场景族,便于回归分析。",
"recommended_threshold": 0.3,
"negative_patterns": ["用 impeccable", "impeccable 审查", "ui-ux-pro-max", "查个配色", "公众号", "写一篇"],
"should_trigger": [
{"text": "帮我设计一个习惯打卡 App,要能看的高保真原型", "family": "full_design_cn"},
{"text": "把这个产品想法做成可交互的 UI 原型,从设计到成品", "family": "full_design_cn"},
{"text": "design an app for my recipe startup, high-fidelity prototype", "family": "full_design_en"},
{"text": "给我的工具类产品做一轮完整的设计:规范、视觉、原型加审查", "family": "full_pipeline"},
{"text": "我要做个妈妈育儿 App,帮我把首页和详情页设计出来", "family": "app_screens"},
{"text": "用 le-app-design 把这个 idea 变成原型", "family": "explicit_name"}
],
"should_not_trigger": [
{"text": "用 impeccable 审查一下这个页面", "family": "single_upstream_direct"},
{"text": "ui-ux-pro-max 帮我查个配色", "family": "single_upstream_direct"},
{"text": "帮我写一篇公众号文章", "family": "other_le_skill"},
{"text": "给我生成一张 App 封面图", "family": "image_only"},
{"text": "这个按钮的点击事件没反应,帮我修一下", "family": "pure_dev"}
],
"near_neighbor": [
{"text": "把这个页面布局调好看一点", "family": "local_tweak", "note": "局部修改走轻量路由(S0+S4′+S5′),仍触发但流程裁剪"}
]
}
+150
View File
@@ -0,0 +1,150 @@
# 七阶段流水线细则
SKILL.md 是路由,本文是执行手册。每阶段给出:输入 → 动作(含确切命令)→ 产物 → 验收。命令中 `<SKILL>` = le-app-design 目录,`<RUN>` = 本次运行目录,`<DEP_UIUX>` / `<DEP_FD>` / `<DEP_IMP>` = S0 解析出的三个上游路径(读 `<RUN>/deps.json` 的 `deps.*.path`)。
## S0 依赖门
**动作**
```bash
mkdir -p <RUN>
python3 <SKILL>/scripts/check_deps.py --out <RUN>/deps.json
```
**验收**:exit 0 且 deps.json 的 `ok=true`。任一依赖 `alive=false` → 向用户输出缺失项、原因(`check` 字段)与安装指引(`install` 字段),**终止运行**。deps.json 是后续所有阶段的路径来源,不要手写上游路径。
## S1 产品理解
**动作**:从用户输入提炼并写 `<RUN>/BRIEF.md`,包含:
- 产品:一句话说清这是什么、解决什么
- 受众与场景:谁、在什么场景用(决定 impeccable 模式与 frontend-design 的取材世界)
- 核心任务:用户打开它要完成的最重要 1–3 件事
- 平台形态:移动 Web(默认 390×844)/ 桌面 / 响应式;明确「Web 高保真原型」是交付形态
- 模式归属:Persuade / Operate / Read / Experience 四选一(impeccable 按表面而非产品选模式:App 内页=Operate,落地页=Persuade,阅读页=Read,作品展示=Experience)
- 范围:本次做哪些屏/页面,明确排除什么
信息不足且**影响方向**才问用户(用结构化选项,别开放式问卷);否则写明「假设:…」直接做。多次触达同一项目时先读已有 BRIEF.md,增量更新而非重写。
**验收**:BRIEF.md 落盘,模式归属有理由。
## S2 视觉探索 — frontend-design(方向先行)
**动作**
1. 宿主有 Skill 工具 → 调它装载 `frontend-design`;没有 → Read `<DEP_FD>/SKILL.md` 全文(两路等价,装载即调用)。
2. 立即记账:`python3 <SKILL>/scripts/log_call.py frontend-design --run-dir <RUN> --note S2-load --load <DEP_FD>/SKILL.md`
3. 以其「设计总监」立场产出 `<RUN>/design-plan.md`:
- **题材世界**:从产品自己的世界取材(subject matter 是差异化来源),写明方向一句话
- **色板**:4–6 个具名 hex(`--纸面 #FDFDF8` 式命名)
- **字体**:选型 + 角色,说明为何不是默认脸
- **布局**:一句话概念 + 每屏 ASCII 线框 + 对齐策略
- **原则**:这个产品独有的 3–5 条
- **两遍法第二遍**:对照其「AI 味特征清单」逐条自检;发现默认味 → 写明改了什么、为什么
4. (可选调研模式)方向成形前可用 pro-max `--domain style|color|typography` 检索选项当情绪板(记账 note=S2-research)——**只供参考不做决定**,此阶段禁止 `--design-system`。
**验收**:design-plan.md 四要素齐全 + 自检一节存在 + 记账在案;方向不来自品类匹配。
## S3 设计规范 — ui-ux-pro-max(系统后行)
**动作**:把已定方向编撰成规范 + 硬约束校验:
```bash
# 主调用:用方向关键词(不是品类词)驱动系统生成;dials 按方向调
python3 <SKILL>/scripts/log_call.py ui-ux-pro-max --run-dir <RUN> --note S3-design-system -- \
python3 <DEP_UIUX>/scripts/search.py "<方向关键词>" --design-system --persist \
-p "<项目名>" --output-dir <项目根> [--variance N --motion N --density N]
# 硬约束校验(按需,全部记账):对比度/触控/排版/相关 UX 规则
python3 <SKILL>/scripts/log_call.py ui-ux-pro-max --run-dir <RUN> --note S3-ux-check -- \
python3 <DEP_UIUX>/scripts/search.py "<如 contrast touch target>" --domain ux
```
**两条规则**:
- **硬约束否决权**:frontend-design 定的 hex/字号若不达标(WCAG/HIG/MD 数据为准),**改值不改方向**;每处修改记入 design-plan 决策记录。
- **目录回弹降级(操作化判据)**:生成结果命中以下任意两条即判回弹、不采用该 MASTER——①Style 名与方向关键词无词面或语义关联(如「练习本手写」→ Swiss/Helvetica);②色板命中 AI-tell 家族(暖奶油底、默认靛蓝/紫系、近黑+酸绿)且与 design-plan 色板无共享主色;③关键 Effects/Anti-patterns 与方向的手感相反(如手绘方向得到 corporate/rational)。降级后:design-plan 的 token 表即规范 + pro-max 逐项校验补硬约束,决策记录写明命中哪两条。方向的独特性优先于系统的便利性。
**验收**:MASTER.md 存在且与方向一致,或降级路径已记录;校验检索在账本;design-plan 决策记录含 S3 的改值/降级条目。
## 停点 1
把 BRIEF(要点)、design-plan(方向 + ASCII + 色板 + 原则)、MASTER 要点或 S3 降级说明展示给用户,明确请求确认或调整意见,**终止回合**。用户豁免(已说「一口气跑完」)→ 跳过继续。
## S4 高保真构建(a 首屏 → 停点 1.5 → b 全量)
**S4a 首屏**
1. Read `<DEP_IMP>/reference/craft-floor.md` 并记账:
`python3 <SKILL>/scripts/log_call.py impeccable --run-dir <RUN> --note S4-craft-floor --load <DEP_IMP>/reference/craft-floor.md`
2. 只构建**主界面一屏**(真实内容 + 规范 tokens + design-plan 个性),390×844 截一张。
**停点 1.5(首屏确认)**:截图发到对话请用户确认方向落地;豁免时跳过停顿但截图必须发出。**终止回合**;这一站是「盲盒阀门」——成品首见从 S5 之后提前到构建开头。
**S4b 全量**
1. 构建:默认无构建链的 HTML/CSS/JS(单文件优先,可拆 CSS/JS 文件)。硬性要求:
- token 来自 MASTER.md(或降级时的 design-plan token 表),不发明规范之外的美学量
- 布局与个性按 design-plan;SVG 图标(真图标库或手绘一致笔画),禁 emoji 图标;插画/吉祥物/场景图可按 runtime 用图像生成(Codex=内建 image_gen,边界与产物规则见 [runtimes.md](runtimes.md)),图标系统与精确几何仍走 SVG
- craft-floor 全条目施工时遵守(尤其:eyebrow 是 ban;嵌套卡片必错;elevation 声明一次;浏览器表面——selection/caret/scrollbar/focus ring/表格数字——从色板定制)
- 交互真实可用(点击/切换/滚动行为写 JS),不是静态画片
- pro-max Pre-Delivery 逐条过:44px 触控、4.5:1 对比、focus ring、reduced-motion、375px 无横滚
2. 截图口径(统一):**390×844 主档 + 375 兼容档各至少一张**;移动 Web 任务免桌面档(body 居中列已保证桌面不破)。按 craft-floor「一次看一批」:Read 截图找问题 → 修完一批 → 再截。编排器无法亲验截图图像时,交 S5 Assessment A 首个看图。
3. **显隐与布局断言用渲染事实(computed display / getBoundingClientRect),不信 hidden 属性**——id/类选择器的 display 会压过 [hidden](实测翻车:两页堆叠渲染而属性检查全绿)。
**验收**:原型可打开(`file://` 直开无 console error 级问题)、双档截图在 `<RUN>/`、craft-floor 关键项肉眼过。
## S5 设计审查 — impeccable
**动作**
1. 上下文装载:
```bash
python3 <SKILL>/scripts/log_call.py impeccable --run-dir <RUN> --note S5-context -- \
sh <DEP_IMP>/scripts/impeccable context --target <原型主文件>
```
按 context 输出的指令行事(它可能要求 init/PRODUCT.md——轻量原型按其 SCOPED_EXISTING_ALLOWED 通道走,不阻塞)。
2. Read `<DEP_IMP>/reference/critique.md` 并记账(`--load`)。**按 critique.md 原文执行**,特别注意其 Hard Invariants:
- 双独立 sub-agent:Assessment A(设计评审)/ Assessment B(detect + 浏览器证据)。ZCode/Claude Code 用 Agent 工具并行派发,两者互不可见对方输出;Codex 有 spawn_agent 先问一次;都没有才单上下文顺序跑并打 `⚠️ DEGRADED` 横幅
- **派发模板要点**(子代理看不到主上下文,prompt 必须自带全部材料):
- A 传:目标文件路径、BRIEF/design-plan 路径、全部截图路径(要求「真正看图」)、四模式与受众摘要、输出契约(specificity/十启发式表/认知负荷/情绪/P0-P3/persona)
- B 传:经 log_call 包裹的 detect 命令全文(注明 exit 0=clean / 2=findings 属成功)、截图清点、误报核对要求(对源码实算)、fallback signal 原文案
- B 的 CLI 扫描:
```bash
python3 <SKILL>/scripts/log_call.py impeccable --run-dir <RUN> --note S5-detect -- \
sh <DEP_IMP>/scripts/impeccable detect --json <原型目录或主文件>
```
(exit 0=clean,2=有 findings;只传 markup 文件)
- 浏览器证据:无浏览器自动化时用 headless Chrome 截图代替并在 Run Notes 声明 fallback
- 快照持久化 + trend:按 critique.md 的 `critique-storage write/trend` 执行并记账
3. 出完整报告:方法横幅、启发式评分表(0–4 ×10,适用 max 规范化)、Design Specificity、P0–P3 优先问题、persona 红旗、Run Notes。
**验收**:报告完整落 `<RUN>/critique-report.md`(同时按 critique.md 在聊天中交付全文)、detect 结果在账本、快照已写(或记明原因)。
## 停点 2
展示审查报告**并附修复前后关键截图**,四选一:全部 / Top 3 / 仅 P0–P1 / 保持现状(跳过 S6 直接 S7,报告照出)。**终止回合**;豁免规则同停点 1(豁免时默认修 P0+P1,并在审查报告末尾补字面行 `Questions skipped: 停点2经用户豁免(默认修复 P0+P1)`,满足 impeccable critique 的收尾契约)。
## S6 优化迭代
**动作**:按用户选定范围修(P0/P1 无用户明示保留也必修)→ 复跑 detect(记账,note=S6-redetect)至 exit 0(=0 findings;残留项逐一记明理由)→ 复截图核对修复效果。修复遵守 S4 的同一套约束(token/设计/craft-floor),不许「修着修着」脱离体系。
**验收**:detect 清零或残留有据;前后截图对比留档 `<RUN>/`。
## S7 交付 + 验收门
**动作**
```bash
# 完整流程:默认三方门,pro-max 计履职前缀(S2-research 不混门)
python3 <SKILL>/scripts/evidence_report.py --run-dir <RUN> \
--out <RUN>/INTEGRATION-REPORT.md --title "<项目代号> 集成证据" \
--require "ui-ux-pro-max:S3-,frontend-design:S2-,impeccable" \
--artifact <原型主文件> --artifact <RUN>/shot-<主档>.png --artifact <RUN>/design-plan.md
# 仅审查路由:--require "impeccable";局部修改:--require "impeccable[,frontend-design]"
# 系统路径(非降级)另加 --artifact design-system/<slug>/MASTER.md;
# slug 由上游按 -p 项目代号生成(小写连字符),跑完 S3 用 ls design-system/ 确认实际目录名
```
向用户交付:原型入口(`file://` 路径,可交互)、关键截图、INTEGRATION-REPORT 判定与调用统计、审查分数与修复摘要、运行目录索引。**FAIL 时不宣称完成**,把缺口(哪个门没过/哪个产物缺失)说清楚。
## 全程通用约束
- **note 命名规范**(验收门的履职前缀依赖它):`S2-load`(frontend-design 装载)、`S2-research`(可选调研,不计入门)、`S3-design-system` / `S3-ux-check` / `S3-typography-check`(pro-max 履职)、`S4-craft-floor`、`S5-context` / `S5-critique-playbook` / `S5-detect`、`S6-redetect`。
- 每次对上游的调用都过 `log_call.py`,`--run-dir` 显式传(缺省会另建 `<时间戳>-run` 目录,账本裂成两份、验收门读空账本 FAIL);失败调用也要留痕。
- 不修改三个上游目录的任何文件;PYTHONDONTWRITEBYTECODE 已由脚本注入。
- 运行目录是本次任务的唯一工作痕迹区:BRIEF / design-plan / 截图 / 报告全进 `<RUN>`;MASTER.md 在项目根(上游约定位置,slug 跑完 S3 后 `ls design-system/` 确认实际目录名)。
- 用户豁免只作用于停点,不作用于铁律与验收门;豁免停点 1.5 时首屏截图仍须发出。
@@ -0,0 +1,38 @@
# 跨 Runtime 适配
CLI 调用(check_deps / log_call / 上游两个 CLI)在所有 runtime 完全一致——它们只依赖 python3 与 sh。差异只在两处:frontend-design 怎么装载、critique 的双 sub-agent 怎么派。
## 适配矩阵
| Runtime | frontend-design 装载 | critique 双评审 | 备注 |
|---|---|---|---|
| ZCode | Skill 工具(首选)或 Read 原文 | Agent 工具并行派 A/B 两个子代理 | 全功能路径,E2E 验证即在此环境跑 |
| Claude Code | Skill 工具(首选)或 Read 原文 | Task/Agent 工具并行派 A/B | 同上 |
| Codex | Read 原文全文(一等路径) | 有 `spawn_agent`:按 critique.md 的 Codex 门**先问用户一次**再派;无则顺序跑 + `⚠️ DEGRADED` 横幅 | 上游需先装进 `~/.codex/skills/`(见 README);**插画资产可用内建 image_gen**(见下节) |
| OpenCode | Read 原文全文(一等路径) | 有 sub-agent 工具就派,无则降级横幅 | 同 Codex 逻辑 |
| MausBot / 其它 | Read 原文全文(一等路径) | 无 sub-agent 则降级横幅 | 限制:见下 |
## Codex 的图像生成(image_gen)
Codex 内建 `image_gen` 工具可直接生成图片,le-app-design 在 Codex 运行时允许用它产出小型插画资产:
- **适用**:S4 构建需要的插画类资产——吉祥物/角色形象(手绘 SVG 常见「认不出是什么」的翻车)、场景插图、封面图。prompt 必须携带 design-plan 的色板 hex 与题材世界描述,保证与方向一致;生成后按 craft-floor「Real illustration or none」的插图标准对待。
- **不适用**:UI 图标系统仍走 SVG(真图标库或手绘一致描边——icons are drawn,要可缩放可主题化);需要精确几何的图形(图表、印章、格线)走 SVG。
- 产物落 `<项目>/imgs/`(或 `<RUN>/assets/`)并进 S7 产物清单;生成图随首屏截图一起在停点 1.5 接受视觉复核(错字/形近字/认不出主体即重生成或退回 SVG)。
- image_gen 是宿主能力不是上游 Skill,不进调用账本;**非 Codex runtime 不为此引入依赖**——维持 SVG 自绘,宿主另有图像能力(如本机 le-gpt-image)则由用户指示后使用。
「一等路径」的含义:frontend-design 是纯指导型 Skill,官方机制就是把它的 SKILL.md 全文装进执行上下文并照做。Read 原文 + `log_call.py --load` 记 sha256,与 Skill 工具装载等效且可审计——**不是降级**。降级只发生在 critique 的双评审(impeccable 自己的定义,见其 critique.md Hard Invariants)。
## 已知限制(如实告知用户,不静默吞掉)
1. **impeccable detect 只读 web markup**(HTML/JSX 等)。原生 Swift/Kotlin 源码扫不出东西——纯原生方案 critique 走 Assessment A 单评审(这是上游定义的完整方法,非降级)。本 Skill 交付形态固定为 Web 高保真原型,不受影响。
2. **impeccable 二进制按平台分发**(本机 darwin-arm64 已带)。换机器跑 check_deps 会立刻发现缺 `scripts/impeccable` 或其 bin/ 子目录——按报告里的安装指引处理;上游 README 说明二进制会在首跑时按平台下载。
3. **截图依赖本机 Chrome**。找不到时 S4/S5 的截图核对降级为 craft-floor 逐条人工核对 + 在 INTEGRATION-REPORT 的 Run Notes 声明,不静默跳过。
4. **Windows**:impeccable 用 `scripts/impeccable.cmd`,python3 可能叫 `python`——check_deps/liveness 若失败按报告指引调整;其余逻辑不变。
5. **上游版本漂移**:check_deps 记录的是路径 + 活性,不锁版本。上游大版本更新导致 playbook 接口变化时(如 critique.md 的命令名变了),S5 会暴露失败——按失败模式处理并报告,不猜测新接口。
## 路径解析优先级(check_deps 的顺序)
`$LE_APP_DESIGN_DEP_ROOTS`(冒号分隔,自定义安装位)→ `~/.agents/skills`(首选默认,与 impeccable SKILL.md 自引用路径一致)→ `~/.zcode/skills` → `~/.codex/skills` → `~/.claude/skills`。
同一机器多份副本时取优先级最高的一份并在 deps.json 记录实际路径;S2/S5 的命令一律用该路径,不硬编码。
+223
View File
@@ -0,0 +1,223 @@
#!/usr/bin/env python3
"""le-app-design 硬依赖门:解析并活性检查三个上游 Skill。
用法:
python3 check_deps.py [--out <RUN>/deps.json] [--quiet]
解析顺序: $LE_APP_DESIGN_DEP_ROOTS(冒号分隔) > ~/.agents/skills > ~/.zcode/skills
> ~/.codex/skills > ~/.claude/skills
(优先 ~/.agents/skills:impeccable 的 SKILL.md 自引用该路径。)
活性检查按依赖形态各自不同——这就是「真实调用形态」的机器定义:
ui-ux-pro-max 真跑一次 search.py 检索(stdout 含结果段即活)
frontend-design 校验 SKILL.md 原文完整可读(纯文档型 Skill,装载即调用)
impeccable 跑其自包含二进制 --version
全部通过 exit 0;任何缺失/失活 exit 1 并输出明确报告。仅 python3 标准库。
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import re
import subprocess
import sys
import tempfile
from datetime import datetime, timezone
from pathlib import Path
SKILL_KEY = "le-app-design-dep-check"
UPSTREAMS = [
{
"key": "ui-ux-pro-max",
"repo": "nextlevelbuilder/ui-ux-pro-max-skill",
"license": "MIT",
"install": "git clone https://github.com/nextlevelbuilder/ui-ux-pro-max-skill && 拷贝/链接其 .claude/skills/ui-ux-pro-max 到 ~/.agents/skills/ui-ux-pro-max",
"kind": "cli-search",
},
{
"key": "frontend-design",
"repo": "anthropics/skills",
"license": "Apache-2.0",
"install": "git clone https://github.com/anthropics/skills && 拷贝/链接 skills/frontend-design 到 ~/.agents/skills/frontend-design",
"kind": "doc-guidance",
},
{
"key": "impeccable",
"repo": "pbakaus/impeccable",
"license": "Apache-2.0",
"install": "git clone https://github.com/pbakaus/impeccable && 拷贝/链接到 ~/.agents/skills/impeccable(含 scripts/ 自包含二进制)",
"kind": "cli-engine",
},
]
FRONTMATTER_MIN_BYTES = 2000 # frontend-design 原文约 10KB;残缺副本在此拦下
def candidate_roots() -> list[Path]:
roots: list[Path] = []
env = os.environ.get("LE_APP_DESIGN_DEP_ROOTS", "")
for part in env.split(os.pathsep):
if part.strip():
roots.append(Path(part).expanduser())
home = Path.home()
roots += [
home / ".agents" / "skills",
home / ".zcode" / "skills",
home / ".codex" / "skills",
home / ".claude" / "skills",
]
seen: set[str] = set()
unique: list[Path] = []
for r in roots:
key = str(r)
if key not in seen:
seen.add(key)
unique.append(r)
return unique
def read_frontmatter_name(skill_md: Path) -> str | None:
try:
text = skill_md.read_text(encoding="utf-8", errors="replace")
except OSError:
return None
m = re.match(r"^---\s*\n(.*?\n)?---", text, re.DOTALL)
if not m:
return None
block = m.group(1) or ""
nm = re.search(r"^name:\s*['\"]?([A-Za-z0-9_-]+)", block, re.MULTILINE)
return nm.group(1) if nm else None
def resolve(key: str) -> tuple[Path | None, str]:
"""返回 (解析到的目录, 说明)。"""
for root in candidate_roots():
dep_dir = root / key
skill_md = dep_dir / "SKILL.md"
if skill_md.is_file():
name = read_frontmatter_name(skill_md)
if name == key:
return dep_dir, f"resolved at {dep_dir}"
return dep_dir, f"SKILL.md frontmatter name={name!r} != {key!r}(目录在但身份不符)"
return None, "not found in any candidate root"
def _run(cmd: list[str], timeout: int = 90) -> subprocess.CompletedProcess:
env = dict(os.environ, PYTHONDONTWRITEBYTECODE="1")
return subprocess.run(
cmd, capture_output=True, text=True, timeout=timeout, env=env
)
def liveness(spec: dict, dep_dir: Path) -> tuple[bool, str]:
key, kind = spec["key"], spec["kind"]
try:
if kind == "cli-search":
script = dep_dir / "scripts" / "search.py"
if not script.is_file():
return False, f"missing {script}"
with tempfile.TemporaryDirectory() as td:
proc = subprocess.run(
[sys.executable, str(script), "mobile app", "--domain", "style", "-n", "1"],
capture_output=True, text=True, timeout=90, cwd=td,
env=dict(os.environ, PYTHONDONTWRITEBYTECODE="1"),
)
if proc.returncode != 0:
return False, f"search.py exit={proc.returncode}: {proc.stderr.strip()[:300]}"
if "Result" not in proc.stdout:
return False, "search.py exit=0 but no result section in stdout"
return True, "search.py live query ok"
if kind == "doc-guidance":
skill_md = dep_dir / "SKILL.md"
try:
data = skill_md.read_bytes()
except OSError as e:
return False, f"unreadable SKILL.md: {e}"
if len(data) < FRONTMATTER_MIN_BYTES:
return False, f"SKILL.md only {len(data)} bytes (<{FRONTMATTER_MIN_BYTES}); likely truncated copy"
text = data.decode("utf-8", errors="replace")
if "name: frontend-design" not in text or "description:" not in text:
return False, "SKILL.md frontmatter incomplete"
return True, f"original SKILL.md intact ({len(data)} bytes, sha256 {hashlib.sha256(data).hexdigest()[:12]}…)"
if kind == "cli-engine":
launcher = dep_dir / "scripts" / "impeccable"
if not launcher.is_file():
return False, f"missing launcher {launcher}"
proc = _run(["sh", str(launcher), "--version"])
if proc.returncode != 0:
return False, f"impeccable --version exit={proc.returncode}: {proc.stderr.strip()[:300]}"
version = (proc.stdout or "").strip() or (proc.stderr or "").strip()
return True, f"impeccable binary ok ({version[:60]})"
except subprocess.TimeoutExpired:
return False, f"{key} liveness check timed out"
except OSError as e:
return False, f"liveness check error: {e}"
return False, "unknown kind"
def main() -> int:
ap = argparse.ArgumentParser(description="le-app-design 依赖门")
ap.add_argument("--out", help="JSON 结果写入路径(如 <RUN>/deps.json)")
ap.add_argument("--quiet", action="store_true", help="只输出摘要一行")
args = ap.parse_args()
result = {
"skill": SKILL_KEY,
"ts": datetime.now(timezone.utc).isoformat(timespec="seconds"),
"roots_searched": [str(r) for r in candidate_roots()],
"deps": {},
"ok": False,
}
for spec in UPSTREAMS:
dep_dir, note = resolve(spec["key"])
entry = {
"repo": spec["repo"],
"license": spec["license"],
"kind": spec["kind"],
"path": str(dep_dir) if dep_dir else None,
"alive": False,
"check": note if dep_dir is None else "",
"install": spec["install"],
}
if dep_dir is not None:
alive, check = liveness(spec, dep_dir)
entry["alive"] = alive
entry["check"] = check
result["deps"][spec["key"]] = entry
result["ok"] = all(d["alive"] for d in result["deps"].values())
if args.out:
out_path = Path(args.out).expanduser()
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
lines = []
for key, d in result["deps"].items():
mark = "✅" if d["alive"] else "❌"
where = d["path"] or "未找到"
lines.append(f"{mark} {key:18} {where}")
if not d["alive"]:
lines.append(f" 原因: {d['check']}")
lines.append(f" 安装: {d['install']}")
verdict = "依赖门通过(三方解析且活性正常)" if result["ok"] else "依赖门未通过——禁止继续设计流程"
if args.quiet:
print(f"{'PASS' if result['ok'] else 'FAIL'}: " + ", ".join(
f"{k}={'ok' if v['alive'] else 'MISSING'}" for k, v in result["deps"].items()))
else:
print("\n".join(lines))
print(verdict)
if args.out:
print(f"JSON: {args.out}")
return 0 if result["ok"] else 1
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,171 @@
#!/usr/bin/env python3
"""le-app-design 验收门:从 calls.jsonl 生成本次运行的集成证据报告。
用法:
python3 evidence_report.py --run-dir <RUN> [--out <RUN>/INTEGRATION-REPORT.md] \
[--artifact <路径>]... [--title "任务名"] \
[--require "dep[:note前缀],..."]
验收规则(硬门):
默认要求三个上游 ui-ux-pro-max / frontend-design / impeccable 各 ≥1 条成功调用
(exec 且 exit_code==0,或 load 且 exit_code==0)。
轻量路由用 --require 指定参与该路由的上游子集,如仅审查任务:
--require "impeccable"
每项可带 note 前缀限定履职阶段,防止非履职调用(如 S2-research 调研检索)
混过门,如完整流程的严格口径:
--require "ui-ux-pro-max:S3-,frontend-design:S2-,impeccable"
--artifact 指定的每个文件必须存在。
任一不满足 → 报告标 FAIL,exit 1;全满足 → PASS,exit 0。
报告内容: 判定横幅、调用统计表、逐条调用清单(时间/依赖/命令/退出码)、
产物核验清单、失败与缺口说明。这份报告就是「真实集成」的可检查证据。
仅 python3 标准库。
"""
from __future__ import annotations
import argparse
import json
import sys
from datetime import datetime, timezone
from pathlib import Path
GATE_DEPS = ["ui-ux-pro-max", "frontend-design", "impeccable"]
def load_records(ledger: Path) -> list[dict]:
if not ledger.is_file():
return []
records = []
for line in ledger.read_text(encoding="utf-8", errors="replace").splitlines():
line = line.strip()
if not line:
continue
try:
rec = json.loads(line)
except json.JSONDecodeError:
records.append({"dep": "(unparsable)", "kind": "raw", "exit_code": None, "command": line[:120], "ts": ""})
continue
records.append(rec)
return records
def is_success(rec: dict) -> bool:
return rec.get("exit_code") == 0 and rec.get("kind") in ("exec", "load")
def one_line(rec: dict) -> str:
cmd = rec.get("command", "")
try: # exec 记录的 command 是 JSON 数组字符串
parts = json.loads(cmd)
if isinstance(parts, list):
cmd = " ".join(str(p) for p in parts)
except (json.JSONDecodeError, TypeError):
pass
return cmd if len(cmd) <= 160 else cmd[:157] + "..."
def main() -> int:
ap = argparse.ArgumentParser(description="le-app-design 验收门")
ap.add_argument("--run-dir", required=True)
ap.add_argument("--out", default=None, help="报告写入路径,缺省 <run-dir>/INTEGRATION-REPORT.md")
ap.add_argument("--artifact", action="append", default=[], help="须存在的交付物路径(可多次)")
ap.add_argument("--title", default="le-app-design 集成证据报告")
ap.add_argument("--require", default=None,
help='门规格 "dep[:note前缀],...";缺省=三个上游全部、不限前缀。轻量路由传子集')
args = ap.parse_args()
# 解析门规格:dep[:note-prefix]
gate: dict[str, str | None] = {}
if args.require:
for item in args.require.split(","):
item = item.strip()
if not item:
continue
dep, _, prefix = item.partition(":")
gate[dep] = prefix or None
else:
gate = {dep: None for dep in GATE_DEPS}
run_dir = Path(args.run_dir).expanduser()
records = load_records(run_dir / "calls.jsonl")
out_path = Path(args.out).expanduser() if args.out else run_dir / "INTEGRATION-REPORT.md"
stats = {dep: {"ok": 0, "fail": 0} for dep in gate}
for rec in records:
dep = rec.get("dep")
if dep in stats:
stats[dep]["ok" if is_success(rec) else "fail"] += 1
def gate_ok(dep: str) -> bool:
prefix = gate[dep]
if prefix is None:
return stats[dep]["ok"] >= 1
return any(
is_success(rec) and str(rec.get("note", "")).startswith(prefix)
for rec in records if rec.get("dep") == dep
)
gate_pass = all(gate_ok(dep) for dep in gate)
artifacts_missing = [a for a in args.artifact if not Path(a).expanduser().exists()]
overall = gate_pass and not artifacts_missing
lines: list[str] = []
lines.append(f"# {args.title}")
lines.append("")
lines.append(f"- 运行目录: `{run_dir}`")
lines.append(f"- 生成时间: {datetime.now(timezone.utc).isoformat(timespec='seconds')}")
lines.append(f"- 调用记录: {len(records)} 条(`calls.jsonl`)")
lines.append("")
lines.append(f"## 判定: {'✅ PASS' if overall else '❌ FAIL'}")
lines.append("")
lines.append("| 上游 | 成功调用 | 失败调用 | 门(≥1 成功) |")
lines.append("|---|---|---|---|")
for dep in gate:
s = stats[dep]
req = f"(限 note={gate[dep]}*)" if gate[dep] else ""
lines.append(f"| {dep}{req} | {s['ok']} | {s['fail']} | {'✅' if gate_ok(dep) else '❌ 未被真实调用'} |")
lines.append("")
if artifacts_missing:
lines.append("**缺失产物:**")
for a in artifacts_missing:
lines.append(f"- ❌ `{a}`")
lines.append("")
elif args.artifact:
lines.append("**产物核验:**")
for a in args.artifact:
lines.append(f"- ✅ `{a}`")
lines.append("")
lines.append("## 调用清单")
lines.append("")
lines.append("| # | 时间(UTC) | 依赖 | 类型 | 退出码 | 命令/装载 |")
lines.append("|---|---|---|---|---|---|")
for i, rec in enumerate(records, 1):
lines.append(
f"| {i} | {rec.get('ts', '')} | {rec.get('dep', '?')} | {rec.get('kind', '?')} "
f"| {rec.get('exit_code', '?')} | `{one_line(rec)}` |"
)
lines.append("")
if not overall:
lines.append("## 缺口说明")
lines.append("")
for dep in gate:
if not gate_ok(dep):
prefix = f"(履职阶段 note={gate[dep]}*)" if gate[dep] else ""
lines.append(f"- `{dep}` 无成功调用记录{prefix}——该上游未被真实使用,本次流程**不得标记为成功**。")
for a in artifacts_missing:
lines.append(f"- 产物缺失: `{a}`")
lines.append("")
report = "\n".join(lines)
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(report + "\n", encoding="utf-8")
print(report)
print(f"\n[report] {'PASS' if overall else 'FAIL'} → {out_path}", file=sys.stderr)
return 0 if overall else 1
if __name__ == "__main__":
sys.exit(main())
+140
View File
@@ -0,0 +1,140 @@
#!/usr/bin/env python3
"""le-app-design 证据账本:所有对上游 Skill 的真实调用经此包裹,落入 calls.jsonl。
用法:
执行型调用(CLI 类上游):
python3 log_call.py <dep> --run-dir <RUN> -- python3 <DEP>/scripts/search.py "…" --design-system
装载型调用(文档型上游 / playbook):
python3 log_call.py <dep> --run-dir <RUN> --load <DEP>/SKILL.md
dep ∈ {ui-ux-pro-max, frontend-design, impeccable, le-app-design(自检)}
--run-dir 缺省时按序: $LE_APP_DESIGN_RUN_DIR > <cwd>/le-app-design-runs/<UTC 时间戳>-run/
行为:
- exec: 运行命令,透传 stdout/stderr,透传退出码;成功失败都记账。
- load: 读文件记 sha256 与字节数(证明用的是原文而非转述),文件缺失记 FAIL 退出 1。
- 记录字段: ts / dep / kind / command / exit_code / duration_ms / stdout_tail /
stderr_tail / bytes / sha256 / note。stdout/stderr 仅保留尾部 4000 字符防账本膨胀。
仅 python3 标准库。
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import subprocess
import sys
import time
from datetime import datetime, timezone
from pathlib import Path
TAIL = 4000
KNOWN_DEPS = {"ui-ux-pro-max", "frontend-design", "impeccable", "le-app-design"}
def default_run_dir() -> Path:
env = os.environ.get("LE_APP_DESIGN_RUN_DIR")
if env:
return Path(env).expanduser()
stamp = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%SZ")
return Path.cwd() / "le-app-design-runs" / f"{stamp}-run"
def append_record(run_dir: Path, record: dict) -> Path:
run_dir.mkdir(parents=True, exist_ok=True)
ledger = run_dir / "calls.jsonl"
with ledger.open("a", encoding="utf-8") as f:
f.write(json.dumps(record, ensure_ascii=False) + "\n")
return ledger
def confirm(dep: str, kind: str, status: str, ledger: Path, extra: str = "") -> None:
print(
f"[log_call] {dep} {kind} {status} → {ledger}"
+ (f" ({extra})" if extra else ""),
file=sys.stderr,
)
def main() -> int:
# 手工在第一个裸 "--" 处切分:argparse 的 REMAINDER 会把 -- 之前的选项误吞进命令
argv = sys.argv[1:]
cmd: list[str] = []
if "--" in argv:
split = argv.index("--")
argv, cmd = argv[:split], argv[split + 1:]
ap = argparse.ArgumentParser(description="le-app-design 调用账本")
ap.add_argument("dep", help="上游标识: ui-ux-pro-max | frontend-design | impeccable | le-app-design")
ap.add_argument("--run-dir", default=None, help="运行目录(缺省自动创建)")
ap.add_argument("--note", default="", help="阶段/用途备注,如 S2-design-system")
ap.add_argument("--load", metavar="PATH", default=None, help="装载型调用:记录读取的原文文件")
args = ap.parse_args(argv)
dep = args.dep
if dep not in KNOWN_DEPS:
print(f"[log_call] 未知依赖标识 {dep!r};允许: {sorted(KNOWN_DEPS)}", file=sys.stderr)
return 2
run_dir = Path(args.run_dir).expanduser() if args.run_dir else default_run_dir()
base = {"ts": datetime.now(timezone.utc).isoformat(timespec="seconds"), "dep": dep, "note": args.note}
if args.load:
path = Path(args.load).expanduser()
rec = dict(base, kind="load", command=f"load:{path}")
try:
data = path.read_bytes()
except OSError as e:
rec.update(exit_code=1, stderr_tail=f"read failed: {e}"[:TAIL])
ledger = append_record(run_dir, rec)
confirm(dep, "load", "FAIL", ledger, str(e))
return 1
rec.update(
exit_code=0,
bytes=len(data),
sha256=hashlib.sha256(data).hexdigest(),
)
ledger = append_record(run_dir, rec)
confirm(dep, "load", "OK", ledger, f"{len(data)} bytes sha256:{rec['sha256'][:12]}…")
return 0
if not cmd:
print("[log_call] 需要 --load PATH 或 -- <命令…>", file=sys.stderr)
return 2
rec = dict(base, kind="exec", command=json.dumps(cmd, ensure_ascii=False))
t0 = time.monotonic()
try:
proc = subprocess.run(
cmd, capture_output=True, text=True, timeout=900,
env=dict(os.environ, PYTHONDONTWRITEBYTECODE="1"),
)
rc, out, err = proc.returncode, proc.stdout or "", proc.stderr or ""
except subprocess.TimeoutExpired as e:
rc, out, err = 124, (e.stdout or b"").decode(errors="replace") if isinstance(e.stdout, bytes) else (e.stdout or ""), f"timeout after {e.timeout}s"
except FileNotFoundError as e:
rc, out, err = 127, "", f"command not found: {e}"
rec.update(
exit_code=rc,
duration_ms=int((time.monotonic() - t0) * 1000),
stdout_tail=out[-TAIL:],
stderr_tail=err[-TAIL:],
)
ledger = append_record(run_dir, rec)
if out:
sys.stdout.write(out)
if not out.endswith("\n"):
sys.stdout.write("\n")
if err:
sys.stderr.write(err)
if not err.endswith("\n"):
sys.stderr.write("\n")
confirm(dep, "exec", f"exit={rc}", ledger, f"{rec['duration_ms']}ms")
return rc
if __name__ == "__main__":
sys.exit(main())
+67
View File
@@ -0,0 +1,67 @@
# Le SPEC
> 智能需求分析与 SPEC 生成总控:把模糊、零散、矛盾的想法,经**真实集成的** Grill 深度访谈、Think 方案挑战、OpenSpec 规格工程,转化为可执行、可测试的正式 SPEC。VibeCoding / AI Coding 工作流的需求工程入口。
**状态**:rigorous 档 | **正式名称 Le SPEC,技术标识 `le-spec`** | 优先适配 Codex,兼容 ZCode / Claude Code
## 它解决什么问题
你有一堆没想清楚的想法(甚至互相矛盾),直接丢给 Coding Agent 会得到一堆它自行猜测的「需求」。Le SPEC 在编码之前接管:先访谈把该想清楚的想清楚,再挑战不合理方案,最后产出结构化的 OpenSpec 规格(产品需求 / 技术设计 / 开发任务三分离),让 Coding Agent 不用猜。
## 架构:真实集成,不是借鉴
| 引擎 | 来源 | 在 Le SPEC 中的职责 | 调用方式 |
|---|---|---|---|
| grilling (+ domain-modeling) | [mattpocock/skills](https://github.com/mattpocock/skills) | Phase 2 深度访谈:design tree / frontier 轮次 / 推荐答案 | Skill 工具按名调用,或 Read 其原始 SKILL.md 遵循原义;记调用账本 |
| think | [tw93/Waza](https://github.com/tw93/Waza) | Phase 3 方案分析:单一推荐、Simplicity Gate、premise collapse | 同上 |
| openspec CLI + skills | [Fission-AI/OpenSpec](https://github.com/Fission-AI/OpenSpec) | Phase 5 规格生成(proposal/specs/design/tasks)、validate、变更与归档 | `openspec` CLI 真实执行(new change / instructions / validate) |
「真实调用」的证据是**调用账本**(项目内 `openspec/le-spec/ledger.jsonl`):每次依赖交互记录 dep/kind/method/cmd/exit。审查与交付阶段核对账本,无账本条目视为未发生。协议细节见 [references/dependencies.md](references/dependencies.md)。
## 安装
```bash
# 1. 依赖(一次性;skills.sh 落 ~/.agents/skills,软链各 runtime)
npx skills@latest add mattpocock/skills --skill grilling grill-me grill-with-docs domain-modeling -g -a claude-code codex
npx skills@latest add tw93/Waza --skill think -g -a claude-code codex
npx skills@latest add Fission-AI/OpenSpec --skill openspec-propose openspec-new-change openspec-update-change openspec-verify-change openspec-archive-change openspec-apply-change openspec-explore -g -a claude-code codex
npm install -g @fission-ai/openspec@latest
# 2. 本 skill(MetaSkill 仓库真源 + 软链)
ln -sfn <本仓库路径>/skills/le-spec ~/.agents/skills/le-spec
# 3. 验证
python3 ~/.agents/skills/le-spec/scripts/deps_check.py # required 全绿 exit 0
```
目标项目无需预装 OpenSpec:Phase 5 检测到无根时会征求用户意见再 `openspec init`。
## 使用
自然语言直接开始,例如:
- 「帮我把这个想法整理成 spec:我想做一个……」(零散长文也行,直接贴)
- 「先 grill 我一下,把这个功能想透」
- 「需求变了,更新之前那个 spec」
中断随时可恢复:状态存 `<项目>/openspec/le-spec/state.md`,新会话说「继续上次的 le-spec 分析」即可。产出批准后:实现交给 Coding Agent(openspec-apply-change 工作流),需求变更走 openspec-update-change,完成归档 openspec-archive-change。
## 工作流(逻辑七阶段,按复杂度自适应压缩)
```
0 识别分级 → 1 需求结构化(十类分拣) → 2 Grill 访谈(动态轮次·每4轮检查点·收敛即停)
→ 3 Think 挑战(有分歧才进) → 4 反向验收(独立工程师视角找"还得猜什么")
→ 5 OpenSpec 生成(validate 通过) → 6 四视角独立审查 → 7 用户批准(硬停点)
```
详见 [SKILL.md](SKILL.md) 与 [references/](references/)。
## 安全边界
- 未经用户明确批准不进入实现(Phase 7 回合终止硬停点)。
- required 依赖缺失 → 停止并给安装命令,不以模拟调用冒充。
- 不编造业务规则/验收标准;AI 推荐方案标 [建议],不冒充已批准。
## 已知限制
见 [reports/test-report.md](reports/test-report.md)(测试 A-I 执行记录与未验证项)。
+70
View File
@@ -0,0 +1,70 @@
---
name: le-spec
description: |
Le SPEC —— 智能需求分析与 SPEC 生成总控:把模糊、零散、矛盾的自然语言想法,经真实集成的 Grill 深度访谈、Think 方案挑战、OpenSpec 规格工程,转化为可执行、可测试的正式 SPEC。
何时触发:用户想从想法或零散需求出发,先梳理清楚再开发("帮我把这个想法整理成 spec""我要做个 App,先分析下需求""这些需求有点乱,帮我梳理""先想清楚再写代码""来一场需求访谈""write a spec""refine these requirements");提到 grill / openspec / 需求工程 / SPEC 生成;已有项目新增功能或变更需求要走规格("给这个功能写个 spec""需求变了,更新规格")。
不要触发:已批准 SPEC 的实现与编码(交给 Coding Agent / openspec-apply-change);纯 PRD/需求文档的润色与排版;与需求澄清无关的普通文档写作;用户明说"别做 spec、直接写代码、直接改代码"。
metadata:
author: Leroy
version: "1.0.0"
runtimes: zcode, claude-code, codex
requires: grilling + grill-me/grill-with-docs + domain-modeling (mattpocock/skills), think (tw93/Waza), openspec CLI + openspec-* skills (Fission-AI/OpenSpec)
---
# Le SPEC —— 需求分析与 SPEC 生成总控
用户负责表达意图与做关键决策;你负责理解、挖掘、思考、推荐、整理与验证。
产出物不是"更漂亮的需求文档",而是**在开始编码前把该想清楚的都想清楚**的正式 SPEC。
## 三条铁律(先于一切流程)
1. **真实调用,不冒充。** 访谈必须真实加载 `grilling` 原文并遵循其 design-tree/frontier 机制;方案分析必须真实加载 `think` 原文;规格必须经 `openspec` CLI 生成与校验。禁止用自拟提示词模拟依赖行为。每次依赖交互记入调用账本(见下),无账本条目视为未发生。
2. **不重复、不复盘。** 已确认的决策写入状态文件后,后续任何阶段(Think / 验收 / 审查)只消费不重问;发现新不确定性时做**针对性**追问,不重启流程。
3. **批准前不编码。** Phase 7 是回合终止硬停点:向用户呈交确认包后终止回合,未经用户明确批准,不得创建/修改任何实现代码,不得执行 openspec-apply-change。
## 阶段机
| 阶段 | 做什么 | 依赖调用 | 退出条件 |
|---|---|---|---|
| 0 识别 | 判项目类型、只读检查、复杂度分级 | `openspec context --json`(探测已有根) | 复杂度定档、上下文清单成形 |
| 1 理解 | 原始需求结构化(十类分拣) | — | `state.md` 初始化完成 |
| 2 访谈 | Grill 深度访谈 | **grilling**;已有代码库时叠加 **domain-modeling** | 收敛标准满足(见 workflow.md) |
| 3 方案 | 方案分析与挑战 | **think**(无实质分歧可跳过) | 推荐方案获用户确认 |
| 4 验收 | 需求反向验收(独立工程师视角) | 独立子代理(fresh context) | 无 blocking 级未决问题 |
| 5 规格 | OpenSpec 生成正式 SPEC | **openspec CLI** + openspec-propose/new-change 工作流 | artifacts 齐全且 `validate` 通过 |
| 6 审查 | SPEC 独立质量审查(四视角) | 独立子代理 + `openspec validate` | 审查问题清零或降为非阻塞 |
| 7 交接 | 用户确认与交接 | — | 用户明确批准 → 交给 Coding Agent |
- 每阶段详细规程:[workflow.md](references/workflow.md)
- 依赖真实调用协议:[dependencies.md](references/dependencies.md)(**Phase 2/3/5 入口必读**)
- Phase 4 规程:[acceptance.md](references/acceptance.md);Phase 6 规程:[review.md](references/review.md)
阶段是逻辑流不是机械串行:simple 档压缩执行(见 workflow.md 复杂度分档),用户可随时切换讨论模块、修改需求、查看草稿——但**只能向前跳过 AI 侧环节,不能跳过用户确认停点**。
## 启动协议(每次触发本 skill 时)
1. 定位项目 OpenSpec 根:`openspec context --json`。有根且存在 `<root>/openspec/le-spec/state.md` → 读状态,从其「下一步」恢复,不重做已完成阶段。
2. 无状态文件 → 全新分析:跑 `python3 <skill_dir>/scripts/deps_check.py` 检查依赖(见铁律 1)。缺失 required 级依赖 → 停止并给出精确安装命令(见 dependencies.md),不得以降级模拟继续。
3. 按 Phase 0 → 1 进入。
## 状态与账本(持久化,跨会话恢复)
- `<root>/openspec/le-spec/state.md` —— 阶段、已确认决策、否定方案、开放问题、假设、下一步、访谈轮次计数。每轮重要交互后增量更新;用户变更需求时记录影响范围。
- `<root>/openspec/le-spec/ledger.jsonl` —— 调用账本,经 `scripts/ledger.py append` 记录每次依赖交互(dep / kind / cmd / exit / summary)。Phase 7 交付与 Phase 6 审查以账本为真实调用证据。
- 项目无 OpenSpec 根时暂用 `.le-spec/` 存放,待 Phase 5 init 后迁移。
## 停手点(回合终止硬约束)
1. 依赖检查失败 → 停,给安装指引。
2. Phase 2 每轮问完 → 终止回合等用户回答;Phase 3 推荐方案给出 → 等用户决断。
3. Phase 4 发现 blocking 问题 → 终止回合,向用户澄清后才能进 Phase 5。
4. Phase 7 呈交确认包 → **终止回合**;未获明确批准不得动实现代码。
5. 用户要求删除/重写已批准 SPEC 的大范围内容 → 停,先确认影响范围。
## 反例黑名单
- 不把临时想法当已确认需求,不把 AI 推荐方案当用户已批准。
- 不为凑轮数追问,也不为省事跳过 blocking 问题。
- 不在 SPEC 里编造业务规则、验收标准或技术约束。
- 不把产品需求、技术设计、开发任务混写进一个文件(OpenSpec 三 artifact 天然分离,遵循它)。
- 不在旧会话假设新装依赖"应该可用"——调用前先 deps_check 或确认账本路径可写。
+23
View File
@@ -0,0 +1,23 @@
{
"_说明": "Le SPEC 触发用例。应触发:从想法/零散需求出发要梳理成规格;不应触发:已批准 SPEC 的实现、纯文案、无关任务。family 标注场景族。",
"recommended_threshold": 0.3,
"negative_patterns": ["直接写代码", "直接改代码", "别做 spec", "别做访谈", "润色", "排版", "只要总结", "翻译一下", "帮我实现"],
"should_trigger": [
{"text": "帮我把这个想法整理成 spec,我想做一个记账 App", "family": "idea_to_spec"},
{"text": "这些需求有点乱,你先帮我梳理梳理,想清楚了再动手", "family": "messy_requirements"},
{"text": "我要做个 App,先分析下需求,来一场深度访谈", "family": "explicit_interview"},
{"text": "用 openspec 给这个新功能写个正式的规格", "family": "openspec_named"},
{"text": "先 grill 我一下,把这个方案想透再写规格", "family": "grill_named"},
{"text": "需求变了,把之前批准的 spec 更新一下,加一个导出功能", "family": "spec_change"},
{"text": "refine these rough requirements into a testable spec", "family": "en_trigger"}
],
"should_not_trigger": [
{"text": "spec 已经批了,现在开始实现 tasks 里的任务吧", "family": "implementation_phase"},
{"text": "帮我把这份 PRD 润色一下排版更好看", "family": "doc_polish"},
{"text": "这段代码有个 bug,帮我修一下", "family": "unrelated_task"},
{"text": "别做 spec 了直接改代码,改完告诉我就行", "family": "explicit_skip"}
],
"near_neighbor": [
{"text": "帮我把这个想法整理成 skill", "family": "meta_confusion_should_route_le_meta_skill"}
]
}
+36
View File
@@ -0,0 +1,36 @@
# Phase 4:需求反向验收规程
**问题意识**:Phase 4 检查的不是"文档漂不漂亮",而是**需求是否明确到足以形成规格**。核心一问:
> "一个完全没有参与讨论的资深工程师现在接手开发,哪些重要产品行为仍然需要他自己猜测?哪些必须回头找需求负责人确认?"
## 执行方式(按优先级)
1. **子代理独立审查(首选)**:用 Agent 工具派 fresh-context 子代理(general-purpose 或 Explore)。提示词要点:
- 角色:资深开发工程师,第一次见到这个项目;
- 输入:只给 state.md 路径(+ 已有 openspec specs 路径,如有)——**不给会话历史、不给 Le SPEC 的阶段叙事**,确保视角独立;
- 任务:逐条检查下列清单,输出"我会被迫猜测/必须确认"的清单,每条注明影响哪个功能点与后果严重度;
- 产出格式:问题列表(问题/影响/严重度建议/建议澄清方式)。
2. **无子代理能力时的降级**:自审前显式重置角色("放下前文立场,作为首次接手的工程师仅凭 state.md 判断"),只允许引用 state.md 与项目文件内容。**必须在产出中声明"本轮为同上下文自审,独立性弱于子代理审查"**——不装作独立。
## 检查清单
- [ ] 用户流程有断点(走不到下一步/回不去)
- [ ] 功能之间缺关联规则(A 发生时 B 如何)
- [ ] 数据关系歧义(谁拥有谁、一对多还是多对多、删除级联)
- [ ] 状态机不完整(缺状态、缺转换、缺终态)
- [ ] 权限与业务边界不明(谁能做什么、越界怎样)
- [ ] 异常场景缺处理原则(失败/超时/并发/空数据)
- [ ] 需求间冲突(同一对象两种定义)
- [ ] 未经确认的重要默认假设(state.md 里的 [默认] 项逐条复核用户知情度)
- [ ] 验收判据缺失(做完了怎么算"对")
## 分级与处置
| 级别 | 判定 | 处置 |
|---|---|---|
| blocking | 不澄清就无法实现核心行为 / 两种理解导致完全不同的产品 | 终止回合向用户澄清(用 grilling 单轮格式,记账本) |
| major | 影响正确性但可从已确认决策推导 | 推导入 state.md 并标注依据;推导不了升 blocking |
| minor | 边缘场景、可延后 | 记开放问题,进 SPEC 的"非阻塞事项" |
更新后**只复核受影响项**,不重跑全清单。清零 blocking → 进 Phase 5。
+100
View File
@@ -0,0 +1,100 @@
# 依赖真实调用协议
Le SPEC 的价值建立在三个外部能力**真实运行**之上。本文件定义每个依赖的本体位置、调用条件、调用方式、上下文交接与失败处理。
## 0. 调用方式总则(skill 类依赖)
所有 skill 类依赖(grilling / domain-modeling / think / openspec-*)按以下顺序选择调用方式:
1. **宿主 Skill 工具**:若当前 runtime 提供 Skill 工具且该依赖已在可用列表中,用 Skill 工具按名调用。
2. **文件直读**:否则用 Read 读取依赖原始 `SKILL.md` **全文**,把其内容作为当前回合的执行指令遵循。这不是降级——skill 的本质就是指令文档,宿主 Skill 工具内部做的也是加载原文;直读与工具调用在语义上等价,前提是**读原文、遵循原义**(包括其引用的 references 子文件,如 think 的 mode-evaluation.md)。
两种方式都必须:
- 先记入调用账本(`ledger.py append --dep <name> --kind skill-load --method skill-tool|file-read`);
- 遵循原文机制,不自拟替代提示词(例如 grilling 的 frontier 轮次格式 ❓Q/➡️推荐、think 的 Simplicity Gate、openspec-propose 的 artifact 循环)。
## 1. grilling —— 访谈引擎
| 项 | 值 |
|---|---|
| 来源 | github.com/mattpocock/skills(productivity/grilling) |
| 本体 | `~/.agents/skills/grilling/SKILL.md`(软链解析后读真源) |
| 入口语义 | `grill-me` = 只调 grilling;`grill-with-docs` = grilling + domain-modeling。Le SPEC 按此原义路由:**全新项目 → grilling;已有代码/文档 → grilling + domain-modeling** |
| 调用时机 | Phase 2 全程(simple 档也走它,只是轮数少) |
| 输入交接 | state.md 的「已确认决策」「开放问题」两节——已确认项不得再问;开放问题构成 design tree 的初始节点 |
| 原生机制(必须保留) | design tree、frontier(前置决策已定的可问集合)、每轮问整个 frontier 并给推荐答案、答案重塑树、"事实自己查(可派子代理)、决策问用户"、frontier 空且用户确认共识才算完 |
| 产物 | 每轮问题(❓Q1… ➡️推荐格式);用户回答由 Le SPEC 写入 state.md |
**Le SPEC 叠加规则**(不与原义冲突):
- 每轮用户回答后:决策入 state.md,轮次 +1;每约 4 轮做一次检查点(见 workflow.md Phase 2)。
- grilling 的"会话结束条件 = frontier 空"之外,Le SPEC 用需求收敛标准(workflow.md)做结束判定;两者取先到者,且需向用户确认共识后收口。
- 已有项目时叠加 domain-modeling:访谈中发现术语模糊 → 按其"Sharpen fuzzy language"当场澄清;硬决策按其三条件(难逆转/无上下文会困惑/真实取舍)写 ADR。GLOSSARY.md/ADR 由 domain-modeling 机制管理,Le SPEC 不另建。
## 2. think —— 方案分析引擎
| 项 | 值 |
|---|---|
| 来源 | github.com/tw93/Waza(skills/think) |
| 本体 | `~/.agents/skills/think/SKILL.md` |
| 调用时机 | Phase 3:出现重大产品分歧、技术选型、架构取舍、过度设计疑虑、值不值得判断。**simple 档且无实质争议 → 跳过并注明** |
| 模式选择(遵循其原生定义) | 定义清楚的修复计划 → Lightweight Mode;价值/存废判断 → 读 references/mode-evaluation.md;批量诉求 → mode-triage.md。默认全模式 |
| 输入交接 | state.md 已确认需求 + 约束 + 已否定方案 + 待选方案。think 的 "Durable Context Preflight" 要求读项目 AGENTS.md/CLAUDE.md 索引——照做 |
| 原生机制(必须保留) | 先查官方方案、推荐单一方案+理由+一个替代、premise collapse(最脆弱假设)、attack angles(外部依赖/并发/迁移时)、Simplicity Gate、批准前不动代码 |
| 产物 | 推荐方案 + 关键决策 + 风险 + 被否替代 → 写入 state.md「已确认决策」(待用户批准的标 [待批准]) |
**回流规则**:think 发现新的重大需求不确定性(Blocking ambiguities)→ 带回 Phase 2 用 grilling 格式做**针对性**追问(不是重启访谈),解决后回到 Phase 3 继续。
## 3. OpenSpec —— 规格工程引擎(CLI + skills 双通道)
| 项 | 值 |
|---|---|
| 来源 | github.com/Fission-AI/OpenSpec(npm: @fission-ai/openspec) |
| CLI | `openspec`(PATH 中,≥1.3 可用 artifact 工作流;本 skill 按 1.16.x 验证) |
| skills | `~/.agents/skills/openspec-{propose,new-change,update-change,verify-change,archive-change,apply-change,explore}/SKILL.md` |
| 调用时机 | Phase 0(context 探测)、Phase 5(生成)、Phase 6(validate)、Phase 7 后的变更与归档 |
**Phase 5 标准路径**(遵循 openspec-propose 原生步骤,Le SPEC 已备齐输入故跳过其澄清步):
```bash
openspec context --json # 定位根;root:null → 见下"项目初始化"
openspec new change "<kebab-name>" # 脚手架(默认 spec-driven schema)
openspec status --change "<name>" --json # artifact 顺序与路径
openspec instructions <artifact> --change "<name>" --json # 逐个:proposal → specs → design → tasks
# 按 template + instruction 撰写每个 artifact 文件
openspec validate "<name>" # 必须通过
```
- **artifact 分工是 OpenSpec 原生的**:`specs/` = 产品需求(行为契约、Requirement/Scenario)、`design.md` = 技术设计(HOW,conditional)、`tasks.md` = 开发任务(checkbox)、`proposal.md` = Why/What/Capabilities。不混写。
- **项目初始化边界**(遵循其 Project check 原义):`root:null` 且用户未提 OpenSpec → 停下问用户:init(`openspec init --tools none`,工具注入由用户后续决定)/ 走 store / 不用 OpenSpec 本次降级输出。绝不让 `new change` 的副作用悄悄创建根。
- **变更管理**:SPEC 已批准后的需求变化 → openspec-update-change 工作流(新 change 或修订现有),归档 → openspec-archive-change。Le SPEC 交接到此为止,apply 交给 Coding Agent。
## 4. 依赖检查与失败处理
`scripts/deps_check.py`(--json 供脚本、默认人读输出):
| 依赖 | 级别 | 缺失时 |
|---|---|---|
| grilling skill | required | 停止访谈阶段,输出安装命令(下表),不降级为普通问答 |
| think skill | required(Phase 3 入口) | 方案分歧时停止并提示安装;无分歧场景不受影响 |
| openspec CLI | required(Phase 5 入口) | 停止规格生成,提示 `npm install -g @fission-ai/openspec` |
| openspec-* skills | recommended | CLI 工作流仍可走(Phase 5 标准路径即 CLI 驱动),但提示安装以获得完整工作流指令 |
| domain-modeling skill | recommended(已有项目时升 required) | 已有项目的访谈照常,但声明"术语沉淀与 ADR 不可用" |
| grill-me / grill-with-docs | optional(入口薄壳) | 不影响——Le SPEC 直接路由到底层组合 |
安装命令(真实可执行,2026-11 验证):
```bash
npx skills@latest add mattpocock/skills --skill grilling grill-me grill-with-docs domain-modeling -g
npx skills@latest add tw93/Waza --skill think -g
npx skills@latest add Fission-AI/OpenSpec --skill openspec-propose openspec-new-change openspec-update-change openspec-verify-change openspec-archive-change openspec-apply-change openspec-explore -g
npm install -g @fission-ai/openspec@latest
```
**失败语义**:CLI 非零退出 ≠ 永远失败(如 `openspec list` 在无根项目返回非零,那是"未初始化"的答案不是崩溃)——读 JSON 输出判断,不盲目重试。skill 加载失败(文件不存在/为空)→ 记账本(method=file-read, exit=1)→ 按缺失处理。**任何情况下不得用模拟输出冒充调用成功。**
## 5. 防重复机制
- grilling 问过的 → state.md「已确认决策」,think 直接消费;
- think 已否定的方案 → state.md「已否定方案」,Phase 4/6 审查不再重提,除非发现新证据;
- OpenSpec artifacts 已写内容 → Phase 6 审查对照 state.md 检查一致性,不重新发起需求讨论;
- 每阶段入口先读 state.md 的阶段标记,已完成阶段不重做(中断恢复同理)。
+35
View File
@@ -0,0 +1,35 @@
# Phase 6:SPEC 独立质量审查规程
**问题意识**:Phase 4 检查"需求是否明确到足以形成规格";Phase 6 检查**"规格是否准确到足以指导开发和测试"**。审查对象是 OpenSpec artifacts(proposal/specs/design/tasks),不是重新审需求——需求问题应已在 Phase 4 清零。
## 审查组成(三路并行)
1. **四视角子代理审查**(首选 fresh-context 子代理;降级规则同 acceptance.md,须声明独立性等级)。输入:change 目录全部 artifact 路径 + state.md 路径 + (已有项目时)相关既有 specs 路径。四视角各答各的:
| 视角 | 追问 |
|---|---|
| 产品 | 完整表达真实需求了吗?有无遗漏功能?有无混入用户未确认的新要求(对照 state.md 逐条溯源)? |
| 架构 | 与现有系统/既有 specs 冲突?重大技术风险?不必要的复杂度(对照 design.md 的 Decisions 与 think 结论)? |
| 开发 | 还要猜什么?缺数据/流程/状态定义?互相矛盾的要求(specs 内部 + specs↔design↔tasks)? |
| 测试 | 每条 Requirement 的 Scenario 可执行可判定的吗?正常/异常/边界覆盖?有没有判不了 pass/fail 的模糊表述? |
2. **CLI 校验**:`openspec validate "<change>"`(严格模式可用则用)+ `openspec status --change "<name>"` 确认 artifact 齐全。
3. **账本核对**:`ledger.py check` 确认 grilling/think/openspec 均有真实调用记录;缺失 → 回补(真实调用)或如实声明。
## 产出格式(每问题一行,不许无依据打分)
```markdown
| # | 视角 | 严重度 | 问题(引 artifact 行文) | 影响 | 修正建议 | 阻塞? |
```
严重度:blocker(规格错误/矛盾/无法实现或测试)|major(会导致误解或返工)|minor(措辞与边缘)。
## 修正循环
blocker/major 逐条修正 artifact → 只复验受影响项(重跑 validate + 相关视角复查)→ 清零或全部降为 minor 后出结论:
```markdown
审查结论:blocker 0 / major 0 / minor N(列表,随 SPEC 一并呈交)
```
修正时保持铁律 2:修正只动规格文本与事实呈现,不重启需求讨论;发现规格确实承载不了的需求歧义 → 回 Phase 4 流程处理。
+106
View File
@@ -0,0 +1,106 @@
# 七阶段工作流规程
阶段是逻辑流。每阶段入口先读 state.md 阶段标记,已完成的跳过。
## Phase 0:项目识别与上下文理解
1. 判任务类型:全新产品 / 已有产品加功能 / 已有功能修改 / 复杂业务模块 / 架构重构 / 探索性想法。
2. 有代码库时只读检查(按复杂度控制深度,simple 档可缩到 3-5 分钟扫读):
- 项目结构、技术栈、入口文件;已有文档与 `openspec/specs/`(`openspec context --json` 探测);GLOSSARY/ADR(domain-modeling 的管理物);近期 git log 的设计决策线索。
- **能从代码和文档确认的事实,绝不进访谈问题**(grilling 原义:事实自己查,决策问用户)。
3. 复杂度定档(可随讨论动态升降级,升降级写入 state.md 并注明理由):
| 档 | 特征 | 流程 |
|---|---|---|
| simple | 字段/筛选/文案级修改,影响单一界面或接口 | 0 → 1 → grilling 单轮(≤1 轮)→ (跳过 3)→ 清单式 4 → 5(propose,design 多为 skip)→ 快速 6 → 7 |
| medium | 新功能模块(日历、用户管理级) | 全流程,3 按需 |
| complex | 全新产品、多角色/多流程系统、架构重构 | 全流程;3 必用于重大决策;访谈允许多轮充分展开 |
## Phase 1:原始需求理解
对用户原始输入做**十类分拣**,产出写入 state.md:
1. 明确需求 2. 目标与期望结果 3. 用户角色与场景 4. 未定想法 5. 用户假设 6. 约束(产品+技术) 7. 矛盾描述(列出冲突双方与出处) 8. 重要缺失信息 9. 已否定方案 10. 明确不做
规则:保留原始措辞的关键短语与来源(哪条消息);不擅自增需求;临时想法标注 [未定];AI 推荐方案标注 [建议],不得记为已批准。矛盾不在此阶段裁决——入开放问题,Phase 2 问。
state.md 初始化模板:
```markdown
# Le SPEC 状态 — <项目/功能名>
更新: <日期> | 阶段: Phase 1 | 复杂度: <档>(理由)
## 项目
类型: <全新/加功能/修改/…> | 目标: <一句话>
## 原始输入摘要
- (来源: 首条消息) <要点,保留关键原话>
## 已确认决策
| # | 决策 | 内容 | 来源 | 状态 |
|---|------|------|------|------|
## 已否定方案
- <方案> — 否定原因 <来源>
## 开放问题
- [ ] Q1 <问题>(影响: 核心/边缘;依赖: -)
## 假设(待验证)
- <假设>(依据)
## 访谈计数
轮次: 0 | 检查点: -
## 下一步
- <具体动作>
```
## Phase 2:Grill 深度访谈
入口动作(顺序固定):
1. 读 [dependencies.md](dependencies.md) 第 1 节,按调用方式总则**真实加载 grilling**(已有项目叠加 domain-modeling),记账本。
2. 以 state.md 开放问题 + 十类分拣中的矛盾/缺失为 design tree 初始节点。
访谈中 Le SPEC 叠加于 grilling 原义之上的规则:
- **每轮结束**(用户答完):新决策入表、开放问题更新、轮次 +1、增量写 state.md。
- **检查点**:每逢第 4、8、12…轮答完,附一段 ≤6 行小结(已确认 N 项 / 开放 M 项 / 收敛判断:继续|接近|已达标)。有 blocking 问题默认继续,不要求用户批准继续;已达标主动提议收口。
- **收敛标准**(全部满足才收口):目标明确 | 范围明确(含不做) | 角色明确 | 核心流程闭环 | 业务规则明确 | 数据关系与权限边界明确(适用时) | 异常边界已有处理原则 | 无阻塞实现的重大未决。非关键细节可用「明确标注的默认方案」填充(标注 [默认] 进 state.md),不无限追问。
- **用户控制权**:随时可换模块、改需求(记录影响范围,受影响的已确认项重开并注明)、看草稿、暂停(state.md 存「下一步」)、先谈技术(跳 Phase 3 再回)。
- 关键需求仍有歧义时不得自行拍板;重大未决可出「标注风险的草稿」但明示未经审查。
## Phase 3:Think 方案分析与挑战
1. 无实质方案争议(simple 或方案唯一且用户已定)→ state.md 记"Phase 3 跳过(无分歧)",进 Phase 4。
2. 否则按 [dependencies.md](dependencies.md) 第 2 节**真实加载 think**(含其 Preflight:读项目 AGENTS/CLAUDE 索引与相关规则),记账本。
3. think 产出推荐方案 → 向用户呈现(推荐+理由+一个替代+风险+最脆弱假设)→ **终止回合等决断** → 批准则入 state.md [已批准],新不确定性回流 Phase 2。
## Phase 4:需求反向验收
详见 [acceptance.md](acceptance.md)。核心:fresh-context 子代理扮演"没参与讨论的资深工程师",只读 state.md + 已确认材料,回答"由我实现的话,哪些产品行为还得靠猜?"。产出 blocking / major / minor 问题清单:
- blocking → 以 grilling 单轮格式向用户澄清(记账本 dep=grilling, summary=acceptance-driven)→ 更新后**只复核受影响项**;
- major → 能由已确认决策推导的当场补进 state.md 并标注推导依据,推导不了的升级 blocking;
- minor → 记入开放问题,随 SPEC 的"非阻塞事项"呈现。
清零 blocking 才进 Phase 5。
## Phase 5:OpenSpec 生成正式 SPEC
按 [dependencies.md](dependencies.md) 第 3 节标准路径执行(每条 CLI 命令记账本)。要点:
- change 名 kebab-case,从需求导出;同名已存在 → 问用户续用还是新建(遵循原生 guardrail)。
- 逐 artifact:先 `instructions`,按其 template + instruction 撰写;内容全部来自 state.md(已批准决策/已确认需求/[默认] 项如实标注),**不编造规则**;验收场景用 Requirement/Scenario 结构,可测、可观察。
- `design.md` 遵循其 conditional 规则(跨切面/新依赖/数据模型等才写);Phase 3 有 think 结论的,方案与已否替代直接沉淀为 Decisions。
- `openspec validate` 必须通过;失败按输出修正后重跑。
- SPEC 语言聚焦"系统做什么、表现什么行为、如何判断正确"——实现机制进 design/tasks(OpenSpec config 的 product-language 约定同理)。
## Phase 6:SPEC 独立质量审查
详见 [review.md](review.md)。四视角(产品/架构/开发/测试)独立审查 artifacts + `openspec validate` 复跑 + 账本核对。问题分级(blocker/major/minor)→ 修正 → 只复验受影响项 → 出审查结论(无事实依据的分数不打)。
## Phase 7:用户确认与交接
确认包含:正式 SPEC 位置与摘要、核心功能与范围、关键决策(含被否方案)、明确不做、尚存风险与非阻塞问题、审查结果摘要、账本统计(grilling/think/openspec 各 N 次真实调用)。
**呈交后终止回合。** 用户批准 → 更新 state.md 阶段为"已交接",提示后续:实现用 openspec-apply-change 工作流、需求变化用 openspec-update-change、完成后 openspec-archive-change。用户要改 → 只更新受影响 artifact 并复验(不重跑全流程)。
## 中断恢复
任何会话重入:`openspec context --json` → 读 `<root>/openspec/le-spec/state.md` → 校验其「下一步」与 OpenSpec 实际状态(`openspec status --change` 或 `openspec list --json`)一致 → 从断点继续。state.md 阶段标记与实际不符时以实际产物为准并修正状态。
+6
View File
@@ -0,0 +1,6 @@
# reports/ —— 证据政策
- `test-report.md` —— 测试 A-I 的执行记录、修正记录与已知限制(**入库**:本次交付要求的可复查证据)
- `trigger-eval.json` —— trigger_eval.py 生成物(**不入库**,gitignore:按需重跑即得,遵循仓库先例)
诚实条款:验证不了的写「证据缺失」,不编造;计划中的工作不是证据。测试脚手架项目(/tmp/lespec-test/)不入库,证据以本报告 + 文中账本数字为指针。
+40
View File
@@ -0,0 +1,40 @@
# Le SPEC 测试报告(v1.0.0,2026-11-08)
测试方法:以 fresh-context 子代理为 Le SPEC 执行者、主会话扮演真实用户的多轮对话实测(非单发提示词测试),产物全部可复核。证据分级:[已验证] 有执行记录/文件/账本;[设计优势] 源码可见;[假设] 合理未证。
## 总览
| 测试 | 验证点 | 结果 | 关键证据 |
|---|---|---|---|
| A 零散需求理解 | 混乱输入十类分拣 | ✅ [已验证] | app-idea state.md:矛盾 A(安卓↔iCloud)入开放问题、语音标[未定]、「先不管」搁置不重问 |
| B 全新产品+Grill | grilling 真实加载+frontier 机制 | ✅ [已验证] | 账本 grilling×6(file-read);❓Q/➡️推荐格式、每轮问整 frontier、答案重塑树(Q1-C 解锁 R2) |
| C 已有项目 | 读代码、grill-with-docs、不重复问 | ✅ [已验证] | todo-app:README/代码被读(29 行/数据路径/无账号),零代码可答问题;GLOSSARY 6 术语(domain-modeling×1 账本) |
| D 不合理方案 | think 真实调用+有效挑战 | ✅ [已验证] | 账本 think×2;🥷 前缀/官方方案核查(真实链接)/minimal 选项/premise collapse/被否替代;Redis 被识别为唯一过度点,账号/排序/审计被否 |
| E 深度访谈 | >4 轮、每 4 轮检查点、收敛即停 | ✅ [已验证] | 4 轮(变更延长第 4 轮);R4 后检查点(≤6 行/8/8 收敛/frontier 空);收敛后主动收口不凑轮 |
| F 反向验收 | 独立视角挖隐藏歧义 | ✅ [已验证] | 子代理场景:D25-D27 推导(编辑规则/清单快照/独立性);主会话独立子代理补测:抓到 2 blocking(用量数据模型、Mac 交付范围)+4 major——**独立审查显著强于自审** |
| G SPEC 生成与审查 | openspec 真实工作流 | ✅ [已验证] | 两项目 validate --strict 复跑通过;场景1:6 capability/21 Requirement/46 Scenario;场景2:3 capability/17 tasks;WHEN/THEN 可测场景 |
| H 变更与中断恢复 | 状态存取/局部更新/不重复问 | ✅ [已验证] | R3 变更(+采购清单/−微信)→ 变更日志+D1/D4/D9 影响标注;新会话恢复:21 项决策未重问、断点续问 |
| I 依赖真实性 | 真实运行/缺失提示/无模拟 | ✅ [已验证] | deps_check 缺失场景 exit 1+精确安装命令;账本共 28 条真实调用(grilling 9/think 2/openspec CLI 14/propose 2/domain-modeling 1);CLI exit 1 被正确解读为「未初始化」非崩溃 |
测试项目(脚手架,不入库):/tmp/lespec-test/{app-idea,todo-app}。
## 修正记录(测试驱动)
1. **触发词面缺口**:初版 description 对「PRD 润色」「别做 spec 直接改代码」误触发 → 补负触发词面(润色/排版/直接改代码/别做 spec),trigger_eval 11/11 通过。
2. **ledger.py 元组解包 bug**:append 直接崩溃 → 拆 resolve_ledger(),通过。
3. **ledger 误报**:think 缺失警告未区分「Phase 3 合规跳过」→ 提示语区分,合规跳过需 state.md 有记录佐证。
4. **Phase 4 独立性差异实测**:子代理执行环境(无嵌套 Agent 工具)自动降级自审并声明——机制正确,但自审 vs 独立审查的质量差是真实的(见测试 F),主会话使用时 Agent 工具可用,acceptance.md「首选子代理」规则已经覆盖。
## 已知限制与未验证项(如实声明)
1. **[假设→部分验证] 多 runtime**:ZCode 路径全程实测;Claude Code / Codex 仅建软链未实测(Skill 工具按名调用路径未跑)。
2. **[假设] think 其它模式**:Evaluation / Triage / Lightweight 模式未被单独触发测试(全模式已两轮实测)。
3. **[假设] openspec store**:--store 语义未测(本机无 store 环境)。
4. **[已知局限] 嵌套子代理**:le-spec 跑在子代理内时 Phase 4/6 必然降级自审(子代理无 Agent 工具);主会话直接使用无此限制。
5. **[已知边界] init 授权解释**:用户宽泛授权(「继续吧」)时,执行者可能将授权泛化到 openspec init 决策(场景 1 记录为「用户批准」,场景 2 无授权时正确显式三选一询问)。explicit 判定留给执行 agent,建议用户对 init 表态时明确。
6. **[已知局限] 触发实测深度**:冒烟 11/11;真实对话自动触发未测(本 skill 为本会话期间创建,同会话无法模拟新会话触发——见 le-meta-skill 惯例:skill 触发一次性读入,需新会话验证)。
7. **[已知边界] 访谈轮次上限语义**:未验证 12+ 轮超长访谈的检查点节奏与上下文膨胀行为。
## 结论
九项测试全部通过,其中 A-I 的核心机制均有文件级证据;「真实集成、不冒充」约束由账本+CLI 退出码双重背书。未验证项集中于多 runtime 与依赖 skill 的非主路径模式,已在 README 与本报告声明,不声称已测。
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
"""Le SPEC 依赖检查:skill 目录 + openspec CLI。
用法:
python3 deps_check.py # 人读输出, 退出码 0=required 全就位, 1=有缺失
python3 deps_check.py --json # 机器可读
"""
import argparse
import json
import os
import shutil
import subprocess
import sys
from pathlib import Path
# 依赖清单: (name, level, source, install_cmd)
SKILL_DEPS = [
("grilling", "required", "mattpocock/skills", "npx skills@latest add mattpocock/skills --skill grilling grill-me grill-with-docs domain-modeling -g"),
("domain-modeling", "recommended", "mattpocock/skills", "npx skills@latest add mattpocock/skills --skill domain-modeling -g"),
("grill-me", "optional", "mattpocock/skills", None), # 入口薄壳
("grill-with-docs", "optional", "mattpocock/skills", None), # 入口薄壳
("think", "required", "tw93/Waza", "npx skills@latest add tw93/Waza --skill think -g"),
("openspec-propose", "recommended", "Fission-AI/OpenSpec", "npx skills@latest add Fission-AI/OpenSpec --skill openspec-propose openspec-new-change openspec-update-change openspec-verify-change openspec-archive-change openspec-apply-change openspec-explore -g"),
("openspec-new-change", "recommended", "Fission-AI/OpenSpec", None),
("openspec-update-change", "recommended", "Fission-AI/OpenSpec", None),
("openspec-verify-change", "recommended", "Fission-AI/OpenSpec", None),
("openspec-archive-change", "recommended", "Fission-AI/OpenSpec", None),
]
# 候选 skill 根目录(顺序即优先级); 可用 LESPEC_SKILL_PATHS 覆盖, 冒号分隔
DEFAULT_ROOTS = [
"~/.agents/skills",
"~/.zcode/skills",
"~/.claude/skills",
"~/.codex/skills",
]
def skill_roots():
env = os.environ.get("LESPEC_SKILL_PATHS")
if env:
return [Path(p).expanduser() for p in env.split(":") if p.strip()]
return [Path(p).expanduser() for p in DEFAULT_ROOTS]
def find_skill(name):
"""返回 (resolved_skill_md_path | None, root_dir)。软链解析到真源。"""
for root in skill_roots():
cand = root / name / "SKILL.md"
if cand.exists():
try:
return cand.resolve(), root
except OSError:
return cand, root
return None, None
def check_openspec_cli():
exe = shutil.which("openspec")
if not exe:
return {"name": "openspec-cli", "level": "required", "ok": False,
"detail": "openspec not in PATH",
"install": "npm install -g @fission-ai/openspec@latest"}
try:
out = subprocess.run([exe, "--version"], capture_output=True, text=True,
timeout=15)
ver = (out.stdout or out.returncode and out.stderr or "").strip().splitlines()[-1] \
if (out.stdout or out.stderr) else "?"
return {"name": "openspec-cli", "level": "required", "ok": out.returncode == 0,
"detail": f"{exe} (v{ver})",
"install": "npm install -g @fission-ai/openspec@latest"}
except Exception as e: # timeout / spawn 失败
return {"name": "openspec-cli", "level": "required", "ok": False,
"detail": f"exec failed: {e}",
"install": "npm install -g @fission-ai/openspec@latest"}
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--json", action="store_true", help="JSON 输出")
args = ap.parse_args()
results = []
for name, level, source, install in SKILL_DEPS:
path, root = find_skill(name)
results.append({
"name": name, "kind": "skill", "level": level,
"ok": path is not None,
"detail": str(path) if path else f"not found under {':'.join(str(r) for r in skill_roots())}",
"source": source, "install": install,
})
results.append(check_openspec_cli())
missing_required = [r for r in results if not r["ok"] and r["level"] == "required"]
missing_recommended = [r for r in results if not r["ok"] and r["level"] == "recommended"]
if args.json:
print(json.dumps({
"ok": not missing_required,
"missing_required": [r["name"] for r in missing_required],
"missing_recommended": [r["name"] for r in missing_recommended],
"items": results,
}, ensure_ascii=False, indent=2))
else:
mark = lambda r: "✅" if r["ok"] else ("❌" if r["level"] == "required" else "⚠️ ")
print(f"{'':2}{'状态':4} {'级别':12} {'依赖':24} 详情")
for r in results:
print(f"{mark(r)} {r['level']:<12} {r['name']:<24} {r['detail']}")
print()
if not missing_required and not missing_recommended:
print("全部依赖就位。")
for r in missing_required + missing_recommended:
if r.get("install"):
print(f"补装 {r['name']}: {r['install']}")
print("提示: optional 级(grill-me/grill-with-docs)是入口薄壳, 缺失不影响运行。")
sys.exit(1 if missing_required else 0)
if __name__ == "__main__":
main()
+143
View File
@@ -0,0 +1,143 @@
#!/usr/bin/env python3
"""Le SPEC 调用账本: 记录每次依赖真实交互, 供审查与交付作证据。
用法:
python3 ledger.py append --dep grilling --kind skill-load --method file-read \
--summary "Phase2 R1 frontier 4问" [--cmd "..."] [--exit 0] [--file PATH]
python3 ledger.py check [--file PATH] [--json]
python3 ledger.py tail [-n 5] [--file PATH]
账本定位优先级: --file > openspec root 下 openspec/le-spec/ledger.jsonl > ./.le-spec/ledger.jsonl
"""
import argparse
import datetime
import json
import subprocess
import sys
from pathlib import Path
KINDS = {"skill-load", "cli", "subagent"}
METHODS = {"skill-tool", "file-read", "bash", "agent"}
def default_ledger():
try:
out = subprocess.run(["openspec", "context", "--json"],
capture_output=True, text=True, timeout=15)
ctx = json.loads(out.stdout or "{}")
root = (ctx.get("root") or {}).get("path")
if root:
p = Path(root) / "openspec" / "le-spec" / "ledger.jsonl"
return p, True
except Exception:
pass
return Path.cwd() / ".le-spec" / "ledger.jsonl", False
def resolve_ledger(args):
"""返回 (path, has_openspec_root)。"""
if getattr(args, "file", None):
return Path(args.file).expanduser(), None
return default_ledger()
def cmd_append(args):
if args.kind not in KINDS or args.method not in METHODS:
print(f"kind 须为 {KINDS}, method 须为 {METHODS}", file=sys.stderr)
return 2
path, has_root = resolve_ledger(args)
path.parent.mkdir(parents=True, exist_ok=True)
entry = {
"ts": datetime.datetime.now().isoformat(timespec="seconds"),
"phase": args.phase, "dep": args.dep, "kind": args.kind,
"method": args.method, "cmd": args.cmd, "exit": args.exit,
"summary": args.summary,
}
with path.open("a", encoding="utf-8") as f:
f.write(json.dumps({k: v for k, v in entry.items() if v is not None},
ensure_ascii=False) + "\n")
print(f"账本已记: {path}")
if has_root is False and not args.file:
print("(未找到 openspec root, 暂写 .le-spec/; Phase 5 init 后迁移)", file=sys.stderr)
return 0
def _load(path):
if not path.exists():
return []
rows = []
for line in path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if line:
try:
rows.append(json.loads(line))
except json.JSONDecodeError:
rows.append({"_unparsed": line})
return rows
def cmd_check(args):
path, _ = resolve_ledger(args)
rows = _load(path)
by_dep = {}
for r in rows:
d = by_dep.setdefault(r.get("dep", "?"), {"count": 0, "last": None, "kinds": {}})
d["count"] += 1
d["last"] = r.get("ts", d["last"])
d["kinds"][r.get("kind", "?")] = d["kinds"].get(r.get("kind", "?"), 0) + 1
if args.json:
print(json.dumps({"ledger": str(path), "total": len(rows), "by_dep": by_dep},
ensure_ascii=False, indent=2))
else:
print(f"账本: {path} | 总计 {len(rows)} 条")
for dep, d in sorted(by_dep.items()):
kinds = ", ".join(f"{k}×{v}" for k, v in d["kinds"].items())
print(f" {dep:<16} {d['count']:>3} 次 ({kinds}) 末次 {d['last']}")
expected = ["grilling", "think", "openspec"]
missing = [e for e in expected if e not in by_dep]
if missing:
hint = {"think": "(若 Phase 3 合规跳过——无实质方案分歧——属正常,确认 state.md 有跳过记录即可)"}
for m in missing:
print(f"⚠️ 未见 {m} 的调用记录 {hint.get(m, '')} — 否则 Phase 6 审查前需真实补调或如实声明")
return 0
def cmd_tail(args):
path, _ = resolve_ledger(args)
rows = _load(path)[-args.n:]
for r in rows:
print(json.dumps(r, ensure_ascii=False))
return 0
def main():
ap = argparse.ArgumentParser()
sub = ap.add_subparsers(dest="cmd", required=True)
a = sub.add_parser("append")
a.add_argument("--dep", required=True)
a.add_argument("--kind", required=True)
a.add_argument("--method", required=True)
a.add_argument("--phase")
a.add_argument("--cmd")
a.add_argument("--exit", type=int)
a.add_argument("--summary")
a.add_argument("--file")
a.set_defaults(fn=cmd_append)
c = sub.add_parser("check")
c.add_argument("--file")
c.add_argument("--json", action="store_true")
c.set_defaults(fn=cmd_check)
t = sub.add_parser("tail")
t.add_argument("-n", type=int, default=5)
t.add_argument("--file")
t.set_defaults(fn=cmd_tail)
args = ap.parse_args()
sys.exit(args.fn(args))
if __name__ == "__main__":
main()