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
@@ -0,0 +1,651 @@
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
+
9
+ import {
10
+ ROLE_SCOPED_SAFETY_DISCIPLINES,
11
+ UNIVERSAL_SAFETY_DISCIPLINES,
12
+ } from 'borgmcp-shared/templates';
13
+ import { parseRoleSections } from 'borgmcp-shared/role-section';
14
+ import { formatDroneAddressToken } from 'borgmcp-shared/drone-address';
15
+ import { formatRoleAgentLabel } from './roster-render.js';
16
+ import { shellEscape } from './shell-escape.js';
17
+ import { resolveInboxMonitorPath } from './self-path.js';
18
+
19
+ /**
20
+ * Extract the SessionStart `source` from a Claude Code hook payload (gh#926).
21
+ *
22
+ * SessionStart hooks receive a JSON object on stdin whose `source` field is
23
+ * one of `startup` / `resume` / `clear` / `compact`. The `borg-regen`
24
+ * SessionStart hook uses this to detect a `/clear` re-orientation, which is
25
+ * the FIRST time the hook is the SOLE orientation path (the launch kickoff
26
+ * prompt is gone) AND the moment Claude Code clears the session-scoped
27
+ * `/loop` + `ScheduleWakeup` — so the re-injected orientation must instruct
28
+ * an operational re-arm.
29
+ *
30
+ * Best-effort + total: empty input (manual / TTY run with no stdin),
31
+ * malformed JSON, a missing `source`, or a non-string `source` all return
32
+ * `null` so the caller falls back to the default (full-regen) behavior. A
33
+ * hook bin must never throw on unexpected stdin.
34
+ */
35
+ export function parseHookSource(raw: string): string | null {
36
+ if (!raw || !raw.trim()) return null;
37
+ try {
38
+ const parsed = JSON.parse(raw);
39
+ const source = parsed?.source;
40
+ return typeof source === 'string' ? source : null;
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ /** The agent runtime a session runs under — drives the wake-path branch. */
47
+ export type AgentKind = 'claude' | 'codex' | 'opencode';
48
+
49
+ /**
50
+ * The agent-branched WAKE-PATH ARMING sub-block (gh#929/gh#927) — the single
51
+ * shared "re-establish your wake path" instruction reused by the launch
52
+ * kickoff prompt, the lean SessionStart-hook orientation, and the /clear
53
+ * re-orient. Factored to ONE place so the most load-bearing (and most
54
+ * drift-prone) liveness instruction can never diverge across the three
55
+ * surfaces.
56
+ *
57
+ * Agent-branched on the existing env-agnostic signal (BORG_SESSION-style
58
+ * `isCodexRemoteWakeEnabled`), NOT on a mutable server-recorded field:
59
+ * - claude: arm the inbox-file tail Monitor + engage `/loop` + maintain one
60
+ * adaptive `ScheduleWakeup` recovery deadline (long while the Monitor is
61
+ * healthy or indeterminate; short only while explicitly broken).
62
+ * - codex: Borg's activity stream reaches the app-server remote-control inbox
63
+ * channel; each wake is followed by an unread-log drain. Manual full regen
64
+ * + drain is a degraded fallback when remote control is unavailable.
65
+ *
66
+ * `inboxPath` is the deterministic client-generated UUID path
67
+ * (`~/.config/borgmcp/inboxes/<cubeId>/<droneId>.log`), while the optional
68
+ * explicit state root is derived from the saved worktree path. Both are
69
+ * shell-escaped before rendering the launch/orientation command.
70
+ */
71
+ export function wakePathArming(
72
+ agentKind: AgentKind,
73
+ inboxPath: string,
74
+ monitorStateRoot?: string | null
75
+ ): string {
76
+ if (agentKind === 'codex') {
77
+ return [
78
+ 'Required Codex wake path: Borg activity stream → inbox wake channel via app-server remote control.',
79
+ 'On every wake, run `borg_read-log unread_only=true` and drain until caught up.',
80
+ 'No additional scheduler setup is required.',
81
+ 'Degraded fallback (only if remote control is unavailable): on return, call `borg_regen mode="full"` and drain unread log.',
82
+ ].join(' ');
83
+ }
84
+ if (agentKind === 'opencode') {
85
+ return (
86
+ 'Wake path: OpenCode wakes via SDK-driven entry injection into the TUI session — ' +
87
+ 'there is no tail-Monitor or `/loop` heartbeat to arm. Cube posts are delivered ' +
88
+ 'directly into your active session. Use \`borg_regen\` at any time for fresh context.'
89
+ );
90
+ }
91
+ // gh#client#18: use absolute path to THIS installation's borg-inbox-monitor
92
+ // so the orientation command always resolves to the same version as the
93
+ // running client — never a different one via PATH.
94
+ const monitorBin = shellEscape(resolveInboxMonitorPath());
95
+ const monitorCommand = monitorStateRoot
96
+ ? `${monitorBin} --state-root ${shellEscape(monitorStateRoot)} ${shellEscape(inboxPath)}`
97
+ : `${monitorBin} ${shellEscape(inboxPath)}`;
98
+ return [
99
+ 'Arm your wake path before working:',
100
+ `1. **Inbox Monitor** (wake path) — run a persistent Monitor on \`${monitorCommand}\` so cube posts wake you in real time.`,
101
+ '2. **Engage `/loop`** (self-paced) so you keep waking to triage the cube.',
102
+ '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.',
103
+ '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.',
104
+ ].join('\n');
105
+ }
106
+
107
+ /**
108
+ * Resolve the lean-orientation identity (gh#927), preferring the fresh
109
+ * network `regen()` result and falling back per-field to the local
110
+ * `getActiveCube` state. When `result` is null — the net-free fallback path
111
+ * taken on a `regen()` network failure — identity comes entirely from local
112
+ * state, so a weak drone that hits a SessionStart network blip still gets
113
+ * oriented (with its wake-path arming) instead of left dormant.
114
+ */
115
+ export function resolveLeanIdentity(
116
+ active: { name: string; droneLabel: string; roleName?: string | null },
117
+ result: { cube?: any; drone?: any; role?: any } | null
118
+ ): { cubeName: string; droneLabel: string; roleName: string | null } {
119
+ return {
120
+ cubeName: result?.cube?.name ?? active.name,
121
+ droneLabel: result?.drone?.label ?? active.droneLabel,
122
+ roleName: result?.role?.name ?? active.roleName ?? null,
123
+ };
124
+ }
125
+
126
+ /**
127
+ * The canonical LEAN orientation core (gh#929/gh#927) — the single shared
128
+ * "minimal operational orientation" rendered for a drone at launch, on every
129
+ * SessionStart source (startup/resume/clear/compact), and on /clear. It
130
+ * SUPERSEDES the per-surface variants: the SessionStart hook renders this
131
+ * instead of the full ~20.7KB `formatRegenMarkdown` (which the harness
132
+ * truncates to a ~2KB preview, leaving weak models partially oriented), and
133
+ * the /clear re-orient is just this with `source: 'clear'`.
134
+ *
135
+ * Three load-bearing parts, all kept (per the SEC/PM/CR rails):
136
+ * - IDENTITY: cube + drone label + role, so a weak model knows who it is.
137
+ * - WAKE-PATH ARMING: the shared `wakePathArming` block (liveness — correct
138
+ * to carry pre-`borg_regen`).
139
+ * - `borg_regen` POINTER: the path to the full operating context and safety
140
+ * floor. Role-specific lifecycle guidance remains reachable through the
141
+ * role-text pointer. Kept in EVERY render.
142
+ *
143
+ * Template-agnostic (#921): the escalation target is "your cube's coordinating
144
+ * role" — NEVER a hardcoded `coordinator` / `drone-1` (this is the
145
+ * single most-rendered instruction surface in the product, and the first
146
+ * thing a weak model on a NON-sw-dev template reads). `roleName` is optional
147
+ * so the net-free fallback can render from local `getActiveCube` state when a
148
+ * `regen()` network call is unavailable.
149
+ */
150
+ export function formatLeanOrientation(args: {
151
+ cubeName: string;
152
+ droneLabel: string;
153
+ roleName?: string | null;
154
+ inboxPath: string;
155
+ /** Explicit worktree-local root for Claude monitor PID/heartbeat state. */
156
+ monitorStateRoot?: string | null;
157
+ agentKind: AgentKind;
158
+ source?: string | null;
159
+ }): string {
160
+ const { cubeName, droneLabel, roleName, inboxPath, monitorStateRoot, agentKind, source } = args;
161
+ const clearNote =
162
+ source === 'clear'
163
+ ? agentKind === 'codex'
164
+ ? '\n_(`/clear` cleared your conversation; Codex remote-control wake remains active. Follow the required Codex wake path below.)_\n'
165
+ : agentKind === 'claude'
166
+ ? [
167
+ '\n_(`/clear` cleared Claude\'s `/loop` and recovery deadline — re-arm them now.)_',
168
+ '_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',
169
+ ].join('\n')
170
+ : ''
171
+ : '';
172
+ return [
173
+ `# Cube: ${cubeName} — ${droneLabel}`,
174
+ '',
175
+ `**Your role:** ${roleName || '_(call `borg_regen` to load)_'}`,
176
+ clearNote,
177
+ 'You are a Borg drone — coordinate through the cube log, and never pause for the user. Blocked → escalate to your cube\'s coordinating role.',
178
+ '',
179
+ wakePathArming(agentKind, inboxPath, monitorStateRoot),
180
+ '',
181
+ '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.',
182
+ '',
183
+ ].join('\n');
184
+ }
185
+
186
+ // gh#927 S3: formatClearReorientation (gh#926) is SUPERSEDED by the shared
187
+ // formatLeanOrientation core — the /clear case is now just
188
+ // formatLeanOrientation({ ..., source: 'clear' }), so the per-surface
189
+ // /clear block is gone (one place, not three).
190
+
191
+ /**
192
+ * Build the universal drone playbook.
193
+ *
194
+ * The playbook is appended to every regen / cube / assimilate response.
195
+ * Including it on every refresh is intentional: it protects against
196
+ * /compact and /clear losing the procedural knowledge while state still
197
+ * flows through.
198
+ *
199
+ * The playbook describes the autonomous-default behavior shared by every
200
+ * role. Role-specific overrides (e.g., "consult the human Queen" for the
201
+ * Coordinator role; "ship on consensus" for the Queen role when the seat
202
+ * is delegated to a drone) live in each role's detailed_description, not
203
+ * here.
204
+ */
205
+ // copy-param-claim: borg_ack.kind
206
+ // The drone playbook below recommends `borg_ack ... kind=claim` to claim a
207
+ // review gate; this inline marker pins the param so the #490/#529 copy↔
208
+ // mechanism guard verifies borg_ack actually exposes `kind`
209
+ // (client/__tests__/copy-mechanism-guard.test.ts).
210
+ // copy-param-claim: borg_docs.topic
211
+ // The playbook below points drones to `borg_docs {topic}` for user questions
212
+ // about how Borg MCP works; this marker pins the param so the #490/#529 guard
213
+ // verifies borg_docs exposes `topic`.
214
+ export function getDronePlaybook(): string {
215
+ return `## How to operate as a Drone
216
+
217
+ You're a Drone in a Cube. Coordinate with other drones through the activity log.
218
+
219
+ **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.
220
+
221
+ **Tools:**
222
+ - \`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
223
+ - \`borg_cube\` — re-read the cube directive and the role overview
224
+ - \`borg_role\` — re-read your role's detailed playbook
225
+ - \`borg_roster\` — see who else is connected
226
+ - \`borg_read-log unread_only=true [limit]\` — drain unread log entries from your server-side cursor
227
+ - \`borg_log <message>\` — append to the log
228
+ - \`borg_assimilate <cube>\` — switch to a different cube
229
+
230
+ **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.
231
+
232
+ **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.
233
+
234
+ **Operating loop — each wake, in order:**
235
+ 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.
236
+ 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.
237
+ 3. Actionable signal → act + post the convention. Don't wait to be asked.
238
+ 4. User prompt waiting → respond, informed by cube context; log substantive units (shipped changes, blockers, findings) regardless of who initiated.
239
+ 5. Nothing actionable + no prompt → done; wait for next wake.
240
+
241
+ **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).
242
+
243
+ **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).
244
+
245
+ **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.
246
+
247
+ **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.
248
+
249
+ **When stuck:** post your blocker per your role's conventions, continue other work. Escalation is per your role detail, not by stalling.
250
+
251
+ **Anti-passive (lane idle = no work routed to you, no actionable signal in the log):**
252
+ - 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?).
253
+ - 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).
254
+ - Route work-asks through your cube's coordinating role, never directly to the human Queen.
255
+
256
+ **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).
257
+
258
+ **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.
259
+
260
+ **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:
261
+ - 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]\`.
262
+ - 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.
263
+
264
+ **Pre-commit git hygiene (universal):**
265
+
266
+ 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).`;
267
+ }
268
+
269
+ /**
270
+ * Eager export of the playbook text. Cheap to compute (string concat);
271
+ * exporting as a constant lets callers splice it directly without a
272
+ * function call site.
273
+ */
274
+ export const DRONE_PLAYBOOK = getDronePlaybook();
275
+
276
+ /**
277
+ * gh#912: the verbose operating-discipline DETAIL externalized out of the
278
+ * bootstrap regen into an on-demand chapter (fetched via the borg_playbook
279
+ * tool). The inline core (getDronePlaybook) keeps the rule-spine + triggers +
280
+ * forcing-functions + safety; this chapter carries the WHY, the per-level
281
+ * sharpening, the concrete surfaces, and the four-surface propagation that a
282
+ * drone only needs when doing review/verify-class work. Static text.
283
+ */
284
+ // copy-param-claim: borg_decisions.topic
285
+ // The chapter below tells drones to cite a ratified decision via
286
+ // `borg_decisions {topic}`; this marker pins the param so the #490/#529
287
+ // copy-guard verifies borg_decisions exposes `topic`
288
+ // (client/__tests__/copy-mechanism-guard.test.ts).
289
+ export function getDronePlaybookChapter(): string {
290
+ return `## Operating playbook — full disciplines (borg_playbook chapter)
291
+
292
+ 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.
293
+
294
+ **Verifying factual claims:**
295
+
296
+ 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:
297
+
298
+ - **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.
299
+ - **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.
300
+ - **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.
301
+
302
+ **Concrete verification surfaces by claim type:**
303
+ - Version attribution → \`git tag --contains <sha>\` or \`git log --oneline <tag>\`
304
+ - Code state → match the grep surface to the claim surface:
305
+ - Local uncommitted claim → \`grep -n "<symbol>" <file>\` or direct file read in the working tree
306
+ - \`origin/main\`, PR head, branch, merge-SHA, or tag claim → \`git show <ref>:<path>\` followed by a symbol search in the returned source
307
+ - Prod state → \`curl https://<endpoint>\` or \`wrangler tail --env production\`
308
+ - npm registry state → \`npm view <package>@<version>\` or \`npm view <package>@latest\`
309
+ - DB state → query through the existing \`db\` interface; never trust a doc claim about row counts / column values
310
+ - 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
311
+ - 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.
312
+
313
+ **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.
314
+
315
+ **Four-surface propagation:**
316
+
317
+ 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:
318
+
319
+ - **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.
320
+ - **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.
321
+ - **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.
322
+ - **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.
323
+
324
+ **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.**`;
325
+ }
326
+
327
+ /**
328
+ * Format an absolute timestamp as a coarse "Xs/Xm/Xh ago" string.
329
+ */
330
+ export function humanAgo(date: Date | string): string {
331
+ const then = typeof date === 'string' ? new Date(date) : date;
332
+ const ms = Date.now() - then.getTime();
333
+ if (!Number.isFinite(ms) || ms < 0) return 'just now';
334
+ const sec = Math.floor(ms / 1000);
335
+ if (sec < 60) return `${sec}s ago`;
336
+ const min = Math.floor(sec / 60);
337
+ if (min < 60) return `${min}m ago`;
338
+ const hr = Math.floor(min / 60);
339
+ if (hr < 24) return `${hr}h ago`;
340
+ const days = Math.floor(hr / 24);
341
+ return `${days}d ago`;
342
+ }
343
+
344
+ /**
345
+ * Format a regen() composite into the markdown text shown to drones.
346
+ *
347
+ * The playbook is always appended. The token cost is bounded (~500 tokens),
348
+ * but the risk of a drone losing the playbook to /compact or /clear and
349
+ * being left with state but no procedural knowledge is unbounded. Always
350
+ * include — robustness wins.
351
+ */
352
+ /**
353
+ * gh#479 — discoverability tip for intent-based routing (#468). When a
354
+ * cube has no `message_taxonomy` declared, borg_regen + borg_cube append
355
+ * this tip so operators discover how to enable smart routing. Self-
356
+ * removing: returns '' once a taxonomy exists. Copy is UX-locked
357
+ * (design d45098c1) — keep verbatim.
358
+ */
359
+ export function nullTaxonomyTip(messageTaxonomy: unknown): string {
360
+ const isEmpty =
361
+ messageTaxonomy == null ||
362
+ (Array.isArray(messageTaxonomy) && messageTaxonomy.length === 0);
363
+ if (!isEmpty) return '';
364
+ // copy-param-claim: borg_update-cube.message_taxonomy
365
+ // The tip says "with a taxonomy array" rather than the literal param name;
366
+ // this inline marker pins the real inputSchema param so the #490/#529 guard
367
+ // (client/__tests__/copy-mechanism-guard.test.ts) verifies the tool actually
368
+ // exposes it — the #479 miss class, now caught co-located with the copy.
369
+ 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.';
370
+ }
371
+
372
+ export function regenWakePathDroneLabel(
373
+ result: { drone?: { label?: string | null } },
374
+ cachedDroneLabel: string | null | undefined
375
+ ): string | null {
376
+ return result.drone?.label ?? cachedDroneLabel ?? null;
377
+ }
378
+
379
+ export type RegenMode = 'full' | 'lite';
380
+
381
+ let boilerplateEmittedThisSession = false;
382
+ let cachedRoleTextHash: string | null = null;
383
+
384
+ export function __resetRegenSessionState(): void {
385
+ boilerplateEmittedThisSession = false;
386
+ cachedRoleTextHash = null;
387
+ }
388
+
389
+ function safetyDisciplinesForRole(detailedDescription: string | null | undefined): string[] {
390
+ const text = detailedDescription ?? '';
391
+ const roleScoped = ROLE_SCOPED_SAFETY_DISCIPLINES.filter((discipline) =>
392
+ text.includes(discipline)
393
+ );
394
+ return [...UNIVERSAL_SAFETY_DISCIPLINES, ...roleScoped];
395
+ }
396
+
397
+ export function formatRationalePointer(role: string, section: string): string {
398
+ return `rationale → borg_role-rationale ${JSON.stringify(role)} ${JSON.stringify(section)}`;
399
+ }
400
+
401
+ export function parseRationalePointer(stub: string): { role: string; section: string } | null {
402
+ const match = stub.match(/borg_role-rationale\s+("(?:(?:\\.)|[^"\\])*")\s+("(?:(?:\\.)|[^"\\])*")/);
403
+ if (!match) return null;
404
+ try {
405
+ return { role: JSON.parse(match[1]), section: JSON.parse(match[2]) };
406
+ } catch {
407
+ return null;
408
+ }
409
+ }
410
+
411
+ /** The full safety-discipline corpus — a `… rationale:` section is NEVER
412
+ * compressed if its body contains ANY of these (⛔ safety-never-compress
413
+ * fail-safe). Over-inclusive on purpose: checks ALL role-scoped disciplines,
414
+ * not just the role's own, so a wrongly-placed LIVE rule can never be stubbed. */
415
+ const ALL_SAFETY_DISCIPLINES: readonly string[] = [
416
+ ...UNIVERSAL_SAFETY_DISCIPLINES,
417
+ ...ROLE_SCOPED_SAFETY_DISCIPLINES,
418
+ ];
419
+
420
+ /**
421
+ * gh#496-A(b) — compress a role's `detailed_description` for rendering.
422
+ *
423
+ * Splits the role text into sections (via the client port of the worker's
424
+ * `parseRoleSections`, parity-guarded) and replaces each `… rationale:`
425
+ * plain-label section's BODY with a one-line on-demand stub
426
+ * (`formatRationalePointer(role, heading)` verbatim — heading sans colon).
427
+ * Every other section — preamble, operational-rule sections, and ALL woven
428
+ * safety-discipline text — is emitted INLINE, fetch-free. No content is lost:
429
+ * `getRoleRationale(role, heading)` serves the full section on demand, so
430
+ * core-inline + Σ(every stub resolved) reconstructs the stored text.
431
+ *
432
+ * ⛔ SAFETY-NEVER-COMPRESS: a section is stubbed ONLY when (a) its heading,
433
+ * sans-colon/trimmed/lowercased, ends with `rationale`, AND (b) its body
434
+ * contains NONE of `ALL_SAFETY_DISCIPLINES`. Any ambiguity (a safety string
435
+ * present, or simply not a `rationale:` heading) fails safe to INLINE — a
436
+ * wrongly-compressed LIVE rule is the catastrophic mode, so we over-include.
437
+ */
438
+ export function compressRoleText(
439
+ roleName: string,
440
+ detailedDescription: string | null | undefined
441
+ ): string {
442
+ const text = detailedDescription ?? '';
443
+ const sections = parseRoleSections(text);
444
+ return sections
445
+ .map((section) => {
446
+ if (section.kind !== 'label' || section.heading == null) return section.body;
447
+ const isRationale = section.heading.trim().toLowerCase().endsWith('rationale');
448
+ if (!isRationale) return section.body;
449
+ // ⛔ fail-safe: never stub a section carrying any safety-discipline text.
450
+ if (ALL_SAFETY_DISCIPLINES.some((d) => section.body.includes(d))) return section.body;
451
+ // Preserve the heading line verbatim; replace the rationale body with the stub.
452
+ const nlIdx = section.body.indexOf('\n');
453
+ const headingLine = nlIdx === -1 ? section.body + '\n' : section.body.slice(0, nlIdx + 1);
454
+ return headingLine + formatRationalePointer(roleName, section.heading) + '\n';
455
+ })
456
+ .join('');
457
+ }
458
+
459
+ export function formatRegenMarkdown(
460
+ result: {
461
+ cube: any & { directive_hash?: string | null };
462
+ role: any & { detailed_description_hash?: string | null };
463
+ drone: any;
464
+ roles: any[];
465
+ drones: any[];
466
+ // gh#886: recentLog is no longer rendered into the bundle (it ballooned
467
+ // the regen and blocked simpler-model bootstrap). Kept optional for
468
+ // rollout-compat — a pre-gh#886 worker still sends it; we fall back to
469
+ // rendering it only when behind_by is absent.
470
+ recentLog?: any[];
471
+ // gh#886: the caller's unread count (worker countUnreadForDrone). When
472
+ // present, regen renders a smart drain instruction instead of the payload.
473
+ behind_by?: number;
474
+ // gh#740: active ratified decisions for the cube. Rendered in the
475
+ // always-shown band (lite + full) so the source of truth is in context at
476
+ // mid-session restatement moments. Absent on a pre-gh#740 worker → omitted.
477
+ decisions?: any[];
478
+ },
479
+ opts: { mode?: RegenMode } = {}
480
+ ): string {
481
+ const mode = opts.mode ?? 'full';
482
+ const roleOverview = result.roles
483
+ .map(
484
+ (r: any) =>
485
+ `- **${r.name}**${r.is_default ? ' _(default)_' : ''} — ${r.short_description || '_(no short description)_'}`
486
+ )
487
+ .join('\n');
488
+
489
+ const droneOverview =
490
+ result.drones
491
+ .map((d: any) => {
492
+ const role = result.roles.find((r: any) => r.id === d.role_id);
493
+ const roleLabel = formatRoleAgentLabel(role?.name ?? '?', d.agent_kind);
494
+ return `- **${d.label}** (${roleLabel}) — last seen ${humanAgo(new Date(d.last_seen))}`;
495
+ })
496
+ .join('\n') || '_(no drones connected)_';
497
+
498
+ // gh#886: the cube log is NO LONGER inlined as a payload. Render a smart
499
+ // unread-count instruction from the caller's behind_by (worker
500
+ // countUnreadForDrone) — the drone learns how-many + whether-to-fetch
501
+ // without the token cost of the entries. The worker is a single atomic
502
+ // deploy and always sends behind_by, so there is no "old worker" branch;
503
+ // the `behind_by` absent case is one-line null-safety (a brief
504
+ // new-worker-meets-not-yet-updated-client skew) that renders the drain
505
+ // instruction without a number rather than crashing — never inlines a payload.
506
+ const unread = typeof result.behind_by === 'number' ? result.behind_by : null;
507
+ const cubeLogSection =
508
+ unread === null
509
+ ? 'Call `borg_read-log unread_only=true` to check for and drain any unread log entries (the log payload is not inlined in regen).'
510
+ : unread > 0
511
+ ? `You have **${unread}** unread log ${unread === 1 ? 'entry' : 'entries'}. ` +
512
+ 'Drain them with `borg_read-log unread_only=true` (oldest-unread first; ' +
513
+ 'repeat until `behind_by=0`). The log payload is not inlined here — fetch on demand.'
514
+ : "You're caught up — **0** unread log entries. No need to read the log right now.";
515
+
516
+ const isEmptyCube = (unread ?? 0) === 0 && result.drones.length <= 1;
517
+ const gettingStarted = isEmptyCube
518
+ ? [
519
+ '## Getting started',
520
+ '',
521
+ 'Welcome to your first cube. Here\'s how to get going:',
522
+ '',
523
+ '1. Post your first activity: `borg_log message="Starting work on <your task>"`',
524
+ '2. Invite another agent session: open a new terminal and run `borg assimilate --worktree <name>`',
525
+ '3. Check who\'s here: `borg_roster`',
526
+ '',
527
+ '---',
528
+ '',
529
+ ].join('\n')
530
+ : '';
531
+
532
+ const taxonomyTip = nullTaxonomyTip(result.cube.message_taxonomy);
533
+
534
+ // gh#740: render active ratified decisions concisely (one line each), capped
535
+ // with an elision footer. Section omitted when there are none (or the field
536
+ // is absent on a pre-gh#740 worker — mixed-client safe). Lives in the
537
+ // always-shown band below so it surfaces on LITE wakes (the mid-session
538
+ // restatement moment), not just the session-start full regen (PM F1).
539
+ const RATIFIED_DECISIONS_CAP = 12;
540
+ const activeDecisions = Array.isArray(result.decisions) ? result.decisions : [];
541
+ const decisionsSection = (() => {
542
+ if (activeDecisions.length === 0) return '';
543
+ const shown = activeDecisions.slice(0, RATIFIED_DECISIONS_CAP);
544
+ const lines = shown.map((d: any) => `- **${d.topic}:** ${d.decision}`);
545
+ const remaining = activeDecisions.length - shown.length;
546
+ if (remaining > 0) lines.push(`- _+${remaining} more — \`borg_decisions\`_`);
547
+ return ['## Ratified decisions', 'Cite these by topic — do NOT restate a ratified decision from memory.', ...lines].join('\n');
548
+ })();
549
+
550
+ const roleTextHash = result.role.detailed_description_hash ?? null;
551
+ // gh#496-A(b): full mode (and the lite emit-role-text branch) render the
552
+ // COMPRESSED-core role text — `… rationale:` sections become on-demand
553
+ // borg_role-rationale stubs; rules + all safety stay inline. The lite
554
+ // hash-gating path (shouldEmitRoleText, over the STORED detailed_description
555
+ // hash) and the lite-omitted safety set below are unchanged.
556
+ const roleText = result.role.detailed_description
557
+ ? compressRoleText(result.role.name, result.role.detailed_description)
558
+ : '_(no detailed description set)_';
559
+ // gh#912-followup: ONE consolidated session-start fetch block (PM
560
+ // campaign-level catch 7a42d0e3) — replaces BOTH #912's standalone
561
+ // borg_playbook pointer AND a separate directive pointer. N competing
562
+ // "required first step" pointers let a weak model satisfy the loudest and
563
+ // skip the rest; a single atomic block = one triage decision, uniform
564
+ // forcing, and gh#512-ready (the role-text fetch appends a bullet here). The
565
+ // directive (opaque ~1-2K) is fetched via the EXISTING borg_cube. SAFETY is
566
+ // NOT deferred — git/wake-path safety lives inline in the role-text safety
567
+ // floor, so deferring the directive defers zero safety.
568
+ const sessionStartBlock =
569
+ 'Before you post or act, load your full operating context — once per session; static, do NOT re-fetch on every wake:\n' +
570
+ '- `borg_playbook` — your full operating disciplines (verification, four-surface propagation, ack / routing / idle detail).\n' +
571
+ '- `borg_cube` — the cube directive + conventions (log vocabulary, project / git / dispatch conventions).';
572
+ const shouldEmitRoleText =
573
+ mode === 'full' || roleTextHash == null || roleTextHash !== cachedRoleTextHash;
574
+ const shouldEmitPlaybook = mode === 'full' || !boilerplateEmittedThisSession;
575
+
576
+ const lines = [
577
+ gettingStarted + `# Cube: ${result.cube.name} — ${result.drone.label}`,
578
+ '',
579
+ `**Your role:** ${result.role.name}`,
580
+ '',
581
+ ];
582
+
583
+ if (mode === 'lite') {
584
+ lines.push(
585
+ "_(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.)_",
586
+ ''
587
+ );
588
+ }
589
+
590
+ lines.push(
591
+ // gh#917: full forcing block ONLY on bootstrap/compaction-recovery
592
+ // (mode==='full'); a soft 1-liner on lite wakes. Stops a weak model
593
+ // reflexively re-fetching both chapters every wake — which re-inflates
594
+ // per-wake processing toward the 60s timeout the campaign fights.
595
+ mode === 'full' ? `## Session start — required before acting` : `## Session start`,
596
+ mode === 'full'
597
+ ? sessionStartBlock
598
+ : '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.',
599
+ '',
600
+ ...(taxonomyTip ? [taxonomyTip, ''] : []),
601
+ `## Your role: ${result.role.name}`,
602
+ shouldEmitRoleText
603
+ ? roleText
604
+ : [
605
+ '_(role playbook unchanged since your last full/lite regen; omitted in lite mode)_',
606
+ '',
607
+ ...safetyDisciplinesForRole(result.role.detailed_description),
608
+ ].join('\n'),
609
+ '',
610
+ `## Roles in this cube`,
611
+ roleOverview,
612
+ '',
613
+ `## Connected drones`,
614
+ droneOverview,
615
+ '',
616
+ `## Cube log`,
617
+ cubeLogSection,
618
+ ...(decisionsSection ? ['', decisionsSection] : []),
619
+ );
620
+
621
+ if (shouldEmitPlaybook) {
622
+ lines.push('', getDronePlaybook());
623
+ boilerplateEmittedThisSession = true;
624
+ }
625
+ if (shouldEmitRoleText && roleTextHash != null) {
626
+ cachedRoleTextHash = roleTextHash;
627
+ }
628
+
629
+ return lines.join('\n');
630
+ }
631
+
632
+ export function formatLogEntryMarkdown(
633
+ entry: any,
634
+ droneById: Map<string, any>,
635
+ roleById: Map<string, any>
636
+ ): string {
637
+ const d = droneById.get(entry.drone_id) as any;
638
+ const r = d ? (roleById.get(d.role_id) as any) : null;
639
+ const ts = new Date(entry.created_at).toISOString();
640
+ const entryId =
641
+ typeof entry.id === 'string' && entry.id.length > 0
642
+ ? ` [entry_id: ${entry.id}]`
643
+ : '';
644
+ // gh#371: the stable short-uuid address token (`id:<8hex>`), distinct from
645
+ // the entry_id bracket above. Address a dispatch to this drone via to:[<id>].
646
+ const addr =
647
+ typeof entry.drone_id === 'string' && entry.drone_id.length > 0
648
+ ? ` ${formatDroneAddressToken(entry.drone_id)}`
649
+ : '';
650
+ return `**[${ts}]**${entryId}${addr} ${d?.label ?? '?'} (${r?.name ?? '?'}): ${entry.message}`;
651
+ }