ccgx-workflow 1.0.3 → 1.0.5

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.
Files changed (46) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.mjs +182 -2
  3. package/dist/index.mjs +3 -2
  4. package/dist/shared/{ccgx-workflow.BnfaZnVu.mjs → ccgx-workflow.CZSjTyQd.mjs} +79 -1
  5. package/package.json +1 -1
  6. package/templates/commands/agents/code-fixer.md +1 -1
  7. package/templates/commands/agents/codebase-mapper.md +1 -1
  8. package/templates/commands/agents/debug-session-manager.md +1 -1
  9. package/templates/commands/agents/debugger.md +1 -1
  10. package/templates/commands/agents/interface-auditor.md +34 -8
  11. package/templates/commands/agents/phase-runner.md +27 -27
  12. package/templates/commands/agents/plan-checker.md +4 -4
  13. package/templates/commands/analyze.md +10 -10
  14. package/templates/commands/autonomous.md +45 -46
  15. package/templates/commands/cancel.md +8 -8
  16. package/templates/commands/codex-exec.md +2 -2
  17. package/templates/commands/debate.md +5 -5
  18. package/templates/commands/debug.md +8 -8
  19. package/templates/commands/execute.md +6 -6
  20. package/templates/commands/init.md +1 -1
  21. package/templates/commands/optimize.md +10 -10
  22. package/templates/commands/plan.md +15 -15
  23. package/templates/commands/result.md +1 -1
  24. package/templates/commands/review.md +70 -31
  25. package/templates/commands/spec-impl.md +1 -1
  26. package/templates/commands/spec-plan.md +2 -2
  27. package/templates/commands/spec-research.md +1 -1
  28. package/templates/commands/spec-review.md +1 -1
  29. package/templates/commands/status.md +15 -15
  30. package/templates/commands/team-exec.md +1 -1
  31. package/templates/commands/team.md +6 -6
  32. package/templates/commands/test.md +9 -9
  33. package/templates/commands/verify-work.md +8 -8
  34. package/templates/commands/verify.md +3 -3
  35. package/templates/commands/workflow.md +2 -2
  36. package/templates/rules/ccg-skills.md +1 -1
  37. package/templates/scripts/ccgx-call-plugin.mjs +324 -0
  38. package/templates/scripts/repatch-gemini-plugin.mjs +10 -0
  39. package/templates/skills/tools/extract-learnings/SKILL.md +1 -3
  40. package/templates/skills/tools/forensics/SKILL.md +0 -2
  41. package/templates/skills/tools/health/SKILL.md +0 -2
  42. package/templates/skills/tools/map-codebase/SKILL.md +0 -2
  43. package/templates/skills/tools/verify-change/SKILL.md +2 -2
  44. package/templates/skills/tools/verify-module/SKILL.md +2 -2
  45. package/templates/skills/tools/verify-quality/SKILL.md +2 -2
  46. package/templates/skills/tools/verify-security/SKILL.md +2 -2
package/README.md CHANGED
@@ -16,7 +16,7 @@
16
16
  > `ccgx-workflow` is a deep rewrite of [`ccg-workflow`](https://www.npmjs.com/package/ccg-workflow) v3.x.
17
17
  > The original project went unmaintained after 2026-05 (the original author's
18
18
  > GitHub homepage went offline), leaving its multi-model collaboration users
19
- > exposed to drift. This project re-architected from v4.0 onward:
19
+ > exposed to drift. This project re-architected from the ground up:
20
20
  > fresh-context subagent protocols, Plan-Critic-Verify quality tiers,
21
21
  > OS-level three-layer process isolation, broker tx_id anti-drift,
22
22
  > and 8 plugin patches with a one-shot repatch script.
package/dist/cli.mjs CHANGED
@@ -1,16 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import cac from 'cac';
3
3
  import ansis from 'ansis';
4
- import { d as diagnoseMcpConfig, i as isWindows, r as readClaudeCodeConfig, f as fixWindowsMcpConfig, w as writeClaudeCodeConfig, a as readCcgConfig, b as initI18n, c as i18n, s as showMainMenu, e as init, g as configMcp, v as version } from './shared/ccgx-workflow.BnfaZnVu.mjs';
4
+ import { d as diagnoseMcpConfig, i as isWindows, r as readClaudeCodeConfig, f as fixWindowsMcpConfig, w as writeClaudeCodeConfig, a as readCcgConfig, b as initI18n, c as i18n, s as showMainMenu, e as init, g as configMcp, v as version } from './shared/ccgx-workflow.CZSjTyQd.mjs';
5
+ import { execSync } from 'node:child_process';
5
6
  import 'inquirer';
6
7
  import 'ora';
7
- import 'node:child_process';
8
8
  import 'node:util';
9
9
  import 'node:os';
10
10
  import 'node:url';
11
11
  import 'pathe';
12
12
  import 'fs-extra';
13
13
  import 'smol-toml';
14
+ import 'node:fs';
15
+ import 'node:path/posix';
16
+ import 'node:path';
14
17
  import 'i18next';
15
18
 
16
19
  async function diagnoseMcp() {
@@ -72,6 +75,177 @@ async function fixMcp() {
72
75
  }
73
76
  }
74
77
 
78
+ function categorize(cmdLine) {
79
+ const cmd = cmdLine.toLowerCase();
80
+ if (/quasar|vite|webpack|next\b|nuxt|astro|svelte-kit|pnpm.*run.*dev|npm.*run.*dev|yarn.*run.*dev|yarn dev|pnpm dev|npm run dev/.test(cmd)) {
81
+ return "dev-server";
82
+ }
83
+ if (/ccg-phase-runner-launcher|ccgx-call-plugin/.test(cmd)) return "phase-runner";
84
+ if (/codex-companion|codex-cli|@openai\/codex|openai-codex.*codex/.test(cmd)) return "codex-cli";
85
+ if (/gemini-companion|@google\/gemini-cli|google-gemini.*gemini/.test(cmd)) return "gemini-cli";
86
+ if (/playwright|context7|contextweaver|fast-context|ace-tool/.test(cmd)) return "mcp-server";
87
+ return "other";
88
+ }
89
+ function listNodeProcessesWindows() {
90
+ const ps = `Get-CimInstance Win32_Process -Filter "Name='node.exe'" | ForEach-Object {
91
+ $cd = $_.CreationDate
92
+ $h = if ($cd) { ((Get-Date) - $cd).TotalHours } else { 0 }
93
+ $cmd = if ($_.CommandLine) { $_.CommandLine } else { '' }
94
+ "$($_.ProcessId)|$h|$cmd"
95
+ }`;
96
+ let out;
97
+ try {
98
+ out = execSync(`powershell -NoProfile -Command "${ps.replace(/\n\s+/g, " ")}"`, {
99
+ encoding: "utf-8",
100
+ maxBuffer: 16 * 1024 * 1024
101
+ });
102
+ } catch {
103
+ return [];
104
+ }
105
+ const procs = [];
106
+ for (const line of out.split(/\r?\n/)) {
107
+ const trimmed = line.trim();
108
+ if (!trimmed) continue;
109
+ const sep1 = trimmed.indexOf("|");
110
+ const sep2 = trimmed.indexOf("|", sep1 + 1);
111
+ if (sep1 < 0 || sep2 < 0) continue;
112
+ const pid = Number.parseInt(trimmed.slice(0, sep1), 10);
113
+ const ageHours = Number.parseFloat(trimmed.slice(sep1 + 1, sep2));
114
+ const cmdLine = trimmed.slice(sep2 + 1);
115
+ if (Number.isNaN(pid)) continue;
116
+ procs.push({
117
+ pid,
118
+ ageHours: Number.isFinite(ageHours) ? ageHours : 0,
119
+ cmdLine,
120
+ category: categorize(cmdLine)
121
+ });
122
+ }
123
+ return procs;
124
+ }
125
+ function listNodeProcessesPosix() {
126
+ let out;
127
+ try {
128
+ out = execSync("ps -eo pid,etime,command", { encoding: "utf-8", maxBuffer: 16 * 1024 * 1024 });
129
+ } catch {
130
+ return [];
131
+ }
132
+ const procs = [];
133
+ for (const line of out.split("\n").slice(1)) {
134
+ const m = line.match(/^\s*(\d+)\s+(\S+)\s+(.+)$/);
135
+ if (!m) continue;
136
+ const cmdLine = m[3];
137
+ if (!/(^|\/)node(\s|$)/.test(cmdLine)) continue;
138
+ const pid = Number.parseInt(m[1], 10);
139
+ const etime = m[2];
140
+ const parts = etime.split(/[-:]/).map((s) => Number.parseInt(s, 10));
141
+ let ageHours = 0;
142
+ if (parts.length === 4) ageHours = parts[0] * 24 + parts[1] + parts[2] / 60;
143
+ else if (parts.length === 3) ageHours = parts[0] + parts[1] / 60;
144
+ else if (parts.length === 2) ageHours = parts[0] / 60;
145
+ procs.push({
146
+ pid,
147
+ ageHours,
148
+ cmdLine,
149
+ category: categorize(cmdLine)
150
+ });
151
+ }
152
+ return procs;
153
+ }
154
+ function listNodeProcesses() {
155
+ return isWindows() ? listNodeProcessesWindows() : listNodeProcessesPosix();
156
+ }
157
+ function killProcessWindows(pid) {
158
+ try {
159
+ execSync(`powershell -NoProfile -Command "Stop-Process -Id ${pid} -Force -ErrorAction Stop"`, { stdio: "pipe" });
160
+ return { ok: true, method: "Stop-Process" };
161
+ } catch {
162
+ }
163
+ try {
164
+ execSync(`taskkill /F /PID ${pid}`, { stdio: "pipe" });
165
+ return { ok: true, method: "taskkill /F" };
166
+ } catch {
167
+ }
168
+ try {
169
+ execSync(`wmic process where "ProcessId=${pid}" delete`, { stdio: "pipe" });
170
+ return { ok: true, method: "wmic delete" };
171
+ } catch (e) {
172
+ return { ok: false, method: "all-failed", error: e instanceof Error ? e.message : String(e) };
173
+ }
174
+ }
175
+ function killProcessPosix(pid) {
176
+ try {
177
+ execSync(`kill -TERM ${pid}`, { stdio: "pipe" });
178
+ return { ok: true, method: "SIGTERM" };
179
+ } catch {
180
+ }
181
+ try {
182
+ execSync(`kill -KILL ${pid}`, { stdio: "pipe" });
183
+ return { ok: true, method: "SIGKILL" };
184
+ } catch (e) {
185
+ return { ok: false, method: "all-failed", error: e instanceof Error ? e.message : String(e) };
186
+ }
187
+ }
188
+ async function killOrphans(options = {}) {
189
+ const dryRun = options.dryRun ?? true;
190
+ const minAgeHours = options.minAgeHours ?? 1;
191
+ console.log(ansis.cyan.bold("\n ccgx kill-orphans"));
192
+ console.log(ansis.gray(` ${dryRun ? "[DRY-RUN]" : "[KILL MODE]"} target: orphan node processes >${minAgeHours}h
193
+ `));
194
+ const all = listNodeProcesses();
195
+ if (all.length === 0) {
196
+ console.log(ansis.yellow(" No node processes found (or unable to list)."));
197
+ return;
198
+ }
199
+ const groups = /* @__PURE__ */ new Map();
200
+ for (const p of all) {
201
+ if (!groups.has(p.category)) groups.set(p.category, []);
202
+ groups.get(p.category).push(p);
203
+ }
204
+ console.log(ansis.bold(" Inventory:"));
205
+ for (const cat of ["mcp-server", "codex-cli", "gemini-cli", "phase-runner", "dev-server", "other"]) {
206
+ const list = groups.get(cat) ?? [];
207
+ if (list.length === 0) continue;
208
+ const oldestH = Math.max(...list.map((p) => p.ageHours));
209
+ const tag = cat === "dev-server" ? ansis.green("SAFE") : cat === "other" ? ansis.gray("UNKN") : ansis.yellow("ORPH");
210
+ console.log(` ${tag} ${cat.padEnd(14)} ${String(list.length).padStart(3)} processes (oldest ${oldestH.toFixed(1)}h)`);
211
+ }
212
+ console.log();
213
+ const ORPHAN_CATS = ["mcp-server", "codex-cli", "gemini-cli", "phase-runner"];
214
+ const targets = all.filter((p) => ORPHAN_CATS.includes(p.category) && p.ageHours >= minAgeHours);
215
+ if (targets.length === 0) {
216
+ console.log(ansis.green(` \u2713 No orphans >${minAgeHours}h to clean.`));
217
+ return;
218
+ }
219
+ console.log(ansis.bold(` Targets (${targets.length}):`));
220
+ for (const p of targets) {
221
+ const cmdShort = p.cmdLine.length > 70 ? `${p.cmdLine.slice(0, 70)}...` : p.cmdLine;
222
+ console.log(` PID ${String(p.pid).padStart(6)} ${p.ageHours.toFixed(1).padStart(5)}h ${ansis.gray(p.category.padEnd(14))} ${cmdShort}`);
223
+ }
224
+ console.log();
225
+ if (dryRun) {
226
+ console.log(ansis.gray(` Run with --kill to actually terminate (skips dev-server / other categories).`));
227
+ return;
228
+ }
229
+ const killer = isWindows() ? killProcessWindows : killProcessPosix;
230
+ let killed = 0;
231
+ let failed = 0;
232
+ for (const p of targets) {
233
+ const r = killer(p.pid);
234
+ if (r.ok) {
235
+ console.log(ansis.green(` \u2713 killed PID ${p.pid} via ${r.method}`));
236
+ killed += 1;
237
+ } else {
238
+ console.log(ansis.red(` \u2717 PID ${p.pid} could not be terminated (${r.error})`));
239
+ failed += 1;
240
+ }
241
+ }
242
+ console.log();
243
+ console.log(ansis.bold(` Result: ${killed} killed, ${failed} failed`));
244
+ if (failed > 0) {
245
+ console.log(ansis.yellow(" Some processes refused all kill methods (may require reboot or admin elevation)."));
246
+ }
247
+ }
248
+
75
249
  function customizeHelp(sections) {
76
250
  sections.unshift({
77
251
  title: "",
@@ -150,6 +324,12 @@ async function setupCommands(cli) {
150
324
  cli.command("diagnose-mcp", i18n.t("cli:help.commandDescriptions.diagnoseMcp")).action(async () => {
151
325
  await diagnoseMcp();
152
326
  });
327
+ cli.command("kill-orphans", "\u6E05\u7406 Claude Code session \u9000\u51FA\u540E\u6B8B\u7559\u7684\u5B64\u513F node \u8FDB\u7A0B\uFF08MCP server / codex / gemini\uFF09").option("--kill", "\u5B9E\u9645\u6267\u884C kill (\u9ED8\u8BA4 dry-run)").option("--min-age-hours <N>", "\u53EA\u6E05\u7406\u8D85\u8FC7 N \u5C0F\u65F6\u7684\u8FDB\u7A0B (\u9ED8\u8BA4 1)").action(async (options) => {
328
+ await killOrphans({
329
+ dryRun: !options.kill,
330
+ minAgeHours: options.minAgeHours ? Number.parseFloat(options.minAgeHours) : 1
331
+ });
332
+ });
153
333
  cli.command("fix-mcp", i18n.t("cli:help.commandDescriptions.fixMcp")).action(async () => {
154
334
  await fixMcp();
155
335
  });
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { h as collectSkills } from './shared/ccgx-workflow.BnfaZnVu.mjs';
2
- export { j as changeLanguage, F as checkForUpdates, H as collectInvocableSkills, G as compareVersions, l as createDefaultConfig, m as createDefaultRouting, I as generateCommandContent, n as getCcgDir, o as getConfigPath, D as getCurrentVersion, E as getLatestVersion, q as getWorkflowById, p as getWorkflowConfigs, c as i18n, e as init, b as initI18n, x as installAceTool, y as installAceToolRs, J as installSkillCommands, t as installWorkflows, B as migrateToV1_4_0, C as needsMigration, K as parseFrontmatter, a as readCcgConfig, s as showMainMenu, A as uninstallAceTool, z as uninstallWorkflows, u as update, k as writeCcgConfig } from './shared/ccgx-workflow.BnfaZnVu.mjs';
1
+ import { h as collectSkills } from './shared/ccgx-workflow.CZSjTyQd.mjs';
2
+ export { j as changeLanguage, F as checkForUpdates, H as collectInvocableSkills, G as compareVersions, l as createDefaultConfig, m as createDefaultRouting, I as generateCommandContent, n as getCcgDir, o as getConfigPath, D as getCurrentVersion, E as getLatestVersion, q as getWorkflowById, p as getWorkflowConfigs, c as i18n, e as init, b as initI18n, x as installAceTool, y as installAceToolRs, J as installSkillCommands, t as installWorkflows, B as migrateToV1_4_0, C as needsMigration, K as parseFrontmatter, a as readCcgConfig, s as showMainMenu, A as uninstallAceTool, z as uninstallWorkflows, u as update, k as writeCcgConfig } from './shared/ccgx-workflow.CZSjTyQd.mjs';
3
3
  import { existsSync, readFileSync, mkdirSync, writeFileSync, statSync, readdirSync } from 'node:fs';
4
4
  import { join, dirname } from 'node:path';
5
5
  import { homedir } from 'node:os';
@@ -12,6 +12,7 @@ import 'node:url';
12
12
  import 'pathe';
13
13
  import 'fs-extra';
14
14
  import 'smol-toml';
15
+ import 'node:path/posix';
15
16
  import 'i18next';
16
17
 
17
18
  function phaseDir(workdir, phase) {
@@ -8,9 +8,12 @@ import { fileURLToPath } from 'node:url';
8
8
  import { join, dirname, relative, sep, basename } from 'pathe';
9
9
  import fs from 'fs-extra';
10
10
  import { parse, stringify } from 'smol-toml';
11
+ import { existsSync, readFileSync } from 'node:fs';
12
+ import { join as join$2 } from 'node:path/posix';
13
+ import { join as join$1 } from 'node:path';
11
14
  import i18next from 'i18next';
12
15
 
13
- const version = "1.0.3";
16
+ const version = "1.0.5";
14
17
 
15
18
  function cmd(id, order, category, name, nameEn, description, descriptionEn, cmdOverride) {
16
19
  return {
@@ -291,6 +294,61 @@ function getMcpCommand(command) {
291
294
  return [command];
292
295
  }
293
296
 
297
+ const VENDOR_MARKETPLACE_KEYS = {
298
+ codex: "codex@openai-codex",
299
+ gemini: "gemini@google-gemini"
300
+ };
301
+ function discoverCompanion(vendor, homeDir = homedir()) {
302
+ const ssotPath = join$1(homeDir, ".claude", "plugins", "installed_plugins.json");
303
+ if (!existsSync(ssotPath)) return null;
304
+ let raw;
305
+ try {
306
+ raw = JSON.parse(readFileSync(ssotPath, "utf-8"));
307
+ } catch {
308
+ return null;
309
+ }
310
+ const key = VENDOR_MARKETPLACE_KEYS[vendor];
311
+ const instances = raw?.plugins?.[key];
312
+ if (!Array.isArray(instances) || instances.length === 0) return null;
313
+ const inst = instances[0];
314
+ const installPath = inst?.installPath;
315
+ if (typeof installPath !== "string" || !installPath) return null;
316
+ const companionPath = join$1(installPath, "scripts", `${vendor}-companion.mjs`);
317
+ if (!existsSync(companionPath)) return null;
318
+ const version = typeof inst?.version === "string" ? inst.version : "unknown";
319
+ return { vendor, installPath, companionPath, version };
320
+ }
321
+ function shellQuotePosix(s) {
322
+ return `'${s.replace(/'/g, "'\\''")}'`;
323
+ }
324
+ function defaultHelperPath(homeDir) {
325
+ const normalized = homeDir.replace(/\\/g, "/");
326
+ return join$2(normalized, ".claude", ".ccg", "scripts", "ccgx-call-plugin.mjs");
327
+ }
328
+ function buildBashCommand(_loc, options = {}) {
329
+ const jsonOutput = options.jsonOutput ?? true;
330
+ const homeDir = options.homeDir ?? homedir();
331
+ const helperPath = options.helperPath ?? defaultHelperPath(homeDir);
332
+ const quotedHelper = shellQuotePosix(helperPath);
333
+ const vendor = _loc.vendor;
334
+ const jsonFlag = jsonOutput ? " --json" : "";
335
+ return `node ${quotedHelper} ${vendor}${jsonFlag}`;
336
+ }
337
+ function buildPluginMissingFallback(vendor) {
338
+ const key = VENDOR_MARKETPLACE_KEYS[vendor];
339
+ return [
340
+ `# CCG: ${vendor} plugin (${key}) not installed at CCG install time.`,
341
+ `# Install with: claude plugin install ${key}`,
342
+ `# Then re-run: npx ccgx-workflow init --skip-prompt --skip-mcp`,
343
+ `echo 'CCG: ${vendor} plugin not available' >&2 && exit 1`
344
+ ].join("\n");
345
+ }
346
+ function resolvePluginBashCommand(vendor, options = {}, homeDir) {
347
+ const loc = discoverCompanion(vendor, homeDir);
348
+ if (!loc) return buildPluginMissingFallback(vendor);
349
+ return buildBashCommand(loc, { ...options, homeDir: options.homeDir });
350
+ }
351
+
294
352
  const __filename$2 = fileURLToPath(import.meta.url);
295
353
  const __dirname$2 = dirname(__filename$2);
296
354
  function findPackageRoot$1(startDir) {
@@ -356,6 +414,18 @@ function injectConfigVariables(content, config) {
356
414
  }
357
415
  const liteModeFlag = config.liteMode ? "--lite " : "";
358
416
  processed = processed.replace(/\{\{LITE_MODE_FLAG\}\}/g, liteModeFlag);
417
+ if (processed.includes("{{CODEX_BASH_TASK}}")) {
418
+ processed = processed.replace(/\{\{CODEX_BASH_TASK\}\}/g, resolvePluginBashCommand("codex"));
419
+ }
420
+ if (processed.includes("{{CODEX_BASH_TASK_TEXT}}")) {
421
+ processed = processed.replace(/\{\{CODEX_BASH_TASK_TEXT\}\}/g, resolvePluginBashCommand("codex", { jsonOutput: false }));
422
+ }
423
+ if (processed.includes("{{GEMINI_BASH_TASK}}")) {
424
+ processed = processed.replace(/\{\{GEMINI_BASH_TASK\}\}/g, resolvePluginBashCommand("gemini"));
425
+ }
426
+ if (processed.includes("{{GEMINI_BASH_TASK_TEXT}}")) {
427
+ processed = processed.replace(/\{\{GEMINI_BASH_TASK_TEXT\}\}/g, resolvePluginBashCommand("gemini", { jsonOutput: false }));
428
+ }
359
429
  const mcpProvider = config.mcpProvider || "ace-tool";
360
430
  if (mcpProvider === "skip") {
361
431
  processed = processed.replace(/,\s*\{\{MCP_SEARCH_TOOL\}\}/g, "");
@@ -1282,6 +1352,14 @@ async function installShim(ctx) {
1282
1352
  await fs.chmod(destLauncher, 493);
1283
1353
  }
1284
1354
  }
1355
+ const srcCallPlugin = join(ctx.templateDir, "scripts", "ccgx-call-plugin.mjs");
1356
+ if (await fs.pathExists(srcCallPlugin)) {
1357
+ const destCallPlugin = join(scriptDir, "ccgx-call-plugin.mjs");
1358
+ await fs.copy(srcCallPlugin, destCallPlugin, { overwrite: true });
1359
+ if (process.platform !== "win32") {
1360
+ await fs.chmod(destCallPlugin, 493);
1361
+ }
1362
+ }
1285
1363
  if (process.platform === "win32") {
1286
1364
  const cmdMjs = destMjs.replace(/\//g, "\\");
1287
1365
  const cmdPath = join(binDir, "codeagent-wrapper.cmd");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccgx-workflow",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Multi-model orchestration for Claude Code. Codex + Gemini parallel collaboration with fresh-context subagent protocols, OS-level process isolation, and Plan-Critic-Verify quality tiers. Successor to ccg-workflow.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.17.1",
@@ -5,7 +5,7 @@ tools: Read, Write, Edit, Bash, Glob, Grep
5
5
  color: orange
6
6
  ---
7
7
 
8
- 你是 **Code Fixer**——CCG v4.0 `/ccg:review --fix` 闭环修复链路的专职修复 agent。你的任务**不**是探索代码或讨论方案,而是**严格按 REVIEW.md 的 finding 逐项应用机械化修复**,并在工程级保护下提交原子 commit。
8
+ 你是 **Code Fixer**——CCG `/ccg:review --fix` 闭环修复链路的专职修复 agent。你的任务**不**是探索代码或讨论方案,而是**严格按 REVIEW.md 的 finding 逐项应用机械化修复**,并在工程级保护下提交原子 commit。
9
9
 
10
10
  主线 spawn 你时已经把 base SHA / phase 编号 / REVIEW.md 路径喂给你。**你必须先建 worktree 隔离再动代码**——这不是建议而是契约。
11
11
 
@@ -5,7 +5,7 @@ tools: Read, Bash, Grep, Glob, Write
5
5
  color: cyan
6
6
  ---
7
7
 
8
- 你是 **代码库映射师 (Codebase Mapper)**——CCG v4.0 借鉴 GSD `gsd-codebase-mapper` 的廉价 codebase 摘要机制。**调用方不会读你的扫描中间产物**:你直接把分析写到 `.context/codebase/` 下的目标文件,调用方只接收一行确认 + 文件路径。
8
+ 你是 **代码库映射师 (Codebase Mapper)**——CCG 借鉴 GSD `gsd-codebase-mapper` 的廉价 codebase 摘要机制。**调用方不会读你的扫描中间产物**:你直接把分析写到 `.context/codebase/` 下的目标文件,调用方只接收一行确认 + 文件路径。
9
9
 
10
10
  主线 spawn 你时通常采用**4 路并行**(tech / arch / quality / concerns 各一个 instance),让 plan/execute/init 启动前有一份廉价的 codebase 全貌索引,主线不必每次重新探索。
11
11
 
@@ -5,7 +5,7 @@ tools: Read, Write, Edit, Bash, Grep, Glob, Task, AskUserQuestion
5
5
  color: orange
6
6
  ---
7
7
 
8
- 你是 **Debug Session Manager**——CCG v4.0 `/ccg:debug` 重写的核心子 agent。主线(debug.md)把整个多轮调试循环托付给你,你内部 spawn `debugger` subagent 反复构造 / 验证 hypothesis,最终返回主线一条**严格 ≤ 200 token** 的紧凑结构化摘要。
8
+ 你是 **Debug Session Manager**——CCG `/ccg:debug` 重写的核心子 agent。主线(debug.md)把整个多轮调试循环托付给你,你内部 spawn `debugger` subagent 反复构造 / 验证 hypothesis,最终返回主线一条**严格 ≤ 200 token** 的紧凑结构化摘要。
9
9
 
10
10
  主线**不会读你的 transcript**——你的所有中间产出都不会污染主线 context。所以摘要必须自包含、机器可解析。
11
11
 
@@ -5,7 +5,7 @@ tools: Read, Bash, Grep, Glob, WebSearch
5
5
  color: orange
6
6
  ---
7
7
 
8
- 你是 **Debugger**——CCG v4.0 `/ccg:debug` 链路里负责"提出 hypothesis + 给可证伪测试方案"的最底层 agent。你**不**直接修改代码、**不**应用 fix、**不**写 session 文件——这些是 `debug-session-manager` 的活。你的输出**只**是结构化的下一个 hypothesis 建议。
8
+ 你是 **Debugger**——CCG `/ccg:debug` 链路里负责"提出 hypothesis + 给可证伪测试方案"的最底层 agent。你**不**直接修改代码、**不**应用 fix、**不**写 session 文件——这些是 `debug-session-manager` 的活。你的输出**只**是结构化的下一个 hypothesis 建议。
9
9
 
10
10
  > 移植参考:GSD `gsd-debugger`(02-subagent-matrix.md Section 2.3)。CCG 工程契约见 `src/utils/debug-session.ts:makeHypothesis()`。
11
11
 
@@ -5,7 +5,7 @@ tools: Read, Glob, Grep, Bash
5
5
  color: cyan
6
6
  ---
7
7
 
8
- 你是 **接口审计专员 (Interface Auditor)**——CCG v4.3 Phase 27 引入的跨 phase verifier specialist。每个 phase commit 后由主线 spawn 一次(在 quality-router triple/debate 的 verify wave 内并行),审视本次 commit 引入的代码改动是否违反 5 类**真实事故型**风险。
8
+ 你是 **接口审计专员 (Interface Auditor)**——跨 phase verifier specialist。每个 phase commit 后由主线 spawn 一次(在 quality-router triple/debate 的 verify wave 内并行),审视本次 commit 引入的代码改动是否违反 5 类**真实事故型**风险。
9
9
 
10
10
  ## 你必须诚实的五条铁则
11
11
 
@@ -35,7 +35,7 @@ phase_files: [<本 phase 修改/新增的相对路径>]
35
35
 
36
36
  ### 1. SSoT 违反(critical)
37
37
 
38
- **目的**:检测重复 type / 重复实现(v4.2 P22 重新引入 `planVerifyWave` 重复同型事故)。
38
+ **目的**:检测重复 type / 重复实现(曾出现过 `planVerifyWave` 重复定义的同型事故)。
39
39
 
40
40
  **怎么做**:
41
41
  1. `git show <commit_hash> --name-only` 拿到本 phase 修改文件
@@ -51,7 +51,7 @@ phase_files: [<本 phase 修改/新增的相对路径>]
51
51
 
52
52
  ### 2. 半成品 export(major)
53
53
 
54
- **目的**:检测有 export 但全仓库无 import consumer(v4.1 P19 `paths:` 字段无 consumer 同型事故)。
54
+ **目的**:检测有 export 但全仓库无 import consumer(曾出现过 `paths:` 字段无 consumer 的同型事故)。
55
55
 
56
56
  **怎么做**:
57
57
  1. 本 phase commit 文件中 grep `^export\s+(?:async\s+)?(?:function|const|class|interface|type)\s+(\w+)` → 候选导出名
@@ -66,7 +66,7 @@ phase_files: [<本 phase 修改/新增的相对路径>]
66
66
 
67
67
  ### 3. Magic string vs ground truth(critical)
68
68
 
69
- **目的**:检测代码里硬编码的 `subagent_type` / plugin 名 / hook event 是否跟 ground truth latest.json 实际值一致(v4.4.1 hotfix 校正:v4.0–4.4.0 全仓 `codex:rescue` / `gemini:rescue` 单前缀同型事故 — Agent subagent_type 真名是 `codex:codex-rescue` / `gemini:gemini-rescue` 双前缀;单前缀是 Skill 名,命名空间不同)。
69
+ **目的**:检测代码里硬编码的 `subagent_type` / plugin 名 / hook event 是否跟 ground truth latest.json 实际值一致(曾出现过全仓 `codex:rescue` / `gemini:rescue` 单前缀的同型事故 — Agent subagent_type 真名是 `codex:codex-rescue` / `gemini:gemini-rescue` 双前缀;单前缀是 Skill 名,命名空间不同)。
70
70
 
71
71
  **怎么做**:
72
72
  1. Read ground_truth_path(若不存在 → skip 本检查 + info finding)
@@ -99,7 +99,32 @@ phase_files: [<本 phase 修改/新增的相对路径>]
99
99
  [{severity: info, category: commit-diff-drift, message: "subject says 'add foo' but git stat 无新建 foo 路径文件 (best-effort 检测,可能误报)"}]
100
100
  ```
101
101
 
102
- ### 5. Mock ground truth schema 偏差(info/major)
102
+ ### 5. Inline plugin Bash 命令拼接(critical,1.0.4 新增)
103
+
104
+ **目的**:检测模板里有没有手写 inline plugin companion 调用命令——绕过 install-time codegen 占位符(`{{CODEX_BASH_TASK}}` / `{{GEMINI_BASH_TASK}}` / `{{CODEX_BASH_TASK_TEXT}}` / `{{GEMINI_BASH_TASK_TEXT}}`),重新引入"flag 漂移 + 路径 glob hack"的同型风险。
105
+
106
+ **为什么这是 critical**:
107
+
108
+ - inline 拼接给 LLM 自由发挥空间——LLM 看到 inline 命令示例后会**编造**未在示例中出现的 flag(历史曾出现 195 处接口名漂移事故)
109
+ - 用 glob + `head` 解析 plugin 路径在多版本 cache 下行为不可预测(installed_plugins.json 才是 SSoT)
110
+ - 跳过 install-time codegen 意味着 plugin 未装时模板**静默坏掉**而不是给出清晰错误
111
+
112
+ **怎么做**:
113
+ 1. 本 phase commit 影响的 `.md` 文件中(仅扫 `templates/commands/**.md`)grep:
114
+ - `node\s+["'`].*?-companion\.mjs.*?\btask\b.*?-p\b` — inline 命令模式
115
+ - `\$\(ls\s+.*-companion\.mjs.*head\s+-1\)` — glob hack 模式
116
+ - `buildPluginBashCommand\s*\(` — TS-helper 伪代码模式(codex 审计提到的"helper opts 漂移"风险)
117
+ 2. 命中即 critical,除非该出现位置是:
118
+ - 1.0.4+ CHANGELOG / migration doc 的"反例对照"段落
119
+ - `agents/interface-auditor.md` 自身(本 rule 说明)
120
+ - 测试 fixture(`__tests__/`、`fixtures/`、`*.test.*`)
121
+
122
+ **critical 例子**:
123
+ ```
124
+ [{severity: critical, category: inline-plugin-bash, message: "review.md line N: inline plugin companion 命令拼接 — 应改用 {{CODEX_BASH_TASK}} install-time codegen 占位符"}]
125
+ ```
126
+
127
+ ### 6. Mock 与 ground truth schema 偏差(info/major)
103
128
 
104
129
  **目的**:测试 mock 数据跟真实 schema 不一致(与 P28 fixtures 协作;本 agent 仅做轻量提示)。
105
130
 
@@ -123,14 +148,15 @@ phase_files: [<本 phase 修改/新增的相对路径>]
123
148
  2. `git show <commit_hash> --name-only` → phase_files 验证;若 prompt 给的列表跟 git 不一致以 git 为准
124
149
  3. 过滤掉非 `.ts` / `.md` / `package.json` 的文件(图片、bin 等不审)
125
150
 
126
- ### Step 2: 五项检查并行思考
127
- 对每个 phase 修改文件分别跑 5 项检查的 grep。每条命中产出一个 Finding 候选。
151
+ ### Step 2: 六项检查并行思考
152
+ 对每个 phase 修改文件分别跑 6 项检查的 grep。每条命中产出一个 Finding 候选。
128
153
 
129
154
  ### Step 3: 假阳性过滤
130
155
  - SSoT 违反:排除测试文件、type union 同名、re-export
131
156
  - 半成品:排除 default export、type-only re-export 到 index.ts
132
157
  - magic string:排除 CCG 自家 agent 名(白名单)
133
158
  - commit drift:用模糊匹配,命中率不高时降级 info
159
+ - inline-plugin-bash:排除 CHANGELOG/migration 反例段、interface-auditor.md 自身说明、测试 fixture
134
160
  - mock drift:纯 best-effort,全部标 info severity
135
161
 
136
162
  ### Step 4: 输出 ≤200 token 摘要
@@ -139,7 +165,7 @@ phase_files: [<本 phase 修改/新增的相对路径>]
139
165
 
140
166
  ```
141
167
  STATUS: complete | error
142
- FINDINGS: [{severity: critical|major|info, category: ssot-violation|leftover|magic-string-mismatch|commit-diff-drift|mock-drift, message: "<具体证据 + 文件路径 + 行号>"}, ...]
168
+ FINDINGS: [{severity: critical|major|info, category: ssot-violation|leftover|magic-string-mismatch|commit-diff-drift|inline-plugin-bash|mock-drift, message: "<具体证据 + 文件路径 + 行号>"}, ...]
143
169
  NOTES: <≤80 字一行总结>
144
170
  ```
145
171