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)
This commit is contained in:
@@ -25,3 +25,6 @@ __pycache__/
|
|||||||
|
|
||||||
# le-app-design:trigger_eval.py 每次运行会重建 reports/trigger-eval.json(生成物,按需重跑即得,不入库)
|
# le-app-design:trigger_eval.py 每次运行会重建 reports/trigger-eval.json(生成物,按需重跑即得,不入库)
|
||||||
skills/le-app-design/reports/
|
skills/le-app-design/reports/
|
||||||
|
|
||||||
|
# le-spec:同上,trigger-eval.json 为生成物不入库;test-report.md 为交付证据入库
|
||||||
|
skills/le-spec/reports/trigger-eval.json
|
||||||
|
|||||||
@@ -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 执行记录与未验证项)。
|
||||||
@@ -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 或确认账本路径可写。
|
||||||
@@ -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"}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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。
|
||||||
@@ -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 的阶段标记,已完成阶段不重做(中断恢复同理)。
|
||||||
@@ -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 流程处理。
|
||||||
@@ -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 阶段标记与实际不符时以实际产物为准并修正状态。
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# reports/ —— 证据政策
|
||||||
|
|
||||||
|
- `test-report.md` —— 测试 A-I 的执行记录、修正记录与已知限制(**入库**:本次交付要求的可复查证据)
|
||||||
|
- `trigger-eval.json` —— trigger_eval.py 生成物(**不入库**,gitignore:按需重跑即得,遵循仓库先例)
|
||||||
|
|
||||||
|
诚实条款:验证不了的写「证据缺失」,不编造;计划中的工作不是证据。测试脚手架项目(/tmp/lespec-test/)不入库,证据以本报告 + 文中账本数字为指针。
|
||||||
@@ -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 与本报告声明,不声称已测。
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user