ccgx-workflow 2.1.0 → 2.1.1

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.DVsx1kL4.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.BIcRT31-.mjs';
5
5
  import { execSync } from 'node:child_process';
6
6
  import 'inquirer';
7
7
  import 'ora';
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { h as collectSkills, j as resolvePluginBashCommand } from './shared/ccgx-workflow.DVsx1kL4.mjs';
2
- export { k as changeLanguage, G as checkForUpdates, I as collectInvocableSkills, H as compareVersions, m as createDefaultConfig, n as createDefaultRouting, J as generateCommandContent, o as getCcgDir, p as getConfigPath, E as getCurrentVersion, F as getLatestVersion, t as getWorkflowById, q as getWorkflowConfigs, c as i18n, e as init, b as initI18n, y as installAceTool, z as installAceToolRs, K as installSkillCommands, x as installWorkflows, C as migrateToV1_4_0, D as needsMigration, L as parseFrontmatter, a as readCcgConfig, s as showMainMenu, B as uninstallAceTool, A as uninstallWorkflows, u as update, l as writeCcgConfig } from './shared/ccgx-workflow.DVsx1kL4.mjs';
1
+ import { h as collectSkills, j as resolvePluginBashCommand } from './shared/ccgx-workflow.BIcRT31-.mjs';
2
+ export { k as changeLanguage, G as checkForUpdates, I as collectInvocableSkills, H as compareVersions, m as createDefaultConfig, n as createDefaultRouting, J as generateCommandContent, o as getCcgDir, p as getConfigPath, E as getCurrentVersion, F as getLatestVersion, t as getWorkflowById, q as getWorkflowConfigs, c as i18n, e as init, b as initI18n, y as installAceTool, z as installAceToolRs, K as installSkillCommands, x as installWorkflows, C as migrateToV1_4_0, D as needsMigration, L as parseFrontmatter, a as readCcgConfig, s as showMainMenu, B as uninstallAceTool, A as uninstallWorkflows, u as update, l as writeCcgConfig } from './shared/ccgx-workflow.BIcRT31-.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';
@@ -13,7 +13,7 @@ import { join as join$2 } from 'node:path/posix';
13
13
  import { join as join$1 } from 'node:path';
14
14
  import i18next from 'i18next';
15
15
 
16
- const version = "2.1.0";
16
+ const version = "2.1.1";
17
17
 
18
18
  function cmd(id, order, category, name, nameEn, description, descriptionEn, cmdOverride) {
19
19
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccgx-workflow",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
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",
@@ -66,20 +66,20 @@ argument-hint: "[代码或描述] [--adversarial] [--fix [--all] [--auto]] [--ro
66
66
  Step 1. 用 Write 工具把【小任务描述】写到 tmpfile
67
67
  ⚠️ 内容必须是任务说明(≤ 2KB),不要塞 git diff / 文件内容
68
68
  Write({
69
- file_path: "/tmp/ccg-review-codex-$JOB.txt",
69
+ file_path: ".context/tmp/ccg-review-codex-$JOB.txt",
70
70
  content: <下方"任务描述模板">
71
71
  })
72
72
  Write({ 同上,gemini 版本 })
73
73
 
74
74
  Step 2. 用 Bash 调 helper(占位符已渲染):
75
75
  Bash({
76
- command: `{{CODEX_BASH_TASK}} --prompt-file /tmp/ccg-review-codex-$JOB.txt`,
76
+ command: `{{CODEX_BASH_TASK}} --prompt-file .context/tmp/ccg-review-codex-$JOB.txt`,
77
77
  description: "Review: backend (codex direct)",
78
78
  run_in_background: true,
79
79
  timeout: 3600000
80
80
  })
81
81
  Bash({
82
- command: `{{GEMINI_BASH_TASK}} --prompt-file /tmp/ccg-review-gemini-$JOB.txt`,
82
+ command: `{{GEMINI_BASH_TASK}} --prompt-file .context/tmp/ccg-review-gemini-$JOB.txt`,
83
83
  description: "Review: frontend (gemini direct)",
84
84
  run_in_background: true,
85
85
  timeout: 3600000
@@ -148,14 +148,14 @@ CLI 空答 / auth 过期时 wrapper 受 instruction-tuning 驱动**自答 fabric
148
148
 
149
149
  ```
150
150
  Bash({
151
- command: "cat /tmp/ccg-review-codex-$JOB.txt | ~/.claude/bin/codeagent-wrapper {{LITE_MODE_FLAG}}--progress --backend codex - \"{{WORKDIR}}\"",
151
+ command: "cat .context/tmp/ccg-review-codex-$JOB.txt | ~/.claude/bin/codeagent-wrapper {{LITE_MODE_FLAG}}--progress --backend codex - \"{{WORKDIR}}\"",
152
152
  run_in_background: true,
153
153
  timeout: 3600000,
154
154
  description: "Review: backend (wrapper fallback)"
155
155
  })
156
156
  ```
157
157
 
158
- `/tmp/ccg-review-codex-$JOB.txt` 同 通道 A 的"任务描述模板"——不变。
158
+ `.context/tmp/ccg-review-codex-$JOB.txt` 同 通道 A 的"任务描述模板"——不变。
159
159
 
160
160
  > ⚠️ 通道 B `codeagent-wrapper` 已标 **deprecated**,仅 plugin 未装时使用。
161
161
 
@@ -227,11 +227,11 @@ Bash({
227
227
 
228
228
  ```
229
229
  Step 1. 用 Write 把任务描述写入 tmpfile(同样不塞 diff,让 codex 自己读):
230
- Write({ file_path: "/tmp/ccg-review-adv-$JOB.txt", content: <下方任务描述> })
230
+ Write({ file_path: ".context/tmp/ccg-review-adv-$JOB.txt", content: <下方任务描述> })
231
231
 
232
232
  Step 2. 调 helper:
233
233
  Bash({
234
- command: `{{CODEX_BASH_TASK}} --prompt-file /tmp/ccg-review-adv-$JOB.txt`,
234
+ command: `{{CODEX_BASH_TASK}} --prompt-file .context/tmp/ccg-review-adv-$JOB.txt`,
235
235
  description: "Adversarial review (codex direct)",
236
236
  run_in_background: true,
237
237
  timeout: 3600000
@@ -44,7 +44,7 @@
44
44
  // --prompt-file <path> Required. Path to file containing prompt body.
45
45
  // --json Pass --json to companion (default: true).
46
46
  // --no-json Disable --json (text output).
47
- // --timeout-ms <N> Kill companion after N ms (default: 600000).
47
+ // --timeout-ms <N> Kill companion if total wall-time exceeds N ms (default: 7200000, 2h; pass 0 to disable).
48
48
  // --max-budget-usd <N> Forwarded to companion (default: 50).
49
49
  //
50
50
  // Cross-cutting:
@@ -55,9 +55,9 @@
55
55
  // =============================================================================
56
56
 
57
57
  import { spawn } from 'node:child_process'
58
- import { existsSync, readFileSync } from 'node:fs'
58
+ import { existsSync, readFileSync, realpathSync, unlinkSync } from 'node:fs'
59
59
  import { homedir, platform } from 'node:os'
60
- import { join } from 'node:path'
60
+ import { basename, join, resolve, sep } from 'node:path'
61
61
  import { fileURLToPath } from 'node:url'
62
62
 
63
63
  // ---------------------------------------------------------------------------
@@ -103,7 +103,17 @@ function parseArgs(argv) {
103
103
  vendor: null,
104
104
  promptFile: null,
105
105
  json: true,
106
- timeoutMs: 600000,
106
+ // 2.1.1: wall-time bumped 600s → 7200s (2h). Real codex audits /
107
+ // multi-file reviews / planning tasks routinely run 5-30 min; the old
108
+ // 600s default was SIGTERM-ing healthy tasks mid-thought. 2h is a
109
+ // generous safety ceiling — pass 0 to disable entirely.
110
+ timeoutMs: 7200000,
111
+ // 2.1.1: NEW idle timeout. Wall-time alone is the wrong signal — a
112
+ // healthy long-running audit produces continuous stdout/stderr (tool
113
+ // calls, progress lines). A truly hung companion produces nothing.
114
+ // We track lastActivityAt on every stdout/stderr chunk and SIGTERM if
115
+ // silent for idleTimeoutMs (default 600s = 10min). Pass 0 to disable.
116
+ idleTimeoutMs: 600000,
107
117
  maxBudgetUsd: 50,
108
118
  // 1.0.5 regression fix: pre-1.0.5 callers always passed --write directly
109
119
  // to codex-companion. Default true preserves BC; --no-write opt-out for
@@ -127,6 +137,7 @@ function parseArgs(argv) {
127
137
  case '--json': opts.json = true; break
128
138
  case '--no-json': opts.json = false; break
129
139
  case '--timeout-ms': opts.timeoutMs = Number.parseInt(next(), 10); break
140
+ case '--idle-timeout-ms': opts.idleTimeoutMs = Number.parseInt(next(), 10); break
130
141
  case '--max-budget-usd': opts.maxBudgetUsd = Number.parseFloat(next()); break
131
142
  case '--write': opts.write = true; break
132
143
  case '--no-write': opts.write = false; break
@@ -166,7 +177,8 @@ Required:
166
177
  Optional:
167
178
  --json Pass --json to companion (default: true)
168
179
  --no-json Disable --json (text output)
169
- --timeout-ms <N> Kill companion after N ms (default: 600000)
180
+ --timeout-ms <N> Kill companion if total wall-time exceeds N ms (default: 7200000, 2h; pass 0 to disable)
181
+ --idle-timeout-ms <N> Kill companion if no stdout/stderr output for N ms (default: 600000, 10min; pass 0 to disable). Idle detection is the right signal for "hung" — healthy long-running audits keep producing tool-call progress.
170
182
  --max-budget-usd <N> Per-call cost cap (default: 50)
171
183
  --write Enable workspace-write sandbox (default: true; codex needs this to spawn read commands too under default approval policy)
172
184
  --no-write Force read-only sandbox (companion approval-policy will decline most commands)
@@ -285,6 +297,35 @@ async function main(argv) {
285
297
  process.exit(66) // EX_NOINPUT
286
298
  }
287
299
 
300
+ // 2.1.1: auto-delete the prompt file after a successful read iff the
301
+ // PHYSICAL path sits inside the caller's `<cwd>/.context/tmp/` AND the
302
+ // filename starts with `ccg-`. This is the workflow-managed throwaway-
303
+ // prompt directory (see templates/commands/review.md — Step 1 writes
304
+ // prompts here).
305
+ //
306
+ // CRITICAL — codex audit 2026-05-12 raised: lexical `resolve()` is
307
+ // insufficient. If `.context` or `.context/tmp` is a symlink/junction
308
+ // pointing outside the workspace, OR the caller passes a path whose
309
+ // lexical form is `<cwd>/.context/tmp/ccg-x.txt` but its physical
310
+ // target is `/etc/passwd` (or worse, `C:\Windows\System32\...`), the
311
+ // lexical-only check would still authorize the unlink. realpathSync
312
+ // canonicalizes ALL symlinks/junctions/`..` segments before the
313
+ // whitelist compare — both for the candidate file AND the safeRoot.
314
+ // Two ANDed gates: physical prefix + filename prefix.
315
+ try {
316
+ const resolvedFile = realpathSync(opts.promptFile)
317
+ const safeRoot = realpathSync(resolve(process.cwd(), '.context', 'tmp')) + sep
318
+ if (resolvedFile.startsWith(safeRoot) && basename(resolvedFile).startsWith('ccg-')) {
319
+ unlinkSync(resolvedFile)
320
+ }
321
+ }
322
+ catch {
323
+ // Best-effort. ENOENT (.context/tmp absent, file already gone, etc.)
324
+ // is the dominant non-error case here; we deliberately don't surface
325
+ // it. Real failures (permission etc.) will leave a file behind that
326
+ // .gitignore catches and the user's next run overwrites.
327
+ }
328
+
288
329
  // Discover companion via SSoT (no glob, no head -1)
289
330
  const disc = discoverCompanion(opts.vendor)
290
331
  if (disc.error) {
@@ -327,24 +368,53 @@ async function main(argv) {
327
368
 
328
369
  let stdoutBuf = ''
329
370
  let stderrBuf = ''
330
- child.stdout.on('data', (chunk) => { stdoutBuf += chunk.toString('utf-8') })
331
- child.stderr.on('data', (chunk) => { stderrBuf += chunk.toString('utf-8') })
371
+ let lastActivityAt = Date.now()
372
+ child.stdout.on('data', (chunk) => {
373
+ stdoutBuf += chunk.toString('utf-8')
374
+ lastActivityAt = Date.now()
375
+ })
376
+ child.stderr.on('data', (chunk) => {
377
+ stderrBuf += chunk.toString('utf-8')
378
+ lastActivityAt = Date.now()
379
+ })
332
380
 
333
- // Timeout: kill child if exceeds opts.timeoutMs
381
+ // 2.1.1: two-layer timeout. wall-time is the safety ceiling; idle is the
382
+ // primary "stuck" detector — a healthy long task keeps producing tool-call
383
+ // / progress chunks, a truly hung companion produces nothing. SIGTERM is
384
+ // best-effort on Windows where the kernel doesn't propagate to the
385
+ // grandchild tree; companion processes on this code path are expected to
386
+ // do their own subtree cleanup (taskkill /T) when they receive SIGTERM.
334
387
  let timedOut = false
335
- const timer = setTimeout(() => {
388
+ let timeoutReason = null
389
+ const killChild = (reason) => {
336
390
  timedOut = true
391
+ timeoutReason = reason
337
392
  try { child.kill('SIGTERM') } catch { /* ignore */ }
338
393
  setTimeout(() => {
339
394
  try { child.kill('SIGKILL') } catch { /* ignore */ }
340
395
  }, 5000).unref?.()
341
- }, opts.timeoutMs)
396
+ }
397
+ const wallTimer = opts.timeoutMs > 0
398
+ ? setTimeout(() => killChild(`wall-time ${opts.timeoutMs}ms`), opts.timeoutMs)
399
+ : null
400
+ // Check idle every 30s. A 30s sampling granularity adds at most 30s slop
401
+ // to the configured idle threshold, which is negligible at 600s default.
402
+ const idleChecker = opts.idleTimeoutMs > 0
403
+ ? setInterval(() => {
404
+ const silent = Date.now() - lastActivityAt
405
+ if (silent >= opts.idleTimeoutMs) {
406
+ killChild(`idle ${silent}ms exceeds ${opts.idleTimeoutMs}ms`)
407
+ }
408
+ }, 30000)
409
+ : null
410
+ if (idleChecker?.unref) idleChecker.unref()
342
411
 
343
412
  const exit = await new Promise((resolve) => {
344
413
  child.once('exit', (code, signal) => resolve({ code, signal }))
345
414
  child.once('error', (err) => resolve({ code: 70, signal: null, errorMsg: err.message }))
346
415
  })
347
- clearTimeout(timer)
416
+ if (wallTimer) clearTimeout(wallTimer)
417
+ if (idleChecker) clearInterval(idleChecker)
348
418
 
349
419
  const durationMs = Date.now() - startedAt
350
420
  const status = (!timedOut && exit.code === 0) ? 'ok' : 'error'
@@ -357,7 +427,7 @@ async function main(argv) {
357
427
  stdout: stdoutBuf,
358
428
  stderr: stderrBuf,
359
429
  }
360
- if (timedOut) result.error = `timeout after ${opts.timeoutMs}ms`
430
+ if (timedOut) result.error = `companion killed: ${timeoutReason}`
361
431
  else if (exit.errorMsg) result.error = `spawn error: ${exit.errorMsg}`
362
432
  else if (exit.code !== 0) result.error = `companion exited with code ${exit.code}`
363
433