triflux 2.4.7 → 3.0.0
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/README.ko.md +9 -7
- package/README.md +9 -7
- package/bin/tfx-doctor.mjs +1 -1
- package/bin/tfx-setup.mjs +1 -1
- package/bin/triflux.mjs +25 -13
- package/hud/hud-qos-status.mjs +221 -108
- package/package.json +1 -1
- package/scripts/cli-route.sh +2 -967
- package/scripts/mcp-check.mjs +2 -2
- package/scripts/notion-read.mjs +104 -49
- package/scripts/setup.mjs +10 -5
- package/scripts/tfx-batch-stats.mjs +96 -0
- package/scripts/tfx-route-post.mjs +366 -0
- package/scripts/tfx-route.sh +448 -0
- package/skills/tfx-auto/SKILL.md +31 -31
- package/skills/tfx-codex/SKILL.md +1 -1
- package/skills/tfx-doctor/SKILL.md +2 -2
- package/skills/tfx-gemini/SKILL.md +1 -1
- package/skills/tfx-setup/SKILL.md +1 -1
package/scripts/cli-route.sh
CHANGED
|
@@ -1,968 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# cli-route.sh
|
|
3
|
-
|
|
4
|
-
# v1.1: stderr 분리, 출력 필터링, 타임아웃, MCP 프로필 지원
|
|
5
|
-
# v1.2: effort 동적 라우팅, bg/fg 모드, Opus 직접 수행, Gemini 모델 분기, 실행 로그
|
|
6
|
-
# v1.3: architect/critic Codex 이관, 리뷰어 exec review 전환, multi_agent 활성화
|
|
7
|
-
# v1.4: TFX_CLI_MODE 지원 (codex-only/gemini-only), CLI 미설치 자동 fallback
|
|
8
|
-
# v1.5: MCP 인벤토리 캐싱 — 실제 서버 가용성 기반 동적 힌트 생성
|
|
9
|
-
# v1.6: 토큰 사용량 추출 + sv-accumulator.json 누적
|
|
10
|
-
VERSION="1.6"
|
|
11
|
-
#
|
|
12
|
-
# 설치: cp scripts/cli-route.sh ~/.claude/scripts/cli-route.sh
|
|
13
|
-
#
|
|
14
|
-
# 사용법:
|
|
15
|
-
# cli-route.sh <agent_type> <prompt> [mcp_profile] [timeout_sec]
|
|
16
|
-
#
|
|
17
|
-
# 예시:
|
|
18
|
-
# cli-route.sh executor "코드 구현" implement 300
|
|
19
|
-
# cli-route.sh architect "아키텍처 분석" analyze 600
|
|
20
|
-
# cli-route.sh designer "UI 리뷰"
|
|
21
|
-
# cli-route.sh debugger "버그 분석" analyze
|
|
22
|
-
|
|
23
|
-
set -euo pipefail
|
|
24
|
-
|
|
25
|
-
# ── 인자 파싱 ──
|
|
26
|
-
AGENT_TYPE="${1:?에이전트 타입 필수 (executor, debugger, designer 등)}"
|
|
27
|
-
PROMPT="${2:?프롬프트 필수}"
|
|
28
|
-
MCP_PROFILE="${3:-auto}"
|
|
29
|
-
USER_TIMEOUT="${4:-}"
|
|
30
|
-
|
|
31
|
-
# ── CLI 경로 해석 (Windows npm global 대응) ──
|
|
32
|
-
CODEX_BIN="${CODEX_BIN:-$(command -v codex 2>/dev/null || echo codex)}"
|
|
33
|
-
GEMINI_BIN="${GEMINI_BIN:-$(command -v gemini 2>/dev/null || echo gemini)}"
|
|
34
|
-
|
|
35
|
-
# ── 상수 ──
|
|
36
|
-
MAX_STDOUT_BYTES=51200 # 50KB — Claude 컨텍스트 절약
|
|
37
|
-
TIMESTAMP=$(date +%s)
|
|
38
|
-
STDERR_LOG="/tmp/omc-route-${AGENT_TYPE}-${TIMESTAMP}-stderr.log"
|
|
39
|
-
STDOUT_LOG="/tmp/omc-route-${AGENT_TYPE}-${TIMESTAMP}-stdout.log"
|
|
40
|
-
|
|
41
|
-
# ── 라우팅 테이블 ──
|
|
42
|
-
# 반환: CLI_CMD, CLI_ARGS, CLI_TYPE, CLI_EFFORT, DEFAULT_TIMEOUT, RUN_MODE, OPUS_OVERSIGHT
|
|
43
|
-
#
|
|
44
|
-
# RUN_MODE: bg (백그라운드 — 독립 실행, 결과 나중에 수집)
|
|
45
|
-
# fg (포어그라운드 — 결과가 다음 단계를 블로킹)
|
|
46
|
-
#
|
|
47
|
-
# OPUS_OVERSIGHT: true — Codex 결과를 Claude Opus가 검증/보완해야 함
|
|
48
|
-
# false — Codex/Gemini 결과를 그대로 사용
|
|
49
|
-
#
|
|
50
|
-
route_agent() {
|
|
51
|
-
local agent="$1"
|
|
52
|
-
local codex_base="--dangerously-bypass-approvals-and-sandbox --skip-git-repo-check"
|
|
53
|
-
|
|
54
|
-
case "$agent" in
|
|
55
|
-
# ─── 구현 레인 ───
|
|
56
|
-
|
|
57
|
-
# Codex — 코드 구현 (effort: high, 360s, fg — 후속 태스크가 의존)
|
|
58
|
-
executor)
|
|
59
|
-
CLI_TYPE="codex"
|
|
60
|
-
CLI_CMD="codex"
|
|
61
|
-
CLI_ARGS="exec ${codex_base}"
|
|
62
|
-
CLI_EFFORT="high"
|
|
63
|
-
DEFAULT_TIMEOUT=360
|
|
64
|
-
RUN_MODE="fg"
|
|
65
|
-
OPUS_OVERSIGHT="false"
|
|
66
|
-
;;
|
|
67
|
-
# Codex — 빌드 수정 (effort: fast, 180s, fg — 빌드 통과 확인 필요)
|
|
68
|
-
build-fixer)
|
|
69
|
-
CLI_TYPE="codex"
|
|
70
|
-
CLI_CMD="codex"
|
|
71
|
-
CLI_ARGS="--profile fast exec ${codex_base}"
|
|
72
|
-
CLI_EFFORT="fast"
|
|
73
|
-
DEFAULT_TIMEOUT=180
|
|
74
|
-
RUN_MODE="fg"
|
|
75
|
-
OPUS_OVERSIGHT="false"
|
|
76
|
-
;;
|
|
77
|
-
# Codex — 디버깅 (effort: high, 300s, bg — 분석 결과 나중에 수집)
|
|
78
|
-
debugger)
|
|
79
|
-
CLI_TYPE="codex"
|
|
80
|
-
CLI_CMD="codex"
|
|
81
|
-
CLI_ARGS="exec ${codex_base}"
|
|
82
|
-
CLI_EFFORT="high"
|
|
83
|
-
DEFAULT_TIMEOUT=300
|
|
84
|
-
RUN_MODE="bg"
|
|
85
|
-
OPUS_OVERSIGHT="false"
|
|
86
|
-
;;
|
|
87
|
-
# Codex — 자율 실행 (effort: xhigh, 900s, bg — 장시간 독립 수행)
|
|
88
|
-
deep-executor)
|
|
89
|
-
CLI_TYPE="codex"
|
|
90
|
-
CLI_CMD="codex"
|
|
91
|
-
CLI_ARGS="--profile xhigh exec ${codex_base}"
|
|
92
|
-
CLI_EFFORT="xhigh"
|
|
93
|
-
DEFAULT_TIMEOUT=1200
|
|
94
|
-
RUN_MODE="bg"
|
|
95
|
-
OPUS_OVERSIGHT="true"
|
|
96
|
-
;;
|
|
97
|
-
|
|
98
|
-
# ─── 설계/분석 레인 ───
|
|
99
|
-
|
|
100
|
-
# Codex — 아키텍처 (effort: xhigh, 900s, bg — Opus가 설계 품질 검증)
|
|
101
|
-
architect)
|
|
102
|
-
CLI_TYPE="codex"
|
|
103
|
-
CLI_CMD="codex"
|
|
104
|
-
CLI_ARGS="--profile xhigh exec ${codex_base}"
|
|
105
|
-
CLI_EFFORT="xhigh"
|
|
106
|
-
DEFAULT_TIMEOUT=1200
|
|
107
|
-
RUN_MODE="bg"
|
|
108
|
-
OPUS_OVERSIGHT="true"
|
|
109
|
-
;;
|
|
110
|
-
# Codex — 태스크 분해 (effort: xhigh, 900s, fg — Opus가 검증)
|
|
111
|
-
planner)
|
|
112
|
-
CLI_TYPE="codex"
|
|
113
|
-
CLI_CMD="codex"
|
|
114
|
-
CLI_ARGS="--profile xhigh exec ${codex_base}"
|
|
115
|
-
CLI_EFFORT="xhigh"
|
|
116
|
-
DEFAULT_TIMEOUT=1200
|
|
117
|
-
RUN_MODE="fg"
|
|
118
|
-
OPUS_OVERSIGHT="true"
|
|
119
|
-
;;
|
|
120
|
-
# Codex — 비판적 검토 (effort: xhigh, 900s, bg — Opus가 비판 품질 검증)
|
|
121
|
-
critic)
|
|
122
|
-
CLI_TYPE="codex"
|
|
123
|
-
CLI_CMD="codex"
|
|
124
|
-
CLI_ARGS="--profile xhigh exec ${codex_base}"
|
|
125
|
-
CLI_EFFORT="xhigh"
|
|
126
|
-
DEFAULT_TIMEOUT=1200
|
|
127
|
-
RUN_MODE="bg"
|
|
128
|
-
OPUS_OVERSIGHT="true"
|
|
129
|
-
;;
|
|
130
|
-
# Codex — 요구사항 분석 (effort: xhigh, 900s, fg — Opus가 검증)
|
|
131
|
-
analyst)
|
|
132
|
-
CLI_TYPE="codex"
|
|
133
|
-
CLI_CMD="codex"
|
|
134
|
-
CLI_ARGS="--profile xhigh exec ${codex_base}"
|
|
135
|
-
CLI_EFFORT="xhigh"
|
|
136
|
-
DEFAULT_TIMEOUT=1200
|
|
137
|
-
RUN_MODE="fg"
|
|
138
|
-
OPUS_OVERSIGHT="true"
|
|
139
|
-
;;
|
|
140
|
-
|
|
141
|
-
# ─── 리뷰 레인 ───
|
|
142
|
-
|
|
143
|
-
# Codex — 코드 리뷰 (exec review, effort: thorough, 600s, bg — 전용 리뷰 커맨드)
|
|
144
|
-
code-reviewer)
|
|
145
|
-
CLI_TYPE="codex"
|
|
146
|
-
CLI_CMD="codex"
|
|
147
|
-
CLI_ARGS="--profile thorough exec ${codex_base} review"
|
|
148
|
-
CLI_EFFORT="thorough"
|
|
149
|
-
DEFAULT_TIMEOUT=600
|
|
150
|
-
RUN_MODE="bg"
|
|
151
|
-
OPUS_OVERSIGHT="false"
|
|
152
|
-
;;
|
|
153
|
-
# Codex — 보안 리뷰 (exec review, effort: thorough, 600s, bg — Opus 검증 권장)
|
|
154
|
-
security-reviewer)
|
|
155
|
-
CLI_TYPE="codex"
|
|
156
|
-
CLI_CMD="codex"
|
|
157
|
-
CLI_ARGS="--profile thorough exec ${codex_base} review"
|
|
158
|
-
CLI_EFFORT="thorough"
|
|
159
|
-
DEFAULT_TIMEOUT=600
|
|
160
|
-
RUN_MODE="bg"
|
|
161
|
-
OPUS_OVERSIGHT="true"
|
|
162
|
-
;;
|
|
163
|
-
# Codex — 품질 리뷰 (exec review, effort: thorough, 600s, bg — 전용 리뷰 커맨드)
|
|
164
|
-
quality-reviewer)
|
|
165
|
-
CLI_TYPE="codex"
|
|
166
|
-
CLI_CMD="codex"
|
|
167
|
-
CLI_ARGS="--profile thorough exec ${codex_base} review"
|
|
168
|
-
CLI_EFFORT="thorough"
|
|
169
|
-
DEFAULT_TIMEOUT=600
|
|
170
|
-
RUN_MODE="bg"
|
|
171
|
-
OPUS_OVERSIGHT="false"
|
|
172
|
-
;;
|
|
173
|
-
|
|
174
|
-
# ─── 리서치 레인 ───
|
|
175
|
-
|
|
176
|
-
# Codex — 일반 리서치 (effort: high, 480s, bg — 빠른 검색+요약)
|
|
177
|
-
scientist)
|
|
178
|
-
CLI_TYPE="codex"
|
|
179
|
-
CLI_CMD="codex"
|
|
180
|
-
CLI_ARGS="exec ${codex_base}"
|
|
181
|
-
CLI_EFFORT="high"
|
|
182
|
-
DEFAULT_TIMEOUT=480
|
|
183
|
-
RUN_MODE="bg"
|
|
184
|
-
OPUS_OVERSIGHT="false"
|
|
185
|
-
;;
|
|
186
|
-
# Codex — 심층 리서치 (effort: thorough, 1200s, bg — 논문 심층 분석)
|
|
187
|
-
scientist-deep)
|
|
188
|
-
CLI_TYPE="codex"
|
|
189
|
-
CLI_CMD="codex"
|
|
190
|
-
CLI_ARGS="--profile thorough exec ${codex_base}"
|
|
191
|
-
CLI_EFFORT="thorough"
|
|
192
|
-
DEFAULT_TIMEOUT=1200
|
|
193
|
-
RUN_MODE="bg"
|
|
194
|
-
OPUS_OVERSIGHT="false"
|
|
195
|
-
;;
|
|
196
|
-
# Codex — 문서 조사 (effort: high, 480s, bg — 웹 검색 폴백 체인)
|
|
197
|
-
document-specialist)
|
|
198
|
-
CLI_TYPE="codex"
|
|
199
|
-
CLI_CMD="codex"
|
|
200
|
-
CLI_ARGS="exec ${codex_base}"
|
|
201
|
-
CLI_EFFORT="high"
|
|
202
|
-
DEFAULT_TIMEOUT=480
|
|
203
|
-
RUN_MODE="bg"
|
|
204
|
-
OPUS_OVERSIGHT="false"
|
|
205
|
-
;;
|
|
206
|
-
|
|
207
|
-
# ─── UI/문서 레인 ───
|
|
208
|
-
|
|
209
|
-
# Gemini Pro 3.1 — UI/디자인 (높은 품질, 시각적 추론)
|
|
210
|
-
designer)
|
|
211
|
-
CLI_TYPE="gemini"
|
|
212
|
-
CLI_CMD="gemini"
|
|
213
|
-
CLI_ARGS="-m gemini-3.1-pro-preview -y --prompt"
|
|
214
|
-
CLI_EFFORT="pro"
|
|
215
|
-
DEFAULT_TIMEOUT=600
|
|
216
|
-
RUN_MODE="bg"
|
|
217
|
-
OPUS_OVERSIGHT="false"
|
|
218
|
-
;;
|
|
219
|
-
# Gemini Flash 3 — 문서/가이드 작성 (빠른 생성)
|
|
220
|
-
writer)
|
|
221
|
-
CLI_TYPE="gemini"
|
|
222
|
-
CLI_CMD="gemini"
|
|
223
|
-
CLI_ARGS="-m gemini-3-flash-preview -y --prompt"
|
|
224
|
-
CLI_EFFORT="flash"
|
|
225
|
-
DEFAULT_TIMEOUT=600
|
|
226
|
-
RUN_MODE="bg"
|
|
227
|
-
OPUS_OVERSIGHT="false"
|
|
228
|
-
;;
|
|
229
|
-
|
|
230
|
-
# ─── Claude 네이티브 ───
|
|
231
|
-
|
|
232
|
-
# Claude Haiku — 코드베이스 탐색 (fg — 탐색 결과가 분해에 필요)
|
|
233
|
-
explore)
|
|
234
|
-
CLI_TYPE="claude-native"
|
|
235
|
-
CLI_CMD=""
|
|
236
|
-
CLI_ARGS=""
|
|
237
|
-
CLI_EFFORT="n/a"
|
|
238
|
-
DEFAULT_TIMEOUT=300
|
|
239
|
-
RUN_MODE="fg"
|
|
240
|
-
OPUS_OVERSIGHT="false"
|
|
241
|
-
;;
|
|
242
|
-
# Claude Sonnet — 검증 (fg — 검증 결과가 다음 단계 결정)
|
|
243
|
-
verifier)
|
|
244
|
-
CLI_TYPE="claude-native"
|
|
245
|
-
CLI_CMD=""
|
|
246
|
-
CLI_ARGS=""
|
|
247
|
-
CLI_EFFORT="n/a"
|
|
248
|
-
DEFAULT_TIMEOUT=300
|
|
249
|
-
RUN_MODE="fg"
|
|
250
|
-
OPUS_OVERSIGHT="false"
|
|
251
|
-
;;
|
|
252
|
-
# Claude Sonnet — 테스트 (bg — 테스트 독립 실행 가능)
|
|
253
|
-
test-engineer)
|
|
254
|
-
CLI_TYPE="claude-native"
|
|
255
|
-
CLI_CMD=""
|
|
256
|
-
CLI_ARGS=""
|
|
257
|
-
CLI_EFFORT="n/a"
|
|
258
|
-
DEFAULT_TIMEOUT=300
|
|
259
|
-
RUN_MODE="bg"
|
|
260
|
-
OPUS_OVERSIGHT="false"
|
|
261
|
-
;;
|
|
262
|
-
# Claude Sonnet — QA (bg — QA 독립 실행 가능)
|
|
263
|
-
qa-tester)
|
|
264
|
-
CLI_TYPE="claude-native"
|
|
265
|
-
CLI_CMD=""
|
|
266
|
-
CLI_ARGS=""
|
|
267
|
-
CLI_EFFORT="n/a"
|
|
268
|
-
DEFAULT_TIMEOUT=300
|
|
269
|
-
RUN_MODE="bg"
|
|
270
|
-
OPUS_OVERSIGHT="false"
|
|
271
|
-
;;
|
|
272
|
-
|
|
273
|
-
# ─── 경량 ───
|
|
274
|
-
|
|
275
|
-
# Spark — 린트/보일러플레이트 (120s, fg — 즉시 완료 기대)
|
|
276
|
-
spark)
|
|
277
|
-
CLI_TYPE="codex"
|
|
278
|
-
CLI_CMD="codex"
|
|
279
|
-
CLI_ARGS="--profile spark_fast exec ${codex_base}"
|
|
280
|
-
CLI_EFFORT="spark_fast"
|
|
281
|
-
DEFAULT_TIMEOUT=120
|
|
282
|
-
RUN_MODE="fg"
|
|
283
|
-
OPUS_OVERSIGHT="false"
|
|
284
|
-
;;
|
|
285
|
-
*)
|
|
286
|
-
echo "ERROR: 알 수 없는 에이전트 타입: $agent" >&2
|
|
287
|
-
echo "사용 가능: executor, build-fixer, debugger, deep-executor, architect, planner, critic, analyst," >&2
|
|
288
|
-
echo " code-reviewer, security-reviewer, quality-reviewer, scientist, document-specialist," >&2
|
|
289
|
-
echo " designer, writer, explore, verifier, test-engineer, qa-tester, spark" >&2
|
|
290
|
-
exit 1
|
|
291
|
-
;;
|
|
292
|
-
esac
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
# ── CLI 모드 오버라이드 (tfx-codex / tfx-gemini 스킬용) ──
|
|
296
|
-
# TFX_CLI_MODE: auto (기본), codex (Codex-only), gemini (Gemini-only)
|
|
297
|
-
TFX_CLI_MODE="${TFX_CLI_MODE:-auto}"
|
|
298
|
-
|
|
299
|
-
apply_cli_mode() {
|
|
300
|
-
local codex_base="--dangerously-bypass-approvals-and-sandbox --skip-git-repo-check"
|
|
301
|
-
|
|
302
|
-
case "$TFX_CLI_MODE" in
|
|
303
|
-
codex)
|
|
304
|
-
# Gemini 에이전트를 Codex로 리매핑
|
|
305
|
-
if [[ "$CLI_TYPE" == "gemini" ]]; then
|
|
306
|
-
CLI_TYPE="codex"
|
|
307
|
-
CLI_CMD="codex"
|
|
308
|
-
case "$AGENT_TYPE" in
|
|
309
|
-
designer)
|
|
310
|
-
# UI/디자인은 코드 생성이 필요 → high effort
|
|
311
|
-
CLI_ARGS="exec ${codex_base}"
|
|
312
|
-
CLI_EFFORT="high"
|
|
313
|
-
DEFAULT_TIMEOUT=600
|
|
314
|
-
;;
|
|
315
|
-
writer)
|
|
316
|
-
# 문서/가이드 작성은 경량 → spark
|
|
317
|
-
CLI_ARGS="--profile spark_fast exec ${codex_base}"
|
|
318
|
-
CLI_EFFORT="spark_fast"
|
|
319
|
-
DEFAULT_TIMEOUT=180
|
|
320
|
-
;;
|
|
321
|
-
esac
|
|
322
|
-
echo "[cli-route] TFX_CLI_MODE=codex: $AGENT_TYPE → codex($CLI_EFFORT)로 리매핑" >&2
|
|
323
|
-
fi
|
|
324
|
-
;;
|
|
325
|
-
gemini)
|
|
326
|
-
# Codex 에이전트를 Gemini로 리매핑
|
|
327
|
-
if [[ "$CLI_TYPE" == "codex" ]]; then
|
|
328
|
-
CLI_TYPE="gemini"
|
|
329
|
-
CLI_CMD="gemini"
|
|
330
|
-
# 복잡한 작업(구현/설계/리뷰/심층분석) → Pro 3.1
|
|
331
|
-
# 경량 작업(빌드/린트/검색/문서) → Flash 3
|
|
332
|
-
case "$AGENT_TYPE" in
|
|
333
|
-
# Pro 3.1 — 깊이 필요한 작업
|
|
334
|
-
executor|debugger|deep-executor)
|
|
335
|
-
CLI_ARGS="-m gemini-3.1-pro-preview -y --prompt"
|
|
336
|
-
CLI_EFFORT="pro"
|
|
337
|
-
;;
|
|
338
|
-
architect|planner|critic|analyst)
|
|
339
|
-
CLI_ARGS="-m gemini-3.1-pro-preview -y --prompt"
|
|
340
|
-
CLI_EFFORT="pro"
|
|
341
|
-
;;
|
|
342
|
-
code-reviewer|security-reviewer|quality-reviewer)
|
|
343
|
-
CLI_ARGS="-m gemini-3.1-pro-preview -y --prompt"
|
|
344
|
-
CLI_EFFORT="pro"
|
|
345
|
-
;;
|
|
346
|
-
scientist-deep)
|
|
347
|
-
CLI_ARGS="-m gemini-3.1-pro-preview -y --prompt"
|
|
348
|
-
CLI_EFFORT="pro"
|
|
349
|
-
;;
|
|
350
|
-
# Flash 3 — 경량/빠른 작업
|
|
351
|
-
build-fixer|spark)
|
|
352
|
-
CLI_ARGS="-m gemini-3-flash-preview -y --prompt"
|
|
353
|
-
CLI_EFFORT="flash"
|
|
354
|
-
DEFAULT_TIMEOUT=180
|
|
355
|
-
;;
|
|
356
|
-
scientist|document-specialist)
|
|
357
|
-
CLI_ARGS="-m gemini-3-flash-preview -y --prompt"
|
|
358
|
-
CLI_EFFORT="flash"
|
|
359
|
-
;;
|
|
360
|
-
*)
|
|
361
|
-
CLI_ARGS="-m gemini-3-flash-preview -y --prompt"
|
|
362
|
-
CLI_EFFORT="flash"
|
|
363
|
-
;;
|
|
364
|
-
esac
|
|
365
|
-
echo "[cli-route] TFX_CLI_MODE=gemini: $AGENT_TYPE → gemini($CLI_EFFORT)로 리매핑" >&2
|
|
366
|
-
fi
|
|
367
|
-
;;
|
|
368
|
-
auto)
|
|
369
|
-
# 자동 감지: CLI 미설치 시 대체
|
|
370
|
-
if [[ "$CLI_TYPE" == "codex" ]] && ! command -v "$CODEX_BIN" &>/dev/null; then
|
|
371
|
-
if command -v "$GEMINI_BIN" &>/dev/null; then
|
|
372
|
-
TFX_CLI_MODE="gemini"
|
|
373
|
-
apply_cli_mode
|
|
374
|
-
return
|
|
375
|
-
else
|
|
376
|
-
CLI_TYPE="claude-native"
|
|
377
|
-
CLI_CMD=""
|
|
378
|
-
CLI_ARGS=""
|
|
379
|
-
echo "[cli-route] codex/gemini 모두 미설치: $AGENT_TYPE → claude-native fallback" >&2
|
|
380
|
-
fi
|
|
381
|
-
elif [[ "$CLI_TYPE" == "gemini" ]] && ! command -v "$GEMINI_BIN" &>/dev/null; then
|
|
382
|
-
if command -v "$CODEX_BIN" &>/dev/null; then
|
|
383
|
-
TFX_CLI_MODE="codex"
|
|
384
|
-
apply_cli_mode
|
|
385
|
-
return
|
|
386
|
-
else
|
|
387
|
-
CLI_TYPE="claude-native"
|
|
388
|
-
CLI_CMD=""
|
|
389
|
-
CLI_ARGS=""
|
|
390
|
-
echo "[cli-route] codex/gemini 모두 미설치: $AGENT_TYPE → claude-native fallback" >&2
|
|
391
|
-
fi
|
|
392
|
-
fi
|
|
393
|
-
;;
|
|
394
|
-
esac
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
# ── MCP 인벤토리 캐시 읽기 ──
|
|
398
|
-
MCP_CACHE="${HOME}/.claude/cache/mcp-inventory.json"
|
|
399
|
-
|
|
400
|
-
# 캐시에서 특정 CLI의 서버 목록 추출 (캐시 없으면 빈 문자열)
|
|
401
|
-
get_cached_servers() {
|
|
402
|
-
local cli_type="$1"
|
|
403
|
-
if [[ -f "$MCP_CACHE" ]]; then
|
|
404
|
-
# node로 JSON 파싱 — 인자 전달 방식 (Windows 호환)
|
|
405
|
-
node -e 'const[,f,t]=process.argv;const inv=JSON.parse(require("fs").readFileSync(f,"utf8"));const s=(inv[t]||{}).servers||[];console.log(s.filter(x=>x.status==="enabled"||x.status==="configured").map(x=>x.name).join(","))' -- "$MCP_CACHE" "$cli_type" 2>/dev/null
|
|
406
|
-
fi
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
# ── MCP 프로필 → 프롬프트 접미사 ──
|
|
410
|
-
get_mcp_hint() {
|
|
411
|
-
local profile="$1"
|
|
412
|
-
local agent="$2"
|
|
413
|
-
|
|
414
|
-
# auto 모드: 에이전트에 따라 자동 결정
|
|
415
|
-
if [[ "$profile" == "auto" ]]; then
|
|
416
|
-
case "$agent" in
|
|
417
|
-
executor|build-fixer|debugger)
|
|
418
|
-
profile="implement"
|
|
419
|
-
;;
|
|
420
|
-
architect|planner|critic|analyst)
|
|
421
|
-
profile="analyze"
|
|
422
|
-
;;
|
|
423
|
-
code-reviewer|security-reviewer|quality-reviewer)
|
|
424
|
-
profile="review"
|
|
425
|
-
;;
|
|
426
|
-
scientist|document-specialist)
|
|
427
|
-
profile="analyze"
|
|
428
|
-
;;
|
|
429
|
-
deep-executor)
|
|
430
|
-
profile="implement"
|
|
431
|
-
;;
|
|
432
|
-
designer|writer)
|
|
433
|
-
profile="docs"
|
|
434
|
-
;;
|
|
435
|
-
*)
|
|
436
|
-
profile="minimal"
|
|
437
|
-
;;
|
|
438
|
-
esac
|
|
439
|
-
fi
|
|
440
|
-
|
|
441
|
-
# 동적 힌트: 캐시가 있으면 실제 서버 목록 기반으로 생성
|
|
442
|
-
local cached_servers=""
|
|
443
|
-
cached_servers=$(get_cached_servers "$CLI_TYPE")
|
|
444
|
-
|
|
445
|
-
# 서버 존재 여부 헬퍼
|
|
446
|
-
has_server() { echo ",$cached_servers," | grep -q ",$1,"; }
|
|
447
|
-
|
|
448
|
-
# 캐시가 있으면 동적 힌트, 없으면 기본 힌트
|
|
449
|
-
if [[ -n "$cached_servers" ]]; then
|
|
450
|
-
local hint=""
|
|
451
|
-
case "$profile" in
|
|
452
|
-
implement)
|
|
453
|
-
has_server "context7" && hint="${hint}context7으로 라이브러리 문서를 조회하세요. "
|
|
454
|
-
if has_server "brave-search"; then
|
|
455
|
-
hint="${hint}웹 검색은 brave-search를 사용하세요. "
|
|
456
|
-
elif has_server "exa"; then
|
|
457
|
-
hint="${hint}웹 검색은 exa를 사용하세요. "
|
|
458
|
-
elif has_server "tavily"; then
|
|
459
|
-
hint="${hint}웹 검색은 tavily를 사용하세요. "
|
|
460
|
-
fi
|
|
461
|
-
hint="${hint}검색 도구 실패 시 재시도하지 말고 다음 도구로 전환하세요."
|
|
462
|
-
echo "$hint"
|
|
463
|
-
;;
|
|
464
|
-
analyze)
|
|
465
|
-
has_server "context7" && hint="${hint}context7으로 관련 문서를 조회하세요. "
|
|
466
|
-
# 검색 도구 우선순위 동적 구성
|
|
467
|
-
local search_tools=""
|
|
468
|
-
has_server "brave-search" && search_tools="${search_tools}brave-search, "
|
|
469
|
-
has_server "tavily" && search_tools="${search_tools}tavily, "
|
|
470
|
-
has_server "exa" && search_tools="${search_tools}exa, "
|
|
471
|
-
if [[ -n "$search_tools" ]]; then
|
|
472
|
-
hint="${hint}웹 검색 우선순위: ${search_tools%%, }. 402 에러 시 즉시 다음 도구로 전환. "
|
|
473
|
-
fi
|
|
474
|
-
has_server "playwright" && hint="${hint}모든 검색 실패 시 playwright로 직접 방문 (최대 3 URL). "
|
|
475
|
-
hint="${hint}검색 깊이를 제한하고 결과를 빠르게 요약하세요."
|
|
476
|
-
echo "$hint"
|
|
477
|
-
;;
|
|
478
|
-
review)
|
|
479
|
-
has_server "sequential-thinking" && echo "sequential-thinking으로 체계적으로 분석하세요." || echo ""
|
|
480
|
-
;;
|
|
481
|
-
docs)
|
|
482
|
-
has_server "context7" && hint="${hint}context7으로 공식 문서를 참조하세요. "
|
|
483
|
-
has_server "brave-search" && hint="${hint}추가 검색은 brave-search를 사용하세요. "
|
|
484
|
-
hint="${hint}검색 결과의 출처 URL을 함께 제시하세요."
|
|
485
|
-
echo "$hint"
|
|
486
|
-
;;
|
|
487
|
-
minimal|none) echo "" ;;
|
|
488
|
-
*) echo "" ;;
|
|
489
|
-
esac
|
|
490
|
-
else
|
|
491
|
-
# 캐시 없음 → 기본 힌트 (기존 동작 유지)
|
|
492
|
-
case "$profile" in
|
|
493
|
-
implement)
|
|
494
|
-
echo "context7으로 라이브러리 문서를 조회하세요. 웹 검색이 필요하면 brave-search를 우선 사용하고, 실패 시 exa를 시도하세요. exa/tavily가 402 에러를 반환하면 즉시 brave-search로 전환하세요."
|
|
495
|
-
;;
|
|
496
|
-
analyze)
|
|
497
|
-
echo "context7으로 관련 문서를 조회하세요. 웹 검색은 다음 우선순위로 사용: 1) brave-search (무료, 우선), 2) tavily (실패 시), 3) exa (최후 수단). 402 에러 발생 시 해당 도구를 재시도하지 말고 즉시 다음 도구로 전환하세요. 모든 검색 도구가 실패하면 playwright로 직접 웹페이지를 방문하여 정보를 수집하세요. 주의: URL 크롤링은 최대 3개까지만. 논문 전문 대신 제목/초록/핵심 수치만 정리하세요. 검색 깊이를 제한하고 결과를 빠르게 요약하세요."
|
|
498
|
-
;;
|
|
499
|
-
review)
|
|
500
|
-
echo "sequential-thinking으로 체계적으로 분석하세요."
|
|
501
|
-
;;
|
|
502
|
-
docs)
|
|
503
|
-
echo "context7으로 공식 문서를 참조하세요. 추가 검색이 필요하면 brave-search를 사용하세요. 사실 확인이 필요한 내용은 반드시 Google Search를 활용하여 검증하세요 (google_search 도구 사용). 검색 결과의 출처 URL을 함께 제시하세요."
|
|
504
|
-
;;
|
|
505
|
-
minimal|none) echo "" ;;
|
|
506
|
-
*) echo "" ;;
|
|
507
|
-
esac
|
|
508
|
-
fi
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
# ── Gemini MCP 서버 선택적 로드 (병렬 경합 감소) ──
|
|
512
|
-
# MCP 프로필별 필요한 서버만 로드하여 초기화 시간 단축
|
|
513
|
-
get_gemini_mcp_filter() {
|
|
514
|
-
local profile="$1"
|
|
515
|
-
case "$profile" in
|
|
516
|
-
implement) echo "--allowed-mcp-server-names context7,brave-search" ;;
|
|
517
|
-
analyze) echo "--allowed-mcp-server-names context7,brave-search,exa" ;;
|
|
518
|
-
review) echo "--allowed-mcp-server-names sequential-thinking" ;;
|
|
519
|
-
docs) echo "--allowed-mcp-server-names context7,brave-search" ;;
|
|
520
|
-
*) echo "" ;; # 필터 없음 — 모든 서버 로드
|
|
521
|
-
esac
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
# ── 토큰 사용량 추출 ──
|
|
525
|
-
# Codex JSON-line에서 usage 필드를 파싱하여 "input output" 반환
|
|
526
|
-
# Gemini는 세션 파일에서 추출하므로 여기선 0 반환
|
|
527
|
-
extract_tokens() {
|
|
528
|
-
local raw="$1"
|
|
529
|
-
local cli_type="$2"
|
|
530
|
-
local stderr_file="$3"
|
|
531
|
-
|
|
532
|
-
if [[ "$cli_type" == "codex" ]]; then
|
|
533
|
-
# Codex CLI: stderr에 "tokens used\n76,239" 형식으로 토큰 출력
|
|
534
|
-
if [[ -f "$stderr_file" ]]; then
|
|
535
|
-
local total
|
|
536
|
-
total=$(grep -A1 "tokens used" "$stderr_file" 2>/dev/null | tail -1 | tr -d ',' | tr -d ' ')
|
|
537
|
-
if [[ -n "$total" && "$total" =~ ^[0-9]+$ && "$total" -gt 0 ]]; then
|
|
538
|
-
echo "$total 0"
|
|
539
|
-
return
|
|
540
|
-
fi
|
|
541
|
-
fi
|
|
542
|
-
echo "0 0"
|
|
543
|
-
return
|
|
544
|
-
fi
|
|
545
|
-
|
|
546
|
-
if [[ "$cli_type" == "gemini" ]]; then
|
|
547
|
-
# Gemini CLI: ~/.gemini/tmp/*/chats/session-*.json에서 최신 세션 토큰 추출
|
|
548
|
-
local gemini_tmp="${HOME}/.gemini/tmp"
|
|
549
|
-
if [[ -d "$gemini_tmp" ]]; then
|
|
550
|
-
local latest
|
|
551
|
-
latest=$(find "$gemini_tmp" -name "session-*.json" -path "*/chats/*" -newer "$stderr_file" 2>/dev/null \
|
|
552
|
-
| head -1)
|
|
553
|
-
# stderr보다 새 파일 없으면 가장 최근 파일 사용
|
|
554
|
-
if [[ -z "$latest" ]]; then
|
|
555
|
-
latest=$(find "$gemini_tmp" -name "session-*.json" -path "*/chats/*" -printf '%T@ %p\n' 2>/dev/null \
|
|
556
|
-
| sort -rn | head -1 | cut -d' ' -f2-)
|
|
557
|
-
# Windows Git Bash: -printf 미지원 시 ls fallback
|
|
558
|
-
if [[ -z "$latest" ]]; then
|
|
559
|
-
latest=$(find "$gemini_tmp" -name "session-*.json" -path "*/chats/*" 2>/dev/null \
|
|
560
|
-
| xargs ls -t 2>/dev/null | head -1)
|
|
561
|
-
fi
|
|
562
|
-
fi
|
|
563
|
-
if [[ -n "$latest" && -f "$latest" ]]; then
|
|
564
|
-
local result
|
|
565
|
-
result=$(python3 -c "
|
|
566
|
-
import json, sys
|
|
567
|
-
data = json.load(open(sys.argv[1]))
|
|
568
|
-
inp = sum(m.get('tokens',{}).get('input',0) for m in data.get('messages',[]))
|
|
569
|
-
out = sum(m.get('tokens',{}).get('output',0) for m in data.get('messages',[]))
|
|
570
|
-
print(f'{inp} {out}')
|
|
571
|
-
" "$latest" 2>/dev/null) || result="0 0"
|
|
572
|
-
local inp out
|
|
573
|
-
inp=$(echo "$result" | awk '{print $1}')
|
|
574
|
-
out=$(echo "$result" | awk '{print $2}')
|
|
575
|
-
if [[ $((inp + out)) -gt 0 ]]; then
|
|
576
|
-
echo "$inp $out"
|
|
577
|
-
return
|
|
578
|
-
fi
|
|
579
|
-
fi
|
|
580
|
-
fi
|
|
581
|
-
echo "0 0"
|
|
582
|
-
return
|
|
583
|
-
fi
|
|
584
|
-
|
|
585
|
-
echo "0 0"
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
# ── Codex JSON-line 출력 파서 ──
|
|
589
|
-
filter_codex_output() {
|
|
590
|
-
local raw="$1"
|
|
591
|
-
|
|
592
|
-
# JSON-line 형식이면 파싱, 아니면 그대로 반환
|
|
593
|
-
if echo "$raw" | head -1 | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
|
|
594
|
-
echo "$raw" | python3 -c "
|
|
595
|
-
import sys, json
|
|
596
|
-
for line in sys.stdin:
|
|
597
|
-
line = line.strip()
|
|
598
|
-
if not line:
|
|
599
|
-
continue
|
|
600
|
-
try:
|
|
601
|
-
obj = json.loads(line)
|
|
602
|
-
if obj.get('type') in ('message', 'completed', 'output_text'):
|
|
603
|
-
text = obj.get('text', obj.get('content', obj.get('output', '')))
|
|
604
|
-
if text:
|
|
605
|
-
print(text)
|
|
606
|
-
except json.JSONDecodeError:
|
|
607
|
-
print(line)
|
|
608
|
-
" 2>/dev/null || echo "$raw"
|
|
609
|
-
else
|
|
610
|
-
echo "$raw"
|
|
611
|
-
fi
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
# ── 실행 로그 기록 ──
|
|
615
|
-
# 각 실행의 에이전트, effort, 소요시간, 상태를 로컬에 누적 기록
|
|
616
|
-
LOG_DIR="${HOME}/.claude/logs"
|
|
617
|
-
LOG_FILE="${LOG_DIR}/cli-route-stats.jsonl"
|
|
618
|
-
|
|
619
|
-
log_execution() {
|
|
620
|
-
local agent="$1"
|
|
621
|
-
local cli_type="$2"
|
|
622
|
-
local effort="$3"
|
|
623
|
-
local run_mode="$4"
|
|
624
|
-
local opus="$5"
|
|
625
|
-
local exit_code="$6"
|
|
626
|
-
local elapsed="$7"
|
|
627
|
-
local timeout="$8"
|
|
628
|
-
local mcp_profile="$9"
|
|
629
|
-
local input_tokens="${10:-0}"
|
|
630
|
-
local output_tokens="${11:-0}"
|
|
631
|
-
local total_tokens="${12:-0}"
|
|
632
|
-
|
|
633
|
-
mkdir -p "$LOG_DIR"
|
|
634
|
-
|
|
635
|
-
local ts
|
|
636
|
-
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date +"%Y-%m-%dT%H:%M:%S")
|
|
637
|
-
local status="success"
|
|
638
|
-
if [[ $exit_code -eq 124 ]]; then
|
|
639
|
-
status="timeout"
|
|
640
|
-
elif [[ $exit_code -ne 0 ]]; then
|
|
641
|
-
status="failed"
|
|
642
|
-
fi
|
|
643
|
-
|
|
644
|
-
# JSONL 한 줄 추가 (jq 없이 수동 구성)
|
|
645
|
-
printf '{"ts":"%s","agent":"%s","cli":"%s","effort":"%s","run_mode":"%s","opus_oversight":"%s","status":"%s","exit_code":%d,"elapsed_sec":%d,"timeout_sec":%d,"mcp_profile":"%s","input_tokens":%d,"output_tokens":%d,"total_tokens":%d}\n' \
|
|
646
|
-
"$ts" "$agent" "$cli_type" "$effort" "$run_mode" "$opus" "$status" "$exit_code" "$elapsed" "$timeout" "$mcp_profile" \
|
|
647
|
-
"$input_tokens" "$output_tokens" "$total_tokens" \
|
|
648
|
-
>> "$LOG_FILE" 2>/dev/null || true
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
# ── 토큰 누적 (sv-accumulator.json) ──
|
|
652
|
-
# 실행 성공 시 추출된 토큰을 ~/.claude/cache/sv-accumulator.json에 누적
|
|
653
|
-
accumulate_tokens() {
|
|
654
|
-
local cli_type="$1"
|
|
655
|
-
local input_tokens="$2"
|
|
656
|
-
local output_tokens="$3"
|
|
657
|
-
local total=$((input_tokens + output_tokens))
|
|
658
|
-
|
|
659
|
-
# 토큰 0이면 건너뜀
|
|
660
|
-
if [[ $total -eq 0 ]]; then return; fi
|
|
661
|
-
|
|
662
|
-
local acc_file="${HOME}/.claude/cache/sv-accumulator.json"
|
|
663
|
-
mkdir -p "$(dirname "$acc_file")"
|
|
664
|
-
|
|
665
|
-
# node로 JSON 읽기/수정/쓰기 (jq 의존성 없이)
|
|
666
|
-
node -e '
|
|
667
|
-
const fs = require("fs");
|
|
668
|
-
const [, file, cliType, inp, out] = process.argv;
|
|
669
|
-
let data;
|
|
670
|
-
try { data = JSON.parse(fs.readFileSync(file, "utf-8")); } catch { data = {}; }
|
|
671
|
-
if (!data.codex) data.codex = { tokens: 0, calls: 0 };
|
|
672
|
-
if (!data.gemini) data.gemini = { tokens: 0, calls: 0 };
|
|
673
|
-
const key = cliType === "gemini" ? "gemini" : "codex";
|
|
674
|
-
data[key].tokens += Number(inp) + Number(out);
|
|
675
|
-
data[key].calls += 1;
|
|
676
|
-
data.lastUpdated = new Date().toISOString();
|
|
677
|
-
fs.writeFileSync(file, JSON.stringify(data, null, 2));
|
|
678
|
-
' -- "$acc_file" "$cli_type" "$input_tokens" "$output_tokens" 2>/dev/null || true
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
# ── CLI 이슈 자동 수집 ──
|
|
682
|
-
# stderr에서 알려진 에러 패턴을 감지하여 ~/.claude/cache/cli-issues.jsonl에 기록
|
|
683
|
-
track_cli_issue() {
|
|
684
|
-
local cli_type="$1" agent="$2" stderr_text="$3" exit_code="$4"
|
|
685
|
-
[[ -z "$stderr_text" && "$exit_code" -eq 0 ]] && return
|
|
686
|
-
|
|
687
|
-
local issues_file="${HOME}/.claude/cache/cli-issues.jsonl"
|
|
688
|
-
mkdir -p "$(dirname "$issues_file")"
|
|
689
|
-
|
|
690
|
-
local pattern="" msg="" severity="warn"
|
|
691
|
-
|
|
692
|
-
# 패턴 매칭 (가장 구체적인 것 우선)
|
|
693
|
-
if echo "$stderr_text" | grep -qi "sandbox image.*missing"; then
|
|
694
|
-
pattern="sandbox_missing"; msg="Docker sandbox image not found"; severity="warn"
|
|
695
|
-
elif echo "$stderr_text" | grep -qi "rate.limit\|429\|too many requests"; then
|
|
696
|
-
pattern="rate_limit"; msg="API rate limit exceeded"; severity="warn"
|
|
697
|
-
elif echo "$stderr_text" | grep -qi "ECONNREFUSED\|ENOTFOUND\|network"; then
|
|
698
|
-
pattern="network_error"; msg="Network connection failed"; severity="error"
|
|
699
|
-
elif echo "$stderr_text" | grep -qi "deprecated"; then
|
|
700
|
-
pattern="deprecated_flag"; msg="Deprecated flag/feature detected"; severity="warn"
|
|
701
|
-
elif echo "$stderr_text" | grep -qi "API_KEY.*not.set\|auth.*fail\|unauthorized\|401"; then
|
|
702
|
-
pattern="auth_error"; msg="Authentication failed"; severity="error"
|
|
703
|
-
elif echo "$stderr_text" | grep -qi "ENOMEM\|out of memory\|heap"; then
|
|
704
|
-
pattern="oom"; msg="Out of memory"; severity="error"
|
|
705
|
-
elif [[ "$exit_code" -ne 0 && "$exit_code" -ne 124 ]]; then
|
|
706
|
-
pattern="unknown_error"; msg="Exit code $exit_code"; severity="warn"
|
|
707
|
-
fi
|
|
708
|
-
|
|
709
|
-
[[ -z "$pattern" ]] && return
|
|
710
|
-
|
|
711
|
-
# 중복 방지: 같은 패턴+cli가 최근 5분 내 기록됐으면 건너뜀
|
|
712
|
-
if [[ -f "$issues_file" ]]; then
|
|
713
|
-
local now_ms=$(($(date +%s) * 1000))
|
|
714
|
-
local dedup
|
|
715
|
-
dedup=$(tail -5 "$issues_file" 2>/dev/null | grep "\"$pattern\"" | grep "\"$cli_type\"" | tail -1)
|
|
716
|
-
if [[ -n "$dedup" ]]; then
|
|
717
|
-
local last_ts
|
|
718
|
-
last_ts=$(echo "$dedup" | sed 's/.*"ts":\([0-9]*\).*/\1/' 2>/dev/null)
|
|
719
|
-
if [[ -n "$last_ts" ]] && (( now_ms - last_ts < 300000 )); then
|
|
720
|
-
return # 5분 이내 동일 이슈 → 건너뜀
|
|
721
|
-
fi
|
|
722
|
-
fi
|
|
723
|
-
fi
|
|
724
|
-
|
|
725
|
-
# stderr 첫 200자만 기록 (개인정보 최소화)
|
|
726
|
-
local snippet
|
|
727
|
-
snippet=$(echo "$stderr_text" | head -3 | cut -c1-200 | tr '\n' ' ')
|
|
728
|
-
|
|
729
|
-
# CLI 버전 추출
|
|
730
|
-
local cli_ver=""
|
|
731
|
-
cli_ver=$($cli_type --version 2>/dev/null | head -1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
|
732
|
-
|
|
733
|
-
node -e '
|
|
734
|
-
const fs = require("fs");
|
|
735
|
-
const [,, file, cli, agent, pattern, msg, severity, snippet, ver] = process.argv;
|
|
736
|
-
const entry = JSON.stringify({
|
|
737
|
-
ts: Date.now(), cli, agent, pattern, msg, severity,
|
|
738
|
-
snippet: snippet.substring(0, 200), ver: ver || null, resolved: false
|
|
739
|
-
});
|
|
740
|
-
fs.appendFileSync(file, entry + "\n");
|
|
741
|
-
// 자동 회전: 200줄 초과 시 최근 100줄만 유지
|
|
742
|
-
const lines = fs.readFileSync(file, "utf-8").trim().split("\n");
|
|
743
|
-
if (lines.length > 200) {
|
|
744
|
-
fs.writeFileSync(file, lines.slice(-100).join("\n") + "\n");
|
|
745
|
-
}
|
|
746
|
-
' -- "$issues_file" "$cli_type" "$agent" "$pattern" "$msg" "$severity" "$snippet" "$cli_ver" 2>/dev/null || true
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
# ── 출력 크기 제한 ──
|
|
750
|
-
truncate_output() {
|
|
751
|
-
local input="$1"
|
|
752
|
-
local max_bytes="$2"
|
|
753
|
-
local byte_count
|
|
754
|
-
byte_count=$(echo "$input" | wc -c)
|
|
755
|
-
|
|
756
|
-
if [[ $byte_count -gt $max_bytes ]]; then
|
|
757
|
-
echo "$input" | head -c "$max_bytes"
|
|
758
|
-
echo ""
|
|
759
|
-
echo "--- [출력 ${byte_count}B → ${max_bytes}B로 절삭됨] ---"
|
|
760
|
-
else
|
|
761
|
-
echo "$input"
|
|
762
|
-
fi
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
# ── 메인 실행 ──
|
|
766
|
-
main() {
|
|
767
|
-
route_agent "$AGENT_TYPE"
|
|
768
|
-
|
|
769
|
-
# CLI 모드 오버라이드 적용 (tfx-codex/tfx-gemini 또는 auto-fallback)
|
|
770
|
-
apply_cli_mode
|
|
771
|
-
|
|
772
|
-
# CLI 경로 해석 (bare command → 절대경로)
|
|
773
|
-
case "$CLI_CMD" in
|
|
774
|
-
codex) CLI_CMD="$CODEX_BIN" ;;
|
|
775
|
-
gemini) CLI_CMD="$GEMINI_BIN" ;;
|
|
776
|
-
esac
|
|
777
|
-
|
|
778
|
-
# 사용자 지정 타임아웃이 없으면 에이전트별 기본값 사용
|
|
779
|
-
if [[ -n "$USER_TIMEOUT" ]]; then
|
|
780
|
-
TIMEOUT_SEC="$USER_TIMEOUT"
|
|
781
|
-
else
|
|
782
|
-
TIMEOUT_SEC="$DEFAULT_TIMEOUT"
|
|
783
|
-
fi
|
|
784
|
-
|
|
785
|
-
# kteam 안정화: Gemini 에이전트 기본 타임아웃 축소 (사용자 미지정 시만)
|
|
786
|
-
if [[ -z "$USER_TIMEOUT" ]]; then
|
|
787
|
-
case "$AGENT_TYPE" in
|
|
788
|
-
designer|writer)
|
|
789
|
-
if [[ "$DEFAULT_TIMEOUT" -gt 60 ]]; then
|
|
790
|
-
TIMEOUT_SEC=60
|
|
791
|
-
fi
|
|
792
|
-
;;
|
|
793
|
-
esac
|
|
794
|
-
fi
|
|
795
|
-
|
|
796
|
-
# Claude 네이티브 에이전트는 이 스크립트로 처리 불가
|
|
797
|
-
if [[ "$CLI_TYPE" == "claude-native" ]]; then
|
|
798
|
-
# 에이전트별 모델 결정
|
|
799
|
-
local model="sonnet"
|
|
800
|
-
case "$AGENT_TYPE" in
|
|
801
|
-
explore) model="haiku" ;;
|
|
802
|
-
verifier|test-engineer|qa-tester) model="sonnet" ;;
|
|
803
|
-
esac
|
|
804
|
-
|
|
805
|
-
echo "ROUTE_TYPE=claude-native"
|
|
806
|
-
echo "AGENT=$AGENT_TYPE"
|
|
807
|
-
echo "MODEL=$model"
|
|
808
|
-
echo "RUN_MODE=$RUN_MODE"
|
|
809
|
-
echo "OPUS_OVERSIGHT=$OPUS_OVERSIGHT"
|
|
810
|
-
echo "TIMEOUT=$TIMEOUT_SEC"
|
|
811
|
-
echo "PROMPT=$PROMPT"
|
|
812
|
-
echo "--- Claude Task($model) 에이전트로 위임하세요 ---"
|
|
813
|
-
exit 0
|
|
814
|
-
fi
|
|
815
|
-
|
|
816
|
-
# MCP 힌트를 프롬프트에 주입
|
|
817
|
-
MCP_HINT=$(get_mcp_hint "$MCP_PROFILE" "$AGENT_TYPE")
|
|
818
|
-
if [[ -n "$MCP_HINT" ]]; then
|
|
819
|
-
FULL_PROMPT="${PROMPT}. ${MCP_HINT}"
|
|
820
|
-
else
|
|
821
|
-
FULL_PROMPT="$PROMPT"
|
|
822
|
-
fi
|
|
823
|
-
|
|
824
|
-
# 메타정보 출력 (stderr로)
|
|
825
|
-
echo "[cli-route] type=$CLI_TYPE agent=$AGENT_TYPE effort=$CLI_EFFORT mode=$RUN_MODE timeout=${TIMEOUT_SEC}s" >&2
|
|
826
|
-
echo "[cli-route] opus_oversight=$OPUS_OVERSIGHT mcp_profile=$MCP_PROFILE stderr_log=$STDERR_LOG" >&2
|
|
827
|
-
|
|
828
|
-
# CLI 실행 (stderr 분리 + 타임아웃 + 소요시간 측정)
|
|
829
|
-
local exit_code=0
|
|
830
|
-
local raw_output=""
|
|
831
|
-
local start_time
|
|
832
|
-
start_time=$(date +%s)
|
|
833
|
-
|
|
834
|
-
if [[ "$CLI_TYPE" == "codex" ]]; then
|
|
835
|
-
raw_output=$(timeout "$TIMEOUT_SEC" $CLI_CMD $CLI_ARGS "$FULL_PROMPT" 2>"$STDERR_LOG") || exit_code=$?
|
|
836
|
-
elif [[ "$CLI_TYPE" == "gemini" ]]; then
|
|
837
|
-
# Gemini 안정화 v2: 프로세스 생존 기반 health check + MCP 선택적 로드
|
|
838
|
-
# - 프로세스 살아있음 → 정상 (MCP 초기화 중일 수 있음)
|
|
839
|
-
# - 프로세스 죽음 + 출력 없음 → crash → 재시도
|
|
840
|
-
# - 메인 timeout이 진짜 hang을 처리
|
|
841
|
-
|
|
842
|
-
# Fix 4: MCP 프로필별 필요한 서버만 로드 (병렬 경합 감소)
|
|
843
|
-
local gemini_mcp_filter
|
|
844
|
-
gemini_mcp_filter=$(get_gemini_mcp_filter "$MCP_PROFILE")
|
|
845
|
-
local gemini_args="$CLI_ARGS"
|
|
846
|
-
if [[ -n "$gemini_mcp_filter" ]]; then
|
|
847
|
-
gemini_args="${CLI_ARGS/--prompt/$gemini_mcp_filter --prompt}"
|
|
848
|
-
echo "[cli-route] Gemini MCP 필터: $gemini_mcp_filter" >&2
|
|
849
|
-
fi
|
|
850
|
-
|
|
851
|
-
timeout "$TIMEOUT_SEC" $CLI_CMD $gemini_args "$FULL_PROMPT" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
|
|
852
|
-
local pid=$!
|
|
853
|
-
|
|
854
|
-
# Fix 2+3: 프로세스 생존 기반 health check (30초)
|
|
855
|
-
local health_ok=true
|
|
856
|
-
local HEALTH_TIMEOUT=30
|
|
857
|
-
for i in $(seq 1 $HEALTH_TIMEOUT); do
|
|
858
|
-
sleep 1
|
|
859
|
-
# 출력 있으면 확실히 정상 → 조기 탈출
|
|
860
|
-
if [[ -s "$STDOUT_LOG" ]] || [[ -s "$STDERR_LOG" ]]; then
|
|
861
|
-
break
|
|
862
|
-
fi
|
|
863
|
-
# 프로세스 사망 + 출력 없음 → crash
|
|
864
|
-
if ! kill -0 "$pid" 2>/dev/null; then
|
|
865
|
-
health_ok=false
|
|
866
|
-
echo "[cli-route] Gemini: 출력 없이 프로세스 종료 (${i}초)" >&2
|
|
867
|
-
break
|
|
868
|
-
fi
|
|
869
|
-
# 프로세스 살아있고 출력 없음 → MCP 초기화 중, 계속 대기
|
|
870
|
-
done
|
|
871
|
-
|
|
872
|
-
if [[ "$health_ok" == "false" ]]; then
|
|
873
|
-
# crash 감지 → 1회 재시도
|
|
874
|
-
wait "$pid" 2>/dev/null
|
|
875
|
-
echo "[cli-route] Gemini crash 감지, 재시도 중..." >&2
|
|
876
|
-
timeout "$TIMEOUT_SEC" $CLI_CMD $gemini_args "$FULL_PROMPT" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
|
|
877
|
-
pid=$!
|
|
878
|
-
wait "$pid"
|
|
879
|
-
exit_code=$?
|
|
880
|
-
else
|
|
881
|
-
wait "$pid"
|
|
882
|
-
exit_code=$?
|
|
883
|
-
fi
|
|
884
|
-
|
|
885
|
-
raw_output=$(cat "$STDOUT_LOG" 2>/dev/null)
|
|
886
|
-
fi
|
|
887
|
-
|
|
888
|
-
local end_time
|
|
889
|
-
end_time=$(date +%s)
|
|
890
|
-
local elapsed=$((end_time - start_time))
|
|
891
|
-
|
|
892
|
-
# 토큰 추출
|
|
893
|
-
local token_info input_tokens output_tokens total_tokens
|
|
894
|
-
token_info=$(extract_tokens "$raw_output" "$CLI_TYPE" "$STDERR_LOG") || token_info="0 0"
|
|
895
|
-
input_tokens=$(echo "$token_info" | awk '{print $1}')
|
|
896
|
-
output_tokens=$(echo "$token_info" | awk '{print $2}')
|
|
897
|
-
total_tokens=$((input_tokens + output_tokens))
|
|
898
|
-
|
|
899
|
-
# 실행 로그 기록 (토큰 포함)
|
|
900
|
-
log_execution "$AGENT_TYPE" "$CLI_TYPE" "$CLI_EFFORT" "$RUN_MODE" "$OPUS_OVERSIGHT" \
|
|
901
|
-
"$exit_code" "$elapsed" "$TIMEOUT_SEC" "$MCP_PROFILE" \
|
|
902
|
-
"$input_tokens" "$output_tokens" "$total_tokens"
|
|
903
|
-
|
|
904
|
-
# 성공 시 토큰 누적
|
|
905
|
-
if [[ $exit_code -eq 0 ]]; then
|
|
906
|
-
accumulate_tokens "$CLI_TYPE" "$input_tokens" "$output_tokens" || true
|
|
907
|
-
fi
|
|
908
|
-
|
|
909
|
-
# CLI 이슈 자동 수집
|
|
910
|
-
local _stderr_for_track=""
|
|
911
|
-
[[ -f "$STDERR_LOG" ]] && _stderr_for_track=$(cat "$STDERR_LOG" 2>/dev/null || echo "")
|
|
912
|
-
track_cli_issue "$CLI_TYPE" "$AGENT_TYPE" "$_stderr_for_track" "$exit_code" || true
|
|
913
|
-
|
|
914
|
-
# 결과 처리
|
|
915
|
-
local stderr_content=""
|
|
916
|
-
if [[ -f "$STDERR_LOG" ]]; then
|
|
917
|
-
stderr_content=$(cat "$STDERR_LOG" 2>/dev/null || echo "")
|
|
918
|
-
fi
|
|
919
|
-
|
|
920
|
-
# 헤더 (구조화된 메타데이터)
|
|
921
|
-
echo "=== CLI-ROUTE RESULT ==="
|
|
922
|
-
echo "agent: $AGENT_TYPE"
|
|
923
|
-
echo "cli: $CLI_TYPE ($CLI_CMD)"
|
|
924
|
-
echo "effort: $CLI_EFFORT"
|
|
925
|
-
echo "run_mode: $RUN_MODE"
|
|
926
|
-
echo "opus_oversight: $OPUS_OVERSIGHT"
|
|
927
|
-
echo "exit_code: $exit_code"
|
|
928
|
-
echo "timeout: ${TIMEOUT_SEC}s"
|
|
929
|
-
echo "elapsed: ${elapsed}s"
|
|
930
|
-
echo "mcp_profile: $MCP_PROFILE"
|
|
931
|
-
echo "stderr_log: $STDERR_LOG"
|
|
932
|
-
|
|
933
|
-
# exit code 분석
|
|
934
|
-
if [[ $exit_code -eq 0 ]]; then
|
|
935
|
-
if [[ -n "$stderr_content" ]]; then
|
|
936
|
-
echo "status: success_with_warnings"
|
|
937
|
-
echo "warnings: $(echo "$stderr_content" | head -3)"
|
|
938
|
-
else
|
|
939
|
-
echo "status: success"
|
|
940
|
-
fi
|
|
941
|
-
echo "=== OUTPUT ==="
|
|
942
|
-
|
|
943
|
-
if [[ "$CLI_TYPE" == "codex" ]]; then
|
|
944
|
-
filtered=$(filter_codex_output "$raw_output")
|
|
945
|
-
truncate_output "$filtered" "$MAX_STDOUT_BYTES"
|
|
946
|
-
else
|
|
947
|
-
truncate_output "$raw_output" "$MAX_STDOUT_BYTES"
|
|
948
|
-
fi
|
|
949
|
-
|
|
950
|
-
elif [[ $exit_code -eq 124 ]]; then
|
|
951
|
-
echo "status: timeout (${TIMEOUT_SEC}s 초과)"
|
|
952
|
-
echo "=== PARTIAL OUTPUT ==="
|
|
953
|
-
truncate_output "$raw_output" "$MAX_STDOUT_BYTES"
|
|
954
|
-
echo "=== STDERR ==="
|
|
955
|
-
echo "$stderr_content" | tail -10
|
|
956
|
-
|
|
957
|
-
else
|
|
958
|
-
echo "status: failed (exit_code=$exit_code)"
|
|
959
|
-
echo "=== STDERR ==="
|
|
960
|
-
echo "$stderr_content" | tail -20
|
|
961
|
-
if [[ -n "$raw_output" ]]; then
|
|
962
|
-
echo "=== PARTIAL OUTPUT ==="
|
|
963
|
-
truncate_output "$raw_output" "$MAX_STDOUT_BYTES"
|
|
964
|
-
fi
|
|
965
|
-
fi
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
main
|
|
2
|
+
# cli-route.sh — backward-compat 래퍼
|
|
3
|
+
exec bash "$(dirname "$0")/tfx-route.sh" "$@"
|