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.
|
|
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.
|
|
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.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.
|
|
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.
|
|
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
|
-
|
|
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,17 @@ 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.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
|
|
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
|
-
|
|
331
|
-
child.
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
}
|
|
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(
|
|
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 = `
|
|
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
|
|