Skip to content

Commit 00cdd6d

Browse files
committed
feat: enforce backup boundaries and add gmn transactional backup
1 parent 0af541a commit 00cdd6d

23 files changed

+806
-144
lines changed

docs/analysis/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# 备份分析文档索引
2+
3+
本目录汇总了本次关于“ccman 全命令备份”的分析结果。
4+
5+
## 文档列表
6+
7+
1. [备份现状全量盘点](./备份现状全量盘点.md)
8+
2. [更安全备份方案](./更安全备份方案.md)
9+
3. [备份覆盖复测报告](./备份覆盖复测报告.md)
10+
4. [备份策略最终收敛](./备份策略最终收敛.md)
11+
5. [命令级备份清单](./命令级备份清单.md)
12+
13+
## 建议阅读顺序
14+
15+
1. 先看“现状全量盘点”,确认当前缺口。
16+
2. 再看“更安全备份方案”,按 Phase 1/2/3 逐步落地。
17+
3. 最后看“备份策略最终收敛”,对齐最终执行边界。
18+
4. 需要核对具体命令时,查“命令级备份清单”。
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# 命令级备份清单(2026-02-27)
2+
3+
## 判定口径
4+
5+
- 会备份:命令执行前会创建 `.bak``.backup.<timestamp>` 备份文件。
6+
- 不会备份:命令会写文件,但不会先创建备份(通常使用原子写入)。
7+
- 只读:命令不写文件。
8+
9+
## ccman CLI
10+
11+
### 顶层命令
12+
13+
| 命令 | 备份结论 | 说明 |
14+
| --- | --- | --- |
15+
| `ccman`(无参数) | 只读 | 进入交互菜单,本身不写文件。 |
16+
| `ccman cx` / `ccman cc` / `ccman gm` / `ccman oc` / `ccman openclaw` / `ccman sync`(无子命令) | 只读 | 进入对应交互菜单,本身不写文件。 |
17+
| `ccman mcp`(无子命令) | 只读 | 仅展示帮助。 |
18+
19+
### Codex(`ccman cx`
20+
21+
| 命令 | 备份结论 | 说明 |
22+
| --- | --- | --- |
23+
| `ccman cx add` | 不会备份 |`.ccman/codex.json`;若选择立即启用,会继续写 `~/.codex/*`,均不备份。 |
24+
| `ccman cx edit` | 不会备份 |`.ccman/codex.json`;若编辑的是当前 provider,会写 `~/.codex/*`,不备份。 |
25+
| `ccman cx remove` | 不会备份 | 仅写 `.ccman/codex.json`|
26+
| `ccman cx clone` | 不会备份 | 仅写 `.ccman/codex.json`|
27+
| `ccman cx use` | 不会备份 |`.ccman/codex.json` + `~/.codex/*`(switch/use 路径不备份)。 |
28+
| `ccman cx list` / `ccman cx ls` | 只读 | 不写文件。 |
29+
| `ccman cx current` | 只读 | 不写文件。 |
30+
31+
### Claude(`ccman cc`
32+
33+
| 命令 | 备份结论 | 说明 |
34+
| --- | --- | --- |
35+
| `ccman cc add` | 不会备份 |`.ccman/claude.json`;若选择立即启用,会写 `~/.claude/settings.json`,不备份。 |
36+
| `ccman cc edit` | 不会备份 |`.ccman/claude.json`;当前 provider 会同步 `~/.claude/settings.json`,不备份。 |
37+
| `ccman cc remove` | 不会备份 | 仅写 `.ccman/claude.json`|
38+
| `ccman cc clone` | 不会备份 | 仅写 `.ccman/claude.json`|
39+
| `ccman cc use` | 不会备份 |`.ccman/claude.json` + `~/.claude/settings.json`(switch/use 不备份)。 |
40+
| `ccman cc list` / `ccman cc ls` | 只读 | 不写文件。 |
41+
| `ccman cc current` | 只读 | 不写文件。 |
42+
| `ccman cc clean:analyze` | 只读 | 仅读取并分析 `~/.claude.json`|
43+
| `ccman cc clean` | 会备份 | 先备份 `~/.claude.json`,再执行清理。 |
44+
45+
### Gemini(`ccman gm`
46+
47+
| 命令 | 备份结论 | 说明 |
48+
| --- | --- | --- |
49+
| `ccman gm add` | 不会备份 |`.ccman/gemini.json`;若立即启用,会写 `~/.gemini/settings.json``.env`,不备份。 |
50+
| `ccman gm edit` | 不会备份 |`.ccman/gemini.json`;当前 provider 会同步 `~/.gemini/*`,不备份。 |
51+
| `ccman gm remove` / `ccman gm rm` | 不会备份 | 仅写 `.ccman/gemini.json`|
52+
| `ccman gm clone` | 不会备份 | 仅写 `.ccman/gemini.json`|
53+
| `ccman gm use` | 不会备份 |`.ccman/gemini.json` + `~/.gemini/*`(switch/use 不备份)。 |
54+
| `ccman gm list` / `ccman gm ls` | 只读 | 不写文件。 |
55+
| `ccman gm current` | 只读 | 不写文件。 |
56+
57+
### OpenCode(`ccman oc`
58+
59+
| 命令 | 备份结论 | 说明 |
60+
| --- | --- | --- |
61+
| `ccman oc add` | 不会备份 |`.ccman/opencode.json`;若立即启用,会写 `~/.config/opencode/opencode.json`,不备份。 |
62+
| `ccman oc edit` | 不会备份 |`.ccman/opencode.json`;当前 provider 会同步 OpenCode 配置,不备份。 |
63+
| `ccman oc remove` / `ccman oc rm` | 不会备份 | 仅写 `.ccman/opencode.json`|
64+
| `ccman oc clone` | 不会备份 | 仅写 `.ccman/opencode.json`|
65+
| `ccman oc use` | 不会备份 |`.ccman/opencode.json` + OpenCode 配置(switch/use 不备份)。 |
66+
| `ccman oc list` / `ccman oc ls` | 只读 | 不写文件。 |
67+
| `ccman oc current` | 只读 | 不写文件。 |
68+
69+
### OpenClaw(`ccman openclaw` / `ccman ow`
70+
71+
| 命令 | 备份结论 | 说明 |
72+
| --- | --- | --- |
73+
| `ccman openclaw add` | 不会备份 |`.ccman/openclaw.json`;若立即启用,会写 `~/.openclaw/*`,不备份。 |
74+
| `ccman openclaw edit` | 不会备份 |`.ccman/openclaw.json`;当前 provider 会同步 `~/.openclaw/*`,不备份。 |
75+
| `ccman openclaw remove` / `ccman openclaw rm` | 不会备份 | 仅写 `.ccman/openclaw.json`|
76+
| `ccman openclaw clone` | 不会备份 | 仅写 `.ccman/openclaw.json`|
77+
| `ccman openclaw use` | 不会备份 |`.ccman/openclaw.json` + `~/.openclaw/*`(switch/use 不备份)。 |
78+
| `ccman openclaw list` / `ccman openclaw ls` | 只读 | 不写文件。 |
79+
| `ccman openclaw current` | 只读 | 不写文件。 |
80+
81+
### MCP(`ccman mcp`
82+
83+
| 命令 | 备份结论 | 说明 |
84+
| --- | --- | --- |
85+
| `ccman mcp add` | 不会备份 |`.ccman/mcp.json` 并自动同步应用配置(默认 Claude),均不备份。 |
86+
| `ccman mcp edit` | 不会备份 |`.ccman/mcp.json` 并自动同步应用配置,不备份。 |
87+
| `ccman mcp remove` / `ccman mcp rm` | 不会备份 |`.ccman/mcp.json` 并自动同步应用配置,不备份。 |
88+
| `ccman mcp list` / `ccman mcp ls` | 只读 | 不写文件。 |
89+
90+
### 同步、导入导出与 GMN(顶层命令)
91+
92+
| 命令 | 备份结论 | 说明 |
93+
| --- | --- | --- |
94+
| `ccman sync config` | 不会备份 |`~/.ccman/config.json`(同步配置)但不备份。 |
95+
| `ccman sync test` | 只读 | 仅测试连接;若用户选择先配置,会转入 `sync config`(不备份)。 |
96+
| `ccman sync status` | 只读 | 不写文件。 |
97+
| `ccman sync upload` | 不会备份 | 上传云端并更新 `lastSync`(写 `~/.ccman/config.json`),不备份。 |
98+
| `ccman sync download` | 会备份 | 覆盖本地前备份 `.ccman/*.json``.backup.<ts>`),失败可回滚。 |
99+
| `ccman sync merge` | 会备份 | 有变化时先备份 `.ccman/*.json``.backup.<ts>`)再合并。 |
100+
| `ccman export <dir>` | 会备份 | 若目标目录同名文件存在,会先备份目标文件(`.bak`,fail-closed)。 |
101+
| `ccman import <dir>` | 会备份 | 覆盖前备份本地 `.ccman/*.json``.backup.<ts>`,失败回滚)。 |
102+
| `ccman gmn [apiKey] ...` | 不会备份 | 本质是 add/edit + switch 流程;`.ccman` 与外部 switch/use 路径均不备份。 |
103+
104+
## desktop(IPC 写入接口)
105+
106+
> 下面仅列写入类 IPC;读取类接口略。
107+
108+
| IPC 名称 | 备份结论 | 说明 |
109+
| --- | --- | --- |
110+
| `write-config-files` | 会备份 | 手动改写外部工具配置前会 `.bak`,备份失败立即中止(fail-closed)。 |
111+
| `write-ccman-config-files` | 不会备份 | 改写 `.ccman/*.json` 不做备份(仍保留失败回滚)。 |
112+
| `codex:*` / `claude:*` / `gemini:*` / `opencode:*` / `openclaw:*` 写入类(`add/edit/remove/clone/switch` + `preset add/edit/remove`| 不会备份 | 走 tool-manager 与 writer,符合“.ccman + switch/use 不备份”。 |
113+
| `mcp:add-server` / `mcp:edit-server` / `mcp:clone-server` / `mcp:remove-server` / `mcp:toggle-app` | 不会备份 | `.ccman/mcp.json` 与应用同步写入均不备份。 |
114+
| `sync:save-config` | 不会备份 |`~/.ccman/config.json` 不备份。 |
115+
| `sync:upload-to-cloud` | 不会备份 | 更新 `lastSync` 时写 `~/.ccman/config.json`,不备份。 |
116+
| `sync:download-from-cloud` | 会备份 | 覆盖本地 `.ccman/*.json` 前会做 `.backup.<ts>`|
117+
| `sync:merge-sync` | 会备份 | 合并前会备份 `.ccman/*.json`(有变更时)。 |
118+
| `importexport:export` | 会备份 | 导出到目标目录时,若目标已存在会先 `.bak`|
119+
| `importexport:import` | 会备份 | 导入覆盖前备份本地 `.ccman/*.json`,失败回滚。 |
120+
| `clean:run` / `clean:delete-project` / `clean:delete-cache` / `clean:delete-history-entry` / `clean:clear-project-history` | 会备份 | 破坏性清理流程均先备份 `~/.claude.json`|
121+
| `migrate-config` | 会备份 | 迁移旧配置时会生成 `.bak` 迁移备份。 |
122+
123+
## aicoding(`npx @2ue/aicoding` / `aicoding`
124+
125+
| 命令 | 备份结论 | 说明 |
126+
| --- | --- | --- |
127+
| `aicoding [apiKey] [--overwrite] [-p/--platform ...] [--openai-base-url ...]` | 会备份 | 写入已存在目标文件前会先 `.bak`;任一备份失败会中止该工具后续写入(fail-closed)。 |
128+
129+
### aicoding 具体备份目标
130+
131+
- `codex``~/.codex/config.toml``~/.codex/auth.json`
132+
- `opencode``~/.config/opencode/opencode.json`
133+
- `openclaw``~/.openclaw/openclaw.json``~/.openclaw/agents/main/agent/models.json`
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# ccman 备份现状全量盘点(CLI / Desktop / ai-coding)
2+
3+
## 1. 目标与范围
4+
5+
本文档落地当前项目中“会写入配置文件”的所有主要链路,判断是否具备“写前备份”,并明确缺口位置。
6+
7+
范围包含:
8+
- `packages/core`
9+
- `packages/cli`
10+
- `packages/desktop`
11+
- `packages/aicoding`
12+
13+
## 2. 总体结论
14+
15+
当前项目还未实现“所有写操作都先落盘备份”。
16+
17+
已覆盖备份的核心场景:
18+
- Codex 官方配置写入(`config.toml``auth.json`
19+
- Sync 下载/合并(备份 `~/.ccman/*.json`
20+
- Import 导入(备份被覆盖的 `~/.ccman/*.json`
21+
- Claude clean 的部分操作(整体验证清理、删项目、删缓存)
22+
23+
主要缺口:
24+
- ToolManager 对 `~/.ccman/*.json` 的增删改切换均无备份
25+
- Claude/Gemini/OpenCode/OpenClaw/MCP writer 写官方配置前无备份
26+
- Desktop 的“配置编辑器写入”仅做内存回滚,不生成备份文件
27+
- `clean:delete-history-entry``clean:clear-project-history` 不备份
28+
- ai-coding 仅 Codex 有备份,OpenCode/OpenClaw 无备份
29+
- sync 配置 `~/.ccman/config.json` 写入无备份
30+
31+
## 3. 已有备份能力(现状)
32+
33+
### 3.1 Codex writer(已备份)
34+
35+
- 备份 `config.toml``packages/core/src/writers/codex.ts:214`
36+
- 备份 `auth.json``packages/core/src/writers/codex.ts:258`
37+
38+
说明:当前实现是“备份失败也继续写”。若要求“必须有备份”,此处仍需收紧策略。
39+
40+
### 3.2 Sync download/merge(已备份)
41+
42+
- download 备份:`packages/core/src/sync/sync-v2.ts:195`
43+
- merge 备份:`packages/core/src/sync/sync-v2.ts:336`
44+
- 备份实现:`packages/core/src/sync/merge.ts:21`
45+
46+
### 3.3 Import(已备份)
47+
48+
- 导入前备份:`packages/core/src/export.ts:184`
49+
50+
### 3.4 Claude clean 的部分操作(已备份)
51+
52+
- 统一清理:`packages/core/src/claude-clean.ts:154`
53+
- 删除项目:`packages/core/src/claude-clean.ts:335`
54+
- 删除缓存:`packages/core/src/claude-clean.ts:368`
55+
56+
## 4. 备份缺口清单(按优先级)
57+
58+
## P0(高风险,优先修)
59+
60+
1. ToolManager 全写操作无备份(影响 CLI + Desktop 主流程)
61+
- 保存入口:`packages/core/src/tool-manager.ts:202`
62+
- 具体写入调用:`...:268`, `...:311`, `...:362`, `...:390`, `...:428`, `...:464`, `...:532`, `...:556`
63+
64+
2. 官方配置 writer 普遍无备份(除 Codex)
65+
- Claude:`packages/core/src/writers/claude.ts:134`
66+
- Gemini:`packages/core/src/writers/gemini.ts:214`, `...:149`
67+
- OpenCode:`packages/core/src/writers/opencode.ts:229`
68+
- OpenClaw:`packages/core/src/writers/openclaw.ts:326`, `...:327`
69+
- MCP:`packages/core/src/writers/mcp.ts:251`, `...:99`
70+
71+
3. Desktop 配置编辑器写入不落盘备份
72+
- `write-config-files``packages/desktop/src/main/index.ts:864`
73+
- `write-ccman-config-files``packages/desktop/src/main/index.ts:970`
74+
75+
## P1(中风险)
76+
77+
4. `~/.ccman/config.json`(sync 配置)无备份
78+
- `packages/core/src/config.ts:56`
79+
80+
5. clean 的两类操作明确“不备份”
81+
- `deleteHistoryEntry``packages/core/src/claude-clean.ts:406`
82+
- `clearProjectHistory``packages/core/src/claude-clean.ts:434`
83+
84+
6. ai-coding 对 OpenCode/OpenClaw 写入前不备份
85+
- OpenCode 写入:`packages/aicoding/bin/aicoding.js:303`
86+
- OpenClaw 写入:`packages/aicoding/bin/aicoding.js:380`, `...:381`
87+
88+
## P2(一致性与可运维)
89+
90+
7. Import 支持文件集不完整
91+
- 当前仅:`codex.json`, `claude.json`, `openclaw.json`
92+
- 位置:`packages/core/src/export.ts:17`
93+
- 若目标是“所有命令一致备份/恢复”,建议纳入 `gemini.json`, `opencode.json`, `mcp.json`, `config.json`
94+
95+
## 5. 命令维度映射(是否应具备备份)
96+
97+
应具备“写前备份”的命令/动作:
98+
- `ccman cx/cc/gm/oc/openclaw add|edit|remove|clone|use`
99+
- `ccman mcp add|edit|remove` 以及 Desktop `mcp:toggle-app`
100+
- `ccman gmn`
101+
- `ccman import`
102+
- `ccman sync download|merge`
103+
- Desktop 里的“编辑配置文件并保存”
104+
- Desktop/CLI 的 clean 写操作(包括删除单条、清空项目)
105+
- `@2ue/aicoding` 的所有写动作(Codex/OpenCode/OpenClaw)
106+
107+
可不备份(只读):
108+
- `list/current/status/test/analyze/export/read-config`
109+
110+
## 6. 补充观察
111+
112+
- 当前 `writeJSON` 仅保证原子写,不等于“可恢复备份”:`packages/core/src/utils/file.ts:24`
113+
- 目前有两种备份命名:`.bak``.backup.<timestamp>`,策略不统一。
114+
- Desktop 编辑器的“回滚”只对本次进程有效,无法作为用户可追溯备份。
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# 备份策略最终收敛(2026-02-27)
2+
3+
## 最终规则
4+
5+
1. 仅在“改写用户外部工具配置”场景执行备份。
6+
2. `.ccman` 目录下配置写入默认不备份。
7+
3. 外部工具 `switch/use` 触发的写入默认不备份。
8+
4. 需要备份的场景采用 `fail-closed`
9+
- 备份失败时立即中止后续写入。
10+
- 抛出明确提示:`备份失败,已中止后续写入(...)`
11+
12+
## 覆盖矩阵
13+
14+
### 需要备份(保留)
15+
16+
- `desktop` 手动编辑外部工具配置:`write-config-files`
17+
- `aicoding` 写入外部工具配置(Codex/OpenCode/OpenClaw)
18+
- `core` 导出覆盖目标文件:`exportConfig`(目标文件已存在时)
19+
- `core` 导入/同步高风险流程:
20+
- `importConfig`
21+
- `sync` 下载/合并(`backupConfig` 路径)
22+
- `core` 破坏性清理流程:`claude-clean`
23+
24+
### 不需要备份(已去除)
25+
26+
- `.ccman` 常规配置写入:
27+
- `core/config.ts``saveConfig`
28+
- `tool-manager``saveConfig``codex.json/claude.json/...`
29+
- `mcp``saveMCPConfig`
30+
- `desktop``write-ccman-config-files`
31+
- 外部工具 `switch/use` 触发写入:
32+
- `writers/codex.ts`
33+
- `writers/claude.ts`
34+
- `writers/gemini.ts`
35+
- `writers/opencode.ts`
36+
- `writers/openclaw.ts`
37+
- `writers/mcp.ts`(写入 app 配置)
38+
39+
## 设计取舍
40+
41+
- 收益:避免“切换工具时频繁生成备份”的噪音与磁盘堆积。
42+
- 风险:`switch/use` 误写时缺少本地 `.bak` 快照。
43+
- 缓解:保留导入/同步/手动编辑/清理等高风险路径的备份与回滚。
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# 备份覆盖复测报告(第二轮)
2+
3+
日期:2026-02-27
4+
5+
## 1. 复测结果
6+
7+
已通过:
8+
- `pnpm --filter ./packages/core test`(39/39)
9+
- `pnpm --filter ./packages/cli build`
10+
- `node --check packages/aicoding/bin/aicoding.js`
11+
12+
未通过(仓库现有类型问题,与本次备份逻辑无关):
13+
- `pnpm --filter ./packages/core build`
14+
- `pnpm --filter ./packages/desktop type-check`
15+
16+
共同错误特征:`OPENCLAW` 在类型常量中缺失,导致编译报错。
17+
18+
## 2. 本轮已补齐的备份覆盖
19+
20+
1. fail-closed 统一错误提示
21+
- `packages/core/src/utils/file.ts` 新增 `backupFileOrThrow`
22+
23+
2. Core 主链路
24+
- ToolManager 保存前备份
25+
- Codex/Claude/Gemini/OpenCode/OpenClaw/MCP writer 写前备份
26+
- `ccman config.json`(sync 配置)写前备份
27+
- `claude-clean` 中单条删除/清空项目历史写前备份
28+
29+
3. Desktop
30+
- `write-config-files``write-ccman-config-files`:写前备份、备份失败中止、写入失败回滚
31+
- 写入改为 `tmp + rename` 原子落盘
32+
33+
4. ai-coding
34+
- OpenCode/OpenClaw 补齐写前备份
35+
- 统一 fail-closed 提示
36+
37+
5. backup 文件权限
38+
- `backupConfig`(sync merge)新增 `chmod 600`
39+
40+
## 3. 仍未完全覆盖备份的点位(最新)
41+
42+
### P1(建议后续补齐)
43+
44+
1. 迁移模块的写入策略仍不统一(`migrate.ts`
45+
- `migrateConfig``codex.json/claude.json` 写入为直接写;旧 `config.json` 在后续才重命名为 `.bak`
46+
- 若中途失败可能产生“部分新文件 + 旧文件未迁移”的中间态
47+
48+
2. `migrateV2ToV3``claude.json` 未走统一备份封装
49+
- 当前逻辑依赖“目标不存在”前置条件,风险较低,但风格不一致
50+
51+
### P2(策略一致性)
52+
53+
3. `export` 命令对目标目录属于“用户指定输出目录”
54+
- 本轮已补:目标文件存在时先备份再覆盖
55+
- 但与核心配置目录策略仍有命名差异(`.bak` vs `.backup.<ts>`
56+
57+
## 4. 功能正常性结论
58+
59+
在当前可执行的测试和构建范围内:
60+
- 核心行为正常(core 全测通过)
61+
- CLI 构建正常
62+
- ai-coding 脚本语法正常
63+
64+
阻塞“全量构建通过”的剩余问题是仓库原有类型定义不一致(OpenClaw 常量缺失),不是本次备份 fail-closed 改动引入。

0 commit comments

Comments
 (0)