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,76 @@
1
+ #!/usr/bin/env node
2
+ // hooks/pipeline-stop.mjs — Stop 훅: 활성 파이프라인 감지 시 구조화 decision 반환
3
+ //
4
+ // Claude Code Stop 이벤트에서 실행.
5
+ // 비터미널 단계의 파이프라인이 있으면 decision:"block" + reason으로 중단을 방지한다.
6
+ // 파이프라인이 없으면 정상 종료를 허용한다.
7
+
8
+ import { existsSync } from "node:fs";
9
+
10
+ let getPipelineStateDbPath;
11
+ let ensurePipelineTable;
12
+ let listPipelineStates;
13
+ try {
14
+ ({
15
+ getPipelineStateDbPath,
16
+ ensurePipelineTable,
17
+ listPipelineStates,
18
+ } = await import("../hub/pipeline/state.mjs"));
19
+ } catch {
20
+ // hub/pipeline 모듈 없으면 훅 무동작
21
+ process.exit(0);
22
+ }
23
+
24
+ const PROJECT_ROOT = process.env.CLAUDE_CWD || process.cwd();
25
+ const HUB_DB_PATH = getPipelineStateDbPath(PROJECT_ROOT);
26
+ const TERMINAL = new Set(["complete", "failed"]);
27
+
28
+ async function checkActivePipelines() {
29
+ if (!existsSync(HUB_DB_PATH)) return [];
30
+
31
+ try {
32
+ const { default: Database } = await import("better-sqlite3");
33
+
34
+ const db = new Database(HUB_DB_PATH, { readonly: true });
35
+ ensurePipelineTable(db);
36
+ const states = listPipelineStates(db);
37
+ db.close();
38
+
39
+ return states.filter((s) => !TERMINAL.has(s.phase));
40
+ } catch {
41
+ return [];
42
+ }
43
+ }
44
+
45
+ try {
46
+ const active = await checkActivePipelines();
47
+
48
+ if (active.length === 0) {
49
+ // 활성 파이프라인 없음 → 정상 종료 허용
50
+ process.exit(0);
51
+ }
52
+
53
+ // 활성 파이프라인 발견 → 구조화 decision으로 block
54
+ const lines = active.map(
55
+ (s) =>
56
+ ` - 팀 ${s.team_name}: ${s.phase} 단계 (fix: ${s.fix_attempt}/${s.fix_max}, ralph: ${s.ralph_iteration}/${s.ralph_max})`
57
+ );
58
+
59
+ const reason =
60
+ `[tfx-multi 파이프라인 진행 중]\n` +
61
+ `활성 파이프라인 ${active.length}개가 아직 완료되지 않았습니다:\n` +
62
+ `${lines.join("\n")}\n\n` +
63
+ `파이프라인을 이어서 진행하려면 /tfx-multi status 로 상태를 확인하세요.\n` +
64
+ `강제 종료하려면 /tfx-multi cancel 을 먼저 실행하세요.`;
65
+
66
+ // 구조화된 Stop hook 출력: decision + reason
67
+ const output = {
68
+ decision: "block",
69
+ reason,
70
+ };
71
+
72
+ process.stdout.write(JSON.stringify(output));
73
+ } catch {
74
+ // 훅 실패 시 종료 허용
75
+ process.exit(0);
76
+ }
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+ // hooks/safety-guard.mjs — PreToolUse:Bash 훅
3
+ //
4
+ // 위험한 Bash 명령을 사전 차단(exit 2)하거나 경고(additionalContext)한다.
5
+ // hooks.json에서 `if: "Bash(*)"` 필터와 함께 사용.
6
+ //
7
+ // 차단 레벨:
8
+ // BLOCK (exit 2) — 복구 불가능한 파괴적 명령
9
+ // WARN (allow + context) — 주의가 필요한 명령
10
+
11
+ import { readFileSync } from "node:fs";
12
+
13
+ // ── 차단 규칙 ──────────────────────────────────────────────
14
+ const BLOCK_RULES = [
15
+ { pattern: /\brm\s+(-[^\s]*)?-rf?\s+[/~](?!tmp\b)(?!\S*node_modules)/i, reason: "루트/홈 디렉토리 rm -rf 차단" },
16
+ { pattern: /\brm\s+(-[^\s]*)?-rf?\s+\.\s*$/i, reason: "현재 디렉토리 rm -rf . 차단" },
17
+ { pattern: /\bgit\s+push\s+.*--force\s+.*\b(main|master)\b/i, reason: "main/master force push 차단" },
18
+ { pattern: /\bgit\s+push\s+--force\s*$/i, reason: "대상 미지정 force push 차단" },
19
+ { pattern: /\bgit\s+reset\s+--hard\s+origin\//i, reason: "remote reset --hard 차단 — 로컬 작업 소실 위험" },
20
+ { pattern: /\bdrop\s+(table|database|schema)\b/i, reason: "SQL DROP 차단" },
21
+ { pattern: /\btruncate\s+table\b/i, reason: "SQL TRUNCATE 차단" },
22
+ { pattern: /\bformat\s+[a-z]:/i, reason: "디스크 포맷 차단" },
23
+ { pattern: /\b(del|rmdir)\s+\/[sq]\b/i, reason: "Windows 재귀 삭제 차단" },
24
+ { pattern: /\bgit\s+clean\s+.*-fd/i, reason: "git clean -fd 차단 — 추적되지 않은 파일 소실 위험" },
25
+ { pattern: /\bpsmux\s+kill-session\b/i, reason: "raw psmux kill-session 차단 — WT ConPTY 프리징 위험. 안전 경로: node hub/team/psmux.mjs kill --session <name>", skipIfGit: true },
26
+ { pattern: /\bpsmux\s+kill-server\b/i, reason: "psmux kill-server 차단 — 모든 세션이 즉시 종료됩니다. node hub/team/psmux.mjs kill-swarm 사용", skipIfGit: true },
27
+ ];
28
+
29
+ const WT_DIRECT_PATTERNS = [
30
+ /\bwt\.exe\b/i,
31
+ /\bwt\s+new-tab\b/i,
32
+ /\bwt\s+split-pane\b/i,
33
+ /\bwt\s+-w\b/i,
34
+ /\bStart-Process\s+wt/i,
35
+ /\bStart-Process\s+['"]?wt\.exe/i,
36
+ ];
37
+
38
+ const WT_DIRECT_BLOCK_MESSAGE =
39
+ "[safety-guard] wt.exe 직접 호출 차단됨. → hub/team/wt-manager.mjs의 createTab() / splitPane() / applySplitLayout()을 사용하세요.";
40
+
41
+ // ── 경고 규칙 ──────────────────────────────────────────────
42
+ const WARN_RULES = [
43
+ { pattern: /\bgit\s+push\b(?!.*--force)/i, warn: "git push 감지. 원격 저장소에 반영됩니다." },
44
+ { pattern: /\bgit\s+rebase\b/i, warn: "git rebase 감지. 커밋 히스토리가 변경됩니다." },
45
+ { pattern: /\bgit\s+branch\s+-[dD]\b/i, warn: "브랜치 삭제 감지." },
46
+ { pattern: /\bnpm\s+publish\b/i, warn: "npm publish 감지. 공개 레지스트리에 배포됩니다." },
47
+ { pattern: /\brm\s+(-[^\s]*)?-rf?\s/i, warn: "재귀 삭제 감지. 대상을 확인하세요." },
48
+ { pattern: /--no-verify\b/i, warn: "--no-verify 감지. 훅 건너뛰기는 권장하지 않습니다." },
49
+ { pattern: /\bchmod\s+777\b/i, warn: "chmod 777 감지. 보안 위험." },
50
+ { pattern: /\bcurl\s.*\|\s*(bash|sh)\b/i, warn: "curl | sh 감지. 원격 스크립트 실행 주의." },
51
+ ];
52
+
53
+ function readStdin() {
54
+ try {
55
+ return readFileSync(0, "utf8");
56
+ } catch {
57
+ return "";
58
+ }
59
+ }
60
+
61
+ function shouldSkipSegment(segment) {
62
+ return !segment || segment.startsWith("#") || /^\s*(echo|printf|grep|git\s+commit)\b/i.test(segment);
63
+ }
64
+
65
+ function hasSegmentInvocation(cmd, patterns) {
66
+ if (!patterns.some((pattern) => pattern.test(cmd))) return false;
67
+
68
+ const lines = cmd.split(/\n/);
69
+ let heredocDelimiter = null;
70
+ return lines.some((line) => {
71
+ if (heredocDelimiter !== null) {
72
+ if (line.trim() === heredocDelimiter) heredocDelimiter = null;
73
+ return false;
74
+ }
75
+
76
+ const heredocMatch = line.match(/<<['"]?(\w+)['"]?/);
77
+ if (heredocMatch) {
78
+ heredocDelimiter = heredocMatch[1];
79
+ return false;
80
+ }
81
+
82
+ const segments = line.split(/\s*(?:&&|;|\|\|)\s*/);
83
+ return segments.some((seg) => {
84
+ const trimmed = seg.trim();
85
+ if (shouldSkipSegment(trimmed)) return false;
86
+ return patterns.some((pattern) => pattern.test(trimmed));
87
+ });
88
+ });
89
+ }
90
+
91
+ function blockCommand(message, command) {
92
+ process.stderr.write(
93
+ `${message}\n` +
94
+ `명령어: ${command.slice(0, 120)}${command.length > 120 ? "..." : ""}\n` +
95
+ "이 명령은 실행할 수 없습니다. 안전한 대안을 사용하세요."
96
+ );
97
+ process.exit(2);
98
+ }
99
+
100
+ function main() {
101
+ const raw = readStdin();
102
+ if (!raw.trim()) process.exit(0);
103
+
104
+ let input;
105
+ try {
106
+ input = JSON.parse(raw);
107
+ } catch {
108
+ process.exit(0);
109
+ }
110
+
111
+ if (input.tool_name !== "Bash") process.exit(0);
112
+
113
+ const command = (input.tool_input?.command || "").trim();
114
+ if (!command) process.exit(0);
115
+
116
+ // psmux 명령이 실제 CLI 호출인지 판별 (오탐 방지)
117
+ // git commit 메시지, echo, grep, cat, heredoc 안의 텍스트는 무시
118
+ function isPsmuxInvocation(cmd) {
119
+ return hasSegmentInvocation(cmd, [/\bpsmux\s+kill-(session|server)\b/i]);
120
+ }
121
+
122
+ function isWtDirectInvocation(cmd) {
123
+ return hasSegmentInvocation(cmd, WT_DIRECT_PATTERNS);
124
+ }
125
+
126
+ if (isWtDirectInvocation(command)) {
127
+ blockCommand(WT_DIRECT_BLOCK_MESSAGE, command);
128
+ }
129
+
130
+ // 1. BLOCK 체크 — exit 2로 차단
131
+ for (const rule of BLOCK_RULES) {
132
+ if (rule.skipIfGit && !isPsmuxInvocation(command)) continue;
133
+ if (rule.pattern.test(command)) {
134
+ blockCommand(`[triflux safety-guard] BLOCKED: ${rule.reason}`, command);
135
+ }
136
+ }
137
+
138
+ // 2. WARN 체크 — allow + additionalContext
139
+ const warnings = [];
140
+ for (const rule of WARN_RULES) {
141
+ if (rule.pattern.test(command)) {
142
+ warnings.push(rule.warn);
143
+ }
144
+ }
145
+
146
+ if (warnings.length > 0) {
147
+ const output = {
148
+ hookSpecificOutput: {
149
+ hookEventName: "PreToolUse",
150
+ permissionDecision: "allow",
151
+ additionalContext:
152
+ `[safety-guard] ⚠ ${warnings.join(" | ")}\n` +
153
+ `명령어: ${command.slice(0, 200)}`,
154
+ },
155
+ };
156
+ process.stdout.write(JSON.stringify(output));
157
+ process.exit(0);
158
+ }
159
+
160
+ // 3. 안전한 명령 → 통과
161
+ process.exit(0);
162
+ }
163
+
164
+ try {
165
+ main();
166
+ } catch {
167
+ // 훅 실패 시 블로킹하지 않음
168
+ process.exit(0);
169
+ }
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ // hooks/subagent-verifier.mjs — SubagentStop 훅
3
+ //
4
+ // 서브에이전트 완료 시 결과 품질을 체크한다:
5
+ // - 빈 결과 감지 → 재시도 제안
6
+ // - 에러 종료 감지 → 원인 분석 컨텍스트 주입
7
+ // - 과도한 토큰 사용 감지 → 효율성 알림
8
+
9
+ import { readFileSync } from "node:fs";
10
+
11
+ function readStdin() {
12
+ try {
13
+ return readFileSync(0, "utf8");
14
+ } catch {
15
+ return "";
16
+ }
17
+ }
18
+
19
+ function main() {
20
+ const raw = readStdin();
21
+ if (!raw.trim()) process.exit(0);
22
+
23
+ let input;
24
+ try {
25
+ input = JSON.parse(raw);
26
+ } catch {
27
+ process.exit(0);
28
+ }
29
+
30
+ const agentType = input.agent_type || input.subagent_type || "unknown";
31
+ const result = input.tool_output || input.result || "";
32
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result);
33
+
34
+ const issues = [];
35
+
36
+ // 1. 빈 결과 체크
37
+ if (!resultStr.trim() || resultStr.trim().length < 20) {
38
+ issues.push(
39
+ `서브에이전트(${agentType})가 거의 빈 결과를 반환했습니다. ` +
40
+ "프롬프트를 더 구체적으로 작성하거나, 다른 subagent_type을 시도하세요."
41
+ );
42
+ }
43
+
44
+ // 2. 에러 키워드 감지
45
+ const errorPatterns = [
46
+ /error:|exception:|traceback|failed to|fatal:/i,
47
+ /❌|FAILED|ERROR/,
48
+ ];
49
+ const hasError = errorPatterns.some((p) => p.test(resultStr));
50
+ if (hasError && resultStr.length > 50) {
51
+ issues.push(
52
+ `서브에이전트(${agentType}) 결과에 에러 신호가 감지되었습니다. ` +
53
+ "결과를 검토하고, 필요 시 다른 접근 방식을 사용하세요."
54
+ );
55
+ }
56
+
57
+ // 3. 결과가 너무 길면 요약 필요 알림
58
+ if (resultStr.length > 15000) {
59
+ issues.push(
60
+ `서브에이전트(${agentType}) 결과가 ${Math.round(resultStr.length / 1000)}K 자입니다. ` +
61
+ "핵심만 추출하여 컨텍스트 윈도우를 절약하세요."
62
+ );
63
+ }
64
+
65
+ if (issues.length === 0) process.exit(0);
66
+
67
+ const output = {
68
+ systemMessage:
69
+ `[subagent-verifier] ${agentType} 완료 — 주의사항:\n` +
70
+ issues.map((i) => ` → ${i}`).join("\n"),
71
+ };
72
+
73
+ process.stdout.write(JSON.stringify(output));
74
+ }
75
+
76
+ try {
77
+ main();
78
+ } catch {
79
+ process.exit(0);
80
+ }
@@ -0,0 +1,251 @@
1
+ // hub/account-broker.mjs — Multi-account CLI pool broker
2
+ // Manages lease/release/cooldown for Codex and Gemini accounts.
3
+ // Singleton export. All state changes create new objects (immutable pattern).
4
+
5
+ import { readFileSync, existsSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import { homedir } from 'node:os';
8
+ import * as z from 'zod';
9
+
10
+ // ── Zod schema ───────────────────────────────────────────────────
11
+
12
+ const AccountSchema = z.object({
13
+ id: z.string().min(1),
14
+ mode: z.enum(['profile', 'env', 'auth']),
15
+ profile: z.string().optional(),
16
+ env: z.record(z.string(), z.string()).optional(),
17
+ authFile: z.string().optional(),
18
+ tier: z.enum(['pro', 'plus', 'free', 'unknown']).optional().default('unknown'),
19
+ }).superRefine((val, ctx) => {
20
+ if (val.mode === 'auth' && !val.authFile) {
21
+ ctx.addIssue({
22
+ code: z.ZodIssueCode.custom,
23
+ message: 'authFile is required when mode is "auth"',
24
+ path: ['authFile'],
25
+ });
26
+ }
27
+ });
28
+
29
+ const ConfigSchema = z.object({
30
+ defaults: z.object({
31
+ cooldownMs: z.number().int().positive().optional(),
32
+ }).optional(),
33
+ codex: z.array(AccountSchema).optional(),
34
+ gemini: z.array(AccountSchema).optional(),
35
+ });
36
+
37
+ const DEFAULT_COOLDOWN_MS = 300_000; // 5 minutes
38
+ const TIER_PRIORITY = { pro: 0, plus: 1, unknown: 2, free: 3 };
39
+ const LEASE_TTL_MS = 30 * 60 * 1000; // 30 minutes
40
+ const AUTH_BASE_PATH = join(homedir(), '.claude', 'cache', 'tfx-hub');
41
+
42
+ // ── env var resolution ───────────────────────────────────────────
43
+
44
+ function resolveEnvValues(env) {
45
+ if (!env) return undefined;
46
+ const resolved = {};
47
+ for (const [key, value] of Object.entries(env)) {
48
+ if (typeof value === 'string' && value.startsWith('$')) {
49
+ const varName = value.slice(1);
50
+ resolved[key] = process.env[varName] ?? '';
51
+ } else {
52
+ resolved[key] = value;
53
+ }
54
+ }
55
+ return resolved;
56
+ }
57
+
58
+ // ── AccountBroker ────────────────────────────────────────────────
59
+
60
+ class AccountBroker {
61
+ #config;
62
+ #state; // Map<accountId, accountState>
63
+ #roundRobinIndex; // Map<provider, number>
64
+
65
+ constructor(config) {
66
+ const parsed = ConfigSchema.parse(config);
67
+ this.#config = parsed;
68
+
69
+ this.#state = new Map();
70
+ this.#roundRobinIndex = new Map();
71
+
72
+ const allAccounts = [
73
+ ...(parsed.codex || []).map((a) => ({ ...a, provider: 'codex' })),
74
+ ...(parsed.gemini || []).map((a) => ({ ...a, provider: 'gemini' })),
75
+ ];
76
+
77
+ for (const account of allAccounts) {
78
+ this.#state.set(account.id, {
79
+ id: account.id,
80
+ provider: account.provider,
81
+ mode: account.mode,
82
+ profile: account.profile,
83
+ env: account.env,
84
+ authFile: account.authFile,
85
+ tier: account.tier ?? 'unknown',
86
+ busy: false,
87
+ leasedAt: null,
88
+ cooldownUntil: 0,
89
+ failures: 0,
90
+ lastUsedAt: 0,
91
+ totalSessions: 0,
92
+ });
93
+ }
94
+ }
95
+
96
+ // ── lease TTL pruning ──────────────────────────────────────────
97
+
98
+ #pruneExpiredLeases(now) {
99
+ for (const [id, acct] of this.#state) {
100
+ if (acct.busy && acct.leasedAt !== null && now - acct.leasedAt > LEASE_TTL_MS) {
101
+ this.#state.set(id, { ...acct, busy: false, leasedAt: null });
102
+ }
103
+ }
104
+ }
105
+
106
+ // ── lease ─────────────────────────────────────────────────────
107
+
108
+ lease({ provider }) {
109
+ const now = Date.now();
110
+ this.#pruneExpiredLeases(now);
111
+
112
+ const accounts = [...this.#state.values()].filter((a) => a.provider === provider);
113
+ if (!accounts.length) return null;
114
+
115
+ // group available accounts by tier, preserving insertion order within each tier
116
+ const available = accounts.filter((a) => !a.busy && a.cooldownUntil <= now);
117
+ if (!available.length) return null;
118
+
119
+ // sort by tier priority; stable sort preserves original order within same priority
120
+ const sorted = [...available].sort(
121
+ (a, b) => (TIER_PRIORITY[a.tier] ?? 2) - (TIER_PRIORITY[b.tier] ?? 2),
122
+ );
123
+
124
+ // pick the best tier, then apply round-robin within that tier's accounts
125
+ const bestTier = sorted[0].tier;
126
+ const sameTierAccounts = sorted.filter((a) => a.tier === bestTier);
127
+
128
+ // use a per-provider+tier round-robin key to distribute within the tier
129
+ const rrKey = `${provider}:${bestTier}`;
130
+ const rrCurrent = this.#roundRobinIndex.get(rrKey) ?? 0;
131
+ const tierCount = sameTierAccounts.length;
132
+ const idx = rrCurrent % tierCount;
133
+ const acct = sameTierAccounts[idx];
134
+
135
+ // advance round-robin index for this tier
136
+ this.#roundRobinIndex.set(rrKey, (idx + 1) % tierCount);
137
+
138
+ // update state (immutable)
139
+ this.#state.set(acct.id, {
140
+ ...acct,
141
+ busy: true,
142
+ leasedAt: now,
143
+ lastUsedAt: now,
144
+ totalSessions: acct.totalSessions + 1,
145
+ });
146
+
147
+ return {
148
+ id: acct.id,
149
+ mode: acct.mode,
150
+ profile: acct.mode === 'profile' ? acct.profile : undefined,
151
+ env: acct.mode === 'env' ? resolveEnvValues(acct.env) : undefined,
152
+ authFile: acct.mode === 'auth' ? join(AUTH_BASE_PATH, acct.authFile) : undefined,
153
+ };
154
+ }
155
+
156
+ // ── release ───────────────────────────────────────────────────
157
+
158
+ release(accountId, result) {
159
+ const acct = this.#state.get(accountId);
160
+ if (!acct) return;
161
+
162
+ const ok = result?.ok === true;
163
+ const newFailures = ok ? 0 : acct.failures + 1;
164
+ const cooldownMs = this.#config.defaults?.cooldownMs ?? DEFAULT_COOLDOWN_MS;
165
+
166
+ const updated = {
167
+ ...acct,
168
+ busy: false,
169
+ leasedAt: null,
170
+ failures: newFailures,
171
+ };
172
+
173
+ // consecutive failure guard: 3+ failures → auto-cooldown
174
+ if (newFailures >= 3) {
175
+ updated.cooldownUntil = Date.now() + cooldownMs;
176
+ updated.failures = 0; // reset after cooldown triggered
177
+ }
178
+
179
+ this.#state.set(accountId, updated);
180
+ }
181
+
182
+ // ── markRateLimited ───────────────────────────────────────────
183
+
184
+ markRateLimited(id, coolMs) {
185
+ const acct = this.#state.get(id);
186
+ if (!acct) return;
187
+ this.#state.set(id, {
188
+ ...acct,
189
+ busy: false,
190
+ leasedAt: null,
191
+ cooldownUntil: Date.now() + coolMs,
192
+ });
193
+ }
194
+
195
+ // ── snapshot ──────────────────────────────────────────────────
196
+
197
+ snapshot() {
198
+ return [...this.#state.values()].map((acct) => ({ ...acct }));
199
+ }
200
+
201
+ // ── nextAvailableEta ──────────────────────────────────────────
202
+
203
+ nextAvailableEta(provider) {
204
+ const now = Date.now();
205
+ this.#pruneExpiredLeases(now);
206
+
207
+ const accounts = [...this.#state.values()].filter((a) => a.provider === provider);
208
+ if (!accounts.length) return null;
209
+
210
+ // find minimum cooldownUntil among accounts that are in cooldown or busy
211
+ let earliest = null;
212
+ for (const acct of accounts) {
213
+ if (!acct.busy && acct.cooldownUntil <= now) {
214
+ // this account is available now — no ETA needed
215
+ return null;
216
+ }
217
+ const eta = acct.busy ? (acct.leasedAt ?? now) + LEASE_TTL_MS : acct.cooldownUntil;
218
+ if (earliest === null || eta < earliest) {
219
+ earliest = eta;
220
+ }
221
+ }
222
+ return earliest;
223
+ }
224
+ }
225
+
226
+ // ── Config loader ────────────────────────────────────────────────
227
+
228
+ function loadConfig() {
229
+ const configPath = join(homedir(), '.claude', 'cache', 'tfx-hub', 'accounts.json');
230
+ if (!existsSync(configPath)) return null;
231
+ try {
232
+ return JSON.parse(readFileSync(configPath, 'utf8'));
233
+ } catch {
234
+ return null;
235
+ }
236
+ }
237
+
238
+ // ── Singleton ────────────────────────────────────────────────────
239
+
240
+ function createBroker() {
241
+ const config = loadConfig();
242
+ if (!config) return null;
243
+ try {
244
+ return new AccountBroker(config);
245
+ } catch {
246
+ return null;
247
+ }
248
+ }
249
+
250
+ export const broker = createBroker();
251
+ export { AccountBroker };