openfeel 1.1.0 → 1.1.2
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/README.en.md +42 -12
- package/README.md +8 -3
- package/README.zh-CN.md +43 -13
- package/bin/openfeel.js +5 -0
- package/dist/cli/index.d.ts +15 -3
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +61 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/output-encoding.d.ts +41 -0
- package/dist/cli/output-encoding.d.ts.map +1 -0
- package/dist/cli/output-encoding.js +165 -0
- package/dist/cli/output-encoding.js.map +1 -0
- package/dist/cli/repl.d.ts +2 -2
- package/dist/cli/repl.d.ts.map +1 -1
- package/dist/cli/repl.js +46 -25
- package/dist/cli/repl.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +56 -9
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/flow.d.ts.map +1 -1
- package/dist/commands/flow.js +827 -138
- package/dist/commands/flow.js.map +1 -1
- package/dist/commands/init.d.ts +2 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +58 -39
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/knowledge.d.ts.map +1 -1
- package/dist/commands/knowledge.js +69 -0
- package/dist/commands/knowledge.js.map +1 -1
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +17 -5
- package/dist/commands/lint.js.map +1 -1
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +15 -1
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/model.d.ts.map +1 -1
- package/dist/commands/model.js +18 -1
- package/dist/commands/model.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +185 -7
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/project.js +2 -2
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/roadmap.d.ts.map +1 -1
- package/dist/commands/roadmap.js +20 -5
- package/dist/commands/roadmap.js.map +1 -1
- package/dist/commands/setup.d.ts +7 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +37 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/shared/errors.d.ts +22 -0
- package/dist/commands/shared/errors.d.ts.map +1 -0
- package/dist/commands/shared/errors.js +45 -0
- package/dist/commands/shared/errors.js.map +1 -0
- package/dist/commands/stage.d.ts.map +1 -1
- package/dist/commands/stage.js +133 -23
- package/dist/commands/stage.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +1 -10
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/view.d.ts +1 -1
- package/dist/commands/view.d.ts.map +1 -1
- package/dist/commands/view.js +4 -26
- package/dist/commands/view.js.map +1 -1
- package/dist/core/archive/merge.d.ts.map +1 -1
- package/dist/core/archive/merge.js +9 -5
- package/dist/core/archive/merge.js.map +1 -1
- package/dist/core/artifact-graph/graph.d.ts +7 -2
- package/dist/core/artifact-graph/graph.d.ts.map +1 -1
- package/dist/core/artifact-graph/graph.js +25 -18
- package/dist/core/artifact-graph/graph.js.map +1 -1
- package/dist/core/artifact-graph/instruction-loader.d.ts.map +1 -1
- package/dist/core/artifact-graph/instruction-loader.js +4 -33
- package/dist/core/artifact-graph/instruction-loader.js.map +1 -1
- package/dist/core/backup.d.ts +28 -0
- package/dist/core/backup.d.ts.map +1 -0
- package/dist/core/backup.js +118 -0
- package/dist/core/backup.js.map +1 -0
- package/dist/core/config.d.ts +30 -19
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +86 -26
- package/dist/core/config.js.map +1 -1
- package/dist/core/flow-manager.d.ts +256 -5
- package/dist/core/flow-manager.d.ts.map +1 -1
- package/dist/core/flow-manager.js +908 -68
- package/dist/core/flow-manager.js.map +1 -1
- package/dist/core/fs/atomic-write.d.ts +3 -0
- package/dist/core/fs/atomic-write.d.ts.map +1 -1
- package/dist/core/fs/atomic-write.js +3 -0
- package/dist/core/fs/atomic-write.js.map +1 -1
- package/dist/core/fs/safe-read.d.ts +11 -0
- package/dist/core/fs/safe-read.d.ts.map +1 -0
- package/dist/core/fs/safe-read.js +31 -0
- package/dist/core/fs/safe-read.js.map +1 -0
- package/dist/core/global-paths.d.ts +17 -6
- package/dist/core/global-paths.d.ts.map +1 -1
- package/dist/core/global-paths.js +26 -7
- package/dist/core/global-paths.js.map +1 -1
- package/dist/core/i18n-data/en.d.ts +3 -0
- package/dist/core/i18n-data/en.d.ts.map +1 -1
- package/dist/core/i18n-data/en.js +271 -18
- package/dist/core/i18n-data/en.js.map +1 -1
- package/dist/core/i18n-data/zh-CN.d.ts +3 -0
- package/dist/core/i18n-data/zh-CN.d.ts.map +1 -1
- package/dist/core/i18n-data/zh-CN.js +278 -18
- package/dist/core/i18n-data/zh-CN.js.map +1 -1
- package/dist/core/i18n.d.ts +8 -0
- package/dist/core/i18n.d.ts.map +1 -1
- package/dist/core/i18n.js +14 -4
- package/dist/core/i18n.js.map +1 -1
- package/dist/core/init.d.ts +12 -15
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +85 -142
- package/dist/core/init.js.map +1 -1
- package/dist/core/managed-region.d.ts +1 -1
- package/dist/core/managed-region.js +1 -1
- package/dist/core/metrics.d.ts +16 -4
- package/dist/core/metrics.d.ts.map +1 -1
- package/dist/core/metrics.js +31 -11
- package/dist/core/metrics.js.map +1 -1
- package/dist/core/migrate.d.ts +12 -0
- package/dist/core/migrate.d.ts.map +1 -1
- package/dist/core/migrate.js +88 -29
- package/dist/core/migrate.js.map +1 -1
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +4 -2
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/opencode-config.d.ts +4 -4
- package/dist/core/opencode-config.d.ts.map +1 -1
- package/dist/core/opencode-config.js +20 -7
- package/dist/core/opencode-config.js.map +1 -1
- package/dist/core/plan/path.d.ts +40 -0
- package/dist/core/plan/path.d.ts.map +1 -1
- package/dist/core/plan/path.js +108 -1
- package/dist/core/plan/path.js.map +1 -1
- package/dist/core/plan/roadmap.js +2 -2
- package/dist/core/plan/roadmap.js.map +1 -1
- package/dist/core/plan/scheme.d.ts +74 -2
- package/dist/core/plan/scheme.d.ts.map +1 -1
- package/dist/core/plan/scheme.js +311 -26
- package/dist/core/plan/scheme.js.map +1 -1
- package/dist/core/plan/stage.d.ts +34 -3
- package/dist/core/plan/stage.d.ts.map +1 -1
- package/dist/core/plan/stage.js +90 -6
- package/dist/core/plan/stage.js.map +1 -1
- package/dist/core/public-logger.d.ts +18 -5
- package/dist/core/public-logger.d.ts.map +1 -1
- package/dist/core/public-logger.js +62 -23
- package/dist/core/public-logger.js.map +1 -1
- package/dist/core/runtime-log.d.ts +25 -0
- package/dist/core/runtime-log.d.ts.map +1 -0
- package/dist/core/runtime-log.js +94 -0
- package/dist/core/runtime-log.js.map +1 -0
- package/dist/core/setup.d.ts +10 -0
- package/dist/core/setup.d.ts.map +1 -0
- package/dist/core/setup.js +103 -0
- package/dist/core/setup.js.map +1 -0
- package/dist/core/template-loader.d.ts +4 -5
- package/dist/core/template-loader.d.ts.map +1 -1
- package/dist/core/template-loader.js +1322 -259
- package/dist/core/template-loader.js.map +1 -1
- package/dist/core/templates.d.ts +4 -4
- package/dist/core/templates.d.ts.map +1 -1
- package/dist/core/templates.js +15 -11
- package/dist/core/templates.js.map +1 -1
- package/dist/core/update-infos.d.ts +14 -3
- package/dist/core/update-infos.d.ts.map +1 -1
- package/dist/core/update-infos.js +67 -15
- package/dist/core/update-infos.js.map +1 -1
- package/dist/core/update-state.d.ts.map +1 -1
- package/dist/core/update-state.js +7 -4
- package/dist/core/update-state.js.map +1 -1
- package/dist/core/update.d.ts +2 -7
- package/dist/core/update.d.ts.map +1 -1
- package/dist/core/update.js +338 -149
- package/dist/core/update.js.map +1 -1
- package/dist/core/view/entry.d.ts +53 -1
- package/dist/core/view/entry.d.ts.map +1 -1
- package/dist/core/view/entry.js +71 -2
- package/dist/core/view/entry.js.map +1 -1
- package/dist/core/workspace/identity.d.ts.map +1 -1
- package/dist/core/workspace/identity.js +15 -5
- package/dist/core/workspace/identity.js.map +1 -1
- package/dist/core/workspace/knowledge.d.ts.map +1 -1
- package/dist/core/workspace/knowledge.js +125 -19
- package/dist/core/workspace/knowledge.js.map +1 -1
- package/dist/core/workspace/structure.d.ts.map +1 -1
- package/dist/core/workspace/structure.js +6 -2
- package/dist/core/workspace/structure.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/utils/kb-dedup.d.ts +5 -1
- package/dist/utils/kb-dedup.d.ts.map +1 -1
- package/dist/utils/kb-dedup.js +11 -5
- package/dist/utils/kb-dedup.js.map +1 -1
- package/package.json +6 -6
- package/dist/utils/path.d.ts +0 -22
- package/dist/utils/path.d.ts.map +0 -1
- package/dist/utils/path.js +0 -37
- package/dist/utils/path.js.map +0 -1
- package/scripts/patch-inquirer.js +0 -70
package/dist/core/update.js
CHANGED
|
@@ -1,34 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenCode 适配器更新编排
|
|
3
|
-
*
|
|
3
|
+
* 增量部署全局资产(全局 AGENTS.md、agent、skill)并更新全局/项目 opencode.jsonc 配置。
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* - 新增 instructions/core.md 创建(从 init.ts
|
|
5
|
+
* 变更摘要:
|
|
6
|
+
* - v3-stage-04: 新增 instructions/core.md 创建(从 init.ts 迁移)
|
|
7
|
+
* - v1.1.1: 拆除项目 AGENTS.md 部署;全局约束由 core.md 改为全局 AGENTS.md(收归 openfeel setup)
|
|
7
8
|
*/
|
|
8
|
-
import { existsSync, readFileSync, mkdirSync
|
|
9
|
+
import { existsSync, readFileSync, mkdirSync } from 'node:fs';
|
|
9
10
|
import { resolve, dirname, join, basename, isAbsolute, relative } from 'node:path';
|
|
10
11
|
import { loadAgentTemplate, listAgentIds, loadTemplate } from './template-loader.js';
|
|
11
|
-
import { recordProjectLang
|
|
12
|
-
import { getCliLang } from './i18n.js';
|
|
13
|
-
import { getOpencodeGlobalDir, getGlobalAgentsDir, getGlobalSkillsDir, getGlobalCoreMdPath, getGlobalOpencodeJsoncPath, getGlobalUpdateStatePath } from './global-paths.js';
|
|
12
|
+
import { recordProjectLang } from './workspace/identity.js';
|
|
13
|
+
import { t, getCliLang } from './i18n.js';
|
|
14
|
+
import { getOpencodeGlobalDir, getGlobalAgentsDir, getGlobalSkillsDir, getGlobalCoreMdPath, getGlobalAgentsMdPath, getGlobalOpencodeJsoncPath, getGlobalUpdateStatePath } from './global-paths.js';
|
|
14
15
|
import { mergeGlobalOpencodeJsonc, buildProjectOpencodeJsoncObj } from './opencode-config.js';
|
|
15
16
|
import { atomicWriteFileSync } from './fs/atomic-write.js';
|
|
16
17
|
import { withFileLock, globalLockPath } from './fs/file-lock.js';
|
|
17
18
|
import { detectFileType, normalize, splitFrontmatter, parseRegion, wrapRegion, replaceRegion, mergeFrontmatter, serializeFrontmatter } from './managed-region.js';
|
|
18
19
|
import { appendUpdateInfo } from './update-infos.js';
|
|
20
|
+
import { backupFileBeforeWrite, BackupError, notifyBackupIfTTY } from './backup.js';
|
|
21
|
+
import { readJsoncFile } from './fs/safe-read.js';
|
|
19
22
|
// 新增:update_state.json hash 比对与冲突检测(项目 state + 全局 state)
|
|
20
23
|
import { hashContent, loadUpdateState, saveUpdateState, createUpdateState, loadGlobalUpdateState, saveGlobalUpdateState, createGlobalUpdateState, updateFileHash, markFileConflict, getOpenfeelVersion, } from './update-state.js';
|
|
21
|
-
/** AGENTS.md 语言冲突错误(由命令层捕获处理) */
|
|
22
|
-
export class AgentsMdLangConflictError extends Error {
|
|
23
|
-
projectLang;
|
|
24
|
-
requestedLang;
|
|
25
|
-
constructor(projectLang, requestedLang) {
|
|
26
|
-
super(`AGENTS.md language conflict: project=${projectLang}, requested=${requestedLang}`);
|
|
27
|
-
this.projectLang = projectLang;
|
|
28
|
-
this.requestedLang = requestedLang;
|
|
29
|
-
this.name = 'AgentsMdLangConflictError';
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
24
|
/** 当前支持部署的 AI 工具列表 */
|
|
33
25
|
export const supportedTools = [
|
|
34
26
|
{
|
|
@@ -73,7 +65,7 @@ export async function selectTools() {
|
|
|
73
65
|
export const SKILL_DEFINITIONS = {
|
|
74
66
|
'openfeel-agent-model-check': `---
|
|
75
67
|
name: openfeel-agent-model-check
|
|
76
|
-
description: Agent 模型检查与修复。当 Agent 报 "Model not found" 或需要排查模型配置时使用。涵盖 auth.json
|
|
68
|
+
description: Agent 模型检查与修复。当 Agent 报 "Model not found" 或需要排查模型配置时使用。涵盖 auth.json 校验(opencode 适配器)、provider key 匹配、模型能力确认、openfeel-vision 多模态专项指南。
|
|
77
69
|
---
|
|
78
70
|
|
|
79
71
|
# Skill: openfeel-agent-model-check
|
|
@@ -341,6 +333,130 @@ Skill 加载后,根据步骤 3 的匹配结果决定是否追加提示:
|
|
|
341
333
|
此提示确保 Agent 不会跳过知识库查询直接编码。
|
|
342
334
|
|
|
343
335
|
- **若步骤 3 无相关条目**:不追加强制提示,静默返回(已在步骤 4 输出"知识库中暂无相关记录,可继续执行")。
|
|
336
|
+
`,
|
|
337
|
+
'openfeel-cli-usage': `---
|
|
338
|
+
name: openfeel-cli-usage
|
|
339
|
+
description: OpenFeel CLI 命令用法参考:命令清单与关键参数、15 个 phase 枚举与转移表、stageId 命名与目录映射约定、v1.1.2 新增能力(自描述/可纠错、--json 结构化输出、health --fix、scheme draft、knowledge dedup、纠正清理侧命令面)。当需要查询 CLI 命令、参数、phase、stageId、阶段命名时加载。
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
# OpenFeel CLI 用法参考
|
|
343
|
+
|
|
344
|
+
## 说明
|
|
345
|
+
|
|
346
|
+
- 定位:**查询型参考手册**(只读,不自执行)。回答「命令是什么、参数怎么给、phase/stageId 规则如何」。
|
|
347
|
+
- 边界:**执行型交互向导**请用 \`openfeel-wizard\` skill(跑 \`openfeel flow wizard\` 推进流水线);本 skill 只承载静态知识查阅。
|
|
348
|
+
- 与 \`openfeel-tool-usage\` 协同:工具选择规范见该 skill;本 skill 补充 CLI 命令细节。
|
|
349
|
+
|
|
350
|
+
> 本文档为 v1.1.2 快照;命令/参数细节以 \`openfeel <cmd> --help\`(本仓执行)实时输出为准(CLI 演进后本文档可能滞后)。
|
|
351
|
+
>
|
|
352
|
+
> ⚠️ **本仓自举**:本仓(openfeel 源码仓库)开发/执行时请用 \`node bin/openfeel.js <cmd>\`;安装后使用 \`openfeel <cmd>\`。
|
|
353
|
+
|
|
354
|
+
## 命令速查
|
|
355
|
+
|
|
356
|
+
| 命令 | 用途 | 关键参数 |
|
|
357
|
+
|------|------|----------|
|
|
358
|
+
| \`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\` |
|
|
361
|
+
| \`openfeel migrate [path]\` | Legacy 布局迁移(检测/备份/迁移/回滚) | \`--dry-run\`、\`--remap-assignee\`、\`--clean-global-core-md\`;\`migrate rollback\` |
|
|
362
|
+
| \`openfeel flow ...\` | 流水线状态管理 | 见下「flow 子命令」 |
|
|
363
|
+
| \`openfeel plan stage add\\|list\` | 工作阶段管理 | \`add <name> --deps <ids...> --tasks <items...>\` |
|
|
364
|
+
| \`openfeel plan scheme create\\|publish\\|rename\\|list\\|remove\` | 操作方案管理 | \`create <stage> <title> [--draft]\`、\`publish <stage> <opId>\`、\`rename <stage> <opId> --title <text>\`、\`remove <stage> <opId> [--force] [--dry-run]\` |
|
|
365
|
+
| \`openfeel stage status\\|set\\|task\` | \`status.md\` 原子操作 | \`set <id> --status/--exec-mode/--auto-advance/--review-agent\`、\`task <id> [no] --add/--done/--undone\`(\`stage create\` 已弃用) |
|
|
366
|
+
| \`openfeel config ...\` | 配置管理 | \`get\\|set <key> <value> [--global]\`(支持全量 \`defaults.*\`)、\`get-lang\\|set-lang\`、\`list-projects\`、\`effective [key]\` |
|
|
367
|
+
| \`openfeel model set\\|get\\|list\` | 模型三层级配置 | \`set <agent> <model>\` |
|
|
368
|
+
| \`openfeel lint i18n\\|kb\` | 健康检查(i18n 键一致性 / kb 过期引用) | 发现问题**非 0 退出** |
|
|
369
|
+
| \`openfeel knowledge ...\` | 知识库管理 | \`list\` / \`add\` / \`search\` / \`index\` / \`dedup\` |
|
|
370
|
+
| \`openfeel archive <stage>\` | 归档指定阶段(汇总产出、生成摘要、提取知识) | \`<stage>\` |
|
|
371
|
+
| \`openfeel view list\\|accept\` | 审查条目验收(\`add\` 已移除,改用 \`flow review add\`) | — |
|
|
372
|
+
| \`openfeel project overview\` | 项目结构概览 | \`overview\`(无 \`list\`/\`info\` 子命令) |
|
|
373
|
+
| \`openfeel roadmap create\\|show\` | 分期大纲管理 | \`create <version>\`、\`show [version]\` |
|
|
374
|
+
| \`openfeel instructions <artifactId>\` | 为指定 artifact 生成结构化指令 | \`--change <name>\`、\`--schema <name>\`、\`--json\` |
|
|
375
|
+
|
|
376
|
+
**flow 子命令**:\`status\` / \`current\` / \`overview\` / \`phases\` / \`stage\` / \`metrics\` / \`advance\`(\`--stage <id> --to <phase>\`、\`--op\`、\`--force\`、\`--dry-run\`、\`--quiet\`)/ \`attempt\` / \`log\` / \`review\` / \`retry\` / \`repair\` / \`ops\` / \`migrate\` / \`checkpoint\` / \`health\` / \`recover\` / \`wizard\`。
|
|
377
|
+
|
|
378
|
+
**纠错/清理侧补充**:\`flow review add|resolve|update|remove\`;\`flow ops list [--stage <id>] [--json]\`(操作方案视图,draft 分组展示);\`flow repair [--prune-orphans]\`(默认只报告,\`--prune-orphans\` 仅清键孤儿,单向不删文件)。
|
|
379
|
+
|
|
380
|
+
> 命令职责分层:\`plan stage add\`(完整,推荐)> \`flow stage add\`(仅注册 \`flow.json\`,不建目录)> \`stage create\`(已弃用)。
|
|
381
|
+
|
|
382
|
+
## v1.1.2 新增能力(stage-41~55)
|
|
383
|
+
|
|
384
|
+
> 以下为 v1.1.2 相对上版的增量能力(分组摘要,细节以 \`<cmd> --help\` 为准)。
|
|
385
|
+
|
|
386
|
+
- **A. 自描述与可纠错(stage-41)**:\`flow phases [--json]\`(**5 键**:\`schemaVersion\`/\`phases\`/\`transitions\`/\`advanceAccepted\`/\`transitionsDiff\`)、\`flow stage remove [--force] [--dry-run] [--purge]\`、stageId 校验/冲突检测(\`validateStageId\` / \`suggestStageId\` / \`findStageDirConflict\`)、\`plan stage add --deps\`。
|
|
387
|
+
- **B. 状态与口径(stage-42)**:\`config effective [key]\`(有效值 + 生效来源 \`status.md > config.yaml > profile.yaml > builtin\`)、\`pipeline.phase\` 全量 done 判定、审计日志 \`register_stage\` / \`register_op\`。
|
|
388
|
+
- **C. 部署与备份(stage-46)**:覆盖写前自动备份 \`~/.openfeel/backup/{ts}/\` + \`update_infos.md\`「备份」类条目(Agent 按 \`feel.md\` 检查规则核对)。
|
|
389
|
+
- **D. 纠正/清理侧命令面(stage-51/52)**:\`plan scheme remove\`(仅删 \`flow.json\` 注册键,不删 op 文件;done/checkpoint 保护)、\`plan scheme rename --title\`(必填,空标题报错)、\`plan scheme publish\` + \`create --draft\`(draft→pending,空模板拒绝)、\`flow stage set --deps\`(覆盖写入;**不带 \`--deps\` 视为清空**)、\`flow review update|remove\`、\`stage set\`(**幂等**:同值 no-op + 同值不生成 \`.bak\`;\`--exec-mode <manual\\|auto>\` / \`--auto-advance <enabled\\|disabled>\` / \`--review-agent <agent>\`)、\`stage task --add|--done|--undone\`、\`plan stage add --tasks\`、\`flow repair --prune-orphans\`、\`flow health --fix [--dry-run]\`(默认只报告;\`--fix\` **仅回写 status.md「状态」字段**;\`--dry-run\` 预览零写盘)、\`flow ops list\`、\`flow advance --quiet\` + \`--to <远距 phase>\`(存在唯一路径时自动逐步;\`--dry-run\` 展示完整路径)、\`knowledge dedup\`(检索相似条目,**只读建议,不修改 kb**)。\`flow attempt\` 对 **draft op 拒绝**。
|
|
390
|
+
- **E. 输出/门禁/约定(stage-50/52)**:\`flow status/current/health/metrics/overview --json\`(纯 JSON 单文档,含 \`schemaVersion\`)、\`lint i18n\`/\`lint kb\` **非 0 退出**(门禁语义,无逃生阀)、\`config set/get\` 支持**全量 \`defaults.*\`**(Schema 驱动键白名单 + 值类型归一 + 枚举校验,非法报错不写盘)、\`view add\` **已移除**(改用 \`flow review add\`)、\`NO_COLOR\` 环境变量 / \`--no-color\`。
|
|
391
|
+
|
|
392
|
+
## phase 枚举与转移表
|
|
393
|
+
|
|
394
|
+
15 个 phase(\`plan_pending\` … \`done\`):
|
|
395
|
+
|
|
396
|
+
\`\`\`
|
|
397
|
+
plan_pending → plan_review → plan_passed → scheme_pending → scheme_review
|
|
398
|
+
→ scheme_passed → exec_running → review_pending → review_passed → test_pending
|
|
399
|
+
→ test_passed → archiving → done
|
|
400
|
+
\`\`\`
|
|
401
|
+
|
|
402
|
+
| phase | 合法目标 |
|
|
403
|
+
|-------|----------|
|
|
404
|
+
| \`plan_pending\` | \`plan_review\`, \`plan_passed\` |
|
|
405
|
+
| \`plan_review\` | \`plan_passed\`, \`plan_pending\` |
|
|
406
|
+
| \`plan_passed\` | \`scheme_pending\` |
|
|
407
|
+
| \`scheme_pending\` | \`scheme_review\`, \`scheme_passed\` |
|
|
408
|
+
| \`scheme_review\` | \`scheme_passed\`, \`scheme_pending\` |
|
|
409
|
+
| \`scheme_passed\` | \`exec_running\` |
|
|
410
|
+
| \`exec_running\` | \`review_pending\`, \`scheme_pending\` |
|
|
411
|
+
| \`review_pending\` | \`review_failed\`, \`review_passed\` |
|
|
412
|
+
| \`review_failed\` | \`review_pending\`, \`scheme_pending\` |
|
|
413
|
+
| \`review_passed\` | \`test_pending\` |
|
|
414
|
+
| \`test_pending\` | \`test_failed\`, \`test_passed\` |
|
|
415
|
+
| \`test_failed\` | \`test_pending\`, \`scheme_pending\` |
|
|
416
|
+
| \`test_passed\` | \`archiving\` |
|
|
417
|
+
| \`archiving\` | \`done\` |
|
|
418
|
+
| \`done\` | (终态) |
|
|
419
|
+
|
|
420
|
+
- 转移表以**运行时** \`.openfeel/pipeline.yaml\` 为准;\`openfeel flow phases\` 查看当前生效值。
|
|
421
|
+
- 推进:\`openfeel flow advance --stage <id> --to <phase>\`。**\`advanceAccepted\` = 内置 15 个 phase 的「推进白名单」**(\`flow advance\` 只接受这 15 个值),**不是**组合式条件键的替代品(旧文案曾误述,此处已更正)。**组合式条件差异**(如内置默认含 \`review_passed|test_passed\`,本仓 \`pipeline.yaml\` 未列)经 \`--json.transitionsDiff.missing\` **显式可见**(stage-50 裁定:**不补组合键**,改以 \`transitionsDiff\` 显式化)。
|
|
422
|
+
- 参考:\`.openfeel/manual/**\`(CLI/flow 模块文档)。
|
|
423
|
+
|
|
424
|
+
## stageId 命名与目录映射
|
|
425
|
+
|
|
426
|
+
| 格式 | 示例 | series / stageDir |
|
|
427
|
+
|------|------|-------------------|
|
|
428
|
+
| 完整四级 | \`v1.1.2-stage-43\` | \`v1\` / \`stage-43\` |
|
|
429
|
+
| 历史短版 | \`v4-stage-04\` | \`v4\` / \`stage-04\` |
|
|
430
|
+
| 短名 | \`stage-01\` | \`v1\`(默认)/ \`stage-01\` |
|
|
431
|
+
|
|
432
|
+
- \`series = v{MAJOR}\`;目录映射 \`.openfeel/plan/{series}/stage-{NN}/\`。
|
|
433
|
+
- 唯一性:\`(series, stageDir)\` 冲突(如 \`v4-stage-04\` 与 \`v4.0.0-stage-04\` 映射同目录)会被 \`findStageDirConflict\` 检出并阻止;非法输入由 \`validateStageId\` 报原因 + \`suggestStageId\` 给建议名(\`NN\` 限定在同 series 内 max+1)。
|
|
434
|
+
- 权威实现与三级回退:见 \`.openfeel/manual/core/plan-path.md\`。
|
|
435
|
+
|
|
436
|
+
## 典型场景
|
|
437
|
+
|
|
438
|
+
1. **落地新阶段**:\`openfeel plan stage add v1.1.2-stage-43\`(建目录 + 注册)→ \`openfeel flow advance --stage v1.1.2-stage-43 --to plan_pending\` 起步。
|
|
439
|
+
2. **声明依赖**:\`openfeel plan stage add v1.1.2-stage-43 --deps v1.1.2-stage-41 v1.1.2-stage-47\`(写入 \`overview.md\`「## 依赖」+ \`flow.json.stages[].deps\`)。
|
|
440
|
+
3. **纠错移除误建阶段**:\`openfeel flow stage remove v1.1.2-stage-43 --dry-run\` 预览 → 确认后 \`--force\`(必要时 \`--purge\` 删目录)。
|
|
441
|
+
4. **查询有效配置**:\`openfeel config effective auto_advance\`(来源优先级 \`status.md\` > \`config.yaml\` > \`profile.yaml\` > \`builtin\`)。
|
|
442
|
+
5. **部署前备份**:\`setup\`/\`update\`/\`init\`/\`migrate\` 覆盖写入前自动备份至 \`~/.openfeel/backup/{ts}/\`;\`update_infos.md\` 会新增「备份」类条目,Agent 须按 \`feel.md\` 检查规则核对(存在性 + 失败重跑)。
|
|
443
|
+
|
|
444
|
+
## 权限模型要点
|
|
445
|
+
|
|
446
|
+
- 9 个 agent 内联 \`permission:\` 白名单,均含 \`external_directory: "allow"\`(单一键,无读写粒度)。
|
|
447
|
+
- **合并语义**:agent \`.md\` frontmatter 与 \`opencode.jsonc\` 按权限键**深合并**,同名键以 agent \`.md\` 为准(配置文件无法覆盖已声明键)。
|
|
448
|
+
- **收紧入口**:项目根 \`.opencode/agent/<name>.md\` 覆盖全局同名 agent 并重写完整 \`permission\` 块;勿手改全局 agent frontmatter(会被 \`update\` 覆盖)。
|
|
449
|
+
- 详 \`.openfeel/manual/core/permission.md\`。
|
|
450
|
+
|
|
451
|
+
## 与 openfeel-wizard 的区别
|
|
452
|
+
|
|
453
|
+
| 维度 | \`openfeel-cli-usage\`(本 skill) | \`openfeel-wizard\` |
|
|
454
|
+
|------|--------------------------------|-------------------|
|
|
455
|
+
| 定位 | 查询型**参考手册**(只读静态知识) | 执行型**交互向导**(跑 \`openfeel flow wizard\`) |
|
|
456
|
+
| 动作 | 回答「命令/参数/phase/stageId 是什么」 | 逐步选择并推进阶段 phase |
|
|
457
|
+
| 前置 | 无 | 需交互式 TTY(非 TTY 用 \`flow advance --stage <id> --to <phase>\`) |
|
|
458
|
+
|
|
459
|
+
> wizard skill 正文已交叉引用本 skill;二者正文互引,边界为「查手册 vs 跑向导」。
|
|
344
460
|
`,
|
|
345
461
|
'openfeel-get-bugs': `---
|
|
346
462
|
name: openfeel-get-bugs
|
|
@@ -514,6 +630,8 @@ description: 加载流水线健康检查结果,供 Agent 判断 flow.json 与
|
|
|
514
630
|
|
|
515
631
|
# 流水线健康检查
|
|
516
632
|
|
|
633
|
+
> ⚠️ **本仓自举**:本仓(openfeel 源码仓库)开发/执行时请用 \`node bin/openfeel.js <cmd>\`;安装后使用 \`openfeel <cmd>\`。
|
|
634
|
+
|
|
517
635
|
## 输入
|
|
518
636
|
|
|
519
637
|
无
|
|
@@ -535,6 +653,8 @@ description: Feel 自检时检查所有 Agent 的模型配置状态,识别期
|
|
|
535
653
|
|
|
536
654
|
# 模型配置检查
|
|
537
655
|
|
|
656
|
+
> ⚠️ **本仓自举**:本仓(openfeel 源码仓库)开发/执行时请用 \`node bin/openfeel.js <cmd>\`;安装后使用 \`openfeel <cmd>\`。
|
|
657
|
+
|
|
538
658
|
## 触发时机
|
|
539
659
|
|
|
540
660
|
Feel Agent 在以下时机加载本 Skill:
|
|
@@ -550,7 +670,7 @@ Feel Agent 在以下时机加载本 Skill:
|
|
|
550
670
|
|
|
551
671
|
| 配置文件 | 平台 |
|
|
552
672
|
|----------|------|
|
|
553
|
-
| \`opencode.jsonc\` | OpenCode |
|
|
673
|
+
| \`opencode.jsonc\` | OpenCode(当前适配器) |
|
|
554
674
|
| \`kilo/kilo.json\` | Kilo |
|
|
555
675
|
| \`claude/claude.json\` | Claude |
|
|
556
676
|
|
|
@@ -647,7 +767,7 @@ models:
|
|
|
647
767
|
\`\`\`markdown
|
|
648
768
|
## 🔍 模型配置检查报告
|
|
649
769
|
|
|
650
|
-
**平台**:OpenCode
|
|
770
|
+
**平台**:OpenCode(当前适配器)
|
|
651
771
|
**配置文件**:.openfeel/config.yaml
|
|
652
772
|
**检查时间**:yyyy-mm-dd HH:MM
|
|
653
773
|
|
|
@@ -700,7 +820,7 @@ models:
|
|
|
700
820
|
# 或直接复制此文件到 .openfeel/ 并重命名为 config.yaml(需合并其他节)
|
|
701
821
|
#
|
|
702
822
|
# 最近配置时间:yyyy-mm-dd HH:MM
|
|
703
|
-
# 平台:OpenCode
|
|
823
|
+
# 平台:OpenCode(当前适配器)
|
|
704
824
|
|
|
705
825
|
models:
|
|
706
826
|
default:
|
|
@@ -727,7 +847,7 @@ models:
|
|
|
727
847
|
`,
|
|
728
848
|
'openfeel-model-config': `---
|
|
729
849
|
name: openfeel-model-config
|
|
730
|
-
description: 查找和配置 Agent 模型。当 Agent 报 "Model not found" 或需要调整/新增 Agent 模型时使用。覆盖 opencode.jsonc
|
|
850
|
+
description: 查找和配置 Agent 模型。当 Agent 报 "Model not found" 或需要调整/新增 Agent 模型时使用。覆盖 opencode.jsonc 配置(opencode 适配器)、模型名查找方法、多模态模型(openfeel-vision)特殊注意事项。
|
|
731
851
|
---
|
|
732
852
|
|
|
733
853
|
# Skill: openfeel-model-config
|
|
@@ -809,6 +929,8 @@ description: 跨会话上下文恢复,供 Agent 在会话启动时重建流水
|
|
|
809
929
|
|
|
810
930
|
# 跨会话上下文恢复
|
|
811
931
|
|
|
932
|
+
> ⚠️ **本仓自举**:本仓(openfeel 源码仓库)开发/执行时请用 \`node bin/openfeel.js <cmd>\`;安装后使用 \`openfeel <cmd>\`。
|
|
933
|
+
|
|
812
934
|
## 输入
|
|
813
935
|
|
|
814
936
|
无
|
|
@@ -816,9 +938,11 @@ description: 跨会话上下文恢复,供 Agent 在会话启动时重建流水
|
|
|
816
938
|
## 执行步骤
|
|
817
939
|
|
|
818
940
|
1. 运行 \`openfeel flow recover\` 获取全局状态、流水线阶段、当前操作、阻塞原因与待处理任务
|
|
819
|
-
2.
|
|
941
|
+
2. 读取**索引** \`.openfeel/users/{username}/dev_last.md\`(「用户偏好」「主题索引」「公共交接区」)→ **按需**再读主题文件(**文件名英文,如 \`decisions.md\`/\`pending.md\`**)恢复详情;**旧格式**(无 \`## 主题索引\` 节)→ 触发惰性迁移(按英文命名映射拆分生成 \`dev_last/\` + 索引,原文保留)
|
|
820
942
|
3. 将两者合并为当前会话起点
|
|
821
943
|
|
|
944
|
+
> **加锁提示(A9)**:恢复流程为**只读**,无需加锁;仅**会话末尾写入**走 \`withFileLock\` 临界区(见 agents-md「并发写加锁协议」)。
|
|
945
|
+
|
|
822
946
|
## 输出
|
|
823
947
|
|
|
824
948
|
恢复摘要:流水线状态 + 阻塞项 + 待处理任务列表
|
|
@@ -970,7 +1094,7 @@ description: 语义检索 .openfeel/kb/ 项目知识库。当精确匹配无结
|
|
|
970
1094
|
`,
|
|
971
1095
|
'openfeel-sync-status': `---
|
|
972
1096
|
name: openfeel-sync-status
|
|
973
|
-
description:
|
|
1097
|
+
description: 聚合所有成员的任务进度视图(各用户 dev_last 索引主题 + flow.json 阶段状态),供任意 Agent 快速了解项目整体协作状态。
|
|
974
1098
|
---
|
|
975
1099
|
|
|
976
1100
|
# Skill: openfeel-sync-status
|
|
@@ -979,63 +1103,91 @@ description: 聚合所有成员的任务进度视图,供任意 Agent 快速了
|
|
|
979
1103
|
|
|
980
1104
|
## 输入
|
|
981
1105
|
|
|
982
|
-
|
|
1106
|
+
无(**自动**:各用户 \`.openfeel/users/*/dev_last.md\` 索引「主题索引」+ \`flow.json\` 阶段状态 \`stages[].phase/status\`)
|
|
983
1107
|
|
|
984
1108
|
## 执行步骤
|
|
985
1109
|
|
|
986
|
-
### 1.
|
|
1110
|
+
### 1. 读取进度来源
|
|
987
1111
|
|
|
988
|
-
读取 \`.openfeel/
|
|
1112
|
+
读取 \`.openfeel/users/*/dev_last.md\` 的「主题索引」摘要 + \`flow.json\` 的 active stages;**按用户聚合**展示。
|
|
989
1113
|
|
|
990
|
-
### 2.
|
|
1114
|
+
### 2. 解析条目
|
|
991
1115
|
|
|
992
|
-
|
|
993
|
-
-
|
|
994
|
-
-
|
|
995
|
-
-
|
|
996
|
-
-
|
|
997
|
-
- **锁定**:若有 \`🔒\` 标记,列出锁定的文件
|
|
1116
|
+
对每个用户提取:
|
|
1117
|
+
- **成员**:用户目录名(\`.openfeel/users/{username}/\`)
|
|
1118
|
+
- **主题**:\`dev_last.md\`「主题索引」中的显示主题名(附英文文件名 \`dev_last/{english-name}.md\`)
|
|
1119
|
+
- **摘要**:各主题下 ≤5 条核心摘要(≤100 字)
|
|
1120
|
+
- **阶段**:\`flow.json\` 中与该用户相关的阶段 \`phase\` / \`status\`
|
|
998
1121
|
|
|
999
1122
|
### 3. 查漏补缺
|
|
1000
1123
|
|
|
1001
|
-
- 对比
|
|
1124
|
+
- 对比 **\`flow.json\` active stages** 与各用户 \`dev_last\` 索引覆盖的 stage,检查是否存在**无任何用户索引覆盖的活跃阶段**
|
|
1002
1125
|
- 若有,标记为「未同步」
|
|
1003
1126
|
|
|
1004
1127
|
### 4. 格式化输出
|
|
1005
1128
|
|
|
1006
|
-
|
|
1129
|
+
按阶段/用户分组输出,格式:
|
|
1007
1130
|
|
|
1008
1131
|
\`\`\`
|
|
1009
1132
|
📊 项目协作进度
|
|
1010
1133
|
|
|
1011
|
-
🟢
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
@bob [db] 数据库迁移脚本编写
|
|
1015
|
-
🔒 migrations/v2.sql
|
|
1016
|
-
|
|
1017
|
-
🟡 阻塞(M 人)
|
|
1018
|
-
@charlie [api] 等待第三方 OAuth 审批
|
|
1134
|
+
🟢 {stage-id} [{phase}]
|
|
1135
|
+
{username}
|
|
1136
|
+
- {主题显示名}(\`dev_last/{english-name}.md\`):{摘要(≤100 字)}
|
|
1019
1137
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1138
|
+
🟡 阻塞
|
|
1139
|
+
{username} — {阻塞原因(若 dev_last 索引标注)}
|
|
1022
1140
|
|
|
1023
|
-
⚪
|
|
1024
|
-
|
|
1141
|
+
⚪ 未同步
|
|
1142
|
+
{username} — 尚无主题索引条目
|
|
1143
|
+
{stage-id} — 无用户索引覆盖
|
|
1025
1144
|
\`\`\`
|
|
1026
1145
|
|
|
1027
1146
|
### 5. 偏离告警
|
|
1028
1147
|
|
|
1029
|
-
|
|
1148
|
+
若同一 stage 在 \`flow.json\` 标记为活跃,但**所有用户索引均未覆盖**,输出告警:
|
|
1030
1149
|
|
|
1031
1150
|
\`\`\`
|
|
1032
|
-
⚠️
|
|
1151
|
+
⚠️ 阶段 {stage_id} 处于活跃状态但无用户索引覆盖,请确认存在负责成员
|
|
1033
1152
|
\`\`\`
|
|
1034
1153
|
|
|
1035
1154
|
## 输出
|
|
1036
1155
|
|
|
1037
1156
|
格式化后的 Markdown 进度摘要,不含文件修改。
|
|
1157
|
+
`,
|
|
1158
|
+
'openfeel-tool-usage': `---
|
|
1159
|
+
name: openfeel-tool-usage
|
|
1160
|
+
description: Agent 工具使用规范(todowrite/question/task/skill 四工具触发条件、使用要求、禁止行为 + 优先级表)。
|
|
1161
|
+
---
|
|
1162
|
+
|
|
1163
|
+
# Agent 工具使用规范
|
|
1164
|
+
|
|
1165
|
+
## 输入
|
|
1166
|
+
|
|
1167
|
+
无
|
|
1168
|
+
|
|
1169
|
+
## 执行步骤
|
|
1170
|
+
|
|
1171
|
+
### 1. todowrite — 任务列表管理
|
|
1172
|
+
触发条件(任一即用):多个独立步骤(多文件改动、需跨会话跟踪等) / 多任务下达 / 跨文件修改。使用要求:执行前创建、单条 in_progress、完成即标 completed、新步骤追加末尾。
|
|
1038
1173
|
|
|
1174
|
+
### 2. question — 向用户提问
|
|
1175
|
+
触发条件(任一必问):需求歧义 / 多个同等合理方案 / 不可逆后果 / 架构决策。使用要求:(Recommended) 标记、选项附后果、≤3 选项、高风险含"取消"。禁止:模糊时自行假设、多方案不选直接实施。
|
|
1176
|
+
|
|
1177
|
+
### 3. task — 子 Agent 调度
|
|
1178
|
+
触发条件:并行探索多代码区 / 复杂多步委托 general / 下游 Agent(经 Feel)。使用要求:并行一条消息多 task、prompt 含任务描述+期望返回、明确只读/可写。
|
|
1179
|
+
|
|
1180
|
+
### 4. skill — 技能加载
|
|
1181
|
+
触发条件:查阶段状态→openfeel-get-stage-status / 查知识库→openfeel-check-kb / 取 Bug→openfeel-get-bugs。使用要求:会话开始载 check-kb、处理阶段任务前载 get-stage-status、不凭记忆跳过。
|
|
1182
|
+
|
|
1183
|
+
### 5. 工具使用优先级
|
|
1184
|
+
| 场景 | 优先工具 | 禁止做法 |
|
|
1185
|
+
|------|---------|----------|
|
|
1186
|
+
| 多步骤任务 | todowrite | 凭记忆逐条执行 |
|
|
1187
|
+
| 需求不明确 | question | 自行假设后动手 |
|
|
1188
|
+
| 探索代码 | task(explore) | 手动逐个 grep/read |
|
|
1189
|
+
| 获取状态 | skill(openfeel-get-stage-status) | 凭记忆推断 |
|
|
1190
|
+
| 批量文件操作 | task(general) | 串行逐个处理 |
|
|
1039
1191
|
`,
|
|
1040
1192
|
'openfeel-update-stage-status': `---
|
|
1041
1193
|
name: openfeel-update-stage-status
|
|
@@ -1179,6 +1331,43 @@ description: 交互式流水线向导,供 Agent 在终端中逐步推进流水
|
|
|
1179
1331
|
向导推进结果:阶段 phase 变化(from → to),结束/退出提示
|
|
1180
1332
|
|
|
1181
1333
|
> 注:需交互式终端(TTY),非交互环境请改用 \`openfeel flow advance --stage <id> --to <phase>\`
|
|
1334
|
+
>
|
|
1335
|
+
> ⚠️ **本仓自举**:本仓(openfeel 源码仓库)开发/执行时请用 \`node bin/openfeel.js <cmd>\`;安装后使用 \`openfeel <cmd>\`。
|
|
1336
|
+
>
|
|
1337
|
+
> 静态命令/参数/phase/stageId 参考见 \`openfeel-cli-usage\` skill(本 skill 负责交互式执行推进)。
|
|
1338
|
+
`,
|
|
1339
|
+
'openfeel-workspace': `---
|
|
1340
|
+
name: openfeel-workspace
|
|
1341
|
+
description: 会话启动时检查并补齐 .openfeel/ 工作区目录结构与空文件的标准化操作步骤(mkdir 哪些目录、创建哪些空文件、读 .info.json 取用户名)。
|
|
1342
|
+
---
|
|
1343
|
+
|
|
1344
|
+
# 工作区启动自检
|
|
1345
|
+
|
|
1346
|
+
## 输入
|
|
1347
|
+
|
|
1348
|
+
无(自动按 .openfeel/.info.json 与 ~/.openfeel/update_infos.md 推断)
|
|
1349
|
+
|
|
1350
|
+
## 执行步骤
|
|
1351
|
+
|
|
1352
|
+
### 1. 读取用户名
|
|
1353
|
+
读 \`.openfeel/.info.json\` 的 \`user\` 字段;缺失则 \`git config user.name\`。
|
|
1354
|
+
|
|
1355
|
+
### 2. 检查公共域目录(缺失则 mkdir -p)
|
|
1356
|
+
\`.openfeel/dev/note/\`、\`.openfeel/dev/current_archive/\`、\`.openfeel/log/\`、\`.openfeel/code_review/\`、\`.openfeel/bugs/\`、\`.openfeel/plan/\`、\`.openfeel/kb/\`、\`.openfeel/tmp/\`
|
|
1357
|
+
|
|
1358
|
+
### 3. 检查公共域文件(缺失则创建空文件)
|
|
1359
|
+
\`.openfeel/dev/dev_core.md\`、\`.openfeel/dev/current.md\`、\`.openfeel/dev/decisions.md\`、\`.openfeel/kb/index.md\`
|
|
1360
|
+
|
|
1361
|
+
### 4. 检查私域目录(基于 {username})
|
|
1362
|
+
\`.openfeel/users/{username}/log/\`、\`note/\`、\`code_review/\`、\`bugs/\`、\`tmp/\`、\`dev_last/\`(主题记录目录;**主题文件名英文**,如 \`pending.md\`/\`decisions.md\`)
|
|
1363
|
+
|
|
1364
|
+
### 5. 检查私域文件
|
|
1365
|
+
\`.openfeel/users/{username}/dev_last.md\`(**索引**;同名目录 \`dev_last/\` 存主题文件,**文件名英文**)
|
|
1366
|
+
|
|
1367
|
+
### 6. 增量更新复核
|
|
1368
|
+
检查 \`~/.openfeel/update_infos.md\`,若存在未修复条目(追加/异常),提醒用户重启会话或委托 Feel 处理;本 Agent 不自行修改该文件。
|
|
1369
|
+
|
|
1370
|
+
> 目录结构语义、公共/私域分区、用户身份约束、路径自校验规则见全局 AGENTS.md(约束);本 skill 仅承载操作步骤。
|
|
1182
1371
|
`,
|
|
1183
1372
|
};
|
|
1184
1373
|
// AUTO-GENERATED-END: SKILL_DEFINITIONS
|
|
@@ -1196,7 +1385,7 @@ function composeManagedContent(content, type) {
|
|
|
1196
1385
|
* 分派依据 detectFileType;jsonc 不走本函数(调用方走深度合并)。
|
|
1197
1386
|
* target 供 update_infos 路径记录:全局资产 { isGlobal: true },项目资产 { isGlobal: false, projectRoot }。
|
|
1198
1387
|
*/
|
|
1199
|
-
function writeManagedFile(filePath, content, stateKey, updateState, target) {
|
|
1388
|
+
function writeManagedFile(filePath, content, stateKey, updateState, target, command) {
|
|
1200
1389
|
const type = detectFileType(filePath);
|
|
1201
1390
|
if (type === null || type === 'jsonc') {
|
|
1202
1391
|
// 无策略 / JSONC:不应到达(jsonc 由 updateProject 单独走深度合并)
|
|
@@ -1216,6 +1405,33 @@ function writeManagedFile(filePath, content, stateKey, updateState, target) {
|
|
|
1216
1405
|
const existingFm = existingSplit?.frontmatter ?? null;
|
|
1217
1406
|
const existingBody = existingSplit?.body ?? existing;
|
|
1218
1407
|
const region = parseRegion(existingBody, type);
|
|
1408
|
+
// 写前备份(B3):目标已存在且本次将实际改变内容时调用;备份失败 → 'skipped'(不覆盖)
|
|
1409
|
+
const runPreWriteBackup = () => {
|
|
1410
|
+
try {
|
|
1411
|
+
const backup = backupFileBeforeWrite(filePath, {
|
|
1412
|
+
command,
|
|
1413
|
+
projectPath: target.isGlobal ? undefined : target.projectRoot,
|
|
1414
|
+
});
|
|
1415
|
+
if (backup) {
|
|
1416
|
+
appendUpdateInfo('backed', target.isGlobal
|
|
1417
|
+
? { absolutePath: filePath, backupRel: backup.backupRel, command }
|
|
1418
|
+
: { projectRoot: target.projectRoot, relativePath: stateKey, backupRel: backup.backupRel, command });
|
|
1419
|
+
notifyBackupIfTTY(backup.backupRel); // B6:仅 TTY
|
|
1420
|
+
}
|
|
1421
|
+
return 'ok';
|
|
1422
|
+
}
|
|
1423
|
+
catch (err) {
|
|
1424
|
+
if (err instanceof BackupError) {
|
|
1425
|
+
// 备份失败 → 记可区分异常(REV-004)并跳过该文件写入,返回既有 'skipped'
|
|
1426
|
+
appendUpdateInfo('anomaly', target.isGlobal
|
|
1427
|
+
? { absolutePath: filePath, note: 'backup_failed' }
|
|
1428
|
+
: { projectRoot: target.projectRoot, relativePath: stateKey, note: 'backup_failed' });
|
|
1429
|
+
console.warn(t('update.fileWriteSkipTmpl', getCliLang(target.projectRoot ?? process.cwd()), { err: err.message }));
|
|
1430
|
+
return 'skipped';
|
|
1431
|
+
}
|
|
1432
|
+
throw err;
|
|
1433
|
+
}
|
|
1434
|
+
};
|
|
1219
1435
|
if (region.status === 'ok') {
|
|
1220
1436
|
// 含标记:frontmatter 合并 + 正文区内替换,全文比对(REV-901 skip 判定)
|
|
1221
1437
|
// incoming 无 frontmatter(如 core.md/AGENTS.md 模板)时保留用户已有 frontmatter,避免写入丢失(REV-1101)
|
|
@@ -1225,6 +1441,9 @@ function writeManagedFile(filePath, content, stateKey, updateState, target) {
|
|
|
1225
1441
|
if (newContent === normalize(existing)) {
|
|
1226
1442
|
return 'skipped';
|
|
1227
1443
|
}
|
|
1444
|
+
if (runPreWriteBackup() === 'skipped') {
|
|
1445
|
+
return 'skipped';
|
|
1446
|
+
}
|
|
1228
1447
|
atomicWriteFileSync(filePath, newContent);
|
|
1229
1448
|
return 'updated';
|
|
1230
1449
|
}
|
|
@@ -1233,12 +1452,18 @@ function writeManagedFile(filePath, content, stateKey, updateState, target) {
|
|
|
1233
1452
|
const fileState = updateState?.files[stateKey];
|
|
1234
1453
|
if (fileState && hashContent(existing) === fileState.hash) {
|
|
1235
1454
|
// adopt:存量框架文件未改 → 直接写带标记的新框架内容(等价 created 记为 updated)
|
|
1455
|
+
if (runPreWriteBackup() === 'skipped') {
|
|
1456
|
+
return 'skipped';
|
|
1457
|
+
}
|
|
1236
1458
|
atomicWriteFileSync(filePath, composeManagedContent(content, type));
|
|
1237
1459
|
return 'updated';
|
|
1238
1460
|
}
|
|
1239
1461
|
// 追加受管区(标记包裹正文,不触碰 frontmatter),并记录待会话启动复核
|
|
1240
1462
|
const appendedRegion = wrapRegion(incomingBody, type);
|
|
1241
1463
|
const sep = existing.endsWith('\n') ? '' : '\n';
|
|
1464
|
+
if (runPreWriteBackup() === 'skipped') {
|
|
1465
|
+
return 'skipped';
|
|
1466
|
+
}
|
|
1242
1467
|
atomicWriteFileSync(filePath, `${existing}${sep}${appendedRegion}`);
|
|
1243
1468
|
appendUpdateInfo('appended', target.isGlobal ? { absolutePath: filePath } : { projectRoot: target.projectRoot, relativePath: stateKey });
|
|
1244
1469
|
return 'appended';
|
|
@@ -1254,8 +1479,8 @@ function writeManagedFile(filePath, content, stateKey, updateState, target) {
|
|
|
1254
1479
|
* 即 stateKey = 绝对路径、target 为全局资产。
|
|
1255
1480
|
* @returns 动作分类,供调用方报告。
|
|
1256
1481
|
*/
|
|
1257
|
-
export function deployGlobalAsset(filePath, content, state) {
|
|
1258
|
-
return writeManagedFile(filePath, content, filePath, state, { isGlobal: true });
|
|
1482
|
+
export function deployGlobalAsset(filePath, content, state, command) {
|
|
1483
|
+
return writeManagedFile(filePath, content, filePath, state, { isGlobal: true }, command);
|
|
1259
1484
|
}
|
|
1260
1485
|
/** 将 writeManagedFile 的动作分发到对应结果数组 */
|
|
1261
1486
|
function pushAction(action, stateKey, created, updated, skipped, appended) {
|
|
@@ -1299,77 +1524,14 @@ export function updateProject(projectPath, selectedTools = ["opencode"], lang =
|
|
|
1299
1524
|
const legacyDirs = ['agents', 'skills', 'instructions']
|
|
1300
1525
|
.filter((d) => existsSync(resolve(projectPath, '.opencode', d)));
|
|
1301
1526
|
if (legacyDirs.length > 0) {
|
|
1302
|
-
console.warn(
|
|
1527
|
+
console.warn(t('update.legacyLayoutWarnTmpl', getCliLang(projectPath), { dirs: legacyDirs.join(',') }));
|
|
1303
1528
|
}
|
|
1304
1529
|
// 全局部署目标(D1)
|
|
1305
1530
|
const agentsDir = getGlobalAgentsDir();
|
|
1306
1531
|
const skillsDir = getGlobalSkillsDir();
|
|
1307
|
-
// 项目内旧布局 agents 目录:仅用于 AGENTS.md 首次部署判定,保持原语义(项目级)
|
|
1308
|
-
const projectLegacyAgentsDir = resolve(projectPath, '.opencode', 'agents');
|
|
1309
1532
|
// 确保全局目标目录存在
|
|
1310
1533
|
mkdirSync(agentsDir, { recursive: true });
|
|
1311
1534
|
mkdirSync(skillsDir, { recursive: true });
|
|
1312
|
-
// ── AGENTS.md 语言同步逻辑 ──
|
|
1313
|
-
let agentsDirHasContent = false;
|
|
1314
|
-
try {
|
|
1315
|
-
agentsDirHasContent = existsSync(projectLegacyAgentsDir) && readdirSync(projectLegacyAgentsDir).length > 0;
|
|
1316
|
-
}
|
|
1317
|
-
catch {
|
|
1318
|
-
agentsDirHasContent = false;
|
|
1319
|
-
}
|
|
1320
|
-
const agentsMdPath = resolve(projectPath, 'AGENTS.md');
|
|
1321
|
-
const agentsMdExists = existsSync(agentsMdPath);
|
|
1322
|
-
if (!agentsDirHasContent && !agentsMdExists) {
|
|
1323
|
-
// 情况 1:首次部署 → 使用全局默认语言部署 AGENTS.md(受管区标记包裹)
|
|
1324
|
-
const globalConfig = getGlobalConfig();
|
|
1325
|
-
const deployLang = lang ?? globalConfig.lang ?? 'zh-CN';
|
|
1326
|
-
const agentsMdContent = loadTemplate(deployLang, 'agents-md');
|
|
1327
|
-
atomicWriteFileSync(agentsMdPath, composeManagedContent(agentsMdContent, 'markdown'));
|
|
1328
|
-
created.push('AGENTS.md');
|
|
1329
|
-
}
|
|
1330
|
-
else if (!agentsMdExists && options?.lang) {
|
|
1331
|
-
// 情况 2b:已有 agents 目录但 AGENTS.md 被手动删除 + --lang 指定
|
|
1332
|
-
// → 用指定语言重新创建 AGENTS.md(受管区标记包裹)
|
|
1333
|
-
const agentsMdContent = loadTemplate(options.lang, 'agents-md');
|
|
1334
|
-
atomicWriteFileSync(agentsMdPath, composeManagedContent(agentsMdContent, 'markdown'));
|
|
1335
|
-
created.push('AGENTS.md');
|
|
1336
|
-
}
|
|
1337
|
-
else if (agentsMdExists && options?.lang) {
|
|
1338
|
-
// 情况 2:已有项目 + --lang 参数指定了语言
|
|
1339
|
-
const projectLang = getLang(projectPath);
|
|
1340
|
-
const requestedLang = options.lang;
|
|
1341
|
-
if (requestedLang !== projectLang) {
|
|
1342
|
-
// 语言不同 → 提示确认
|
|
1343
|
-
if (options?.force) {
|
|
1344
|
-
// --force → 跳过确认,直接覆盖(受管区标记包裹)
|
|
1345
|
-
const agentsMdContent = loadTemplate(requestedLang, 'agents-md');
|
|
1346
|
-
atomicWriteFileSync(agentsMdPath, composeManagedContent(agentsMdContent, 'markdown'));
|
|
1347
|
-
updated.push('AGENTS.md');
|
|
1348
|
-
}
|
|
1349
|
-
else if (options?.interactive) {
|
|
1350
|
-
// 交互模式 → 输出冲突警告,跳过 AGENTS.md,继续执行后续框架更新
|
|
1351
|
-
console.warn(`[update] AGENTS.md language mismatch: project=${projectLang}, requested=${requestedLang}. Skipped. Use --force to override.`);
|
|
1352
|
-
skipped.push('AGENTS.md (language conflict)');
|
|
1353
|
-
}
|
|
1354
|
-
else {
|
|
1355
|
-
// 非交互模式 → 输出警告,不覆盖
|
|
1356
|
-
console.warn(`[update] AGENTS.md language mismatch: project=${projectLang}, requested=${requestedLang}. Skipped. Use --force to override.`);
|
|
1357
|
-
skipped.push('AGENTS.md');
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
else {
|
|
1361
|
-
// 语言相同 → 走受管文件三态(含标记区内替换 / 无标记 hash 兜底 / 追加)
|
|
1362
|
-
const templateContent = loadTemplate(requestedLang, 'agents-md');
|
|
1363
|
-
const action = writeManagedFile(agentsMdPath, templateContent, 'AGENTS.md', state, { isGlobal: false, projectRoot: projectPath });
|
|
1364
|
-
pushAction(action, 'AGENTS.md', created, updated, skipped, appended);
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
else if (agentsMdExists) {
|
|
1368
|
-
// 情况 3:已有项目 + 无 --lang 参数 → 走受管文件三态
|
|
1369
|
-
const templateContent = loadTemplate(lang, 'agents-md');
|
|
1370
|
-
const action = writeManagedFile(agentsMdPath, templateContent, 'AGENTS.md', state, { isGlobal: false, projectRoot: projectPath });
|
|
1371
|
-
pushAction(action, 'AGENTS.md', created, updated, skipped, appended);
|
|
1372
|
-
}
|
|
1373
1535
|
// 记录项目语言映射到全局配置
|
|
1374
1536
|
// 使用 options?.lang(用户通过 --lang 显式指定的语言)优先,
|
|
1375
1537
|
// 回退到 lang(getCliLang 三级回退结果)。确保记录的语言与实际部署语言一致。
|
|
@@ -1381,18 +1543,26 @@ export function updateProject(projectPath, selectedTools = ["opencode"], lang =
|
|
|
1381
1543
|
}
|
|
1382
1544
|
// 全局 state 可写实例:首次 null → 降级重建(后续全量写入全局资产,防静默不部署)
|
|
1383
1545
|
const newGlobalState = globalState ?? createGlobalUpdateState({});
|
|
1384
|
-
//
|
|
1385
|
-
|
|
1386
|
-
|
|
1546
|
+
// v1.1.1:存量全局 state 的 core.md key → 全局 AGENTS.md key(一次性重映射)
|
|
1547
|
+
// 说明:在 newGlobalState 创建后执行(newGlobalState = globalState ?? createGlobalUpdateState({});
|
|
1548
|
+
// 非 null 时二者同引用,故直接改 globalState 即改 newGlobalState)。
|
|
1549
|
+
const staleCoreKey = getGlobalCoreMdPath();
|
|
1550
|
+
if (globalState && globalState.files[staleCoreKey] && !globalState.files[getGlobalAgentsMdPath()]) {
|
|
1551
|
+
globalState.files[getGlobalAgentsMdPath()] = globalState.files[staleCoreKey];
|
|
1552
|
+
delete globalState.files[staleCoreKey];
|
|
1553
|
+
}
|
|
1554
|
+
// 0. 生成全局框架约束 AGENTS.md(~/.config/opencode/AGENTS.md)
|
|
1555
|
+
const agentsMdPath = getGlobalAgentsMdPath();
|
|
1556
|
+
const agentsMdContent = loadTemplate(lang, 'agents-md');
|
|
1387
1557
|
{
|
|
1388
|
-
const action = deployGlobalAsset(
|
|
1389
|
-
pushAction(action,
|
|
1558
|
+
const action = deployGlobalAsset(agentsMdPath, agentsMdContent, newGlobalState, 'update');
|
|
1559
|
+
pushAction(action, agentsMdPath, created, updated, skipped, appended);
|
|
1390
1560
|
}
|
|
1391
1561
|
// 1. 生成 Agent 定义文件到全局 agents 目录(绝对路径作 state key)
|
|
1392
1562
|
for (const name of listAgentIds(lang)) {
|
|
1393
1563
|
const content = loadAgentTemplate(lang, name);
|
|
1394
1564
|
const filePath = join(agentsDir, `${name}.md`);
|
|
1395
|
-
const action = deployGlobalAsset(filePath, content, newGlobalState);
|
|
1565
|
+
const action = deployGlobalAsset(filePath, content, newGlobalState, 'update');
|
|
1396
1566
|
pushAction(action, filePath, created, updated, skipped, appended);
|
|
1397
1567
|
}
|
|
1398
1568
|
// 2. 生成 Skill 定义文件到全局 skills 目录(每个 Skill 一个子目录,包含 SKILL.md)
|
|
@@ -1400,27 +1570,51 @@ export function updateProject(projectPath, selectedTools = ["opencode"], lang =
|
|
|
1400
1570
|
const skillSubDir = join(skillsDir, name);
|
|
1401
1571
|
mkdirSync(skillSubDir, { recursive: true });
|
|
1402
1572
|
const filePath = join(skillSubDir, 'SKILL.md');
|
|
1403
|
-
const action = deployGlobalAsset(filePath, content, newGlobalState);
|
|
1573
|
+
const action = deployGlobalAsset(filePath, content, newGlobalState, 'update');
|
|
1404
1574
|
pushAction(action, filePath, created, updated, skipped, appended);
|
|
1405
1575
|
}
|
|
1406
1576
|
// 3. 全局 opencode.jsonc:深度合并(保留用户字段),加全局锁 + 原子写
|
|
1407
1577
|
const globalJsoncPath = getGlobalOpencodeJsoncPath();
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
const
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
merged = mergeGlobalOpencodeJsonc(current);
|
|
1578
|
+
try {
|
|
1579
|
+
// 写前备份(B3):备份须在 jsonc 锁之外(之前)完成,避免 backup 锁与 jsonc 锁嵌套
|
|
1580
|
+
const globalJsoncBackup = backupFileBeforeWrite(globalJsoncPath, { command: 'update' });
|
|
1581
|
+
if (globalJsoncBackup) {
|
|
1582
|
+
appendUpdateInfo('backed', { absolutePath: globalJsoncPath, backupRel: globalJsoncBackup.backupRel, command: 'update' });
|
|
1583
|
+
notifyBackupIfTTY(globalJsoncBackup.backupRel);
|
|
1415
1584
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1585
|
+
// REV-1805:read-merge-write 全部置于锁内,消除与 model-config 锁内读写的 TOCTOU 竞态
|
|
1586
|
+
const globalJsoncNew = withFileLock(globalLockPath('global-opencode-jsonc'), () => {
|
|
1587
|
+
// 安全读取(T50):目录占位/不可读 → null,跳过合并与写入,按空配置继续
|
|
1588
|
+
const current = readJsoncFile(globalJsoncPath);
|
|
1589
|
+
if (current === null) {
|
|
1590
|
+
return null;
|
|
1591
|
+
}
|
|
1592
|
+
let merged;
|
|
1593
|
+
// 合并可能因块注释 parse 失败抛异常(parseJsonc 边界):降级为「跳过合并、保留原文件」并告警
|
|
1594
|
+
try {
|
|
1595
|
+
merged = mergeGlobalOpencodeJsonc(current);
|
|
1596
|
+
}
|
|
1597
|
+
catch (err) {
|
|
1598
|
+
console.warn(t('update.globalJsoncParseSkipTmpl', getCliLang(projectPath), { err: err.message }));
|
|
1599
|
+
merged = current;
|
|
1600
|
+
}
|
|
1601
|
+
atomicWriteFileSync(globalJsoncPath, merged);
|
|
1602
|
+
return merged;
|
|
1603
|
+
});
|
|
1604
|
+
if (globalJsoncNew !== null) {
|
|
1605
|
+
updateFileHash(newGlobalState, globalJsoncPath, globalJsoncNew);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
catch (err) {
|
|
1609
|
+
if (err instanceof BackupError) {
|
|
1610
|
+
// 备份失败(REV-011-A):跳过全局 jsonc 写入、记 anomaly、继续其余步骤(对齐 B3 幂等语义)
|
|
1611
|
+
appendUpdateInfo('anomaly', { absolutePath: globalJsoncPath, note: 'backup_failed' });
|
|
1612
|
+
console.warn(t('update.globalJsoncWriteSkipTmpl', getCliLang(projectPath), { err: err.message }));
|
|
1419
1613
|
}
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1614
|
+
else {
|
|
1615
|
+
throw err; // 非备份错误照旧上抛(锁超时等不回退)
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1424
1618
|
// 4. 项目 opencode.jsonc:不存在则写最小 { $schema };已存在则保留(旧非法字段清理属 stage-39)
|
|
1425
1619
|
const projectJsoncPath = resolve(projectPath, 'opencode.jsonc');
|
|
1426
1620
|
if (!existsSync(projectJsoncPath)) {
|
|
@@ -1520,17 +1714,16 @@ function writeConflictFile(projectPath, relativePath, currentContent, incomingCo
|
|
|
1520
1714
|
}
|
|
1521
1715
|
/**
|
|
1522
1716
|
* 根据路径获取 incoming(openfeel 期望写入)内容
|
|
1523
|
-
* relativePath 现为全局绝对路径(框架资产)或项目相对路径(
|
|
1524
|
-
* -
|
|
1717
|
+
* relativePath 现为全局绝对路径(框架资产)或项目相对路径(opencode.jsonc):
|
|
1718
|
+
* - getGlobalAgentsMdPath() → loadTemplate(lang, 'agents-md')
|
|
1525
1719
|
* - getGlobalAgentsDir()/{name}.md → loadAgentTemplate(lang, name)
|
|
1526
1720
|
* - getGlobalSkillsDir()/{name}/SKILL.md → SKILL_DEFINITIONS[name]
|
|
1527
1721
|
* - opencode.jsonc → buildProjectOpencodeJsoncObj()
|
|
1528
|
-
* - AGENTS.md → loadTemplate(lang, 'agents-md')
|
|
1529
1722
|
*/
|
|
1530
1723
|
function getIncomingContent(relativePath, lang) {
|
|
1531
|
-
// 全局框架约束
|
|
1532
|
-
if (relativePath ===
|
|
1533
|
-
return loadTemplate(lang, '
|
|
1724
|
+
// 全局框架约束 AGENTS.md(v1.1.1:约束唯一权威)
|
|
1725
|
+
if (relativePath === getGlobalAgentsMdPath()) {
|
|
1726
|
+
return loadTemplate(lang, 'agents-md');
|
|
1534
1727
|
}
|
|
1535
1728
|
// 全局 agent:位于 getGlobalAgentsDir() 下的 {name}.md
|
|
1536
1729
|
if (relativePath.startsWith(getGlobalAgentsDir())) {
|
|
@@ -1546,10 +1739,6 @@ function getIncomingContent(relativePath, lang) {
|
|
|
1546
1739
|
if (relativePath === 'opencode.jsonc') {
|
|
1547
1740
|
return JSON.stringify(buildProjectOpencodeJsoncObj(), null, 2) + '\n';
|
|
1548
1741
|
}
|
|
1549
|
-
// 项目 AGENTS.md
|
|
1550
|
-
if (relativePath === 'AGENTS.md') {
|
|
1551
|
-
return loadTemplate(lang, 'agents-md');
|
|
1552
|
-
}
|
|
1553
1742
|
// 未知路径 → 空内容(不应发生)
|
|
1554
1743
|
return '';
|
|
1555
1744
|
}
|