@zerwiz/ymir 0.1.4 → 0.1.5

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 (850) hide show
  1. package/.agents/README.md +291 -0
  2. package/.agents/agents/bragi-marketer.md +111 -0
  3. package/.agents/agents/brokk.md +352 -0
  4. package/.agents/agents/forseti-reviewer.md +93 -0
  5. package/.agents/agents/frigg-consent.md +77 -0
  6. package/.agents/agents/groa-update.md +77 -0
  7. package/.agents/agents/hnoss-designer.md +69 -0
  8. package/.agents/agents/huginn-researcher.md +99 -0
  9. package/.agents/agents/jord-projects.md +78 -0
  10. package/.agents/agents/kvasir-scout.md +91 -0
  11. package/.agents/agents/mimir-planner.md +92 -0
  12. package/.agents/agents/muninn-stow.md +77 -0
  13. package/.agents/agents/saga-bearings.md +78 -0
  14. package/.agents/agents/sindri-developer.md +114 -0
  15. package/.agents/agents/snotra-documenter.md +105 -0
  16. package/.agents/agents/syn-recovery.md +78 -0
  17. package/.agents/agents/tyr-check.md +78 -0
  18. package/.agents/agents/urdh-hold.md +77 -0
  19. package/.agents/agents/volundr-smith.md +80 -0
  20. package/.agents/agents/vor-diagnostics.md +77 -0
  21. package/.agents/assets/agents/naming.md +50 -0
  22. package/.agents/assets/agents/registry.md +93 -0
  23. package/.agents/assets/agents/runtime.md +36 -0
  24. package/.agents/assets/agents/toon-tasks-cli.md +42 -0
  25. package/.agents/assets/templates/company_entity.template.md +37 -0
  26. package/.agents/backend/backends/cmux.sh +649 -0
  27. package/.agents/backend/backends/herdr-eventwait.py +157 -0
  28. package/.agents/backend/backends/herdr-workspace-move.py +114 -0
  29. package/.agents/backend/backends/herdr.sh +3386 -0
  30. package/.agents/backend/backends/orca.sh +290 -0
  31. package/.agents/backend/backends/tmux.sh +357 -0
  32. package/.agents/backend/backends/zellij.sh +700 -0
  33. package/.agents/backend/fm-afk-launch.sh +654 -0
  34. package/.agents/backend/fm-afk-return.sh +241 -0
  35. package/.agents/backend/fm-afk-start.sh +172 -0
  36. package/.agents/backend/fm-arm-command-policy.mjs +960 -0
  37. package/.agents/backend/fm-arm-pretool-check.sh +200 -0
  38. package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
  39. package/.agents/backend/fm-backend.sh +981 -0
  40. package/.agents/backend/fm-backlog-handoff.sh +909 -0
  41. package/.agents/backend/fm-backlog-receive.sh +187 -0
  42. package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
  43. package/.agents/backend/fm-bearings-board.sh +204 -0
  44. package/.agents/backend/fm-bearings-snapshot.sh +546 -0
  45. package/.agents/backend/fm-bootstrap.sh +1568 -0
  46. package/.agents/backend/fm-branch-outcome.sh +203 -0
  47. package/.agents/backend/fm-branch-prompt.sh +101 -0
  48. package/.agents/backend/fm-brief.sh +452 -0
  49. package/.agents/backend/fm-busy-event.sh +234 -0
  50. package/.agents/backend/fm-busy-lib.sh +986 -0
  51. package/.agents/backend/fm-captain-hold.sh +1001 -0
  52. package/.agents/backend/fm-cd-command-policy.mjs +153 -0
  53. package/.agents/backend/fm-cd-pretool-check.sh +190 -0
  54. package/.agents/backend/fm-check-lib.sh +74 -0
  55. package/.agents/backend/fm-check-register.sh +42 -0
  56. package/.agents/backend/fm-check-unregister.sh +52 -0
  57. package/.agents/backend/fm-classify-lib.sh +1770 -0
  58. package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
  59. package/.agents/backend/fm-composer-lib.sh +1416 -0
  60. package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
  61. package/.agents/backend/fm-config-push.sh +254 -0
  62. package/.agents/backend/fm-control-lib.sh +251 -0
  63. package/.agents/backend/fm-control.sh +878 -0
  64. package/.agents/backend/fm-crew-state.sh +625 -0
  65. package/.agents/backend/fm-cursor-lib.sh +243 -0
  66. package/.agents/backend/fm-decision-hold.sh +232 -0
  67. package/.agents/backend/fm-doc-audience-check.sh +269 -0
  68. package/.agents/backend/fm-dod-lib.sh +67 -0
  69. package/.agents/backend/fm-ensure-agents-md.sh +253 -0
  70. package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
  71. package/.agents/backend/fm-extension.mjs +2577 -0
  72. package/.agents/backend/fm-extension.sh +16 -0
  73. package/.agents/backend/fm-ff-lib.sh +420 -0
  74. package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
  75. package/.agents/backend/fm-fleet-sync.sh +458 -0
  76. package/.agents/backend/fm-fleet-view.sh +96 -0
  77. package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
  78. package/.agents/backend/fm-guard.sh +242 -0
  79. package/.agents/backend/fm-harness.sh +195 -0
  80. package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
  81. package/.agents/backend/fm-herdr-lab.sh +346 -0
  82. package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
  83. package/.agents/backend/fm-home-seed.sh +984 -0
  84. package/.agents/backend/fm-home-summary-refresh.sh +255 -0
  85. package/.agents/backend/fm-hook-host-lib.sh +36 -0
  86. package/.agents/backend/fm-inactive-reconcile.sh +531 -0
  87. package/.agents/backend/fm-inbox.sh +399 -0
  88. package/.agents/backend/fm-install-actionlint.sh +84 -0
  89. package/.agents/backend/fm-install-herdr.sh +96 -0
  90. package/.agents/backend/fm-install-shellcheck.sh +84 -0
  91. package/.agents/backend/fm-install-treehouse.sh +95 -0
  92. package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
  93. package/.agents/backend/fm-lease-lib.sh +218 -0
  94. package/.agents/backend/fm-lease.sh +189 -0
  95. package/.agents/backend/fm-line-cap-lib.sh +51 -0
  96. package/.agents/backend/fm-lint-workflows.sh +137 -0
  97. package/.agents/backend/fm-lint.sh +591 -0
  98. package/.agents/backend/fm-lock-lib.sh +104 -0
  99. package/.agents/backend/fm-lock.sh +107 -0
  100. package/.agents/backend/fm-marker-lib.sh +12 -0
  101. package/.agents/backend/fm-merge-local.sh +74 -0
  102. package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
  103. package/.agents/backend/fm-nm-run-lib.sh +124 -0
  104. package/.agents/backend/fm-on.sh +125 -0
  105. package/.agents/backend/fm-operational-input.sh +253 -0
  106. package/.agents/backend/fm-peek.sh +46 -0
  107. package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
  108. package/.agents/backend/fm-pr-check.sh +135 -0
  109. package/.agents/backend/fm-pr-lib.sh +1016 -0
  110. package/.agents/backend/fm-pr-merge.sh +707 -0
  111. package/.agents/backend/fm-pr-poll.sh +110 -0
  112. package/.agents/backend/fm-primary-scope-lib.sh +33 -0
  113. package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
  114. package/.agents/backend/fm-procevent-lavish.sh +628 -0
  115. package/.agents/backend/fm-procevent-lib.sh +892 -0
  116. package/.agents/backend/fm-procevent-quota.sh +290 -0
  117. package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
  118. package/.agents/backend/fm-procevent-when.sh +504 -0
  119. package/.agents/backend/fm-procevent.sh +1466 -0
  120. package/.agents/backend/fm-project-mode.sh +92 -0
  121. package/.agents/backend/fm-project-origin-lib.sh +180 -0
  122. package/.agents/backend/fm-promote.sh +282 -0
  123. package/.agents/backend/fm-public-followup-emit.sh +260 -0
  124. package/.agents/backend/fm-public-followup-lib.sh +398 -0
  125. package/.agents/backend/fm-public-followup.sh +1330 -0
  126. package/.agents/backend/fm-push-transition-lib.sh +168 -0
  127. package/.agents/backend/fm-quota-axi-lib.sh +93 -0
  128. package/.agents/backend/fm-quota-choose.sh +384 -0
  129. package/.agents/backend/fm-remote-delta-read.sh +189 -0
  130. package/.agents/backend/fm-remote-doctor.sh +800 -0
  131. package/.agents/backend/fm-remote-entrypoint.sh +193 -0
  132. package/.agents/backend/fm-remote-file.sh +234 -0
  133. package/.agents/backend/fm-remote-home-provision.sh +259 -0
  134. package/.agents/backend/fm-remote-home-seed.sh +259 -0
  135. package/.agents/backend/fm-remote-inherit-push.sh +90 -0
  136. package/.agents/backend/fm-remote-inherit.sh +179 -0
  137. package/.agents/backend/fm-remote-job-lib.sh +1241 -0
  138. package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
  139. package/.agents/backend/fm-remote-job-worker.sh +1124 -0
  140. package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
  141. package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
  142. package/.agents/backend/fm-review-diff.sh +158 -0
  143. package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
  144. package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
  145. package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
  146. package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
  147. package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
  148. package/.agents/backend/fm-secondmate-report.sh +88 -0
  149. package/.agents/backend/fm-send.sh +1059 -0
  150. package/.agents/backend/fm-session-lock-lib.sh +176 -0
  151. package/.agents/backend/fm-session-start.sh +970 -0
  152. package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
  153. package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
  154. package/.agents/backend/fm-sessionstart-run.sh +145 -0
  155. package/.agents/backend/fm-spawn.sh +3153 -0
  156. package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
  157. package/.agents/backend/fm-startup-memory-budget.sh +94 -0
  158. package/.agents/backend/fm-startup-network.sh +642 -0
  159. package/.agents/backend/fm-stow-cascade.sh +251 -0
  160. package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
  161. package/.agents/backend/fm-supervise-daemon.sh +1732 -0
  162. package/.agents/backend/fm-supervision-instructions.sh +215 -0
  163. package/.agents/backend/fm-supervision-lib.sh +90 -0
  164. package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
  165. package/.agents/backend/fm-tangle-lib.sh +53 -0
  166. package/.agents/backend/fm-task-inbox-lib.sh +402 -0
  167. package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
  168. package/.agents/backend/fm-teardown.sh +2922 -0
  169. package/.agents/backend/fm-test-isolation-proof.sh +588 -0
  170. package/.agents/backend/fm-test-run.sh +2191 -0
  171. package/.agents/backend/fm-timeout-lib.sh +141 -0
  172. package/.agents/backend/fm-timing-lib.sh +179 -0
  173. package/.agents/backend/fm-tmux-lib.sh +291 -0
  174. package/.agents/backend/fm-tool-update-check.sh +898 -0
  175. package/.agents/backend/fm-trace-context-lib.sh +227 -0
  176. package/.agents/backend/fm-transition-lib.sh +103 -0
  177. package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
  178. package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
  179. package/.agents/backend/fm-turnend-guard.sh +432 -0
  180. package/.agents/backend/fm-update.sh +110 -0
  181. package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
  182. package/.agents/backend/fm-voice-client.py +1373 -0
  183. package/.agents/backend/fm-voice-relay.py +1256 -0
  184. package/.agents/backend/fm-wake-drain.sh +592 -0
  185. package/.agents/backend/fm-wake-grant.sh +127 -0
  186. package/.agents/backend/fm-wake-lib.sh +1849 -0
  187. package/.agents/backend/fm-watch-arm.sh +602 -0
  188. package/.agents/backend/fm-watch-checkpoint.sh +109 -0
  189. package/.agents/backend/fm-watch.sh +1962 -0
  190. package/.agents/backend/fm-x-dismiss.sh +116 -0
  191. package/.agents/backend/fm-x-followup.sh +286 -0
  192. package/.agents/backend/fm-x-lib.sh +1000 -0
  193. package/.agents/backend/fm-x-link.sh +234 -0
  194. package/.agents/backend/fm-x-poll.sh +185 -0
  195. package/.agents/backend/fm-x-reply.sh +385 -0
  196. package/.agents/backend/fm_voice_frame.py +166 -0
  197. package/.agents/backend/fm_voice_records.py +574 -0
  198. package/.agents/backend/model-bridge.py +290 -0
  199. package/.agents/backend/opencode-go-bridge.py +30 -0
  200. package/.agents/bus/README.md +12 -0
  201. package/.agents/bus/messages.json +1 -0
  202. package/.agents/config/agents.machine.example.yaml +16 -0
  203. package/.agents/config/agents.yaml +50 -0
  204. package/.agents/config/agents.yaml.example +72 -0
  205. package/.agents/config/crew-dispatch.json +22 -0
  206. package/.agents/config/cron.yaml +9 -0
  207. package/.agents/config/eindri-dispatch.json +67 -0
  208. package/.agents/config/eindri-harness +1 -0
  209. package/.agents/config/startup-memory-budget +1 -0
  210. package/.agents/config/tailscale-sync.yaml +27 -0
  211. package/.agents/filebrowser/.filebrowser.json +6 -0
  212. package/.agents/filebrowser/README.md +11 -0
  213. package/.agents/gateway/README.md +12 -0
  214. package/.agents/github/README.md +15 -0
  215. package/.agents/github/webhooks/.gitkeep +0 -0
  216. package/.agents/github/workflows/.gitkeep +0 -0
  217. package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
  218. package/.agents/harness/opencode/plugins/package.json +4 -0
  219. package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
  220. package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
  221. package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
  222. package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
  223. package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
  224. package/.agents/memory/README.md +46 -0
  225. package/.agents/memory/kaia.engram +0 -0
  226. package/.agents/memory/kaia.engram-shm +0 -0
  227. package/.agents/memory/kaia.engram-wal +0 -0
  228. package/.agents/memory/runes_audit.md +5 -0
  229. package/.agents/memory/well/episodes.jsonl +367 -0
  230. package/.agents/memory/well/workspace.jsonl +27 -0
  231. package/.agents/migrations/0001-hodd-layout.sh +38 -0
  232. package/.agents/migrations/0002-a2a-mcp.sh +27 -0
  233. package/.agents/migrations/0003-private-data-separation.sh +111 -0
  234. package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
  235. package/.agents/sandbox/Dockerfile.utgard +19 -0
  236. package/.agents/sandbox/README.md +14 -0
  237. package/.agents/sandbox/utgard.config.json +14 -0
  238. package/.agents/skills/README.md +59 -0
  239. package/.agents/skills/bragi-marketing/README.md +3 -0
  240. package/.agents/skills/bragi-marketing/SKILL.md +82 -0
  241. package/.agents/skills/eindri-homes/README.md +3 -0
  242. package/.agents/skills/eindri-homes/SKILL.md +291 -0
  243. package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
  244. package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
  245. package/.agents/skills/frigg-consent/README.md +3 -0
  246. package/.agents/skills/frigg-consent/SKILL.md +56 -0
  247. package/.agents/skills/galdr-ymirsystem/README.md +3 -0
  248. package/.agents/skills/galdr-ymirsystem/SKILL.md +158 -0
  249. package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
  250. package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
  251. package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
  252. package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
  253. package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
  254. package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
  255. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +673 -0
  256. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
  257. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
  258. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
  259. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
  260. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
  261. package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +496 -0
  262. package/.agents/skills/galdr-ymirsystem/assets/installation.md +571 -0
  263. package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
  264. package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
  265. package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +161 -0
  266. package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +377 -0
  267. package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
  268. package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +129 -0
  269. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
  270. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
  271. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +96 -0
  272. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
  273. package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
  274. package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
  275. package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
  276. package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
  277. package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
  278. package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
  279. package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
  280. package/.agents/skills/galdr-ymirsystem/assets/smidja.md +175 -0
  281. package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
  282. package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
  283. package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
  284. package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +360 -0
  285. package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
  286. package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
  287. package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
  288. package/.agents/skills/gjallarhorn-relay/README.md +3 -0
  289. package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
  290. package/.agents/skills/groa-update/SKILL.md +70 -0
  291. package/.agents/skills/hamr-adapters/README.md +3 -0
  292. package/.agents/skills/hamr-adapters/SKILL.md +123 -0
  293. package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
  294. package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
  295. package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
  296. package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
  297. package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
  298. package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
  299. package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
  300. package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
  301. package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
  302. package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
  303. package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
  304. package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
  305. package/.agents/skills/herdr-panes/README.md +3 -0
  306. package/.agents/skills/herdr-panes/SKILL.md +92 -0
  307. package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
  308. package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
  309. package/.agents/skills/hnoss-design/DESIGN.md +19 -0
  310. package/.agents/skills/hnoss-design/README.md +3 -0
  311. package/.agents/skills/hnoss-design/SKILL.md +83 -0
  312. package/.agents/skills/hvild-afk/README.md +3 -0
  313. package/.agents/skills/hvild-afk/SKILL.md +241 -0
  314. package/.agents/skills/jord-projects/README.md +3 -0
  315. package/.agents/skills/jord-projects/SKILL.md +92 -0
  316. package/.agents/skills/lifecycle/SKILL.md +56 -0
  317. package/.agents/skills/lifecycle/smoke_test.sh +52 -0
  318. package/.agents/skills/lifecycle/start.sh +21 -0
  319. package/.agents/skills/lifecycle/status.sh +35 -0
  320. package/.agents/skills/lifecycle/stop.sh +16 -0
  321. package/.agents/skills/muninn-stow/README.md +3 -0
  322. package/.agents/skills/muninn-stow/SKILL.md +309 -0
  323. package/.agents/skills/no-mistakes/SKILL.md +379 -0
  324. package/.agents/skills/nornir-schedule/README.md +3 -0
  325. package/.agents/skills/nornir-schedule/SKILL.md +25 -0
  326. package/.agents/skills/nornir-schedule/assets/events.md +137 -0
  327. package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
  328. package/.agents/skills/nsr-compliance/README.md +3 -0
  329. package/.agents/skills/nsr-compliance/SKILL.md +29 -0
  330. package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
  331. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
  332. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
  333. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
  334. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
  335. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
  336. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
  337. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
  338. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
  339. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
  340. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
  341. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
  342. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
  343. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
  344. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
  345. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
  346. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
  347. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
  348. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
  349. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
  350. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
  351. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
  352. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
  353. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  354. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
  355. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
  356. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
  357. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
  358. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
  359. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
  360. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
  361. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
  362. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
  363. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
  364. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
  365. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
  366. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
  367. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
  368. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
  369. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
  370. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
  371. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
  372. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
  373. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
  374. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
  375. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
  376. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
  377. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
  378. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
  379. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
  380. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
  381. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
  382. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
  383. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
  384. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
  385. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
  386. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
  387. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
  388. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
  389. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
  390. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
  391. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
  392. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
  393. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
  394. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
  395. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
  396. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
  397. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
  398. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
  399. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
  400. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
  401. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
  402. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
  403. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
  404. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
  405. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
  406. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
  407. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
  408. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
  409. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
  410. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
  411. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
  412. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
  413. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
  414. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
  415. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
  416. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
  417. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
  418. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
  419. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
  420. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
  421. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
  422. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
  423. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
  424. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
  425. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
  426. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
  427. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
  428. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
  429. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
  430. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
  431. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
  432. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
  433. package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
  434. package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
  435. package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
  436. package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
  437. package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
  438. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
  439. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
  440. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
  441. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
  442. package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
  443. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
  444. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
  445. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
  446. package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
  447. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
  448. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
  449. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
  450. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
  451. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
  452. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
  453. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
  454. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  455. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
  456. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
  457. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
  458. package/.agents/skills/pr-ops/README.md +3 -0
  459. package/.agents/skills/pr-ops/SKILL.md +109 -0
  460. package/.agents/skills/ratatoskr-a2a/README.md +3 -0
  461. package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
  462. package/.agents/skills/rules-check-drift/SKILL.md +81 -0
  463. package/.agents/skills/saga-bearings/README.md +3 -0
  464. package/.agents/skills/saga-bearings/SKILL.md +26 -0
  465. package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
  466. package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
  467. package/.agents/skills/saga-bearings/assets/recap.md +56 -0
  468. package/.agents/skills/syn-recovery/README.md +3 -0
  469. package/.agents/skills/syn-recovery/SKILL.md +54 -0
  470. package/.agents/skills/tyr-check/README.md +3 -0
  471. package/.agents/skills/tyr-check/SKILL.md +218 -0
  472. package/.agents/skills/urdh-hold/README.md +3 -0
  473. package/.agents/skills/urdh-hold/SKILL.md +25 -0
  474. package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
  475. package/.agents/skills/urdh-hold/assets/hold.md +54 -0
  476. package/.agents/skills/vor-diagnostics/README.md +3 -0
  477. package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
  478. package/.agents/skills/ymir-host/README.md +3 -0
  479. package/.agents/skills/ymir-host/SKILL.md +34 -0
  480. package/.agents/skills/ymir-host/assets/desktop.md +36 -0
  481. package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
  482. package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
  483. package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
  484. package/.agents/skills/ymir-host/assets/update.md +64 -0
  485. package/.agents/tests/assets/board-render-harness.mjs +117 -0
  486. package/.agents/tests/cmux-test-safety.sh +51 -0
  487. package/.agents/tests/fixtures.sh +291 -0
  488. package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
  489. package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
  490. package/.agents/tests/fm-afk-launch.test.sh +961 -0
  491. package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
  492. package/.agents/tests/fm-afk-return.test.sh +283 -0
  493. package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
  494. package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
  495. package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
  496. package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
  497. package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
  498. package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
  499. package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
  500. package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
  501. package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
  502. package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
  503. package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
  504. package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
  505. package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
  506. package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
  507. package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
  508. package/.agents/tests/fm-backend-orca.test.sh +1357 -0
  509. package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
  510. package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
  511. package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
  512. package/.agents/tests/fm-backend.test.sh +1142 -0
  513. package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
  514. package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
  515. package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
  516. package/.agents/tests/fm-bearings-board.test.sh +414 -0
  517. package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
  518. package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
  519. package/.agents/tests/fm-bootstrap.test.sh +1178 -0
  520. package/.agents/tests/fm-branch-supervision.test.sh +552 -0
  521. package/.agents/tests/fm-brief.test.sh +785 -0
  522. package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
  523. package/.agents/tests/fm-busy-state.test.sh +465 -0
  524. package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
  525. package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
  526. package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
  527. package/.agents/tests/fm-check-unregister.test.sh +198 -0
  528. package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
  529. package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
  530. package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
  531. package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
  532. package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
  533. package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
  534. package/.agents/tests/fm-composer-ghost.test.sh +716 -0
  535. package/.agents/tests/fm-composer-lib.test.sh +666 -0
  536. package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
  537. package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
  538. package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
  539. package/.agents/tests/fm-control.test.sh +910 -0
  540. package/.agents/tests/fm-crew-state.test.sh +1610 -0
  541. package/.agents/tests/fm-cursor-harness.test.sh +404 -0
  542. package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
  543. package/.agents/tests/fm-cursor-primary.test.sh +708 -0
  544. package/.agents/tests/fm-daemon.test.sh +2740 -0
  545. package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
  546. package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
  547. package/.agents/tests/fm-extension-binding.test.sh +2187 -0
  548. package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
  549. package/.agents/tests/fm-fleet-sync.test.sh +721 -0
  550. package/.agents/tests/fm-gate-refuse.test.sh +356 -0
  551. package/.agents/tests/fm-gitignore-config.test.sh +89 -0
  552. package/.agents/tests/fm-gotmp.test.sh +230 -0
  553. package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
  554. package/.agents/tests/fm-grok-harness.test.sh +117 -0
  555. package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
  556. package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
  557. package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
  558. package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
  559. package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
  560. package/.agents/tests/fm-herdr-lab.test.sh +243 -0
  561. package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
  562. package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
  563. package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
  564. package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
  565. package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
  566. package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
  567. package/.agents/tests/fm-kimi-harness.test.sh +687 -0
  568. package/.agents/tests/fm-lint-workflows.test.sh +532 -0
  569. package/.agents/tests/fm-lint.test.sh +1023 -0
  570. package/.agents/tests/fm-muse-harness.test.sh +932 -0
  571. package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
  572. package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
  573. package/.agents/tests/fm-on.test.sh +507 -0
  574. package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
  575. package/.agents/tests/fm-operational-input.test.sh +160 -0
  576. package/.agents/tests/fm-peek-remote.test.sh +110 -0
  577. package/.agents/tests/fm-pending-reply.test.sh +1288 -0
  578. package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
  579. package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
  580. package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
  581. package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
  582. package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
  583. package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
  584. package/.agents/tests/fm-pr-merge.test.sh +2135 -0
  585. package/.agents/tests/fm-procevent-quota.test.sh +225 -0
  586. package/.agents/tests/fm-procevent-when.test.sh +406 -0
  587. package/.agents/tests/fm-procevent.test.sh +1745 -0
  588. package/.agents/tests/fm-project-origin.test.sh +110 -0
  589. package/.agents/tests/fm-public-followup.test.sh +2272 -0
  590. package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
  591. package/.agents/tests/fm-quota-choose.test.sh +626 -0
  592. package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
  593. package/.agents/tests/fm-remote-doctor.test.sh +625 -0
  594. package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
  595. package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
  596. package/.agents/tests/fm-remote-job.test.sh +768 -0
  597. package/.agents/tests/fm-remote-reply.test.sh +520 -0
  598. package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
  599. package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
  600. package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
  601. package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
  602. package/.agents/tests/fm-review-diff.test.sh +176 -0
  603. package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
  604. package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
  605. package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
  606. package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
  607. package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
  608. package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
  609. package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
  610. package/.agents/tests/fm-send-inbox.test.sh +352 -0
  611. package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
  612. package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
  613. package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
  614. package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
  615. package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
  616. package/.agents/tests/fm-send-settle.test.sh +147 -0
  617. package/.agents/tests/fm-send-strict.test.sh +241 -0
  618. package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
  619. package/.agents/tests/fm-session-start.test.sh +2515 -0
  620. package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
  621. package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
  622. package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
  623. package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
  624. package/.agents/tests/fm-spawn-batch.test.sh +148 -0
  625. package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
  626. package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
  627. package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
  628. package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
  629. package/.agents/tests/fm-startup-network.test.sh +712 -0
  630. package/.agents/tests/fm-stow-cascade.test.sh +370 -0
  631. package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
  632. package/.agents/tests/fm-supervision-events.test.sh +156 -0
  633. package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
  634. package/.agents/tests/fm-tangle-guard.test.sh +277 -0
  635. package/.agents/tests/fm-task-delivery.test.sh +420 -0
  636. package/.agents/tests/fm-task-inbox.test.sh +513 -0
  637. package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
  638. package/.agents/tests/fm-teardown.test.sh +2665 -0
  639. package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
  640. package/.agents/tests/fm-test-fixtures.test.sh +132 -0
  641. package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
  642. package/.agents/tests/fm-test-run.test.sh +1196 -0
  643. package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
  644. package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
  645. package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
  646. package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
  647. package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
  648. package/.agents/tests/fm-transition-lib.test.sh +54 -0
  649. package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
  650. package/.agents/tests/fm-update.test.sh +304 -0
  651. package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
  652. package/.agents/tests/fm-voice-relay.test.sh +4741 -0
  653. package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
  654. package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
  655. package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
  656. package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
  657. package/.agents/tests/fm-wake-queue.test.sh +1224 -0
  658. package/.agents/tests/fm-watch-arm.test.sh +821 -0
  659. package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
  660. package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
  661. package/.agents/tests/fm-watch-triage.test.sh +3889 -0
  662. package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
  663. package/.agents/tests/fm-x-mode.test.sh +3042 -0
  664. package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
  665. package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
  666. package/.agents/tests/herdr-test-safety.sh +42 -0
  667. package/.agents/tests/lib.sh +315 -0
  668. package/.agents/tests/remote-herdr-fixture.sh +125 -0
  669. package/.agents/tests/secondmate-helpers.sh +193 -0
  670. package/.agents/tests/smoke.test.sh +93 -0
  671. package/.agents/tests/wake-helpers.sh +338 -0
  672. package/.agents/tests/zellij-test-safety.sh +60 -0
  673. package/.agents/tools/README.md +10 -0
  674. package/.agents/tools/bin/tasks-cli +267 -0
  675. package/.agents/tools/tasks-cli.ts +216 -0
  676. package/AGENTS.md +454 -0
  677. package/CHANGELOG.md +881 -0
  678. package/LICENSE +1 -1
  679. package/NOTICE +63 -0
  680. package/README.md +113 -59
  681. package/RULES/01-domains.md +51 -0
  682. package/RULES/02-agents.md +78 -0
  683. package/RULES/03-houses.md +61 -0
  684. package/RULES/04-hoard.md +65 -0
  685. package/RULES/05-platforms.md +67 -0
  686. package/RULES/06-append-only.md +58 -0
  687. package/RULES/07-config.md +45 -0
  688. package/RULES/08-delivery-gate.md +75 -0
  689. package/RULES/README.md +20 -0
  690. package/bin/README.md +107 -0
  691. package/bin/a2a-mcp.sh +98 -0
  692. package/bin/a2a-serve.py +103 -0
  693. package/bin/a2a-serve.sh +8 -0
  694. package/bin/a2a-talk.sh +50 -0
  695. package/bin/a2abridge-ensure.sh +108 -0
  696. package/bin/agent-run.sh +43 -0
  697. package/bin/agents-config.sh +233 -0
  698. package/bin/apodex-smoke-test.sh +92 -0
  699. package/bin/bifrost-bridge.sh +112 -0
  700. package/bin/bifrost-ingress.sh +77 -0
  701. package/bin/bootstrap-macos.sh +107 -0
  702. package/bin/bootstrap-windows.ps1 +112 -0
  703. package/bin/branch-guard.sh +64 -0
  704. package/bin/brokk +73 -0
  705. package/bin/brokk-classify-lib.sh +1770 -0
  706. package/bin/brokk-lease-lib.sh +222 -0
  707. package/bin/brokk-lease.sh +193 -0
  708. package/bin/brokk-lint.sh +65 -0
  709. package/bin/brokk-send.sh +38 -0
  710. package/bin/brokk-update.sh +9 -0
  711. package/bin/brokk-wake-grant.sh +127 -0
  712. package/bin/brokk-wake-lib.sh +1849 -0
  713. package/bin/changelog-guard.sh +165 -0
  714. package/bin/crash-sense.sh +160 -0
  715. package/bin/desktop-place.sh +224 -0
  716. package/bin/docs-guard.sh +39 -0
  717. package/bin/editor-place.sh +207 -0
  718. package/bin/eindri-acclaim.sh +34 -0
  719. package/bin/eindri-control.sh +28 -0
  720. package/bin/eindri-role.sh +83 -0
  721. package/bin/eindri-seen.sh +41 -0
  722. package/bin/eindri-send.sh +15 -0
  723. package/bin/eindri-start.sh +85 -0
  724. package/bin/eindri-watch.sh +57 -0
  725. package/bin/einherjar-spawn.sh +576 -0
  726. package/bin/eir-doctor.sh +112 -0
  727. package/bin/erindi-brief.sh +271 -0
  728. package/bin/fleet-apply.sh +68 -0
  729. package/bin/github-deploy.sh +81 -0
  730. package/bin/gjallarhorn-expose.sh +102 -0
  731. package/bin/gjallarhorn-notify.sh +58 -0
  732. package/bin/gjallarhorn-purge.sh +54 -0
  733. package/bin/gjallarhorn-tunnel.sh +72 -0
  734. package/bin/gleipnir-lock-lib.sh +284 -0
  735. package/bin/groa-update.sh +122 -0
  736. package/bin/hall-snapshot.sh +92 -0
  737. package/bin/hamr-harness.sh +246 -0
  738. package/bin/heimdall-ssh-keys.sh +374 -0
  739. package/bin/herdr-agents.py +44 -0
  740. package/bin/herdr-ensure.sh +104 -0
  741. package/bin/herdr-run.sh +431 -0
  742. package/bin/hermes-ensure.sh +88 -0
  743. package/bin/hoard-lib.sh +25 -0
  744. package/bin/hodd.sh +78 -0
  745. package/bin/host-sense.sh +199 -0
  746. package/bin/huginn-research-worker.sh +108 -0
  747. package/bin/local-model-lock.sh +51 -0
  748. package/bin/mimir-bridge.py +226 -0
  749. package/bin/mimir-bridge.sh +87 -0
  750. package/bin/mimir-ingest.sh +131 -0
  751. package/bin/mimir.sh +113 -0
  752. package/bin/mjollnir-webhook.sh +59 -0
  753. package/bin/mjollnir.sh +85 -0
  754. package/bin/model-hardware.sh +82 -0
  755. package/bin/model-resolve.sh +147 -0
  756. package/bin/models-detect.sh +178 -0
  757. package/bin/nornir-cron-start.sh +162 -0
  758. package/bin/nornir-job-daily-briefing.sh +191 -0
  759. package/bin/nornir-job-git-sync.sh +152 -0
  760. package/bin/nornir-job-memory-housekeeping.sh +114 -0
  761. package/bin/nornir-job-observer.sh +166 -0
  762. package/bin/nornir-job-skillopt-sleep.sh +76 -0
  763. package/bin/omarchy-hook-install.sh +60 -0
  764. package/bin/omarchy-install.sh +148 -0
  765. package/bin/omarchy-plugins.sh +174 -0
  766. package/bin/omarchy-sense.sh +149 -0
  767. package/bin/perm-guard.sh +30 -0
  768. package/bin/pi-agent.sh +68 -0
  769. package/bin/pi-ensure.sh +77 -0
  770. package/bin/pi-local.sh +37 -0
  771. package/bin/pi-seat.sh +63 -0
  772. package/bin/prereq-ensure.sh +166 -0
  773. package/bin/private-guard.sh +38 -0
  774. package/bin/project-git.sh +55 -0
  775. package/bin/public-guard.sh +70 -0
  776. package/bin/ratatoskr.sh +49 -0
  777. package/bin/realm-lib.sh +32 -0
  778. package/bin/repo-scrub.sh +62 -0
  779. package/bin/rodd-operational-input.sh +250 -0
  780. package/bin/runes-append.sh +198 -0
  781. package/bin/saga-session-start.sh +172 -0
  782. package/bin/saga-sessionstart-run.sh +79 -0
  783. package/bin/saga-wake-drain.sh +41 -0
  784. package/bin/secret-guard.sh +63 -0
  785. package/bin/sessrumnir-ensure.sh +100 -0
  786. package/bin/sessrumnir-sync.sh +237 -0
  787. package/bin/sessrumnir.sh +91 -0
  788. package/bin/skillopt-setup.sh +76 -0
  789. package/bin/skuld-branch-outcome.sh +203 -0
  790. package/bin/skuld-branch-prompt.sh +101 -0
  791. package/bin/smidja-bootstrap.sh +90 -0
  792. package/bin/smidja-observe.sh +86 -0
  793. package/bin/syn-arm-pretool-check.sh +57 -0
  794. package/bin/syn-asset-pretool-check.sh +76 -0
  795. package/bin/syn-cd-pretool-check.sh +28 -0
  796. package/bin/syn-guard-pretool-check.sh +83 -0
  797. package/bin/syn-turnend-guard.sh +36 -0
  798. package/bin/syn-watch-arm.sh +99 -0
  799. package/bin/tailscale-sync.sh +190 -0
  800. package/bin/telegram-bot.sh +69 -0
  801. package/bin/toolchain.sh +74 -0
  802. package/bin/utgard.sh +111 -0
  803. package/bin/valhalla.sh +115 -0
  804. package/bin/valknut-load.sh +260 -0
  805. package/bin/vor-crew-state.sh +124 -0
  806. package/bin/wedge-notify.sh +47 -0
  807. package/bin/workspace-provision.sh +80 -0
  808. package/bin/workspace-rag.sh +150 -0
  809. package/bin/wyrd-db.sh +47 -0
  810. package/bin/yggdrasil.sh +152 -0
  811. package/bin/ymir-edit.sh +145 -0
  812. package/bin/ymir-install.sh +641 -0
  813. package/bin/ymir-invite.sh +93 -0
  814. package/bin/ymir-isolation.sh +88 -0
  815. package/bin/ymir-migrate.sh +61 -0
  816. package/bin/ymir-platform.sh +348 -0
  817. package/bin/ymir-say.sh +97 -0
  818. package/bin/ymir-setup-auth.sh +108 -0
  819. package/bin/ymir-validate.sh +173 -0
  820. package/bin/ymir.js +74 -16
  821. package/docs/Architecture.md +339 -0
  822. package/docs/design.md +351 -0
  823. package/docs/installations/README.md +63 -0
  824. package/docs/installations/ymir-home.md +93 -0
  825. package/docs/integration.md +105 -0
  826. package/docs/lore.md +1056 -0
  827. package/docs/research/agent-harnesses.md +290 -0
  828. package/docs/runbooks/README.md +19 -0
  829. package/docs/runbooks/a2a.md +91 -0
  830. package/docs/runbooks/agent-permissions.md +99 -0
  831. package/docs/runbooks/agents.md +57 -0
  832. package/docs/runbooks/models.md +69 -0
  833. package/docs/runbooks/realm-onboarding.md +60 -0
  834. package/docs/runbooks/secrets-and-hoard.md +60 -0
  835. package/docs/runbooks/updates-and-migrations.md +110 -0
  836. package/docs/runbooks/worktrees-isolation.md +72 -0
  837. package/docs/session-start.md +105 -0
  838. package/docs/skillopt-integration.md +321 -0
  839. package/docs/sync-system.md +66 -0
  840. package/docs/workspaces.md +104 -0
  841. package/docs/ymir-rut.md +260 -0
  842. package/package.json +44 -14
  843. package/packaging/build.sh +74 -0
  844. package/packaging/macos/Ymir Installer.command +43 -0
  845. package/packaging/windows/ymir-setup.nsi +74 -0
  846. package/scripts/README.md +22 -0
  847. package/scripts/electron.sh +210 -0
  848. package/scripts/start.sh +167 -0
  849. package/scripts/stop.sh +113 -0
  850. package/install.sh +0 -35
@@ -0,0 +1,700 @@
1
+ #!/usr/bin/env bash
2
+ # bin/backends/zellij.sh - the zellij session-provider adapter (EXPERIMENTAL).
3
+ #
4
+ # Design: data/fm-backend-design-d7/report.md ("Zellij Backend" section - the
5
+ # interface mapping, implementation choices, and "Zellij gaps to verify" list)
6
+ # and herdr-addendum.md D2/D3 (zellij is P3, after herdr; treehouse stays the
7
+ # worktree provider). Zellij is a session provider ONLY: the worktree provider
8
+ # stays treehouse, exactly like tmux and herdr. Sourced only through
9
+ # bin/fm-backend.sh's fm_backend_source in normal operation; the unit tests
10
+ # source it directly.
11
+ #
12
+ # Session shape (report "Zellij implementation choices" #1, unchanged by
13
+ # empirical verification): ONE zellij session (default name "firstmate",
14
+ # overridable via FM_ZELLIJ_SESSION for test isolation - mirrors herdr's
15
+ # HERDR_SESSION), ONE tab per task, with caller-facing label "fm-<id>" and a
16
+ # home-scoped actual title. No per-home workspace split (unlike herdr's later
17
+ # P3 refinement): zellij has no workspace concept, only sessions/tabs/panes,
18
+ # so this stays exactly the report's original choice. Target string shape:
19
+ # "<zellij-session>:<pane-id>" (pane id is a bare non-negative integer with no
20
+ # embedded colon, so splitting on the FIRST colon is trivially correct and
21
+ # mirrors herdr's target-string convention).
22
+ #
23
+ # Home-scoped tab titles (closes a cross-home collision gap): because every
24
+ # task in every firstmate home - primary or secondmate - shares this ONE
25
+ # session's tab bar with no per-home split, and zellij enforces no tab-name
26
+ # uniqueness at all, two firstmate homes whose task ids happen to collide
27
+ # could send/peek/close each other's tabs. This is the exact gap a
28
+ # captain-directed no-mistakes review gate caught for the cmux backend
29
+ # (docs/cmux-backend.md) and this same tag mechanism (bin/backends/cmux.sh's
30
+ # fm_backend_cmux_scoped_title, now shared via bin/fm-backend-hometag-lib.sh)
31
+ # is ported here for the identical reason. Every NEW tab is created with a
32
+ # title tagged with this installation's home label (fm_backend_zellij_scoped_title,
33
+ # "fm-<hometag>-<id>"); every list/find/recover/kill path is scoped to this
34
+ # home's own tag. A tab created before this change carries the old untagged
35
+ # "fm-<id>" title; target_ready, kill, and ad hoc selector fallback still
36
+ # match it, but ONLY when that bare title is unambiguous (exactly one live tab
37
+ # in the session carries it) - see fm_backend_zellij_tab_matches_label and
38
+ # docs/zellij-backend.md "Home-scoped tab titles" for the full migration
39
+ # posture. Moving/relocating a firstmate installation changes its tag
40
+ # (acceptable - recorded worktree paths do not survive a move either).
41
+ #
42
+ # Empirical verification (real zellij 0.44.0, macOS aarch64, 2026-07-02;
43
+ # docs/zellij-backend.md has the full evidence log) resolved every "gaps to
44
+ # verify" item in the design report, plus additional real findings not
45
+ # anticipated by the report:
46
+ #
47
+ # 1. dump-screen on a background session with NO attached client: WORKS.
48
+ # 2. Key names: Enter -> "Enter", Escape -> "Esc" (NOT "Escape"), Ctrl-C ->
49
+ # "Ctrl c" as ONE shell argument with an embedded space (NOT two argv
50
+ # words, NOT "C-c" or "Ctrl+c" - all verified to fail).
51
+ # 3. `new-tab --cwd --name` DOES return the created tab's bare integer id on
52
+ # stdout, exactly as documented.
53
+ # 4. `list-panes --json`'s `pane_cwd` reflects a `cd` run DIRECTLY in the
54
+ # pane's own top-level shell within one poll (<0.3s) - but does NOT
55
+ # reflect a `cd` performed by a NESTED SUBSHELL the pane's shell
56
+ # launched as a foreground command (verified: `treehouse get` opens
57
+ # exactly such a subshell). `pane_cwd` stays frozen at wherever the
58
+ # pane's shell was when it invoked that foreground command - worse than
59
+ # herdr's frozen-cwd trap (herdr at least exposes a `foreground_cwd`
60
+ # that tracks this; zellij's CLI exposes no live-process cwd field and
61
+ # no per-pane pid to read it from `/proc`/`lsof` either). This directly
62
+ # contradicts the design report's assumption ("acceptable for tmux and
63
+ # zellij") and required a different implementation strategy - see
64
+ # fm_backend_zellij_current_path below and docs/zellij-backend.md
65
+ # "Worktree-path discovery: pane_cwd does not track a subshell".
66
+ # 5. `new-tab` DOES steal focus from an attached client with NO flag to
67
+ # suppress it (unlike herdr's --no-focus and tmux's new-window -d).
68
+ # Mitigated (fm_backend_zellij_create_task): capture the previously
69
+ # active tab id before creating, restore it with go-to-tab-by-id
70
+ # afterward - verified to correctly restore an attached client's view
71
+ # and to be a safe no-op with no client attached.
72
+ #
73
+ # Additional un-anticipated findings, load-bearing for this adapter:
74
+ # - Every pane-targeting action (write-chars, send-keys, dump-screen, ...)
75
+ # MUST pass an explicit --pane-id. The "focused pane" default is
76
+ # unreliable: a fresh session auto-opens a floating "About Zellij"/release
77
+ # notes PLUGIN pane that starts FOCUSED and shadows the real terminal pane
78
+ # - a pane-id-less send silently goes nowhere.
79
+ # - `zellij action <subcommand>` ALWAYS exits 0, even against a nonexistent
80
+ # session (prints the live session list to stdout, an error to stderr) or
81
+ # a nonexistent pane id in a live session (prints nothing at all, to
82
+ # neither stream). The exit code can NEVER be trusted to detect a bad
83
+ # target. Mitigated: send/capture/cwd ops verify session liveness first
84
+ # (fm_backend_zellij_session_exists, a passive list-sessions query, never
85
+ # auto-creating), verify the specific pane still appears in list-panes JSON,
86
+ # and, for metadata-routed task selector operations, verify the pane's tab
87
+ # still matches the expected caller-facing task label through the home-scoped or
88
+ # unambiguous legacy title before use. Kill verifies the session and, when
89
+ # teardown supplies an expected tab label, verifies a tab id still matches
90
+ # that label before closing it. Output-SHAPE validation (a bare integer tab
91
+ # id, JSON that parses) rejects the "session not found" text fallback. A
92
+ # pane can still die between the preflight check and the operation call;
93
+ # docs/zellij-backend.md records that residual race.
94
+ # - `zellij list-tabs`/`new-tab` does NOT enforce unique tab names (same as
95
+ # herdr's tabs, unlike tmux's own window-name uniqueness), so the
96
+ # duplicate check below is ours, mirroring both prior adapters.
97
+ # - Closing a tab's only pane (`close-pane`) does NOT close the now-empty
98
+ # tab (unlike herdr, where the analogous close DOES remove the tab) - an
99
+ # empty "ghost" tab survives in `list-tabs` until explicitly closed. Kill
100
+ # therefore always resolves the owning tab id and calls
101
+ # `close-tab-by-id`, which verified cleanly removes a live tab (pane and
102
+ # all) in one call - never a separate close-pane first.
103
+ #
104
+ # Requires: zellij (CLI), jq (JSON parsing). Bootstrap detects these through
105
+ # fm_backend_required_tools only when zellij is the resolved backend; this
106
+ # adapter also gates them again before spawning.
107
+
108
+ # FM_HOME fallback: every real caller already sets FM_HOME as a global before
109
+ # sourcing fm-backend.sh (which sources this file); this exists only so this
110
+ # file's own unit tests, which source it directly, resolve sanely. Mirrors
111
+ # bin/backends/herdr.sh's identical fallback; unlike herdr this adapter still
112
+ # has no per-home CONTAINER split (one shared session for every home), but
113
+ # FM_HOME/FM_ROOT now also feed fm_backend_zellij_home_label's tab-title tag
114
+ # below.
115
+ FM_BACKEND_ZELLIJ_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
116
+ FM_ROOT="${FM_ROOT_OVERRIDE:-${FM_ROOT:-$FM_BACKEND_ZELLIJ_ROOT}}"
117
+ FM_HOME="${FM_HOME:-${FM_ROOT_OVERRIDE:-$FM_ROOT}}"
118
+
119
+ # shellcheck source=bin/fm-backend-hometag-lib.sh
120
+ . "$FM_BACKEND_ZELLIJ_ROOT/bin/fm-backend-hometag-lib.sh"
121
+
122
+ # Shared composer classification (the fleet-wide shape catalogue and verdict
123
+ # owner; this adapter contributes only capture and capability facts).
124
+ # shellcheck source=bin/fm-composer-lib.sh
125
+ . "$FM_BACKEND_ZELLIJ_ROOT/bin/fm-composer-lib.sh"
126
+
127
+ # Verified minimum: report.md recommends "likely Zellij 0.44 or newer" for
128
+ # returned pane/tab IDs and dump-screen --pane-id; empirically verified
129
+ # against the installed 0.44.0 (docs/zellij-backend.md).
130
+ FM_BACKEND_ZELLIJ_MIN_MAJOR=0
131
+ FM_BACKEND_ZELLIJ_MIN_MINOR=44
132
+
133
+ # fm_backend_zellij_session: the session name this spawn/op uses.
134
+ # FM_ZELLIJ_SESSION mirrors herdr's HERDR_SESSION ambient-selection knob: an
135
+ # operator (or firstmate's own isolated test harness) sets it explicitly;
136
+ # absent means the shared "firstmate" session. Do not use this alone for
137
+ # destructive test cleanup; tests/zellij-test-safety.sh documents and guards
138
+ # that path (mirrors tests/herdr-test-safety.sh).
139
+ fm_backend_zellij_session() {
140
+ printf '%s' "${FM_ZELLIJ_SESSION:-firstmate}"
141
+ }
142
+
143
+ # fm_backend_zellij_home_label: readable home prefix plus a short hash of the
144
+ # resolved FM_ROOT path (bin/fm-backend-hometag-lib.sh). Zellij has one
145
+ # session-global tab namespace shared by every firstmate home, so the path
146
+ # hash distinguishes every installation, including multiple primary homes.
147
+ # Moving an installation changes this tag and old zellij tab titles stop
148
+ # matching; task meta already records absolute worktree paths, so repo
149
+ # relocation is already outside the supported recovery contract.
150
+ fm_backend_zellij_home_label() {
151
+ fm_backend_hometag
152
+ }
153
+
154
+ # fm_backend_zellij_scoped_title: the actual tab title a NEW task's tab is
155
+ # created with - the caller-facing "fm-<id>" label, home-tagged as
156
+ # "fm-<hometag>-<id>" (mirrors bin/backends/cmux.sh's identical
157
+ # fm_backend_cmux_scoped_title). Every list/find/recover/kill path below
158
+ # scopes its own-home matches through this.
159
+ fm_backend_zellij_scoped_title() { # <fm-task-label>
160
+ local label=$1 rest home
161
+ home=$(fm_backend_zellij_home_label)
162
+ case "$label" in
163
+ fm-*) rest=${label#fm-} ;;
164
+ *) rest=$label ;;
165
+ esac
166
+ printf 'fm-%s-%s' "$home" "$rest"
167
+ }
168
+
169
+ # fm_backend_zellij_tool_check: refuse loudly if zellij or jq is missing.
170
+ fm_backend_zellij_tool_check() {
171
+ command -v zellij >/dev/null 2>&1 || { echo "error: backend=zellij selected but the 'zellij' CLI is not installed (https://zellij.dev)" >&2; return 1; }
172
+ command -v jq >/dev/null 2>&1 || { echo "error: backend=zellij selected but 'jq' is not installed (required to parse zellij's JSON output)" >&2; return 1; }
173
+ return 0
174
+ }
175
+
176
+ # fm_backend_zellij_version_check: refuse loudly on a missing/incompatible
177
+ # zellij client. Verified locally: 0.44.0 (`zellij --version` -> "zellij
178
+ # 0.44.0", session-independent - no server/session needs to exist yet).
179
+ fm_backend_zellij_version_check() {
180
+ fm_backend_zellij_tool_check || return 1
181
+ local raw ver major rest minor
182
+ raw=$(zellij --version 2>/dev/null) || { echo "error: 'zellij --version' failed; is zellij installed correctly?" >&2; return 1; }
183
+ ver=$(printf '%s' "$raw" | awk '{print $2}')
184
+ case "$ver" in
185
+ ''|*[!0-9.]*)
186
+ echo "error: could not parse a zellij version from '$raw'; refusing to use an unverified zellij build" >&2
187
+ return 1
188
+ ;;
189
+ esac
190
+ major=${ver%%.*}
191
+ rest=${ver#*.}
192
+ minor=${rest%%.*}
193
+ case "$major" in ''|*[!0-9]*) major=0 ;; esac
194
+ case "$minor" in ''|*[!0-9]*) minor=0 ;; esac
195
+ if [ "$major" -lt "$FM_BACKEND_ZELLIJ_MIN_MAJOR" ] || { [ "$major" -eq "$FM_BACKEND_ZELLIJ_MIN_MAJOR" ] && [ "$minor" -lt "$FM_BACKEND_ZELLIJ_MIN_MINOR" ]; }; then
196
+ echo "error: zellij $ver is older than the verified minimum $FM_BACKEND_ZELLIJ_MIN_MAJOR.$FM_BACKEND_ZELLIJ_MIN_MINOR; update zellij before using backend=zellij" >&2
197
+ return 1
198
+ fi
199
+ return 0
200
+ }
201
+
202
+ # fm_backend_zellij_cli: run `zellij --session <session> action <args...>`,
203
+ # setting BOTH the ZELLIJ_SESSION_NAME env var AND the leading global
204
+ # `--session <name>` flag (zellij's session-target flag is GLOBAL, before the
205
+ # subcommand - unlike herdr's trailing --session - verified both forms
206
+ # independently route correctly on the installed 0.44.0 client; kept together
207
+ # for defense in depth, mirroring bin/backends/herdr.sh's fm_backend_herdr_cli
208
+ # rationale even though no equivalent env-var-unreliable incident has been
209
+ # observed for zellij).
210
+ fm_backend_zellij_cli() { # <session> <action-subcommand-and-args...>
211
+ local session=$1
212
+ shift
213
+ ZELLIJ_SESSION_NAME="$session" zellij --session "$session" "$@"
214
+ }
215
+
216
+ # fm_backend_zellij_session_exists: passive, READ-ONLY liveness check - never
217
+ # starts or creates a session (unlike herdr's target_ready, which DOES
218
+ # auto-start its server: a herdr server restart is non-destructive and
219
+ # recovers persisted state, but zellij's `kill-session` is destructive and
220
+ # recreating an unrelated target session under the same name would silently
221
+ # orphan whatever the caller actually meant to reach). Every op below calls
222
+ # this first and fails rather than guessing.
223
+ fm_backend_zellij_session_exists() { # <session>
224
+ zellij list-sessions --short --no-formatting 2>/dev/null | grep -qxF "$1"
225
+ }
226
+
227
+ # fm_backend_zellij_server_ensure: create the named session in the background,
228
+ # headless (no attached client), if it does not already exist - mirrors
229
+ # tmux's `tmux has-session || tmux new-session -d` and herdr's server_ensure.
230
+ # Verified: `zellij attach -b <name>` with stdin redirected from /dev/null and
231
+ # no controlling TTY creates the session and returns promptly (it cannot
232
+ # actually attach without a TTY, so it exits after creating); running it again
233
+ # against an EXISTING session prints "Session already exists" and exits 1 -
234
+ # harmless here because existence is checked first and the launch is
235
+ # backgrounded, its exit status never inspected.
236
+ fm_backend_zellij_server_ensure() { # <session>
237
+ local session=$1 i
238
+ fm_backend_zellij_session_exists "$session" && return 0
239
+ ( nohup zellij attach -b "$session" </dev/null >/dev/null 2>&1 & ) || return 1
240
+ for i in $(seq 1 20); do
241
+ fm_backend_zellij_session_exists "$session" && return 0
242
+ sleep 0.5
243
+ done
244
+ echo "error: zellij session '$session' did not come up within 10s" >&2
245
+ return 1
246
+ }
247
+
248
+ # fm_backend_zellij_container_ensure: the full spawn-time container-ensure
249
+ # sequence (version gate, session). Echoes the session name (no second
250
+ # "workspace" component - zellij has no such concept, unlike herdr).
251
+ fm_backend_zellij_container_ensure() {
252
+ local session
253
+ fm_backend_zellij_version_check || return 1
254
+ session=$(fm_backend_zellij_session)
255
+ fm_backend_zellij_server_ensure "$session" || return 1
256
+ printf '%s' "$session"
257
+ }
258
+
259
+ # fm_backend_zellij_pane_for_tab: the terminal (non-plugin) pane id for
260
+ # <tab_id> in <session>, via one list-panes call filtered by tab_id and
261
+ # is_plugin==false. Terminal pane ids are globally unique across a session's
262
+ # whole tab set (verified: sequential across tabs, a SEPARATE numbering
263
+ # namespace from plugin panes, which is why a plugin pane and a terminal pane
264
+ # can share the same bare "id" - the CLI's own --pane-id contract, "3
265
+ # (equivalent to terminal_3)", already documents this split. Never assumes a
266
+ # tab-position/pane-number correspondence.
267
+ fm_backend_zellij_pane_for_tab() { # <session> <tab_id>
268
+ local session=$1 tab_id=$2
269
+ fm_backend_zellij_cli "$session" action list-panes --json 2>/dev/null \
270
+ | jq -r --argjson t "$tab_id" '.[]? | select(.tab_id == $t and .is_plugin == false) | .id' 2>/dev/null | head -1
271
+ }
272
+
273
+ # fm_backend_zellij_tab_for_pane: the owning tab id for <pane_id> in
274
+ # <session>, the reverse lookup kill needs (meta stores only the pane in the
275
+ # target string; the tab id is looked up fresh rather than trusted stale,
276
+ # mirroring herdr's label-based, never-trust-a-stored-id recovery posture).
277
+ fm_backend_zellij_tab_for_pane() { # <session> <pane_id>
278
+ local session=$1 pane_id=$2
279
+ fm_backend_zellij_cli "$session" action list-panes --json 2>/dev/null \
280
+ | jq -r --argjson p "$pane_id" '.[]? | select(.id == $p and .is_plugin == false) | .tab_id' 2>/dev/null | head -1
281
+ }
282
+
283
+ fm_backend_zellij_pane_exists() { # <session> <pane_id>
284
+ local session=$1 pane_id=$2
285
+ fm_backend_zellij_cli "$session" action list-panes --json 2>/dev/null \
286
+ | jq -e --argjson p "$pane_id" '[.[]? | select(.id == $p and .is_plugin == false)] | length > 0' >/dev/null 2>&1
287
+ }
288
+
289
+ # fm_backend_zellij_tab_matches_label: does <tab_id> in <session> carry the
290
+ # tab name firstmate expects for the caller-facing task label <label>?
291
+ # Checks the home-scoped, tagged title first (fm_backend_zellij_scoped_title
292
+ # - what every NEW tab is created with), then falls back to the legacy
293
+ # untagged bare title (the plain <label>, e.g. "fm-<id>") for a tab created
294
+ # before this home-scoping change shipped - but ONLY when that bare name is
295
+ # not ambiguous: exactly one live tab in the whole session carries it. A bare
296
+ # name shared by 2+ live tabs (this home's own pre-migration tab plus, say, a
297
+ # same-named tab from a different firstmate home sharing this one zellij
298
+ # session) refuses rather than silently trusting whichever one happened to
299
+ # match - the migration posture documented in docs/zellij-backend.md
300
+ # "Home-scoped tab titles". One list-tabs call serves every check here (the
301
+ # scoped check, the bare check, and the ambiguity count all read the SAME
302
+ # already-fetched JSON), so a caller whose fake-CLI fixture supplies exactly
303
+ # one list-tabs response keeps working unchanged.
304
+ fm_backend_zellij_tab_matches_label() { # <session> <tab_id> <label>
305
+ local session=$1 tab_id=$2 label=$3 scoped tabs count
306
+ scoped=$(fm_backend_zellij_scoped_title "$label")
307
+ tabs=$(fm_backend_zellij_cli "$session" action list-tabs --json 2>/dev/null)
308
+ printf '%s' "$tabs" | jq -e --argjson t "$tab_id" --arg want "$scoped" \
309
+ '[.[]? | select(.tab_id == $t and .name == $want)] | length > 0' >/dev/null 2>&1 && return 0
310
+ printf '%s' "$tabs" | jq -e --argjson t "$tab_id" --arg want "$label" \
311
+ '[.[]? | select(.tab_id == $t and .name == $want)] | length > 0' >/dev/null 2>&1 || return 1
312
+ count=$(printf '%s' "$tabs" | jq -r --arg want "$label" '[.[]? | select(.name == $want)] | length' 2>/dev/null)
313
+ [ "$count" = "1" ]
314
+ }
315
+
316
+ # fm_backend_zellij_create_task: create the task's tab (one terminal pane) in
317
+ # <session>, refusing an existing <label>. Zellij does NOT enforce tab-name
318
+ # uniqueness itself (verified: two tabs can share a name), so the duplicate
319
+ # check is ours, mirroring both tmux's and herdr's adapters. The tab is
320
+ # always created with the home-scoped, tagged title
321
+ # (fm_backend_zellij_scoped_title), never the bare caller-facing label - see
322
+ # the file header's "Home-scoped tab titles" note.
323
+ #
324
+ # Focus-steal mitigation (verified real finding, no upstream suppression
325
+ # flag exists): `new-tab` unconditionally focuses the created tab for every
326
+ # attached client. Capture the previously-active tab id (if any) before
327
+ # creating, and restore it with `go-to-tab-by-id` afterward - verified to
328
+ # correctly move an attached client's view back and to be a safe, silent
329
+ # no-op when no client is attached (the common case: an unattended firstmate
330
+ # spawn). Best-effort: a failure to restore never fails the spawn.
331
+ #
332
+ # Echoes "<tab_id> <pane_id>" on success.
333
+ fm_backend_zellij_create_task() { # <session> <label> <cwd>
334
+ local session=$1 label=$2 cwd=$3 title tabs dup prev_active tab_id pane_id
335
+ fm_backend_zellij_session_exists "$session" || { echo "error: zellij session '$session' does not exist; run container_ensure first" >&2; return 1; }
336
+ title=$(fm_backend_zellij_scoped_title "$label")
337
+ tabs=$(fm_backend_zellij_cli "$session" action list-tabs --json 2>/dev/null)
338
+ dup=$(printf '%s' "$tabs" | jq -r --arg want "$title" '.[]? | select(.name == $want) | .tab_id' 2>/dev/null | head -1)
339
+ if [ -n "$dup" ]; then
340
+ echo "error: zellij tab '$title' already exists in session '$session'" >&2
341
+ return 1
342
+ fi
343
+ prev_active=$(printf '%s' "$tabs" | jq -r '.[]? | select(.active == true) | .tab_id' 2>/dev/null | head -1)
344
+ tab_id=$(fm_backend_zellij_cli "$session" action new-tab --cwd "$cwd" --name "$title" 2>/dev/null | tr -d '[:space:]')
345
+ case "$tab_id" in
346
+ ''|*[!0-9]*)
347
+ echo "error: zellij new-tab did not return a numeric tab id for '$title' (got '$tab_id'; session '$session' may not exist)" >&2
348
+ return 1
349
+ ;;
350
+ esac
351
+ pane_id=$(fm_backend_zellij_pane_for_tab "$session" "$tab_id")
352
+ if [ -z "$pane_id" ]; then
353
+ echo "error: could not find a terminal pane for zellij tab $tab_id (session '$session')" >&2
354
+ return 1
355
+ fi
356
+ if [ -n "$prev_active" ] && [ "$prev_active" != "$tab_id" ]; then
357
+ fm_backend_zellij_cli "$session" action go-to-tab-by-id "$prev_active" >/dev/null 2>&1 || true
358
+ fi
359
+ printf '%s %s' "$tab_id" "$pane_id"
360
+ }
361
+
362
+ # fm_backend_zellij_parse_target: split "<session>:<pane_id>" on the FIRST
363
+ # colon (the pane id is a bare integer with no embedded colon, so this is
364
+ # simpler than herdr's equivalent but kept structurally parallel). Sets
365
+ # FM_BACKEND_ZELLIJ_SESSION and FM_BACKEND_ZELLIJ_PANE for the caller.
366
+ fm_backend_zellij_parse_target() { # <target>
367
+ local target=$1
368
+ FM_BACKEND_ZELLIJ_SESSION=${target%%:*}
369
+ FM_BACKEND_ZELLIJ_PANE=${target#*:}
370
+ [ -n "$FM_BACKEND_ZELLIJ_SESSION" ] && [ -n "$FM_BACKEND_ZELLIJ_PANE" ] && [ "$FM_BACKEND_ZELLIJ_PANE" != "$target" ]
371
+ }
372
+
373
+ # fm_backend_zellij_target_ready: parse the target and verify its session and
374
+ # pane are alive. When the caller knows the owning firstmate task label, verify
375
+ # the pane belongs to that named tab before trusting the numeric pane id.
376
+ fm_backend_zellij_target_ready() { # <target> [expected-label]
377
+ local expected_label=${2:-} tab_id
378
+ fm_backend_zellij_parse_target "$1" || return 1
379
+ fm_backend_zellij_session_exists "$FM_BACKEND_ZELLIJ_SESSION" || return 1
380
+ if [ -n "$expected_label" ]; then
381
+ tab_id=$(fm_backend_zellij_tab_for_pane "$FM_BACKEND_ZELLIJ_SESSION" "$FM_BACKEND_ZELLIJ_PANE" 2>/dev/null)
382
+ [ -n "$tab_id" ] || return 1
383
+ fm_backend_zellij_tab_matches_label "$FM_BACKEND_ZELLIJ_SESSION" "$tab_id" "$expected_label"
384
+ return $?
385
+ fi
386
+ fm_backend_zellij_pane_exists "$FM_BACKEND_ZELLIJ_SESSION" "$FM_BACKEND_ZELLIJ_PANE"
387
+ }
388
+
389
+ # fm_backend_zellij_current_path: the live pane's cwd, or empty on any error.
390
+ # Mirrors tmux's pane_current_path poll used for worktree-path discovery after
391
+ # `treehouse get`.
392
+ #
393
+ # Verified pitfall (docs/zellij-backend.md "Worktree-path discovery: pane_cwd
394
+ # does not track a subshell"): `list-panes --json`'s `pane_cwd` DOES reflect a
395
+ # `cd` run directly in the pane's own top-level shell, but stays FROZEN at
396
+ # whatever directory the pane's shell was in when it launched `treehouse get`
397
+ # as a foreground command - it never follows that command's own internal `cd`
398
+ # into the acquired worktree, even after the subshell is fully interactive and
399
+ # a `pwd` typed into it prints the correct live path on screen. Zellij's CLI
400
+ # exposes no per-pane pid and no live-process cwd field to read instead
401
+ # (unlike herdr's `foreground_cwd`), so passive JSON polling cannot solve
402
+ # this. Active probe instead: print the pane's `$PWD` with a unique marker
403
+ # (atomically submitted, mirroring send_text_line), briefly settle, then capture
404
+ # and read only that marker line. Scoped to fm-spawn.sh's own worktree-discovery
405
+ # poll loop (the only caller of this op), where injecting a harmless extra
406
+ # command before the harness ever launches is an acceptable trade for a reliable
407
+ # answer.
408
+ fm_backend_zellij_current_path() { # <target> [expected-label]
409
+ local target=$1 expected_label=${2:-} out line marker_begin="__FM_ZELLIJ_CWD_BEGIN__" marker_end="__FM_ZELLIJ_CWD_END__" in_block=0 chunk="" last=""
410
+ fm_backend_zellij_target_ready "$target" "$expected_label" || return 0
411
+ fm_backend_zellij_send_text_line "$target" "printf '%s\n' '$marker_begin'; pwd; printf '%s\n' '$marker_end'" "$expected_label" || return 0
412
+ sleep 0.3
413
+ out=$(fm_backend_zellij_capture "$target" 200 "$expected_label") || return 0
414
+ while IFS= read -r line; do
415
+ if [ "$line" = "$marker_begin" ]; then
416
+ in_block=1
417
+ chunk=""
418
+ continue
419
+ fi
420
+ if [ "$line" = "$marker_end" ]; then
421
+ case "$chunk" in /*) last=$chunk ;; esac
422
+ in_block=0
423
+ continue
424
+ fi
425
+ [ "$in_block" -eq 1 ] && chunk="$chunk$line"
426
+ done <<EOF
427
+ $out
428
+ EOF
429
+ printf '%s' "$last"
430
+ }
431
+
432
+ # fm_backend_zellij_send_literal: send TEXT as literal, UNSUBMITTED input via
433
+ # bracketed paste - the caller sends Enter separately. Mirrors tmux's
434
+ # `send-keys -t T -l text` / herdr's `pane send-text`. Verified: `action
435
+ # paste` does NOT auto-submit and uses bracketed paste mode (the report's
436
+ # recommendation over write-chars, for popup-safety parity with tmux/herdr).
437
+ fm_backend_zellij_send_literal() { # <target> <text> [expected-label]
438
+ fm_backend_zellij_target_ready "$1" "${3:-}" || return 1
439
+ fm_backend_zellij_cli "$FM_BACKEND_ZELLIJ_SESSION" action paste --pane-id "$FM_BACKEND_ZELLIJ_PANE" -- "$2" >/dev/null 2>&1
440
+ }
441
+
442
+ # fm_backend_zellij_normalize_key: map firstmate's key vocabulary (Enter,
443
+ # Escape, C-c, as used by fm-send.sh --key and stuck-crewmate-recovery) onto
444
+ # zellij's verified `action send-keys` names. Verified empirically: "Enter"
445
+ # and "Esc" work; "Escape" and "escape" are REJECTED ("Invalid key"); Ctrl-C
446
+ # must be the single argument "Ctrl c" (a space-separated two-word key
447
+ # expression passed as ONE shell arg) - "C-c", "Ctrl+c", and two separate argv
448
+ # words all fail.
449
+ fm_backend_zellij_normalize_key() { # <key>
450
+ case "$1" in
451
+ Enter|enter) printf 'Enter' ;;
452
+ Escape|escape|Esc|esc) printf 'Esc' ;;
453
+ C-c|c-c|ctrl+c|Ctrl+c|Ctrl+C|'Ctrl c'|'ctrl c') printf 'Ctrl c' ;;
454
+ # C-u clears a composer line. fm-send.sh's muse interrupt path needs it to
455
+ # drop the prompt muse restores into the composer after Escape.
456
+ C-u|c-u|ctrl+u|Ctrl+u|Ctrl+U|'Ctrl u'|'ctrl u') printf 'Ctrl u' ;;
457
+ *) printf '%s' "$1" ;;
458
+ esac
459
+ }
460
+
461
+ # fm_backend_zellij_send_key: one named special key, targeted at the pane by
462
+ # its EXPLICIT --pane-id (never the ambient "focused pane" default - verified
463
+ # unreliable, see file header). Mirrors fm-send.sh's --key path.
464
+ fm_backend_zellij_send_key() { # <target> <key> [expected-label]
465
+ fm_backend_zellij_target_ready "$1" "${3:-}" || return 1
466
+ local key
467
+ key=$(fm_backend_zellij_normalize_key "$2")
468
+ fm_backend_zellij_cli "$FM_BACKEND_ZELLIJ_SESSION" action send-keys --pane-id "$FM_BACKEND_ZELLIJ_PANE" "$key" >/dev/null 2>&1
469
+ }
470
+
471
+ # fm_backend_zellij_send_text_line: send one line of TEXT then submit.
472
+ fm_backend_zellij_send_text_line() { # <target> <text> [expected-label]
473
+ fm_backend_zellij_send_literal "$1" "$2" "${3:-}" || return 1
474
+ fm_backend_zellij_send_key "$1" Enter "${3:-}" && return 0
475
+ fm_backend_zellij_send_key "$1" C-c "${3:-}" >/dev/null 2>&1 && return 1
476
+ return 2
477
+ }
478
+
479
+ # fm_backend_zellij_capture: bounded plain-text pane capture. Mirrors
480
+ # fm-peek.sh's/fm-watch.sh's `tmux capture-pane -p -t T -S -N`. `dump-screen`
481
+ # has no --lines bound, so routine 40-line-or-smaller reads use the current
482
+ # viewport and larger explicit reads use --full scrollback, then trim locally.
483
+ # On a very short viewport, a small read can see fewer than the requested lines.
484
+ fm_backend_zellij_capture() { # <target> <lines> [expected-label]
485
+ fm_backend_zellij_target_ready "$1" "${3:-}" || return 1
486
+ local lines=${2:-40} out
487
+ case "$lines" in ''|*[!0-9]*) lines=40 ;; esac
488
+ if [ "$lines" -le 40 ]; then
489
+ out=$(fm_backend_zellij_cli "$FM_BACKEND_ZELLIJ_SESSION" action dump-screen --pane-id "$FM_BACKEND_ZELLIJ_PANE" 2>/dev/null) || return 1
490
+ else
491
+ out=$(fm_backend_zellij_cli "$FM_BACKEND_ZELLIJ_SESSION" action dump-screen --pane-id "$FM_BACKEND_ZELLIJ_PANE" --full 2>/dev/null) || return 1
492
+ fi
493
+ printf '%s' "$out" | tail -n "$lines"
494
+ }
495
+
496
+ # --- zellij composer capture and capability primitives ----------------------
497
+ #
498
+ # `zellij action dump-screen --ansi` ("Preserve ANSI styling in the dump
499
+ # output", verified live at zellij 0.44.0 against real Claude Code) gives
500
+ # zellij a styled capture, so the shared classifier reads its composer with
501
+ # the same ghost-stripping confidence as tmux and herdr. Every shape lives in
502
+ # the shared owner (bin/fm-composer-lib.sh, fm_composer_classify_screen);
503
+ # this adapter contributes only the capture and its capability facts.
504
+
505
+ # fm_backend_zellij_composer_capture: bounded styled tail of the pane. When
506
+ # --ansi is unsupported (an older zellij), the caller falls back to the plain
507
+ # dump and a styled=0 descriptor - see fm_backend_zellij_composer_state.
508
+ fm_backend_zellij_composer_capture() { # <target> [expected-label]
509
+ fm_backend_zellij_target_ready "$1" "${2:-}" || return 1
510
+ local out
511
+ out=$(fm_backend_zellij_cli "$FM_BACKEND_ZELLIJ_SESSION" action dump-screen --pane-id "$FM_BACKEND_ZELLIJ_PANE" --ansi 2>/dev/null) || return 1
512
+ [ -n "$out" ] || return 1
513
+ printf '%s' "$out" | tail -n "$FM_COMPOSER_CAPTURE_LINES"
514
+ }
515
+
516
+ # fm_backend_zellij_composer_state: thin adapter - capture plus capabilities
517
+ # in, shared verdict out. This replaced the content-diff submit heuristic
518
+ # that was the fleet's only FALSE-POSITIVE delivery confirmation: a pane
519
+ # whose content changed for any reason (a spinner, streaming output, a
520
+ # clock) read as "submitted", which could close a --resolve-key decision for
521
+ # a message the crew never received. A dead pane still fails safe here: the
522
+ # unconditional-exit-0 CLI quirk (file header) yields an empty dump, which
523
+ # classifies unknown - never a confirmation.
524
+ fm_backend_zellij_composer_state() { # <target> [expected-label] -> empty|pending|pending-unproven|unknown
525
+ local target=$1 expected_label=${2:-} cap caps verdict
526
+ if cap=$(fm_backend_zellij_composer_capture "$target" "$expected_label"); then
527
+ caps=$(printf 'styled=1\ncursor=0\nidentity=0\nrows=%s' "$FM_COMPOSER_CAPTURE_LINES")
528
+ elif cap=$(fm_backend_zellij_capture "$target" "$FM_COMPOSER_CAPTURE_LINES" "$expected_label") && [ -n "$cap" ]; then
529
+ caps=$(printf 'styled=0\ncursor=0\nidentity=0\nrows=%s' "$FM_COMPOSER_CAPTURE_LINES")
530
+ else
531
+ printf 'unknown'
532
+ return 0
533
+ fi
534
+ verdict=$(fm_composer_classify_screen "$caps" "$cap")
535
+ [ "$verdict" != need-identity ] || verdict=unknown
536
+ printf '%s' "$verdict"
537
+ }
538
+
539
+ fm_backend_zellij_composer_content() { # <target> [expected-label]
540
+ local target=$1 expected_label=${2:-} cap caps
541
+ cap=$(fm_backend_zellij_composer_capture "$target" "$expected_label") || return 1
542
+ caps=$(printf 'styled=1\ncursor=0\nidentity=0\nrows=%s' "$FM_COMPOSER_CAPTURE_LINES")
543
+ fm_composer_extract_selected_content "$caps" "$cap"
544
+ }
545
+
546
+ fm_backend_zellij_composer_observed_append() { # <target> <before> <text> [expected-label]
547
+ local target=$1 before=$2 text=$3 expected_label=${4:-} cap caps after expected
548
+ [ -n "$text" ] || return 1
549
+ cap=$(fm_backend_zellij_composer_capture "$target" "$expected_label") || return 1
550
+ caps=$(printf 'styled=1\ncursor=0\nidentity=0\nrows=%s' "$FM_COMPOSER_CAPTURE_LINES")
551
+ after=$(fm_composer_extract_selected_content "$caps" "$cap") || return 1
552
+ fm_composer_normalize_spaces_var before
553
+ fm_composer_normalize_spaces_var text
554
+ fm_composer_normalize_spaces_var after
555
+ before=${before//[$' \t\r\n\v\f']/}
556
+ text=${text//[$' \t\r\n\v\f']/}
557
+ after=${after//[$' \t\r\n\v\f']/}
558
+ [ -n "$text" ] || return 1
559
+ expected=$before$text
560
+ [ "$after" = "$expected" ]
561
+ }
562
+
563
+ # fm_backend_zellij_send_text_submit: type <text> into <target> once (raw,
564
+ # unsubmitted, via send_literal), then drive the shared verify-and-retry-Enter
565
+ # loop (bin/fm-composer-lib.sh: fm_composer_submit_retry_core) against the
566
+ # real composer verdict above. Echoes empty|pending|unknown|send-failed, a
567
+ # subset of the proof-carrying submit vocabulary. Only a positively classified
568
+ # empty composer confirms delivery - a pane that merely CHANGED does not, so
569
+ # the old heuristic's false "delivery confirmed" cannot recur.
570
+ fm_backend_zellij_send_text_submit() { # <target> <text> <retries> <enter-sleep> <settle> [expected-label]
571
+ local target=$1 text=$2 retries=$3 sleep_s=$4 settle=$5 expected_label=${6:-} before
572
+ before=$(fm_backend_zellij_composer_content "$target" "$expected_label") \
573
+ || { printf 'send-failed'; return 0; }
574
+ fm_backend_zellij_send_literal "$target" "$text" "$expected_label" || { printf 'send-failed'; return 0; }
575
+ sleep "$settle"
576
+ fm_backend_zellij_composer_observed_append "$target" "$before" "$text" "$expected_label" \
577
+ || { printf 'send-failed'; return 0; }
578
+ fm_composer_submit_retry_core fm_backend_zellij_send_key fm_backend_zellij_composer_state \
579
+ "$target" "$retries" "$sleep_s" "$expected_label"
580
+ }
581
+
582
+ # fm_backend_zellij_kill: remove the task's tab, best-effort (mirrors
583
+ # tmux-kill-window's/herdr-pane-close's `|| true` contract). Verified: unlike
584
+ # herdr, closing a zellij tab's only PANE does NOT close the tab itself (an
585
+ # empty tab survives in list-tabs); `close-tab-by-id` on a live tab DOES
586
+ # cleanly remove both the pane and the tab in one call, verified to need no
587
+ # separate pane-close first. The owning tab id is looked up fresh from the
588
+ # pane id when possible via fm_backend_zellij_tab_for_pane; teardown also
589
+ # passes the recorded tab id and expected tab label for already-empty ghost
590
+ # tabs. Any tab id is verified against the expected label when one is provided.
591
+ fm_backend_zellij_kill() { # <target> [tab_id] [expected_label]
592
+ fm_backend_zellij_parse_target "$1" || return 0
593
+ fm_backend_zellij_session_exists "$FM_BACKEND_ZELLIJ_SESSION" || return 0
594
+ local tab_id fallback_tab_id=${2:-} expected_label=${3:-}
595
+ tab_id=$(fm_backend_zellij_tab_for_pane "$FM_BACKEND_ZELLIJ_SESSION" "$FM_BACKEND_ZELLIJ_PANE" 2>/dev/null)
596
+ if [ -n "$tab_id" ] && [ -n "$expected_label" ] && ! fm_backend_zellij_tab_matches_label "$FM_BACKEND_ZELLIJ_SESSION" "$tab_id" "$expected_label"; then
597
+ tab_id=
598
+ fi
599
+ case "$fallback_tab_id" in
600
+ ''|*[!0-9]*) ;;
601
+ *)
602
+ if [ -z "$tab_id" ]; then
603
+ if [ -z "$expected_label" ] || fm_backend_zellij_tab_matches_label "$FM_BACKEND_ZELLIJ_SESSION" "$fallback_tab_id" "$expected_label"; then
604
+ tab_id=$fallback_tab_id
605
+ fi
606
+ fi
607
+ ;;
608
+ esac
609
+ if [ -n "$tab_id" ]; then
610
+ fm_backend_zellij_cli "$FM_BACKEND_ZELLIJ_SESSION" action close-tab-by-id "$tab_id" >/dev/null 2>&1 || true
611
+ elif [ -z "$expected_label" ]; then
612
+ fm_backend_zellij_cli "$FM_BACKEND_ZELLIJ_SESSION" action close-pane --pane-id "$FM_BACKEND_ZELLIJ_PANE" >/dev/null 2>&1 || true
613
+ fi
614
+ }
615
+
616
+ # fm_backend_zellij_list_live: recovery/orphan discovery. Lists every tab in
617
+ # <session> whose title carries THIS firstmate home's own tag
618
+ # (fm-<hometag>-, fm_backend_zellij_home_label) - never any other home's
619
+ # tagged tabs, and never a bare untagged "fm-<id>" tab either (this sweep
620
+ # deliberately does NOT attempt the legacy-bare-title fallback
621
+ # fm_backend_zellij_tab_matches_label uses for a single already-known tab:
622
+ # telling apart "our own pre-migration tab" from "another home's same-shaped
623
+ # bare title" in a bulk, no-numeric-id-in-hand sweep is not something this
624
+ # adapter can do safely - see docs/zellij-backend.md "Home-scoped tab
625
+ # titles"). A pre-migration task is still reachable through its recorded
626
+ # window= meta, which target_ready/kill DO accept via that bare-title
627
+ # fallback. One "<session>:<pane_id>\t<plain fm-<id> label>" line per live,
628
+ # in-home task tab (the home tag is stripped back off before printing, so
629
+ # callers see the same plain label they always have). Read-only: a session
630
+ # that does not exist yet simply lists nothing.
631
+ fm_backend_zellij_list_live() { # <session>
632
+ local session=$1 home prefix tabs tab_id name pane_id plain
633
+ fm_backend_zellij_session_exists "$session" || return 0
634
+ home=$(fm_backend_zellij_home_label)
635
+ prefix="fm-$home-"
636
+ tabs=$(fm_backend_zellij_cli "$session" action list-tabs --json 2>/dev/null) || return 0
637
+ while IFS=$'\t' read -r tab_id name; do
638
+ [ -n "$tab_id" ] || continue
639
+ plain=${name#"$prefix"}
640
+ [ -n "$plain" ] || continue
641
+ pane_id=$(fm_backend_zellij_pane_for_tab "$session" "$tab_id") || continue
642
+ [ -n "$pane_id" ] || continue
643
+ printf '%s:%s\tfm-%s\n' "$session" "$pane_id" "$plain"
644
+ done < <(printf '%s' "$tabs" | jq -r --arg prefix "$prefix" '.[]? | select(.name | startswith($prefix)) | "\(.tab_id)\t\(.name)"' 2>/dev/null)
645
+ }
646
+
647
+ # fm_backend_zellij_resolve_bare_selector: the live-tab-listing fallback for
648
+ # an ad hoc selector with no meta (mirrors tmux's list-windows grep and
649
+ # herdr's equivalent). Searches every active zellij session for a tab
650
+ # matching <name>: the home-scoped, tagged title first
651
+ # (fm_backend_zellij_scoped_title), then falls back to an exact bare <name>
652
+ # match ONLY when unambiguous - exactly one tab across every active session
653
+ # carries it - mirroring fm_backend_zellij_tab_matches_label's migration
654
+ # posture. Rare path in practice (zellij tasks normally carry meta);
655
+ # best-effort. Not wired into fm_backend_resolve_selector's dispatcher
656
+ # (bin/fm-backend.sh), mirroring herdr: that bare-selector fallback stays
657
+ # tmux-only by design, and zellij/herdr tasks are targeted via task-selector
658
+ # meta or an explicit recorded target.
659
+ fm_backend_zellij_resolve_bare_selector() { # <name>
660
+ local name=$1 scoped sessions session tabs tab_id count=0 pane_id bare_session='' bare_tab_id=''
661
+ scoped=$(fm_backend_zellij_scoped_title "$name")
662
+ sessions=$(zellij list-sessions --short --no-formatting 2>/dev/null)
663
+ while IFS= read -r session; do
664
+ [ -n "$session" ] || continue
665
+ tabs=$(fm_backend_zellij_cli "$session" action list-tabs --json 2>/dev/null)
666
+ tab_id=$(printf '%s' "$tabs" | jq -r --arg want "$scoped" '.[]? | select(.name == $want) | .tab_id' 2>/dev/null | head -1)
667
+ [ -n "$tab_id" ] || continue
668
+ pane_id=$(fm_backend_zellij_pane_for_tab "$session" "$tab_id") || continue
669
+ [ -n "$pane_id" ] || continue
670
+ printf '%s:%s' "$session" "$pane_id"
671
+ return 0
672
+ done <<EOF
673
+ $sessions
674
+ EOF
675
+ while IFS= read -r session; do
676
+ [ -n "$session" ] || continue
677
+ tabs=$(fm_backend_zellij_cli "$session" action list-tabs --json 2>/dev/null)
678
+ while IFS= read -r tab_id; do
679
+ [ -n "$tab_id" ] || continue
680
+ count=$((count + 1))
681
+ if [ "$count" -eq 1 ]; then
682
+ bare_session=$session
683
+ bare_tab_id=$tab_id
684
+ fi
685
+ done <<EOF_MATCHES
686
+ $(printf '%s' "$tabs" | jq -r --arg want "$name" '.[]? | select(.name == $want) | .tab_id' 2>/dev/null)
687
+ EOF_MATCHES
688
+ done <<EOF
689
+ $sessions
690
+ EOF
691
+ if [ "$count" -eq 1 ]; then
692
+ pane_id=$(fm_backend_zellij_pane_for_tab "$bare_session" "$bare_tab_id") || pane_id=
693
+ if [ -n "$pane_id" ]; then
694
+ printf '%s:%s' "$bare_session" "$pane_id"
695
+ return 0
696
+ fi
697
+ fi
698
+ echo "error: no zellij tab named $name in any active session" >&2
699
+ return 1
700
+ }