borgmcp 1.1.14 → 2.0.1

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 (459) hide show
  1. package/CONTRIBUTING.md +15 -0
  2. package/LICENSE +201 -33
  3. package/NOTICE +2 -0
  4. package/README.md +86 -37
  5. package/SECURITY.md +7 -0
  6. package/dist/agent-runtime.d.ts.map +1 -0
  7. package/dist/agent-runtime.js +63 -1
  8. package/dist/agent-runtime.js.map +1 -0
  9. package/dist/assimilate-cmd.d.ts +96 -17
  10. package/dist/assimilate-cmd.d.ts.map +1 -0
  11. package/dist/assimilate-cmd.js +1396 -48
  12. package/dist/assimilate-cmd.js.map +1 -0
  13. package/dist/assimilate-deps.d.ts.map +1 -0
  14. package/dist/assimilate-deps.js +384 -3
  15. package/dist/assimilate-deps.js.map +1 -0
  16. package/dist/assimilate-guard.d.ts +5 -7
  17. package/dist/assimilate-guard.d.ts.map +1 -0
  18. package/dist/assimilate-guard.js +60 -2
  19. package/dist/assimilate-guard.js.map +1 -0
  20. package/dist/assimilate-welcome.d.ts +1 -1
  21. package/dist/assimilate-welcome.d.ts.map +1 -0
  22. package/dist/assimilate-welcome.js +32 -2
  23. package/dist/assimilate-welcome.js.map +1 -0
  24. package/dist/auth-env.d.ts +7 -50
  25. package/dist/auth-env.d.ts.map +1 -0
  26. package/dist/auth-env.js +22 -1
  27. package/dist/auth-env.js.map +1 -0
  28. package/dist/backends/launch-all-pastelist.d.ts.map +1 -0
  29. package/dist/backends/launch-all-pastelist.js +11 -4
  30. package/dist/backends/launch-all-pastelist.js.map +1 -0
  31. package/dist/backends/launch-all-tmux.d.ts.map +1 -0
  32. package/dist/backends/launch-all-tmux.js +49 -1
  33. package/dist/backends/launch-all-tmux.js.map +1 -0
  34. package/dist/backends/launch-all-windows.d.ts.map +1 -0
  35. package/dist/backends/launch-all-windows.js +86 -12
  36. package/dist/backends/launch-all-windows.js.map +1 -0
  37. package/dist/bare-launch-menu.d.ts.map +1 -0
  38. package/dist/bare-launch-menu.js +83 -6
  39. package/dist/bare-launch-menu.js.map +1 -0
  40. package/dist/claude-launch-args.d.ts.map +1 -0
  41. package/dist/claude-launch-args.js +34 -1
  42. package/dist/claude-launch-args.js.map +1 -0
  43. package/dist/claude.d.ts.map +1 -0
  44. package/dist/claude.js +486 -14
  45. package/dist/claude.js.map +1 -0
  46. package/dist/cleanup-cmd.d.ts +5 -5
  47. package/dist/cleanup-cmd.d.ts.map +1 -0
  48. package/dist/cleanup-cmd.js +453 -14
  49. package/dist/cleanup-cmd.js.map +1 -0
  50. package/dist/clear-rewake-core.d.ts.map +1 -0
  51. package/dist/clear-rewake-core.js +23 -2
  52. package/dist/clear-rewake-core.js.map +1 -0
  53. package/dist/clear-rewake.d.ts.map +1 -0
  54. package/dist/clear-rewake.js +19 -1
  55. package/dist/clear-rewake.js.map +1 -0
  56. package/dist/cli-help.d.ts +5 -0
  57. package/dist/cli-help.d.ts.map +1 -0
  58. package/dist/cli-help.js +104 -56
  59. package/dist/cli-help.js.map +1 -0
  60. package/dist/cli-platform.d.ts.map +1 -0
  61. package/dist/cli-platform.js +106 -4
  62. package/dist/cli-platform.js.map +1 -0
  63. package/dist/cli-tool-approval.d.ts +84 -0
  64. package/dist/cli-tool-approval.d.ts.map +1 -0
  65. package/dist/cli-tool-approval.js +583 -0
  66. package/dist/cli-tool-approval.js.map +1 -0
  67. package/dist/codex-app-server.d.ts.map +1 -0
  68. package/dist/codex-app-server.js +228 -4
  69. package/dist/codex-app-server.js.map +1 -0
  70. package/dist/codex-app-wake.d.ts.map +1 -0
  71. package/dist/codex-app-wake.js +432 -2
  72. package/dist/codex-app-wake.js.map +1 -0
  73. package/dist/codex-launch.d.ts.map +1 -0
  74. package/dist/codex-launch.js +102 -2
  75. package/dist/codex-launch.js.map +1 -0
  76. package/dist/codex-remote.d.ts +6 -1
  77. package/dist/codex-remote.d.ts.map +1 -0
  78. package/dist/codex-remote.js +394 -1
  79. package/dist/codex-remote.js.map +1 -0
  80. package/dist/codex-wake-resolve.d.ts.map +1 -0
  81. package/dist/codex-wake-resolve.js +127 -1
  82. package/dist/codex-wake-resolve.js.map +1 -0
  83. package/dist/config-utils.d.ts +2 -2
  84. package/dist/config-utils.d.ts.map +1 -0
  85. package/dist/config-utils.js +766 -3
  86. package/dist/config-utils.js.map +1 -0
  87. package/dist/config.d.ts +98 -61
  88. package/dist/config.d.ts.map +1 -0
  89. package/dist/config.js +426 -1
  90. package/dist/config.js.map +1 -0
  91. package/dist/console-prefix.d.ts.map +1 -0
  92. package/dist/console-prefix.js +96 -1
  93. package/dist/console-prefix.js.map +1 -0
  94. package/dist/cube-name.d.ts.map +1 -0
  95. package/dist/cube-name.js +67 -1
  96. package/dist/cube-name.js.map +1 -0
  97. package/dist/cubes.d.ts +126 -18
  98. package/dist/cubes.d.ts.map +1 -0
  99. package/dist/cubes.js +412 -4
  100. package/dist/cubes.js.map +1 -0
  101. package/dist/debug.d.ts.map +1 -0
  102. package/dist/debug.js +71 -1
  103. package/dist/debug.js.map +1 -0
  104. package/dist/direct-log.d.ts.map +1 -0
  105. package/dist/direct-log.js +11 -1
  106. package/dist/direct-log.js.map +1 -0
  107. package/dist/docs-sections.d.ts +11 -10
  108. package/dist/docs-sections.d.ts.map +1 -0
  109. package/dist/docs-sections.js +113 -3
  110. package/dist/docs-sections.js.map +1 -0
  111. package/dist/drone-lifecycle.d.ts +5 -21
  112. package/dist/drone-lifecycle.d.ts.map +1 -0
  113. package/dist/drone-lifecycle.js +57 -8
  114. package/dist/drone-lifecycle.js.map +1 -0
  115. package/dist/drone-management.d.ts +29 -0
  116. package/dist/drone-management.d.ts.map +1 -0
  117. package/dist/drone-management.js +109 -0
  118. package/dist/drone-management.js.map +1 -0
  119. package/dist/ensure-mcp-config.d.ts.map +1 -0
  120. package/dist/ensure-mcp-config.js +36 -1
  121. package/dist/ensure-mcp-config.js.map +1 -0
  122. package/dist/evict-drone.d.ts +10 -2
  123. package/dist/evict-drone.d.ts.map +1 -0
  124. package/dist/evict-drone.js +60 -1
  125. package/dist/evict-drone.js.map +1 -0
  126. package/dist/gc-orphan-inboxes.d.ts.map +1 -0
  127. package/dist/gc-orphan-inboxes.js +177 -1
  128. package/dist/gc-orphan-inboxes.js.map +1 -0
  129. package/dist/inbox-monitor.d.ts +1 -1
  130. package/dist/inbox-monitor.d.ts.map +1 -0
  131. package/dist/inbox-monitor.js +856 -3
  132. package/dist/inbox-monitor.js.map +1 -0
  133. package/dist/index.d.ts +6 -4
  134. package/dist/index.d.ts.map +1 -0
  135. package/dist/index.js +1034 -37
  136. package/dist/index.js.map +1 -0
  137. package/dist/launch-all-cmd.d.ts.map +1 -0
  138. package/dist/launch-all-cmd.js +377 -37
  139. package/dist/launch-all-cmd.js.map +1 -0
  140. package/dist/launch-all-command.d.ts.map +1 -0
  141. package/dist/launch-all-command.js +24 -1
  142. package/dist/launch-all-command.js.map +1 -0
  143. package/dist/launch-all-deps.d.ts +3 -8
  144. package/dist/launch-all-deps.d.ts.map +1 -0
  145. package/dist/launch-all-deps.js +99 -1
  146. package/dist/launch-all-deps.js.map +1 -0
  147. package/dist/launch-all-discovery.d.ts +1 -0
  148. package/dist/launch-all-discovery.d.ts.map +1 -0
  149. package/dist/launch-all-discovery.js +90 -4
  150. package/dist/launch-all-discovery.js.map +1 -0
  151. package/dist/launch-all-locks.d.ts.map +1 -0
  152. package/dist/launch-all-locks.js +54 -1
  153. package/dist/launch-all-locks.js.map +1 -0
  154. package/dist/launch-gate.d.ts +2 -2
  155. package/dist/launch-gate.d.ts.map +1 -0
  156. package/dist/launch-gate.js +73 -1
  157. package/dist/launch-gate.js.map +1 -0
  158. package/dist/lifecycle-log-guard.d.ts.map +1 -0
  159. package/dist/lifecycle-log-guard.js +93 -2
  160. package/dist/lifecycle-log-guard.js.map +1 -0
  161. package/dist/list-roles-render.d.ts.map +1 -0
  162. package/dist/list-roles-render.js +40 -6
  163. package/dist/list-roles-render.js.map +1 -0
  164. package/dist/local-log-routing.d.ts +19 -0
  165. package/dist/local-log-routing.d.ts.map +1 -0
  166. package/dist/local-log-routing.js +67 -0
  167. package/dist/local-log-routing.js.map +1 -0
  168. package/dist/local-manage-tool-result.d.ts +8 -0
  169. package/dist/local-manage-tool-result.d.ts.map +1 -0
  170. package/dist/local-manage-tool-result.js +10 -0
  171. package/dist/local-manage-tool-result.js.map +1 -0
  172. package/dist/local-server-cursor.d.ts +34 -0
  173. package/dist/local-server-cursor.d.ts.map +1 -0
  174. package/dist/local-server-cursor.js +151 -0
  175. package/dist/local-server-cursor.js.map +1 -0
  176. package/dist/log-audit.d.ts.map +1 -0
  177. package/dist/log-audit.js +194 -3
  178. package/dist/log-audit.js.map +1 -0
  179. package/dist/log-stream.d.ts +31 -18
  180. package/dist/log-stream.d.ts.map +1 -0
  181. package/dist/log-stream.js +1214 -12
  182. package/dist/log-stream.js.map +1 -0
  183. package/dist/model-presets.d.ts.map +1 -0
  184. package/dist/model-presets.js +19 -1
  185. package/dist/model-presets.js.map +1 -0
  186. package/dist/name-validator.d.ts.map +1 -0
  187. package/dist/name-validator.js +22 -1
  188. package/dist/name-validator.js.map +1 -0
  189. package/dist/opencode-drone.d.ts.map +1 -0
  190. package/dist/opencode-drone.js +438 -4
  191. package/dist/opencode-drone.js.map +1 -0
  192. package/dist/opencode-plugin.d.ts +1 -1
  193. package/dist/opencode-plugin.d.ts.map +1 -0
  194. package/dist/opencode-plugin.js +29 -4
  195. package/dist/opencode-plugin.js.map +1 -0
  196. package/dist/parse-assimilate-args.d.ts +1 -1
  197. package/dist/parse-assimilate-args.d.ts.map +1 -0
  198. package/dist/parse-assimilate-args.js +125 -1
  199. package/dist/parse-assimilate-args.js.map +1 -0
  200. package/dist/parse-launch-all-args.d.ts.map +1 -0
  201. package/dist/parse-launch-all-args.js +70 -1
  202. package/dist/parse-launch-all-args.js.map +1 -0
  203. package/dist/postinstall-banner.d.ts.map +1 -0
  204. package/dist/postinstall-banner.js +27 -2
  205. package/dist/postinstall-banner.js.map +1 -0
  206. package/dist/postinstall.d.ts.map +1 -0
  207. package/dist/postinstall.js +32 -5
  208. package/dist/postinstall.js.map +1 -0
  209. package/dist/readiness-probe.d.ts.map +1 -0
  210. package/dist/readiness-probe.js +9 -1
  211. package/dist/readiness-probe.js.map +1 -0
  212. package/dist/regen-format.d.ts.map +1 -0
  213. package/dist/regen-format.js +545 -13
  214. package/dist/regen-format.js.map +1 -0
  215. package/dist/regen.d.ts.map +1 -0
  216. package/dist/regen.js +141 -5
  217. package/dist/regen.js.map +1 -0
  218. package/dist/remote-client.d.ts +65 -175
  219. package/dist/remote-client.d.ts.map +1 -0
  220. package/dist/remote-client.js +942 -1
  221. package/dist/remote-client.js.map +1 -0
  222. package/dist/reset-local-seat-cmd.d.ts +50 -0
  223. package/dist/reset-local-seat-cmd.d.ts.map +1 -0
  224. package/dist/reset-local-seat-cmd.js +169 -0
  225. package/dist/reset-local-seat-cmd.js.map +1 -0
  226. package/dist/role-match.d.ts.map +1 -0
  227. package/dist/role-match.js +23 -1
  228. package/dist/role-match.js.map +1 -0
  229. package/dist/role-resolver.d.ts.map +1 -0
  230. package/dist/role-resolver.js +60 -1
  231. package/dist/role-resolver.js.map +1 -0
  232. package/dist/roster-render.d.ts.map +1 -0
  233. package/dist/roster-render.js +121 -3
  234. package/dist/roster-render.js.map +1 -0
  235. package/dist/seat-probe.d.ts +32 -13
  236. package/dist/seat-probe.d.ts.map +1 -0
  237. package/dist/seat-probe.js +78 -1
  238. package/dist/seat-probe.js.map +1 -0
  239. package/dist/seat-store.d.ts +76 -0
  240. package/dist/seat-store.d.ts.map +1 -0
  241. package/dist/seat-store.js +309 -0
  242. package/dist/seat-store.js.map +1 -0
  243. package/dist/seats.d.ts +303 -0
  244. package/dist/seats.d.ts.map +1 -0
  245. package/dist/seats.js +551 -0
  246. package/dist/seats.js.map +1 -0
  247. package/dist/self-path.d.ts +27 -0
  248. package/dist/self-path.d.ts.map +1 -0
  249. package/dist/self-path.js +43 -0
  250. package/dist/self-path.js.map +1 -0
  251. package/dist/server-endpoint.d.ts +9 -0
  252. package/dist/server-endpoint.d.ts.map +1 -0
  253. package/dist/server-endpoint.js +38 -0
  254. package/dist/server-endpoint.js.map +1 -0
  255. package/dist/server-errors.d.ts +50 -0
  256. package/dist/server-errors.d.ts.map +1 -0
  257. package/dist/server-errors.js +79 -0
  258. package/dist/server-errors.js.map +1 -0
  259. package/dist/server-handshake.d.ts +197 -0
  260. package/dist/server-handshake.d.ts.map +1 -0
  261. package/dist/server-handshake.js +505 -0
  262. package/dist/server-handshake.js.map +1 -0
  263. package/dist/server-response.d.ts +3 -0
  264. package/dist/server-response.d.ts.map +1 -0
  265. package/dist/server-response.js +67 -0
  266. package/dist/server-response.js.map +1 -0
  267. package/dist/server-trust.d.ts +14 -0
  268. package/dist/server-trust.d.ts.map +1 -0
  269. package/dist/server-trust.js +225 -0
  270. package/dist/server-trust.js.map +1 -0
  271. package/dist/setup-confirm.d.ts +3 -3
  272. package/dist/setup-confirm.d.ts.map +1 -0
  273. package/dist/setup-confirm.js +126 -2
  274. package/dist/setup-confirm.js.map +1 -0
  275. package/dist/setup.d.ts +5 -3
  276. package/dist/setup.d.ts.map +1 -0
  277. package/dist/setup.js +169 -46
  278. package/dist/setup.js.map +1 -0
  279. package/dist/shell-escape.d.ts.map +1 -0
  280. package/dist/shell-escape.js +22 -1
  281. package/dist/shell-escape.js.map +1 -0
  282. package/dist/spawn.d.ts +2 -2
  283. package/dist/spawn.d.ts.map +1 -0
  284. package/dist/spawn.js +29 -10
  285. package/dist/spawn.js.map +1 -0
  286. package/dist/startup-services.d.ts +0 -1
  287. package/dist/startup-services.d.ts.map +1 -0
  288. package/dist/startup-services.js +26 -1
  289. package/dist/startup-services.js.map +1 -0
  290. package/dist/stream-owner.d.ts +6 -2
  291. package/dist/stream-owner.d.ts.map +1 -0
  292. package/dist/stream-owner.js +547 -2
  293. package/dist/stream-owner.js.map +1 -0
  294. package/dist/stream-status.d.ts +1 -1
  295. package/dist/stream-status.d.ts.map +1 -0
  296. package/dist/stream-status.js +268 -3
  297. package/dist/stream-status.js.map +1 -0
  298. package/dist/sync-roles-render.d.ts.map +1 -0
  299. package/dist/sync-roles-render.js +118 -3
  300. package/dist/sync-roles-render.js.map +1 -0
  301. package/dist/sync.d.ts +1 -1
  302. package/dist/sync.d.ts.map +1 -0
  303. package/dist/sync.js +286 -22
  304. package/dist/sync.js.map +1 -0
  305. package/dist/terminal-title.d.ts.map +1 -0
  306. package/dist/terminal-title.js +68 -1
  307. package/dist/terminal-title.js.map +1 -0
  308. package/dist/token-store.d.ts +17 -111
  309. package/dist/token-store.d.ts.map +1 -0
  310. package/dist/token-store.js +81 -1
  311. package/dist/token-store.js.map +1 -0
  312. package/dist/tool-manifest.d.ts +4 -8
  313. package/dist/tool-manifest.d.ts.map +1 -0
  314. package/dist/tool-manifest.js +554 -1
  315. package/dist/tool-manifest.js.map +1 -0
  316. package/dist/tool-scope.d.ts +8 -12
  317. package/dist/tool-scope.d.ts.map +1 -0
  318. package/dist/tool-scope.js +97 -1
  319. package/dist/tool-scope.js.map +1 -0
  320. package/dist/types.d.ts +1 -5
  321. package/dist/types.d.ts.map +1 -0
  322. package/dist/types.js +5 -0
  323. package/dist/types.js.map +1 -0
  324. package/dist/unknown-subcommand.d.ts +1 -1
  325. package/dist/unknown-subcommand.d.ts.map +1 -0
  326. package/dist/unknown-subcommand.js +37 -1
  327. package/dist/unknown-subcommand.js.map +1 -0
  328. package/dist/version.d.ts +1 -1
  329. package/dist/version.d.ts.map +1 -0
  330. package/dist/version.js +78 -2
  331. package/dist/version.js.map +1 -0
  332. package/dist/working-repo.d.ts.map +1 -0
  333. package/dist/working-repo.js +81 -1
  334. package/dist/working-repo.js.map +1 -0
  335. package/dist/worktree-lifecycle.d.ts.map +1 -0
  336. package/dist/worktree-lifecycle.js +205 -2
  337. package/dist/worktree-lifecycle.js.map +1 -0
  338. package/docs/EXTRACTION_PROVENANCE.md +40 -0
  339. package/docs/LOCAL_SERVER.md +103 -0
  340. package/docs/RELEASING.md +120 -0
  341. package/package.json +52 -34
  342. package/src/agent-runtime.ts +69 -0
  343. package/src/assimilate-cmd.ts +1864 -0
  344. package/src/assimilate-deps.ts +444 -0
  345. package/src/assimilate-guard.ts +84 -0
  346. package/src/assimilate-welcome.ts +39 -0
  347. package/src/auth-env.ts +21 -0
  348. package/src/backends/launch-all-pastelist.ts +18 -0
  349. package/src/backends/launch-all-tmux.ts +75 -0
  350. package/src/backends/launch-all-windows.ts +110 -0
  351. package/src/bare-launch-menu.ts +148 -0
  352. package/src/claude-launch-args.ts +35 -0
  353. package/src/claude.ts +561 -0
  354. package/src/cleanup-cmd.ts +581 -0
  355. package/src/clear-rewake-core.ts +34 -0
  356. package/src/clear-rewake.ts +18 -0
  357. package/src/cli-help.ts +116 -0
  358. package/src/cli-platform.ts +127 -0
  359. package/src/cli-tool-approval.ts +716 -0
  360. package/src/codex-app-server.ts +238 -0
  361. package/src/codex-app-wake.ts +512 -0
  362. package/src/codex-launch.ts +140 -0
  363. package/src/codex-remote.ts +500 -0
  364. package/src/codex-wake-resolve.ts +155 -0
  365. package/src/config-utils.ts +811 -0
  366. package/src/config.ts +595 -0
  367. package/src/console-prefix.ts +99 -0
  368. package/src/cube-name.ts +69 -0
  369. package/src/cubes.ts +583 -0
  370. package/src/debug.ts +73 -0
  371. package/src/direct-log.ts +9 -0
  372. package/src/docs-sections.ts +129 -0
  373. package/src/drone-lifecycle.ts +62 -0
  374. package/src/drone-management.ts +162 -0
  375. package/src/ensure-mcp-config.ts +54 -0
  376. package/src/evict-drone.ts +82 -0
  377. package/src/gc-orphan-inboxes.ts +229 -0
  378. package/src/inbox-monitor.ts +1005 -0
  379. package/src/index.ts +1201 -0
  380. package/src/launch-all-cmd.ts +450 -0
  381. package/src/launch-all-command.ts +30 -0
  382. package/src/launch-all-deps.ts +188 -0
  383. package/src/launch-all-discovery.ts +118 -0
  384. package/src/launch-all-locks.ts +74 -0
  385. package/src/launch-gate.ts +80 -0
  386. package/src/lifecycle-log-guard.ts +144 -0
  387. package/src/list-roles-render.ts +52 -0
  388. package/src/local-log-routing.ts +106 -0
  389. package/src/local-manage-tool-result.ts +10 -0
  390. package/src/local-server-cursor.ts +188 -0
  391. package/src/log-audit.ts +207 -0
  392. package/src/log-stream.ts +1505 -0
  393. package/src/model-presets.ts +22 -0
  394. package/src/name-validator.ts +23 -0
  395. package/src/opencode-drone.ts +528 -0
  396. package/src/opencode-plugin.ts +38 -0
  397. package/src/parse-assimilate-args.ts +125 -0
  398. package/src/parse-launch-all-args.ts +100 -0
  399. package/src/postinstall-banner.ts +37 -0
  400. package/src/postinstall.ts +36 -0
  401. package/src/readiness-probe.ts +12 -0
  402. package/src/regen-format.ts +651 -0
  403. package/src/regen.ts +150 -0
  404. package/src/remote-client.ts +1379 -0
  405. package/src/reset-local-seat-cmd.ts +227 -0
  406. package/src/role-match.ts +29 -0
  407. package/src/role-resolver.ts +93 -0
  408. package/src/roster-render.ts +196 -0
  409. package/src/seat-probe.ts +115 -0
  410. package/src/seat-store.ts +333 -0
  411. package/src/seats.ts +741 -0
  412. package/src/self-path.ts +51 -0
  413. package/src/server-endpoint.ts +38 -0
  414. package/src/server-errors.ts +93 -0
  415. package/src/server-handshake.ts +783 -0
  416. package/src/server-response.ts +76 -0
  417. package/src/server-trust.ts +245 -0
  418. package/src/setup-confirm.ts +171 -0
  419. package/src/setup.ts +186 -0
  420. package/src/shell-escape.ts +22 -0
  421. package/src/spawn.ts +31 -0
  422. package/src/startup-services.ts +36 -0
  423. package/src/stream-owner.ts +686 -0
  424. package/src/stream-status.ts +337 -0
  425. package/src/sync-roles-render.ts +163 -0
  426. package/src/sync.ts +363 -0
  427. package/src/terminal-title.ts +76 -0
  428. package/src/token-store.ts +102 -0
  429. package/src/tool-manifest.ts +610 -0
  430. package/src/tool-scope.ts +110 -0
  431. package/src/types.ts +5 -0
  432. package/src/unknown-subcommand.ts +35 -0
  433. package/src/version.ts +82 -0
  434. package/src/working-repo.ts +99 -0
  435. package/src/worktree-lifecycle.ts +254 -0
  436. package/dist/auth-recovery.d.ts +0 -33
  437. package/dist/auth-recovery.js +0 -1
  438. package/dist/auth.d.ts +0 -103
  439. package/dist/auth.js +0 -39
  440. package/dist/device-auth.d.ts +0 -75
  441. package/dist/device-auth.js +0 -1
  442. package/dist/drone-address.d.ts +0 -26
  443. package/dist/drone-address.js +0 -1
  444. package/dist/get-started.d.ts +0 -20
  445. package/dist/get-started.js +0 -2
  446. package/dist/health-beat.d.ts +0 -134
  447. package/dist/health-beat.js +0 -1
  448. package/dist/role-section.d.ts +0 -110
  449. package/dist/role-section.js +0 -8
  450. package/dist/setup-action.d.ts +0 -20
  451. package/dist/setup-action.js +0 -1
  452. package/dist/stale-version-check.d.ts +0 -40
  453. package/dist/stale-version-check.js +0 -1
  454. package/dist/subscription-retry.d.ts +0 -40
  455. package/dist/subscription-retry.js +0 -1
  456. package/dist/templates.d.ts +0 -107
  457. package/dist/templates.js +0 -359
  458. package/dist/token-crypto.d.ts +0 -50
  459. package/dist/token-crypto.js +0 -1
@@ -1,13 +1,545 @@
1
- import{ROLE_SCOPED_SAFETY_DISCIPLINES as _,UNIVERSAL_SAFETY_DISCIPLINES as x}from"./templates.js";import{parseRoleSections as C}from"./role-section.js";import{formatRoleAgentLabel as N}from"./roster-render.js";import{formatDroneAddressToken as T}from"./drone-address.js";import{shellEscape as f}from"./shell-escape.js";function q(e){if(!e||!e.trim())return null;try{const t=JSON.parse(e)?.source;return typeof t=="string"?t:null}catch{return null}}function D(e,o,t){return e==="codex"?["Required Codex wake path: Borg activity stream \u2192 inbox wake channel via app-server remote control.","On every wake, run `borg_read-log unread_only=true` and drain until caught up.","No additional scheduler setup is required.",'Degraded fallback (only if remote control is unavailable): on return, call `borg_regen mode="full"` and drain unread log.'].join(" "):e==="opencode"?"Wake path: OpenCode wakes via SDK-driven entry injection into the TUI session \u2014 there is no tail-Monitor or `/loop` heartbeat to arm. Cube posts are delivered directly into your active session. Use `borg_regen` at any time for fresh context.":["Arm your wake path before working:",`1. **Inbox Monitor** (wake path) \u2014 run a persistent Monitor on \`${t?`borg-inbox-monitor --state-root ${f(t)} ${f(o)}`:`borg-inbox-monitor ${f(o)}`}\` so cube posts wake you in real time.`,"2. **Engage `/loop`** (self-paced) so you keep waking to triage the cube.","3. **Adaptive recovery deadline** \u2014 after wake/triage, set ONE `ScheduleWakeup`: [9000, 12600]s (3h \xB130m) when Monitor is healthy or indeterminate; [720, 1080]s (15m \xB13m) only when explicitly broken. Re-arm Monitor; retry short until healthy. A real Monitor wake resets\u2014not stacks\u2014it.","4. **Recovery tick** \u2014 drain `borg_read-log unread_only=true` first. If empty, do not full-regen or make a liveness post: check wake status, set the deadline, resume prior work. This reduces client fallback churn; safety probes may still wake."].join(`
2
- `)}function H(e,o){return{cubeName:o?.cube?.name??e.name,droneLabel:o?.drone?.label??e.droneLabel,roleName:o?.role?.name??e.roleName??null}}function V(e){const{cubeName:o,droneLabel:t,roleName:n,inboxPath:r,monitorStateRoot:i,agentKind:a,source:c}=e,l=c==="clear"?a==="codex"?"\n_(`/clear` cleared your conversation; Codex remote-control wake remains active. Follow the required Codex wake path below.)_\n":a==="claude"?["\n_(`/clear` cleared Claude's `/loop` and recovery deadline \u2014 re-arm them now.)_",'_Quiet-clear fallback: if a later turn follows silence, inspect `borg_stream-status` + `borg_roster`; call `borg_regen mode="full"`, drain `borg_read-log unread_only=true`, then re-arm Monitor, `/loop`, and the recovery deadline._\n'].join(`
3
- `):"":"";return[`# Cube: ${o} \u2014 ${t}`,"",`**Your role:** ${n||"_(call `borg_regen` to load)_"}`,l,"You are a Borg drone \u2014 coordinate through the cube log, and never pause for the user. Blocked \u2192 escalate to your cube's coordinating role.","",D(a,r,i),"",'REQUIRED BEFORE ACTING OR POSTING: (1) call `borg_regen mode="full"`; (2) load the cube directive and conventions with `borg_cube`; (3) ensure your own role playbook/details are loaded \u2014 full regen normally supplies them, but call `borg_role` if missing or after compaction; (4) load `borg_playbook` once per session for the complete operating disciplines. Do not proceed until all four are in context. This orientation stays lean and does not inline them. If you know this session\'s model, include `model="<model-id>"` in the initial full regen for advisory roster metadata.',""].join(`
4
- `)}function E(){return"## How to operate as a Drone\n\nYou're a Drone in a Cube. Coordinate with other drones through the activity log.\n\n**User asks how Borg MCP works** \u2014 a feature, setup, pricing, or concept question? Call `borg_docs {topic}` for the documentation index, then WebFetch the matching section URL and answer from the page. Don't guess borgmcp's own behavior from memory.\n\n**Tools:**\n- `borg_regen` \u2014 refresh full state (your role, roster, unread-log COUNT, and fetch-on-demand pointers) in one call; the cube directive (\u2192 `borg_cube`), the operating-playbook detail (\u2192 `borg_playbook`), and the recent-log payload (\u2192 `borg_read-log` when count >0) are NOT inlined \u2014 fetch them on demand\n- `borg_cube` \u2014 re-read the cube directive and the role overview\n- `borg_role` \u2014 re-read your role's detailed playbook\n- `borg_roster` \u2014 see who else is connected\n- `borg_read-log unread_only=true [limit]` \u2014 drain unread log entries from your server-side cursor\n- `borg_log <message>` \u2014 append to the log\n- `borg_assimilate <cube>` \u2014 switch to a different cube\n\n**How coordination works:** the Cube gives primitives, not workflows. Your role's `detailed_description` (above) is your playbook \u2014 its conventions + signals come from there, not the system. The log is the coordination channel. Different cubes, different conventions.\n\n**Default: act autonomously, coordinate through the log.** Don't wait for user input. Need input \u2192 post the question, continue other work, other drones respond. The human supervisor is reachable through your cube's coordinating / human-seat role (the role your cube designates for direction + integration), or the Queen role when the seat is delegated to a drone \u2014 one continuous seat. Your role's `detailed_description` says when to escalate + which decisions need human input; follow it.\n\n**Operating loop \u2014 each wake, in order:**\n1. Drain unread: `borg_read-log unread_only=true` (oldest-first, repeat until `behind_by=0`) before acting. The \"Cube log\" section gives your UNREAD COUNT.\n2. Apply your role's conventions to each entry. Act on: questions you can answer; blocked peers you can unblock; unowned work you can claim; decisions affecting you.\n3. Actionable signal \u2192 act + post the convention. Don't wait to be asked.\n4. User prompt waiting \u2192 respond, informed by cube context; log substantive units (shipped changes, blockers, findings) regardless of who initiated.\n5. Nothing actionable + no prompt \u2192 done; wait for next wake.\n\n**On a `<task-notification>` wake:** the payload is a truncatable preview; the full entry is in the DB. Drain: `borg_read-log unread_only=true limit=20`, repeat until `behind_by=0`. Do NOT triage with `since=<notification timestamp>` (strict-after \u2014 skips the boundary entry) or a bare window (skips older-unread during bursts).\n\n**On first wake this session:** post one `ARRIVAL: <your-label> (<your-role>) online on <hostname> at <project-path>` (run `hostname`; use cwd for the path). One-time per session \u2014 don't repeat on later wakes; skip if already posted this session (e.g. after a `/mcp` reconnect).\n\n**When a log entry routes work to you** (a routing/assignment-class entry per your cube's conventions that names your label + asks for action, or a direct `<your-label>:` mention): call `borg_ack entry_id=<id>` within ~60s. Use the `borg_ack` TOOL, not an in-band `ACK:` post (it records a queryable flag + wakes the author's Monitor + keeps the log clean). Ack = receipt, not completion (`STARTING` / `DONE` still apply). Ack only routing-class signals \u2014 not every mention.\n\n**Claim a work item before you start it (`borg_ack ... kind=claim`):** `borg_ack` has two kinds \u2014 `ack` (receipt, the default) and `claim` (advisory ownership of a routed work item you are about to take). When a routed entry could be picked up by more than one drone, `borg_ack entry_id=<id> kind=claim` BEFORE starting \u2014 it announces you are taking it so peers skip the duplicate work, and wakes the rest of the entry's audience. If a live peer already holds the claim, skip it; if the claim is STALE (the claimant went silent past the wake-path SLA), re-claim and proceed. A claim is ADVISORY only \u2014 it NEVER substitutes for the completion or approval signal your role's conventions require; a bogus or abandoned claim can at most delay a work item, never bypass its real gate.\n\n**When stuck:** post your blocker per your role's conventions, continue other work. Escalation is per your role detail, not by stalling.\n\n**Anti-passive (lane idle = no work routed to you, no actionable signal in the log):**\n- If your work arrives via dispatch / a work queue: when your lane goes idle, post your role's availability signal (capacity clean, awaiting next assignment from your coordinating role) \u2014 once per idle period, don't spam. No assignment in ~15 min \u2192 ping your coordinating role (capacity available since <time>; any queue item to pick up?).\n- If your work is SELF-DIRECTED (not dispatch-driven): do NOT post an availability signal \u2014 proactively surface lane-substantive work per your role (reviews, audits, proposals, coherence / quality sweeps on relevant in-flight work).\n- Route work-asks through your cube's coordinating role, never directly to the human Queen.\n\n**Verify factual claims:** verify any verifiable claim \u2014 versions, code-state, prod behavior, npm state \u2014 against the SOURCE-OF-TRUTH surface (`git tag` / `git show <ref>:<path>` / grep, `curl` / `wrangler tail`, `npm view`, the live DB) BEFORE writing it; never a derivative artifact (another post, summary, or your own prior framing). The full discipline \u2014 the v1/v2/v3 sharpening levels, the per-claim-type concrete surfaces, and four-surface propagation (brainstorm / comment / review / issue-filing) \u2014 is in the operating-playbook chapter (`borg_playbook`; loaded via the session-start block in your regen).\n\n**Posting to the log:** post per your role's conventions whenever you start/finish a task, get stuck, answer a drone, or learn something others need \u2014 regardless of who initiated (a log signal, your own scan, or a user prompt). Conventions live in your role detail; the system is vocabulary-agnostic.\n\n**Routing posts \u2014 widen the directed default:** the taxonomy routes most prefixes DIRECTED to your cube's coordinating role; your `to:` / `visibility:` overrides it. Widen when a post must reach more than the coordinating role:\n- Posting a verdict / decision / result a specific drone is waiting on: add `to:[that drone]` so they're WOKEN \u2014 without it they can be left UNAWARE of their own merge or feedback. Directed governs the WAKE; it is NOT read-confidentiality: every member can read every entry \u2014 the cube is the trust boundary \u2014 so never post secrets relying on `to:[x]`.\n- Any drone posting a multi-seat DELIVERABLE (spec / security classification / review artifact 3+ seats build or gate against): pass `visibility:broadcast` (or `to:[the seats]`) EVEN IF your prefix (`DONE` etc.) is a directed status class \u2014 else only your coordinating role wakes (taxonomy routes by prefix, not payload) and the building/gating seats miss it.\n\n**Pre-commit git hygiene (universal):**\n\nAny drone that commits code: run `git diff --staged --stat` before `git commit` to verify file count + LOC direction + paths match your intent. Catches deleted files / anomalous -LOC / wrong paths pre-push. Your role may layer more git rules (code-implementing + coordinating roles typically carry the full set)."}const G=E();function J(){return'## Operating playbook \u2014 full disciplines (borg_playbook chapter)\n\nThis is the on-demand detail behind the rule-spine in your regen. Load it ONCE per session; it is static \u2014 do not re-fetch on every wake.\n\n**Verifying factual claims:**\n\nAny time you make a factual claim that could be verified \u2014 "this shipped as version Y", "function Z does W", "endpoint A returns B in prod", "package P is at version Q on npm" \u2014 verify the claim against a SOURCE-OF-TRUTH surface BEFORE writing it, not against a derivative artifact (another post, doc, summary, or your own prior framing). Three sharpening levels:\n\n- **v1 (verify against the actual surface):** check the claim against the surface it describes (e.g. a code-state claim \u2192 grep the file). Apply when the claim is about code-state.\n- **v2 (source-of-truth vs derivative artifacts):** when the verification surface itself could carry the original error chain (another post citing the same wrong claim, a doc copy-mirrored from the post you\'re checking), verify against the canonical source-of-truth: `git tag` for version-attribution, code-by-grep / direct file read for code-state, live `curl` or `wrangler tail` for prod-state, `npm view` for npm-state. Apply when version numbers, deploy timestamps, or other discrete facts are in scope.\n- **v3 (end-to-end execution path vs originating mechanism):** when verifying a live-mechanism claim ("the watchdog wakes silent drones"), verify the END-TO-END execution path, not just each isolated component \u2014 each isolated mechanism can be correct while the path between them silently breaks. Apply when live-mechanism correctness is being claimed; trace the path the wake/value/state actually takes from origin to terminal observer.\n\n**Concrete verification surfaces by claim type:**\n- Version attribution \u2192 `git tag --contains <sha>` or `git log --oneline <tag>`\n- Code state \u2192 match the grep surface to the claim surface:\n - Local uncommitted claim \u2192 `grep -n "<symbol>" <file>` or direct file read in the working tree\n - `origin/main`, PR head, branch, merge-SHA, or tag claim \u2192 `git show <ref>:<path> | grep -n "<symbol>"` (examples: `git show origin/main:workers/heartbeat.ts | grep -n "last_log_post"`; `git show origin/feat/foo:client/src/log-stream.ts | grep -n "ownDrone"`; `git show abc1234:workers/cubes.ts | grep -n "visibility"`)\n- Prod state \u2192 `curl https://<endpoint>` or `wrangler tail --env production`\n- npm registry state \u2192 `npm view <package>@<version>` or `npm view <package>@latest`\n- DB state \u2192 query through the existing `db` interface; never trust a doc claim about row counts / column values\n- Cube log state \u2192 `borg_read-log unread_only=true` for wake triage, draining until `behind_by=0`; don\'t cite from memory or from another drone\'s summary\n- Ratified cube decision \u2192 `borg_decisions {topic}` \u2014 cite the registry\'s active decision by topic; NEVER restate a ratified decision from memory (a memory restatement drifts on the axis). A ratified decision is a first-class verifiable claim type with its own source of truth: the active registry entry. Recording one is `borg_decide` (seat-holder only \u2014 recording IS the ratification act).\n\n**The discipline is universal to reviewer-class actions** (Code Reviewer formal gates + Security Auditor SR gates + PM-courtesy verifications + UX-courtesy reviews + any drone making a verification-worthy factual claim in their cube-log post). It lives in this universal playbook rather than any one role\'s text because it applies to ALL reviewers.\n\n**Four-surface propagation:**\n\nThe discipline applies at FOUR surfaces. Catches at the surface closest to origin are cheapest; catches at later surfaces have already propagated through earlier consumers:\n\n- **Surface 1 (brainstorm-proposal time)**: when a brainstorm contribution names specific code identifiers / API field names / enum values / column names / function signatures, the PROPOSING drone source-grep\'s the referenced file BEFORE composing the proposal. If the proposal cites current `origin/main` or a branch/SHA, grep that ref via `git show <ref>:<path> | grep`; working-tree grep is only for explicitly local/uncommitted claims. Cheapest catch surface; one drone catches one error.\n- **Surface 2 (comment/JSDoc/docstring writing time)**: when an implementation comment cites cross-file invariants (other modules\' thresholds, schema columns, enum values, semantic contracts), the WRITING drone source-grep\'s the referenced file BEFORE writing the comment. If the comment describes a merged/base/PR-head state, grep the named ref via `git show <ref>:<path> | grep`; don\'t let a stale local checkout stand in for the ref being described. Mid-cost catch; one drone catches one error but downstream reviewers may inherit the wrong mental model from the comment.\n- **Surface 3 (review-time verification)**: the existing review-class discipline (Code Reviewer formal gates + Security Auditor SR gates + PM/UX/QA courtesy reviews). Late catch opportunity; if the error propagated through Surfaces 1 + 2, multiple reviewers may have already trusted the framing instead of source-grepping themselves.\n- **Surface 4 (durable-tracking-artifact-writing time)**: when filing a deferred-tracking issue from a cube event payload, the FILING drone fetches the originating entry\'s full body from the cube log BEFORE composing the issue body. For routine wake triage, use `borg_read-log unread_only=true` and drain until caught up; do not rely on a truncated event preview or a `since=<same timestamp>` read, which can skip the boundary entry. Cube event previews can truncate substantive content (mid-paragraph cuts on long entries); filing from the truncated preview trusts a derivative artifact instead of the source-of-truth full entry. Most expensive surface \u2014 the filed issue becomes the cube\'s durable cross-cycle memory; correcting it requires a follow-up correction post, and later pickup drones inherit the incomplete framing if the correction is missed.\n\n**Ratified-decision drift is a four-surface drift-class.** A ratified cube decision restated from memory drifts exactly like a code-identifier claim \u2014 it propagates dispatch (Surface 1, brainstorm) \u2192 copy (Surface 2, comment) \u2192 gate (Surface 3, review), and the cheapest catch is at the brainstorm surface. At each surface, a drone restating a ratified decision source-reads `borg_decisions {topic}` FIRST: the active registry entry is the source of truth; your memory is a derivative artifact. Core rule \u2014 **cite ratified decisions by topic; never restate one from memory.**'}function I(e){const o=typeof e=="string"?new Date(e):e,t=Date.now()-o.getTime();if(!Number.isFinite(t)||t<0)return"just now";const n=Math.floor(t/1e3);if(n<60)return`${n}s ago`;const r=Math.floor(n/60);if(r<60)return`${r}m ago`;const i=Math.floor(r/60);return i<24?`${i}h ago`:`${Math.floor(i/24)}d ago`}function L(e){return e==null||Array.isArray(e)&&e.length===0?"Tip: no message taxonomy declared \u2014 set one to enable intent-based smart routing (#468). Use borg_update-cube with a taxonomy array, or add classes with borg_patch-taxonomy-class.":""}function Q(e,o){return e.drone?.label??o??null}let y=!1,b=null;function K(){y=!1,b=null}function $(e){const o=e??"",t=_.filter(n=>o.includes(n));return[...x,...t]}function P(e,o){return`rationale \u2192 borg_role-rationale ${JSON.stringify(e)} ${JSON.stringify(o)}`}function X(e){const o=e.match(/borg_role-rationale\s+("(?:(?:\\.)|[^"\\])*")\s+("(?:(?:\\.)|[^"\\])*")/);if(!o)return null;try{return{role:JSON.parse(o[1]),section:JSON.parse(o[2])}}catch{return null}}const F=[...x,..._];function U(e,o){return C(o??"").map(r=>{if(r.kind!=="label"||r.heading==null||!r.heading.trim().toLowerCase().endsWith("rationale")||F.some(l=>r.body.includes(l)))return r.body;const a=r.body.indexOf(`
5
- `);return(a===-1?r.body+`
6
- `:r.body.slice(0,a+1))+P(e,r.heading)+`
7
- `}).join("")}function Z(e,o={}){const t=o.mode??"full",n=e.roles.map(s=>`- **${s.name}**${s.is_default?" _(default)_":""} \u2014 ${s.short_description||"_(no short description)_"}`).join(`
8
- `),r=e.drones.map(s=>{const h=e.roles.find(p=>p.id===s.role_id),g=N(h?.name??"?",s.agent_kind);return`- **${s.label}** (${g}) \u2014 last seen ${I(new Date(s.last_seen))}`}).join(`
9
- `)||"_(no drones connected)_",i=typeof e.behind_by=="number"?e.behind_by:null,a=i===null?"Call `borg_read-log unread_only=true` to check for and drain any unread log entries (the log payload is not inlined in regen).":i>0?`You have **${i}** unread log ${i===1?"entry":"entries"}. Drain them with \`borg_read-log unread_only=true\` (oldest-unread first; repeat until \`behind_by=0\`). The log payload is not inlined here \u2014 fetch on demand.`:"You're caught up \u2014 **0** unread log entries. No need to read the log right now.",l=(i??0)===0&&e.drones.length<=1?["## Getting started","","Welcome to your first cube. Here's how to get going:","",'1. Post your first activity: `borg_log message="Starting work on <your task>"`',"2. Invite another agent session: open a new terminal and run `borg assimilate --worktree <name>`","3. Check who's here: `borg_roster`","","---",""].join(`
10
- `):"",w=L(e.cube.message_taxonomy),R=12,m=Array.isArray(e.decisions)?e.decisions:[],v=(()=>{if(m.length===0)return"";const s=m.slice(0,R),h=s.map(p=>`- **${p.topic}:** ${p.decision}`),g=m.length-s.length;return g>0&&h.push(`- _+${g} more \u2014 \`borg_decisions\`_`),["## Ratified decisions","Cite these by topic \u2014 do NOT restate a ratified decision from memory.",...h].join(`
11
- `)})(),d=e.role.detailed_description_hash??null,S=e.role.detailed_description?U(e.role.name,e.role.detailed_description):"_(no detailed description set)_",O="Before you post or act, load your full operating context \u2014 once per session; static, do NOT re-fetch on every wake:\n- `borg_playbook` \u2014 your full operating disciplines (verification, four-surface propagation, ack / routing / idle detail).\n- `borg_cube` \u2014 the cube directive + conventions (log vocabulary, project / git / dispatch conventions).",k=t==="full"||d==null||d!==b,A=t==="full"||!y,u=[l+`# Cube: ${e.cube.name} \u2014 ${e.drone.label}`,"",`**Your role:** ${e.role.name}`,""];return t==="lite"&&u.push('_(lite regen \u2014 the role playbook may be omitted when unchanged; your operating context (playbook + cube directive) loads via the Session-start block (borg_playbook + borg_cube). If the playbook is NOT in your current context (e.g. after a context-compaction), call `borg_regen mode="full"` to re-orient.)_',""),u.push(t==="full"?"## Session start \u2014 required before acting":"## Session start",t==="full"?O:'Operating context (playbook + cube directive) was loaded at session start \u2014 re-fetch `borg_playbook` / `borg_cube` ONLY after a context-compaction (a `mode="full"` regen), not on every wake.',"",...w?[w,""]:[],`## Your role: ${e.role.name}`,k?S:["_(role playbook unchanged since your last full/lite regen; omitted in lite mode)_","",...$(e.role.detailed_description)].join(`
12
- `),"","## Roles in this cube",n,"","## Connected drones",r,"","## Cube log",a,...v?["",v]:[]),A&&(u.push("",E()),y=!0),k&&d!=null&&(b=d),u.join(`
13
- `)}function z(e,o,t){const n=o.get(e.drone_id),r=n?t.get(n.role_id):null,i=new Date(e.created_at).toISOString(),a=typeof e.id=="string"&&e.id.length>0?` [entry_id: ${e.id}]`:"",c=typeof e.drone_id=="string"&&e.drone_id.length>0?` ${T(e.drone_id)}`:"";return`**[${i}]**${a}${c} ${n?.label??"?"} (${r?.name??"?"}): ${e.message}`}export{G as DRONE_PLAYBOOK,K as __resetRegenSessionState,U as compressRoleText,V as formatLeanOrientation,z as formatLogEntryMarkdown,P as formatRationalePointer,Z as formatRegenMarkdown,E as getDronePlaybook,J as getDronePlaybookChapter,I as humanAgo,L as nullTaxonomyTip,q as parseHookSource,X as parseRationalePointer,Q as regenWakePathDroneLabel,H as resolveLeanIdentity,D as wakePathArming};
1
+ /**
2
+ * Shared formatting helpers used by both the MCP `borg_regen` handler in
3
+ * index.ts and the standalone `borg-regen` CLI in regen.ts.
4
+ *
5
+ * Lives in its own module so regen.ts can import these without pulling in
6
+ * index.ts's stdio MCP server bootstrap.
7
+ */
8
+ import { ROLE_SCOPED_SAFETY_DISCIPLINES, UNIVERSAL_SAFETY_DISCIPLINES, } from 'borgmcp-shared/templates';
9
+ import { parseRoleSections } from 'borgmcp-shared/role-section';
10
+ import { formatDroneAddressToken } from 'borgmcp-shared/drone-address';
11
+ import { formatRoleAgentLabel } from './roster-render.js';
12
+ import { shellEscape } from './shell-escape.js';
13
+ import { resolveInboxMonitorPath } from './self-path.js';
14
+ /**
15
+ * Extract the SessionStart `source` from a Claude Code hook payload (gh#926).
16
+ *
17
+ * SessionStart hooks receive a JSON object on stdin whose `source` field is
18
+ * one of `startup` / `resume` / `clear` / `compact`. The `borg-regen`
19
+ * SessionStart hook uses this to detect a `/clear` re-orientation, which is
20
+ * the FIRST time the hook is the SOLE orientation path (the launch kickoff
21
+ * prompt is gone) AND the moment Claude Code clears the session-scoped
22
+ * `/loop` + `ScheduleWakeup` — so the re-injected orientation must instruct
23
+ * an operational re-arm.
24
+ *
25
+ * Best-effort + total: empty input (manual / TTY run with no stdin),
26
+ * malformed JSON, a missing `source`, or a non-string `source` all return
27
+ * `null` so the caller falls back to the default (full-regen) behavior. A
28
+ * hook bin must never throw on unexpected stdin.
29
+ */
30
+ export function parseHookSource(raw) {
31
+ if (!raw || !raw.trim())
32
+ return null;
33
+ try {
34
+ const parsed = JSON.parse(raw);
35
+ const source = parsed?.source;
36
+ return typeof source === 'string' ? source : null;
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ }
42
+ /**
43
+ * The agent-branched WAKE-PATH ARMING sub-block (gh#929/gh#927) — the single
44
+ * shared "re-establish your wake path" instruction reused by the launch
45
+ * kickoff prompt, the lean SessionStart-hook orientation, and the /clear
46
+ * re-orient. Factored to ONE place so the most load-bearing (and most
47
+ * drift-prone) liveness instruction can never diverge across the three
48
+ * surfaces.
49
+ *
50
+ * Agent-branched on the existing env-agnostic signal (BORG_SESSION-style
51
+ * `isCodexRemoteWakeEnabled`), NOT on a mutable server-recorded field:
52
+ * - claude: arm the inbox-file tail Monitor + engage `/loop` + maintain one
53
+ * adaptive `ScheduleWakeup` recovery deadline (long while the Monitor is
54
+ * healthy or indeterminate; short only while explicitly broken).
55
+ * - codex: Borg's activity stream reaches the app-server remote-control inbox
56
+ * channel; each wake is followed by an unread-log drain. Manual full regen
57
+ * + drain is a degraded fallback when remote control is unavailable.
58
+ *
59
+ * `inboxPath` is the deterministic client-generated UUID path
60
+ * (`~/.config/borgmcp/inboxes/<cubeId>/<droneId>.log`), while the optional
61
+ * explicit state root is derived from the saved worktree path. Both are
62
+ * shell-escaped before rendering the launch/orientation command.
63
+ */
64
+ export function wakePathArming(agentKind, inboxPath, monitorStateRoot) {
65
+ if (agentKind === 'codex') {
66
+ return [
67
+ 'Required Codex wake path: Borg activity stream → inbox wake channel via app-server remote control.',
68
+ 'On every wake, run `borg_read-log unread_only=true` and drain until caught up.',
69
+ 'No additional scheduler setup is required.',
70
+ 'Degraded fallback (only if remote control is unavailable): on return, call `borg_regen mode="full"` and drain unread log.',
71
+ ].join(' ');
72
+ }
73
+ if (agentKind === 'opencode') {
74
+ return ('Wake path: OpenCode wakes via SDK-driven entry injection into the TUI session — ' +
75
+ 'there is no tail-Monitor or `/loop` heartbeat to arm. Cube posts are delivered ' +
76
+ 'directly into your active session. Use \`borg_regen\` at any time for fresh context.');
77
+ }
78
+ // gh#client#18: use absolute path to THIS installation's borg-inbox-monitor
79
+ // so the orientation command always resolves to the same version as the
80
+ // running client — never a different one via PATH.
81
+ const monitorBin = shellEscape(resolveInboxMonitorPath());
82
+ const monitorCommand = monitorStateRoot
83
+ ? `${monitorBin} --state-root ${shellEscape(monitorStateRoot)} ${shellEscape(inboxPath)}`
84
+ : `${monitorBin} ${shellEscape(inboxPath)}`;
85
+ return [
86
+ 'Arm your wake path before working:',
87
+ `1. **Inbox Monitor** (wake path) — run a persistent Monitor on \`${monitorCommand}\` so cube posts wake you in real time.`,
88
+ '2. **Engage `/loop`** (self-paced) so you keep waking to triage the cube.',
89
+ '3. **Adaptive recovery** — set ONE `ScheduleWakeup`: [9000, 12600]s (3h ±30m) when Monitor healthy/indeterminate; [720, 1080]s (15m ±3m) when broken. Re-arm Monitor; retry short until healthy. A real Monitor wake resets, not stacks.',
90
+ '4. **Recovery tick** — drain `borg_read-log unread_only=true`. If empty, check wake status, set deadline, resume prior work. No full-regen or liveness post on empty; safety probes may still wake.',
91
+ ].join('\n');
92
+ }
93
+ /**
94
+ * Resolve the lean-orientation identity (gh#927), preferring the fresh
95
+ * network `regen()` result and falling back per-field to the local
96
+ * `getActiveCube` state. When `result` is null — the net-free fallback path
97
+ * taken on a `regen()` network failure — identity comes entirely from local
98
+ * state, so a weak drone that hits a SessionStart network blip still gets
99
+ * oriented (with its wake-path arming) instead of left dormant.
100
+ */
101
+ export function resolveLeanIdentity(active, result) {
102
+ return {
103
+ cubeName: result?.cube?.name ?? active.name,
104
+ droneLabel: result?.drone?.label ?? active.droneLabel,
105
+ roleName: result?.role?.name ?? active.roleName ?? null,
106
+ };
107
+ }
108
+ /**
109
+ * The canonical LEAN orientation core (gh#929/gh#927) — the single shared
110
+ * "minimal operational orientation" rendered for a drone at launch, on every
111
+ * SessionStart source (startup/resume/clear/compact), and on /clear. It
112
+ * SUPERSEDES the per-surface variants: the SessionStart hook renders this
113
+ * instead of the full ~20.7KB `formatRegenMarkdown` (which the harness
114
+ * truncates to a ~2KB preview, leaving weak models partially oriented), and
115
+ * the /clear re-orient is just this with `source: 'clear'`.
116
+ *
117
+ * Three load-bearing parts, all kept (per the SEC/PM/CR rails):
118
+ * - IDENTITY: cube + drone label + role, so a weak model knows who it is.
119
+ * - WAKE-PATH ARMING: the shared `wakePathArming` block (liveness — correct
120
+ * to carry pre-`borg_regen`).
121
+ * - `borg_regen` POINTER: the path to the full operating context and safety
122
+ * floor. Role-specific lifecycle guidance remains reachable through the
123
+ * role-text pointer. Kept in EVERY render.
124
+ *
125
+ * Template-agnostic (#921): the escalation target is "your cube's coordinating
126
+ * role" — NEVER a hardcoded `coordinator` / `drone-1` (this is the
127
+ * single most-rendered instruction surface in the product, and the first
128
+ * thing a weak model on a NON-sw-dev template reads). `roleName` is optional
129
+ * so the net-free fallback can render from local `getActiveCube` state when a
130
+ * `regen()` network call is unavailable.
131
+ */
132
+ export function formatLeanOrientation(args) {
133
+ const { cubeName, droneLabel, roleName, inboxPath, monitorStateRoot, agentKind, source } = args;
134
+ const clearNote = source === 'clear'
135
+ ? agentKind === 'codex'
136
+ ? '\n_(`/clear` cleared your conversation; Codex remote-control wake remains active. Follow the required Codex wake path below.)_\n'
137
+ : agentKind === 'claude'
138
+ ? [
139
+ '\n_(`/clear` cleared Claude\'s `/loop` and recovery deadline — re-arm them now.)_',
140
+ '_Quiet-clear fallback: if a later turn follows silence, inspect `borg_stream-status` + `borg_roster`; call `borg_regen mode="full"`, drain `borg_read-log unread_only=true`, then re-arm Monitor, `/loop`, and the recovery deadline._\n',
141
+ ].join('\n')
142
+ : ''
143
+ : '';
144
+ return [
145
+ `# Cube: ${cubeName} — ${droneLabel}`,
146
+ '',
147
+ `**Your role:** ${roleName || '_(call `borg_regen` to load)_'}`,
148
+ clearNote,
149
+ 'You are a Borg drone — coordinate through the cube log, and never pause for the user. Blocked → escalate to your cube\'s coordinating role.',
150
+ '',
151
+ wakePathArming(agentKind, inboxPath, monitorStateRoot),
152
+ '',
153
+ 'REQUIRED BEFORE ACTING OR POSTING: (1) `borg_regen mode="full"`; (2) `borg_cube` for directive + conventions; (3) confirm role playbook loaded — full regen supplies it, else `borg_role`; (4) `borg_playbook` once per session for operating disciplines. This orientation stays lean — they are not inlined. Include `model="<model-id>"` in initial regen when known.',
154
+ '',
155
+ ].join('\n');
156
+ }
157
+ // gh#927 S3: formatClearReorientation (gh#926) is SUPERSEDED by the shared
158
+ // formatLeanOrientation core — the /clear case is now just
159
+ // formatLeanOrientation({ ..., source: 'clear' }), so the per-surface
160
+ // /clear block is gone (one place, not three).
161
+ /**
162
+ * Build the universal drone playbook.
163
+ *
164
+ * The playbook is appended to every regen / cube / assimilate response.
165
+ * Including it on every refresh is intentional: it protects against
166
+ * /compact and /clear losing the procedural knowledge while state still
167
+ * flows through.
168
+ *
169
+ * The playbook describes the autonomous-default behavior shared by every
170
+ * role. Role-specific overrides (e.g., "consult the human Queen" for the
171
+ * Coordinator role; "ship on consensus" for the Queen role when the seat
172
+ * is delegated to a drone) live in each role's detailed_description, not
173
+ * here.
174
+ */
175
+ // copy-param-claim: borg_ack.kind
176
+ // The drone playbook below recommends `borg_ack ... kind=claim` to claim a
177
+ // review gate; this inline marker pins the param so the #490/#529 copy↔
178
+ // mechanism guard verifies borg_ack actually exposes `kind`
179
+ // (client/__tests__/copy-mechanism-guard.test.ts).
180
+ // copy-param-claim: borg_docs.topic
181
+ // The playbook below points drones to `borg_docs {topic}` for user questions
182
+ // about how Borg MCP works; this marker pins the param so the #490/#529 guard
183
+ // verifies borg_docs exposes `topic`.
184
+ export function getDronePlaybook() {
185
+ return `## How to operate as a Drone
186
+
187
+ You're a Drone in a Cube. Coordinate with other drones through the activity log.
188
+
189
+ **User asks how Borg MCP works** — a feature, setup, pricing, or concept question? Call \`borg_docs {topic}\` for the documentation index, then WebFetch the matching section URL and answer from the page. Don't guess borgmcp's own behavior from memory.
190
+
191
+ **Tools:**
192
+ - \`borg_regen\` — refresh full state (your role, roster, unread-log COUNT, and fetch-on-demand pointers) in one call; the cube directive (→ \`borg_cube\`), the operating-playbook detail (→ \`borg_playbook\`), and the recent-log payload (→ \`borg_read-log\` when count >0) are NOT inlined — fetch them on demand
193
+ - \`borg_cube\` — re-read the cube directive and the role overview
194
+ - \`borg_role\` — re-read your role's detailed playbook
195
+ - \`borg_roster\` — see who else is connected
196
+ - \`borg_read-log unread_only=true [limit]\` — drain unread log entries from your server-side cursor
197
+ - \`borg_log <message>\` — append to the log
198
+ - \`borg_assimilate <cube>\` — switch to a different cube
199
+
200
+ **How coordination works:** the Cube gives primitives, not workflows. Your role's \`detailed_description\` (above) is your playbook — its conventions + signals come from there, not the system. The log is the coordination channel. Different cubes, different conventions.
201
+
202
+ **Default: act autonomously, coordinate through the log.** Don't wait for user input. Need input → post the question, continue other work, other drones respond. The human supervisor is reachable through your cube's coordinating / human-seat role (the role your cube designates for direction + integration), or the Queen role when the seat is delegated to a drone — one continuous seat. Your role's \`detailed_description\` says when to escalate + which decisions need human input; follow it.
203
+
204
+ **Operating loop — each wake, in order:**
205
+ 1. Drain unread: \`borg_read-log unread_only=true\` (oldest-first, repeat until \`behind_by=0\`) before acting. The "Cube log" section gives your UNREAD COUNT.
206
+ 2. Apply your role's conventions to each entry. Act on: questions you can answer; blocked peers you can unblock; unowned work you can claim; decisions affecting you.
207
+ 3. Actionable signal → act + post the convention. Don't wait to be asked.
208
+ 4. User prompt waiting → respond, informed by cube context; log substantive units (shipped changes, blockers, findings) regardless of who initiated.
209
+ 5. Nothing actionable + no prompt → done; wait for next wake.
210
+
211
+ **On a \`<task-notification>\` wake:** the payload is a truncatable preview; the full entry is in the DB. Drain: \`borg_read-log unread_only=true limit=20\`, repeat until \`behind_by=0\`. Do NOT triage with \`since=<notification timestamp>\` (strict-after — skips the boundary entry) or a bare window (skips older-unread during bursts).
212
+
213
+ **On first wake this session:** post one \`ARRIVAL: <your-label> (<your-role>) online on <hostname> at <project-path>\` (run \`hostname\`; use cwd for the path). One-time per session — don't repeat on later wakes; skip if already posted this session (e.g. after a \`/mcp\` reconnect).
214
+
215
+ **When a log entry routes work to you** (a routing/assignment-class entry per your cube's conventions that names your label + asks for action, or a direct \`<your-label>:\` mention): call \`borg_ack entry_id=<id>\` within ~60s. Use the \`borg_ack\` TOOL, not an in-band \`ACK:\` post (it records a queryable flag + wakes the author's Monitor + keeps the log clean). Ack = receipt, not completion (\`STARTING\` / \`DONE\` still apply). Ack only routing-class signals — not every mention.
216
+
217
+ **Claim a work item before you start it (\`borg_ack ... kind=claim\`):** \`borg_ack\` has two kinds — \`ack\` (receipt, the default) and \`claim\` (advisory ownership of a routed work item you are about to take). When a routed entry could be picked up by more than one drone, \`borg_ack entry_id=<id> kind=claim\` BEFORE starting — it announces you are taking it so peers skip the duplicate work, and wakes the rest of the entry's audience. If a live peer already holds the claim, skip it; if the claim is STALE (the claimant went silent past the wake-path SLA), re-claim and proceed. A claim is ADVISORY only — it NEVER substitutes for the completion or approval signal your role's conventions require; a bogus or abandoned claim can at most delay a work item, never bypass its real gate.
218
+
219
+ **When stuck:** post your blocker per your role's conventions, continue other work. Escalation is per your role detail, not by stalling.
220
+
221
+ **Anti-passive (lane idle = no work routed to you, no actionable signal in the log):**
222
+ - If your work arrives via dispatch / a work queue: when your lane goes idle, post your role's availability signal (capacity clean, awaiting next assignment from your coordinating role) — once per idle period, don't spam. No assignment in ~15 min → ping your coordinating role (capacity available since <time>; any queue item to pick up?).
223
+ - If your work is SELF-DIRECTED (not dispatch-driven): do NOT post an availability signal — proactively surface lane-substantive work per your role (reviews, audits, proposals, coherence / quality sweeps on relevant in-flight work).
224
+ - Route work-asks through your cube's coordinating role, never directly to the human Queen.
225
+
226
+ **Verify factual claims:** verify any verifiable claim — versions, code-state, prod behavior, npm state — against the SOURCE-OF-TRUTH surface (\`git tag\` / \`git show <ref>:<path>\` / grep, \`curl\` / \`wrangler tail\`, \`npm view\`, the live DB) BEFORE writing it; never a derivative artifact (another post, summary, or your own prior framing). The full discipline — the v1/v2/v3 sharpening levels, the per-claim-type concrete surfaces, and four-surface propagation (brainstorm / comment / review / issue-filing) — is in the operating-playbook chapter (\`borg_playbook\`; loaded via the session-start block in your regen).
227
+
228
+ **Posting to the log:** post per your role's conventions whenever you start/finish a task, get stuck, answer a drone, or learn something others need — regardless of who initiated (a log signal, your own scan, or a user prompt). Conventions live in your role detail; the system is vocabulary-agnostic.
229
+
230
+ **Routing posts — widen the directed default:** the taxonomy routes most prefixes DIRECTED to your cube's coordinating role; your \`to:\` / \`visibility:\` overrides it. Widen when a post must reach more than the coordinating role:
231
+ - Posting a verdict / decision / result a specific drone is waiting on: add \`to:[that drone]\` so they're WOKEN — without it they can be left UNAWARE of their own merge or feedback. Directed governs the WAKE; it is NOT read-confidentiality: every member can read every entry — the cube is the trust boundary — so never post secrets relying on \`to:[x]\`.
232
+ - Any drone posting a multi-seat DELIVERABLE (spec / security classification / review artifact 3+ seats build or gate against): pass \`visibility:broadcast\` (or \`to:[the seats]\`) EVEN IF your prefix (\`DONE\` etc.) is a directed status class — else only your coordinating role wakes (taxonomy routes by prefix, not payload) and the building/gating seats miss it.
233
+
234
+ **Pre-commit git hygiene (universal):**
235
+
236
+ Any drone that commits code: run \`git diff --staged --stat\` before \`git commit\` to verify file count + LOC direction + paths match your intent. Catches deleted files / anomalous -LOC / wrong paths pre-push. Your role may layer more git rules (code-implementing + coordinating roles typically carry the full set).`;
237
+ }
238
+ /**
239
+ * Eager export of the playbook text. Cheap to compute (string concat);
240
+ * exporting as a constant lets callers splice it directly without a
241
+ * function call site.
242
+ */
243
+ export const DRONE_PLAYBOOK = getDronePlaybook();
244
+ /**
245
+ * gh#912: the verbose operating-discipline DETAIL externalized out of the
246
+ * bootstrap regen into an on-demand chapter (fetched via the borg_playbook
247
+ * tool). The inline core (getDronePlaybook) keeps the rule-spine + triggers +
248
+ * forcing-functions + safety; this chapter carries the WHY, the per-level
249
+ * sharpening, the concrete surfaces, and the four-surface propagation that a
250
+ * drone only needs when doing review/verify-class work. Static text.
251
+ */
252
+ // copy-param-claim: borg_decisions.topic
253
+ // The chapter below tells drones to cite a ratified decision via
254
+ // `borg_decisions {topic}`; this marker pins the param so the #490/#529
255
+ // copy-guard verifies borg_decisions exposes `topic`
256
+ // (client/__tests__/copy-mechanism-guard.test.ts).
257
+ export function getDronePlaybookChapter() {
258
+ return `## Operating playbook — full disciplines (borg_playbook chapter)
259
+
260
+ This is the on-demand detail behind the rule-spine in your regen. Load it ONCE per session; it is static — do not re-fetch on every wake.
261
+
262
+ **Verifying factual claims:**
263
+
264
+ Any time you make a factual claim that could be verified — "this shipped as version Y", "function Z does W", "endpoint A returns B in prod", "package P is at version Q on npm" — verify the claim against a SOURCE-OF-TRUTH surface BEFORE writing it, not against a derivative artifact (another post, doc, summary, or your own prior framing). Three sharpening levels:
265
+
266
+ - **v1 (verify against the actual surface):** check the claim against the surface it describes (e.g. a code-state claim → grep the file). Apply when the claim is about code-state.
267
+ - **v2 (source-of-truth vs derivative artifacts):** when the verification surface itself could carry the original error chain (another post citing the same wrong claim, a doc copy-mirrored from the post you're checking), verify against the canonical source-of-truth: \`git tag\` for version-attribution, code-by-grep / direct file read for code-state, live \`curl\` or \`wrangler tail\` for prod-state, \`npm view\` for npm-state. Apply when version numbers, deploy timestamps, or other discrete facts are in scope.
268
+ - **v3 (end-to-end execution path vs originating mechanism):** when verifying a live-mechanism claim ("the watchdog wakes silent drones"), verify the END-TO-END execution path, not just each isolated component — each isolated mechanism can be correct while the path between them silently breaks. Apply when live-mechanism correctness is being claimed; trace the path the wake/value/state actually takes from origin to terminal observer.
269
+
270
+ **Concrete verification surfaces by claim type:**
271
+ - Version attribution → \`git tag --contains <sha>\` or \`git log --oneline <tag>\`
272
+ - Code state → match the grep surface to the claim surface:
273
+ - Local uncommitted claim → \`grep -n "<symbol>" <file>\` or direct file read in the working tree
274
+ - \`origin/main\`, PR head, branch, merge-SHA, or tag claim → \`git show <ref>:<path>\` followed by a symbol search in the returned source
275
+ - Prod state → \`curl https://<endpoint>\` or \`wrangler tail --env production\`
276
+ - npm registry state → \`npm view <package>@<version>\` or \`npm view <package>@latest\`
277
+ - DB state → query through the existing \`db\` interface; never trust a doc claim about row counts / column values
278
+ - Cube log state → \`borg_read-log unread_only=true\` for wake triage, draining until \`behind_by=0\`; don't cite from memory or from another drone's summary
279
+ - Ratified cube decision → \`borg_decisions {topic}\` — cite the registry's active decision by topic; NEVER restate a ratified decision from memory (a memory restatement drifts on the axis). A ratified decision is a first-class verifiable claim type with its own source of truth: the active registry entry. Recording one is \`borg_decide\`: Coordinator/Queen are workflow-eligible to ratify, but role labels grant no server permission; the selected local client needs a live cube-manage grant.
280
+
281
+ **The discipline is universal to reviewer-class actions** (Code Reviewer formal gates + Security Auditor SR gates + PM-courtesy verifications + UX-courtesy reviews + any drone making a verification-worthy factual claim in their cube-log post). It lives in this universal playbook rather than any one role's text because it applies to ALL reviewers.
282
+
283
+ **Four-surface propagation:**
284
+
285
+ The discipline applies at FOUR surfaces. Catches at the surface closest to origin are cheapest; catches at later surfaces have already propagated through earlier consumers:
286
+
287
+ - **Surface 1 (brainstorm-proposal time)**: when a brainstorm contribution names specific code identifiers / API field names / enum values / column names / function signatures, the PROPOSING drone source-grep's the referenced file BEFORE composing the proposal. If the proposal cites current \`origin/main\` or a branch/SHA, grep that ref via \`git show <ref>:<path> | grep\`; working-tree grep is only for explicitly local/uncommitted claims. Cheapest catch surface; one drone catches one error.
288
+ - **Surface 2 (comment/JSDoc/docstring writing time)**: when an implementation comment cites cross-file invariants (other modules' thresholds, schema columns, enum values, semantic contracts), the WRITING drone source-grep's the referenced file BEFORE writing the comment. If the comment describes a merged/base/PR-head state, grep the named ref via \`git show <ref>:<path> | grep\`; don't let a stale local checkout stand in for the ref being described. Mid-cost catch; one drone catches one error but downstream reviewers may inherit the wrong mental model from the comment.
289
+ - **Surface 3 (review-time verification)**: the existing review-class discipline (Code Reviewer formal gates + Security Auditor SR gates + PM/UX/QA courtesy reviews). Late catch opportunity; if the error propagated through Surfaces 1 + 2, multiple reviewers may have already trusted the framing instead of source-grepping themselves.
290
+ - **Surface 4 (durable-tracking-artifact-writing time)**: when filing a deferred-tracking issue from a cube event payload, the FILING drone fetches the originating entry's full body from the cube log BEFORE composing the issue body. For routine wake triage, use \`borg_read-log unread_only=true\` and drain until caught up; do not rely on a truncated event preview or a \`since=<same timestamp>\` read, which can skip the boundary entry. Cube event previews can truncate substantive content (mid-paragraph cuts on long entries); filing from the truncated preview trusts a derivative artifact instead of the source-of-truth full entry. Most expensive surface — the filed issue becomes the cube's durable cross-cycle memory; correcting it requires a follow-up correction post, and later pickup drones inherit the incomplete framing if the correction is missed.
291
+
292
+ **Ratified-decision drift is a four-surface drift-class.** A ratified cube decision restated from memory drifts exactly like a code-identifier claim — it propagates dispatch (Surface 1, brainstorm) → copy (Surface 2, comment) → gate (Surface 3, review), and the cheapest catch is at the brainstorm surface. At each surface, a drone restating a ratified decision source-reads \`borg_decisions {topic}\` FIRST: the active registry entry is the source of truth; your memory is a derivative artifact. Core rule — **cite ratified decisions by topic; never restate one from memory.**`;
293
+ }
294
+ /**
295
+ * Format an absolute timestamp as a coarse "Xs/Xm/Xh ago" string.
296
+ */
297
+ export function humanAgo(date) {
298
+ const then = typeof date === 'string' ? new Date(date) : date;
299
+ const ms = Date.now() - then.getTime();
300
+ if (!Number.isFinite(ms) || ms < 0)
301
+ return 'just now';
302
+ const sec = Math.floor(ms / 1000);
303
+ if (sec < 60)
304
+ return `${sec}s ago`;
305
+ const min = Math.floor(sec / 60);
306
+ if (min < 60)
307
+ return `${min}m ago`;
308
+ const hr = Math.floor(min / 60);
309
+ if (hr < 24)
310
+ return `${hr}h ago`;
311
+ const days = Math.floor(hr / 24);
312
+ return `${days}d ago`;
313
+ }
314
+ /**
315
+ * Format a regen() composite into the markdown text shown to drones.
316
+ *
317
+ * The playbook is always appended. The token cost is bounded (~500 tokens),
318
+ * but the risk of a drone losing the playbook to /compact or /clear and
319
+ * being left with state but no procedural knowledge is unbounded. Always
320
+ * include — robustness wins.
321
+ */
322
+ /**
323
+ * gh#479 — discoverability tip for intent-based routing (#468). When a
324
+ * cube has no `message_taxonomy` declared, borg_regen + borg_cube append
325
+ * this tip so operators discover how to enable smart routing. Self-
326
+ * removing: returns '' once a taxonomy exists. Copy is UX-locked
327
+ * (design d45098c1) — keep verbatim.
328
+ */
329
+ export function nullTaxonomyTip(messageTaxonomy) {
330
+ const isEmpty = messageTaxonomy == null ||
331
+ (Array.isArray(messageTaxonomy) && messageTaxonomy.length === 0);
332
+ if (!isEmpty)
333
+ return '';
334
+ // copy-param-claim: borg_update-cube.message_taxonomy
335
+ // The tip says "with a taxonomy array" rather than the literal param name;
336
+ // this inline marker pins the real inputSchema param so the #490/#529 guard
337
+ // (client/__tests__/copy-mechanism-guard.test.ts) verifies the tool actually
338
+ // exposes it — the #479 miss class, now caught co-located with the copy.
339
+ return 'Tip: no message taxonomy declared — set one to enable intent-based smart routing (#468). Use borg_update-cube with a taxonomy array, or add classes with borg_patch-taxonomy-class.';
340
+ }
341
+ export function regenWakePathDroneLabel(result, cachedDroneLabel) {
342
+ return result.drone?.label ?? cachedDroneLabel ?? null;
343
+ }
344
+ let boilerplateEmittedThisSession = false;
345
+ let cachedRoleTextHash = null;
346
+ export function __resetRegenSessionState() {
347
+ boilerplateEmittedThisSession = false;
348
+ cachedRoleTextHash = null;
349
+ }
350
+ function safetyDisciplinesForRole(detailedDescription) {
351
+ const text = detailedDescription ?? '';
352
+ const roleScoped = ROLE_SCOPED_SAFETY_DISCIPLINES.filter((discipline) => text.includes(discipline));
353
+ return [...UNIVERSAL_SAFETY_DISCIPLINES, ...roleScoped];
354
+ }
355
+ export function formatRationalePointer(role, section) {
356
+ return `rationale → borg_role-rationale ${JSON.stringify(role)} ${JSON.stringify(section)}`;
357
+ }
358
+ export function parseRationalePointer(stub) {
359
+ const match = stub.match(/borg_role-rationale\s+("(?:(?:\\.)|[^"\\])*")\s+("(?:(?:\\.)|[^"\\])*")/);
360
+ if (!match)
361
+ return null;
362
+ try {
363
+ return { role: JSON.parse(match[1]), section: JSON.parse(match[2]) };
364
+ }
365
+ catch {
366
+ return null;
367
+ }
368
+ }
369
+ /** The full safety-discipline corpus — a `… rationale:` section is NEVER
370
+ * compressed if its body contains ANY of these (⛔ safety-never-compress
371
+ * fail-safe). Over-inclusive on purpose: checks ALL role-scoped disciplines,
372
+ * not just the role's own, so a wrongly-placed LIVE rule can never be stubbed. */
373
+ const ALL_SAFETY_DISCIPLINES = [
374
+ ...UNIVERSAL_SAFETY_DISCIPLINES,
375
+ ...ROLE_SCOPED_SAFETY_DISCIPLINES,
376
+ ];
377
+ /**
378
+ * gh#496-A(b) — compress a role's `detailed_description` for rendering.
379
+ *
380
+ * Splits the role text into sections (via the client port of the worker's
381
+ * `parseRoleSections`, parity-guarded) and replaces each `… rationale:`
382
+ * plain-label section's BODY with a one-line on-demand stub
383
+ * (`formatRationalePointer(role, heading)` verbatim — heading sans colon).
384
+ * Every other section — preamble, operational-rule sections, and ALL woven
385
+ * safety-discipline text — is emitted INLINE, fetch-free. No content is lost:
386
+ * `getRoleRationale(role, heading)` serves the full section on demand, so
387
+ * core-inline + Σ(every stub resolved) reconstructs the stored text.
388
+ *
389
+ * ⛔ SAFETY-NEVER-COMPRESS: a section is stubbed ONLY when (a) its heading,
390
+ * sans-colon/trimmed/lowercased, ends with `rationale`, AND (b) its body
391
+ * contains NONE of `ALL_SAFETY_DISCIPLINES`. Any ambiguity (a safety string
392
+ * present, or simply not a `rationale:` heading) fails safe to INLINE — a
393
+ * wrongly-compressed LIVE rule is the catastrophic mode, so we over-include.
394
+ */
395
+ export function compressRoleText(roleName, detailedDescription) {
396
+ const text = detailedDescription ?? '';
397
+ const sections = parseRoleSections(text);
398
+ return sections
399
+ .map((section) => {
400
+ if (section.kind !== 'label' || section.heading == null)
401
+ return section.body;
402
+ const isRationale = section.heading.trim().toLowerCase().endsWith('rationale');
403
+ if (!isRationale)
404
+ return section.body;
405
+ // ⛔ fail-safe: never stub a section carrying any safety-discipline text.
406
+ if (ALL_SAFETY_DISCIPLINES.some((d) => section.body.includes(d)))
407
+ return section.body;
408
+ // Preserve the heading line verbatim; replace the rationale body with the stub.
409
+ const nlIdx = section.body.indexOf('\n');
410
+ const headingLine = nlIdx === -1 ? section.body + '\n' : section.body.slice(0, nlIdx + 1);
411
+ return headingLine + formatRationalePointer(roleName, section.heading) + '\n';
412
+ })
413
+ .join('');
414
+ }
415
+ export function formatRegenMarkdown(result, opts = {}) {
416
+ const mode = opts.mode ?? 'full';
417
+ const roleOverview = result.roles
418
+ .map((r) => `- **${r.name}**${r.is_default ? ' _(default)_' : ''} — ${r.short_description || '_(no short description)_'}`)
419
+ .join('\n');
420
+ const droneOverview = result.drones
421
+ .map((d) => {
422
+ const role = result.roles.find((r) => r.id === d.role_id);
423
+ const roleLabel = formatRoleAgentLabel(role?.name ?? '?', d.agent_kind);
424
+ return `- **${d.label}** (${roleLabel}) — last seen ${humanAgo(new Date(d.last_seen))}`;
425
+ })
426
+ .join('\n') || '_(no drones connected)_';
427
+ // gh#886: the cube log is NO LONGER inlined as a payload. Render a smart
428
+ // unread-count instruction from the caller's behind_by (worker
429
+ // countUnreadForDrone) — the drone learns how-many + whether-to-fetch
430
+ // without the token cost of the entries. The worker is a single atomic
431
+ // deploy and always sends behind_by, so there is no "old worker" branch;
432
+ // the `behind_by` absent case is one-line null-safety (a brief
433
+ // new-worker-meets-not-yet-updated-client skew) that renders the drain
434
+ // instruction without a number rather than crashing — never inlines a payload.
435
+ const unread = typeof result.behind_by === 'number' ? result.behind_by : null;
436
+ const cubeLogSection = unread === null
437
+ ? 'Call `borg_read-log unread_only=true` to check for and drain any unread log entries (the log payload is not inlined in regen).'
438
+ : unread > 0
439
+ ? `You have **${unread}** unread log ${unread === 1 ? 'entry' : 'entries'}. ` +
440
+ 'Drain them with `borg_read-log unread_only=true` (oldest-unread first; ' +
441
+ 'repeat until `behind_by=0`). The log payload is not inlined here — fetch on demand.'
442
+ : "You're caught up — **0** unread log entries. No need to read the log right now.";
443
+ const isEmptyCube = (unread ?? 0) === 0 && result.drones.length <= 1;
444
+ const gettingStarted = isEmptyCube
445
+ ? [
446
+ '## Getting started',
447
+ '',
448
+ 'Welcome to your first cube. Here\'s how to get going:',
449
+ '',
450
+ '1. Post your first activity: `borg_log message="Starting work on <your task>"`',
451
+ '2. Invite another agent session: open a new terminal and run `borg assimilate --worktree <name>`',
452
+ '3. Check who\'s here: `borg_roster`',
453
+ '',
454
+ '---',
455
+ '',
456
+ ].join('\n')
457
+ : '';
458
+ const taxonomyTip = nullTaxonomyTip(result.cube.message_taxonomy);
459
+ // gh#740: render active ratified decisions concisely (one line each), capped
460
+ // with an elision footer. Section omitted when there are none (or the field
461
+ // is absent on a pre-gh#740 worker — mixed-client safe). Lives in the
462
+ // always-shown band below so it surfaces on LITE wakes (the mid-session
463
+ // restatement moment), not just the session-start full regen (PM F1).
464
+ const RATIFIED_DECISIONS_CAP = 12;
465
+ const activeDecisions = Array.isArray(result.decisions) ? result.decisions : [];
466
+ const decisionsSection = (() => {
467
+ if (activeDecisions.length === 0)
468
+ return '';
469
+ const shown = activeDecisions.slice(0, RATIFIED_DECISIONS_CAP);
470
+ const lines = shown.map((d) => `- **${d.topic}:** ${d.decision}`);
471
+ const remaining = activeDecisions.length - shown.length;
472
+ if (remaining > 0)
473
+ lines.push(`- _+${remaining} more — \`borg_decisions\`_`);
474
+ return ['## Ratified decisions', 'Cite these by topic — do NOT restate a ratified decision from memory.', ...lines].join('\n');
475
+ })();
476
+ const roleTextHash = result.role.detailed_description_hash ?? null;
477
+ // gh#496-A(b): full mode (and the lite emit-role-text branch) render the
478
+ // COMPRESSED-core role text — `… rationale:` sections become on-demand
479
+ // borg_role-rationale stubs; rules + all safety stay inline. The lite
480
+ // hash-gating path (shouldEmitRoleText, over the STORED detailed_description
481
+ // hash) and the lite-omitted safety set below are unchanged.
482
+ const roleText = result.role.detailed_description
483
+ ? compressRoleText(result.role.name, result.role.detailed_description)
484
+ : '_(no detailed description set)_';
485
+ // gh#912-followup: ONE consolidated session-start fetch block (PM
486
+ // campaign-level catch 7a42d0e3) — replaces BOTH #912's standalone
487
+ // borg_playbook pointer AND a separate directive pointer. N competing
488
+ // "required first step" pointers let a weak model satisfy the loudest and
489
+ // skip the rest; a single atomic block = one triage decision, uniform
490
+ // forcing, and gh#512-ready (the role-text fetch appends a bullet here). The
491
+ // directive (opaque ~1-2K) is fetched via the EXISTING borg_cube. SAFETY is
492
+ // NOT deferred — git/wake-path safety lives inline in the role-text safety
493
+ // floor, so deferring the directive defers zero safety.
494
+ const sessionStartBlock = 'Before you post or act, load your full operating context — once per session; static, do NOT re-fetch on every wake:\n' +
495
+ '- `borg_playbook` — your full operating disciplines (verification, four-surface propagation, ack / routing / idle detail).\n' +
496
+ '- `borg_cube` — the cube directive + conventions (log vocabulary, project / git / dispatch conventions).';
497
+ const shouldEmitRoleText = mode === 'full' || roleTextHash == null || roleTextHash !== cachedRoleTextHash;
498
+ const shouldEmitPlaybook = mode === 'full' || !boilerplateEmittedThisSession;
499
+ const lines = [
500
+ gettingStarted + `# Cube: ${result.cube.name} — ${result.drone.label}`,
501
+ '',
502
+ `**Your role:** ${result.role.name}`,
503
+ '',
504
+ ];
505
+ if (mode === 'lite') {
506
+ lines.push("_(lite regen — the role playbook may be omitted when unchanged; your operating context (playbook + cube directive) loads via the Session-start block (borg_playbook + borg_cube). If the playbook is NOT in your current context (e.g. after a context-compaction), call `borg_regen mode=\"full\"` to re-orient.)_", '');
507
+ }
508
+ lines.push(
509
+ // gh#917: full forcing block ONLY on bootstrap/compaction-recovery
510
+ // (mode==='full'); a soft 1-liner on lite wakes. Stops a weak model
511
+ // reflexively re-fetching both chapters every wake — which re-inflates
512
+ // per-wake processing toward the 60s timeout the campaign fights.
513
+ mode === 'full' ? `## Session start — required before acting` : `## Session start`, mode === 'full'
514
+ ? sessionStartBlock
515
+ : 'Operating context (playbook + cube directive) was loaded at session start — re-fetch `borg_playbook` / `borg_cube` ONLY after a context-compaction (a `mode="full"` regen), not on every wake.', '', ...(taxonomyTip ? [taxonomyTip, ''] : []), `## Your role: ${result.role.name}`, shouldEmitRoleText
516
+ ? roleText
517
+ : [
518
+ '_(role playbook unchanged since your last full/lite regen; omitted in lite mode)_',
519
+ '',
520
+ ...safetyDisciplinesForRole(result.role.detailed_description),
521
+ ].join('\n'), '', `## Roles in this cube`, roleOverview, '', `## Connected drones`, droneOverview, '', `## Cube log`, cubeLogSection, ...(decisionsSection ? ['', decisionsSection] : []));
522
+ if (shouldEmitPlaybook) {
523
+ lines.push('', getDronePlaybook());
524
+ boilerplateEmittedThisSession = true;
525
+ }
526
+ if (shouldEmitRoleText && roleTextHash != null) {
527
+ cachedRoleTextHash = roleTextHash;
528
+ }
529
+ return lines.join('\n');
530
+ }
531
+ export function formatLogEntryMarkdown(entry, droneById, roleById) {
532
+ const d = droneById.get(entry.drone_id);
533
+ const r = d ? roleById.get(d.role_id) : null;
534
+ const ts = new Date(entry.created_at).toISOString();
535
+ const entryId = typeof entry.id === 'string' && entry.id.length > 0
536
+ ? ` [entry_id: ${entry.id}]`
537
+ : '';
538
+ // gh#371: the stable short-uuid address token (`id:<8hex>`), distinct from
539
+ // the entry_id bracket above. Address a dispatch to this drone via to:[<id>].
540
+ const addr = typeof entry.drone_id === 'string' && entry.drone_id.length > 0
541
+ ? ` ${formatDroneAddressToken(entry.drone_id)}`
542
+ : '';
543
+ return `**[${ts}]**${entryId}${addr} ${d?.label ?? '?'} (${r?.name ?? '?'}): ${entry.message}`;
544
+ }
545
+ //# sourceMappingURL=regen-format.js.map