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
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+
2
3
  // scripts/test-lock.mjs — 테스트 러너 싱글톤 lockfile 가드
3
4
  //
4
5
  // npm test 동시 실행 방지. conductor 같은 child-spawn 테스트가
@@ -6,9 +7,9 @@
6
7
  //
7
8
  // 사용: node scripts/test-lock.mjs [-- ...node --test args]
8
9
 
9
- import { writeFileSync, readFileSync, unlinkSync, mkdirSync } from "node:fs";
10
- import { join } from "node:path";
11
10
  import { spawn } from "node:child_process";
11
+ import { mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
12
+ import { join } from "node:path";
12
13
 
13
14
  const LOCK_DIR = join(import.meta.dirname, "..", ".test-lock");
14
15
  const LOCK_FILE = join(LOCK_DIR, "pid.lock");
@@ -31,8 +32,12 @@ function acquireLock() {
31
32
  // Windows에서 process.kill(pid,0)이 git-bash PID를 못 잡음.
32
33
  // 단순하게: lockfile이 있고 STALE_THRESHOLD 이내면 거부.
33
34
  if (age >= 0 && age < STALE_THRESHOLD_MS) {
34
- console.error(`\x1b[31m✗ 테스트 이미 실행 중 (PID ${existing.pid}, ${Math.round(age / 1000)}초 전 시작)\x1b[0m`);
35
- console.error(` 동시 실행은 WT 메모리 폭발을 유발합니다. 기다리거나 PID를 kill하세요.`);
35
+ console.error(
36
+ `\x1b[31m✗ 테스트 이미 실행 (PID ${existing.pid}, ${Math.round(age / 1000)}초 전 시작)\x1b[0m`,
37
+ );
38
+ console.error(
39
+ ` 동시 실행은 WT 메모리 폭발을 유발합니다. 기다리거나 PID를 kill하세요.`,
40
+ );
36
41
  console.error(` 강제 해제: rm ${LOCK_FILE}`);
37
42
  process.exit(1);
38
43
  }
@@ -46,7 +51,9 @@ function releaseLock() {
46
51
  try {
47
52
  const lock = readLock();
48
53
  if (lock && lock.pid === process.pid) unlinkSync(LOCK_FILE);
49
- } catch { /* ignore */ }
54
+ } catch {
55
+ /* ignore */
56
+ }
50
57
  }
51
58
 
52
59
  // ── main ──
@@ -55,8 +62,14 @@ acquireLock();
55
62
 
56
63
  // cleanup on exit
57
64
  process.on("exit", releaseLock);
58
- process.on("SIGINT", () => { releaseLock(); process.exit(130); });
59
- process.on("SIGTERM", () => { releaseLock(); process.exit(143); });
65
+ process.on("SIGINT", () => {
66
+ releaseLock();
67
+ process.exit(130);
68
+ });
69
+ process.on("SIGTERM", () => {
70
+ releaseLock();
71
+ process.exit(143);
72
+ });
60
73
 
61
74
  // forward args after -- to node --test
62
75
  const args = process.argv.slice(2);
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import test from "node:test";
3
2
  import assert from "node:assert/strict";
4
3
  import { spawnSync } from "node:child_process";
5
4
  import { dirname, resolve } from "node:path";
5
+ import test from "node:test";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
8
  const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
@@ -14,13 +14,13 @@ function runBash(command) {
14
14
  encoding: "utf8",
15
15
  env: {
16
16
  ...process.env,
17
- TFX_TEAM_NAME: '',
18
- TFX_TEAM_TASK_ID: '',
19
- TFX_TEAM_AGENT_NAME: '',
20
- TFX_TEAM_LEAD_NAME: '',
21
- TFX_HUB_URL: '',
22
- TMUX: '',
23
- }
17
+ TFX_TEAM_NAME: "",
18
+ TFX_TEAM_TASK_ID: "",
19
+ TFX_TEAM_AGENT_NAME: "",
20
+ TFX_TEAM_LEAD_NAME: "",
21
+ TFX_HUB_URL: "",
22
+ TMUX: "",
23
+ },
24
24
  });
25
25
  }
26
26
 
@@ -30,7 +30,7 @@ function out(result) {
30
30
 
31
31
  test("gemini 모드에서는 no-claude-native 강제 치환이 적용되지 않는다", () => {
32
32
  const result = runBash(
33
- "TFX_CLI_MODE=gemini TFX_NO_CLAUDE_NATIVE=1 bash scripts/tfx-route.sh explore 'test-case'"
33
+ "TFX_CLI_MODE=gemini TFX_NO_CLAUDE_NATIVE=1 bash scripts/tfx-route.sh explore 'test-case'",
34
34
  );
35
35
 
36
36
  assert.equal(result.status, 0, out(result));
@@ -39,17 +39,21 @@ test("gemini 모드에서는 no-claude-native 강제 치환이 적용되지 않
39
39
 
40
40
  test("auto 모드 + no-claude-native=1이면 explore가 codex로 치환된다", () => {
41
41
  const result = runBash(
42
- "TFX_CLI_MODE=auto TFX_NO_CLAUDE_NATIVE=1 CODEX_BIN=true bash scripts/tfx-route.sh explore 'test-case' minimal 5"
42
+ "TFX_CLI_MODE=auto TFX_NO_CLAUDE_NATIVE=1 CODEX_BIN=true bash scripts/tfx-route.sh explore 'test-case' minimal 5",
43
43
  );
44
44
 
45
45
  assert.equal(result.status, 0, out(result));
46
- assert.match(out(result), /TFX_NO_CLAUDE_NATIVE=1: explore -> codex/, out(result));
46
+ assert.match(
47
+ out(result),
48
+ /TFX_NO_CLAUDE_NATIVE=1: explore -> codex/,
49
+ out(result),
50
+ );
47
51
  assert.match(out(result), /type=codex|cli:\\s*codex/i, out(result));
48
52
  });
49
53
 
50
54
  test("TFX_NO_CLAUDE_NATIVE는 0/1 값만 허용한다", () => {
51
55
  const result = runBash(
52
- "TFX_NO_CLAUDE_NATIVE=2 bash scripts/tfx-route.sh explore 'test-case'"
56
+ "TFX_NO_CLAUDE_NATIVE=2 bash scripts/tfx-route.sh explore 'test-case'",
53
57
  );
54
58
 
55
59
  assert.notEqual(result.status, 0, out(result));
@@ -11,8 +11,8 @@
11
11
  // node tfx-batch-stats.mjs agent <name> 특정 에이전트 통계
12
12
 
13
13
  import { readFileSync } from "node:fs";
14
- import { join } from "node:path";
15
14
  import { homedir } from "node:os";
15
+ import { join } from "node:path";
16
16
 
17
17
  const CACHE_DIR = join(homedir(), ".claude", "cache");
18
18
  const EVENTS_FILE = join(CACHE_DIR, "batch-events.jsonl");
@@ -21,18 +21,30 @@ const EVENTS_FILE = join(CACHE_DIR, "batch-events.jsonl");
21
21
  const AIMD_INITIAL = 3;
22
22
  const AIMD_MIN = 1;
23
23
  const AIMD_MAX = 10;
24
- const AIMD_INC = 1; // 성공 시 +1
25
- const AIMD_DEC = 0.5; // 실패 시 ×0.5
24
+ const AIMD_INC = 1; // 성공 시 +1
25
+ const AIMD_DEC = 0.5; // 실패 시 ×0.5
26
26
  const WINDOW_MS = 30 * 60 * 1000; // 30분 윈도우
27
27
 
28
28
  // ── 이벤트 읽기 ──
29
29
  export function readBatchEvents(opts = {}) {
30
30
  const { sinceMs = 0, agent = null } = opts;
31
31
  try {
32
- const lines = readFileSync(EVENTS_FILE, "utf-8").trim().split("\n").filter(Boolean);
32
+ const lines = readFileSync(EVENTS_FILE, "utf-8")
33
+ .trim()
34
+ .split("\n")
35
+ .filter(Boolean);
33
36
  return lines
34
- .map((l) => { try { return JSON.parse(l); } catch { return null; } })
35
- .filter((e) => e && (!sinceMs || e.ts >= sinceMs) && (!agent || e.agent === agent));
37
+ .map((l) => {
38
+ try {
39
+ return JSON.parse(l);
40
+ } catch {
41
+ return null;
42
+ }
43
+ })
44
+ .filter(
45
+ (e) =>
46
+ e && (!sinceMs || e.ts >= sinceMs) && (!agent || e.agent === agent),
47
+ );
36
48
  } catch {
37
49
  return [];
38
50
  }
@@ -44,9 +56,11 @@ export function getAgentStats(opts = {}) {
44
56
  const stats = {};
45
57
 
46
58
  for (const ev of events) {
47
- if (!stats[ev.agent]) stats[ev.agent] = { success: 0, fail: 0, timeout: 0, total: 0 };
59
+ if (!stats[ev.agent])
60
+ stats[ev.agent] = { success: 0, fail: 0, timeout: 0, total: 0 };
48
61
  const s = stats[ev.agent];
49
- if (ev.result === "success" || ev.result === "success_with_warnings") s.success++;
62
+ if (ev.result === "success" || ev.result === "success_with_warnings")
63
+ s.success++;
50
64
  else if (ev.result === "timeout") s.timeout++;
51
65
  else s.fail++;
52
66
  s.total++;
@@ -85,11 +99,18 @@ if (scriptName.endsWith("tfx-batch-stats.mjs")) {
85
99
  const sinceMs = recent ? Date.now() - WINDOW_MS : 0;
86
100
 
87
101
  if (cmd === "batch") {
88
- console.log(JSON.stringify({ batchSize: getAimdBatchSize(), window: "30m" }));
102
+ console.log(
103
+ JSON.stringify({ batchSize: getAimdBatchSize(), window: "30m" }),
104
+ );
89
105
  } else if (cmd === "agent") {
90
106
  const name = process.argv[3];
91
- if (!name) { console.error("에이전트명 필수: node tfx-batch-stats.mjs agent executor"); process.exit(1); }
92
- console.log(JSON.stringify(getAgentStats({ sinceMs, agent: name }), null, 2));
107
+ if (!name) {
108
+ console.error("에이전트명 필수: node tfx-batch-stats.mjs agent executor");
109
+ process.exit(1);
110
+ }
111
+ console.log(
112
+ JSON.stringify(getAgentStats({ sinceMs, agent: name }), null, 2),
113
+ );
93
114
  } else {
94
115
  console.log(JSON.stringify(getAgentStats({ sinceMs }), null, 2));
95
116
  }
@@ -9,7 +9,7 @@
9
9
  * 자동 만료: 30분
10
10
  */
11
11
 
12
- import { writeFileSync, existsSync, readFileSync } from "node:fs";
12
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
13
13
  import { tmpdir } from "node:os";
14
14
  import { join } from "node:path";
15
15
 
@@ -42,15 +42,22 @@ async function main() {
42
42
 
43
43
  // 모든 tfx CLI 라우팅 스킬에 gate 적용
44
44
  const TFX_ROUTING_SKILLS = new Set([
45
- "tfx-multi", "tfx-team", "tfx-auto", "tfx-auto-codex",
46
- "tfx-codex", "tfx-gemini", "tfx-autoresearch",
45
+ "tfx-multi",
46
+ "tfx-team",
47
+ "tfx-auto",
48
+ "tfx-auto-codex",
49
+ "tfx-codex",
50
+ "tfx-gemini",
51
+ "tfx-autoresearch",
47
52
  ]);
48
53
 
49
54
  if (TFX_ROUTING_SKILLS.has(skill)) {
50
55
  // 활성화: 상태 파일 생성/갱신
56
+ // ppid = Claude Code 세션 PID (훅은 Claude Code의 자식 프로세스)
51
57
  const state = {
52
58
  active: true,
53
59
  activatedAt: Date.now(),
60
+ ownerPid: process.ppid,
54
61
  dispatched: false,
55
62
  nativeWorkCalls: 0,
56
63
  nativeWorkCallsSinceDispatch: 0,
@@ -64,7 +71,7 @@ async function main() {
64
71
  hookEventName: "PreToolUse",
65
72
  additionalContext:
66
73
  "[tfx-multi] gate 활성화됨. CLI 작업은 headless로 dispatch 필수:\n" +
67
- 'Bash("tfx multi --teammate-mode headless --auto-attach --dashboard --assign \'codex:프롬프트:역할\' --timeout 600")',
74
+ "Bash(\"tfx multi --teammate-mode headless --auto-attach --dashboard --assign 'codex:프롬프트:역할' --timeout 600\")",
68
75
  },
69
76
  }),
70
77
  );
@@ -13,9 +13,17 @@
13
13
  // 6. CLI 이슈 자동 수집
14
14
  // 7. 구조화된 결과 출력 (=== TFX-ROUTE RESULT ===)
15
15
 
16
- import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
17
- import { join } from "path";
16
+ import {
17
+ appendFileSync,
18
+ existsSync,
19
+ mkdirSync,
20
+ readdirSync,
21
+ readFileSync,
22
+ statSync,
23
+ writeFileSync,
24
+ } from "fs";
18
25
  import { homedir } from "os";
26
+ import { join } from "path";
19
27
 
20
28
  const HOME = homedir();
21
29
  const CACHE_DIR = join(HOME, ".claude", "cache");
@@ -171,7 +179,10 @@ function extractCodexSessionId(rawOutput, stderrContent = "") {
171
179
 
172
180
  function appendCodexResumeHint(output, rawOutput, stderrContent = "") {
173
181
  const normalizedOutput = String(output || "").trim();
174
- if (/Codex session ID:/i.test(normalizedOutput) || /codex resume\s+/i.test(normalizedOutput)) {
182
+ if (
183
+ /Codex session ID:/i.test(normalizedOutput) ||
184
+ /codex resume\s+/i.test(normalizedOutput)
185
+ ) {
175
186
  return normalizedOutput;
176
187
  }
177
188
 
@@ -184,7 +195,11 @@ function appendCodexResumeHint(output, rawOutput, stderrContent = "") {
184
195
  return normalizedOutput ? `${normalizedOutput}\n\n${hint}` : hint;
185
196
  }
186
197
 
187
- function prepareCliOutput(rawOutput, cliType, { cleanTui = true, stderrContent = "" } = {}) {
198
+ function prepareCliOutput(
199
+ rawOutput,
200
+ cliType,
201
+ { cleanTui = true, stderrContent = "" } = {},
202
+ ) {
188
203
  let prepared = cliType === "codex" ? filterCodexOutput(rawOutput) : rawOutput;
189
204
  if (cleanTui && process.env.TFX_CLEAN_TUI !== "0") {
190
205
  prepared = cleanTuiArtifacts(prepared, cliType);
@@ -214,7 +229,9 @@ function cleanTuiArtifacts(output, cliType) {
214
229
  .replace(/^\s*codex\s*$/gm, "")
215
230
  .replace(/^[^\S\n]*[›❯]\s*Applied.*$/gm, "");
216
231
  } else if (normalizedCliType.startsWith("gemini")) {
217
- cleaned = cleaned.replace(/^[^\S\n]*[╭╮╰╯│─═].*$/gm, "").replace(/^[^\S\n]*>\s*$/gm, "");
232
+ cleaned = cleaned
233
+ .replace(/^[^\S\n]*[╭╮╰╯│─═].*$/gm, "")
234
+ .replace(/^[^\S\n]*>\s*$/gm, "");
218
235
  } else if (normalizedCliType.startsWith("claude")) {
219
236
  cleaned = cleaned.replace(/^[^\S\n]*[━─]{5,}.*$/gm, "");
220
237
  }
@@ -285,7 +302,10 @@ function recordBatchEvent(result, agent) {
285
302
  const eventsFile = join(CACHE_DIR, "batch-events.jsonl");
286
303
  try {
287
304
  mkdirSync(CACHE_DIR, { recursive: true });
288
- appendFileSync(eventsFile, JSON.stringify({ ts: Date.now(), agent, result }) + "\n");
305
+ appendFileSync(
306
+ eventsFile,
307
+ JSON.stringify({ ts: Date.now(), agent, result }) + "\n",
308
+ );
289
309
 
290
310
  // 자동 회전: 200줄 초과 시 최근 100줄 유지
291
311
  const content = readFileSync(eventsFile, "utf-8").trim();
@@ -301,12 +321,42 @@ function trackCliIssue(cliType, agent, stderrText, exitCode) {
301
321
  if (!stderrText && exitCode === 0) return;
302
322
 
303
323
  const patterns = [
304
- { regex: /sandbox image.*missing/i, pattern: "sandbox_missing", msg: "Docker sandbox image not found", severity: "warn" },
305
- { regex: /rate.limit|429|too many requests/i, pattern: "rate_limit", msg: "API rate limit exceeded", severity: "warn" },
306
- { regex: /ECONNREFUSED|ENOTFOUND|network/i, pattern: "network_error", msg: "Network connection failed", severity: "error" },
307
- { regex: /deprecated/i, pattern: "deprecated_flag", msg: "Deprecated flag/feature detected", severity: "warn" },
308
- { regex: /API_KEY.*not.set|auth.*fail|unauthorized|401/i, pattern: "auth_error", msg: "Authentication failed", severity: "error" },
309
- { regex: /ENOMEM|out of memory|heap/i, pattern: "oom", msg: "Out of memory", severity: "error" },
324
+ {
325
+ regex: /sandbox image.*missing/i,
326
+ pattern: "sandbox_missing",
327
+ msg: "Docker sandbox image not found",
328
+ severity: "warn",
329
+ },
330
+ {
331
+ regex: /rate.limit|429|too many requests/i,
332
+ pattern: "rate_limit",
333
+ msg: "API rate limit exceeded",
334
+ severity: "warn",
335
+ },
336
+ {
337
+ regex: /ECONNREFUSED|ENOTFOUND|network/i,
338
+ pattern: "network_error",
339
+ msg: "Network connection failed",
340
+ severity: "error",
341
+ },
342
+ {
343
+ regex: /deprecated/i,
344
+ pattern: "deprecated_flag",
345
+ msg: "Deprecated flag/feature detected",
346
+ severity: "warn",
347
+ },
348
+ {
349
+ regex: /API_KEY.*not.set|auth.*fail|unauthorized|401/i,
350
+ pattern: "auth_error",
351
+ msg: "Authentication failed",
352
+ severity: "error",
353
+ },
354
+ {
355
+ regex: /ENOMEM|out of memory|heap/i,
356
+ pattern: "oom",
357
+ msg: "Out of memory",
358
+ severity: "error",
359
+ },
310
360
  ];
311
361
 
312
362
  let matched = null;
@@ -318,7 +368,11 @@ function trackCliIssue(cliType, agent, stderrText, exitCode) {
318
368
  }
319
369
 
320
370
  if (!matched && exitCode !== 0 && exitCode !== 124) {
321
- matched = { pattern: "unknown_error", msg: `Exit code ${exitCode}`, severity: "warn" };
371
+ matched = {
372
+ pattern: "unknown_error",
373
+ msg: `Exit code ${exitCode}`,
374
+ severity: "warn",
375
+ };
322
376
  }
323
377
 
324
378
  if (!matched) return;
@@ -329,21 +383,30 @@ function trackCliIssue(cliType, agent, stderrText, exitCode) {
329
383
 
330
384
  // 중복 방지: 같은 패턴+cli가 최근 5분 내 기록됐으면 건너뜀
331
385
  if (existsSync(issuesFile)) {
332
- const lines = readFileSync(issuesFile, "utf-8").trim().split("\n").slice(-5);
386
+ const lines = readFileSync(issuesFile, "utf-8")
387
+ .trim()
388
+ .split("\n")
389
+ .slice(-5);
333
390
  const now = Date.now();
334
391
  for (const line of lines) {
335
392
  try {
336
393
  const entry = JSON.parse(line);
337
- if (entry.pattern === matched.pattern && entry.cli === cliType && now - entry.ts < 300000) return;
394
+ if (
395
+ entry.pattern === matched.pattern &&
396
+ entry.cli === cliType &&
397
+ now - entry.ts < 300000
398
+ )
399
+ return;
338
400
  } catch {}
339
401
  }
340
402
  }
341
403
 
342
404
  const snippet = stderrText.substring(0, 200).replace(/\n/g, " ");
343
405
 
344
- const retryCount = (matched.pattern === "rate_limit" && cliType === "gemini")
345
- ? parseInt(process.env.TFX_GEMINI_429_RETRIES || "0", 10)
346
- : undefined;
406
+ const retryCount =
407
+ matched.pattern === "rate_limit" && cliType === "gemini"
408
+ ? parseInt(process.env.TFX_GEMINI_429_RETRIES || "0", 10)
409
+ : undefined;
347
410
 
348
411
  const issueEntry = {
349
412
  ts: Date.now(),
@@ -440,7 +503,8 @@ function main() {
440
503
  if (exitCode === 0) accumulateTokens(cliType, tokens);
441
504
 
442
505
  // 5. AIMD 배치 이벤트
443
- const aimdResult = exitCode === 0 ? "success" : exitCode === 124 ? "timeout" : "failed";
506
+ const aimdResult =
507
+ exitCode === 0 ? "success" : exitCode === 124 ? "timeout" : "failed";
444
508
  recordBatchEvent(aimdResult, agent);
445
509
 
446
510
  // 6. CLI 이슈 추적
@@ -462,7 +526,9 @@ function main() {
462
526
  if (exitCode === 0) {
463
527
  if (stderrContent) {
464
528
  console.log("status: success_with_warnings");
465
- console.log(`warnings: ${stderrContent.split("\n").slice(0, 3).join(" ")}`);
529
+ console.log(
530
+ `warnings: ${stderrContent.split("\n").slice(0, 3).join(" ")}`,
531
+ );
466
532
  } else {
467
533
  console.log("status: success");
468
534
  }
@@ -498,6 +564,7 @@ function main() {
498
564
  }
499
565
 
500
566
  import { fileURLToPath } from "url";
567
+
501
568
  if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
502
569
  main();
503
570
  }