@mcgrapeng/ccg 3.1.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ccg.md CHANGED
@@ -1,50 +1,67 @@
1
1
  ---
2
- description: Code-divergence detector. Run Codex+Gemini in parallel on a diff, then surface where they DISAGREE (high-signal) vs where they agree. Auto-picks risk-aware mode. Logs each review to ledger. See README.md.
2
+ description: Code Change Guardian multi-model review, AI commit gate, merge conflict resolution, and pre-push analysis.
3
3
  ---
4
4
 
5
- # CCG v3代码分歧检测器
5
+ # CCG v4Code Change Guardian
6
6
 
7
- **身份**:不是 review 工具,是**分歧检测器**。两个独立模型家族(Codex/Gemini)评审同一份 diff,Claude 综合时把"两边都说没事 / 两边都说有问题"压成低优先级,把"两边判断不一致的点"放到聚光灯下——这才是真正值得人类介入的地方。
8
-
9
- **默认行为**:无参数 → 抓 git diff → 风险打分 → 自动选 mode → 并行评审 → 输出 AGREEMENT / DIVERGENCE / BLINDSPOT 三段 → 落 ledger。
7
+ > **4-Stage Guardian**: Two independent AI model families guard every change across Review · Commit · Merge · Push—surfacing where they disagree, auto-filtering low-risk changes, and providing a pre-push quality gate before code leaves your machine.
10
8
 
11
9
  ## 三柱设计
12
10
 
13
11
  | 柱 | 解决的问题 | 实现 |
14
12
  |---|---|---|
15
- | Divergence Engine | 单源 review 看不到"自己看不到的盲区" | 同 prompt 双投喂 + Claude 综合时强调分歧 |
13
+ | Divergence Engine | 单源 review 看不到"自己看不到的盲区" | 同 prompt 投喂两个**不同厂商**模型,综合时强调分歧 |
16
14
  | Risk-Aware Routing | 用户不应该手选 cost/balanced/quality | `ccg_risk_score` 基于路径/内容/规模规则打分自动选 |
17
15
  | Review Ledger | 这次评审的判断下次没法复用 | 每次评审追加 JSONL,按路径可查历史 |
18
16
 
17
+ > **模型策略**:各阶段主力是 BAILIAN 平台**两个不同厂商**的顶级模型(厂商限定
18
+ > `qwen / glm / mimo / deepseek / kimi / minimax`)。`codex / gemini / claude`
19
+ > **仅在质量优先(`CCG_MODE=quality`)时启用**——此时 Stage 1 在三者中任选 2 个,
20
+ > 剩下一个作为 synthesizer。风险评分会自动路由:低/中风险走便宜的 Bailian 对,
21
+ > 高风险自动进入 quality 解锁 premium 三件套。
22
+
19
23
  ## 配置 (环境变量)
20
24
 
21
25
  | 变量 | 默认 | 说明 |
22
26
  |---|---|---|
23
27
  | `CCG_MODE` | `auto` | `auto` (按 risk score 决) / `cost` / `balanced` / `quality` |
24
- | `CCG_CODEX_MODEL` | | 显式 codex 模型(优先于 CCG_MODE) |
25
- | `CCG_GEMINI_MODEL` | | 显式 gemini 模型 |
28
+ | `CCG_PROVIDERS` | (按 mode) | Stage 1 provider 列表,最多 2 个。非 quality 默认两个不同厂商 Bailian;quality 默认 `codex gemini`。支持 `bailian:qwen-3.6 bailian:deepseek-v4` 语法 |
29
+ | `CCG_ALLOW_SAME_VENDOR` | `0` | `1` = 允许 Stage 1 两个 slot 同厂商(默认禁止,破坏 divergence) |
30
+ | `BAILIAN_API_KEY` | — | Bailian 主力模型必需 |
31
+ | `CCG_BAILIAN_MODEL` | — | 显式 bailian 模型(优先于 CCG_MODE) |
32
+ | `CCG_CODEX_MODEL` | — | 显式 codex 模型(仅 quality 生效) |
33
+ | `CCG_GEMINI_MODEL` | — | 显式 gemini 模型(仅 quality 生效) |
34
+ | `CCG_SYNTH_PROVIDER` | (按 mode) | 覆盖 synthesizer:quality 默认用三件套中未上场的那个(缺省 claude),非 quality 用 bailian |
35
+ | `CCG_RISK_LLM` | `0` | `1` = 用 LLM 做风险评分(需 BAILIAN_API_KEY);默认纯规则、确定性、不花钱 |
26
36
  | `CCG_CODEX_TIMEOUT` | `240` | Codex 超时秒数 |
27
37
  | `CCG_GEMINI_TIMEOUT` | `120` | Gemini 超时秒数 |
28
38
  | `CCG_NO_CACHE` | `0` | `1` = 跳过 24h prompt-hash 缓存 |
29
39
  | `CCG_CACHE_TTL_HOURS` | `24` | 缓存 TTL |
30
- | `CCG_MAX_PROMPT_KB` | `100` | 防止把整个 repo 塞进 prompt |
40
+ | `CCG_MAX_PROMPT_KB` | `100` | 防止把整个 repo 塞进 prompt(超过此值各 provider 直接拒绝) |
31
41
  | `CCG_KEEP_ARTIFACTS` | `0` | `1` = 保留临时文件 |
32
42
  | `CCG_LEDGER_LOG` | `$XDG_DATA_HOME/ccg/ledger.jsonl` | 评审历史落盘位置(fallback `~/.local/share/ccg/`,自动迁移老路径 `~/.ccg/`) |
43
+ | `CCG_NO_REPORT` | `0` | `1` = 跳过 `.ccg/reports/<sha>_<ts>.md` 持久化 |
44
+ | `CCG_REPORT_DIR` | `<repo>/.ccg/reports` | 改写报告目录(默认 git repo 根下的 `.ccg/reports/`) |
45
+ | `CCG_NO_HISTORY` | `0` | `1` = 跳过把过去同路径的评审摘要注入 prompt |
46
+ | `CCG_HISTORY_MAX` | `3` | 注入 prompt 的历史评审条数上限 |
33
47
 
34
48
  ### Mode → 默认模型映射
35
49
 
36
- | Mode | Codex | Gemini |
50
+ | Mode | Stage 1(主力,两个不同厂商) | Synthesizer |
37
51
  |---|---|---|
38
- | `cost` | gpt-5-nano | gemini-2.5-flash-lite |
39
- | `balanced` (默认) | gpt-5-mini | gemini-2.5-flash |
40
- | `quality` | gpt-5 | gemini-2.5-pro |
52
+ | `cost` | `qwen-3.5-haiku` + `deepseek-v4-lite`(Bailian) | bailian |
53
+ | `balanced` (默认) | `qwen-3.6` + `deepseek-v4`(Bailian) | bailian |
54
+ | `quality` | `codex` + `gemini`(三件套任选 2 | 剩下那个(缺省 `claude`) |
41
55
 
42
- > ⚠️ 第三方代理若不支持上述模型名,会 5xx/404。用 `CCG_CODEX_MODEL`/`CCG_GEMINI_MODEL` 显式指定代理实际支持的型号。
56
+ > ⚠️ codex/gemini/claude 仅在 `quality` 模式启用。非 quality 模式即使在
57
+ > `CCG_PROVIDERS` 里写了它们,也会被跳过。
58
+ > ⚠️ 第三方代理若不支持上述模型名,会 5xx/404。用 `CCG_*_MODEL` 显式指定代理实际支持的型号。
43
59
 
44
60
  ## 前置依赖
45
61
 
46
- - `npm i -g @openai/codex` `npm i -g @google/gemini-cli`
47
- - `export GEMINI_API_KEY=...` 放在 `~/.zshenv`(非交互 shell 也能读到)
62
+ - **Bailian(主力,非 quality 模式必需)**:`export BAILIAN_API_KEY=...`
63
+ - **premium(仅 quality 模式)**:`npm i -g @openai/codex`、`npm i -g @google/gemini-cli` + `export GEMINI_API_KEY=...`
64
+ - API key 放在 `~/.zshenv`(非交互 shell 也能读到)
48
65
 
49
66
  ## 执行协议(Claude 严格按以下步骤)
50
67
 
@@ -63,9 +80,10 @@ ccg_init
63
80
  source ~/.claude/commands/ccg.sh
64
81
  ccg_preflight
65
82
  ```
66
- - `CCG_PREFLIGHT_CODEX=missing` → 说明缺哪个 CLI + 安装命令
67
- - `CCG_PREFLIGHT_GEMINI=no-api-key` → 标注 Gemini 不可用,跳过
68
- - 两者都不可用Claude 独立回答
83
+ - `CCG_PREFLIGHT_BAILIAN=ok` → 主力可用(非 quality 模式用两个不同厂商 Bailian 模型)
84
+ - `CCG_PREFLIGHT_BAILIAN=no-api-key` → quality 模式不可用;提示设 `BAILIAN_API_KEY`
85
+ - `CCG_PREFLIGHT_CODEX` / `CCG_PREFLIGHT_GEMINI` **quality 模式**用得到(premium 三件套)
86
+ - 主力与 premium 都不可用 → Claude 独立回答
69
87
 
70
88
  ### 步骤 2. 确定任务输入(两种模式)
71
89
 
@@ -88,6 +106,22 @@ ccg_diff_capture "$CCG_DIR/diff.txt"
88
106
 
89
107
  **Claude 必须在最终输出中显示对比的是哪个 source,让用户知道评审的范围。**
90
108
 
109
+ ### 步骤 2.5. 历史评审注入(让 ledger 不再 write-only)
110
+
111
+ ```bash
112
+ CCG_DIR=<字面路径>
113
+ source ~/.claude/commands/ccg.sh
114
+ ccg_ledger_context "$CCG_DIR/diff.txt"
115
+ ```
116
+
117
+ 读 `CCG_HISTORY_*`:
118
+ - `CCG_HISTORY_OK=<n>_matches_<max>_max` + `CCG_HISTORY_FILE=<path>` → 文件已写入 `<CCG_DIR>/history.txt`,**步骤 4 写 prompt 时必须把这段内容拼到 diff 前面**
119
+ - `CCG_HISTORY_NONE=0_matches` → ledger 里没匹配(首次评审这些文件);正常情况,prompt 不带 history 块
120
+ - `CCG_HISTORY_SKIPPED=<no-ledger|no-diff|disabled|no-paths-in-diff>` → 跳过即可,不报错
121
+ - `CCG_HISTORY_FAIL=...` → 临时文件写不进;忽略并继续(这一步是增强信号,不是必需)
122
+
123
+ > 设计立场:L6 ledger 不再是日记本——每次评审都把"过去对同一文件的判断"作为先验喂给两个评审模型。recurring patterns、未解决的 fix-required 都进入第一现场。环境变量 `CCG_NO_HISTORY=1` 可关闭;`CCG_HISTORY_MAX` 改条数(默认 3)。
124
+
91
125
  ### 步骤 3. 风险打分 + 自动选 mode(仅 B 模式)
92
126
 
93
127
  ```bash
@@ -102,7 +136,20 @@ ccg_risk_score "$CCG_DIR/diff.txt" | tee "$CCG_DIR/risk.txt"
102
136
 
103
137
  > 设计立场:路径/内容/规模规则比 LLM 判断更**可解释、零成本、可改**。社区贡献者可以直接 PR 改权重。
104
138
 
105
- ### 步骤 4. 写 prompt(结构化输出协议)
139
+ ### 步骤 4. 选择两个评审模型 + 写 prompt(结构化输出协议)
140
+
141
+ **先按 `CCG_MODE` 选定两个 Stage 1 评审模型(必须是不同厂商):**
142
+
143
+ - **非 quality(cost / balanced)→ 两个不同厂商的 Bailian 主力模型**:
144
+
145
+ ```bash
146
+ CCG_DIR=<字面路径>
147
+ source ~/.claude/commands/ccg.sh
148
+ _ccg_resolve_bailian_pair "${CCG_MODE:-balanced}" # 输出两行,如:qwen-3.6 / deepseek-v4
149
+ ```
150
+ 第 1 行 = Reviewer A 的模型,第 2 行 = Reviewer B 的模型(厂商天然不同)。
151
+
152
+ - **quality → premium 三件套(codex / gemini / claude)任选 2 个**作为 Reviewer A/B,剩下那个留给步骤 7 的 synthesis(缺省 claude)。默认 A=codex、B=gemini。
106
153
 
107
154
  **核心改动**:要求两端都按下面格式输出(便于 Claude 后续做对齐和分歧检测)。
108
155
 
@@ -128,59 +175,81 @@ detail: <2-4 行解释,必须可独立读懂>
128
175
 
129
176
  不要寒暄,不要在外面加任何文字。如果没有发现问题,FINDINGS 段落留空,但格式仍要保留。
130
177
 
178
+ <如果 step 2.5 产出了 history.txt,把它的完整内容贴在这里,标题"=== PRIOR REVIEWS ===" 自带>
179
+
131
180
  待评审的 diff(source: <CCG_DIFF_SOURCE>):
132
181
 
133
182
  <贴入 diff 内容>
134
183
  ````
135
184
 
136
- 用 **Write tool**(不是 echo)写入:
137
- - `<CCG_DIR>/codex.prompt`
138
- - `<CCG_DIR>/gemini.prompt`
185
+ 用 **Write tool**(不是 echo)写入两份**完全相同**的 prompt(含 history 段——两个 reviewer 看相同的过往):
186
+ - `<CCG_DIR>/slot1.prompt`
187
+ - `<CCG_DIR>/slot2.prompt`
139
188
 
140
- 两份 prompt 内容**完全相同**。让两个独立大脑产生差异——这正是分歧检测的来源。
141
-
142
- ### 步骤 5. 并行调用 CLI(单消息两个 Bash 调用)
189
+ ### 步骤 5. 并行调用两个评审模型(单消息两个 Bash 调用)
143
190
 
144
191
  helper 内部:检查大小 → 查缓存(命中则 $0)→ 真打 API → 落 cache + usage.log。
192
+ **两个 Bash 调用必须在同一条 assistant message 内发出**,才能真并行。
193
+
194
+ **非 quality(Bailian × 2,不同厂商):**
145
195
 
146
- **Codex** (timeout 260000):
147
196
  ```bash
148
197
  CCG_DIR=<字面路径>
149
198
  source ~/.claude/commands/ccg.sh
150
- ccg_codex "$CCG_DIR/codex.prompt" "$CCG_DIR/codex.result"
151
- echo "---ANSWER---"
152
- cat "$CCG_DIR/codex.result"
199
+ CCG_BAILIAN_MODEL=<Reviewer A 模型> ccg_bailian "$CCG_DIR/slot1.prompt" "$CCG_DIR/slot1.result"
200
+ echo "---ANSWER---"; cat "$CCG_DIR/slot1.result"
153
201
  ```
202
+ ```bash
203
+ CCG_DIR=<字面路径>
204
+ source ~/.claude/commands/ccg.sh
205
+ CCG_BAILIAN_MODEL=<Reviewer B 模型> ccg_bailian "$CCG_DIR/slot2.prompt" "$CCG_DIR/slot2.result"
206
+ echo "---ANSWER---"; cat "$CCG_DIR/slot2.result"
207
+ ```
208
+
209
+ **quality(premium,默认 codex + gemini;timeout 由 helper 内部控制):**
154
210
 
155
- **Gemini** (timeout 140000):
156
211
  ```bash
157
212
  CCG_DIR=<字面路径>
158
213
  source ~/.claude/commands/ccg.sh
159
- ccg_gemini "$CCG_DIR/gemini.prompt" "$CCG_DIR/gemini.result"
160
- echo "---ANSWER---"
161
- cat "$CCG_DIR/gemini.result"
214
+ ccg_codex "$CCG_DIR/slot1.prompt" "$CCG_DIR/slot1.result"
215
+ echo "---ANSWER---"; cat "$CCG_DIR/slot1.result"
216
+ ```
217
+ ```bash
218
+ CCG_DIR=<字面路径>
219
+ source ~/.claude/commands/ccg.sh
220
+ ccg_gemini "$CCG_DIR/slot2.prompt" "$CCG_DIR/slot2.result"
221
+ echo "---ANSWER---"; cat "$CCG_DIR/slot2.result"
162
222
  ```
163
223
 
164
- **两个 Bash 调用必须在同一条 assistant message 内发出**,才能真并行。
224
+ > 让两个不同厂商的独立大脑产生差异——这正是分歧检测的来源。
165
225
 
166
226
  ### 步骤 6. 实际成本
167
227
 
228
+ 按各 slot 实际用的 provider 调用(`<provA>`/`<provB>` ∈ codex|gemini|bailian|claude)。
229
+ **bailian 时把同一个 `CCG_BAILIAN_MODEL` 一起传**,成本才准确:
230
+
168
231
  ```bash
169
232
  CCG_DIR=<字面路径>
170
233
  source ~/.claude/commands/ccg.sh
171
- ccg_actual "$CCG_DIR/codex.prompt" "$CCG_DIR/codex.result" codex
172
- ccg_actual "$CCG_DIR/gemini.prompt" "$CCG_DIR/gemini.result" gemini
234
+ # quality 示例(两个不同厂商 Bailian):
235
+ CCG_BAILIAN_MODEL=<Reviewer A 模型> ccg_actual "$CCG_DIR/slot1.prompt" "$CCG_DIR/slot1.result" bailian
236
+ CCG_BAILIAN_MODEL=<Reviewer B 模型> ccg_actual "$CCG_DIR/slot2.prompt" "$CCG_DIR/slot2.result" bailian
237
+ # quality 示例:
238
+ # ccg_actual "$CCG_DIR/slot1.prompt" "$CCG_DIR/slot1.result" codex
239
+ # ccg_actual "$CCG_DIR/slot2.prompt" "$CCG_DIR/slot2.result" gemini
173
240
  ```
174
241
 
175
242
  ### 步骤 7. 健康判定 + 综合输出(**Pillar 1 关键**)
176
243
 
177
- | Codex | Gemini | 路径 |
244
+ | Reviewer A | Reviewer B | 路径 |
178
245
  |---|---|---|
179
246
  | OK | OK | 完整三段输出(AGREEMENT / DIVERGENCE / BLINDSPOT)|
180
- | OK | FAIL | 只能输出 Codex 视角,标注分歧无法验证 |
181
- | FAIL | OK | 只能输出 Gemini 视角,标注分歧无法验证 |
247
+ | OK | FAIL | 只能输出 A 视角,标注分歧无法验证 |
248
+ | FAIL | OK | 只能输出 B 视角,标注分歧无法验证 |
182
249
  | FAIL | FAIL | Claude 独立回答 + 标注顾问不可用 |
183
250
 
251
+ > **谁来综合(synthesizer)**:非 quality → 一个 Bailian 模型(最好是与 A/B 不同的第三个厂商,如 glm/kimi);quality → codex/gemini/claude 三件套里没上场的那个(缺省 claude)。Claude 本体在 Claude Code 内即可直接做这步综合。
252
+
184
253
  **Claude 的综合规则(必须严格遵守):**
185
254
 
186
255
  1. **解析两边的 [FINDING] 块**,按 `(file, line, category, title 关键字)` 做对齐。Levenshtein 不重要,类别 + 文件 + 大致位置一致即视为同一发现。
@@ -200,12 +269,12 @@ ccg_actual "$CCG_DIR/gemini.prompt" "$CCG_DIR/gemini.result" gemini
200
269
  ### 步骤 8. 综合输出模板(**严格按此格式**)
201
270
 
202
271
  ```
203
- ## CCG v3 综合结果
272
+ ## CCG 综合结果
204
273
 
205
274
  📍 评审范围:<source 标签,如 worktree | staged | upstream:origin/main>
206
275
  🎯 模式:<mode>(risk score: <分数> | 触发: <reasons>)
207
- 🩺 顾问状态:Codex ✓ | Gemini
208
- 💰 本次成本:Codex $0.0023 + Gemini $0.0008 = **$0.0031**
276
+ 🩺 评审模型:A=<provider:model> ✓ | B=<provider:model> (非 quality 为两个不同厂商 Bailian)
277
+ 💰 本次成本:A $0.0009 + B $0.0011 = **$0.0020**
209
278
 
210
279
  ═══ AGREEMENT (N) ═══
211
280
  两边都指出,新增信息量低:
@@ -215,10 +284,10 @@ ccg_actual "$CCG_DIR/gemini.prompt" "$CCG_DIR/gemini.result" gemini
215
284
  ═══ DIVERGENCE (M) ═══ ★ 这一段是 ccg 的核心价值 ★
216
285
 
217
286
  ▸ DIV#1 — file:line
218
- 🔵 Codex: <Codex 的判断>
219
- 🟢 Gemini: <Gemini 的判断 / 没提到>
287
+ 🔵 Reviewer A (<model>): <A 的判断>
288
+ 🟢 Reviewer B (<model>): <B 的判断 / 没提到>
220
289
  ⚖️ Claude 综合: <哪边更可信,为什么>
221
- ➡️ 建议: <accept Codex / accept Gemini / NEEDS HUMAN DECISION>
290
+ ➡️ 建议: <accept A / accept B / NEEDS HUMAN DECISION>
222
291
 
223
292
  ▸ DIV#2 — ...
224
293
 
@@ -231,8 +300,8 @@ ccg_actual "$CCG_DIR/gemini.prompt" "$CCG_DIR/gemini.result" gemini
231
300
  <一句话理由>
232
301
 
233
302
  ═══ 来源原文(折叠展示)═══
234
- 🔵 Codex (gpt-5-mini): <VERDICT 段原样>
235
- 🟢 Gemini (gemini-2.5-flash): <VERDICT 段原样>
303
+ 🔵 Reviewer A (<provider:model>): <VERDICT 段原样>
304
+ 🟢 Reviewer B (<provider:model>): <VERDICT 段原样>
236
305
  ```
237
306
 
238
307
  **关键纪律:**
@@ -243,7 +312,7 @@ ccg_actual "$CCG_DIR/gemini.prompt" "$CCG_DIR/gemini.result" gemini
243
312
 
244
313
  ### 步骤 9. 落 ledger
245
314
 
246
- 把上面综合输出的核心部分写到 `$CCG_DIR/synthesis.txt`(前 400 字符即可,ledger 自截断),然后:
315
+ 把上面综合输出的**完整**核心结论写到 `$CCG_DIR/synthesis.txt`(ledger 自动截断前 400 字符做摘要;持久化报告则使用完整内容),然后:
247
316
 
248
317
  ```bash
249
318
  CCG_DIR=<字面路径>
@@ -251,7 +320,22 @@ source ~/.claude/commands/ccg.sh
251
320
  ccg_ledger_record "$CCG_DIR"
252
321
  ```
253
322
 
254
- ### 步骤 10. 清理
323
+ ### 步骤 10. 持久化报告到仓库(让评审结果在 session 结束后还能被找到)
324
+
325
+ ```bash
326
+ CCG_DIR=<字面路径>
327
+ source ~/.claude/commands/ccg.sh
328
+ ccg_persist_report "$CCG_DIR"
329
+ ```
330
+
331
+ - `CCG_REPORT_OK=<path>` → 把这个路径告诉用户:"本次评审完整记录已写入 `<path>`"
332
+ - `CCG_REPORT_SKIPPED=not-a-git-repo` → 跳过持久化(不在 git 仓库下),是正常情况,不要报错
333
+ - `CCG_REPORT_SKIPPED=disabled` → 用户显式 `CCG_NO_REPORT=1`,不要报错
334
+ - `CCG_REPORT_FAIL=<reason>` → 提示用户检查 `.ccg/reports/` 目录权限
335
+
336
+ 报告位置(默认):`<repo_root>/.ccg/reports/<sha-or-WIP>_<UTC-timestamp>.md`。建议用户把 `.ccg/` 加入 `.gitignore`(首次出现时可顺手提醒一句)。
337
+
338
+ ### 步骤 11. 清理
255
339
 
256
340
  ```bash
257
341
  CCG_DIR=<字面路径>
@@ -272,6 +356,157 @@ ccg_ledger_query # 最近 5 条评审
272
356
  ccg_ledger_query "src/auth.ts" # 这个文件历史评审过几次
273
357
  ```
274
358
 
359
+ ## /ccg ship — 一键交付(review → commit → merge)
360
+
361
+ **语义**:`/ccg ship [target-branch] [commit-message]` = 把当前分支的 staged 改动 review 后提交,再合并到 target。
362
+
363
+ | 命令 | 含义 |
364
+ |---|---|
365
+ | `/ccg ship` | staged → autocommit → merge 到默认保护分支 |
366
+ | `/ccg ship main` | staged → autocommit → merge 到 main |
367
+ | `/ccg ship main "feat: login"` | 同上,指定 commit message |
368
+
369
+ **执行协议(Claude 必须严格按以下步骤):**
370
+
371
+ ### 步骤 S.1 调用 ccg_ship
372
+
373
+ ```bash
374
+ source ~/.claude/commands/ccg.sh
375
+ ccg_ship "<target-branch>" "<commit-message>"
376
+ ```
377
+
378
+ helper 内部自动:
379
+ 1. 若有 staged 改动 → 调 `ccg_autocommit`(AI review → 通过才 commit)
380
+ 2. 若无 staged 改动 → 跳过 commit,直接进入 merge
381
+ 3. 调 `ccg_merge <target>` → AI 辅助合并,冲突自动解决
382
+
383
+ ### 步骤 S.2 解读输出
384
+
385
+ | 输出 | 含义 |
386
+ |---|---|
387
+ | `CCG_SHIP_COMMITTED=1` | staged 改动已通过 review 并 commit |
388
+ | `CCG_SHIP_COMMITTED=0` | 没有 staged 改动,跳过 commit |
389
+ | `CCG_SHIP_DONE=1` | 全流程完成 |
390
+ | `CCG_SHIP_FAIL=autocommit-blocked` | review 不通过,commit 被阻断,merge 未执行 |
391
+ | `CCG_SHIP_FAIL=merge-blocked` | commit 成功但 merge 失败(冲突需人工处理) |
392
+
393
+ ### 步骤 S.3 输出格式
394
+
395
+ ```
396
+ ## 🚀 CCG Ship 结果
397
+
398
+ **分支**:`<source>` → `<target>`
399
+
400
+ **Step 1 — Commit**:✅ 已提交 / ⏭️ 无 staged 改动(跳过)/ ❌ review 不通过(已阻断)
401
+
402
+ **Step 2 — Merge**:✅ 合并完成 / ⚠️ 需人工处理冲突 / ❌ 失败
403
+
404
+ <如果 merge 有冲突,原样转发 ccg_merge 的冲突报告表格>
405
+ ```
406
+
407
+ ### 环境变量
408
+
409
+ | 变量 | 说明 |
410
+ |---|---|
411
+ | `CCG_GATE_OFFLINE=1` | 跳过 AI review,直接 commit |
412
+ | `CCG_MERGE_DRY_RUN=1` | merge 演练,不实际提交 |
413
+ | `CCG_MERGE_NO_FETCH=1` | 跳过 fetch(离线环境) |
414
+
415
+ ---
416
+
417
+ ## /ccg merge — AI 辅助合并(用户主动触发)
418
+
419
+ **语义**:`/ccg merge [target-branch]` = **把当前分支合并到 target**。
420
+
421
+ | 命令 | 含义 |
422
+ |---|---|
423
+ | `/ccg merge` | 站在 `feature` 上,合并到默认保护分支(main → master → develop 顺序) |
424
+ | `/ccg merge develop` | 站在 `feature` 上,合并到 `develop` |
425
+ | `/ccg merge feature` 站在 `main` 上 | ❌ 报 same-branch(请先 checkout feature 再用) |
426
+
427
+ > **重要**:参数是"合并到哪",不是"合并什么"。你的当前分支永远是 source。
428
+
429
+ **执行协议(Claude 必须严格按以下步骤):**
430
+
431
+ ### 步骤 M.0 安全前置
432
+
433
+ 调用前**强制确认**:
434
+ - 当前分支(source) = `git rev-parse --abbrev-ref HEAD`
435
+ - 工作区是否干净?
436
+ - 目标分支是否存在?(helper 内部会校验,但 Claude 必须在 prompt 里告知用户即将合并的是 `<current> → <target>`)
437
+
438
+ ### 步骤 M.1 调用 ccg_merge
439
+
440
+ ```bash
441
+ source ~/.claude/commands/ccg.sh
442
+ ccg_merge "<target-branch>" # 不传参数则默认 main/master/develop
443
+ ```
444
+
445
+ helper 内部自动:
446
+ 1. 校验工作区干净(脏则 `CCG_MERGE_FAIL=dirty-working-tree` 退出)
447
+ 2. **备份 target 分支**(受影响的那一方)到 `ccg-backup/<target>-<timestamp>`
448
+ 3. `git checkout <target>` — 切到 target 让 merge commit 落在那
449
+ 4. `git merge --no-commit --no-ff <source>` — 把当前分支合并进 target
450
+ 5. 无冲突 → 直接 commit + 输出 `CCG_MERGE_RESULT=clean`
451
+ 6. 有冲突 → 解析所有冲突块 → 并行调 Codex+Gemini 决策每个冲突
452
+ 7. AI 给 `NEEDS_HUMAN_DECISION` 的冲突 → 不 commit,**留在 target 分支上**供人审
453
+
454
+ ### 步骤 M.2 解读 helper 输出
455
+
456
+ | 输出 | 含义 | 给用户怎么说 |
457
+ |---|---|---|
458
+ | `CCG_MERGE_SOURCE=<x>` + `CCG_MERGE_TARGET=<y>` | 标识本次方向 | 一定要明确告诉用户:source → target |
459
+ | `CCG_MERGE_RESULT=clean` + `CCG_MERGE_COMMITTED=1` | 合并完成、无冲突 | "✅ `<source>` 已合并到 `<target>`,你现在在 `<target>` 分支" |
460
+ | `CCG_MERGE_RESULT=conflicts` + `CCG_MERGE_COMMITTED=1` | 有冲突,AI 解决了,已 commit | 展示冲突报告表格(helper 已输出) |
461
+ | `CCG_MERGE_BLOCKED=needs-human` | 部分冲突 AI 不敢决策,**未 commit** | "你在 `<target>` 分支(未 commit 的 merge 状态)。请审查 `<files>`,确认后 `git commit`,或 `git merge --abort && git checkout <source>` 撤销" |
462
+ | `CCG_MERGE_FAIL=dirty-working-tree` | 工作区脏 | "请先 `git commit` 或 `git stash` 当前改动" |
463
+ | `CCG_MERGE_FAIL=same-branch` | 当前已经在 target 上 | "你已经在 `<target>` 分支上。请先 `git checkout <feature>` 再合并" |
464
+ | `CCG_MERGE_FAIL=target-not-found:<x>` | target 分支不存在 | "目标分支 `<x>` 不存在。本地分支:`git branch`,远程:`git branch -r`" |
465
+ | `CCG_MERGE_FAIL=no-target-branch` | 找不到默认保护分支 | "请显式指定目标分支:`/ccg merge <branch>`" |
466
+
467
+ ### 步骤 M.3 输出可视化合并报告(**严格按此格式**)
468
+
469
+ helper 已经在 stdout 输出了 Markdown 表格,Claude **必须原样转发**给用户,并在表格前后加摘要:
470
+
471
+ ````
472
+ ## 🔀 CCG Merge 报告
473
+
474
+ **Source**:`<source_branch>`(你的工作分支)
475
+ **Target**:`<target_branch>`(被合并到,已自动切到此分支)
476
+ **Backup**:`ccg-backup/<target>-<ts>`(target 合并前的快照,后悔时可恢复)
477
+
478
+ <helper 输出的 OURS/THEIRS 表格原样贴在这里>
479
+
480
+ > 表格中:OURS = target 分支原内容,THEIRS = 你的 feature 改动
481
+
482
+ ═══ 决策摘要 ═══
483
+ - ✅ AI 解决:N 处
484
+ - ⚠️ 需人审:M 处(如有,列出 file:idx)
485
+
486
+ ═══ 你现在在哪 ═══
487
+ - 如果 CCG_MERGE_COMMITTED=1:你已切到 `<target>`,合并已完成,可以 `git push origin <target>`
488
+ - 如果 CCG_MERGE_BLOCKED:你在 `<target>` 但 merge 未 commit,请审查冲突文件
489
+
490
+ ═══ 撤销方法 ═══
491
+ - 撤销已 commit 的合并:`git reset --hard ccg-backup/<target>-<ts>`
492
+ - 撤销未 commit 的合并:`git merge --abort && git checkout <source>`
493
+ ````
494
+
495
+ ### 关键纪律
496
+
497
+ 1. **永远不要弄混 source/target**:source 是用户的工作分支(current),target 是要合并到的地方(参数/默认)
498
+ 2. **OURS = target**:因为 git checkout target 后,target 的内容就是 ours
499
+ 3. **NEEDS_HUMAN_DECISION 不要替用户决策**:明确告诉用户"AI 不敢决,请你看"
500
+ 4. **保留双方代码**:AI 默认行为是合并双方逻辑,绝不静默丢弃任何一方
501
+ 5. **可视化优先**:表格比一段一段叙述清楚 10 倍
502
+
503
+ ### 环境变量
504
+
505
+ | 变量 | 默认 | 说明 |
506
+ |---|---|---|
507
+ | `CCG_MERGE_DRY_RUN` | `0` | `1` = 跑全流程但不 commit(预览模式,结束时切回 source) |
508
+ | `CCG_MERGE_NO_AI` | `0` | `1` = 跳过 AI 解决,保留冲突标记给人手工解 |
509
+
275
510
  ## 故障排除
276
511
 
277
512
  | 症状 | 原因 | 解决 |
@@ -285,10 +520,15 @@ ccg_ledger_query "src/auth.ts" # 这个文件历史评审过几
285
520
  | `CCG_*_FAIL=Model ... not registered / 503` | 代理不支持当前模型 | 显式 `CCG_CODEX_MODEL=<代理支持的型号>` |
286
521
  | `CCG_GEMINI_FAIL=error-leaked-to-stdout` | 代理把错误写到 stdout | 检查 `$CCG_DIR/gemini.err`(需 `CCG_KEEP_ARTIFACTS=1`) |
287
522
  | `CCG_*_FAIL=timeout-Ns` | CLI 超时 | 调大 `CCG_*_TIMEOUT` |
523
+ | `CCG_REPORT_FAIL=cannot-create-dir:...` | `.ccg/reports/` 无法创建 | 检查仓库目录写权限或设 `CCG_REPORT_DIR=/path/you/own` |
524
+ | `CCG_REPORT_SKIPPED=not-a-git-repo` | 当前目录不在 git 仓库里 | 这是预期行为,不报错;要持久化就 cd 进仓库或显式给 `CCG_REPORT_DIR` |
525
+ | `CCG_HISTORY_SKIPPED=no-ledger` | 还没攒下评审历史 | 这是预期行为;多跑几次 `/ccg` 后历史就有了 |
526
+ | `CCG_HISTORY_NONE=0_matches` | 这次 diff 触及的文件之前没评审过 | 这是预期行为,prompt 里不带 history 块 |
288
527
 
289
528
  ## 已知设计取舍
290
529
 
291
530
  - **Divergence over consensus**:放弃"给一份完整 review 报告",转向"标记需要人裁决的点"。AGREEMENT 段意识形态上**故意降级**——单源 Claude 也能发现,无新增信号
531
+ - **Ledger 双向化**:v3.x 起 ledger 不再只写——`ccg_ledger_context` 在每次评审前把"过去对同一文件的判断"作为先验注入 prompt。recurring patterns 和未解决的 fix-required 进入第一现场,不再随 session 关闭而蒸发。这是把 L6 从"日记本"升级成"结构化记忆"。
292
532
  - **同 prompt 双投喂**:训练数据差异自然产生多样性,比拍脑袋分工(codex=arch、gemini=ux)更可靠
293
533
  - **24h 缓存**:调试同段代码反复跑时省 90% 费用;改了代码 prompt hash 自然变了,无需手动失效
294
534
  - **prompt 大小硬限**:100KB ≈ 32k token,比 codex/gemini context window 小一个数量级,防止把 repo 塞进去