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 CHANGED
@@ -85,19 +85,25 @@ tfx doctor
85
85
  /analyze "결제 모듈 보안 리뷰"
86
86
  /research "최신 React Server Components 패턴"
87
87
 
88
- # 단일 CLI 모드
89
- /tfx-codex "리팩터링 + 리뷰" # Codex 사용
90
- /tfx-gemini "구현 + 문서화" # Gemini만 사용
91
- ```
92
-
93
- ## 스킬
94
-
95
- | 스킬 | 모드 | 설명 |
96
- |------|------|------|
97
- | `/tfx-auto` | 자동 | 트리아지 → 분해 → DAG 병렬 실행 |
98
- | `/tfx-codex` | Codex 전용 | 모든 CLI 작업을 Codex로 라우팅 |
99
- | `/tfx-gemini` | Gemini 전용 | 모든 CLI 작업을 Gemini로 라우팅 |
100
- | `/tfx-setup` | 설정 | 파일 동기화, HUD 설정, CLI 진단 |
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" # Codex only
90
- /tfx-gemini "implement + document" # Gemini only
91
- ```
92
-
93
- ## Skills
94
-
95
- | Skill | Mode | Description |
96
- |-------|------|-------------|
97
- | `/tfx-auto` | Auto | Triage → decompose → parallel execute via DAG |
98
- | `/tfx-codex` | Codex-only | All CLI tasks routed to Codex |
99
- | `/tfx-gemini` | Gemini-only | All CLI tasks routed to Gemini |
100
- | `/tfx-setup` | Setup | File sync, HUD config, CLI diagnostics |
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} ${GRAY}Codex 전용 모드${RESET}
930
- ${BLUE}/tfx-gemini${RESET} ${GRAY}Gemini 전용 모드${RESET}
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 inject = [];
968
- if (!isControl && !hasLead) inject.push("--lead", "codex");
969
- if (!isControl && !hasAgents) inject.push("--agents", "codex,codex");
970
- if (!isControl && !hasLayout) inject.push("--layout", "1xN");
971
- const forwarded = isControl ? args : [...inject, ...args];
972
-
973
- const { pathToFileURL } = await import("node:url");
974
- const { cmdTeam } = await import(pathToFileURL(join(PKG_ROOT, "hub", "team", "cli.mjs")).href);
975
-
976
- const prevArgv = process.argv;
977
- process.argv = [prevArgv[0], prevArgv[1], "team", ...forwarded];
978
- try {
979
- await cmdTeam();
980
- } finally {
981
- process.argv = prevArgv;
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
- switch (sub) {
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
- execSync("node -e \"setTimeout(()=>{},100)\"", { stdio: "ignore", timeout: 500 });
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
- console.log(`\n ${DIM}hub 미실행${RESET}\n`);
1126
- return;
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
- console.log(`\n ${AMBER}${BOLD}⬡ tfx-hub${RESET} ${RED}offline${RESET}\n`);
1143
- return;
1144
- }
1145
- try {
1146
- const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
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 statusUrl = info.url.replace("/mcp", "/status");
1161
- const result = execSync(`curl -s "${statusUrl}"`, { encoding: "utf8", timeout: 3000, stdio: ["pipe", "pipe", "ignore"] });
1162
- const data = JSON.parse(result);
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
- console.log(`\n ${AMBER}${BOLD}⬡ tfx-hub${RESET} ${RED}offline${RESET} ${DIM}(stale PID 정리됨)${RESET}\n`);
1175
- }
1176
- break;
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/team-keyword.mjs\"",
23
- "timeout": 3
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
  }