ccgx-workflow 1.0.2 → 1.0.4

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/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 { 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.Bq9vAaEw.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.Bl0vlpC_.mjs';
5
5
  import 'inquirer';
6
6
  import 'ora';
7
7
  import 'node:child_process';
@@ -11,6 +11,8 @@ 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';
14
16
  import 'i18next';
15
17
 
16
18
  async function diagnoseMcp() {
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { h as collectSkills } from './shared/ccgx-workflow.Bq9vAaEw.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.Bq9vAaEw.mjs';
1
+ import { h as collectSkills } from './shared/ccgx-workflow.Bl0vlpC_.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.Bl0vlpC_.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';
@@ -986,9 +986,9 @@ function resolveQualityTier(input) {
986
986
  return { tier: "triple", source: "default" };
987
987
  }
988
988
  const PHASE_RUNNER_BUDGET_USD = {
989
- fast: 1,
990
- triple: 2,
991
- debate: 5
989
+ fast: 50,
990
+ triple: 100,
991
+ debate: 250
992
992
  };
993
993
  function shellSingleQuote(s) {
994
994
  return `'${s.replace(/'/g, `'\\''`)}'`;
@@ -8,9 +8,11 @@ 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$1 } from 'node:path';
11
13
  import i18next from 'i18next';
12
14
 
13
- const version = "1.0.2";
15
+ const version = "1.0.4";
14
16
 
15
17
  function cmd(id, order, category, name, nameEn, description, descriptionEn, cmdOverride) {
16
18
  return {
@@ -291,6 +293,61 @@ function getMcpCommand(command) {
291
293
  return [command];
292
294
  }
293
295
 
296
+ const VENDOR_MARKETPLACE_KEYS = {
297
+ codex: "codex@openai-codex",
298
+ gemini: "gemini@google-gemini"
299
+ };
300
+ function discoverCompanion(vendor, homeDir = homedir()) {
301
+ const ssotPath = join$1(homeDir, ".claude", "plugins", "installed_plugins.json");
302
+ if (!existsSync(ssotPath)) return null;
303
+ let raw;
304
+ try {
305
+ raw = JSON.parse(readFileSync(ssotPath, "utf-8"));
306
+ } catch {
307
+ return null;
308
+ }
309
+ const key = VENDOR_MARKETPLACE_KEYS[vendor];
310
+ const instances = raw?.plugins?.[key];
311
+ if (!Array.isArray(instances) || instances.length === 0) return null;
312
+ const inst = instances[0];
313
+ const installPath = inst?.installPath;
314
+ if (typeof installPath !== "string" || !installPath) return null;
315
+ const companionPath = join$1(installPath, "scripts", `${vendor}-companion.mjs`);
316
+ if (!existsSync(companionPath)) return null;
317
+ const version = typeof inst?.version === "string" ? inst.version : "unknown";
318
+ return { vendor, installPath, companionPath, version };
319
+ }
320
+ function shellQuotePosix(s) {
321
+ return `'${s.replace(/'/g, "'\\''")}'`;
322
+ }
323
+ function buildBashCommand(loc, options = {}) {
324
+ const jsonOutput = options.jsonOutput ?? true;
325
+ const promptPlaceholder = options.promptPlaceholder ?? "%PROMPT%";
326
+ const heredocDelimiter = options.heredocDelimiter ?? "CCG_PROMPT_EOF";
327
+ const quotedPath = shellQuotePosix(loc.companionPath);
328
+ const flags = jsonOutput ? "--json" : "";
329
+ return [
330
+ `node ${quotedPath} task ${flags}-p "$(cat <<'${heredocDelimiter}'`,
331
+ promptPlaceholder,
332
+ heredocDelimiter,
333
+ `)"`
334
+ ].join("\n").replace(/ -p/, " -p").replace(/ +/g, " ").trimEnd();
335
+ }
336
+ function buildPluginMissingFallback(vendor) {
337
+ const key = VENDOR_MARKETPLACE_KEYS[vendor];
338
+ return [
339
+ `# CCG: ${vendor} plugin (${key}) not installed at CCG install time.`,
340
+ `# Install with: claude plugin install ${key}`,
341
+ `# Then re-run: npx ccgx-workflow init --skip-prompt --skip-mcp`,
342
+ `echo 'CCG: ${vendor} plugin not available' >&2 && exit 1`
343
+ ].join("\n");
344
+ }
345
+ function resolvePluginBashCommand(vendor, options = {}, homeDir) {
346
+ const loc = discoverCompanion(vendor, homeDir);
347
+ if (!loc) return buildPluginMissingFallback(vendor);
348
+ return buildBashCommand(loc, options);
349
+ }
350
+
294
351
  const __filename$2 = fileURLToPath(import.meta.url);
295
352
  const __dirname$2 = dirname(__filename$2);
296
353
  function findPackageRoot$1(startDir) {
@@ -356,6 +413,18 @@ function injectConfigVariables(content, config) {
356
413
  }
357
414
  const liteModeFlag = config.liteMode ? "--lite " : "";
358
415
  processed = processed.replace(/\{\{LITE_MODE_FLAG\}\}/g, liteModeFlag);
416
+ if (processed.includes("{{CODEX_BASH_TASK}}")) {
417
+ processed = processed.replace(/\{\{CODEX_BASH_TASK\}\}/g, resolvePluginBashCommand("codex"));
418
+ }
419
+ if (processed.includes("{{CODEX_BASH_TASK_TEXT}}")) {
420
+ processed = processed.replace(/\{\{CODEX_BASH_TASK_TEXT\}\}/g, resolvePluginBashCommand("codex", { jsonOutput: false }));
421
+ }
422
+ if (processed.includes("{{GEMINI_BASH_TASK}}")) {
423
+ processed = processed.replace(/\{\{GEMINI_BASH_TASK\}\}/g, resolvePluginBashCommand("gemini"));
424
+ }
425
+ if (processed.includes("{{GEMINI_BASH_TASK_TEXT}}")) {
426
+ processed = processed.replace(/\{\{GEMINI_BASH_TASK_TEXT\}\}/g, resolvePluginBashCommand("gemini", { jsonOutput: false }));
427
+ }
359
428
  const mcpProvider = config.mcpProvider || "ace-tool";
360
429
  if (mcpProvider === "skip") {
361
430
  processed = processed.replace(/,\s*\{\{MCP_SEARCH_TOOL\}\}/g, "");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccgx-workflow",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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",
@@ -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 `node "$(ls .../codex-companion.mjs | head -1)" task -p ...` 命令——绕过 1.0.4 install-time codegen 占位符(`{{CODEX_BASH_TASK}}` / `{{GEMINI_BASH_TASK}}` / `{{CODEX_BASH_TASK_TEXT}}` / `{{GEMINI_BASH_TASK_TEXT}}`),重新引入 v4.4.1 同型的"flag 漂移 + 路径 glob hack"风险。
105
+
106
+ **为什么这是 critical**:
107
+
108
+ - inline 拼接给 LLM 自由发挥空间——LLM 看到示例后会**编造**未在示例中出现的 flag(与 v4.4.1 编造 `codex:rescue` 单前缀同型)
109
+ - `ls .../*-companion.mjs | head -1` 在 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:58 inline `node ... codex-companion.mjs ... task -p` — 应改用 {{CODEX_BASH_TASK}} install-time codegen 占位符(1.0.4 起)"}]
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
 
@@ -51,25 +51,44 @@ argument-hint: "[代码或描述] [--adversarial] [--fix [--all] [--auto]] [--ro
51
51
 
52
52
  **调用语法**(v4.4.2 起 review/verify 路径默认走 Bash 直调):
53
53
 
54
- **通道 A — Bash 直调 plugin script(v4.4.2 默认,绕开 sonnet wrapper)**:
54
+ **通道 A — Bash 直调 plugin script(默认,绕开 sonnet wrapper)**:
55
+
56
+ > 1.0.4 起 Bash 命令字符串由 install 时直接渲染(基于 `~/.claude/plugins/installed_plugins.json`
57
+ > 真值源),LLM 只需 copy 整段 + 替换 `%PROMPT%` 即可。**禁止凭印象拼 `node "$(ls ...) | head -1"
58
+ > task -p ...` inline 命令** —— glob hack 在 plugin 多版本 cache 下行为不可预测,且参数漂移
59
+ > 与 v4.4.1 的 195 处错名 bug 同型。
55
60
 
56
61
  ```
62
+ # 后端审查(codex 视角)
57
63
  Bash({
58
- command: `node "$(ls ~/.claude/plugins/cache/openai-codex/codex/*/scripts/codex-companion.mjs | head -1)" task -p "<整段 prompt 含 ROLE_FILE + TASK + OUTPUT 要求>" --json`,
64
+ command: `{{CODEX_BASH_TASK}}`, ← install 时已渲染为完整命令,含 heredoc 安全 prompt 注入
59
65
  description: "Review: backend (codex direct)",
60
66
  run_in_background: true,
61
67
  timeout: 3600000
62
68
  })
63
69
 
70
+ # 前端审查(gemini 视角)
64
71
  Bash({
65
- command: `node "$(ls ~/.claude/plugins/cache/google-gemini/gemini/*/scripts/gemini-companion.mjs | head -1)" task -p "<整段 prompt>" --json`,
72
+ command: `{{GEMINI_BASH_TASK}}`,
66
73
  description: "Review: frontend (gemini direct)",
67
74
  run_in_background: true,
68
75
  timeout: 3600000
69
76
  })
70
77
  ```
71
78
 
72
- **不要**用 `Agent(subagent_type="codex:codex-rescue"|"gemini:gemini-rescue")` —— plugin spawn sonnet wrapper,broker 故障/CLI 空答时 wrapper 可能 silent fallback 自答冒充 cross-vendor 视角(v4.4.2 hotfix 决策,详见 `src/utils/verify-orchestrator.ts:planVerifyWave` `useDirectBashInvocation` 选项)。
79
+ LLM 消费协议:把上面命令字符串里的 `%PROMPT%` 替换为完整 prompt 文本(含 ROLE_FILE 引用 +
80
+ TASK 描述 + OUTPUT 要求),**不要在外面加任何 escape**——heredoc `<<'CCG_PROMPT_EOF'`
81
+ 的单引号 delimiter 已保证 `$` / `'` / `"` / `\` 全部按字面量处理。
82
+
83
+ ⛔ **不要**用 `Agent(subagent_type="codex:codex-rescue"|"gemini:gemini-rescue")`:
84
+
85
+ review/verify 路径输出**直接落地**决策(PR merge / advance / revise / escalate),无下游兜底。
86
+ plugin 经由 `Agent(...)` spawn 时引擎启动 sonnet wrapper 扮演 codex/gemini 客户端,broker 故障 /
87
+ CLI 空答 / auth 过期时 wrapper 受 instruction-tuning 驱动**自答 fabricated cross-vendor 视角**
88
+ (silent fallback),主线无法察觉。Bash 直调消除 wrapper 层,stdout 即真实 plugin 输出。
89
+
90
+ 详见 `src/utils/verify-orchestrator.ts:planVerifyWave` 的 `useDirectBashInvocation` 选项 +
91
+ `src/utils/plugin-bash-codegen.ts`(1.0.4 install-time codegen)。
73
92
 
74
93
  主线接 stdout JSON(5-50KB),按 plugin --json schema 解析 `result.text` 字段,失败信号:
75
94
  - exit code ≠ 0 → loud fail,触发 v1.7.87 标准 2-retry / 5s / 3-attempts 规则
@@ -165,11 +184,21 @@ EOF",
165
184
 
166
185
  **仅当 `$ARGUMENTS` 含 `--adversarial` 字面量时启动**。否则跳过本阶段。
167
186
 
168
- 调用方式(v4.4.2 起 Bash 直调,绕开 sonnet wrapper):
187
+ 调用方式(Bash 直调,绕开 sonnet wrapper):
169
188
 
170
189
  ```
171
190
  Bash({
172
- command: `node "$(ls ~/.claude/plugins/cache/openai-codex/codex/*/scripts/codex-companion.mjs | head -1)" task -p "--adversarial-review
191
+ command: `{{CODEX_BASH_TASK}}`, ← 同上,install 时渲染为完整命令
192
+ description: "Adversarial review (codex direct)",
193
+ run_in_background: true,
194
+ timeout: 3600000
195
+ })
196
+ ```
197
+
198
+ 把命令里的 `%PROMPT%` 替换为以下完整 prompt 文本(heredoc 已保证字面量传递,不需要 escape):
199
+
200
+ ```
201
+ --adversarial-review
173
202
 
174
203
  请对以下代码变更进行敌对视角审查:
175
204
 
@@ -184,14 +213,11 @@ Bash({
184
213
  你的任务:
185
214
  1. 找出前两轮**未发现或低估**的安全/性能/正确性漏洞
186
215
  2. 假设代码作者刻意误导,挑刺
187
- 3. 输出格式:[Critical-Adversarial] / [Major-Adversarial] 列表,每条标'为什么前两轮没发现'" --json`,
188
- description: "Adversarial review (codex direct)",
189
- run_in_background: true,
190
- timeout: 3600000
191
- })
216
+ 3. 输出格式:[Critical-Adversarial] / [Major-Adversarial] 列表,每条标"为什么前两轮没发现"
192
217
  ```
193
218
 
194
- ⛔ 不用 `Agent(subagent_type="codex:codex-rescue")` —— v4.4.2 决策:review/verify 路径绕开 sonnet wrapper,避免 silent fallback 冒充 adversarial 视角。
219
+ ⛔ 不用 `Agent(subagent_type="codex:codex-rescue")`:review/verify 路径输出直接落地,silent fallback
220
+ 风险(sonnet wrapper 受 instruction-tuning 自答冒充 adversarial 视角)不可接受。详见前文「通道 A」段。
195
221
 
196
222
  收到 stdout JSON 后解析 `result.text` 字段保留待阶段 3 综合。stdout 空或 exit≠0 → 走标准 2-retry 规则。
197
223
 
@@ -152,7 +152,7 @@ Required:
152
152
 
153
153
  Optional:
154
154
  --tier <fast|triple|debate> Quality tier; maps to --max-budget-usd
155
- (fast=1, triple=2, debate=5). Default: triple.
155
+ (fast=50, triple=100, debate=250). Default: triple.
156
156
  --max-budget-usd <N> Override per-call budget cap.
157
157
  --grace-ms <N> SIGTERM->SIGKILL grace (default 5000).
158
158
 
@@ -193,7 +193,13 @@ function writeState(workdir, jobId, state) {
193
193
  // production phase-runner spawn).
194
194
  // ---------------------------------------------------------------------------
195
195
 
196
- const TIER_BUDGET = { fast: 1.0, triple: 2.0, debate: 5.0 }
196
+ // v1.0.3: ×50 raise after user feedback. PoC D3 baseline (fast=$1 / triple=$2
197
+ // / debate=$5) was tuned for "p90 + 50% margin" of *single phase* spawns, but
198
+ // real autonomous runs hit the cap on edge cases ($1.034 over $1) — tight
199
+ // enough to surface as failures rather than runaway loops. Raising to 50/100/
200
+ // 250 keeps the runaway-loop guardrail intact (a stuck loop blows >$1000 fast)
201
+ // while removing the false-positive failure mode for legitimately complex phases.
202
+ const TIER_BUDGET = { fast: 50.0, triple: 100.0, debate: 250.0 }
197
203
 
198
204
  function buildClaudeArgs({ promptFile, workdir, tier, maxBudgetUsd }) {
199
205
  const budget = maxBudgetUsd ?? TIER_BUDGET[tier]
@@ -132,6 +132,16 @@ const PATCHES = [
132
132
  match: /(spawnSync\(command,\s*\[name\],\s*\{\s*encoding:\s*"utf8",\s*stdio:\s*"pipe")(\s*\})/,
133
133
  replace: '$1, windowsHide: true$2',
134
134
  },
135
+ {
136
+ id: "P-9",
137
+ file: "lib/acp-client.mjs",
138
+ description: "JSON-RPC error swallowing (reject with bare {code,message} → caller sees '[object Object]')",
139
+ // Guard: any patched marker present
140
+ guard: /CCG P-9 patch/,
141
+ // Match: the bare reject(message.error) inside the response branch
142
+ match: /(if \(message\.error\) \{\s*\r?\n\s*)pending\.reject\(message\.error\);(\s*\r?\n\s*\} else \{)/,
143
+ replace: `$1// CCG P-9 patch: wrap JSON-RPC error object in Error instance.\n // Without this, callers doing \`e instanceof Error ? e.message : String(e)\`\n // get "[object Object]" — losing real error info (auth-expired, broker-dead,\n // parse-error, etc). See .ccg-migration/PLUGIN-PATCHES.md P-9.\n const _err = message.error;\n const _wrapped = Object.assign(\n new Error(typeof _err === "object" && _err !== null && _err.message\n ? String(_err.message)\n : String(_err)),\n {\n jsonrpcCode: typeof _err === "object" && _err !== null ? _err.code : undefined,\n jsonrpcData: typeof _err === "object" && _err !== null ? _err.data : undefined,\n },\n );\n pending.reject(_wrapped);$2`,
144
+ },
135
145
  ];
136
146
 
137
147
  let applied = 0;