triflux 10.0.0 → 10.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (426) hide show
  1. package/CLAUDE.md +171 -0
  2. package/README.md +32 -15
  3. package/bin/triflux.mjs +62 -5
  4. package/hooks/agent-route-guard.mjs +109 -0
  5. package/hooks/cross-review-tracker.mjs +122 -0
  6. package/hooks/error-context.mjs +148 -0
  7. package/hooks/hook-adaptive-collector.mjs +86 -0
  8. package/hooks/hook-manager.mjs +365 -0
  9. package/hooks/hook-orchestrator.mjs +312 -0
  10. package/hooks/hook-registry.json +246 -0
  11. package/hooks/hooks.json +89 -0
  12. package/hooks/keyword-rules.json +574 -0
  13. package/hooks/lib/resolve-root.mjs +59 -0
  14. package/hooks/mcp-config-watcher.mjs +80 -0
  15. package/hooks/pipeline-stop.mjs +76 -0
  16. package/hooks/safety-guard.mjs +169 -0
  17. package/hooks/subagent-verifier.mjs +80 -0
  18. package/hub/account-broker.mjs +251 -0
  19. package/hub/adaptive-diagnostic.mjs +323 -0
  20. package/hub/adaptive-inject.mjs +186 -0
  21. package/hub/adaptive-memory.mjs +163 -0
  22. package/hub/adaptive.mjs +143 -0
  23. package/hub/assign-callbacks.mjs +133 -0
  24. package/hub/bridge.mjs +799 -0
  25. package/hub/cli-adapter-base.mjs +280 -0
  26. package/hub/codex-adapter.mjs +199 -0
  27. package/hub/codex-compat.mjs +11 -0
  28. package/hub/codex-preflight.mjs +166 -0
  29. package/hub/delegator/contracts.mjs +37 -0
  30. package/hub/delegator/index.mjs +14 -0
  31. package/hub/delegator/schema/delegator-tools.schema.json +250 -0
  32. package/hub/delegator/service.mjs +307 -0
  33. package/hub/delegator/tool-definitions.mjs +35 -0
  34. package/hub/fullcycle.mjs +96 -0
  35. package/hub/gemini-adapter.mjs +180 -0
  36. package/hub/hitl.mjs +143 -0
  37. package/hub/intent.mjs +193 -0
  38. package/hub/lib/cache-guard.mjs +114 -0
  39. package/hub/lib/known-errors.json +72 -0
  40. package/hub/lib/memory-store.mjs +748 -0
  41. package/hub/lib/process-utils.mjs +361 -0
  42. package/hub/lib/ssh-command.mjs +211 -0
  43. package/hub/lib/ssh-retry.mjs +59 -0
  44. package/hub/lib/uuidv7.mjs +44 -0
  45. package/hub/memory-doctor.mjs +480 -0
  46. package/hub/middleware/request-logger.mjs +161 -0
  47. package/hub/paths.mjs +30 -0
  48. package/hub/pipe.mjs +664 -0
  49. package/hub/pipeline/gates/confidence.mjs +56 -0
  50. package/hub/pipeline/gates/consensus.mjs +94 -0
  51. package/hub/pipeline/gates/index.mjs +5 -0
  52. package/hub/pipeline/gates/selfcheck.mjs +82 -0
  53. package/hub/pipeline/index.mjs +318 -0
  54. package/hub/pipeline/state.mjs +191 -0
  55. package/hub/pipeline/transitions.mjs +124 -0
  56. package/hub/platform.mjs +225 -0
  57. package/hub/public/dashboard.html +355 -0
  58. package/hub/public/tray-icon.ico +0 -0
  59. package/hub/public/tray-icon.png +0 -0
  60. package/hub/quality/deslop.mjs +253 -0
  61. package/hub/reflexion.mjs +372 -0
  62. package/hub/research.mjs +146 -0
  63. package/hub/router.mjs +791 -0
  64. package/hub/routing/complexity.mjs +166 -0
  65. package/hub/routing/index.mjs +117 -0
  66. package/hub/routing/q-learning.mjs +336 -0
  67. package/hub/schema.sql +148 -0
  68. package/hub/server.mjs +1264 -0
  69. package/hub/session-fingerprint.mjs +352 -0
  70. package/hub/state.mjs +258 -0
  71. package/hub/store-adapter.mjs +118 -0
  72. package/hub/store.mjs +857 -0
  73. package/hub/team/agent-map.json +11 -0
  74. package/hub/team/ansi.mjs +379 -0
  75. package/hub/team/backend.mjs +90 -0
  76. package/hub/team/cli/commands/attach.mjs +37 -0
  77. package/hub/team/cli/commands/control.mjs +43 -0
  78. package/hub/team/cli/commands/debug.mjs +74 -0
  79. package/hub/team/cli/commands/focus.mjs +53 -0
  80. package/hub/team/cli/commands/interrupt.mjs +36 -0
  81. package/hub/team/cli/commands/kill.mjs +37 -0
  82. package/hub/team/cli/commands/list.mjs +24 -0
  83. package/hub/team/cli/commands/send.mjs +37 -0
  84. package/hub/team/cli/commands/start/index.mjs +106 -0
  85. package/hub/team/cli/commands/start/parse-args.mjs +130 -0
  86. package/hub/team/cli/commands/start/start-headless.mjs +109 -0
  87. package/hub/team/cli/commands/start/start-in-process.mjs +40 -0
  88. package/hub/team/cli/commands/start/start-mux.mjs +73 -0
  89. package/hub/team/cli/commands/start/start-wt.mjs +69 -0
  90. package/hub/team/cli/commands/status.mjs +87 -0
  91. package/hub/team/cli/commands/stop.mjs +31 -0
  92. package/hub/team/cli/commands/task.mjs +30 -0
  93. package/hub/team/cli/commands/tasks.mjs +13 -0
  94. package/hub/team/cli/help.mjs +42 -0
  95. package/hub/team/cli/index.mjs +41 -0
  96. package/hub/team/cli/manifest.mjs +29 -0
  97. package/hub/team/cli/render.mjs +30 -0
  98. package/hub/team/cli/services/attach-fallback.mjs +54 -0
  99. package/hub/team/cli/services/hub-client.mjs +227 -0
  100. package/hub/team/cli/services/member-selector.mjs +30 -0
  101. package/hub/team/cli/services/native-control.mjs +117 -0
  102. package/hub/team/cli/services/runtime-mode.mjs +62 -0
  103. package/hub/team/cli/services/state-store.mjs +48 -0
  104. package/hub/team/cli/services/task-model.mjs +30 -0
  105. package/hub/team/conductor-mesh-bridge.mjs +121 -0
  106. package/hub/team/conductor.mjs +671 -0
  107. package/hub/team/dashboard-anchor.mjs +14 -0
  108. package/hub/team/dashboard-layout.mjs +33 -0
  109. package/hub/team/dashboard-open.mjs +153 -0
  110. package/hub/team/dashboard.mjs +274 -0
  111. package/hub/team/event-log.mjs +76 -0
  112. package/hub/team/handoff.mjs +303 -0
  113. package/hub/team/headless.mjs +1156 -0
  114. package/hub/team/health-probe.mjs +272 -0
  115. package/hub/team/launcher-template.mjs +95 -0
  116. package/hub/team/lead-control.mjs +104 -0
  117. package/hub/team/native-supervisor.mjs +392 -0
  118. package/hub/team/native.mjs +649 -0
  119. package/hub/team/nativeProxy.mjs +688 -0
  120. package/hub/team/notify.mjs +293 -0
  121. package/hub/team/orchestrator.mjs +161 -0
  122. package/hub/team/pane.mjs +153 -0
  123. package/hub/team/process-cleanup.mjs +342 -0
  124. package/hub/team/psmux.mjs +1354 -0
  125. package/hub/team/remote-probe.mjs +276 -0
  126. package/hub/team/remote-session.mjs +299 -0
  127. package/hub/team/remote-watcher.mjs +478 -0
  128. package/hub/team/routing.mjs +223 -0
  129. package/hub/team/session-sync.mjs +169 -0
  130. package/hub/team/session.mjs +611 -0
  131. package/hub/team/shared.mjs +13 -0
  132. package/hub/team/staleState.mjs +361 -0
  133. package/hub/team/swarm-hypervisor.mjs +589 -0
  134. package/hub/team/swarm-locks.mjs +204 -0
  135. package/hub/team/swarm-planner.mjs +260 -0
  136. package/hub/team/swarm-reconciler.mjs +137 -0
  137. package/hub/team/tui-lite.mjs +380 -0
  138. package/hub/team/tui-remote-adapter.mjs +393 -0
  139. package/hub/team/tui-viewer.mjs +463 -0
  140. package/hub/team/tui.mjs +1449 -0
  141. package/hub/team/worktree-lifecycle.mjs +193 -0
  142. package/hub/team/wt-manager.mjs +407 -0
  143. package/hub/team/wt-templates.json +43 -0
  144. package/hub/team-bridge.mjs +27 -0
  145. package/hub/token-mode.mjs +224 -0
  146. package/hub/tools.mjs +636 -0
  147. package/hub/tray.mjs +376 -0
  148. package/hub/workers/claude-worker.mjs +475 -0
  149. package/hub/workers/codex-mcp.mjs +507 -0
  150. package/hub/workers/delegator-mcp.mjs +1076 -0
  151. package/hub/workers/factory.mjs +21 -0
  152. package/hub/workers/gemini-worker.mjs +374 -0
  153. package/hub/workers/interface.mjs +52 -0
  154. package/hub/workers/worker-utils.mjs +104 -0
  155. package/hud/colors.mjs +88 -0
  156. package/hud/constants.mjs +88 -0
  157. package/hud/context-monitor.mjs +403 -0
  158. package/hud/hud-qos-status.mjs +210 -0
  159. package/hud/providers/claude.mjs +314 -0
  160. package/hud/providers/codex.mjs +151 -0
  161. package/hud/providers/gemini.mjs +320 -0
  162. package/hud/renderers.mjs +442 -0
  163. package/hud/terminal.mjs +140 -0
  164. package/hud/utils.mjs +313 -0
  165. package/mesh/index.mjs +63 -0
  166. package/mesh/mesh-budget.mjs +128 -0
  167. package/mesh/mesh-heartbeat.mjs +100 -0
  168. package/mesh/mesh-protocol.mjs +96 -0
  169. package/mesh/mesh-queue.mjs +165 -0
  170. package/mesh/mesh-registry.mjs +78 -0
  171. package/mesh/mesh-router.mjs +76 -0
  172. package/package.json +8 -1
  173. package/references/hosts.json +33 -0
  174. package/scripts/__tests__/gen-skill-docs.test.mjs +87 -0
  175. package/scripts/__tests__/keyword-detector.test.mjs +234 -0
  176. package/scripts/__tests__/mcp-guard-engine.test.mjs +118 -0
  177. package/scripts/__tests__/remote-spawn-transfer.test.mjs +117 -0
  178. package/scripts/__tests__/remote-spawn.test.mjs +92 -0
  179. package/scripts/__tests__/skill-template.test.mjs +193 -0
  180. package/scripts/__tests__/smoke.test.mjs +34 -0
  181. package/scripts/cache-buildup.mjs +30 -0
  182. package/scripts/cache-doctor.mjs +149 -0
  183. package/scripts/cache-warmup.mjs +557 -0
  184. package/scripts/claudemd-sync.mjs +148 -0
  185. package/scripts/cli-route.sh +3 -0
  186. package/scripts/completions/tfx.bash +47 -0
  187. package/scripts/completions/tfx.fish +44 -0
  188. package/scripts/completions/tfx.zsh +83 -0
  189. package/scripts/cross-review-gate.mjs +126 -0
  190. package/scripts/cross-review-tracker.mjs +238 -0
  191. package/scripts/gen-skill-docs.mjs +111 -0
  192. package/scripts/headless-guard-fast.sh +21 -0
  193. package/scripts/headless-guard.mjs +360 -0
  194. package/scripts/hub-ensure.mjs +120 -0
  195. package/scripts/keyword-detector.mjs +272 -0
  196. package/scripts/keyword-rules-expander.mjs +521 -0
  197. package/scripts/lib/claudemd-scanner.mjs +218 -0
  198. package/scripts/lib/context.mjs +67 -0
  199. package/scripts/lib/cross-review-utils.mjs +51 -0
  200. package/scripts/lib/env-probe.mjs +241 -0
  201. package/scripts/lib/gemini-profiles.mjs +85 -0
  202. package/scripts/lib/handoff.mjs +171 -0
  203. package/scripts/lib/hook-utils.mjs +14 -0
  204. package/scripts/lib/keyword-rules.mjs +166 -0
  205. package/scripts/lib/logger.mjs +105 -0
  206. package/scripts/lib/mcp-filter.mjs +739 -0
  207. package/scripts/lib/mcp-guard-engine.mjs +954 -0
  208. package/scripts/lib/mcp-manifest.mjs +79 -0
  209. package/scripts/lib/mcp-server-catalog.mjs +118 -0
  210. package/scripts/lib/psmux-info.mjs +119 -0
  211. package/scripts/lib/remote-spawn-transfer.mjs +196 -0
  212. package/scripts/lib/skill-template.mjs +326 -0
  213. package/scripts/mcp-check.mjs +237 -0
  214. package/scripts/mcp-cleanup.ps1 +17 -0
  215. package/scripts/mcp-gateway-config.mjs +207 -0
  216. package/scripts/mcp-gateway-ensure.mjs +85 -0
  217. package/scripts/mcp-gateway-integration-test.mjs +228 -0
  218. package/scripts/mcp-gateway-start.mjs +226 -0
  219. package/scripts/mcp-gateway-start.ps1 +141 -0
  220. package/scripts/mcp-gateway-verify.mjs +77 -0
  221. package/scripts/mcp-safety-guard.mjs +44 -0
  222. package/scripts/notion-read.mjs +556 -0
  223. package/scripts/pack.mjs +295 -0
  224. package/scripts/preflight-cache.mjs +69 -0
  225. package/scripts/preinstall.mjs +96 -0
  226. package/scripts/remote-spawn.mjs +1376 -0
  227. package/scripts/run.cjs +79 -0
  228. package/scripts/session-spawn-helper.mjs +185 -0
  229. package/scripts/setup.mjs +1178 -0
  230. package/scripts/test-lock.mjs +71 -0
  231. package/scripts/test-tfx-route-no-claude-native.mjs +57 -0
  232. package/scripts/tfx-batch-stats.mjs +96 -0
  233. package/scripts/tfx-gate-activate.mjs +89 -0
  234. package/scripts/tfx-route-post.mjs +505 -0
  235. package/scripts/tfx-route-worker.mjs +223 -0
  236. package/scripts/tfx-route.sh +2014 -0
  237. package/scripts/tmp-cleanup.mjs +103 -0
  238. package/scripts/token-snapshot.mjs +575 -0
  239. package/skills/tfx-auto/SKILL.md.tmpl +2 -3
  240. package/skills/tfx-autoresearch/SKILL.md +6 -5
  241. package/skills/tfx-codex/SKILL.md.tmpl +2 -3
  242. package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +33 -0
  243. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/eval_metadata.json +42 -0
  244. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +11 -0
  245. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/analysis.md +87 -0
  246. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/classification.md +35 -0
  247. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/commands.sh +275 -0
  248. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/routing.md +56 -0
  249. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/timing.json +5 -0
  250. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +11 -0
  251. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/analysis.md +92 -0
  252. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/classification.md +71 -0
  253. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/commands.sh +264 -0
  254. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/routing.md +113 -0
  255. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/timing.json +5 -0
  256. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/eval_metadata.json +32 -0
  257. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +9 -0
  258. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/analysis.md +96 -0
  259. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/classification.md +38 -0
  260. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/commands.sh +151 -0
  261. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/routing.md +51 -0
  262. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/timing.json +5 -0
  263. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +9 -0
  264. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/analysis.md +127 -0
  265. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/classification.md +57 -0
  266. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/commands.sh +129 -0
  267. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/routing.md +84 -0
  268. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/timing.json +5 -0
  269. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/eval_metadata.json +27 -0
  270. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +8 -0
  271. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/analysis.md +98 -0
  272. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/classification.md +65 -0
  273. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/commands.sh +123 -0
  274. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/routing.md +66 -0
  275. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/timing.json +5 -0
  276. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +8 -0
  277. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/analysis.md +88 -0
  278. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/classification.md +40 -0
  279. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/commands.sh +130 -0
  280. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/routing.md +61 -0
  281. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/timing.json +5 -0
  282. package/skills/tfx-deep-interview/SKILL.md +1 -2
  283. package/skills/tfx-plan/SKILL.md.tmpl +2 -3
  284. package/skills/tfx-psmux-rules/SKILL.md +11 -2
  285. package/skills/tfx-qa/SKILL.md.tmpl +2 -3
  286. package/skills/tfx-remote-spawn/SKILL.md +8 -11
  287. package/skills/tfx-research/SKILL.md.tmpl +2 -3
  288. package/skills/tfx-review/SKILL.md.tmpl +2 -3
  289. package/skills/tfx-workspace/async-tests/run-tests.sh +203 -0
  290. package/skills/tfx-workspace/evals/evals.json +79 -0
  291. package/skills/tfx-workspace/iteration-1/benchmark.json +162 -0
  292. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/eval_metadata.json +11 -0
  293. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/grading.json +9 -0
  294. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/outputs/analysis.md +154 -0
  295. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/timing.json +5 -0
  296. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/grading.json +9 -0
  297. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/outputs/analysis.md +126 -0
  298. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/timing.json +5 -0
  299. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/eval_metadata.json +11 -0
  300. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/grading.json +9 -0
  301. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/outputs/analysis.md +119 -0
  302. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/timing.json +5 -0
  303. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/grading.json +9 -0
  304. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/outputs/analysis.md +115 -0
  305. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/timing.json +5 -0
  306. package/skills/tfx-workspace/iteration-1/hub-start-sequence/eval_metadata.json +10 -0
  307. package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/grading.json +8 -0
  308. package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/outputs/analysis.md +86 -0
  309. package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/timing.json +5 -0
  310. package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/grading.json +8 -0
  311. package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/outputs/analysis.md +81 -0
  312. package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/timing.json +5 -0
  313. package/skills/tfx-workspace/iteration-1/multi-team-creation/eval_metadata.json +12 -0
  314. package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/grading.json +10 -0
  315. package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/outputs/analysis.md +316 -0
  316. package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/timing.json +5 -0
  317. package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/grading.json +10 -0
  318. package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/outputs/analysis.md +352 -0
  319. package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/timing.json +5 -0
  320. package/skills/tfx-workspace/iteration-1/review.html +1325 -0
  321. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/eval_metadata.json +12 -0
  322. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/grading.json +10 -0
  323. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/outputs/analysis.md +97 -0
  324. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/timing.json +5 -0
  325. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/grading.json +10 -0
  326. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/outputs/analysis.md +94 -0
  327. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/timing.json +5 -0
  328. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/eval_metadata.json +12 -0
  329. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/grading.json +10 -0
  330. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/outputs/analysis.md +209 -0
  331. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/timing.json +5 -0
  332. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/grading.json +10 -0
  333. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/outputs/analysis.md +193 -0
  334. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/timing.json +5 -0
  335. package/skills/tfx-workspace/iteration-2/benchmark.json +62 -0
  336. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/eval_metadata.json +13 -0
  337. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/grading.json +11 -0
  338. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/outputs/analysis.md +382 -0
  339. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/timing.json +5 -0
  340. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/grading.json +11 -0
  341. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/outputs/analysis.md +333 -0
  342. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/timing.json +5 -0
  343. package/skills/tfx-workspace/iteration-2/review.html +1325 -0
  344. package/skills/tfx-workspace/skill-snapshot/tfx-auto/SKILL.md +217 -0
  345. package/skills/{tfx-auto-codex/SKILL.md.tmpl → tfx-workspace/skill-snapshot/tfx-auto-codex/SKILL.md} +3 -31
  346. package/skills/tfx-workspace/skill-snapshot/tfx-codex/SKILL.md +65 -0
  347. package/skills/tfx-workspace/skill-snapshot/tfx-doctor/SKILL.md +94 -0
  348. package/skills/{tfx-gemini/SKILL.md.tmpl → tfx-workspace/skill-snapshot/tfx-gemini/SKILL.md} +6 -14
  349. package/skills/tfx-workspace/skill-snapshot/tfx-hub/SKILL.md +133 -0
  350. package/skills/tfx-workspace/skill-snapshot/tfx-multi/SKILL.md +426 -0
  351. package/skills/tfx-workspace/skill-snapshot/tfx-setup/SKILL.md +101 -0
  352. package/skills/merge-worktree/SKILL.md.tmpl +0 -144
  353. package/skills/shared/arguments-processing.md +0 -2
  354. package/skills/shared/mandatory-rules.md +0 -6
  355. package/skills/shared/telemetry-segment.md +0 -6
  356. package/skills/star-prompt/SKILL.md.tmpl +0 -122
  357. package/skills/tfx-analysis/SKILL.md.tmpl +0 -106
  358. package/skills/tfx-analysis/skill.json +0 -11
  359. package/skills/tfx-auto/skill.json +0 -26
  360. package/skills/tfx-auto-codex/skill.json +0 -8
  361. package/skills/tfx-autopilot/SKILL.md.tmpl +0 -115
  362. package/skills/tfx-autopilot/skill.json +0 -10
  363. package/skills/tfx-autoresearch/SKILL.md.tmpl +0 -135
  364. package/skills/tfx-autoresearch/skill.json +0 -14
  365. package/skills/tfx-autoroute/SKILL.md.tmpl +0 -188
  366. package/skills/tfx-autoroute/skill.json +0 -12
  367. package/skills/tfx-codex/skill.json +0 -8
  368. package/skills/tfx-codex-swarm/SKILL.md.tmpl +0 -16
  369. package/skills/tfx-codex-swarm/skill.json +0 -5
  370. package/skills/tfx-consensus/SKILL.md.tmpl +0 -145
  371. package/skills/tfx-consensus/skill.json +0 -8
  372. package/skills/tfx-debate/SKILL.md.tmpl +0 -191
  373. package/skills/tfx-debate/skill.json +0 -12
  374. package/skills/tfx-deep-analysis/SKILL.md.tmpl +0 -227
  375. package/skills/tfx-deep-analysis/skill.json +0 -10
  376. package/skills/tfx-deep-interview/SKILL.md.tmpl +0 -203
  377. package/skills/tfx-deep-interview/skill.json +0 -12
  378. package/skills/tfx-deep-plan/SKILL.md.tmpl +0 -281
  379. package/skills/tfx-deep-plan/skill.json +0 -13
  380. package/skills/tfx-deep-qa/SKILL.md.tmpl +0 -164
  381. package/skills/tfx-deep-qa/skill.json +0 -11
  382. package/skills/tfx-deep-research/SKILL.md.tmpl +0 -216
  383. package/skills/tfx-deep-research/skill.json +0 -14
  384. package/skills/tfx-deep-review/SKILL.md.tmpl +0 -178
  385. package/skills/tfx-deep-review/skill.json +0 -12
  386. package/skills/tfx-doctor/SKILL.md.tmpl +0 -172
  387. package/skills/tfx-doctor/skill.json +0 -8
  388. package/skills/tfx-find/skill.json +0 -12
  389. package/skills/tfx-forge/SKILL.md.tmpl +0 -187
  390. package/skills/tfx-forge/skill.json +0 -12
  391. package/skills/tfx-fullcycle/SKILL.md.tmpl +0 -285
  392. package/skills/tfx-fullcycle/skill.json +0 -11
  393. package/skills/tfx-gemini/skill.json +0 -8
  394. package/skills/tfx-hooks/SKILL.md.tmpl +0 -216
  395. package/skills/tfx-hooks/skill.json +0 -8
  396. package/skills/tfx-hub/SKILL.md.tmpl +0 -212
  397. package/skills/tfx-hub/skill.json +0 -8
  398. package/skills/tfx-index/skill.json +0 -11
  399. package/skills/tfx-interview/SKILL.md.tmpl +0 -284
  400. package/skills/tfx-interview/skill.json +0 -12
  401. package/skills/tfx-multi/SKILL.md.tmpl +0 -183
  402. package/skills/tfx-multi/skill.json +0 -8
  403. package/skills/tfx-panel/SKILL.md.tmpl +0 -188
  404. package/skills/tfx-panel/skill.json +0 -12
  405. package/skills/tfx-persist/SKILL.md.tmpl +0 -269
  406. package/skills/tfx-persist/skill.json +0 -12
  407. package/skills/tfx-plan/skill.json +0 -11
  408. package/skills/tfx-profile/SKILL.md.tmpl +0 -239
  409. package/skills/tfx-profile/skill.json +0 -8
  410. package/skills/tfx-prune/SKILL.md.tmpl +0 -199
  411. package/skills/tfx-prune/skill.json +0 -12
  412. package/skills/tfx-psmux-rules/SKILL.md.tmpl +0 -317
  413. package/skills/tfx-psmux-rules/skill.json +0 -8
  414. package/skills/tfx-qa/skill.json +0 -11
  415. package/skills/tfx-ralph/SKILL.md.tmpl +0 -27
  416. package/skills/tfx-ralph/skill.json +0 -8
  417. package/skills/tfx-remote-setup/SKILL.md.tmpl +0 -576
  418. package/skills/tfx-remote-setup/skill.json +0 -8
  419. package/skills/tfx-remote-spawn/SKILL.md.tmpl +0 -263
  420. package/skills/tfx-remote-spawn/skill.json +0 -9
  421. package/skills/tfx-research/skill.json +0 -13
  422. package/skills/tfx-review/skill.json +0 -11
  423. package/skills/tfx-setup/SKILL.md.tmpl +0 -380
  424. package/skills/tfx-setup/skill.json +0 -8
  425. package/skills/tfx-swarm/SKILL.md.tmpl +0 -154
  426. package/skills/tfx-swarm/skill.json +0 -5
@@ -0,0 +1,148 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { TFX_START, TFX_END, OMC_END, writeSection } from "./lib/claudemd-scanner.mjs";
5
+
6
+ const PROJECT_ROOT = fileURLToPath(new URL("..", import.meta.url));
7
+ const PROJECT_CLAUDE_MD_PATH = join(PROJECT_ROOT, "CLAUDE.md");
8
+ const ROUTING_TAG_OPEN = "<routing>";
9
+ const ROUTING_TAG_CLOSE = "</routing>";
10
+ // Legacy heading fallback
11
+ const ROUTING_SECTION_HEADING = "## triflux CLI 라우팅";
12
+
13
+ function findRoutingSection(markdown) {
14
+ const content = String(markdown || "");
15
+
16
+ // XML 태그 기반 (우선)
17
+ const openIdx = content.indexOf(ROUTING_TAG_OPEN);
18
+ const closeIdx = content.indexOf(ROUTING_TAG_CLOSE);
19
+ if (openIdx !== -1 && closeIdx !== -1 && closeIdx > openIdx) {
20
+ const endIndex = closeIdx + ROUTING_TAG_CLOSE.length;
21
+ return {
22
+ found: true,
23
+ startIndex: openIdx,
24
+ endIndex: content[endIndex] === "\n" ? endIndex + 1 : endIndex,
25
+ section: content.slice(openIdx, endIndex),
26
+ };
27
+ }
28
+
29
+ // Legacy heading fallback
30
+ const headingPattern = new RegExp(`(^|\\n)${ROUTING_SECTION_HEADING.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}(?=\\n|$)`, "u");
31
+ const match = headingPattern.exec(content);
32
+
33
+ if (!match) {
34
+ return { found: false, startIndex: -1, endIndex: -1, section: "" };
35
+ }
36
+
37
+ const startIndex = match.index + match[1].length;
38
+ const nextHeadingIndex = content.indexOf("\n## ", startIndex + ROUTING_SECTION_HEADING.length);
39
+ const endIndex = nextHeadingIndex === -1 ? content.length : nextHeadingIndex + 1;
40
+
41
+ return {
42
+ found: true,
43
+ startIndex,
44
+ endIndex,
45
+ section: content.slice(startIndex, endIndex),
46
+ };
47
+ }
48
+
49
+ function normalizeRoutingSection(routingTable) {
50
+ const section = String(routingTable || "").trim();
51
+ return section ? `${section}\n` : "";
52
+ }
53
+
54
+ function buildNextMarkdown(currentMarkdown, routingSection) {
55
+ const current = String(currentMarkdown || "");
56
+ const nextSection = normalizeRoutingSection(routingSection);
57
+ const existing = findRoutingSection(current);
58
+
59
+ if (existing.found) {
60
+ return `${current.slice(0, existing.startIndex)}${nextSection}${current.slice(existing.endIndex)}`;
61
+ }
62
+
63
+ if (!current) {
64
+ return nextSection;
65
+ }
66
+
67
+ const separator = current.endsWith("\n\n") ? "" : current.endsWith("\n") ? "\n" : "\n\n";
68
+ return `${current}${separator}${nextSection}`;
69
+ }
70
+
71
+ function toSkippedResult(path, reason) {
72
+ return { action: "unchanged", path, skipped: true, reason };
73
+ }
74
+
75
+ export function getLatestRoutingTable() {
76
+ if (!existsSync(PROJECT_CLAUDE_MD_PATH)) {
77
+ throw new Error(`project CLAUDE.md not found: ${PROJECT_CLAUDE_MD_PATH}`);
78
+ }
79
+
80
+ const projectMarkdown = readFileSync(PROJECT_CLAUDE_MD_PATH, "utf8");
81
+ const section = findRoutingSection(projectMarkdown);
82
+
83
+ if (!section.found) {
84
+ throw new Error(`routing section not found in: ${PROJECT_CLAUDE_MD_PATH}`);
85
+ }
86
+
87
+ return section.section.trim();
88
+ }
89
+
90
+ export function ensureTfxSection(claudeMdPath, routingTable) {
91
+ if (!existsSync(claudeMdPath)) {
92
+ return toSkippedResult(claudeMdPath, "missing_file");
93
+ }
94
+
95
+ const currentMarkdown = readFileSync(claudeMdPath, "utf8");
96
+ const nextMarkdown = buildNextMarkdown(currentMarkdown, routingTable);
97
+
98
+ if (nextMarkdown === currentMarkdown) {
99
+ return { action: "unchanged", path: claudeMdPath };
100
+ }
101
+
102
+ writeFileSync(claudeMdPath, nextMarkdown, "utf8");
103
+
104
+ return {
105
+ action: findRoutingSection(currentMarkdown).found ? "updated" : "created",
106
+ path: claudeMdPath,
107
+ };
108
+ }
109
+
110
+ export function ensureTfxCrown(claudeMdPath, options = {}) {
111
+ const absolutePath = resolve(claudeMdPath);
112
+ if (!existsSync(absolutePath)) {
113
+ return toSkippedResult(absolutePath, "missing_file");
114
+ }
115
+
116
+ const content = readFileSync(absolutePath, "utf8");
117
+ const startIdx = content.indexOf(TFX_START);
118
+ const omcEndIdx = content.indexOf(OMC_END);
119
+
120
+ if (startIdx === -1) {
121
+ const result = writeSection(absolutePath, options);
122
+ return { action: result.action, path: absolutePath };
123
+ }
124
+
125
+ const expectedPos = omcEndIdx !== -1 ? omcEndIdx + OMC_END.length : 0;
126
+ const textBefore = content.slice(expectedPos, startIdx).trim();
127
+
128
+ if (textBefore.length === 0) {
129
+ return { action: "unchanged", path: absolutePath };
130
+ }
131
+
132
+ const result = writeSection(absolutePath, options);
133
+ return { action: "repositioned", path: absolutePath, detail: result.action };
134
+ }
135
+
136
+ export function ensureGlobalClaudeRoutingSection(claudeDir) {
137
+ const claudeMdPath = join(claudeDir, "CLAUDE.md");
138
+ if (!existsSync(claudeMdPath)) {
139
+ return toSkippedResult(claudeMdPath, "missing_file");
140
+ }
141
+
142
+ try {
143
+ return ensureTfxSection(claudeMdPath, getLatestRoutingTable());
144
+ } catch (error) {
145
+ const reason = error instanceof Error ? error.message : "routing_table_unavailable";
146
+ return toSkippedResult(claudeMdPath, reason);
147
+ }
148
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # cli-route.sh — backward-compat 래퍼
3
+ exec bash "$(dirname "$0")/tfx-route.sh" "$@"
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env bash
2
+ # Installation: source /path/to/tfx.bash 또는 ~/.bashrc에 추가
3
+
4
+ _tfx_completion() {
5
+ local cur prev words cword
6
+ COMPREPLY=()
7
+ cur="${COMP_WORDS[COMP_CWORD]}"
8
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
9
+ words=("${COMP_WORDS[@]}")
10
+ cword=$COMP_CWORD
11
+
12
+ local commands="setup doctor multi hub auto codex gemini"
13
+ local multi_cmds="status stop kill attach list"
14
+ local hub_cmds="start stop status restart"
15
+ local flags="--thorough --quick --tmux --psmux --agents --no-attach --timeout"
16
+
17
+ if [[ $cword -eq 1 ]]; then
18
+ COMPREPLY=( $(compgen -W "${commands}" -- "$cur") )
19
+ return 0
20
+ fi
21
+
22
+ local cmd="${words[1]}"
23
+ case "${cmd}" in
24
+ multi)
25
+ if [[ $cword -eq 2 && ! "$cur" == -* ]]; then
26
+ COMPREPLY=( $(compgen -W "${multi_cmds}" -- "$cur") )
27
+ else
28
+ COMPREPLY=( $(compgen -W "${flags}" -- "$cur") )
29
+ fi
30
+ ;;
31
+ hub)
32
+ if [[ $cword -eq 2 ]]; then
33
+ COMPREPLY=( $(compgen -W "${hub_cmds}" -- "$cur") )
34
+ fi
35
+ ;;
36
+ doctor)
37
+ COMPREPLY=( $(compgen -W "--fix --reset" -- "$cur") )
38
+ ;;
39
+ setup|auto|codex|gemini)
40
+ if [[ "$cur" == -* ]]; then
41
+ COMPREPLY=( $(compgen -W "${flags}" -- "$cur") )
42
+ fi
43
+ ;;
44
+ esac
45
+ }
46
+
47
+ complete -F _tfx_completion tfx
@@ -0,0 +1,44 @@
1
+ # Installation: ~/.config/fish/completions/에 복사
2
+ # e.g., cp /path/to/tfx.fish ~/.config/fish/completions/tfx.fish
3
+
4
+ set -l commands setup doctor multi hub auto codex gemini
5
+ set -l multi_cmds status stop kill attach list
6
+ set -l hub_cmds start stop status restart
7
+
8
+ complete -c tfx -f
9
+
10
+ # Subcommands
11
+ complete -c tfx -n "not __fish_seen_subcommand_from $commands" -a "setup" -d "Setup and sync files"
12
+ complete -c tfx -n "not __fish_seen_subcommand_from $commands" -a "doctor" -d "Diagnose CLI and issues"
13
+ complete -c tfx -n "not __fish_seen_subcommand_from $commands" -a "multi" -d "Multi-CLI team mode"
14
+ complete -c tfx -n "not __fish_seen_subcommand_from $commands" -a "hub" -d "MCP message bus management"
15
+ complete -c tfx -n "not __fish_seen_subcommand_from $commands" -a "auto" -d "Auto mode"
16
+ complete -c tfx -n "not __fish_seen_subcommand_from $commands" -a "codex" -d "Codex mode"
17
+ complete -c tfx -n "not __fish_seen_subcommand_from $commands" -a "gemini" -d "Gemini mode"
18
+
19
+ # Doctor flags
20
+ complete -c tfx -n "__fish_seen_subcommand_from doctor" -l fix -d "Auto fix issues"
21
+ complete -c tfx -n "__fish_seen_subcommand_from doctor" -l reset -d "Reset all caches"
22
+
23
+ # Multi subcommands
24
+ complete -c tfx -n "__fish_seen_subcommand_from multi; and not __fish_seen_subcommand_from $multi_cmds" -a "status"
25
+ complete -c tfx -n "__fish_seen_subcommand_from multi; and not __fish_seen_subcommand_from $multi_cmds" -a "stop"
26
+ complete -c tfx -n "__fish_seen_subcommand_from multi; and not __fish_seen_subcommand_from $multi_cmds" -a "kill"
27
+ complete -c tfx -n "__fish_seen_subcommand_from multi; and not __fish_seen_subcommand_from $multi_cmds" -a "attach"
28
+ complete -c tfx -n "__fish_seen_subcommand_from multi; and not __fish_seen_subcommand_from $multi_cmds" -a "list"
29
+
30
+ # Hub subcommands
31
+ complete -c tfx -n "__fish_seen_subcommand_from hub; and not __fish_seen_subcommand_from $hub_cmds" -a "start"
32
+ complete -c tfx -n "__fish_seen_subcommand_from hub; and not __fish_seen_subcommand_from $hub_cmds" -a "stop"
33
+ complete -c tfx -n "__fish_seen_subcommand_from hub; and not __fish_seen_subcommand_from $hub_cmds" -a "status"
34
+ complete -c tfx -n "__fish_seen_subcommand_from hub; and not __fish_seen_subcommand_from $hub_cmds" -a "restart"
35
+
36
+ # Global or multi flags
37
+ set -l flags_cond "__fish_seen_subcommand_from setup multi auto codex gemini"
38
+ complete -c tfx -n "$flags_cond" -l thorough -d "Thorough execution"
39
+ complete -c tfx -n "$flags_cond" -l quick -d "Quick execution"
40
+ complete -c tfx -n "$flags_cond" -l tmux -d "Use tmux"
41
+ complete -c tfx -n "$flags_cond" -l psmux -d "Use psmux"
42
+ complete -c tfx -n "$flags_cond" -l agents -d "Specify agents"
43
+ complete -c tfx -n "$flags_cond" -l no-attach -d "Do not attach"
44
+ complete -c tfx -n "$flags_cond" -l timeout -d "Set timeout"
@@ -0,0 +1,83 @@
1
+ #compdef tfx
2
+ # Installation: fpath에 추가 후 compinit
3
+ # e.g., fpath=(/path/to/dir $fpath) && compinit
4
+
5
+ _tfx() {
6
+ local line state
7
+ local -a commands multi_cmds hub_cmds flags
8
+
9
+ commands=(
10
+ 'setup:Setup and sync files'
11
+ 'doctor:Diagnose CLI and issues'
12
+ 'multi:Multi-CLI team mode'
13
+ 'hub:MCP message bus management'
14
+ 'auto:Auto mode'
15
+ 'codex:Codex mode'
16
+ 'gemini:Gemini mode'
17
+ )
18
+
19
+ multi_cmds=(
20
+ 'status:Show status'
21
+ 'stop:Stop multi'
22
+ 'kill:Kill multi'
23
+ 'attach:Attach to multi'
24
+ 'list:List multi sessions'
25
+ )
26
+
27
+ hub_cmds=(
28
+ 'start:Start hub'
29
+ 'stop:Stop hub'
30
+ 'status:Show hub status'
31
+ 'restart:Restart hub'
32
+ )
33
+
34
+ _arguments -C \
35
+ '1: :->cmds' \
36
+ '*: :->args'
37
+
38
+ case $state in
39
+ cmds)
40
+ _describe -t commands 'tfx commands' commands
41
+ ;;
42
+ args)
43
+ case $words[2] in
44
+ multi)
45
+ if (( CURRENT == 3 )) && [[ $words[CURRENT] != -* ]]; then
46
+ _describe -t multi_cmds 'multi commands' multi_cmds
47
+ else
48
+ _arguments \
49
+ '--thorough[Thorough execution]' \
50
+ '--quick[Quick execution]' \
51
+ '--tmux[Use tmux]' \
52
+ '--psmux[Use psmux]' \
53
+ '--agents[Specify agents]' \
54
+ '--no-attach[Do not attach]' \
55
+ '--timeout[Set timeout]'
56
+ fi
57
+ ;;
58
+ hub)
59
+ if (( CURRENT == 3 )); then
60
+ _describe -t hub_cmds 'hub commands' hub_cmds
61
+ fi
62
+ ;;
63
+ doctor)
64
+ _arguments \
65
+ '--fix[Auto fix issues]' \
66
+ '--reset[Reset all caches]'
67
+ ;;
68
+ *)
69
+ _arguments \
70
+ '--thorough[Thorough execution]' \
71
+ '--quick[Quick execution]' \
72
+ '--tmux[Use tmux]' \
73
+ '--psmux[Use psmux]' \
74
+ '--agents[Specify agents]' \
75
+ '--no-attach[Do not attach]' \
76
+ '--timeout[Set timeout]'
77
+ ;;
78
+ esac
79
+ ;;
80
+ esac
81
+ }
82
+
83
+ _tfx "$@"
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, readFileSync, unlinkSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { nudge, deny } from "./lib/hook-utils.mjs";
6
+ import {
7
+ readStdin,
8
+ parseJson,
9
+ nowSec,
10
+ resolveBaseDir,
11
+ shouldTrackPath,
12
+ expectedReviewer,
13
+ SESSION_TTL_SEC,
14
+ STATE_REL_PATH,
15
+ } from "./lib/cross-review-utils.mjs";
16
+
17
+ function loadState(statePath) {
18
+ if (!existsSync(statePath)) return null;
19
+
20
+ try {
21
+ const state = JSON.parse(readFileSync(statePath, "utf8"));
22
+ const startedAt = Number(state?.session_start || 0);
23
+ const expired = !startedAt || nowSec() - startedAt > SESSION_TTL_SEC;
24
+ if (expired) {
25
+ try {
26
+ unlinkSync(statePath);
27
+ } catch {}
28
+ return null;
29
+ }
30
+ return state;
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ function isGitCommitCommand(command) {
37
+ if (typeof command !== "string") return false;
38
+ return /\bgit\s+commit\b/i.test(command);
39
+ }
40
+
41
+ function summarizePending(entries) {
42
+ return entries
43
+ .map((item) => {
44
+ const reviewer = item.expectedReviewer || "cross-reviewer";
45
+ return ` * ${item.path} (author=${item.author}, reviewer=${reviewer})`;
46
+ })
47
+ .join("\n");
48
+ }
49
+
50
+ async function main() {
51
+ if (process.env.TFX_SKIP_CROSS_REVIEW === "1") {
52
+ process.exit(0);
53
+ }
54
+
55
+ const raw = await readStdin();
56
+ if (!raw.trim()) process.exit(0);
57
+
58
+ const payload = parseJson(raw);
59
+ if (!payload) process.exit(0);
60
+
61
+ const toolName = payload.tool_name || "";
62
+ const toolInput = payload.tool_input || {};
63
+
64
+ if (toolName !== "Bash") process.exit(0);
65
+ if (!isGitCommitCommand(toolInput.command || "")) process.exit(0);
66
+
67
+ // cwd 전파: tracker와 동일한 resolveBaseDir 사용
68
+ const baseDir = resolveBaseDir(payload);
69
+ const statePath = join(baseDir, STATE_REL_PATH);
70
+
71
+ const state = loadState(statePath);
72
+ if (!state || !state.files || typeof state.files !== "object") process.exit(0);
73
+
74
+ const pending = [];
75
+ const selfApproved = [];
76
+
77
+ for (const [path, info] of Object.entries(state.files)) {
78
+ if (!shouldTrackPath(path)) continue;
79
+ const meta = info && typeof info === "object" ? info : {};
80
+ const author = String(meta.author || "").toLowerCase();
81
+ const reviewer = String(meta.reviewer || "").toLowerCase();
82
+ const reviewed = meta.reviewed === true;
83
+ const requiredReviewer = expectedReviewer(author);
84
+
85
+ // tracker가 설정한 self_approved 플래그 명시적 체크
86
+ if (meta.self_approved === true) {
87
+ selfApproved.push({ path, author, reviewer: meta.reviewer || author, expectedReviewer: requiredReviewer });
88
+ continue;
89
+ }
90
+
91
+ if (reviewed && reviewer && reviewer === author) {
92
+ selfApproved.push({ path, author, reviewer, expectedReviewer: requiredReviewer });
93
+ continue;
94
+ }
95
+
96
+ if (reviewed && requiredReviewer && reviewer && reviewer !== requiredReviewer) {
97
+ selfApproved.push({ path, author, reviewer, expectedReviewer: requiredReviewer });
98
+ continue;
99
+ }
100
+
101
+ if (!reviewed) {
102
+ pending.push({ path, author, expectedReviewer: requiredReviewer });
103
+ }
104
+ }
105
+
106
+ if (selfApproved.length > 0) {
107
+ const lines = selfApproved
108
+ .map((item) => ` * ${item.path} (author=${item.author}, reviewer=${item.reviewer}, required=${item.expectedReviewer || "n/a"})`)
109
+ .join("\n");
110
+ deny(
111
+ `[cross-review] self-approve 차단: 동일/비허용 reviewer가 감지되었습니다.\n${lines}\n` +
112
+ "규칙: author=claude -> reviewer=codex, author=codex -> reviewer=claude",
113
+ );
114
+ }
115
+
116
+ if (pending.length > 0) {
117
+ nudge(
118
+ `[cross-review] git commit 전에 교차 검증이 필요합니다.\n${summarizePending(pending)}\n` +
119
+ "규칙: author=claude -> reviewer=codex, author=codex -> reviewer=claude",
120
+ );
121
+ }
122
+
123
+ process.exit(0);
124
+ }
125
+
126
+ main().catch(() => process.exit(0));
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
4
+ import { dirname, isAbsolute, join, relative } from "node:path";
5
+ import {
6
+ readStdin,
7
+ parseJson,
8
+ nowSec,
9
+ resolveBaseDir,
10
+ shouldTrackPath,
11
+ expectedReviewer,
12
+ SESSION_TTL_SEC,
13
+ STATE_REL_PATH,
14
+ } from "./lib/cross-review-utils.mjs";
15
+
16
+ function resolveStatePath(baseDir) {
17
+ return join(baseDir, STATE_REL_PATH);
18
+ }
19
+
20
+ function createEmptyState() {
21
+ return {
22
+ files: {},
23
+ session_start: nowSec(),
24
+ };
25
+ }
26
+
27
+ function loadState(statePath) {
28
+ if (!existsSync(statePath)) return createEmptyState();
29
+
30
+ try {
31
+ const parsed = JSON.parse(readFileSync(statePath, "utf8"));
32
+ const sessionStart = Number(parsed?.session_start || 0);
33
+ const expired = !sessionStart || nowSec() - sessionStart > SESSION_TTL_SEC;
34
+ if (expired) {
35
+ try {
36
+ unlinkSync(statePath);
37
+ } catch {}
38
+ return createEmptyState();
39
+ }
40
+
41
+ return {
42
+ files: parsed?.files && typeof parsed.files === "object" ? parsed.files : {},
43
+ session_start: sessionStart,
44
+ };
45
+ } catch {
46
+ return createEmptyState();
47
+ }
48
+ }
49
+
50
+ function saveState(statePath, state) {
51
+ mkdirSync(dirname(statePath), { recursive: true });
52
+ writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
53
+ }
54
+
55
+ function normalizePath(filePath, baseDir) {
56
+ if (typeof filePath !== "string" || !filePath.trim()) return "";
57
+
58
+ const raw = filePath.trim();
59
+ let normalized = raw;
60
+
61
+ if (isAbsolute(raw)) {
62
+ const relPath = relative(baseDir, raw);
63
+ if (relPath.startsWith("..")) return "";
64
+ normalized = relPath;
65
+ }
66
+
67
+ return normalized.replace(/\\/g, "/").replace(/^\.\//, "");
68
+ }
69
+
70
+ function extractFilePath(toolInput) {
71
+ if (!toolInput || typeof toolInput !== "object") return "";
72
+ const candidate = toolInput.file_path ?? toolInput.path ?? toolInput.filePath ?? "";
73
+ return typeof candidate === "string" ? candidate : "";
74
+ }
75
+
76
+ function extractCandidatePaths(payload, baseDir) {
77
+ const candidates = new Set();
78
+
79
+ const looksLikePath = (value) => {
80
+ if (typeof value !== "string") return false;
81
+ const trimmed = value.trim();
82
+ if (!trimmed || /\s/.test(trimmed)) return false;
83
+ if (trimmed.length > 260) return false;
84
+ if (!trimmed.includes(".") && !trimmed.includes("/") && !trimmed.includes("\\")) return false;
85
+ return /^[./\\A-Za-z0-9_-]/.test(trimmed);
86
+ };
87
+
88
+ const addPath = (value) => {
89
+ if (!looksLikePath(value)) return;
90
+ const normalized = normalizePath(value, baseDir);
91
+ if (shouldTrackPath(normalized)) candidates.add(normalized);
92
+ };
93
+
94
+ const scanValue = (value, depth = 0) => {
95
+ if (depth > 3 || value == null) return;
96
+ if (typeof value === "string") {
97
+ addPath(value);
98
+ return;
99
+ }
100
+ if (Array.isArray(value)) {
101
+ for (const item of value) scanValue(item, depth + 1);
102
+ return;
103
+ }
104
+ if (typeof value !== "object") return;
105
+
106
+ for (const [key, child] of Object.entries(value)) {
107
+ const keyLower = key.toLowerCase();
108
+ if (keyLower.includes("file") || keyLower.includes("path")) {
109
+ scanValue(child, depth + 1);
110
+ }
111
+ }
112
+ };
113
+
114
+ addPath(extractFilePath(payload?.tool_input));
115
+
116
+ scanValue(payload?.tool_response);
117
+ scanValue(payload?.tool_output);
118
+ scanValue(payload?.result);
119
+ scanValue(payload?.output);
120
+
121
+ return [...candidates];
122
+ }
123
+
124
+ function collectStrings(value, out = [], depth = 0) {
125
+ if (depth > 4) return out;
126
+ if (typeof value === "string") {
127
+ out.push(value);
128
+ return out;
129
+ }
130
+ if (!value || typeof value !== "object") return out;
131
+ if (Array.isArray(value)) {
132
+ for (const item of value) collectStrings(item, out, depth + 1);
133
+ return out;
134
+ }
135
+
136
+ for (const key of Object.keys(value)) {
137
+ collectStrings(value[key], out, depth + 1);
138
+ }
139
+ return out;
140
+ }
141
+
142
+ function detectCliActor(payload) {
143
+ const lines = collectStrings(payload).join("\n");
144
+ const match = lines.match(/\bcli\s*[:=]\s*(claude|codex|gemini)\b/i);
145
+ return match ? match[1].toLowerCase() : "";
146
+ }
147
+
148
+ function detectAuthor(payload) {
149
+ const actor = detectCliActor(payload);
150
+ if (actor) return actor;
151
+ return "claude";
152
+ }
153
+
154
+ function applyReviewer(state, reviewer, ts) {
155
+ for (const [filePath, meta] of Object.entries(state.files)) {
156
+ if (!meta || typeof meta !== "object") continue;
157
+ if (!shouldTrackPath(filePath)) continue;
158
+
159
+ const author = String(meta.author || "").toLowerCase();
160
+ const expected = expectedReviewer(author);
161
+
162
+ if (expected && reviewer === expected) {
163
+ meta.reviewed = true;
164
+ meta.reviewer = reviewer;
165
+ meta.reviewed_ts = ts;
166
+ delete meta.self_approved;
167
+ continue;
168
+ }
169
+
170
+ if (reviewer === author) {
171
+ meta.reviewed = false;
172
+ meta.reviewer = reviewer;
173
+ meta.reviewed_ts = ts;
174
+ meta.self_approved = true;
175
+ }
176
+ }
177
+ }
178
+
179
+ async function main() {
180
+ if (process.env.TFX_SKIP_CROSS_REVIEW === "1") {
181
+ process.exit(0);
182
+ }
183
+
184
+ const raw = await readStdin();
185
+ if (!raw.trim()) {
186
+ process.exit(0);
187
+ }
188
+
189
+ const payload = parseJson(raw);
190
+ if (!payload) {
191
+ process.exit(0);
192
+ }
193
+
194
+ const baseDir = resolveBaseDir(payload);
195
+ const statePath = resolveStatePath(baseDir);
196
+ const state = loadState(statePath);
197
+ const toolName = payload.tool_name || "";
198
+ const ts = nowSec();
199
+ let changed = false;
200
+
201
+ if (toolName === "Edit" || toolName === "Write") {
202
+ const toolInput = payload.tool_input || {};
203
+ const normalizedPath = normalizePath(extractFilePath(toolInput), baseDir);
204
+ if (shouldTrackPath(normalizedPath)) {
205
+ state.files[normalizedPath] = {
206
+ author: detectAuthor(payload),
207
+ ts,
208
+ reviewed: false,
209
+ };
210
+ changed = true;
211
+ }
212
+ } else if (toolName === "Bash") {
213
+ const actor = detectCliActor(payload);
214
+ if (actor) {
215
+ const paths = extractCandidatePaths(payload, baseDir);
216
+ if (paths.length > 0) {
217
+ for (const path of paths) {
218
+ state.files[path] = {
219
+ author: actor,
220
+ ts,
221
+ reviewed: false,
222
+ };
223
+ }
224
+ } else {
225
+ applyReviewer(state, actor, ts);
226
+ }
227
+ changed = true;
228
+ }
229
+ }
230
+
231
+ if (changed) {
232
+ saveState(statePath, state);
233
+ }
234
+
235
+ process.exit(0);
236
+ }
237
+
238
+ main().catch(() => process.exit(0));