ccgx-workflow 2.1.0 → 2.1.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/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.
|
|
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.dK6O-vwe.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.
|
|
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.
|
|
1
|
+
import { h as collectSkills, j as resolvePluginBashCommand } from './shared/ccgx-workflow.dK6O-vwe.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.dK6O-vwe.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.
|
|
16
|
+
const version = "2.1.2";
|
|
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.
|
|
3
|
+
"version": "2.1.2",
|
|
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
|
-
|
|
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
|
|
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,26 @@ function parseArgs(argv) {
|
|
|
103
103
|
vendor: null,
|
|
104
104
|
promptFile: null,
|
|
105
105
|
json: true,
|
|
106
|
-
|
|
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.2: idle detection is OPT-IN (default 0 = disabled). The original
|
|
112
|
+
// 2.1.1 assumption that "healthy long-running audits emit continuous
|
|
113
|
+
// stdout/stderr chunks" is FALSE for both vendors:
|
|
114
|
+
// - codex-companion `task`: writes progress to its internal job
|
|
115
|
+
// state.json (read via `codex status`); helper sees ZERO stdout
|
|
116
|
+
// until the final JSON envelope.
|
|
117
|
+
// - gemini-batch / gemini-cli `--output-format json`: emits 4-5
|
|
118
|
+
// startup warning lines, then SILENT through model reasoning + all
|
|
119
|
+
// tool calls, then the final JSON.
|
|
120
|
+
// Deep reasoning passes can silently exceed any reasonable idle
|
|
121
|
+
// threshold while being perfectly healthy. Wall-time (default 2h)
|
|
122
|
+
// remains the only safety bound by default. Callers who know their
|
|
123
|
+
// task class is supposed to stream (e.g. future protocol additions)
|
|
124
|
+
// can pass --idle-timeout-ms <N> explicitly.
|
|
125
|
+
idleTimeoutMs: 0,
|
|
107
126
|
maxBudgetUsd: 50,
|
|
108
127
|
// 1.0.5 regression fix: pre-1.0.5 callers always passed --write directly
|
|
109
128
|
// to codex-companion. Default true preserves BC; --no-write opt-out for
|
|
@@ -127,6 +146,7 @@ function parseArgs(argv) {
|
|
|
127
146
|
case '--json': opts.json = true; break
|
|
128
147
|
case '--no-json': opts.json = false; break
|
|
129
148
|
case '--timeout-ms': opts.timeoutMs = Number.parseInt(next(), 10); break
|
|
149
|
+
case '--idle-timeout-ms': opts.idleTimeoutMs = Number.parseInt(next(), 10); break
|
|
130
150
|
case '--max-budget-usd': opts.maxBudgetUsd = Number.parseFloat(next()); break
|
|
131
151
|
case '--write': opts.write = true; break
|
|
132
152
|
case '--no-write': opts.write = false; break
|
|
@@ -166,7 +186,8 @@ Required:
|
|
|
166
186
|
Optional:
|
|
167
187
|
--json Pass --json to companion (default: true)
|
|
168
188
|
--no-json Disable --json (text output)
|
|
169
|
-
--timeout-ms <N> Kill companion
|
|
189
|
+
--timeout-ms <N> Kill companion if total wall-time exceeds N ms (default: 7200000, 2h; pass 0 to disable)
|
|
190
|
+
--idle-timeout-ms <N> Kill companion if no stdout/stderr output for N ms (default: 0 = disabled). NOTE: codex-companion and gemini-batch both emit NOTHING to stdout/stderr during execution (progress lives in internal state files); idle defaults to disabled to avoid false-positive kills of healthy long tasks. Set to a positive value only if your task class is known to stream.
|
|
170
191
|
--max-budget-usd <N> Per-call cost cap (default: 50)
|
|
171
192
|
--write Enable workspace-write sandbox (default: true; codex needs this to spawn read commands too under default approval policy)
|
|
172
193
|
--no-write Force read-only sandbox (companion approval-policy will decline most commands)
|
|
@@ -285,6 +306,35 @@ async function main(argv) {
|
|
|
285
306
|
process.exit(66) // EX_NOINPUT
|
|
286
307
|
}
|
|
287
308
|
|
|
309
|
+
// 2.1.1: auto-delete the prompt file after a successful read iff the
|
|
310
|
+
// PHYSICAL path sits inside the caller's `<cwd>/.context/tmp/` AND the
|
|
311
|
+
// filename starts with `ccg-`. This is the workflow-managed throwaway-
|
|
312
|
+
// prompt directory (see templates/commands/review.md — Step 1 writes
|
|
313
|
+
// prompts here).
|
|
314
|
+
//
|
|
315
|
+
// CRITICAL — codex audit 2026-05-12 raised: lexical `resolve()` is
|
|
316
|
+
// insufficient. If `.context` or `.context/tmp` is a symlink/junction
|
|
317
|
+
// pointing outside the workspace, OR the caller passes a path whose
|
|
318
|
+
// lexical form is `<cwd>/.context/tmp/ccg-x.txt` but its physical
|
|
319
|
+
// target is `/etc/passwd` (or worse, `C:\Windows\System32\...`), the
|
|
320
|
+
// lexical-only check would still authorize the unlink. realpathSync
|
|
321
|
+
// canonicalizes ALL symlinks/junctions/`..` segments before the
|
|
322
|
+
// whitelist compare — both for the candidate file AND the safeRoot.
|
|
323
|
+
// Two ANDed gates: physical prefix + filename prefix.
|
|
324
|
+
try {
|
|
325
|
+
const resolvedFile = realpathSync(opts.promptFile)
|
|
326
|
+
const safeRoot = realpathSync(resolve(process.cwd(), '.context', 'tmp')) + sep
|
|
327
|
+
if (resolvedFile.startsWith(safeRoot) && basename(resolvedFile).startsWith('ccg-')) {
|
|
328
|
+
unlinkSync(resolvedFile)
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
// Best-effort. ENOENT (.context/tmp absent, file already gone, etc.)
|
|
333
|
+
// is the dominant non-error case here; we deliberately don't surface
|
|
334
|
+
// it. Real failures (permission etc.) will leave a file behind that
|
|
335
|
+
// .gitignore catches and the user's next run overwrites.
|
|
336
|
+
}
|
|
337
|
+
|
|
288
338
|
// Discover companion via SSoT (no glob, no head -1)
|
|
289
339
|
const disc = discoverCompanion(opts.vendor)
|
|
290
340
|
if (disc.error) {
|
|
@@ -327,24 +377,53 @@ async function main(argv) {
|
|
|
327
377
|
|
|
328
378
|
let stdoutBuf = ''
|
|
329
379
|
let stderrBuf = ''
|
|
330
|
-
|
|
331
|
-
child.
|
|
380
|
+
let lastActivityAt = Date.now()
|
|
381
|
+
child.stdout.on('data', (chunk) => {
|
|
382
|
+
stdoutBuf += chunk.toString('utf-8')
|
|
383
|
+
lastActivityAt = Date.now()
|
|
384
|
+
})
|
|
385
|
+
child.stderr.on('data', (chunk) => {
|
|
386
|
+
stderrBuf += chunk.toString('utf-8')
|
|
387
|
+
lastActivityAt = Date.now()
|
|
388
|
+
})
|
|
332
389
|
|
|
333
|
-
//
|
|
390
|
+
// 2.1.1: two-layer timeout. wall-time is the safety ceiling; idle is the
|
|
391
|
+
// primary "stuck" detector — a healthy long task keeps producing tool-call
|
|
392
|
+
// / progress chunks, a truly hung companion produces nothing. SIGTERM is
|
|
393
|
+
// best-effort on Windows where the kernel doesn't propagate to the
|
|
394
|
+
// grandchild tree; companion processes on this code path are expected to
|
|
395
|
+
// do their own subtree cleanup (taskkill /T) when they receive SIGTERM.
|
|
334
396
|
let timedOut = false
|
|
335
|
-
|
|
397
|
+
let timeoutReason = null
|
|
398
|
+
const killChild = (reason) => {
|
|
336
399
|
timedOut = true
|
|
400
|
+
timeoutReason = reason
|
|
337
401
|
try { child.kill('SIGTERM') } catch { /* ignore */ }
|
|
338
402
|
setTimeout(() => {
|
|
339
403
|
try { child.kill('SIGKILL') } catch { /* ignore */ }
|
|
340
404
|
}, 5000).unref?.()
|
|
341
|
-
}
|
|
405
|
+
}
|
|
406
|
+
const wallTimer = opts.timeoutMs > 0
|
|
407
|
+
? setTimeout(() => killChild(`wall-time ${opts.timeoutMs}ms`), opts.timeoutMs)
|
|
408
|
+
: null
|
|
409
|
+
// Check idle every 30s. A 30s sampling granularity adds at most 30s slop
|
|
410
|
+
// to the configured idle threshold, which is negligible at 600s default.
|
|
411
|
+
const idleChecker = opts.idleTimeoutMs > 0
|
|
412
|
+
? setInterval(() => {
|
|
413
|
+
const silent = Date.now() - lastActivityAt
|
|
414
|
+
if (silent >= opts.idleTimeoutMs) {
|
|
415
|
+
killChild(`idle ${silent}ms exceeds ${opts.idleTimeoutMs}ms`)
|
|
416
|
+
}
|
|
417
|
+
}, 30000)
|
|
418
|
+
: null
|
|
419
|
+
if (idleChecker?.unref) idleChecker.unref()
|
|
342
420
|
|
|
343
421
|
const exit = await new Promise((resolve) => {
|
|
344
422
|
child.once('exit', (code, signal) => resolve({ code, signal }))
|
|
345
423
|
child.once('error', (err) => resolve({ code: 70, signal: null, errorMsg: err.message }))
|
|
346
424
|
})
|
|
347
|
-
clearTimeout(
|
|
425
|
+
if (wallTimer) clearTimeout(wallTimer)
|
|
426
|
+
if (idleChecker) clearInterval(idleChecker)
|
|
348
427
|
|
|
349
428
|
const durationMs = Date.now() - startedAt
|
|
350
429
|
const status = (!timedOut && exit.code === 0) ? 'ok' : 'error'
|
|
@@ -357,7 +436,7 @@ async function main(argv) {
|
|
|
357
436
|
stdout: stdoutBuf,
|
|
358
437
|
stderr: stderrBuf,
|
|
359
438
|
}
|
|
360
|
-
if (timedOut) result.error = `
|
|
439
|
+
if (timedOut) result.error = `companion killed: ${timeoutReason}`
|
|
361
440
|
else if (exit.errorMsg) result.error = `spawn error: ${exit.errorMsg}`
|
|
362
441
|
else if (exit.code !== 0) result.error = `companion exited with code ${exit.code}`
|
|
363
442
|
|