openfeel 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.en.md +140 -0
- package/README.md +17 -0
- package/README.zh-CN.md +140 -0
- package/bin/openfeel.js +13 -0
- package/dist/cli/index.d.ts +11 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +119 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/repl.d.ts +10 -0
- package/dist/cli/repl.d.ts.map +1 -0
- package/dist/cli/repl.js +74 -0
- package/dist/cli/repl.js.map +1 -0
- package/dist/commands/archive.d.ts +7 -0
- package/dist/commands/archive.d.ts.map +1 -0
- package/dist/commands/archive.js +38 -0
- package/dist/commands/archive.js.map +1 -0
- package/dist/commands/config.d.ts +15 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +208 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/flow.d.ts +24 -0
- package/dist/commands/flow.d.ts.map +1 -0
- package/dist/commands/flow.js +1024 -0
- package/dist/commands/flow.js.map +1 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +62 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/instructions.d.ts +7 -0
- package/dist/commands/instructions.d.ts.map +1 -0
- package/dist/commands/instructions.js +36 -0
- package/dist/commands/instructions.js.map +1 -0
- package/dist/commands/knowledge.d.ts +7 -0
- package/dist/commands/knowledge.d.ts.map +1 -0
- package/dist/commands/knowledge.js +174 -0
- package/dist/commands/knowledge.js.map +1 -0
- package/dist/commands/lint.d.ts +12 -0
- package/dist/commands/lint.d.ts.map +1 -0
- package/dist/commands/lint.js +192 -0
- package/dist/commands/lint.js.map +1 -0
- package/dist/commands/plan.d.ts +7 -0
- package/dist/commands/plan.d.ts.map +1 -0
- package/dist/commands/plan.js +73 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/commands/project.d.ts +7 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +189 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/roadmap.d.ts +7 -0
- package/dist/commands/roadmap.d.ts.map +1 -0
- package/dist/commands/roadmap.js +26 -0
- package/dist/commands/roadmap.js.map +1 -0
- package/dist/commands/stage.d.ts +17 -0
- package/dist/commands/stage.d.ts.map +1 -0
- package/dist/commands/stage.js +291 -0
- package/dist/commands/stage.js.map +1 -0
- package/dist/commands/update.d.ts +8 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +87 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/view.d.ts +7 -0
- package/dist/commands/view.d.ts.map +1 -0
- package/dist/commands/view.js +77 -0
- package/dist/commands/view.js.map +1 -0
- package/dist/core/archive/merge.d.ts +20 -0
- package/dist/core/archive/merge.d.ts.map +1 -0
- package/dist/core/archive/merge.js +118 -0
- package/dist/core/archive/merge.js.map +1 -0
- package/dist/core/artifact-graph/graph.d.ts +62 -0
- package/dist/core/artifact-graph/graph.d.ts.map +1 -0
- package/dist/core/artifact-graph/graph.js +184 -0
- package/dist/core/artifact-graph/graph.js.map +1 -0
- package/dist/core/artifact-graph/index.d.ts +10 -0
- package/dist/core/artifact-graph/index.d.ts.map +1 -0
- package/dist/core/artifact-graph/index.js +8 -0
- package/dist/core/artifact-graph/index.js.map +1 -0
- package/dist/core/artifact-graph/instruction-loader.d.ts +35 -0
- package/dist/core/artifact-graph/instruction-loader.d.ts.map +1 -0
- package/dist/core/artifact-graph/instruction-loader.js +358 -0
- package/dist/core/artifact-graph/instruction-loader.js.map +1 -0
- package/dist/core/artifact-graph/resolver.d.ts +10 -0
- package/dist/core/artifact-graph/resolver.d.ts.map +1 -0
- package/dist/core/artifact-graph/resolver.js +32 -0
- package/dist/core/artifact-graph/resolver.js.map +1 -0
- package/dist/core/artifact-graph/state.d.ts +9 -0
- package/dist/core/artifact-graph/state.d.ts.map +1 -0
- package/dist/core/artifact-graph/state.js +24 -0
- package/dist/core/artifact-graph/state.js.map +1 -0
- package/dist/core/artifact-graph/types.d.ts +22 -0
- package/dist/core/artifact-graph/types.d.ts.map +1 -0
- package/dist/core/artifact-graph/types.js +2 -0
- package/dist/core/artifact-graph/types.js.map +1 -0
- package/dist/core/config.d.ts +256 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +393 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/flow-manager.d.ts +492 -0
- package/dist/core/flow-manager.d.ts.map +1 -0
- package/dist/core/flow-manager.js +2379 -0
- package/dist/core/flow-manager.js.map +1 -0
- package/dist/core/i18n-data/en.d.ts +26 -0
- package/dist/core/i18n-data/en.d.ts.map +1 -0
- package/dist/core/i18n-data/en.js +492 -0
- package/dist/core/i18n-data/en.js.map +1 -0
- package/dist/core/i18n-data/types.d.ts +18 -0
- package/dist/core/i18n-data/types.d.ts.map +1 -0
- package/dist/core/i18n-data/types.js +8 -0
- package/dist/core/i18n-data/types.js.map +1 -0
- package/dist/core/i18n-data/zh-CN.d.ts +28 -0
- package/dist/core/i18n-data/zh-CN.d.ts.map +1 -0
- package/dist/core/i18n-data/zh-CN.js +524 -0
- package/dist/core/i18n-data/zh-CN.js.map +1 -0
- package/dist/core/i18n.d.ts +40 -0
- package/dist/core/i18n.d.ts.map +1 -0
- package/dist/core/i18n.js +135 -0
- package/dist/core/i18n.js.map +1 -0
- package/dist/core/init.d.ts +22 -0
- package/dist/core/init.d.ts.map +1 -0
- package/dist/core/init.js +292 -0
- package/dist/core/init.js.map +1 -0
- package/dist/core/metrics.d.ts +60 -0
- package/dist/core/metrics.d.ts.map +1 -0
- package/dist/core/metrics.js +137 -0
- package/dist/core/metrics.js.map +1 -0
- package/dist/core/pipeline-schema.d.ts +78 -0
- package/dist/core/pipeline-schema.d.ts.map +1 -0
- package/dist/core/pipeline-schema.js +57 -0
- package/dist/core/pipeline-schema.js.map +1 -0
- package/dist/core/plan/roadmap.d.ts +17 -0
- package/dist/core/plan/roadmap.d.ts.map +1 -0
- package/dist/core/plan/roadmap.js +71 -0
- package/dist/core/plan/roadmap.js.map +1 -0
- package/dist/core/plan/scheme.d.ts +34 -0
- package/dist/core/plan/scheme.d.ts.map +1 -0
- package/dist/core/plan/scheme.js +262 -0
- package/dist/core/plan/scheme.js.map +1 -0
- package/dist/core/plan/stage.d.ts +20 -0
- package/dist/core/plan/stage.d.ts.map +1 -0
- package/dist/core/plan/stage.js +120 -0
- package/dist/core/plan/stage.js.map +1 -0
- package/dist/core/public-logger.d.ts +84 -0
- package/dist/core/public-logger.d.ts.map +1 -0
- package/dist/core/public-logger.js +344 -0
- package/dist/core/public-logger.js.map +1 -0
- package/dist/core/schema.d.ts +70 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/core/schema.js +61 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/core/template-loader.d.ts +22 -0
- package/dist/core/template-loader.d.ts.map +1 -0
- package/dist/core/template-loader.js +2848 -0
- package/dist/core/template-loader.js.map +1 -0
- package/dist/core/templates.d.ts +18 -0
- package/dist/core/templates.d.ts.map +1 -0
- package/dist/core/templates.js +50 -0
- package/dist/core/templates.js.map +1 -0
- package/dist/core/update.d.ts +40 -0
- package/dist/core/update.d.ts.map +1 -0
- package/dist/core/update.js +1569 -0
- package/dist/core/update.js.map +1 -0
- package/dist/core/view/entry.d.ts +37 -0
- package/dist/core/view/entry.d.ts.map +1 -0
- package/dist/core/view/entry.js +108 -0
- package/dist/core/view/entry.js.map +1 -0
- package/dist/core/workspace/identity.d.ts +60 -0
- package/dist/core/workspace/identity.d.ts.map +1 -0
- package/dist/core/workspace/identity.js +169 -0
- package/dist/core/workspace/identity.js.map +1 -0
- package/dist/core/workspace/knowledge.d.ts +55 -0
- package/dist/core/workspace/knowledge.d.ts.map +1 -0
- package/dist/core/workspace/knowledge.js +230 -0
- package/dist/core/workspace/knowledge.js.map +1 -0
- package/dist/core/workspace/structure.d.ts +9 -0
- package/dist/core/workspace/structure.d.ts.map +1 -0
- package/dist/core/workspace/structure.js +59 -0
- package/dist/core/workspace/structure.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/kb-dedup.d.ts +46 -0
- package/dist/utils/kb-dedup.d.ts.map +1 -0
- package/dist/utils/kb-dedup.js +169 -0
- package/dist/utils/kb-dedup.js.map +1 -0
- package/dist/utils/path.d.ts +22 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +37 -0
- package/dist/utils/path.js.map +1 -0
- package/package.json +61 -0
- package/schemas/spec-driven/schema.yaml +50 -0
- package/schemas/spec-driven/templates/design.md +43 -0
- package/schemas/spec-driven/templates/plan.md +46 -0
- package/schemas/spec-driven/templates/proposal.md +35 -0
- package/schemas/spec-driven/templates/tasks.md +39 -0
- package/scripts/patch-inquirer.js +70 -0
|
@@ -0,0 +1,1569 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode 适配器更新编排
|
|
3
|
+
* 在目标项目中生成 Agent 定义、Skill 定义、instructions/core.md,并更新 opencode.jsonc 配置。
|
|
4
|
+
*
|
|
5
|
+
* 变更摘要 (v3-stage-04 第二轮):
|
|
6
|
+
* - 新增 instructions/core.md 创建(从 init.ts 迁移至此,职责归位适配器层)
|
|
7
|
+
*/
|
|
8
|
+
import { writeFileSync, existsSync, readFileSync, mkdirSync, readdirSync } from 'node:fs';
|
|
9
|
+
import { resolve, join } from 'node:path';
|
|
10
|
+
import { loadAgentTemplate, listAgentIds, loadTemplate } from './template-loader.js';
|
|
11
|
+
import { recordProjectLang, getGlobalConfig, getLang } from './workspace/identity.js';
|
|
12
|
+
/** AGENTS.md 语言冲突错误(由命令层捕获处理) */
|
|
13
|
+
export class AgentsMdLangConflictError extends Error {
|
|
14
|
+
projectLang;
|
|
15
|
+
requestedLang;
|
|
16
|
+
constructor(projectLang, requestedLang) {
|
|
17
|
+
super(`AGENTS.md language conflict: project=${projectLang}, requested=${requestedLang}`);
|
|
18
|
+
this.projectLang = projectLang;
|
|
19
|
+
this.requestedLang = requestedLang;
|
|
20
|
+
this.name = 'AgentsMdLangConflictError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** 当前支持部署的 AI 工具列表 */
|
|
24
|
+
export const supportedTools = [
|
|
25
|
+
{
|
|
26
|
+
id: 'opencode',
|
|
27
|
+
name: 'OpenCode',
|
|
28
|
+
description: 'OpenCode AI 编码助手 — 生成 Agent 定义和 /opfx:* 技能',
|
|
29
|
+
enabled: true,
|
|
30
|
+
},
|
|
31
|
+
// 后续扩展:
|
|
32
|
+
// { id: 'claude', name: 'Claude Code', description: 'Anthropic Claude Code CLI', enabled: false },
|
|
33
|
+
// { id: 'cursor', name: 'Cursor', description: 'Cursor IDE', enabled: false },
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* 交互式选择部署目标工具
|
|
37
|
+
* 使用 @inquirer/prompts checkbox,上下键导航,空格选中,回车确认
|
|
38
|
+
* @returns 用户选中的工具 ID 列表
|
|
39
|
+
*/
|
|
40
|
+
export async function selectTools() {
|
|
41
|
+
try {
|
|
42
|
+
const { checkbox } = await import('@inquirer/prompts');
|
|
43
|
+
const choices = supportedTools.map((t) => ({
|
|
44
|
+
name: `${t.name} — ${t.description}`,
|
|
45
|
+
value: t.id,
|
|
46
|
+
checked: t.enabled,
|
|
47
|
+
}));
|
|
48
|
+
const selected = await checkbox({
|
|
49
|
+
message: '选择要部署的 AI 工具(空格选中,回车确认)',
|
|
50
|
+
choices,
|
|
51
|
+
pageSize: 10,
|
|
52
|
+
});
|
|
53
|
+
return selected;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// 交互不可用时(如非 TTY 环境),回退到全部启用项
|
|
57
|
+
console.log('(非交互环境,使用默认工具列表)');
|
|
58
|
+
return supportedTools.filter((t) => t.enabled).map((t) => t.id);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// ─── Agent 定义由 template-loader.ts 统一管理 ──────────────────────────
|
|
62
|
+
// ─── Skill 定义内容 ─────────────────────────────────────────────────
|
|
63
|
+
// AUTO-GENERATED-BEGIN: SKILL_DEFINITIONS
|
|
64
|
+
const SKILL_DEFINITIONS = {
|
|
65
|
+
'agent-model-check': `---
|
|
66
|
+
name: agent-model-check
|
|
67
|
+
description: Agent 模型检查与修复。当 Agent 报 "Model not found" 或需要排查模型配置时使用。涵盖 auth.json 校验、provider key 匹配、模型能力确认、Vision 多模态专项指南。
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
# Skill: agent-model-check
|
|
71
|
+
|
|
72
|
+
# Agent 模型检查与修复
|
|
73
|
+
|
|
74
|
+
## 何时使用
|
|
75
|
+
|
|
76
|
+
- Agent 调度时报 \`Model not found: xxx\`
|
|
77
|
+
- 需要验证某个 Agent 的模型是否可用
|
|
78
|
+
- 新增 Agent 后需确认模型配置正确
|
|
79
|
+
- 排查多模态(Vision)Agent 无法处理图片的问题
|
|
80
|
+
|
|
81
|
+
## 排查流程
|
|
82
|
+
|
|
83
|
+
### 第一步:确认报错信息
|
|
84
|
+
|
|
85
|
+
\`\`\`
|
|
86
|
+
Model not found: {provider_key}/{model_id}
|
|
87
|
+
\`\`\`
|
|
88
|
+
|
|
89
|
+
注意是否有 \`Did you mean: xxx\` 提示——如有,直接使用建议的模型名。
|
|
90
|
+
|
|
91
|
+
### 第二步:读取 auth.json 确认实际 provider key
|
|
92
|
+
|
|
93
|
+
\`\`\`bash
|
|
94
|
+
# Windows PowerShell / macOS / Linux
|
|
95
|
+
cat ~/.local/share/opencode/auth.json
|
|
96
|
+
\`\`\`
|
|
97
|
+
|
|
98
|
+
**关键点**:模型引用中的 provider 部分必须与 \`auth.json\` 中的 key 完全一致,而非 \`opencode.jsonc\` 中 \`provider.name\` 或 \`provider.id\`。
|
|
99
|
+
|
|
100
|
+
常见 provider key 示例:
|
|
101
|
+
- \`alibaba-cn\` — 阿里云中国区(DashScope)
|
|
102
|
+
- \`deepseek\` — DeepSeek
|
|
103
|
+
- \`anthropic\` — Anthropic
|
|
104
|
+
- \`openai\` — OpenAI
|
|
105
|
+
- \`zhipuai\` — 智谱 AI
|
|
106
|
+
|
|
107
|
+
### 第三步:确认模型是否支持目标能力
|
|
108
|
+
|
|
109
|
+
查阅 [Models.dev](https://models.dev) 确认模型属性:
|
|
110
|
+
|
|
111
|
+
| 能力需求 | 需确认的字段 | 示例 |
|
|
112
|
+
|----------|-------------|------|
|
|
113
|
+
| 视觉/图像分析 | Input = Yes | \`qwen3-vl-plus\` |
|
|
114
|
+
| 工具调用 | Tool Call = Yes | \`qwen3.7-plus\` |
|
|
115
|
+
| 结构化输出 | Structured = Yes | \`qwen3.7-flash\` |
|
|
116
|
+
| 推理/思考 | Reasoning = Yes | \`qwq-plus\` |
|
|
117
|
+
|
|
118
|
+
**常见陷阱**:
|
|
119
|
+
- \`qwen3.7-plus\` 是纯文本模型,不支持图像输入
|
|
120
|
+
- \`qwen3-vl-plus\` 是视觉模型,支持图像分析
|
|
121
|
+
- 模型名中的 \`vl\` 表示 Vision-Language
|
|
122
|
+
|
|
123
|
+
### 第四步:检查 opencode.jsonc 配置
|
|
124
|
+
|
|
125
|
+
\`\`\`jsonc
|
|
126
|
+
{
|
|
127
|
+
"agent": {
|
|
128
|
+
"vision": {
|
|
129
|
+
"model": "{auth.json_key}/{model_id}" // 格式:provider_key/model_id
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
\`\`\`
|
|
134
|
+
|
|
135
|
+
**配置规则**:
|
|
136
|
+
1. \`provider\` 块中的 \`name\` 和 \`id\` 仅用于显示,**不影响模型解析**
|
|
137
|
+
2. 模型引用格式严格为 \`{auth.json中的key}/{model_id}\`
|
|
138
|
+
3. 不要随意添加前缀(如 \`alibaba/\`、\`Alibaba(China)/\`)
|
|
139
|
+
4. 如果不需要自定义 provider 选项(如 baseURL),可以完全不写 \`provider\` 块
|
|
140
|
+
|
|
141
|
+
### 第五步:修改并重启
|
|
142
|
+
|
|
143
|
+
修改 \`opencode.jsonc\` 后**必须重启 opencode** 才能生效。运行中的会话使用启动时加载的配置。
|
|
144
|
+
|
|
145
|
+
### 第六步:验证
|
|
146
|
+
|
|
147
|
+
重启后调度目标 Agent 执行简单测试任务,确认无报错。
|
|
148
|
+
|
|
149
|
+
## 快速诊断清单
|
|
150
|
+
|
|
151
|
+
| 检查项 | 命令/操作 | 期望结果 |
|
|
152
|
+
|--------|----------|---------|
|
|
153
|
+
| auth.json 存在 | \`cat ~/.local/share/opencode/auth.json\` | 包含目标 provider 的 key |
|
|
154
|
+
| provider key 匹配 | 对比 auth.json key 与模型引用前缀 | 完全一致 |
|
|
155
|
+
| 模型支持目标能力 | 查阅 models.dev | Input/Tool Call 等字段 = Yes |
|
|
156
|
+
| opencode.jsonc 语法 | 检查 JSON 格式 | 无语法错误 |
|
|
157
|
+
| 重启生效 | 重启 opencode 后重新测试 | 无 Model not found 报错 |
|
|
158
|
+
|
|
159
|
+
## 多模态(Vision)Agent 专项
|
|
160
|
+
|
|
161
|
+
Vision Agent 必须配置多模态模型。Alibaba 系列视觉模型:
|
|
162
|
+
|
|
163
|
+
| 模型 ID | 完整引用(alibaba-cn) | 上下文 | 图像输入 |
|
|
164
|
+
|---------|----------------------|--------|---------|
|
|
165
|
+
| qwen3-vl-plus | \`alibaba-cn/qwen3-vl-plus\` | 262K | ✅ |
|
|
166
|
+
| qwen-vl-plus | \`alibaba-cn/qwen-vl-plus\` | 131K | ❌(旧版) |
|
|
167
|
+
| qwen-vl-max | \`alibaba-cn/qwen-vl-max\` | 131K | ❌(旧版) |
|
|
168
|
+
|
|
169
|
+
**推荐**:优先使用 \`qwen3-vl-plus\`,上下文最大且为最新视觉模型。
|
|
170
|
+
|
|
171
|
+
## 常见错误与修复
|
|
172
|
+
|
|
173
|
+
| 错误信息 | 原因 | 修复 |
|
|
174
|
+
|----------|------|------|
|
|
175
|
+
| \`Model not found: Alibaba(China)/xxx\` | 使用了自定义 provider name 而非 auth.json key | 改为 auth.json 中的实际 key |
|
|
176
|
+
| \`Model not found: alibaba/xxx\` | 内置 key 与实际注册的 key 不一致 | 检查 auth.json,使用实际 key |
|
|
177
|
+
| \`Model not found: xxx. Did you mean: yyy\` | 模型名拼写错误或不存在 | 使用 \`Did you mean\` 建议的名称 |
|
|
178
|
+
| Agent 调度成功但无法处理图片 | 配置了纯文本模型 | 改为带 \`vl\` 后缀的视觉模型 |
|
|
179
|
+
`,
|
|
180
|
+
'bug-acceptance': `---
|
|
181
|
+
name: bug-acceptance
|
|
182
|
+
description: 标准化 Bug 验收流程,供测试 Agent 或代码 Agent(自测后自查)调用。
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
# Bug 验收
|
|
186
|
+
|
|
187
|
+
## 输入
|
|
188
|
+
|
|
189
|
+
- 模块名 和 Bug 编号(如 \`模块A/BUG-001\`)
|
|
190
|
+
|
|
191
|
+
## 执行步骤
|
|
192
|
+
|
|
193
|
+
### 1. 读取 Bug 文件
|
|
194
|
+
|
|
195
|
+
读取 \`.openfeel/users/{username}/bugs/{模块名}/{编号}_{标题}.md\`,提取以下关键信息:
|
|
196
|
+
- 期望行为(\`## 期望行为\`)
|
|
197
|
+
- 复现步骤(\`## 复现步骤\`)
|
|
198
|
+
- 修复记录中的 Commit(\`## 修复记录\` 表格)
|
|
199
|
+
|
|
200
|
+
### 2. 运行测试套件
|
|
201
|
+
|
|
202
|
+
执行项目测试命令,确认修复未引入回归问题。
|
|
203
|
+
|
|
204
|
+
- 若测试未通过 → 验收不通过,备注记录失败用例。
|
|
205
|
+
- 跳过后续步骤,直接写入验收记录。
|
|
206
|
+
|
|
207
|
+
### 3. 按复现步骤比对
|
|
208
|
+
|
|
209
|
+
逐条执行复现步骤,对比实际行为与期望行为:
|
|
210
|
+
|
|
211
|
+
- 每条步骤匹配 → 通过。
|
|
212
|
+
- 任何步骤行为不符 → 不通过,备注记录差异。
|
|
213
|
+
|
|
214
|
+
### 4. 写入验收记录
|
|
215
|
+
|
|
216
|
+
在 Bug 文件的 \`## 验收记录\` 表格中追加一行:
|
|
217
|
+
|
|
218
|
+
| 时间 | 验收人 | 结论 | 备注 |
|
|
219
|
+
|------|--------|------|------|
|
|
220
|
+
| {当前时间} | {username} | 通过 / 不通过 | {测试摘要或失败原因} |
|
|
221
|
+
|
|
222
|
+
### 5. 更新状态与索引
|
|
223
|
+
|
|
224
|
+
- **验收通过**:Bug 状态改为 \`closed\`。
|
|
225
|
+
- **验收不通过**:Bug 状态退回 \`fixing\`。
|
|
226
|
+
- 更新 \`.openfeel/users/{username}/bugs/index.md\` 中该 Bug 的状态。
|
|
227
|
+
- 更新 \`.openfeel/users/{username}/bugs/log.md\` 追加变更摘要。
|
|
228
|
+
|
|
229
|
+
### 6. 归入公共域
|
|
230
|
+
|
|
231
|
+
验收通过后,核心结论写入 \`.openfeel/bugs/{module}.md\`,并在公共日志简要记录。
|
|
232
|
+
`,
|
|
233
|
+
'check-kb': `---
|
|
234
|
+
name: check-kb
|
|
235
|
+
description: 渐进式查阅 .openfeel/kb/ 项目知识库,按当前任务需求返回最相关的参考信息。精确匹配无结果时自动触发语义检索回退,避免一次加载全量内容。
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
# 查阅知识库
|
|
239
|
+
|
|
240
|
+
## 输入
|
|
241
|
+
|
|
242
|
+
无(自动按当前任务上下文推断阅读范围)
|
|
243
|
+
|
|
244
|
+
## 执行步骤
|
|
245
|
+
|
|
246
|
+
### 1. 读取总索引
|
|
247
|
+
|
|
248
|
+
读取 \`.openfeel/kb/index.md\`,获取所有分类文件的摘要和最近更新时间。
|
|
249
|
+
|
|
250
|
+
### 2. 匹配相关分类
|
|
251
|
+
|
|
252
|
+
根据当前任务特征,确定需要查阅的分类:
|
|
253
|
+
|
|
254
|
+
| 任务特征 | 优先查阅 |
|
|
255
|
+
|----------|----------|
|
|
256
|
+
| 新功能开发、架构变更 | \`architecture.md\` |
|
|
257
|
+
| 代码编写、重构 | \`patterns.md\` |
|
|
258
|
+
| 编译/运行报错 | \`troubleshooting.md\` |
|
|
259
|
+
| 环境搭建、依赖变更 | \`setup.md\` |
|
|
260
|
+
| 跨领域任务或不熟悉模块 | 全部 \`index.md\` 摘要 |
|
|
261
|
+
|
|
262
|
+
### 3. 提取相关条目
|
|
263
|
+
|
|
264
|
+
读取匹配的分类文件,提取与当前任务相关的 \`[+]\` 条目。不加载标记为 \`[-]\` 的已禁用条目。
|
|
265
|
+
|
|
266
|
+
### 4. 输出摘要
|
|
267
|
+
|
|
268
|
+
按以下格式输出:
|
|
269
|
+
|
|
270
|
+
\`\`\`
|
|
271
|
+
📁 知识库查阅结果({分类})
|
|
272
|
+
|
|
273
|
+
[index.md 中的分类摘要]
|
|
274
|
+
|
|
275
|
+
相关条目:
|
|
276
|
+
- [architecture] "登录流程使用 OAuth2..." — 与当前任务相关:是
|
|
277
|
+
- [troubleshooting] "Module not found 时运行 npm ci" — 与当前任务相关:否
|
|
278
|
+
\`\`\`
|
|
279
|
+
|
|
280
|
+
若无相关条目,输出:\`知识库中暂无与当前任务相关的记录。\`
|
|
281
|
+
|
|
282
|
+
### 5. 语义检索回退(自动)
|
|
283
|
+
|
|
284
|
+
当精确匹配未找到任何相关条目(无 \`[+]\` 条目或所有条目与当前任务无关)时,**本技能自行执行语义检索**,无需调用方另行加载 \`search-kb\`:
|
|
285
|
+
|
|
286
|
+
1. **索引就绪检查**:检查 \`.openfeel/tmp/vectors/index.json\` 是否存在
|
|
287
|
+
2. **索引缺失时**:输出以下提示后结束,不再继续回退:
|
|
288
|
+
\`\`\`
|
|
289
|
+
💡 精确匹配未找到相关条目,语义检索的向量索引尚未构建。
|
|
290
|
+
|
|
291
|
+
运行以下命令构建索引后再重试:
|
|
292
|
+
pip install sentence-transformers
|
|
293
|
+
python scripts/build_kb_index.py
|
|
294
|
+
\`\`\`
|
|
295
|
+
3. **索引就绪时**:从当前任务上下文中提取关键词和需求描述,构造查询文本,执行:
|
|
296
|
+
\`\`\`
|
|
297
|
+
python scripts/search_kb.py "<查询文本>" --top-k 10 --verbose
|
|
298
|
+
\`\`\`
|
|
299
|
+
4. **解析并输出**:将检索结果格式化输出:
|
|
300
|
+
|
|
301
|
+
\`\`\`
|
|
302
|
+
💡 精确匹配未找到相关条目,已自动回退到语义检索。
|
|
303
|
+
|
|
304
|
+
🔍 语义检索结果(共 {N} 条)
|
|
305
|
+
|
|
306
|
+
#{1} [{分类}] {条目标题}
|
|
307
|
+
文件: {分类}.md | 得分: {score}
|
|
308
|
+
内容: {摘要}
|
|
309
|
+
|
|
310
|
+
#{2} ...
|
|
311
|
+
\`\`\`
|
|
312
|
+
|
|
313
|
+
5. **低分提示**:若所有结果得分均低于 0.3,追加提示:
|
|
314
|
+
\`\`\`
|
|
315
|
+
⚠️ 所有语义检索结果得分均低于 0.3,建议优化查询词或确认知识库覆盖范围。
|
|
316
|
+
\`\`\`
|
|
317
|
+
|
|
318
|
+
6. **索引过期提示**:检索完成后追加:
|
|
319
|
+
\`\`\`
|
|
320
|
+
💡 如需确保索引为最新,可运行: python scripts/build_kb_index.py --dry-run
|
|
321
|
+
\`\`\`
|
|
322
|
+
|
|
323
|
+
> 注:语义检索是 \`check-kb\` 的内置回退能力。调用方只需 \`skill("check-kb")\` 一次,无需再手动调用 \`search-kb\`。
|
|
324
|
+
|
|
325
|
+
### 6. 强制检索标记
|
|
326
|
+
|
|
327
|
+
Skill 加载后,根据步骤 3 的匹配结果决定是否追加提示:
|
|
328
|
+
|
|
329
|
+
- **若步骤 3 找到 ≥1 条相关条目**:在返回内容末尾追加以下提示:
|
|
330
|
+
> ⚠️ 执行任务前,若知识库中有相关条目,务必参考。重复踩过的坑不可再犯。
|
|
331
|
+
此提示确保 Agent 不会跳过知识库查询直接编码。
|
|
332
|
+
|
|
333
|
+
- **若步骤 3 无相关条目**:不追加强制提示,静默返回(已在步骤 4 输出"知识库中暂无相关记录,可继续执行")。
|
|
334
|
+
`,
|
|
335
|
+
'get-bugs': `---
|
|
336
|
+
name: get-bugs
|
|
337
|
+
description: 获取当前模块下状态为 open 或 fixing 的 Bug 列表,供代码 Agent 会话启动或承接时使用。
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
# 获取当前 Bug
|
|
341
|
+
|
|
342
|
+
## 输入
|
|
343
|
+
|
|
344
|
+
无(自动从 \`.openfeel/users/{username}/bugs/index.md\` 和模块归属中提取)
|
|
345
|
+
|
|
346
|
+
## 执行步骤
|
|
347
|
+
|
|
348
|
+
### 1. 读取模块索引
|
|
349
|
+
|
|
350
|
+
读取 \`.openfeel/users/{username}/bugs/index.md\`,获取当前 Agent 负责模块下的所有 Bug 条目(编号、标题、状态、优先级)。
|
|
351
|
+
|
|
352
|
+
### 2. 筛选活跃 Bug
|
|
353
|
+
|
|
354
|
+
过滤出状态为 \`open\` 或 \`fixing\` 的 Bug。
|
|
355
|
+
|
|
356
|
+
### 3. 格式化输出
|
|
357
|
+
|
|
358
|
+
按优先级排序(high → medium → low),输出格式:
|
|
359
|
+
|
|
360
|
+
\`\`\`
|
|
361
|
+
模块 [模块名] 待处理 Bug:
|
|
362
|
+
[BUG-001] (open, high) 登录页面崩溃
|
|
363
|
+
[BUG-003] (fixing, medium) 用户列表排序异常
|
|
364
|
+
|
|
365
|
+
共 2 个:1 个待承接(open) / 1 个修复中(fixing)
|
|
366
|
+
\`\`\`
|
|
367
|
+
|
|
368
|
+
### 4. 无 Bug 时
|
|
369
|
+
|
|
370
|
+
输出:\`模块 [模块名] 当前无待处理 Bug。\`
|
|
371
|
+
`,
|
|
372
|
+
'get-stage-status': `---
|
|
373
|
+
name: get-stage-status
|
|
374
|
+
description: 读取 .openfeel/plan/{stage}/status.md,判断当前子计划状态、责任 Agent、是否允许自动推进以及下一步建议。用于 Reviewer/Executor/Feel Tester 在处理阶段任务前确认流程状态。
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
# 获取子计划状态
|
|
378
|
+
|
|
379
|
+
## 输入
|
|
380
|
+
|
|
381
|
+
- 计划阶段名 \`{stage}\`(如 \`stage01\`、\`auth-login\`)
|
|
382
|
+
- 若用户未提供阶段名,先读取 \`.openfeel/plan/index.md\` 查找当前活跃阶段;仍不明确时询问用户
|
|
383
|
+
|
|
384
|
+
## 执行步骤
|
|
385
|
+
|
|
386
|
+
### 0. 读取全局配置
|
|
387
|
+
|
|
388
|
+
读取 \`.openfeel/config.yaml\`,解析 \`defaults\` 中的 \`execution_mode\`、\`auto_advance\`、\`test_enabled\`、\`merge_mode\`。
|
|
389
|
+
|
|
390
|
+
### 1. 定位状态文件
|
|
391
|
+
|
|
392
|
+
读取 \`.openfeel/plan/{stage}/status.md\`。
|
|
393
|
+
|
|
394
|
+
若文件不存在:
|
|
395
|
+
- 不要自行进入自动流程。
|
|
396
|
+
- 返回 \`missing_status\`,提示需要 Architect 先创建状态文件。
|
|
397
|
+
|
|
398
|
+
### 2. 提取字段
|
|
399
|
+
|
|
400
|
+
解析以下字段:
|
|
401
|
+
|
|
402
|
+
- \`执行模式\`
|
|
403
|
+
- \`自动推进\`
|
|
404
|
+
- \`状态\`
|
|
405
|
+
- \`当前责任 Agent\`
|
|
406
|
+
- \`上一责任 Agent\`
|
|
407
|
+
- \`更新时间\`
|
|
408
|
+
- \`当前任务\`
|
|
409
|
+
- \`阻塞 / 暂停原因\`
|
|
410
|
+
- \`前置依赖\`
|
|
411
|
+
- \`依赖状态\`
|
|
412
|
+
|
|
413
|
+
### 3. 依赖就绪检查
|
|
414
|
+
|
|
415
|
+
若 \`status.md\` 中存在 \`前置依赖\` 字段且不为 \`无\`:
|
|
416
|
+
|
|
417
|
+
1. 读取 \`.openfeel/plan/deps.yaml\`,查找当前阶段的 \`depends_on\` 列表。
|
|
418
|
+
2. 对每条依赖检查其阶段状态:
|
|
419
|
+
- \`type: hard\` 且依赖阶段状态为 \`done\` → 已满足
|
|
420
|
+
- \`type: hard\` 且依赖阶段状态非 \`done\` → 未满足,阻塞
|
|
421
|
+
- \`type: soft\` 且依赖阶段状态为 \`done\` → 已满足
|
|
422
|
+
- \`type: soft\` 且依赖阶段状态非 \`done\` → 弱阻塞(警告但可启动)
|
|
423
|
+
- \`type: mutual_exclusion\` 且依赖阶段状态为 \`done\` → 已满足
|
|
424
|
+
- \`type: mutual_exclusion\` 且依赖阶段状态非 \`done\` → 阻塞,必须等待
|
|
425
|
+
3. 综合判断 \`deps_satisfied\`:
|
|
426
|
+
- 所有 \`hard\` 和 \`mutual_exclusion\` 依赖满足 → \`true\`
|
|
427
|
+
- 任一 \`hard\` 或 \`mutual_exclusion\` 依赖未满足 → \`false\`
|
|
428
|
+
4. 若 \`deps.yaml\` 不存在,视为无依赖声明,\`deps_satisfied = true\`。
|
|
429
|
+
|
|
430
|
+
### 4. 并行候选检测
|
|
431
|
+
|
|
432
|
+
当 \`deps_satisfied = true\` 时:
|
|
433
|
+
|
|
434
|
+
1. 读取所有阶段的 \`status.md\`,筛选满足以下条件的阶段:
|
|
435
|
+
- \`deps_satisfied = true\`(本 Skill 递归判断)
|
|
436
|
+
- \`状态\` 为 \`ready_for_code\` 或 \`auto_running\`
|
|
437
|
+
- \`自动推进\` 为 \`enabled\`(若 status.md 未填则回退到 config.yaml \`auto_advance\`)
|
|
438
|
+
2. 收集为 \`parallel_candidates\` 列表,供 Feel 批量调度执行。
|
|
439
|
+
|
|
440
|
+
### 5. 判断自动推进资格
|
|
441
|
+
|
|
442
|
+
**字段回退**:若 \`status.md\` 未填写 \`执行模式\` 或 \`自动推进\`,从 \`.openfeel/config.yaml\` \`defaults\` 中读取对应值。
|
|
443
|
+
|
|
444
|
+
**测试状态排除**:若 \`.openfeel/config.yaml\` 中 \`test_enabled=false\`,则以下测试链路状态视为已禁用,不参与自动推进:
|
|
445
|
+
- \`ready_for_test\`、\`test_writing\`、\`testing\`、\`bug_found\`、\`bug_fixing\`
|
|
446
|
+
- 当前处于上述任一状态时,建议直接切换至 \`done\`(跳过测试链路)
|
|
447
|
+
- \`review_passed\` 在 \`test_enabled=false\` 时等价于 \`done\`
|
|
448
|
+
|
|
449
|
+
只有同时满足以下条件才返回 \`can_auto_continue = true\`:
|
|
450
|
+
|
|
451
|
+
- \`执行模式\` 为 \`auto\`
|
|
452
|
+
- \`自动推进\` 为 \`enabled\`
|
|
453
|
+
- \`状态\` 不是 \`done\` 或 \`paused\`
|
|
454
|
+
- \`当前责任 Agent\` 不是 \`user\`
|
|
455
|
+
- \`依赖状态\` 不为 \`blocked\`(所有 hard 依赖必须满足)
|
|
456
|
+
|
|
457
|
+
否则返回 \`can_auto_continue = false\`,并说明原因。
|
|
458
|
+
|
|
459
|
+
### 6. 输出格式
|
|
460
|
+
|
|
461
|
+
\`\`\`markdown
|
|
462
|
+
## 子计划状态
|
|
463
|
+
|
|
464
|
+
- 阶段:{stage}
|
|
465
|
+
- 执行模式:manual | auto
|
|
466
|
+
- 自动推进:disabled | enabled
|
|
467
|
+
- 状态:{status}
|
|
468
|
+
- 当前责任 Agent:{agent}
|
|
469
|
+
- 前置依赖:{依赖列表 或 无}
|
|
470
|
+
- 依赖就绪:true | false
|
|
471
|
+
- 可自动推进:true | false
|
|
472
|
+
- 阻塞原因:{reason 或 无}
|
|
473
|
+
|
|
474
|
+
## 并行候选
|
|
475
|
+
{若依赖就绪且可自动推进,列出同批次可并行启动的其他阶段}
|
|
476
|
+
|
|
477
|
+
## 下一步建议
|
|
478
|
+
{根据状态给出下一步,例如:启动 Code、等待用户、启动 Tester、停止流程;若存在并行候选则建议批量启动}
|
|
479
|
+
\`\`\`
|
|
480
|
+
|
|
481
|
+
## 状态到下一步映射
|
|
482
|
+
|
|
483
|
+
| 状态 | 下一步建议 |
|
|
484
|
+
|------|------------|
|
|
485
|
+
| \`planned\` | 等待用户确认或 Planner 细化计划 |
|
|
486
|
+
| \`ready_for_code\` | Planner 可启动 Executor |
|
|
487
|
+
| \`coding\` | Executor 正在开发 |
|
|
488
|
+
| \`ready_for_review\` | Executor 可启动 Reviewer 审查,或等待用户触发 |
|
|
489
|
+
| \`review_failed\` | Reviewer 可启动 Executor 修复审查问题 |
|
|
490
|
+
| \`review_passed\` | Feel 可推进到 ready_for_test |
|
|
491
|
+
| \`ready_for_test\` | Feel 可启动 Feel Tester |
|
|
492
|
+
| \`test_writing\` | Feel Tester 正在写测试 |
|
|
493
|
+
| \`testing\` | Feel Tester 正在测试 |
|
|
494
|
+
| \`bug_found\` | Feel Tester 可启动 Executor 修复 Bug |
|
|
495
|
+
| \`bug_fixing\` | Executor 正在修复 Bug |
|
|
496
|
+
| \`done\` | 流程完成,停止 |
|
|
497
|
+
| \`paused\` | 等待用户处理暂停原因 |
|
|
498
|
+
`,
|
|
499
|
+
'health': `---
|
|
500
|
+
name: health
|
|
501
|
+
description: 加载流水线健康检查结果,供 Agent 判断 flow.json 与工作区状态是否一致。
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
# 流水线健康检查
|
|
505
|
+
|
|
506
|
+
## 输入
|
|
507
|
+
|
|
508
|
+
无
|
|
509
|
+
|
|
510
|
+
## 执行步骤
|
|
511
|
+
|
|
512
|
+
1. 运行 \`openfeel flow health --quick\` 检查关键项(phase/current 合法性)
|
|
513
|
+
2. 需要全面检查时运行 \`openfeel flow health\`(含跨文件一致性、僵尸状态、config.yaml)
|
|
514
|
+
3. 解析输出中的 ✅ / ⚠️ / ❌ 项
|
|
515
|
+
|
|
516
|
+
## 输出
|
|
517
|
+
|
|
518
|
+
健康检查摘要:通过项数、失败项列表及原因,失败时给出修复建议
|
|
519
|
+
`,
|
|
520
|
+
'model-check': `---
|
|
521
|
+
name: model-check
|
|
522
|
+
description: Feel 自检时检查所有 Agent 的模型配置状态,识别期望模型 vs 实际模型的差距,引导用户在目标工具中完成配置。首次配置后存储为部署模板,新项目可直接复用。
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
# 模型配置检查
|
|
526
|
+
|
|
527
|
+
## 触发时机
|
|
528
|
+
|
|
529
|
+
Feel Agent 在以下时机加载本 Skill:
|
|
530
|
+
- 会话启动自检(每次)
|
|
531
|
+
- 用户请求检查模型配置(按需)
|
|
532
|
+
- 新项目首次初始化后(\`openfeel init\`)
|
|
533
|
+
|
|
534
|
+
## 执行步骤
|
|
535
|
+
|
|
536
|
+
### 1. 识别当前平台
|
|
537
|
+
|
|
538
|
+
读取 \`opencode.jsonc\`(或对应平台的配置文件),确定当前适配器平台:
|
|
539
|
+
|
|
540
|
+
| 配置文件 | 平台 |
|
|
541
|
+
|----------|------|
|
|
542
|
+
| \`opencode.jsonc\` | OpenCode |
|
|
543
|
+
| \`kilo/kilo.json\` | Kilo |
|
|
544
|
+
| \`claude/claude.json\` | Claude |
|
|
545
|
+
|
|
546
|
+
若无平台配置文件,提示用户当前不在支持的平台中。
|
|
547
|
+
|
|
548
|
+
### 2. 扫描 Agent 定义
|
|
549
|
+
|
|
550
|
+
扫描 \`.opencode/agents/\`(或对应平台的 agents/ 目录)下所有 \`.md\` 文件,提取每个 Agent 的模型需求。
|
|
551
|
+
|
|
552
|
+
**提取规则(优先级从高到低)**:
|
|
553
|
+
|
|
554
|
+
| 优先级 | 来源 | 识别方式 |
|
|
555
|
+
|:--:|------|----------|
|
|
556
|
+
| 1 | YAML frontmatter \`model\` 字段 | 如 \`model: fast\`,直接提取 |
|
|
557
|
+
| 2 | 正文「模型选择」章节 | 搜索关键词:\`主力推理模型\` / \`推理模型\` / \`快速模型\` / \`异种推理模型\` |
|
|
558
|
+
| 3 | frontmatter \`description\` 字段 | 搜索上述关键词 |
|
|
559
|
+
| 4 | 角色回退表 | 按 Agent 文件名回退(见下方角色映射表) |
|
|
560
|
+
|
|
561
|
+
**角色映射回退表**(当 Agent 文件中无任何模型声明时使用):
|
|
562
|
+
|
|
563
|
+
| Agent 文件 | 默认模型角色 |
|
|
564
|
+
|------------|-------------|
|
|
565
|
+
| \`feel.md\` | \`primary_reasoning\`(主力推理) |
|
|
566
|
+
| \`planner.md\` | \`reasoning\`(推理) |
|
|
567
|
+
| \`executor.md\` | \`fast\`(快速) |
|
|
568
|
+
| \`reviewer.md\` | \`cross_model\`(异种推理) |
|
|
569
|
+
| \`archiver.md\` | \`reasoning\`(推理) |
|
|
570
|
+
| \`schemer.md\` | \`reasoning\`(推理) |
|
|
571
|
+
| \`feel-tester.md\` | \`reasoning\`(推理) |
|
|
572
|
+
| \`utility.md\` | \`fast\`(快速) |
|
|
573
|
+
| \`vision.md\` | \`multimodal\`(多模态) |
|
|
574
|
+
|
|
575
|
+
### 3. 检查 config.yaml 模型配置
|
|
576
|
+
|
|
577
|
+
读取 \`.openfeel/config.yaml\`,检查 \`models\` 节是否存在。
|
|
578
|
+
|
|
579
|
+
**已配置状态**:
|
|
580
|
+
\`\`\`yaml
|
|
581
|
+
models:
|
|
582
|
+
default: # 兜底配置(必填)
|
|
583
|
+
provider: deepseek
|
|
584
|
+
model_name: deepseek-v4-pro
|
|
585
|
+
base_url: https://api.deepseek.com
|
|
586
|
+
api_key_env: DEEPSEEK_API_KEY
|
|
587
|
+
agents: # Agent 级覆盖(可选)
|
|
588
|
+
reviewer:
|
|
589
|
+
provider: anthropic
|
|
590
|
+
model_name: claude-sonnet-4-20250514
|
|
591
|
+
base_url: https://api.anthropic.com
|
|
592
|
+
api_key_env: ANTHROPIC_API_KEY
|
|
593
|
+
executor:
|
|
594
|
+
provider: deepseek
|
|
595
|
+
model_name: deepseek-v4-flash
|
|
596
|
+
roles: # 角色级覆盖(可选)
|
|
597
|
+
cross_model:
|
|
598
|
+
provider: openai
|
|
599
|
+
model_name: gpt-4o
|
|
600
|
+
\`\`\`
|
|
601
|
+
|
|
602
|
+
**配置字段说明**:
|
|
603
|
+
- \`provider\`:模型供应商(deepseek / openai / anthropic / zhipu / qwen 等)
|
|
604
|
+
- \`model_name\`:具体模型 ID
|
|
605
|
+
- \`base_url\`:API endpoint
|
|
606
|
+
- \`api_key_env\`:环境变量名,存储 API Key
|
|
607
|
+
|
|
608
|
+
### 4. 交叉对比:期望 vs 实际
|
|
609
|
+
|
|
610
|
+
对每个 Agent,执行三级匹配(与 Architect Agent 定义的优先级一致):
|
|
611
|
+
|
|
612
|
+
\`\`\`
|
|
613
|
+
当前Agent → models.agents.{agent_id} 存在?
|
|
614
|
+
├─ 是 → 使用该配置 ✅
|
|
615
|
+
└─ 否 → models.roles.{角色} 存在?
|
|
616
|
+
├─ 是 → 使用该配置 ✅
|
|
617
|
+
└─ 否 → models.default 存在?
|
|
618
|
+
├─ 是 → 使用默认配置 ⚠️(可能不满足角色要求)
|
|
619
|
+
└─ 否 → 无配置 ❌
|
|
620
|
+
\`\`\`
|
|
621
|
+
|
|
622
|
+
输出对比结果表:
|
|
623
|
+
|
|
624
|
+
\`\`\`markdown
|
|
625
|
+
| Agent | 角色要求 | 实际模型 | 配置来源 | 状态 |
|
|
626
|
+
|-------|----------|----------|----------|:--:|
|
|
627
|
+
| Feel | 主力推理 | deepseek-v4-pro | default | ⚠️ |
|
|
628
|
+
| Reviewer | **异种推理** | deepseek-v4-pro | default | ❌ 与主力相同! |
|
|
629
|
+
| Executor | 快速 | deepseek-v4-pro | default | ⚠️ 未使用快速模型 |
|
|
630
|
+
\`\`\`
|
|
631
|
+
|
|
632
|
+
### 5. 输出检查报告
|
|
633
|
+
|
|
634
|
+
按以下格式向用户展示:
|
|
635
|
+
|
|
636
|
+
\`\`\`markdown
|
|
637
|
+
## 🔍 模型配置检查报告
|
|
638
|
+
|
|
639
|
+
**平台**:OpenCode
|
|
640
|
+
**配置文件**:.openfeel/config.yaml
|
|
641
|
+
**检查时间**:yyyy-mm-dd HH:MM
|
|
642
|
+
|
|
643
|
+
### 总览
|
|
644
|
+
|
|
645
|
+
- 已定义 Agent:{N} 个
|
|
646
|
+
- 有模型声明:{M} 个
|
|
647
|
+
- 模型配置已就绪:{K}/{N}
|
|
648
|
+
- 异种审查就绪:{是/否}
|
|
649
|
+
|
|
650
|
+
### Agent 模型匹配详情
|
|
651
|
+
|
|
652
|
+
| Agent | 角色要求 | 当前模型 | 来源 | 状态 |
|
|
653
|
+
|-------|----------|----------|------|:--:|
|
|
654
|
+
| ... | ... | ... | ... | ✅/⚠️/❌ |
|
|
655
|
+
|
|
656
|
+
### 关键问题
|
|
657
|
+
|
|
658
|
+
{列出所有 ❌ 和关键 ⚠️ 项}
|
|
659
|
+
|
|
660
|
+
### 下一步
|
|
661
|
+
|
|
662
|
+
{根据问题严重程度给出建议}
|
|
663
|
+
\`\`\`
|
|
664
|
+
|
|
665
|
+
### 6. 引导用户配置
|
|
666
|
+
|
|
667
|
+
若检查发现以下任一问题,**必须**使用 \`question\` 工具引导用户:
|
|
668
|
+
|
|
669
|
+
| 触发条件 | 引导内容 |
|
|
670
|
+
|----------|----------|
|
|
671
|
+
| \`models\` 节不存在 | "未检测到模型配置。你需要为不同角色分配模型吗?" → 引导创建 |
|
|
672
|
+
| Reviewer 使用与主力相同模型 | "⚠️ Reviewer 当前与 Feel 使用相同模型,异种交叉审查的核心优势无法发挥。建议为 Reviewer 配置不同的模型系列。" |
|
|
673
|
+
| Executor 使用推理模型 | "⚠️ Executor 建议使用快速模型以节省成本。是否配置?" |
|
|
674
|
+
| 关键 Agent 无任何配置 | "以下 Agent 无模型配置:{列表}。请配置。" |
|
|
675
|
+
|
|
676
|
+
### 7. 写入配置
|
|
677
|
+
|
|
678
|
+
用户确认后,将模型配置写入 \`.openfeel/config.yaml\` 的 \`models\` 节。若 \`models\` 节已存在则更新,不存在则追加。
|
|
679
|
+
|
|
680
|
+
写入后执行格式校验(\`python -m yaml.tool\` 或等效检查),确保 YAML 合法。
|
|
681
|
+
|
|
682
|
+
### 8. 存储部署模板
|
|
683
|
+
|
|
684
|
+
配置完成后,自动将 \`models\` 节导出为独立模板文件 \`.openfeel/models.template.yaml\`:
|
|
685
|
+
|
|
686
|
+
\`\`\`yaml
|
|
687
|
+
# OpenFeel 模型配置模板
|
|
688
|
+
# 部署新项目时,复制此文件内容到目标项目的 config.yaml models 节
|
|
689
|
+
# 或直接复制此文件到 .openfeel/ 并重命名为 config.yaml(需合并其他节)
|
|
690
|
+
#
|
|
691
|
+
# 最近配置时间:yyyy-mm-dd HH:MM
|
|
692
|
+
# 平台:OpenCode
|
|
693
|
+
|
|
694
|
+
models:
|
|
695
|
+
default:
|
|
696
|
+
provider: xxx
|
|
697
|
+
model_name: xxx
|
|
698
|
+
...
|
|
699
|
+
agents:
|
|
700
|
+
reviewer:
|
|
701
|
+
provider: xxx
|
|
702
|
+
...
|
|
703
|
+
roles:
|
|
704
|
+
cross_model:
|
|
705
|
+
provider: xxx
|
|
706
|
+
...
|
|
707
|
+
\`\`\`
|
|
708
|
+
|
|
709
|
+
此模板在下次 \`openfeel init\` 或新项目部署时自动检测并建议复用。
|
|
710
|
+
|
|
711
|
+
## 输出规范
|
|
712
|
+
|
|
713
|
+
- 状态图标:✅ 已满足、⚠️ 降级使用(可接受但非最优)、❌ 缺失或严重不匹配
|
|
714
|
+
- 报告语言:中文
|
|
715
|
+
- 每次检查后将结果摘要写入 \`.openfeel/log/\`(仅首次发现关键问题时)
|
|
716
|
+
`,
|
|
717
|
+
'model-config': `---
|
|
718
|
+
name: model-config
|
|
719
|
+
description: 查找和配置 Agent 模型。当 Agent 报 "Model not found" 或需要调整/新增 Agent 模型时使用。覆盖 opencode.jsonc 配置、模型名查找方法、多模态模型(Vision)特殊注意事项。
|
|
720
|
+
---
|
|
721
|
+
|
|
722
|
+
# Skill: model-config
|
|
723
|
+
|
|
724
|
+
# Agent 模型查找与配置
|
|
725
|
+
|
|
726
|
+
## 何时使用
|
|
727
|
+
|
|
728
|
+
- Agent 调用时报 \`Model not found: xxx\`
|
|
729
|
+
- 需要为 Agent 更换或指定模型
|
|
730
|
+
- 新增 Agent 后需要配置其模型
|
|
731
|
+
- Vision / 多模态模型无法正常调用
|
|
732
|
+
|
|
733
|
+
## 配置位置
|
|
734
|
+
|
|
735
|
+
Agent 模型配置在 **\`opencode.jsonc\`**(项目根目录)中:
|
|
736
|
+
|
|
737
|
+
\`\`\`jsonc
|
|
738
|
+
{
|
|
739
|
+
"agent": {
|
|
740
|
+
"vision": {
|
|
741
|
+
"model": "qwen3-vl-plus" // 模型名格式:provider/model-id 或 model-id
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
\`\`\`
|
|
746
|
+
|
|
747
|
+
> ⚠️ **配置修改后必须重启 opencode 才能生效**。运行中的会话使用启动时加载的配置。
|
|
748
|
+
|
|
749
|
+
## 查找可用模型名
|
|
750
|
+
|
|
751
|
+
当收到 \`"Model not found: xxx. Did you mean: aaa, bbb?"\` 错误时:
|
|
752
|
+
- 列出平台已安装的可用模型名在 \`Did you mean:\` 之后
|
|
753
|
+
- 从中选择一个作为新模型名
|
|
754
|
+
- 不建议凭记忆猜测模型名,以平台提示为准
|
|
755
|
+
|
|
756
|
+
## Agent 定义文件中的 model 字段
|
|
757
|
+
|
|
758
|
+
\`.opencode/agents/<name>.md\` 中的 \`model\` 字段是声明性的(供 Agent 自述),**不直接控制平台模型分配**。实际的模型绑定由 \`opencode.jsonc\` 的 \`agent.<name>.model\` 控制。
|
|
759
|
+
|
|
760
|
+
因此修改模型需要两步:
|
|
761
|
+
1. 修改 \`opencode.jsonc\` 中的 \`agent.<name>.model\`
|
|
762
|
+
2. (可选)同步修改 \`.opencode/agents/<name>.md\` 和模板文件 \`src/core/templates-data/agents/\` 中的 \`model\` 字段,保持一致性
|
|
763
|
+
|
|
764
|
+
## 多模态(Vision)模型特殊规则
|
|
765
|
+
|
|
766
|
+
- Feel 的主力模型(DeepSeek V4 Pro)不支持图片输入
|
|
767
|
+
- 遇到图片输入时 Feel 会自动委托 Vision Agent
|
|
768
|
+
- Vision Agent 需要配置多模态模型(如 \`qwen-vl-plus\`、\`qwen3-vl-plus\`)
|
|
769
|
+
- 模型名不要随意添加前缀(如 \`alibaba/\`),以平台提示的可用名为准
|
|
770
|
+
- **模型引用格式**:\`{auth.json中的key}/{模型ID}\`,不是 \`provider.name\` 也不是 \`provider.id\`
|
|
771
|
+
- 读取 \`~/.local/share/opencode/auth.json\` 确认实际 provider key(常见:\`alibaba-cn\`、\`deepseek\`、\`zhipuai\`)
|
|
772
|
+
- \`provider\` 块中的 \`name\` 和 \`id\` 仅用于显示,不影响模型解析
|
|
773
|
+
|
|
774
|
+
## 项目 Agent 模型概览
|
|
775
|
+
|
|
776
|
+
| Agent | 模型类型 | 备注 |
|
|
777
|
+
|-------|---------|------|
|
|
778
|
+
| Feel(总统领) | 推理模型 | DeepSeek V4 Pro — 不支持多模态 |
|
|
779
|
+
| Planner | 推理模型 | — |
|
|
780
|
+
| Schemer | 推理模型 | — |
|
|
781
|
+
| Executor | 快速模型 (Flash) | — |
|
|
782
|
+
| Reviewer | 异种推理模型 (GLM) | — |
|
|
783
|
+
| Feel Tester | 推理模型 | — |
|
|
784
|
+
| 事务官 | 快速模型 (Flash) | — |
|
|
785
|
+
| Vision | 多模态模型 | 需配 qwen3-vl-plus |
|
|
786
|
+
| Archiver | 推理模型 | — |
|
|
787
|
+
`,
|
|
788
|
+
'recover': `---
|
|
789
|
+
name: recover
|
|
790
|
+
description: 跨会话上下文恢复,供 Agent 在会话启动时重建流水线状态。
|
|
791
|
+
---
|
|
792
|
+
|
|
793
|
+
# 跨会话上下文恢复
|
|
794
|
+
|
|
795
|
+
## 输入
|
|
796
|
+
|
|
797
|
+
无
|
|
798
|
+
|
|
799
|
+
## 执行步骤
|
|
800
|
+
|
|
801
|
+
1. 运行 \`openfeel flow recover\` 获取全局状态、流水线阶段、当前操作、阻塞原因与待处理任务
|
|
802
|
+
2. 读取 \`.openfeel/users/{username}/dev_last.md\` 恢复上次操作状态与待续事项
|
|
803
|
+
3. 将两者合并为当前会话起点
|
|
804
|
+
|
|
805
|
+
## 输出
|
|
806
|
+
|
|
807
|
+
恢复摘要:流水线状态 + 阻塞项 + 待处理任务列表
|
|
808
|
+
`,
|
|
809
|
+
'roadmap': `---
|
|
810
|
+
name: roadmap
|
|
811
|
+
description: 加载项目路线图,供 Agent 查看版本规划和里程碑。
|
|
812
|
+
---
|
|
813
|
+
|
|
814
|
+
# 路线图加载
|
|
815
|
+
|
|
816
|
+
## 输入
|
|
817
|
+
|
|
818
|
+
无(可传入版本号过滤,如 \`v5\`)
|
|
819
|
+
|
|
820
|
+
## 执行步骤
|
|
821
|
+
|
|
822
|
+
1. 运行 \`openfeel roadmap show\` 列出 \`.openfeel/roadmap/\` 下所有版本大纲,或读取指定 \`v{version}.md\`
|
|
823
|
+
2. 提取各版本「目标」「阶段划分」「里程碑」节
|
|
824
|
+
3. 对照 \`.openfeel/flow.json\` 中各阶段 phase 判断进度状态
|
|
825
|
+
|
|
826
|
+
## 输出
|
|
827
|
+
|
|
828
|
+
格式化路线图摘要:版本清单 + 各版本阶段进度
|
|
829
|
+
`,
|
|
830
|
+
'search-kb': `---
|
|
831
|
+
name: search-kb
|
|
832
|
+
description: 语义检索 .openfeel/kb/ 项目知识库。当精确匹配无结果或任务描述模糊时,通过向量相似度搜索语义相关的知识条目。支持图谱遍历返回关联条目。
|
|
833
|
+
---
|
|
834
|
+
|
|
835
|
+
# 语义检索知识库
|
|
836
|
+
|
|
837
|
+
## 输入
|
|
838
|
+
|
|
839
|
+
- \`query\`(必需):查询文本,描述当前任务需求、遇到的问题或想要查找的知识点。
|
|
840
|
+
- \`top_k\`(可选):返回结果数量,默认 10。
|
|
841
|
+
- \`min_score\`(可选):最低分数阈值,默认 0.1。分数低于此值的结果将被过滤。
|
|
842
|
+
|
|
843
|
+
## 前置条件
|
|
844
|
+
|
|
845
|
+
- 向量索引已构建(运行 \`python scripts/build_kb_index.py\`)
|
|
846
|
+
- 已安装 \`sentence-transformers\`(\`pip install sentence-transformers\`)
|
|
847
|
+
- 索引文件 \`.openfeel/tmp/vectors/index.json\` 存在
|
|
848
|
+
- (可选)图谱已构建(运行 \`python scripts/build_kb_index.py --graph\`),用于返回关联条目
|
|
849
|
+
|
|
850
|
+
## 执行步骤
|
|
851
|
+
|
|
852
|
+
### 1. 检查索引就绪
|
|
853
|
+
|
|
854
|
+
确认 \`.openfeel/tmp/vectors/index.json\` 文件存在。若不存在,拒绝执行并提示先运行 \`build_kb_index.py\`。
|
|
855
|
+
|
|
856
|
+
### 2. 执行语义检索
|
|
857
|
+
|
|
858
|
+
执行 \`python scripts/search_kb.py "<query>" --top-k <top_k> --min-score <min_score> --verbose\`。
|
|
859
|
+
|
|
860
|
+
### 3. 解析结果
|
|
861
|
+
|
|
862
|
+
输出格式化的检索结果摘要:
|
|
863
|
+
|
|
864
|
+
\`\`\`
|
|
865
|
+
🔍 语义检索结果(共 {N} 条)
|
|
866
|
+
|
|
867
|
+
#{1} [architecture] OAuth2 登录流程设计
|
|
868
|
+
文件: architecture.md | 得分: 0.87
|
|
869
|
+
内容: 采用 Authorization Code Grant 流程...
|
|
870
|
+
|
|
871
|
+
#{2} [patterns] 状态机模式使用约定
|
|
872
|
+
文件: patterns.md | 得分: 0.72
|
|
873
|
+
内容: 项目中所有状态流转统一使用 Switch + Enum...
|
|
874
|
+
\`\`\`
|
|
875
|
+
|
|
876
|
+
### 4. 图谱遍历(关联条目发现)
|
|
877
|
+
|
|
878
|
+
当查询命中条目后,若 \`.openfeel/tmp/graph.json\` 存在且命中条目在其中,按以下步骤执行图谱遍历:
|
|
879
|
+
|
|
880
|
+
#### 4.1 一度关联(直接关联)
|
|
881
|
+
|
|
882
|
+
读取 \`graph.json\`,查找命中条目的所有**直接引用**和**被直接引用**的条目:
|
|
883
|
+
|
|
884
|
+
\`\`\`
|
|
885
|
+
🔗 一度关联条目(直接关联)
|
|
886
|
+
|
|
887
|
+
引用 → {N} 个条目:[[条目A]]、[[条目B]]
|
|
888
|
+
被引用 ← {M} 个条目:[[条目C]]
|
|
889
|
+
\`\`\`
|
|
890
|
+
|
|
891
|
+
#### 4.2 二度关联(间接关联)
|
|
892
|
+
|
|
893
|
+
在一度关联的基础上,再展开一层,返回间接关联的条目:
|
|
894
|
+
|
|
895
|
+
\`\`\`
|
|
896
|
+
🔗🔗 二度关联条目(间接关联)
|
|
897
|
+
|
|
898
|
+
引用 → [[条目D]](经由 [[条目A]])
|
|
899
|
+
被引用 ← [[条目E]](经由 [[条目C]])
|
|
900
|
+
\`\`\`
|
|
901
|
+
|
|
902
|
+
二度关联按"经由哪个一度节点"分组展示,便于理解关联路径。
|
|
903
|
+
|
|
904
|
+
#### 4.3 遍历实现
|
|
905
|
+
|
|
906
|
+
执行 \`python scripts/kb_graph.py --from "<命中条目标题>" --depth 2\`,解析子图输出提取关联节点和边。
|
|
907
|
+
|
|
908
|
+
### 5. 智能解读
|
|
909
|
+
|
|
910
|
+
结合当前任务上下文解读检索结果:
|
|
911
|
+
- 标注与当前任务高度相关的条目
|
|
912
|
+
- 标注可能需要进一步查阅的条目
|
|
913
|
+
- 若所有结果得分均低于 0.3,建议用户优化查询词或确认知识库覆盖范围
|
|
914
|
+
- 若图谱返回的关联条目与检索结果重叠,合并去重并标注来源(语义匹配 / 图谱关联)
|
|
915
|
+
|
|
916
|
+
## 输出格式
|
|
917
|
+
|
|
918
|
+
\`\`\`
|
|
919
|
+
## 语义检索结果
|
|
920
|
+
|
|
921
|
+
查询: "{query}"
|
|
922
|
+
结果数: {N}
|
|
923
|
+
|
|
924
|
+
{格式化结果列表}
|
|
925
|
+
|
|
926
|
+
## 关联条目(图遍历)
|
|
927
|
+
|
|
928
|
+
{一度关联条目列表}
|
|
929
|
+
|
|
930
|
+
{二度关联条目列表}
|
|
931
|
+
|
|
932
|
+
### 解读
|
|
933
|
+
- 相关条目(得分 ≥ 0.5): {count} 条,可直接参考
|
|
934
|
+
- 弱相关条目(0.3 ≤ 得分 < 0.5): {count} 条,建议进一步确认
|
|
935
|
+
- 低相关条目(得分 < 0.3): {count} 条,可能不适用
|
|
936
|
+
- 图关联条目(未被语义检索命中): {count} 条
|
|
937
|
+
\`\`\`
|
|
938
|
+
|
|
939
|
+
## 参数扩展
|
|
940
|
+
|
|
941
|
+
新增可选参数:
|
|
942
|
+
|
|
943
|
+
- \`--with-graph\`:启用图谱遍历返回关联条目(默认启用,若 graph.json 不可用则静默跳过)
|
|
944
|
+
- \`--graph-depth\`:图谱遍历深度,1=一度关联,2=二度关联(默认: 2)
|
|
945
|
+
|
|
946
|
+
## 注意事项
|
|
947
|
+
|
|
948
|
+
- 向量索引是缓存层,文件系统始终是 single source of truth。若检索结果与预期不符,检查索引是否过期(运行 \`--dry-run\` 查看变更文件)
|
|
949
|
+
- 图谱链接亦是缓存层,Markdown 文件中 \`[[wikilink]]\` 是真实数据源;图谱可随时通过 \`python scripts/build_kb_index.py --graph\` 重建
|
|
950
|
+
- 语义检索适合模糊查询和探索性搜索,精确关键词匹配优先使用 \`check-kb\`
|
|
951
|
+
- 此技能是 \`check-kb\` 的回退方案——当 \`check-kb\` 精确匹配无结果时可自动调用
|
|
952
|
+
- 图遍历返回的关联条目仅基于已建立的 wikilink 链接,若条目未引用或被引用其他条目,图谱中不会出现对应关联
|
|
953
|
+
`,
|
|
954
|
+
'sync-status': `---
|
|
955
|
+
name: sync-status
|
|
956
|
+
description: 聚合所有成员的任务进度视图,供任意 Agent 快速了解项目整体协作状态。
|
|
957
|
+
---
|
|
958
|
+
|
|
959
|
+
# Skill: sync-status
|
|
960
|
+
|
|
961
|
+
# 聚合任务进度
|
|
962
|
+
|
|
963
|
+
## 输入
|
|
964
|
+
|
|
965
|
+
无(自动从 \`.openfeel/dev/current.md\` 提取)
|
|
966
|
+
|
|
967
|
+
## 执行步骤
|
|
968
|
+
|
|
969
|
+
### 1. 读取进度文件
|
|
970
|
+
|
|
971
|
+
读取 \`.openfeel/dev/current.md\`,提取所有 \`@{username}\` 行。
|
|
972
|
+
|
|
973
|
+
### 2. 解析任务条目
|
|
974
|
+
|
|
975
|
+
对每行提取:
|
|
976
|
+
- **成员**:\`@{username}\` 后的用户名
|
|
977
|
+
- **模块**:\`[模块名]\` 或 \`[-]\`
|
|
978
|
+
- **状态**:\`进行中\` / \`阻塞\` / \`已完成\`
|
|
979
|
+
- **描述**:状态后的任务描述文本
|
|
980
|
+
- **锁定**:若有 \`🔒\` 标记,列出锁定的文件
|
|
981
|
+
|
|
982
|
+
### 3. 查漏补缺
|
|
983
|
+
|
|
984
|
+
- 对比 \`.openfeel/users/\` 下的所有用户目录,检查是否有成员在 \`current.md\` 中无记录
|
|
985
|
+
- 若有,标记为「未同步」
|
|
986
|
+
|
|
987
|
+
### 4. 格式化输出
|
|
988
|
+
|
|
989
|
+
按状态分组输出(进行中 → 阻塞 → 已完成 → 未同步),格式:
|
|
990
|
+
|
|
991
|
+
\`\`\`
|
|
992
|
+
📊 项目协作进度
|
|
993
|
+
|
|
994
|
+
🟢 进行中(N 人)
|
|
995
|
+
@alice [auth] 登录模块重构
|
|
996
|
+
🔒 src/auth/login.py
|
|
997
|
+
@bob [db] 数据库迁移脚本编写
|
|
998
|
+
🔒 migrations/v2.sql
|
|
999
|
+
|
|
1000
|
+
🟡 阻塞(M 人)
|
|
1001
|
+
@charlie [api] 等待第三方 OAuth 审批
|
|
1002
|
+
|
|
1003
|
+
🔵 已完成(K 人)
|
|
1004
|
+
@dave [config] 环境变量模板补充
|
|
1005
|
+
|
|
1006
|
+
⚪ 未同步(L 人)
|
|
1007
|
+
@eve — 尚未在 current.md 中声明任务
|
|
1008
|
+
\`\`\`
|
|
1009
|
+
|
|
1010
|
+
### 5. 偏离告警
|
|
1011
|
+
|
|
1012
|
+
若发现同一模块有 2 人同时标记为「进行中」且无 🔒 区分,输出告警:
|
|
1013
|
+
|
|
1014
|
+
\`\`\`
|
|
1015
|
+
⚠️ 模块 [module_name] 多人同时活跃,请确认无冲突
|
|
1016
|
+
\`\`\`
|
|
1017
|
+
|
|
1018
|
+
## 输出
|
|
1019
|
+
|
|
1020
|
+
格式化后的 Markdown 进度摘要,不含文件修改。
|
|
1021
|
+
|
|
1022
|
+
Base directory for this skill: file:///C:/Code/AI/AI_Prompt/.kilo/skills/sync-status
|
|
1023
|
+
`,
|
|
1024
|
+
'update-stage-status': `---
|
|
1025
|
+
name: update-stage-status
|
|
1026
|
+
description: 标准化更新 .openfeel/plan/{stage}/status.md 的子计划状态、责任 Agent 和状态记录,避免各 Agent 随意改写状态文件。适用于自动闭环和人工流程中的阶段状态变更。
|
|
1027
|
+
---
|
|
1028
|
+
|
|
1029
|
+
# 更新子计划状态
|
|
1030
|
+
|
|
1031
|
+
## 输入
|
|
1032
|
+
|
|
1033
|
+
- 计划阶段名 \`{stage}\`
|
|
1034
|
+
- 新状态 \`{status}\`
|
|
1035
|
+
- 当前责任 Agent \`{current_agent}\`
|
|
1036
|
+
- 上一责任 Agent \`{previous_agent}\`
|
|
1037
|
+
- 说明 \`{note}\`
|
|
1038
|
+
- 是否保持自动推进 \`{keep_auto}\`(默认保持原值)
|
|
1039
|
+
|
|
1040
|
+
### 可选输入(Worktree / 并行 管理)
|
|
1041
|
+
|
|
1042
|
+
当阶段以 worktree 模式运行时,可额外传入以下字段:
|
|
1043
|
+
|
|
1044
|
+
- \`worktree_branch\`:worktree 分支名(如 \`auto-stage-02\`)
|
|
1045
|
+
- \`parallel_batch\`:并行批次标识(如 \`batch-2026-05-15-001\`),同一批次并行启动的 worktree 共享此标识
|
|
1046
|
+
- \`parallel_stages\`:同批次并行阶段列表(如 \`[stage-04]\`)
|
|
1047
|
+
- \`merge_status\`:合并状态(\`not_started\` / \`pending_merge\` / \`merged\` / \`cleanup_ready\` / \`cleaned\`)
|
|
1048
|
+
- \`depends_status\`:依赖状态(\`pending\` / \`satisfied\` / \`blocked\`),当依赖阶段完成时更新
|
|
1049
|
+
|
|
1050
|
+
## 执行步骤
|
|
1051
|
+
|
|
1052
|
+
### 0. 读取全局配置
|
|
1053
|
+
|
|
1054
|
+
读取 \`.openfeel/config.yaml\`,获取 \`defaults\` 中的 \`execution_mode\`、\`auto_advance\`、\`merge_mode\`。
|
|
1055
|
+
|
|
1056
|
+
### 1. 读取状态文件
|
|
1057
|
+
|
|
1058
|
+
读取 \`.openfeel/plan/{stage}/status.md\`。
|
|
1059
|
+
|
|
1060
|
+
若文件不存在且当前 Agent 为 Architect:
|
|
1061
|
+
1. 从 \`.openfeel/config.yaml\` \`defaults\` 读取 \`execution_mode\`、\`auto_advance\` 作为初始值
|
|
1062
|
+
2. 按模板创建 \`status.md\`,将 config 默认值写入对应字段
|
|
1063
|
+
其他 Agent 不得自行创建,必须提示用户或 Architect 先初始化阶段状态。
|
|
1064
|
+
|
|
1065
|
+
### 2. 校验状态变更
|
|
1066
|
+
|
|
1067
|
+
允许的状态值:
|
|
1068
|
+
|
|
1069
|
+
\`\`\`text
|
|
1070
|
+
planned | ready_for_code | coding | ready_for_review | review_failed | review_passed | ready_for_test | test_writing | testing | bug_found | bug_fixing | done | paused
|
|
1071
|
+
\`\`\`
|
|
1072
|
+
|
|
1073
|
+
若新状态不在列表中,停止并说明错误。
|
|
1074
|
+
|
|
1075
|
+
### 3. 更新字段
|
|
1076
|
+
|
|
1077
|
+
**常规更新**(每次状态变更必须更新):
|
|
1078
|
+
|
|
1079
|
+
- \`状态\`
|
|
1080
|
+
- \`当前责任 Agent\`
|
|
1081
|
+
- \`上一责任 Agent\`
|
|
1082
|
+
- \`更新时间\`
|
|
1083
|
+
|
|
1084
|
+
**Worktree / 并行 更新**(仅在可选输入传入时更新,位于 \`## Worktree / Session\` 块):
|
|
1085
|
+
|
|
1086
|
+
- \`分支名\` → \`worktree_branch\`(如 \`auto-stage-02\`)
|
|
1087
|
+
- \`并行批次\` → \`parallel_batch\`
|
|
1088
|
+
- \`并行阶段\` → \`parallel_stages\`
|
|
1089
|
+
- \`合并状态\` → \`merge_status\`
|
|
1090
|
+
|
|
1091
|
+
**依赖状态更新**(位于文件顶部字段):
|
|
1092
|
+
|
|
1093
|
+
- \`依赖状态\` → \`depends_status\`(当 Architect 检测到依赖阶段完成时更新,典型值:\`pending → satisfied\`)
|
|
1094
|
+
|
|
1095
|
+
除非用户明确要求,否则不得改变:
|
|
1096
|
+
|
|
1097
|
+
- \`执行模式\`
|
|
1098
|
+
- \`自动推进\`
|
|
1099
|
+
- \`前置依赖\`(由 Architect 在 Phase 3.5 中声明,运行时不应修改)
|
|
1100
|
+
|
|
1101
|
+
### 4. 追加状态记录
|
|
1102
|
+
|
|
1103
|
+
在 \`## 状态记录\` 表格末尾追加:
|
|
1104
|
+
|
|
1105
|
+
\`\`\`markdown
|
|
1106
|
+
| yyyy-mm-dd HH:MM | {agent} | {旧状态} → {新状态} | {note} |
|
|
1107
|
+
\`\`\`
|
|
1108
|
+
|
|
1109
|
+
### 5. 安全暂停规则
|
|
1110
|
+
|
|
1111
|
+
遇到以下情况必须将状态改为 \`paused\`,当前责任 Agent 改为 \`user\`:
|
|
1112
|
+
|
|
1113
|
+
- 计划外架构变更
|
|
1114
|
+
- 需要修改范围超过原计划
|
|
1115
|
+
- 权限不明确
|
|
1116
|
+
- 测试环境缺失
|
|
1117
|
+
- 连续两次验收失败
|
|
1118
|
+
- 自动推进链路无法判断下一步
|
|
1119
|
+
|
|
1120
|
+
### 6. 合并状态处理
|
|
1121
|
+
|
|
1122
|
+
当子计划状态变为 \`done\` 或 \`review_passed\`(且 Reviewer 验收完毕)时,根据 \`.openfeel/config.yaml\` 中的 \`merge_mode\` 决定合并行为:
|
|
1123
|
+
|
|
1124
|
+
- **\`merge_mode=auto\`**:
|
|
1125
|
+
1. 将 \`合并状态\` 更新为 \`merged\`(实际合并由 Executor 执行 git 操作,Skill 仅更新状态字段)
|
|
1126
|
+
2. 将 \`合并状态\` 更新为 \`cleaned\`
|
|
1127
|
+
3. 在状态记录中注明"自动合并"
|
|
1128
|
+
- **\`merge_mode=manual\`**:
|
|
1129
|
+
1. 将 \`合并状态\` 设为 \`pending_merge\`
|
|
1130
|
+
2. 输出提示:合并与清理需手动完成
|
|
1131
|
+
|
|
1132
|
+
### 7. 输出结果
|
|
1133
|
+
|
|
1134
|
+
输出更新摘要:
|
|
1135
|
+
|
|
1136
|
+
\`\`\`markdown
|
|
1137
|
+
已更新 {stage}/status.md:
|
|
1138
|
+
- 状态:{旧状态} → {新状态}
|
|
1139
|
+
- 当前责任 Agent:{current_agent}
|
|
1140
|
+
- 自动推进:保持 {enabled/disabled}
|
|
1141
|
+
\`\`\`
|
|
1142
|
+
`,
|
|
1143
|
+
'wizard': `---
|
|
1144
|
+
name: wizard
|
|
1145
|
+
description: 交互式流水线向导,供 Agent 在终端中逐步推进流水线阶段。
|
|
1146
|
+
---
|
|
1147
|
+
|
|
1148
|
+
# 交互式流水线向导
|
|
1149
|
+
|
|
1150
|
+
## 输入
|
|
1151
|
+
|
|
1152
|
+
无
|
|
1153
|
+
|
|
1154
|
+
## 执行步骤
|
|
1155
|
+
|
|
1156
|
+
1. 运行 \`openfeel flow wizard\` 启动交互式向导
|
|
1157
|
+
2. 按提示选择要推进的阶段和下一步 phase(基于当前阶段的可达 transitions)
|
|
1158
|
+
3. 确认后执行推进,循环直至阶段 done 或退出
|
|
1159
|
+
|
|
1160
|
+
## 输出
|
|
1161
|
+
|
|
1162
|
+
向导推进结果:阶段 phase 变化(from → to),结束/退出提示
|
|
1163
|
+
|
|
1164
|
+
> 注:需交互式终端(TTY),非交互环境请改用 \`openfeel flow advance --stage <id> --to <phase>\`
|
|
1165
|
+
`,
|
|
1166
|
+
};
|
|
1167
|
+
// AUTO-GENERATED-END: SKILL_DEFINITIONS
|
|
1168
|
+
// ─── 新增的 Skill 名称列表 ─────────────────────────────────────────
|
|
1169
|
+
const NEW_SKILL_NAMES = [
|
|
1170
|
+
'bug-acceptance',
|
|
1171
|
+
'check-kb',
|
|
1172
|
+
'get-bugs',
|
|
1173
|
+
'get-stage-status',
|
|
1174
|
+
'model-check',
|
|
1175
|
+
'search-kb',
|
|
1176
|
+
'sync-status',
|
|
1177
|
+
'update-stage-status',
|
|
1178
|
+
];
|
|
1179
|
+
// ─── 辅助函数 ──────────────────────────────────────────────────────
|
|
1180
|
+
/**
|
|
1181
|
+
* 写入文件,返回操作类型
|
|
1182
|
+
* 若文件已存在且内容一致 → skipped
|
|
1183
|
+
* 若文件已存在但内容不同 → updated
|
|
1184
|
+
* 若文件不存在 → created
|
|
1185
|
+
*/
|
|
1186
|
+
function writeIfChanged(filePath, content, relativePath, created, updated, skipped) {
|
|
1187
|
+
if (existsSync(filePath)) {
|
|
1188
|
+
const existing = readFileSync(filePath, 'utf-8');
|
|
1189
|
+
if (existing === content) {
|
|
1190
|
+
skipped.push(relativePath);
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
1194
|
+
updated.push(relativePath);
|
|
1195
|
+
}
|
|
1196
|
+
else {
|
|
1197
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
1198
|
+
created.push(relativePath);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* 解析 JSONC 文本为 JavaScript 对象
|
|
1203
|
+
* 先规范化换行符,再去除行注释(跳过字符串内部),最后解析
|
|
1204
|
+
*/
|
|
1205
|
+
function parseJsonc(text) {
|
|
1206
|
+
// 去除 \r 控制字符(避免 Windows CRLF 导致 JSON 解析报 "bad control character")
|
|
1207
|
+
const normalized = text.replace(/\r/g, '');
|
|
1208
|
+
// 逐字符处理,去除行注释(跳过字符串内部,避免匹配到 https:// 等 URL 中的 //)
|
|
1209
|
+
let result = '';
|
|
1210
|
+
let inString = false;
|
|
1211
|
+
let inEscape = false;
|
|
1212
|
+
let i = 0;
|
|
1213
|
+
while (i < normalized.length) {
|
|
1214
|
+
const ch = normalized[i];
|
|
1215
|
+
const next = normalized[i + 1];
|
|
1216
|
+
// 处理转义字符
|
|
1217
|
+
if (inEscape) {
|
|
1218
|
+
result += ch;
|
|
1219
|
+
inEscape = false;
|
|
1220
|
+
i++;
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
if (ch === '\\' && inString) {
|
|
1224
|
+
result += ch;
|
|
1225
|
+
inEscape = true;
|
|
1226
|
+
i++;
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
1229
|
+
// 字符串边界
|
|
1230
|
+
if (ch === '"') {
|
|
1231
|
+
inString = !inString;
|
|
1232
|
+
result += ch;
|
|
1233
|
+
i++;
|
|
1234
|
+
continue;
|
|
1235
|
+
}
|
|
1236
|
+
// 行注释:不在字符串内时遇到 //
|
|
1237
|
+
if (!inString && ch === '/' && next === '/') {
|
|
1238
|
+
// 跳过直到行尾
|
|
1239
|
+
i += 2; // 跳过 //
|
|
1240
|
+
while (i < normalized.length && normalized[i] !== '\n') {
|
|
1241
|
+
i++;
|
|
1242
|
+
}
|
|
1243
|
+
// 保留换行符
|
|
1244
|
+
if (i < normalized.length && normalized[i] === '\n') {
|
|
1245
|
+
result += '\n';
|
|
1246
|
+
i++;
|
|
1247
|
+
}
|
|
1248
|
+
continue;
|
|
1249
|
+
}
|
|
1250
|
+
result += ch;
|
|
1251
|
+
i++;
|
|
1252
|
+
}
|
|
1253
|
+
return JSON.parse(result);
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* 获取 opencode.jsonc 更新后的完整内容
|
|
1257
|
+
* 读取现有文件(若存在),合并更新后生成新的 JSONC 文本
|
|
1258
|
+
*/
|
|
1259
|
+
function buildUpdatedJsonc(projectPath) {
|
|
1260
|
+
const jsoncPath = resolve(projectPath, 'opencode.jsonc');
|
|
1261
|
+
let jsoncObj;
|
|
1262
|
+
let rawContent;
|
|
1263
|
+
if (existsSync(jsoncPath)) {
|
|
1264
|
+
// 已有文件 → 解析并合并,保留原始字符串用于后续补丁式更新
|
|
1265
|
+
rawContent = readFileSync(jsoncPath, 'utf-8');
|
|
1266
|
+
jsoncObj = parseJsonc(rawContent);
|
|
1267
|
+
}
|
|
1268
|
+
else {
|
|
1269
|
+
// 新文件 → 基础结构
|
|
1270
|
+
jsoncObj = {
|
|
1271
|
+
$schema: 'https://opencode.openfeel/config.json',
|
|
1272
|
+
default_agent: 'feel',
|
|
1273
|
+
instructions: ['AGENTS.md', '.opencode/instructions/core.md'],
|
|
1274
|
+
skills: {},
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
// 更新 default_agent
|
|
1278
|
+
jsoncObj.default_agent = 'feel';
|
|
1279
|
+
// 确保 instructions 存在
|
|
1280
|
+
if (!jsoncObj.instructions) {
|
|
1281
|
+
jsoncObj.instructions = ['AGENTS.md', '.opencode/instructions/core.md'];
|
|
1282
|
+
}
|
|
1283
|
+
// 合并 skills:保留原有 skill,添加新的 /opfx:* skill
|
|
1284
|
+
const skills = jsoncObj.skills || {};
|
|
1285
|
+
for (const name of NEW_SKILL_NAMES) {
|
|
1286
|
+
skills[name] = `.opencode/skills/${name}`;
|
|
1287
|
+
}
|
|
1288
|
+
jsoncObj.skills = skills;
|
|
1289
|
+
// 格式化为 JSONC 输出:若有原始内容则基于它做补丁式替换,否则从头构建
|
|
1290
|
+
return formatJsonc(jsoncObj, rawContent);
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* 将对象格式化为美观的 JSONC 字符串
|
|
1294
|
+
* 若提供了 originalContent,基于原始字符串做补丁式替换,
|
|
1295
|
+
* 仅更新 default_agent 和 skills,其余字段(含注释和未识别字段)原样保留。
|
|
1296
|
+
*/
|
|
1297
|
+
function formatJsonc(obj, originalContent) {
|
|
1298
|
+
// 有原始内容 → 补丁模式:仅替换需要更新的字段,保留注释和未知字段
|
|
1299
|
+
if (originalContent) {
|
|
1300
|
+
let result = originalContent;
|
|
1301
|
+
// 替换 "default_agent" 的值
|
|
1302
|
+
result = result.replace(/("default_agent"\s*:\s*)("[^"]*")/, `$1"${obj.default_agent}"`);
|
|
1303
|
+
// 替换 "skills" 块
|
|
1304
|
+
const skills = obj.skills;
|
|
1305
|
+
result = replaceSkillsFieldInJsonc(result, skills);
|
|
1306
|
+
return result;
|
|
1307
|
+
}
|
|
1308
|
+
// 无原始内容 → 从头构建(新文件场景)
|
|
1309
|
+
return buildJsoncFromObject(obj);
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* 在 JSONC 原始字符串中查找并替换 "skills" 字段的整个对象块
|
|
1313
|
+
* 使用括号计数来正确处理嵌套对象,并自动处理末尾逗号
|
|
1314
|
+
*/
|
|
1315
|
+
function replaceSkillsFieldInJsonc(content, skills) {
|
|
1316
|
+
// 查找 "skills" 键及冒号
|
|
1317
|
+
const keyRegex = /("skills"\s*:\s*)\{/;
|
|
1318
|
+
const keyMatch = content.match(keyRegex);
|
|
1319
|
+
if (!keyMatch || keyMatch.index === undefined) {
|
|
1320
|
+
// skills 字段不存在于原始文件中,不需要替换
|
|
1321
|
+
return content;
|
|
1322
|
+
}
|
|
1323
|
+
const keyStart = keyMatch.index;
|
|
1324
|
+
const openBraceIdx = keyStart + keyMatch[0].length - 1; // { 的位置
|
|
1325
|
+
// 找到匹配的 },处理字符串内的大括号
|
|
1326
|
+
let depth = 1;
|
|
1327
|
+
let closeBraceIdx = openBraceIdx;
|
|
1328
|
+
let inString = false;
|
|
1329
|
+
let inEscape = false;
|
|
1330
|
+
for (let i = openBraceIdx + 1; i < content.length && depth > 0; i++) {
|
|
1331
|
+
const ch = content[i];
|
|
1332
|
+
if (inEscape) {
|
|
1333
|
+
inEscape = false;
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1336
|
+
if (ch === '\\' && inString) {
|
|
1337
|
+
inEscape = true;
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
if (ch === '"') {
|
|
1341
|
+
inString = !inString;
|
|
1342
|
+
continue;
|
|
1343
|
+
}
|
|
1344
|
+
if (!inString) {
|
|
1345
|
+
if (ch === '{') {
|
|
1346
|
+
depth++;
|
|
1347
|
+
}
|
|
1348
|
+
else if (ch === '}') {
|
|
1349
|
+
depth--;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
if (depth === 0) {
|
|
1353
|
+
closeBraceIdx = i;
|
|
1354
|
+
break;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
// 检查 } 后面是否有逗号:若 } 后紧跟(仅跳过空白)有逗号,则将逗号及其后空白纳入 endIdx
|
|
1358
|
+
let endIdx = closeBraceIdx + 1;
|
|
1359
|
+
let scanIdx = endIdx;
|
|
1360
|
+
while (scanIdx < content.length && /[ \t]/.test(content[scanIdx])) {
|
|
1361
|
+
scanIdx++;
|
|
1362
|
+
}
|
|
1363
|
+
if (scanIdx < content.length && content[scanIdx] === ',') {
|
|
1364
|
+
scanIdx++; // 跳过逗号
|
|
1365
|
+
// 跳过逗号后的空白(包括换行)
|
|
1366
|
+
while (scanIdx < content.length && /[\s\n\r]/.test(content[scanIdx])) {
|
|
1367
|
+
scanIdx++;
|
|
1368
|
+
}
|
|
1369
|
+
endIdx = scanIdx;
|
|
1370
|
+
}
|
|
1371
|
+
// 确定替换后是否需要末尾逗号:检查 skills 之后是否还有其他顶层字段
|
|
1372
|
+
let hasMoreFields = false;
|
|
1373
|
+
if (endIdx < content.length) {
|
|
1374
|
+
// 从 endIdx 找到内容末尾前最后一个 }(根对象闭合)
|
|
1375
|
+
const remaining = content.slice(endIdx);
|
|
1376
|
+
const rootCloseIdx = remaining.lastIndexOf('}');
|
|
1377
|
+
if (rootCloseIdx > 0) {
|
|
1378
|
+
const between = remaining.slice(0, rootCloseIdx);
|
|
1379
|
+
hasMoreFields = /[^\s]/.test(between);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
// 构建新的 skills 块内容
|
|
1383
|
+
const skillEntries = Object.entries(skills);
|
|
1384
|
+
const skillLines = skillEntries.map(([key, value], idx) => {
|
|
1385
|
+
const comma = idx < skillEntries.length - 1 ? ',' : '';
|
|
1386
|
+
return ` "${key}": "${value}"${comma}`;
|
|
1387
|
+
});
|
|
1388
|
+
const blockContent = skillLines.join('\n');
|
|
1389
|
+
const newBlock = `"skills": {\n${blockContent}\n }${hasMoreFields ? ',' : ''}`;
|
|
1390
|
+
return content.slice(0, keyStart) + newBlock + content.slice(endIdx);
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* 从对象从头构建 JSONC(无原始内容时的回退路径)
|
|
1394
|
+
*/
|
|
1395
|
+
function buildJsoncFromObject(obj) {
|
|
1396
|
+
const skills = obj.skills;
|
|
1397
|
+
// 构建 skills 条目行
|
|
1398
|
+
const skillEntries = Object.entries(skills);
|
|
1399
|
+
const skillsLines = skillEntries.map(([key, value], index) => {
|
|
1400
|
+
const comma = index < skillEntries.length - 1 ? ',' : '';
|
|
1401
|
+
return ` "${key}": "${value}"${comma}`;
|
|
1402
|
+
});
|
|
1403
|
+
const instructions = obj.instructions;
|
|
1404
|
+
const instructionLines = instructions.map((inst, index) => {
|
|
1405
|
+
const comma = index < instructions.length - 1 ? ',' : '';
|
|
1406
|
+
return ` "${inst}"${comma}`;
|
|
1407
|
+
});
|
|
1408
|
+
// 构建完整 JSONC
|
|
1409
|
+
const hasExperimental = typeof obj.experimental === 'object' && obj.experimental !== null;
|
|
1410
|
+
const lines = [
|
|
1411
|
+
'{',
|
|
1412
|
+
' "$schema": "https://opencode.openfeel/config.json",',
|
|
1413
|
+
` "default_agent": "${obj.default_agent}",`,
|
|
1414
|
+
' "instructions": [',
|
|
1415
|
+
...instructionLines,
|
|
1416
|
+
' ],',
|
|
1417
|
+
' "skills": {',
|
|
1418
|
+
...skillsLines,
|
|
1419
|
+
hasExperimental ? ' },' : ' }',
|
|
1420
|
+
];
|
|
1421
|
+
// 如果有 experimental 字段,保留它
|
|
1422
|
+
if (hasExperimental) {
|
|
1423
|
+
const exp = obj.experimental;
|
|
1424
|
+
const expEntries = Object.entries(exp);
|
|
1425
|
+
const expLines = expEntries.map(([key, value], index) => {
|
|
1426
|
+
const comma = index < expEntries.length - 1 ? ',' : '';
|
|
1427
|
+
const val = typeof value === 'string' ? `"${value}"` : String(value);
|
|
1428
|
+
return ` "${key}": ${val}${comma}`;
|
|
1429
|
+
});
|
|
1430
|
+
lines.push(' "experimental": {');
|
|
1431
|
+
lines.push(...expLines);
|
|
1432
|
+
lines.push(' }');
|
|
1433
|
+
}
|
|
1434
|
+
lines.push('}');
|
|
1435
|
+
lines.push(''); // 末尾换行符
|
|
1436
|
+
return lines.join('\n');
|
|
1437
|
+
}
|
|
1438
|
+
// ─── 主函数 ────────────────────────────────────────────────────────
|
|
1439
|
+
/**
|
|
1440
|
+
* 更新项目中的 OpenCode 适配文件
|
|
1441
|
+
* 在目标项目中生成 Agent 定义、Skill 定义,并更新 opencode.jsonc 配置
|
|
1442
|
+
*
|
|
1443
|
+
* @param projectPath - 目标项目根路径
|
|
1444
|
+
* @returns 更新结果(created / updated / skipped 文件列表)
|
|
1445
|
+
*/
|
|
1446
|
+
export function updateProject(projectPath, selectedTools = ["opencode"], lang = 'zh-CN', options) {
|
|
1447
|
+
const created = [];
|
|
1448
|
+
const updated = [];
|
|
1449
|
+
const skipped = [];
|
|
1450
|
+
const agentsDir = resolve(projectPath, '.opencode', 'agents');
|
|
1451
|
+
const skillsDir = resolve(projectPath, '.opencode', 'skills');
|
|
1452
|
+
// 确保目标目录存在
|
|
1453
|
+
mkdirSync(agentsDir, { recursive: true });
|
|
1454
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
1455
|
+
// 过滤:仅处理选中的工具
|
|
1456
|
+
if (!selectedTools.includes('opencode')) {
|
|
1457
|
+
return { created: [], updated: [], skipped: [] };
|
|
1458
|
+
}
|
|
1459
|
+
// ── AGENTS.md 语言同步逻辑 ──
|
|
1460
|
+
let agentsDirHasContent = false;
|
|
1461
|
+
try {
|
|
1462
|
+
agentsDirHasContent = existsSync(agentsDir) && readdirSync(agentsDir).length > 0;
|
|
1463
|
+
}
|
|
1464
|
+
catch {
|
|
1465
|
+
agentsDirHasContent = false;
|
|
1466
|
+
}
|
|
1467
|
+
const agentsMdPath = resolve(projectPath, 'AGENTS.md');
|
|
1468
|
+
const agentsMdExists = existsSync(agentsMdPath);
|
|
1469
|
+
if (!agentsDirHasContent && !agentsMdExists) {
|
|
1470
|
+
// 情况 1:首次部署 → 使用全局默认语言部署 AGENTS.md
|
|
1471
|
+
const globalConfig = getGlobalConfig();
|
|
1472
|
+
const deployLang = lang ?? globalConfig.lang ?? 'zh-CN';
|
|
1473
|
+
const agentsMdContent = loadTemplate(deployLang, 'agents-md');
|
|
1474
|
+
writeFileSync(agentsMdPath, agentsMdContent, 'utf-8');
|
|
1475
|
+
created.push('AGENTS.md');
|
|
1476
|
+
}
|
|
1477
|
+
else if (!agentsMdExists && options?.lang) {
|
|
1478
|
+
// 情况 2b:已有 agents 目录但 AGENTS.md 被手动删除 + --lang 指定
|
|
1479
|
+
// → 用指定语言重新创建 AGENTS.md
|
|
1480
|
+
const agentsMdContent = loadTemplate(options.lang, 'agents-md');
|
|
1481
|
+
writeFileSync(agentsMdPath, agentsMdContent, 'utf-8');
|
|
1482
|
+
created.push('AGENTS.md');
|
|
1483
|
+
}
|
|
1484
|
+
else if (agentsMdExists && options?.lang) {
|
|
1485
|
+
// 情况 2:已有项目 + --lang 参数指定了语言
|
|
1486
|
+
const projectLang = getLang(projectPath);
|
|
1487
|
+
const requestedLang = options.lang;
|
|
1488
|
+
if (requestedLang !== projectLang) {
|
|
1489
|
+
// 语言不同 → 提示确认
|
|
1490
|
+
if (options?.force) {
|
|
1491
|
+
// --force → 跳过确认,直接覆盖
|
|
1492
|
+
const agentsMdContent = loadTemplate(requestedLang, 'agents-md');
|
|
1493
|
+
writeFileSync(agentsMdPath, agentsMdContent, 'utf-8');
|
|
1494
|
+
updated.push('AGENTS.md');
|
|
1495
|
+
}
|
|
1496
|
+
else if (options?.interactive) {
|
|
1497
|
+
// 交互模式 → 输出冲突警告,跳过 AGENTS.md,继续执行后续框架更新
|
|
1498
|
+
console.warn(`[update] AGENTS.md language mismatch: project=${projectLang}, requested=${requestedLang}. Skipped. Use --force to override.`);
|
|
1499
|
+
skipped.push('AGENTS.md (language conflict)');
|
|
1500
|
+
}
|
|
1501
|
+
else {
|
|
1502
|
+
// 非交互模式 → 输出警告,不覆盖
|
|
1503
|
+
console.warn(`[update] AGENTS.md language mismatch: project=${projectLang}, requested=${requestedLang}. Skipped. Use --force to override.`);
|
|
1504
|
+
skipped.push('AGENTS.md');
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
else {
|
|
1508
|
+
// 语言相同 → 比较内容,模板更新时传播部署
|
|
1509
|
+
const templateContent = loadTemplate(requestedLang, 'agents-md');
|
|
1510
|
+
const existingContent = readFileSync(agentsMdPath, 'utf-8');
|
|
1511
|
+
if (existingContent !== templateContent) {
|
|
1512
|
+
writeFileSync(agentsMdPath, templateContent, 'utf-8');
|
|
1513
|
+
updated.push('AGENTS.md');
|
|
1514
|
+
}
|
|
1515
|
+
else {
|
|
1516
|
+
skipped.push('AGENTS.md (language unchanged)');
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
else if (agentsMdExists) {
|
|
1521
|
+
// 情况 3:已有项目 + 无 --lang 参数 → 比较内容,模板更新时传播部署
|
|
1522
|
+
const templateContent = loadTemplate(lang, 'agents-md');
|
|
1523
|
+
const existingContent = readFileSync(agentsMdPath, 'utf-8');
|
|
1524
|
+
if (existingContent !== templateContent) {
|
|
1525
|
+
writeFileSync(agentsMdPath, templateContent, 'utf-8');
|
|
1526
|
+
updated.push('AGENTS.md');
|
|
1527
|
+
}
|
|
1528
|
+
else {
|
|
1529
|
+
skipped.push('AGENTS.md (use existing)');
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
// 记录项目语言映射到全局配置
|
|
1533
|
+
// 使用 options?.lang(用户通过 --lang 显式指定的语言)优先,
|
|
1534
|
+
// 回退到 lang(getCliLang 三级回退结果)。确保记录的语言与实际部署语言一致。
|
|
1535
|
+
try {
|
|
1536
|
+
recordProjectLang(projectPath, options?.lang ?? lang);
|
|
1537
|
+
}
|
|
1538
|
+
catch {
|
|
1539
|
+
// 记录失败不影响主流程
|
|
1540
|
+
}
|
|
1541
|
+
// 0. 生成 instructions/core.md(适配器层核心指令,与 init 的核心层分离)
|
|
1542
|
+
const instructionsDir = resolve(projectPath, '.opencode', 'instructions');
|
|
1543
|
+
mkdirSync(instructionsDir, { recursive: true });
|
|
1544
|
+
const coreInstructionsPath = join(instructionsDir, 'core.md');
|
|
1545
|
+
const coreContent = loadTemplate(lang, 'core-instructions');
|
|
1546
|
+
writeIfChanged(coreInstructionsPath, coreContent, '.opencode/instructions/core.md', created, updated, skipped);
|
|
1547
|
+
// 1. 生成 Agent 定义文件(从 template-loader 加载模板,按语言选择)
|
|
1548
|
+
for (const name of listAgentIds(lang)) {
|
|
1549
|
+
const content = loadAgentTemplate(lang, name);
|
|
1550
|
+
const filePath = join(agentsDir, `${name}.md`);
|
|
1551
|
+
const relPath = `.opencode/agents/${name}.md`;
|
|
1552
|
+
writeIfChanged(filePath, content, relPath, created, updated, skipped);
|
|
1553
|
+
}
|
|
1554
|
+
// 2. 生成 Skill 定义文件(每个 Skill 一个子目录,包含 SKILL.md)
|
|
1555
|
+
for (const [name, content] of Object.entries(SKILL_DEFINITIONS)) {
|
|
1556
|
+
const skillSubDir = join(skillsDir, name);
|
|
1557
|
+
mkdirSync(skillSubDir, { recursive: true });
|
|
1558
|
+
const filePath = join(skillSubDir, 'SKILL.md');
|
|
1559
|
+
const relPath = `.opencode/skills/${name}/SKILL.md`;
|
|
1560
|
+
writeIfChanged(filePath, content, relPath, created, updated, skipped);
|
|
1561
|
+
}
|
|
1562
|
+
// 3. 更新 opencode.jsonc
|
|
1563
|
+
const jsoncPath = resolve(projectPath, 'opencode.jsonc');
|
|
1564
|
+
const newContent = buildUpdatedJsonc(projectPath);
|
|
1565
|
+
const relJsoncPath = 'opencode.jsonc';
|
|
1566
|
+
writeIfChanged(jsoncPath, newContent, relJsoncPath, created, updated, skipped);
|
|
1567
|
+
return { created, updated, skipped };
|
|
1568
|
+
}
|
|
1569
|
+
//# sourceMappingURL=update.js.map
|