ccgx-workflow 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.mjs +180 -2
- package/dist/index.mjs +3 -2
- package/dist/shared/{ccgx-workflow.Bl0vlpC_.mjs → ccgx-workflow.BrRMgXjP.mjs} +22 -13
- package/package.json +1 -1
- package/templates/commands/agents/code-fixer.md +1 -1
- package/templates/commands/agents/codebase-mapper.md +1 -1
- package/templates/commands/agents/debug-session-manager.md +1 -1
- package/templates/commands/agents/debugger.md +1 -1
- package/templates/commands/agents/interface-auditor.md +40 -12
- package/templates/commands/agents/phase-runner.md +27 -27
- package/templates/commands/agents/plan-checker.md +4 -4
- package/templates/commands/analyze.md +10 -10
- package/templates/commands/autonomous.md +45 -46
- package/templates/commands/cancel.md +8 -8
- package/templates/commands/codex-exec.md +2 -2
- package/templates/commands/debate.md +5 -5
- package/templates/commands/debug.md +8 -8
- package/templates/commands/execute.md +6 -6
- package/templates/commands/init.md +1 -1
- package/templates/commands/optimize.md +10 -10
- package/templates/commands/plan.md +15 -15
- package/templates/commands/result.md +1 -1
- package/templates/commands/review.md +120 -65
- package/templates/commands/spec-impl.md +1 -1
- package/templates/commands/spec-plan.md +2 -2
- package/templates/commands/spec-research.md +1 -1
- package/templates/commands/spec-review.md +1 -1
- package/templates/commands/status.md +15 -15
- package/templates/commands/team-exec.md +1 -1
- package/templates/commands/team.md +6 -6
- package/templates/commands/test.md +9 -9
- package/templates/commands/verify-work.md +8 -8
- package/templates/commands/verify.md +3 -3
- package/templates/commands/workflow.md +2 -2
- package/templates/rules/ccg-skills.md +1 -1
- package/templates/scripts/ccgx-call-plugin.mjs +324 -0
- package/templates/skills/tools/extract-learnings/SKILL.md +1 -3
- package/templates/skills/tools/forensics/SKILL.md +0 -2
- package/templates/skills/tools/health/SKILL.md +0 -2
- package/templates/skills/tools/map-codebase/SKILL.md +0 -2
- package/templates/skills/tools/verify-change/SKILL.md +2 -2
- package/templates/skills/tools/verify-module/SKILL.md +2 -2
- package/templates/skills/tools/verify-quality/SKILL.md +2 -2
- package/templates/skills/tools/verify-security/SKILL.md +2 -2
|
@@ -3,16 +3,16 @@ description: 'Agent Teams 8 阶段企业级工作流 - 7 角色全流程统一
|
|
|
3
3
|
---
|
|
4
4
|
<!-- CCG:TEAM:UNIFIED:START -->
|
|
5
5
|
|
|
6
|
-
##
|
|
6
|
+
## 子命令路由
|
|
7
7
|
|
|
8
8
|
`/ccg:team` 同时承载子命令调度。根据 `$ARGUMENTS` 第一个 token 路由到具体阶段:
|
|
9
9
|
|
|
10
10
|
| 子命令 | 含义 | 替代旧命令 |
|
|
11
11
|
|--------|------|----------|
|
|
12
12
|
| `/ccg:team` (无参 / 任意非保留字) | 8 阶段全流程 | (主流程) |
|
|
13
|
-
| `/ccg:team research <需求>` | 仅跑需求研究阶段 | `/ccg:team-research
|
|
14
|
-
| `/ccg:team plan <约束文件>` | 仅跑规划阶段 | `/ccg:team-plan
|
|
15
|
-
| `/ccg:team review [git-range]` | 仅跑双模型审查阶段 | `/ccg:team-review
|
|
13
|
+
| `/ccg:team research <需求>` | 仅跑需求研究阶段 | `/ccg:team-research`(已删除) |
|
|
14
|
+
| `/ccg:team plan <约束文件>` | 仅跑规划阶段 | `/ccg:team-plan`(已删除) |
|
|
15
|
+
| `/ccg:team review [git-range]` | 仅跑双模型审查阶段 | `/ccg:team-review`(已删除) |
|
|
16
16
|
| `/ccg:team exec <plan-file>` | 仅跑并行实施阶段 | 等价 `/ccg:team-exec` |
|
|
17
17
|
|
|
18
18
|
> **路由规则**:将 `$ARGUMENTS` 拆分为 `[subcmd, ...rest]`。若 `subcmd ∈ {research, plan, review, exec}`,跳到对应 Phase(Research → Phase 1 / plan → Phase 3 / review → Phase 6 / exec → Phase 4);否则走完整 Phase 0-8。
|
|
@@ -178,7 +178,7 @@ Phase 8: INTEGRATION → Lead 全量验证 + 报告 + 清理
|
|
|
178
178
|
})
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
**事件驱动等待**:spawn 两个 Bash bg 后说明 task-id 然后 **turn end**。引擎在每个 task 完成时自动发 `<task-notification>`,主线在通知触发的新 turn 处理结果。**不调 TaskOutput**。两个 task 都收到通知后才进下一步。
|
|
182
182
|
|
|
183
183
|
⛔ **禁止**:调 `TaskOutput({block: true, timeout: 600000})` (旧 freeze poll 模式) / Kill task。
|
|
184
184
|
⚠️ **失败处理**:notification status=failed / exit ≠ 0 / parse 失败 → v1.7.87 标准 2-retry / 5s / 3-attempts;3 次全失败才降级单模型。
|
|
@@ -330,7 +330,7 @@ Phase 8: INTEGRATION → Lead 全量验证 + 报告 + 清理
|
|
|
330
330
|
})
|
|
331
331
|
```
|
|
332
332
|
|
|
333
|
-
|
|
333
|
+
**事件驱动等待**:spawn 两个 Bash bg 后说明 task-id 然后 **turn end**。引擎在每个 task 完成时自动发 `<task-notification>`,主线在通知触发的新 turn 处理。**不调 TaskOutput**。两个 task 都收到通知才进 step 3。
|
|
334
334
|
|
|
335
335
|
⛔ **禁止**:调 `TaskOutput({block: true, timeout: 600000})` (旧 freeze poll 模式) / Kill task。
|
|
336
336
|
⚠️ **失败处理**:notification status=failed / exit ≠ 0 / parse 失败 → v1.7.87 标准 2-retry / 5s / 3-attempts;3 次全失败才降级单模型。
|
|
@@ -13,7 +13,7 @@ argument-hint: "<测试目标> [--role=architect|critic|implementer|tester|write
|
|
|
13
13
|
/test <测试目标> [--role=<name>]
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
## Role-based routing(
|
|
16
|
+
## Role-based routing(specialist matrix)
|
|
17
17
|
|
|
18
18
|
可选 `--role=<name>` 叠加 role 维度路由(默认 `tester`,可显式覆盖):
|
|
19
19
|
|
|
@@ -23,7 +23,7 @@ argument-hint: "<测试目标> [--role=architect|critic|implementer|tester|write
|
|
|
23
23
|
| **frontend** | gemini/architect.md | gemini/reviewer.md (adversarial) | gemini/architect.md | gemini/tester.md | gemini/analyzer.md |
|
|
24
24
|
| **fullstack** | codex+gemini/architect.md | both reviewer.md (adversarial) | runner 决(per-file) | runner 决(per-file) | claude |
|
|
25
25
|
|
|
26
|
-
**未传 --role
|
|
26
|
+
**未传 --role 时按智能路由(后端 → {{BACKEND_PRIMARY}}/tester.md,前端 → {{FRONTEND_PRIMARY}}/tester.md,全栈并行),完全兼容**。`--role=critic` 触发"测试用例反例挖掘"——专门构造打破现有测试假设的边界条件。详见 `src/utils/specialist-router.ts`。
|
|
27
27
|
|
|
28
28
|
## 上下文
|
|
29
29
|
|
|
@@ -40,12 +40,12 @@ argument-hint: "<测试目标> [--role=architect|critic|implementer|tester|write
|
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
43
|
-
## 调用通道路由(
|
|
43
|
+
## 调用通道路由(CCG codeagent 退役)
|
|
44
44
|
|
|
45
|
-
CCG
|
|
45
|
+
CCG 把双模型并行通道从 `Bash(codeagent-wrapper)` **默认切换**为 plugin spawn:
|
|
46
46
|
|
|
47
47
|
1. **优先 plugin spawn**(默认):装了 `codex@openai-codex` + `gemini@google-gemini` plugin → 用 `Agent(subagent_type="codex:codex-rescue")` + `Agent(subagent_type="gemini:gemini-rescue")` 并行,主线接 ≤200 token 摘要。
|
|
48
|
-
2. **降级 codeagent-wrapper**(
|
|
48
|
+
2. **降级 codeagent-wrapper**(BC fallback):plugin 未装 → fallback 到 Bash 调用,行为与 plugin 路径等价。
|
|
49
49
|
|
|
50
50
|
**判定**:preflight `Bash` 跑 `ls ~/.claude/plugins/` 看有无 `codex@*` / `gemini@*` 子目录。helper 见 `src/utils/plugin-detection.ts`。
|
|
51
51
|
|
|
@@ -60,7 +60,7 @@ CCG v4.1 把双模型并行通道从 `Bash(codeagent-wrapper)` **默认切换**
|
|
|
60
60
|
- 如果用户通过 `/add-dir` 添加了多个工作区,先用 Glob/Grep 确定任务相关的工作区
|
|
61
61
|
- 如果无法确定,用 `AskUserQuestion` 询问用户选择目标工作区
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
**调用语法**(双通道):
|
|
64
64
|
|
|
65
65
|
**通道 A — plugin spawn(默认)**:
|
|
66
66
|
|
|
@@ -109,7 +109,7 @@ EOF",
|
|
|
109
109
|
})
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
> ⚠️ 通道 B `codeagent-wrapper`
|
|
112
|
+
> ⚠️ 通道 B `codeagent-wrapper` 已标 **deprecated**。
|
|
113
113
|
|
|
114
114
|
**角色提示词**:
|
|
115
115
|
|
|
@@ -126,7 +126,7 @@ EOF",
|
|
|
126
126
|
| 前端 | {{FRONTEND_PRIMARY}} |
|
|
127
127
|
| 全栈 | 并行执行两者 |
|
|
128
128
|
|
|
129
|
-
**并行调用 +
|
|
129
|
+
**并行调用 + 事件驱动等待**:
|
|
130
130
|
|
|
131
131
|
1. 同 message 内 spawn 多个 `Bash(run_in_background: true)` 并行任务
|
|
132
132
|
2. spawn 完后主线说明已启动 task-id,**直接 turn end**,**不调 TaskOutput**
|
|
@@ -135,7 +135,7 @@ EOF",
|
|
|
135
135
|
5. **必须等所有相关 task 都收到通知**才进入下一阶段(按 task-id 计数已收齐)
|
|
136
136
|
|
|
137
137
|
⛔ **禁止**:
|
|
138
|
-
- 调 `TaskOutput({block: true, timeout: 600000})` ——
|
|
138
|
+
- 调 `TaskOutput({block: true, timeout: 600000})` —— 旧 freeze poll 模式,已废弃
|
|
139
139
|
- 收到部分通知就跳过等其他模型
|
|
140
140
|
- 主动 Kill task
|
|
141
141
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ccg:verify-work
|
|
3
|
-
description: 会话式 UAT 工作流 - UAT.md 状态文件 + cold-start smoke 自动注入 + 自动 diagnose-plan-fix
|
|
3
|
+
description: 会话式 UAT 工作流 - UAT.md 状态文件 + cold-start smoke 自动注入 + 自动 diagnose-plan-fix 收敛环
|
|
4
4
|
argument-hint: "[task-id]"
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- Read
|
|
@@ -13,23 +13,23 @@ allowed-tools:
|
|
|
13
13
|
- Agent
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
# Verify Work — 会话式 UAT
|
|
16
|
+
# Verify Work — 会话式 UAT 工作流
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
早期的 verify-work 是纯**编排器**——按变更性质开 verify-{module,security,quality,change} 子门,跑完聚合报告。但它**没法做真正的 UAT**:
|
|
19
19
|
|
|
20
20
|
1. 用户得自己拿着报告人肉对照"这事儿到底验没验过";
|
|
21
21
|
2. `/clear` 后所有上下文丢失,UAT 进度归零;
|
|
22
22
|
3. 只看代码不跑冷启动——race condition / silent seed failure / 缺环境变量在生产才暴露;
|
|
23
23
|
4. 用户报 issue 后没有自动收敛环——靠用户手动来回贴报告。
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
当前 verify-work 是**有状态的会话工作流**:
|
|
26
26
|
|
|
27
27
|
- **UAT.md frontmatter 状态文件**:跨 `/clear` 持久化,下次进入命令自动 resume;
|
|
28
28
|
- **逐项核对**(show expected → ask if matches):每条期望行为都明示问,不让模糊滑过;
|
|
29
29
|
- **Cold-start smoke 自动注入**:扫 git diff 命中关键路径即注入"杀进程 → 清临时态 → 冷启动 → 主查询返回数据"测试;
|
|
30
30
|
- **自动 diagnose → planner --gaps → plan-checker 收敛环**(max 3 轮):用户报 issue 立即触发,无需手动调度。
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
**多门聚合保留**:早期的多门聚合不删,迁移为 Step 2(verify-* 子门作为静态扫描)。会话循环裹在外层(Step 0/1/3/4/5)。
|
|
33
33
|
|
|
34
34
|
---
|
|
35
35
|
|
|
@@ -132,9 +132,9 @@ fi
|
|
|
132
132
|
|
|
133
133
|
### Step 2 — 静态门 + cold-start smoke 注入
|
|
134
134
|
|
|
135
|
-
#### 2a.
|
|
135
|
+
#### 2a. 静态扫描(多门保留)
|
|
136
136
|
|
|
137
|
-
按 git diff
|
|
137
|
+
按 git diff 性质开门(决策矩阵):
|
|
138
138
|
|
|
139
139
|
| 变更性质 | 触发判据 | 门组 |
|
|
140
140
|
|---------|---------|------|
|
|
@@ -317,7 +317,7 @@ else:
|
|
|
317
317
|
| verify-change | Step 2a 静态门,常规改动必跑 |
|
|
318
318
|
| verifier agent | Step 2a 末尾兜底,做需求矩阵反向溯源(v4 Phase 8 加 Level 4 数据流) |
|
|
319
319
|
|
|
320
|
-
## 与
|
|
320
|
+
## 与 helper 的契约
|
|
321
321
|
|
|
322
322
|
| Helper | 用途 |
|
|
323
323
|
|--------|------|
|
|
@@ -9,9 +9,9 @@ allowed-tools:
|
|
|
9
9
|
- Agent
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
# /ccg:verify -
|
|
12
|
+
# /ccg:verify - 统一校验关卡
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
替代早期的 4 个独立命令 `/ccg:verify-{change,quality,security,module}`,统一入口 + 子门路由,降低命令面板认知负担。
|
|
15
15
|
|
|
16
16
|
## 使用方法
|
|
17
17
|
|
|
@@ -48,7 +48,7 @@ $ARGUMENTS
|
|
|
48
48
|
|
|
49
49
|
## 兼容性
|
|
50
50
|
|
|
51
|
-
旧的 `/ccg:verify-change` / `/ccg:verify-quality` / `/ccg:verify-security` / `/ccg:verify-module` 仍可工作(由 Skill Registry 自动生成),但 SKILL.md 已标记 `deprecated_in:
|
|
51
|
+
旧的 `/ccg:verify-change` / `/ccg:verify-quality` / `/ccg:verify-security` / `/ccg:verify-module` 仍可工作(由 Skill Registry 自动生成),但 SKILL.md 已标记 `deprecated_in: 1.0.0`、`replaced_by: /ccg:verify --gate=<name>`。建议新工作流使用本统一命令。
|
|
52
52
|
|
|
53
53
|
## 执行流程
|
|
54
54
|
|
|
@@ -83,7 +83,7 @@ EOF",
|
|
|
83
83
|
|
|
84
84
|
**会话复用**:每次调用返回 `SESSION_ID: xxx`,后续阶段用 `resume xxx` 复用上下文(注意:是 `resume`,不是 `--resume`)。
|
|
85
85
|
|
|
86
|
-
**并行调用 +
|
|
86
|
+
**并行调用 + 事件驱动等待**:
|
|
87
87
|
|
|
88
88
|
1. 同 message 内 spawn 多个 `Bash(run_in_background: true)` 并行任务
|
|
89
89
|
2. spawn 完后主线说明已启动 task-id,**直接 turn end**,**不调 TaskOutput**
|
|
@@ -92,7 +92,7 @@ EOF",
|
|
|
92
92
|
5. **必须等所有相关 task 都收到通知**才进入下一阶段(按 task-id 计数已收齐)
|
|
93
93
|
|
|
94
94
|
⛔ **禁止**:
|
|
95
|
-
- 调 `TaskOutput({block: true, timeout: 600000})` ——
|
|
95
|
+
- 调 `TaskOutput({block: true, timeout: 600000})` —— 旧 freeze poll 模式,已废弃
|
|
96
96
|
- 收到部分通知就跳过等其他模型
|
|
97
97
|
- 主动 Kill task
|
|
98
98
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
When working in a project, automatically invoke the corresponding quality gate skills based on the scenario below. These skills are installed at `~/.claude/skills/ccg/` and can be called directly.
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**NOTE**: The unified entry point `/ccg:verify --gate=<name>` is preferred over the legacy `verify-*` skill names. Both still work for BC. Examples below show the new form first, with the legacy alias in parentheses.
|
|
6
6
|
|
|
7
7
|
## Trigger Rules
|
|
8
8
|
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// ccgx-call-plugin.mjs 1.0.5
|
|
4
|
+
// -----------------------------------------------------------------------------
|
|
5
|
+
// Pure-Node helper for invoking codex/gemini plugin companions WITHOUT any
|
|
6
|
+
// shell-escape risk. Replaces the 1.0.4 heredoc-via-Bash pattern.
|
|
7
|
+
//
|
|
8
|
+
// Why this exists (1.0.5 design after 1.0.4 dogfood):
|
|
9
|
+
// LLM-constructed Bash commands proved unreliable. Two failure modes hit
|
|
10
|
+
// in 1.0.4:
|
|
11
|
+
// 1. LLM cargo-culted anti-example code from review.md docs
|
|
12
|
+
// 2. LLM in actual review session still wrote `ls $(...) | head -1`
|
|
13
|
+
// glob-hack patterns despite placeholder system
|
|
14
|
+
//
|
|
15
|
+
// Root cause: any design that asks the LLM to construct or substitute parts
|
|
16
|
+
// of a shell command has X% failure rate. X varies but is never zero.
|
|
17
|
+
//
|
|
18
|
+
// Fix: collapse the LLM surface to "choose vendor + pass prompt-file path".
|
|
19
|
+
// All path resolution, flag construction, shell-quote-avoidance are done
|
|
20
|
+
// internally by Node spawn with array args (no shell).
|
|
21
|
+
//
|
|
22
|
+
// Usage (LLM workflow):
|
|
23
|
+
// 1. Write prompt body to a temp file (via Write tool):
|
|
24
|
+
// /tmp/ccg-codex-1234.txt
|
|
25
|
+
// 2. Run helper via Bash:
|
|
26
|
+
// node ~/.claude/.ccg/scripts/ccgx-call-plugin.mjs codex \
|
|
27
|
+
// --prompt-file /tmp/ccg-codex-1234.txt
|
|
28
|
+
// 3. Parse the JSON output emitted to stdout
|
|
29
|
+
//
|
|
30
|
+
// Output schema (always JSON, even on error):
|
|
31
|
+
// {
|
|
32
|
+
// "status": "ok" | "error",
|
|
33
|
+
// "vendor": "codex" | "gemini",
|
|
34
|
+
// "version": "<plugin version>",
|
|
35
|
+
// "durationMs": <number>,
|
|
36
|
+
// "exitCode": <number | null>,
|
|
37
|
+
// "stdout": "<companion stdout>",
|
|
38
|
+
// "stderr": "<companion stderr>",
|
|
39
|
+
// "error": "<error message if status=error, else absent>"
|
|
40
|
+
// }
|
|
41
|
+
//
|
|
42
|
+
// CLI args:
|
|
43
|
+
// <vendor> Required. 'codex' or 'gemini'.
|
|
44
|
+
// --prompt-file <path> Required. Path to file containing prompt body.
|
|
45
|
+
// --json Pass --json to companion (default: true).
|
|
46
|
+
// --no-json Disable --json (text output).
|
|
47
|
+
// --timeout-ms <N> Kill companion after N ms (default: 600000).
|
|
48
|
+
// --max-budget-usd <N> Forwarded to companion (default: 50).
|
|
49
|
+
//
|
|
50
|
+
// Cross-cutting:
|
|
51
|
+
// - Pure stdlib (fs, child_process, path, os). No deps.
|
|
52
|
+
// - Always emits valid JSON to stdout (so LLM can always JSON.parse).
|
|
53
|
+
// - Plugin path resolution via SSoT (~/.claude/plugins/installed_plugins.json).
|
|
54
|
+
// - spawn uses array args + windowsHide:true → zero shell escape surface.
|
|
55
|
+
// =============================================================================
|
|
56
|
+
|
|
57
|
+
import { spawn } from 'node:child_process'
|
|
58
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
59
|
+
import { homedir, platform } from 'node:os'
|
|
60
|
+
import { join } from 'node:path'
|
|
61
|
+
import { fileURLToPath } from 'node:url'
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Vendor → marketplace key (matches plugin-bash-codegen.ts SSoT lookup)
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
const VENDOR_KEYS = {
|
|
68
|
+
codex: 'codex@openai-codex',
|
|
69
|
+
gemini: 'gemini@google-gemini',
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Argument parsing — minimal, KISS, no external CLI lib.
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
function parseArgs(argv) {
|
|
77
|
+
const opts = {
|
|
78
|
+
vendor: null,
|
|
79
|
+
promptFile: null,
|
|
80
|
+
json: true,
|
|
81
|
+
timeoutMs: 600000,
|
|
82
|
+
maxBudgetUsd: 50,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const positional = []
|
|
86
|
+
for (let i = 2; i < argv.length; i++) {
|
|
87
|
+
const arg = argv[i]
|
|
88
|
+
const next = () => {
|
|
89
|
+
const v = argv[++i]
|
|
90
|
+
if (v === undefined) throw new Error(`flag ${arg} requires a value`)
|
|
91
|
+
return v
|
|
92
|
+
}
|
|
93
|
+
switch (arg) {
|
|
94
|
+
case '--prompt-file': opts.promptFile = next(); break
|
|
95
|
+
case '--json': opts.json = true; break
|
|
96
|
+
case '--no-json': opts.json = false; break
|
|
97
|
+
case '--timeout-ms': opts.timeoutMs = Number.parseInt(next(), 10); break
|
|
98
|
+
case '--max-budget-usd': opts.maxBudgetUsd = Number.parseFloat(next()); break
|
|
99
|
+
case '--help':
|
|
100
|
+
case '-h':
|
|
101
|
+
printHelp()
|
|
102
|
+
process.exit(0)
|
|
103
|
+
default:
|
|
104
|
+
if (arg.startsWith('--')) throw new Error(`unknown flag: ${arg}`)
|
|
105
|
+
positional.push(arg)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (positional.length === 0) throw new Error('vendor (codex|gemini) is required')
|
|
110
|
+
if (positional.length > 1) throw new Error(`too many positional args: ${positional.join(' ')}`)
|
|
111
|
+
opts.vendor = positional[0]
|
|
112
|
+
|
|
113
|
+
if (!VENDOR_KEYS[opts.vendor]) {
|
|
114
|
+
throw new Error(`unknown vendor: ${opts.vendor} (must be 'codex' or 'gemini')`)
|
|
115
|
+
}
|
|
116
|
+
if (!opts.promptFile) {
|
|
117
|
+
throw new Error('--prompt-file is required (path to file containing prompt body)')
|
|
118
|
+
}
|
|
119
|
+
return opts
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function printHelp() {
|
|
123
|
+
process.stderr.write(`Usage: ccgx-call-plugin.mjs <vendor> --prompt-file <path> [flags]
|
|
124
|
+
|
|
125
|
+
Required:
|
|
126
|
+
<vendor> 'codex' or 'gemini'
|
|
127
|
+
--prompt-file <path> File containing prompt body (any content, no escape needed)
|
|
128
|
+
|
|
129
|
+
Optional:
|
|
130
|
+
--json Pass --json to companion (default: true)
|
|
131
|
+
--no-json Disable --json (text output)
|
|
132
|
+
--timeout-ms <N> Kill companion after N ms (default: 600000)
|
|
133
|
+
--max-budget-usd <N> Per-call cost cap (default: 50)
|
|
134
|
+
|
|
135
|
+
Outputs JSON to stdout: {status, vendor, version, durationMs, exitCode, stdout, stderr, error?}
|
|
136
|
+
`)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// Plugin discovery (mirror of plugin-bash-codegen.ts:discoverCompanion)
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
function discoverCompanion(vendor, homeDir = homedir()) {
|
|
144
|
+
const ssotPath = join(homeDir, '.claude', 'plugins', 'installed_plugins.json')
|
|
145
|
+
if (!existsSync(ssotPath)) {
|
|
146
|
+
return { error: `installed_plugins.json not found at ${ssotPath}` }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let raw
|
|
150
|
+
try {
|
|
151
|
+
raw = JSON.parse(readFileSync(ssotPath, 'utf-8'))
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
return { error: `installed_plugins.json parse failed: ${e.message}` }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const key = VENDOR_KEYS[vendor]
|
|
158
|
+
const instances = raw?.plugins?.[key]
|
|
159
|
+
if (!Array.isArray(instances) || instances.length === 0) {
|
|
160
|
+
return { error: `plugin ${key} not installed (no entry in installed_plugins.json)` }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const inst = instances[0]
|
|
164
|
+
const installPath = inst?.installPath
|
|
165
|
+
if (typeof installPath !== 'string' || !installPath) {
|
|
166
|
+
return { error: `plugin ${key} has no installPath in installed_plugins.json` }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const companionPath = join(installPath, 'scripts', `${vendor}-companion.mjs`)
|
|
170
|
+
if (!existsSync(companionPath)) {
|
|
171
|
+
return { error: `companion script missing: ${companionPath}` }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
companionPath,
|
|
176
|
+
version: typeof inst?.version === 'string' ? inst.version : 'unknown',
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// Output emission — always JSON, never throws to stdout
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
function emitJson(result) {
|
|
185
|
+
process.stdout.write(`${JSON.stringify(result)}\n`)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function emitError(vendor, version, error, extra = {}) {
|
|
189
|
+
emitJson({
|
|
190
|
+
status: 'error',
|
|
191
|
+
vendor,
|
|
192
|
+
version,
|
|
193
|
+
durationMs: 0,
|
|
194
|
+
exitCode: null,
|
|
195
|
+
stdout: '',
|
|
196
|
+
stderr: '',
|
|
197
|
+
error,
|
|
198
|
+
...extra,
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// Main: spawn companion with array args (no shell)
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
async function main(argv) {
|
|
207
|
+
let opts
|
|
208
|
+
try {
|
|
209
|
+
opts = parseArgs(argv)
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
emitError(null, null, `arg parse: ${err.message}`)
|
|
213
|
+
process.exit(64) // EX_USAGE
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Read prompt body from file (no shell, no escape concerns)
|
|
217
|
+
let promptBody
|
|
218
|
+
try {
|
|
219
|
+
promptBody = readFileSync(opts.promptFile, 'utf-8')
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
emitError(opts.vendor, null, `prompt file read failed: ${err.message}`)
|
|
223
|
+
process.exit(66) // EX_NOINPUT
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Discover companion via SSoT (no glob, no head -1)
|
|
227
|
+
const disc = discoverCompanion(opts.vendor)
|
|
228
|
+
if (disc.error) {
|
|
229
|
+
emitError(opts.vendor, null, disc.error)
|
|
230
|
+
process.exit(69) // EX_UNAVAILABLE
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Build companion argv as ARRAY — no shell layer ever
|
|
234
|
+
const companionArgs = [
|
|
235
|
+
disc.companionPath,
|
|
236
|
+
'task',
|
|
237
|
+
'-p',
|
|
238
|
+
promptBody,
|
|
239
|
+
]
|
|
240
|
+
if (opts.json) companionArgs.push('--json')
|
|
241
|
+
|
|
242
|
+
const startedAt = Date.now()
|
|
243
|
+
|
|
244
|
+
// spawn 'node' with array args. Node will look up its own executable;
|
|
245
|
+
// companionPath is passed as a literal arg, no shell interpretation.
|
|
246
|
+
const child = spawn(process.execPath, companionArgs, {
|
|
247
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
248
|
+
windowsHide: true,
|
|
249
|
+
env: {
|
|
250
|
+
...process.env,
|
|
251
|
+
// Forward budget cap if companion respects it (codex does)
|
|
252
|
+
CODEX_MAX_BUDGET_USD: String(opts.maxBudgetUsd),
|
|
253
|
+
GEMINI_MAX_BUDGET_USD: String(opts.maxBudgetUsd),
|
|
254
|
+
},
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
let stdoutBuf = ''
|
|
258
|
+
let stderrBuf = ''
|
|
259
|
+
child.stdout.on('data', (chunk) => { stdoutBuf += chunk.toString('utf-8') })
|
|
260
|
+
child.stderr.on('data', (chunk) => { stderrBuf += chunk.toString('utf-8') })
|
|
261
|
+
|
|
262
|
+
// Timeout: kill child if exceeds opts.timeoutMs
|
|
263
|
+
let timedOut = false
|
|
264
|
+
const timer = setTimeout(() => {
|
|
265
|
+
timedOut = true
|
|
266
|
+
try { child.kill('SIGTERM') } catch { /* ignore */ }
|
|
267
|
+
setTimeout(() => {
|
|
268
|
+
try { child.kill('SIGKILL') } catch { /* ignore */ }
|
|
269
|
+
}, 5000).unref?.()
|
|
270
|
+
}, opts.timeoutMs)
|
|
271
|
+
|
|
272
|
+
const exit = await new Promise((resolve) => {
|
|
273
|
+
child.once('exit', (code, signal) => resolve({ code, signal }))
|
|
274
|
+
child.once('error', (err) => resolve({ code: 70, signal: null, errorMsg: err.message }))
|
|
275
|
+
})
|
|
276
|
+
clearTimeout(timer)
|
|
277
|
+
|
|
278
|
+
const durationMs = Date.now() - startedAt
|
|
279
|
+
const status = (!timedOut && exit.code === 0) ? 'ok' : 'error'
|
|
280
|
+
const result = {
|
|
281
|
+
status,
|
|
282
|
+
vendor: opts.vendor,
|
|
283
|
+
version: disc.version,
|
|
284
|
+
durationMs,
|
|
285
|
+
exitCode: exit.code,
|
|
286
|
+
stdout: stdoutBuf,
|
|
287
|
+
stderr: stderrBuf,
|
|
288
|
+
}
|
|
289
|
+
if (timedOut) result.error = `timeout after ${opts.timeoutMs}ms`
|
|
290
|
+
else if (exit.errorMsg) result.error = `spawn error: ${exit.errorMsg}`
|
|
291
|
+
else if (exit.code !== 0) result.error = `companion exited with code ${exit.code}`
|
|
292
|
+
|
|
293
|
+
emitJson(result)
|
|
294
|
+
process.exit(status === 'ok' ? 0 : 1)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
// Entry point — only run main() when invoked as a script (not on import).
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
|
|
301
|
+
function isMainModule() {
|
|
302
|
+
if (!process.argv[1]) return false
|
|
303
|
+
try {
|
|
304
|
+
const here = fileURLToPath(import.meta.url)
|
|
305
|
+
return here === process.argv[1]
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
return false
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (isMainModule()) {
|
|
313
|
+
main(process.argv).catch((err) => {
|
|
314
|
+
emitError(null, null, `fatal: ${err.stack || err.message}`)
|
|
315
|
+
process.exit(70)
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Test surface for unit tests
|
|
320
|
+
export const ccgxCallPluginExports = {
|
|
321
|
+
parseArgs,
|
|
322
|
+
discoverCompanion,
|
|
323
|
+
VENDOR_KEYS,
|
|
324
|
+
}
|
|
@@ -10,8 +10,6 @@ argument-hint: "[--since=30d]"
|
|
|
10
10
|
|
|
11
11
|
# 📚 复盘关卡 · 经验萃取
|
|
12
12
|
|
|
13
|
-
> v4.1-p18:作为 skill 暴露(v3.0 曾有 `/ccg:extract-learnings` 命令规划,v4.0/v4.1 收敛到 skill 化触发)。
|
|
14
|
-
|
|
15
13
|
从最近的开发活动中萃取**可复用知识**,写入 `.context/learnings/<日期>-extract.md`,避免反复踩同坑。
|
|
16
14
|
|
|
17
15
|
## 使用方法
|
|
@@ -58,7 +56,7 @@ ls .claude/team-plan/*-report.md 2>/dev/null
|
|
|
58
56
|
1. ...
|
|
59
57
|
|
|
60
58
|
## 决策记录
|
|
61
|
-
- **D1: 用 SessionStart hook 注入 roadmap** —
|
|
59
|
+
- **D1: 用 SessionStart hook 注入 roadmap** — 原因:主线零项目记忆痛点
|
|
62
60
|
|
|
63
61
|
## 工具陷阱
|
|
64
62
|
- **subagent 不能 spawn 子 agent**(commit a7cdffd 实测)
|
|
@@ -10,8 +10,6 @@ argument-hint: "[--repair]"
|
|
|
10
10
|
|
|
11
11
|
# 🏥 健康度关卡 · 项目体检
|
|
12
12
|
|
|
13
|
-
> v4.1-p18:从 `/ccg:health` 命令迁移为 skill。`/ccg:health` 自动生成路由保留。
|
|
14
|
-
|
|
15
13
|
一次性盘点项目"是不是在烂掉"。不深入业务正确性,只看**工程层面的卫生指标**:依赖陈旧度、已知漏洞、文档与代码同步度、堆积的 TODO、CLAUDE.md 是否还反映现状、测试覆盖率(如可拿到)。输出 markdown 报告,每项打分 + 给出可执行修复建议。
|
|
16
14
|
|
|
17
15
|
`--repair` 模式:对**安全且确定**的问题(如自动生成的过期文档骨架)询问后修复,绝不擅自动核心代码或依赖。
|
|
@@ -7,9 +7,9 @@ user-invocable: true
|
|
|
7
7
|
disable-model-invocation: false
|
|
8
8
|
allowed-tools: Bash, Read, Grep
|
|
9
9
|
argument-hint: [--mode working|staged|committed]
|
|
10
|
-
deprecated_in:
|
|
10
|
+
deprecated_in: 1.0.0
|
|
11
11
|
replaced_by: /ccg:verify --gate=change
|
|
12
|
-
deprecation_message:
|
|
12
|
+
deprecation_message: 推荐使用 `/ccg:verify --gate=change`。本命令仍可用以保持 BC。详见 .ccg-migration/DEPRECATIONS.md
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
# ⚖ 校验关卡 · 变更校验
|
|
@@ -7,9 +7,9 @@ user-invocable: true
|
|
|
7
7
|
disable-model-invocation: false
|
|
8
8
|
allowed-tools: Bash, Read, Glob
|
|
9
9
|
argument-hint: <模块路径>
|
|
10
|
-
deprecated_in:
|
|
10
|
+
deprecated_in: 1.0.0
|
|
11
11
|
replaced_by: /ccg:verify --gate=module
|
|
12
|
-
deprecation_message:
|
|
12
|
+
deprecation_message: 推荐使用 `/ccg:verify --gate=module`。本命令仍可用以保持 BC。详见 .ccg-migration/DEPRECATIONS.md
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
# ⚖ 校验关卡 · 模块完整性
|
|
@@ -7,9 +7,9 @@ user-invocable: true
|
|
|
7
7
|
disable-model-invocation: false
|
|
8
8
|
allowed-tools: Bash, Read, Glob
|
|
9
9
|
argument-hint: <扫描路径>
|
|
10
|
-
deprecated_in:
|
|
10
|
+
deprecated_in: 1.0.0
|
|
11
11
|
replaced_by: /ccg:verify --gate=quality
|
|
12
|
-
deprecation_message:
|
|
12
|
+
deprecation_message: 推荐使用 `/ccg:verify --gate=quality`。本命令仍可用以保持 BC。详见 .ccg-migration/DEPRECATIONS.md
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
# ⚖ 校验关卡 · 代码质量
|
|
@@ -7,9 +7,9 @@ user-invocable: true
|
|
|
7
7
|
disable-model-invocation: false
|
|
8
8
|
allowed-tools: Bash, Read, Grep
|
|
9
9
|
argument-hint: <扫描路径>
|
|
10
|
-
deprecated_in:
|
|
10
|
+
deprecated_in: 1.0.0
|
|
11
11
|
replaced_by: /ccg:verify --gate=security
|
|
12
|
-
deprecation_message:
|
|
12
|
+
deprecation_message: 推荐使用 `/ccg:verify --gate=security`。本命令仍可用以保持 BC。详见 .ccg-migration/DEPRECATIONS.md
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
# ⚖ 校验关卡 · 安全校验
|