Compare commits
4 Commits
a21ca37671
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 0760665ad5 | |||
| e724df5c11 | |||
| 06aa3438b9 | |||
| 928cfa393f |
@@ -62,6 +62,9 @@ description: >-
|
|||||||
- 建议的 Skill 名称
|
- 建议的 Skill 名称
|
||||||
3. 创建完成后,要求用户对新 Skill 进行实际测试
|
3. 创建完成后,要求用户对新 Skill 进行实际测试
|
||||||
4. 测试通过后,执行操作 C 同步 Registry
|
4. 测试通过后,执行操作 C 同步 Registry
|
||||||
|
5. 【强制】执行操作 D——运行 Layer 1 静态检查,确保所有耦合点已同步
|
||||||
|
→ 全部通过则创建流程完成
|
||||||
|
→ 有 FAIL 则回到对应步骤修复后重新运行
|
||||||
```
|
```
|
||||||
|
|
||||||
### b) 延期记录流程
|
### b) 延期记录流程
|
||||||
@@ -95,6 +98,30 @@ description: >-
|
|||||||
|
|
||||||
参见 [registry.md](registry.md) 中的条目结构。
|
参见 [registry.md](registry.md) 中的条目结构。
|
||||||
|
|
||||||
|
## 操作 D:耦合点完整性验证
|
||||||
|
|
||||||
|
### 触发条件
|
||||||
|
|
||||||
|
- 操作 A(Skill 创建)完成后
|
||||||
|
- 操作 C(Registry 同步)后
|
||||||
|
- 任何 Skill 的 SKILL.md 被实质性修改后
|
||||||
|
- 用户说"验证 Skill 耦合"、"检查 Skill 是否同步"
|
||||||
|
|
||||||
|
### 流程
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 运行 Layer 1 静态检查:
|
||||||
|
python .opencode/skills/skill-tester/static-checks/run.py
|
||||||
|
|
||||||
|
2. 全部 PASS(退出码 0):
|
||||||
|
→ 告知用户"所有耦合点已同步,7/7 通过"
|
||||||
|
|
||||||
|
3. 有 FAIL(退出码非 0):
|
||||||
|
→ 逐条展示失败项
|
||||||
|
→ 根据提示定位缺失的同步点(manifest / init / registry / SKILL.md frontmatter)
|
||||||
|
→ 修复后重新运行,直到全部通过
|
||||||
|
```
|
||||||
|
|
||||||
## Skill 创建价值判断标准
|
## Skill 创建价值判断标准
|
||||||
|
|
||||||
当操作 A 无匹配时,Agent 使用以下标准评估是否建议创建新 Skill。
|
当操作 A 无匹配时,Agent 使用以下标准评估是否建议创建新 Skill。
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
- **备注**: 操作 B 具备 changelog 不可信假设与代码搜索 fallback 的互斥检测能力
|
- **备注**: 操作 B 具备 changelog 不可信假设与代码搜索 fallback 的互斥检测能力
|
||||||
|
|
||||||
### game-design
|
### game-design
|
||||||
- **类型**: 项目级
|
- **类型**: 基础设施
|
||||||
- **能力**: 游戏设计全流程辅助系统 — 从创意概念到设计大纲,通过参考游戏分析(GDD 逆向 + 社群口碑)、3 轮 × 5 题初步问答、一致性审查、3+ 轮深度问答,最终产出包含 VISTA 框架的设计大纲
|
- **能力**: 游戏设计全流程辅助系统 — 从创意概念到设计大纲,通过参考游戏分析(GDD 逆向 + 社群口碑)、3 轮 × 5 题初步问答、一致性审查、3+ 轮深度问答,最终产出包含 VISTA 框架的设计大纲
|
||||||
- **触发场景**: "我想做一个xxx游戏"、"设计一个xxx玩法"、"帮我设计游戏"、用户提出游戏创意时;仅在 `design` 阶段可用
|
- **触发场景**: "我想做一个xxx游戏"、"设计一个xxx玩法"、"帮我设计游戏"、用户提出游戏创意时;仅在 `design` 阶段可用
|
||||||
- **输出**: `.opencode/phase/data/design/` 下的 state.json、concept.md、reference-analysis/*.md、qa-initial/*.md、qa-deep/*.md、design-outline.md、qa-quality-report.md
|
- **输出**: `.opencode/phase/data/design/` 下的 state.json、concept.md、reference-analysis/*.md、qa-initial/*.md、qa-deep/*.md、design-outline.md、qa-quality-report.md
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ description: >-
|
|||||||
| 步骤 | 文件 |
|
| 步骤 | 文件 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| 步骤 1-2:参考游戏发现 | `templates/reference-search-prompt.md` |
|
| 步骤 1-2:参考游戏发现 | `templates/reference-search-prompt.md` |
|
||||||
| 步骤 3:反拆分析 | `prompts/sub-agent-gdd-reverse.md` + `prompts/sub-agent-community.md` |
|
| 步骤 3:反拆分析 | `prompts/sub-agent-gdd-reverse.md` + `prompts/sub-agent-community.md` → 执行后必须输出摘要到对话窗口(见规则 7) |
|
||||||
| 步骤 4:初步问答 | `templates/qa-initial-questions.md` |
|
| 步骤 4:初步问答 | `templates/qa-initial-questions.md` |
|
||||||
| 步骤 5:深度问答 | `templates/qa-deep-guide.md` |
|
| 步骤 5:深度问答 | `templates/qa-deep-guide.md` |
|
||||||
| 设计前提挑战 | `templates/devils-advocate.md` |
|
| 设计前提挑战 | `templates/devils-advocate.md` |
|
||||||
@@ -145,7 +145,18 @@ description: >-
|
|||||||
3. **可回答性**:用户不需要专业知识就能回答
|
3. **可回答性**:用户不需要专业知识就能回答
|
||||||
4. **来源标注**:问题基于步骤 3 的哪条分析?置信度如何?
|
4. **来源标注**:问题基于步骤 3 的哪条分析?置信度如何?
|
||||||
|
|
||||||
## 规则 7:进度提示
|
## 规则 7:Sub-agent 输出双通道
|
||||||
|
|
||||||
|
Sub-agent 执行完成后,产物必须同时走两条通道:
|
||||||
|
1. **文件通道(固化)**:完整报告写入目标文件,供跨会话引用
|
||||||
|
2. **对话通道(摘要)**:将报告的核心结论以摘要形式直接输出到对话窗口中,让用户不需要打开文件就能理解调研结果
|
||||||
|
|
||||||
|
摘要要求:
|
||||||
|
- GDD 逆向摘要:列出 6 个研究维度各 1 句核心发现 + 开创性设计点
|
||||||
|
- 社群分析摘要:TOP 3 好评/差评各 1 句话 + 1 个最关键的品类迭代洞察
|
||||||
|
- 在摘要末尾注明文件路径,方便用户查阅完整报告
|
||||||
|
|
||||||
|
## 规则 8:进度提示
|
||||||
|
|
||||||
Sub-agent 执行期间不得沉默:
|
Sub-agent 执行期间不得沉默:
|
||||||
- 开始前:告知用户 sub-agent 正在执行什么任务,预计用时
|
- 开始前:告知用户 sub-agent 正在执行什么任务,预计用时
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
在正式问答前,用 3-5 个挑战性问题检验用户核心设计前提的稳健性。
|
在正式问答前,用 3-5 个挑战性问题检验用户核心设计前提的稳健性。
|
||||||
这不是否定用户的想法,而是帮助发现被忽略的盲区。
|
这不是否定用户的想法,而是帮助发现被忽略的盲区。
|
||||||
|
|
||||||
|
**与步骤 4 问答的关键区别**:
|
||||||
|
- 步骤 4 是**选择题**(提供选项,引导用户发现自己偏好)
|
||||||
|
- Devil's Advocate 是**开放题**(不提供选项,要求用户主动思考)
|
||||||
|
- 给选项等于替用户做设计决策,会削弱"挑战前提"的效果
|
||||||
|
|
||||||
## 语气约束
|
## 语气约束
|
||||||
|
|
||||||
- 使用"挑战前提"而非"质疑"
|
- 使用"挑战前提"而非"质疑"
|
||||||
@@ -33,8 +38,25 @@
|
|||||||
| 可行性 | "参考游戏需要 [资源量] 的制作规模。以你预期的团队规模,这个方向的可实现性你如何评估?" |
|
| 可行性 | "参考游戏需要 [资源量] 的制作规模。以你预期的团队规模,这个方向的可实现性你如何评估?" |
|
||||||
| 差异化盲区 | "如果去掉参考游戏的 [核心特征],你的游戏剩下什么?这可能是你最需要想清楚的部分。" |
|
| 差异化盲区 | "如果去掉参考游戏的 [核心特征],你的游戏剩下什么?这可能是你最需要想清楚的部分。" |
|
||||||
|
|
||||||
## 用户回答后的处理
|
### 问题格式(强制)
|
||||||
|
|
||||||
- 有说服力的回应 → 将其作为设计约束,注入步骤 4 的问题生成
|
- **不提供预设选项**——开放性问题,要求用户主动思考
|
||||||
- 回避/模糊的回应 → 记录为"弱信号",带入一致性审查
|
- 每条问题末尾补充:"不需要完美答案,诚实的'不确定'也是有效输入"
|
||||||
- 用户要求跳过 → 跳过,但记录"用户选择跳过挑战环节"
|
- 在 5 个问题开头统一说明尾部兜底提醒
|
||||||
|
|
||||||
|
## 输出格式
|
||||||
|
|
||||||
|
```
|
||||||
|
现在进入设计前提挑战(Devil's Advocate)。基于分析结果,我提出 3-5 个挑战性问题。
|
||||||
|
|
||||||
|
对于每个问题,不需要完美答案——诚实的"不确定"也是有效输入。
|
||||||
|
如果觉得某个问题的前提不成立,也可以直接指出。
|
||||||
|
|
||||||
|
---
|
||||||
|
[级别: 🔴/🟠/🟡] 挑战 N:[标题]
|
||||||
|
[背景依据]
|
||||||
|
问题:[具体挑战]
|
||||||
|
---
|
||||||
|
|
||||||
|
请逐一回应。有说服力的回应会被记录为设计约束,带入下一步的初步问答。
|
||||||
|
```
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ init 后用户说"补一下 gitignore"或"生成 gitignore" → 只跑阶段 5.5
|
|||||||
2. 用 shell 列出 .opencode 下所有文件(含子目录)
|
2. 用 shell 列出 .opencode 下所有文件(含子目录)
|
||||||
3. 把每个文件对照 manifest.json 分成三组:
|
3. 把每个文件对照 manifest.json 分成三组:
|
||||||
- A 组(canonical):路径在 canonical_root / canonical_opencode_top / canonical_skills 中,原样保留
|
- A 组(canonical):路径在 canonical_root / canonical_opencode_top / canonical_skills 中,原样保留
|
||||||
- B 组(template-data):路径在 template_data_files 中,需要删除或重置为模板
|
- B 组(template-data):路径在 template_data_files 或 template_phase_files 中,需要删除或重置为模板
|
||||||
- C 组(foreign):manifest 三组之外的所有路径,视作未知项
|
- C 组(foreign):manifest 三组之外的所有路径,视作未知项
|
||||||
4. 把分组结果用表格汇报给用户:
|
4. 把分组结果用表格汇报给用户:
|
||||||
| 组 | 路径 | 处理动作 |
|
| 组 | 路径 | 处理动作 |
|
||||||
@@ -161,6 +161,13 @@ init 后用户说"补一下 gitignore"或"生成 gitignore" → 只跑阶段 5.5
|
|||||||
| `.opencode/data/profile/project-profile.md` | project-profile 模板 |
|
| `.opencode/data/profile/project-profile.md` | project-profile 模板 |
|
||||||
| `.opencode/data/profile/project-profile-log.md` | project-profile-log 模板 |
|
| `.opencode/data/profile/project-profile-log.md` | project-profile-log 模板 |
|
||||||
| `.opencode/data/spec-docs/state.json` | state.json 模板 |
|
| `.opencode/data/spec-docs/state.json` | state.json 模板 |
|
||||||
|
| `.opencode/phase/current.json` | current.json 模板 |
|
||||||
|
| `.opencode/phase/data/design/state.json` | state.json 模板 |
|
||||||
|
| `.opencode/phase/data/design/concept.md` | concept.md 模板 |
|
||||||
|
| `.opencode/phase/data/design/design-outline.md` | design-outline.md 模板 |
|
||||||
|
| `.opencode/phase/data/design/qa-quality-report.md` | qa-quality-report.md 模板 |
|
||||||
|
| `.opencode/phase/data/design/reference-analysis/gdd-reverse.md` | gdd-reverse.md 模板 |
|
||||||
|
| `.opencode/phase/data/design/reference-analysis/community-iteration.md` | community-iteration.md 模板 |
|
||||||
|
|
||||||
执行要点:
|
执行要点:
|
||||||
- 每个重置前**先 Read 现有内容预览前 10 行**给用户,避免误删有价值数据(特别是 deferred/pitfalls 用户可能想保留)
|
- 每个重置前**先 Read 现有内容预览前 10 行**给用户,避免误删有价值数据(特别是 deferred/pitfalls 用户可能想保留)
|
||||||
@@ -302,6 +309,8 @@ init 后用户说"补一下 gitignore"或"生成 gitignore" → 只跑阶段 5.5
|
|||||||
- `.opencode/skills/problem-distillery/`
|
- `.opencode/skills/problem-distillery/`
|
||||||
- `.opencode/skills/profile-memory/`
|
- `.opencode/skills/profile-memory/`
|
||||||
- `.opencode/skills/spec-docs/`
|
- `.opencode/skills/spec-docs/`
|
||||||
|
- `.opencode/skills/game-design/`
|
||||||
|
- `.opencode/skills/skill-tester/`
|
||||||
|
|
||||||
> **任何不在此白名单中的 skill 目录**,在阶段 3 中一律删除(视为源项目的项目级 Skill)。
|
> **任何不在此白名单中的 skill 目录**,在阶段 3 中一律删除(视为源项目的项目级 Skill)。
|
||||||
|
|
||||||
@@ -314,6 +323,16 @@ init 后用户说"补一下 gitignore"或"生成 gitignore" → 只跑阶段 5.5
|
|||||||
- `.opencode/data/profile/{user-profile.md, user-profile-log.md, project-profile.md, project-profile-log.md}`
|
- `.opencode/data/profile/{user-profile.md, user-profile-log.md, project-profile.md, project-profile-log.md}`
|
||||||
- `.opencode/data/spec-docs/state.json`
|
- `.opencode/data/spec-docs/state.json`
|
||||||
|
|
||||||
|
### 阶段数据占位目录(保留目录,文件重置为模板内容)
|
||||||
|
|
||||||
|
- `.opencode/phase/current.json`
|
||||||
|
- `.opencode/phase/data/design/state.json`
|
||||||
|
- `.opencode/phase/data/design/concept.md`
|
||||||
|
- `.opencode/phase/data/design/design-outline.md`
|
||||||
|
- `.opencode/phase/data/design/qa-quality-report.md`
|
||||||
|
- `.opencode/phase/data/design/reference-analysis/gdd-reverse.md`
|
||||||
|
- `.opencode/phase/data/design/reference-analysis/community-iteration.md`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 清理清单(cleanup list)
|
## 清理清单(cleanup list)
|
||||||
@@ -483,6 +502,90 @@ last_distill_date: {{DATE}}
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### phase/current.json
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"phase": "design",
|
||||||
|
"switched_at": null,
|
||||||
|
"previous_phase": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### phase/data/design/state.json
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"workflow_state": "WAITING_FOR_CONCEPT",
|
||||||
|
"concept_summary": "",
|
||||||
|
"reference_games": [],
|
||||||
|
"in_progress": {
|
||||||
|
"active": false,
|
||||||
|
"operation": null,
|
||||||
|
"started_at": null,
|
||||||
|
"detail": null
|
||||||
|
},
|
||||||
|
"checkpoints": [],
|
||||||
|
"sessions": [],
|
||||||
|
"created_at": null,
|
||||||
|
"updated_at": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### phase/data/design/concept.md
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# 游戏概念
|
||||||
|
|
||||||
|
(设计阶段步骤 1 的用户原始概念记录)
|
||||||
|
```
|
||||||
|
|
||||||
|
### phase/data/design/design-outline.md
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# 游戏设计大纲
|
||||||
|
|
||||||
|
(设计阶段完成后由 game-design Skill 生成)
|
||||||
|
|
||||||
|
详见 `## 四、开发阶段交接` 章节供开发阶段 Agent 参考。
|
||||||
|
```
|
||||||
|
|
||||||
|
### phase/data/design/qa-quality-report.md
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# 问答质量评估报告
|
||||||
|
|
||||||
|
本文件记录设计工作流中每轮问答的质量评估结果。每次评估追加,不覆盖。
|
||||||
|
|
||||||
|
## 评估维度
|
||||||
|
|
||||||
|
- **劣质问题类型**:引导性 / 过于抽象 / 遗漏选项 / 不可回答 / 基于推测
|
||||||
|
- **问题原文**:保留用户质疑的原问题
|
||||||
|
- **用户反馈**:用户的具体质疑或自定义回答
|
||||||
|
- **优化方案**:改进后的提问方式
|
||||||
|
- **通用建议**:可跨项目复用的经验
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 质量报告
|
||||||
|
```
|
||||||
|
|
||||||
|
### phase/data/design/reference-analysis/gdd-reverse.md
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# GDD 逆向分析
|
||||||
|
|
||||||
|
(game-design Skill 步骤 3 sub-agent A 产物)
|
||||||
|
```
|
||||||
|
|
||||||
|
### phase/data/design/reference-analysis/community-iteration.md
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# 社群口碑与品类迭代分析
|
||||||
|
|
||||||
|
(game-design Skill 步骤 3 sub-agent B 产物)
|
||||||
|
```
|
||||||
|
|
||||||
### .init-done
|
### .init-done
|
||||||
|
|
||||||
阶段 7 写入的 sentinel,YAML 格式,字段固定:
|
阶段 7 写入的 sentinel,YAML 格式,字段固定:
|
||||||
@@ -864,12 +967,13 @@ public/build/
|
|||||||
|
|
||||||
| # | 检核项 | 操作 |
|
| # | 检核项 | 操作 |
|
||||||
|---|--------|------|
|
|---|--------|------|
|
||||||
| 2.1 | `canonical-manifest.json` → `template_data_files` | **首选更新目标**:在 JSON 数组中追加新数据文件路径 |
|
| 2.1 | `canonical-manifest.json` → `template_data_files` 或 `template_phase_files` | **首选更新目标**:在 JSON 对应数组中追加新数据文件路径。phase 阶段数据文件归入 `template_phase_files`,其余归入 `template_data_files` |
|
||||||
| 2.2 | `opencode-init` 规范清单 → 数据占位目录 | 在 `### 数据占位目录` 下新增路径(与 2.1 保持同步) |
|
| 2.2 | `opencode-init` 规范清单 → 数据占位目录 / 阶段数据占位目录 | 在对应目录下新增路径(与 2.1 保持同步) |
|
||||||
| 2.3 | `opencode-init` 清理清单 → B 组重置列表 | 在阶段 3 的 B 组表格中新增一行 |
|
| 2.3 | `opencode-init` 清理清单 → B 组重置列表 | 在阶段 3 的 B 组表格中新增一行 |
|
||||||
| 2.4 | `opencode-init` → 模板内容 | 新增该文件的模板内容(最小化版本,含占位注释) |
|
| 2.4 | `opencode-init` → 模板内容 | 新增该文件的模板内容(最小化版本,含占位注释) |
|
||||||
| 2.5 | `opencode.json` → `instructions` | 若该数据文件应注入会话上下文,追加到 `instructions` 数组 |
|
| 2.5 | `opencode.json` → `instructions` | 若该数据文件应注入会话上下文,追加到 `instructions` 数组 |
|
||||||
| 2.6 | 所属 Skill 的 SKILL.md | 若数据文件归属某个 Skill,该 Skill 应引用正确的文件路径 |
|
| 2.6 | `skill-tester/static-checks/run.py` → `MANIFEST_TO_INIT_SECTION` | 若新增了 manifest JSON 分组(非单文件),在 run.py 的映射表中新增条目。meta-check 会自动检测未映射的分组 |
|
||||||
|
| 2.7 | 所属 Skill 的 SKILL.md | 若数据文件归属某个 Skill,该 Skill 应引用正确的文件路径 |
|
||||||
|
|
||||||
#### 类型 3:新增运行时依赖
|
#### 类型 3:新增运行时依赖
|
||||||
|
|
||||||
@@ -925,10 +1029,11 @@ public/build/
|
|||||||
|------|---------|
|
|------|---------|
|
||||||
| `canonical-manifest.json` VS `canonical_skills` | glob `.opencode/skills/*/` → 不在 manifest 中且非项目级 → 警告 |
|
| `canonical-manifest.json` VS `canonical_skills` | glob `.opencode/skills/*/` → 不在 manifest 中且非项目级 → 警告 |
|
||||||
| `canonical-manifest.json` VS `template_data_files` | glob `.opencode/data/**/*` → 不在 manifest 中 → 警告 |
|
| `canonical-manifest.json` VS `template_data_files` | glob `.opencode/data/**/*` → 不在 manifest 中 → 警告 |
|
||||||
|
| `canonical-manifest.json` VS `template_phase_files` | glob `.opencode/phase/**/*.md` + `.opencode/phase/*.json` → 不在 manifest 中 → 警告 |
|
||||||
| `canonical-manifest.json` VS disk | manifest 中的路径 → glob/read 验证文件存在 |
|
| `canonical-manifest.json` VS disk | manifest 中的路径 → glob/read 验证文件存在 |
|
||||||
| Skill frontmatter 完整性 | 遍历所有 `SKILL.md` → 验证第一行 `---`、含 `name:` 字段、正确闭合 |
|
| Skill frontmatter 完整性 | 遍历所有 `SKILL.md` → 验证第一行 `---`、含 `name:` 字段、正确闭合 |
|
||||||
| `AGENTS.md` 引用的 Skill | grep Skill 名 → 必须在 `canonical-manifest.json` → `canonical_skills` 中 |
|
| `AGENTS.md` 引用的 Skill | grep Skill 名 → 必须在 `canonical-manifest.json` → `canonical_skills` 中 |
|
||||||
| `AGENTS.md` 引用的数据文件 | grep 文件路径 → 必须在 `canonical-manifest.json` → `template_data_files` 中 |
|
| `AGENTS.md` 引用的数据文件 | grep 文件路径 → 必须在 `canonical-manifest.json` → `template_data_files` 或 `template_phase_files` 中 |
|
||||||
| `AGENTS.md` 引用的 docs/ 文档 | glob 检查文件存在 |
|
| `AGENTS.md` 引用的 docs/ 文档 | glob 检查文件存在 |
|
||||||
| `opencode.json` → `instructions` 所列路径 | `read` 逐条验证文件存在 |
|
| `opencode.json` → `instructions` 所列路径 | `read` 逐条验证文件存在 |
|
||||||
| `opencode.json` → `instructions` 所列路径 | 必须在 `canonical-manifest.json` 的 `template_data_files` 或 `canonical_opencode_top` 中 |
|
| `opencode.json` → `instructions` 所列路径 | 必须在 `canonical-manifest.json` 的 `template_data_files` 或 `canonical_opencode_top` 中 |
|
||||||
@@ -937,6 +1042,7 @@ public/build/
|
|||||||
| `.opencode/skills/` 下的 Skill 目录 | 若不在 `epee-orchestrator/registry.md` 中 → 缺失注册 |
|
| `.opencode/skills/` 下的 Skill 目录 | 若不在 `epee-orchestrator/registry.md` 中 → 缺失注册 |
|
||||||
| `.opencode/skills/` 下的 Skill 目录 | 若不在 `canonical_skills` 中且非项目级 → 警告 |
|
| `.opencode/skills/` 下的 Skill 目录 | 若不在 `canonical_skills` 中且非项目级 → 警告 |
|
||||||
| `.opencode/data/` 下的数据文件 | 若不在 `template_data_files` 中 → 警告 |
|
| `.opencode/data/` 下的数据文件 | 若不在 `template_data_files` 中 → 警告 |
|
||||||
|
| `.opencode/phase/` 下的数据文件 | 若不在 `template_phase_files` 中 → 警告 |
|
||||||
| `.opencode/data/` 下的数据文件 | 若需要注入上下文 → 检查是否在 `opencode.json` → `instructions` 中 |
|
| `.opencode/data/` 下的数据文件 | 若需要注入上下文 → 检查是否在 `opencode.json` → `instructions` 中 |
|
||||||
|
|
||||||
### 整合自检执行规则
|
### 整合自检执行规则
|
||||||
@@ -945,6 +1051,7 @@ public/build/
|
|||||||
2. **自检发现缺口 → 回到对应类型的检核清单补充**,不可手动修补绕过
|
2. **自检发现缺口 → 回到对应类型的检核清单补充**,不可手动修补绕过
|
||||||
3. **每项自检必须输出结果**:`✅ 通过` / `❌ 缺失:<具体描述>` / `⚠️ 跳过:<原因>`
|
3. **每项自检必须输出结果**:`✅ 通过` / `❌ 缺失:<具体描述>` / `⚠️ 跳过:<原因>`
|
||||||
4. **任何 ❌ 结果必须在最终报告前修复**
|
4. **任何 ❌ 结果必须在最终报告前修复**
|
||||||
|
5. **运行 Layer 1 静态检查**:`python .opencode/skills/skill-tester/static-checks/run.py`,7 项检查必须全部通过。脚本验证 manifest↔init↔registry↔SKILL.md 之间的交叉引用一致性。**meta-check(第0项)会自动检测 manifest 新增分组是否被覆盖**——如有 FAIL,先在 run.py 的 `MANIFEST_TO_INIT_SECTION` 中补充映射再重跑。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,12 @@ description: >-
|
|||||||
|
|
||||||
# 概述
|
# 概述
|
||||||
|
|
||||||
skill-tester 是元层测试工具,通过 LLM-as-Judge 模式验证 SKILL.md 的行为逻辑。
|
skill-tester 是元层测试工具,分两层:
|
||||||
|
- **Layer 1(静态检查)**:无需 LLM,毫秒级。检测文件间交叉引用一致性。每次框架变更后必跑。
|
||||||
|
- **Layer 2(行为测试)**:LLM-as-Judge,验证 SKILL.md 的指令逻辑。
|
||||||
|
|
||||||
|
**建议顺序**:先跑 Layer 1(免费+快),通过后再跑 Layer 2(消耗 token)。
|
||||||
|
|
||||||
**核心原理**:将 SKILL.md 内容 + 模拟用户输入注入 sub-agent,观察其输出是否符合预期,
|
**核心原理**:将 SKILL.md 内容 + 模拟用户输入注入 sub-agent,观察其输出是否符合预期,
|
||||||
再用另一个 sub-agent 作为裁判评分。
|
再用另一个 sub-agent 作为裁判评分。
|
||||||
|
|
||||||
@@ -29,6 +34,59 @@ skill-tester 是元层测试工具,通过 LLM-as-Judge 模式验证 SKILL.md
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Layer 1:静态一致性检查(无需 LLM)
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
Layer 1 是纯文件级检查,检测 canonical-manifest.json、opencode-init SKILL.md、
|
||||||
|
registry.md、各 SKILL.md 之间的交叉引用一致性。**不需要 LLM 调用,毫秒级运行。**
|
||||||
|
|
||||||
|
适合作为 git pre-commit hook 或 CI 检查项。
|
||||||
|
|
||||||
|
### 安装 git pre-commit hook(可选)
|
||||||
|
|
||||||
|
```
|
||||||
|
# Windows
|
||||||
|
copy .opencode\skills\skill-tester\hooks\pre-commit .git\hooks\pre-commit
|
||||||
|
|
||||||
|
# Unix
|
||||||
|
ln -sf ../../.opencode/skills/skill-tester/hooks/pre-commit .git/hooks/pre-commit
|
||||||
|
```
|
||||||
|
|
||||||
|
安装后,每次 `git commit` 如果涉及 `.opencode/skills/` 下的文件变更,会自动运行 7 项检查。不通过则阻止提交。
|
||||||
|
|
||||||
|
## 检查项
|
||||||
|
|
||||||
|
| 检查 | 描述 | 覆盖的问题 |
|
||||||
|
|------|------|-----------|
|
||||||
|
| manifest-skills-sync | manifest canonical_skills 与 init Skills 白名单一致 | 新增 Skill 后忘记更新 init → 被误删 |
|
||||||
|
| manifest-phase-sync | manifest template_phase_files 与 init 阶段数据占位目录一致 | 新增 phase 文件后落入 C 组 |
|
||||||
|
| registry-types | registry 中 phases≠["all"] 的 Skill 类型为"基础设施" | game-design 被标为"项目级"而被误删 |
|
||||||
|
| phase-guard-all | 所有 SKILL.md Phase Guard 包含 "all" 处理 | "all" 特殊值未处理导致基础设施 Skill 被错误拒绝 |
|
||||||
|
| init-templates | manifest 每个文件在 init 模板内容章节有对应模板 | 缺少重置模板导致 init 时文件丢失 |
|
||||||
|
| frontmatter | 所有 SKILL.md 的 YAML frontmatter 完整(name + phases) | opencode 无法注册该 Skill |
|
||||||
|
|
||||||
|
## 运行方式
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python .opencode/skills/skill-tester/static-checks/run.py
|
||||||
|
```
|
||||||
|
|
||||||
|
退出码:全部通过 = 0,有失败 = 1。可直接集成到 CI/pre-commit。
|
||||||
|
|
||||||
|
## 新增检查项
|
||||||
|
|
||||||
|
编辑 `static-checks/run.py`,在 `CHECKS` 列表追加新函数即可,格式为:
|
||||||
|
```python
|
||||||
|
("check-id", "检查名称", check_function)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Layer 2:LLM-as-Judge 行为测试
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# 操作 A:测试单个 Skill
|
# 操作 A:测试单个 Skill
|
||||||
|
|
||||||
## 触发
|
## 触发
|
||||||
@@ -38,6 +96,10 @@ skill-tester 是元层测试工具,通过 LLM-as-Judge 模式验证 SKILL.md
|
|||||||
## 流程
|
## 流程
|
||||||
|
|
||||||
```
|
```
|
||||||
|
0. 【强制】先运行 Layer 1 静态检查:
|
||||||
|
python .opencode/skills/skill-tester/static-checks/run.py
|
||||||
|
→ 如有 FAIL,提示用户先修复再跑 Layer 2
|
||||||
|
→ 全部 PASS 则继续
|
||||||
1. 读取目标 Skill 的 SKILL.md
|
1. 读取目标 Skill 的 SKILL.md
|
||||||
2. 读取 .opencode/skills/skill-tester/test-cases/ 下匹配该 Skill 的测试用例
|
2. 读取 .opencode/skills/skill-tester/test-cases/ 下匹配该 Skill 的测试用例
|
||||||
3. 列出测试用例清单,询问用户确认
|
3. 列出测试用例清单,询问用户确认
|
||||||
@@ -204,3 +266,7 @@ Agent 输出(Markdown):
|
|||||||
# 自迭代日志
|
# 自迭代日志
|
||||||
|
|
||||||
本节记录使用本 Skill 过程中发现的必要检查项。
|
本节记录使用本 Skill 过程中发现的必要检查项。
|
||||||
|
|
||||||
|
### 已知必要检查
|
||||||
|
|
||||||
|
1. **Layer 1 必须在每次框架结构变更后运行** — 新增 Skill、修改 manifest 分组、新增数据文件后,`static-checks/run.py` 的 6 项检查必须全部通过。历史教训:新增 `template_phase_files` 后未同步 init Skill 的 3 个截面,导致 phase 文件落入 C 组;`game-design` 被标为"项目级"导致 init 时被 registry 删除。
|
||||||
|
|||||||
28
.opencode/skills/skill-tester/hooks/pre-commit
Normal file
28
.opencode/skills/skill-tester/hooks/pre-commit
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Git pre-commit hook — 在提交前强制运行 SKILL 静态一致性检查。
|
||||||
|
安装方式:复制或软链接到 .git/hooks/pre-commit
|
||||||
|
Windows: copy .opencode\skills\skill-tester\hooks\pre-commit .git\hooks\pre-commit
|
||||||
|
Unix: ln -sf ../../.opencode/skills/skill-tester/hooks/pre-commit .git/hooks/pre-commit
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
SCRIPT = Path(__file__).resolve().parent.parent / "static-checks" / "run.py"
|
||||||
|
|
||||||
|
# 检查是否有 .opencode/skills/ 下的文件被 staged
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "diff", "--cached", "--name-only", "--", ".opencode/skills/"],
|
||||||
|
capture_output=True, text=True
|
||||||
|
)
|
||||||
|
staged = [f for f in result.stdout.strip().split("\n") if f]
|
||||||
|
|
||||||
|
if not staged:
|
||||||
|
print("[pre-commit] 无 Skill 文件变更,跳过静态检查")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
print(f"[pre-commit] 检测到 {len(staged)} 个 Skill 文件变更,运行 Layer 1 静态检查...\n")
|
||||||
|
result = subprocess.run([sys.executable, str(SCRIPT)])
|
||||||
|
sys.exit(result.returncode)
|
||||||
445
.opencode/skills/skill-tester/static-checks/run.py
Normal file
445
.opencode/skills/skill-tester/static-checks/run.py
Normal file
@@ -0,0 +1,445 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
SKILL 体系静态一致性检查 (Layer 1)
|
||||||
|
检测 canonical-manifest.json、opencode-init SKILL.md、registry.md、各 SKILL.md 之间的
|
||||||
|
交叉引用一致性,不需要 LLM 调用。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# --- 路径解析 ---
|
||||||
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||||
|
SKILL_TESTER_DIR = SCRIPT_DIR.parent
|
||||||
|
SKILLS_DIR = SKILL_TESTER_DIR.parent
|
||||||
|
OPENDODE_DIR = SKILLS_DIR.parent
|
||||||
|
PROJECT_ROOT = OPENDODE_DIR.parent
|
||||||
|
|
||||||
|
MANIFEST_PATH = OPENDODE_DIR / "canonical-manifest.json"
|
||||||
|
INIT_SKILL_PATH = SKILLS_DIR / "opencode-init" / "SKILL.md"
|
||||||
|
REGISTRY_PATH = SKILLS_DIR / "epee-orchestrator" / "registry.md"
|
||||||
|
|
||||||
|
|
||||||
|
class CheckResult:
|
||||||
|
def __init__(self, name, passed, detail=""):
|
||||||
|
self.name = name
|
||||||
|
self.passed = passed
|
||||||
|
self.detail = detail
|
||||||
|
|
||||||
|
|
||||||
|
def load_manifest():
|
||||||
|
with open(MANIFEST_PATH, encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
def load_init_md():
|
||||||
|
with open(INIT_SKILL_PATH, encoding="utf-8") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def load_registry_md():
|
||||||
|
with open(REGISTRY_PATH, encoding="utf-8") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
# ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
|
||||||
|
# 检查 1:manifest canonical_skills 与 init Skills 白名单一致
|
||||||
|
# ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
|
||||||
|
|
||||||
|
def check_manifest_vs_init_skills():
|
||||||
|
manifest = load_manifest()
|
||||||
|
init_md = load_init_md()
|
||||||
|
|
||||||
|
manifest_skills = set()
|
||||||
|
for s in manifest.get("canonical_skills", []):
|
||||||
|
name = s.rstrip("/").split("/")[-1]
|
||||||
|
manifest_skills.add(name)
|
||||||
|
|
||||||
|
# 从 init 的 Skills 白名单提取
|
||||||
|
init_skills = set()
|
||||||
|
in_whitelist = False
|
||||||
|
for line in init_md.split("\n"):
|
||||||
|
if "Skills(规范 Skill 目录白名单)" in line or "Skills 白名单" in line:
|
||||||
|
in_whitelist = True
|
||||||
|
continue
|
||||||
|
if in_whitelist:
|
||||||
|
if line.strip().startswith(">") or line.strip() == "":
|
||||||
|
continue
|
||||||
|
if line.strip().startswith("-"):
|
||||||
|
m = re.search(r"`\.opencode/skills/([^/]+)/`", line)
|
||||||
|
if m:
|
||||||
|
init_skills.add(m.group(1))
|
||||||
|
elif not line.strip().startswith("-") and not line.strip().startswith("`"):
|
||||||
|
in_whitelist = False
|
||||||
|
|
||||||
|
only_manifest = manifest_skills - init_skills
|
||||||
|
only_init = init_skills - manifest_skills
|
||||||
|
|
||||||
|
if not only_manifest and not only_init:
|
||||||
|
return CheckResult("manifest vs init Skills 白名单一致", True)
|
||||||
|
|
||||||
|
details = []
|
||||||
|
if only_manifest:
|
||||||
|
details.append(f"manifest 有但 init 缺: {only_manifest}")
|
||||||
|
if only_init:
|
||||||
|
details.append(f"init 有但 manifest 缺: {only_init}")
|
||||||
|
return CheckResult("manifest vs init Skills 白名单一致", False, "; ".join(details))
|
||||||
|
|
||||||
|
|
||||||
|
# ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
|
||||||
|
# 检查 2:manifest template_phase_files 与 init 阶段数据占位目录一致
|
||||||
|
# ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
|
||||||
|
|
||||||
|
def check_manifest_vs_init_phase_files():
|
||||||
|
manifest = load_manifest()
|
||||||
|
init_md = load_init_md()
|
||||||
|
|
||||||
|
manifest_files = set()
|
||||||
|
for f in manifest.get("template_phase_files", []):
|
||||||
|
manifest_files.add(f.replace(".opencode/", ""))
|
||||||
|
|
||||||
|
# 从 init 的 "阶段数据占位目录" 提取
|
||||||
|
init_files = set()
|
||||||
|
in_section = False
|
||||||
|
for line in init_md.split("\n"):
|
||||||
|
if "阶段数据占位目录" in line:
|
||||||
|
in_section = True
|
||||||
|
continue
|
||||||
|
if in_section:
|
||||||
|
if line.strip().startswith("---"):
|
||||||
|
break
|
||||||
|
if line.strip().startswith("-"):
|
||||||
|
m = re.search(r"`\.opencode/(phase/[^`]+)`", line)
|
||||||
|
if m:
|
||||||
|
init_files.add(m.group(1))
|
||||||
|
if "{" in line:
|
||||||
|
base = re.search(r"`\.opencode/(phase/[^{`]+)\{", line)
|
||||||
|
sub = re.findall(r"([\w-]+\.[\w]+)", line)
|
||||||
|
if base and sub:
|
||||||
|
for s in sub:
|
||||||
|
init_files.add(f"{base.group(1)}{s}")
|
||||||
|
|
||||||
|
only_manifest = {f for f in manifest_files if not any(f.endswith(m.split('/')[-1]) and f.count('/') == m.count('/') for m in init_files)}
|
||||||
|
|
||||||
|
# 简化比较:提取文件名对比
|
||||||
|
manifest_names = {f.split("/")[-1] for f in manifest_files}
|
||||||
|
init_names = set()
|
||||||
|
for f in init_files:
|
||||||
|
init_names.add(f.split("/")[-1])
|
||||||
|
|
||||||
|
only_m = manifest_names - init_names
|
||||||
|
only_i = init_names - manifest_names
|
||||||
|
|
||||||
|
if not only_m and not only_i:
|
||||||
|
return CheckResult("manifest vs init 阶段数据文件一致", True)
|
||||||
|
|
||||||
|
details = []
|
||||||
|
if only_m:
|
||||||
|
details.append(f"manifest 有但 init 缺: {only_m}")
|
||||||
|
if only_i:
|
||||||
|
details.append(f"init 有但 manifest 缺: {only_i}")
|
||||||
|
return CheckResult("manifest vs init 阶段数据文件一致", False, "; ".join(details))
|
||||||
|
|
||||||
|
|
||||||
|
# ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
|
||||||
|
# 检查 3:registry 中非 ["all"] 的 Skill 类型应为"基础设施"(防误删)
|
||||||
|
# ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
|
||||||
|
|
||||||
|
def check_registry_types():
|
||||||
|
registry_md = load_registry_md()
|
||||||
|
|
||||||
|
# 解析 registry 条目
|
||||||
|
entries = {}
|
||||||
|
current_skill = None
|
||||||
|
current_type = None
|
||||||
|
for line in registry_md.split("\n"):
|
||||||
|
if line.startswith("### "):
|
||||||
|
current_skill = line.replace("### ", "").strip()
|
||||||
|
current_type = None
|
||||||
|
if line.startswith("- **类型**:"):
|
||||||
|
current_type = line.split("**类型**:")[1].strip()
|
||||||
|
if current_skill and current_type:
|
||||||
|
entries[current_skill] = current_type
|
||||||
|
|
||||||
|
issues = []
|
||||||
|
for skill_name in entries:
|
||||||
|
skill_path = SKILLS_DIR / skill_name / "SKILL.md"
|
||||||
|
if not skill_path.exists():
|
||||||
|
continue
|
||||||
|
|
||||||
|
content = skill_path.read_text(encoding="utf-8")
|
||||||
|
# 检查 phases
|
||||||
|
m = re.search(r'phases:\s*\[(.*?)\]', content)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
|
||||||
|
phases_str = m.group(1)
|
||||||
|
phases = [p.strip().strip('"') for p in phases_str.split(",")]
|
||||||
|
|
||||||
|
if "all" in phases:
|
||||||
|
if entries[skill_name] != "基础设施" and entries[skill_name] != "个人级":
|
||||||
|
issues.append(f"{skill_name}: phases=[\"all\"] 但类型={entries[skill_name]}(应为基础设施或个人级)")
|
||||||
|
else:
|
||||||
|
if entries[skill_name] == "项目级":
|
||||||
|
issues.append(f"{skill_name}: phases≠[\"all\"] 但类型=项目级(init 会删除。应为基础设施)")
|
||||||
|
|
||||||
|
if not issues:
|
||||||
|
return CheckResult("registry 类型与 phases 一致", True)
|
||||||
|
|
||||||
|
return CheckResult("registry 类型与 phases 一致", False, "; ".join(issues))
|
||||||
|
|
||||||
|
|
||||||
|
# ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
|
||||||
|
# 检查 4:所有 SKILL.md Phase Guard 包含 "all" 处理
|
||||||
|
# ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
|
||||||
|
|
||||||
|
def check_phase_guard_all():
|
||||||
|
issues = []
|
||||||
|
for skill_dir in SKILLS_DIR.iterdir():
|
||||||
|
if not skill_dir.is_dir():
|
||||||
|
continue
|
||||||
|
skill_md = skill_dir / "SKILL.md"
|
||||||
|
if not skill_md.exists():
|
||||||
|
continue
|
||||||
|
|
||||||
|
content = skill_md.read_text(encoding="utf-8")
|
||||||
|
# 只检查有 Phase Guard 块的 Skill
|
||||||
|
if "相位守卫" not in content and "Phase Guard" not in content:
|
||||||
|
# 无 Phase Guard 块的 Skill 依赖 AGENTS.md 的外层检查,无需本地守卫
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查守卫逻辑是否包含 "all" 处理
|
||||||
|
guard_section = re.search(
|
||||||
|
r'相位守卫.*?(?=\n#|\n---|\Z)',
|
||||||
|
content, re.DOTALL
|
||||||
|
)
|
||||||
|
if not guard_section:
|
||||||
|
guard_section = re.search(
|
||||||
|
r'Phase Guard.*?(?=\n#|\n---|\Z)',
|
||||||
|
content, re.DOTALL
|
||||||
|
)
|
||||||
|
|
||||||
|
if guard_section:
|
||||||
|
guard_text = guard_section.group(0)
|
||||||
|
if '"all"' not in guard_text:
|
||||||
|
issues.append(
|
||||||
|
f"{skill_dir.name}: Phase Guard 未包含 '\"all\"' 特殊值处理 "
|
||||||
|
f"(仅检查 phase 值是否在 phases 列表中)"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not issues:
|
||||||
|
return CheckResult("Phase Guard 包含 all 处理", True)
|
||||||
|
|
||||||
|
return CheckResult("Phase Guard 包含 all 处理", False, "; ".join(issues))
|
||||||
|
|
||||||
|
|
||||||
|
# ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
|
||||||
|
# 检查 5:manifest 每个文件在 init 模板内容章节有对应模板
|
||||||
|
# ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
|
||||||
|
|
||||||
|
def check_init_templates_exist():
|
||||||
|
manifest = load_manifest()
|
||||||
|
init_md = load_init_md()
|
||||||
|
|
||||||
|
all_template_files = (
|
||||||
|
manifest.get("template_data_files", []) +
|
||||||
|
manifest.get("template_phase_files", [])
|
||||||
|
)
|
||||||
|
|
||||||
|
missing = []
|
||||||
|
for fpath in all_template_files:
|
||||||
|
fname = fpath.split("/")[-1]
|
||||||
|
# 检查 init 的 "模板内容" 章节是否包含该文件名
|
||||||
|
templates_section = re.search(
|
||||||
|
r'## 模板内容.*?(?=## 模板内容 ·|## 冲突|$)',
|
||||||
|
init_md, re.DOTALL
|
||||||
|
)
|
||||||
|
if templates_section and f"### {fname}" not in templates_section.group(0):
|
||||||
|
# 尝试更宽松的匹配
|
||||||
|
base_name = fname.replace(".md", "").replace(".json", "")
|
||||||
|
if base_name not in templates_section.group(0):
|
||||||
|
missing.append(fname)
|
||||||
|
|
||||||
|
if not missing:
|
||||||
|
return CheckResult("init 模板内容覆盖所有 manifest 文件", True)
|
||||||
|
|
||||||
|
return CheckResult("init 模板内容覆盖所有 manifest 文件", False,
|
||||||
|
f"缺少模板: {missing}")
|
||||||
|
|
||||||
|
|
||||||
|
# ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
|
||||||
|
# 检查 6:所有 SKILL.md frontmatter 完整性
|
||||||
|
# ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
|
||||||
|
|
||||||
|
def check_skill_frontmatter():
|
||||||
|
issues = []
|
||||||
|
for skill_dir in SKILLS_DIR.iterdir():
|
||||||
|
if not skill_dir.is_dir():
|
||||||
|
continue
|
||||||
|
skill_md = skill_dir / "SKILL.md"
|
||||||
|
if not skill_md.exists():
|
||||||
|
continue
|
||||||
|
|
||||||
|
content = skill_md.read_text(encoding="utf-8")
|
||||||
|
lines = content.split("\n")
|
||||||
|
|
||||||
|
# 检查第一行是否为 ---
|
||||||
|
if not lines or lines[0].strip() != "---":
|
||||||
|
issues.append(f"{skill_dir.name}: frontmatter 未以 --- 开头")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查是否有 name 字段
|
||||||
|
if "name:" not in content[:500]:
|
||||||
|
issues.append(f"{skill_dir.name}: frontmatter 缺少 name 字段")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查是否有 phases 字段
|
||||||
|
if "phases:" not in content[:500]:
|
||||||
|
issues.append(f"{skill_dir.name}: frontmatter 缺少 phases 字段")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查是否正确闭合(第二个 ---)
|
||||||
|
fm_end = content.find("---", 3)
|
||||||
|
if fm_end == -1:
|
||||||
|
issues.append(f"{skill_dir.name}: frontmatter 未正确闭合(缺少第二个 ---)")
|
||||||
|
|
||||||
|
if not issues:
|
||||||
|
return CheckResult("所有 SKILL.md frontmatter 完整", True)
|
||||||
|
|
||||||
|
return CheckResult("所有 SKILL.md frontmatter 完整", False, "; ".join(issues))
|
||||||
|
|
||||||
|
|
||||||
|
# ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
|
||||||
|
# 入口
|
||||||
|
# ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
|
||||||
|
|
||||||
|
# ── manifest 分组 → init 章节 的映射表 ──
|
||||||
|
# 键:manifest JSON 中的数组名;值:init SKILL.md 中对应的章节标题关键字
|
||||||
|
MANIFEST_TO_INIT_SECTION = {
|
||||||
|
"canonical_skills": "Skills 白名单",
|
||||||
|
"template_data_files": "数据占位目录",
|
||||||
|
"template_phase_files": "阶段数据占位目录",
|
||||||
|
}
|
||||||
|
|
||||||
|
# ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
|
||||||
|
# 检查 0(自举):manifest 分组是否被 init 和 CHECKS 覆盖
|
||||||
|
# ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
|
||||||
|
|
||||||
|
def check_meta_coverage():
|
||||||
|
"""确保 manifest 中的每个文件分组在 init 和 CHECKS 中都有对应覆盖"""
|
||||||
|
manifest = load_manifest()
|
||||||
|
init_md = load_init_md()
|
||||||
|
|
||||||
|
# manifest 中所有文件数组分组(跳过非文件分组的字段)
|
||||||
|
manifest_groups = {}
|
||||||
|
for key in ["canonical_skills", "template_data_files", "template_phase_files"]:
|
||||||
|
if key in manifest:
|
||||||
|
manifest_groups[key] = manifest[key]
|
||||||
|
|
||||||
|
issues = []
|
||||||
|
for group_name in manifest_groups:
|
||||||
|
# 检查 MANIFEST_TO_INIT_SECTION 是否有映射
|
||||||
|
if group_name not in MANIFEST_TO_INIT_SECTION:
|
||||||
|
issues.append(
|
||||||
|
f"manifest 分组 '{group_name}' 在 MANIFEST_TO_INIT_SECTION 中无映射 → "
|
||||||
|
f"需在 run.py 中新增映射条目"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查 init SKILL.md 是否有对应章节
|
||||||
|
section_keyword = MANIFEST_TO_INIT_SECTION[group_name]
|
||||||
|
if section_keyword not in init_md:
|
||||||
|
issues.append(
|
||||||
|
f"manifest 分组 '{group_name}' 对应的 init 章节 '{section_keyword}' 未找到"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 检查 CHECKS 列表中是否有对应检查
|
||||||
|
check_ids = [c[0] for c in CHECKS]
|
||||||
|
has_check = any(
|
||||||
|
gid in check_ids or group_name.replace("_", "-") in cid
|
||||||
|
for cid in check_ids
|
||||||
|
for gid in [group_name]
|
||||||
|
)
|
||||||
|
# 更可靠的方式:检查是否有检查函数引用了这个 manifest key
|
||||||
|
covered = False
|
||||||
|
for cid, cname, cfn in CHECKS:
|
||||||
|
src = (cfn.__code__.co_consts if hasattr(cfn, '__code__') else ())
|
||||||
|
# 简单启发式:函数名或检查 ID 中包含分组关键字
|
||||||
|
if group_name.replace("_", "-") in cid:
|
||||||
|
covered = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not covered and group_name != "canonical_skills":
|
||||||
|
# canonical_skills 的覆盖在 manifest-skills-sync 里,但它的 ID 不直接包含 canonical_skills
|
||||||
|
# 不做字符串匹配警告,而是在下面单独处理
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not issues:
|
||||||
|
return CheckResult("manifest 所有分组均被覆盖", True)
|
||||||
|
|
||||||
|
return CheckResult("manifest 所有分组均被覆盖", False, "; ".join(issues))
|
||||||
|
|
||||||
|
|
||||||
|
# ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
|
||||||
|
# 入口 main
|
||||||
|
# ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
|
||||||
|
|
||||||
|
CHECKS = [
|
||||||
|
# ═══ 自举检查(优先级最高) ═══
|
||||||
|
("meta-manifest-coverage", "manifest 分组在 init 和 CHECKS 中的覆盖", check_meta_coverage),
|
||||||
|
|
||||||
|
# ═══ 交叉引用一致性 ═══
|
||||||
|
("manifest-skills-sync", "manifest vs init Skills 白名单", check_manifest_vs_init_skills),
|
||||||
|
("manifest-phase-sync", "manifest vs init 阶段数据文件", check_manifest_vs_init_phase_files),
|
||||||
|
("init-templates", "init 模板内容覆盖", check_init_templates_exist),
|
||||||
|
|
||||||
|
# ═══ 语义规则 ═══
|
||||||
|
("registry-types", "registry 类型与 phases 一致性", check_registry_types),
|
||||||
|
("phase-guard-all", "Phase Guard all 逻辑", check_phase_guard_all),
|
||||||
|
("frontmatter", "Skill frontmatter 完整性", check_skill_frontmatter),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Windows GBK 编码兼容
|
||||||
|
import io
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||||
|
|
||||||
|
if not MANIFEST_PATH.exists():
|
||||||
|
print("[FAIL] canonical-manifest.json 不存在,不是有效的 .opencode 模板")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for check_id, check_name, check_fn in CHECKS:
|
||||||
|
try:
|
||||||
|
result = check_fn()
|
||||||
|
except Exception as e:
|
||||||
|
result = CheckResult(check_name, False, f"执行异常: {e}")
|
||||||
|
results.append(result)
|
||||||
|
|
||||||
|
passed = sum(1 for r in results if r.passed)
|
||||||
|
total = len(results)
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print(" SKILL 体系静态一致性检查 (Layer 1)")
|
||||||
|
print("=" * 60)
|
||||||
|
for r in results:
|
||||||
|
status = "[PASS]" if r.passed else "[FAIL]"
|
||||||
|
print(f"\n{status} {r.name}")
|
||||||
|
if r.detail:
|
||||||
|
for line in r.detail.split(";"):
|
||||||
|
print(f" {line.strip()}")
|
||||||
|
print(f"\n{'=' * 60}")
|
||||||
|
print(f" 结果: {passed}/{total} 通过")
|
||||||
|
if passed < total:
|
||||||
|
print(f" ({total - passed} 项需要修复)")
|
||||||
|
print(f"{'=' * 60}")
|
||||||
|
|
||||||
|
sys.exit(0 if passed == total else 1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -125,6 +125,13 @@ Agent 在制定方案或执行任务时,如果发现当前任务符合以下
|
|||||||
### Registry 同步(强制)
|
### Registry 同步(强制)
|
||||||
每次创建或修改任何 Skill 后,必须更新 `.opencode/skills/epee-orchestrator/registry.md`。
|
每次创建或修改任何 Skill 后,必须更新 `.opencode/skills/epee-orchestrator/registry.md`。
|
||||||
|
|
||||||
|
### 耦合点完整性验证(强制)
|
||||||
|
每次创建新 Skill 或修改现有 Skill 的 frontmatter(name/phases/description)后,必须运行 Layer 1 静态检查:
|
||||||
|
```
|
||||||
|
python .opencode/skills/skill-tester/static-checks/run.py
|
||||||
|
```
|
||||||
|
退出码非 0 时不得视为任务完成——根据 FAIL 项提示修复 manifest、init Skill、registry 或 SKILL.md frontmatter,直到 7/7 全部通过。此规则是 Registry 同步的自动化验证层。
|
||||||
|
|
||||||
### Skill 自迭代(强制)
|
### Skill 自迭代(强制)
|
||||||
当 Skill 使用中出现信息缺失导致错误、用户需要反复补充同类信息、或生成产物与预期系统性偏差时,必须触发自迭代流程——识别根因、向用户确认、追加到对应 SKILL.md 的"自迭代日志"章节。
|
当 Skill 使用中出现信息缺失导致错误、用户需要反复补充同类信息、或生成产物与预期系统性偏差时,必须触发自迭代流程——识别根因、向用户确认、追加到对应 SKILL.md 的"自迭代日志"章节。
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user