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,589 @@
1
+ // hub/team/swarm-hypervisor.mjs — Multi-model swarm orchestration hypervisor
2
+ // Consumes a SwarmPlan (from swarm-planner.mjs) and orchestrates parallel
3
+ // conductor sessions with file-lease enforcement, result validation,
4
+ // and ordered integration.
5
+ //
6
+ // Failure modes handled:
7
+ // F1: Worker crash → conductor auto-restart (maxRestarts)
8
+ // F2: Rate limit → account-broker cooldown + fallback agent
9
+ // F3: Stall → health probe L1 detection + kill + restart
10
+ // F4: File lease violation → revert worker changes, flag shard as failed
11
+ // F5: Merge conflict → retry integration with conflict resolution
12
+
13
+ import { EventEmitter } from 'node:events';
14
+ import { join } from 'node:path';
15
+ import { mkdirSync, readFileSync, existsSync } from 'node:fs';
16
+ import { execSync } from 'node:child_process';
17
+
18
+ import { createConductor, STATES } from './conductor.mjs';
19
+ import { createSwarmLocks } from './swarm-locks.mjs';
20
+ import { createEventLog } from './event-log.mjs';
21
+ import { probeRemoteEnv, resolveRemoteDir } from './remote-session.mjs';
22
+
23
+ // ── Swarm states ──────────────────────────────────────────────
24
+
25
+ export const SWARM_STATES = Object.freeze({
26
+ PLANNING: 'planning',
27
+ LAUNCHING: 'launching',
28
+ RUNNING: 'running',
29
+ INTEGRATING: 'integrating',
30
+ VALIDATING: 'validating',
31
+ COMPLETED: 'completed',
32
+ FAILED: 'failed',
33
+ });
34
+
35
+ // ── Failure mode classification ───────────────────────────────
36
+
37
+ const FAILURE_MODES = Object.freeze({
38
+ F1_CRASH: 'F1_crash',
39
+ F2_RATE_LIMIT: 'F2_rate_limit',
40
+ F3_STALL: 'F3_stall',
41
+ F4_LEASE_VIOLATION: 'F4_lease_violation',
42
+ F5_MERGE_CONFLICT: 'F5_merge_conflict',
43
+ });
44
+
45
+ const FALLBACK_AGENTS = Object.freeze({
46
+ codex: 'gemini',
47
+ gemini: 'codex',
48
+ claude: 'codex',
49
+ });
50
+
51
+ /**
52
+ * Create a swarm hypervisor.
53
+ * @param {object} opts
54
+ * @param {string} opts.workdir — repository root / working directory
55
+ * @param {string} opts.logsDir — base directory for all logs
56
+ * @param {number} [opts.maxRestarts=2] — per-shard max restarts
57
+ * @param {number} [opts.graceMs=10000] — conductor shutdown grace period
58
+ * @param {number} [opts.integrationTimeoutMs=60000] — max time for integration phase
59
+ * @param {object} [opts.probeOpts] — health probe overrides
60
+ * @param {object} [opts.deps] — dependency injection for testing
61
+ * @returns {SwarmHypervisor}
62
+ */
63
+ export function createSwarmHypervisor(opts) {
64
+ const {
65
+ workdir,
66
+ logsDir,
67
+ maxRestarts = 2,
68
+ graceMs = 10_000,
69
+ integrationTimeoutMs = 60_000,
70
+ probeOpts = {},
71
+ deps = {},
72
+ } = opts;
73
+
74
+ if (!workdir) throw new Error('workdir is required');
75
+ if (!logsDir) throw new Error('logsDir is required');
76
+
77
+ mkdirSync(logsDir, { recursive: true });
78
+
79
+ const emitter = new EventEmitter();
80
+ const eventLog = createEventLog(join(logsDir, 'swarm-events.jsonl'));
81
+
82
+ let state = SWARM_STATES.PLANNING;
83
+ let plan = null;
84
+ let lockManager = null;
85
+
86
+ /** @type {Map<string, { conductor, shardConfig, result, status }>} */
87
+ const workers = new Map();
88
+
89
+ /** @type {Map<string, { conductor, shardConfig }>} redundant workers for critical shards */
90
+ const redundantWorkers = new Map();
91
+
92
+ const results = new Map(); // shardName → validated result
93
+ const failures = new Map(); // shardName → failure info
94
+
95
+ // ── State machine ───────────────────────────────────────────
96
+
97
+ function setState(next, reason = '') {
98
+ const prev = state;
99
+ state = next;
100
+ eventLog.append('swarm_state', { from: prev, to: next, reason });
101
+ emitter.emit('stateChange', { from: prev, to: next, reason });
102
+ }
103
+
104
+ // ── Worker lifecycle ────────────────────────────────────────
105
+
106
+ function buildSessionConfig(shard) {
107
+ const config = {
108
+ id: `swarm-${shard.name}-${Date.now()}`,
109
+ agent: shard.agent,
110
+ prompt: shard.prompt,
111
+ workdir,
112
+ mcpServers: shard.mcp,
113
+ };
114
+
115
+ // Remote shard: add conductor remote fields
116
+ if (shard.host && shard._remoteEnv) {
117
+ const remoteDir = resolveRemoteDir(workdir, shard._remoteEnv);
118
+ return {
119
+ ...config,
120
+ remote: true,
121
+ host: shard.host,
122
+ sessionName: `swarm-${shard.name}-${Date.now()}`,
123
+ paneTarget: `swarm-${shard.name}-${Date.now()}:0.0`,
124
+ workdir: remoteDir,
125
+ };
126
+ }
127
+
128
+ return config;
129
+ }
130
+
131
+ function launchShard(shard, isRedundant = false) {
132
+ const shardLogsDir = join(logsDir, isRedundant ? `${shard.name}-redundant` : shard.name);
133
+ mkdirSync(shardLogsDir, { recursive: true });
134
+
135
+ // Remote shard: probe environment before conductor creation
136
+ if (shard.host && !shard._remoteEnv) {
137
+ try {
138
+ shard._remoteEnv = probeRemoteEnv(shard.host);
139
+ if (!shard._remoteEnv.claudePath) {
140
+ eventLog.append('remote_probe_no_claude', { shard: shard.name, host: shard.host });
141
+ failures.set(shard.name, { mode: FAILURE_MODES.F1_CRASH, reason: `claude not found on ${shard.host}` });
142
+ return null;
143
+ }
144
+ eventLog.append('remote_probe_ok', { shard: shard.name, host: shard.host, env: shard._remoteEnv });
145
+ } catch (err) {
146
+ eventLog.append('remote_probe_failed', { shard: shard.name, host: shard.host, error: err.message });
147
+ failures.set(shard.name, { mode: FAILURE_MODES.F1_CRASH, reason: `remote probe failed: ${err.message}` });
148
+ return null;
149
+ }
150
+ }
151
+
152
+ const conductor = createConductor({
153
+ logsDir: shardLogsDir,
154
+ maxRestarts,
155
+ graceMs,
156
+ probeOpts,
157
+ onCompleted: (sessionId) => handleShardCompleted(shard.name, sessionId, isRedundant),
158
+ });
159
+
160
+ const sessionConfig = buildSessionConfig(shard);
161
+
162
+ // Acquire file leases
163
+ if (!isRedundant) {
164
+ const leaseResult = lockManager.acquire(shard.name, shard.files);
165
+ if (!leaseResult.ok) {
166
+ eventLog.append('lease_denied', {
167
+ shard: shard.name,
168
+ conflicts: leaseResult.conflicts,
169
+ });
170
+ failures.set(shard.name, {
171
+ mode: FAILURE_MODES.F4_LEASE_VIOLATION,
172
+ conflicts: leaseResult.conflicts,
173
+ });
174
+ return null;
175
+ }
176
+ }
177
+
178
+ conductor.spawnSession(sessionConfig);
179
+
180
+ eventLog.append('shard_launched', {
181
+ shard: shard.name,
182
+ agent: shard.agent,
183
+ sessionId: sessionConfig.id,
184
+ isRedundant,
185
+ files: shard.files,
186
+ remote: Boolean(shard.host),
187
+ host: shard.host || null,
188
+ });
189
+
190
+ const entry = { conductor, shardConfig: shard, sessionConfig, startedAt: Date.now() };
191
+
192
+ if (isRedundant) {
193
+ redundantWorkers.set(shard.name, entry);
194
+ } else {
195
+ workers.set(shard.name, entry);
196
+ }
197
+
198
+ // Listen for dead events (F1/F2/F3)
199
+ conductor.on('dead', ({ sessionId, reason }) => {
200
+ handleShardFailed(shard.name, sessionId, reason, isRedundant);
201
+ });
202
+
203
+ return entry;
204
+ }
205
+
206
+ // ── Completion handling ─────────────────────────────────────
207
+
208
+ function handleShardCompleted(shardName, sessionId, isRedundant) {
209
+ eventLog.append('shard_completed', { shard: shardName, sessionId, isRedundant });
210
+
211
+ if (isRedundant) {
212
+ // Redundant worker completed first — kill primary if still running
213
+ const primary = workers.get(shardName);
214
+ if (primary && !isTerminal(primary)) {
215
+ eventLog.append('redundant_wins', { shard: shardName });
216
+ void primary.conductor.shutdown('redundant_completed_first');
217
+ }
218
+ } else {
219
+ // Primary completed — kill redundant if exists
220
+ const redundant = redundantWorkers.get(shardName);
221
+ if (redundant) {
222
+ void redundant.conductor.shutdown('primary_completed_first');
223
+ }
224
+ }
225
+
226
+ emitter.emit('shardCompleted', { shardName, sessionId, isRedundant });
227
+ checkAllShardsCompleted();
228
+ }
229
+
230
+ function handleShardFailed(shardName, sessionId, reason, isRedundant) {
231
+ const failureMode = classifyFailure(reason);
232
+
233
+ eventLog.append('shard_failed', {
234
+ shard: shardName,
235
+ sessionId,
236
+ reason,
237
+ failureMode,
238
+ isRedundant,
239
+ });
240
+
241
+ if (isRedundant) return; // redundant failure is non-critical
242
+
243
+ // F2: Rate limit — try fallback agent
244
+ if (failureMode === FAILURE_MODES.F2_RATE_LIMIT) {
245
+ const shard = plan.shards.find((s) => s.name === shardName);
246
+ if (shard) {
247
+ const fallbackAgent = FALLBACK_AGENTS[shard.agent];
248
+ if (fallbackAgent) {
249
+ eventLog.append('fallback_agent', {
250
+ shard: shardName,
251
+ from: shard.agent,
252
+ to: fallbackAgent,
253
+ });
254
+ const fallbackShard = { ...shard, agent: fallbackAgent };
255
+ lockManager.release(shardName);
256
+ launchShard(fallbackShard);
257
+ return;
258
+ }
259
+ }
260
+ }
261
+
262
+ failures.set(shardName, { mode: failureMode, reason, sessionId });
263
+ lockManager.release(shardName);
264
+
265
+ emitter.emit('shardFailed', { shardName, failureMode, reason });
266
+ checkAllShardsCompleted();
267
+ }
268
+
269
+ function classifyFailure(reason) {
270
+ if (!reason) return FAILURE_MODES.F1_CRASH;
271
+ const r = String(reason).toLowerCase();
272
+ if (/rate.?limit|cooldown/u.test(r)) return FAILURE_MODES.F2_RATE_LIMIT;
273
+ if (/stall|l1_stall|timeout/u.test(r)) return FAILURE_MODES.F3_STALL;
274
+ if (/lease|violation/u.test(r)) return FAILURE_MODES.F4_LEASE_VIOLATION;
275
+ if (/merge|conflict/u.test(r)) return FAILURE_MODES.F5_MERGE_CONFLICT;
276
+ return FAILURE_MODES.F1_CRASH;
277
+ }
278
+
279
+ function isTerminal(entry) {
280
+ const snap = entry.conductor.getSnapshot();
281
+ return snap.every((s) => s.state === STATES.COMPLETED || s.state === STATES.DEAD);
282
+ }
283
+
284
+ // ── Integration ─────────────────────────────────────────────
285
+
286
+ function checkAllShardsCompleted() {
287
+ if (state !== SWARM_STATES.RUNNING) return;
288
+
289
+ const allDone = plan.mergeOrder.every((name) => {
290
+ const w = workers.get(name);
291
+ return (w && isTerminal(w)) || failures.has(name);
292
+ });
293
+
294
+ if (allDone) {
295
+ void integrateResults();
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Validate a shard's output — check for file lease violations.
301
+ * @param {string} shardName
302
+ * @param {string[]} changedFiles — files the shard actually modified
303
+ * @returns {{ ok: boolean, violations: Array }}
304
+ */
305
+ function validateResult(shardName, changedFiles) {
306
+ const violations = lockManager.validateChanges(shardName, changedFiles);
307
+
308
+ eventLog.append('validate_result', {
309
+ shard: shardName,
310
+ changedFiles,
311
+ violations,
312
+ ok: violations.length === 0,
313
+ });
314
+
315
+ return {
316
+ ok: violations.length === 0,
317
+ violations,
318
+ };
319
+ }
320
+
321
+ /**
322
+ * Integrate results from all completed shards in merge order.
323
+ * Uses git operations for conflict detection.
324
+ */
325
+ async function integrateResults() {
326
+ setState(SWARM_STATES.INTEGRATING, 'all_shards_done');
327
+
328
+ const integrated = [];
329
+ const integrationFailures = [];
330
+
331
+ for (const shardName of plan.mergeOrder) {
332
+ if (failures.has(shardName)) {
333
+ eventLog.append('skip_failed_shard', { shard: shardName });
334
+ continue;
335
+ }
336
+
337
+ const worker = workers.get(shardName);
338
+ if (!worker) continue;
339
+
340
+ // Read shard output log for changed files
341
+ const changedFiles = detectChangedFiles(shardName, worker);
342
+
343
+ // Validate against lease map
344
+ const validation = validateResult(shardName, changedFiles);
345
+ if (!validation.ok) {
346
+ failures.set(shardName, {
347
+ mode: FAILURE_MODES.F4_LEASE_VIOLATION,
348
+ violations: validation.violations,
349
+ });
350
+ eventLog.append('lease_violation_revert', {
351
+ shard: shardName,
352
+ violations: validation.violations,
353
+ });
354
+ integrationFailures.push(shardName);
355
+ continue;
356
+ }
357
+
358
+ results.set(shardName, {
359
+ shard: shardName,
360
+ changedFiles,
361
+ completedAt: Date.now(),
362
+ });
363
+ integrated.push(shardName);
364
+ }
365
+
366
+ eventLog.append('integration_complete', {
367
+ integrated,
368
+ failed: integrationFailures,
369
+ skipped: [...failures.keys()].filter((n) => !integrationFailures.includes(n)),
370
+ });
371
+
372
+ if (integrationFailures.length > 0 && integrated.length === 0) {
373
+ setState(SWARM_STATES.FAILED, 'all_shards_failed_integration');
374
+ } else {
375
+ setState(SWARM_STATES.COMPLETED, `${integrated.length}/${plan.shards.length} integrated`);
376
+ }
377
+
378
+ emitter.emit('integrationComplete', {
379
+ integrated,
380
+ failed: integrationFailures,
381
+ results: [...results.values()],
382
+ });
383
+ }
384
+
385
+ /**
386
+ * Detect which files a shard modified by reading its output logs.
387
+ * Falls back to an empty list if detection fails.
388
+ * @param {string} shardName
389
+ * @param {object} worker
390
+ * @returns {string[]}
391
+ */
392
+ function detectChangedFiles(shardName, worker) {
393
+ // Best-effort: parse output log for file paths
394
+ const outPath = join(logsDir, shardName);
395
+ try {
396
+ const snap = worker.conductor.getSnapshot();
397
+ for (const session of snap) {
398
+ if (session.outPath && existsSync(session.outPath)) {
399
+ const output = readFileSync(session.outPath, 'utf8');
400
+ return extractFilePathsFromOutput(output, plan.leaseMap.get(shardName) || []);
401
+ }
402
+ }
403
+ } catch { /* best-effort */ }
404
+
405
+ // Fallback: trust the lease map (shard was allowed these files)
406
+ return plan.leaseMap.get(shardName) || [];
407
+ }
408
+
409
+ /**
410
+ * Extract modified file paths from worker output text.
411
+ * Looks for common patterns: "wrote file.mjs", "modified file.mjs", diff headers.
412
+ * @param {string} output
413
+ * @param {string[]} allowedFiles — lease map files to match against
414
+ * @returns {string[]}
415
+ */
416
+ function extractFilePathsFromOutput(output, allowedFiles) {
417
+ if (!output) return allowedFiles;
418
+
419
+ const found = new Set();
420
+ const lines = output.split(/\r?\n/);
421
+
422
+ for (const line of lines) {
423
+ // Match common patterns
424
+ const patterns = [
425
+ /(?:wrote|created|modified|updated|edited)\s+['"]?([^\s'"]+\.\w+)/i,
426
+ /^[+-]{3}\s+[ab]\/(.+)/, // diff headers
427
+ /^diff --git a\/(.+)\s+b\//, // git diff headers
428
+ ];
429
+
430
+ for (const re of patterns) {
431
+ const match = line.match(re);
432
+ if (match) found.add(match[1]);
433
+ }
434
+ }
435
+
436
+ // Intersect with allowed files if we found anything
437
+ if (found.size > 0) {
438
+ return [...found].filter((f) => allowedFiles.some(
439
+ (a) => f.endsWith(a) || a.endsWith(f) || f === a,
440
+ ));
441
+ }
442
+
443
+ return allowedFiles;
444
+ }
445
+
446
+ // ── Status monitor ──────────────────────────────────────────
447
+
448
+ /**
449
+ * Get current swarm status snapshot.
450
+ * @returns {SwarmStatus}
451
+ */
452
+ function getStatus() {
453
+ const workerStatuses = [];
454
+
455
+ for (const [name, w] of workers) {
456
+ const snap = w.conductor.getSnapshot();
457
+ workerStatuses.push({
458
+ shard: name,
459
+ agent: w.shardConfig.agent,
460
+ sessions: snap,
461
+ failed: failures.has(name),
462
+ failureInfo: failures.get(name) || null,
463
+ integrated: results.has(name),
464
+ });
465
+ }
466
+
467
+ return Object.freeze({
468
+ state,
469
+ totalShards: plan?.shards.length || 0,
470
+ completedShards: results.size,
471
+ failedShards: failures.size,
472
+ workers: workerStatuses,
473
+ mergeOrder: plan?.mergeOrder || [],
474
+ criticalShards: plan?.criticalShards || [],
475
+ locks: lockManager?.snapshot() || [],
476
+ });
477
+ }
478
+
479
+ // ── Public API ──────────────────────────────────────────────
480
+
481
+ /**
482
+ * Launch the swarm from a pre-built plan.
483
+ * @param {SwarmPlan} swarmPlan — from planSwarm()
484
+ * @returns {SwarmStatus}
485
+ */
486
+ function launch(swarmPlan) {
487
+ if (state !== SWARM_STATES.PLANNING) {
488
+ throw new Error(`Cannot launch in state "${state}"`);
489
+ }
490
+
491
+ plan = swarmPlan;
492
+
493
+ // Warn about file conflicts but don't block
494
+ if (plan.conflicts.length > 0) {
495
+ eventLog.append('file_conflicts_warning', { conflicts: plan.conflicts });
496
+ emitter.emit('warning', {
497
+ type: 'file_conflicts',
498
+ conflicts: plan.conflicts,
499
+ });
500
+ }
501
+
502
+ // Initialize lock manager
503
+ lockManager = createSwarmLocks({
504
+ repoRoot: workdir,
505
+ persistPath: join(workdir, '.triflux', 'swarm-locks.json'),
506
+ });
507
+
508
+ setState(SWARM_STATES.LAUNCHING, `${plan.shards.length} shards`);
509
+
510
+ // Launch shards respecting dependency order
511
+ const launched = new Set();
512
+ const pending = new Set(plan.mergeOrder);
513
+
514
+ function launchReady() {
515
+ for (const name of pending) {
516
+ const shard = plan.shards.find((s) => s.name === name);
517
+ if (!shard) continue;
518
+
519
+ // Check all dependencies are launched (not necessarily completed)
520
+ const depsReady = shard.depends.every((d) => launched.has(d));
521
+ if (!depsReady) continue;
522
+
523
+ pending.delete(name);
524
+ launched.add(name);
525
+ launchShard(shard);
526
+
527
+ // Launch redundant worker for critical shards
528
+ if (shard.critical) {
529
+ const redundantShard = {
530
+ ...shard,
531
+ agent: FALLBACK_AGENTS[shard.agent] || shard.agent,
532
+ };
533
+ launchShard(redundantShard, true);
534
+ }
535
+ }
536
+ }
537
+
538
+ launchReady();
539
+
540
+ // Re-check pending on each shard completion (dependency chains)
541
+ emitter.on('shardCompleted', () => {
542
+ if (pending.size > 0) launchReady();
543
+ });
544
+
545
+ setState(SWARM_STATES.RUNNING, `${launched.size} launched, ${pending.size} pending deps`);
546
+
547
+ return getStatus();
548
+ }
549
+
550
+ /**
551
+ * Graceful shutdown — kill all workers and release locks.
552
+ * @param {string} [reason]
553
+ */
554
+ async function shutdown(reason = 'shutdown') {
555
+ eventLog.append('swarm_shutdown', { reason, state });
556
+
557
+ const shutdowns = [];
558
+ for (const [, w] of workers) {
559
+ shutdowns.push(w.conductor.shutdown(reason));
560
+ }
561
+ for (const [, w] of redundantWorkers) {
562
+ shutdowns.push(w.conductor.shutdown(reason));
563
+ }
564
+
565
+ await Promise.allSettled(shutdowns);
566
+
567
+ lockManager?.releaseAll();
568
+ await eventLog.flush();
569
+ await eventLog.close();
570
+
571
+ if (state !== SWARM_STATES.COMPLETED && state !== SWARM_STATES.FAILED) {
572
+ setState(SWARM_STATES.FAILED, reason);
573
+ }
574
+
575
+ emitter.emit('shutdown', { reason });
576
+ }
577
+
578
+ return Object.freeze({
579
+ launch,
580
+ shutdown,
581
+ getStatus,
582
+ validateResult,
583
+ on: emitter.on.bind(emitter),
584
+ off: emitter.off.bind(emitter),
585
+ get state() { return state; },
586
+ get plan() { return plan; },
587
+ get eventLogPath() { return eventLog.filePath; },
588
+ });
589
+ }