triflux 10.3.4 → 10.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/LICENSE +21 -21
  2. package/bin/tfx-doctor-tui.mjs +1 -1
  3. package/bin/tfx-doctor.mjs +6 -1
  4. package/bin/tfx-profile.mjs +1 -1
  5. package/bin/tfx-setup-tui.mjs +1 -1
  6. package/bin/tfx-setup.mjs +6 -1
  7. package/bin/triflux.mjs +2396 -1140
  8. package/hooks/agent-route-guard.mjs +12 -8
  9. package/hooks/cross-review-tracker.mjs +21 -8
  10. package/hooks/error-context.mjs +19 -7
  11. package/hooks/hook-adaptive-collector.mjs +18 -16
  12. package/hooks/hook-manager.mjs +93 -32
  13. package/hooks/hook-orchestrator.mjs +108 -24
  14. package/hooks/hook-registry.json +11 -0
  15. package/hooks/keyword-rules.json +6 -10
  16. package/hooks/lib/resolve-root.mjs +1 -1
  17. package/hooks/mcp-config-watcher.mjs +6 -2
  18. package/hooks/pipeline-stop.mjs +3 -6
  19. package/hooks/safety-guard.mjs +99 -28
  20. package/hooks/session-start-fast.mjs +143 -0
  21. package/hooks/subagent-verifier.mjs +5 -4
  22. package/hub/account-broker.mjs +256 -60
  23. package/hub/adaptive-diagnostic.mjs +75 -48
  24. package/hub/adaptive-inject.mjs +95 -57
  25. package/hub/adaptive-memory.mjs +156 -42
  26. package/hub/adaptive.mjs +60 -31
  27. package/hub/assign-callbacks.mjs +67 -30
  28. package/hub/bridge.mjs +0 -1
  29. package/hub/cli-adapter-base.mjs +200 -48
  30. package/hub/codex-adapter.mjs +76 -96
  31. package/hub/codex-compat.mjs +3 -3
  32. package/hub/codex-preflight.mjs +63 -37
  33. package/hub/delegator/contracts.mjs +19 -23
  34. package/hub/delegator/index.mjs +3 -3
  35. package/hub/delegator/service.mjs +88 -64
  36. package/hub/delegator/tool-definitions.mjs +5 -5
  37. package/hub/fullcycle.mjs +33 -17
  38. package/hub/gemini-adapter.mjs +69 -94
  39. package/hub/hitl.mjs +89 -30
  40. package/hub/intent.mjs +161 -38
  41. package/hub/lib/cache-guard.mjs +43 -17
  42. package/hub/lib/mcp-response-cache.mjs +66 -32
  43. package/hub/lib/memory-store.mjs +285 -111
  44. package/hub/lib/path-utils.mjs +35 -37
  45. package/hub/lib/process-utils.mjs +106 -37
  46. package/hub/lib/spawn-trace.mjs +527 -0
  47. package/hub/lib/ssh-command.mjs +34 -4
  48. package/hub/lib/ssh-retry.mjs +5 -1
  49. package/hub/lib/uuidv7.mjs +4 -3
  50. package/hub/memory-doctor.mjs +266 -106
  51. package/hub/middleware/request-logger.mjs +61 -34
  52. package/hub/paths.mjs +9 -9
  53. package/hub/pipeline/gates/confidence.mjs +34 -15
  54. package/hub/pipeline/gates/consensus.mjs +27 -15
  55. package/hub/pipeline/gates/index.mjs +7 -3
  56. package/hub/pipeline/gates/selfcheck.mjs +57 -19
  57. package/hub/pipeline/index.mjs +77 -42
  58. package/hub/pipeline/state.mjs +10 -10
  59. package/hub/pipeline/transitions.mjs +40 -23
  60. package/hub/platform.mjs +57 -48
  61. package/hub/promote-penalties.mjs +25 -7
  62. package/hub/quality/deslop.mjs +70 -49
  63. package/hub/research.mjs +32 -25
  64. package/hub/router.mjs +240 -107
  65. package/hub/routing/complexity.mjs +132 -29
  66. package/hub/routing/index.mjs +17 -12
  67. package/hub/routing/q-learning.mjs +76 -28
  68. package/hub/server.mjs +4 -4
  69. package/hub/session-fingerprint.mjs +126 -60
  70. package/hub/state.mjs +84 -43
  71. package/hub/store-adapter.mjs +59 -26
  72. package/hub/store.mjs +356 -153
  73. package/hub/team/agent-map.json +22 -7
  74. package/hub/team/ansi.mjs +186 -122
  75. package/hub/team/backend.mjs +28 -10
  76. package/hub/team/cli/commands/attach.mjs +29 -9
  77. package/hub/team/cli/commands/control.mjs +29 -8
  78. package/hub/team/cli/commands/debug.mjs +32 -11
  79. package/hub/team/cli/commands/focus.mjs +38 -11
  80. package/hub/team/cli/commands/interrupt.mjs +18 -6
  81. package/hub/team/cli/commands/kill.mjs +16 -5
  82. package/hub/team/cli/commands/list.mjs +11 -4
  83. package/hub/team/cli/commands/send.mjs +19 -6
  84. package/hub/team/cli/commands/start/index.mjs +154 -31
  85. package/hub/team/cli/commands/start/parse-args.mjs +38 -11
  86. package/hub/team/cli/commands/start/start-headless.mjs +112 -36
  87. package/hub/team/cli/commands/start/start-in-process.mjs +12 -2
  88. package/hub/team/cli/commands/start/start-mux.mjs +70 -21
  89. package/hub/team/cli/commands/start/start-wt.mjs +29 -12
  90. package/hub/team/cli/commands/status.mjs +43 -14
  91. package/hub/team/cli/commands/stop.mjs +11 -4
  92. package/hub/team/cli/commands/task.mjs +8 -3
  93. package/hub/team/cli/commands/tasks.mjs +1 -1
  94. package/hub/team/cli/index.mjs +2 -2
  95. package/hub/team/cli/manifest.mjs +38 -8
  96. package/hub/team/cli/render.mjs +30 -8
  97. package/hub/team/cli/services/attach-fallback.mjs +31 -11
  98. package/hub/team/cli/services/hub-client.mjs +42 -14
  99. package/hub/team/cli/services/member-selector.mjs +11 -4
  100. package/hub/team/cli/services/native-control.mjs +48 -21
  101. package/hub/team/cli/services/runtime-mode.mjs +2 -1
  102. package/hub/team/cli/services/state-store.mjs +25 -8
  103. package/hub/team/cli/services/task-model.mjs +16 -6
  104. package/hub/team/conductor-mesh-bridge.mjs +24 -23
  105. package/hub/team/conductor.mjs +8 -4
  106. package/hub/team/dashboard-anchor.mjs +4 -5
  107. package/hub/team/dashboard-layout.mjs +3 -1
  108. package/hub/team/dashboard-open.mjs +41 -21
  109. package/hub/team/dashboard.mjs +76 -28
  110. package/hub/team/event-log.mjs +18 -10
  111. package/hub/team/handoff.mjs +31 -15
  112. package/hub/team/headless.mjs +2 -1
  113. package/hub/team/health-probe.mjs +69 -54
  114. package/hub/team/launcher-template.mjs +16 -13
  115. package/hub/team/native-supervisor.mjs +65 -21
  116. package/hub/team/native.mjs +74 -35
  117. package/hub/team/nativeProxy.mjs +184 -113
  118. package/hub/team/notify.mjs +119 -76
  119. package/hub/team/orchestrator.mjs +9 -4
  120. package/hub/team/pane.mjs +12 -7
  121. package/hub/team/process-cleanup.mjs +25 -16
  122. package/hub/team/psmux.mjs +491 -201
  123. package/hub/team/remote-probe.mjs +68 -52
  124. package/hub/team/remote-session.mjs +117 -59
  125. package/hub/team/remote-watcher.mjs +61 -33
  126. package/hub/team/routing.mjs +51 -25
  127. package/hub/team/runtime-strategy.mjs +3 -1
  128. package/hub/team/session.mjs +98 -34
  129. package/hub/team/staleState.mjs +72 -30
  130. package/hub/team/swarm-locks.mjs +15 -13
  131. package/hub/team/swarm-planner.mjs +32 -21
  132. package/hub/team/swarm-reconciler.mjs +48 -23
  133. package/hub/team/tui-lite.mjs +266 -68
  134. package/hub/team/tui-remote-adapter.mjs +14 -10
  135. package/hub/team/tui-viewer.mjs +99 -43
  136. package/hub/team/tui.mjs +708 -271
  137. package/hub/team/worktree-lifecycle.mjs +152 -58
  138. package/hub/team/wt-manager.mjs +24 -14
  139. package/hub/token-mode.mjs +71 -71
  140. package/hub/tray.mjs +66 -23
  141. package/hub/workers/claude-worker.mjs +162 -118
  142. package/hub/workers/codex-mcp.mjs +192 -141
  143. package/hub/workers/delegator-mcp.mjs +507 -333
  144. package/hub/workers/factory.mjs +8 -8
  145. package/hub/workers/gemini-worker.mjs +115 -84
  146. package/hub/workers/interface.mjs +6 -1
  147. package/hub/workers/worker-utils.mjs +21 -14
  148. package/hud/colors.mjs +27 -9
  149. package/hud/constants.mjs +162 -26
  150. package/hud/context-monitor.mjs +82 -41
  151. package/hud/hud-qos-status.mjs +129 -49
  152. package/hud/mission-board.mjs +6 -3
  153. package/hud/providers/claude.mjs +226 -115
  154. package/hud/providers/codex.mjs +62 -22
  155. package/hud/providers/gemini.mjs +168 -56
  156. package/hud/renderers.mjs +384 -119
  157. package/hud/terminal.mjs +101 -31
  158. package/hud/utils.mjs +78 -38
  159. package/mesh/index.mjs +11 -5
  160. package/mesh/mesh-budget.mjs +18 -9
  161. package/mesh/mesh-heartbeat.mjs +1 -1
  162. package/mesh/mesh-queue.mjs +3 -5
  163. package/mesh/mesh-router.mjs +5 -4
  164. package/package.json +2 -1
  165. package/scripts/__tests__/gen-skill-docs.test.mjs +36 -7
  166. package/scripts/__tests__/keyword-detector.test.mjs +77 -28
  167. package/scripts/__tests__/mcp-guard-engine.test.mjs +58 -20
  168. package/scripts/__tests__/remote-spawn-transfer.test.mjs +30 -19
  169. package/scripts/__tests__/remote-spawn.test.mjs +10 -4
  170. package/scripts/__tests__/session-start-fast.test.mjs +36 -0
  171. package/scripts/__tests__/skill-template.test.mjs +98 -50
  172. package/scripts/__tests__/smoke.test.mjs +1 -1
  173. package/scripts/__tests__/spawn-trace.test.mjs +102 -0
  174. package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +48 -0
  175. package/scripts/cache-doctor.mjs +11 -4
  176. package/scripts/cache-warmup.mjs +96 -37
  177. package/scripts/claudemd-sync.mjs +27 -17
  178. package/scripts/codex-gateway-preflight.mjs +52 -37
  179. package/scripts/codex-mcp-gateway-sync.mjs +59 -39
  180. package/scripts/completions/tfx.bash +47 -47
  181. package/scripts/completions/tfx.fish +44 -44
  182. package/scripts/completions/tfx.zsh +83 -83
  183. package/scripts/config-audit.mjs +232 -0
  184. package/scripts/convert-to-tmpl.mjs +54 -0
  185. package/scripts/cross-review-gate.mjs +35 -12
  186. package/scripts/cross-review-tracker.mjs +21 -8
  187. package/scripts/demo.mjs +35 -17
  188. package/scripts/doctor-diagnose.mjs +284 -0
  189. package/scripts/gen-skill-docs.mjs +7 -2
  190. package/scripts/gen-skill-manifest.mjs +2 -1
  191. package/scripts/headless-guard.mjs +86 -48
  192. package/scripts/hub-ensure.mjs +45 -26
  193. package/scripts/keyword-detector.mjs +41 -20
  194. package/scripts/keyword-rules-expander.mjs +47 -30
  195. package/scripts/lib/claudemd-scanner.mjs +6 -1
  196. package/scripts/lib/context.mjs +3 -3
  197. package/scripts/lib/cross-review-utils.mjs +6 -3
  198. package/scripts/lib/env-probe.mjs +47 -28
  199. package/scripts/lib/gemini-profiles.mjs +44 -10
  200. package/scripts/lib/handoff.mjs +33 -17
  201. package/scripts/lib/hook-utils.mjs +8 -6
  202. package/scripts/lib/keyword-rules.mjs +43 -19
  203. package/scripts/lib/logger.mjs +24 -24
  204. package/scripts/lib/mcp-filter.mjs +377 -239
  205. package/scripts/lib/mcp-guard-engine.mjs +194 -79
  206. package/scripts/lib/mcp-manifest.mjs +23 -13
  207. package/scripts/lib/mcp-server-catalog.mjs +300 -63
  208. package/scripts/lib/psmux-info.mjs +11 -6
  209. package/scripts/lib/remote-spawn-transfer.mjs +44 -14
  210. package/scripts/lib/skill-template.mjs +30 -7
  211. package/scripts/mcp-check.mjs +58 -39
  212. package/scripts/mcp-gateway-config.mjs +83 -39
  213. package/scripts/mcp-gateway-ensure.mjs +43 -35
  214. package/scripts/mcp-gateway-integration-test.mjs +70 -58
  215. package/scripts/mcp-gateway-start.mjs +126 -60
  216. package/scripts/mcp-gateway-verify.mjs +24 -22
  217. package/scripts/mcp-safety-guard.mjs +44 -11
  218. package/scripts/notion-read.mjs +199 -84
  219. package/scripts/pack.mjs +94 -89
  220. package/scripts/preflight-cache.mjs +27 -10
  221. package/scripts/preinstall.mjs +42 -13
  222. package/scripts/remote-spawn.mjs +309 -94
  223. package/scripts/run.cjs +8 -5
  224. package/scripts/session-spawn-helper.mjs +130 -39
  225. package/scripts/session-stale-cleanup.mjs +123 -0
  226. package/scripts/setup.mjs +941 -492
  227. package/scripts/test-lock.mjs +20 -7
  228. package/scripts/test-tfx-route-no-claude-native.mjs +16 -12
  229. package/scripts/tfx-batch-stats.mjs +32 -11
  230. package/scripts/tfx-gate-activate.mjs +11 -4
  231. package/scripts/tfx-route-post.mjs +87 -20
  232. package/scripts/tfx-route-worker.mjs +57 -51
  233. package/scripts/tfx-route.sh +41 -124
  234. package/scripts/tmp-cleanup.mjs +21 -7
  235. package/scripts/token-snapshot.mjs +204 -85
  236. package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
  237. package/skills/.omc/state/idle-notif-cooldown.json +3 -0
  238. package/skills/.omc/state/last-tool-error.json +7 -0
  239. package/skills/.omc/state/subagent-tracking.json +7 -0
  240. package/skills/_templates/base.md +1 -6
  241. package/skills/merge-worktree/SKILL.md.tmpl +144 -0
  242. package/skills/shared/telemetry-segment.md +6 -0
  243. package/skills/star-prompt/SKILL.md.tmpl +222 -0
  244. package/skills/tfx-analysis/SKILL.md.tmpl +107 -0
  245. package/skills/tfx-analysis/skill.json +1 -6
  246. package/skills/tfx-auto/SKILL.md +1 -0
  247. package/skills/tfx-auto-codex/SKILL.md.tmpl +106 -0
  248. package/skills/tfx-auto-codex/skill.json +1 -3
  249. package/skills/tfx-autopilot/SKILL.md.tmpl +116 -0
  250. package/skills/tfx-autopilot/skill.json +1 -5
  251. package/skills/tfx-autoresearch/SKILL.md.tmpl +136 -0
  252. package/skills/tfx-autoroute/SKILL.md.tmpl +189 -0
  253. package/skills/tfx-autoroute/skill.json +1 -7
  254. package/skills/tfx-codex/SKILL.md +1 -0
  255. package/skills/tfx-codex/skill.json +1 -3
  256. package/skills/tfx-codex-swarm/SKILL.md.tmpl +16 -0
  257. package/skills/tfx-codex-swarm/evals/evals.json +1 -1
  258. package/skills/tfx-codex-swarm/skill.json +1 -4
  259. package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +54 -12
  260. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +35 -7
  261. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +35 -7
  262. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +25 -5
  263. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +25 -5
  264. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +20 -4
  265. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +16 -4
  266. package/skills/tfx-consensus/SKILL.md.tmpl +146 -0
  267. package/skills/tfx-debate/SKILL.md.tmpl +192 -0
  268. package/skills/tfx-debate/skill.json +1 -7
  269. package/skills/tfx-deep-analysis/SKILL.md.tmpl +228 -0
  270. package/skills/tfx-deep-analysis/skill.json +1 -5
  271. package/skills/tfx-deep-interview/SKILL.md.tmpl +203 -0
  272. package/skills/tfx-deep-plan/SKILL.md.tmpl +282 -0
  273. package/skills/tfx-deep-qa/SKILL.md.tmpl +165 -0
  274. package/skills/tfx-deep-qa/skill.json +1 -6
  275. package/skills/tfx-deep-research/SKILL.md.tmpl +217 -0
  276. package/skills/tfx-deep-review/SKILL.md.tmpl +179 -0
  277. package/skills/tfx-doctor/SKILL.md +21 -0
  278. package/skills/tfx-doctor/SKILL.md.tmpl +172 -0
  279. package/skills/tfx-doctor/skill.json +1 -3
  280. package/skills/tfx-find/SKILL.md +1 -0
  281. package/skills/tfx-forge/SKILL.md.tmpl +187 -0
  282. package/skills/tfx-fullcycle/SKILL.md.tmpl +286 -0
  283. package/skills/tfx-fullcycle/skill.json +1 -6
  284. package/skills/tfx-gemini/SKILL.md.tmpl +91 -0
  285. package/skills/tfx-gemini/skill.json +1 -3
  286. package/skills/tfx-hooks/SKILL.md.tmpl +216 -0
  287. package/skills/tfx-hooks/skill.json +1 -3
  288. package/skills/tfx-hub/SKILL.md.tmpl +212 -0
  289. package/skills/tfx-hub/skill.json +1 -3
  290. package/skills/tfx-index/SKILL.md +1 -0
  291. package/skills/tfx-index/skill.json +1 -6
  292. package/skills/tfx-interview/SKILL.md.tmpl +285 -0
  293. package/skills/tfx-multi/SKILL.md.tmpl +183 -0
  294. package/skills/tfx-multi/skill.json +1 -3
  295. package/skills/tfx-panel/SKILL.md.tmpl +189 -0
  296. package/skills/tfx-panel/skill.json +1 -7
  297. package/skills/tfx-persist/SKILL.md.tmpl +270 -0
  298. package/skills/tfx-persist/skill.json +1 -7
  299. package/skills/tfx-plan/SKILL.md +1 -0
  300. package/skills/tfx-plan/skill.json +1 -6
  301. package/skills/tfx-profile/SKILL.md.tmpl +239 -0
  302. package/skills/tfx-profile/skill.json +1 -3
  303. package/skills/tfx-prune/SKILL.md.tmpl +200 -0
  304. package/skills/tfx-prune/skill.json +1 -7
  305. package/skills/tfx-psmux-rules/SKILL.md.tmpl +326 -0
  306. package/skills/tfx-psmux-rules/skill.json +1 -4
  307. package/skills/tfx-qa/SKILL.md +1 -0
  308. package/skills/tfx-qa/skill.json +1 -6
  309. package/skills/tfx-ralph/SKILL.md.tmpl +28 -0
  310. package/skills/tfx-ralph/skill.json +1 -4
  311. package/skills/tfx-remote-setup/SKILL.md.tmpl +576 -0
  312. package/skills/tfx-remote-setup/skill.json +1 -3
  313. package/skills/tfx-remote-spawn/SKILL.md.tmpl +263 -0
  314. package/skills/tfx-remote-spawn/references/hosts.json +16 -0
  315. package/skills/tfx-remote-spawn/skill.json +1 -4
  316. package/skills/tfx-research/SKILL.md +1 -0
  317. package/skills/tfx-review/SKILL.md +1 -0
  318. package/skills/tfx-review/skill.json +1 -6
  319. package/skills/tfx-setup/SKILL.md.tmpl +504 -0
  320. package/skills/tfx-setup/skill.json +1 -3
  321. package/skills/tfx-swarm/SKILL.md +22 -0
  322. package/skills/tfx-swarm/SKILL.md.tmpl +218 -0
  323. package/tui/codex-profile.mjs +88 -33
  324. package/tui/core.mjs +45 -15
  325. package/tui/doctor.mjs +75 -28
  326. package/tui/gemini-profile.mjs +74 -29
  327. package/tui/monitor-data.mjs +8 -4
  328. package/tui/monitor.mjs +71 -27
  329. package/tui/setup.mjs +133 -42
@@ -2,21 +2,22 @@
2
2
  // scripts/lib/mcp-filter.mjs
3
3
  // 역할/컨텍스트 기반 MCP 도구 노출 정책의 단일 소스.
4
4
 
5
- import { readFileSync } from 'node:fs';
6
- import process from 'node:process';
7
- import { fileURLToPath } from 'node:url';
8
-
5
+ import { readFileSync } from "node:fs";
6
+ import process from "node:process";
7
+ import { fileURLToPath } from "node:url";
8
+ import { CORE_SERVERS, readManifest } from "./mcp-manifest.mjs";
9
9
  import {
10
10
  DOMAIN_TAG_KEYWORDS,
11
11
  MCP_SERVER_TOOL_CATALOG,
12
+ normalizeServerMetadata,
12
13
  SEARCH_SERVER_ORDER,
13
14
  SERVER_EXPLICIT_KEYWORDS,
14
- normalizeServerMetadata,
15
15
  uniqueStrings,
16
- } from './mcp-server-catalog.mjs';
17
- import { readManifest, CORE_SERVERS } from './mcp-manifest.mjs';
16
+ } from "./mcp-server-catalog.mjs";
18
17
 
19
- export const KNOWN_MCP_SERVERS = Object.freeze(Object.keys(MCP_SERVER_TOOL_CATALOG));
18
+ export const KNOWN_MCP_SERVERS = Object.freeze(
19
+ Object.keys(MCP_SERVER_TOOL_CATALOG),
20
+ );
20
21
 
21
22
  const SEARCH_INTENT_PATTERNS = Object.freeze([
22
23
  /\b(search|web|browse|look ?up|find|latest|recent|news|current|today|release(?: note)?s?|changelog|announcement|pricing|status|verify|fact[- ]?check)\b/i,
@@ -25,102 +26,129 @@ const SEARCH_INTENT_PATTERNS = Object.freeze([
25
26
 
26
27
  const PROFILE_DEFINITIONS = Object.freeze({
27
28
  default: Object.freeze({
28
- description: '보수적 기본 프로필. 문서 조회 + 최소 검색만 허용',
29
- allowedServers: Object.freeze(['context7', 'brave-search']),
30
- alwaysOnServers: Object.freeze(['context7']),
29
+ description: "보수적 기본 프로필. 문서 조회 + 최소 검색만 허용",
30
+ allowedServers: Object.freeze(["context7", "brave-search"]),
31
+ alwaysOnServers: Object.freeze(["context7"]),
31
32
  maxSearchServers: 1,
32
33
  allowedToolsByServer: Object.freeze({
33
- context7: Object.freeze(['resolve-library-id', 'query-docs']),
34
- 'brave-search': Object.freeze(['brave_web_search', 'brave_news_search']),
34
+ context7: Object.freeze(["resolve-library-id", "query-docs"]),
35
+ "brave-search": Object.freeze(["brave_web_search", "brave_news_search"]),
35
36
  }),
36
37
  }),
37
38
  executor: Object.freeze({
38
- description: '구현 워커용. 문서/검색/브라우징 보조 MCP 허용',
39
- allowedServers: Object.freeze(['context7', 'playwright', 'brave-search', 'tavily', 'exa']),
40
- alwaysOnServers: Object.freeze(['context7']),
39
+ description: "구현 워커용. 문서/검색/브라우징 보조 MCP 허용",
40
+ allowedServers: Object.freeze([
41
+ "context7",
42
+ "playwright",
43
+ "brave-search",
44
+ "tavily",
45
+ "exa",
46
+ ]),
47
+ alwaysOnServers: Object.freeze(["context7"]),
41
48
  maxSearchServers: 2,
42
49
  allowedToolsByServer: Object.freeze({
43
- context7: Object.freeze(['resolve-library-id', 'query-docs']),
44
- 'brave-search': Object.freeze(['brave_web_search', 'brave_news_search']),
45
- exa: Object.freeze(['web_search_exa', 'get_code_context_exa']),
46
- tavily: Object.freeze(['tavily_search', 'tavily_extract']),
50
+ context7: Object.freeze(["resolve-library-id", "query-docs"]),
51
+ "brave-search": Object.freeze(["brave_web_search", "brave_news_search"]),
52
+ exa: Object.freeze(["web_search_exa", "get_code_context_exa"]),
53
+ tavily: Object.freeze(["tavily_search", "tavily_extract"]),
47
54
  playwright: Object.freeze([
48
- 'browser_navigate',
49
- 'browser_navigate_back',
50
- 'browser_snapshot',
51
- 'browser_take_screenshot',
52
- 'browser_wait_for',
55
+ "browser_navigate",
56
+ "browser_navigate_back",
57
+ "browser_snapshot",
58
+ "browser_take_screenshot",
59
+ "browser_wait_for",
53
60
  ]),
54
61
  }),
55
62
  }),
56
63
  designer: Object.freeze({
57
- description: '디자인/UI 워커용. 브라우저 관찰 + 문서 조회 중심 MCP 허용',
58
- allowedServers: Object.freeze(['context7', 'playwright', 'tavily', 'exa', 'brave-search']),
59
- alwaysOnServers: Object.freeze(['context7']),
64
+ description: "디자인/UI 워커용. 브라우저 관찰 + 문서 조회 중심 MCP 허용",
65
+ allowedServers: Object.freeze([
66
+ "context7",
67
+ "playwright",
68
+ "tavily",
69
+ "exa",
70
+ "brave-search",
71
+ ]),
72
+ alwaysOnServers: Object.freeze(["context7"]),
60
73
  maxSearchServers: 2,
61
74
  allowedToolsByServer: Object.freeze({
62
- context7: Object.freeze(['resolve-library-id', 'query-docs']),
63
- 'brave-search': Object.freeze(['brave_web_search', 'brave_news_search']),
64
- exa: Object.freeze(['web_search_exa', 'get_code_context_exa']),
65
- tavily: Object.freeze(['tavily_search', 'tavily_extract']),
75
+ context7: Object.freeze(["resolve-library-id", "query-docs"]),
76
+ "brave-search": Object.freeze(["brave_web_search", "brave_news_search"]),
77
+ exa: Object.freeze(["web_search_exa", "get_code_context_exa"]),
78
+ tavily: Object.freeze(["tavily_search", "tavily_extract"]),
66
79
  playwright: Object.freeze([
67
- 'browser_navigate',
68
- 'browser_navigate_back',
69
- 'browser_snapshot',
70
- 'browser_take_screenshot',
71
- 'browser_wait_for',
80
+ "browser_navigate",
81
+ "browser_navigate_back",
82
+ "browser_snapshot",
83
+ "browser_take_screenshot",
84
+ "browser_wait_for",
72
85
  ]),
73
86
  }),
74
87
  }),
75
88
  analyze: Object.freeze({
76
- description: '분석/설계 워커용. 추론 + 검색 MCP 허용',
77
- allowedServers: Object.freeze(['context7', 'brave-search', 'tavily', 'exa', 'sequential-thinking']),
78
- alwaysOnServers: Object.freeze(['context7', 'sequential-thinking']),
89
+ description: "분석/설계 워커용. 추론 + 검색 MCP 허용",
90
+ allowedServers: Object.freeze([
91
+ "context7",
92
+ "brave-search",
93
+ "tavily",
94
+ "exa",
95
+ "sequential-thinking",
96
+ ]),
97
+ alwaysOnServers: Object.freeze(["context7", "sequential-thinking"]),
79
98
  maxSearchServers: 2,
80
99
  allowedToolsByServer: Object.freeze({
81
- context7: Object.freeze(['resolve-library-id', 'query-docs']),
82
- 'brave-search': Object.freeze(['brave_web_search', 'brave_news_search']),
83
- exa: Object.freeze(['web_search_exa', 'get_code_context_exa']),
84
- tavily: Object.freeze(['tavily_search', 'tavily_extract']),
85
- 'sequential-thinking': Object.freeze(['sequentialthinking']),
100
+ context7: Object.freeze(["resolve-library-id", "query-docs"]),
101
+ "brave-search": Object.freeze(["brave_web_search", "brave_news_search"]),
102
+ exa: Object.freeze(["web_search_exa", "get_code_context_exa"]),
103
+ tavily: Object.freeze(["tavily_search", "tavily_extract"]),
104
+ "sequential-thinking": Object.freeze(["sequentialthinking"]),
86
105
  }),
87
106
  }),
88
107
  explore: Object.freeze({
89
- description: '탐색/리서치 워커용. 읽기/검색 중심 MCP만 허용',
90
- allowedServers: Object.freeze(['context7', 'brave-search', 'tavily', 'exa']),
91
- alwaysOnServers: Object.freeze(['context7']),
108
+ description: "탐색/리서치 워커용. 읽기/검색 중심 MCP만 허용",
109
+ allowedServers: Object.freeze([
110
+ "context7",
111
+ "brave-search",
112
+ "tavily",
113
+ "exa",
114
+ ]),
115
+ alwaysOnServers: Object.freeze(["context7"]),
92
116
  maxSearchServers: 2,
93
117
  allowedToolsByServer: Object.freeze({
94
- context7: Object.freeze(['resolve-library-id', 'query-docs']),
95
- 'brave-search': Object.freeze(['brave_web_search', 'brave_news_search']),
96
- exa: Object.freeze(['web_search_exa', 'get_code_context_exa']),
97
- tavily: Object.freeze(['tavily_search', 'tavily_extract']),
118
+ context7: Object.freeze(["resolve-library-id", "query-docs"]),
119
+ "brave-search": Object.freeze(["brave_web_search", "brave_news_search"]),
120
+ exa: Object.freeze(["web_search_exa", "get_code_context_exa"]),
121
+ tavily: Object.freeze(["tavily_search", "tavily_extract"]),
98
122
  }),
99
123
  }),
100
124
  reviewer: Object.freeze({
101
- description: '리뷰 워커용. 문서 조회 + 분석 전용 MCP만 허용',
102
- allowedServers: Object.freeze(['context7', 'brave-search', 'sequential-thinking']),
103
- alwaysOnServers: Object.freeze(['context7', 'sequential-thinking']),
125
+ description: "리뷰 워커용. 문서 조회 + 분석 전용 MCP만 허용",
126
+ allowedServers: Object.freeze([
127
+ "context7",
128
+ "brave-search",
129
+ "sequential-thinking",
130
+ ]),
131
+ alwaysOnServers: Object.freeze(["context7", "sequential-thinking"]),
104
132
  maxSearchServers: 1,
105
133
  allowedToolsByServer: Object.freeze({
106
- context7: Object.freeze(['resolve-library-id', 'query-docs']),
107
- 'brave-search': Object.freeze(['brave_web_search']),
108
- 'sequential-thinking': Object.freeze(['sequentialthinking']),
134
+ context7: Object.freeze(["resolve-library-id", "query-docs"]),
135
+ "brave-search": Object.freeze(["brave_web_search"]),
136
+ "sequential-thinking": Object.freeze(["sequentialthinking"]),
109
137
  }),
110
138
  }),
111
139
  writer: Object.freeze({
112
- description: '문서/작성 워커용. 공식 문서와 최소 검색 MCP만 허용',
113
- allowedServers: Object.freeze(['context7', 'brave-search', 'exa']),
114
- alwaysOnServers: Object.freeze(['context7']),
140
+ description: "문서/작성 워커용. 공식 문서와 최소 검색 MCP만 허용",
141
+ allowedServers: Object.freeze(["context7", "brave-search", "exa"]),
142
+ alwaysOnServers: Object.freeze(["context7"]),
115
143
  maxSearchServers: 2,
116
144
  allowedToolsByServer: Object.freeze({
117
- context7: Object.freeze(['resolve-library-id', 'query-docs']),
118
- 'brave-search': Object.freeze(['brave_web_search', 'brave_news_search']),
119
- exa: Object.freeze(['web_search_exa']),
145
+ context7: Object.freeze(["resolve-library-id", "query-docs"]),
146
+ "brave-search": Object.freeze(["brave_web_search", "brave_news_search"]),
147
+ exa: Object.freeze(["web_search_exa"]),
120
148
  }),
121
149
  }),
122
150
  none: Object.freeze({
123
- description: '모든 선택적 MCP 서버 비활성화',
151
+ description: "모든 선택적 MCP 서버 비활성화",
124
152
  allowedServers: Object.freeze([]),
125
153
  alwaysOnServers: Object.freeze([]),
126
154
  maxSearchServers: 0,
@@ -134,110 +162,111 @@ const PROFILE_DEFINITIONS = Object.freeze({
134
162
  */
135
163
  export const PHASE_OVERRIDES = Object.freeze({
136
164
  plan: Object.freeze({
137
- description: '계획 단계: 읽기 전용 탐색만 허용',
138
- allowedServers: Object.freeze(['context7']),
139
- blockedServers: Object.freeze(['playwright', 'tavily', 'exa']),
165
+ description: "계획 단계: 읽기 전용 탐색만 허용",
166
+ allowedServers: Object.freeze(["context7"]),
167
+ blockedServers: Object.freeze(["playwright", "tavily", "exa"]),
140
168
  }),
141
169
  prd: Object.freeze({
142
- description: 'PRD 단계: 읽기 전용 탐색 + 문서 조회',
143
- allowedServers: Object.freeze(['context7', 'brave-search']),
144
- blockedServers: Object.freeze(['playwright']),
170
+ description: "PRD 단계: 읽기 전용 탐색 + 문서 조회",
171
+ allowedServers: Object.freeze(["context7", "brave-search"]),
172
+ blockedServers: Object.freeze(["playwright"]),
145
173
  }),
146
174
  exec: Object.freeze({
147
- description: '실행 단계: 프로필 기반 전체 허용 (제한 없음)',
175
+ description: "실행 단계: 프로필 기반 전체 허용 (제한 없음)",
148
176
  }),
149
177
  verify: Object.freeze({
150
- description: '검증 단계: 읽기 전용 + 분석 도구',
151
- allowedServers: Object.freeze(['context7', 'brave-search', 'exa']),
152
- blockedServers: Object.freeze(['playwright']),
178
+ description: "검증 단계: 읽기 전용 + 분석 도구",
179
+ allowedServers: Object.freeze(["context7", "brave-search", "exa"]),
180
+ blockedServers: Object.freeze(["playwright"]),
153
181
  }),
154
182
  });
155
183
 
156
184
  export const LEGACY_PROFILE_ALIASES = Object.freeze({
157
- implement: 'executor',
158
- analyze: 'analyze',
159
- review: 'reviewer',
160
- docs: 'writer',
161
- minimal: 'default',
185
+ implement: "executor",
186
+ analyze: "analyze",
187
+ review: "reviewer",
188
+ docs: "writer",
189
+ minimal: "default",
162
190
  });
163
191
 
164
192
  export const SUPPORTED_MCP_PROFILES = Object.freeze([
165
- 'auto',
193
+ "auto",
166
194
  ...Object.keys(PROFILE_DEFINITIONS),
167
195
  ...Object.keys(LEGACY_PROFILE_ALIASES),
168
196
  ]);
169
197
 
170
- function normalizeTaskText(taskText = '') {
171
- if (typeof taskText !== 'string') return '';
172
- return taskText.replace(/\s+/g, ' ').trim();
198
+ function normalizeTaskText(taskText = "") {
199
+ if (typeof taskText !== "string") return "";
200
+ return taskText.replace(/\s+/g, " ").trim();
173
201
  }
174
202
 
175
203
  function normalizeProfileName(profile) {
176
- const raw = typeof profile === 'string' && profile.trim() ? profile.trim() : 'auto';
177
- if (raw === 'auto') return raw;
204
+ const raw =
205
+ typeof profile === "string" && profile.trim() ? profile.trim() : "auto";
206
+ if (raw === "auto") return raw;
178
207
  if (PROFILE_DEFINITIONS[raw]) return raw;
179
208
  if (LEGACY_PROFILE_ALIASES[raw]) return LEGACY_PROFILE_ALIASES[raw];
180
209
  // graceful fallback: --flag나 잘못된 프로필 → 'auto'로 폴백 (hard crash 방지)
181
- if (raw.startsWith('-') || raw.startsWith('/')) return 'auto';
210
+ if (raw.startsWith("-") || raw.startsWith("/")) return "auto";
182
211
  console.error(`[mcp-filter] 경고: 알 수 없는 프로필 '${raw}', 'auto'로 폴백`);
183
- return 'auto';
212
+ return "auto";
184
213
  }
185
214
 
186
- function resolveAutoProfile(agentType = '') {
215
+ function resolveAutoProfile(agentType = "") {
187
216
  switch (agentType) {
188
- case 'executor':
189
- case 'build-fixer':
190
- case 'debugger':
191
- case 'deep-executor':
192
- return 'executor';
193
- case 'test-engineer':
194
- case 'qa-tester':
195
- return 'none';
196
- case 'designer':
197
- return 'designer';
198
- case 'architect':
199
- case 'planner':
200
- case 'critic':
201
- case 'analyst':
202
- return 'analyze';
203
- case 'scientist':
204
- case 'scientist-deep':
205
- case 'document-specialist':
206
- case 'explore':
207
- return 'explore';
208
- case 'code-reviewer':
209
- case 'security-reviewer':
210
- case 'quality-reviewer':
211
- case 'verifier':
212
- return 'reviewer';
213
- case 'writer':
214
- return 'writer';
217
+ case "executor":
218
+ case "build-fixer":
219
+ case "debugger":
220
+ case "deep-executor":
221
+ return "executor";
222
+ case "test-engineer":
223
+ case "qa-tester":
224
+ return "none";
225
+ case "designer":
226
+ return "designer";
227
+ case "architect":
228
+ case "planner":
229
+ case "critic":
230
+ case "analyst":
231
+ return "analyze";
232
+ case "scientist":
233
+ case "scientist-deep":
234
+ case "document-specialist":
235
+ case "explore":
236
+ return "explore";
237
+ case "code-reviewer":
238
+ case "security-reviewer":
239
+ case "quality-reviewer":
240
+ case "verifier":
241
+ return "reviewer";
242
+ case "writer":
243
+ return "writer";
215
244
  default:
216
- return 'default';
245
+ return "default";
217
246
  }
218
247
  }
219
248
 
220
249
  function escapeRegExp(value) {
221
- return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
250
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
222
251
  }
223
252
 
224
253
  function countKeywordMatches(text, keywords = []) {
225
254
  let matches = 0;
226
255
  for (const keyword of keywords) {
227
- const source = String(keyword || '').trim();
256
+ const source = String(keyword || "").trim();
228
257
  if (!source) continue;
229
258
  const pattern = /^[a-z0-9- ]+$/i.test(source)
230
- ? new RegExp(`\\b${escapeRegExp(source)}\\b`, 'i')
231
- : new RegExp(escapeRegExp(source), 'iu');
259
+ ? new RegExp(`\\b${escapeRegExp(source)}\\b`, "i")
260
+ : new RegExp(escapeRegExp(source), "iu");
232
261
  if (pattern.test(text)) matches += 1;
233
262
  }
234
263
  return matches;
235
264
  }
236
265
 
237
- function loadInventory(inventoryFile = '') {
238
- if (typeof inventoryFile !== 'string' || !inventoryFile.trim()) return null;
266
+ function loadInventory(inventoryFile = "") {
267
+ if (typeof inventoryFile !== "string" || !inventoryFile.trim()) return null;
239
268
  try {
240
- return JSON.parse(readFileSync(inventoryFile, 'utf8'));
269
+ return JSON.parse(readFileSync(inventoryFile, "utf8"));
241
270
  } catch {
242
271
  return null;
243
272
  }
@@ -245,12 +274,15 @@ function loadInventory(inventoryFile = '') {
245
274
 
246
275
  function buildInventoryIndex(inventory = null) {
247
276
  const index = new Map();
248
- if (!inventory || typeof inventory !== 'object') return index;
277
+ if (!inventory || typeof inventory !== "object") return index;
249
278
 
250
- for (const client of ['codex', 'gemini']) {
251
- const servers = Array.isArray(inventory[client]?.servers) ? inventory[client].servers : [];
279
+ for (const client of ["codex", "gemini"]) {
280
+ const servers = Array.isArray(inventory[client]?.servers)
281
+ ? inventory[client].servers
282
+ : [];
252
283
  for (const server of servers) {
253
- if (!server || typeof server.name !== 'string' || !server.name.trim()) continue;
284
+ if (!server || typeof server.name !== "string" || !server.name.trim())
285
+ continue;
254
286
  const name = server.name.trim();
255
287
  const previous = index.get(name) || {};
256
288
  index.set(name, {
@@ -278,7 +310,7 @@ function getServerMetadata(server, inventoryIndex) {
278
310
  });
279
311
  }
280
312
 
281
- function scoreServer(server, taskText = '', inventoryIndex = new Map()) {
313
+ function scoreServer(server, taskText = "", inventoryIndex = new Map()) {
282
314
  const normalized = normalizeTaskText(taskText);
283
315
  const metadata = getServerMetadata(server, inventoryIndex);
284
316
  if (!normalized) {
@@ -294,14 +326,20 @@ function scoreServer(server, taskText = '', inventoryIndex = new Map()) {
294
326
  let score = 0;
295
327
  const matchedTags = [];
296
328
  for (const tag of metadata.domain_tags) {
297
- const matches = countKeywordMatches(normalized, DOMAIN_TAG_KEYWORDS[tag] || []);
329
+ const matches = countKeywordMatches(
330
+ normalized,
331
+ DOMAIN_TAG_KEYWORDS[tag] || [],
332
+ );
298
333
  if (matches > 0) {
299
334
  matchedTags.push(tag);
300
335
  score += matches * 2;
301
336
  }
302
337
  }
303
338
 
304
- const explicitMatches = countKeywordMatches(normalized, SERVER_EXPLICIT_KEYWORDS[server] || []);
339
+ const explicitMatches = countKeywordMatches(
340
+ normalized,
341
+ SERVER_EXPLICIT_KEYWORDS[server] || [],
342
+ );
305
343
  if (explicitMatches > 0) {
306
344
  score += explicitMatches * 4;
307
345
  }
@@ -325,61 +363,86 @@ function compareRankedServers(left, right, workerIndex, availableOrder = []) {
325
363
  return Number(right.explicitMatch) - Number(left.explicitMatch);
326
364
  }
327
365
  if (right.score !== left.score) return right.score - left.score;
328
- if (left.toolCount !== right.toolCount) return left.toolCount - right.toolCount;
329
-
330
- if (Number.isInteger(workerIndex) && workerIndex > 0 && availableOrder.length > 1) {
366
+ if (left.toolCount !== right.toolCount)
367
+ return left.toolCount - right.toolCount;
368
+
369
+ if (
370
+ Number.isInteger(workerIndex) &&
371
+ workerIndex > 0 &&
372
+ availableOrder.length > 1
373
+ ) {
331
374
  const offset = (workerIndex - 1) % availableOrder.length;
332
- const rotated = availableOrder.slice(offset).concat(availableOrder.slice(0, offset));
375
+ const rotated = availableOrder
376
+ .slice(offset)
377
+ .concat(availableOrder.slice(0, offset));
333
378
  return rotated.indexOf(left.server) - rotated.indexOf(right.server);
334
379
  }
335
380
 
336
- return availableOrder.indexOf(left.server) - availableOrder.indexOf(right.server);
381
+ return (
382
+ availableOrder.indexOf(left.server) - availableOrder.indexOf(right.server)
383
+ );
337
384
  }
338
385
 
339
386
  function rankServers(servers = [], options = {}) {
340
- const inventoryIndex = options.inventoryIndex instanceof Map
341
- ? options.inventoryIndex
342
- : buildInventoryIndex(options.inventory);
387
+ const inventoryIndex =
388
+ options.inventoryIndex instanceof Map
389
+ ? options.inventoryIndex
390
+ : buildInventoryIndex(options.inventory);
343
391
  return servers
344
392
  .map((server) => scoreServer(server, options.taskText, inventoryIndex))
345
- .sort((left, right) => compareRankedServers(left, right, options.workerIndex, servers));
393
+ .sort((left, right) =>
394
+ compareRankedServers(left, right, options.workerIndex, servers),
395
+ );
346
396
  }
347
397
 
348
398
  function hasContextSignals(servers = [], options = {}) {
349
399
  return rankServers(servers, options).some((server) => server.score > 0);
350
400
  }
351
401
 
352
- function inferPreferredSearchTool(taskText = '', inventoryIndex = new Map(), allowedServers = SEARCH_SERVER_ORDER) {
402
+ function inferPreferredSearchTool(
403
+ taskText = "",
404
+ inventoryIndex = new Map(),
405
+ allowedServers = SEARCH_SERVER_ORDER,
406
+ ) {
353
407
  const ranked = rankServers(
354
408
  SEARCH_SERVER_ORDER.filter((server) => allowedServers.includes(server)),
355
409
  { taskText, inventoryIndex },
356
410
  );
357
- return ranked.find((server) => server.score > 0)?.server || '';
411
+ return ranked.find((server) => server.score > 0)?.server || "";
358
412
  }
359
413
 
360
414
  function selectContextualServers(baseServers, profile, options = {}) {
361
415
  const taskText = normalizeTaskText(options.taskText);
362
416
  if (!taskText || !baseServers.length) return [...baseServers];
363
417
 
364
- const inventoryIndex = options.inventoryIndex instanceof Map
365
- ? options.inventoryIndex
366
- : buildInventoryIndex(options.inventory);
367
- if (!hasContextSignals(baseServers, { ...options, inventoryIndex })) return [...baseServers];
418
+ const inventoryIndex =
419
+ options.inventoryIndex instanceof Map
420
+ ? options.inventoryIndex
421
+ : buildInventoryIndex(options.inventory);
422
+ if (!hasContextSignals(baseServers, { ...options, inventoryIndex }))
423
+ return [...baseServers];
368
424
 
369
425
  const selected = new Set(
370
- (profile.alwaysOnServers || []).filter((server) => baseServers.includes(server)),
371
- );
372
- const wantsBrowserObservation = (
373
- baseServers.includes('playwright')
374
- && /(?:browser|screenshot|layout|responsive|visual|screen|page|ui|ux|regression|캡처|스크린샷|레이아웃|반응형|화면|브라우저)/iu.test(taskText)
426
+ (profile.alwaysOnServers || []).filter((server) =>
427
+ baseServers.includes(server),
428
+ ),
375
429
  );
430
+ const wantsBrowserObservation =
431
+ baseServers.includes("playwright") &&
432
+ /(?:browser|screenshot|layout|responsive|visual|screen|page|ui|ux|regression|캡처|스크린샷|레이아웃|반응형|화면|브라우저)/iu.test(
433
+ taskText,
434
+ );
376
435
  if (wantsBrowserObservation) {
377
- selected.add('playwright');
436
+ selected.add("playwright");
378
437
  }
379
- const requestedSearchTool = typeof options.searchTool === 'string' ? options.searchTool : '';
438
+ const requestedSearchTool =
439
+ typeof options.searchTool === "string" ? options.searchTool : "";
380
440
 
381
441
  const rankedServers = rankServers(
382
- baseServers.filter((server) => !SEARCH_SERVER_ORDER.includes(server) && !selected.has(server)),
442
+ baseServers.filter(
443
+ (server) =>
444
+ !SEARCH_SERVER_ORDER.includes(server) && !selected.has(server),
445
+ ),
383
446
  { ...options, inventoryIndex },
384
447
  );
385
448
  for (const ranked of rankedServers) {
@@ -399,11 +462,13 @@ function selectContextualServers(baseServers, profile, options = {}) {
399
462
  taskText,
400
463
  { inventoryIndex },
401
464
  );
402
- const rankedSearchSet = new Set(orderedSearchServers.filter((server) => {
403
- if (requestedSearchTool === server) return true;
404
- const ranked = scoreServer(server, taskText, inventoryIndex);
405
- return ranked.score > 0 || ranked.explicitMatch;
406
- }));
465
+ const rankedSearchSet = new Set(
466
+ orderedSearchServers.filter((server) => {
467
+ if (requestedSearchTool === server) return true;
468
+ const ranked = scoreServer(server, taskText, inventoryIndex);
469
+ return ranked.score > 0 || ranked.explicitMatch;
470
+ }),
471
+ );
407
472
  const maxSearchServers = Number.isInteger(profile.maxSearchServers)
408
473
  ? profile.maxSearchServers
409
474
  : orderedSearchServers.length;
@@ -419,12 +484,22 @@ function selectContextualServers(baseServers, profile, options = {}) {
419
484
  selected.add(server);
420
485
  }
421
486
 
422
- const alwaysOnServers = baseServers.filter((server) => selected.has(server) && (profile.alwaysOnServers || []).includes(server));
487
+ const alwaysOnServers = baseServers.filter(
488
+ (server) =>
489
+ selected.has(server) && (profile.alwaysOnServers || []).includes(server),
490
+ );
423
491
  const contextualNonSearch = rankServers(
424
- baseServers.filter((server) => selected.has(server) && !SEARCH_SERVER_ORDER.includes(server) && !alwaysOnServers.includes(server)),
492
+ baseServers.filter(
493
+ (server) =>
494
+ selected.has(server) &&
495
+ !SEARCH_SERVER_ORDER.includes(server) &&
496
+ !alwaysOnServers.includes(server),
497
+ ),
425
498
  { ...options, inventoryIndex },
426
499
  ).map((entry) => entry.server);
427
- const contextualSearch = orderedSearchServers.filter((server) => selected.has(server));
500
+ const contextualSearch = orderedSearchServers.filter((server) =>
501
+ selected.has(server),
502
+ );
428
503
  const contextualServers = uniqueStrings([
429
504
  ...alwaysOnServers,
430
505
  ...contextualNonSearch,
@@ -433,34 +508,53 @@ function selectContextualServers(baseServers, profile, options = {}) {
433
508
  return contextualServers.length ? contextualServers : [...baseServers];
434
509
  }
435
510
 
436
- export function resolveMcpProfile(agentType = '', requestedProfile = 'auto') {
511
+ export function resolveMcpProfile(agentType = "", requestedProfile = "auto") {
437
512
  const normalized = normalizeProfileName(requestedProfile);
438
- return normalized === 'auto' ? resolveAutoProfile(agentType) : normalized;
513
+ return normalized === "auto" ? resolveAutoProfile(agentType) : normalized;
439
514
  }
440
515
 
441
- export function parseAvailableServers(rawAvailableServers = '') {
442
- if (Array.isArray(rawAvailableServers)) return uniqueStrings(rawAvailableServers);
443
- if (typeof rawAvailableServers !== 'string' || !rawAvailableServers.trim()) return [];
516
+ export function parseAvailableServers(rawAvailableServers = "") {
517
+ if (Array.isArray(rawAvailableServers))
518
+ return uniqueStrings(rawAvailableServers);
519
+ if (typeof rawAvailableServers !== "string" || !rawAvailableServers.trim())
520
+ return [];
444
521
  return uniqueStrings(rawAvailableServers.split(/[,\s]+/));
445
522
  }
446
523
 
447
- export function resolveSearchToolOrder(searchTool = '', workerIndex, allowedServers = SEARCH_SERVER_ORDER, taskText = '', options = {}) {
448
- const available = SEARCH_SERVER_ORDER.filter((tool) => allowedServers.includes(tool));
524
+ export function resolveSearchToolOrder(
525
+ searchTool = "",
526
+ workerIndex,
527
+ allowedServers = SEARCH_SERVER_ORDER,
528
+ taskText = "",
529
+ options = {},
530
+ ) {
531
+ const available = SEARCH_SERVER_ORDER.filter((tool) =>
532
+ allowedServers.includes(tool),
533
+ );
449
534
  if (!available.length) return [];
450
535
 
451
- const inventoryIndex = options.inventoryIndex instanceof Map
452
- ? options.inventoryIndex
453
- : buildInventoryIndex(options.inventory);
454
- const preferredSearchTool = searchTool && available.includes(searchTool)
455
- ? searchTool
456
- : inferPreferredSearchTool(taskText, inventoryIndex, available);
536
+ const inventoryIndex =
537
+ options.inventoryIndex instanceof Map
538
+ ? options.inventoryIndex
539
+ : buildInventoryIndex(options.inventory);
540
+ const preferredSearchTool =
541
+ searchTool && available.includes(searchTool)
542
+ ? searchTool
543
+ : inferPreferredSearchTool(taskText, inventoryIndex, available);
457
544
 
458
- const ranked = rankServers(available, { taskText, workerIndex, inventoryIndex }).map((entry) => entry.server);
545
+ const ranked = rankServers(available, {
546
+ taskText,
547
+ workerIndex,
548
+ inventoryIndex,
549
+ }).map((entry) => entry.server);
459
550
  if (!preferredSearchTool || !available.includes(preferredSearchTool)) {
460
551
  return ranked;
461
552
  }
462
553
 
463
- return [preferredSearchTool, ...ranked.filter((tool) => tool !== preferredSearchTool)];
554
+ return [
555
+ preferredSearchTool,
556
+ ...ranked.filter((tool) => tool !== preferredSearchTool),
557
+ ];
464
558
  }
465
559
 
466
560
  function getProfileDefinition(resolvedProfile) {
@@ -476,27 +570,43 @@ function applyManifestFilter(servers) {
476
570
  }
477
571
 
478
572
  export function resolveAllowedServers(options = {}) {
479
- const resolvedProfile = resolveMcpProfile(options.agentType, options.requestedProfile);
573
+ const resolvedProfile = resolveMcpProfile(
574
+ options.agentType,
575
+ options.requestedProfile,
576
+ );
480
577
  const profile = getProfileDefinition(resolvedProfile);
481
578
  const availableServers = parseAvailableServers(options.availableServers);
482
579
  const inventory = options.inventory || loadInventory(options.inventoryFile);
483
580
  const inventoryIndex = buildInventoryIndex(inventory);
484
581
  const baseServers = availableServers.length
485
- ? profile.allowedServers.filter((server) => availableServers.includes(server))
582
+ ? profile.allowedServers.filter((server) =>
583
+ availableServers.includes(server),
584
+ )
486
585
  : [...profile.allowedServers];
487
586
  const manifestFiltered = availableServers.length
488
587
  ? baseServers
489
588
  : applyManifestFilter(baseServers);
490
- return selectContextualServers(manifestFiltered, profile, { ...options, inventory, inventoryIndex });
589
+ return selectContextualServers(manifestFiltered, profile, {
590
+ ...options,
591
+ inventory,
592
+ inventoryIndex,
593
+ });
491
594
  }
492
595
 
493
596
  export function buildPromptHint(options = {}) {
494
- const resolvedProfile = resolveMcpProfile(options.agentType, options.requestedProfile);
495
- if (resolvedProfile === 'none') return '';
597
+ const resolvedProfile = resolveMcpProfile(
598
+ options.agentType,
599
+ options.requestedProfile,
600
+ );
601
+ if (resolvedProfile === "none") return "";
496
602
 
497
603
  const inventory = options.inventory || loadInventory(options.inventoryFile);
498
604
  const inventoryIndex = buildInventoryIndex(inventory);
499
- const allowedServers = resolveAllowedServers({ ...options, inventory, inventoryIndex });
605
+ const allowedServers = resolveAllowedServers({
606
+ ...options,
607
+ inventory,
608
+ inventoryIndex,
609
+ });
500
610
  const orderedTools = resolveSearchToolOrder(
501
611
  options.searchTool,
502
612
  Number.isInteger(options.workerIndex) ? options.workerIndex : undefined,
@@ -505,28 +615,40 @@ export function buildPromptHint(options = {}) {
505
615
  { inventory, inventoryIndex },
506
616
  );
507
617
  const has = (server) => allowedServers.includes(server);
508
- const orderedSearchHint = orderedTools.length > 1
509
- ? `웹 검색 우선순위: ${orderedTools.join(', ')}.`
510
- : orderedTools[0]
511
- ? `웹 검색은 ${orderedTools[0]}를 사용하세요.`
512
- : '';
513
- const searchFallbackHint = orderedTools.length > 1
514
- ? '검색 도구 실패 시 402, 429, 432, 433, quota 에러에서 재시도하지 말고 다음 도구로 전환하세요.'
515
- : '';
618
+ const orderedSearchHint =
619
+ orderedTools.length > 1
620
+ ? `웹 검색 우선순위: ${orderedTools.join(", ")}.`
621
+ : orderedTools[0]
622
+ ? `웹 검색은 ${orderedTools[0]}를 사용하세요.`
623
+ : "";
624
+ const searchFallbackHint =
625
+ orderedTools.length > 1
626
+ ? "검색 도구 실패 시 402, 429, 432, 433, quota 에러에서 재시도하지 말고 다음 도구로 전환하세요."
627
+ : "";
516
628
  return [
517
- has('context7') ? 'context7으로 관련 문서를 조회하세요.' : '',
518
- has('playwright')
519
- ? resolvedProfile === 'designer'
520
- ? '화면/레이아웃 확인은 playwright를 우선 사용하세요.'
521
- : '브라우저/UI 검증이 필요하면 playwright를 사용하세요.'
522
- : '',
523
- has('sequential-thinking') ? 'sequential-thinking으로 체계적으로 분석하세요.' : '',
524
- resolvedProfile === 'reviewer' && orderedTools[0] ? `외부 근거가 더 필요하면 ${orderedTools[0]}를 사용하세요.` : '',
525
- resolvedProfile !== 'reviewer' ? orderedSearchHint : '',
526
- resolvedProfile !== 'reviewer' ? searchFallbackHint : '',
527
- resolvedProfile === 'explore' ? '검색 깊이를 제한하고 읽기 전용 조사에 집중하세요.' : '',
528
- resolvedProfile === 'writer' ? '검색 결과의 출처 URL을 함께 제시하세요.' : '',
529
- ].filter(Boolean).join(' ');
629
+ has("context7") ? "context7으로 관련 문서를 조회하세요." : "",
630
+ has("playwright")
631
+ ? resolvedProfile === "designer"
632
+ ? "화면/레이아웃 확인은 playwright를 우선 사용하세요."
633
+ : "브라우저/UI 검증이 필요하면 playwright를 사용하세요."
634
+ : "",
635
+ has("sequential-thinking")
636
+ ? "sequential-thinking으로 체계적으로 분석하세요."
637
+ : "",
638
+ resolvedProfile === "reviewer" && orderedTools[0]
639
+ ? `외부 근거가 필요하면 ${orderedTools[0]}를 사용하세요.`
640
+ : "",
641
+ resolvedProfile !== "reviewer" ? orderedSearchHint : "",
642
+ resolvedProfile !== "reviewer" ? searchFallbackHint : "",
643
+ resolvedProfile === "explore"
644
+ ? "검색 깊이를 제한하고 읽기 전용 조사에 집중하세요."
645
+ : "",
646
+ resolvedProfile === "writer"
647
+ ? "검색 결과의 출처 URL을 함께 제시하세요."
648
+ : "",
649
+ ]
650
+ .filter(Boolean)
651
+ .join(" ");
530
652
  }
531
653
 
532
654
  export function getGeminiAllowedServers(options = {}) {
@@ -535,7 +657,10 @@ export function getGeminiAllowedServers(options = {}) {
535
657
 
536
658
  export function getCodexMcpConfig(options = {}) {
537
659
  const allowedServers = new Set(resolveAllowedServers(options));
538
- const resolvedProfile = resolveMcpProfile(options.agentType, options.requestedProfile);
660
+ const resolvedProfile = resolveMcpProfile(
661
+ options.agentType,
662
+ options.requestedProfile,
663
+ );
539
664
  // Codex에 실제 등록된 서버만 대상으로 config override 생성.
540
665
  // 미등록 서버에 enabled=false를 보내면 "invalid transport" 에러 발생.
541
666
  const registeredServers = parseAvailableServers(options.availableServers);
@@ -546,14 +671,15 @@ export function getCodexMcpConfig(options = {}) {
546
671
  }
547
672
  const targetServers = registeredServers;
548
673
 
549
- if (resolvedProfile === 'none') {
674
+ if (resolvedProfile === "none") {
550
675
  // Codex 0.115+: transport 없는 서버에 enabled=false를 보내면 "invalid transport" 에러.
551
676
  // 비허용 서버는 override에서 제외하고, 허용 서버만 명시적으로 설정한다.
552
677
  return { mcp_servers: {} };
553
678
  }
554
679
 
555
680
  const config = { mcp_servers: {} };
556
- const allowedToolsByServer = getProfileDefinition(resolvedProfile).allowedToolsByServer;
681
+ const allowedToolsByServer =
682
+ getProfileDefinition(resolvedProfile).allowedToolsByServer;
557
683
  for (const server of targetServers) {
558
684
  // Codex 0.115+: transport 없는 서버에 enabled=false를 보내면 "invalid transport" 에러.
559
685
  // 비허용 서버는 override에서 제외한다 (Codex 기본 설정이 유지됨).
@@ -571,15 +697,16 @@ export function getCodexMcpConfig(options = {}) {
571
697
 
572
698
  function toTomlLiteral(value) {
573
699
  if (Array.isArray(value)) {
574
- return `[${value.map((item) => toTomlLiteral(item)).join(',')}]`;
700
+ return `[${value.map((item) => toTomlLiteral(item)).join(",")}]`;
575
701
  }
576
- if (typeof value === 'string') return JSON.stringify(value);
577
- if (typeof value === 'number' || typeof value === 'boolean') return String(value);
702
+ if (typeof value === "string") return JSON.stringify(value);
703
+ if (typeof value === "number" || typeof value === "boolean")
704
+ return String(value);
578
705
  throw new Error(`지원하지 않는 TOML 값 타입: ${typeof value}`);
579
706
  }
580
707
 
581
708
  function flattenConfig(prefix, value, output) {
582
- if (value && typeof value === 'object' && !Array.isArray(value)) {
709
+ if (value && typeof value === "object" && !Array.isArray(value)) {
583
710
  for (const [key, nestedValue] of Object.entries(value)) {
584
711
  flattenConfig(prefix ? `${prefix}.${key}` : key, nestedValue, output);
585
712
  }
@@ -591,7 +718,7 @@ function flattenConfig(prefix, value, output) {
591
718
  export function getCodexConfigOverrides(options = {}) {
592
719
  const config = getCodexMcpConfig(options);
593
720
  const overrides = [];
594
- flattenConfig('', config, overrides);
721
+ flattenConfig("", config, overrides);
595
722
  return overrides;
596
723
  }
597
724
 
@@ -599,7 +726,10 @@ export function buildMcpPolicy(options = {}) {
599
726
  const inventory = options.inventory || loadInventory(options.inventoryFile);
600
727
  const inventoryIndex = buildInventoryIndex(inventory);
601
728
  const resolvedOptions = { ...options, inventory, inventoryIndex };
602
- const resolvedProfile = resolveMcpProfile(options.agentType, options.requestedProfile);
729
+ const resolvedProfile = resolveMcpProfile(
730
+ options.agentType,
731
+ options.requestedProfile,
732
+ );
603
733
  let allowedServers = resolveAllowedServers(resolvedOptions);
604
734
  const hint = buildPromptHint(resolvedOptions);
605
735
 
@@ -612,9 +742,10 @@ export function buildMcpPolicy(options = {}) {
612
742
  }
613
743
 
614
744
  return {
615
- requestedProfile: typeof options.requestedProfile === 'string' && options.requestedProfile
616
- ? options.requestedProfile
617
- : 'auto',
745
+ requestedProfile:
746
+ typeof options.requestedProfile === "string" && options.requestedProfile
747
+ ? options.requestedProfile
748
+ : "auto",
618
749
  resolvedProfile,
619
750
  resolvedPhase: phase || null,
620
751
  allowedServers,
@@ -630,7 +761,7 @@ function shellEscape(value) {
630
761
  }
631
762
 
632
763
  function shellArray(name, values) {
633
- return `${name}=(${values.map((value) => shellEscape(value)).join(' ')})`;
764
+ return `${name}=(${values.map((value) => shellEscape(value)).join(" ")})`;
634
765
  }
635
766
 
636
767
  export function toShellExports(policy) {
@@ -638,30 +769,33 @@ export function toShellExports(policy) {
638
769
  `MCP_PROFILE_REQUESTED=${shellEscape(policy.requestedProfile)}`,
639
770
  `MCP_RESOLVED_PROFILE=${shellEscape(policy.resolvedProfile)}`,
640
771
  `MCP_HINT=${shellEscape(policy.hint)}`,
641
- shellArray('GEMINI_ALLOWED_SERVERS', policy.geminiAllowedServers),
642
- shellArray('CODEX_CONFIG_FLAGS', policy.codexConfigOverrides.flatMap((override) => ['-c', override])),
772
+ shellArray("GEMINI_ALLOWED_SERVERS", policy.geminiAllowedServers),
773
+ shellArray(
774
+ "CODEX_CONFIG_FLAGS",
775
+ policy.codexConfigOverrides.flatMap((override) => ["-c", override]),
776
+ ),
643
777
  `CODEX_CONFIG_JSON=${shellEscape(JSON.stringify(policy.codexConfig))}`,
644
778
  ];
645
779
  if (policy.resolvedPhase) {
646
780
  lines.push(`MCP_PIPELINE_PHASE=${shellEscape(policy.resolvedPhase)}`);
647
781
  }
648
- return lines.join('\n');
782
+ return lines.join("\n");
649
783
  }
650
784
 
651
785
  function parseCliArgs(argv) {
652
786
  const args = {
653
- command: 'json',
654
- agentType: '',
655
- requestedProfile: 'auto',
787
+ command: "json",
788
+ agentType: "",
789
+ requestedProfile: "auto",
656
790
  availableServers: [],
657
- inventoryFile: '',
658
- searchTool: '',
659
- taskText: '',
791
+ inventoryFile: "",
792
+ searchTool: "",
793
+ taskText: "",
660
794
  workerIndex: undefined,
661
795
  };
662
796
 
663
- const [first = 'json'] = argv;
664
- if (!first.startsWith('--')) {
797
+ const [first = "json"] = argv;
798
+ if (!first.startsWith("--")) {
665
799
  args.command = first;
666
800
  argv = argv.slice(1);
667
801
  }
@@ -676,28 +810,28 @@ function parseCliArgs(argv) {
676
810
  };
677
811
 
678
812
  switch (token) {
679
- case '--agent':
813
+ case "--agent":
680
814
  args.agentType = next();
681
815
  break;
682
- case '--profile':
816
+ case "--profile":
683
817
  args.requestedProfile = next();
684
818
  break;
685
- case '--available':
819
+ case "--available":
686
820
  args.availableServers = parseAvailableServers(next());
687
821
  break;
688
- case '--inventory-file':
822
+ case "--inventory-file":
689
823
  args.inventoryFile = next();
690
824
  break;
691
- case '--search-tool':
825
+ case "--search-tool":
692
826
  args.searchTool = next();
693
827
  break;
694
- case '--task-text':
828
+ case "--task-text":
695
829
  args.taskText = next();
696
830
  break;
697
- case '--worker-index':
831
+ case "--worker-index":
698
832
  args.workerIndex = Number.parseInt(next(), 10);
699
833
  break;
700
- case '--phase':
834
+ case "--phase":
701
835
  args.phase = next();
702
836
  break;
703
837
  default:
@@ -713,7 +847,9 @@ export async function runCli(argv = process.argv.slice(2)) {
713
847
  try {
714
848
  args = parseCliArgs(argv);
715
849
  } catch (error) {
716
- console.error(`[mcp-filter] ${error instanceof Error ? error.message : String(error)}`);
850
+ console.error(
851
+ `[mcp-filter] ${error instanceof Error ? error.message : String(error)}`,
852
+ );
717
853
  process.exitCode = 64;
718
854
  return;
719
855
  }
@@ -722,11 +858,13 @@ export async function runCli(argv = process.argv.slice(2)) {
722
858
  try {
723
859
  policy = buildMcpPolicy(args);
724
860
  } catch (error) {
725
- console.error(`[mcp-filter] ${error instanceof Error ? error.message : String(error)}`);
861
+ console.error(
862
+ `[mcp-filter] ${error instanceof Error ? error.message : String(error)}`,
863
+ );
726
864
  process.exitCode = 65;
727
865
  return;
728
866
  }
729
- if (args.command === 'shell') {
867
+ if (args.command === "shell") {
730
868
  process.stdout.write(`${toShellExports(policy)}\n`);
731
869
  return;
732
870
  }