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,111 @@
1
+ import { readdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join, relative, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ import {
6
+ buildSkillTemplateContext,
7
+ loadTemplatePartials,
8
+ parseFrontmatter,
9
+ renderSkillTemplate,
10
+ } from "./lib/skill-template.mjs";
11
+
12
+ function walkFiles(rootDir, currentDir = rootDir) {
13
+ const entries = readdirSync(currentDir, { withFileTypes: true });
14
+ const files = [];
15
+
16
+ for (const entry of entries) {
17
+ const fullPath = join(currentDir, entry.name);
18
+ if (entry.isDirectory()) {
19
+ if (entry.name.startsWith(".")) continue;
20
+ files.push(...walkFiles(rootDir, fullPath));
21
+ continue;
22
+ }
23
+
24
+ if (!entry.isFile()) continue;
25
+ files.push(fullPath);
26
+ }
27
+
28
+ return files;
29
+ }
30
+
31
+ function collectSkillTemplateFiles(skillsDir) {
32
+ return walkFiles(skillsDir)
33
+ .filter((file) => file.endsWith("SKILL.md.tmpl"))
34
+ .filter(
35
+ (file) =>
36
+ !relative(skillsDir, file).replace(/\\/g, "/").startsWith("_templates/"),
37
+ )
38
+ .sort((left, right) => left.localeCompare(right));
39
+ }
40
+
41
+ function resolveOutputPath(templatePath) {
42
+ if (!templatePath.endsWith(".tmpl")) {
43
+ throw new Error(`Template file must end with .tmpl: ${templatePath}`);
44
+ }
45
+ return templatePath.slice(0, -".tmpl".length);
46
+ }
47
+
48
+ function createRenderContext(templateContent, templatePath) {
49
+ const { data: frontmatter } = parseFrontmatter(templateContent);
50
+ const skillDirName = dirname(templatePath).split(/[/\\]/).pop() || "";
51
+ return buildSkillTemplateContext({ frontmatter, skillDirName });
52
+ }
53
+
54
+ export function generateSkillDocs({
55
+ skillsDir,
56
+ templatesDir = join(skillsDir, "_templates"),
57
+ write = true,
58
+ } = {}) {
59
+ if (!skillsDir) throw new Error("skillsDir is required");
60
+
61
+ const partials = loadTemplatePartials(templatesDir);
62
+ const templateFiles = collectSkillTemplateFiles(skillsDir);
63
+ const generated = [];
64
+
65
+ for (const templatePath of templateFiles) {
66
+ const templateContent = readFileSync(templatePath, "utf8");
67
+ const context = createRenderContext(templateContent, templatePath);
68
+ const rendered = renderSkillTemplate(templateContent, context, { partials });
69
+ const outputPath = resolveOutputPath(templatePath);
70
+
71
+ if (write) {
72
+ writeFileSync(outputPath, rendered, "utf8");
73
+ }
74
+
75
+ generated.push({
76
+ templatePath,
77
+ outputPath,
78
+ relativeTemplatePath: relative(skillsDir, templatePath).replace(/\\/g, "/"),
79
+ context,
80
+ rendered,
81
+ });
82
+ }
83
+
84
+ return {
85
+ generated,
86
+ count: generated.length,
87
+ };
88
+ }
89
+
90
+ function runCli() {
91
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
92
+ const repoRoot = resolve(scriptDir, "..");
93
+ const skillsDir = join(repoRoot, "skills");
94
+
95
+ const result = generateSkillDocs({ skillsDir });
96
+ console.log(`Generated ${result.count} SKILL.md file(s) from templates.`);
97
+ }
98
+
99
+ const isDirectRun = process.argv[1]
100
+ ? resolve(process.argv[1]) === fileURLToPath(import.meta.url)
101
+ : false;
102
+
103
+ if (isDirectRun) {
104
+ try {
105
+ runCli();
106
+ } catch (error) {
107
+ const message = error instanceof Error ? error.message : String(error);
108
+ console.error(`[gen-skill-docs] ${message}`);
109
+ process.exitCode = 1;
110
+ }
111
+ }
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env bash
2
+ # headless-guard-fast.sh — bash pre-filter for headless-guard.mjs
3
+ # psmux 미설치(캐시 ok=false) 시 Node.js 기동을 생략하여 89ms→~2ms로 단축
4
+ CACHE="${TMPDIR:-${TEMP:-/tmp}}/tfx-psmux-check.json"
5
+ GUARD_DIR="$(cd "$(dirname "$0")" && pwd)"
6
+
7
+ if [[ -f "$CACHE" ]]; then
8
+ # jq 없이 순수 bash로 파싱
9
+ ok_val=$(grep -o '"ok":[[:space:]]*\(true\|false\)' "$CACHE" | grep -o 'true\|false')
10
+ ts_val=$(grep -o '"ts":[[:space:]]*[0-9]*' "$CACHE" | grep -o '[0-9]*')
11
+ now_ms=$(($(date +%s) * 1000))
12
+ age_ms=$((now_ms - ${ts_val:-0}))
13
+
14
+ # 캐시 유효(5분 이내) + psmux 미설치 → 즉시 통과
15
+ if [[ "$ok_val" == "false" && $age_ms -lt 300000 ]]; then
16
+ exit 0
17
+ fi
18
+ fi
19
+
20
+ # 캐시 미스 또는 psmux 설치됨 → Node.js 실행
21
+ exec node "$GUARD_DIR/headless-guard.mjs"
@@ -0,0 +1,360 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * headless-guard.mjs — PreToolUse 훅 (상시 활성 auto-route)
4
+ *
5
+ * psmux가 설치된 환경에서 Bash(tfx-route.sh) 개별 호출을
6
+ * 자동으로 headless 명령으로 변환한다.
7
+ *
8
+ * v2: 마커 파일 의존 제거. psmux 설치 여부만으로 판단.
9
+ * Opus가 SKILL.md를 무시해도 auto-route가 작동한다.
10
+ *
11
+ * v3: A(gate) + B(nudge) — OMC 패턴 도입
12
+ * A: tfx-multi 활성 시 headless dispatch 전까지 Agent 작업 위임 차단
13
+ * B: dispatch 후 네이티브 드리프트 감지 시 nudge
14
+ * 상태: $TMPDIR/tfx-multi-state.json (tfx-multi-activate.mjs가 생성)
15
+ *
16
+ * 동작:
17
+ * - psmux 설치 + Bash(tfx-route.sh) → updatedInput: tfx multi --headless --assign
18
+ * - psmux 설치 + Bash(codex exec / gemini --prompt) → deny
19
+ * - psmux 설치 + Agent(codex/gemini CLI 래핑) → deny
20
+ * - psmux 미설치 → 전부 통과
21
+ * - tfx-multi 활성 + Agent(work) before dispatch → deny (A: gate)
22
+ * - tfx-multi 활성 + Agent(work) after dispatch → nudge (B: nudge)
23
+ *
24
+ * 성능: psmux 감지 결과를 5분간 캐시 ($TMPDIR/tfx-psmux-check.json)
25
+ */
26
+
27
+ import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
28
+ import { execFileSync } from "node:child_process";
29
+ import { tmpdir } from "node:os";
30
+ import { join } from "node:path";
31
+ import { nudge, deny } from "./lib/hook-utils.mjs";
32
+ import { probePsmuxSupport } from "./lib/psmux-info.mjs";
33
+
34
+ const CACHE_FILE = join(tmpdir(), "tfx-psmux-check.json");
35
+ const CACHE_TTL_MS = 5 * 60 * 1000; // 5분
36
+
37
+ // ── tfx-multi 상태 관리 (A+B) ──
38
+ const MULTI_STATE_FILE = join(tmpdir(), "tfx-multi-state.json");
39
+ const MULTI_EXPIRE_MS = 30 * 60 * 1000; // 30분 자동 만료
40
+ const GATE_THRESHOLD = 2; // A: dispatch 전 허용할 Agent 호출 수
41
+ const NUDGE_THRESHOLD = 4; // B: dispatch 후 nudge 트리거 횟수
42
+
43
+ function readMultiState() {
44
+ try {
45
+ if (!existsSync(MULTI_STATE_FILE)) return null;
46
+ const state = JSON.parse(readFileSync(MULTI_STATE_FILE, "utf8"));
47
+ if (!state.active) return null;
48
+ // 자동 만료
49
+ if (Date.now() - state.activatedAt > MULTI_EXPIRE_MS) {
50
+ try { unlinkSync(MULTI_STATE_FILE); } catch { /* ignore */ }
51
+ return null;
52
+ }
53
+ return state;
54
+ } catch { return null; }
55
+ }
56
+
57
+ function writeMultiState(state) {
58
+ try {
59
+ writeFileSync(MULTI_STATE_FILE, JSON.stringify(state));
60
+ } catch { /* ignore */ }
61
+ }
62
+
63
+ function isPsmuxInstalled() {
64
+ // 캐시 확인 (미래 타임스탬프 오염 방어)
65
+ try {
66
+ if (existsSync(CACHE_FILE)) {
67
+ const cache = JSON.parse(readFileSync(CACHE_FILE, "utf8"));
68
+ const age = Date.now() - cache.ts;
69
+ if (age >= 0 && age < CACHE_TTL_MS) return cache.ok;
70
+ // age < 0 → 미래 ts (오염) → 캐시 무시하고 재검사
71
+ }
72
+ } catch { /* cache miss */ }
73
+
74
+ const probe = probePsmuxSupport({ execFileSyncFn: execFileSync });
75
+ const ok = probe.ok;
76
+
77
+ // 캐시 저장
78
+ try {
79
+ writeFileSync(CACHE_FILE, JSON.stringify({ ts: Date.now(), ok, version: probe.version, missingCommands: probe.missingCommands }));
80
+ } catch { /* ignore */ }
81
+
82
+ return ok;
83
+ }
84
+
85
+ /**
86
+ * tfx-route.sh 명령에서 agent, prompt, mcp, 추가 플래그를 파싱한다.
87
+ * v3: 손실 없는 파싱 — 원본 명령의 모든 플래그를 보존.
88
+ */
89
+ function parseRouteCommand(cmd) {
90
+ const MCP_PROFILES = ["implement", "analyze", "review", "docs"];
91
+
92
+ const agentMatch = cmd.match(/tfx-route\.sh\s+(\S+)\s+/);
93
+ if (!agentMatch) return null;
94
+
95
+ const agent = agentMatch[1];
96
+ const afterAgent = cmd.slice(agentMatch.index + agentMatch[0].length);
97
+
98
+ let mcp = "";
99
+ let promptRaw = afterAgent;
100
+ for (const profile of MCP_PROFILES) {
101
+ const profileIdx = afterAgent.lastIndexOf(` ${profile}`);
102
+ if (profileIdx >= 0) {
103
+ mcp = profile;
104
+ promptRaw = afterAgent.slice(0, profileIdx);
105
+ break;
106
+ }
107
+ }
108
+
109
+ const prompt = promptRaw
110
+ .replace(/^['"]/, "")
111
+ .replace(/['"]$/, "")
112
+ .replace(/'\\''/g, "'")
113
+ .replace(/'"'"'/g, "'")
114
+ .trim();
115
+
116
+ // v3: 원본 명령에서 추가 플래그 추출
117
+ const flags = {};
118
+ const afterPrompt = cmd.replace(/'.+?'/gs, "").replace(/".+?"/gs, "");
119
+ const timeoutMatch = afterPrompt.match(/(?:^|\s)(\d{2,4})(?:\s|$)/); // 4번째 인자 (timeout)
120
+ if (timeoutMatch) flags.timeout = parseInt(timeoutMatch[1], 10);
121
+
122
+ // 환경변수 기반 글로벌 플래그
123
+ if (process.env.TFX_VERBOSE === "1") flags.verbose = true;
124
+ if (process.env.TFX_NO_AUTO_ATTACH === "1") flags.noAutoAttach = true;
125
+
126
+ return { agent, prompt, mcp, flags };
127
+ }
128
+
129
+ function autoRoute(updatedCommand, reason) {
130
+ process.stdout.write(JSON.stringify({
131
+ hookSpecificOutput: {
132
+ hookEventName: "PreToolUse",
133
+ updatedInput: { command: updatedCommand },
134
+ additionalContext: reason,
135
+ },
136
+ }));
137
+ process.exit(0);
138
+ }
139
+
140
+ const HEADLESS_FALLBACK_COMMAND =
141
+ 'Bash("tfx multi --teammate-mode headless --assign \'codex:prompt:role\' ...")';
142
+ const DIRECT_CLI_BYPASS_HINT =
143
+ "로컬 디버깅이 목적이면 TFX_ALLOW_DIRECT_CLI=1로 일시 우회할 수 있습니다.";
144
+
145
+ async function main() {
146
+ // P0: TFX_ALLOW_DIRECT_CLI 환경변수 바이패스 — psmux 세션 생성 불가 시 수동 활성화
147
+ if (process.env.TFX_ALLOW_DIRECT_CLI === "1") {
148
+ nudge("[headless-guard] direct CLI mode (TFX_ALLOW_DIRECT_CLI=1)");
149
+ }
150
+
151
+ // psmux 미설치 → 전부 통과
152
+ if (!isPsmuxInstalled()) process.exit(0);
153
+
154
+ let raw = "";
155
+ for await (const chunk of process.stdin) raw += chunk;
156
+
157
+ if (!raw || !raw.trim()) {
158
+ console.error('[headless-guard] stdin이 비어있습니다 — 기본 허용');
159
+ process.exit(0);
160
+ }
161
+
162
+ let input;
163
+ try {
164
+ input = JSON.parse(raw);
165
+ } catch {
166
+ process.exit(0);
167
+ }
168
+
169
+ const toolName = input.tool_name || "";
170
+ const toolInput = input.tool_input || {};
171
+
172
+ // ── Bash ──
173
+ if (toolName === "Bash") {
174
+ const cmd = toolInput.command || "";
175
+
176
+ // headless 명령은 통과 + dispatch 감지 (A: gate 해제)
177
+ if (cmd.includes("tfx multi") || cmd.includes("triflux.mjs multi")) {
178
+ const multiState = readMultiState();
179
+ if (multiState && cmd.includes("--assign")) {
180
+ multiState.dispatched = true;
181
+ multiState.nativeWorkCallsSinceDispatch = 0;
182
+ writeMultiState(multiState);
183
+ }
184
+ process.exit(0);
185
+ }
186
+
187
+ // psmux send-keys / capture-pane은 통과 (pane 내 간접 실행)
188
+ if (/psmux\s+(send-keys|capture-pane|list-panes|split-window|select-pane)/.test(cmd)) {
189
+ process.exit(0);
190
+ }
191
+
192
+ // codex/gemini 직접 CLI 호출 → deny (인라인 TFX_ALLOW_DIRECT_CLI=1 우회 허용)
193
+ // 복합 명령(&&, ||, ;) 분리 후 각 세그먼트의 커맨드 위치만 검사 (args/quotes 안의 codex는 무시)
194
+ const cmdParts = cmd.split(/\s*(?:&&|\|\||;)\s*/);
195
+ const hasDirectCli = cmdParts.some(part => {
196
+ const stripped = part.replace(/^\s*(?:[\w_]+=\S+\s+)*/, "");
197
+ return /^\s*codex\b.*\bexec\b/i.test(stripped) || /^\s*gemini\s+(-p|--prompt)\b/i.test(stripped);
198
+ });
199
+ if (hasDirectCli) {
200
+ if (/\bTFX_ALLOW_DIRECT_CLI=1\b/.test(cmd)) {
201
+ nudge("[headless-guard] direct CLI mode (inline TFX_ALLOW_DIRECT_CLI=1)");
202
+ }
203
+ deny(
204
+ "[headless-guard] codex/gemini 직접 호출은 spawn-session에서 차단됩니다. " +
205
+ `승인된 경로: ${HEADLESS_FALLBACK_COMMAND}. ` +
206
+ DIRECT_CLI_BYPASS_HINT,
207
+ );
208
+ }
209
+
210
+ // tfx-route.sh 실행만 감지: 명령이 bash로 시작할 때만 (커밋 메시지/echo 등 무시)
211
+ if (/^\s*bash\s+.*tfx-route\.sh\s/.test(cmd)) {
212
+ // --async, --job-status, --job-result, --job-wait는 tfx-route.sh 내부 플래그 → 통과
213
+ if (/tfx-route\.sh\s+--(async|job-status|job-result|job-wait)\b/.test(cmd)) {
214
+ process.exit(0);
215
+ }
216
+
217
+ const parsed = parseRouteCommand(cmd);
218
+ if (parsed) {
219
+ // P1a: 단일 워커는 headless 변환 건너뛰기 (직접 실행이 523~1173ms 절약)
220
+ // TFX_FORCE_HEADLESS=1이면 단일이어도 headless 변환 강제
221
+ if (!process.env.TFX_FORCE_HEADLESS) {
222
+ const isMultiWorker = /\s--(multi|parallel)\b/.test(cmd);
223
+ if (!isMultiWorker) {
224
+ process.exit(0); // 원본 tfx-route.sh 명령 그대로 통과
225
+ }
226
+ }
227
+
228
+ const safePrompt = parsed.prompt.replace(/'/g, "'\\''");
229
+ const VALID_MCP = new Set(["implement", "analyze", "review", "docs"]);
230
+ const f = parsed.flags || {};
231
+
232
+ // v3: 플래그 빌더 — 하드코딩 제거, 원본 의도 보존
233
+ const parts = ["tfx multi --teammate-mode headless"];
234
+ if (!f.noAutoAttach) parts.push("--auto-attach");
235
+ if (!f.noAutoAttach) parts.push("--dashboard"); // 워커 요약 스플릿이 기본
236
+ if (f.verbose) parts.push("--verbose");
237
+ parts.push(`--assign '${parsed.agent}:${safePrompt}:${parsed.agent}'`);
238
+ if (parsed.mcp && VALID_MCP.has(parsed.mcp)) parts.push(`--mcp-profile ${parsed.mcp}`);
239
+ parts.push(`--timeout ${f.timeout || 600}`);
240
+
241
+ const builtCmd = parts.join(" ");
242
+ autoRoute(
243
+ builtCmd,
244
+ `[headless-guard] auto-route: tfx-route.sh ${parsed.agent} → headless. mcp=${parsed.mcp} dashboard=${!f.noAutoAttach}`,
245
+ );
246
+ }
247
+ deny(
248
+ "[headless-guard] tfx-route.sh를 headless로 변환 실패. " +
249
+ 'Bash("tfx multi --teammate-mode headless --assign \'cli:prompt:role\' ...") 형식을 사용하세요.',
250
+ );
251
+ }
252
+ }
253
+
254
+ // ── Edit/Write: tfx-multi 코드 수정 게이트 ──
255
+ if (toolName === "Edit" || toolName === "Write") {
256
+ const multiState = readMultiState();
257
+
258
+ // 일반 모드(tfx-multi 비활성)에서는 기존처럼 통과
259
+ if (!multiState || !multiState.active) {
260
+ process.exit(0);
261
+ }
262
+
263
+ if (!multiState.dispatched) {
264
+ multiState.nativeWorkCalls = (multiState.nativeWorkCalls || 0) + 1;
265
+ writeMultiState(multiState);
266
+
267
+ if (multiState.nativeWorkCalls > GATE_THRESHOLD) {
268
+ deny(
269
+ `[headless-guard] tfx-multi gate: ${toolName} 호출 ${multiState.nativeWorkCalls}회 — headless dispatch 먼저 하세요.\n` +
270
+ 'Bash("tfx multi --teammate-mode headless --auto-attach --dashboard --assign \'codex:프롬프트:역할\' --timeout 600")',
271
+ );
272
+ }
273
+
274
+ nudge(
275
+ `[headless-guard] tfx-multi 활성 (${multiState.nativeWorkCalls}/${GATE_THRESHOLD}). ` +
276
+ "headless dispatch 후 작업을 시작하세요.",
277
+ );
278
+ }
279
+
280
+ // H3 fix: Agent gate와 동일하게 NUDGE_THRESHOLD 기반 주기적 nudge
281
+ multiState.nativeWorkCallsSinceDispatch = (multiState.nativeWorkCallsSinceDispatch || 0) + 1;
282
+ writeMultiState(multiState);
283
+
284
+ if (multiState.nativeWorkCallsSinceDispatch >= NUDGE_THRESHOLD) {
285
+ multiState.nativeWorkCallsSinceDispatch = 0;
286
+ writeMultiState(multiState);
287
+ nudge("[headless-guard] nudge: headless 워커가 코드 수정 중. 직접 수정은 충돌 위험.");
288
+ }
289
+ process.exit(0); // threshold 미만이면 조용히 통과
290
+ }
291
+
292
+ // ── Agent: A(gate) + B(nudge) + CLI 래핑 deny ──
293
+ if (toolName === "Agent") {
294
+ const subType = (toolInput.subagent_type || "").toLowerCase();
295
+ const NATIVE_TYPES = new Set(["explore", "plan", "general-purpose", ""]);
296
+ const isNative = NATIVE_TYPES.has(subType) || subType.startsWith("oh-my-claudecode:");
297
+
298
+ // ── A+B: tfx-multi 상태 기반 처리 ──
299
+ const multiState = readMultiState();
300
+ if (multiState && multiState.active && isNative) {
301
+ if (!multiState.dispatched) {
302
+ // ── A: gate — dispatch 전, Agent 작업 위임 제한 ──
303
+ multiState.nativeWorkCalls = (multiState.nativeWorkCalls || 0) + 1;
304
+ writeMultiState(multiState);
305
+
306
+ if (multiState.nativeWorkCalls > GATE_THRESHOLD) {
307
+ deny(
308
+ `[headless-guard] tfx-multi gate: Agent(${subType || "default"}) 호출 ${multiState.nativeWorkCalls}회 — headless에 먼저 dispatch하세요.\n` +
309
+ 'Bash("tfx multi --teammate-mode headless --auto-attach --dashboard --assign \'codex:프롬프트:역할\' --timeout 600")',
310
+ );
311
+ }
312
+ // 허용 범위 내 → 경고 + 통과
313
+ nudge(
314
+ `[headless-guard] tfx-multi 활성 (${multiState.nativeWorkCalls}/${GATE_THRESHOLD}). ` +
315
+ "headless dispatch 후 작업을 시작하세요.",
316
+ );
317
+ } else {
318
+ // ── B: nudge — dispatch 후, 네이티브 드리프트 감지 ──
319
+ multiState.nativeWorkCallsSinceDispatch = (multiState.nativeWorkCallsSinceDispatch || 0) + 1;
320
+ writeMultiState(multiState);
321
+
322
+ if (multiState.nativeWorkCallsSinceDispatch >= NUDGE_THRESHOLD) {
323
+ multiState.nativeWorkCallsSinceDispatch = 0;
324
+ writeMultiState(multiState);
325
+ nudge(
326
+ "[headless-guard] nudge: headless 워커가 실행 중입니다. " +
327
+ "결과를 기다리거나 추가 --assign으로 위임하세요.",
328
+ );
329
+ }
330
+ }
331
+ // native → 통과 (gate deny 안 걸린 경우)
332
+ process.exit(0);
333
+ }
334
+
335
+ // native → 통과 (tfx-multi 비활성)
336
+ if (isNative) process.exit(0);
337
+
338
+ // ── CLI 래핑 체크 (기존) ──
339
+ const combined = `${(toolInput.prompt || "").toLowerCase()} ${(toolInput.description || "").toLowerCase()}`;
340
+ const cliPatterns = [
341
+ /codex\s+(exec|run|실행)/,
342
+ /gemini\s+(-p|run|실행)/,
343
+ /tfx-route/,
344
+ /bash.*codex/,
345
+ /bash.*gemini/,
346
+ ];
347
+
348
+ if (cliPatterns.some((p) => p.test(combined))) {
349
+ deny(
350
+ "[headless-guard] Codex/Gemini를 Agent()로 래핑하지 마세요. " +
351
+ `승인된 경로: ${HEADLESS_FALLBACK_COMMAND}. ` +
352
+ DIRECT_CLI_BYPASS_HINT,
353
+ );
354
+ }
355
+ }
356
+
357
+ process.exit(0);
358
+ }
359
+
360
+ main().catch(() => process.exit(0));
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ // SessionStart 훅에서 호출되는 Hub 보장 스크립트.
3
+ // - /status 기반 헬스체크
4
+ // - 비정상 시 Hub를 detached로 기동
5
+
6
+ import { existsSync, readFileSync } from "fs";
7
+ import { join, dirname } from "path";
8
+ import { homedir } from "os";
9
+ import { spawn } from "child_process";
10
+ import { fileURLToPath } from "url";
11
+
12
+ const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
13
+ const PLUGIN_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
14
+ const HUB_PID_FILE = join(homedir(), ".claude", "cache", "tfx-hub", "hub.pid");
15
+
16
+ function formatHostForUrl(host) {
17
+ return host.includes(":") ? `[${host}]` : host;
18
+ }
19
+
20
+ function buildHubBaseUrl(host, port) {
21
+ return `http://${formatHostForUrl(host)}:${port}`;
22
+ }
23
+
24
+ function resolveHubTarget() {
25
+ const envPortRaw = Number(process.env.TFX_HUB_PORT || "");
26
+ const envPort = Number.isFinite(envPortRaw) && envPortRaw > 0 ? envPortRaw : null;
27
+ const target = {
28
+ host: "127.0.0.1",
29
+ port: envPort || 27888,
30
+ };
31
+
32
+ if (existsSync(HUB_PID_FILE)) {
33
+ try {
34
+ const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
35
+ if (!envPort) {
36
+ const pidPort = Number(info?.port);
37
+ if (Number.isFinite(pidPort) && pidPort > 0) target.port = pidPort;
38
+ }
39
+ if (typeof info?.host === "string") {
40
+ const host = info.host.trim();
41
+ if (LOOPBACK_HOSTS.has(host)) target.host = host;
42
+ }
43
+ } catch {
44
+ // ignore parse errors and use env/default
45
+ }
46
+ }
47
+
48
+ return target;
49
+ }
50
+
51
+ async function isHubHealthy(host, port) {
52
+ try {
53
+ const res = await fetch(`${buildHubBaseUrl(host, port)}/status`, {
54
+ signal: AbortSignal.timeout(1000),
55
+ });
56
+ if (!res.ok) return false;
57
+ const data = await res.json();
58
+ return data?.hub?.state === "healthy";
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+
64
+ function startHubDetached(port) {
65
+ const serverPath = join(PLUGIN_ROOT, "hub", "server.mjs");
66
+ if (!existsSync(serverPath)) return false;
67
+
68
+ try {
69
+ const env = { ...process.env, TFX_HUB_PORT: String(port) };
70
+ if (process.platform === "win32") {
71
+ // Windows: cmd.exe /c start /b → 완전 독립 프로세스 트리 생성
72
+ // hook timeout 시 프로세스 트리 킬에서 살아남음
73
+ const child = spawn("cmd.exe", ["/c", "start", "/b", "", process.execPath, serverPath], {
74
+ env,
75
+ stdio: "ignore",
76
+ windowsHide: true,
77
+ });
78
+ child.unref();
79
+ } else {
80
+ const child = spawn(process.execPath, [serverPath], {
81
+ env,
82
+ detached: true,
83
+ stdio: "ignore",
84
+ });
85
+ child.unref();
86
+ }
87
+ return true;
88
+ } catch {
89
+ return false;
90
+ }
91
+ }
92
+
93
+ /** Hub 기동 후 ready 상태까지 대기 (최대 maxWaitMs) */
94
+ async function waitForHubReady(host, port, maxWaitMs = 5000) {
95
+ const interval = 250;
96
+ const deadline = Date.now() + maxWaitMs;
97
+ while (Date.now() < deadline) {
98
+ if (await isHubHealthy(host, port)) return true;
99
+ await new Promise((r) => setTimeout(r, interval));
100
+ }
101
+ return false;
102
+ }
103
+
104
+ const { host, port } = resolveHubTarget();
105
+ if (!(await isHubHealthy(host, port))) {
106
+ const started = startHubDetached(port);
107
+ if (started) {
108
+ const ready = await waitForHubReady(host, port, 3000);
109
+ if (ready) {
110
+ process.stdout.write("hub: ok");
111
+ } else {
112
+ // fire-and-forget: hub이 아직 기동 중일 수 있음 — 에러가 아닌 경고
113
+ process.stdout.write("hub: starting");
114
+ }
115
+ } else {
116
+ process.stderr.write("[hub-ensure] hub 시작 실패");
117
+ }
118
+ } else {
119
+ process.stdout.write("hub: ok");
120
+ }