openfeel 1.1.5 → 1.1.6

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.
Files changed (40) hide show
  1. package/README.en.md +13 -4
  2. package/README.md +13 -3
  3. package/README.zh-CN.md +14 -4
  4. package/dist/commands/setup.d.ts.map +1 -1
  5. package/dist/commands/setup.js +24 -2
  6. package/dist/commands/setup.js.map +1 -1
  7. package/dist/commands/update.d.ts.map +1 -1
  8. package/dist/commands/update.js +16 -4
  9. package/dist/commands/update.js.map +1 -1
  10. package/dist/core/backup.d.ts.map +1 -1
  11. package/dist/core/backup.js +18 -5
  12. package/dist/core/backup.js.map +1 -1
  13. package/dist/core/claude-settings.d.ts +31 -0
  14. package/dist/core/claude-settings.d.ts.map +1 -0
  15. package/dist/core/claude-settings.js +120 -0
  16. package/dist/core/claude-settings.js.map +1 -0
  17. package/dist/core/config.js +2 -2
  18. package/dist/core/global-paths.d.ts +10 -0
  19. package/dist/core/global-paths.d.ts.map +1 -1
  20. package/dist/core/global-paths.js +21 -0
  21. package/dist/core/global-paths.js.map +1 -1
  22. package/dist/core/i18n-data/en.d.ts.map +1 -1
  23. package/dist/core/i18n-data/en.js +19 -6
  24. package/dist/core/i18n-data/en.js.map +1 -1
  25. package/dist/core/i18n-data/zh-CN.d.ts.map +1 -1
  26. package/dist/core/i18n-data/zh-CN.js +19 -6
  27. package/dist/core/i18n-data/zh-CN.js.map +1 -1
  28. package/dist/core/setup.d.ts +2 -2
  29. package/dist/core/setup.d.ts.map +1 -1
  30. package/dist/core/setup.js +81 -53
  31. package/dist/core/setup.js.map +1 -1
  32. package/dist/core/template-loader.d.ts +15 -0
  33. package/dist/core/template-loader.d.ts.map +1 -1
  34. package/dist/core/template-loader.js +3891 -109
  35. package/dist/core/template-loader.js.map +1 -1
  36. package/dist/core/update.d.ts +8 -0
  37. package/dist/core/update.d.ts.map +1 -1
  38. package/dist/core/update.js +327 -133
  39. package/dist/core/update.js.map +1 -1
  40. package/package.json +1 -1
@@ -8,11 +8,12 @@
8
8
  */
9
9
  import { existsSync, readFileSync, mkdirSync } from 'node:fs';
10
10
  import { resolve, dirname, join, basename, isAbsolute, relative } from 'node:path';
11
- import { loadAgentTemplate, listAgentIds, loadTemplate } from './template-loader.js';
11
+ import { loadAgentTemplate, listAgentIds, loadTemplate, loadClaudeConstraintTemplate, loadClaudeAgentTemplate, listClaudeAgentIds } from './template-loader.js';
12
12
  import { recordProjectLang } from './workspace/identity.js';
13
13
  import { t, getCliLang } from './i18n.js';
14
- import { getOpencodeGlobalDir, getGlobalAgentsDir, getGlobalSkillsDir, getGlobalCoreMdPath, getGlobalAgentsMdPath, getGlobalOpencodeJsoncPath, getGlobalUpdateStatePath } from './global-paths.js';
14
+ import { getOpencodeGlobalDir, getGlobalAgentsDir, getGlobalSkillsDir, getGlobalCoreMdPath, getGlobalAgentsMdPath, getGlobalOpencodeJsoncPath, getGlobalUpdateStatePath, getClaudeAgentsDir, getClaudeSkillsDir, getClaudeMdPath, getClaudeSettingsPath } from './global-paths.js';
15
15
  import { mergeGlobalOpencodeJsonc, buildProjectOpencodeJsoncObj } from './opencode-config.js';
16
+ import { applyClaudeSettings } from './claude-settings.js';
16
17
  import { atomicWriteFileSync } from './fs/atomic-write.js';
17
18
  import { withFileLock, globalLockPath } from './fs/file-lock.js';
18
19
  import { detectFileType, normalize, splitFrontmatter, parseRegion, wrapRegion, replaceRegion, mergeFrontmatter, serializeFrontmatter } from './managed-region.js';
@@ -29,8 +30,13 @@ export const supportedTools = [
29
30
  description: 'OpenCode AI 编码助手 — 生成 Agent 定义和 openfeel-* 技能',
30
31
  enabled: true,
31
32
  },
33
+ {
34
+ id: 'claude',
35
+ name: 'Claude Code',
36
+ description: 'Anthropic Claude Code CLI — 生成 CLAUDE.md、Claude subagent 定义与共享 openfeel-* 技能',
37
+ enabled: false,
38
+ },
32
39
  // 后续扩展:
33
- // { id: 'claude', name: 'Claude Code', description: 'Anthropic Claude Code CLI', enabled: false },
34
40
  // { id: 'cursor', name: 'Cursor', description: 'Cursor IDE', enabled: false },
35
41
  ];
36
42
  /**
@@ -55,23 +61,51 @@ export async function selectTools() {
55
61
  }
56
62
  catch {
57
63
  // 交互不可用时(如非 TTY 环境),回退到全部启用项
58
- console.log('(非交互环境,使用默认工具列表)');
64
+ console.log(t('update.nonInteractiveToolsTmpl', getCliLang(process.cwd())));
59
65
  return supportedTools.filter((t) => t.enabled).map((t) => t.id);
60
66
  }
61
67
  }
68
+ /**
69
+ * 解析 `--tools` 逗号分隔列表。
70
+ * @returns tools=合法 id(保序去重);invalid=非法项。空串/纯空白视为非法(触发 exit 1)。
71
+ */
72
+ export function parseToolList(raw) {
73
+ if (raw.trim() === '') {
74
+ return { tools: [], invalid: [''] };
75
+ }
76
+ const valid = new Set(supportedTools.map((t) => t.id));
77
+ const tools = [];
78
+ const invalid = [];
79
+ for (const part of raw.split(',')) {
80
+ const v = part.trim();
81
+ if (v === '') {
82
+ continue;
83
+ } // 忽略尾随/空段
84
+ if (!valid.has(v)) {
85
+ invalid.push(v);
86
+ continue;
87
+ }
88
+ if (!tools.includes(v)) {
89
+ tools.push(v);
90
+ }
91
+ }
92
+ return { tools, invalid };
93
+ }
62
94
  // ─── Agent 定义由 template-loader.ts 统一管理 ──────────────────────────
63
95
  // ─── Skill 定义内容 ─────────────────────────────────────────────────
64
96
  // AUTO-GENERATED-BEGIN: SKILL_DEFINITIONS
65
97
  export const SKILL_DEFINITIONS = {
66
98
  'openfeel-agent-model-check': `---
67
99
  name: openfeel-agent-model-check
68
- description: Agent 模型检查与修复。当 Agent 报 "Model not found" 或需要排查模型配置时使用。涵盖 auth.json 校验(opencode 适配器)、provider key 匹配、模型能力确认、openfeel-vision 多模态专项指南。
100
+ description: Agent 模型检查与修复。当 Agent 报 "Model not found" 或需要排查模型配置时使用。涵盖 auth.json 校验(opencode 适配器)、provider key 匹配、模型能力确认、openfeel-vision 多模态专项指南、Claude Code 侧模型(\`/model\`、网关)。
69
101
  ---
70
102
 
71
103
  # Skill: openfeel-agent-model-check
72
104
 
73
105
  # Agent 模型检查与修复
74
106
 
107
+ > 正文以**能力名**叙述;平台差异见 \`### opencode 适配器\` / \`### Claude Code 适配器\` 两小节。
108
+
75
109
  ## 何时使用
76
110
 
77
111
  - Agent 调度时报 \`Model not found: xxx\`
@@ -89,22 +123,6 @@ Model not found: {provider_key}/{model_id}
89
123
 
90
124
  注意是否有 \`Did you mean: xxx\` 提示——如有,直接使用建议的模型名。
91
125
 
92
- ### 第二步:读取 auth.json 确认实际 provider key
93
-
94
- \`\`\`bash
95
- # Windows PowerShell / macOS / Linux
96
- cat ~/.local/share/opencode/auth.json
97
- \`\`\`
98
-
99
- **关键点**:模型引用中的 provider 部分必须与 \`auth.json\` 中的 key 完全一致,而非 \`opencode.jsonc\` 中 \`provider.name\` 或 \`provider.id\`。
100
-
101
- 常见 provider key 示例:
102
- - \`alibaba-cn\` — 阿里云中国区(DashScope)
103
- - \`deepseek\` — DeepSeek
104
- - \`anthropic\` — Anthropic
105
- - \`openai\` — OpenAI
106
- - \`zhipuai\` — 智谱 AI
107
-
108
126
  ### 第三步:确认模型是否支持目标能力
109
127
 
110
128
  查阅 [Models.dev](https://models.dev) 确认模型属性:
@@ -121,7 +139,31 @@ cat ~/.local/share/opencode/auth.json
121
139
  - \`deepseek-flash\` 支持多模态图像输入(openfeel-vision 现用模型)
122
140
  - 备选视觉模型:\`qwen3-vl-plus\`(模型名中的 \`vl\` 表示 Vision-Language)
123
141
 
124
- ### 第四步:检查 opencode.jsonc 配置
142
+ ### 第六步:验证
143
+
144
+ 重启后调度目标 Agent 执行简单测试任务,确认无报错。
145
+
146
+ ## 平台适配
147
+
148
+ ### opencode 适配器
149
+
150
+ #### 第二步:读取 auth.json 确认实际 provider key
151
+
152
+ \`\`\`bash
153
+ # Windows PowerShell / macOS / Linux
154
+ cat ~/.local/share/opencode/auth.json
155
+ \`\`\`
156
+
157
+ **关键点**:模型引用中的 provider 部分必须与 \`auth.json\` 中的 key 完全一致,而非 \`opencode.jsonc\` 中 \`provider.name\` 或 \`provider.id\`。
158
+
159
+ 常见 provider key 示例:
160
+ - \`alibaba-cn\` — 阿里云中国区(DashScope)
161
+ - \`deepseek\` — DeepSeek
162
+ - \`anthropic\` — Anthropic
163
+ - \`openai\` — OpenAI
164
+ - \`zhipuai\` — 智谱 AI
165
+
166
+ #### 第四步:检查 opencode.jsonc 配置
125
167
 
126
168
  \`\`\`jsonc
127
169
  {
@@ -139,13 +181,15 @@ cat ~/.local/share/opencode/auth.json
139
181
  3. 不要随意添加前缀(如 \`alibaba/\`、\`Alibaba(China)/\`)
140
182
  4. 如果不需要自定义 provider 选项(如 baseURL),可以完全不写 \`provider\` 块
141
183
 
142
- ### 第五步:修改并重启
184
+ #### 第五步:修改并重启
143
185
 
144
186
  修改 \`opencode.jsonc\` 后**必须重启 opencode** 才能生效。运行中的会话使用启动时加载的配置。
145
187
 
146
- ### 第六步:验证
188
+ ### Claude Code 适配器
147
189
 
148
- 重启后调度目标 Agent 执行简单测试任务,确认无报错。
190
+ - Claude Code **无 \`auth.json\`**。
191
+ - 模型/provider 由 **Claude 侧**决定(\`/model\`、\`settings.json\`/\`env\`、网关如 cc-switch)。
192
+ - Claude Code **无 \`opencode.jsonc\`**,模型引用不进 jsonc。
149
193
 
150
194
  ## 快速诊断清单
151
195
 
@@ -156,6 +200,7 @@ cat ~/.local/share/opencode/auth.json
156
200
  | 模型支持目标能力 | 查阅 models.dev | Input/Tool Call 等字段 = Yes |
157
201
  | opencode.jsonc 语法 | 检查 JSON 格式 | 无语法错误 |
158
202
  | 重启生效 | 重启 opencode 后重新测试 | 无 Model not found 报错 |
203
+ | 模型由 Claude 侧配置 | \`/model\` 或 settings/env | 模型可用 |
159
204
 
160
205
  ## 多模态(openfeel-vision)Agent 专项
161
206
 
@@ -170,6 +215,8 @@ openfeel-vision Agent 必须配置多模态模型。当前默认使用 \`deepsee
170
215
 
171
216
  **推荐**:优先使用 \`deepseek-flash\`(已内置多模态);如需更大上下文可备选 \`qwen3-vl-plus\`。
172
217
 
218
+ > **Claude Code 适配器**:多模态由 Claude 侧模型决定;\`openfeel-vision\` subagent 须选支持图像输入的模型。
219
+
173
220
  ## 常见错误与修复
174
221
 
175
222
  | 错误信息 | 原因 | 修复 |
@@ -178,6 +225,8 @@ openfeel-vision Agent 必须配置多模态模型。当前默认使用 \`deepsee
178
225
  | \`Model not found: alibaba/xxx\` | 内置 key 与实际注册的 key 不一致 | 检查 auth.json,使用实际 key |
179
226
  | \`Model not found: xxx. Did you mean: yyy\` | 模型名拼写错误或不存在 | 使用 \`Did you mean\` 建议的名称 |
180
227
  | Agent 调度成功但无法处理图片 | 配置了纯文本模型 | 改为支持图像输入的模型(如 \`deepseek-flash\`) |
228
+
229
+ > **Claude Code 适配器**:以上排查针对 opencode 的 \`auth.json\`/\`opencode.jsonc\`;Claude 侧的模型解析以 \`/model\`、\`settings.json\`/\`env\` 或网关配置为准。
181
230
  `,
182
231
  'openfeel-bug-acceptance': `---
183
232
  name: openfeel-bug-acceptance
@@ -322,7 +371,10 @@ description: 渐进式查阅 .openfeel/kb/ 项目知识库,按当前任务需
322
371
  💡 如需确保索引为最新,可运行: python scripts/build_kb_index.py --dry-run
323
372
  \`\`\`
324
373
 
325
- > 注:语义检索是 \`openfeel-check-kb\` 的内置回退能力。调用方只需 \`skill("openfeel-check-kb")\` 一次,无需再手动调用 \`openfeel-search-kb\`。
374
+ > 注:语义检索是 \`openfeel-check-kb\` 的内置回退能力。调用方只需加载一次:
375
+ > - opencode 适配器:\`skill("openfeel-check-kb")\`;
376
+ > - Claude Code 适配器:以 \`Skill\` 工具加载 \`openfeel-check-kb\`(工具名映射见 \`openfeel-tool-usage\`)。
377
+ > 无需再手动调用 \`openfeel-search-kb\`。
326
378
 
327
379
  ### 6. 强制检索标记
328
380
 
@@ -336,7 +388,7 @@ Skill 加载后,根据步骤 3 的匹配结果决定是否追加提示:
336
388
  `,
337
389
  'openfeel-cli-usage': `---
338
390
  name: openfeel-cli-usage
339
- description: OpenFeel CLI 命令用法参考:命令清单与关键参数、15 个 phase 枚举与转移表、stageId 命名与目录映射约定、v1.1.2 新增能力(自描述/可纠错、--json 结构化输出、health --fix、scheme draft、knowledge dedup、纠正清理侧命令面)、v1.1.4 新增能力(checkpoint 选择性恢复 --stage/--dry-run、flow stage reset 精准复位、故障恢复路径)、v1.1.5 新增能力(setup --check [--json] 诊断 / 被动部署提示)。当需要查询 CLI 命令、参数、phase、stageId、阶段命名时加载。
391
+ description: OpenFeel CLI 命令用法参考:命令清单与关键参数、15 个 phase 枚举与转移表、stageId 命名与目录映射约定、v1.1.2 新增能力(自描述/可纠错、--json 结构化输出、health --fix、scheme draft、knowledge dedup、纠正清理侧命令面)、v1.1.4 新增能力(checkpoint 选择性恢复 --stage/--dry-run、flow stage reset 精准复位、故障恢复路径)、v1.1.5 新增能力(setup --check [--json] 诊断 / 被动部署提示)、v1.1.6 新增能力(Claude Code 适配器 \`--tools claude\` / settings 注入)。当需要查询 CLI 命令、参数、phase、stageId、阶段命名时加载。
340
392
  ---
341
393
 
342
394
  # OpenFeel CLI 用法参考
@@ -346,8 +398,9 @@ description: OpenFeel CLI 命令用法参考:命令清单与关键参数、15
346
398
  - 定位:**查询型参考手册**(只读,不自执行)。回答「命令是什么、参数怎么给、phase/stageId 规则如何」。
347
399
  - 边界:**执行型交互向导**请用 \`openfeel-wizard\` skill(跑 \`openfeel flow wizard\` 推进流水线);本 skill 只承载静态知识查阅。
348
400
  - 与 \`openfeel-tool-usage\` 协同:工具选择规范见该 skill;本 skill 补充 CLI 命令细节。
401
+ - 正文以**能力名**叙述;平台差异见 \`### opencode 适配器\` / \`### Claude Code 适配器\` 两小节。
349
402
 
350
- > 本文档为 v1.1.5 快照;命令/参数细节以 \`openfeel <cmd> --help\`(本仓执行)实时输出为准(CLI 演进后本文档可能滞后)。
403
+ > 本文档为 v1.1.6 快照;命令/参数细节以 \`openfeel <cmd> --help\`(本仓执行)实时输出为准(CLI 演进后本文档可能滞后)。
351
404
  >
352
405
  > ⚠️ **本仓自举**:本仓(openfeel 源码仓库)开发/执行时请用 \`node bin/openfeel.js <cmd>\`;安装后使用 \`openfeel <cmd>\`。
353
406
 
@@ -356,8 +409,8 @@ description: OpenFeel CLI 命令用法参考:命令清单与关键参数、15
356
409
  | 命令 | 用途 | 关键参数 |
357
410
  |------|------|----------|
358
411
  | \`openfeel init [path]\` | 初始化项目工作区 | \`--lang <zh-CN\\|en>\`、\`--demo\`、\`--workspace-only\`、\`--non-interactive\` |
359
- | \`openfeel setup\` | 部署全局框架配置(不建项目 \`.openfeel/\`);\`--check\` 只读诊断全局部署一致性 | \`--lang <zh-CN\\|en>\`、\`--check [--json]\` |
360
- | \`openfeel update [path]\` | 部署适配文件到目标项目 | \`--lang\`、\`--force\` |
412
+ | \`openfeel setup\` | 部署全局框架配置(不建项目 \`.openfeel/\`);\`--check\` 只读诊断全局部署一致性 | \`--lang <zh-CN\\|en>\`、\`--check [--json]\`、\`--tools <list>\` |
413
+ | \`openfeel update [path]\` | 部署适配文件到目标项目 | \`--lang\`、\`--force\`、\`--tools <list>\` |
361
414
  | \`openfeel migrate [path]\` | Legacy 布局迁移(检测/备份/迁移/回滚) | \`--dry-run\`、\`--remap-assignee\`、\`--clean-global-core-md\`;\`migrate rollback\` |
362
415
  | \`openfeel flow ...\` | 流水线状态管理 | 见下「flow 子命令」 |
363
416
  | \`openfeel flow checkpoint restore\` | Checkpoint 恢复(全量 / 选择性 / 预览) | \`<file> [--force] [--stage <id>] [--dry-run]\`(无 \`--stage\`=全量覆盖,最后手段;\`--stage\`=按阶段回退;\`--dry-run\`=零写盘预览) |
@@ -381,6 +434,11 @@ description: OpenFeel CLI 命令用法参考:命令清单与关键参数、15
381
434
 
382
435
  > 命令职责分层:\`plan stage add\`(完整,推荐)> \`flow stage add\`(仅注册 \`flow.json\`,不建目录)> \`stage create\`(已弃用)。
383
436
 
437
+ ## v1.1.6 新增能力(stage-68~73)
438
+
439
+ - **多 harness 并存**:\`openfeel setup\` / \`openfeel update\` 新增 \`--tools <逗号列表>\`(如 \`opencode,claude\`);非法值 **exit 1** 前置校验零写盘;默认仅 \`opencode\`。
440
+ - **Claude Code 适配器资产**:\`--tools claude\` 部署 \`~/.claude/\`——\`CLAUDE.md\`(全局约束,用户已有内容 append 不覆盖)、\`agents/\`(**9 个**,含 \`feel\`)、\`skills/\`(**17 个**共享源)、\`settings.json\`(注入 \`"agent":"feel"\` + \`"permissions":{"defaultMode":"auto"}\`,用户既有键逐字保留);默认零触(不带 \`--tools\` 时 \`~/.claude/\` 零变化)。
441
+
384
442
  ## v1.1.5 新增能力(stage-67)
385
443
 
386
444
  - **主动诊断 \`openfeel setup --check [--json]\`**:只读检测全局部署版本(\`~/.openfeel/update_state.json.openfeel_version\`)与 CLI 版本一致性,**不执行部署**;一致退出 \`0\`,\`mismatch\`/\`missing\`/\`unknown\` 退出 \`1\`;\`--json\` 输出 \`{schemaVersion,status,cliVersion,deployedVersion}\`(\`status\` ∈ \`ok|mismatch|missing|unknown\`)。
@@ -388,6 +446,8 @@ description: OpenFeel CLI 命令用法参考:命令清单与关键参数、15
388
446
 
389
447
  ## 升级流程
390
448
 
449
+ ### opencode 适配器
450
+
391
451
  \`\`\`bash
392
452
  npm i -g openfeel@latest # 1. 升级 CLI
393
453
  openfeel setup # 2. 重跑部署,刷新全局 AGENTS.md/agents/skills/opencode.jsonc
@@ -396,6 +456,17 @@ openfeel setup # 2. 重跑部署,刷新全局 AGENTS.md/agents/ski
396
456
 
397
457
  仅 \`npm i -g\` 不会刷新全局资产,须重跑 \`openfeel setup\` 并重启 harness。\`openfeel setup --check\` 可诊断全局部署版本与 CLI 版本是否一致(一致退出 0,否则退出 1)。
398
458
 
459
+ ### Claude Code 适配器
460
+
461
+ \`\`\`bash
462
+ npm i -g openfeel@latest # 1. 升级 CLI
463
+ openfeel setup --tools claude # 2. 重跑部署(claude 目标)
464
+ \`\`\`
465
+
466
+ - **\`--tools\` 由 v1.1.6 提供**:\`openfeel setup --tools claude\`;首次创建 \`~/.claude/{agents,skills}\` 后**重启会话或 \`/reload-skills\`** 使新 skill/agent 生效。
467
+ - 首次创建 \`~/.claude/{agents,skills}\` 后需**重启会话或 \`/reload-skills\`** 使新 skill/agent 生效。
468
+ - \`CLAUDE.md\` 于**下次会话**生效。
469
+
399
470
  ## v1.1.2 新增能力(stage-41~55)
400
471
 
401
472
  > 以下为 v1.1.2 相对上版的增量能力(分组摘要,细节以 \`<cmd> --help\` 为准)。
@@ -469,11 +540,19 @@ plan_pending → plan_review → plan_passed → scheme_pending → scheme_revie
469
540
 
470
541
  ## 权限模型要点
471
542
 
543
+ ### opencode 适配器
544
+
472
545
  - 9 个 agent 内联 \`permission:\` 白名单,均含 \`external_directory: "allow"\`(单一键,无读写粒度)。
473
546
  - **合并语义**:agent \`.md\` frontmatter 与 \`opencode.jsonc\` 按权限键**深合并**,同名键以 agent \`.md\` 为准(配置文件无法覆盖已声明键)。
474
547
  - **收紧入口**:项目根 \`.opencode/agent/<name>.md\` 覆盖全局同名 agent 并重写完整 \`permission\` 块;勿手改全局 agent frontmatter(会被 \`update\` 覆盖)。
475
548
  - 详 \`.openfeel/manual/core/permission.md\`。
476
549
 
550
+ ### Claude Code 适配器
551
+
552
+ - subagent 于 frontmatter 声明 \`tools\`(白名单,未列即不可用)、\`disallowedTools\`(黑名单)与 \`permissionMode\`。
553
+ - **无 \`external_directory\` 对等概念**。
554
+ - \`settings.json\` 由 \`openfeel setup/update --tools claude\` 注入(\`agent:"feel"\` + \`permissions.defaultMode:"auto"\`,用户键保留),**v1.1.6 起**。
555
+
477
556
  ## 与 openfeel-wizard 的区别
478
557
 
479
558
  | 维度 | \`openfeel-cli-usage\`(本 skill) | \`openfeel-wizard\` |
@@ -588,7 +667,9 @@ description: 读取 .openfeel/plan/{series}/{stage}/status.md,判断当前子
588
667
  - \`deps_satisfied = true\`(本 Skill 递归判断)
589
668
  - \`状态\` 为 \`ready_for_code\` 或 \`auto_running\`
590
669
  - \`自动推进\` 为 \`enabled\`(若 status.md 未填则回退到 config.yaml \`auto_advance\`)
591
- 2. 收集为 \`parallel_candidates\` 列表,供 Feel 批量调度执行。
670
+ 2. 收集为 \`parallel_candidates\` 列表,供主会话(Feel)批量调度执行。
671
+
672
+ > 口径:**subagent 不互调**(不直接调用其他 subagent);需向用户确认的事项一律由主会话负责。
592
673
 
593
674
  ### 5. 判断自动推进资格
594
675
 
@@ -635,8 +716,8 @@ description: 读取 .openfeel/plan/{series}/{stage}/status.md,判断当前子
635
716
  | \`coding\` | openfeel-executor 正在开发 |
636
717
  | \`ready_for_review\` | openfeel-executor 可启动 openfeel-reviewer 审查,或等待用户触发 |
637
718
  | \`review_failed\` | openfeel-reviewer 可启动 openfeel-executor 修复审查问题 |
638
- | \`review_passed\` | Feel 可推进到 ready_for_test |
639
- | \`ready_for_test\` | Feel 可启动 openfeel-feel-tester |
719
+ | \`review_passed\` | 主会话可推进到 ready_for_test |
720
+ | \`ready_for_test\` | 主会话可启动 openfeel-feel-tester |
640
721
  | \`test_writing\` | openfeel-feel-tester 正在写测试 |
641
722
  | \`testing\` | openfeel-feel-tester 正在测试 |
642
723
  | \`bug_found\` | openfeel-feel-tester 可启动 openfeel-executor 修复 Bug |
@@ -669,16 +750,18 @@ description: 加载流水线健康检查结果,供 Agent 判断 flow.json 与
669
750
  `,
670
751
  'openfeel-model-check': `---
671
752
  name: openfeel-model-check
672
- description: Feel 自检时检查所有 Agent 的模型配置状态,识别期望模型 vs 实际模型的差距,引导用户在目标工具中完成配置。首次配置后存储为部署模板,新项目可直接复用。
753
+ description: 主会话(Feel)自检时检查所有 Agent 的模型配置状态,识别期望模型 vs 实际模型的差距,引导用户在目标工具中完成配置。首次配置后存储为部署模板,新项目可直接复用。
673
754
  ---
674
755
 
675
756
  # 模型配置检查
676
757
 
677
758
  > ⚠️ **本仓自举**:本仓(openfeel 源码仓库)开发/执行时请用 \`node bin/openfeel.js <cmd>\`;安装后使用 \`openfeel <cmd>\`。
678
759
 
760
+ > 正文以**能力名**叙述;平台差异见 \`### opencode 适配器\` / \`### Claude Code 适配器\` 两小节。
761
+
679
762
  ## 触发时机
680
763
 
681
- Feel Agent 在以下时机加载本 Skill:
764
+ 主会话在以下时机加载本 Skill:
682
765
  - 会话启动自检(每次)
683
766
  - 用户请求检查模型配置(按需)
684
767
  - 新项目首次初始化后(\`openfeel init\`)
@@ -687,19 +770,11 @@ Feel Agent 在以下时机加载本 Skill:
687
770
 
688
771
  ### 1. 识别当前平台
689
772
 
690
- 读取 \`opencode.jsonc\`(或对应平台的配置文件),确定当前适配器平台:
691
-
692
- | 配置文件 | 平台 |
693
- |----------|------|
694
- | \`opencode.jsonc\` | OpenCode(当前适配器) |
695
- | \`kilo/kilo.json\` | Kilo |
696
- | \`claude/claude.json\` | Claude |
697
-
698
- 若无平台配置文件,提示用户当前不在支持的平台中。
773
+ 读取当前 harness 的平台配置文件,确定当前适配器平台(已知平台配置文件与 agents 目录差异见 \`## 平台适配\`)。
699
774
 
700
775
  ### 2. 扫描 Agent 定义
701
776
 
702
- 扫描 \`.opencode/agents/\`(或对应平台的 agents/ 目录)下所有 \`.md\` 文件,提取每个 Agent 的模型需求。
777
+ 扫描 agents 目录(目录差异见 \`## 平台适配\`)下所有 \`.md\` 文件,提取每个 Agent 的模型需求。
703
778
 
704
779
  **提取规则(优先级从高到低)**:
705
780
 
@@ -776,7 +851,7 @@ models:
776
851
  \`\`\`markdown
777
852
  | Agent | 角色要求 | 实际模型 | 配置来源 | 状态 |
778
853
  |-------|----------|----------|----------|:--:|
779
- | Feel | 主力推理 | deepseek-v4-pro | default | ⚠️ |
854
+ | 主会话 | 主力推理 | deepseek-v4-pro | default | ⚠️ |
780
855
  | openfeel-reviewer | **异种推理** | deepseek-v4-pro | default | ❌ 与主力相同! |
781
856
  | openfeel-executor | 快速 | deepseek-v4-pro | default | ⚠️ 未使用快速模型 |
782
857
  \`\`\`
@@ -788,7 +863,7 @@ models:
788
863
  \`\`\`markdown
789
864
  ## 🔍 模型配置检查报告
790
865
 
791
- **平台**:OpenCode(当前适配器)
866
+ **平台**:当前 harness(opencode 适配器 / Claude Code 适配器)
792
867
  **配置文件**:.openfeel/config.yaml
793
868
  **检查时间**:yyyy-mm-dd HH:MM
794
869
 
@@ -816,12 +891,12 @@ models:
816
891
 
817
892
  ### 6. 引导用户配置
818
893
 
819
- 若检查发现以下任一问题,**必须**使用 \`question\` 工具引导用户:
894
+ 若检查发现以下任一问题,**必须**使用向用户提问工具(opencode 适配器:\`question\`;Claude Code 适配器:\`AskUserQuestion\`)引导用户:
820
895
 
821
896
  | 触发条件 | 引导内容 |
822
897
  |----------|----------|
823
898
  | \`models\` 节不存在 | "未检测到模型配置。你需要为不同角色分配模型吗?" → 引导创建 |
824
- | openfeel-reviewer 使用与主力相同模型 | "⚠️ openfeel-reviewer 当前与 Feel 使用相同模型,异种交叉审查的核心优势无法发挥。建议为 openfeel-reviewer 配置不同的模型系列。" |
899
+ | openfeel-reviewer 使用与主力相同模型 | "⚠️ openfeel-reviewer 当前与主会话使用相同模型,异种交叉审查的核心优势无法发挥。建议为 openfeel-reviewer 配置不同的模型系列。" |
825
900
  | openfeel-executor 使用推理模型 | "⚠️ openfeel-executor 建议使用快速模型以节省成本。是否配置?" |
826
901
  | 关键 Agent 无任何配置 | "以下 Agent 无模型配置:{列表}。请配置。" |
827
902
 
@@ -841,7 +916,7 @@ models:
841
916
  # 或直接复制此文件到 .openfeel/ 并重命名为 config.yaml(需合并其他节)
842
917
  #
843
918
  # 最近配置时间:yyyy-mm-dd HH:MM
844
- # 平台:OpenCode(当前适配器)
919
+ # 平台:当前 harness(opencode 适配器 / Claude Code 适配器)
845
920
 
846
921
  models:
847
922
  default:
@@ -860,6 +935,31 @@ models:
860
935
 
861
936
  此模板在下次 \`openfeel init\` 或新项目部署时自动检测并建议复用。
862
937
 
938
+ ## 平台适配
939
+
940
+ ### opencode 适配器
941
+
942
+ 平台配置文件:
943
+
944
+ | 配置文件 | 平台 |
945
+ |----------|------|
946
+ | \`opencode.jsonc\` | OpenCode(当前适配器) |
947
+ | \`kilo/kilo.json\` | Kilo |
948
+
949
+ 若无平台配置文件,提示用户当前不在支持的平台中。
950
+
951
+ - agents 目录:\`.opencode/agents/\`(或对应平台的 agents/ 目录)下所有 \`.md\` 文件。
952
+ - 报告/模板中的平台标签:\`**平台**:OpenCode(当前适配器)\`(原文标签,正文已改为中性表述「当前 harness(opencode 适配器 / Claude Code 适配器)」)。
953
+ - 向用户提问使用 \`question\` 工具。
954
+
955
+ ### Claude Code 适配器
956
+
957
+ - Claude Code **无平台配置文件**(原平台识别表中指向 Claude 的错误配置行已删除)。
958
+ - 全局约束:\`~/.claude/CLAUDE.md\`;本期由 **stage-73** 注入 \`settings.json\`。
959
+ - agents 目录:\`~/.claude/agents/\`。
960
+ - 报告平台标签:\`Claude Code 适配器\`。
961
+ - 向用户提问使用 \`AskUserQuestion\` 工具。
962
+
863
963
  ## 输出规范
864
964
 
865
965
  - 状态图标:✅ 已满足、⚠️ 降级使用(可接受但非最优)、❌ 缺失或严重不匹配
@@ -868,13 +968,15 @@ models:
868
968
  `,
869
969
  'openfeel-model-config': `---
870
970
  name: openfeel-model-config
871
- description: 查找和配置 Agent 模型。当 Agent 报 "Model not found" 或需要调整/新增 Agent 模型时使用。覆盖 opencode.jsonc 配置(opencode 适配器)、模型名查找方法、多模态模型(openfeel-vision)特殊注意事项。
971
+ description: 查找和配置 Agent 模型。当 Agent 报 "Model not found" 或需要调整/新增 Agent 模型时使用。覆盖模型配置位置与优先级链(opencode 适配器 / Claude Code 适配器)、模型名查找方法、多模态模型(openfeel-vision)特殊注意事项。
872
972
  ---
873
973
 
874
974
  # Skill: openfeel-model-config
875
975
 
876
976
  # Agent 模型查找与配置
877
977
 
978
+ > 正文以**能力名**叙述;平台差异见 \`### opencode 适配器\` / \`### Claude Code 适配器\` 两小节。
979
+
878
980
  ## 何时使用
879
981
 
880
982
  - Agent 调用时报 \`Model not found: xxx\`
@@ -884,6 +986,8 @@ description: 查找和配置 Agent 模型。当 Agent 报 "Model not found" 或
884
986
 
885
987
  ## 配置位置
886
988
 
989
+ ### opencode 适配器
990
+
887
991
  Agent 模型配置在 **\`opencode.jsonc\`**(项目根目录)中:
888
992
 
889
993
  \`\`\`jsonc
@@ -900,6 +1004,12 @@ Agent 模型配置在 **\`opencode.jsonc\`**(项目根目录)中:
900
1004
 
901
1005
  > 💡 **推荐**:可用 \`openfeel model set/get/list --scope default|global|project\` 一键读写三层级模型(工具默认 / 全局 / 当前项目),无需手工定位文件。
902
1006
 
1007
+ ### Claude Code 适配器
1008
+
1009
+ - Agent 模型配置在 Claude Code 侧的 \`~/.claude/agents/*.md\` frontmatter \`model\` 字段。
1010
+ - 模型配置经 \`/model\`、\`settings.json\`/\`env\` 或网关(如 cc-switch)等途径确定。
1011
+ - \`settings.json\`(本期由 stage-73 注入)仅作参考,模板默认 \`model: inherit\`。
1012
+
903
1013
  ## 查找可用模型名
904
1014
 
905
1015
  当收到 \`"Model not found: xxx. Did you mean: aaa, bbb?"\` 错误时:
@@ -909,6 +1019,8 @@ Agent 模型配置在 **\`opencode.jsonc\`**(项目根目录)中:
909
1019
 
910
1020
  ## Agent 定义文件中的 model 字段
911
1021
 
1022
+ ### opencode 适配器
1023
+
912
1024
  \`.opencode/agents/<name>.md\` 的 frontmatter \`model:\` 字段**是生效的,且优先于** \`opencode.jsonc\` 的 \`agent.<name>.model\`(stage-40 REV-1606 实测)。opencode 模型解析优先级链:
913
1025
 
914
1026
  \`\`\`
@@ -919,21 +1031,31 @@ Agent 模型配置在 **\`opencode.jsonc\`**(项目根目录)中:
919
1031
  1. 修改 \`.opencode/agents/<name>.md\` 的 frontmatter \`model:\`(模板源 \`src/core/templates-data/opencode/agents/\` 同步)
920
1032
  2. (可选,保持一致)同步修改 \`opencode.jsonc\` 的 \`agent.<name>.model\`
921
1033
 
1034
+ ### Claude Code 适配器
1035
+
1036
+ Claude Code 模型解析优先级链:项目 \`.claude/agents\` > 用户 \`~/.claude/agents\`;frontmatter \`model\` **优先于** \`inherit\`。
1037
+
922
1038
  ## 多模态(openfeel-vision)模型特殊规则
923
1039
 
924
- - Feel 的主力模型(DeepSeek V4 Pro)不支持图片输入
925
- - 遇到图片输入时 Feel 会自动委托 openfeel-vision Agent
1040
+ ### opencode 适配器
1041
+
1042
+ - 主会话(Feel)的主力模型(DeepSeek V4 Pro)不支持图片输入
1043
+ - 遇到图片输入时主会话会自动委托 openfeel-vision Agent
926
1044
  - openfeel-vision Agent 需要配置多模态模型(现用 \`deepseek-flash\`,备选 \`qwen3-vl-plus\`)
927
1045
  - 模型名不要随意添加前缀(如 \`alibaba/\`),以平台提示的可用名为准
928
1046
  - **模型引用格式**:\`{auth.json中的key}/{模型ID}\`,不是 \`provider.name\` 也不是 \`provider.id\`
929
1047
  - 读取 \`~/.local/share/opencode/auth.json\` 确认实际 provider key(常见:\`alibaba-cn\`、\`deepseek\`、\`zhipuai\`)
930
1048
  - \`provider\` 块中的 \`name\` 和 \`id\` 仅用于显示,不影响模型解析
931
1049
 
1050
+ ### Claude Code 适配器
1051
+
1052
+ - 多模态由 Claude 侧模型决定;\`openfeel-vision\` subagent 须选支持图像输入的模型。
1053
+
932
1054
  ## 项目 Agent 模型概览
933
1055
 
934
1056
  | Agent | 模型类型 | 备注 |
935
1057
  |-------|---------|------|
936
- | Feel(总统领) | 推理模型 | DeepSeek V4 Pro — 不支持多模态 |
1058
+ | 主会话(Feel) | 推理模型 | DeepSeek V4 Pro — 不支持多模态 |
937
1059
  | openfeel-planner | 推理模型 | — |
938
1060
  | openfeel-schemer | 推理模型 | — |
939
1061
  | openfeel-executor | 快速模型 (Flash) | — |
@@ -1196,7 +1318,7 @@ description: Agent 工具使用规范(todowrite/question/task/skill 四工具
1196
1318
  触发条件(任一必问):需求歧义 / 多个同等合理方案 / 不可逆后果 / 架构决策。使用要求:(Recommended) 标记、选项附后果、≤3 选项、高风险含"取消"。禁止:模糊时自行假设、多方案不选直接实施。
1197
1319
 
1198
1320
  ### 3. task — 子 Agent 调度
1199
- 触发条件:并行探索多代码区 / 复杂多步委托 general / 下游 Agent(经 Feel)。使用要求:并行一条消息多 task、prompt 含任务描述+期望返回、明确只读/可写。
1321
+ 触发条件:并行探索多代码区 / 复杂多步委托 general / 下游 Agent(经主会话(Feel))。使用要求:并行一条消息多 task、prompt 含任务描述+期望返回、明确只读/可写。
1200
1322
 
1201
1323
  ### 4. skill — 技能加载
1202
1324
  触发条件:查阶段状态→openfeel-get-stage-status / 查知识库→openfeel-check-kb / 取 Bug→openfeel-get-bugs。使用要求:会话开始载 check-kb、处理阶段任务前载 get-stage-status、不凭记忆跳过。
@@ -1209,6 +1331,23 @@ description: Agent 工具使用规范(todowrite/question/task/skill 四工具
1209
1331
  | 探索代码 | task(explore) | 手动逐个 grep/read |
1210
1332
  | 获取状态 | skill(openfeel-get-stage-status) | 凭记忆推断 |
1211
1333
  | 批量文件操作 | task(general) | 串行逐个处理 |
1334
+
1335
+ ## 工具名映射(跨 harness)
1336
+
1337
+ 本 skill 的四种核心工具(\`todowrite\`/\`question\`/\`task\`/\`skill\`)在各 harness 的等价工具名见下表;**不改变**既有触发条件/使用要求/优先级表语义。
1338
+
1339
+ | opencode(小写) | Claude Code | 说明 |
1340
+ |------|------|------|
1341
+ | \`bash\` | \`Bash\` | 命令执行 |
1342
+ | \`read\` | \`Read\` | 读文件 |
1343
+ | \`write\` | \`Write\` | 写文件 |
1344
+ | \`edit\` | \`Edit\` | 编辑文件 |
1345
+ | \`glob\` | \`Glob\` | 文件匹配 |
1346
+ | \`grep\` | \`Grep\` | 内容检索 |
1347
+ | \`todowrite\` | \`TodoWrite\` | 任务列表 |
1348
+ | \`skill\` | \`Skill\` | 技能加载 |
1349
+ | \`task\` | \`Task\` | 子 Agent 调度 |
1350
+ | \`question\` | \`AskUserQuestion\` | 向用户提问 |
1212
1351
  `,
1213
1352
  'openfeel-update-stage-status': `---
1214
1353
  name: openfeel-update-stage-status
@@ -1386,9 +1525,9 @@ description: 会话启动时检查并补齐 .openfeel/ 工作区目录结构与
1386
1525
  \`.openfeel/users/{username}/dev_last.md\`(**索引**;同名目录 \`dev_last/\` 存主题文件,**文件名英文**)
1387
1526
 
1388
1527
  ### 6. 增量更新复核
1389
- 检查 \`~/.openfeel/update_infos.md\`,若存在未修复条目(追加/异常),提醒用户重启会话或委托 Feel 处理;本 Agent 不自行修改该文件。
1528
+ 检查 \`~/.openfeel/update_infos.md\`,若存在未修复条目(追加/异常),提醒用户重启会话或委托主会话(Feel)处理;本 Agent 不自行修改该文件。
1390
1529
 
1391
- > 目录结构语义、公共/私域分区、用户身份约束、路径自校验规则见全局 AGENTS.md(约束);本 skill 仅承载操作步骤。
1530
+ > 目录结构语义、公共/私域分区、用户身份约束、路径自校验规则见全局规则文件(opencode 适配器:\`~/.config/opencode/AGENTS.md\`;Claude Code 适配器:\`~/.claude/CLAUDE.md\`)(约束);本 skill 仅承载操作步骤。
1392
1531
  `,
1393
1532
  };
1394
1533
  // AUTO-GENERATED-END: SKILL_DEFINITIONS
@@ -1537,8 +1676,10 @@ export function updateProject(projectPath, selectedTools = ["opencode"], lang =
1537
1676
  const state = loadUpdateState(projectPath);
1538
1677
  const globalState = loadGlobalUpdateState();
1539
1678
  const conflicts = [];
1540
- // 过滤:仅处理选中的工具
1541
- if (!selectedTools.includes('opencode')) {
1679
+ // 过滤:仅处理选中的工具(两工具都未选才早退)
1680
+ const wantOpencode = selectedTools.includes('opencode');
1681
+ const wantClaude = selectedTools.includes('claude');
1682
+ if (!wantOpencode && !wantClaude) {
1542
1683
  return { created: [], updated: [], skipped: [], conflicts: [], appended: [] };
1543
1684
  }
1544
1685
  // N8:检测项目内遗留 .opencode/{agents,skills,instructions},只提示不迁移
@@ -1547,12 +1688,6 @@ export function updateProject(projectPath, selectedTools = ["opencode"], lang =
1547
1688
  if (legacyDirs.length > 0) {
1548
1689
  console.warn(t('update.legacyLayoutWarnTmpl', getCliLang(projectPath), { dirs: legacyDirs.join(',') }));
1549
1690
  }
1550
- // 全局部署目标(D1)
1551
- const agentsDir = getGlobalAgentsDir();
1552
- const skillsDir = getGlobalSkillsDir();
1553
- // 确保全局目标目录存在
1554
- mkdirSync(agentsDir, { recursive: true });
1555
- mkdirSync(skillsDir, { recursive: true });
1556
1691
  // 记录项目语言映射到全局配置
1557
1692
  // 使用 options?.lang(用户通过 --lang 显式指定的语言)优先,
1558
1693
  // 回退到 lang(getCliLang 三级回退结果)。确保记录的语言与实际部署语言一致。
@@ -1572,85 +1707,132 @@ export function updateProject(projectPath, selectedTools = ["opencode"], lang =
1572
1707
  globalState.files[getGlobalAgentsMdPath()] = globalState.files[staleCoreKey];
1573
1708
  delete globalState.files[staleCoreKey];
1574
1709
  }
1575
- // 0. 生成全局框架约束 AGENTS.md(~/.config/opencode/AGENTS.md)
1576
- const agentsMdPath = getGlobalAgentsMdPath();
1577
- const agentsMdContent = loadTemplate(lang, 'agents-md');
1578
- {
1579
- const action = deployGlobalAsset(agentsMdPath, agentsMdContent, newGlobalState, 'update');
1580
- pushAction(action, agentsMdPath, created, updated, skipped, appended);
1581
- }
1582
- // 1. 生成 Agent 定义文件到全局 agents 目录(绝对路径作 state key)
1583
- for (const name of listAgentIds(lang)) {
1584
- const content = loadAgentTemplate(lang, name);
1585
- const filePath = join(agentsDir, `${name}.md`);
1586
- const action = deployGlobalAsset(filePath, content, newGlobalState, 'update');
1587
- pushAction(action, filePath, created, updated, skipped, appended);
1588
- }
1589
- // 2. 生成 Skill 定义文件到全局 skills 目录(每个 Skill 一个子目录,包含 SKILL.md)
1590
- for (const [name, content] of Object.entries(SKILL_DEFINITIONS)) {
1591
- const skillSubDir = join(skillsDir, name);
1592
- mkdirSync(skillSubDir, { recursive: true });
1593
- const filePath = join(skillSubDir, 'SKILL.md');
1594
- const action = deployGlobalAsset(filePath, content, newGlobalState, 'update');
1595
- pushAction(action, filePath, created, updated, skipped, appended);
1596
- }
1597
- // 3. 全局 opencode.jsonc:深度合并(保留用户字段),加全局锁 + 原子写
1598
- const globalJsoncPath = getGlobalOpencodeJsoncPath();
1599
- try {
1600
- // 写前备份(B3):备份须在 jsonc 锁之外(之前)完成,避免 backup 锁与 jsonc 锁嵌套
1601
- const globalJsoncBackup = backupFileBeforeWrite(globalJsoncPath, { command: 'update' });
1602
- if (globalJsoncBackup) {
1603
- appendUpdateInfo('backed', { absolutePath: globalJsoncPath, backupRel: globalJsoncBackup.backupRel, command: 'update' });
1604
- notifyBackupIfTTY(globalJsoncBackup.backupRel);
1710
+ // opencode 分支(步骤 0~4)——原样保留
1711
+ if (wantOpencode) {
1712
+ // 全局部署目标(D1)
1713
+ const agentsDir = getGlobalAgentsDir();
1714
+ const skillsDir = getGlobalSkillsDir();
1715
+ // 确保全局目标目录存在
1716
+ mkdirSync(agentsDir, { recursive: true });
1717
+ mkdirSync(skillsDir, { recursive: true });
1718
+ // 0. 生成全局框架约束 AGENTS.md(~/.config/opencode/AGENTS.md)
1719
+ const agentsMdPath = getGlobalAgentsMdPath();
1720
+ const agentsMdContent = loadTemplate(lang, 'agents-md');
1721
+ {
1722
+ const action = deployGlobalAsset(agentsMdPath, agentsMdContent, newGlobalState, 'update');
1723
+ pushAction(action, agentsMdPath, created, updated, skipped, appended);
1724
+ }
1725
+ // 1. 生成 Agent 定义文件到全局 agents 目录(绝对路径作 state key)
1726
+ for (const name of listAgentIds(lang)) {
1727
+ const content = loadAgentTemplate(lang, name);
1728
+ const filePath = join(agentsDir, `${name}.md`);
1729
+ const action = deployGlobalAsset(filePath, content, newGlobalState, 'update');
1730
+ pushAction(action, filePath, created, updated, skipped, appended);
1605
1731
  }
1606
- // REV-1805:read-merge-write 全部置于锁内,消除与 model-config 锁内读写的 TOCTOU 竞态
1607
- const globalJsoncNew = withFileLock(globalLockPath('global-opencode-jsonc'), () => {
1608
- // 安全读取(T50):目录占位/不可读 → null,跳过合并与写入,按空配置继续
1609
- const current = readJsoncFile(globalJsoncPath);
1610
- if (current === null) {
1611
- return null;
1732
+ // 2. 生成 Skill 定义文件到全局 skills 目录(每个 Skill 一个子目录,包含 SKILL.md)
1733
+ for (const [name, content] of Object.entries(SKILL_DEFINITIONS)) {
1734
+ const skillSubDir = join(skillsDir, name);
1735
+ mkdirSync(skillSubDir, { recursive: true });
1736
+ const filePath = join(skillSubDir, 'SKILL.md');
1737
+ const action = deployGlobalAsset(filePath, content, newGlobalState, 'update');
1738
+ pushAction(action, filePath, created, updated, skipped, appended);
1739
+ }
1740
+ // 3. 全局 opencode.jsonc:深度合并(保留用户字段),加全局锁 + 原子写
1741
+ const globalJsoncPath = getGlobalOpencodeJsoncPath();
1742
+ try {
1743
+ // 写前备份(B3):备份须在 jsonc 锁之外(之前)完成,避免 backup 锁与 jsonc 锁嵌套
1744
+ const globalJsoncBackup = backupFileBeforeWrite(globalJsoncPath, { command: 'update' });
1745
+ if (globalJsoncBackup) {
1746
+ appendUpdateInfo('backed', { absolutePath: globalJsoncPath, backupRel: globalJsoncBackup.backupRel, command: 'update' });
1747
+ notifyBackupIfTTY(globalJsoncBackup.backupRel);
1612
1748
  }
1613
- let merged;
1614
- // 合并可能因块注释 parse 失败抛异常(parseJsonc 边界):降级为「跳过合并、保留原文件」并告警
1615
- try {
1616
- merged = mergeGlobalOpencodeJsonc(current);
1749
+ // REV-1805:read-merge-write 全部置于锁内,消除与 model-config 锁内读写的 TOCTOU 竞态
1750
+ const globalJsoncNew = withFileLock(globalLockPath('global-opencode-jsonc'), () => {
1751
+ // 安全读取(T50):目录占位/不可读 → null,跳过合并与写入,按空配置继续
1752
+ const current = readJsoncFile(globalJsoncPath);
1753
+ if (current === null) {
1754
+ return null;
1755
+ }
1756
+ let merged;
1757
+ // 合并可能因块注释 parse 失败抛异常(parseJsonc 边界):降级为「跳过合并、保留原文件」并告警
1758
+ try {
1759
+ merged = mergeGlobalOpencodeJsonc(current);
1760
+ }
1761
+ catch (err) {
1762
+ console.warn(t('update.globalJsoncParseSkipTmpl', getCliLang(projectPath), { err: err.message }));
1763
+ merged = current;
1764
+ }
1765
+ atomicWriteFileSync(globalJsoncPath, merged);
1766
+ return merged;
1767
+ });
1768
+ if (globalJsoncNew !== null) {
1769
+ updateFileHash(newGlobalState, globalJsoncPath, globalJsoncNew);
1770
+ }
1771
+ }
1772
+ catch (err) {
1773
+ if (err instanceof BackupError) {
1774
+ // 备份失败(REV-011-A):跳过全局 jsonc 写入、记 anomaly、继续其余步骤(对齐 B3 幂等语义)
1775
+ appendUpdateInfo('anomaly', { absolutePath: globalJsoncPath, note: 'backup_failed' });
1776
+ console.warn(t('update.globalJsoncWriteSkipTmpl', getCliLang(projectPath), { err: err.message }));
1617
1777
  }
1618
- catch (err) {
1619
- console.warn(t('update.globalJsoncParseSkipTmpl', getCliLang(projectPath), { err: err.message }));
1620
- merged = current;
1778
+ else {
1779
+ throw err; // 非备份错误照旧上抛(锁超时等不回退)
1621
1780
  }
1622
- atomicWriteFileSync(globalJsoncPath, merged);
1623
- return merged;
1624
- });
1625
- if (globalJsoncNew !== null) {
1626
- updateFileHash(newGlobalState, globalJsoncPath, globalJsoncNew);
1627
1781
  }
1628
- }
1629
- catch (err) {
1630
- if (err instanceof BackupError) {
1631
- // 备份失败(REV-011-A):跳过全局 jsonc 写入、记 anomaly、继续其余步骤(对齐 B3 幂等语义)
1632
- appendUpdateInfo('anomaly', { absolutePath: globalJsoncPath, note: 'backup_failed' });
1633
- console.warn(t('update.globalJsoncWriteSkipTmpl', getCliLang(projectPath), { err: err.message }));
1782
+ // 4. 项目 opencode.jsonc:不存在则写最小 { $schema };已存在则保留(旧非法字段清理属 stage-39)
1783
+ const projectJsoncPath = resolve(projectPath, 'opencode.jsonc');
1784
+ if (!existsSync(projectJsoncPath)) {
1785
+ atomicWriteFileSync(projectJsoncPath, JSON.stringify(buildProjectOpencodeJsoncObj(), null, 2) + '\n');
1786
+ created.push('opencode.jsonc');
1634
1787
  }
1635
1788
  else {
1636
- throw err; // 非备份错误照旧上抛(锁超时等不回退)
1789
+ skipped.push('opencode.jsonc (use existing)');
1637
1790
  }
1638
1791
  }
1639
- // 4. 项目 opencode.jsonc:不存在则写最小 { $schema };已存在则保留(旧非法字段清理属 stage-39)
1640
- const projectJsoncPath = resolve(projectPath, 'opencode.jsonc');
1641
- if (!existsSync(projectJsoncPath)) {
1642
- atomicWriteFileSync(projectJsoncPath, JSON.stringify(buildProjectOpencodeJsoncObj(), null, 2) + '\n');
1643
- created.push('opencode.jsonc');
1644
- }
1645
- else {
1646
- skipped.push('opencode.jsonc (use existing)');
1792
+ // claude 分支:CLAUDE.md + 9 agent + 17 共享 skill + settings.json(stage-73)
1793
+ if (wantClaude) {
1794
+ const claudeAgentsDir = getClaudeAgentsDir();
1795
+ const claudeSkillsDir = getClaudeSkillsDir();
1796
+ mkdirSync(claudeAgentsDir, { recursive: true });
1797
+ mkdirSync(claudeSkillsDir, { recursive: true });
1798
+ // 1c. CLAUDE.md(受管区三态复用 deployGlobalAsset;用户已有 → append 不覆盖)
1799
+ const claudeMd = getClaudeMdPath();
1800
+ pushAction(deployGlobalAsset(claudeMd, loadClaudeConstraintTemplate(lang), newGlobalState, 'update'), claudeMd, created, updated, skipped, appended);
1801
+ // 2c. 9 agents(含 feel)
1802
+ for (const name of listClaudeAgentIds(lang)) {
1803
+ const p = join(claudeAgentsDir, `${name}.md`);
1804
+ pushAction(deployGlobalAsset(p, loadClaudeAgentTemplate(lang, name), newGlobalState, 'update'), p, created, updated, skipped, appended);
1805
+ }
1806
+ // 3c. 17 共享 skills
1807
+ for (const [name, content] of Object.entries(SKILL_DEFINITIONS)) {
1808
+ const dir = join(claudeSkillsDir, name);
1809
+ mkdirSync(dir, { recursive: true });
1810
+ const p = join(dir, 'SKILL.md');
1811
+ pushAction(deployGlobalAsset(p, content, newGlobalState, 'update'), p, created, updated, skipped, appended);
1812
+ }
1813
+ // 4c. settings.json(与 setup 对称,共享 applyClaudeSettings,杜绝双实现漂移)
1814
+ const settingsPath = getClaudeSettingsPath();
1815
+ pushAction(applyClaudeSettings(newGlobalState, 'update', getCliLang(projectPath)), settingsPath, created, updated, skipped, appended);
1647
1816
  }
1648
- // 重启提醒(仅在 opencode 全局 agent 配置更新时,且为交互模式)
1649
- if (updated.some((f) => f.startsWith(getGlobalAgentsDir())) && process.stdout.isTTY) {
1817
+ // 重启提醒(按 harness 组合;isTTY 守卫、非交互静默)
1818
+ if (process.stdout.isTTY) {
1650
1819
  const reminderLang = getCliLang(projectPath);
1651
- console.log(reminderLang === 'en'
1652
- ? 'opencode global configuration updated. Please restart opencode to load the new configuration.'
1653
- : 'opencode 全局配置已更新,请重启 opencode 以加载新配置。');
1820
+ const opencodeChanged = updated.some((f) => f.startsWith(getGlobalAgentsDir()));
1821
+ const claudeChanged = [...created, ...updated, ...appended].some((f) => f.startsWith(getClaudeAgentsDir()) || f.startsWith(getClaudeSkillsDir())
1822
+ || f === getClaudeMdPath() || f === getClaudeSettingsPath());
1823
+ const harnesses = [];
1824
+ if (opencodeChanged) {
1825
+ harnesses.push('opencode');
1826
+ }
1827
+ if (claudeChanged) {
1828
+ harnesses.push('Claude Code');
1829
+ }
1830
+ if (harnesses.length > 0) {
1831
+ console.log(t('update.restartHarnessTmpl', reminderLang, { harness: harnesses.join(' / ') }));
1832
+ if (claudeChanged) {
1833
+ console.log(t('update.claudeReloadHintTmpl', reminderLang));
1834
+ }
1835
+ }
1654
1836
  }
1655
1837
  // ── 写入冲突标记文件 ──
1656
1838
  if (conflicts.length > 0) {
@@ -1758,6 +1940,18 @@ function getIncomingContent(relativePath, lang) {
1758
1940
  const name = basename(dirname(relativePath));
1759
1941
  return SKILL_DEFINITIONS[name] ?? '';
1760
1942
  }
1943
+ // Claude 全局约束 CLAUDE.md
1944
+ if (relativePath === getClaudeMdPath()) {
1945
+ return loadClaudeConstraintTemplate(lang);
1946
+ }
1947
+ // Claude agent:位于 getClaudeAgentsDir() 下的 {name}.md
1948
+ if (relativePath.startsWith(getClaudeAgentsDir())) {
1949
+ return loadClaudeAgentTemplate(lang, basename(relativePath, '.md'));
1950
+ }
1951
+ // Claude skill:位于 getClaudeSkillsDir() 下的 {name}/SKILL.md
1952
+ if (relativePath.startsWith(getClaudeSkillsDir()) && basename(relativePath) === 'SKILL.md') {
1953
+ return SKILL_DEFINITIONS[basename(dirname(relativePath))] ?? '';
1954
+ }
1761
1955
  // 项目 opencode.jsonc
1762
1956
  if (relativePath === 'opencode.jsonc') {
1763
1957
  return JSON.stringify(buildProjectOpencodeJsoncObj(), null, 2) + '\n';