ccg-workflow 3.0.0 → 3.0.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.md +23 -4
- package/README.zh-CN.md +22 -5
- package/dist/cli.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/shared/{ccg-workflow.81OoN8XX.mjs → ccg-workflow.CGOOq1xo.mjs} +5 -1
- package/package.json +1 -1
- package/templates/commands/go.md +8 -2
- package/templates/engine/phase-guide.md +112 -0
- package/templates/engine/strategies/debug-investigate.md +14 -1
- package/templates/engine/strategies/full-collaborate.md +54 -11
- package/templates/engine/strategies/guided-develop.md +33 -8
- package/templates/engine/strategies/refactor-safely.md +32 -9
- package/templates/engine/strategies/review-audit.md +7 -0
- package/templates/hooks/subagent-context.js +63 -20
- package/templates/hooks/task-utils.js +82 -5
- package/templates/hooks/workflow-state.js +17 -1
- package/templates/rules/ccg-skill-routing.md +5 -1
package/README.md
CHANGED
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
# CCG - Claude + Codex + Gemini Multi-Model Collaboration
|
|
2
2
|
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
<img src="assets/logo/ccg-logo-cropped.png" alt="CCG Workflow" width="400">
|
|
6
|
+
|
|
3
7
|
[](https://github.com/fengshao1227/ccg-workflow)
|
|
4
8
|
[](https://www.npmjs.com/package/ccg-workflow)
|
|
5
9
|
[](https://www.npmjs.com/package/ccg-workflow)
|
|
6
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
|
+
[](https://claude.ai/code)
|
|
12
|
+
[]()
|
|
13
|
+
[](https://x.com/CCG_Workflow)
|
|
14
|
+

|
|
7
15
|
|
|
8
16
|
[简体中文](./README.zh-CN.md) | English
|
|
9
17
|
|
|
10
|
-
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
## ♥️ Sponsor
|
|
11
21
|
|
|
12
|
-
[302.AI](https://share.302.ai/oUDqQ6)
|
|
22
|
+
[](https://share.302.ai/oUDqQ6)
|
|
13
23
|
|
|
14
|
-
[
|
|
24
|
+
[302.AI](https://share.302.ai/oUDqQ6) is a pay-as-you-go enterprise AI resource hub that offers the latest and most comprehensive AI models and APIs on the market, along with a variety of ready-to-use online AI applications.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
[**n1n.ai**](https://api.n1n.ai/register?channel=c_ivgzug0w) — Global LLM API Gateway. One API Key to access 500+ top AI models (GPT-5, Claude 4.5, Gemini 3 Pro, and more).
|
|
15
29
|
|
|
16
30
|
---
|
|
17
31
|
|
|
@@ -224,10 +238,15 @@ npx ccg-workflow # Select "Uninstall" from menu
|
|
|
224
238
|
- **Issues**: [GitHub Issues](https://github.com/fengshao1227/ccg-workflow/issues)
|
|
225
239
|
- **Community**: [Linux.do](https://linux.do)
|
|
226
240
|
|
|
241
|
+
|
|
242
|
+
## Star History
|
|
243
|
+
|
|
244
|
+
[](https://www.star-history.com/#fengshao1227/ccg-workflow&type=timeline&legend=top-left)
|
|
245
|
+
|
|
227
246
|
## License
|
|
228
247
|
|
|
229
248
|
MIT
|
|
230
249
|
|
|
231
250
|
---
|
|
232
251
|
|
|
233
|
-
v3.0.
|
|
252
|
+
v3.0.2 | [Issues](https://github.com/fengshao1227/ccg-workflow/issues) | [Contributing](./CONTRIBUTING.md)
|
package/README.zh-CN.md
CHANGED
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
# CCG - Claude + Codex + Gemini 多模型协作
|
|
2
|
+
# CCG - Claude + Codex + Gemini Multi-Model Collaboration
|
|
2
3
|
|
|
4
|
+
<div align="center">
|
|
5
|
+
|
|
6
|
+
<img src="assets/logo/ccg-logo-cropped.png" alt="CCG Workflow" width="400">
|
|
7
|
+
|
|
8
|
+
[](https://github.com/fengshao1227/ccg-workflow)
|
|
9
|
+
[](https://www.npmjs.com/package/ccg-workflow)
|
|
3
10
|
[](https://www.npmjs.com/package/ccg-workflow)
|
|
4
11
|
[](https://opensource.org/licenses/MIT)
|
|
12
|
+
[](https://claude.ai/code)
|
|
13
|
+
[]()
|
|
14
|
+
[](https://x.com/CCG_Workflow)
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
[简体中文](./README.zh-CN.md) | English
|
|
5
18
|
|
|
6
|
-
|
|
19
|
+
</div>
|
|
7
20
|
|
|
8
|
-
##
|
|
21
|
+
## ♥️ Sponsor
|
|
9
22
|
|
|
10
|
-
[302.AI](https://share.302.ai/oUDqQ6)
|
|
23
|
+
[](https://share.302.ai/oUDqQ6)
|
|
24
|
+
|
|
25
|
+
[302.AI](https://share.302.ai/oUDqQ6) is a pay-as-you-go enterprise AI resource hub that offers the latest and most comprehensive AI models and APIs on the market, along with a variety of ready-to-use online AI applications.
|
|
26
|
+
|
|
27
|
+
---
|
|
11
28
|
|
|
12
|
-
[n1n.ai](https://api.n1n.ai/register?channel=c_ivgzug0w) —
|
|
29
|
+
[**n1n.ai**](https://api.n1n.ai/register?channel=c_ivgzug0w) — Global LLM API Gateway. One API Key to access 500+ top AI models (GPT-5, Claude 4.5, Gemini 3 Pro, and more).
|
|
13
30
|
|
|
14
31
|
---
|
|
15
32
|
|
|
@@ -227,4 +244,4 @@ MIT
|
|
|
227
244
|
|
|
228
245
|
---
|
|
229
246
|
|
|
230
|
-
v3.0.
|
|
247
|
+
v3.0.2 | [Issues](https://github.com/fengshao1227/ccg-workflow/issues) | [Contributing](./CONTRIBUTING.md)
|
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import cac from 'cac';
|
|
3
3
|
import ansis from 'ansis';
|
|
4
|
-
import { z as diagnoseMcpConfig, A as isWindows, B as readClaudeCodeConfig, C as fixWindowsMcpConfig, D as writeClaudeCodeConfig, r as readCcgConfig, b as initI18n, a as i18n, s as showMainMenu, i as init, E as configMcp, F as version } from './shared/ccg-workflow.
|
|
4
|
+
import { z as diagnoseMcpConfig, A as isWindows, B as readClaudeCodeConfig, C as fixWindowsMcpConfig, D as writeClaudeCodeConfig, r as readCcgConfig, b as initI18n, a as i18n, s as showMainMenu, i as init, E as configMcp, F as version } from './shared/ccg-workflow.CGOOq1xo.mjs';
|
|
5
5
|
import 'inquirer';
|
|
6
6
|
import 'ora';
|
|
7
7
|
import 'node:child_process';
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { c as changeLanguage, x as checkForUpdates, y as compareVersions, d as createDefaultConfig, e as createDefaultRouting, g as getCcgDir, f as getConfigPath, t as getCurrentVersion, v as getLatestVersion, j as getWorkflowById, h as getWorkflowConfigs, a as i18n, i as init, b as initI18n, l as installAceTool, m as installAceToolRs, k as installWorkflows, p as migrateToV1_4_0, q as needsMigration, r as readCcgConfig, s as showMainMenu, o as uninstallAceTool, n as uninstallWorkflows, u as update, w as writeCcgConfig } from './shared/ccg-workflow.
|
|
1
|
+
export { c as changeLanguage, x as checkForUpdates, y as compareVersions, d as createDefaultConfig, e as createDefaultRouting, g as getCcgDir, f as getConfigPath, t as getCurrentVersion, v as getLatestVersion, j as getWorkflowById, h as getWorkflowConfigs, a as i18n, i as init, b as initI18n, l as installAceTool, m as installAceToolRs, k as installWorkflows, p as migrateToV1_4_0, q as needsMigration, r as readCcgConfig, s as showMainMenu, o as uninstallAceTool, n as uninstallWorkflows, u as update, w as writeCcgConfig } from './shared/ccg-workflow.CGOOq1xo.mjs';
|
|
2
2
|
import 'ansis';
|
|
3
3
|
import 'inquirer';
|
|
4
4
|
import 'ora';
|
|
@@ -10,7 +10,7 @@ import fs from 'fs-extra';
|
|
|
10
10
|
import { parse, stringify } from 'smol-toml';
|
|
11
11
|
import i18next from 'i18next';
|
|
12
12
|
|
|
13
|
-
const version = "3.0.
|
|
13
|
+
const version = "3.0.2";
|
|
14
14
|
|
|
15
15
|
function cmd(id, order, category, name, nameEn, description, descriptionEn, cmdOverride) {
|
|
16
16
|
return {
|
|
@@ -1028,6 +1028,10 @@ async function installSkillFiles(ctx) {
|
|
|
1028
1028
|
overwrite: true,
|
|
1029
1029
|
errorOnExist: false
|
|
1030
1030
|
});
|
|
1031
|
+
const securityDir = join(skillsDestDir, "domains", "security");
|
|
1032
|
+
if (await fs.pathExists(securityDir)) {
|
|
1033
|
+
await fs.remove(securityDir);
|
|
1034
|
+
}
|
|
1031
1035
|
const replacePathsInDir = async (dir) => {
|
|
1032
1036
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1033
1037
|
for (const entry of entries) {
|
package/package.json
CHANGED
package/templates/commands/go.md
CHANGED
|
@@ -127,7 +127,9 @@ $ARGUMENTS
|
|
|
127
127
|
mkdir -p .ccg/tasks/{task-name}
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
**Step 3**:
|
|
130
|
+
**Step 3**: 获取当前 git 分支名:`git rev-parse --abbrev-ref HEAD`
|
|
131
|
+
|
|
132
|
+
**Step 4**: 写入 `.ccg/tasks/{task-name}/task.json`:
|
|
131
133
|
|
|
132
134
|
```json
|
|
133
135
|
{
|
|
@@ -138,11 +140,15 @@ mkdir -p .ccg/tasks/{task-name}
|
|
|
138
140
|
"currentPhase": "1",
|
|
139
141
|
"nextAction": "{策略第一阶段的描述}",
|
|
140
142
|
"gate": null,
|
|
143
|
+
"branch": "{当前 git 分支}",
|
|
144
|
+
"scope": "{task-name}",
|
|
141
145
|
"createdAt": "{当前 ISO 日期时间}"
|
|
142
146
|
}
|
|
143
147
|
```
|
|
144
148
|
|
|
145
|
-
**Step
|
|
149
|
+
**Step 5**: 创建 `.ccg/tasks/{task-name}/context.jsonl` 种子文件:
|
|
150
|
+
- 第一行写种子示例:`{"_example": "Fill with {\"file\": \"path\", \"reason\": \"why\"}. Seed rows are skipped."}`
|
|
151
|
+
- 如果 `.ccg/spec/` 存在 → 追加 spec 文件条目
|
|
146
152
|
|
|
147
153
|
**复杂度 S → 跳过任务创建**(保持轻量)。
|
|
148
154
|
|
|
@@ -93,3 +93,115 @@ TeamCreate 失败(Agent Teams 未启用)→ Claude 自己按计划顺序实
|
|
|
93
93
|
- 代码块标明语言
|
|
94
94
|
- 变更摘要用 git diff 格式
|
|
95
95
|
- 研究结果用表格对比
|
|
96
|
+
|
|
97
|
+
## 8. Spec Evolution Protocol — Spec 反馈环
|
|
98
|
+
|
|
99
|
+
> 让 `.ccg/spec/` 从静态文档变为随项目开发自动进化的活知识库。
|
|
100
|
+
|
|
101
|
+
### 触发条件
|
|
102
|
+
|
|
103
|
+
任务归档前(status → "archived"),如果以下任一条件成立,**必须执行 Spec Evolution**:
|
|
104
|
+
- 本次开发中发现了可复用的编码模式或约定
|
|
105
|
+
- 外部模型审查提出了有价值的规范建议
|
|
106
|
+
- 修复了一个非显而易见的坑(未来可能再踩)
|
|
107
|
+
- 引入了新的第三方库/API/架构模式
|
|
108
|
+
|
|
109
|
+
### 执行步骤
|
|
110
|
+
|
|
111
|
+
1. **提炼经验**:分析 `git diff` + review.md(如有),提取可复用的经验教训
|
|
112
|
+
2. **分类归属**:判断经验属于哪个 Spec 域:
|
|
113
|
+
- 后端相关 → `.ccg/spec/backend/index.md`
|
|
114
|
+
- 前端相关 → `.ccg/spec/frontend/index.md`
|
|
115
|
+
- 跨模块/通用 → `.ccg/spec/guides/index.md`
|
|
116
|
+
3. **草拟更新**:以追加方式写出建议新增的 Spec 条目(不覆盖现有内容)
|
|
117
|
+
4. **展示给用户**:
|
|
118
|
+
```
|
|
119
|
+
📝 Spec Evolution — 本次开发经验提炼
|
|
120
|
+
|
|
121
|
+
建议新增到 .ccg/spec/backend/index.md:
|
|
122
|
+
- [规范条目](来源:{task-name},{日期})
|
|
123
|
+
|
|
124
|
+
确认写入?[Y/n]
|
|
125
|
+
```
|
|
126
|
+
5. **用户确认后写入**(⛔ 不可静默写入 Spec)
|
|
127
|
+
6. **无值得提炼的经验 → 跳过**(不要强行凑条目)
|
|
128
|
+
|
|
129
|
+
### 条目质量标准
|
|
130
|
+
|
|
131
|
+
好的 Spec 条目:
|
|
132
|
+
- ✅ 具体:引用真实文件路径和 API 签名
|
|
133
|
+
- ✅ 说明 Why:不只说"要这样做",还说"因为…"
|
|
134
|
+
- ✅ 可验证:子 Agent 能根据条目判断对错
|
|
135
|
+
|
|
136
|
+
坏的 Spec 条目:
|
|
137
|
+
- ❌ 空泛:"写好的代码" / "注意安全"
|
|
138
|
+
- ❌ 一次性:只对本次任务有价值,对未来无意义
|
|
139
|
+
|
|
140
|
+
## 9. Loop Detection & Recovery — 死循环检测
|
|
141
|
+
|
|
142
|
+
> workflow-state Hook 自动追踪每轮的 phase + nextAction。连续 3 轮无变化触发 Break-Loop Protocol。
|
|
143
|
+
|
|
144
|
+
### 机制
|
|
145
|
+
|
|
146
|
+
- Hook 在每轮用户消息时写入 `.ccg/tasks/{name}/.turns.json`(最近 10 轮滚动缓冲)
|
|
147
|
+
- 检测规则:连续 3 轮 `phase` + `nextAction` 完全相同 → 判定为死循环
|
|
148
|
+
- 触发后在 `<ccg-state>` 面包屑中注入 `⚠️ LOOP DETECTED` 警告
|
|
149
|
+
|
|
150
|
+
### Break-Loop Protocol(Claude 收到警告后必须执行)
|
|
151
|
+
|
|
152
|
+
1. **立即停止**当前重复动作
|
|
153
|
+
2. **根因分析**(5 Why):
|
|
154
|
+
- 是外部依赖阻塞?(网络/API/权限)→ 告知用户
|
|
155
|
+
- 是策略不适配?→ 建议升级策略
|
|
156
|
+
- 是信息不足?→ 向用户提问
|
|
157
|
+
- 是实现路径走死?→ 换方案
|
|
158
|
+
3. **更新 task.json**:`nextAction` 必须变更为新的动作描述(打破循环)
|
|
159
|
+
4. **如果连续 2 次触发 Break-Loop**(即 6 轮无进展)→ 强制暂停,输出完整状态摘要请用户介入
|
|
160
|
+
|
|
161
|
+
## 10. Ralph Loop — 迭代审查协议
|
|
162
|
+
|
|
163
|
+
> 审查不是一次性动作。每轮 spawn 新 Agent(干净上下文),读取磁盘最新状态重新验证,循环自修复。
|
|
164
|
+
|
|
165
|
+
### 适用场景
|
|
166
|
+
|
|
167
|
+
策略中标注为 `[Ralph Loop]` 的审查阶段,使用迭代审查代替一次性审查。
|
|
168
|
+
|
|
169
|
+
### 标准流程
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
Round N (N=1,2,...,MAX_ROUNDS):
|
|
173
|
+
1. 双模型并行审查(每次 spawn 新 Agent,干净上下文)
|
|
174
|
+
2. 质量关卡(verify-security / verify-quality / verify-change)
|
|
175
|
+
3. 综合审查报告,按 Critical / Warning / Info 分级
|
|
176
|
+
4. 展示给用户,询问:
|
|
177
|
+
- 有 Critical → "发现 N 个 Critical 问题,是否修复后再审?[Y/n]"
|
|
178
|
+
- 无 Critical → "审查通过,是否需要再审一轮?[y/N]"
|
|
179
|
+
5. 用户选择继续 →
|
|
180
|
+
a. spawn fix-dev(新 Agent,干净上下文)修复 Critical 问题
|
|
181
|
+
b. 进度追加到 .ccg/tasks/{name}/fix-log.jsonl
|
|
182
|
+
c. 回到 Round N+1
|
|
183
|
+
6. 用户选择停止 → 退出循环,进入下一阶段
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 关键规则
|
|
187
|
+
|
|
188
|
+
- **每轮审查必须是新 Agent** — 不复用上一轮的 Agent 上下文,避免"上下文污染越修越烂"
|
|
189
|
+
- **fix-dev 也是新 Agent** — 从磁盘读取最新代码状态,只修分配的问题
|
|
190
|
+
- **最多 3 轮**(MAX_ROUNDS=3)— 超过 3 轮说明问题根深,应该回退到规划阶段
|
|
191
|
+
- **用户始终有决定权** — 每轮结束后由用户决定是否继续,不自动循环
|
|
192
|
+
- **fix-log.jsonl 追踪进度** — 每轮结果追加一行 JSON,格式:
|
|
193
|
+
```jsonl
|
|
194
|
+
{"round": 1, "critical": 2, "warning": 5, "fixed": ["file1:issue", "file2:issue"], "ts": "ISO"}
|
|
195
|
+
{"round": 2, "critical": 0, "warning": 3, "fixed": ["file3:issue"], "ts": "ISO"}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### context.jsonl 角色标注
|
|
199
|
+
|
|
200
|
+
策展 context.jsonl 时,按角色标注 `roles` 字段:
|
|
201
|
+
```jsonl
|
|
202
|
+
{"file": ".ccg/spec/backend/index.md", "reason": "后端规范", "roles": ["implement", "review"]}
|
|
203
|
+
{"file": ".ccg/tasks/{name}/plan.md", "reason": "实施计划", "roles": ["implement"]}
|
|
204
|
+
{"file": ".ccg/tasks/{name}/research/lib-comparison.md", "reason": "库选型", "roles": ["research", "implement"]}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
SubAgent-context Hook 自动按角色过滤:无 `roles` 字段 = 注入所有角色。
|
|
@@ -144,7 +144,20 @@ Gate: 用户已确认修复方向 ✓
|
|
|
144
144
|
📍 Next: /ccg commit 提交修复
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
#### Spec Evolution(归档前必须执行)
|
|
148
|
+
|
|
149
|
+
参考 `phase-guide.md § 8 Spec Evolution Protocol` 执行:
|
|
150
|
+
1. 分析本次调试的根因和修复方案,提炼可复用的调试经验和防御性编码约定
|
|
151
|
+
2. 如有值得记录的经验(特别是非显而易见的坑)→ 草拟 Spec 条目,展示给用户确认后追加到 `.ccg/spec/{domain}/index.md`
|
|
152
|
+
3. 无值得提炼的经验 → 跳过
|
|
153
|
+
|
|
154
|
+
**Task 更新**:`status → "archived"`
|
|
155
|
+
|
|
156
|
+
**归档任务**:
|
|
157
|
+
```bash
|
|
158
|
+
mkdir -p .ccg/tasks/archive/$(date +%Y-%m) && mv .ccg/tasks/{task-name} .ccg/tasks/archive/$(date +%Y-%m)/
|
|
159
|
+
git add .ccg/tasks/ && git commit -m "chore: archive ccg task"
|
|
160
|
+
```
|
|
148
161
|
|
|
149
162
|
---
|
|
150
163
|
|
|
@@ -225,21 +225,25 @@ SendMessage({ to: "reviewer", message: { type: "shutdown_request" } })
|
|
|
225
225
|
2. 按 plan.md 中的 Layer 顺序逐文件实施
|
|
226
226
|
3. 仍然遵守质量关卡
|
|
227
227
|
|
|
228
|
-
### Phase 5:
|
|
228
|
+
### Phase 5: 迭代审查 [required · Ralph Loop]
|
|
229
229
|
|
|
230
230
|
`[模式:优化]`
|
|
231
231
|
|
|
232
232
|
**Gate check**: 实施已完成
|
|
233
233
|
|
|
234
|
-
**Task 更新**:`currentPhase → "5-optimization"`, `nextAction → "双模型审查 + 质量关卡"`
|
|
234
|
+
**Task 更新**:`currentPhase → "5-optimization"`, `nextAction → "Ralph Loop Round 1: 双模型审查 + 质量关卡"`
|
|
235
235
|
|
|
236
|
-
|
|
236
|
+
参考 `phase-guide.md § 10 Ralph Loop` 执行迭代审查。最多 3 轮。
|
|
237
237
|
|
|
238
|
-
|
|
238
|
+
#### Round N 流程(N=1,2,3)
|
|
239
|
+
|
|
240
|
+
**5a. 双模型交叉审查(每轮 spawn 新 Agent,干净上下文)**
|
|
241
|
+
|
|
242
|
+
**并行调用**(`run_in_background: true`):
|
|
239
243
|
- **backend 模型**:reviewer 角色 — 关注安全、性能、错误处理
|
|
240
244
|
- **frontend 模型**:reviewer 角色 — 关注可访问性、设计一致性
|
|
241
245
|
|
|
242
|
-
|
|
246
|
+
**5b. 质量关卡**
|
|
243
247
|
|
|
244
248
|
**⛔ 以下 Skill 必须逐个调用执行,不可跳过,不可用自己的判断替代:**
|
|
245
249
|
|
|
@@ -247,17 +251,35 @@ SendMessage({ to: "reviewer", message: { type: "shutdown_request" } })
|
|
|
247
251
|
2. 调用 Skill `verify-quality` — 等待报告
|
|
248
252
|
3. 调用 Skill `verify-change` — 等待报告
|
|
249
253
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
#### 5c. 综合报告
|
|
254
|
+
**5c. 综合报告**
|
|
253
255
|
|
|
254
256
|
整合审查意见 + 质量关卡结果,按严重度分级:
|
|
255
257
|
- **Critical**:必须修复(阻塞交付)
|
|
256
258
|
- **Warning**:建议修复
|
|
257
259
|
- **Info**:供参考
|
|
258
260
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
+
**持久化**:写入 `.ccg/tasks/{task-name}/review.md`(每轮覆盖)
|
|
262
|
+
|
|
263
|
+
追加进度到 `.ccg/tasks/{task-name}/fix-log.jsonl`:
|
|
264
|
+
```jsonl
|
|
265
|
+
{"round": N, "critical": X, "warning": Y, "info": Z, "ts": "ISO"}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**5d. 用户决定(⛔ 必须等待)**
|
|
269
|
+
|
|
270
|
+
展示审查结果后询问用户:
|
|
271
|
+
- 有 Critical → `发现 N 个 Critical 问题。修复后再审一轮?[Y/n]`
|
|
272
|
+
- 无 Critical 但有 Warning → `无 Critical 问题。需要再审一轮处理 Warning?[y/N]`
|
|
273
|
+
- 全部通过 → 直接进入 Phase 6
|
|
274
|
+
|
|
275
|
+
用户选择继续 →
|
|
276
|
+
1. spawn fix-dev(**新 Agent,干净上下文**)修复 Critical/Warning
|
|
277
|
+
2. fix-dev 完成后回到 5a 开始 Round N+1
|
|
278
|
+
3. 追加修复记录到 fix-log.jsonl
|
|
279
|
+
|
|
280
|
+
用户选择停止 → 进入 Phase 6
|
|
281
|
+
|
|
282
|
+
**第 3 轮仍有 Critical** → 强制停止,建议回退到 Phase 3 重新规划。
|
|
261
283
|
|
|
262
284
|
### Phase 6: 最终验收
|
|
263
285
|
|
|
@@ -277,7 +299,28 @@ SendMessage({ to: "reviewer", message: { type: "shutdown_request" } })
|
|
|
277
299
|
📍 Next: /ccg commit 提交,或查看 .ccg/tasks/{task-name}/ 中的完整记录
|
|
278
300
|
```
|
|
279
301
|
|
|
280
|
-
|
|
302
|
+
#### Spec Evolution(归档前必须执行)
|
|
303
|
+
|
|
304
|
+
参考 `phase-guide.md § 8 Spec Evolution Protocol` 执行:
|
|
305
|
+
1. 分析本次 `git diff` + `review.md`,提炼可复用的编码约定和经验教训
|
|
306
|
+
2. 如有值得记录的经验 → 草拟 Spec 条目,展示给用户确认后追加到 `.ccg/spec/{domain}/index.md`
|
|
307
|
+
3. 无值得提炼的经验 → 跳过(不强行凑)
|
|
308
|
+
|
|
309
|
+
**Task 更新**:`status → "archived"`
|
|
310
|
+
|
|
311
|
+
**归档任务**:将 `.ccg/tasks/{task-name}/` 移动到 `.ccg/tasks/archive/YYYY-MM/{task-name}/`
|
|
312
|
+
```bash
|
|
313
|
+
mkdir -p .ccg/tasks/archive/$(date +%Y-%m) && mv .ccg/tasks/{task-name} .ccg/tasks/archive/$(date +%Y-%m)/
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**自动提交归档**:
|
|
317
|
+
```bash
|
|
318
|
+
git add .ccg/tasks/ && git commit -m "chore: archive ccg task {task-name}"
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
📍 Next: /ccg:commit 提交产品代码
|
|
323
|
+
```
|
|
281
324
|
|
|
282
325
|
---
|
|
283
326
|
|
|
@@ -161,34 +161,59 @@ TaskOutput({ task_id: "<id>", block: true, timeout: 600000 })
|
|
|
161
161
|
|
|
162
162
|
**Task 更新**:`currentPhase → "5-implement"`, `nextAction → "按计划执行实施"`
|
|
163
163
|
|
|
164
|
-
### Phase 6:
|
|
164
|
+
### Phase 6: 迭代审查 [Ralph Loop]
|
|
165
165
|
|
|
166
166
|
1. `git diff` 展示所有变更
|
|
167
167
|
2. 运行测试(如果有)
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
参考 `phase-guide.md § 10 Ralph Loop` 执行迭代审查(变更 >30 行时,最多 3 轮)。
|
|
170
|
+
|
|
171
|
+
#### Round N 流程
|
|
172
|
+
|
|
173
|
+
**⛔ 双模型交叉审查(每轮 spawn 新调用,干净上下文):**
|
|
170
174
|
3. 并行调用双模型(`run_in_background: true`,使用 model-router.md 模板):
|
|
171
175
|
- backend 模型 + reviewer 角色 — 安全、性能、错误处理
|
|
172
176
|
- frontend 模型 + reviewer 角色 — 设计一致性(如涉及前端)
|
|
173
177
|
4. 综合审查意见
|
|
174
178
|
|
|
175
|
-
**⛔
|
|
179
|
+
**⛔ 质量关卡(必须逐个调用 Skill,不可跳过):**
|
|
176
180
|
5. 调用 Skill `verify-quality` — 等待报告
|
|
177
181
|
6. 调用 Skill `verify-security` — 等待报告(涉及 auth/input/crypto 时)
|
|
178
182
|
7. 调用 Skill `verify-change` — 等待报告
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
183
|
+
|
|
184
|
+
**用户决定(⛔ 必须等待):**
|
|
185
|
+
- 有 Critical → `发现 N 个 Critical 问题。修复后再审一轮?[Y/n]`
|
|
186
|
+
- 无 Critical → `审查通过。需要再审一轮?[y/N]`
|
|
187
|
+
- 用户选择继续 → 修复 Critical 后回到 Round N+1
|
|
188
|
+
- 用户选择停止 → 退出审查循环
|
|
189
|
+
|
|
190
|
+
追加进度到 `.ccg/tasks/{task-name}/fix-log.jsonl`。
|
|
191
|
+
|
|
192
|
+
8. 检查是否满足验收标准
|
|
193
|
+
9. 输出结果:
|
|
182
194
|
```
|
|
183
195
|
✅ 开发完成
|
|
184
196
|
变更: [N] 文件,[M] 行
|
|
185
197
|
实现: [摘要]
|
|
186
198
|
测试: [通过/跳过/失败情况]
|
|
187
|
-
|
|
199
|
+
审查: [N] 轮,[Critical: N, Warning: N, Info: N]
|
|
188
200
|
📍 Next: 可以用 /ccg:commit 提交
|
|
189
201
|
```
|
|
190
202
|
|
|
191
|
-
|
|
203
|
+
#### Spec Evolution(归档前必须执行)
|
|
204
|
+
|
|
205
|
+
参考 `phase-guide.md § 8 Spec Evolution Protocol` 执行:
|
|
206
|
+
1. 分析本次 `git diff` + 审查结果,提炼可复用的编码约定
|
|
207
|
+
2. 如有值得记录的经验 → 草拟 Spec 条目,展示给用户确认后追加到 `.ccg/spec/{domain}/index.md`
|
|
208
|
+
3. 无值得提炼的经验 → 跳过
|
|
209
|
+
|
|
210
|
+
**Task 更新**:`status → "archived"`
|
|
211
|
+
|
|
212
|
+
**归档任务**:
|
|
213
|
+
```bash
|
|
214
|
+
mkdir -p .ccg/tasks/archive/$(date +%Y-%m) && mv .ccg/tasks/{task-name} .ccg/tasks/archive/$(date +%Y-%m)/
|
|
215
|
+
git add .ccg/tasks/ && git commit -m "chore: archive ccg task"
|
|
216
|
+
```
|
|
192
217
|
|
|
193
218
|
---
|
|
194
219
|
|
|
@@ -110,12 +110,16 @@ Gate: 所有步骤已执行 ✓
|
|
|
110
110
|
Step [N/M]: [描述] — ✅ 测试通过 / ❌ 测试失败
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
-
### Phase 5:
|
|
113
|
+
### Phase 5: 迭代审查 [Ralph Loop]
|
|
114
114
|
|
|
115
115
|
1. 运行完整测试套件
|
|
116
116
|
2. 对比基线:确保不引入新的失败
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
参考 `phase-guide.md § 10 Ralph Loop` 执行迭代审查(最多 3 轮)。
|
|
119
|
+
|
|
120
|
+
#### Round N 流程
|
|
121
|
+
|
|
122
|
+
**⛔ 双模型交叉审查(每轮 spawn 新调用,干净上下文):**
|
|
119
123
|
|
|
120
124
|
3. 获取变更:`git diff` 全量输出
|
|
121
125
|
4. 并行调用双模型审查(`run_in_background: true`):
|
|
@@ -129,23 +133,42 @@ Step [N/M]: [描述] — ✅ 测试通过 / ❌ 测试失败
|
|
|
129
133
|
7. 调用 Skill `verify-security` — 等待报告
|
|
130
134
|
8. 调用 Skill `verify-change` — 等待报告
|
|
131
135
|
|
|
132
|
-
**综合报告**:双模型审查 +
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
-
|
|
136
|
+
**综合报告**:双模型审查 + 质量关卡,按严重度分级
|
|
137
|
+
|
|
138
|
+
**用户决定(⛔ 必须等待):**
|
|
139
|
+
- 有 Critical → `发现 N 个 Critical 问题。修复后再审一轮?[Y/n]`
|
|
140
|
+
- 无 Critical → `审查通过。需要再审一轮?[y/N]`
|
|
141
|
+
- 用户选择继续 → 修复后回到 Round N+1
|
|
142
|
+
- 用户选择停止 → 退出审查循环
|
|
143
|
+
|
|
144
|
+
追加进度到 `.ccg/tasks/{task-name}/fix-log.jsonl`。
|
|
136
145
|
|
|
137
146
|
9. `git diff` 展示全部变更
|
|
138
|
-
|
|
147
|
+
10. 对比基线,确认无回归
|
|
148
|
+
11. 输出结果:
|
|
139
149
|
```
|
|
140
150
|
✅ 重构完成
|
|
141
151
|
步骤: [N] 步全部通过
|
|
142
152
|
变更: [文件数] 文件,[行数] 行
|
|
143
153
|
测试: 基线 [N] 通过 → 重构后 [N] 通过
|
|
144
|
-
|
|
154
|
+
审查: [N] 轮,[Critical: N, Warning: N, Info: N]
|
|
145
155
|
📍 Next: /ccg:commit 提交
|
|
146
156
|
```
|
|
147
157
|
|
|
148
|
-
|
|
158
|
+
#### Spec Evolution(归档前必须执行)
|
|
159
|
+
|
|
160
|
+
参考 `phase-guide.md § 8 Spec Evolution Protocol` 执行:
|
|
161
|
+
1. 分析本次重构的 `git diff`,提炼可复用的重构模式和架构约定
|
|
162
|
+
2. 如有值得记录的经验 → 草拟 Spec 条目,展示给用户确认后追加到 `.ccg/spec/{domain}/index.md`
|
|
163
|
+
3. 无值得提炼的经验 → 跳过
|
|
164
|
+
|
|
165
|
+
**Task 更新**:`status → "archived"`
|
|
166
|
+
|
|
167
|
+
**归档任务**:
|
|
168
|
+
```bash
|
|
169
|
+
mkdir -p .ccg/tasks/archive/$(date +%Y-%m) && mv .ccg/tasks/{task-name} .ccg/tasks/archive/$(date +%Y-%m)/
|
|
170
|
+
git add .ccg/tasks/ && git commit -m "chore: archive ccg task"
|
|
171
|
+
```
|
|
149
172
|
|
|
150
173
|
---
|
|
151
174
|
|
|
@@ -106,6 +106,13 @@ Gate: 双模型审查已返回 ✓
|
|
|
106
106
|
|
|
107
107
|
如果有 Critical 发现,询问用户是否立即修复(可切换到 `direct-fix` 策略)。
|
|
108
108
|
|
|
109
|
+
#### Spec Evolution(审查完成后执行)
|
|
110
|
+
|
|
111
|
+
参考 `phase-guide.md § 8 Spec Evolution Protocol` 执行:
|
|
112
|
+
1. 从审查发现中提炼可复用的编码规范(特别是 Critical/Warning 级反复出现的模式)
|
|
113
|
+
2. 如有值得记录的经验 → 草拟 Spec 条目,展示给用户确认后追加到 `.ccg/spec/{domain}/index.md`
|
|
114
|
+
3. 无值得提炼的经验 → 跳过
|
|
115
|
+
|
|
109
116
|
---
|
|
110
117
|
|
|
111
118
|
## 铁律
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// Injects spec + task context when:
|
|
4
4
|
// 1. codeagent-wrapper is about to be called (Bash)
|
|
5
5
|
// 2. Agent Team member is about to be spawned (Agent)
|
|
6
|
+
// Supports role-based filtering: context.jsonl entries with "roles" field
|
|
7
|
+
// are only injected when the agent's detected role matches.
|
|
6
8
|
|
|
7
9
|
'use strict';
|
|
8
10
|
|
|
@@ -28,12 +30,11 @@ try {
|
|
|
28
30
|
// Determine trigger type
|
|
29
31
|
const command = toolInput.command || '';
|
|
30
32
|
const teamName = toolInput.team_name || '';
|
|
31
|
-
const
|
|
33
|
+
const agentName = toolInput.name || '';
|
|
32
34
|
|
|
33
35
|
const isCodeagentCall = command.includes('codeagent-wrapper');
|
|
34
36
|
const isTeamSpawn = !!teamName;
|
|
35
37
|
|
|
36
|
-
// Only activate for codeagent-wrapper calls or Agent Team spawns
|
|
37
38
|
if (!isCodeagentCall && !isTeamSpawn) {
|
|
38
39
|
process.exit(0);
|
|
39
40
|
}
|
|
@@ -45,20 +46,60 @@ try {
|
|
|
45
46
|
const task = getActiveTask(root);
|
|
46
47
|
if (!task) process.exit(0);
|
|
47
48
|
|
|
49
|
+
// --- Role detection ---
|
|
50
|
+
const ROLE_FILE_MAP = {
|
|
51
|
+
'reviewer': 'review',
|
|
52
|
+
'analyzer': 'research',
|
|
53
|
+
'debugger': 'debug',
|
|
54
|
+
'tester': 'review',
|
|
55
|
+
'architect': 'implement',
|
|
56
|
+
'optimizer': 'implement',
|
|
57
|
+
'frontend': 'implement'
|
|
58
|
+
};
|
|
59
|
+
const AGENT_NAME_PATTERNS = [
|
|
60
|
+
{ pattern: /dev|builder|fix|impl/i, role: 'implement' },
|
|
61
|
+
{ pattern: /review|check|audit/i, role: 'review' },
|
|
62
|
+
{ pattern: /research|scout|explore|analy/i, role: 'research' },
|
|
63
|
+
{ pattern: /debug|diagnos/i, role: 'debug' }
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
let detectedRole = 'implement'; // default
|
|
67
|
+
|
|
68
|
+
if (isCodeagentCall) {
|
|
69
|
+
const roleMatch = command.match(/ROLE_FILE:.*\/(\w+)\.md/);
|
|
70
|
+
if (roleMatch) {
|
|
71
|
+
detectedRole = ROLE_FILE_MAP[roleMatch[1]] || 'implement';
|
|
72
|
+
}
|
|
73
|
+
} else if (isTeamSpawn && agentName) {
|
|
74
|
+
for (const { pattern, role } of AGENT_NAME_PATTERNS) {
|
|
75
|
+
if (pattern.test(agentName)) {
|
|
76
|
+
detectedRole = role;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
48
82
|
const contextParts = [];
|
|
49
83
|
|
|
50
|
-
// Inject active task info for team members
|
|
51
84
|
if (isTeamSpawn) {
|
|
52
85
|
contextParts.push(`<ccg-active-task>
|
|
53
86
|
Active task: ${task.dir}
|
|
54
87
|
Task: ${task.title || task.id} (${task.status})
|
|
55
88
|
Strategy: ${task.strategy}
|
|
56
89
|
Phase: ${task.currentPhase}
|
|
90
|
+
Agent role: ${detectedRole}
|
|
57
91
|
</ccg-active-task>`);
|
|
58
92
|
}
|
|
59
93
|
|
|
60
|
-
// Read context.jsonl
|
|
61
|
-
const
|
|
94
|
+
// Read context.jsonl with role-based filtering
|
|
95
|
+
const allEntries = readContextJsonl(task.dir);
|
|
96
|
+
const entries = allEntries.filter(entry => {
|
|
97
|
+
if (!entry.roles || !Array.isArray(entry.roles) || entry.roles.length === 0) {
|
|
98
|
+
return true; // no roles field = inject to all
|
|
99
|
+
}
|
|
100
|
+
return entry.roles.includes(detectedRole) || entry.roles.includes('all');
|
|
101
|
+
});
|
|
102
|
+
|
|
62
103
|
if (entries.length > 0) {
|
|
63
104
|
const specContents = [];
|
|
64
105
|
for (const entry of entries) {
|
|
@@ -75,7 +116,7 @@ Phase: ${task.currentPhase}
|
|
|
75
116
|
}
|
|
76
117
|
}
|
|
77
118
|
|
|
78
|
-
// Read PRD and plan
|
|
119
|
+
// Read PRD and plan (always inject, role-independent)
|
|
79
120
|
const prd = readFileSafe(path.join(task.dir, 'requirements.md'));
|
|
80
121
|
const plan = readFileSafe(path.join(task.dir, 'plan.md'));
|
|
81
122
|
|
|
@@ -93,21 +134,23 @@ Phase: ${task.currentPhase}
|
|
|
93
134
|
contextParts.push(taskContext.join('\n'));
|
|
94
135
|
}
|
|
95
136
|
|
|
96
|
-
// Read research files
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
137
|
+
// Read research files (only for research + implement roles)
|
|
138
|
+
if (detectedRole === 'research' || detectedRole === 'implement') {
|
|
139
|
+
const researchDir = path.join(task.dir, 'research');
|
|
140
|
+
if (fs.existsSync(researchDir)) {
|
|
141
|
+
try {
|
|
142
|
+
const researchFiles = fs.readdirSync(researchDir).filter(f => f.endsWith('.md'));
|
|
143
|
+
if (researchFiles.length > 0) {
|
|
144
|
+
const researchContents = researchFiles.map(f => {
|
|
145
|
+
const content = readFileSafe(path.join(researchDir, f));
|
|
146
|
+
return content ? `--- research/${f} ---\n${content.substring(0, 1500)}` : null;
|
|
147
|
+
}).filter(Boolean);
|
|
148
|
+
if (researchContents.length > 0) {
|
|
149
|
+
contextParts.push(`<ccg-research>\n${researchContents.join('\n\n')}\n</ccg-research>`);
|
|
150
|
+
}
|
|
108
151
|
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
152
|
+
} catch { /* silent */ }
|
|
153
|
+
}
|
|
111
154
|
}
|
|
112
155
|
|
|
113
156
|
if (contextParts.length === 0) process.exit(0);
|
|
@@ -25,9 +25,10 @@ function getActiveTask(projectRoot) {
|
|
|
25
25
|
try {
|
|
26
26
|
const dirs = fs.readdirSync(tasksDir)
|
|
27
27
|
.filter(d => {
|
|
28
|
+
if (d === 'archive') return false;
|
|
28
29
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
const full = path.join(tasksDir, d);
|
|
31
|
+
return fs.statSync(full).isDirectory() && fs.existsSync(path.join(full, 'task.json'));
|
|
31
32
|
} catch { return false; }
|
|
32
33
|
})
|
|
33
34
|
.sort()
|
|
@@ -35,10 +36,12 @@ function getActiveTask(projectRoot) {
|
|
|
35
36
|
|
|
36
37
|
for (const dir of dirs) {
|
|
37
38
|
try {
|
|
38
|
-
const
|
|
39
|
+
const taskPath = path.join(tasksDir, dir, 'task.json');
|
|
40
|
+
if (!fs.existsSync(taskPath)) continue; // stale pointer detection
|
|
41
|
+
const raw = fs.readFileSync(taskPath, 'utf-8');
|
|
39
42
|
const task = JSON.parse(raw);
|
|
40
43
|
if (task.status !== 'completed' && task.status !== 'archived') {
|
|
41
|
-
return { dir: path.join(tasksDir, dir), ...task };
|
|
44
|
+
return { dir: path.join(tasksDir, dir), ...task, _stale: false };
|
|
42
45
|
}
|
|
43
46
|
} catch { /* skip malformed */ }
|
|
44
47
|
}
|
|
@@ -101,6 +104,75 @@ function outputHook(eventName, additionalContext) {
|
|
|
101
104
|
}));
|
|
102
105
|
}
|
|
103
106
|
|
|
107
|
+
function archiveTask(taskDir, projectRoot) {
|
|
108
|
+
try {
|
|
109
|
+
const now = new Date();
|
|
110
|
+
const month = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
|
111
|
+
const archiveDir = path.join(projectRoot, '.ccg', 'tasks', 'archive', month);
|
|
112
|
+
if (!fs.existsSync(archiveDir)) fs.mkdirSync(archiveDir, { recursive: true });
|
|
113
|
+
const name = path.basename(taskDir);
|
|
114
|
+
const dest = path.join(archiveDir, name);
|
|
115
|
+
fs.renameSync(taskDir, dest);
|
|
116
|
+
return dest;
|
|
117
|
+
} catch { return null; }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function autoCommitTask(projectRoot, message) {
|
|
121
|
+
try {
|
|
122
|
+
const { execSync } = require('child_process');
|
|
123
|
+
execSync('git add .ccg/tasks/', { cwd: projectRoot, stdio: 'pipe' });
|
|
124
|
+
const diff = execSync('git diff --cached --quiet', { cwd: projectRoot, stdio: 'pipe' }).toString();
|
|
125
|
+
return false; // nothing to commit
|
|
126
|
+
} catch {
|
|
127
|
+
try {
|
|
128
|
+
const { execSync } = require('child_process');
|
|
129
|
+
execSync(`git commit -m "${message || 'chore: archive ccg task'}"`, { cwd: projectRoot, stdio: 'pipe' });
|
|
130
|
+
return true;
|
|
131
|
+
} catch { return false; }
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function seedContextJsonl(taskDir, projectRoot) {
|
|
136
|
+
const jsonlPath = path.join(taskDir, 'context.jsonl');
|
|
137
|
+
if (fs.existsSync(jsonlPath)) return;
|
|
138
|
+
const specDir = path.join(projectRoot, '.ccg', 'spec');
|
|
139
|
+
const lines = ['{"_example": "Fill with {\\\"file\\\": \\\"path\\\", \\\"reason\\\": \\\"why\\\"}. One entry per line. Seed rows (with _example key) are skipped."}'];
|
|
140
|
+
if (fs.existsSync(specDir)) {
|
|
141
|
+
try {
|
|
142
|
+
const walk = (dir, prefix) => {
|
|
143
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
144
|
+
const rel = prefix ? `${prefix}/${e.name}` : e.name;
|
|
145
|
+
if (e.isDirectory()) walk(path.join(dir, e.name), rel);
|
|
146
|
+
else if (e.name.endsWith('.md')) lines.push(JSON.stringify({ file: `.ccg/spec/${rel}`, reason: 'project spec' }));
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
walk(specDir, '');
|
|
150
|
+
} catch { /* silent */ }
|
|
151
|
+
}
|
|
152
|
+
try { fs.writeFileSync(jsonlPath, lines.join('\n') + '\n', 'utf-8'); } catch { /* silent */ }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function trackTurn(taskDir, phase, nextAction) {
|
|
156
|
+
const turnsPath = path.join(taskDir, '.turns.json');
|
|
157
|
+
let turns = [];
|
|
158
|
+
try { turns = JSON.parse(fs.readFileSync(turnsPath, 'utf-8')); } catch { /* fresh */ }
|
|
159
|
+
turns.push({ phase: phase || '', next: nextAction || '', ts: Date.now() });
|
|
160
|
+
if (turns.length > 10) turns = turns.slice(-10);
|
|
161
|
+
try { fs.writeFileSync(turnsPath, JSON.stringify(turns), 'utf-8'); } catch { /* silent */ }
|
|
162
|
+
return turns;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function detectLoop(turns, threshold) {
|
|
166
|
+
threshold = threshold || 3;
|
|
167
|
+
if (turns.length < threshold) return null;
|
|
168
|
+
const recent = turns.slice(-threshold);
|
|
169
|
+
const key = `${recent[0].phase}|${recent[0].next}`;
|
|
170
|
+
const allSame = recent.every(t => `${t.phase}|${t.next}` === key);
|
|
171
|
+
if (!allSame) return null;
|
|
172
|
+
const elapsed = (recent[recent.length - 1].ts - recent[0].ts) / 1000;
|
|
173
|
+
return { phase: recent[0].phase, nextAction: recent[0].next, count: threshold, elapsedSec: Math.round(elapsed) };
|
|
174
|
+
}
|
|
175
|
+
|
|
104
176
|
module.exports = {
|
|
105
177
|
findProjectRoot,
|
|
106
178
|
getActiveTask,
|
|
@@ -109,5 +181,10 @@ module.exports = {
|
|
|
109
181
|
readContextJsonl,
|
|
110
182
|
detectTechStack,
|
|
111
183
|
getGitInfo,
|
|
112
|
-
outputHook
|
|
184
|
+
outputHook,
|
|
185
|
+
archiveTask,
|
|
186
|
+
autoCommitTask,
|
|
187
|
+
seedContextJsonl,
|
|
188
|
+
trackTurn,
|
|
189
|
+
detectLoop
|
|
113
190
|
};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// CCG Workflow State Hook — UserPromptSubmit
|
|
3
3
|
// Injects per-turn breadcrumb based on active task state.
|
|
4
|
+
// Includes loop detection: warns when same phase+nextAction repeats 3+ turns.
|
|
4
5
|
// Runs on EVERY user message. Must be fast (<1s) and never crash.
|
|
5
6
|
|
|
6
7
|
'use strict';
|
|
7
8
|
|
|
8
9
|
try {
|
|
9
|
-
const { findProjectRoot, getActiveTask, outputHook } = require('./task-utils.js');
|
|
10
|
+
const { findProjectRoot, getActiveTask, outputHook, trackTurn, detectLoop } = require('./task-utils.js');
|
|
10
11
|
|
|
11
12
|
const cwd = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
12
13
|
const root = findProjectRoot(cwd);
|
|
@@ -19,6 +20,9 @@ try {
|
|
|
19
20
|
process.exit(0);
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
const turns = trackTurn(task.dir, task.currentPhase, task.nextAction);
|
|
24
|
+
const loop = detectLoop(turns, 3);
|
|
25
|
+
|
|
22
26
|
const lines = [
|
|
23
27
|
'<ccg-state>',
|
|
24
28
|
`Task: ${task.title || task.id} (${task.status})`,
|
|
@@ -31,6 +35,18 @@ try {
|
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
lines.push(`Next: ${task.nextAction || 'Continue current phase'}`);
|
|
38
|
+
|
|
39
|
+
if (loop) {
|
|
40
|
+
lines.push('');
|
|
41
|
+
lines.push(`⚠️ LOOP DETECTED: Phase "${loop.phase}" with same nextAction repeated ${loop.count} turns (${loop.elapsedSec}s).`);
|
|
42
|
+
lines.push('🔄 BREAK-LOOP PROTOCOL:');
|
|
43
|
+
lines.push(' 1. STOP current approach immediately');
|
|
44
|
+
lines.push(' 2. Root-cause analysis: why is this phase not progressing?');
|
|
45
|
+
lines.push(' 3. Options: (a) try alternative approach, (b) escalate to user, (c) upgrade strategy');
|
|
46
|
+
lines.push(' 4. If blocked by external dependency → tell user explicitly');
|
|
47
|
+
lines.push(' 5. Do NOT repeat the same action — that is what caused this loop');
|
|
48
|
+
}
|
|
49
|
+
|
|
34
50
|
lines.push('</ccg-state>');
|
|
35
51
|
|
|
36
52
|
outputHook('UserPromptSubmit', lines.join('\n'));
|
|
@@ -4,7 +4,11 @@ When the user's request matches trigger keywords below, automatically READ the c
|
|
|
4
4
|
|
|
5
5
|
**IMPORTANT**: Read the skill file FIRST, then respond. Do NOT fabricate domain knowledge from training data when a skill file exists.
|
|
6
6
|
|
|
7
|
-
## Security Domain (`domains/security/`)
|
|
7
|
+
## Security Domain (`domains/security/`) — NOT installed by default
|
|
8
|
+
|
|
9
|
+
> Security domain files contain red team/pentest reference content that may trigger antivirus false positives.
|
|
10
|
+
> They are NOT installed by default. To enable, manually copy from the npm package:
|
|
11
|
+
> `cp -r $(npm root -g)/ccg-workflow/templates/skills/domains/security/ ~/.claude/skills/ccg/domains/security/`
|
|
8
12
|
|
|
9
13
|
| Trigger Keywords | Skill File | Description |
|
|
10
14
|
|------------------|-----------|-------------|
|