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,165 @@
1
+ const DEFAULT_MAX_QUEUE_SIZE = 100;
2
+ const DEFAULT_TTL_MS = 0; // 0 = no expiry
3
+
4
+ /**
5
+ * Creates a per-agent message queue with TTL and size limits.
6
+ *
7
+ * @param {object} [opts]
8
+ * @param {number} [opts.maxQueueSize=100] - Max messages per agent queue
9
+ * @param {number} [opts.ttlMs=0] - Message TTL in ms (0 = no expiry)
10
+ * @returns {object} Queue API
11
+ */
12
+ export function createMessageQueue(opts = {}) {
13
+ const {
14
+ maxQueueSize = DEFAULT_MAX_QUEUE_SIZE,
15
+ ttlMs = DEFAULT_TTL_MS,
16
+ } = opts;
17
+
18
+ /** @type {Map<string, Array<{ message: object, enqueuedAt: number }>>} */
19
+ const queues = new Map();
20
+
21
+ /**
22
+ * Returns the queue array for an agent (creates if absent).
23
+ * @param {string} agentId
24
+ * @returns {Array}
25
+ */
26
+ function getQueue(agentId) {
27
+ let q = queues.get(agentId);
28
+ if (!q) {
29
+ q = [];
30
+ queues.set(agentId, q);
31
+ }
32
+ return q;
33
+ }
34
+
35
+ /**
36
+ * Removes expired messages from the front of a queue.
37
+ * @param {Array} q
38
+ * @param {number} now
39
+ */
40
+ function purgeExpired(q, now) {
41
+ if (ttlMs <= 0) return;
42
+ while (q.length > 0 && (now - q[0].enqueuedAt) > ttlMs) {
43
+ q.shift();
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Adds a message to the target agent's queue.
49
+ * If queue exceeds maxQueueSize, the oldest message is dropped.
50
+ *
51
+ * @param {string} agentId - Target agent
52
+ * @param {object} message - Mesh message
53
+ * @returns {{ queued: boolean, dropped: boolean }}
54
+ */
55
+ function enqueue(agentId, message) {
56
+ if (!agentId || typeof agentId !== "string") {
57
+ throw new TypeError("agentId must be a non-empty string");
58
+ }
59
+ const q = getQueue(agentId);
60
+ const now = Date.now();
61
+
62
+ purgeExpired(q, now);
63
+
64
+ let dropped = false;
65
+ if (q.length >= maxQueueSize) {
66
+ q.shift();
67
+ dropped = true;
68
+ }
69
+
70
+ q.push({ message, enqueuedAt: now });
71
+ return { queued: true, dropped };
72
+ }
73
+
74
+ /**
75
+ * Removes and returns the next message for an agent.
76
+ *
77
+ * @param {string} agentId
78
+ * @returns {object | null} The message, or null if queue is empty
79
+ */
80
+ function dequeue(agentId) {
81
+ const q = queues.get(agentId);
82
+ if (!q || q.length === 0) return null;
83
+
84
+ const now = Date.now();
85
+ purgeExpired(q, now);
86
+
87
+ if (q.length === 0) return null;
88
+ return q.shift().message;
89
+ }
90
+
91
+ /**
92
+ * Returns the next message without removing it.
93
+ *
94
+ * @param {string} agentId
95
+ * @returns {object | null}
96
+ */
97
+ function peek(agentId) {
98
+ const q = queues.get(agentId);
99
+ if (!q || q.length === 0) return null;
100
+
101
+ const now = Date.now();
102
+ purgeExpired(q, now);
103
+
104
+ if (q.length === 0) return null;
105
+ return q[0].message;
106
+ }
107
+
108
+ /**
109
+ * Returns the number of (non-expired) messages in an agent's queue.
110
+ *
111
+ * @param {string} agentId
112
+ * @returns {number}
113
+ */
114
+ function size(agentId) {
115
+ const q = queues.get(agentId);
116
+ if (!q) return 0;
117
+
118
+ const now = Date.now();
119
+ purgeExpired(q, now);
120
+
121
+ return q.length;
122
+ }
123
+
124
+ /**
125
+ * Drains all messages for an agent.
126
+ *
127
+ * @param {string} agentId
128
+ * @returns {object[]} Array of messages
129
+ */
130
+ function drain(agentId) {
131
+ const q = queues.get(agentId);
132
+ if (!q || q.length === 0) return [];
133
+
134
+ const now = Date.now();
135
+ purgeExpired(q, now);
136
+
137
+ const messages = q.map((entry) => entry.message);
138
+ q.length = 0;
139
+ return messages;
140
+ }
141
+
142
+ /**
143
+ * Removes an agent's queue entirely.
144
+ * @param {string} agentId
145
+ */
146
+ function clear(agentId) {
147
+ queues.delete(agentId);
148
+ }
149
+
150
+ /**
151
+ * Returns total message count across all agent queues.
152
+ * @returns {number}
153
+ */
154
+ function totalSize() {
155
+ let total = 0;
156
+ const now = Date.now();
157
+ for (const [, q] of queues) {
158
+ purgeExpired(q, now);
159
+ total += q.length;
160
+ }
161
+ return total;
162
+ }
163
+
164
+ return { enqueue, dequeue, peek, size, drain, clear, totalSize };
165
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Creates an agent registry for the mesh network.
3
+ * Agents register with capabilities; registry enables discovery.
4
+ * @returns {object} Registry API
5
+ */
6
+ export function createRegistry() {
7
+ // Map<agentId, AgentInfo>
8
+ const agents = new Map();
9
+
10
+ /**
11
+ * Registers an agent with the registry.
12
+ * @param {string} agentId
13
+ * @param {string[]} capabilities
14
+ */
15
+ function register(agentId, capabilities = []) {
16
+ if (!agentId || typeof agentId !== "string") {
17
+ throw new TypeError("agentId must be a non-empty string");
18
+ }
19
+ if (!Array.isArray(capabilities)) {
20
+ throw new TypeError("capabilities must be an array");
21
+ }
22
+ const info = Object.freeze({
23
+ agentId,
24
+ capabilities: Object.freeze([...capabilities]),
25
+ registeredAt: new Date().toISOString(),
26
+ });
27
+ agents.set(agentId, info);
28
+ }
29
+
30
+ /**
31
+ * Unregisters an agent from the registry.
32
+ * @param {string} agentId
33
+ */
34
+ function unregister(agentId) {
35
+ agents.delete(agentId);
36
+ }
37
+
38
+ /**
39
+ * Discovers agents that have a specific capability.
40
+ * @param {string} capability
41
+ * @returns {string[]} Array of agentIds
42
+ */
43
+ function discover(capability) {
44
+ const result = [];
45
+ for (const [agentId, info] of agents) {
46
+ if (info.capabilities.includes(capability)) {
47
+ result.push(agentId);
48
+ }
49
+ }
50
+ return result;
51
+ }
52
+
53
+ /**
54
+ * Gets agent info by ID.
55
+ * @param {string} agentId
56
+ * @returns {object | null}
57
+ */
58
+ function getAgent(agentId) {
59
+ return agents.get(agentId) ?? null;
60
+ }
61
+
62
+ /**
63
+ * Lists all registered agents.
64
+ * @returns {object[]}
65
+ */
66
+ function listAll() {
67
+ return [...agents.values()];
68
+ }
69
+
70
+ /**
71
+ * Clears all registered agents.
72
+ */
73
+ function clear() {
74
+ agents.clear();
75
+ }
76
+
77
+ return { register, unregister, discover, getAgent, listAll, clear };
78
+ }
@@ -0,0 +1,76 @@
1
+ import { validate } from "./mesh-protocol.mjs";
2
+
3
+ /**
4
+ * Routes a mesh message to target agent(s) based on the `to` field.
5
+ *
6
+ * Addressing modes:
7
+ * - "agent-id" → direct delivery (registry lookup)
8
+ * - "*" → broadcast to all registered agents
9
+ * - "capability:X" → discover agents with capability X
10
+ *
11
+ * @param {object} message - A mesh-protocol message
12
+ * @param {object} registry - A mesh-registry instance
13
+ * @returns {{ routed: boolean, targets?: string[], reason?: string }}
14
+ */
15
+ export function routeMessage(message, registry) {
16
+ const { valid, errors } = validate(message);
17
+ if (!valid) {
18
+ return { routed: false, reason: `invalid message: ${errors.join(", ")}` };
19
+ }
20
+
21
+ const { to, from } = message;
22
+
23
+ // Broadcast
24
+ if (to === "*") {
25
+ const all = registry.listAll();
26
+ const targets = all
27
+ .map((a) => a.agentId)
28
+ .filter((id) => id !== from);
29
+ if (targets.length === 0) {
30
+ return { routed: false, reason: "broadcast: no agents registered" };
31
+ }
32
+ return { routed: true, targets };
33
+ }
34
+
35
+ // Capability-based routing
36
+ if (to.startsWith("capability:")) {
37
+ const capability = to.slice("capability:".length);
38
+ if (!capability) {
39
+ return { routed: false, reason: "capability: empty capability name" };
40
+ }
41
+ const targets = registry.discover(capability);
42
+ if (targets.length === 0) {
43
+ return { routed: false, reason: `capability: no agents with "${capability}"` };
44
+ }
45
+ return { routed: true, targets };
46
+ }
47
+
48
+ // Direct addressing
49
+ const agent = registry.getAgent(to);
50
+ if (!agent) {
51
+ return { routed: false, reason: `agent not found: "${to}"` };
52
+ }
53
+ return { routed: true, targets: [to] };
54
+ }
55
+
56
+ /**
57
+ * Routes a message and collects dead-letter info when delivery fails.
58
+ *
59
+ * @param {object} message
60
+ * @param {object} registry
61
+ * @returns {{ routed: boolean, targets?: string[], deadLetter?: object }}
62
+ */
63
+ export function routeOrDeadLetter(message, registry) {
64
+ const result = routeMessage(message, registry);
65
+ if (!result.routed) {
66
+ return {
67
+ ...result,
68
+ deadLetter: {
69
+ originalMessage: message,
70
+ reason: result.reason,
71
+ timestamp: new Date().toISOString(),
72
+ },
73
+ };
74
+ }
75
+ return result;
76
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "triflux",
3
- "version": "10.0.0",
3
+ "version": "10.0.2",
4
4
  "description": "CLI-first multi-model orchestrator for Claude Code — route tasks to Codex, Gemini, and Claude",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,6 +21,13 @@
21
21
  "files": [
22
22
  "bin",
23
23
  "skills",
24
+ "hooks",
25
+ "hud",
26
+ "scripts",
27
+ "hub",
28
+ "mesh",
29
+ "references",
30
+ "CLAUDE.md",
24
31
  "README.md",
25
32
  "LICENSE"
26
33
  ],
@@ -0,0 +1,33 @@
1
+ {
2
+ "hosts": {
3
+ "ultra4": {
4
+ "description": "Windows 데스크탑 (22코어/64GB RAM)",
5
+ "aliases": [
6
+ "울트라",
7
+ "울트라4",
8
+ "데스크탑"
9
+ ],
10
+ "default_dir": "~/Desktop/Projects/cli/triflux",
11
+ "tailscale": {
12
+ "ip": "100.110.136.64",
13
+ "dns": "ultra-book-4-1",
14
+ "ssh_mode": "ssh-over-vpn"
15
+ },
16
+ "ssh_user": "SSAFY",
17
+ "os": "windows",
18
+ "specs": {
19
+ "cores": 22,
20
+ "ram_gb": 64,
21
+ "codex": "0.118.0",
22
+ "node": "24.14.0"
23
+ },
24
+ "capabilities": ["codex", "claude", "high-memory"]
25
+ }
26
+ },
27
+ "default_host": "ultra4",
28
+ "triggers": [
29
+ "원격에서",
30
+ "다른 머신에서",
31
+ "다른 컴퓨터에서"
32
+ ]
33
+ }
@@ -0,0 +1,87 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { describe, it } from "node:test";
6
+
7
+ import { generateSkillDocs } from "../gen-skill-docs.mjs";
8
+
9
+ function makeTempDir() {
10
+ return mkdtempSync(join(tmpdir(), "tfx-gen-skill-docs-"));
11
+ }
12
+
13
+ describe("gen-skill-docs", () => {
14
+ it("SKILL.md.tmpl 파일을 SKILL.md로 생성한다", () => {
15
+ const root = makeTempDir();
16
+ try {
17
+ const skillsDir = join(root, "skills");
18
+ const templatesDir = join(skillsDir, "_templates");
19
+ const skillDir = join(skillsDir, "tfx-sample");
20
+
21
+ mkdirSync(templatesDir, { recursive: true });
22
+ mkdirSync(skillDir, { recursive: true });
23
+
24
+ writeFileSync(join(templatesDir, "base.md"), "BASE {{SKILL_NAME}}", "utf8");
25
+ writeFileSync(join(templatesDir, "deep.md"), "DEEP BLOCK", "utf8");
26
+
27
+ writeFileSync(
28
+ join(skillDir, "SKILL.md.tmpl"),
29
+ [
30
+ "---",
31
+ "name: tfx-sample",
32
+ "description: sample skill",
33
+ "---",
34
+ "{{> base}}",
35
+ "{{#if DEEP}}",
36
+ "{{> deep}}",
37
+ "{{/if}}",
38
+ ].join("\n"),
39
+ "utf8",
40
+ );
41
+
42
+ const result = generateSkillDocs({ skillsDir, templatesDir, write: true });
43
+ assert.equal(result.count, 1);
44
+
45
+ const output = readFileSync(join(skillDir, "SKILL.md"), "utf8");
46
+ assert.match(output, /BASE tfx-sample/);
47
+ assert.doesNotMatch(output, /DEEP BLOCK/);
48
+ } finally {
49
+ rmSync(root, { recursive: true, force: true });
50
+ }
51
+ });
52
+
53
+ it("deep 스킬은 DEEP 조건부 섹션을 포함한다", () => {
54
+ const root = makeTempDir();
55
+ try {
56
+ const skillsDir = join(root, "skills");
57
+ const templatesDir = join(skillsDir, "_templates");
58
+ const skillDir = join(skillsDir, "tfx-deep-sample");
59
+
60
+ mkdirSync(templatesDir, { recursive: true });
61
+ mkdirSync(skillDir, { recursive: true });
62
+
63
+ writeFileSync(join(templatesDir, "base.md"), "BASE", "utf8");
64
+ writeFileSync(join(templatesDir, "deep.md"), "DEEP {{SKILL_NAME}}", "utf8");
65
+ writeFileSync(
66
+ join(skillDir, "SKILL.md.tmpl"),
67
+ [
68
+ "---",
69
+ "name: tfx-deep-sample",
70
+ "description: deep sample",
71
+ "---",
72
+ "{{> base}}",
73
+ "{{#if DEEP}}",
74
+ "{{> deep}}",
75
+ "{{/if}}",
76
+ ].join("\n"),
77
+ "utf8",
78
+ );
79
+
80
+ generateSkillDocs({ skillsDir, templatesDir, write: true });
81
+ const output = readFileSync(join(skillDir, "SKILL.md"), "utf8");
82
+ assert.match(output, /DEEP tfx-deep-sample/);
83
+ } finally {
84
+ rmSync(root, { recursive: true, force: true });
85
+ }
86
+ });
87
+ });
@@ -0,0 +1,234 @@
1
+ import assert from "node:assert/strict";
2
+ import { spawnSync } from "node:child_process";
3
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import test from "node:test";
7
+ import { fileURLToPath } from "node:url";
8
+ import { compileRules, loadRules, matchRules, resolveConflicts } from "../lib/keyword-rules.mjs";
9
+
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ const projectRoot = resolve(__dirname, "..", "..");
12
+ const rulesPath = join(projectRoot, "hooks", "keyword-rules.json");
13
+ const detectorScriptPath = join(projectRoot, "scripts", "keyword-detector.mjs");
14
+
15
+ // keyword-detector는 import 시 main()이 실행되므로, 테스트 로딩 단계에서만 안전하게 비활성화한다.
16
+ const previousDisable = process.env.TRIFLUX_DISABLE_MAGICWORDS;
17
+ const previousLog = console.log;
18
+ process.env.TRIFLUX_DISABLE_MAGICWORDS = "1";
19
+ console.log = () => {};
20
+ const detectorModule = await import("../keyword-detector.mjs");
21
+ console.log = previousLog;
22
+ if (previousDisable === undefined) {
23
+ delete process.env.TRIFLUX_DISABLE_MAGICWORDS;
24
+ } else {
25
+ process.env.TRIFLUX_DISABLE_MAGICWORDS = previousDisable;
26
+ }
27
+
28
+ const { extractPrompt, sanitizeForKeywordDetection } = detectorModule;
29
+
30
+ function loadCompiledRules() {
31
+ const rules = loadRules(rulesPath);
32
+ assert.equal(rules.length, 33);
33
+ return compileRules(rules);
34
+ }
35
+
36
+ function runDetector(prompt) {
37
+ const payload = { prompt, cwd: projectRoot };
38
+ const result = spawnSync(process.execPath, [detectorScriptPath], {
39
+ input: JSON.stringify(payload),
40
+ encoding: "utf8"
41
+ });
42
+
43
+ assert.equal(result.status, 0, result.stderr);
44
+ assert.ok(result.stdout.trim(), "keyword-detector 출력이 비어 있습니다.");
45
+ return JSON.parse(result.stdout.trim());
46
+ }
47
+
48
+ test("extractPrompt: prompt/message.content/parts[].text 우선순위", () => {
49
+ assert.equal(
50
+ extractPrompt({
51
+ prompt: "from prompt",
52
+ message: { content: "from message" },
53
+ parts: [{ text: "from parts" }]
54
+ }),
55
+ "from prompt"
56
+ );
57
+
58
+ assert.equal(
59
+ extractPrompt({
60
+ prompt: " ",
61
+ message: { content: "from message" },
62
+ parts: [{ text: "from parts" }]
63
+ }),
64
+ "from message"
65
+ );
66
+
67
+ assert.equal(
68
+ extractPrompt({
69
+ message: { content: [{ text: "from message-part" }] },
70
+ parts: [{ text: "from parts" }]
71
+ }),
72
+ "from message-part"
73
+ );
74
+
75
+ assert.equal(extractPrompt({ parts: [{ text: "from parts" }] }), "from parts");
76
+ });
77
+
78
+ test("sanitizeForKeywordDetection: 코드블록/URL/파일경로/XML 태그 제거", () => {
79
+ const input = [
80
+ "정상 문장",
81
+ "```sh",
82
+ "tfx multi",
83
+ "```",
84
+ "https://example.com/path?q=1",
85
+ "C:\\Users\\SSAFY\\Desktop\\Projects\\tools\\triflux",
86
+ "./hooks/keyword-rules.json",
87
+ "<tag>jira 이슈 생성</tag>"
88
+ ].join("\n");
89
+
90
+ const sanitized = sanitizeForKeywordDetection(input);
91
+
92
+ assert.ok(sanitized.includes("정상 문장"));
93
+ assert.ok(!sanitized.includes("tfx multi"));
94
+ assert.ok(!sanitized.includes("https://"));
95
+ assert.ok(!sanitized.includes("C:\\Users\\"));
96
+ assert.ok(!sanitized.includes("./hooks/keyword-rules.json"));
97
+ assert.ok(!sanitized.includes("<tag>"));
98
+ assert.ok(!sanitized.includes("jira 이슈 생성"));
99
+ });
100
+
101
+ test("loadRules: 유효한 JSON 로드", () => {
102
+ const rules = loadRules(rulesPath);
103
+ assert.equal(rules.length, 33);
104
+ assert.equal(rules.filter((rule) => rule.skill).length, 20);
105
+ assert.equal(rules.filter((rule) => rule.mcp_route).length, 10);
106
+ });
107
+
108
+ test("loadRules: 잘못된 파일 처리", () => {
109
+ const tempDir = mkdtempSync(join(tmpdir(), "triflux-rules-"));
110
+ const invalidPath = join(tempDir, "invalid.json");
111
+ writeFileSync(invalidPath, "{ invalid json", "utf8");
112
+
113
+ const malformed = loadRules(invalidPath);
114
+ const missing = loadRules(join(tempDir, "missing.json"));
115
+
116
+ assert.deepEqual(malformed, []);
117
+ assert.deepEqual(missing, []);
118
+
119
+ rmSync(tempDir, { recursive: true, force: true });
120
+ });
121
+
122
+ test("compileRules: 정규식 컴파일 성공", () => {
123
+ const rules = loadRules(rulesPath);
124
+ const compiled = compileRules(rules);
125
+ assert.equal(compiled.length, 33);
126
+ for (const rule of compiled) {
127
+ assert.ok(Array.isArray(rule.compiledPatterns));
128
+ assert.ok(rule.compiledPatterns.length > 0);
129
+ for (const pattern of rule.compiledPatterns) {
130
+ assert.ok(pattern instanceof RegExp);
131
+ }
132
+ }
133
+ });
134
+
135
+ test("compileRules: 정규식 컴파일 실패", () => {
136
+ const compiled = compileRules([
137
+ {
138
+ id: "bad-pattern",
139
+ priority: 1,
140
+ patterns: [{ source: "[", flags: "" }],
141
+ skill: "tfx-multi",
142
+ supersedes: [],
143
+ exclusive: false,
144
+ state: null,
145
+ mcp_route: null
146
+ }
147
+ ]);
148
+
149
+ assert.deepEqual(compiled, []);
150
+ });
151
+
152
+ test("matchRules: tfx 키워드 매칭", () => {
153
+ const compiledRules = loadCompiledRules();
154
+ const cases = [
155
+ { text: "tfx multi 세션 시작", expectedId: "tfx-multi" },
156
+ { text: "tfx auto 돌려줘", expectedId: "tfx-auto" },
157
+ { text: "tfx codex 로 실행", expectedId: "tfx-codex" },
158
+ { text: "tfx gemini 로 실행", expectedId: "tfx-gemini" },
159
+ { text: "canceltfx", expectedId: "tfx-cancel" }
160
+ ];
161
+
162
+ for (const { text, expectedId } of cases) {
163
+ const clean = sanitizeForKeywordDetection(text);
164
+ const matches = matchRules(compiledRules, clean);
165
+ assert.ok(matches.some((match) => match.id === expectedId), `${text} => ${expectedId} 미매칭`);
166
+ }
167
+ });
168
+
169
+ test("matchRules: MCP 라우팅 매칭", () => {
170
+ const compiledRules = loadCompiledRules();
171
+ const cases = [
172
+ { text: "노션 페이지 조회해줘", expectedId: "notion-route", expectedRoute: "gemini" },
173
+ { text: "jira 이슈 생성", expectedId: "jira-route", expectedRoute: "codex" },
174
+ { text: "크롬 열고 로그인", expectedId: "chrome-route", expectedRoute: "gemini" },
175
+ { text: "이메일 보내줘", expectedId: "mail-route", expectedRoute: "gemini" },
176
+ { text: "캘린더 일정 생성", expectedId: "calendar-route", expectedRoute: "gemini" },
177
+ { text: "playwright 테스트 작성", expectedId: "playwright-route", expectedRoute: "gemini" },
178
+ { text: "canva 디자인 생성", expectedId: "canva-route", expectedRoute: "gemini" }
179
+ ];
180
+
181
+ for (const { text, expectedId, expectedRoute } of cases) {
182
+ const matches = matchRules(compiledRules, sanitizeForKeywordDetection(text));
183
+ const matched = matches.find((match) => match.id === expectedId);
184
+ assert.ok(matched, `${text} => ${expectedId} 미매칭`);
185
+ assert.equal(matched.mcp_route, expectedRoute);
186
+ }
187
+ });
188
+
189
+ test("matchRules: 일반 대화는 매칭 없음", () => {
190
+ const compiledRules = loadCompiledRules();
191
+ const matches = matchRules(compiledRules, sanitizeForKeywordDetection("오늘 점심 메뉴 추천해줘"));
192
+ assert.deepEqual(matches, []);
193
+ });
194
+
195
+ test("resolveConflicts: priority 정렬 및 supersedes 처리", () => {
196
+ const resolved = resolveConflicts([
197
+ { id: "rule-c", priority: 3, supersedes: [], exclusive: false },
198
+ { id: "rule-b", priority: 2, supersedes: ["rule-c"], exclusive: false },
199
+ { id: "rule-a", priority: 1, supersedes: [], exclusive: false },
200
+ { id: "rule-a", priority: 1, supersedes: [], exclusive: false }
201
+ ]);
202
+
203
+ assert.deepEqual(
204
+ resolved.map((rule) => rule.id),
205
+ ["rule-a", "rule-b"]
206
+ );
207
+ });
208
+
209
+ test("resolveConflicts: exclusive 처리", () => {
210
+ const resolved = resolveConflicts([
211
+ { id: "normal", priority: 1, supersedes: [], exclusive: false },
212
+ { id: "exclusive", priority: 0, supersedes: [], exclusive: true },
213
+ { id: "later", priority: 2, supersedes: [], exclusive: false }
214
+ ]);
215
+
216
+ assert.deepEqual(resolved.map((rule) => rule.id), ["exclusive"]);
217
+ });
218
+
219
+ test("코드블록 내 키워드: sanitize 후 매칭 안 됨", () => {
220
+ const compiledRules = loadCompiledRules();
221
+ const input = ["```txt", "tfx multi", "jira 이슈 생성", "```"].join("\n");
222
+ const clean = sanitizeForKeywordDetection(input);
223
+ const matches = matchRules(compiledRules, clean);
224
+ assert.deepEqual(matches, []);
225
+ });
226
+
227
+ test("OMC 키워드와 triflux 키워드 비간섭 + TRIFLUX 네임스페이스", () => {
228
+ const omcLike = runDetector("my tfx multi 세션 보여줘");
229
+ assert.equal(omcLike.suppressOutput, true);
230
+
231
+ const triflux = runDetector("tfx multi 세션 시작");
232
+ const additionalContext = triflux?.hookSpecificOutput?.additionalContext || "";
233
+ assert.match(additionalContext, /^\[TRIFLUX MAGIC KEYWORD: tfx-multi\]/);
234
+ });