triflux 10.3.4 → 10.7.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.
Files changed (329) hide show
  1. package/LICENSE +21 -21
  2. package/bin/tfx-doctor-tui.mjs +1 -1
  3. package/bin/tfx-doctor.mjs +6 -1
  4. package/bin/tfx-profile.mjs +1 -1
  5. package/bin/tfx-setup-tui.mjs +1 -1
  6. package/bin/tfx-setup.mjs +6 -1
  7. package/bin/triflux.mjs +2396 -1140
  8. package/hooks/agent-route-guard.mjs +12 -8
  9. package/hooks/cross-review-tracker.mjs +21 -8
  10. package/hooks/error-context.mjs +19 -7
  11. package/hooks/hook-adaptive-collector.mjs +18 -16
  12. package/hooks/hook-manager.mjs +93 -32
  13. package/hooks/hook-orchestrator.mjs +108 -24
  14. package/hooks/hook-registry.json +11 -0
  15. package/hooks/keyword-rules.json +6 -10
  16. package/hooks/lib/resolve-root.mjs +1 -1
  17. package/hooks/mcp-config-watcher.mjs +6 -2
  18. package/hooks/pipeline-stop.mjs +3 -6
  19. package/hooks/safety-guard.mjs +99 -28
  20. package/hooks/session-start-fast.mjs +143 -0
  21. package/hooks/subagent-verifier.mjs +5 -4
  22. package/hub/account-broker.mjs +256 -60
  23. package/hub/adaptive-diagnostic.mjs +75 -48
  24. package/hub/adaptive-inject.mjs +95 -57
  25. package/hub/adaptive-memory.mjs +156 -42
  26. package/hub/adaptive.mjs +60 -31
  27. package/hub/assign-callbacks.mjs +67 -30
  28. package/hub/bridge.mjs +0 -1
  29. package/hub/cli-adapter-base.mjs +200 -48
  30. package/hub/codex-adapter.mjs +76 -96
  31. package/hub/codex-compat.mjs +3 -3
  32. package/hub/codex-preflight.mjs +63 -37
  33. package/hub/delegator/contracts.mjs +19 -23
  34. package/hub/delegator/index.mjs +3 -3
  35. package/hub/delegator/service.mjs +88 -64
  36. package/hub/delegator/tool-definitions.mjs +5 -5
  37. package/hub/fullcycle.mjs +33 -17
  38. package/hub/gemini-adapter.mjs +69 -94
  39. package/hub/hitl.mjs +89 -30
  40. package/hub/intent.mjs +161 -38
  41. package/hub/lib/cache-guard.mjs +43 -17
  42. package/hub/lib/mcp-response-cache.mjs +66 -32
  43. package/hub/lib/memory-store.mjs +285 -111
  44. package/hub/lib/path-utils.mjs +35 -37
  45. package/hub/lib/process-utils.mjs +106 -37
  46. package/hub/lib/spawn-trace.mjs +527 -0
  47. package/hub/lib/ssh-command.mjs +34 -4
  48. package/hub/lib/ssh-retry.mjs +5 -1
  49. package/hub/lib/uuidv7.mjs +4 -3
  50. package/hub/memory-doctor.mjs +266 -106
  51. package/hub/middleware/request-logger.mjs +61 -34
  52. package/hub/paths.mjs +9 -9
  53. package/hub/pipeline/gates/confidence.mjs +34 -15
  54. package/hub/pipeline/gates/consensus.mjs +27 -15
  55. package/hub/pipeline/gates/index.mjs +7 -3
  56. package/hub/pipeline/gates/selfcheck.mjs +57 -19
  57. package/hub/pipeline/index.mjs +77 -42
  58. package/hub/pipeline/state.mjs +10 -10
  59. package/hub/pipeline/transitions.mjs +40 -23
  60. package/hub/platform.mjs +57 -48
  61. package/hub/promote-penalties.mjs +25 -7
  62. package/hub/quality/deslop.mjs +70 -49
  63. package/hub/research.mjs +32 -25
  64. package/hub/router.mjs +240 -107
  65. package/hub/routing/complexity.mjs +132 -29
  66. package/hub/routing/index.mjs +17 -12
  67. package/hub/routing/q-learning.mjs +76 -28
  68. package/hub/server.mjs +4 -4
  69. package/hub/session-fingerprint.mjs +126 -60
  70. package/hub/state.mjs +84 -43
  71. package/hub/store-adapter.mjs +59 -26
  72. package/hub/store.mjs +356 -153
  73. package/hub/team/agent-map.json +22 -7
  74. package/hub/team/ansi.mjs +186 -122
  75. package/hub/team/backend.mjs +28 -10
  76. package/hub/team/cli/commands/attach.mjs +29 -9
  77. package/hub/team/cli/commands/control.mjs +29 -8
  78. package/hub/team/cli/commands/debug.mjs +32 -11
  79. package/hub/team/cli/commands/focus.mjs +38 -11
  80. package/hub/team/cli/commands/interrupt.mjs +18 -6
  81. package/hub/team/cli/commands/kill.mjs +16 -5
  82. package/hub/team/cli/commands/list.mjs +11 -4
  83. package/hub/team/cli/commands/send.mjs +19 -6
  84. package/hub/team/cli/commands/start/index.mjs +154 -31
  85. package/hub/team/cli/commands/start/parse-args.mjs +38 -11
  86. package/hub/team/cli/commands/start/start-headless.mjs +112 -36
  87. package/hub/team/cli/commands/start/start-in-process.mjs +12 -2
  88. package/hub/team/cli/commands/start/start-mux.mjs +70 -21
  89. package/hub/team/cli/commands/start/start-wt.mjs +29 -12
  90. package/hub/team/cli/commands/status.mjs +43 -14
  91. package/hub/team/cli/commands/stop.mjs +11 -4
  92. package/hub/team/cli/commands/task.mjs +8 -3
  93. package/hub/team/cli/commands/tasks.mjs +1 -1
  94. package/hub/team/cli/index.mjs +2 -2
  95. package/hub/team/cli/manifest.mjs +38 -8
  96. package/hub/team/cli/render.mjs +30 -8
  97. package/hub/team/cli/services/attach-fallback.mjs +31 -11
  98. package/hub/team/cli/services/hub-client.mjs +42 -14
  99. package/hub/team/cli/services/member-selector.mjs +11 -4
  100. package/hub/team/cli/services/native-control.mjs +48 -21
  101. package/hub/team/cli/services/runtime-mode.mjs +2 -1
  102. package/hub/team/cli/services/state-store.mjs +25 -8
  103. package/hub/team/cli/services/task-model.mjs +16 -6
  104. package/hub/team/conductor-mesh-bridge.mjs +24 -23
  105. package/hub/team/conductor.mjs +8 -4
  106. package/hub/team/dashboard-anchor.mjs +4 -5
  107. package/hub/team/dashboard-layout.mjs +3 -1
  108. package/hub/team/dashboard-open.mjs +41 -21
  109. package/hub/team/dashboard.mjs +76 -28
  110. package/hub/team/event-log.mjs +18 -10
  111. package/hub/team/handoff.mjs +31 -15
  112. package/hub/team/headless.mjs +2 -1
  113. package/hub/team/health-probe.mjs +69 -54
  114. package/hub/team/launcher-template.mjs +16 -13
  115. package/hub/team/native-supervisor.mjs +65 -21
  116. package/hub/team/native.mjs +74 -35
  117. package/hub/team/nativeProxy.mjs +184 -113
  118. package/hub/team/notify.mjs +119 -76
  119. package/hub/team/orchestrator.mjs +9 -4
  120. package/hub/team/pane.mjs +12 -7
  121. package/hub/team/process-cleanup.mjs +25 -16
  122. package/hub/team/psmux.mjs +491 -201
  123. package/hub/team/remote-probe.mjs +68 -52
  124. package/hub/team/remote-session.mjs +117 -59
  125. package/hub/team/remote-watcher.mjs +61 -33
  126. package/hub/team/routing.mjs +51 -25
  127. package/hub/team/runtime-strategy.mjs +3 -1
  128. package/hub/team/session.mjs +98 -34
  129. package/hub/team/staleState.mjs +72 -30
  130. package/hub/team/swarm-locks.mjs +15 -13
  131. package/hub/team/swarm-planner.mjs +32 -21
  132. package/hub/team/swarm-reconciler.mjs +48 -23
  133. package/hub/team/tui-lite.mjs +266 -68
  134. package/hub/team/tui-remote-adapter.mjs +14 -10
  135. package/hub/team/tui-viewer.mjs +99 -43
  136. package/hub/team/tui.mjs +708 -271
  137. package/hub/team/worktree-lifecycle.mjs +152 -58
  138. package/hub/team/wt-manager.mjs +24 -14
  139. package/hub/token-mode.mjs +71 -71
  140. package/hub/tray.mjs +66 -23
  141. package/hub/workers/claude-worker.mjs +162 -118
  142. package/hub/workers/codex-mcp.mjs +192 -141
  143. package/hub/workers/delegator-mcp.mjs +507 -333
  144. package/hub/workers/factory.mjs +8 -8
  145. package/hub/workers/gemini-worker.mjs +115 -84
  146. package/hub/workers/interface.mjs +6 -1
  147. package/hub/workers/worker-utils.mjs +21 -14
  148. package/hud/colors.mjs +27 -9
  149. package/hud/constants.mjs +162 -26
  150. package/hud/context-monitor.mjs +82 -41
  151. package/hud/hud-qos-status.mjs +129 -49
  152. package/hud/mission-board.mjs +6 -3
  153. package/hud/providers/claude.mjs +226 -115
  154. package/hud/providers/codex.mjs +62 -22
  155. package/hud/providers/gemini.mjs +168 -56
  156. package/hud/renderers.mjs +384 -119
  157. package/hud/terminal.mjs +101 -31
  158. package/hud/utils.mjs +78 -38
  159. package/mesh/index.mjs +11 -5
  160. package/mesh/mesh-budget.mjs +18 -9
  161. package/mesh/mesh-heartbeat.mjs +1 -1
  162. package/mesh/mesh-queue.mjs +3 -5
  163. package/mesh/mesh-router.mjs +5 -4
  164. package/package.json +2 -1
  165. package/scripts/__tests__/gen-skill-docs.test.mjs +36 -7
  166. package/scripts/__tests__/keyword-detector.test.mjs +77 -28
  167. package/scripts/__tests__/mcp-guard-engine.test.mjs +58 -20
  168. package/scripts/__tests__/remote-spawn-transfer.test.mjs +30 -19
  169. package/scripts/__tests__/remote-spawn.test.mjs +10 -4
  170. package/scripts/__tests__/session-start-fast.test.mjs +36 -0
  171. package/scripts/__tests__/skill-template.test.mjs +98 -50
  172. package/scripts/__tests__/smoke.test.mjs +1 -1
  173. package/scripts/__tests__/spawn-trace.test.mjs +102 -0
  174. package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +48 -0
  175. package/scripts/cache-doctor.mjs +11 -4
  176. package/scripts/cache-warmup.mjs +96 -37
  177. package/scripts/claudemd-sync.mjs +27 -17
  178. package/scripts/codex-gateway-preflight.mjs +52 -37
  179. package/scripts/codex-mcp-gateway-sync.mjs +59 -39
  180. package/scripts/completions/tfx.bash +47 -47
  181. package/scripts/completions/tfx.fish +44 -44
  182. package/scripts/completions/tfx.zsh +83 -83
  183. package/scripts/config-audit.mjs +232 -0
  184. package/scripts/convert-to-tmpl.mjs +54 -0
  185. package/scripts/cross-review-gate.mjs +35 -12
  186. package/scripts/cross-review-tracker.mjs +21 -8
  187. package/scripts/demo.mjs +35 -17
  188. package/scripts/doctor-diagnose.mjs +284 -0
  189. package/scripts/gen-skill-docs.mjs +7 -2
  190. package/scripts/gen-skill-manifest.mjs +2 -1
  191. package/scripts/headless-guard.mjs +86 -48
  192. package/scripts/hub-ensure.mjs +45 -26
  193. package/scripts/keyword-detector.mjs +41 -20
  194. package/scripts/keyword-rules-expander.mjs +47 -30
  195. package/scripts/lib/claudemd-scanner.mjs +6 -1
  196. package/scripts/lib/context.mjs +3 -3
  197. package/scripts/lib/cross-review-utils.mjs +6 -3
  198. package/scripts/lib/env-probe.mjs +47 -28
  199. package/scripts/lib/gemini-profiles.mjs +44 -10
  200. package/scripts/lib/handoff.mjs +33 -17
  201. package/scripts/lib/hook-utils.mjs +8 -6
  202. package/scripts/lib/keyword-rules.mjs +43 -19
  203. package/scripts/lib/logger.mjs +24 -24
  204. package/scripts/lib/mcp-filter.mjs +377 -239
  205. package/scripts/lib/mcp-guard-engine.mjs +194 -79
  206. package/scripts/lib/mcp-manifest.mjs +23 -13
  207. package/scripts/lib/mcp-server-catalog.mjs +300 -63
  208. package/scripts/lib/psmux-info.mjs +11 -6
  209. package/scripts/lib/remote-spawn-transfer.mjs +44 -14
  210. package/scripts/lib/skill-template.mjs +30 -7
  211. package/scripts/mcp-check.mjs +58 -39
  212. package/scripts/mcp-gateway-config.mjs +83 -39
  213. package/scripts/mcp-gateway-ensure.mjs +43 -35
  214. package/scripts/mcp-gateway-integration-test.mjs +70 -58
  215. package/scripts/mcp-gateway-start.mjs +126 -60
  216. package/scripts/mcp-gateway-verify.mjs +24 -22
  217. package/scripts/mcp-safety-guard.mjs +44 -11
  218. package/scripts/notion-read.mjs +199 -84
  219. package/scripts/pack.mjs +94 -89
  220. package/scripts/preflight-cache.mjs +27 -10
  221. package/scripts/preinstall.mjs +42 -13
  222. package/scripts/remote-spawn.mjs +309 -94
  223. package/scripts/run.cjs +8 -5
  224. package/scripts/session-spawn-helper.mjs +130 -39
  225. package/scripts/session-stale-cleanup.mjs +123 -0
  226. package/scripts/setup.mjs +941 -492
  227. package/scripts/test-lock.mjs +20 -7
  228. package/scripts/test-tfx-route-no-claude-native.mjs +16 -12
  229. package/scripts/tfx-batch-stats.mjs +32 -11
  230. package/scripts/tfx-gate-activate.mjs +11 -4
  231. package/scripts/tfx-route-post.mjs +87 -20
  232. package/scripts/tfx-route-worker.mjs +57 -51
  233. package/scripts/tfx-route.sh +41 -124
  234. package/scripts/tmp-cleanup.mjs +21 -7
  235. package/scripts/token-snapshot.mjs +204 -85
  236. package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
  237. package/skills/.omc/state/idle-notif-cooldown.json +3 -0
  238. package/skills/.omc/state/last-tool-error.json +7 -0
  239. package/skills/.omc/state/subagent-tracking.json +7 -0
  240. package/skills/_templates/base.md +1 -6
  241. package/skills/merge-worktree/SKILL.md.tmpl +144 -0
  242. package/skills/shared/telemetry-segment.md +6 -0
  243. package/skills/star-prompt/SKILL.md.tmpl +222 -0
  244. package/skills/tfx-analysis/SKILL.md.tmpl +107 -0
  245. package/skills/tfx-analysis/skill.json +1 -6
  246. package/skills/tfx-auto/SKILL.md +1 -0
  247. package/skills/tfx-auto-codex/SKILL.md.tmpl +106 -0
  248. package/skills/tfx-auto-codex/skill.json +1 -3
  249. package/skills/tfx-autopilot/SKILL.md.tmpl +116 -0
  250. package/skills/tfx-autopilot/skill.json +1 -5
  251. package/skills/tfx-autoresearch/SKILL.md.tmpl +136 -0
  252. package/skills/tfx-autoroute/SKILL.md.tmpl +189 -0
  253. package/skills/tfx-autoroute/skill.json +1 -7
  254. package/skills/tfx-codex/SKILL.md +1 -0
  255. package/skills/tfx-codex/skill.json +1 -3
  256. package/skills/tfx-codex-swarm/SKILL.md.tmpl +16 -0
  257. package/skills/tfx-codex-swarm/evals/evals.json +1 -1
  258. package/skills/tfx-codex-swarm/skill.json +1 -4
  259. package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +54 -12
  260. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +35 -7
  261. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +35 -7
  262. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +25 -5
  263. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +25 -5
  264. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +20 -4
  265. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +16 -4
  266. package/skills/tfx-consensus/SKILL.md.tmpl +146 -0
  267. package/skills/tfx-debate/SKILL.md.tmpl +192 -0
  268. package/skills/tfx-debate/skill.json +1 -7
  269. package/skills/tfx-deep-analysis/SKILL.md.tmpl +228 -0
  270. package/skills/tfx-deep-analysis/skill.json +1 -5
  271. package/skills/tfx-deep-interview/SKILL.md.tmpl +203 -0
  272. package/skills/tfx-deep-plan/SKILL.md.tmpl +282 -0
  273. package/skills/tfx-deep-qa/SKILL.md.tmpl +165 -0
  274. package/skills/tfx-deep-qa/skill.json +1 -6
  275. package/skills/tfx-deep-research/SKILL.md.tmpl +217 -0
  276. package/skills/tfx-deep-review/SKILL.md.tmpl +179 -0
  277. package/skills/tfx-doctor/SKILL.md +21 -0
  278. package/skills/tfx-doctor/SKILL.md.tmpl +172 -0
  279. package/skills/tfx-doctor/skill.json +1 -3
  280. package/skills/tfx-find/SKILL.md +1 -0
  281. package/skills/tfx-forge/SKILL.md.tmpl +187 -0
  282. package/skills/tfx-fullcycle/SKILL.md.tmpl +286 -0
  283. package/skills/tfx-fullcycle/skill.json +1 -6
  284. package/skills/tfx-gemini/SKILL.md.tmpl +91 -0
  285. package/skills/tfx-gemini/skill.json +1 -3
  286. package/skills/tfx-hooks/SKILL.md.tmpl +216 -0
  287. package/skills/tfx-hooks/skill.json +1 -3
  288. package/skills/tfx-hub/SKILL.md.tmpl +212 -0
  289. package/skills/tfx-hub/skill.json +1 -3
  290. package/skills/tfx-index/SKILL.md +1 -0
  291. package/skills/tfx-index/skill.json +1 -6
  292. package/skills/tfx-interview/SKILL.md.tmpl +285 -0
  293. package/skills/tfx-multi/SKILL.md.tmpl +183 -0
  294. package/skills/tfx-multi/skill.json +1 -3
  295. package/skills/tfx-panel/SKILL.md.tmpl +189 -0
  296. package/skills/tfx-panel/skill.json +1 -7
  297. package/skills/tfx-persist/SKILL.md.tmpl +270 -0
  298. package/skills/tfx-persist/skill.json +1 -7
  299. package/skills/tfx-plan/SKILL.md +1 -0
  300. package/skills/tfx-plan/skill.json +1 -6
  301. package/skills/tfx-profile/SKILL.md.tmpl +239 -0
  302. package/skills/tfx-profile/skill.json +1 -3
  303. package/skills/tfx-prune/SKILL.md.tmpl +200 -0
  304. package/skills/tfx-prune/skill.json +1 -7
  305. package/skills/tfx-psmux-rules/SKILL.md.tmpl +326 -0
  306. package/skills/tfx-psmux-rules/skill.json +1 -4
  307. package/skills/tfx-qa/SKILL.md +1 -0
  308. package/skills/tfx-qa/skill.json +1 -6
  309. package/skills/tfx-ralph/SKILL.md.tmpl +28 -0
  310. package/skills/tfx-ralph/skill.json +1 -4
  311. package/skills/tfx-remote-setup/SKILL.md.tmpl +576 -0
  312. package/skills/tfx-remote-setup/skill.json +1 -3
  313. package/skills/tfx-remote-spawn/SKILL.md.tmpl +263 -0
  314. package/skills/tfx-remote-spawn/references/hosts.json +16 -0
  315. package/skills/tfx-remote-spawn/skill.json +1 -4
  316. package/skills/tfx-research/SKILL.md +1 -0
  317. package/skills/tfx-review/SKILL.md +1 -0
  318. package/skills/tfx-review/skill.json +1 -6
  319. package/skills/tfx-setup/SKILL.md.tmpl +504 -0
  320. package/skills/tfx-setup/skill.json +1 -3
  321. package/skills/tfx-swarm/SKILL.md +22 -0
  322. package/skills/tfx-swarm/SKILL.md.tmpl +218 -0
  323. package/tui/codex-profile.mjs +88 -33
  324. package/tui/core.mjs +45 -15
  325. package/tui/doctor.mjs +75 -28
  326. package/tui/gemini-profile.mjs +74 -29
  327. package/tui/monitor-data.mjs +8 -4
  328. package/tui/monitor.mjs +71 -27
  329. package/tui/setup.mjs +133 -42
@@ -0,0 +1,218 @@
1
+ ---
2
+ name: tfx-swarm
3
+ description: "tfx-swarm — 다중 기기 x 다중 모델 스웜 오케스트레이션"
4
+ triggers:
5
+ - swarm
6
+ - 스웜
7
+ - 병렬 실행
8
+ - codex-swarm
9
+ ---
10
+
11
+ # {{SKILL_NAME}} — 다중 기기 x 다중 모델 스웜 오케스트레이션
12
+
13
+ {{> base}}
14
+
15
+ > **Multi-Machine x Multi-Model Swarm.** PRD 하나로 로컬과 원격 머신에서
16
+ > Claude + Codex + Gemini를 병렬 실행하고, file-lease로 충돌을 방지하며,
17
+ > 결과를 자동 통합한다. triflux의 킬러 스킬.
18
+
19
+ ## 트리거
20
+
21
+ - `swarm`, `스웜`, `병렬 실행`, `다중 워커`, `PRD 실행`, `swarm launch`
22
+ - `codex-swarm` (backward compat -> 이 스킬로 라우팅)
23
+
24
+ ## 핵심 기능
25
+
26
+ | 기능 | 설명 |
27
+ |------|------|
28
+ | **다중 모델** | shard별 `agent: codex\|gemini\|claude` 지정. 작업 특성에 맞는 모델 배치 |
29
+ | **다중 기기** | shard별 `host: <ssh-host>` 지정. 로컬/원격 혼합 실행 |
30
+ | **File Lease** | shard별 파일 소유권. 동일 파일 동시 수정 방지 |
31
+ | **Redundant Execution** | critical shard는 다른 모델로 이중 실행 후 reconcile |
32
+ | **자동 통합** | 의존 순서대로 merge. 원격 shard는 SSH 경유 fetch |
33
+ | **장애 폴백** | rate limit -> 다른 모델로 자동 전환 (codex<->gemini<->claude) |
34
+
35
+ ## 전제조건
36
+
37
+ - psmux >= 3.3.0
38
+ - Hub 실행 중 (`curl -sf http://127.0.0.1:27888/status`)
39
+ - Codex CLI 또는 Gemini CLI (사용할 agent에 따라)
40
+ - 원격 shard 사용 시: SSH 키 인증 + 원격 머신에 Claude Code 설치
41
+ - 프로젝트에 `docs/prd/` 디렉토리 존재
42
+
43
+ ## 실행 흐름
44
+
45
+ ### Step 1: PRD 탐색
46
+
47
+ ```bash
48
+ find docs/prd -name '*.md' -not -name '_template.md' -not -path '*/archived/*' | sort
49
+ ```
50
+
51
+ AskUserQuestion으로 실행할 PRD 선택. 복수 선택 시 각각 독립 shard.
52
+
53
+ ### Step 2: 원격/모델 구성 확인
54
+
55
+ PRD 파싱 후, shard에 `host:` 필드가 있으면 AskUserQuestion으로 원격 실행 여부를 확인한다.
56
+
57
+ AskUserQuestion:
58
+
59
+ > PRD에 원격 호스트가 지정된 shard가 있습니다:
60
+ > - `{shard_name}` -> `{host}` ({agent})
61
+ >
62
+ > 원격 실행을 어떻게 할까요?
63
+
64
+ Options:
65
+ - A) 원격 포함 실행 (PRD 그대로) — 로컬+원격 혼합
66
+ - B) 전부 로컬에서 실행 — host 필드 무시
67
+ - C) 원격만 실행 — 로컬 shard 건너뛰기
68
+
69
+ PRD에 다중 agent가 지정된 경우에도 AskUserQuestion:
70
+
71
+ > PRD에 여러 모델이 지정되어 있습니다:
72
+ > - codex: {N}개 shard
73
+ > - gemini: {N}개 shard
74
+ > - claude: {N}개 shard
75
+ >
76
+ > 모델 배치를 어떻게 할까요?
77
+
78
+ Options:
79
+ - A) PRD 그대로 — shard별 지정 모델 사용 (권장)
80
+ - B) 전부 Codex로 — 단일 모델
81
+ - C) 전부 Gemini로 — 단일 모델
82
+
83
+ ### Step 3: 계획 생성
84
+
85
+ ```javascript
86
+ import { planSwarm } from '../../hub/team/swarm-planner.mjs';
87
+
88
+ const swarmPlan = planSwarm(selectedPrdPath);
89
+ ```
90
+
91
+ 계획을 사용자에게 보여주고 승인 요청:
92
+ - shard 수, 파일 배분, lease 맵, merge 순서
93
+ - 원격 shard 표시 (host 정보)
94
+ - 모델 배분 표시 (agent 정보)
95
+ - critical shard 표시 (redundant execution 대상)
96
+
97
+ ### Step 4: Hypervisor 실행
98
+
99
+ ```javascript
100
+ import { createSwarmHypervisor } from '../../hub/team/swarm-hypervisor.mjs';
101
+
102
+ const hyper = createSwarmHypervisor({
103
+ workdir: process.cwd(),
104
+ logsDir: join(process.cwd(), '.triflux', 'swarm-logs'),
105
+ maxRestarts: 2,
106
+ });
107
+
108
+ const run = hyper.launch(swarmPlan);
109
+ ```
110
+
111
+ 실행 중 상태 모니터링:
112
+ - `hyper.on('shardLaunched', ...)` -> 진행 표시
113
+ - `hyper.on('shardCompleted', ...)` -> 완료/실패 표시
114
+ - `hyper.on('warning', ...)` -> 경고 (파일 충돌 등)
115
+
116
+ 원격 shard 실행 시:
117
+ 1. `probeRemoteEnv(host)` -> 원격 환경 감지 (OS, shell, Claude 경로)
118
+ 2. conductor가 `remote: true` 설정으로 SSH 경유 세션 실행
119
+ 3. 완료 후 `fetchRemoteShard()`로 원격 브랜치를 로컬로 fetch
120
+
121
+ ### Step 5: 결과 검증 + 통합
122
+
123
+ ```javascript
124
+ const status = hyper.getStatus();
125
+ // status.completedShards, status.failedShards, status.workers
126
+ ```
127
+
128
+ 통합 결과를 사용자에게 보고:
129
+ - 성공: merged shard 목록 (로컬/원격 구분 표시)
130
+ - 실패: 충돌/실패 shard 목록 + 수동 해결 안내
131
+
132
+ ### Step 6: 정리
133
+
134
+ ```javascript
135
+ await hyper.shutdown('completed');
136
+ ```
137
+
138
+ ## PRD 예제: 다중 기기 x 다중 모델
139
+
140
+ ```markdown
141
+ ## Shard: auth-refactor
142
+ - agent: codex
143
+ - files: src/auth.mjs, src/middleware/jwt.mjs
144
+ - prompt: JWT 인증 미들웨어 리팩터링
145
+
146
+ ## Shard: ui-dashboard
147
+ - agent: gemini
148
+ - files: src/ui/dashboard.mjs, src/ui/charts.mjs
149
+ - prompt: 대시보드 UI 개선
150
+
151
+ ## Shard: security-audit
152
+ - agent: claude
153
+ - host: ryzen5-7600
154
+ - files: src/security.mjs
155
+ - critical: true
156
+ - prompt: 보안 취약점 감사
157
+
158
+ ## Shard: perf-optimization
159
+ - agent: codex
160
+ - host: m2
161
+ - files: src/engine/optimizer.mjs
162
+ - depends: auth-refactor
163
+ - prompt: 성능 최적화 (auth 리팩터 완료 후)
164
+ ```
165
+
166
+ 위 PRD 하나로: Codex(로컬) + Gemini(로컬) + Claude(ryzen5-7600) + Codex(m2) 4개 워커가 병렬 실행된다.
167
+ security-audit는 `critical: true`이므로 다른 모델로 이중 실행 후 reconcile.
168
+
169
+ ## PRD Shard 필드 레퍼런스
170
+
171
+ | 필드 | 필수 | 기본값 | 설명 |
172
+ |------|------|--------|------|
173
+ | `agent` | - | `codex` | 실행 모델: `codex`, `gemini`, `claude` |
174
+ | `host` | - | (로컬) | SSH 호스트. 미지정 시 로컬 실행 |
175
+ | `files` | O | - | shard가 수정할 파일 목록 (file-lease 대상) |
176
+ | `depends` | - | - | 의존하는 shard 이름. 해당 shard 완료 후 실행 |
177
+ | `critical` | - | `false` | `true`면 다른 모델로 이중 실행 + reconcile |
178
+ | `mcp` | - | - | 필요한 MCP 서버 목록 |
179
+ | `prompt` | O | - | shard 실행 프롬프트. `\|`로 멀티라인 |
180
+
181
+ ## 제약
182
+
183
+ - `codex exec` / `gemini -p` 직접 호출 금지 (headless-guard)
184
+ - MAX_CONCURRENCY 기본 4 (WT 프리징 방지)
185
+ - WT 조작 간 sleep 2s (race-guard)
186
+ - config.toml과 CLI 플래그 중복 지정 금지
187
+
188
+ ## Redundant Execution
189
+
190
+ critical shard (hard lease 또는 고위험 파일)에 대해:
191
+
192
+ ```javascript
193
+ import { shouldRunRedundant, reconcile } from '../../hub/team/swarm-reconciler.mjs';
194
+
195
+ if (shouldRunRedundant(shard)) {
196
+ // primary + verifier 이중 실행
197
+ const decision = await reconcile(primaryResult, verifierResult);
198
+ // decision.selected: 'primary' | 'verifier' | 'hitl'
199
+ }
200
+ ```
201
+
202
+ HITL fallback 시 AskUserQuestion으로 사용자에게 선택 요청.
203
+
204
+ ## 장애 처리
205
+
206
+ | 장애 | 분류 | 대응 |
207
+ |------|------|------|
208
+ | 워커 크래시 | F1 | conductor auto-restart (최대 2회) |
209
+ | Rate limit | F2 | 다른 모델로 자동 전환 (codex->gemini->claude) |
210
+ | Stall | F3 | health probe 감지 -> kill -> restart |
211
+ | File lease 위반 | F4 | 워커 변경 revert, shard 실패 처리 |
212
+ | Merge 충돌 | F5 | 충돌 해결 재시도 |
213
+
214
+ ## 기존 스킬과의 관계
215
+
216
+ - **tfx-codex-swarm**: deprecated. 이 스킬로 통합됨 (backward compat alias)
217
+ - **tfx-remote-spawn**: 단독 원격 세션 관리(list, attach, send)용으로 유지. swarm은 다중 shard 병렬 관리
218
+ - **tfx-multi**: Claude Native Teams 기반 로컬 오케스트레이션. swarm은 PRD 기반 worktree 분할
@@ -1,32 +1,56 @@
1
1
  #!/usr/bin/env node
2
2
  // tui/codex-profile.mjs — Interactive Codex Profile Manager
3
- import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync } from "node:fs";
4
- import { join } from "node:path";
3
+ import {
4
+ copyFileSync,
5
+ existsSync,
6
+ mkdirSync,
7
+ readFileSync,
8
+ writeFileSync,
9
+ } from "node:fs";
5
10
  import { homedir } from "node:os";
11
+ import { join } from "node:path";
6
12
  import {
7
- clear, box, table, divider, label, ok, warn, fail, info,
8
- select, confirm, input, spinner,
9
- RESET, DIM, BOLD, CYAN, AMBER, GREEN, RED, YELLOW, WHITE, GRAY,
10
- onExit, showCursor,
13
+ BOLD,
14
+ box,
15
+ CYAN,
16
+ clear,
17
+ confirm,
18
+ DIM,
19
+ divider,
20
+ fail,
21
+ GREEN,
22
+ info,
23
+ input,
24
+ label,
25
+ ok,
26
+ onExit,
27
+ RED,
28
+ RESET,
29
+ select,
30
+ showCursor,
31
+ table,
32
+ WHITE,
33
+ warn,
34
+ YELLOW,
11
35
  } from "./core.mjs";
12
36
 
13
37
  const CODEX_DIR = join(homedir(), ".codex");
14
38
  const CONFIG_PATH = join(CODEX_DIR, "config.toml");
15
39
 
16
40
  const KNOWN_MODELS = [
17
- { label: "gpt-5.4", hint: "최신 플래그십" },
18
- { label: "gpt-5.3-codex", hint: "코딩 특화" },
41
+ { label: "gpt-5.4", hint: "최신 플래그십" },
42
+ { label: "gpt-5.3-codex", hint: "코딩 특화" },
19
43
  { label: "gpt-5.1-codex-mini", hint: "경량 Spark" },
20
- { label: "o3", hint: "추론 특화" },
21
- { label: "o4-mini", hint: "추론 경량" },
22
- { label: "직접 입력", hint: "" },
44
+ { label: "o3", hint: "추론 특화" },
45
+ { label: "o4-mini", hint: "추론 경량" },
46
+ { label: "직접 입력", hint: "" },
23
47
  ];
24
48
 
25
49
  const EFFORT_LEVELS = [
26
- { label: "low", hint: "빠른 응답, 최소 추론" },
50
+ { label: "low", hint: "빠른 응답, 최소 추론" },
27
51
  { label: "medium", hint: "균형 잡힌 추론" },
28
- { label: "high", hint: "깊은 추론" },
29
- { label: "xhigh", hint: "최대 추론 (느림)" },
52
+ { label: "high", hint: "깊은 추론" },
53
+ { label: "xhigh", hint: "최대 추론 (느림)" },
30
54
  ];
31
55
 
32
56
  // ── TOML Parsing ──
@@ -175,7 +199,10 @@ function showStatus(config) {
175
199
 
176
200
  console.log();
177
201
  label("기본 모델", `${WHITE}${defaults.model || "미설정"}${RESET}`);
178
- label("기본 Effort", `${WHITE}${defaults.model_reasoning_effort || "미설정"}${RESET}`);
202
+ label(
203
+ "기본 Effort",
204
+ `${WHITE}${defaults.model_reasoning_effort || "미설정"}${RESET}`,
205
+ );
179
206
  console.log();
180
207
 
181
208
  if (profiles.length === 0) {
@@ -208,7 +235,9 @@ function effortColor(effort) {
208
235
 
209
236
  async function pickModel(current) {
210
237
  const idx = KNOWN_MODELS.findIndex((m) => m.label === current);
211
- const choice = await select("모델 선택", KNOWN_MODELS, { initial: Math.max(0, idx) });
238
+ const choice = await select("모델 선택", KNOWN_MODELS, {
239
+ initial: Math.max(0, idx),
240
+ });
212
241
  if (!choice) return null;
213
242
  if (choice.value.label === "직접 입력") {
214
243
  return await input("모델 ID", current || "");
@@ -218,7 +247,9 @@ async function pickModel(current) {
218
247
 
219
248
  async function pickEffort(current) {
220
249
  const idx = EFFORT_LEVELS.findIndex((e) => e.label === current);
221
- const choice = await select("Reasoning Effort 선택", EFFORT_LEVELS, { initial: Math.max(0, idx) });
250
+ const choice = await select("Reasoning Effort 선택", EFFORT_LEVELS, {
251
+ initial: Math.max(0, idx),
252
+ });
222
253
  if (!choice) return null;
223
254
  return choice.value.label;
224
255
  }
@@ -240,7 +271,9 @@ async function editProfile(config) {
240
271
 
241
272
  const profile = profiles[picked.index];
242
273
  console.log();
243
- info(`현재: ${BOLD}${profile.name}${RESET} → ${profile.model} / ${profile.model_reasoning_effort}`);
274
+ info(
275
+ `현재: ${BOLD}${profile.name}${RESET} → ${profile.model} / ${profile.model_reasoning_effort}`,
276
+ );
244
277
 
245
278
  const newModel = await pickModel(profile.model);
246
279
  if (newModel === null) return config;
@@ -249,7 +282,9 @@ async function editProfile(config) {
249
282
  if (newEffort === null) return config;
250
283
 
251
284
  console.log();
252
- info(`변경: ${profile.model} → ${BOLD}${newModel}${RESET}, ${profile.model_reasoning_effort} → ${BOLD}${newEffort}${RESET}`);
285
+ info(
286
+ `변경: ${profile.model} → ${BOLD}${newModel}${RESET}, ${profile.model_reasoning_effort} → ${BOLD}${newEffort}${RESET}`,
287
+ );
253
288
 
254
289
  if (!(await confirm("저장하시겠습니까?"))) return config;
255
290
 
@@ -259,7 +294,7 @@ async function editProfile(config) {
259
294
  if (!["name", "model", "model_reasoning_effort"].includes(k)) props[k] = v;
260
295
  }
261
296
 
262
- let raw = writeProfile(config.raw, profile.name, props);
297
+ const raw = writeProfile(config.raw, profile.name, props);
263
298
  save(raw);
264
299
  ok(`${profile.name} 프로파일 저장 완료`);
265
300
  return readConfig();
@@ -268,7 +303,9 @@ async function editProfile(config) {
268
303
  async function editDefault(config) {
269
304
  const { defaults } = config;
270
305
  info(`현재 기본 모델: ${BOLD}${defaults.model || "미설정"}${RESET}`);
271
- info(`현재 기본 Effort: ${BOLD}${defaults.model_reasoning_effort || "미설정"}${RESET}`);
306
+ info(
307
+ `현재 기본 Effort: ${BOLD}${defaults.model_reasoning_effort || "미설정"}${RESET}`,
308
+ );
272
309
 
273
310
  const newModel = await pickModel(defaults.model);
274
311
  if (newModel === null) return config;
@@ -277,7 +314,9 @@ async function editDefault(config) {
277
314
  if (newEffort === null) return config;
278
315
 
279
316
  console.log();
280
- info(`변경: ${defaults.model} → ${BOLD}${newModel}${RESET}, ${defaults.model_reasoning_effort} → ${BOLD}${newEffort}${RESET}`);
317
+ info(
318
+ `변경: ${defaults.model} → ${BOLD}${newModel}${RESET}, ${defaults.model_reasoning_effort} → ${BOLD}${newEffort}${RESET}`,
319
+ );
281
320
 
282
321
  if (!(await confirm("저장하시겠습니까?"))) return config;
283
322
 
@@ -307,7 +346,10 @@ async function addProfile(config) {
307
346
  info(`추가: ${BOLD}${name}${RESET} → ${model} / ${effort}`);
308
347
  if (!(await confirm("저장하시겠습니까?"))) return config;
309
348
 
310
- const raw = writeProfile(config.raw, name, { model, model_reasoning_effort: effort });
349
+ const raw = writeProfile(config.raw, name, {
350
+ model,
351
+ model_reasoning_effort: effort,
352
+ });
311
353
  save(raw);
312
354
  ok(`${name} 프로파일 추가 완료`);
313
355
  return readConfig();
@@ -325,7 +367,12 @@ async function removeProfile(config) {
325
367
  if (!picked) return config;
326
368
 
327
369
  const name = profiles[picked.index].name;
328
- if (!(await confirm(`${RED}${name}${RESET} 프로파일을 삭제하시겠습니까?`, false))) {
370
+ if (
371
+ !(await confirm(
372
+ `${RED}${name}${RESET} 프로파일을 삭제하시겠습니까?`,
373
+ false,
374
+ ))
375
+ ) {
329
376
  return config;
330
377
  }
331
378
 
@@ -350,11 +397,11 @@ function save(content) {
350
397
  // ── Main Loop ──
351
398
 
352
399
  const MENU = [
353
- { label: "프로파일 모델 변경", hint: "모델/effort 수정" },
354
- { label: "기본 모델 변경", hint: "top-level default" },
355
- { label: "프로파일 추가", hint: "새 프로파일 생성" },
356
- { label: "프로파일 삭제", hint: "기존 프로파일 제거" },
357
- { label: "종료", hint: "Ctrl+C" },
400
+ { label: "프로파일 모델 변경", hint: "모델/effort 수정" },
401
+ { label: "기본 모델 변경", hint: "top-level default" },
402
+ { label: "프로파일 추가", hint: "새 프로파일 생성" },
403
+ { label: "프로파일 삭제", hint: "기존 프로파일 제거" },
404
+ { label: "종료", hint: "Ctrl+C" },
358
405
  ];
359
406
 
360
407
  async function main() {
@@ -384,10 +431,18 @@ async function main() {
384
431
 
385
432
  console.log();
386
433
  switch (choice.index) {
387
- case 0: config = await editProfile(config); break;
388
- case 1: config = await editDefault(config); break;
389
- case 2: config = await addProfile(config); break;
390
- case 3: config = await removeProfile(config); break;
434
+ case 0:
435
+ config = await editProfile(config);
436
+ break;
437
+ case 1:
438
+ config = await editDefault(config);
439
+ break;
440
+ case 2:
441
+ config = await addProfile(config);
442
+ break;
443
+ case 3:
444
+ config = await removeProfile(config);
445
+ break;
391
446
  }
392
447
 
393
448
  console.log();
package/tui/core.mjs CHANGED
@@ -45,7 +45,9 @@ export function box(title, width = 50) {
45
45
  const left = Math.floor((inner - padded.length) / 2);
46
46
  const right = inner - left - padded.length;
47
47
  console.log(` ${DIM}┌${"─".repeat(inner)}┐${RESET}`);
48
- console.log(` ${DIM}│${RESET}${" ".repeat(left)}${BOLD}${AMBER}${padded}${RESET}${" ".repeat(right)}${DIM}│${RESET}`);
48
+ console.log(
49
+ ` ${DIM}│${RESET}${" ".repeat(left)}${BOLD}${AMBER}${padded}${RESET}${" ".repeat(right)}${DIM}│${RESET}`,
50
+ );
49
51
  console.log(` ${DIM}└${"─".repeat(inner)}┘${RESET}`);
50
52
  }
51
53
 
@@ -58,8 +60,8 @@ export function table(headers, rows, { indent = 2 } = {}) {
58
60
  const widths = headers.map((h, i) =>
59
61
  Math.max(
60
62
  stripAnsi(h).length,
61
- ...rows.map((r) => stripAnsi(String(r[i] ?? "")).length)
62
- )
63
+ ...rows.map((r) => stripAnsi(String(r[i] ?? "")).length),
64
+ ),
63
65
  );
64
66
 
65
67
  const top = widths.map((w) => "─".repeat(w + 2)).join("┬");
@@ -84,10 +86,18 @@ export function table(headers, rows, { indent = 2 } = {}) {
84
86
  console.log(`${pad}└${bot}┘`);
85
87
  }
86
88
 
87
- export function ok(msg) { console.log(` ${GREEN}✓${RESET} ${msg}`); }
88
- export function warn(msg) { console.log(` ${YELLOW}⚠${RESET} ${msg}`); }
89
- export function fail(msg) { console.log(` ${RED}✗${RESET} ${msg}`); }
90
- export function info(msg) { console.log(` ${CYAN}ℹ${RESET} ${msg}`); }
89
+ export function ok(msg) {
90
+ console.log(` ${GREEN}✓${RESET} ${msg}`);
91
+ }
92
+ export function warn(msg) {
93
+ console.log(` ${YELLOW}⚠${RESET} ${msg}`);
94
+ }
95
+ export function fail(msg) {
96
+ console.log(` ${RED}✗${RESET} ${msg}`);
97
+ }
98
+ export function info(msg) {
99
+ console.log(` ${CYAN}ℹ${RESET} ${msg}`);
100
+ }
91
101
 
92
102
  export function label(key, value) {
93
103
  console.log(` ${DIM}${key}:${RESET} ${BOLD}${value}${RESET}`);
@@ -102,9 +112,14 @@ export async function select(title, options, { initial = 0 } = {}) {
102
112
  const o = typeof options[i] === "string" ? options[i] : options[i].label;
103
113
  console.log(` ${DIM}${i + 1}.${RESET} ${o}`);
104
114
  }
105
- const answer = await input(`선택 (1-${options.length})`, String(initial + 1));
115
+ const answer = await input(
116
+ `선택 (1-${options.length})`,
117
+ String(initial + 1),
118
+ );
106
119
  const idx = parseInt(answer, 10) - 1;
107
- return idx >= 0 && idx < options.length ? { index: idx, value: options[idx] } : null;
120
+ return idx >= 0 && idx < options.length
121
+ ? { index: idx, value: options[idx] }
122
+ : null;
108
123
  }
109
124
 
110
125
  readline.emitKeypressEvents(process.stdin);
@@ -116,7 +131,8 @@ export async function select(title, options, { initial = 0 } = {}) {
116
131
  const total = options.length;
117
132
 
118
133
  const getLabel = (o) => (typeof o === "string" ? o : o.label);
119
- const getHint = (o) => (typeof o === "object" && o.hint ? ` ${DIM}${o.hint}${RESET}` : "");
134
+ const getHint = (o) =>
135
+ typeof o === "object" && o.hint ? ` ${DIM}${o.hint}${RESET}` : "";
120
136
 
121
137
  const render = (first = false) => {
122
138
  if (!first) moveUp(total);
@@ -167,8 +183,13 @@ export async function select(title, options, { initial = 0 } = {}) {
167
183
  // ── Input: Confirm ──
168
184
 
169
185
  export async function confirm(message, defaultYes = true) {
170
- const hint = defaultYes ? `${BOLD}Y${RESET}${DIM}/n${RESET}` : `${DIM}y/${RESET}${BOLD}N${RESET}`;
171
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
186
+ const hint = defaultYes
187
+ ? `${BOLD}Y${RESET}${DIM}/n${RESET}`
188
+ : `${DIM}y/${RESET}${BOLD}N${RESET}`;
189
+ const rl = readline.createInterface({
190
+ input: process.stdin,
191
+ output: process.stdout,
192
+ });
172
193
 
173
194
  return new Promise((resolve) => {
174
195
  rl.question(` ${CYAN}?${RESET} ${message} [${hint}] `, (answer) => {
@@ -184,7 +205,10 @@ export async function confirm(message, defaultYes = true) {
184
205
 
185
206
  export async function input(message, defaultValue = "") {
186
207
  const hint = defaultValue ? ` ${DIM}(${defaultValue})${RESET}` : "";
187
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
208
+ const rl = readline.createInterface({
209
+ input: process.stdin,
210
+ output: process.stdout,
211
+ });
188
212
 
189
213
  return new Promise((resolve) => {
190
214
  rl.question(` ${CYAN}?${RESET} ${message}${hint}: `, (answer) => {
@@ -202,7 +226,9 @@ export function spinner(message) {
202
226
  hideCursor();
203
227
  const id = setInterval(() => {
204
228
  clearLine();
205
- process.stdout.write(` ${CYAN}${frames[i++ % frames.length]}${RESET} ${message}`);
229
+ process.stdout.write(
230
+ ` ${CYAN}${frames[i++ % frames.length]}${RESET} ${message}`,
231
+ );
206
232
  }, 80);
207
233
 
208
234
  return {
@@ -230,7 +256,11 @@ export function sleep(ms) {
230
256
 
231
257
  // graceful exit
232
258
  export function onExit(fn) {
233
- const handler = () => { showCursor(); fn?.(); process.exit(0); };
259
+ const handler = () => {
260
+ showCursor();
261
+ fn?.();
262
+ process.exit(0);
263
+ };
234
264
  process.on("SIGINT", handler);
235
265
  process.on("SIGTERM", handler);
236
266
  }