triflux 10.0.0 → 10.0.2

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 (426) hide show
  1. package/CLAUDE.md +171 -0
  2. package/README.md +32 -15
  3. package/bin/triflux.mjs +62 -5
  4. package/hooks/agent-route-guard.mjs +109 -0
  5. package/hooks/cross-review-tracker.mjs +122 -0
  6. package/hooks/error-context.mjs +148 -0
  7. package/hooks/hook-adaptive-collector.mjs +86 -0
  8. package/hooks/hook-manager.mjs +365 -0
  9. package/hooks/hook-orchestrator.mjs +312 -0
  10. package/hooks/hook-registry.json +246 -0
  11. package/hooks/hooks.json +89 -0
  12. package/hooks/keyword-rules.json +574 -0
  13. package/hooks/lib/resolve-root.mjs +59 -0
  14. package/hooks/mcp-config-watcher.mjs +80 -0
  15. package/hooks/pipeline-stop.mjs +76 -0
  16. package/hooks/safety-guard.mjs +169 -0
  17. package/hooks/subagent-verifier.mjs +80 -0
  18. package/hub/account-broker.mjs +251 -0
  19. package/hub/adaptive-diagnostic.mjs +323 -0
  20. package/hub/adaptive-inject.mjs +186 -0
  21. package/hub/adaptive-memory.mjs +163 -0
  22. package/hub/adaptive.mjs +143 -0
  23. package/hub/assign-callbacks.mjs +133 -0
  24. package/hub/bridge.mjs +799 -0
  25. package/hub/cli-adapter-base.mjs +280 -0
  26. package/hub/codex-adapter.mjs +199 -0
  27. package/hub/codex-compat.mjs +11 -0
  28. package/hub/codex-preflight.mjs +166 -0
  29. package/hub/delegator/contracts.mjs +37 -0
  30. package/hub/delegator/index.mjs +14 -0
  31. package/hub/delegator/schema/delegator-tools.schema.json +250 -0
  32. package/hub/delegator/service.mjs +307 -0
  33. package/hub/delegator/tool-definitions.mjs +35 -0
  34. package/hub/fullcycle.mjs +96 -0
  35. package/hub/gemini-adapter.mjs +180 -0
  36. package/hub/hitl.mjs +143 -0
  37. package/hub/intent.mjs +193 -0
  38. package/hub/lib/cache-guard.mjs +114 -0
  39. package/hub/lib/known-errors.json +72 -0
  40. package/hub/lib/memory-store.mjs +748 -0
  41. package/hub/lib/process-utils.mjs +361 -0
  42. package/hub/lib/ssh-command.mjs +211 -0
  43. package/hub/lib/ssh-retry.mjs +59 -0
  44. package/hub/lib/uuidv7.mjs +44 -0
  45. package/hub/memory-doctor.mjs +480 -0
  46. package/hub/middleware/request-logger.mjs +161 -0
  47. package/hub/paths.mjs +30 -0
  48. package/hub/pipe.mjs +664 -0
  49. package/hub/pipeline/gates/confidence.mjs +56 -0
  50. package/hub/pipeline/gates/consensus.mjs +94 -0
  51. package/hub/pipeline/gates/index.mjs +5 -0
  52. package/hub/pipeline/gates/selfcheck.mjs +82 -0
  53. package/hub/pipeline/index.mjs +318 -0
  54. package/hub/pipeline/state.mjs +191 -0
  55. package/hub/pipeline/transitions.mjs +124 -0
  56. package/hub/platform.mjs +225 -0
  57. package/hub/public/dashboard.html +355 -0
  58. package/hub/public/tray-icon.ico +0 -0
  59. package/hub/public/tray-icon.png +0 -0
  60. package/hub/quality/deslop.mjs +253 -0
  61. package/hub/reflexion.mjs +372 -0
  62. package/hub/research.mjs +146 -0
  63. package/hub/router.mjs +791 -0
  64. package/hub/routing/complexity.mjs +166 -0
  65. package/hub/routing/index.mjs +117 -0
  66. package/hub/routing/q-learning.mjs +336 -0
  67. package/hub/schema.sql +148 -0
  68. package/hub/server.mjs +1264 -0
  69. package/hub/session-fingerprint.mjs +352 -0
  70. package/hub/state.mjs +258 -0
  71. package/hub/store-adapter.mjs +118 -0
  72. package/hub/store.mjs +857 -0
  73. package/hub/team/agent-map.json +11 -0
  74. package/hub/team/ansi.mjs +379 -0
  75. package/hub/team/backend.mjs +90 -0
  76. package/hub/team/cli/commands/attach.mjs +37 -0
  77. package/hub/team/cli/commands/control.mjs +43 -0
  78. package/hub/team/cli/commands/debug.mjs +74 -0
  79. package/hub/team/cli/commands/focus.mjs +53 -0
  80. package/hub/team/cli/commands/interrupt.mjs +36 -0
  81. package/hub/team/cli/commands/kill.mjs +37 -0
  82. package/hub/team/cli/commands/list.mjs +24 -0
  83. package/hub/team/cli/commands/send.mjs +37 -0
  84. package/hub/team/cli/commands/start/index.mjs +106 -0
  85. package/hub/team/cli/commands/start/parse-args.mjs +130 -0
  86. package/hub/team/cli/commands/start/start-headless.mjs +109 -0
  87. package/hub/team/cli/commands/start/start-in-process.mjs +40 -0
  88. package/hub/team/cli/commands/start/start-mux.mjs +73 -0
  89. package/hub/team/cli/commands/start/start-wt.mjs +69 -0
  90. package/hub/team/cli/commands/status.mjs +87 -0
  91. package/hub/team/cli/commands/stop.mjs +31 -0
  92. package/hub/team/cli/commands/task.mjs +30 -0
  93. package/hub/team/cli/commands/tasks.mjs +13 -0
  94. package/hub/team/cli/help.mjs +42 -0
  95. package/hub/team/cli/index.mjs +41 -0
  96. package/hub/team/cli/manifest.mjs +29 -0
  97. package/hub/team/cli/render.mjs +30 -0
  98. package/hub/team/cli/services/attach-fallback.mjs +54 -0
  99. package/hub/team/cli/services/hub-client.mjs +227 -0
  100. package/hub/team/cli/services/member-selector.mjs +30 -0
  101. package/hub/team/cli/services/native-control.mjs +117 -0
  102. package/hub/team/cli/services/runtime-mode.mjs +62 -0
  103. package/hub/team/cli/services/state-store.mjs +48 -0
  104. package/hub/team/cli/services/task-model.mjs +30 -0
  105. package/hub/team/conductor-mesh-bridge.mjs +121 -0
  106. package/hub/team/conductor.mjs +671 -0
  107. package/hub/team/dashboard-anchor.mjs +14 -0
  108. package/hub/team/dashboard-layout.mjs +33 -0
  109. package/hub/team/dashboard-open.mjs +153 -0
  110. package/hub/team/dashboard.mjs +274 -0
  111. package/hub/team/event-log.mjs +76 -0
  112. package/hub/team/handoff.mjs +303 -0
  113. package/hub/team/headless.mjs +1156 -0
  114. package/hub/team/health-probe.mjs +272 -0
  115. package/hub/team/launcher-template.mjs +95 -0
  116. package/hub/team/lead-control.mjs +104 -0
  117. package/hub/team/native-supervisor.mjs +392 -0
  118. package/hub/team/native.mjs +649 -0
  119. package/hub/team/nativeProxy.mjs +688 -0
  120. package/hub/team/notify.mjs +293 -0
  121. package/hub/team/orchestrator.mjs +161 -0
  122. package/hub/team/pane.mjs +153 -0
  123. package/hub/team/process-cleanup.mjs +342 -0
  124. package/hub/team/psmux.mjs +1354 -0
  125. package/hub/team/remote-probe.mjs +276 -0
  126. package/hub/team/remote-session.mjs +299 -0
  127. package/hub/team/remote-watcher.mjs +478 -0
  128. package/hub/team/routing.mjs +223 -0
  129. package/hub/team/session-sync.mjs +169 -0
  130. package/hub/team/session.mjs +611 -0
  131. package/hub/team/shared.mjs +13 -0
  132. package/hub/team/staleState.mjs +361 -0
  133. package/hub/team/swarm-hypervisor.mjs +589 -0
  134. package/hub/team/swarm-locks.mjs +204 -0
  135. package/hub/team/swarm-planner.mjs +260 -0
  136. package/hub/team/swarm-reconciler.mjs +137 -0
  137. package/hub/team/tui-lite.mjs +380 -0
  138. package/hub/team/tui-remote-adapter.mjs +393 -0
  139. package/hub/team/tui-viewer.mjs +463 -0
  140. package/hub/team/tui.mjs +1449 -0
  141. package/hub/team/worktree-lifecycle.mjs +193 -0
  142. package/hub/team/wt-manager.mjs +407 -0
  143. package/hub/team/wt-templates.json +43 -0
  144. package/hub/team-bridge.mjs +27 -0
  145. package/hub/token-mode.mjs +224 -0
  146. package/hub/tools.mjs +636 -0
  147. package/hub/tray.mjs +376 -0
  148. package/hub/workers/claude-worker.mjs +475 -0
  149. package/hub/workers/codex-mcp.mjs +507 -0
  150. package/hub/workers/delegator-mcp.mjs +1076 -0
  151. package/hub/workers/factory.mjs +21 -0
  152. package/hub/workers/gemini-worker.mjs +374 -0
  153. package/hub/workers/interface.mjs +52 -0
  154. package/hub/workers/worker-utils.mjs +104 -0
  155. package/hud/colors.mjs +88 -0
  156. package/hud/constants.mjs +88 -0
  157. package/hud/context-monitor.mjs +403 -0
  158. package/hud/hud-qos-status.mjs +210 -0
  159. package/hud/providers/claude.mjs +314 -0
  160. package/hud/providers/codex.mjs +151 -0
  161. package/hud/providers/gemini.mjs +320 -0
  162. package/hud/renderers.mjs +442 -0
  163. package/hud/terminal.mjs +140 -0
  164. package/hud/utils.mjs +313 -0
  165. package/mesh/index.mjs +63 -0
  166. package/mesh/mesh-budget.mjs +128 -0
  167. package/mesh/mesh-heartbeat.mjs +100 -0
  168. package/mesh/mesh-protocol.mjs +96 -0
  169. package/mesh/mesh-queue.mjs +165 -0
  170. package/mesh/mesh-registry.mjs +78 -0
  171. package/mesh/mesh-router.mjs +76 -0
  172. package/package.json +8 -1
  173. package/references/hosts.json +33 -0
  174. package/scripts/__tests__/gen-skill-docs.test.mjs +87 -0
  175. package/scripts/__tests__/keyword-detector.test.mjs +234 -0
  176. package/scripts/__tests__/mcp-guard-engine.test.mjs +118 -0
  177. package/scripts/__tests__/remote-spawn-transfer.test.mjs +117 -0
  178. package/scripts/__tests__/remote-spawn.test.mjs +92 -0
  179. package/scripts/__tests__/skill-template.test.mjs +193 -0
  180. package/scripts/__tests__/smoke.test.mjs +34 -0
  181. package/scripts/cache-buildup.mjs +30 -0
  182. package/scripts/cache-doctor.mjs +149 -0
  183. package/scripts/cache-warmup.mjs +557 -0
  184. package/scripts/claudemd-sync.mjs +148 -0
  185. package/scripts/cli-route.sh +3 -0
  186. package/scripts/completions/tfx.bash +47 -0
  187. package/scripts/completions/tfx.fish +44 -0
  188. package/scripts/completions/tfx.zsh +83 -0
  189. package/scripts/cross-review-gate.mjs +126 -0
  190. package/scripts/cross-review-tracker.mjs +238 -0
  191. package/scripts/gen-skill-docs.mjs +111 -0
  192. package/scripts/headless-guard-fast.sh +21 -0
  193. package/scripts/headless-guard.mjs +360 -0
  194. package/scripts/hub-ensure.mjs +120 -0
  195. package/scripts/keyword-detector.mjs +272 -0
  196. package/scripts/keyword-rules-expander.mjs +521 -0
  197. package/scripts/lib/claudemd-scanner.mjs +218 -0
  198. package/scripts/lib/context.mjs +67 -0
  199. package/scripts/lib/cross-review-utils.mjs +51 -0
  200. package/scripts/lib/env-probe.mjs +241 -0
  201. package/scripts/lib/gemini-profiles.mjs +85 -0
  202. package/scripts/lib/handoff.mjs +171 -0
  203. package/scripts/lib/hook-utils.mjs +14 -0
  204. package/scripts/lib/keyword-rules.mjs +166 -0
  205. package/scripts/lib/logger.mjs +105 -0
  206. package/scripts/lib/mcp-filter.mjs +739 -0
  207. package/scripts/lib/mcp-guard-engine.mjs +954 -0
  208. package/scripts/lib/mcp-manifest.mjs +79 -0
  209. package/scripts/lib/mcp-server-catalog.mjs +118 -0
  210. package/scripts/lib/psmux-info.mjs +119 -0
  211. package/scripts/lib/remote-spawn-transfer.mjs +196 -0
  212. package/scripts/lib/skill-template.mjs +326 -0
  213. package/scripts/mcp-check.mjs +237 -0
  214. package/scripts/mcp-cleanup.ps1 +17 -0
  215. package/scripts/mcp-gateway-config.mjs +207 -0
  216. package/scripts/mcp-gateway-ensure.mjs +85 -0
  217. package/scripts/mcp-gateway-integration-test.mjs +228 -0
  218. package/scripts/mcp-gateway-start.mjs +226 -0
  219. package/scripts/mcp-gateway-start.ps1 +141 -0
  220. package/scripts/mcp-gateway-verify.mjs +77 -0
  221. package/scripts/mcp-safety-guard.mjs +44 -0
  222. package/scripts/notion-read.mjs +556 -0
  223. package/scripts/pack.mjs +295 -0
  224. package/scripts/preflight-cache.mjs +69 -0
  225. package/scripts/preinstall.mjs +96 -0
  226. package/scripts/remote-spawn.mjs +1376 -0
  227. package/scripts/run.cjs +79 -0
  228. package/scripts/session-spawn-helper.mjs +185 -0
  229. package/scripts/setup.mjs +1178 -0
  230. package/scripts/test-lock.mjs +71 -0
  231. package/scripts/test-tfx-route-no-claude-native.mjs +57 -0
  232. package/scripts/tfx-batch-stats.mjs +96 -0
  233. package/scripts/tfx-gate-activate.mjs +89 -0
  234. package/scripts/tfx-route-post.mjs +505 -0
  235. package/scripts/tfx-route-worker.mjs +223 -0
  236. package/scripts/tfx-route.sh +2014 -0
  237. package/scripts/tmp-cleanup.mjs +103 -0
  238. package/scripts/token-snapshot.mjs +575 -0
  239. package/skills/tfx-auto/SKILL.md.tmpl +2 -3
  240. package/skills/tfx-autoresearch/SKILL.md +6 -5
  241. package/skills/tfx-codex/SKILL.md.tmpl +2 -3
  242. package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +33 -0
  243. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/eval_metadata.json +42 -0
  244. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +11 -0
  245. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/analysis.md +87 -0
  246. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/classification.md +35 -0
  247. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/commands.sh +275 -0
  248. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/routing.md +56 -0
  249. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/timing.json +5 -0
  250. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +11 -0
  251. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/analysis.md +92 -0
  252. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/classification.md +71 -0
  253. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/commands.sh +264 -0
  254. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/routing.md +113 -0
  255. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/timing.json +5 -0
  256. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/eval_metadata.json +32 -0
  257. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +9 -0
  258. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/analysis.md +96 -0
  259. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/classification.md +38 -0
  260. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/commands.sh +151 -0
  261. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/routing.md +51 -0
  262. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/timing.json +5 -0
  263. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +9 -0
  264. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/analysis.md +127 -0
  265. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/classification.md +57 -0
  266. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/commands.sh +129 -0
  267. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/routing.md +84 -0
  268. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/timing.json +5 -0
  269. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/eval_metadata.json +27 -0
  270. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +8 -0
  271. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/analysis.md +98 -0
  272. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/classification.md +65 -0
  273. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/commands.sh +123 -0
  274. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/routing.md +66 -0
  275. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/timing.json +5 -0
  276. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +8 -0
  277. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/analysis.md +88 -0
  278. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/classification.md +40 -0
  279. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/commands.sh +130 -0
  280. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/routing.md +61 -0
  281. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/timing.json +5 -0
  282. package/skills/tfx-deep-interview/SKILL.md +1 -2
  283. package/skills/tfx-plan/SKILL.md.tmpl +2 -3
  284. package/skills/tfx-psmux-rules/SKILL.md +11 -2
  285. package/skills/tfx-qa/SKILL.md.tmpl +2 -3
  286. package/skills/tfx-remote-spawn/SKILL.md +8 -11
  287. package/skills/tfx-research/SKILL.md.tmpl +2 -3
  288. package/skills/tfx-review/SKILL.md.tmpl +2 -3
  289. package/skills/tfx-workspace/async-tests/run-tests.sh +203 -0
  290. package/skills/tfx-workspace/evals/evals.json +79 -0
  291. package/skills/tfx-workspace/iteration-1/benchmark.json +162 -0
  292. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/eval_metadata.json +11 -0
  293. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/grading.json +9 -0
  294. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/outputs/analysis.md +154 -0
  295. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/timing.json +5 -0
  296. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/grading.json +9 -0
  297. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/outputs/analysis.md +126 -0
  298. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/timing.json +5 -0
  299. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/eval_metadata.json +11 -0
  300. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/grading.json +9 -0
  301. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/outputs/analysis.md +119 -0
  302. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/timing.json +5 -0
  303. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/grading.json +9 -0
  304. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/outputs/analysis.md +115 -0
  305. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/timing.json +5 -0
  306. package/skills/tfx-workspace/iteration-1/hub-start-sequence/eval_metadata.json +10 -0
  307. package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/grading.json +8 -0
  308. package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/outputs/analysis.md +86 -0
  309. package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/timing.json +5 -0
  310. package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/grading.json +8 -0
  311. package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/outputs/analysis.md +81 -0
  312. package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/timing.json +5 -0
  313. package/skills/tfx-workspace/iteration-1/multi-team-creation/eval_metadata.json +12 -0
  314. package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/grading.json +10 -0
  315. package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/outputs/analysis.md +316 -0
  316. package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/timing.json +5 -0
  317. package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/grading.json +10 -0
  318. package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/outputs/analysis.md +352 -0
  319. package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/timing.json +5 -0
  320. package/skills/tfx-workspace/iteration-1/review.html +1325 -0
  321. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/eval_metadata.json +12 -0
  322. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/grading.json +10 -0
  323. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/outputs/analysis.md +97 -0
  324. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/timing.json +5 -0
  325. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/grading.json +10 -0
  326. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/outputs/analysis.md +94 -0
  327. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/timing.json +5 -0
  328. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/eval_metadata.json +12 -0
  329. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/grading.json +10 -0
  330. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/outputs/analysis.md +209 -0
  331. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/timing.json +5 -0
  332. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/grading.json +10 -0
  333. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/outputs/analysis.md +193 -0
  334. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/timing.json +5 -0
  335. package/skills/tfx-workspace/iteration-2/benchmark.json +62 -0
  336. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/eval_metadata.json +13 -0
  337. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/grading.json +11 -0
  338. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/outputs/analysis.md +382 -0
  339. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/timing.json +5 -0
  340. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/grading.json +11 -0
  341. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/outputs/analysis.md +333 -0
  342. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/timing.json +5 -0
  343. package/skills/tfx-workspace/iteration-2/review.html +1325 -0
  344. package/skills/tfx-workspace/skill-snapshot/tfx-auto/SKILL.md +217 -0
  345. package/skills/{tfx-auto-codex/SKILL.md.tmpl → tfx-workspace/skill-snapshot/tfx-auto-codex/SKILL.md} +3 -31
  346. package/skills/tfx-workspace/skill-snapshot/tfx-codex/SKILL.md +65 -0
  347. package/skills/tfx-workspace/skill-snapshot/tfx-doctor/SKILL.md +94 -0
  348. package/skills/{tfx-gemini/SKILL.md.tmpl → tfx-workspace/skill-snapshot/tfx-gemini/SKILL.md} +6 -14
  349. package/skills/tfx-workspace/skill-snapshot/tfx-hub/SKILL.md +133 -0
  350. package/skills/tfx-workspace/skill-snapshot/tfx-multi/SKILL.md +426 -0
  351. package/skills/tfx-workspace/skill-snapshot/tfx-setup/SKILL.md +101 -0
  352. package/skills/merge-worktree/SKILL.md.tmpl +0 -144
  353. package/skills/shared/arguments-processing.md +0 -2
  354. package/skills/shared/mandatory-rules.md +0 -6
  355. package/skills/shared/telemetry-segment.md +0 -6
  356. package/skills/star-prompt/SKILL.md.tmpl +0 -122
  357. package/skills/tfx-analysis/SKILL.md.tmpl +0 -106
  358. package/skills/tfx-analysis/skill.json +0 -11
  359. package/skills/tfx-auto/skill.json +0 -26
  360. package/skills/tfx-auto-codex/skill.json +0 -8
  361. package/skills/tfx-autopilot/SKILL.md.tmpl +0 -115
  362. package/skills/tfx-autopilot/skill.json +0 -10
  363. package/skills/tfx-autoresearch/SKILL.md.tmpl +0 -135
  364. package/skills/tfx-autoresearch/skill.json +0 -14
  365. package/skills/tfx-autoroute/SKILL.md.tmpl +0 -188
  366. package/skills/tfx-autoroute/skill.json +0 -12
  367. package/skills/tfx-codex/skill.json +0 -8
  368. package/skills/tfx-codex-swarm/SKILL.md.tmpl +0 -16
  369. package/skills/tfx-codex-swarm/skill.json +0 -5
  370. package/skills/tfx-consensus/SKILL.md.tmpl +0 -145
  371. package/skills/tfx-consensus/skill.json +0 -8
  372. package/skills/tfx-debate/SKILL.md.tmpl +0 -191
  373. package/skills/tfx-debate/skill.json +0 -12
  374. package/skills/tfx-deep-analysis/SKILL.md.tmpl +0 -227
  375. package/skills/tfx-deep-analysis/skill.json +0 -10
  376. package/skills/tfx-deep-interview/SKILL.md.tmpl +0 -203
  377. package/skills/tfx-deep-interview/skill.json +0 -12
  378. package/skills/tfx-deep-plan/SKILL.md.tmpl +0 -281
  379. package/skills/tfx-deep-plan/skill.json +0 -13
  380. package/skills/tfx-deep-qa/SKILL.md.tmpl +0 -164
  381. package/skills/tfx-deep-qa/skill.json +0 -11
  382. package/skills/tfx-deep-research/SKILL.md.tmpl +0 -216
  383. package/skills/tfx-deep-research/skill.json +0 -14
  384. package/skills/tfx-deep-review/SKILL.md.tmpl +0 -178
  385. package/skills/tfx-deep-review/skill.json +0 -12
  386. package/skills/tfx-doctor/SKILL.md.tmpl +0 -172
  387. package/skills/tfx-doctor/skill.json +0 -8
  388. package/skills/tfx-find/skill.json +0 -12
  389. package/skills/tfx-forge/SKILL.md.tmpl +0 -187
  390. package/skills/tfx-forge/skill.json +0 -12
  391. package/skills/tfx-fullcycle/SKILL.md.tmpl +0 -285
  392. package/skills/tfx-fullcycle/skill.json +0 -11
  393. package/skills/tfx-gemini/skill.json +0 -8
  394. package/skills/tfx-hooks/SKILL.md.tmpl +0 -216
  395. package/skills/tfx-hooks/skill.json +0 -8
  396. package/skills/tfx-hub/SKILL.md.tmpl +0 -212
  397. package/skills/tfx-hub/skill.json +0 -8
  398. package/skills/tfx-index/skill.json +0 -11
  399. package/skills/tfx-interview/SKILL.md.tmpl +0 -284
  400. package/skills/tfx-interview/skill.json +0 -12
  401. package/skills/tfx-multi/SKILL.md.tmpl +0 -183
  402. package/skills/tfx-multi/skill.json +0 -8
  403. package/skills/tfx-panel/SKILL.md.tmpl +0 -188
  404. package/skills/tfx-panel/skill.json +0 -12
  405. package/skills/tfx-persist/SKILL.md.tmpl +0 -269
  406. package/skills/tfx-persist/skill.json +0 -12
  407. package/skills/tfx-plan/skill.json +0 -11
  408. package/skills/tfx-profile/SKILL.md.tmpl +0 -239
  409. package/skills/tfx-profile/skill.json +0 -8
  410. package/skills/tfx-prune/SKILL.md.tmpl +0 -199
  411. package/skills/tfx-prune/skill.json +0 -12
  412. package/skills/tfx-psmux-rules/SKILL.md.tmpl +0 -317
  413. package/skills/tfx-psmux-rules/skill.json +0 -8
  414. package/skills/tfx-qa/skill.json +0 -11
  415. package/skills/tfx-ralph/SKILL.md.tmpl +0 -27
  416. package/skills/tfx-ralph/skill.json +0 -8
  417. package/skills/tfx-remote-setup/SKILL.md.tmpl +0 -576
  418. package/skills/tfx-remote-setup/skill.json +0 -8
  419. package/skills/tfx-remote-spawn/SKILL.md.tmpl +0 -263
  420. package/skills/tfx-remote-spawn/skill.json +0 -9
  421. package/skills/tfx-research/skill.json +0 -13
  422. package/skills/tfx-review/skill.json +0 -11
  423. package/skills/tfx-setup/SKILL.md.tmpl +0 -380
  424. package/skills/tfx-setup/skill.json +0 -8
  425. package/skills/tfx-swarm/SKILL.md.tmpl +0 -154
  426. package/skills/tfx-swarm/skill.json +0 -5
@@ -0,0 +1,1178 @@
1
+ #!/usr/bin/env node
2
+ // triflux 세션 시작 시 자동 설정 스크립트
3
+ // - tfx-route.sh를 ~/.claude/scripts/에 동기화
4
+ // - hud-qos-status.mjs를 ~/.claude/hud/에 동기화
5
+ // - skills/를 ~/.claude/skills/에 동기화
6
+
7
+ import { copyFileSync, mkdirSync, readFileSync, writeFileSync, readdirSync, existsSync, chmodSync, unlinkSync } from "fs";
8
+ import { join, dirname, relative } from "path";
9
+ import { homedir } from "os";
10
+ import { spawn, execFileSync } from "child_process";
11
+ import { fileURLToPath } from "url";
12
+ import { ensureGlobalClaudeRoutingSection, ensureTfxSection, getLatestRoutingTable } from "./claudemd-sync.mjs";
13
+ import { cleanupTmpFiles } from "./tmp-cleanup.mjs";
14
+
15
+ const PLUGIN_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
16
+ const CLAUDE_DIR = join(homedir(), ".claude");
17
+ const CODEX_DIR = join(homedir(), ".codex");
18
+ const CODEX_CONFIG_PATH = join(CODEX_DIR, "config.toml");
19
+ const SETUP_MARKER_PATH = join(CLAUDE_DIR, "cache", "tfx-setup-marker.json");
20
+
21
+ // ── 로컬 개발 모드 감지 ──
22
+
23
+ /**
24
+ * PLUGIN_ROOT에 .git 디렉토리가 존재하면 dev mode (git clone 직접 사용)로 판정.
25
+ * @param {string} [root] - 검사할 루트 경로 (기본: PLUGIN_ROOT)
26
+ * @returns {boolean}
27
+ */
28
+ function detectDevMode(root = PLUGIN_ROOT) {
29
+ return existsSync(join(root, ".git"));
30
+ }
31
+
32
+ const BREADCRUMB_PATH = join(CLAUDE_DIR, "scripts", ".tfx-pkg-root");
33
+
34
+ const REQUIRED_CODEX_PROFILES = [
35
+ {
36
+ name: "codex53_high",
37
+ lines: [
38
+ 'model = "gpt-5.3-codex"',
39
+ 'model_reasoning_effort = "high"',
40
+ ],
41
+ },
42
+ {
43
+ name: "codex53_xhigh",
44
+ lines: [
45
+ 'model = "gpt-5.3-codex"',
46
+ 'model_reasoning_effort = "xhigh"',
47
+ ],
48
+ },
49
+ {
50
+ name: "spark53_low",
51
+ lines: [
52
+ 'model = "gpt-5.3-codex-spark"',
53
+ 'model_reasoning_effort = "low"',
54
+ ],
55
+ },
56
+ ];
57
+
58
+ const HUD_SYNC_EXCLUDES = new Set(["omc-hud.mjs", "omc-hud.mjs.bak"]);
59
+
60
+ function scanHudFiles(pluginRoot, claudeDir) {
61
+ const hudRoot = join(pluginRoot, "hud");
62
+ if (!existsSync(hudRoot)) return [];
63
+
64
+ const walk = (currentDir) => {
65
+ const entries = readdirSync(currentDir, { withFileTypes: true })
66
+ .sort((left, right) => left.name.localeCompare(right.name));
67
+
68
+ return entries.flatMap((entry) => {
69
+ const absolutePath = join(currentDir, entry.name);
70
+ if (entry.isDirectory()) {
71
+ return walk(absolutePath);
72
+ }
73
+
74
+ if (!entry.isFile() || HUD_SYNC_EXCLUDES.has(entry.name) || !entry.name.endsWith(".mjs")) {
75
+ return [];
76
+ }
77
+
78
+ const hudRelativePath = relative(hudRoot, absolutePath);
79
+ const normalizedRelativePath = hudRelativePath.replace(/\\/g, "/");
80
+
81
+ return [{
82
+ src: absolutePath,
83
+ dst: join(claudeDir, "hud", hudRelativePath),
84
+ label: normalizedRelativePath === "hud-qos-status.mjs"
85
+ ? "hud-qos-status.mjs"
86
+ : `hud/${normalizedRelativePath}`,
87
+ }];
88
+ });
89
+ };
90
+
91
+ return walk(hudRoot);
92
+ }
93
+
94
+ // ── 파일 동기화 ──
95
+
96
+ const SYNC_MAP = [
97
+ {
98
+ src: join(PLUGIN_ROOT, "scripts", "tfx-route.sh"),
99
+ dst: join(CLAUDE_DIR, "scripts", "tfx-route.sh"),
100
+ label: "tfx-route.sh",
101
+ },
102
+ {
103
+ src: join(PLUGIN_ROOT, "scripts", "tfx-route-post.mjs"),
104
+ dst: join(CLAUDE_DIR, "scripts", "tfx-route-post.mjs"),
105
+ label: "tfx-route-post.mjs",
106
+ },
107
+ {
108
+ src: join(PLUGIN_ROOT, "scripts", "tfx-route-worker.mjs"),
109
+ dst: join(CLAUDE_DIR, "scripts", "tfx-route-worker.mjs"),
110
+ label: "tfx-route-worker.mjs",
111
+ },
112
+ {
113
+ src: join(PLUGIN_ROOT, "hub", "workers", "codex-mcp.mjs"),
114
+ dst: join(CLAUDE_DIR, "scripts", "hub", "workers", "codex-mcp.mjs"),
115
+ label: "hub/workers/codex-mcp.mjs",
116
+ },
117
+ {
118
+ src: join(PLUGIN_ROOT, "hub", "workers", "delegator-mcp.mjs"),
119
+ dst: join(CLAUDE_DIR, "scripts", "hub", "workers", "delegator-mcp.mjs"),
120
+ label: "hub/workers/delegator-mcp.mjs",
121
+ },
122
+ {
123
+ src: join(PLUGIN_ROOT, "hub", "workers", "interface.mjs"),
124
+ dst: join(CLAUDE_DIR, "scripts", "hub", "workers", "interface.mjs"),
125
+ label: "hub/workers/interface.mjs",
126
+ },
127
+ {
128
+ src: join(PLUGIN_ROOT, "hub", "workers", "gemini-worker.mjs"),
129
+ dst: join(CLAUDE_DIR, "scripts", "hub", "workers", "gemini-worker.mjs"),
130
+ label: "hub/workers/gemini-worker.mjs",
131
+ },
132
+ {
133
+ src: join(PLUGIN_ROOT, "hub", "workers", "claude-worker.mjs"),
134
+ dst: join(CLAUDE_DIR, "scripts", "hub", "workers", "claude-worker.mjs"),
135
+ label: "hub/workers/claude-worker.mjs",
136
+ },
137
+ {
138
+ src: join(PLUGIN_ROOT, "hub", "workers", "worker-utils.mjs"),
139
+ dst: join(CLAUDE_DIR, "scripts", "hub", "workers", "worker-utils.mjs"),
140
+ label: "hub/workers/worker-utils.mjs",
141
+ },
142
+ {
143
+ src: join(PLUGIN_ROOT, "hub", "workers", "factory.mjs"),
144
+ dst: join(CLAUDE_DIR, "scripts", "hub", "workers", "factory.mjs"),
145
+ label: "hub/workers/factory.mjs",
146
+ },
147
+ ...scanHudFiles(PLUGIN_ROOT, CLAUDE_DIR),
148
+ {
149
+ src: join(PLUGIN_ROOT, "scripts", "notion-read.mjs"),
150
+ dst: join(CLAUDE_DIR, "scripts", "notion-read.mjs"),
151
+ label: "notion-read.mjs",
152
+ },
153
+ {
154
+ src: join(PLUGIN_ROOT, "scripts", "tfx-batch-stats.mjs"),
155
+ dst: join(CLAUDE_DIR, "scripts", "tfx-batch-stats.mjs"),
156
+ label: "tfx-batch-stats.mjs",
157
+ },
158
+ {
159
+ src: join(PLUGIN_ROOT, "scripts", "lib", "mcp-filter.mjs"),
160
+ dst: join(CLAUDE_DIR, "scripts", "lib", "mcp-filter.mjs"),
161
+ label: "lib/mcp-filter.mjs",
162
+ },
163
+ {
164
+ src: join(PLUGIN_ROOT, "scripts", "lib", "mcp-server-catalog.mjs"),
165
+ dst: join(CLAUDE_DIR, "scripts", "lib", "mcp-server-catalog.mjs"),
166
+ label: "lib/mcp-server-catalog.mjs",
167
+ },
168
+ {
169
+ src: join(PLUGIN_ROOT, "scripts", "lib", "keyword-rules.mjs"),
170
+ dst: join(CLAUDE_DIR, "scripts", "lib", "keyword-rules.mjs"),
171
+ label: "lib/keyword-rules.mjs",
172
+ },
173
+ {
174
+ src: join(PLUGIN_ROOT, "hub", "team", "agent-map.json"),
175
+ dst: join(CLAUDE_DIR, "hub", "team", "agent-map.json"),
176
+ label: "hub/team/agent-map.json",
177
+ },
178
+ {
179
+ src: join(PLUGIN_ROOT, "scripts", "headless-guard.mjs"),
180
+ dst: join(CLAUDE_DIR, "scripts", "headless-guard.mjs"),
181
+ label: "headless-guard.mjs",
182
+ },
183
+ {
184
+ src: join(PLUGIN_ROOT, "scripts", "headless-guard-fast.sh"),
185
+ dst: join(CLAUDE_DIR, "scripts", "headless-guard-fast.sh"),
186
+ label: "headless-guard-fast.sh",
187
+ },
188
+ {
189
+ src: join(PLUGIN_ROOT, "scripts", "tfx-gate-activate.mjs"),
190
+ dst: join(CLAUDE_DIR, "scripts", "tfx-gate-activate.mjs"),
191
+ label: "tfx-gate-activate.mjs",
192
+ },
193
+ {
194
+ src: join(PLUGIN_ROOT, "scripts", "remote-spawn.mjs"),
195
+ dst: join(CLAUDE_DIR, "scripts", "remote-spawn.mjs"),
196
+ label: "remote-spawn.mjs",
197
+ },
198
+ ];
199
+
200
+ function getVersion(filePath) {
201
+ try {
202
+ const content = readFileSync(filePath, "utf8");
203
+ const match = content.match(/VERSION\s*=\s*"([^"]+)"/);
204
+ return match ? match[1] : null;
205
+ } catch {
206
+ return null;
207
+ }
208
+ }
209
+
210
+ function shouldSyncTextFile(src, dst) {
211
+ if (!existsSync(dst)) return true;
212
+ try {
213
+ return readFileSync(src, "utf8") !== readFileSync(dst, "utf8");
214
+ } catch {
215
+ return true;
216
+ }
217
+ }
218
+
219
+ function getPackageVersion() {
220
+ try {
221
+ return JSON.parse(readFileSync(join(PLUGIN_ROOT, "package.json"), "utf8")).version;
222
+ } catch {
223
+ return null;
224
+ }
225
+ }
226
+
227
+ function readMarker() {
228
+ if (!existsSync(SETUP_MARKER_PATH)) return null;
229
+
230
+ try {
231
+ return JSON.parse(readFileSync(SETUP_MARKER_PATH, "utf8"));
232
+ } catch {
233
+ return null;
234
+ }
235
+ }
236
+
237
+ function writeMarker(marker) {
238
+ const markerDir = dirname(SETUP_MARKER_PATH);
239
+ if (!existsSync(markerDir)) mkdirSync(markerDir, { recursive: true });
240
+ writeFileSync(SETUP_MARKER_PATH, JSON.stringify(marker, null, 2) + "\n", "utf8");
241
+ }
242
+
243
+ function escapeRegExp(value) {
244
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
245
+ }
246
+
247
+ function normalizeErrorMessage(error, fallback = "unknown error") {
248
+ const isMeaningful = (value) => {
249
+ if (typeof value !== "string") return false;
250
+ const normalized = value.trim().toLowerCase();
251
+ if (!normalized) return false;
252
+ return normalized !== "undefined" && normalized !== "null";
253
+ };
254
+
255
+ if (error instanceof Error && isMeaningful(error.message)) {
256
+ return error.message.trim();
257
+ }
258
+ if (isMeaningful(error)) return error.trim();
259
+ if (error && typeof error === "object") {
260
+ const candidate = /** @type {{ message?: unknown }} */ (error).message;
261
+ if (isMeaningful(candidate)) return candidate.trim();
262
+ }
263
+ return fallback;
264
+ }
265
+
266
+ function hasProfileSection(tomlContent, profileName) {
267
+ const section = `^\\[profiles\\.${escapeRegExp(profileName)}\\]\\s*$`;
268
+ return new RegExp(section, "m").test(tomlContent);
269
+ }
270
+
271
+ function replaceProfileSection(tomlContent, profileName, lines) {
272
+ const header = `[profiles.${profileName}]`;
273
+ const sectionRe = new RegExp(
274
+ `^\\[profiles\\.${escapeRegExp(profileName)}\\]\\s*\\n?(?:(?!\\[)[^\\n]*\\n?)*`,
275
+ "m",
276
+ );
277
+ const replacement = `${header}\n${lines.join("\n")}\n`;
278
+ return tomlContent.replace(sectionRe, replacement);
279
+ }
280
+
281
+ // ── 스킬 별칭 (하나의 소스 스킬을 다른 이름으로도 노출) ──
282
+
283
+ const SKILL_ALIASES = [
284
+ { alias: "tfx-autopilot", source: "tfx-auto" },
285
+ { alias: "tfx-persist", source: "tfx-auto" },
286
+ { alias: "tfx-fullcycle", source: "tfx-auto" },
287
+ ];
288
+
289
+ // ── 폐기 예정 스킬 목록 ──
290
+
291
+ const DEPRECATED_SKILLS = [
292
+ "tfx-codex-route",
293
+ "tfx-gemini-route",
294
+ ];
295
+
296
+ // ── 구형 Codex 모델 (마이그레이션 안내 대상) ──
297
+
298
+ const LEGACY_CODEX_MODELS = [
299
+ "o4-mini",
300
+ "o3",
301
+ "codex-mini-latest",
302
+ ];
303
+
304
+ /**
305
+ * 별칭 스킬 디렉토리를 동기화한다.
306
+ * 소스 스킬의 SKILL.md와 하위 파일을 별칭 디렉토리에 복사하면서
307
+ * SKILL.md 내부의 소스 이름 참조를 별칭으로 치환한다.
308
+ * @param {string} srcDir - 소스 스킬 디렉토리
309
+ * @param {string} dstDir - 대상(별칭) 디렉토리
310
+ * @param {{ alias: string, source: string }} meta - 별칭 메타 정보
311
+ * @returns {number} 동기화된 파일 수
312
+ */
313
+ function syncAliasedSkillDir(srcDir, dstDir, { alias, source }) {
314
+ if (!existsSync(srcDir)) return 0;
315
+ if (!existsSync(dstDir)) mkdirSync(dstDir, { recursive: true });
316
+
317
+ let count = 0;
318
+ for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
319
+ const srcPath = join(srcDir, entry.name);
320
+ const dstPath = join(dstDir, entry.name);
321
+
322
+ if (entry.isDirectory()) {
323
+ count += syncAliasedSkillDir(srcPath, dstPath, { alias, source });
324
+ continue;
325
+ }
326
+ if (!entry.name.endsWith(".md")) continue;
327
+
328
+ const srcContent = readFileSync(srcPath, "utf8");
329
+ const aliased = srcContent.replaceAll(source, alias);
330
+ const existing = existsSync(dstPath) ? readFileSync(dstPath, "utf8") : null;
331
+ if (aliased !== existing) {
332
+ writeFileSync(dstPath, aliased, "utf8");
333
+ count++;
334
+ }
335
+ }
336
+ return count;
337
+ }
338
+
339
+ /**
340
+ * 설치된 스킬 디렉토리에서 패키지에 더 이상 없는 tfx-* 스킬을 제거한다.
341
+ * @param {string} installedDir - ~/.claude/skills
342
+ * @param {string} pkgDir - PLUGIN_ROOT/skills
343
+ * @returns {{ count: number, removed: string[] }}
344
+ */
345
+ function cleanupStaleSkills(installedDir, pkgDir) {
346
+ const removed = [];
347
+ if (!existsSync(installedDir)) return { count: 0, removed };
348
+
349
+ const pkgNames = new Set();
350
+ if (existsSync(pkgDir)) {
351
+ for (const n of readdirSync(pkgDir)) pkgNames.add(n);
352
+ }
353
+ for (const { alias } of SKILL_ALIASES) pkgNames.add(alias);
354
+ for (const dep of DEPRECATED_SKILLS) pkgNames.add(dep);
355
+
356
+ for (const name of readdirSync(installedDir)) {
357
+ if (!name.startsWith("tfx-")) continue;
358
+ if (pkgNames.has(name)) continue;
359
+
360
+ const skillPath = join(installedDir, name);
361
+ try {
362
+ const entries = readdirSync(skillPath);
363
+ for (const f of entries) unlinkSync(join(skillPath, f));
364
+ // rmdir only works on empty dirs; ignore errors for nested
365
+ try { readdirSync(skillPath).length === 0 && unlinkSync(skillPath); } catch {}
366
+ } catch { /* best effort */ }
367
+ removed.push(name);
368
+ }
369
+ return { count: removed.length, removed };
370
+ }
371
+
372
+ /**
373
+ * 훅 커맨드 문자열에서 스크립트 파일명을 추출한다.
374
+ * 예: 'node "/path/to/safety-guard.mjs"' → "safety-guard.mjs"
375
+ * @param {string|undefined} command
376
+ * @returns {string|null}
377
+ */
378
+ function extractManagedHookFilename(command) {
379
+ if (typeof command !== "string") return null;
380
+ const match = command.match(/([^/\\"\s]+\.(?:mjs|js|sh|cjs))(?:["'\s]|$)/);
381
+ return match ? match[1] : null;
382
+ }
383
+
384
+ /**
385
+ * hook-registry.json에서 관리 대상 훅 목록을 플랫 배열로 반환한다.
386
+ * @param {string} registryPath - hook-registry.json 경로
387
+ * @returns {Array<{ event: string, id: string, fileName: string, matcher: string, command: string, priority: number, enabled: boolean }>}
388
+ */
389
+ function getManagedRegistryHooks(registryPath) {
390
+ if (!existsSync(registryPath)) return [];
391
+ try {
392
+ const registry = JSON.parse(readFileSync(registryPath, "utf8"));
393
+ const events = registry.events || {};
394
+ const result = [];
395
+ for (const [event, hooks] of Object.entries(events)) {
396
+ if (!Array.isArray(hooks)) continue;
397
+ for (const hook of hooks) {
398
+ if (!hook.enabled) continue;
399
+ const fileName = extractManagedHookFilename(hook.command);
400
+ result.push({
401
+ event,
402
+ id: hook.id || "",
403
+ fileName,
404
+ matcher: hook.matcher || "*",
405
+ command: hook.command || "",
406
+ priority: hook.priority ?? 100,
407
+ enabled: hook.enabled,
408
+ });
409
+ }
410
+ }
411
+ return result;
412
+ } catch {
413
+ return [];
414
+ }
415
+ }
416
+
417
+ /**
418
+ * hook-registry.json 기준으로 settings.json에 누락된 훅을 자동 등록한다.
419
+ * @param {{ settingsPath: string, registryPath: string }} opts
420
+ * @returns {{ ok: boolean, changed: boolean, added: string[] }}
421
+ */
422
+ function ensureHooksInSettings({ settingsPath, registryPath }) {
423
+ try {
424
+ const managed = getManagedRegistryHooks(registryPath);
425
+ if (managed.length === 0) return { ok: true, changed: false, added: [] };
426
+
427
+ let settings = {};
428
+ if (existsSync(settingsPath)) {
429
+ settings = JSON.parse(readFileSync(settingsPath, "utf8"));
430
+ }
431
+ if (!settings.hooks) settings.hooks = {};
432
+
433
+ const added = [];
434
+ for (const spec of managed) {
435
+ if (!Array.isArray(settings.hooks[spec.event])) {
436
+ settings.hooks[spec.event] = [];
437
+ }
438
+ const entries = settings.hooks[spec.event];
439
+ const alreadyRegistered = entries.some((entry) =>
440
+ Array.isArray(entry?.hooks) &&
441
+ entry.hooks.some((h) => extractManagedHookFilename(h?.command) === spec.fileName),
442
+ );
443
+ if (alreadyRegistered) continue;
444
+
445
+ entries.push({
446
+ matcher: spec.matcher,
447
+ hooks: [{ type: "command", command: spec.command, timeout: 5 }],
448
+ });
449
+ added.push(spec.id || spec.fileName);
450
+ }
451
+
452
+ if (added.length > 0) {
453
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
454
+ }
455
+ return { ok: true, changed: added.length > 0, added };
456
+ } catch {
457
+ return { ok: false, changed: false, added: [] };
458
+ }
459
+ }
460
+
461
+ /**
462
+ * Codex config.json에 tfx-hub MCP 서버 엔트리를 보장한다.
463
+ * @param {{ mcpUrl: string, createIfMissing?: boolean, enabled?: boolean }} opts
464
+ * @returns {{ ok: boolean, changed: boolean, reason?: string }}
465
+ */
466
+ function ensureCodexHubServerConfig({ configFile, mcpUrl, createIfMissing = false, enabled = false }) {
467
+ try {
468
+ const codexConfigDir = join(homedir(), ".codex");
469
+ const configPath = configFile || join(codexConfigDir, "config.json");
470
+
471
+ if (!existsSync(configPath)) {
472
+ if (!createIfMissing) return { ok: true, changed: false, reason: "no-config" };
473
+ const dir = dirname(configPath);
474
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
475
+ const config = { mcpServers: { "tfx-hub": { url: mcpUrl, enabled } } };
476
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
477
+ return { ok: true, changed: true };
478
+ }
479
+
480
+ const config = JSON.parse(readFileSync(configPath, "utf8"));
481
+ if (!config.mcpServers) config.mcpServers = {};
482
+
483
+ const existing = config.mcpServers["tfx-hub"];
484
+ const desired = { ...(existing || {}), url: mcpUrl, enabled };
485
+
486
+ if (existing && existing.url === desired.url && existing.enabled === desired.enabled) {
487
+ return { ok: true, changed: false };
488
+ }
489
+
490
+ const updated = {
491
+ ...config,
492
+ mcpServers: { ...config.mcpServers, "tfx-hub": desired },
493
+ };
494
+ writeFileSync(configPath, JSON.stringify(updated, null, 2) + "\n", "utf8");
495
+ return { ok: true, changed: true };
496
+ } catch (err) {
497
+ return { ok: false, changed: false, reason: err?.message || "unknown" };
498
+ }
499
+ }
500
+
501
+ function ensureCodexProfiles() {
502
+ try {
503
+ if (!existsSync(CODEX_DIR)) mkdirSync(CODEX_DIR, { recursive: true });
504
+
505
+ const original = existsSync(CODEX_CONFIG_PATH)
506
+ ? readFileSync(CODEX_CONFIG_PATH, "utf8")
507
+ : "";
508
+
509
+ let updated = original;
510
+ let changed = 0;
511
+
512
+ for (const profile of REQUIRED_CODEX_PROFILES) {
513
+ const desired = `[profiles.${profile.name}]\n${profile.lines.join("\n")}\n`;
514
+
515
+ if (hasProfileSection(updated, profile.name)) {
516
+ // 기존 프로필이 있으면 강제 갱신
517
+ const before = updated;
518
+ updated = replaceProfileSection(updated, profile.name, profile.lines);
519
+ if (updated !== before) changed++;
520
+ } else {
521
+ // 없으면 추가
522
+ if (updated.length > 0 && !updated.endsWith("\n")) updated += "\n";
523
+ if (updated.trim().length > 0) updated += "\n";
524
+ updated += desired;
525
+ changed++;
526
+ }
527
+ }
528
+
529
+ // headless 모드에서 승인 없이 실행하려면 sandbox 설정 필수
530
+ // Codex 0.117.0+: config.toml 설정과 CLI 플래그 중복 시 에러
531
+ if (process.platform === "win32" && !updated.includes('[windows]')) {
532
+ if (updated.length > 0 && !updated.endsWith("\n")) updated += "\n";
533
+ updated += "\n[windows]\nsandbox = \"elevated\"\n";
534
+ changed++;
535
+ }
536
+
537
+ if (changed > 0) {
538
+ writeFileSync(CODEX_CONFIG_PATH, updated, "utf8");
539
+ }
540
+
541
+ return { ok: true, changed };
542
+ } catch (error) {
543
+ const message = error instanceof Error && error.message ? error.message.trim() : "unknown error";
544
+ return { ok: false, changed: 0, message };
545
+ }
546
+ }
547
+
548
+ function syncClaudeRoutingSections() {
549
+ try {
550
+ const routingTable = getLatestRoutingTable();
551
+ return [
552
+ ensureTfxSection(join(PLUGIN_ROOT, "CLAUDE.md"), routingTable),
553
+ ensureGlobalClaudeRoutingSection(CLAUDE_DIR),
554
+ ];
555
+ } catch (error) {
556
+ const reason = error instanceof Error ? error.message : "routing_sync_failed";
557
+ return [{ action: "unchanged", path: join(PLUGIN_ROOT, "CLAUDE.md"), skipped: true, reason }];
558
+ }
559
+ }
560
+
561
+ export {
562
+ replaceProfileSection,
563
+ hasProfileSection,
564
+ detectDevMode,
565
+ scanHudFiles,
566
+ SYNC_MAP,
567
+ BREADCRUMB_PATH,
568
+ PLUGIN_ROOT,
569
+ CLAUDE_DIR,
570
+ SETUP_MARKER_PATH,
571
+ readMarker,
572
+ writeMarker,
573
+ REQUIRED_CODEX_PROFILES,
574
+ getVersion,
575
+ ensureCodexProfiles,
576
+ SKILL_ALIASES,
577
+ LEGACY_CODEX_MODELS,
578
+ DEPRECATED_SKILLS,
579
+ syncAliasedSkillDir,
580
+ cleanupStaleSkills,
581
+ extractManagedHookFilename,
582
+ getManagedRegistryHooks,
583
+ ensureHooksInSettings,
584
+ ensureCodexHubServerConfig,
585
+ };
586
+
587
+ async function main() {
588
+ const isSync = process.argv.includes("--sync");
589
+ const isForce = process.argv.includes("--force");
590
+ const isDev = detectDevMode();
591
+
592
+ if (isDev) {
593
+ console.log(" [dev] \uB85C\uCEEC \uAC1C\uBC1C \uBAA8\uB4DC \uAC10\uC9C0");
594
+ }
595
+
596
+ if (isSync) {
597
+ console.log(" [sync] \uBA85\uC2DC\uC801 \uC7AC\uB3D9\uAE30\uD654 \uC2E4\uD589");
598
+ }
599
+
600
+ const pkgVersion = getPackageVersion();
601
+ const marker = readMarker();
602
+ const claudeRoutingResults = syncClaudeRoutingSections();
603
+ const claudeRoutingChangedCount = claudeRoutingResults.filter((result) => result.action === "created" || result.action === "updated").length;
604
+ if (pkgVersion && marker?.version === pkgVersion && !isForce) {
605
+ if (claudeRoutingChangedCount > 0) {
606
+ console.log(`setup: skip core sync (v${pkgVersion} already synced, CLAUDE.md ${claudeRoutingChangedCount}건 반영)`);
607
+ } else {
608
+ console.log(`setup: skip (v${pkgVersion} already synced)`);
609
+ }
610
+ process.exit(0);
611
+ }
612
+
613
+ let synced = claudeRoutingChangedCount;
614
+
615
+ // ── Memory Doctor (P0 자동 수정) ──
616
+ const isCIEnv = process.env.CI === "true" || process.env.DOCKER === "true";
617
+ if (!isCIEnv) {
618
+ try {
619
+ const { createMemoryDoctor } = await import("../hub/memory-doctor.mjs");
620
+ const projectSlug = process.cwd().replace(/^([A-Z]):/u, "$1-").replace(/[\\/]/gu, "-");
621
+ const memDir = join(CLAUDE_DIR, "projects", projectSlug, "memory");
622
+ if (existsSync(memDir)) {
623
+ const doctor = createMemoryDoctor({
624
+ memoryDir: memDir,
625
+ rulesDir: join(process.cwd(), ".claude", "rules"),
626
+ projectDir: process.cwd(),
627
+ claudeDir: CLAUDE_DIR,
628
+ });
629
+ const { checks, healthScore } = doctor.scan();
630
+ const p0Auto = checks.filter((c) => c.severity === "P0" && c.autofix && !c.passed);
631
+ if (p0Auto.length > 0) {
632
+ doctor.fixAll({ severity: "P0" });
633
+ console.log(` memory-doctor: ${p0Auto.length}건 P0 자동 수정 (health: ${healthScore})`);
634
+ synced += p0Auto.length;
635
+ }
636
+ }
637
+ } catch (err) {
638
+ console.log(` memory-doctor: skip (${err.message})`);
639
+ }
640
+ }
641
+
642
+ for (const { src, dst } of SYNC_MAP) {
643
+ if (!existsSync(src)) continue;
644
+
645
+ const dstDir = dirname(dst);
646
+ if (!existsSync(dstDir)) {
647
+ mkdirSync(dstDir, { recursive: true });
648
+ }
649
+
650
+ if (!existsSync(dst)) {
651
+ copyFileSync(src, dst);
652
+ try { chmodSync(dst, 0o755); } catch {}
653
+ synced++;
654
+ } else {
655
+ if (shouldSyncTextFile(src, dst)) {
656
+ copyFileSync(src, dst);
657
+ try { chmodSync(dst, 0o755); } catch {}
658
+ synced++;
659
+ }
660
+ }
661
+ }
662
+
663
+ try {
664
+ const claudeGuide = ensureGlobalClaudeRoutingSection(CLAUDE_DIR);
665
+ if (claudeGuide.changed) synced++;
666
+ } catch (e) {
667
+ console.log(` \x1b[33m⚠\x1b[0m CLAUDE.md 라우팅: ${e.message}`);
668
+ }
669
+
670
+ // ── Worker 의존성 동기화 (MCP SDK + transitive deps) ──
671
+
672
+ const workerNodeModules = join(CLAUDE_DIR, "scripts", "node_modules");
673
+ const mcpSdkPath = join(workerNodeModules, "@modelcontextprotocol", "sdk");
674
+ const srcNodeModules = join(PLUGIN_ROOT, "node_modules");
675
+
676
+ // native 모듈은 제외 (플랫폼 의존적, worker에서 불필요)
677
+ const SKIP_PACKAGES = new Set(["better-sqlite3", "prebuild-install", "node-abi", "node-addon-api"]);
678
+
679
+ if (!existsSync(mcpSdkPath) && existsSync(srcNodeModules)) {
680
+ try {
681
+ const { cpSync } = await import("fs");
682
+ for (const entry of readdirSync(srcNodeModules)) {
683
+ if (SKIP_PACKAGES.has(entry)) continue;
684
+
685
+ const src = join(srcNodeModules, entry);
686
+ const dst = join(workerNodeModules, entry);
687
+ if (existsSync(dst)) continue;
688
+
689
+ mkdirSync(dirname(dst), { recursive: true });
690
+ cpSync(src, dst, { recursive: true });
691
+ }
692
+ synced++;
693
+ } catch {
694
+ // best effort: 의존성 복사 실패 시 exec fallback으로 동작
695
+ }
696
+ }
697
+
698
+ // ── 패키지 루트 breadcrumb 기록 ──
699
+ // tfx-route.sh가 hub/server.mjs, hub/bridge.mjs를 찾을 수 있도록
700
+ // 패키지 루트 경로를 ~/.claude/scripts/.tfx-pkg-root에 기록한다.
701
+ // dev mode에서는 항상 최신 경로를 기록 (--sync 시 강제 갱신).
702
+ {
703
+ const pkgRootForward = PLUGIN_ROOT.replace(/\\/g, "/");
704
+ const currentBreadcrumb = existsSync(BREADCRUMB_PATH)
705
+ ? readFileSync(BREADCRUMB_PATH, "utf8").trim()
706
+ : "";
707
+ if (currentBreadcrumb !== pkgRootForward || isSync) {
708
+ const breadcrumbDir = dirname(BREADCRUMB_PATH);
709
+ if (!existsSync(breadcrumbDir)) mkdirSync(breadcrumbDir, { recursive: true });
710
+ writeFileSync(BREADCRUMB_PATH, pkgRootForward + "\n", "utf8");
711
+ synced++;
712
+ }
713
+ }
714
+
715
+ // ── 에이전트 동기화 (.claude/agents/ → ~/.claude/agents/) ──
716
+ // slim-wrapper 등 커스텀 에이전트를 글로벌에 배포하여
717
+ // 다른 프로젝트에서도 subagent_type으로 참조 가능하게 한다.
718
+
719
+ const agentsSrc = join(PLUGIN_ROOT, ".claude", "agents");
720
+ const agentsDst = join(CLAUDE_DIR, "agents");
721
+
722
+ if (existsSync(agentsSrc)) {
723
+ if (!existsSync(agentsDst)) mkdirSync(agentsDst, { recursive: true });
724
+
725
+ for (const name of readdirSync(agentsSrc)) {
726
+ if (!name.endsWith(".md")) continue;
727
+
728
+ const src = join(agentsSrc, name);
729
+ const dst = join(agentsDst, name);
730
+
731
+ if (!existsSync(dst)) {
732
+ copyFileSync(src, dst);
733
+ synced++;
734
+ } else if (shouldSyncTextFile(src, dst)) {
735
+ copyFileSync(src, dst);
736
+ synced++;
737
+ }
738
+ }
739
+ }
740
+
741
+ // ── 스킬 동기화 ──
742
+ // SKILL.md + 하위 디렉토리(references/ 등)를 재귀적으로 동기화
743
+
744
+ const skillsSrc = join(PLUGIN_ROOT, "skills");
745
+ const skillsDst = join(CLAUDE_DIR, "skills");
746
+
747
+ function syncSkillDir(srcDir, dstDir) {
748
+ if (!existsSync(dstDir)) mkdirSync(dstDir, { recursive: true });
749
+
750
+ let count = 0;
751
+ for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
752
+ const srcPath = join(srcDir, entry.name);
753
+ const dstPath = join(dstDir, entry.name);
754
+
755
+ if (entry.isDirectory()) {
756
+ count += syncSkillDir(srcPath, dstPath);
757
+ } else if (entry.name.endsWith(".md")) {
758
+ if (shouldSyncTextFile(srcPath, dstPath)) {
759
+ copyFileSync(srcPath, dstPath);
760
+ count++;
761
+ }
762
+ }
763
+ }
764
+ return count;
765
+ }
766
+
767
+ if (existsSync(skillsSrc)) {
768
+ for (const name of readdirSync(skillsSrc)) {
769
+ const skillDir = join(skillsSrc, name);
770
+ const skillMd = join(skillDir, "SKILL.md");
771
+ if (!existsSync(skillMd)) continue;
772
+
773
+ synced += syncSkillDir(skillDir, join(skillsDst, name));
774
+ }
775
+ }
776
+
777
+ // ── settings.json 통합 R/W ──
778
+ // 3개 섹션(statusLine, agentTeams, hooks)을 1회 read → 일괄 수정 → 1회 write
779
+
780
+ const settingsPath = join(CLAUDE_DIR, "settings.json");
781
+ const hudPath = join(CLAUDE_DIR, "hud", "hud-qos-status.mjs");
782
+
783
+ /**
784
+ * statusLine 섹션 적용.
785
+ * @param {object} s - settings 객체 (직접 변경)
786
+ * @returns {boolean} 변경 여부
787
+ */
788
+ function applyStatusLine(s) {
789
+ if (!existsSync(hudPath)) return false;
790
+ const currentCmd = s.statusLine?.command || "";
791
+ if (currentCmd.includes("hud-qos-status.mjs")) return false;
792
+
793
+ const nodePath = process.execPath.replace(/\\/g, "/");
794
+ const hudForward = hudPath.replace(/\\/g, "/");
795
+ const nodeRef = nodePath.includes(" ") ? `"${nodePath}"` : nodePath;
796
+ const hudRef = hudForward.includes(" ") ? `"${hudForward}"` : hudForward;
797
+
798
+ s.statusLine = { type: "command", command: `${nodeRef} ${hudRef}` };
799
+ return true;
800
+ }
801
+
802
+ /**
803
+ * Agent Teams 환경변수 섹션 적용.
804
+ * @param {object} s - settings 객체 (직접 변경)
805
+ * @returns {boolean} 변경 여부
806
+ */
807
+ function applyAgentTeams(s) {
808
+ if (!s.env) s.env = {};
809
+ let changed = false;
810
+
811
+ if (s.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
812
+ s.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
813
+ changed = true;
814
+ }
815
+ // teammateMode: auto (tmux 밖이면 in-process, 안이면 split-pane)
816
+ if (!s.teammateMode) {
817
+ s.teammateMode = "auto";
818
+ changed = true;
819
+ }
820
+ return changed;
821
+ }
822
+
823
+ /**
824
+ * Remote Control 자동 활성화.
825
+ * 모든 세션에서 remote control URL을 자동 발급하도록 설정.
826
+ * @param {object} s - settings 객체 (직접 변경)
827
+ * @returns {boolean} 변경 여부
828
+ */
829
+ function applyRemoteControl(s) {
830
+ if (s.remoteControlAtStartup === true) return false;
831
+ if (process.env.TFX_REMOTE_CONTROL !== "1" && !detectDevMode()) return false;
832
+ s.remoteControlAtStartup = true;
833
+ return true;
834
+ }
835
+
836
+ /**
837
+ * SessionStart + PreToolUse 훅 섹션 적용.
838
+ * @param {object} s - settings 객체 (직접 변경)
839
+ * @returns {boolean} 변경 여부
840
+ */
841
+ function applyHooks(s) {
842
+ if (!s.hooks) s.hooks = {};
843
+ let changed = false;
844
+
845
+ // ── SessionStart 훅 ──
846
+ if (!Array.isArray(s.hooks.SessionStart)) s.hooks.SessionStart = [];
847
+
848
+ const hasTrifluxHooks = s.hooks.SessionStart.some((entry) =>
849
+ Array.isArray(entry.hooks) &&
850
+ entry.hooks.some((h) => typeof h.command === "string" && h.command.includes("triflux")),
851
+ );
852
+
853
+ if (!hasTrifluxHooks) {
854
+ const nodePath = process.execPath.replace(/\\/g, "/");
855
+ const nodeRef = nodePath.includes(" ") ? `"${nodePath}"` : nodePath;
856
+ const pluginRoot = PLUGIN_ROOT.replace(/\\/g, "/");
857
+
858
+ s.hooks.SessionStart.push({
859
+ matcher: "*",
860
+ hooks: [
861
+ {
862
+ type: "command",
863
+ command: `${nodeRef} "${pluginRoot}/scripts/setup.mjs"`,
864
+ timeout: 10,
865
+ },
866
+ {
867
+ type: "command",
868
+ command: `${nodeRef} "${pluginRoot}/scripts/hub-ensure.mjs"`,
869
+ timeout: 8,
870
+ },
871
+ {
872
+ type: "command",
873
+ command: `${nodeRef} "${pluginRoot}/scripts/preflight-cache.mjs"`,
874
+ timeout: 5,
875
+ },
876
+ ],
877
+ });
878
+ changed = true;
879
+ }
880
+
881
+ // ── PreToolUse 훅: headless-guard (auto-route) ──
882
+ if (!Array.isArray(s.hooks.PreToolUse)) s.hooks.PreToolUse = [];
883
+
884
+ const guardScriptPath = join(CLAUDE_DIR, "scripts", "headless-guard-fast.sh").replace(/\\/g, "/");
885
+ const hasGuardHook = s.hooks.PreToolUse.some((entry) =>
886
+ Array.isArray(entry.hooks) &&
887
+ entry.hooks.some((h) => typeof h.command === "string" && h.command.includes("headless-guard")),
888
+ );
889
+
890
+ if (!hasGuardHook && existsSync(guardScriptPath.replace(/\//g, "\\"))) {
891
+ s.hooks.PreToolUse.push({
892
+ matcher: "Bash|Agent",
893
+ hooks: [
894
+ {
895
+ type: "command",
896
+ command: `bash "${guardScriptPath}"`,
897
+ timeout: 3,
898
+ },
899
+ ],
900
+ });
901
+ changed = true;
902
+ } else if (hasGuardHook) {
903
+ // 기존 훅 경로를 동기화된 경로로 업데이트
904
+ for (const entry of s.hooks.PreToolUse) {
905
+ if (!Array.isArray(entry.hooks)) continue;
906
+ for (const h of entry.hooks) {
907
+ if (typeof h.command === "string" && h.command.includes("headless-guard") && !h.command.includes(guardScriptPath)) {
908
+ h.command = `bash "${guardScriptPath}"`;
909
+ changed = true;
910
+ }
911
+ }
912
+ }
913
+ }
914
+
915
+ // ── PreToolUse 훅: tfx-gate-activate (Skill 감지 → A+B gate) ──
916
+ const gateScriptPath = join(CLAUDE_DIR, "scripts", "tfx-gate-activate.mjs").replace(/\\/g, "/");
917
+ const hasGateHook = s.hooks.PreToolUse.some((entry) =>
918
+ Array.isArray(entry.hooks) &&
919
+ entry.hooks.some((h) => typeof h.command === "string" && h.command.includes("tfx-gate-activate")),
920
+ );
921
+
922
+ if (!hasGateHook && existsSync(gateScriptPath.replace(/\//g, "\\"))) {
923
+ s.hooks.PreToolUse.push({
924
+ matcher: "Skill",
925
+ hooks: [
926
+ {
927
+ type: "command",
928
+ command: `node "${gateScriptPath}"`,
929
+ timeout: 2,
930
+ },
931
+ ],
932
+ });
933
+ changed = true;
934
+ } else if (hasGateHook) {
935
+ for (const entry of s.hooks.PreToolUse) {
936
+ if (!Array.isArray(entry.hooks)) continue;
937
+ for (const h of entry.hooks) {
938
+ if (typeof h.command === "string" && h.command.includes("tfx-gate-activate") && !h.command.includes(gateScriptPath)) {
939
+ h.command = `node "${gateScriptPath}"`;
940
+ changed = true;
941
+ }
942
+ }
943
+ }
944
+ }
945
+
946
+ return changed;
947
+ }
948
+
949
+ // 1회 읽기
950
+ let settings = {};
951
+ if (existsSync(settingsPath)) {
952
+ try { settings = JSON.parse(readFileSync(settingsPath, "utf8")); } catch { /* 기존 설정 보존 */ }
953
+ }
954
+
955
+ // 3개 섹션 일괄 수정 (각각 try-catch로 독립 실행)
956
+ let settingsChanged = false;
957
+ try { if (applyStatusLine(settings)) { settingsChanged = true; synced++; } } catch {}
958
+ try { if (applyAgentTeams(settings)) { settingsChanged = true; synced++; } } catch {}
959
+ try { if (applyRemoteControl(settings)) { settingsChanged = true; synced++; } } catch {}
960
+ try { if (applyHooks(settings)) { settingsChanged = true; synced++; } } catch {}
961
+
962
+ // 1회 쓰기
963
+ if (settingsChanged) {
964
+ try {
965
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
966
+ } catch {
967
+ // settings.json 쓰기 실패 시 무시
968
+ }
969
+ }
970
+
971
+ // ── Stale PID 파일 정리 (hub 좀비 방지) ──
972
+
973
+ const HUB_PID_FILE = join(CLAUDE_DIR, "cache", "tfx-hub", "hub.pid");
974
+ if (existsSync(HUB_PID_FILE)) {
975
+ try {
976
+ const pidInfo = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
977
+ process.kill(pidInfo.pid, 0); // 프로세스 존재 확인 (신호 미전송)
978
+ } catch {
979
+ try { unlinkSync(HUB_PID_FILE); } catch {} // 죽은 프로세스면 PID 파일 삭제
980
+ synced++;
981
+ }
982
+ }
983
+
984
+ // ── psmux 자동 설치 (Windows, headless 모드용) ──
985
+
986
+ if (process.platform === "win32") {
987
+ try {
988
+ execFileSync("where", ["psmux"], { stdio: "ignore" });
989
+ } catch {
990
+ // psmux 미설치 — winget으로 자동 설치 시도
991
+ console.log(" psmux 미설치 — winget으로 설치 중...");
992
+ try {
993
+ execFileSync("winget", ["install", "--id", "marlocarlo.psmux", "--accept-package-agreements", "--accept-source-agreements"], {
994
+ stdio: ["ignore", "pipe", "pipe"],
995
+ timeout: 60000,
996
+ });
997
+ console.log(" \x1b[32m✓\x1b[0m psmux 설치 완료");
998
+ synced++;
999
+ } catch {
1000
+ console.log(" \x1b[33m⚠\x1b[0m psmux 자동 설치 실패 — 수동 설치: winget install psmux");
1001
+ }
1002
+ }
1003
+ }
1004
+
1005
+ // ── HUD 에러 캐시 자동 클리어 (업데이트/재설치 시) ──
1006
+
1007
+ const cacheDir = join(CLAUDE_DIR, "cache");
1008
+ const staleFiles = [
1009
+ "claude-usage-cache.json",
1010
+ ".claude-refresh-lock",
1011
+ "codex-rate-limits-cache.json",
1012
+ ];
1013
+
1014
+ for (const name of staleFiles) {
1015
+ const fp = join(cacheDir, name);
1016
+ if (!existsSync(fp)) continue;
1017
+ try {
1018
+ const content = readFileSync(fp, "utf8");
1019
+ const parsed = JSON.parse(content);
1020
+ // 에러 상태이거나 락 파일이면 삭제 → 새 세션에서 fresh start
1021
+ if (parsed.error || name.startsWith(".")) {
1022
+ unlinkSync(fp);
1023
+ synced++;
1024
+ }
1025
+ } catch {
1026
+ // 파싱 실패 파일도 삭제
1027
+ try { unlinkSync(fp); } catch {}
1028
+ }
1029
+ }
1030
+
1031
+ // ── Windows bash PATH 자동 설정 ──
1032
+ // Codex/Gemini가 cmd에는 있지만 bash에서 못 찾는 문제 해결
1033
+
1034
+ if (process.platform === "win32") {
1035
+ const npmBin = join(process.env.APPDATA || "", "npm");
1036
+ if (existsSync(npmBin)) {
1037
+ const bashrcPath = join(homedir(), ".bashrc");
1038
+ const pathExport = 'export PATH="$PATH:$APPDATA/npm"';
1039
+ let needsUpdate = true;
1040
+
1041
+ if (existsSync(bashrcPath)) {
1042
+ const content = readFileSync(bashrcPath, "utf8");
1043
+ if (content.includes("APPDATA/npm") || content.includes("APPDATA\\npm")) {
1044
+ needsUpdate = false;
1045
+ }
1046
+ }
1047
+
1048
+ if (needsUpdate) {
1049
+ const line = `\n# triflux: Codex/Gemini CLI를 bash에서 사용하기 위한 PATH 설정\n${pathExport}\n`;
1050
+ try {
1051
+ writeFileSync(bashrcPath, (existsSync(bashrcPath) ? readFileSync(bashrcPath, "utf8") : "") + line, "utf8");
1052
+ synced++;
1053
+ } catch {}
1054
+ }
1055
+ }
1056
+ }
1057
+
1058
+ // ── Codex 프로필 자동 보정 ──
1059
+
1060
+ const codexProfilesResult = ensureCodexProfiles();
1061
+ if (codexProfilesResult.ok && codexProfilesResult.changed > 0) {
1062
+ synced++;
1063
+ }
1064
+
1065
+ // ── CLAUDE.md 라우팅 섹션 자동 동기화 ──
1066
+
1067
+ try {
1068
+ const routingTable = getLatestRoutingTable();
1069
+ const projectResult = ensureTfxSection(join(PLUGIN_ROOT, "CLAUDE.md"), routingTable);
1070
+ if (projectResult.action !== "unchanged") {
1071
+ console.log(` \x1b[32m✓\x1b[0m CLAUDE.md (project): ${projectResult.action}`);
1072
+ synced++;
1073
+ }
1074
+ const globalResult = ensureGlobalClaudeRoutingSection(CLAUDE_DIR);
1075
+ if (globalResult.action !== "unchanged") {
1076
+ console.log(` \x1b[32m✓\x1b[0m CLAUDE.md (global): ${globalResult.action}`);
1077
+ synced++;
1078
+ }
1079
+ } catch (error) {
1080
+ console.log(` \x1b[33m⚠\x1b[0m CLAUDE.md 동기화 실패: ${error.message}`);
1081
+ }
1082
+ // ── MCP 인벤토리 백그라운드 갱신 ──
1083
+
1084
+ const mcpCheck = join(PLUGIN_ROOT, "scripts", "mcp-check.mjs");
1085
+ if (existsSync(mcpCheck)) {
1086
+ const child = spawn(process.execPath, [mcpCheck], {
1087
+ detached: true,
1088
+ stdio: "ignore",
1089
+ windowsHide: true,
1090
+ });
1091
+ child.unref(); // 부모 프로세스와 분리 — 비동기 실행
1092
+ }
1093
+
1094
+ // ── /tmp 임시 파일 자동 정리 (setup 지연 방지: fire-and-forget) ──
1095
+ cleanupTmpFiles().catch(() => {});
1096
+
1097
+ // ── npm 글로벌 패키지 동기화 ──
1098
+ // dev mode가 아닌 경우(npm install로 설치), 글로벌 triflux 패키지 버전을 확인하고
1099
+ // 로컬 버전과 다르면 업데이트를 안내한다. dev mode에서는 git 기반이므로 skip.
1100
+ if (pkgVersion && !isDev) {
1101
+ try {
1102
+ const globalVer = execFileSync("npm", ["list", "-g", "triflux", "--json", "--depth=0"], {
1103
+ encoding: "utf8",
1104
+ timeout: 10000,
1105
+ stdio: ["pipe", "pipe", "pipe"],
1106
+ });
1107
+ const parsed = JSON.parse(globalVer);
1108
+ const installedVer = parsed?.dependencies?.triflux?.version;
1109
+ if (installedVer && installedVer !== pkgVersion) {
1110
+ const tag = pkgVersion.includes("alpha") ? "alpha" : "latest";
1111
+ console.log(` npm: triflux global ${installedVer} → ${pkgVersion} (npm i -g triflux@${tag})`);
1112
+ }
1113
+ } catch {
1114
+ // npm list 실패 = 글로벌 미설치. 안내만 출력.
1115
+ if (pkgVersion.includes("alpha")) {
1116
+ console.log(" npm: triflux global 미설치 (npm i -g triflux@alpha 로 설치 가능)");
1117
+ }
1118
+ }
1119
+ }
1120
+
1121
+ if (pkgVersion) {
1122
+ writeMarker({ version: pkgVersion, timestamp: Date.now() });
1123
+ }
1124
+
1125
+ // ── postinstall 배너 (npm install 시에만 출력) ──
1126
+
1127
+ if (process.env.npm_lifecycle_event === "postinstall") {
1128
+ const G = "\x1b[32m";
1129
+ const C = "\x1b[36m";
1130
+ const Y = "\x1b[33m";
1131
+ const D = "\x1b[2m";
1132
+ const B = "\x1b[1m";
1133
+ const R = "\x1b[0m";
1134
+
1135
+ const ver = (() => {
1136
+ return pkgVersion || "?";
1137
+ })();
1138
+
1139
+ console.log(`
1140
+ ${B}╔═══════════════════════════════════════════════╗${R}
1141
+ ${B}║${R} ${C}triflux${R} ${D}v${ver}${R} ${B}— Setup Complete${R} ${B}║${R}
1142
+ ${B}╚═══════════════════════════════════════════════╝${R}
1143
+
1144
+ ${G}✓${R} tfx-route.sh → ~/.claude/scripts/
1145
+ ${G}✓${R} hud-qos-status → ~/.claude/hud/
1146
+ ${G}✓${R} ${synced > 0 ? synced + " files synced" : "all files up to date"}
1147
+ ${G}✓${R} HUD statusLine → settings.json
1148
+
1149
+ ${B}Commands:${R}
1150
+ ${C}triflux${R} setup 파일 동기화 + HUD 설정
1151
+ ${C}triflux${R} doctor CLI 진단 (Codex/Gemini 확인)
1152
+ ${C}triflux${R} list 설치된 스킬 목록
1153
+ ${C}triflux${R} update 최신 안정 버전으로 업데이트
1154
+ ${C}triflux${R} update --dev dev 채널로 업데이트 (${D}dev 별칭 지원${R})
1155
+
1156
+ ${B}Shortcuts:${R}
1157
+ ${C}tfx${R} triflux 축약
1158
+ ${C}tfx-setup${R} triflux setup
1159
+ ${C}tfx-doctor${R} triflux doctor
1160
+
1161
+ ${B}Skills (Claude Code):${R}
1162
+ ${C}/tfx-auto${R} "작업" 자동 분류 + 병렬 실행
1163
+ ${C}/tfx-auto-codex${R} "작업" Codex 리드 + Gemini 유지
1164
+ ${C}/tfx-codex${R} "작업" Codex 전용 모드
1165
+ ${C}/tfx-gemini${R} "작업" Gemini 전용 모드
1166
+ ${C}/tfx-setup${R} HUD 설정 + 진단
1167
+
1168
+ ${Y}!${R} 세션 재시작 후 스킬이 활성화됩니다
1169
+ ${D}https://github.com/tellang/triflux${R}
1170
+ `);
1171
+ }
1172
+
1173
+ process.exit(0);
1174
+ }
1175
+
1176
+ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
1177
+ main();
1178
+ }