openfeel 1.1.4 → 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 (54) hide show
  1. package/README.en.md +25 -4
  2. package/README.md +13 -3
  3. package/README.zh-CN.md +25 -4
  4. package/dist/cli/deploy-check-output.d.ts +35 -0
  5. package/dist/cli/deploy-check-output.d.ts.map +1 -0
  6. package/dist/cli/deploy-check-output.js +53 -0
  7. package/dist/cli/deploy-check-output.js.map +1 -0
  8. package/dist/cli/index.d.ts.map +1 -1
  9. package/dist/cli/index.js +3 -0
  10. package/dist/cli/index.js.map +1 -1
  11. package/dist/cli/repl.d.ts.map +1 -1
  12. package/dist/cli/repl.js +3 -0
  13. package/dist/cli/repl.js.map +1 -1
  14. package/dist/commands/setup.d.ts.map +1 -1
  15. package/dist/commands/setup.js +56 -2
  16. package/dist/commands/setup.js.map +1 -1
  17. package/dist/commands/update.d.ts.map +1 -1
  18. package/dist/commands/update.js +16 -4
  19. package/dist/commands/update.js.map +1 -1
  20. package/dist/core/backup.d.ts.map +1 -1
  21. package/dist/core/backup.js +18 -5
  22. package/dist/core/backup.js.map +1 -1
  23. package/dist/core/claude-settings.d.ts +31 -0
  24. package/dist/core/claude-settings.d.ts.map +1 -0
  25. package/dist/core/claude-settings.js +120 -0
  26. package/dist/core/claude-settings.js.map +1 -0
  27. package/dist/core/config.js +2 -2
  28. package/dist/core/deployment-check.d.ts +44 -0
  29. package/dist/core/deployment-check.d.ts.map +1 -0
  30. package/dist/core/deployment-check.js +98 -0
  31. package/dist/core/deployment-check.js.map +1 -0
  32. package/dist/core/global-paths.d.ts +10 -0
  33. package/dist/core/global-paths.d.ts.map +1 -1
  34. package/dist/core/global-paths.js +21 -0
  35. package/dist/core/global-paths.js.map +1 -1
  36. package/dist/core/i18n-data/en.d.ts.map +1 -1
  37. package/dist/core/i18n-data/en.js +23 -2
  38. package/dist/core/i18n-data/en.js.map +1 -1
  39. package/dist/core/i18n-data/zh-CN.d.ts.map +1 -1
  40. package/dist/core/i18n-data/zh-CN.js +23 -2
  41. package/dist/core/i18n-data/zh-CN.js.map +1 -1
  42. package/dist/core/setup.d.ts +2 -2
  43. package/dist/core/setup.d.ts.map +1 -1
  44. package/dist/core/setup.js +84 -54
  45. package/dist/core/setup.js.map +1 -1
  46. package/dist/core/template-loader.d.ts +15 -0
  47. package/dist/core/template-loader.d.ts.map +1 -1
  48. package/dist/core/template-loader.js +3906 -109
  49. package/dist/core/template-loader.js.map +1 -1
  50. package/dist/core/update.d.ts +8 -0
  51. package/dist/core/update.d.ts.map +1 -1
  52. package/dist/core/update.js +344 -133
  53. package/dist/core/update.js.map +1 -1
  54. 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 精准复位、故障恢复路径)。当需要查询 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.4 快照;命令/参数细节以 \`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/\`) | \`--lang <zh-CN\\|en>\` |
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,39 @@ 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
+
442
+ ## v1.1.5 新增能力(stage-67)
443
+
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\`)。
445
+ - **被动部署提示**:任意命令 / REPL 在 TTY 下检测到全局部署漂移(\`mismatch\`)或缺失(\`missing\`)时,向 **stderr** 提示运行 \`openfeel setup\`;**每进程一次**;\`--json\`/\`--quiet\`/\`--version\`/\`--help\`、CI、\`OPENFEEL_NO_UPDATE_CHECK\` 及部署类命令(\`setup\`/\`update\`/\`init\`/\`migrate\`)静默;\`unknown\`(state 损坏)被动静默、仅 \`--check\` 显式报告;**不改退出码、不写盘**。
446
+
447
+ ## 升级流程
448
+
449
+ ### opencode 适配器
450
+
451
+ \`\`\`bash
452
+ npm i -g openfeel@latest # 1. 升级 CLI
453
+ openfeel setup # 2. 重跑部署,刷新全局 AGENTS.md/agents/skills/opencode.jsonc
454
+ # 3. 重启 harness(opencode)以加载新的全局配置
455
+ \`\`\`
456
+
457
+ 仅 \`npm i -g\` 不会刷新全局资产,须重跑 \`openfeel setup\` 并重启 harness。\`openfeel setup --check\` 可诊断全局部署版本与 CLI 版本是否一致(一致退出 0,否则退出 1)。
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
+
384
470
  ## v1.1.2 新增能力(stage-41~55)
385
471
 
386
472
  > 以下为 v1.1.2 相对上版的增量能力(分组摘要,细节以 \`<cmd> --help\` 为准)。
@@ -454,11 +540,19 @@ plan_pending → plan_review → plan_passed → scheme_pending → scheme_revie
454
540
 
455
541
  ## 权限模型要点
456
542
 
543
+ ### opencode 适配器
544
+
457
545
  - 9 个 agent 内联 \`permission:\` 白名单,均含 \`external_directory: "allow"\`(单一键,无读写粒度)。
458
546
  - **合并语义**:agent \`.md\` frontmatter 与 \`opencode.jsonc\` 按权限键**深合并**,同名键以 agent \`.md\` 为准(配置文件无法覆盖已声明键)。
459
547
  - **收紧入口**:项目根 \`.opencode/agent/<name>.md\` 覆盖全局同名 agent 并重写完整 \`permission\` 块;勿手改全局 agent frontmatter(会被 \`update\` 覆盖)。
460
548
  - 详 \`.openfeel/manual/core/permission.md\`。
461
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
+
462
556
  ## 与 openfeel-wizard 的区别
463
557
 
464
558
  | 维度 | \`openfeel-cli-usage\`(本 skill) | \`openfeel-wizard\` |
@@ -573,7 +667,9 @@ description: 读取 .openfeel/plan/{series}/{stage}/status.md,判断当前子
573
667
  - \`deps_satisfied = true\`(本 Skill 递归判断)
574
668
  - \`状态\` 为 \`ready_for_code\` 或 \`auto_running\`
575
669
  - \`自动推进\` 为 \`enabled\`(若 status.md 未填则回退到 config.yaml \`auto_advance\`)
576
- 2. 收集为 \`parallel_candidates\` 列表,供 Feel 批量调度执行。
670
+ 2. 收集为 \`parallel_candidates\` 列表,供主会话(Feel)批量调度执行。
671
+
672
+ > 口径:**subagent 不互调**(不直接调用其他 subagent);需向用户确认的事项一律由主会话负责。
577
673
 
578
674
  ### 5. 判断自动推进资格
579
675
 
@@ -620,8 +716,8 @@ description: 读取 .openfeel/plan/{series}/{stage}/status.md,判断当前子
620
716
  | \`coding\` | openfeel-executor 正在开发 |
621
717
  | \`ready_for_review\` | openfeel-executor 可启动 openfeel-reviewer 审查,或等待用户触发 |
622
718
  | \`review_failed\` | openfeel-reviewer 可启动 openfeel-executor 修复审查问题 |
623
- | \`review_passed\` | Feel 可推进到 ready_for_test |
624
- | \`ready_for_test\` | Feel 可启动 openfeel-feel-tester |
719
+ | \`review_passed\` | 主会话可推进到 ready_for_test |
720
+ | \`ready_for_test\` | 主会话可启动 openfeel-feel-tester |
625
721
  | \`test_writing\` | openfeel-feel-tester 正在写测试 |
626
722
  | \`testing\` | openfeel-feel-tester 正在测试 |
627
723
  | \`bug_found\` | openfeel-feel-tester 可启动 openfeel-executor 修复 Bug |
@@ -654,16 +750,18 @@ description: 加载流水线健康检查结果,供 Agent 判断 flow.json 与
654
750
  `,
655
751
  'openfeel-model-check': `---
656
752
  name: openfeel-model-check
657
- description: Feel 自检时检查所有 Agent 的模型配置状态,识别期望模型 vs 实际模型的差距,引导用户在目标工具中完成配置。首次配置后存储为部署模板,新项目可直接复用。
753
+ description: 主会话(Feel)自检时检查所有 Agent 的模型配置状态,识别期望模型 vs 实际模型的差距,引导用户在目标工具中完成配置。首次配置后存储为部署模板,新项目可直接复用。
658
754
  ---
659
755
 
660
756
  # 模型配置检查
661
757
 
662
758
  > ⚠️ **本仓自举**:本仓(openfeel 源码仓库)开发/执行时请用 \`node bin/openfeel.js <cmd>\`;安装后使用 \`openfeel <cmd>\`。
663
759
 
760
+ > 正文以**能力名**叙述;平台差异见 \`### opencode 适配器\` / \`### Claude Code 适配器\` 两小节。
761
+
664
762
  ## 触发时机
665
763
 
666
- Feel Agent 在以下时机加载本 Skill:
764
+ 主会话在以下时机加载本 Skill:
667
765
  - 会话启动自检(每次)
668
766
  - 用户请求检查模型配置(按需)
669
767
  - 新项目首次初始化后(\`openfeel init\`)
@@ -672,19 +770,11 @@ Feel Agent 在以下时机加载本 Skill:
672
770
 
673
771
  ### 1. 识别当前平台
674
772
 
675
- 读取 \`opencode.jsonc\`(或对应平台的配置文件),确定当前适配器平台:
676
-
677
- | 配置文件 | 平台 |
678
- |----------|------|
679
- | \`opencode.jsonc\` | OpenCode(当前适配器) |
680
- | \`kilo/kilo.json\` | Kilo |
681
- | \`claude/claude.json\` | Claude |
682
-
683
- 若无平台配置文件,提示用户当前不在支持的平台中。
773
+ 读取当前 harness 的平台配置文件,确定当前适配器平台(已知平台配置文件与 agents 目录差异见 \`## 平台适配\`)。
684
774
 
685
775
  ### 2. 扫描 Agent 定义
686
776
 
687
- 扫描 \`.opencode/agents/\`(或对应平台的 agents/ 目录)下所有 \`.md\` 文件,提取每个 Agent 的模型需求。
777
+ 扫描 agents 目录(目录差异见 \`## 平台适配\`)下所有 \`.md\` 文件,提取每个 Agent 的模型需求。
688
778
 
689
779
  **提取规则(优先级从高到低)**:
690
780
 
@@ -761,7 +851,7 @@ models:
761
851
  \`\`\`markdown
762
852
  | Agent | 角色要求 | 实际模型 | 配置来源 | 状态 |
763
853
  |-------|----------|----------|----------|:--:|
764
- | Feel | 主力推理 | deepseek-v4-pro | default | ⚠️ |
854
+ | 主会话 | 主力推理 | deepseek-v4-pro | default | ⚠️ |
765
855
  | openfeel-reviewer | **异种推理** | deepseek-v4-pro | default | ❌ 与主力相同! |
766
856
  | openfeel-executor | 快速 | deepseek-v4-pro | default | ⚠️ 未使用快速模型 |
767
857
  \`\`\`
@@ -773,7 +863,7 @@ models:
773
863
  \`\`\`markdown
774
864
  ## 🔍 模型配置检查报告
775
865
 
776
- **平台**:OpenCode(当前适配器)
866
+ **平台**:当前 harness(opencode 适配器 / Claude Code 适配器)
777
867
  **配置文件**:.openfeel/config.yaml
778
868
  **检查时间**:yyyy-mm-dd HH:MM
779
869
 
@@ -801,12 +891,12 @@ models:
801
891
 
802
892
  ### 6. 引导用户配置
803
893
 
804
- 若检查发现以下任一问题,**必须**使用 \`question\` 工具引导用户:
894
+ 若检查发现以下任一问题,**必须**使用向用户提问工具(opencode 适配器:\`question\`;Claude Code 适配器:\`AskUserQuestion\`)引导用户:
805
895
 
806
896
  | 触发条件 | 引导内容 |
807
897
  |----------|----------|
808
898
  | \`models\` 节不存在 | "未检测到模型配置。你需要为不同角色分配模型吗?" → 引导创建 |
809
- | openfeel-reviewer 使用与主力相同模型 | "⚠️ openfeel-reviewer 当前与 Feel 使用相同模型,异种交叉审查的核心优势无法发挥。建议为 openfeel-reviewer 配置不同的模型系列。" |
899
+ | openfeel-reviewer 使用与主力相同模型 | "⚠️ openfeel-reviewer 当前与主会话使用相同模型,异种交叉审查的核心优势无法发挥。建议为 openfeel-reviewer 配置不同的模型系列。" |
810
900
  | openfeel-executor 使用推理模型 | "⚠️ openfeel-executor 建议使用快速模型以节省成本。是否配置?" |
811
901
  | 关键 Agent 无任何配置 | "以下 Agent 无模型配置:{列表}。请配置。" |
812
902
 
@@ -826,7 +916,7 @@ models:
826
916
  # 或直接复制此文件到 .openfeel/ 并重命名为 config.yaml(需合并其他节)
827
917
  #
828
918
  # 最近配置时间:yyyy-mm-dd HH:MM
829
- # 平台:OpenCode(当前适配器)
919
+ # 平台:当前 harness(opencode 适配器 / Claude Code 适配器)
830
920
 
831
921
  models:
832
922
  default:
@@ -845,6 +935,31 @@ models:
845
935
 
846
936
  此模板在下次 \`openfeel init\` 或新项目部署时自动检测并建议复用。
847
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
+
848
963
  ## 输出规范
849
964
 
850
965
  - 状态图标:✅ 已满足、⚠️ 降级使用(可接受但非最优)、❌ 缺失或严重不匹配
@@ -853,13 +968,15 @@ models:
853
968
  `,
854
969
  'openfeel-model-config': `---
855
970
  name: openfeel-model-config
856
- 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)特殊注意事项。
857
972
  ---
858
973
 
859
974
  # Skill: openfeel-model-config
860
975
 
861
976
  # Agent 模型查找与配置
862
977
 
978
+ > 正文以**能力名**叙述;平台差异见 \`### opencode 适配器\` / \`### Claude Code 适配器\` 两小节。
979
+
863
980
  ## 何时使用
864
981
 
865
982
  - Agent 调用时报 \`Model not found: xxx\`
@@ -869,6 +986,8 @@ description: 查找和配置 Agent 模型。当 Agent 报 "Model not found" 或
869
986
 
870
987
  ## 配置位置
871
988
 
989
+ ### opencode 适配器
990
+
872
991
  Agent 模型配置在 **\`opencode.jsonc\`**(项目根目录)中:
873
992
 
874
993
  \`\`\`jsonc
@@ -885,6 +1004,12 @@ Agent 模型配置在 **\`opencode.jsonc\`**(项目根目录)中:
885
1004
 
886
1005
  > 💡 **推荐**:可用 \`openfeel model set/get/list --scope default|global|project\` 一键读写三层级模型(工具默认 / 全局 / 当前项目),无需手工定位文件。
887
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
+
888
1013
  ## 查找可用模型名
889
1014
 
890
1015
  当收到 \`"Model not found: xxx. Did you mean: aaa, bbb?"\` 错误时:
@@ -894,6 +1019,8 @@ Agent 模型配置在 **\`opencode.jsonc\`**(项目根目录)中:
894
1019
 
895
1020
  ## Agent 定义文件中的 model 字段
896
1021
 
1022
+ ### opencode 适配器
1023
+
897
1024
  \`.opencode/agents/<name>.md\` 的 frontmatter \`model:\` 字段**是生效的,且优先于** \`opencode.jsonc\` 的 \`agent.<name>.model\`(stage-40 REV-1606 实测)。opencode 模型解析优先级链:
898
1025
 
899
1026
  \`\`\`
@@ -904,21 +1031,31 @@ Agent 模型配置在 **\`opencode.jsonc\`**(项目根目录)中:
904
1031
  1. 修改 \`.opencode/agents/<name>.md\` 的 frontmatter \`model:\`(模板源 \`src/core/templates-data/opencode/agents/\` 同步)
905
1032
  2. (可选,保持一致)同步修改 \`opencode.jsonc\` 的 \`agent.<name>.model\`
906
1033
 
1034
+ ### Claude Code 适配器
1035
+
1036
+ Claude Code 模型解析优先级链:项目 \`.claude/agents\` > 用户 \`~/.claude/agents\`;frontmatter \`model\` **优先于** \`inherit\`。
1037
+
907
1038
  ## 多模态(openfeel-vision)模型特殊规则
908
1039
 
909
- - Feel 的主力模型(DeepSeek V4 Pro)不支持图片输入
910
- - 遇到图片输入时 Feel 会自动委托 openfeel-vision Agent
1040
+ ### opencode 适配器
1041
+
1042
+ - 主会话(Feel)的主力模型(DeepSeek V4 Pro)不支持图片输入
1043
+ - 遇到图片输入时主会话会自动委托 openfeel-vision Agent
911
1044
  - openfeel-vision Agent 需要配置多模态模型(现用 \`deepseek-flash\`,备选 \`qwen3-vl-plus\`)
912
1045
  - 模型名不要随意添加前缀(如 \`alibaba/\`),以平台提示的可用名为准
913
1046
  - **模型引用格式**:\`{auth.json中的key}/{模型ID}\`,不是 \`provider.name\` 也不是 \`provider.id\`
914
1047
  - 读取 \`~/.local/share/opencode/auth.json\` 确认实际 provider key(常见:\`alibaba-cn\`、\`deepseek\`、\`zhipuai\`)
915
1048
  - \`provider\` 块中的 \`name\` 和 \`id\` 仅用于显示,不影响模型解析
916
1049
 
1050
+ ### Claude Code 适配器
1051
+
1052
+ - 多模态由 Claude 侧模型决定;\`openfeel-vision\` subagent 须选支持图像输入的模型。
1053
+
917
1054
  ## 项目 Agent 模型概览
918
1055
 
919
1056
  | Agent | 模型类型 | 备注 |
920
1057
  |-------|---------|------|
921
- | Feel(总统领) | 推理模型 | DeepSeek V4 Pro — 不支持多模态 |
1058
+ | 主会话(Feel) | 推理模型 | DeepSeek V4 Pro — 不支持多模态 |
922
1059
  | openfeel-planner | 推理模型 | — |
923
1060
  | openfeel-schemer | 推理模型 | — |
924
1061
  | openfeel-executor | 快速模型 (Flash) | — |
@@ -1181,7 +1318,7 @@ description: Agent 工具使用规范(todowrite/question/task/skill 四工具
1181
1318
  触发条件(任一必问):需求歧义 / 多个同等合理方案 / 不可逆后果 / 架构决策。使用要求:(Recommended) 标记、选项附后果、≤3 选项、高风险含"取消"。禁止:模糊时自行假设、多方案不选直接实施。
1182
1319
 
1183
1320
  ### 3. task — 子 Agent 调度
1184
- 触发条件:并行探索多代码区 / 复杂多步委托 general / 下游 Agent(经 Feel)。使用要求:并行一条消息多 task、prompt 含任务描述+期望返回、明确只读/可写。
1321
+ 触发条件:并行探索多代码区 / 复杂多步委托 general / 下游 Agent(经主会话(Feel))。使用要求:并行一条消息多 task、prompt 含任务描述+期望返回、明确只读/可写。
1185
1322
 
1186
1323
  ### 4. skill — 技能加载
1187
1324
  触发条件:查阶段状态→openfeel-get-stage-status / 查知识库→openfeel-check-kb / 取 Bug→openfeel-get-bugs。使用要求:会话开始载 check-kb、处理阶段任务前载 get-stage-status、不凭记忆跳过。
@@ -1194,6 +1331,23 @@ description: Agent 工具使用规范(todowrite/question/task/skill 四工具
1194
1331
  | 探索代码 | task(explore) | 手动逐个 grep/read |
1195
1332
  | 获取状态 | skill(openfeel-get-stage-status) | 凭记忆推断 |
1196
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\` | 向用户提问 |
1197
1351
  `,
1198
1352
  'openfeel-update-stage-status': `---
1199
1353
  name: openfeel-update-stage-status
@@ -1371,9 +1525,9 @@ description: 会话启动时检查并补齐 .openfeel/ 工作区目录结构与
1371
1525
  \`.openfeel/users/{username}/dev_last.md\`(**索引**;同名目录 \`dev_last/\` 存主题文件,**文件名英文**)
1372
1526
 
1373
1527
  ### 6. 增量更新复核
1374
- 检查 \`~/.openfeel/update_infos.md\`,若存在未修复条目(追加/异常),提醒用户重启会话或委托 Feel 处理;本 Agent 不自行修改该文件。
1528
+ 检查 \`~/.openfeel/update_infos.md\`,若存在未修复条目(追加/异常),提醒用户重启会话或委托主会话(Feel)处理;本 Agent 不自行修改该文件。
1375
1529
 
1376
- > 目录结构语义、公共/私域分区、用户身份约束、路径自校验规则见全局 AGENTS.md(约束);本 skill 仅承载操作步骤。
1530
+ > 目录结构语义、公共/私域分区、用户身份约束、路径自校验规则见全局规则文件(opencode 适配器:\`~/.config/opencode/AGENTS.md\`;Claude Code 适配器:\`~/.claude/CLAUDE.md\`)(约束);本 skill 仅承载操作步骤。
1377
1531
  `,
1378
1532
  };
1379
1533
  // AUTO-GENERATED-END: SKILL_DEFINITIONS
@@ -1522,8 +1676,10 @@ export function updateProject(projectPath, selectedTools = ["opencode"], lang =
1522
1676
  const state = loadUpdateState(projectPath);
1523
1677
  const globalState = loadGlobalUpdateState();
1524
1678
  const conflicts = [];
1525
- // 过滤:仅处理选中的工具
1526
- if (!selectedTools.includes('opencode')) {
1679
+ // 过滤:仅处理选中的工具(两工具都未选才早退)
1680
+ const wantOpencode = selectedTools.includes('opencode');
1681
+ const wantClaude = selectedTools.includes('claude');
1682
+ if (!wantOpencode && !wantClaude) {
1527
1683
  return { created: [], updated: [], skipped: [], conflicts: [], appended: [] };
1528
1684
  }
1529
1685
  // N8:检测项目内遗留 .opencode/{agents,skills,instructions},只提示不迁移
@@ -1532,12 +1688,6 @@ export function updateProject(projectPath, selectedTools = ["opencode"], lang =
1532
1688
  if (legacyDirs.length > 0) {
1533
1689
  console.warn(t('update.legacyLayoutWarnTmpl', getCliLang(projectPath), { dirs: legacyDirs.join(',') }));
1534
1690
  }
1535
- // 全局部署目标(D1)
1536
- const agentsDir = getGlobalAgentsDir();
1537
- const skillsDir = getGlobalSkillsDir();
1538
- // 确保全局目标目录存在
1539
- mkdirSync(agentsDir, { recursive: true });
1540
- mkdirSync(skillsDir, { recursive: true });
1541
1691
  // 记录项目语言映射到全局配置
1542
1692
  // 使用 options?.lang(用户通过 --lang 显式指定的语言)优先,
1543
1693
  // 回退到 lang(getCliLang 三级回退结果)。确保记录的语言与实际部署语言一致。
@@ -1557,85 +1707,132 @@ export function updateProject(projectPath, selectedTools = ["opencode"], lang =
1557
1707
  globalState.files[getGlobalAgentsMdPath()] = globalState.files[staleCoreKey];
1558
1708
  delete globalState.files[staleCoreKey];
1559
1709
  }
1560
- // 0. 生成全局框架约束 AGENTS.md(~/.config/opencode/AGENTS.md)
1561
- const agentsMdPath = getGlobalAgentsMdPath();
1562
- const agentsMdContent = loadTemplate(lang, 'agents-md');
1563
- {
1564
- const action = deployGlobalAsset(agentsMdPath, agentsMdContent, newGlobalState, 'update');
1565
- pushAction(action, agentsMdPath, created, updated, skipped, appended);
1566
- }
1567
- // 1. 生成 Agent 定义文件到全局 agents 目录(绝对路径作 state key)
1568
- for (const name of listAgentIds(lang)) {
1569
- const content = loadAgentTemplate(lang, name);
1570
- const filePath = join(agentsDir, `${name}.md`);
1571
- const action = deployGlobalAsset(filePath, content, newGlobalState, 'update');
1572
- pushAction(action, filePath, created, updated, skipped, appended);
1573
- }
1574
- // 2. 生成 Skill 定义文件到全局 skills 目录(每个 Skill 一个子目录,包含 SKILL.md)
1575
- for (const [name, content] of Object.entries(SKILL_DEFINITIONS)) {
1576
- const skillSubDir = join(skillsDir, name);
1577
- mkdirSync(skillSubDir, { recursive: true });
1578
- const filePath = join(skillSubDir, 'SKILL.md');
1579
- const action = deployGlobalAsset(filePath, content, newGlobalState, 'update');
1580
- pushAction(action, filePath, created, updated, skipped, appended);
1581
- }
1582
- // 3. 全局 opencode.jsonc:深度合并(保留用户字段),加全局锁 + 原子写
1583
- const globalJsoncPath = getGlobalOpencodeJsoncPath();
1584
- try {
1585
- // 写前备份(B3):备份须在 jsonc 锁之外(之前)完成,避免 backup 锁与 jsonc 锁嵌套
1586
- const globalJsoncBackup = backupFileBeforeWrite(globalJsoncPath, { command: 'update' });
1587
- if (globalJsoncBackup) {
1588
- appendUpdateInfo('backed', { absolutePath: globalJsoncPath, backupRel: globalJsoncBackup.backupRel, command: 'update' });
1589
- 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);
1731
+ }
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);
1590
1739
  }
1591
- // REV-1805:read-merge-write 全部置于锁内,消除与 model-config 锁内读写的 TOCTOU 竞态
1592
- const globalJsoncNew = withFileLock(globalLockPath('global-opencode-jsonc'), () => {
1593
- // 安全读取(T50):目录占位/不可读 → null,跳过合并与写入,按空配置继续
1594
- const current = readJsoncFile(globalJsoncPath);
1595
- if (current === null) {
1596
- return null;
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);
1597
1748
  }
1598
- let merged;
1599
- // 合并可能因块注释 parse 失败抛异常(parseJsonc 边界):降级为「跳过合并、保留原文件」并告警
1600
- try {
1601
- 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);
1602
1770
  }
1603
- catch (err) {
1604
- console.warn(t('update.globalJsoncParseSkipTmpl', getCliLang(projectPath), { err: err.message }));
1605
- merged = current;
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 }));
1777
+ }
1778
+ else {
1779
+ throw err; // 非备份错误照旧上抛(锁超时等不回退)
1606
1780
  }
1607
- atomicWriteFileSync(globalJsoncPath, merged);
1608
- return merged;
1609
- });
1610
- if (globalJsoncNew !== null) {
1611
- updateFileHash(newGlobalState, globalJsoncPath, globalJsoncNew);
1612
1781
  }
1613
- }
1614
- catch (err) {
1615
- if (err instanceof BackupError) {
1616
- // 备份失败(REV-011-A):跳过全局 jsonc 写入、记 anomaly、继续其余步骤(对齐 B3 幂等语义)
1617
- appendUpdateInfo('anomaly', { absolutePath: globalJsoncPath, note: 'backup_failed' });
1618
- 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');
1619
1787
  }
1620
1788
  else {
1621
- throw err; // 非备份错误照旧上抛(锁超时等不回退)
1789
+ skipped.push('opencode.jsonc (use existing)');
1622
1790
  }
1623
1791
  }
1624
- // 4. 项目 opencode.jsonc:不存在则写最小 { $schema };已存在则保留(旧非法字段清理属 stage-39)
1625
- const projectJsoncPath = resolve(projectPath, 'opencode.jsonc');
1626
- if (!existsSync(projectJsoncPath)) {
1627
- atomicWriteFileSync(projectJsoncPath, JSON.stringify(buildProjectOpencodeJsoncObj(), null, 2) + '\n');
1628
- created.push('opencode.jsonc');
1629
- }
1630
- else {
1631
- 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);
1632
1816
  }
1633
- // 重启提醒(仅在 opencode 全局 agent 配置更新时,且为交互模式)
1634
- if (updated.some((f) => f.startsWith(getGlobalAgentsDir())) && process.stdout.isTTY) {
1817
+ // 重启提醒(按 harness 组合;isTTY 守卫、非交互静默)
1818
+ if (process.stdout.isTTY) {
1635
1819
  const reminderLang = getCliLang(projectPath);
1636
- console.log(reminderLang === 'en'
1637
- ? 'opencode global configuration updated. Please restart opencode to load the new configuration.'
1638
- : '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
+ }
1639
1836
  }
1640
1837
  // ── 写入冲突标记文件 ──
1641
1838
  if (conflicts.length > 0) {
@@ -1692,6 +1889,8 @@ export function updateProject(projectPath, selectedTools = ["opencode"], lang =
1692
1889
  // 更新时间戳
1693
1890
  newState.last_update = new Date().toISOString();
1694
1891
  newGlobalState.last_update = new Date().toISOString();
1892
+ // stage-66/D-A:部署完成即刷新「已部署版本」事实源(既有 state 与首次创建均覆盖)
1893
+ newGlobalState.openfeel_version = getOpenfeelVersion();
1695
1894
  // 持久化双 state
1696
1895
  saveUpdateState(projectPath, newState);
1697
1896
  saveGlobalUpdateState(newGlobalState);
@@ -1741,6 +1940,18 @@ function getIncomingContent(relativePath, lang) {
1741
1940
  const name = basename(dirname(relativePath));
1742
1941
  return SKILL_DEFINITIONS[name] ?? '';
1743
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
+ }
1744
1955
  // 项目 opencode.jsonc
1745
1956
  if (relativePath === 'opencode.jsonc') {
1746
1957
  return JSON.stringify(buildProjectOpencodeJsoncObj(), null, 2) + '\n';