triflux 10.9.29 → 10.9.31
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/.claude-plugin/marketplace.json +34 -0
- package/.claude-plugin/plugin.json +22 -0
- package/bin/triflux.mjs +246 -17
- package/config/mcp-registry.json +29 -0
- package/hooks/error-context.mjs +1 -1
- package/hooks/keyword-rules.json +7 -24
- package/hooks/safety-guard.mjs +42 -4
- package/hooks/session-start-fast.mjs +54 -12
- package/hub/account-broker.mjs +405 -28
- package/hub/bridge.mjs +8 -2
- package/hub/cli-adapter-base.mjs +29 -6
- package/hub/codex-adapter.mjs +3 -1
- package/hub/lib/env-detect.mjs +43 -10
- package/hub/pipe.mjs +4 -1
- package/hub/platform.mjs +8 -0
- package/hub/server.mjs +542 -217
- package/hub/team/backend.mjs +5 -1
- package/hub/team/conductor.mjs +83 -4
- package/hub/team/dashboard-open.mjs +57 -13
- package/hub/team/git-preflight.mjs +9 -9
- package/hub/team/handoff.mjs +2 -1
- package/hub/team/headless.mjs +36 -21
- package/hub/team/launcher-template.mjs +2 -0
- package/hub/team/notify.mjs +21 -2
- package/hub/team/process-cleanup.mjs +2 -2
- package/hub/team/psmux.mjs +494 -63
- package/hub/team/runtime-strategy.mjs +70 -0
- package/hub/team/swarm-cli.mjs +186 -0
- package/hub/team/swarm-hypervisor.mjs +76 -16
- package/hub/team/swarm-planner.mjs +3 -1
- package/hub/team/synapse-cli.mjs +19 -5
- package/hub/team/synapse-registry.mjs +29 -8
- package/hub/team/tui-core.mjs +8 -12
- package/hub/team/tui-lite.mjs +2 -2
- package/hub/team/tui-synapse.mjs +20 -10
- package/hub/team/tui.mjs +35 -20
- package/hub/team/worktree-lifecycle.mjs +105 -36
- package/hub/team/wt-manager.mjs +10 -2
- package/hub/workers/codex-app-server-worker.mjs +1018 -0
- package/hub/workers/delegator-mcp.mjs +24 -12
- package/hub/workers/factory.mjs +78 -1
- package/hub/workers/gemini-worker.mjs +8 -1
- package/hub/workers/interface.mjs +17 -1
- package/hub/workers/lib/jsonrpc-stdio.mjs +464 -0
- package/package.json +62 -21
- package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +7 -3
- package/scripts/claude-login-detect.mjs +24 -5
- package/scripts/config-audit.mjs +92 -22
- package/scripts/doctor-diagnose.mjs +64 -22
- package/scripts/headless-guard.mjs +6 -2
- package/scripts/hub-watchdog.mjs +86 -0
- package/scripts/keyword-detector.mjs +3 -1
- package/scripts/lib/cross-review-utils.mjs +6 -1
- package/scripts/lib/keyword-rules.mjs +1 -3
- package/scripts/lib/mcp-filter.mjs +2 -1
- package/scripts/preinstall.mjs +4 -1
- package/scripts/release/lib.mjs +10 -4
- package/scripts/remote-spawn.mjs +13 -5
- package/scripts/session-stale-cleanup.mjs +19 -10
- package/scripts/setup.mjs +17 -13
- package/scripts/sync-codex-auth.mjs +71 -0
- package/scripts/sync-hub-mcp-settings.mjs +185 -0
- package/scripts/tfx-route.sh +15 -3
- package/skills/tfx-auto/SKILL.md +22 -26
- package/skills/tfx-deep-analysis/SKILL.md +6 -1
- package/skills/tfx-doctor/SKILL.md +0 -21
- package/skills/tfx-hub/SKILL.md +85 -273
- package/skills/tfx-panel/SKILL.md +1 -1
- package/skills/tfx-panel/SKILL.md.tmpl +1 -1
- package/skills/tfx-prune/SKILL.md +1 -1
- package/skills/tfx-prune/SKILL.md.tmpl +1 -1
- package/skills/tfx-setup/SKILL.md +41 -0
- package/skills/tfx-setup/SKILL.md.tmpl +41 -0
- package/tui/codex-profile.mjs +457 -0
- package/tui/core.mjs +266 -0
- package/tui/doctor.mjs +375 -0
- package/tui/gemini-profile.mjs +299 -0
- package/tui/monitor-data.mjs +152 -0
- package/tui/monitor.mjs +330 -0
- package/tui/setup.mjs +598 -0
- package/CLAUDE.md +0 -212
- package/references/hosts.json +0 -46
- package/skills/tfx-workspace/async-tests/run-tests.sh +0 -203
- package/skills/tfx-workspace/evals/evals.json +0 -79
- package/skills/tfx-workspace/iteration-1/benchmark.json +0 -524
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/eval_metadata.json +0 -11
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/grading.json +0 -25
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/outputs/analysis.md +0 -154
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/grading.json +0 -25
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/outputs/analysis.md +0 -126
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/eval_metadata.json +0 -11
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/grading.json +0 -25
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/outputs/analysis.md +0 -119
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/grading.json +0 -25
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/outputs/analysis.md +0 -115
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/eval_metadata.json +0 -10
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/grading.json +0 -20
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/outputs/analysis.md +0 -86
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/grading.json +0 -20
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/outputs/analysis.md +0 -81
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/multi-team-creation/eval_metadata.json +0 -12
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/outputs/analysis.md +0 -316
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/outputs/analysis.md +0 -352
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/review.html +0 -1325
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/eval_metadata.json +0 -12
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/outputs/analysis.md +0 -97
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/outputs/analysis.md +0 -94
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/eval_metadata.json +0 -12
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/outputs/analysis.md +0 -209
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/outputs/analysis.md +0 -193
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-2/benchmark.json +0 -144
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/eval_metadata.json +0 -13
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/grading.json +0 -35
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/outputs/analysis.md +0 -382
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/grading.json +0 -35
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/outputs/analysis.md +0 -333
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-2/review.html +0 -1325
- package/skills/tfx-workspace/skill-snapshot/tfx-auto/SKILL.md +0 -217
- package/skills/tfx-workspace/skill-snapshot/tfx-auto-codex/SKILL.md +0 -77
- package/skills/tfx-workspace/skill-snapshot/tfx-codex/SKILL.md +0 -65
- package/skills/tfx-workspace/skill-snapshot/tfx-doctor/SKILL.md +0 -94
- package/skills/tfx-workspace/skill-snapshot/tfx-gemini/SKILL.md +0 -82
- package/skills/tfx-workspace/skill-snapshot/tfx-hub/SKILL.md +0 -133
- package/skills/tfx-workspace/skill-snapshot/tfx-multi/SKILL.md +0 -426
- package/skills/tfx-workspace/skill-snapshot/tfx-setup/SKILL.md +0 -101
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
|
|
3
|
+
"name": "triflux",
|
|
4
|
+
"description": "CLI-first multi-model orchestrator — Codex/Gemini/Claude routing with DAG execution, auto-triage, and cost optimization",
|
|
5
|
+
"owner": {
|
|
6
|
+
"name": "tellang"
|
|
7
|
+
},
|
|
8
|
+
"plugins": [
|
|
9
|
+
{
|
|
10
|
+
"name": "triflux",
|
|
11
|
+
"description": "Tri-CLI orchestrator for Claude Code. Routes tasks across Claude + Codex + Gemini with consensus intelligence, natural language routing, 42 skills, and cross-model review.",
|
|
12
|
+
"version": "10.9.31",
|
|
13
|
+
"author": {
|
|
14
|
+
"name": "tellang"
|
|
15
|
+
},
|
|
16
|
+
"source": {
|
|
17
|
+
"source": "npm",
|
|
18
|
+
"package": "triflux"
|
|
19
|
+
},
|
|
20
|
+
"category": "productivity",
|
|
21
|
+
"homepage": "https://github.com/tellang/triflux",
|
|
22
|
+
"tags": [
|
|
23
|
+
"multi-model",
|
|
24
|
+
"codex",
|
|
25
|
+
"gemini",
|
|
26
|
+
"cli-routing",
|
|
27
|
+
"orchestration",
|
|
28
|
+
"cost-optimization",
|
|
29
|
+
"dag-execution"
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"version": "10.9.31"
|
|
34
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "triflux",
|
|
3
|
+
"version": "10.9.31",
|
|
4
|
+
"description": "CLI-first multi-model orchestrator for Claude Code — route tasks to Codex, Gemini, and Claude",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "tellang"
|
|
7
|
+
},
|
|
8
|
+
"repository": "https://github.com/tellang/triflux",
|
|
9
|
+
"homepage": "https://github.com/tellang/triflux",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"claude-code",
|
|
13
|
+
"plugin",
|
|
14
|
+
"codex",
|
|
15
|
+
"gemini",
|
|
16
|
+
"cli-routing",
|
|
17
|
+
"orchestration",
|
|
18
|
+
"multi-model"
|
|
19
|
+
],
|
|
20
|
+
"skills": "./skills/",
|
|
21
|
+
"hooks": "./hooks/hooks.json"
|
|
22
|
+
}
|
package/bin/triflux.mjs
CHANGED
|
@@ -154,7 +154,8 @@ const CLI_COMMAND_SCHEMAS = Object.freeze({
|
|
|
154
154
|
{
|
|
155
155
|
name: "--diagnose",
|
|
156
156
|
type: "boolean",
|
|
157
|
-
description:
|
|
157
|
+
description:
|
|
158
|
+
"진단 번들(zip) 생성: spawn-trace + hook timing + system info",
|
|
158
159
|
},
|
|
159
160
|
{
|
|
160
161
|
name: "--json",
|
|
@@ -308,6 +309,42 @@ const CLI_COMMAND_SCHEMAS = Object.freeze({
|
|
|
308
309
|
},
|
|
309
310
|
],
|
|
310
311
|
},
|
|
312
|
+
swarm: {
|
|
313
|
+
usage: "tfx swarm <prd-path> [--dry-run|--json|--filter <shard>]",
|
|
314
|
+
description: "PRD 기반 멀티모델 x 멀티기기 스웜 실행 (#93)",
|
|
315
|
+
subcommands: {
|
|
316
|
+
plan: "tfx swarm plan <prd-path> [--json] — 실행 없이 계획만 출력",
|
|
317
|
+
list: "tfx swarm list [--json] — 활성 스웜 세션 조회 (synapse status)",
|
|
318
|
+
status: "tfx swarm status [--json] — list alias",
|
|
319
|
+
},
|
|
320
|
+
options: [
|
|
321
|
+
{
|
|
322
|
+
name: "--dry-run",
|
|
323
|
+
type: "boolean",
|
|
324
|
+
description: "PRD 분석만 수행, shard를 실행하지 않음",
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: "--json",
|
|
328
|
+
type: "boolean",
|
|
329
|
+
description: "구조화된 JSON 출력",
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
name: "--filter",
|
|
333
|
+
type: "string",
|
|
334
|
+
description: "특정 shard만 실행 (이름 매칭)",
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: "--max-restarts",
|
|
338
|
+
type: "number",
|
|
339
|
+
description: "shard별 최대 재시작 횟수 (기본 2)",
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: "--logs-dir",
|
|
343
|
+
type: "string",
|
|
344
|
+
description: "이벤트 로그 출력 디렉토리 오버라이드",
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
},
|
|
311
348
|
why: {
|
|
312
349
|
usage: "tfx why <path> [--json]",
|
|
313
350
|
description: "해당 경로의 마지막 커밋에서 X-Intent 트레일러 추출",
|
|
@@ -525,6 +562,10 @@ function whichInShell(cmd, shell) {
|
|
|
525
562
|
["-c", `source ~/.bashrc 2>/dev/null && command -v "${cmd}" 2>/dev/null`],
|
|
526
563
|
],
|
|
527
564
|
cmd: ["cmd", ["/c", "where", cmd]],
|
|
565
|
+
zsh: [
|
|
566
|
+
"zsh",
|
|
567
|
+
["-c", `source ~/.zshrc 2>/dev/null && command -v "${cmd}" 2>/dev/null`],
|
|
568
|
+
],
|
|
528
569
|
pwsh: [
|
|
529
570
|
"pwsh",
|
|
530
571
|
[
|
|
@@ -852,7 +893,7 @@ function getClaudeRoutingSyncSummary(results) {
|
|
|
852
893
|
|
|
853
894
|
function checkCliCrossShell(cmd, installHint) {
|
|
854
895
|
const shells =
|
|
855
|
-
process.platform === "win32" ? ["bash", "cmd", "pwsh"] : ["bash"];
|
|
896
|
+
process.platform === "win32" ? ["bash", "cmd", "pwsh"] : ["bash", "zsh"];
|
|
856
897
|
let anyFound = false;
|
|
857
898
|
let bashMissing = false;
|
|
858
899
|
const shellResults = [];
|
|
@@ -1186,6 +1227,24 @@ function cmdSetup(options = {}) {
|
|
|
1186
1227
|
}
|
|
1187
1228
|
}
|
|
1188
1229
|
|
|
1230
|
+
// ── tmux 기본 셸 확인 (macOS/Linux) ──
|
|
1231
|
+
if (process.platform !== "win32" && which("tmux")) {
|
|
1232
|
+
try {
|
|
1233
|
+
const shellOut = execSync(
|
|
1234
|
+
"tmux show-options -g default-shell 2>/dev/null",
|
|
1235
|
+
{
|
|
1236
|
+
encoding: "utf8",
|
|
1237
|
+
timeout: 3000,
|
|
1238
|
+
},
|
|
1239
|
+
).trim();
|
|
1240
|
+
if (shellOut) {
|
|
1241
|
+
ok(`tmux 기본 셸: ${shellOut.split(/\s+/).pop() || "확인 완료"}`);
|
|
1242
|
+
}
|
|
1243
|
+
} catch {
|
|
1244
|
+
/* tmux 서버 미실행 — 무시 */
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1189
1248
|
// ── 결과 추적 ──
|
|
1190
1249
|
const summary = [];
|
|
1191
1250
|
|
|
@@ -1881,7 +1940,10 @@ async function cmdDoctor(options = {}) {
|
|
|
1881
1940
|
}
|
|
1882
1941
|
{
|
|
1883
1942
|
const claudeGuide = ensureGlobalClaudeRoutingSection(CLAUDE_DIR);
|
|
1884
|
-
if (
|
|
1943
|
+
if (
|
|
1944
|
+
claudeGuide.skipped &&
|
|
1945
|
+
claudeGuide.reason !== "global_sync_disabled"
|
|
1946
|
+
)
|
|
1885
1947
|
warn(`CLAUDE.md 라우팅 섹션 확인 실패: ${claudeGuide.reason}`);
|
|
1886
1948
|
else if (
|
|
1887
1949
|
claudeGuide.action === "created" ||
|
|
@@ -2449,6 +2511,86 @@ async function cmdDoctor(options = {}) {
|
|
|
2449
2511
|
addDoctorCheck(report, { name: "stale-skills", status: "ok" });
|
|
2450
2512
|
}
|
|
2451
2513
|
|
|
2514
|
+
// 8.5 Dev 의존성 (npm link 환경에서 node_modules 누락 감지 — Issue #101)
|
|
2515
|
+
section("Dev Dependencies");
|
|
2516
|
+
try {
|
|
2517
|
+
const pkgJsonPath = join(PKG_ROOT, "package.json");
|
|
2518
|
+
const nodeModulesPath = join(PKG_ROOT, "node_modules");
|
|
2519
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
|
|
2520
|
+
const runtimeDeps = Object.keys(pkgJson.dependencies || {});
|
|
2521
|
+
const isLinkedDev = (() => {
|
|
2522
|
+
try {
|
|
2523
|
+
const stat = statSync(nodeModulesPath);
|
|
2524
|
+
return stat.isDirectory();
|
|
2525
|
+
} catch {
|
|
2526
|
+
return false;
|
|
2527
|
+
}
|
|
2528
|
+
})();
|
|
2529
|
+
const missingDeps = [];
|
|
2530
|
+
if (!isLinkedDev) {
|
|
2531
|
+
// node_modules 전체 누락 — 전역 install 또는 미압축 릴리즈일 가능성
|
|
2532
|
+
if (runtimeDeps.length > 0) {
|
|
2533
|
+
missingDeps.push(...runtimeDeps);
|
|
2534
|
+
}
|
|
2535
|
+
} else {
|
|
2536
|
+
for (const dep of runtimeDeps) {
|
|
2537
|
+
const depPath = join(nodeModulesPath, ...dep.split("/"));
|
|
2538
|
+
if (!existsSync(depPath)) missingDeps.push(dep);
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
if (missingDeps.length === 0) {
|
|
2542
|
+
ok(
|
|
2543
|
+
`의존성 ${runtimeDeps.length}개 설치됨${
|
|
2544
|
+
isLinkedDev ? ` ${DIM}(dev)${RESET}` : ""
|
|
2545
|
+
}`,
|
|
2546
|
+
);
|
|
2547
|
+
addDoctorCheck(report, {
|
|
2548
|
+
name: "dev-deps",
|
|
2549
|
+
status: "ok",
|
|
2550
|
+
total: runtimeDeps.length,
|
|
2551
|
+
linkedDev: isLinkedDev,
|
|
2552
|
+
});
|
|
2553
|
+
} else {
|
|
2554
|
+
const head = missingDeps.slice(0, 5);
|
|
2555
|
+
const tail = missingDeps.length > 5 ? `+${missingDeps.length - 5}` : "";
|
|
2556
|
+
warn(
|
|
2557
|
+
`누락 의존성 ${missingDeps.length}개: ${head.join(", ")}${tail ? ` ${tail}` : ""}`,
|
|
2558
|
+
);
|
|
2559
|
+
info("수정: tfx doctor --fix 또는 `npm install` (PKG_ROOT 내)");
|
|
2560
|
+
addDoctorCheck(report, {
|
|
2561
|
+
name: "dev-deps",
|
|
2562
|
+
status: "missing",
|
|
2563
|
+
missing: missingDeps,
|
|
2564
|
+
linkedDev: isLinkedDev,
|
|
2565
|
+
pkgRoot: PKG_ROOT,
|
|
2566
|
+
fix: "tfx doctor --fix",
|
|
2567
|
+
});
|
|
2568
|
+
issues++;
|
|
2569
|
+
if (fix) {
|
|
2570
|
+
// --fix 모드: npm install 실행 (Windows 호환 shell: true)
|
|
2571
|
+
info(`npm install 실행 중 (${PKG_ROOT})...`);
|
|
2572
|
+
try {
|
|
2573
|
+
const { execFileSync } = await import("node:child_process");
|
|
2574
|
+
execFileSync("npm", ["install", "--no-audit", "--no-fund"], {
|
|
2575
|
+
cwd: PKG_ROOT,
|
|
2576
|
+
stdio: "inherit",
|
|
2577
|
+
shell: process.platform === "win32",
|
|
2578
|
+
});
|
|
2579
|
+
ok("npm install 완료 — 의존성 복구됨");
|
|
2580
|
+
} catch (err) {
|
|
2581
|
+
warn(`npm install 실패: ${err?.message || err}`);
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
} catch (err) {
|
|
2586
|
+
warn(`dev 의존성 체크 실패: ${err?.message || err}`);
|
|
2587
|
+
addDoctorCheck(report, {
|
|
2588
|
+
name: "dev-deps",
|
|
2589
|
+
status: "error",
|
|
2590
|
+
error: String(err?.message || err),
|
|
2591
|
+
});
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2452
2594
|
// 9. 플러그인 등록
|
|
2453
2595
|
section("Plugin");
|
|
2454
2596
|
const pluginsFile = join(CLAUDE_DIR, "plugins", "installed_plugins.json");
|
|
@@ -4794,13 +4936,35 @@ async function cmdHub(args = [], options = {}) {
|
|
|
4794
4936
|
});
|
|
4795
4937
|
}
|
|
4796
4938
|
|
|
4939
|
+
// Issue #102: spawn stderr 를 임시 파일로 캡처해 실패 시 root cause 노출.
|
|
4940
|
+
// detached spawn 은 pipe 유지가 까다로우니 fd 리다이렉트로 접근.
|
|
4941
|
+
const { openSync: _openSync, closeSync: _closeSync } = await import(
|
|
4942
|
+
"node:fs"
|
|
4943
|
+
);
|
|
4944
|
+
const { tmpdir: _tmpdir } = await import("node:os");
|
|
4945
|
+
const startupErrPath = join(
|
|
4946
|
+
_tmpdir(),
|
|
4947
|
+
`tfx-hub-start-${Date.now()}-${process.pid}.err`,
|
|
4948
|
+
);
|
|
4949
|
+
let errFd;
|
|
4950
|
+
try {
|
|
4951
|
+
errFd = _openSync(startupErrPath, "w");
|
|
4952
|
+
} catch {
|
|
4953
|
+
errFd = undefined;
|
|
4954
|
+
}
|
|
4955
|
+
|
|
4797
4956
|
const child = spawn(process.execPath, [serverPath], {
|
|
4798
4957
|
env: { ...process.env, TFX_HUB_PORT: port },
|
|
4799
|
-
stdio: "ignore",
|
|
4958
|
+
stdio: ["ignore", "ignore", errFd ?? "ignore"],
|
|
4800
4959
|
detached: true,
|
|
4801
4960
|
windowsHide: true,
|
|
4802
4961
|
});
|
|
4803
4962
|
child.unref();
|
|
4963
|
+
if (errFd !== undefined) {
|
|
4964
|
+
try {
|
|
4965
|
+
_closeSync(errFd);
|
|
4966
|
+
} catch {}
|
|
4967
|
+
}
|
|
4804
4968
|
|
|
4805
4969
|
// PID 파일 확인 (최대 3초 대기, 100ms 폴링)
|
|
4806
4970
|
let started = false;
|
|
@@ -4824,13 +4988,46 @@ async function cmdHub(args = [], options = {}) {
|
|
|
4824
4988
|
console.log("");
|
|
4825
4989
|
autoRegisterMcp(hubInfo.url, { codexEnabled: true });
|
|
4826
4990
|
console.log("");
|
|
4991
|
+
// 성공했으면 임시 stderr 파일 정리
|
|
4992
|
+
try {
|
|
4993
|
+
unlinkSync(startupErrPath);
|
|
4994
|
+
} catch {}
|
|
4827
4995
|
} else {
|
|
4828
|
-
//
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4996
|
+
// Issue #102: 캡처된 stderr 에서 root cause 추출
|
|
4997
|
+
let rootCause = "";
|
|
4998
|
+
try {
|
|
4999
|
+
rootCause = readFileSync(startupErrPath, "utf8").trim();
|
|
5000
|
+
} catch {}
|
|
5001
|
+
|
|
5002
|
+
console.log(`\n ${YELLOW}⚠${RESET} 백그라운드 시작 실패`);
|
|
5003
|
+
|
|
5004
|
+
if (rootCause) {
|
|
5005
|
+
// 가장 유용한 에러 라인 강조 (ERR_*, Error:, throw)
|
|
5006
|
+
const highlight = rootCause
|
|
5007
|
+
.split(/\r?\n/)
|
|
5008
|
+
.find((line) => /ERR_[A-Z_]+|^Error:|cannot find/i.test(line));
|
|
5009
|
+
if (highlight) {
|
|
5010
|
+
console.log(` ${RED}▸ ${highlight.trim()}${RESET}`);
|
|
5011
|
+
}
|
|
5012
|
+
console.log(`\n ${DIM}전체 로그: ${startupErrPath}${RESET}`);
|
|
5013
|
+
// 원인별 실전 힌트
|
|
5014
|
+
if (/Cannot find package/i.test(rootCause)) {
|
|
5015
|
+
console.log(
|
|
5016
|
+
` ${DIM}힌트: \`cd ${PKG_ROOT} && npm install\` 로 의존성 복구 (특히 \`npm link\` 환경).${RESET}`,
|
|
5017
|
+
);
|
|
5018
|
+
} else if (/EADDRINUSE/i.test(rootCause)) {
|
|
5019
|
+
console.log(
|
|
5020
|
+
` ${DIM}힌트: 포트 ${port} 이 이미 사용 중. \`tfx hub stop\` 후 재시도.${RESET}`,
|
|
5021
|
+
);
|
|
5022
|
+
}
|
|
5023
|
+
} else {
|
|
5024
|
+
console.log(
|
|
5025
|
+
` ${DIM}stderr 캡처 실패 — 아래 명령으로 포그라운드 실행해 원인 확인:${RESET}`,
|
|
5026
|
+
);
|
|
5027
|
+
}
|
|
5028
|
+
|
|
4832
5029
|
console.log(
|
|
4833
|
-
|
|
5030
|
+
`\n ${DIM}포그라운드 실행: TFX_HUB_PORT=${port} node ${serverPath}${RESET}\n`,
|
|
4834
5031
|
);
|
|
4835
5032
|
}
|
|
4836
5033
|
break;
|
|
@@ -5198,9 +5395,15 @@ async function main() {
|
|
|
5198
5395
|
const auditScript = join(PKG_ROOT, "scripts", "config-audit.mjs");
|
|
5199
5396
|
const auditArgs = JSON_OUTPUT ? ["--json"] : [];
|
|
5200
5397
|
try {
|
|
5201
|
-
const out = execFileSync(
|
|
5202
|
-
|
|
5203
|
-
|
|
5398
|
+
const out = execFileSync(
|
|
5399
|
+
process.execPath,
|
|
5400
|
+
[auditScript, ...auditArgs],
|
|
5401
|
+
{
|
|
5402
|
+
timeout: 15000,
|
|
5403
|
+
encoding: "utf8",
|
|
5404
|
+
windowsHide: true,
|
|
5405
|
+
},
|
|
5406
|
+
);
|
|
5204
5407
|
process.stdout.write(out);
|
|
5205
5408
|
} catch (e) {
|
|
5206
5409
|
process.stdout.write(e.stdout || "");
|
|
@@ -5213,8 +5416,12 @@ async function main() {
|
|
|
5213
5416
|
const result = await diagnose({ json: JSON_OUTPUT });
|
|
5214
5417
|
if (!JSON_OUTPUT) {
|
|
5215
5418
|
if (result.ok) {
|
|
5216
|
-
console.log(
|
|
5217
|
-
|
|
5419
|
+
console.log(
|
|
5420
|
+
`\n ${GREEN_BRIGHT}✓${RESET} 진단 번들 생성: ${result.zipPath}`,
|
|
5421
|
+
);
|
|
5422
|
+
console.log(
|
|
5423
|
+
` spawn 이벤트: ${result.traceCount}건, 훅 타이밍: ${result.hookTimingCount}건\n`,
|
|
5424
|
+
);
|
|
5218
5425
|
} else {
|
|
5219
5426
|
console.log(`\n ${RED}✗${RESET} 진단 실패: ${result.error}\n`);
|
|
5220
5427
|
}
|
|
@@ -5342,9 +5549,7 @@ async function main() {
|
|
|
5342
5549
|
return;
|
|
5343
5550
|
}
|
|
5344
5551
|
case "synapse": {
|
|
5345
|
-
const { cmdSynapseStatus } = await import(
|
|
5346
|
-
"../hub/team/synapse-cli.mjs"
|
|
5347
|
-
);
|
|
5552
|
+
const { cmdSynapseStatus } = await import("../hub/team/synapse-cli.mjs");
|
|
5348
5553
|
const sub = cmdArgs[0] || "status";
|
|
5349
5554
|
if (sub !== "status") {
|
|
5350
5555
|
throw createCliError(`synapse 서브커맨드 미지원: ${sub}`, {
|
|
@@ -5356,6 +5561,30 @@ async function main() {
|
|
|
5356
5561
|
await cmdSynapseStatus(cmdArgs.slice(1), { json: JSON_OUTPUT });
|
|
5357
5562
|
return;
|
|
5358
5563
|
}
|
|
5564
|
+
case "swarm": {
|
|
5565
|
+
await checkHubRunning();
|
|
5566
|
+
const { cmdSwarmRun, cmdSwarmPlan, cmdSwarmList } = await import(
|
|
5567
|
+
"../hub/team/swarm-cli.mjs"
|
|
5568
|
+
);
|
|
5569
|
+
const sub = cmdArgs[0] || "";
|
|
5570
|
+
if (sub === "list" || sub === "status") {
|
|
5571
|
+
await cmdSwarmList(cmdArgs.slice(1), { json: JSON_OUTPUT });
|
|
5572
|
+
return;
|
|
5573
|
+
}
|
|
5574
|
+
if (sub === "plan") {
|
|
5575
|
+
await cmdSwarmPlan(cmdArgs.slice(1), { json: JSON_OUTPUT });
|
|
5576
|
+
return;
|
|
5577
|
+
}
|
|
5578
|
+
if (!sub || sub.startsWith("--")) {
|
|
5579
|
+
throw createCliError("PRD 경로가 필요합니다", {
|
|
5580
|
+
exitCode: EXIT_ARG_ERROR,
|
|
5581
|
+
reason: "argError",
|
|
5582
|
+
fix: "tfx swarm <prd-path> [--dry-run|--json|--filter <shard>]",
|
|
5583
|
+
});
|
|
5584
|
+
}
|
|
5585
|
+
await cmdSwarmRun(cmdArgs, { json: JSON_OUTPUT });
|
|
5586
|
+
return;
|
|
5587
|
+
}
|
|
5359
5588
|
case "why": {
|
|
5360
5589
|
const { cmdSynapseWhy } = await import("../hub/team/synapse-cli.mjs");
|
|
5361
5590
|
await cmdSynapseWhy(cmdArgs, { json: JSON_OUTPUT });
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "mcp-registry-schema",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"description": "MCP 서버 중앙 레지스트리 — 진실의 원천",
|
|
5
|
+
"defaults": {
|
|
6
|
+
"transport": "hub-url",
|
|
7
|
+
"hub_base": "http://127.0.0.1:27888"
|
|
8
|
+
},
|
|
9
|
+
"servers": {
|
|
10
|
+
"tfx-hub": {
|
|
11
|
+
"transport": "hub-url",
|
|
12
|
+
"url": "http://127.0.0.1:27888/mcp",
|
|
13
|
+
"safe": true,
|
|
14
|
+
"targets": ["claude", "gemini", "codex"],
|
|
15
|
+
"description": "triflux Hub MCP 서버"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"policies": {
|
|
19
|
+
"stdio_action": "replace-with-hub",
|
|
20
|
+
"unknown_server_action": "warn",
|
|
21
|
+
"watched_paths": [
|
|
22
|
+
"~/.gemini/settings.json",
|
|
23
|
+
"~/.codex/config.toml",
|
|
24
|
+
"~/.claude/settings.json",
|
|
25
|
+
"~/.claude/settings.local.json",
|
|
26
|
+
".mcp.json"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
package/hooks/error-context.mjs
CHANGED
package/hooks/keyword-rules.json
CHANGED
|
@@ -11,12 +11,7 @@
|
|
|
11
11
|
"skill": null,
|
|
12
12
|
"action": "suppress_all",
|
|
13
13
|
"priority": 0,
|
|
14
|
-
"supersedes": [
|
|
15
|
-
"tfx-multi",
|
|
16
|
-
"tfx-unified",
|
|
17
|
-
"tfx-codex",
|
|
18
|
-
"tfx-gemini"
|
|
19
|
-
],
|
|
14
|
+
"supersedes": ["tfx-multi", "tfx-unified", "tfx-codex", "tfx-gemini"],
|
|
20
15
|
"exclusive": true,
|
|
21
16
|
"state": null,
|
|
22
17
|
"mcp_route": null
|
|
@@ -62,9 +57,7 @@
|
|
|
62
57
|
],
|
|
63
58
|
"skill": "tfx-swarm",
|
|
64
59
|
"priority": 1,
|
|
65
|
-
"supersedes": [
|
|
66
|
-
"tfx-codex"
|
|
67
|
-
],
|
|
60
|
+
"supersedes": ["tfx-codex"],
|
|
68
61
|
"exclusive": false,
|
|
69
62
|
"state": null,
|
|
70
63
|
"mcp_route": null
|
|
@@ -111,9 +104,7 @@
|
|
|
111
104
|
],
|
|
112
105
|
"skill": "tfx-auto",
|
|
113
106
|
"priority": 2,
|
|
114
|
-
"supersedes": [
|
|
115
|
-
"tfx-auto-codex"
|
|
116
|
-
],
|
|
107
|
+
"supersedes": ["tfx-auto-codex"],
|
|
117
108
|
"exclusive": false,
|
|
118
109
|
"state": null,
|
|
119
110
|
"mcp_route": null
|
|
@@ -249,9 +240,7 @@
|
|
|
249
240
|
],
|
|
250
241
|
"skill": "tfx-wt",
|
|
251
242
|
"priority": 1,
|
|
252
|
-
"supersedes": [
|
|
253
|
-
"tfx-unified"
|
|
254
|
-
],
|
|
243
|
+
"supersedes": ["tfx-unified"],
|
|
255
244
|
"exclusive": false,
|
|
256
245
|
"state": null,
|
|
257
246
|
"mcp_route": null
|
|
@@ -270,9 +259,7 @@
|
|
|
270
259
|
],
|
|
271
260
|
"skill": "tfx-wt",
|
|
272
261
|
"priority": 1,
|
|
273
|
-
"supersedes": [
|
|
274
|
-
"tfx-unified"
|
|
275
|
-
],
|
|
262
|
+
"supersedes": ["tfx-unified"],
|
|
276
263
|
"exclusive": false,
|
|
277
264
|
"state": null,
|
|
278
265
|
"mcp_route": null
|
|
@@ -291,9 +278,7 @@
|
|
|
291
278
|
],
|
|
292
279
|
"skill": "tfx-wt",
|
|
293
280
|
"priority": 1,
|
|
294
|
-
"supersedes": [
|
|
295
|
-
"tfx-unified"
|
|
296
|
-
],
|
|
281
|
+
"supersedes": ["tfx-unified"],
|
|
297
282
|
"exclusive": false,
|
|
298
283
|
"state": null,
|
|
299
284
|
"mcp_route": null
|
|
@@ -312,9 +297,7 @@
|
|
|
312
297
|
],
|
|
313
298
|
"skill": "tfx-wt",
|
|
314
299
|
"priority": 1,
|
|
315
|
-
"supersedes": [
|
|
316
|
-
"tfx-unified"
|
|
317
|
-
],
|
|
300
|
+
"supersedes": ["tfx-unified"],
|
|
318
301
|
"exclusive": false,
|
|
319
302
|
"state": null,
|
|
320
303
|
"mcp_route": null
|
package/hooks/safety-guard.mjs
CHANGED
|
@@ -11,6 +11,22 @@
|
|
|
11
11
|
import { existsSync, readFileSync } from "node:fs";
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
|
|
14
|
+
// ── 로컬 우회 플래그 ──────────────────────────────────────────
|
|
15
|
+
// LOCAL ONLY — Issue #89 대안 API 구현 전까지 psmux kill 시리즈 우회용.
|
|
16
|
+
// 활성 조건 (OR):
|
|
17
|
+
// 1) env: TFX_CLEANUP_BYPASS=1
|
|
18
|
+
// 2) 파일: .claude/cleanup-bypass 존재 (repo root)
|
|
19
|
+
// 파일 방식은 Claude Code Bash 도구가 훅에 env 전달 못하는 경우에도 동작.
|
|
20
|
+
// 정식 해결: hub/team/psmux.mjs 에 listSessions/killSessionByTitle/pruneStale 노출.
|
|
21
|
+
const CLEANUP_BYPASS = (() => {
|
|
22
|
+
if (process.env.TFX_CLEANUP_BYPASS === "1") return true;
|
|
23
|
+
try {
|
|
24
|
+
return existsSync(join(process.cwd(), ".claude", "cleanup-bypass"));
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
})();
|
|
29
|
+
|
|
14
30
|
// ── 차단 규칙 ──────────────────────────────────────────────
|
|
15
31
|
const BLOCK_RULES = [
|
|
16
32
|
{
|
|
@@ -44,14 +60,16 @@ const BLOCK_RULES = [
|
|
|
44
60
|
{
|
|
45
61
|
pattern: /\bpsmux\s+kill-session\b/i,
|
|
46
62
|
reason:
|
|
47
|
-
"raw psmux kill-session 차단 — WT ConPTY 프리징 위험.
|
|
63
|
+
"raw psmux kill-session 차단 — WT ConPTY 프리징 위험. 대안: listSessions()/killSessionByTitle()/pruneStale() 또는 node hub/team/psmux.mjs --internal kill-by-title <prefix|/regex/> (또는 TFX_CLEANUP_BYPASS=1/.claude/cleanup-bypass)",
|
|
48
64
|
skipIfGit: true,
|
|
65
|
+
cleanupBypass: true,
|
|
49
66
|
},
|
|
50
67
|
{
|
|
51
68
|
pattern: /\bpsmux\s+kill-server\b/i,
|
|
52
69
|
reason:
|
|
53
|
-
"psmux kill-server 차단 — 모든 세션이 즉시 종료됩니다. node hub/team/psmux.mjs kill-swarm 사용",
|
|
70
|
+
"psmux kill-server 차단 — 모든 세션이 즉시 종료됩니다. node hub/team/psmux.mjs kill-swarm 사용 (또는 TFX_CLEANUP_BYPASS=1)",
|
|
54
71
|
skipIfGit: true,
|
|
72
|
+
cleanupBypass: true,
|
|
55
73
|
},
|
|
56
74
|
];
|
|
57
75
|
|
|
@@ -70,8 +88,11 @@ const WT_DIRECT_BLOCK_MESSAGE =
|
|
|
70
88
|
" wt.createTab({ title, command, profile, cwd }) — 새 탭\n" +
|
|
71
89
|
" wt.splitPane({ direction: 'H'|'V', title, command }) — 패인 분할\n" +
|
|
72
90
|
" wt.applySplitLayout([{ title, command, direction }]) — 다중 배치\n" +
|
|
73
|
-
|
|
91
|
+
"사용법: node -e \"import('./hub/team/wt-manager.mjs').then(m => { const wt = m.createWtManager(); wt.createTab({ title: '제목', command: 'pwsh' }); })\"";
|
|
74
92
|
|
|
93
|
+
const PSMUX_INTERNAL_WRAPPER_PATTERNS = [
|
|
94
|
+
/node(?:\.exe)?\s+.*hub[\\/]+team[\\/]+psmux\.mjs\s+--internal\s+(?:list|kill-by-title|prune-stale)\b/i,
|
|
95
|
+
];
|
|
75
96
|
|
|
76
97
|
// ── SSH+PowerShell bash 문법 차단 ────────────────────────────
|
|
77
98
|
// 원격 기본 셸이 PowerShell인 호스트에 bash redirect/glob을 보내면 오동작
|
|
@@ -261,6 +282,10 @@ function main() {
|
|
|
261
282
|
return hasSegmentInvocation(cmd, [/\bpsmux\s+kill-(session|server)\b/i]);
|
|
262
283
|
}
|
|
263
284
|
|
|
285
|
+
function isAllowedPsmuxWrapperInvocation(cmd) {
|
|
286
|
+
return hasSegmentInvocation(cmd, PSMUX_INTERNAL_WRAPPER_PATTERNS);
|
|
287
|
+
}
|
|
288
|
+
|
|
264
289
|
function isWtDirectInvocation(cmd) {
|
|
265
290
|
return hasSegmentInvocation(cmd, WT_DIRECT_PATTERNS);
|
|
266
291
|
}
|
|
@@ -269,6 +294,10 @@ function main() {
|
|
|
269
294
|
blockCommand(WT_DIRECT_BLOCK_MESSAGE, command);
|
|
270
295
|
}
|
|
271
296
|
|
|
297
|
+
if (isAllowedPsmuxWrapperInvocation(command)) {
|
|
298
|
+
process.exit(0);
|
|
299
|
+
}
|
|
300
|
+
|
|
272
301
|
// 0.1. reflexion 적응형 패널티 — 이전 세션에서 차단된 패턴 사전 경고
|
|
273
302
|
const penalties = loadReflexionPenalties();
|
|
274
303
|
if (penalties.length > 0) {
|
|
@@ -299,7 +328,10 @@ function main() {
|
|
|
299
328
|
|
|
300
329
|
// 0.5. SSH → Windows(PowerShell) 호스트에만 bash 문법 전달 차단
|
|
301
330
|
// macOS/Linux 대상은 bash/zsh이므로 허용. hosts.json OS로 판별.
|
|
302
|
-
if (
|
|
331
|
+
if (
|
|
332
|
+
hasSegmentInvocation(command, [/^\s*ssh\s+/i]) &&
|
|
333
|
+
isSshTargetWindows(command)
|
|
334
|
+
) {
|
|
303
335
|
const segments = command.split(/\s*(?:&&|;|\|\||\|)\s*/);
|
|
304
336
|
for (const seg of segments) {
|
|
305
337
|
const sshMatch = seg.trim().match(/^ssh\s+\S+\s+(.*)/s);
|
|
@@ -317,12 +349,18 @@ function main() {
|
|
|
317
349
|
|
|
318
350
|
// 1. BLOCK 체크 — exit 2로 차단
|
|
319
351
|
for (const rule of BLOCK_RULES) {
|
|
352
|
+
if (rule.cleanupBypass && CLEANUP_BYPASS) continue;
|
|
320
353
|
if (rule.skipIfGit && !isPsmuxInvocation(command)) continue;
|
|
321
354
|
if (rule.pattern.test(command)) {
|
|
322
355
|
blockCommand(`[triflux safety-guard] BLOCKED: ${rule.reason}`, command);
|
|
323
356
|
}
|
|
324
357
|
}
|
|
325
358
|
|
|
359
|
+
// wt 정리 명령 우회 (TFX_CLEANUP_BYPASS=1 한정). new-tab/split-pane만 차단 유지하려면 아래 조건 세분화.
|
|
360
|
+
if (CLEANUP_BYPASS) {
|
|
361
|
+
// bypass 모드에서는 아래 wt 검사를 이미 통과한 상태. 추가 작업 없음.
|
|
362
|
+
}
|
|
363
|
+
|
|
326
364
|
// 2. WARN 체크 — allow + additionalContext
|
|
327
365
|
const warnings = [];
|
|
328
366
|
for (const rule of WARN_RULES) {
|