triflux 3.2.0-dev.3 → 3.2.0-dev.6
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 +19 -13
- package/README.md +19 -13
- package/bin/triflux.mjs +144 -67
- package/hooks/hooks.json +2 -2
- package/hooks/keyword-rules.json +338 -0
- package/hub/server.mjs +19 -10
- package/hub/team/cli.mjs +606 -442
- package/hub/team/dashboard.mjs +164 -55
- package/hub/team/native.mjs +38 -0
- package/hub/team/session.mjs +39 -23
- package/hud/hud-qos-status.mjs +56 -1
- package/package.json +3 -2
- package/scripts/__tests__/keyword-detector.test.mjs +234 -0
- package/scripts/hub-ensure.mjs +82 -0
- package/scripts/keyword-detector.mjs +257 -0
- package/scripts/keyword-rules-expander.mjs +521 -0
- package/scripts/lib/keyword-rules.mjs +165 -0
- package/scripts/run.cjs +62 -0
- package/scripts/setup.mjs +36 -16
- package/scripts/test-tfx-route-no-claude-native.mjs +49 -0
- package/scripts/tfx-route.sh +482 -418
- package/skills/tfx-auto-codex/SKILL.md +79 -0
- package/skills/tfx-team/SKILL.md +108 -63
- package/scripts/team-keyword.mjs +0 -35
package/README.ko.md
CHANGED
|
@@ -85,19 +85,25 @@ tfx doctor
|
|
|
85
85
|
/analyze "결제 모듈 보안 리뷰"
|
|
86
86
|
/research "최신 React Server Components 패턴"
|
|
87
87
|
|
|
88
|
-
# 단일 CLI 모드
|
|
89
|
-
/tfx-codex "리팩터링 + 리뷰"
|
|
90
|
-
/tfx-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
|
88
|
+
# 단일 CLI 모드
|
|
89
|
+
/tfx-auto-codex "리팩터링 + 테스트 + 리뷰" # Codex 리드 + Gemini 유지, Claude 네이티브 역할 제거
|
|
90
|
+
/tfx-codex "리팩터링 + 리뷰" # Codex만 사용
|
|
91
|
+
/tfx-gemini "구현 + 문서화" # Gemini만 사용
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 문서
|
|
95
|
+
|
|
96
|
+
- [docs/INDEX.md](docs/INDEX.md) — 문서 인덱스
|
|
97
|
+
|
|
98
|
+
## 스킬
|
|
99
|
+
|
|
100
|
+
| 스킬 | 모드 | 설명 |
|
|
101
|
+
|------|------|------|
|
|
102
|
+
| `/tfx-auto` | 자동 | 트리아지 → 분해 → DAG 병렬 실행 |
|
|
103
|
+
| `/tfx-auto-codex` | Codex 리드 | Claude 네이티브 역할을 Codex로 치환, Gemini 경로 유지 |
|
|
104
|
+
| `/tfx-codex` | Codex 전용 | 모든 CLI 작업을 Codex로 라우팅 |
|
|
105
|
+
| `/tfx-gemini` | Gemini 전용 | 모든 CLI 작업을 Gemini로 라우팅 |
|
|
106
|
+
| `/tfx-setup` | 설정 | 파일 동기화, HUD 설정, CLI 진단 |
|
|
101
107
|
|
|
102
108
|
### 커맨드 숏컷
|
|
103
109
|
|
package/README.md
CHANGED
|
@@ -85,19 +85,25 @@ tfx doctor
|
|
|
85
85
|
/analyze "security review of payment module"
|
|
86
86
|
/research "latest React Server Components patterns"
|
|
87
87
|
|
|
88
|
-
# Single-CLI modes
|
|
89
|
-
/tfx-codex "refactor + review"
|
|
90
|
-
/tfx-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
|
88
|
+
# Single-CLI modes
|
|
89
|
+
/tfx-auto-codex "refactor + test + review" # Codex lead + Gemini kept, no Claude-native roles
|
|
90
|
+
/tfx-codex "refactor + review" # Codex only
|
|
91
|
+
/tfx-gemini "implement + document" # Gemini only
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Docs
|
|
95
|
+
|
|
96
|
+
- [docs/INDEX.md](docs/INDEX.md) — central documentation index
|
|
97
|
+
|
|
98
|
+
## Skills
|
|
99
|
+
|
|
100
|
+
| Skill | Mode | Description |
|
|
101
|
+
|-------|------|-------------|
|
|
102
|
+
| `/tfx-auto` | Auto | Triage → decompose → parallel execute via DAG |
|
|
103
|
+
| `/tfx-auto-codex` | Codex-lead | Replace Claude-native roles with Codex, keep Gemini path |
|
|
104
|
+
| `/tfx-codex` | Codex-only | All CLI tasks routed to Codex |
|
|
105
|
+
| `/tfx-gemini` | Gemini-only | All CLI tasks routed to Gemini |
|
|
106
|
+
| `/tfx-setup` | Setup | File sync, HUD config, CLI diagnostics |
|
|
101
107
|
|
|
102
108
|
### Command Shortcuts
|
|
103
109
|
|
package/bin/triflux.mjs
CHANGED
|
@@ -923,11 +923,12 @@ ${updateNotice}
|
|
|
923
923
|
${WHITE_BRIGHT}tfx notion-read${RESET} ${GRAY}Notion 페이지 → 마크다운 (Codex/Gemini MCP)${RESET}
|
|
924
924
|
${WHITE_BRIGHT}tfx version${RESET} ${GRAY}버전 표시${RESET}
|
|
925
925
|
|
|
926
|
-
${BOLD}Skills${RESET} ${GRAY}(Claude Code 슬래시 커맨드)${RESET}
|
|
927
|
-
|
|
928
|
-
${AMBER}/tfx-auto${RESET} ${GRAY}자동 분류 + 병렬 실행${RESET}
|
|
929
|
-
${WHITE_BRIGHT}/tfx-codex${RESET}
|
|
930
|
-
${
|
|
926
|
+
${BOLD}Skills${RESET} ${GRAY}(Claude Code 슬래시 커맨드)${RESET}
|
|
927
|
+
|
|
928
|
+
${AMBER}/tfx-auto${RESET} ${GRAY}자동 분류 + 병렬 실행${RESET}
|
|
929
|
+
${WHITE_BRIGHT}/tfx-auto-codex${RESET} ${GRAY}Codex 리드 + Gemini 유지 (no-Claude-native)${RESET}
|
|
930
|
+
${WHITE_BRIGHT}/tfx-codex${RESET} ${GRAY}Codex 전용 모드${RESET}
|
|
931
|
+
${BLUE}/tfx-gemini${RESET} ${GRAY}Gemini 전용 모드${RESET}
|
|
931
932
|
${AMBER}/tfx-setup${RESET} ${GRAY}HUD 설정 + 진단${RESET}
|
|
932
933
|
${YELLOW}/tfx-doctor${RESET} ${GRAY}진단 + 수리 + 캐시 초기화${RESET}
|
|
933
934
|
|
|
@@ -960,27 +961,31 @@ async function cmdCodexTeam() {
|
|
|
960
961
|
return;
|
|
961
962
|
}
|
|
962
963
|
|
|
963
|
-
const hasAgents = args.includes("--agents");
|
|
964
|
-
const hasLead = args.includes("--lead");
|
|
965
|
-
const hasLayout = args.includes("--layout");
|
|
966
|
-
const isControl = passthrough.has(sub);
|
|
967
|
-
const
|
|
968
|
-
|
|
969
|
-
if (!isControl && !
|
|
970
|
-
if (!isControl && !
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
const
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
}
|
|
983
|
-
|
|
964
|
+
const hasAgents = args.includes("--agents");
|
|
965
|
+
const hasLead = args.includes("--lead");
|
|
966
|
+
const hasLayout = args.includes("--layout");
|
|
967
|
+
const isControl = passthrough.has(sub);
|
|
968
|
+
const normalizedArgs = isControl && args.length ? [sub, ...args.slice(1)] : args;
|
|
969
|
+
const inject = [];
|
|
970
|
+
if (!isControl && !hasLead) inject.push("--lead", "codex");
|
|
971
|
+
if (!isControl && !hasAgents) inject.push("--agents", "codex,codex");
|
|
972
|
+
if (!isControl && !hasLayout) inject.push("--layout", "1xN");
|
|
973
|
+
const forwarded = isControl ? normalizedArgs : [...inject, ...args];
|
|
974
|
+
|
|
975
|
+
const prevArgv = process.argv;
|
|
976
|
+
const prevProfile = process.env.TFX_TEAM_PROFILE;
|
|
977
|
+
process.env.TFX_TEAM_PROFILE = "codex-team";
|
|
978
|
+
const { pathToFileURL } = await import("node:url");
|
|
979
|
+
const { cmdTeam } = await import(pathToFileURL(join(PKG_ROOT, "hub", "team", "cli.mjs")).href);
|
|
980
|
+
process.argv = [prevArgv[0], prevArgv[1], "team", ...forwarded];
|
|
981
|
+
try {
|
|
982
|
+
await cmdTeam();
|
|
983
|
+
} finally {
|
|
984
|
+
process.argv = prevArgv;
|
|
985
|
+
if (typeof prevProfile === "string") process.env.TFX_TEAM_PROFILE = prevProfile;
|
|
986
|
+
else delete process.env.TFX_TEAM_PROFILE;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
984
989
|
|
|
985
990
|
// ── hub 서브커맨드 ──
|
|
986
991
|
|
|
@@ -1061,10 +1066,40 @@ function autoRegisterMcp(mcpUrl) {
|
|
|
1061
1066
|
} catch (e) { warn(`Claude 등록 실패: ${e.message}`); }
|
|
1062
1067
|
}
|
|
1063
1068
|
|
|
1064
|
-
function cmdHub() {
|
|
1065
|
-
const sub = process.argv[3] || "status";
|
|
1066
|
-
|
|
1067
|
-
|
|
1069
|
+
async function cmdHub() {
|
|
1070
|
+
const sub = process.argv[3] || "status";
|
|
1071
|
+
const defaultPortRaw = Number(process.env.TFX_HUB_PORT || "27888");
|
|
1072
|
+
const probePort = Number.isFinite(defaultPortRaw) && defaultPortRaw > 0 ? defaultPortRaw : 27888;
|
|
1073
|
+
const formatHostForUrl = (host) => host.includes(":") ? `[${host}]` : host;
|
|
1074
|
+
const probeHubStatus = async (host = "127.0.0.1", port = probePort, timeoutMs = 3000) => {
|
|
1075
|
+
try {
|
|
1076
|
+
const res = await fetch(`http://${formatHostForUrl(host)}:${port}/status`, {
|
|
1077
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
1078
|
+
});
|
|
1079
|
+
if (!res.ok) return null;
|
|
1080
|
+
const data = await res.json();
|
|
1081
|
+
return data?.hub ? data : null;
|
|
1082
|
+
} catch {
|
|
1083
|
+
return null;
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
const recoverPidFile = (statusData, defaultHost = "127.0.0.1") => {
|
|
1087
|
+
const pid = Number(statusData?.pid);
|
|
1088
|
+
const port = Number(statusData?.port) || probePort;
|
|
1089
|
+
if (!Number.isFinite(pid) || pid <= 0) return;
|
|
1090
|
+
try {
|
|
1091
|
+
mkdirSync(HUB_PID_DIR, { recursive: true });
|
|
1092
|
+
writeFileSync(HUB_PID_FILE, JSON.stringify({
|
|
1093
|
+
pid,
|
|
1094
|
+
port,
|
|
1095
|
+
host: defaultHost,
|
|
1096
|
+
url: `http://${formatHostForUrl(defaultHost)}:${port}/mcp`,
|
|
1097
|
+
started: Date.now(),
|
|
1098
|
+
}));
|
|
1099
|
+
} catch {}
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
switch (sub) {
|
|
1068
1103
|
case "start": {
|
|
1069
1104
|
// 이미 실행 중인지 확인
|
|
1070
1105
|
if (existsSync(HUB_PID_FILE)) {
|
|
@@ -1095,13 +1130,13 @@ function cmdHub() {
|
|
|
1095
1130
|
});
|
|
1096
1131
|
child.unref();
|
|
1097
1132
|
|
|
1098
|
-
// PID 파일 확인 (최대 3초 대기, 100ms 폴링)
|
|
1099
|
-
let started = false;
|
|
1100
|
-
const deadline = Date.now() + 3000;
|
|
1101
|
-
while (Date.now() < deadline) {
|
|
1102
|
-
if (existsSync(HUB_PID_FILE)) { started = true; break; }
|
|
1103
|
-
|
|
1104
|
-
}
|
|
1133
|
+
// PID 파일 확인 (최대 3초 대기, 100ms 폴링)
|
|
1134
|
+
let started = false;
|
|
1135
|
+
const deadline = Date.now() + 3000;
|
|
1136
|
+
while (Date.now() < deadline) {
|
|
1137
|
+
if (existsSync(HUB_PID_FILE)) { started = true; break; }
|
|
1138
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
1139
|
+
}
|
|
1105
1140
|
|
|
1106
1141
|
if (started) {
|
|
1107
1142
|
const hubInfo = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
|
|
@@ -1120,11 +1155,20 @@ function cmdHub() {
|
|
|
1120
1155
|
break;
|
|
1121
1156
|
}
|
|
1122
1157
|
|
|
1123
|
-
case "stop": {
|
|
1124
|
-
if (!existsSync(HUB_PID_FILE)) {
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1158
|
+
case "stop": {
|
|
1159
|
+
if (!existsSync(HUB_PID_FILE)) {
|
|
1160
|
+
const probed = await probeHubStatus("127.0.0.1", probePort, 1500)
|
|
1161
|
+
|| (probePort === 27888 ? null : await probeHubStatus("127.0.0.1", 27888, 1500));
|
|
1162
|
+
if (probed && Number.isFinite(Number(probed.pid))) {
|
|
1163
|
+
try {
|
|
1164
|
+
process.kill(Number(probed.pid), "SIGTERM");
|
|
1165
|
+
console.log(`\n ${GREEN_BRIGHT}✓${RESET} hub 종료됨 (PID ${probed.pid})${DIM} (probe)${RESET}\n`);
|
|
1166
|
+
return;
|
|
1167
|
+
} catch {}
|
|
1168
|
+
}
|
|
1169
|
+
console.log(`\n ${DIM}hub 미실행${RESET}\n`);
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1128
1172
|
try {
|
|
1129
1173
|
const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
|
|
1130
1174
|
process.kill(info.pid, "SIGTERM");
|
|
@@ -1136,14 +1180,36 @@ function cmdHub() {
|
|
|
1136
1180
|
}
|
|
1137
1181
|
break;
|
|
1138
1182
|
}
|
|
1139
|
-
|
|
1140
|
-
case "status": {
|
|
1141
|
-
if (!existsSync(HUB_PID_FILE)) {
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1183
|
+
|
|
1184
|
+
case "status": {
|
|
1185
|
+
if (!existsSync(HUB_PID_FILE)) {
|
|
1186
|
+
const probed = await probeHubStatus();
|
|
1187
|
+
if (!probed) {
|
|
1188
|
+
const fallback = probePort === 27888 ? null : await probeHubStatus("127.0.0.1", 27888, 1500);
|
|
1189
|
+
if (fallback) {
|
|
1190
|
+
console.log(`\n ${AMBER}${BOLD}⬡ tfx-hub${RESET} ${GREEN_BRIGHT}online${RESET} ${DIM}(default port probe 성공)${RESET}`);
|
|
1191
|
+
console.log(` URL: http://127.0.0.1:${fallback.port || 27888}/mcp`);
|
|
1192
|
+
if (fallback.pid !== undefined) console.log(` PID: ${fallback.pid}`);
|
|
1193
|
+
if (fallback.hub?.state) console.log(` State: ${fallback.hub.state}`);
|
|
1194
|
+
if (fallback.sessions !== undefined) console.log(` Sessions: ${fallback.sessions}`);
|
|
1195
|
+
recoverPidFile(fallback, "127.0.0.1");
|
|
1196
|
+
console.log("");
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
console.log(`\n ${AMBER}${BOLD}⬡ tfx-hub${RESET} ${RED}offline${RESET}\n`);
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
console.log(`\n ${AMBER}${BOLD}⬡ tfx-hub${RESET} ${GREEN_BRIGHT}online${RESET} ${DIM}(pid file 없음 / probe 성공)${RESET}`);
|
|
1203
|
+
console.log(` URL: http://127.0.0.1:${probed.port || probePort}/mcp`);
|
|
1204
|
+
if (probed.pid !== undefined) console.log(` PID: ${probed.pid}`);
|
|
1205
|
+
if (probed.hub?.state) console.log(` State: ${probed.hub.state}`);
|
|
1206
|
+
if (probed.sessions !== undefined) console.log(` Sessions: ${probed.sessions}`);
|
|
1207
|
+
recoverPidFile(probed, "127.0.0.1");
|
|
1208
|
+
console.log("");
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
try {
|
|
1212
|
+
const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
|
|
1147
1213
|
process.kill(info.pid, 0); // 생존 확인
|
|
1148
1214
|
const uptime = Date.now() - info.started;
|
|
1149
1215
|
const uptimeStr = uptime < 60000 ? `${Math.round(uptime / 1000)}초`
|
|
@@ -1153,28 +1219,39 @@ function cmdHub() {
|
|
|
1153
1219
|
console.log(`\n ${AMBER}${BOLD}⬡ tfx-hub${RESET} ${GREEN_BRIGHT}online${RESET}`);
|
|
1154
1220
|
console.log(` URL: ${info.url}`);
|
|
1155
1221
|
console.log(` PID: ${info.pid}`);
|
|
1156
|
-
console.log(` Uptime: ${uptimeStr}`);
|
|
1157
|
-
|
|
1158
|
-
// HTTP 상태 조회 시도
|
|
1159
|
-
try {
|
|
1160
|
-
const
|
|
1161
|
-
const
|
|
1162
|
-
const data =
|
|
1163
|
-
if (data.hub) {
|
|
1164
|
-
console.log(` State: ${data.hub.state}`);
|
|
1165
|
-
}
|
|
1222
|
+
console.log(` Uptime: ${uptimeStr}`);
|
|
1223
|
+
|
|
1224
|
+
// HTTP 상태 조회 시도
|
|
1225
|
+
try {
|
|
1226
|
+
const host = typeof info.host === "string" ? info.host : "127.0.0.1";
|
|
1227
|
+
const port = Number(info.port) || probePort;
|
|
1228
|
+
const data = await probeHubStatus(host, port, 3000);
|
|
1229
|
+
if (data.hub) {
|
|
1230
|
+
console.log(` State: ${data.hub.state}`);
|
|
1231
|
+
}
|
|
1166
1232
|
if (data.sessions !== undefined) {
|
|
1167
1233
|
console.log(` Sessions: ${data.sessions}`);
|
|
1168
1234
|
}
|
|
1169
1235
|
} catch {}
|
|
1170
1236
|
|
|
1171
1237
|
console.log("");
|
|
1172
|
-
} catch {
|
|
1173
|
-
try { unlinkSync(HUB_PID_FILE); } catch {}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1238
|
+
} catch {
|
|
1239
|
+
try { unlinkSync(HUB_PID_FILE); } catch {}
|
|
1240
|
+
const probed = await probeHubStatus();
|
|
1241
|
+
if (!probed) {
|
|
1242
|
+
console.log(`\n ${AMBER}${BOLD}⬡ tfx-hub${RESET} ${RED}offline${RESET} ${DIM}(stale PID 정리됨)${RESET}\n`);
|
|
1243
|
+
break;
|
|
1244
|
+
}
|
|
1245
|
+
console.log(`\n ${AMBER}${BOLD}⬡ tfx-hub${RESET} ${GREEN_BRIGHT}online${RESET} ${DIM}(stale PID 정리 후 probe 성공)${RESET}`);
|
|
1246
|
+
console.log(` URL: http://127.0.0.1:${probed.port || probePort}/mcp`);
|
|
1247
|
+
if (probed.pid !== undefined) console.log(` PID: ${probed.pid}`);
|
|
1248
|
+
if (probed.hub?.state) console.log(` State: ${probed.hub.state}`);
|
|
1249
|
+
if (probed.sessions !== undefined) console.log(` Sessions: ${probed.sessions}`);
|
|
1250
|
+
recoverPidFile(probed, "127.0.0.1");
|
|
1251
|
+
console.log("");
|
|
1252
|
+
}
|
|
1253
|
+
break;
|
|
1254
|
+
}
|
|
1178
1255
|
|
|
1179
1256
|
default:
|
|
1180
1257
|
console.log(`\n ${AMBER}${BOLD}⬡ tfx-hub${RESET}\n`);
|
|
@@ -1199,7 +1276,7 @@ switch (cmd) {
|
|
|
1199
1276
|
}
|
|
1200
1277
|
case "update": cmdUpdate(); break;
|
|
1201
1278
|
case "list": case "ls": cmdList(); break;
|
|
1202
|
-
case "hub": cmdHub(); break;
|
|
1279
|
+
case "hub": await cmdHub(); break;
|
|
1203
1280
|
case "team": {
|
|
1204
1281
|
const { pathToFileURL } = await import("node:url");
|
|
1205
1282
|
const { cmdTeam } = await import(pathToFileURL(join(PKG_ROOT, "hub", "team", "cli.mjs")).href);
|
package/hooks/hooks.json
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"hooks": [
|
|
20
20
|
{
|
|
21
21
|
"type": "command",
|
|
22
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/
|
|
23
|
-
"timeout":
|
|
22
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/run.cjs\" \"${CLAUDE_PLUGIN_ROOT}/scripts/keyword-detector.mjs\"",
|
|
23
|
+
"timeout": 5
|
|
24
24
|
}
|
|
25
25
|
]
|
|
26
26
|
}
|