@zerwiz/ymir 0.1.3 → 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 -39
  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,1357 @@
1
+ #!/usr/bin/env bash
2
+ # tests/fm-backend-orca.test.sh - fake-Orca-CLI unit tests for the Orca
3
+ # terminal adapter primitives in bin/backends/orca.sh.
4
+ set -u
5
+
6
+ # shellcheck source=tests/lib.sh
7
+ . "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
8
+
9
+ TMP_ROOT=$(fm_test_tmproot fm-backend-orca-tests)
10
+
11
+ make_orca_fakebin() { # <dir> -> echoes fakebin dir
12
+ local fb="$1/fakebin"
13
+ mkdir -p "$fb"
14
+ cat > "$fb/orca" <<'SH'
15
+ #!/usr/bin/env bash
16
+ set -u
17
+ LOG="${FM_ORCA_LOG:?}"
18
+ RESP="${FM_ORCA_RESPONSES:?}"
19
+ COUNT_FILE="$RESP/.count"
20
+ next=$(( $(cat "$COUNT_FILE" 2>/dev/null || echo 0) + 1 ))
21
+ {
22
+ printf 'orca'
23
+ for a in "$@"; do printf '\x1f%s' "$a"; done
24
+ printf '\n'
25
+ } >> "$LOG"
26
+ if [ "${1:-}" = status ] && [ "${FM_ORCA_STATUS_RESPONSE:-ready}" != sequence ]; then
27
+ printf '{"ok":true,"result":{"runtime":{"reachable":true,"state":"ready"}}}\n'
28
+ exit 0
29
+ fi
30
+ n=$next
31
+ echo "$n" > "$COUNT_FILE"
32
+ if [ -f "$RESP/$n.exit" ]; then
33
+ exit "$(cat "$RESP/$n.exit")"
34
+ fi
35
+ [ -f "$RESP/$n.out" ] && cat "$RESP/$n.out"
36
+ exit 0
37
+ SH
38
+ chmod +x "$fb/orca"
39
+ printf '%s\n' "$fb"
40
+ }
41
+
42
+ orca_case() { # <name> -> sets CASE_DIR LOG RESP FB
43
+ CASE_DIR="$TMP_ROOT/$1"
44
+ mkdir -p "$CASE_DIR/responses"
45
+ LOG="$CASE_DIR/log"
46
+ RESP="$CASE_DIR/responses"
47
+ : > "$LOG"
48
+ FB=$(make_orca_fakebin "$CASE_DIR")
49
+ }
50
+
51
+ neutral_fm_root() { # <dir> -> echoes a minimal root with a quiet guard
52
+ local root="$1/root"
53
+ mkdir -p "$root/bin"
54
+ cat > "$root/bin/fm-guard.sh" <<'SH'
55
+ #!/usr/bin/env bash
56
+ exit 0
57
+ SH
58
+ chmod +x "$root/bin/fm-guard.sh"
59
+ printf '%s\n' "$root"
60
+ }
61
+
62
+ add_tmux_fake() {
63
+ local fb=$1
64
+ cat > "$fb/tmux" <<'SH'
65
+ #!/usr/bin/env bash
66
+ set -u
67
+ LOG="${FM_ORCA_LOG:?}"
68
+ {
69
+ printf 'tmux'
70
+ for a in "$@"; do printf '\x1f%s' "$a"; done
71
+ printf '\n'
72
+ } >> "$LOG"
73
+ exit 0
74
+ SH
75
+ chmod +x "$fb/tmux"
76
+ }
77
+
78
+ test_capture_reads_terminal_tail_json() {
79
+ local out
80
+ orca_case capture-tail
81
+ printf '{"result":{"terminal":{"tail":["line one","line two"]}}}\n' > "$RESP/1.out"
82
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
83
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_capture term-123 40' "$ROOT" )
84
+ [ "$out" = $'line one\nline two' ] || fail "capture should print result.terminal.tail joined by newlines, got '$out'"
85
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''read'$'\x1f''--terminal'$'\x1f''term-123'$'\x1f''--limit'$'\x1f''40'$'\x1f''--json' \
86
+ "capture did not call orca terminal read with terminal/limit/json"
87
+ pass "fm_backend_orca_capture: parses result.terminal.tail and calls terminal read"
88
+ }
89
+
90
+ test_capture_falls_back_to_text_fields() {
91
+ local out
92
+ orca_case capture-text
93
+ printf '{"result":{"text":"plain text output"}}\n' > "$RESP/1.out"
94
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
95
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_capture term-abc 5' "$ROOT" )
96
+ [ "$out" = "plain text output" ] || fail "capture should fall back to result.text, got '$out'"
97
+ pass "fm_backend_orca_capture: falls back to result text fields"
98
+ }
99
+
100
+ test_capture_fails_on_orca_error_json() {
101
+ local out status
102
+ orca_case capture-error-json
103
+ printf '{"ok":false,"error":{"code":"terminal_handle_stale","message":"terminal handle stale"}}\n' > "$RESP/1.out"
104
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
105
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_capture term-stale 5' "$ROOT" 2>&1 )
106
+ status=$?
107
+ [ "$status" -ne 0 ] || fail "capture should fail on Orca ok:false read JSON"
108
+ assert_contains "$out" "terminal handle stale" "capture should surface the Orca read error message"
109
+ pass "fm_backend_orca_capture: fails closed on Orca read error JSON"
110
+ }
111
+
112
+ test_runtime_check_accepts_ready_orca_status() {
113
+ local out
114
+ orca_case runtime-ready
115
+ printf '{"ok":true,"result":{"runtime":{"reachable":true,"state":"ready"}}}\n' > "$RESP/1.out"
116
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" FM_ORCA_STATUS_RESPONSE=sequence \
117
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_runtime_check' "$ROOT" )
118
+ [ -z "$out" ] || fail "runtime_check should be quiet on ready status, got '$out'"
119
+ assert_contains "$(cat "$LOG")" $'orca\x1f''status'$'\x1f''--json' \
120
+ "runtime_check did not call orca status --json"
121
+ pass "fm_backend_orca_runtime_check: accepts reachable ready runtime"
122
+ }
123
+
124
+ test_runtime_check_refuses_unready_orca_status() {
125
+ local out status
126
+ orca_case runtime-unready
127
+ printf '{"ok":true,"result":{"runtime":{"reachable":false,"state":"starting"}}}\n' > "$RESP/1.out"
128
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" FM_ORCA_STATUS_RESPONSE=sequence \
129
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_runtime_check' "$ROOT" 2>&1 )
130
+ status=$?
131
+ [ "$status" -ne 0 ] || fail "runtime_check should fail when Orca runtime is not ready"
132
+ assert_contains "$out" "requires a ready Orca runtime" "runtime_check should explain the readiness requirement"
133
+ pass "fm_backend_orca_runtime_check: fails closed when runtime is not ready"
134
+ }
135
+
136
+ test_send_text_submit_verifies_empty_composer_after_enter() {
137
+ local out
138
+ orca_case send-submit
139
+ printf '{"ok":true,"result":{"send":{"handle":"term-123","accepted":true}}}\n' > "$RESP/1.out"
140
+ printf '{"ok":true,"result":{"send":{"handle":"term-123","accepted":true}}}\n' > "$RESP/2.out"
141
+ printf '{"ok":true,"result":{"terminal":{"tail":["╭───╮","│ > │","╰───╯"]}}}\n' > "$RESP/3.out"
142
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
143
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_send_text_submit term-123 "hello captain" 3 0.01 0.01' "$ROOT" )
144
+ [ "$out" = empty ] || fail "send_text_submit should report empty on successful Orca send, got '$out'"
145
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''send'$'\x1f''--terminal'$'\x1f''term-123'$'\x1f''--text'$'\x1f''hello captain'$'\x1f''--json' \
146
+ "send_text_submit did not type the text literally before Enter"
147
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''send'$'\x1f''--terminal'$'\x1f''term-123'$'\x1f''--text'$'\x1f\x1f''--enter'$'\x1f''--json' \
148
+ "send_text_submit did not send Enter after typing"
149
+ # The composer read is ONE bounded tail read: the old backward paging
150
+ # (--cursor follow-ups on a limited page) is deleted, because paging into
151
+ # scrollback is what let a stale startup banner compete with the live
152
+ # composer (audit fm-composer-consolidation-audit-s1, section 3.3).
153
+ assert_not_contains "$(cat "$LOG")" $'\x1f''--cursor'$'\x1f' \
154
+ "the composer read must never page backward into scrollback"
155
+ pass "fm_backend_orca_send_text_submit: verifies empty composer after Enter with one bounded read"
156
+ }
157
+
158
+ test_send_text_submit_borderless_claude_confirms() {
159
+ # The #2029 analogue this adapter never received: a borderless claude
160
+ # composer (bare `❯` row between horizontal rules) must confirm a submit.
161
+ # Before consolidation orca knew only the bordered shape, so every steer to
162
+ # a borderless harness exited unconfirmed and --resolve-key never closed.
163
+ local out
164
+ orca_case send-submit-borderless
165
+ printf '{"ok":true,"result":{"send":{"handle":"term-123","accepted":true}}}\n' > "$RESP/1.out"
166
+ printf '{"ok":true,"result":{"send":{"handle":"term-123","accepted":true}}}\n' > "$RESP/2.out"
167
+ printf '{"ok":true,"result":{"terminal":{"tail":["────────────────","❯","────────────────"]}}}\n' > "$RESP/3.out"
168
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
169
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_send_text_submit term-123 "hello captain" 3 0.01 0.01' "$ROOT" )
170
+ [ "$out" = empty ] || fail "a borderless claude composer should confirm the submit, got '$out'"
171
+ pass "fm_backend_orca_send_text_submit: a borderless claude composer confirms delivery (the missing #2029 shape)"
172
+ }
173
+
174
+ test_composer_state_stale_banner_never_wins() {
175
+ # The audit's confidently-wrong case (section 3.3): codex's startup banner
176
+ # (`│ permissions: YOLO mode │` inside a rounded box) classified as the
177
+ # composer, reading `pending` for a row that is not a composer at all. With
178
+ # the full shape catalogue the live bare row below the banner wins; with a
179
+ # plain capture its trailing hint text is unreadable, so the verdict is
180
+ # `unknown` (defer) - never the banner's false `pending`.
181
+ local out
182
+ orca_case composer-stale-banner
183
+ printf '{"ok":true,"result":{"terminal":{"tail":["╭────────────────────────╮","│ permissions: YOLO mode │","╰────────────────────────╯","› Use /skills to list available skills"]}}}\n' > "$RESP/1.out"
184
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
185
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_composer_state term-123' "$ROOT" )
186
+ [ "$out" != pending ] || fail "a stale startup banner must never classify as pending composer text"
187
+ [ "$out" = unknown ] || fail "the plain-capture codex hint should defer as unknown, got '$out'"
188
+ pass "fm_backend_orca_composer_state: a stale startup banner cannot outrank the live composer row"
189
+ }
190
+
191
+ test_send_text_submit_retries_when_composer_stays_pending() {
192
+ local out log_text enter_count
193
+ orca_case send-submit-pending
194
+ printf '{"ok":true,"result":{"send":{"handle":"term-123","accepted":true}}}\n' > "$RESP/1.out"
195
+ printf '{"ok":true,"result":{"send":{"handle":"term-123","accepted":true}}}\n' > "$RESP/2.out"
196
+ printf '{"ok":true,"result":{"terminal":{"tail":["╭─────────────────╮","│ > hello captain │","╰─────────────────╯"]}}}\n' > "$RESP/3.out"
197
+ printf '{"ok":true,"result":{"send":{"handle":"term-123","accepted":true}}}\n' > "$RESP/4.out"
198
+ printf '{"ok":true,"result":{"terminal":{"tail":["╭─────────────────╮","│ > │","╰─────────────────╯"]}}}\n' > "$RESP/5.out"
199
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
200
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_send_text_submit term-123 "hello captain" 3 0.01 0.01' "$ROOT" )
201
+ [ "$out" = empty ] || fail "send_text_submit should retry Enter until the composer clears, got '$out'"
202
+ log_text=$(cat "$LOG")
203
+ enter_count=$(printf '%s\n' "$log_text" | grep -c $'orca\x1fterminal\x1fsend\x1f--terminal\x1fterm-123\x1f--text\x1f\x1f--enter\x1f--json')
204
+ [ "$enter_count" -eq 2 ] || fail "send_text_submit should send Enter twice when the first read is pending, got $enter_count"
205
+ pass "fm_backend_orca_send_text_submit: retries Enter while composer remains pending"
206
+ }
207
+
208
+ test_composer_state_popup_placeholder_fill_is_pending() {
209
+ local out
210
+ orca_case composer-popup-placeholder
211
+ printf '{"ok":true,"result":{"terminal":{"tail":[" ╭──────────────────────────────────────╮"," │ ❯ /compact compaction instructions │"," ╰──────────────── Composer ────────────╯",""," Enter:send"]}}}\n' > "$RESP/1.out"
212
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
213
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_composer_state term-123' "$ROOT" )
214
+ [ "$out" = pending ] || fail "a popup-close-with-placeholder-fill must still read as pending (not yet submitted), got '$out'"
215
+ pass "fm_backend_orca_composer_state: a slash-command popup's argument-hint placeholder still reads pending"
216
+ }
217
+
218
+ # Dead-shell injection safety (task fm-composer-shellglyph-safety): a pane whose
219
+ # agent has exited to a bare login shell has no bordered composer row, so the
220
+ # classifier finds nothing and reports `unknown` - NOT a safe (empty) injection
221
+ # target. Covers the same guarantee herdr/cmux/tmux tests pin for their backends.
222
+ test_composer_state_bare_shell_prompt_is_unknown() {
223
+ local out
224
+ orca_case composer-bare-shell
225
+ printf '{"ok":true,"result":{"terminal":{"tail":["some earlier output","kunchen@mac firstmate $ "]}}}\n' > "$RESP/1.out"
226
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
227
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_composer_state term-123' "$ROOT" )
228
+ [ "$out" = unknown ] || fail "a bare dead-shell prompt (no bordered composer row) must read unknown, got '$out'"
229
+ pass "fm_backend_orca_composer_state: a bare dead-shell prompt reads unknown (unsafe-for-injection), never empty"
230
+ }
231
+
232
+ test_send_text_submit_popup_autocomplete_requires_second_enter() {
233
+ local out log_text enter_count
234
+ orca_case send-submit-popup-autocomplete
235
+ # 1: literal send "/compact"
236
+ # 2: Enter #1 closes the popup and fills the placeholder
237
+ # 3: read - composer still holds real pending text
238
+ printf '{"ok":true,"result":{"send":{"handle":"term-123","accepted":true}}}\n' > "$RESP/1.out"
239
+ printf '{"ok":true,"result":{"send":{"handle":"term-123","accepted":true}}}\n' > "$RESP/2.out"
240
+ printf '{"ok":true,"result":{"terminal":{"tail":[" ╭──────────────────────────────────────╮"," │ ❯ /compact compaction instructions │"," ╰──────────────── Composer ────────────╯",""," Enter:send"]}}}\n' > "$RESP/3.out"
241
+ # 4: Enter #2 actually submits
242
+ # 5: read - composer is empty
243
+ printf '{"ok":true,"result":{"send":{"handle":"term-123","accepted":true}}}\n' > "$RESP/4.out"
244
+ printf '{"ok":true,"result":{"terminal":{"tail":[" ╭────────────────────────╮"," │ ❯ │"," ╰──────── Composer ──────╯",""," Shift+Tab:mode"]}}}\n' > "$RESP/5.out"
245
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
246
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_send_text_submit term-123 "/compact" 3 0.01 1.2' "$ROOT" )
247
+ [ "$out" = empty ] || fail "send_text_submit should eventually report empty once the SECOND Enter actually clears the composer, got '$out'"
248
+ log_text=$(cat "$LOG")
249
+ enter_count=$(printf '%s\n' "$log_text" | grep -c $'orca\x1fterminal\x1fsend\x1f--terminal\x1fterm-123\x1f--text\x1f\x1f--enter\x1f--json')
250
+ [ "$enter_count" -eq 2 ] || fail "send_text_submit must send a SECOND Enter after the popup-placeholder fill still reads pending, got $enter_count Enter(s)"
251
+ pass "fm_backend_orca_send_text_submit: a slash-command popup's placeholder fill on Enter #1 does not short-circuit as submitted; Enter #2 is retried and lands it"
252
+ }
253
+
254
+ test_send_literal_constructs_non_enter_send() {
255
+ orca_case send-literal
256
+ PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
257
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_send_literal term-123 "typed only"' "$ROOT"
258
+ expect_code 0 $? "send_literal should succeed"
259
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''send'$'\x1f''--terminal'$'\x1f''term-123'$'\x1f''--text'$'\x1f''typed only'$'\x1f''--json' \
260
+ "send_literal did not send text without --enter"
261
+ assert_not_contains "$(cat "$LOG")" $'\x1f''--enter' "send_literal should not submit Enter"
262
+ pass "fm_backend_orca_send_literal: sends text without submitting"
263
+ }
264
+
265
+ test_send_text_submit_reports_send_failed() {
266
+ local out
267
+ orca_case send-fail
268
+ printf '1\n' > "$RESP/1.exit"
269
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
270
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_send_text_submit term-123 "hello" 1 0.01 0.01' "$ROOT" )
271
+ [ "$out" = send-failed ] || fail "failed Orca send should report send-failed, got '$out'"
272
+ pass "fm_backend_orca_send_text_submit: reports send-failed when Orca send fails"
273
+ }
274
+
275
+ test_send_helpers_reject_orca_error_json() {
276
+ local out status
277
+ orca_case send-error-json
278
+ printf '{"ok":false,"error":{"code":"terminal_handle_stale","message":"terminal handle stale"}}\n' > "$RESP/1.out"
279
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
280
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_send_text_line term-stale "hello"' "$ROOT" 2>&1 )
281
+ status=$?
282
+ [ "$status" -ne 0 ] || fail "send_text_line should fail on Orca ok:false JSON"
283
+ assert_contains "$out" "terminal handle stale" "send_text_line should surface the Orca send error"
284
+ printf '{"ok":false,"error":{"code":"terminal_handle_stale","message":"terminal handle stale"}}\n' > "$RESP/2.out"
285
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
286
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_send_literal term-stale "typed"' "$ROOT" 2>&1 )
287
+ status=$?
288
+ [ "$status" -ne 0 ] || fail "send_literal should fail on Orca ok:false JSON"
289
+ printf '{"ok":false,"error":{"code":"terminal_handle_stale","message":"terminal handle stale"}}\n' > "$RESP/3.out"
290
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
291
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_send_key term-stale Enter' "$ROOT" 2>&1 )
292
+ status=$?
293
+ [ "$status" -ne 0 ] || fail "send_key should fail on Orca ok:false JSON"
294
+ printf '{"ok":false,"error":{"code":"terminal_handle_stale","message":"terminal handle stale"}}\n' > "$RESP/4.out"
295
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
296
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_send_text_submit term-stale "hello" 1 0.01 0.01' "$ROOT" 2>/dev/null )
297
+ [ "$out" = send-failed ] || fail "send_text_submit should report send-failed on Orca ok:false JSON, got '$out'"
298
+ pass "Orca send helpers: fail closed on ok:false JSON"
299
+ }
300
+
301
+ test_send_key_enter_and_interrupt() {
302
+ orca_case send-key
303
+ PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
304
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_send_key term-123 Enter; fm_backend_orca_send_key term-123 C-c' "$ROOT"
305
+ expect_code 0 $? "send_key Enter and C-c should succeed"
306
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''send'$'\x1f''--terminal'$'\x1f''term-123'$'\x1f''--text'$'\x1f\x1f''--enter'$'\x1f''--json' \
307
+ "send_key Enter did not send empty text with --enter"
308
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''send'$'\x1f''--terminal'$'\x1f''term-123'$'\x1f''--interrupt'$'\x1f''--json' \
309
+ "send_key C-c did not send --interrupt"
310
+ pass "fm_backend_orca_send_key: Enter maps to empty enter, C-c maps to interrupt"
311
+ }
312
+
313
+ test_send_key_refuses_unknown_key() {
314
+ local out status
315
+ orca_case send-key-unknown
316
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
317
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_send_key term-123 F12' "$ROOT" 2>&1 )
318
+ status=$?
319
+ [ "$status" -ne 0 ] || fail "send_key should refuse unsupported Orca keys"
320
+ assert_contains "$out" "unsupported Orca key 'F12'" "send_key did not name the unsupported key"
321
+ pass "fm_backend_orca_send_key: refuses unsupported keys loudly"
322
+ }
323
+
324
+ test_send_key_refuses_escape_until_supported() {
325
+ local out status
326
+ orca_case send-key-escape
327
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
328
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_send_key term-123 Escape' "$ROOT" 2>&1 )
329
+ status=$?
330
+ [ "$status" -ne 0 ] || fail "send_key should refuse Escape until Orca exposes a real Escape primitive"
331
+ assert_contains "$out" "unsupported Orca key 'Escape'" "send_key did not name Escape as unsupported"
332
+ [ ! -s "$LOG" ] || fail "unsupported Escape should not call orca terminal send"
333
+ pass "fm_backend_orca_send_key: refuses Escape instead of mapping it to interrupt"
334
+ }
335
+
336
+ test_kill_is_best_effort_close() {
337
+ orca_case kill
338
+ printf '1\n' > "$RESP/1.exit"
339
+ PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
340
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_kill term-123' "$ROOT"
341
+ expect_code 0 $? "kill should stay best-effort when Orca close fails"
342
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''close'$'\x1f''--terminal'$'\x1f''term-123'$'\x1f''--json' \
343
+ "kill did not call orca terminal close"
344
+ pass "fm_backend_orca_kill: calls terminal close and stays best-effort"
345
+ }
346
+
347
+ test_remove_worktree_refuses_empty_id() {
348
+ local out status
349
+ orca_case remove-empty
350
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
351
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_remove_worktree ""' "$ROOT" 2>&1 )
352
+ status=$?
353
+ [ "$status" -ne 0 ] || fail "remove_worktree should fail when the Orca worktree id is empty"
354
+ assert_contains "$out" "missing Orca worktree id" "remove_worktree did not explain the missing id"
355
+ [ ! -s "$LOG" ] || fail "remove_worktree should not call Orca with an empty id"
356
+ pass "fm_backend_orca_remove_worktree: refuses empty worktree ids"
357
+ }
358
+
359
+ test_remove_worktree_rejects_orca_error_json() {
360
+ local out status
361
+ orca_case remove-error-json
362
+ printf '{"ok":false,"error":{"code":"worktree_not_found","message":"worktree not found"}}\n' > "$RESP/1.out"
363
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
364
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_remove_worktree wt-gone' "$ROOT" 2>&1 )
365
+ status=$?
366
+ [ "$status" -ne 0 ] || fail "remove_worktree should fail on Orca ok:false JSON"
367
+ assert_contains "$out" "worktree not found" "remove_worktree should surface the Orca removal error"
368
+ pass "fm_backend_orca_remove_worktree: fails closed on ok:false JSON"
369
+ }
370
+
371
+ test_worktree_path_resolves_id() {
372
+ local out
373
+ orca_case path-resolve
374
+ printf '{"ok":true,"result":{"worktree":{"id":"wt-123","path":"/tmp/orca-wt"}}}\n' > "$RESP/1.out"
375
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
376
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_worktree_path wt-123' "$ROOT" )
377
+ [ "$out" = /tmp/orca-wt ] || fail "worktree path helper should print the resolved path, got '$out'"
378
+ assert_contains "$(cat "$LOG")" $'orca\x1f''worktree'$'\x1f''show'$'\x1f''--worktree'$'\x1f''id:wt-123'$'\x1f''--json' \
379
+ "worktree path helper did not call orca worktree show"
380
+ pass "fm_backend_orca_worktree_path: resolves an Orca worktree id to its path"
381
+ }
382
+
383
+ test_json_get_ignores_undocumented_terminal_id_shapes() {
384
+ local out status wt_id wt_path term
385
+ orca_case parser-pruned-terminal-shapes
386
+
387
+ set +e
388
+ out=$( printf '{"ok":true,"result":{"id":"term-root-id"}}\n' | \
389
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_json_get terminal-handle' "$ROOT" )
390
+ status=$?
391
+ set +e
392
+ [ "$status" -ne 0 ] || fail "terminal-handle should not treat undocumented result.id as a terminal handle, got '$out'"
393
+
394
+ printf '1\n' > "$RESP/1.exit"
395
+ printf '{"ok":true,"result":{"repo":{"id":"repo-123"}}}\n' > "$RESP/2.out"
396
+ printf '{"ok":true,"result":{"worktree":{"id":"wt-123","path":"/tmp/orca-wt","terminal":{"handle":"term-nested"}}}}\n' > "$RESP/3.out"
397
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
398
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_worktree_create /repo/path fm-task' "$ROOT" )
399
+ wt_id=${out%%$'\t'*}
400
+ wt_path=${out#*$'\t'}
401
+ term=${wt_path#*$'\t'}
402
+ wt_path=${wt_path%%$'\t'*}
403
+ [ "$wt_id" = wt-123 ] || fail "worktree helper should still print worktree id, got '$wt_id'"
404
+ [ "$wt_path" = /tmp/orca-wt ] || fail "worktree helper should still print worktree path, got '$wt_path'"
405
+ [ "$term" = "$wt_path" ] || fail "worktree helper should ignore undocumented result.worktree.terminal and omit an implicit terminal, got '$out'"
406
+ pass "fm_backend_orca_json_get: ignores undocumented terminal id shapes"
407
+ }
408
+
409
+ test_worktree_and_terminal_helpers_parse_json() {
410
+ local out wt_id wt_path term
411
+ orca_case lifecycle-helpers
412
+ printf '1\n' > "$RESP/1.exit"
413
+ printf '{"ok":true,"result":{"repo":{"id":"repo-123"}}}\n' > "$RESP/2.out"
414
+ printf '{"ok":true,"result":{"worktree":{"id":"wt-123","path":"/tmp/orca-wt"}}}\n' > "$RESP/3.out"
415
+ printf '{"ok":true,"result":{"terminal":{"handle":"term-123"}}}\n' > "$RESP/4.out"
416
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
417
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_worktree_create /repo/path fm-task' "$ROOT" )
418
+ wt_id=${out%%$'\t'*}
419
+ wt_path=${out#*$'\t'}
420
+ [ "$wt_id" = wt-123 ] || fail "worktree helper should print worktree id, got '$wt_id'"
421
+ [ "$wt_path" = /tmp/orca-wt ] || fail "worktree helper should print worktree path, got '$wt_path'"
422
+ term=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
423
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_terminal_create wt-123 fm-task' "$ROOT" )
424
+ [ "$term" = term-123 ] || fail "terminal helper should print terminal handle, got '$term'"
425
+ assert_contains "$(cat "$LOG")" $'orca\x1f''repo'$'\x1f''show'$'\x1f''--repo'$'\x1f''path:/repo/path'$'\x1f''--json' \
426
+ "worktree helper should first check repo registration"
427
+ assert_contains "$(cat "$LOG")" $'orca\x1f''repo'$'\x1f''add'$'\x1f''--path'$'\x1f''/repo/path'$'\x1f''--json' \
428
+ "worktree helper should register an absent repo"
429
+ assert_contains "$(cat "$LOG")" $'orca\x1f''worktree'$'\x1f''create'$'\x1f''--repo'$'\x1f''id:repo-123'$'\x1f''--name'$'\x1f''fm-task'$'\x1f''--no-parent'$'\x1f''--setup'$'\x1f''skip'$'\x1f''--json' \
430
+ "worktree helper did not create an independent no-hook worktree"
431
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''create'$'\x1f''--worktree'$'\x1f''id:wt-123'$'\x1f''--title'$'\x1f''fm-task'$'\x1f''--json' \
432
+ "terminal helper did not create a titled terminal for the worktree"
433
+ pass "Orca lifecycle helpers: register repo, create worktree, create terminal, parse stable ids"
434
+ }
435
+
436
+ test_worktree_create_removes_worktree_when_path_missing() {
437
+ local out status
438
+ orca_case lifecycle-missing-path
439
+ printf '1\n' > "$RESP/1.exit"
440
+ printf '{"ok":true,"result":{"repo":{"id":"repo-no-path"}}}\n' > "$RESP/2.out"
441
+ printf '{"ok":true,"result":{"worktree":{"id":"wt-no-path"},"terminal":{"handle":"term-no-path"}}}\n' > "$RESP/3.out"
442
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
443
+ bash -c '. "$0/bin/backends/orca.sh"; fm_backend_orca_worktree_create /repo/path fm-task' "$ROOT" 2>&1 )
444
+ status=$?
445
+ [ "$status" -ne 0 ] || fail "worktree helper should fail when Orca omits the worktree path"
446
+ assert_contains "$out" "orca worktree create did not return a path for fm-task" \
447
+ "worktree helper did not explain the missing path"
448
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''close'$'\x1f''--terminal'$'\x1f''term-no-path'$'\x1f''--json' \
449
+ "worktree helper did not close the implicit terminal when path parsing failed"
450
+ assert_contains "$(cat "$LOG")" $'orca\x1f''worktree'$'\x1f''rm'$'\x1f''--worktree'$'\x1f''id:wt-no-path'$'\x1f''--force'$'\x1f''--json' \
451
+ "worktree helper did not remove the pathless Orca worktree"
452
+ pass "fm_backend_orca_worktree_create: removes created worktree when path is missing"
453
+ }
454
+
455
+ test_spawn_preserves_orca_metadata_when_pathless_worktree_cleanup_fails() {
456
+ local proj data state config id out status
457
+ id="orcapathlessz6"
458
+ proj="$TMP_ROOT/pathless-cleanup-project"
459
+ data="$TMP_ROOT/pathless-cleanup-data"
460
+ state="$TMP_ROOT/pathless-cleanup-state"
461
+ config="$TMP_ROOT/pathless-cleanup-config"
462
+ fm_git_init_commit "$proj"
463
+ mkdir -p "$data/$id" "$state" "$config"
464
+ printf 'brief\n' > "$data/$id/brief.md"
465
+ touch "$state/.last-watcher-beat"
466
+ orca_case pathless-cleanup-fail
467
+ printf '1\n' > "$RESP/1.exit"
468
+ printf '{"ok":true,"result":{"repo":{"id":"repo-pathless-cleanup"}}}\n' > "$RESP/2.out"
469
+ printf '{"ok":true,"result":{"worktree":{"id":"wt-pathless-cleanup"}}}\n' > "$RESP/3.out"
470
+ printf '{"ok":false,"error":{"code":"worktree_not_removed","message":"worktree not removed"}}\n' > "$RESP/4.out"
471
+ printf '{"ok":false,"error":{"code":"worktree_not_removed","message":"worktree not removed"}}\n' > "$RESP/5.out"
472
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
473
+ FM_ROOT_OVERRIDE="$ROOT" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
474
+ FM_PROJECTS_OVERRIDE="$TMP_ROOT/unused-projects" FM_SPAWN_NO_GUARD=1 \
475
+ "$ROOT/bin/fm-spawn.sh" "$id" "$proj" claude --mode no-mistakes --yolo off --backend orca 2>&1 )
476
+ status=$?
477
+ [ "$status" -ne 0 ] || fail "Orca spawn should fail when path parsing and cleanup fail"
478
+ assert_contains "$out" "orca worktree create did not return a path" \
479
+ "pathless worktree failure should explain the missing path"
480
+ assert_contains "$(cat "$LOG")" $'orca\x1f''worktree'$'\x1f''rm'$'\x1f''--worktree'$'\x1f''id:wt-pathless-cleanup'$'\x1f''--force'$'\x1f''--json' \
481
+ "pathless cleanup should attempt helper-backed worktree removal"
482
+ assert_present "$state/$id.meta" "failed pathless cleanup should preserve metadata"
483
+ assert_grep "window=fm-$id" "$state/$id.meta" "preserved pathless metadata missing stable window alias"
484
+ assert_grep "backend=orca" "$state/$id.meta" "preserved pathless metadata missing backend=orca"
485
+ assert_grep "orca_worktree_id=wt-pathless-cleanup" "$state/$id.meta" "preserved pathless metadata missing Orca worktree id"
486
+ assert_no_grep "terminal=" "$state/$id.meta" "preserved pathless metadata should not invent a terminal handle"
487
+ pass "fm-spawn.sh --backend orca: preserves metadata when pathless cleanup fails"
488
+ }
489
+
490
+ test_spawn_writes_orca_metadata_and_launches_harness() {
491
+ local proj wt data state config id out log
492
+ id="orcaspawnz1"
493
+ proj="$TMP_ROOT/spawn-project"
494
+ wt="$TMP_ROOT/spawn-wt"
495
+ data="$TMP_ROOT/spawn-data"
496
+ state="$TMP_ROOT/spawn-state"
497
+ config="$TMP_ROOT/spawn-config"
498
+ fm_git_worktree "$proj" "$wt" "fm/$id"
499
+ mkdir -p "$data/$id" "$state" "$config"
500
+ printf 'brief\n' > "$data/$id/brief.md"
501
+ touch "$state/.last-watcher-beat"
502
+ orca_case spawn
503
+ log="$LOG"
504
+ printf '1\n' > "$RESP/1.exit"
505
+ printf '{"ok":true,"result":{"repo":{"id":"repo-spawn"}}}\n' > "$RESP/2.out"
506
+ printf '{"ok":true,"result":{"worktree":{"id":"wt-spawn","path":"%s"},"terminal":{"handle":"term-spawn"}}}\n' "$wt" > "$RESP/3.out"
507
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
508
+ FM_ROOT_OVERRIDE="$ROOT" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
509
+ FM_PROJECTS_OVERRIDE="$TMP_ROOT/unused-projects" FM_SPAWN_NO_GUARD=1 \
510
+ "$ROOT/bin/fm-spawn.sh" "$id" "$proj" claude --mode no-mistakes --yolo off --backend orca 2>&1 )
511
+ expect_code 0 $? "fm-spawn.sh --backend orca should succeed with fake Orca"$'\n'"$out"
512
+ assert_contains "$out" "spawned $id harness=claude kind=ship mode=no-mistakes yolo=off window=fm-$id worktree=$wt" \
513
+ "spawn output missing Orca window/worktree summary"
514
+ assert_grep "backend=orca" "$state/$id.meta" "meta missing backend=orca"
515
+ assert_grep "window=fm-$id" "$state/$id.meta" "meta missing stable Orca window alias"
516
+ assert_grep "terminal=term-spawn" "$state/$id.meta" "meta missing terminal handle"
517
+ assert_grep "orca_worktree_id=wt-spawn" "$state/$id.meta" "meta missing Orca worktree id"
518
+ assert_grep "worktree=$wt" "$state/$id.meta" "meta missing Orca worktree path"
519
+ assert_not_contains "$(cat "$log")" $'orca\x1f''terminal'$'\x1f''create' \
520
+ "spawn should reuse the implicit terminal returned by Orca worktree creation"
521
+ assert_contains "$(cat "$log")" $'orca\x1f''terminal'$'\x1f''send'$'\x1f''--terminal'$'\x1f''term-spawn'$'\x1f''--text'$'\x1f''export GOTMPDIR=/tmp/fm-orcaspawnz1/gotmp'$'\x1f''--enter'$'\x1f''--json' \
522
+ "spawn did not export GOTMPDIR through the Orca terminal"
523
+ assert_contains "$(cat "$log")" "CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude --dangerously-skip-permissions" \
524
+ "spawn did not send the selected harness launch command through Orca"
525
+ rm -rf "/tmp/fm-$id"
526
+ pass "fm-spawn.sh --backend orca: reuses implicit terminal, records metadata, launches harness"
527
+ }
528
+
529
+ test_spawn_refuses_orca_secondmate_before_home_mutation() {
530
+ local home subhome data state config id out status
531
+ id="orcasmz1"
532
+ home="$TMP_ROOT/secondmate-refusal-home"
533
+ subhome="$TMP_ROOT/secondmate-refusal-subhome"
534
+ data="$home/data"
535
+ state="$home/state"
536
+ config="$home/config"
537
+ mkdir -p "$data" "$state" "$config" "$subhome/bin" "$subhome/data" "$subhome/state" "$subhome/projects"
538
+ printf '%s\n' "$id" > "$subhome/.fm-secondmate-home"
539
+ printf 'firstmate\n' > "$subhome/AGENTS.md"
540
+ printf 'claude\n' > "$config/crew-harness"
541
+ touch "$state/.last-watcher-beat"
542
+ set +e
543
+ out=$( FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$home" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
544
+ FM_PROJECTS_OVERRIDE="$home/projects" FM_SPAWN_NO_GUARD=1 \
545
+ "$ROOT/bin/fm-spawn.sh" "$id" "$subhome" claude --backend orca --secondmate 2>&1 )
546
+ status=$?
547
+ set +e
548
+ [ "$status" -ne 0 ] || fail "backend=orca --secondmate should be refused"
549
+ assert_contains "$out" "backend=orca does not support --secondmate spawns yet" \
550
+ "orca secondmate refusal should happen at backend selection"
551
+ assert_absent "$subhome/config/crew-harness" \
552
+ "orca secondmate refusal should not propagate inherited local material into the secondmate home"
553
+ pass "fm-spawn.sh --backend orca --secondmate: refuses before secondmate-home mutation"
554
+ }
555
+
556
+ test_spawn_refuses_orca_when_runtime_not_ready() {
557
+ local proj data state config id out status
558
+ id="orcaruntimez6"
559
+ proj="$TMP_ROOT/runtime-down-project"
560
+ data="$TMP_ROOT/runtime-down-data"
561
+ state="$TMP_ROOT/runtime-down-state"
562
+ config="$TMP_ROOT/runtime-down-config"
563
+ fm_git_init_commit "$proj"
564
+ mkdir -p "$data/$id" "$state" "$config"
565
+ printf 'brief\n' > "$data/$id/brief.md"
566
+ touch "$state/.last-watcher-beat"
567
+ orca_case runtime-down-spawn
568
+ printf '{"ok":true,"result":{"runtime":{"reachable":false,"state":"starting"}}}\n' > "$RESP/1.out"
569
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" FM_ORCA_STATUS_RESPONSE=sequence \
570
+ FM_ROOT_OVERRIDE="$ROOT" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
571
+ FM_PROJECTS_OVERRIDE="$TMP_ROOT/unused-projects" FM_SPAWN_NO_GUARD=1 \
572
+ "$ROOT/bin/fm-spawn.sh" "$id" "$proj" claude --mode no-mistakes --yolo off --backend orca 2>&1 )
573
+ status=$?
574
+ [ "$status" -ne 0 ] || fail "fm-spawn.sh --backend orca should refuse when Orca runtime is not ready"
575
+ assert_contains "$out" "requires a ready Orca runtime" \
576
+ "runtime readiness refusal should explain the Orca requirement"
577
+ assert_absent "$state/$id.meta" "runtime refusal must not record metadata"
578
+ assert_contains "$(cat "$LOG")" $'orca\x1f''status'$'\x1f''--json' \
579
+ "spawn did not probe Orca runtime readiness"
580
+ assert_not_contains "$(cat "$LOG")" $'orca\x1f''repo' \
581
+ "spawn should fail before repo/worktree creation when runtime is not ready"
582
+ pass "fm-spawn.sh --backend orca: refuses before mutation when Orca runtime is not ready"
583
+ }
584
+
585
+ test_spawn_refuses_orca_nonisolated_worktree() {
586
+ local proj data state config id out status
587
+ id="orcabadwtz4"
588
+ proj="$TMP_ROOT/bad-spawn-project"
589
+ data="$TMP_ROOT/bad-spawn-data"
590
+ state="$TMP_ROOT/bad-spawn-state"
591
+ config="$TMP_ROOT/bad-spawn-config"
592
+ fm_git_init_commit "$proj"
593
+ mkdir -p "$data/$id" "$state" "$config"
594
+ printf 'brief\n' > "$data/$id/brief.md"
595
+ touch "$state/.last-watcher-beat"
596
+ orca_case bad-spawn
597
+ printf '1\n' > "$RESP/1.exit"
598
+ printf '{"ok":true,"result":{"repo":{"id":"repo-bad"}}}\n' > "$RESP/2.out"
599
+ printf '{"ok":true,"result":{"worktree":{"id":"wt-bad","path":"%s"},"terminal":{"handle":"term-bad"}}}\n' "$proj" > "$RESP/3.out"
600
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
601
+ FM_ROOT_OVERRIDE="$ROOT" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
602
+ FM_PROJECTS_OVERRIDE="$TMP_ROOT/unused-projects" FM_SPAWN_NO_GUARD=1 \
603
+ "$ROOT/bin/fm-spawn.sh" "$id" "$proj" claude --mode no-mistakes --yolo off --backend orca 2>&1 )
604
+ status=$?
605
+ expect_code 1 "$status" "fm-spawn.sh --backend orca should refuse a primary checkout worktree"
606
+ assert_contains "$out" "orca worktree create did not yield an isolated worktree" \
607
+ "Orca spawn should reuse the isolated-worktree guard"
608
+ assert_absent "$state/$id.meta" "aborted Orca spawn must not record meta"
609
+ assert_not_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''create' \
610
+ "Orca spawn should validate the worktree before creating a terminal"
611
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''close'$'\x1f''--terminal'$'\x1f''term-bad'$'\x1f''--json' \
612
+ "Orca spawn should close the implicit terminal after validation aborts"
613
+ assert_contains "$(cat "$LOG")" $'orca\x1f''worktree'$'\x1f''rm'$'\x1f''--worktree'$'\x1f''id:wt-bad'$'\x1f''--force'$'\x1f''--json' \
614
+ "Orca spawn should remove the worktree after validation aborts"
615
+ pass "fm-spawn.sh --backend orca: refuses non-isolated worktrees and closes implicit terminals"
616
+ }
617
+
618
+ test_spawn_removes_orca_worktree_when_terminal_create_fails() {
619
+ local proj wt data state config id out status
620
+ id="orcatermfailz8"
621
+ proj="$TMP_ROOT/terminal-fail-project"
622
+ wt="$TMP_ROOT/terminal-fail-wt"
623
+ data="$TMP_ROOT/terminal-fail-data"
624
+ state="$TMP_ROOT/terminal-fail-state"
625
+ config="$TMP_ROOT/terminal-fail-config"
626
+ fm_git_worktree "$proj" "$wt" "fm/$id"
627
+ mkdir -p "$data/$id" "$state" "$config"
628
+ printf 'brief\n' > "$data/$id/brief.md"
629
+ touch "$state/.last-watcher-beat"
630
+ orca_case terminal-fail
631
+ printf '1\n' > "$RESP/1.exit"
632
+ printf '{"ok":true,"result":{"repo":{"id":"repo-terminal-fail"}}}\n' > "$RESP/2.out"
633
+ printf '{"ok":true,"result":{"worktree":{"id":"wt-terminal-fail","path":"%s"}}}\n' "$wt" > "$RESP/3.out"
634
+ printf '1\n' > "$RESP/4.exit"
635
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
636
+ FM_ROOT_OVERRIDE="$ROOT" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
637
+ FM_PROJECTS_OVERRIDE="$TMP_ROOT/unused-projects" FM_SPAWN_NO_GUARD=1 \
638
+ "$ROOT/bin/fm-spawn.sh" "$id" "$proj" claude --mode no-mistakes --yolo off --backend orca 2>&1 )
639
+ status=$?
640
+ [ "$status" -ne 0 ] || fail "Orca spawn should fail when terminal creation fails"
641
+ assert_absent "$state/$id.meta" "terminal-create abort should not record metadata after successful cleanup"
642
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''create'$'\x1f''--worktree'$'\x1f''id:wt-terminal-fail'$'\x1f''--title'$'\x1f'"fm-$id"$'\x1f''--json' \
643
+ "Orca spawn should attempt terminal creation before abort cleanup"
644
+ assert_contains "$(cat "$LOG")" $'orca\x1f''worktree'$'\x1f''rm'$'\x1f''--worktree'$'\x1f''id:wt-terminal-fail'$'\x1f''--force'$'\x1f''--json' \
645
+ "Orca spawn should remove the worktree when terminal creation fails"
646
+ assert_not_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''close' \
647
+ "Orca spawn should not close a terminal when no handle was recorded"
648
+ pass "fm-spawn.sh --backend orca: removes worktree when terminal creation fails"
649
+ }
650
+
651
+ test_spawn_preserves_orca_metadata_when_abort_cleanup_fails() {
652
+ local proj wt data state config id out status
653
+ id="orcacleanupleakz0"
654
+ proj="$TMP_ROOT/cleanup-fail-project"
655
+ wt="$TMP_ROOT/cleanup-fail-wt"
656
+ data="$TMP_ROOT/cleanup-fail-data"
657
+ state="$TMP_ROOT/cleanup-fail-state"
658
+ config="$TMP_ROOT/cleanup-fail-config"
659
+ fm_git_worktree "$proj" "$wt" "fm/$id"
660
+ mkdir -p "$data/$id" "$state" "$config"
661
+ printf 'brief\n' > "$data/$id/brief.md"
662
+ touch "$state/.last-watcher-beat"
663
+ orca_case cleanup-fail
664
+ printf '1\n' > "$RESP/1.exit"
665
+ printf '{"ok":true,"result":{"repo":{"id":"repo-cleanup-fail"}}}\n' > "$RESP/2.out"
666
+ printf '{"ok":true,"result":{"worktree":{"id":"wt-cleanup-fail","path":"%s"}}}\n' "$wt" > "$RESP/3.out"
667
+ printf '1\n' > "$RESP/4.exit"
668
+ printf '1\n' > "$RESP/5.exit"
669
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
670
+ FM_ROOT_OVERRIDE="$ROOT" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
671
+ FM_PROJECTS_OVERRIDE="$TMP_ROOT/unused-projects" FM_SPAWN_NO_GUARD=1 \
672
+ "$ROOT/bin/fm-spawn.sh" "$id" "$proj" claude --mode no-mistakes --yolo off --backend orca 2>&1 )
673
+ status=$?
674
+ [ "$status" -ne 0 ] || fail "Orca spawn should fail when terminal creation and abort cleanup fail"
675
+ assert_contains "$(cat "$LOG")" $'orca\x1f''worktree'$'\x1f''rm'$'\x1f''--worktree'$'\x1f''id:wt-cleanup-fail'$'\x1f''--force'$'\x1f''--json' \
676
+ "Orca spawn should attempt helper cleanup before preserving metadata"
677
+ assert_present "$state/$id.meta" "failed Orca abort cleanup should preserve metadata"
678
+ assert_grep "window=fm-$id" "$state/$id.meta" "preserved metadata missing stable window alias"
679
+ assert_grep "backend=orca" "$state/$id.meta" "preserved metadata missing backend=orca"
680
+ assert_grep "orca_worktree_id=wt-cleanup-fail" "$state/$id.meta" "preserved metadata missing Orca worktree id"
681
+ assert_no_grep "terminal=" "$state/$id.meta" "preserved metadata should not invent a terminal handle"
682
+ pass "fm-spawn.sh --backend orca: preserves metadata when abort cleanup fails"
683
+ }
684
+
685
+ test_spawn_releases_orca_resources_when_metadata_write_fails() {
686
+ local proj wt data state config id out status
687
+ id="orcametafailz9"
688
+ proj="$TMP_ROOT/meta-fail-project"
689
+ wt="$TMP_ROOT/meta-fail-wt"
690
+ data="$TMP_ROOT/meta-fail-data"
691
+ state="$TMP_ROOT/meta-fail-state"
692
+ config="$TMP_ROOT/meta-fail-config"
693
+ fm_git_worktree "$proj" "$wt" "fm/$id"
694
+ mkdir -p "$data/$id" "$state/$id.meta" "$config"
695
+ printf 'brief\n' > "$data/$id/brief.md"
696
+ orca_case meta-fail
697
+ printf '1\n' > "$RESP/1.exit"
698
+ printf '{"ok":true,"result":{"repo":{"id":"repo-meta-fail"}}}\n' > "$RESP/2.out"
699
+ printf '{"ok":true,"result":{"worktree":{"id":"wt-meta-fail","path":"%s"}}}\n' "$wt" > "$RESP/3.out"
700
+ printf '{"ok":true,"result":{"terminal":{"handle":"term-meta-fail"}}}\n' > "$RESP/4.out"
701
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
702
+ FM_ROOT_OVERRIDE="$ROOT" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
703
+ FM_PROJECTS_OVERRIDE="$TMP_ROOT/unused-projects" FM_SPAWN_NO_GUARD=1 \
704
+ "$ROOT/bin/fm-spawn.sh" "$id" "$proj" claude --mode no-mistakes --yolo off --backend orca 2>&1 )
705
+ status=$?
706
+ [ "$status" -ne 0 ] || fail "Orca spawn should fail when metadata cannot be written"
707
+ assert_contains "$out" "task record for $id could not be published" \
708
+ "spawn should report metadata publication failure without relying on platform-specific mv output"
709
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''close'$'\x1f''--terminal'$'\x1f''term-meta-fail'$'\x1f''--json' \
710
+ "Orca spawn should close the recorded terminal when a later abort occurs"
711
+ assert_contains "$(cat "$LOG")" $'orca\x1f''worktree'$'\x1f''rm'$'\x1f''--worktree'$'\x1f''id:wt-meta-fail'$'\x1f''--force'$'\x1f''--json' \
712
+ "Orca spawn should remove the recorded worktree when a later abort occurs"
713
+ [ ! -f "$state/$id.meta" ] || fail "metadata-write abort should not publish a regular metadata file"
714
+ pass "fm-spawn.sh --backend orca: releases terminal and worktree on later aborts"
715
+ }
716
+
717
+ test_peek_send_and_crew_state_route_through_orca_meta() {
718
+ local wt state id out neutral record body
719
+ id="orcaiopathz2"
720
+ wt="$TMP_ROOT/io-wt"
721
+ fm_git_init_commit "$wt"
722
+ state="$TMP_ROOT/io-state"; mkdir -p "$state"
723
+ fm_write_meta "$state/$id.meta" \
724
+ "window=fm-$id" "endpoint_task_id=$id" "terminal=term-io" "worktree=$wt" "project=$wt" "harness=claude" "kind=scout" "backend=orca"
725
+ touch "$state/.last-watcher-beat"
726
+ orca_case io-path
727
+ neutral=$(neutral_fm_root "$CASE_DIR/neutral")
728
+ printf '{"ok":true,"result":{"terminal":{"tail":["ready"]}}}\n' > "$RESP/1.out"
729
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
730
+ FM_ROOT_OVERRIDE="$neutral" FM_STATE_OVERRIDE="$state" FM_SEND_SETTLE=0 \
731
+ "$ROOT/bin/fm-peek.sh" "fm-$id" 10 )
732
+ [ "$out" = ready ] || fail "fm-peek should read through Orca metadata, got '$out'"
733
+ printf '{"ok":true,"result":{"send":{"handle":"term-io","accepted":true}}}\n' > "$RESP/2.out"
734
+ printf '{"ok":true,"result":{"send":{"handle":"term-io","accepted":true}}}\n' > "$RESP/3.out"
735
+ printf '{"ok":true,"result":{"terminal":{"tail":["│ > │"]}}}\n' > "$RESP/4.out"
736
+ PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
737
+ FM_ROOT_OVERRIDE="$neutral" FM_HOME="$neutral" FM_STATE_OVERRIDE="$state" FM_SEND_SETTLE=0 \
738
+ "$ROOT/bin/fm-send.sh" "fm-$id" "hello orca"
739
+ printf '{"ok":true,"result":{"terminal":{"tail":["idle prompt"]}}}\n' > "$RESP/5.out"
740
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
741
+ FM_ROOT_OVERRIDE="$ROOT" FM_STATE_OVERRIDE="$state" "$ROOT/bin/fm-crew-state.sh" "$id" )
742
+ assert_contains "$out" "state: unknown" "crew-state should fall back cleanly for an idle Orca scout"
743
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''read'$'\x1f''--terminal'$'\x1f''term-io' \
744
+ "peek/crew-state did not read the recorded Orca terminal"
745
+ assert_not_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''read'$'\x1f''--terminal'$'\x1f'"fm-$id" \
746
+ "crew-state should not read the stable Orca alias as a terminal handle"
747
+ record="$state/$id.inbox/001.msg"
748
+ [ -f "$record" ] || fail "send did not enqueue through the task inbox"
749
+ body=$(bash -c '. "$1"; fm_task_inbox_body "$2"' _ "$ROOT/bin/fm-task-inbox-lib.sh" "$record")
750
+ [ "$body" = "hello orca" ] || fail "Orca task inbox did not preserve the send body, got '$body'"
751
+ assert_not_contains "$(cat "$LOG")" $'--text\x1fhello orca\x1f' \
752
+ "send typed the payload instead of recording it"
753
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''send'$'\x1f''--terminal'$'\x1f''term-io'$'\x1f''--text'$'\x1f''Firstmate instruction waiting:' \
754
+ "send did not ring the inbox doorbell through the recorded Orca terminal"
755
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''send'$'\x1f''--terminal'$'\x1f''term-io'$'\x1f''--text'$'\x1f\x1f''--enter'$'\x1f''--json' \
756
+ "send did not submit the doorbell through the recorded Orca terminal"
757
+ pass "fm-peek/fm-send/fm-crew-state route through backend=orca metadata and its durable inbox"
758
+ }
759
+
760
+ test_peek_and_crew_state_fail_closed_on_orca_error_json() {
761
+ local wt state id out status neutral
762
+ id="orcareaderrz7"
763
+ wt="$TMP_ROOT/read-error-wt"
764
+ fm_git_init_commit "$wt"
765
+ state="$TMP_ROOT/read-error-state"; mkdir -p "$state"
766
+ fm_write_meta "$state/$id.meta" \
767
+ "window=fm-$id" "endpoint_task_id=$id" "terminal=term-stale" "worktree=$wt" "project=$wt" "harness=claude" "kind=scout" "backend=orca"
768
+ touch "$state/.last-watcher-beat"
769
+ orca_case read-error-json
770
+ neutral=$(neutral_fm_root "$CASE_DIR/neutral")
771
+ printf '{"ok":false,"error":{"code":"terminal_handle_stale","message":"terminal handle stale"}}\n' > "$RESP/1.out"
772
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
773
+ FM_ROOT_OVERRIDE="$neutral" FM_STATE_OVERRIDE="$state" "$ROOT/bin/fm-peek.sh" "fm-$id" 10 2>&1 )
774
+ status=$?
775
+ [ "$status" -ne 0 ] || fail "fm-peek should fail when Orca reports a stale terminal"
776
+ assert_contains "$out" "terminal handle stale" "fm-peek should surface the Orca read error message"
777
+ printf '{"ok":false,"error":{"code":"terminal_handle_stale","message":"terminal handle stale"}}\n' > "$RESP/2.out"
778
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
779
+ FM_ROOT_OVERRIDE="$ROOT" FM_STATE_OVERRIDE="$state" "$ROOT/bin/fm-crew-state.sh" "$id" )
780
+ assert_contains "$out" "state: unknown" "crew-state should not treat an Orca read error as a live endpoint"
781
+ assert_contains "$out" "backend target gone: term-stale" "crew-state should report the stale Orca terminal as gone"
782
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''read'$'\x1f''--terminal'$'\x1f''term-stale' \
783
+ "fm-peek/fm-crew-state did not read the recorded Orca terminal"
784
+ pass "fm-peek/fm-crew-state: Orca read error JSON fails closed"
785
+ }
786
+
787
+ test_target_exists_rejects_orca_error_json() {
788
+ local status
789
+ orca_case target-exists-error-json
790
+ printf '{"ok":false,"error":{"code":"terminal_handle_stale","message":"terminal handle stale"}}\n' > "$RESP/1.out"
791
+ set +e
792
+ PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
793
+ bash -c '. "$0/bin/fm-backend.sh"; fm_backend_target_exists orca term-stale fm-task' "$ROOT"
794
+ status=$?
795
+ set -e
796
+ [ "$status" -ne 0 ] || fail "fm_backend_target_exists should reject Orca ok:false read JSON"
797
+ pass "fm_backend_target_exists: Orca ok:false read JSON is not live"
798
+ }
799
+
800
+ test_scout_teardown_removes_orca_worktree_via_helper() {
801
+ local proj wt data state config id out rc neutral
802
+ id="orcateardownz3"
803
+ proj="$TMP_ROOT/teardown-project"
804
+ wt="$TMP_ROOT/teardown-wt"
805
+ data="$TMP_ROOT/teardown-data"
806
+ state="$TMP_ROOT/teardown-state"
807
+ config="$TMP_ROOT/teardown-config"
808
+ fm_git_worktree "$proj" "$wt" "fm/$id"
809
+ mkdir -p "$data/$id" "$state" "$config"
810
+ printf 'report\n' > "$data/$id/report.md"
811
+ touch "$state/.last-watcher-beat"
812
+ fm_write_meta "$state/$id.meta" \
813
+ "window=fm-$id" "endpoint_task_id=$id" "terminal=term-teardown" "worktree=$wt" "project=$proj" \
814
+ "harness=claude" "kind=scout" "mode=no-mistakes" "yolo=off" \
815
+ "backend=orca" "orca_worktree_id=wt-teardown" \
816
+ "decisions_reviewed=1" "decision_keys="
817
+ orca_case teardown
818
+ printf '{"ok":true,"result":{"worktree":{"id":"wt-teardown","path":"%s"}}}\n' "$wt" > "$RESP/1.out"
819
+ neutral=$(neutral_fm_root "$CASE_DIR/neutral")
820
+ set +e
821
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
822
+ FM_ROOT_OVERRIDE="$neutral" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
823
+ "$ROOT/bin/fm-teardown.sh" "$id" 2>&1 )
824
+ rc=$?
825
+ set -e
826
+ expect_code 0 "$rc" "Orca scout teardown should succeed once report exists"$'\n'"$out"
827
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''close'$'\x1f''--terminal'$'\x1f''term-teardown'$'\x1f''--json' \
828
+ "teardown did not close the recorded Orca terminal"
829
+ assert_contains "$(cat "$LOG")" $'orca\x1f''worktree'$'\x1f''rm'$'\x1f''--worktree'$'\x1f''id:wt-teardown'$'\x1f''--force'$'\x1f''--json' \
830
+ "teardown did not remove the Orca worktree through orca worktree rm"
831
+ assert_absent "$state/$id.meta" "teardown should remove task metadata"
832
+ pass "fm-teardown.sh backend=orca: scout report gate then helper-backed worktree removal"
833
+ }
834
+
835
+ test_scout_teardown_refuses_orca_id_path_mismatch() {
836
+ local proj wt other_wt data state config id out rc neutral
837
+ id="orcascoutmismatchz5"
838
+ proj="$TMP_ROOT/scout-mismatch-project"
839
+ wt="$TMP_ROOT/scout-mismatch-wt"
840
+ other_wt="$TMP_ROOT/scout-mismatch-other-wt"
841
+ data="$TMP_ROOT/scout-mismatch-data"
842
+ state="$TMP_ROOT/scout-mismatch-state"
843
+ config="$TMP_ROOT/scout-mismatch-config"
844
+ fm_git_worktree "$proj" "$wt" "fm/$id"
845
+ git -C "$proj" worktree add --quiet -b "fm/$id-other" "$other_wt"
846
+ mkdir -p "$data/$id" "$state" "$config"
847
+ printf 'report\n' > "$data/$id/report.md"
848
+ touch "$state/.last-watcher-beat"
849
+ fm_write_meta "$state/$id.meta" \
850
+ "window=fm-$id" "endpoint_task_id=$id" "terminal=term-scout-mismatch" "worktree=$wt" "project=$proj" \
851
+ "harness=claude" "kind=scout" "mode=no-mistakes" "yolo=off" \
852
+ "backend=orca" "orca_worktree_id=wt-scout-mismatch" \
853
+ "decisions_reviewed=1" "decision_keys="
854
+ orca_case scout-mismatch
855
+ printf '{"ok":true,"result":{"worktree":{"id":"wt-scout-mismatch","path":"%s"}}}\n' "$other_wt" > "$RESP/1.out"
856
+ neutral=$(neutral_fm_root "$CASE_DIR/neutral")
857
+ set +e
858
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
859
+ FM_ROOT_OVERRIDE="$neutral" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
860
+ "$ROOT/bin/fm-teardown.sh" "$id" 2>&1 )
861
+ rc=$?
862
+ set -e
863
+ [ "$rc" -ne 0 ] || fail "Orca scout teardown should refuse when id path differs from worktree="
864
+ assert_contains "$out" "not inspected worktree" \
865
+ "mismatched Orca scout worktree path refusal should name the mismatch"
866
+ assert_not_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''close' \
867
+ "refused mismatched Orca scout teardown should not close terminals"
868
+ assert_not_contains "$(cat "$LOG")" $'orca\x1f''worktree'$'\x1f''rm' \
869
+ "refused mismatched Orca scout teardown should not remove worktrees"
870
+ assert_present "$state/$id.meta" "refused mismatched scout teardown should preserve metadata"
871
+ pass "fm-teardown.sh backend=orca: scout teardown refuses id/path mismatches"
872
+ }
873
+
874
+ test_teardown_removes_orca_worktree_when_path_missing() {
875
+ local proj wt data state config id out rc neutral
876
+ id="orcamissingpathz7"
877
+ proj="$TMP_ROOT/missing-path-project"
878
+ wt="$TMP_ROOT/missing-path-wt"
879
+ data="$TMP_ROOT/missing-path-data"
880
+ state="$TMP_ROOT/missing-path-state"
881
+ config="$TMP_ROOT/missing-path-config"
882
+ mkdir -p "$data/$id" "$state" "$config"
883
+ printf 'report\n' > "$data/$id/report.md"
884
+ touch "$state/.last-watcher-beat"
885
+ fm_write_meta "$state/$id.meta" \
886
+ "window=fm-$id" "endpoint_task_id=$id" "terminal=term-missing-path" "worktree=$wt" "project=$proj" \
887
+ "harness=claude" "kind=scout" "mode=no-mistakes" "yolo=off" \
888
+ "backend=orca" "orca_worktree_id=wt-missing-path" \
889
+ "decisions_reviewed=1" "decision_keys="
890
+ orca_case missing-path
891
+ neutral=$(neutral_fm_root "$CASE_DIR/neutral")
892
+ set +e
893
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
894
+ FM_ROOT_OVERRIDE="$neutral" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
895
+ "$ROOT/bin/fm-teardown.sh" "$id" 2>&1 )
896
+ rc=$?
897
+ set -e
898
+ expect_code 0 "$rc" "Orca teardown should release helpers even when the path is absent"$'\n'"$out"
899
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''close'$'\x1f''--terminal'$'\x1f''term-missing-path'$'\x1f''--json' \
900
+ "teardown did not close the recorded Orca terminal when the path was absent"
901
+ assert_contains "$(cat "$LOG")" $'orca\x1f''worktree'$'\x1f''rm'$'\x1f''--worktree'$'\x1f''id:wt-missing-path'$'\x1f''--force'$'\x1f''--json' \
902
+ "teardown did not remove the recorded Orca worktree when the path was absent"
903
+ assert_absent "$state/$id.meta" "successful helper cleanup should remove task metadata"
904
+ pass "fm-teardown.sh backend=orca: releases terminal/worktree when path is absent"
905
+ }
906
+
907
+ test_teardown_preserves_metadata_when_orca_remove_error_json() {
908
+ local proj wt data state config id out rc neutral
909
+ id="orcaremoveerrz2"
910
+ proj="$TMP_ROOT/remove-error-project"
911
+ wt="$TMP_ROOT/remove-error-wt"
912
+ data="$TMP_ROOT/remove-error-data"
913
+ state="$TMP_ROOT/remove-error-state"
914
+ config="$TMP_ROOT/remove-error-config"
915
+ mkdir -p "$data/$id" "$state" "$config"
916
+ printf 'report\n' > "$data/$id/report.md"
917
+ touch "$state/.last-watcher-beat"
918
+ fm_write_meta "$state/$id.meta" \
919
+ "window=fm-$id" "endpoint_task_id=$id" "terminal=term-remove-error" "worktree=$wt" "project=$proj" \
920
+ "harness=claude" "kind=scout" "mode=no-mistakes" "yolo=off" \
921
+ "backend=orca" "orca_worktree_id=wt-remove-error" \
922
+ "decisions_reviewed=1" "decision_keys="
923
+ orca_case remove-error-teardown
924
+ printf '{"ok":true,"result":{}}\n' > "$RESP/1.out"
925
+ printf '{"ok":false,"error":{"code":"worktree_not_removed","message":"worktree not removed"}}\n' > "$RESP/2.out"
926
+ neutral=$(neutral_fm_root "$CASE_DIR/neutral")
927
+ set +e
928
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
929
+ FM_ROOT_OVERRIDE="$neutral" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
930
+ "$ROOT/bin/fm-teardown.sh" "$id" 2>&1 )
931
+ rc=$?
932
+ set -e
933
+ [ "$rc" -ne 0 ] || fail "Orca teardown should fail when worktree removal returns ok:false JSON"
934
+ assert_contains "$out" "worktree not removed" "teardown should surface the Orca removal error"
935
+ assert_present "$state/$id.meta" "failed Orca removal should preserve task metadata"
936
+ pass "fm-teardown.sh backend=orca: preserves metadata on remove ok:false JSON"
937
+ }
938
+
939
+ test_scout_teardown_refuses_orca_missing_report_when_path_missing() {
940
+ local proj wt data state config id out rc neutral
941
+ id="orcanoreportz4"
942
+ proj="$TMP_ROOT/missing-report-project"
943
+ wt="$TMP_ROOT/missing-report-wt"
944
+ data="$TMP_ROOT/missing-report-data"
945
+ state="$TMP_ROOT/missing-report-state"
946
+ config="$TMP_ROOT/missing-report-config"
947
+ mkdir -p "$data/$id" "$state" "$config"
948
+ touch "$state/.last-watcher-beat"
949
+ fm_write_meta "$state/$id.meta" \
950
+ "window=fm-$id" "endpoint_task_id=$id" "terminal=term-missing-report" "worktree=$wt" "project=$proj" \
951
+ "harness=claude" "kind=scout" "mode=no-mistakes" "yolo=off" \
952
+ "backend=orca" "orca_worktree_id=wt-missing-report"
953
+ orca_case missing-report
954
+ neutral=$(neutral_fm_root "$CASE_DIR/neutral")
955
+ set +e
956
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
957
+ FM_ROOT_OVERRIDE="$neutral" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
958
+ "$ROOT/bin/fm-teardown.sh" "$id" 2>&1 )
959
+ rc=$?
960
+ set -e
961
+ [ "$rc" -ne 0 ] || fail "Orca scout teardown should refuse without a report even when the path is absent"
962
+ assert_contains "$out" "has no report" "Orca scout teardown should explain the missing report"
963
+ [ ! -s "$LOG" ] || fail "refused Orca scout teardown should not close terminals or remove worktrees"
964
+ assert_present "$state/$id.meta" "refused Orca scout teardown should preserve metadata"
965
+ pass "fm-teardown.sh backend=orca: scout report gate precedes pathless helper cleanup"
966
+ }
967
+
968
+ test_ship_teardown_refuses_orca_missing_worktree_path() {
969
+ local proj wt data state config id out rc neutral
970
+ id="orcashipmissingz8"
971
+ proj="$TMP_ROOT/missing-ship-project"
972
+ wt="$TMP_ROOT/missing-ship-wt"
973
+ data="$TMP_ROOT/missing-ship-data"
974
+ state="$TMP_ROOT/missing-ship-state"
975
+ config="$TMP_ROOT/missing-ship-config"
976
+ fm_git_init_commit "$proj"
977
+ mkdir -p "$data/$id" "$state" "$config"
978
+ touch "$state/.last-watcher-beat"
979
+ fm_write_meta "$state/$id.meta" \
980
+ "window=fm-$id" "endpoint_task_id=$id" "terminal=term-missing-ship" "worktree=$wt" "project=$proj" \
981
+ "harness=claude" "kind=ship" "mode=no-mistakes" "yolo=off" \
982
+ "backend=orca" "orca_worktree_id=wt-missing-ship"
983
+ orca_case missing-ship-path
984
+ neutral=$(neutral_fm_root "$CASE_DIR/neutral")
985
+ set +e
986
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
987
+ FM_ROOT_OVERRIDE="$neutral" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
988
+ "$ROOT/bin/fm-teardown.sh" "$id" 2>&1 )
989
+ rc=$?
990
+ set -e
991
+ [ "$rc" -ne 0 ] || fail "Orca ship teardown should refuse a missing worktree path"
992
+ assert_contains "$out" "no inspectable git worktree" \
993
+ "Orca ship teardown should explain the fail-closed worktree requirement"
994
+ [ ! -s "$LOG" ] || fail "refused Orca ship teardown should not close terminals or remove worktrees"
995
+ assert_present "$state/$id.meta" "refused Orca ship teardown should preserve metadata"
996
+ pass "fm-teardown.sh backend=orca: ship teardown fails closed when worktree path is missing"
997
+ }
998
+
999
+ test_ship_teardown_removes_orca_worktree_when_id_path_matches() {
1000
+ local proj wt data state config id out rc neutral
1001
+ id="orcashipmatchz2"
1002
+ proj="$TMP_ROOT/ship-match-project"
1003
+ wt="$TMP_ROOT/ship-match-wt"
1004
+ data="$TMP_ROOT/ship-match-data"
1005
+ state="$TMP_ROOT/ship-match-state"
1006
+ config="$TMP_ROOT/ship-match-config"
1007
+ fm_git_worktree "$proj" "$wt" "fm/$id"
1008
+ mkdir -p "$data/$id" "$state" "$config"
1009
+ touch "$state/.last-watcher-beat"
1010
+ fm_write_meta "$state/$id.meta" \
1011
+ "window=fm-$id" "endpoint_task_id=$id" "terminal=term-ship-match" "worktree=$wt" "project=$proj" \
1012
+ "harness=claude" "kind=ship" "mode=local-only" "yolo=off" \
1013
+ "backend=orca" "orca_worktree_id=wt-ship-match"
1014
+ orca_case ship-match
1015
+ printf '{"ok":true,"result":{"worktree":{"id":"wt-ship-match","path":"%s"}}}\n' "$wt" > "$RESP/1.out"
1016
+ neutral=$(neutral_fm_root "$CASE_DIR/neutral")
1017
+ set +e
1018
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
1019
+ FM_ROOT_OVERRIDE="$neutral" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
1020
+ "$ROOT/bin/fm-teardown.sh" "$id" 2>&1 )
1021
+ rc=$?
1022
+ set -e
1023
+ expect_code 0 "$rc" "Orca ship teardown should succeed when the id path matches the inspected worktree"$'\n'"$out"
1024
+ assert_contains "$(cat "$LOG")" $'orca\x1f''worktree'$'\x1f''show'$'\x1f''--worktree'$'\x1f''id:wt-ship-match'$'\x1f''--json' \
1025
+ "teardown did not resolve the Orca worktree id before removal"
1026
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''close'$'\x1f''--terminal'$'\x1f''term-ship-match'$'\x1f''--json' \
1027
+ "teardown did not close the matched Orca terminal"
1028
+ assert_contains "$(cat "$LOG")" $'orca\x1f''worktree'$'\x1f''rm'$'\x1f''--worktree'$'\x1f''id:wt-ship-match'$'\x1f''--force'$'\x1f''--json' \
1029
+ "teardown did not remove the matched Orca worktree"
1030
+ assert_absent "$state/$id.meta" "successful matched teardown should remove task metadata"
1031
+ pass "fm-teardown.sh backend=orca: ship teardown requires a matching Orca id path"
1032
+ }
1033
+
1034
+ test_ship_teardown_refuses_orca_unresolvable_worktree_id() {
1035
+ local proj wt data state config id out rc neutral
1036
+ id="orcashipunresolvedz1"
1037
+ proj="$TMP_ROOT/ship-unresolved-project"
1038
+ wt="$TMP_ROOT/ship-unresolved-wt"
1039
+ data="$TMP_ROOT/ship-unresolved-data"
1040
+ state="$TMP_ROOT/ship-unresolved-state"
1041
+ config="$TMP_ROOT/ship-unresolved-config"
1042
+ fm_git_worktree "$proj" "$wt" "fm/$id"
1043
+ mkdir -p "$data/$id" "$state" "$config"
1044
+ touch "$state/.last-watcher-beat"
1045
+ fm_write_meta "$state/$id.meta" \
1046
+ "window=fm-$id" "endpoint_task_id=$id" "terminal=term-ship-unresolved" "worktree=$wt" "project=$proj" \
1047
+ "harness=claude" "kind=ship" "mode=local-only" "yolo=off" \
1048
+ "backend=orca" "orca_worktree_id=wt-ship-unresolved"
1049
+ orca_case ship-unresolved
1050
+ printf '1\n' > "$RESP/1.exit"
1051
+ neutral=$(neutral_fm_root "$CASE_DIR/neutral")
1052
+ set +e
1053
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
1054
+ FM_ROOT_OVERRIDE="$neutral" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
1055
+ "$ROOT/bin/fm-teardown.sh" "$id" 2>&1 )
1056
+ rc=$?
1057
+ set -e
1058
+ [ "$rc" -ne 0 ] || fail "Orca ship teardown should refuse when the worktree id cannot be resolved"
1059
+ assert_contains "$out" "cannot resolve Orca worktree id wt-ship-unresolved" \
1060
+ "unresolvable Orca worktree id refusal should explain the fail-closed check"
1061
+ assert_contains "$(cat "$LOG")" $'orca\x1f''worktree'$'\x1f''show'$'\x1f''--worktree'$'\x1f''id:wt-ship-unresolved'$'\x1f''--json' \
1062
+ "teardown did not attempt to resolve the Orca worktree id"
1063
+ assert_not_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''close' \
1064
+ "refused unresolved Orca ship teardown should not close terminals"
1065
+ assert_not_contains "$(cat "$LOG")" $'orca\x1f''worktree'$'\x1f''rm' \
1066
+ "refused unresolved Orca ship teardown should not remove worktrees"
1067
+ assert_present "$state/$id.meta" "refused unresolved Orca ship teardown should preserve metadata"
1068
+ pass "fm-teardown.sh backend=orca: ship teardown fails closed when id resolution fails"
1069
+ }
1070
+
1071
+ test_ship_teardown_refuses_orca_id_path_mismatch() {
1072
+ local proj wt other_wt data state config id out rc neutral
1073
+ id="orcashipmismatchz9"
1074
+ proj="$TMP_ROOT/ship-mismatch-project"
1075
+ wt="$TMP_ROOT/ship-mismatch-wt"
1076
+ other_wt="$TMP_ROOT/ship-mismatch-other-wt"
1077
+ data="$TMP_ROOT/ship-mismatch-data"
1078
+ state="$TMP_ROOT/ship-mismatch-state"
1079
+ config="$TMP_ROOT/ship-mismatch-config"
1080
+ fm_git_worktree "$proj" "$wt" "fm/$id"
1081
+ git -C "$proj" worktree add --quiet -b "fm/$id-other" "$other_wt"
1082
+ mkdir -p "$data/$id" "$state" "$config"
1083
+ touch "$state/.last-watcher-beat"
1084
+ fm_write_meta "$state/$id.meta" \
1085
+ "window=fm-$id" "endpoint_task_id=$id" "terminal=term-ship-mismatch" "worktree=$wt" "project=$proj" \
1086
+ "harness=claude" "kind=ship" "mode=local-only" "yolo=off" \
1087
+ "backend=orca" "orca_worktree_id=wt-ship-mismatch"
1088
+ orca_case ship-mismatch
1089
+ printf '{"ok":true,"result":{"worktree":{"id":"wt-ship-mismatch","path":"%s"}}}\n' "$other_wt" > "$RESP/1.out"
1090
+ neutral=$(neutral_fm_root "$CASE_DIR/neutral")
1091
+ set +e
1092
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
1093
+ FM_ROOT_OVERRIDE="$neutral" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
1094
+ "$ROOT/bin/fm-teardown.sh" "$id" 2>&1 )
1095
+ rc=$?
1096
+ set -e
1097
+ [ "$rc" -ne 0 ] || fail "Orca ship teardown should refuse when the id path differs from worktree="
1098
+ assert_contains "$out" "not inspected worktree" \
1099
+ "mismatched Orca worktree path refusal should name the mismatch"
1100
+ assert_contains "$(cat "$LOG")" $'orca\x1f''worktree'$'\x1f''show'$'\x1f''--worktree'$'\x1f''id:wt-ship-mismatch'$'\x1f''--json' \
1101
+ "teardown did not resolve the mismatched Orca worktree id"
1102
+ assert_not_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''close' \
1103
+ "refused mismatched Orca ship teardown should not close terminals"
1104
+ assert_not_contains "$(cat "$LOG")" $'orca\x1f''worktree'$'\x1f''rm' \
1105
+ "refused mismatched Orca ship teardown should not remove worktrees"
1106
+ assert_present "$state/$id.meta" "refused mismatched Orca ship teardown should preserve metadata"
1107
+ pass "fm-teardown.sh backend=orca: ship teardown refuses id/path mismatches"
1108
+ }
1109
+
1110
+ test_teardown_refuses_orca_missing_worktree_id() {
1111
+ local proj wt data state config id out rc neutral
1112
+ id="orcamissingidz5"
1113
+ proj="$TMP_ROOT/missing-id-project"
1114
+ wt="$TMP_ROOT/missing-id-wt"
1115
+ data="$TMP_ROOT/missing-id-data"
1116
+ state="$TMP_ROOT/missing-id-state"
1117
+ config="$TMP_ROOT/missing-id-config"
1118
+ fm_git_worktree "$proj" "$wt" "fm/$id"
1119
+ mkdir -p "$data/$id" "$state" "$config"
1120
+ printf 'report\n' > "$data/$id/report.md"
1121
+ touch "$state/.last-watcher-beat"
1122
+ fm_write_meta "$state/$id.meta" \
1123
+ "window=fm-$id" "endpoint_task_id=$id" "terminal=term-missing-id" "worktree=$wt" "project=$proj" \
1124
+ "harness=claude" "kind=scout" "mode=no-mistakes" "yolo=off" "backend=orca" \
1125
+ "decisions_reviewed=1" "decision_keys="
1126
+ orca_case missing-id
1127
+ neutral=$(neutral_fm_root "$CASE_DIR/neutral")
1128
+ set +e
1129
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
1130
+ FM_ROOT_OVERRIDE="$neutral" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
1131
+ "$ROOT/bin/fm-teardown.sh" "$id" 2>&1 )
1132
+ rc=$?
1133
+ set -e
1134
+ [ "$rc" -ne 0 ] || fail "Orca teardown should refuse missing orca_worktree_id"
1135
+ assert_contains "$out" "missing orca_worktree_id" "teardown did not explain the missing Orca worktree id"
1136
+ assert_present "$state/$id.meta" "failed teardown must preserve task metadata"
1137
+ [ ! -s "$LOG" ] || fail "teardown should fail before closing terminals or removing worktrees without an Orca worktree id"
1138
+ pass "fm-teardown.sh backend=orca: refuses missing worktree ids before cleanup"
1139
+ }
1140
+
1141
+ test_teardown_refuses_orca_worktree_without_terminal_handle() {
1142
+ local proj wt data state config id out rc neutral
1143
+ id="orcanotermz0"
1144
+ proj="$TMP_ROOT/no-terminal-project"
1145
+ wt="$TMP_ROOT/no-terminal-wt"
1146
+ data="$TMP_ROOT/no-terminal-data"
1147
+ state="$TMP_ROOT/no-terminal-state"
1148
+ config="$TMP_ROOT/no-terminal-config"
1149
+ fm_git_worktree "$proj" "$wt" "fm/$id"
1150
+ mkdir -p "$data/$id" "$state" "$config"
1151
+ printf 'report\n' > "$data/$id/report.md"
1152
+ touch "$state/.last-watcher-beat"
1153
+ fm_write_meta "$state/$id.meta" \
1154
+ "window=fm-$id" "endpoint_task_id=$id" "worktree=$wt" "project=$proj" \
1155
+ "harness=claude" "kind=scout" "mode=no-mistakes" "yolo=off" \
1156
+ "backend=orca" "orca_worktree_id=wt-no-terminal" \
1157
+ "decisions_reviewed=1" "decision_keys="
1158
+ orca_case no-terminal
1159
+ neutral=$(neutral_fm_root "$CASE_DIR/neutral")
1160
+ set +e
1161
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
1162
+ FM_ROOT_OVERRIDE="$neutral" FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
1163
+ "$ROOT/bin/fm-teardown.sh" "$id" 2>&1 )
1164
+ rc=$?
1165
+ set -e
1166
+ [ "$rc" -ne 0 ] || fail "Orca teardown accepted metadata without a terminal handle"
1167
+ assert_contains "$out" "missing terminal" "teardown did not explain the incomplete Orca endpoint"
1168
+ [ ! -s "$LOG" ] || fail "teardown dispatched to Orca before rejecting the incomplete endpoint"
1169
+ assert_present "$state/$id.meta" "missing-terminal refusal removed task metadata"
1170
+ pass "fm-teardown.sh backend=orca: refuses incomplete worktree-only endpoint metadata before runtime dispatch"
1171
+ }
1172
+
1173
+ test_secondmate_force_teardown_removes_orca_child_via_orca() {
1174
+ local home subhome childproj childwt child_id neutral out rc
1175
+ home="$TMP_ROOT/orca-child-parent"
1176
+ subhome="$TMP_ROOT/orca-child-secondmate"
1177
+ childproj="$subhome/projects/alpha"
1178
+ childwt="$TMP_ROOT/orca-child-worktree"
1179
+ child_id="orcachildz6"
1180
+ mkdir -p "$home/state" "$home/data" "$subhome/state" "$subhome/projects"
1181
+ printf 'domain\n' > "$subhome/.fm-secondmate-home"
1182
+ fm_git_worktree "$childproj" "$childwt" "fm/$child_id"
1183
+ fm_write_meta "$home/state/domain.meta" \
1184
+ "window=firstmate:fm-domain" "worktree=$subhome" "project=$subhome" \
1185
+ "harness=echo" "kind=secondmate" "mode=secondmate" "yolo=off" \
1186
+ "home=$subhome" "projects=alpha"
1187
+ printf '%s\n' "- domain - Orca child cleanup (home: $subhome; scope: orca cleanup; projects: alpha; added 2026-07-03)" \
1188
+ > "$home/data/secondmates.md"
1189
+ fm_write_meta "$subhome/state/$child_id.meta" \
1190
+ "window=fm-$child_id" "endpoint_task_id=$child_id" \
1191
+ "terminal=term-child-cleanup" "worktree=$childwt" "project=$childproj" \
1192
+ "harness=claude" "kind=ship" "mode=no-mistakes" "yolo=off" \
1193
+ "backend=orca" "orca_worktree_id=wt-child-cleanup"
1194
+ orca_case secondmate-child-cleanup
1195
+ printf '{"ok":true,"result":{"worktree":{"id":"wt-child-cleanup","path":"%s"}}}\n' "$childwt" > "$RESP/1.out"
1196
+ printf '{"ok":true,"result":{"worktree":{"id":"wt-child-cleanup","path":"%s"}}}\n' "$childwt" > "$RESP/2.out"
1197
+ printf '{"ok":true,"result":{}}\n' > "$RESP/3.out"
1198
+ printf '{"ok":true,"result":{}}\n' > "$RESP/4.out"
1199
+ add_tmux_fake "$FB"
1200
+ neutral=$(neutral_fm_root "$CASE_DIR/neutral")
1201
+ set +e
1202
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
1203
+ FM_ROOT_OVERRIDE="$neutral" FM_HOME="$home" "$ROOT/bin/fm-teardown.sh" domain --force 2>&1 )
1204
+ rc=$?
1205
+ set -e
1206
+ expect_code 0 "$rc" "forced secondmate teardown should remove Orca child work through Orca"$'\n'"$out"
1207
+ assert_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''close'$'\x1f''--terminal'$'\x1f''term-child-cleanup'$'\x1f''--json' \
1208
+ "child cleanup did not close the recorded Orca terminal"
1209
+ assert_contains "$(cat "$LOG")" $'orca\x1f''worktree'$'\x1f''rm'$'\x1f''--worktree'$'\x1f''id:wt-child-cleanup'$'\x1f''--force'$'\x1f''--json' \
1210
+ "child cleanup did not remove the Orca worktree through orca worktree rm"
1211
+ assert_not_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''close'$'\x1f''--terminal'$'\x1f'"fm-$child_id" \
1212
+ "child cleanup closed the stable alias instead of the Orca terminal"
1213
+ assert_absent "$home/state/domain.meta" "parent metadata should be removed after forced teardown"
1214
+ pass "fm-teardown.sh --force: removes Orca secondmate children through Orca"
1215
+ }
1216
+
1217
+ test_secondmate_force_teardown_refuses_orca_child_id_path_mismatch() {
1218
+ local home subhome childproj childwt other_wt child_id neutral out rc
1219
+ home="$TMP_ROOT/orca-child-mismatch-parent"
1220
+ subhome="$TMP_ROOT/orca-child-mismatch-secondmate"
1221
+ childproj="$subhome/projects/alpha"
1222
+ childwt="$TMP_ROOT/orca-child-mismatch-worktree"
1223
+ other_wt="$TMP_ROOT/orca-child-mismatch-other-worktree"
1224
+ child_id="orcachildmismatchz1"
1225
+ mkdir -p "$home/state" "$home/data" "$subhome/state" "$subhome/projects"
1226
+ printf 'domain\n' > "$subhome/.fm-secondmate-home"
1227
+ fm_git_worktree "$childproj" "$childwt" "fm/$child_id"
1228
+ git -C "$childproj" worktree add --quiet -b "fm/$child_id-other" "$other_wt"
1229
+ fm_write_meta "$home/state/domain.meta" \
1230
+ "window=firstmate:fm-domain" "worktree=$subhome" "project=$subhome" \
1231
+ "harness=echo" "kind=secondmate" "mode=secondmate" "yolo=off" \
1232
+ "home=$subhome" "projects=alpha"
1233
+ printf '%s\n' "- domain - Orca child cleanup (home: $subhome; scope: orca cleanup; projects: alpha; added 2026-07-03)" \
1234
+ > "$home/data/secondmates.md"
1235
+ fm_write_meta "$subhome/state/$child_id.meta" \
1236
+ "window=fm-$child_id" "endpoint_task_id=$child_id" \
1237
+ "terminal=term-child-mismatch" "worktree=$childwt" "project=$childproj" \
1238
+ "harness=claude" "kind=ship" "mode=no-mistakes" "yolo=off" \
1239
+ "backend=orca" "orca_worktree_id=wt-child-mismatch"
1240
+ orca_case secondmate-child-mismatch
1241
+ printf '{"ok":true,"result":{"worktree":{"id":"wt-child-mismatch","path":"%s"}}}\n' "$other_wt" > "$RESP/1.out"
1242
+ add_tmux_fake "$FB"
1243
+ neutral=$(neutral_fm_root "$CASE_DIR/neutral")
1244
+ set +e
1245
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
1246
+ FM_ROOT_OVERRIDE="$neutral" FM_HOME="$home" "$ROOT/bin/fm-teardown.sh" domain --force 2>&1 )
1247
+ rc=$?
1248
+ set -e
1249
+ [ "$rc" -ne 0 ] || fail "forced secondmate teardown should refuse mismatched Orca child id/path"
1250
+ assert_contains "$out" "not inspected worktree" \
1251
+ "mismatched Orca child worktree path refusal should name the mismatch"
1252
+ assert_not_contains "$(cat "$LOG")" $'orca\x1f''terminal'$'\x1f''close' \
1253
+ "refused mismatched Orca child cleanup should not close terminals"
1254
+ assert_not_contains "$(cat "$LOG")" $'orca\x1f''worktree'$'\x1f''rm' \
1255
+ "refused mismatched Orca child cleanup should not remove worktrees"
1256
+ assert_present "$home/state/domain.meta" "refused forced secondmate teardown should preserve parent metadata"
1257
+ pass "fm-teardown.sh --force: refuses Orca child id/path mismatches"
1258
+ }
1259
+
1260
+ test_secondmate_force_teardown_refuses_partial_orca_child() {
1261
+ local home subhome childproj childwt child_id neutral out rc
1262
+ home="$TMP_ROOT/orca-partial-child-parent"
1263
+ subhome="$TMP_ROOT/orca-partial-child-secondmate"
1264
+ childproj="$subhome/projects/alpha"
1265
+ childwt="$TMP_ROOT/orca-partial-child-worktree"
1266
+ child_id="orcapartialz9"
1267
+ mkdir -p "$home/state" "$home/data" "$subhome/state" "$subhome/projects"
1268
+ printf 'domain\n' > "$subhome/.fm-secondmate-home"
1269
+ fm_git_worktree "$childproj" "$childwt" "fm/$child_id"
1270
+ fm_write_meta "$home/state/domain.meta" \
1271
+ "window=firstmate:fm-domain" "worktree=$subhome" "project=$subhome" \
1272
+ "harness=echo" "kind=secondmate" "mode=secondmate" "yolo=off" \
1273
+ "home=$subhome" "projects=alpha"
1274
+ printf '%s\n' "- domain - Orca partial child cleanup (home: $subhome; scope: orca cleanup; projects: alpha; added 2026-07-03)" \
1275
+ > "$home/data/secondmates.md"
1276
+ fm_write_meta "$subhome/state/$child_id.meta" \
1277
+ "window=fm-$child_id" "endpoint_task_id=$child_id" \
1278
+ "worktree=$childwt" "project=$childproj" \
1279
+ "harness=claude" "kind=ship" "mode=no-mistakes" "yolo=off" \
1280
+ "backend=orca" "orca_worktree_id=wt-partial-child"
1281
+ orca_case secondmate-partial-child-cleanup
1282
+ add_tmux_fake "$FB"
1283
+ neutral=$(neutral_fm_root "$CASE_DIR/neutral")
1284
+ set +e
1285
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
1286
+ FM_ROOT_OVERRIDE="$neutral" FM_HOME="$home" "$ROOT/bin/fm-teardown.sh" domain --force 2>&1 )
1287
+ rc=$?
1288
+ set -e
1289
+ [ "$rc" -ne 0 ] || fail "forced secondmate teardown accepted a child with no terminal identity"
1290
+ assert_contains "$out" "missing terminal" "partial child refusal did not explain the incomplete endpoint"
1291
+ [ ! -s "$LOG" ] || fail "partial child refusal dispatched to Orca or tmux"
1292
+ assert_present "$home/state/domain.meta" "partial child refusal removed parent metadata"
1293
+ assert_present "$subhome/state/$child_id.meta" "partial child refusal removed child metadata"
1294
+ pass "fm-teardown.sh --force: refuses partial Orca secondmate children before runtime dispatch"
1295
+ }
1296
+
1297
+ test_dispatcher_sources_orca_and_routes_primitives() {
1298
+ local out
1299
+ orca_case dispatch
1300
+ printf '{"result":{"terminal":{"tail":["via dispatch"]}}}\n' > "$RESP/1.out"
1301
+ out=$( PATH="$FB:$PATH" FM_ORCA_LOG="$LOG" FM_ORCA_RESPONSES="$RESP" \
1302
+ bash -c '. "$0/bin/fm-backend.sh"; fm_backend_validate orca; fm_backend_capture orca term-123 9' "$ROOT" )
1303
+ [ "$out" = "via dispatch" ] || fail "dispatcher should route capture to the Orca adapter, got '$out'"
1304
+ pass "fm-backend dispatcher: accepts orca and routes capture through bin/backends/orca.sh"
1305
+ }
1306
+
1307
+ test_capture_reads_terminal_tail_json
1308
+ test_capture_falls_back_to_text_fields
1309
+ test_capture_fails_on_orca_error_json
1310
+ test_runtime_check_accepts_ready_orca_status
1311
+ test_runtime_check_refuses_unready_orca_status
1312
+ test_send_text_submit_verifies_empty_composer_after_enter
1313
+ test_send_text_submit_borderless_claude_confirms
1314
+ test_composer_state_stale_banner_never_wins
1315
+ test_send_text_submit_retries_when_composer_stays_pending
1316
+ test_composer_state_popup_placeholder_fill_is_pending
1317
+ test_composer_state_bare_shell_prompt_is_unknown
1318
+ test_send_text_submit_popup_autocomplete_requires_second_enter
1319
+ test_send_literal_constructs_non_enter_send
1320
+ test_send_text_submit_reports_send_failed
1321
+ test_send_helpers_reject_orca_error_json
1322
+ test_send_key_enter_and_interrupt
1323
+ test_send_key_refuses_unknown_key
1324
+ test_send_key_refuses_escape_until_supported
1325
+ test_kill_is_best_effort_close
1326
+ test_remove_worktree_refuses_empty_id
1327
+ test_remove_worktree_rejects_orca_error_json
1328
+ test_worktree_path_resolves_id
1329
+ test_dispatcher_sources_orca_and_routes_primitives
1330
+ test_json_get_ignores_undocumented_terminal_id_shapes
1331
+ test_worktree_and_terminal_helpers_parse_json
1332
+ test_worktree_create_removes_worktree_when_path_missing
1333
+ test_spawn_preserves_orca_metadata_when_pathless_worktree_cleanup_fails
1334
+ test_spawn_writes_orca_metadata_and_launches_harness
1335
+ test_spawn_refuses_orca_secondmate_before_home_mutation
1336
+ test_spawn_refuses_orca_when_runtime_not_ready
1337
+ test_spawn_refuses_orca_nonisolated_worktree
1338
+ test_spawn_removes_orca_worktree_when_terminal_create_fails
1339
+ test_spawn_preserves_orca_metadata_when_abort_cleanup_fails
1340
+ test_spawn_releases_orca_resources_when_metadata_write_fails
1341
+ test_peek_send_and_crew_state_route_through_orca_meta
1342
+ test_peek_and_crew_state_fail_closed_on_orca_error_json
1343
+ test_target_exists_rejects_orca_error_json
1344
+ test_scout_teardown_removes_orca_worktree_via_helper
1345
+ test_scout_teardown_refuses_orca_id_path_mismatch
1346
+ test_teardown_removes_orca_worktree_when_path_missing
1347
+ test_teardown_preserves_metadata_when_orca_remove_error_json
1348
+ test_scout_teardown_refuses_orca_missing_report_when_path_missing
1349
+ test_ship_teardown_refuses_orca_missing_worktree_path
1350
+ test_ship_teardown_removes_orca_worktree_when_id_path_matches
1351
+ test_ship_teardown_refuses_orca_unresolvable_worktree_id
1352
+ test_ship_teardown_refuses_orca_id_path_mismatch
1353
+ test_teardown_refuses_orca_missing_worktree_id
1354
+ test_teardown_refuses_orca_worktree_without_terminal_handle
1355
+ test_secondmate_force_teardown_removes_orca_child_via_orca
1356
+ test_secondmate_force_teardown_refuses_orca_child_id_path_mismatch
1357
+ test_secondmate_force_teardown_refuses_partial_orca_child