@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,1178 @@
1
+ #!/usr/bin/env bash
2
+ # Behavior tests for fm-bootstrap.sh reporting and session-start clone refresh bounds.
3
+ #
4
+ # Bootstrap prints one block or line per actionable problem, optional verbose
5
+ # BOOTSTRAP_INFO fact, or completed bootstrap no-action fact and is silent when
6
+ # all is well. firstmate consumes the exact 'MISSING: treehouse (install: ...)',
7
+ # 'MISSING: tasks-axi (install: ...)', 'MISSING: quota-axi (install: ...)',
8
+ # 'MISSING: gh-axi (install: ...)', 'MISSING: lavish-axi (install: ...)', and
9
+ # 'BOOTSTRAP_INFO: ...' lines, so those contracts are pinned verbatim. The cases
10
+ # are table-driven over the inputs that vary: whether `treehouse get --help`
11
+ # advertises --lease, which (if any) tasks-axi version is on PATH, whether
12
+ # tasks-axi update advertises --archive-body, whether its mv help advertises
13
+ # multi-ID moves, whether quota-axi is on PATH,
14
+ # whether the local backend config opts out of tasks-axi backlog mutations,
15
+ # which no-mistakes version is on PATH, which gh-axi version is on PATH, and
16
+ # which lavish-axi version is on PATH.
17
+ # Dedicated fleet-sync cases pin the computed bootstrap timeout, explicit
18
+ # override, blank-env defaulting, partial-output relay, and pre-launch timeout
19
+ # scan.
20
+ # Dedicated network-phase cases pin FM_BOOTSTRAP_NETWORK as a true partition of
21
+ # one run into its local and network halves, and the one-hop tasks-axi
22
+ # compatibility handoff that keeps a session start from paying for that verdict
23
+ # twice.
24
+ set -u
25
+
26
+ # shellcheck source=tests/lib.sh disable=SC1091
27
+ . "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
28
+
29
+ BASE_PATH=${FM_TEST_BASE_PATH:-/usr/bin:/bin:/usr/sbin:/sbin}
30
+ TMP_ROOT=$(fm_test_tmproot fm-bootstrap-tests)
31
+ export FM_BACKEND_CMUX_BUNDLE_BIN="$TMP_ROOT/no-bundled-cmux"
32
+
33
+ # Hermetic runtime-backend detection. These cases pin the backend per-home via
34
+ # config/backend; the dev shell's ambient runtime markers ($TMUX inside tmux,
35
+ # HERDR_ENV inside herdr, CMUX_* inside a cmux terminal) must not leak into
36
+ # fm_backend_name and flip a default-backend case onto a non-tmux backend. Unset
37
+ # them once so the suite resolves the tmux reference backend unless a case says
38
+ # otherwise - the same hermeticity discipline as pinning PATH via BASE_PATH.
39
+ unset TMUX TMUX_PANE HERDR_ENV HERDR_PANE_ID HERDR_SESSION HERDR_SOCKET_PATH \
40
+ CMUX_WORKSPACE_ID CMUX_SURFACE_ID CMUX_SOCKET_PATH CMUX_TAB_ID CMUX_PANEL_ID 2>/dev/null || true
41
+
42
+ # A fake toolchain where every required tool is present and gh is authenticated.
43
+ # treehouse's `get --help` advertises --lease only when FM_FAKE_TREEHOUSE_LEASE_HELP=1.
44
+ make_fake_toolchain() {
45
+ local dir=$1 fakebin
46
+ fakebin=$(fm_fakebin "$dir")
47
+ fm_fake_exit0 "$fakebin" tmux node chrome-devtools-axi
48
+ fm_fake_version_tool "$fakebin" lavish-axi FM_FAKE_LAVISH_AXI_VERSION 0.1.46
49
+ cat > "$fakebin/gh-axi" <<'SH'
50
+ #!/usr/bin/env bash
51
+ if [ "${1:-}" = --version ]; then
52
+ printf '%s\n' "${FM_FAKE_GH_AXI_VERSION:-0.1.29}"
53
+ exit 0
54
+ fi
55
+ exit 0
56
+ SH
57
+ chmod +x "$fakebin/gh-axi"
58
+ cat > "$fakebin/gh" <<'SH'
59
+ #!/usr/bin/env bash
60
+ if [ "${1:-}" = auth ] && [ "${2:-}" = status ]; then
61
+ exit 0
62
+ fi
63
+ exit 0
64
+ SH
65
+ chmod +x "$fakebin/gh"
66
+ cat > "$fakebin/treehouse" <<'SH'
67
+ #!/usr/bin/env bash
68
+ if [ "${1:-}" = get ] && [ "${2:-}" = --help ]; then
69
+ if [ "${FM_FAKE_TREEHOUSE_LEASE_HELP:-}" = 1 ]; then
70
+ printf '%s\n' 'Usage: treehouse get [--lease] [--lease-holder <holder>]'
71
+ else
72
+ printf '%s\n' 'Usage: treehouse get'
73
+ fi
74
+ exit 0
75
+ fi
76
+ exit 0
77
+ SH
78
+ chmod +x "$fakebin/treehouse"
79
+ cat > "$fakebin/no-mistakes" <<'SH'
80
+ #!/usr/bin/env bash
81
+ if [ "${1:-}" = --version ]; then
82
+ printf '%s\n' "${FM_FAKE_NO_MISTAKES_VERSION:-no-mistakes version v1.46.0 (fake) 2026-06-27T00:02:18Z}"
83
+ exit 0
84
+ fi
85
+ exit 0
86
+ SH
87
+ chmod +x "$fakebin/no-mistakes"
88
+ add_tasks_axi "$fakebin" "0.2.4"
89
+ add_quota_axi "$fakebin"
90
+ printf '%s\n' "$fakebin"
91
+ }
92
+
93
+ add_quota_axi() {
94
+ local fakebin=$1
95
+ cat > "$fakebin/quota-axi" <<'SH'
96
+ #!/usr/bin/env bash
97
+ if [ "${1:-}" = --version ]; then
98
+ printf '%s\n' "${FM_FAKE_QUOTA_AXI_VERSION:-0.1.29}"
99
+ exit 0
100
+ fi
101
+ exit 0
102
+ SH
103
+ chmod +x "$fakebin/quota-axi"
104
+ }
105
+
106
+ add_tasks_axi() {
107
+ local fakebin=$1 version=$2 archive_body=${3:-yes} multi_id=${4:-yes} archive_line mv_usage
108
+ archive_line=""
109
+ [ "$archive_body" = yes ] && archive_line=' --archive-body'
110
+ mv_usage='usage: tasks-axi mv <id> [<id>...] --to <path-or-dir>'
111
+ [ "$multi_id" = yes ] || mv_usage='usage: tasks-axi mv <id> --to <path-or-dir>'
112
+ cat > "$fakebin/tasks-axi" <<SH
113
+ #!/usr/bin/env bash
114
+ if [ "\${1:-}" = --version ]; then
115
+ printf '%s\n' '$version'
116
+ exit 0
117
+ fi
118
+ if [ "\${1:-}" = update ] && [ "\${2:-}" = --help ]; then
119
+ printf '%s\n' 'usage: tasks-axi update <id> [flags]'
120
+ printf '%s\n' ' --body-file <path>'
121
+ [ -z '$archive_line' ] || printf '%s\n' '$archive_line'
122
+ exit 0
123
+ fi
124
+ if [ "\${1:-}" = mv ] && [ "\${2:-}" = --help ]; then
125
+ printf '%s\n' '$mv_usage'
126
+ exit 0
127
+ fi
128
+ exit 0
129
+ SH
130
+ chmod +x "$fakebin/tasks-axi"
131
+ }
132
+
133
+ add_real_jq() {
134
+ local fakebin=$1 real_jq
135
+ real_jq=$(command -v jq 2>/dev/null) || fail "jq is required for dispatch profile validation tests"
136
+ cat > "$fakebin/jq" <<SH
137
+ #!/usr/bin/env bash
138
+ exec '$real_jq' "\$@"
139
+ SH
140
+ chmod +x "$fakebin/jq"
141
+ }
142
+
143
+ make_fake_fleet_sync_root() {
144
+ local dir=$1 fake_root
145
+ fake_root="$dir/fake-root"
146
+ mkdir -p "$fake_root/bin"
147
+ cat > "$fake_root/bin/fm-fleet-sync.sh" <<'SH'
148
+ #!/usr/bin/env bash
149
+ [ -z "${FM_FAKE_FLEET_SYNC_STARTED_MARKER:-}" ] || : > "$FM_FAKE_FLEET_SYNC_STARTED_MARKER"
150
+ printf '%s\n' 'alpha: synced'
151
+ printf '%s\n' 'beta: skipped: no origin remote'
152
+ exec perl -e 'sleep 300'
153
+ SH
154
+ chmod +x "$fake_root/bin/fm-fleet-sync.sh"
155
+ printf '%s\n' "$fake_root"
156
+ }
157
+
158
+ add_origin_backed_projects() {
159
+ local home=$1 count=$2 i repo
160
+ mkdir -p "$home/projects"
161
+ i=1
162
+ while [ "$i" -le "$count" ]; do
163
+ repo=$(printf '%s/projects/repo-%02d' "$home" "$i")
164
+ git init -q "$repo"
165
+ git -C "$repo" remote add origin "file://$home/remotes/repo-$i.git"
166
+ i=$((i + 1))
167
+ done
168
+ }
169
+
170
+ add_no_origin_projects() {
171
+ local home=$1 count=$2 i repo
172
+ mkdir -p "$home/projects"
173
+ i=1
174
+ while [ "$i" -le "$count" ]; do
175
+ repo=$(printf '%s/projects/local-%02d' "$home" "$i")
176
+ git init -q "$repo"
177
+ i=$((i + 1))
178
+ done
179
+ }
180
+
181
+ run_bootstrap_timeout_case() {
182
+ local home=$1 fake_root=$2 fakebin=$3 override started_marker git_record wait_for_marker
183
+ override=__unset__
184
+ started_marker=${5:-}
185
+ git_record=${6:-}
186
+ wait_for_marker=${7:-0}
187
+ [ "$#" -lt 4 ] || override=$4
188
+ (
189
+ # shellcheck disable=SC2317,SC2329 # Exported and invoked by the bootstrap subprocess.
190
+ sleep() {
191
+ local inc=${1:-1}
192
+ SECONDS=$((SECONDS + inc))
193
+ # Advance fake time quickly, but yield on every tick so the background
194
+ # fleet-sync process can deterministically write its partial output before
195
+ # the simulated timeout kills it, even on a busy full-suite runner.
196
+ command sleep 0.01
197
+ }
198
+ # shellcheck disable=SC2317,SC2329 # Exported and invoked by the bootstrap subprocess.
199
+ git() {
200
+ local tries
201
+ if [ "${FM_FAKE_GIT_WAIT_FOR_FLEET_START:-}" = 1 ] && [ -n "${FM_FAKE_FLEET_SYNC_STARTED_MARKER:-}" ]; then
202
+ tries=0
203
+ while [ "$tries" -lt 5 ] && [ ! -e "$FM_FAKE_FLEET_SYNC_STARTED_MARKER" ]; do
204
+ command sleep 0.01
205
+ tries=$((tries + 1))
206
+ done
207
+ fi
208
+ if [ -n "${FM_FAKE_GIT_SYNC_STARTED_RECORD:-}" ] && [ -n "${FM_FAKE_FLEET_SYNC_STARTED_MARKER:-}" ] && [ -e "$FM_FAKE_FLEET_SYNC_STARTED_MARKER" ]; then
209
+ printf '%s\n' "$*" >> "$FM_FAKE_GIT_SYNC_STARTED_RECORD"
210
+ fi
211
+ command git "$@"
212
+ }
213
+ export -f sleep
214
+ export -f git
215
+ if [ "$override" = __unset__ ]; then
216
+ PATH="$fakebin:$BASE_PATH" FM_HOME="$home" FM_ROOT_OVERRIDE="$fake_root" \
217
+ FM_FAKE_FLEET_SYNC_STARTED_MARKER="$started_marker" \
218
+ FM_FAKE_GIT_SYNC_STARTED_RECORD="$git_record" \
219
+ FM_FAKE_GIT_WAIT_FOR_FLEET_START="$wait_for_marker" \
220
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 "$ROOT/bin/fm-bootstrap.sh" 2>/dev/null
221
+ else
222
+ PATH="$fakebin:$BASE_PATH" FM_HOME="$home" FM_ROOT_OVERRIDE="$fake_root" \
223
+ FM_FLEET_SYNC_BOOTSTRAP_TIMEOUT="$override" \
224
+ FM_FAKE_FLEET_SYNC_STARTED_MARKER="$started_marker" \
225
+ FM_FAKE_GIT_SYNC_STARTED_RECORD="$git_record" \
226
+ FM_FAKE_GIT_WAIT_FOR_FLEET_START="$wait_for_marker" \
227
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 "$ROOT/bin/fm-bootstrap.sh" 2>/dev/null
228
+ fi
229
+ )
230
+ }
231
+
232
+ assert_timeout_report() {
233
+ local out=$1 expected_timeout=$2 timing timeout elapsed
234
+ timing=$(printf '%s\n' "$out" | sed -n 's/^FLEET_SYNC: fleet: skipped: bootstrap refresh timed out (timeout=\([0-9][0-9]*\)s elapsed=\([0-9][0-9]*\)s)$/\1 \2/p')
235
+ [ -n "$timing" ] || fail "missing fleet-sync timeout report"
236
+ timeout=${timing%% *}
237
+ elapsed=${timing#* }
238
+ [ "$timeout" -eq "$expected_timeout" ] || fail "expected timeout=${expected_timeout}s, got timeout=${timeout}s"
239
+ [ "$elapsed" -ge "$timeout" ] || fail "expected elapsed >= timeout, got elapsed=${elapsed}s timeout=${timeout}s"
240
+ }
241
+
242
+ # Each row (fields are '^'-separated; the install URL contains a literal '|'):
243
+ # <label>^<lease 1/0>^<tasks-axi version or ->^<quota 1/0>^<backend or ->^<mode>^<expect>^<notcontains>
244
+ # mode=empty -> output must be empty (expect/notcontains ignored)
245
+ # mode=exact -> output must equal <expect>
246
+ # mode=grep -> output must contain <expect> (fixed string); <notcontains> must not appear
247
+ test_bootstrap_reporting() {
248
+ local label lease tasks quota backend mode expect notcontains case_dir fakebin out n archive_body multi_id
249
+ n=0
250
+ while IFS='^' read -r label lease tasks quota backend mode expect notcontains; do
251
+ [ -n "$label" ] || continue
252
+ n=$((n + 1))
253
+ case_dir="$TMP_ROOT/case-$n"
254
+ mkdir -p "$case_dir/home"
255
+ if [ "$backend" != "-" ]; then
256
+ mkdir -p "$case_dir/home/config"
257
+ printf '%s\n' "$backend" > "$case_dir/home/config/backlog-backend"
258
+ fi
259
+ fakebin=$(make_fake_toolchain "$case_dir")
260
+ if [ "$tasks" = "-" ]; then
261
+ rm -f "$fakebin/tasks-axi"
262
+ else
263
+ archive_body=yes
264
+ multi_id=yes
265
+ case "$tasks" in
266
+ *:noarchive)
267
+ archive_body=no
268
+ tasks=${tasks%:noarchive}
269
+ ;;
270
+ esac
271
+ case "$tasks" in
272
+ *:nomulti)
273
+ multi_id=no
274
+ tasks=${tasks%:nomulti}
275
+ ;;
276
+ esac
277
+ add_tasks_axi "$fakebin" "$tasks" "$archive_body" "$multi_id"
278
+ fi
279
+ if [ "$quota" = "0" ]; then
280
+ rm -f "$fakebin/quota-axi"
281
+ fi
282
+ # FM_ROOT_OVERRIDE points the worktree-tangle check at the non-git home dir so
283
+ # it stays inert: this suite pins tool detection, not the tangle guard, and the
284
+ # ambient checkout (CI runs on a feature branch) must not leak a TANGLE line in.
285
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
286
+ FM_FAKE_TREEHOUSE_LEASE_HELP="$lease" "$ROOT/bin/fm-bootstrap.sh")
287
+ case "$mode" in
288
+ empty)
289
+ [ -z "$out" ] || fail "$label: expected silence, got: $out" ;;
290
+ exact)
291
+ [ "$out" = "$expect" ] || fail "$label: expected '$expect', got: $out" ;;
292
+ grep)
293
+ printf '%s\n' "$out" | grep -Fx "$expect" >/dev/null || fail "$label: missing '$expect' (got: $out)"
294
+ if [ -n "$notcontains" ]; then
295
+ printf '%s\n' "$out" | grep -F "$notcontains" >/dev/null && fail "$label: unexpected '$notcontains' in: $out"
296
+ fi
297
+ ;;
298
+ esac
299
+ done <<'ROWS'
300
+ treehouse --lease support is accepted silently^1^0.2.4^1^manual^empty^^
301
+ treehouse without --lease reports an upgrade, gh auth is fine^0^0.2.4^1^-^grep^MISSING: treehouse (install: curl -fsSL https://kunchenguid.github.io/treehouse/install.sh | sh)^NEEDS_GH_AUTH
302
+ compatible tasks-axi is silent by default^1^0.2.4^1^-^empty^^
303
+ missing tasks-axi is required by default^1^-^1^-^exact^MISSING: tasks-axi (install: npm install -g tasks-axi)^
304
+ incompatible tasks-axi is required by default^1^0.1.0^1^-^exact^MISSING: tasks-axi (install: npm install -g tasks-axi)^
305
+ tasks-axi without archive-body is required by default^1^0.2.4:noarchive^1^-^exact^MISSING: tasks-axi (install: npm install -g tasks-axi)^
306
+ tasks-axi without multi-id mv is required by default^1^0.2.4:nomulti^1^-^exact^MISSING: tasks-axi (install: npm install -g tasks-axi)^
307
+ missing quota-axi is required by default^1^0.2.4^0^manual^exact^MISSING: quota-axi (install: npm install -g quota-axi)^
308
+ manual backlog backend still requires missing tasks-axi^1^-^1^manual^exact^MISSING: tasks-axi (install: npm install -g tasks-axi)^
309
+ manual backlog backend suppresses tasks-axi availability^1^0.2.4^1^manual^empty^^
310
+ ROWS
311
+ pass "bootstrap reports treehouse lease + tasks-axi/quota-axi bootstrap contracts"
312
+ }
313
+
314
+ test_no_mistakes_min_version() {
315
+ local label version mode case_dir fakebin out missing n
316
+ missing='MISSING: no-mistakes (install: curl -fsSL https://raw.githubusercontent.com/kunchenguid/no-mistakes/main/docs/install.sh | sh)'
317
+ n=0
318
+ while IFS='^' read -r label version mode; do
319
+ [ -n "$label" ] || continue
320
+ n=$((n + 1))
321
+ case_dir="$TMP_ROOT/no-mistakes-$n"
322
+ mkdir -p "$case_dir/home"
323
+ mkdir -p "$case_dir/home/config"
324
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
325
+ fakebin=$(make_fake_toolchain "$case_dir")
326
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
327
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 FM_FAKE_NO_MISTAKES_VERSION="$version" "$ROOT/bin/fm-bootstrap.sh")
328
+ case "$mode" in
329
+ empty)
330
+ [ -z "$out" ] || fail "$label: expected silence, got: $out" ;;
331
+ missing)
332
+ [ "$out" = "$missing" ] || fail "$label: expected '$missing', got: $out" ;;
333
+ esac
334
+ done <<'ROWS'
335
+ minimum no-mistakes version is accepted^no-mistakes version v1.46.0 (fake)^empty
336
+ newer no-mistakes minor is accepted^no-mistakes version v1.47.0 (fake)^empty
337
+ newer no-mistakes major is accepted^no-mistakes version v2.0.0 (fake)^empty
338
+ older no-mistakes patch reports an upgrade^no-mistakes version v1.45.4 (fake)^missing
339
+ unparseable no-mistakes version reports an upgrade^no-mistakes development build^missing
340
+ ROWS
341
+ pass "bootstrap enforces no-mistakes minimum version"
342
+ }
343
+
344
+ test_gh_axi_min_version() {
345
+ local label version mode case_dir fakebin out missing n
346
+ missing='MISSING: gh-axi (install: npm install -g gh-axi && gh-axi setup hooks)'
347
+ n=0
348
+ while IFS='^' read -r label version mode; do
349
+ [ -n "$label" ] || continue
350
+ n=$((n + 1))
351
+ case_dir="$TMP_ROOT/gh-axi-$n"
352
+ mkdir -p "$case_dir/home/config"
353
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
354
+ fakebin=$(make_fake_toolchain "$case_dir")
355
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
356
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 FM_FAKE_GH_AXI_VERSION="$version" "$ROOT/bin/fm-bootstrap.sh")
357
+ case "$mode" in
358
+ empty)
359
+ [ -z "$out" ] || fail "$label: expected silence, got: $out" ;;
360
+ missing)
361
+ [ "$out" = "$missing" ] || fail "$label: expected '$missing', got: $out" ;;
362
+ esac
363
+ done <<'ROWS'
364
+ minimum gh-axi version is accepted^0.1.29^empty
365
+ newer gh-axi patch is accepted^0.1.30^empty
366
+ newer gh-axi minor is accepted^0.2.0^empty
367
+ newer gh-axi major is accepted^1.0.0^empty
368
+ older gh-axi patch reports an upgrade^0.1.19^missing
369
+ much older gh-axi minor reports an upgrade^0.0.9^missing
370
+ unparseable gh-axi version reports an upgrade^gh-axi development build^missing
371
+ ROWS
372
+ pass "bootstrap enforces gh-axi minimum version"
373
+ }
374
+
375
+ test_lavish_axi_min_version() {
376
+ local label version mode case_dir fakebin out missing n
377
+ missing='MISSING: lavish-axi (install: npm install -g lavish-axi && lavish-axi setup hooks)'
378
+ n=0
379
+ while IFS='^' read -r label version mode; do
380
+ [ -n "$label" ] || continue
381
+ n=$((n + 1))
382
+ case_dir="$TMP_ROOT/lavish-axi-$n"
383
+ mkdir -p "$case_dir/home/config"
384
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
385
+ fakebin=$(make_fake_toolchain "$case_dir")
386
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
387
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 FM_FAKE_LAVISH_AXI_VERSION="$version" "$ROOT/bin/fm-bootstrap.sh")
388
+ case "$mode" in
389
+ empty)
390
+ [ -z "$out" ] || fail "$label: expected silence, got: $out" ;;
391
+ missing)
392
+ [ "$out" = "$missing" ] || fail "$label: expected '$missing', got: $out" ;;
393
+ esac
394
+ done <<'ROWS'
395
+ minimum lavish-axi version is accepted^0.1.46^empty
396
+ newer lavish-axi patch is accepted^0.1.47^empty
397
+ newer lavish-axi minor is accepted^0.2.0^empty
398
+ newer lavish-axi major is accepted^1.0.0^empty
399
+ the patch just below the floor reports an upgrade^0.1.45^missing
400
+ much older lavish-axi minor reports an upgrade^0.0.9^missing
401
+ unparseable lavish-axi version reports an upgrade^lavish-axi development build^missing
402
+ ROWS
403
+ pass "bootstrap enforces lavish-axi minimum version"
404
+ }
405
+
406
+ test_tasks_axi_min_version() {
407
+ local label version mode case_dir fakebin out missing n archive_body multi_id
408
+ missing='MISSING: tasks-axi (install: npm install -g tasks-axi)'
409
+ n=0
410
+ while IFS='^' read -r label version mode; do
411
+ [ -n "$label" ] || continue
412
+ n=$((n + 1))
413
+ case_dir="$TMP_ROOT/tasks-axi-$n"
414
+ mkdir -p "$case_dir/home/config"
415
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
416
+ fakebin=$(make_fake_toolchain "$case_dir")
417
+ archive_body=yes
418
+ multi_id=yes
419
+ case "$version" in
420
+ *:noarchive)
421
+ archive_body=no
422
+ version=${version%:noarchive}
423
+ ;;
424
+ esac
425
+ case "$version" in
426
+ *:nomulti)
427
+ multi_id=no
428
+ version=${version%:nomulti}
429
+ ;;
430
+ esac
431
+ add_tasks_axi "$fakebin" "$version" "$archive_body" "$multi_id"
432
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
433
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 "$ROOT/bin/fm-bootstrap.sh")
434
+ case "$mode" in
435
+ empty)
436
+ [ -z "$out" ] || fail "$label: expected silence, got: $out" ;;
437
+ missing)
438
+ [ "$out" = "$missing" ] || fail "$label: expected '$missing', got: $out" ;;
439
+ esac
440
+ done <<'ROWS'
441
+ minimum tasks-axi version is accepted^0.2.4^empty
442
+ newer tasks-axi patch is accepted^0.2.5^empty
443
+ newer tasks-axi minor is accepted^0.3.0^empty
444
+ newer tasks-axi major is accepted^1.0.0^empty
445
+ older tasks-axi with features reports an upgrade^0.1.1^missing
446
+ the patch just below the floor reports an upgrade^0.2.3^missing
447
+ unparseable tasks-axi version reports an upgrade^tasks-axi development build^missing
448
+ tasks-axi at floor without archive-body reports an upgrade^0.2.4:noarchive^missing
449
+ tasks-axi at floor without multi-id reports an upgrade^0.2.4:nomulti^missing
450
+ ROWS
451
+ pass "bootstrap enforces tasks-axi minimum version"
452
+ }
453
+
454
+ # These rows exercise the real bootstrap check with a fake quota-axi answering
455
+ # --version: below the floor produces MISSING, while at or above is silent.
456
+ test_quota_axi_min_version() {
457
+ local label version mode case_dir fakebin out missing n
458
+ missing='MISSING: quota-axi (install: npm install -g quota-axi)'
459
+ n=0
460
+ while IFS='^' read -r label version mode; do
461
+ [ -n "$label" ] || continue
462
+ n=$((n + 1))
463
+ case_dir="$TMP_ROOT/quota-axi-$n"
464
+ mkdir -p "$case_dir/home/config"
465
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
466
+ fakebin=$(make_fake_toolchain "$case_dir")
467
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
468
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 FM_FAKE_QUOTA_AXI_VERSION="$version" "$ROOT/bin/fm-bootstrap.sh")
469
+ case "$mode" in
470
+ empty)
471
+ [ -z "$out" ] || fail "$label: expected silence, got: $out" ;;
472
+ missing)
473
+ [ "$out" = "$missing" ] || fail "$label: expected '$missing', got: $out" ;;
474
+ esac
475
+ done <<'ROWS'
476
+ minimum quota-axi version is accepted^0.1.29^empty
477
+ newer quota-axi patch is accepted^0.1.30^empty
478
+ newer quota-axi minor is accepted^0.2.0^empty
479
+ newer quota-axi major is accepted^1.0.0^empty
480
+ the patch just below the floor reports an upgrade^0.1.28^missing
481
+ much older quota-axi minor reports an upgrade^0.0.9^missing
482
+ unparseable quota-axi version reports an upgrade^quota-axi development build^missing
483
+ ROWS
484
+ pass "bootstrap enforces quota-axi minimum version"
485
+ }
486
+
487
+ test_git_is_required_with_supported_install_instruction() {
488
+ local case_dir fakebin bash_env out expected
489
+ case_dir="$TMP_ROOT/git-required"
490
+ mkdir -p "$case_dir/home/config"
491
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
492
+ fakebin=$(make_fake_toolchain "$case_dir")
493
+ bash_env="$case_dir/no-git.bash"
494
+ cat > "$bash_env" <<'SH'
495
+ command() {
496
+ if [ "${1:-}" = -v ] && [ "${2:-}" = git ]; then
497
+ return 1
498
+ fi
499
+ builtin command "$@"
500
+ }
501
+ git() {
502
+ return 127
503
+ }
504
+ SH
505
+
506
+ out=$(PATH="$fakebin:$BASE_PATH" BASH_ENV="$bash_env" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
507
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 "$ROOT/bin/fm-bootstrap.sh")
508
+ expected="MISSING: git (install: brew install git # or the platform's package manager)"
509
+ [ "$out" = "$expected" ] || fail "missing git should report the supported install instruction, got: $out"
510
+ pass "bootstrap requires git with an install instruction"
511
+ }
512
+
513
+ test_orca_backend_gates_orca_tool_only_when_selected() {
514
+ local case_dir fakebin out missing_orca
515
+ missing_orca="MISSING: orca (install: brew install orca # or the platform's package manager)"
516
+
517
+ case_dir="$TMP_ROOT/orca-backend-selected"
518
+ mkdir -p "$case_dir/home/config"
519
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
520
+ printf '%s\n' orca > "$case_dir/home/config/backend"
521
+ fakebin=$(make_fake_toolchain "$case_dir")
522
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
523
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 "$ROOT/bin/fm-bootstrap.sh")
524
+ [ "$out" = "$missing_orca" ] || fail "backend=orca should require only the Orca-specific missing tool, got: $out"
525
+
526
+ case_dir="$TMP_ROOT/orca-backend-not-selected"
527
+ mkdir -p "$case_dir/home/config"
528
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
529
+ fakebin=$(make_fake_toolchain "$case_dir")
530
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
531
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 "$ROOT/bin/fm-bootstrap.sh")
532
+ assert_not_contains "$out" "MISSING: orca" "bootstrap should not require orca unless backend=orca is selected"
533
+ pass "bootstrap: backend=orca gates the Orca CLI without requiring it on the default backend"
534
+ }
535
+
536
+ # Build a fake toolchain with tmux REMOVED and the named backend session CLI(s)
537
+ # plus jq added, so a backend that must NOT require tmux can be proven silent
538
+ # with tmux absent. Echoes the fakebin dir. The removed tmux is what makes these
539
+ # cases catch the old "everything but orca demands tmux" bug: with the buggy
540
+ # TOOLS list a herdr/zellij/cmux home would report MISSING: tmux here.
541
+ make_fake_toolchain_no_tmux() { # <case-dir> <extra-cli...>
542
+ local dir=$1 fakebin
543
+ shift
544
+ fakebin=$(make_fake_toolchain "$dir")
545
+ rm -f "$fakebin/tmux"
546
+ fm_fake_exit0 "$fakebin" jq "$@"
547
+ printf '%s\n' "$fakebin"
548
+ }
549
+
550
+ test_session_provider_backends_do_not_require_tmux() {
551
+ local backend cli case_dir fakebin out
552
+ # herdr/zellij/cmux are session providers only: they require their own CLI, jq,
553
+ # and treehouse, never tmux. With all genuine deps present and tmux absent,
554
+ # bootstrap must be silent.
555
+ while IFS='^' read -r backend cli; do
556
+ [ -n "$backend" ] || continue
557
+ case_dir="$TMP_ROOT/$backend-no-tmux"
558
+ mkdir -p "$case_dir/home/config"
559
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
560
+ printf '%s\n' "$backend" > "$case_dir/home/config/backend"
561
+ fakebin=$(make_fake_toolchain_no_tmux "$case_dir" "$cli")
562
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
563
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 "$ROOT/bin/fm-bootstrap.sh")
564
+ [ -z "$out" ] || fail "backend=$backend with tmux absent but its own deps present should be silent, got: $out"
565
+ done <<'ROWS'
566
+ herdr^herdr
567
+ zellij^zellij
568
+ cmux^cmux
569
+ ROWS
570
+ pass "bootstrap: session-provider backends require their own CLI + jq + treehouse, never tmux"
571
+ }
572
+
573
+ test_session_provider_backends_gate_own_cli_not_tmux() {
574
+ local backend cli case_dir fakebin out missing
575
+ # With the backend's OWN session CLI absent (and tmux also absent), bootstrap
576
+ # must fail closed on the genuine dep and never substitute a false tmux demand.
577
+ while IFS='^' read -r backend cli; do
578
+ [ -n "$backend" ] || continue
579
+ case_dir="$TMP_ROOT/$backend-missing-cli"
580
+ mkdir -p "$case_dir/home/config"
581
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
582
+ printf '%s\n' "$backend" > "$case_dir/home/config/backend"
583
+ # Toolchain has jq + treehouse but NOT the session CLI and NOT tmux.
584
+ fakebin=$(make_fake_toolchain_no_tmux "$case_dir")
585
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
586
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 "$ROOT/bin/fm-bootstrap.sh")
587
+ if [ "$backend" = herdr ]; then
588
+ missing="MISSING_MANUAL: herdr (instructions: https://herdr.dev)"
589
+ else
590
+ missing="MISSING: $cli"
591
+ fi
592
+ assert_contains "$out" "$missing" "backend=$backend must fail closed on its own missing session CLI"
593
+ if [ "$backend" = herdr ]; then
594
+ assert_not_contains "$out" "MISSING: herdr (install:" \
595
+ "backend=herdr must not advertise manual guidance as an executable install command"
596
+ fi
597
+ assert_not_contains "$out" "MISSING: tmux" "backend=$backend must not demand tmux when its own CLI is missing"
598
+ done <<'ROWS'
599
+ herdr^herdr
600
+ zellij^zellij
601
+ cmux^cmux
602
+ ROWS
603
+ pass "bootstrap: a session-provider backend gates its own CLI, never a false tmux requirement"
604
+ }
605
+
606
+ test_herdr_install_requires_manual_action() {
607
+ local out status
608
+ out=$("$ROOT/bin/fm-bootstrap.sh" install herdr 2>&1)
609
+ status=$?
610
+ [ "$status" -ne 0 ] || fail "install herdr should fail instead of evaluating its manual-install hint"
611
+ [ "$out" = "error: herdr requires manual installation (instructions: https://herdr.dev)" ] \
612
+ || fail "install herdr should return actionable manual-install guidance, got: $out"
613
+ pass "bootstrap: Herdr manual-install guidance is never executed as a shell command"
614
+ }
615
+
616
+ test_cmux_bundled_cli_satisfies_dependency() {
617
+ local case_dir fakebin bundle out
618
+ case_dir="$TMP_ROOT/cmux-bundled-cli"
619
+ mkdir -p "$case_dir/home/config" "$case_dir/bundle"
620
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
621
+ printf '%s\n' cmux > "$case_dir/home/config/backend"
622
+ fakebin=$(make_fake_toolchain_no_tmux "$case_dir")
623
+ fm_fake_exit0 "$case_dir/bundle" cmux
624
+ bundle="$case_dir/bundle/cmux"
625
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
626
+ FM_BACKEND_CMUX_BUNDLE_BIN="$bundle" FM_FAKE_TREEHOUSE_LEASE_HELP=1 "$ROOT/bin/fm-bootstrap.sh")
627
+ [ -z "$out" ] || fail "a usable bundled cmux CLI should satisfy bootstrap without a PATH shim, got: $out"
628
+ pass "bootstrap: the bundled cmux CLI satisfies the active backend dependency"
629
+ }
630
+
631
+ test_unknown_backend_reports_invalid_configuration() {
632
+ local case_dir fakebin out
633
+ case_dir="$TMP_ROOT/unknown-backend"
634
+ mkdir -p "$case_dir/home/config"
635
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
636
+ printf '%s\n' bogus > "$case_dir/home/config/backend"
637
+ fakebin=$(make_fake_toolchain "$case_dir")
638
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
639
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 "$ROOT/bin/fm-bootstrap.sh")
640
+ assert_contains "$out" "BACKEND_INVALID: bogus (known: tmux herdr zellij orca cmux)" \
641
+ "bootstrap should report an unknown resolved backend"
642
+ assert_not_contains "$out" "MISSING: tmux" "an unknown backend should not silently fall back to tmux dependencies"
643
+ pass "bootstrap: unknown resolved backends fail closed with an actionable diagnostic"
644
+ }
645
+
646
+ test_json_backends_require_jq_not_tmux() {
647
+ local backend case_dir fakebin bash_env out
648
+ # herdr/zellij/cmux parse their backend's JSON output, so jq is a genuine dep.
649
+ # jq lives in a system BASE_PATH dir on many hosts, so force it missing with a
650
+ # command()/jq() override (the same technique the git-required case uses) to keep
651
+ # the assertion host-independent.
652
+ while IFS='^' read -r backend; do
653
+ [ -n "$backend" ] || continue
654
+ case_dir="$TMP_ROOT/$backend-missing-jq"
655
+ mkdir -p "$case_dir/home/config"
656
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
657
+ printf '%s\n' "$backend" > "$case_dir/home/config/backend"
658
+ # Session CLI present, tmux absent, jq deliberately NOT stubbed and masked below.
659
+ fakebin=$(make_fake_toolchain "$case_dir")
660
+ rm -f "$fakebin/tmux"
661
+ fm_fake_exit0 "$fakebin" "$backend"
662
+ bash_env="$case_dir/no-jq.bash"
663
+ cat > "$bash_env" <<'SH'
664
+ command() {
665
+ if [ "${1:-}" = -v ] && [ "${2:-}" = jq ]; then
666
+ return 1
667
+ fi
668
+ builtin command "$@"
669
+ }
670
+ jq() {
671
+ return 127
672
+ }
673
+ SH
674
+ out=$(PATH="$fakebin:$BASE_PATH" BASH_ENV="$bash_env" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
675
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 "$ROOT/bin/fm-bootstrap.sh")
676
+ assert_contains "$out" "MISSING: jq" "backend=$backend must fail closed on missing jq"
677
+ assert_not_contains "$out" "MISSING: tmux" "backend=$backend must not demand tmux when jq is missing"
678
+ done <<'ROWS'
679
+ herdr
680
+ zellij
681
+ cmux
682
+ ROWS
683
+ pass "bootstrap: JSON-emitting backends require jq (their genuine dep), never tmux"
684
+ }
685
+
686
+ test_treehouse_lease_check_follows_resolved_backend() {
687
+ local case_dir fakebin out
688
+ # A treehouse that lacks durable --lease support is only a problem for a backend
689
+ # that actually uses treehouse. Orca owns its own worktrees, so an old treehouse
690
+ # must NOT trip MISSING: treehouse under backend=orca...
691
+ case_dir="$TMP_ROOT/orca-old-treehouse"
692
+ mkdir -p "$case_dir/home/config"
693
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
694
+ printf '%s\n' orca > "$case_dir/home/config/backend"
695
+ fakebin=$(make_fake_toolchain "$case_dir")
696
+ rm -f "$fakebin/tmux"
697
+ fm_fake_exit0 "$fakebin" orca
698
+ # FM_FAKE_TREEHOUSE_LEASE_HELP unset: the fake treehouse advertises NO --lease.
699
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
700
+ "$ROOT/bin/fm-bootstrap.sh")
701
+ [ -z "$out" ] || fail "backend=orca must not require treehouse (even lease-less) or tmux, got: $out"
702
+
703
+ # ...but the same lease-less treehouse IS a problem for a session-provider
704
+ # backend that relies on treehouse for worktrees.
705
+ case_dir="$TMP_ROOT/herdr-old-treehouse"
706
+ mkdir -p "$case_dir/home/config"
707
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
708
+ printf '%s\n' herdr > "$case_dir/home/config/backend"
709
+ fakebin=$(make_fake_toolchain_no_tmux "$case_dir" herdr)
710
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
711
+ "$ROOT/bin/fm-bootstrap.sh")
712
+ assert_contains "$out" "MISSING: treehouse" "backend=herdr must still require treehouse with durable lease support"
713
+ assert_not_contains "$out" "MISSING: tmux" "backend=herdr must not demand tmux even when treehouse is too old"
714
+ pass "bootstrap: the treehouse lease check follows the resolved backend's worktree provider"
715
+ }
716
+
717
+ test_fleet_sync_timeout_scales_with_origin_backed_project_count() {
718
+ local case_dir home fakebin fake_root out
719
+ case_dir="$TMP_ROOT/fleet-timeout-scaled"
720
+ home="$case_dir/home"
721
+ mkdir -p "$home/config"
722
+ printf '%s\n' manual > "$home/config/backlog-backend"
723
+ add_origin_backed_projects "$home" 18
724
+ add_no_origin_projects "$home" 3
725
+ fakebin=$(make_fake_toolchain "$case_dir")
726
+ fake_root=$(make_fake_fleet_sync_root "$case_dir")
727
+
728
+ out=$(run_bootstrap_timeout_case "$home" "$fake_root" "$fakebin")
729
+
730
+ assert_contains "$out" $'FLEET_SYNC: alpha: synced\nFLEET_SYNC: beta: skipped: no origin remote' "bootstrap timeout should relay partial fleet-sync output first"
731
+ assert_timeout_report "$out" 59
732
+ pass "bootstrap computes a fleet-size-aware default timeout and preserves partial fleet-sync output"
733
+ }
734
+
735
+ test_fleet_sync_timeout_floor_preserves_small_fleets() {
736
+ local case_dir home fakebin fake_root out
737
+ case_dir="$TMP_ROOT/fleet-timeout-small"
738
+ home="$case_dir/home"
739
+ mkdir -p "$home/config"
740
+ printf '%s\n' manual > "$home/config/backlog-backend"
741
+ add_origin_backed_projects "$home" 2
742
+ fakebin=$(make_fake_toolchain "$case_dir")
743
+ fake_root=$(make_fake_fleet_sync_root "$case_dir")
744
+
745
+ out=$(run_bootstrap_timeout_case "$home" "$fake_root" "$fakebin")
746
+
747
+ assert_timeout_report "$out" 20
748
+ pass "bootstrap keeps the quick 20s default for small fleets"
749
+ }
750
+
751
+ test_fleet_sync_timeout_explicit_override_wins() {
752
+ local case_dir home fakebin fake_root out
753
+ case_dir="$TMP_ROOT/fleet-timeout-override"
754
+ home="$case_dir/home"
755
+ mkdir -p "$home/config"
756
+ printf '%s\n' manual > "$home/config/backlog-backend"
757
+ add_origin_backed_projects "$home" 18
758
+ fakebin=$(make_fake_toolchain "$case_dir")
759
+ fake_root=$(make_fake_fleet_sync_root "$case_dir")
760
+
761
+ out=$(run_bootstrap_timeout_case "$home" "$fake_root" "$fakebin" 7)
762
+
763
+ assert_timeout_report "$out" 7
764
+ assert_not_contains "$out" "timeout=59s" "explicit override should not be replaced by the computed timeout"
765
+ pass "bootstrap preserves FM_FLEET_SYNC_BOOTSTRAP_TIMEOUT as an explicit override"
766
+ }
767
+
768
+ test_fleet_sync_timeout_empty_override_uses_default() {
769
+ local case_dir home fakebin fake_root out
770
+ case_dir="$TMP_ROOT/fleet-timeout-empty-override"
771
+ home="$case_dir/home"
772
+ mkdir -p "$home/config"
773
+ printf '%s\n' manual > "$home/config/backlog-backend"
774
+ add_origin_backed_projects "$home" 18
775
+ fakebin=$(make_fake_toolchain "$case_dir")
776
+ fake_root=$(make_fake_fleet_sync_root "$case_dir")
777
+
778
+ out=$(run_bootstrap_timeout_case "$home" "$fake_root" "$fakebin" "")
779
+
780
+ assert_timeout_report "$out" 59
781
+ assert_not_contains "$out" "timeout=20s" "blank timeout env should not force the legacy floor on a large fleet"
782
+ pass "bootstrap treats a blank timeout override as unset"
783
+ }
784
+
785
+ test_fleet_sync_timeout_is_computed_before_launch() {
786
+ local case_dir home fakebin fake_root out started_marker git_record
787
+ case_dir="$TMP_ROOT/fleet-timeout-launch-order"
788
+ home="$case_dir/home"
789
+ started_marker="$case_dir/fleet-started"
790
+ git_record="$case_dir/git-after-start"
791
+ mkdir -p "$home/config"
792
+ printf '%s\n' manual > "$home/config/backlog-backend"
793
+ add_origin_backed_projects "$home" 3
794
+ fakebin=$(make_fake_toolchain "$case_dir")
795
+ fake_root=$(make_fake_fleet_sync_root "$case_dir")
796
+
797
+ out=$(run_bootstrap_timeout_case "$home" "$fake_root" "$fakebin" __unset__ "$started_marker" "$git_record" 1)
798
+
799
+ [ ! -s "$git_record" ] || fail "fleet sync launched before timeout scan finished: $(tr '\n' ';' < "$git_record")"
800
+ assert_contains "$out" $'FLEET_SYNC: alpha: synced\nFLEET_SYNC: beta: skipped: no origin remote' "launch-order case should relay partial fleet-sync output before reporting its timeout"
801
+ assert_timeout_report "$out" 20
802
+ pass "bootstrap computes the timeout before launching fleet sync"
803
+ }
804
+
805
+ make_routine_bootstrap_fixture() {
806
+ local case_dir=$1 fakebin root home sm c1
807
+ root="$case_dir/root"
808
+ home="$case_dir/home"
809
+ sm="$case_dir/sm"
810
+ fm_git_identity
811
+ mkdir -p "$home/config" "$home/state"
812
+ printf '%s\n' codex > "$home/config/crew-harness"
813
+ printf '%s\n' '{"rules":[{"when":"normal work","use":{"harness":"codex"}}],"default":{"harness":"claude","effort":"low"}}' \
814
+ > "$home/config/crew-dispatch.json"
815
+ git init -q -b main "$root"
816
+ {
817
+ printf '%s\n' '.fm-secondmate-home'
818
+ printf '%s\n' 'config/crew-harness'
819
+ printf '%s\n' 'config/crew-dispatch.json'
820
+ printf '%s\n' 'config/startup-memory-budget'
821
+ } > "$root/.gitignore"
822
+ printf '%s\n' 'instructions' > "$root/AGENTS.md"
823
+ mkdir -p "$root/bin" "$root/.agents/skills"
824
+ printf '%s\n' 'echo ok' > "$root/bin/fm-spawn.sh"
825
+ printf '%s\n' 'skill' > "$root/.agents/skills/example.md"
826
+ git -C "$root" add -A
827
+ git -C "$root" commit -qm initial
828
+ c1=$(git -C "$root" rev-parse HEAD)
829
+ git -C "$root" worktree add -q --detach "$sm" "$c1"
830
+ printf '%s\n' sm > "$sm/.fm-secondmate-home"
831
+ {
832
+ printf 'window=firstmate:fm-sm\n'
833
+ printf 'kind=secondmate\n'
834
+ printf 'harness=codex\n'
835
+ printf 'home=%s\n' "$sm"
836
+ } > "$home/state/sm.meta"
837
+ fakebin=$(make_fake_toolchain "$case_dir")
838
+ add_real_jq "$fakebin"
839
+ cat > "$fakebin/tmux" <<'SH'
840
+ #!/usr/bin/env bash
841
+ case "${1:-}" in
842
+ display-message)
843
+ case "$*" in
844
+ *'#{cursor_y}'*) printf '%s\n' 0 ;;
845
+ *) printf '%s\n' codex ;;
846
+ esac
847
+ ;;
848
+ capture-pane) printf '❯\n' ;;
849
+ list-windows) printf '%s\n' fm-sm ;;
850
+ esac
851
+ exit 0
852
+ SH
853
+ chmod +x "$fakebin/tmux"
854
+ printf '%s|%s|%s\n' "$root" "$home" "$fakebin"
855
+ }
856
+
857
+ run_routine_bootstrap_fixture() {
858
+ local shell=$1 case_dir=$2 fixture root home fakebin
859
+ fixture=$(make_routine_bootstrap_fixture "$case_dir")
860
+ root=${fixture%%|*}
861
+ fixture=${fixture#*|}
862
+ home=${fixture%%|*}
863
+ fakebin=${fixture#*|}
864
+ PATH="$fakebin:$BASE_PATH" FM_BACKEND=tmux FM_HOME="$home" FM_ROOT_OVERRIDE="$root" \
865
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 \
866
+ "$shell" "$ROOT/bin/fm-bootstrap.sh"
867
+ }
868
+
869
+ test_routine_bootstrap_confirmations_are_silent() {
870
+ local out
871
+ out=$(run_routine_bootstrap_fixture bash "$TMP_ROOT/routine-silent")
872
+ [ -z "$out" ] || fail "routine bootstrap confirmations should be silent, got: $out"
873
+ pass "bootstrap keeps routine tasks-axi, harness, dispatch, and already-live liveness confirmations silent"
874
+ }
875
+
876
+ test_routine_bootstrap_contract_runs_under_system_bash() {
877
+ local out
878
+ [ -x /bin/bash ] || { pass "bootstrap routine contract skipped without /bin/bash"; return; }
879
+ out=$(run_routine_bootstrap_fixture /bin/bash "$TMP_ROOT/routine-bash")
880
+ [ -z "$out" ] || fail "routine bootstrap contract should be silent under /bin/bash, got: $out"
881
+ pass "bootstrap routine contract runs under system /bin/bash"
882
+ }
883
+
884
+ # FM_BOOTSTRAP_NETWORK splits one bootstrap run into its local and network
885
+ # halves so a session start can compose its digest from the local half alone and
886
+ # run the network half concurrently. The property that has to hold is that the
887
+ # split is a PARTITION: `skip` plus `only` together do exactly what `all` does,
888
+ # with no step dropped and no step run twice.
889
+ test_network_phase_partitions_the_run() {
890
+ local case_dir fakebin all_out skip_out only_out combined
891
+ case_dir="$TMP_ROOT/network-phase"
892
+ mkdir -p "$case_dir/home/config"
893
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
894
+ fakebin=$(make_fake_toolchain "$case_dir")
895
+ # Break the two diagnostics that stand for the two halves: a local tool floor
896
+ # and the network GitHub-auth probe.
897
+ rm -f "$fakebin/node"
898
+ cat > "$fakebin/gh" <<'SH'
899
+ #!/usr/bin/env bash
900
+ exit 1
901
+ SH
902
+ chmod +x "$fakebin/gh"
903
+
904
+ all_out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
905
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 "$ROOT/bin/fm-bootstrap.sh")
906
+ assert_contains "$all_out" "MISSING: node (install:" "the unsplit run lost its local diagnostic"
907
+ assert_contains "$all_out" "NEEDS_GH_AUTH" "the unsplit run lost its network diagnostic"
908
+
909
+ skip_out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
910
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 FM_BOOTSTRAP_NETWORK=skip "$ROOT/bin/fm-bootstrap.sh")
911
+ assert_contains "$skip_out" "MISSING: node (install:" "the local half lost its own diagnostic"
912
+ assert_not_contains "$skip_out" "NEEDS_GH_AUTH" "the local half still made a network call"
913
+
914
+ only_out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
915
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 FM_BOOTSTRAP_NETWORK=only "$ROOT/bin/fm-bootstrap.sh")
916
+ assert_contains "$only_out" "NEEDS_GH_AUTH" "the network half lost its own diagnostic"
917
+ assert_not_contains "$only_out" "MISSING: node" "the network half repeated the local half's work"
918
+
919
+ combined=$(printf '%s\n%s\n' "$skip_out" "$only_out" | LC_ALL=C sort)
920
+ [ "$combined" = "$(printf '%s\n' "$all_out" | LC_ALL=C sort)" ] \
921
+ || fail "skip + only is not the same set of findings as an unsplit run"$'\n'"all: $all_out"$'\n'"skip: $skip_out"$'\n'"only: $only_out"
922
+
923
+ # A typo must never silently drop a safety sweep, so anything unrecognized
924
+ # resolves to the complete run.
925
+ [ "$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
926
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 FM_BOOTSTRAP_NETWORK=sikp "$ROOT/bin/fm-bootstrap.sh")" = "$all_out" ] \
927
+ || fail "an unrecognized FM_BOOTSTRAP_NETWORK value did not fall back to the complete run"
928
+ pass "bootstrap: FM_BOOTSTRAP_NETWORK partitions one run into local and network halves"
929
+ }
930
+
931
+ test_network_sweeps_recheck_lock_ownership() {
932
+ local case_dir fakebin fake_root marker out
933
+ case_dir="$TMP_ROOT/network-lock-handoff"
934
+ mkdir -p "$case_dir/home/config" "$case_dir/home/projects" "$case_dir/home/state"
935
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
936
+ printf '222222\n' > "$case_dir/home/state/.lock"
937
+ fakebin=$(make_fake_toolchain "$case_dir")
938
+ fake_root="$case_dir/root"
939
+ marker="$case_dir/fleet-sync.started"
940
+ mkdir -p "$fake_root/bin"
941
+ cat > "$fake_root/bin/fm-fleet-sync.sh" <<'SH'
942
+ #!/usr/bin/env bash
943
+ : > "${FM_FAKE_FLEET_SYNC_STARTED_MARKER:?}"
944
+ SH
945
+ chmod +x "$fake_root/bin/fm-fleet-sync.sh"
946
+
947
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$fake_root" \
948
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 FM_BOOTSTRAP_NETWORK=only \
949
+ FM_BOOTSTRAP_NETWORK_LOCK_PID=111111 FM_FAKE_FLEET_SYNC_STARTED_MARKER="$marker" \
950
+ "$ROOT/bin/fm-bootstrap.sh")
951
+ assert_absent "$marker" "a stale worker refreshed project clones after lock handoff"
952
+ assert_contains "$out" "changed before dead-secondmate relaunch" \
953
+ "the stale worker did not report the refused liveness sweep"
954
+ assert_contains "$out" "changed before secondmate convergence" \
955
+ "the stale worker did not report the refused convergence sweep"
956
+ assert_contains "$out" "changed before pending handoff delivery" \
957
+ "the stale worker did not report the refused handoff sweep"
958
+ assert_contains "$out" "changed before project clone refresh" \
959
+ "the stale worker did not report the refused clone refresh"
960
+ pass "bootstrap: every deferred mutating sweep rechecks fleet-lock ownership"
961
+ }
962
+
963
+ # The verdict costs three subprocesses, so a caller that already has it can hand
964
+ # it over - but only one hop, and never onward into a spawned agent's
965
+ # environment, where it could outlive a tasks-axi upgrade.
966
+ # assert_timing_record <log> <scope> <name> <detail> <msg>: one bin/fm-timing-lib.sh
967
+ # record with exactly these fields must exist. Field-exact rather than a substring
968
+ # match, so a detail that landed in the wrong column cannot pass.
969
+ assert_timing_record() {
970
+ local log=$1 scope=$2 name=$3 detail=$4 msg=$5
971
+ awk -F'\t' -v s="$scope" -v n="$name" -v d="$detail" '
972
+ $1 == "v1" && $2 == s && $3 == n && $6 == d { found = 1 }
973
+ END { exit found ? 0 : 1 }
974
+ ' "$log" || fail "$msg"$'\n'"$(cat "$log")"
975
+ }
976
+
977
+ # The deferred network stage publishes ONE started/finished pair, so a slow run
978
+ # used to be unattributable without re-running it by hand. These are the records
979
+ # that make it attributable, and they must come from the real sweeps rather than
980
+ # a stand-in: what is being pinned is that each network owner is actually wrapped.
981
+ # bin/fm-timing-lib.sh stays inert unless FM_TIMING_LOG names a file, so an
982
+ # ordinary bootstrap run is unaffected either way, which is asserted here too.
983
+ test_network_phases_record_per_step_elapsed_times() {
984
+ local case_dir fakebin log fields
985
+ case_dir="$TMP_ROOT/network-timings"
986
+ mkdir -p "$case_dir/home/config" "$case_dir/home/state" "$case_dir/home/data" "$case_dir/home/projects"
987
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
988
+ printf '%s\n' $$ > "$case_dir/home/state/.lock"
989
+ fakebin=$(make_fake_toolchain "$case_dir")
990
+ # A real clone with a real origin, so fm-fleet-sync.sh genuinely iterates it.
991
+ fm_git_init_commit "$case_dir/home/projects/alpha"
992
+ fm_git_add_origin "$case_dir/home/projects/alpha" "$case_dir/alpha-origin"
993
+ # A secondmate the liveness sweep must account for. Whatever verdict it reaches
994
+ # is owned elsewhere; what matters here is that the step is measured.
995
+ fm_write_secondmate_meta "$case_dir/home/state/mate-a.meta" "$case_dir/home"
996
+
997
+ log="$case_dir/timings.tsv"
998
+ PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$ROOT" \
999
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 FM_BOOTSTRAP_NETWORK=only \
1000
+ FM_BOOTSTRAP_NETWORK_LOCK_PID=$$ FM_TIMING_LOG="$log" FM_TIMING_EPOCH_MS=0 \
1001
+ "$ROOT/bin/fm-bootstrap.sh" >/dev/null 2>&1
1002
+
1003
+ assert_present "$log" "the network phase recorded no elapsed times at all"
1004
+ assert_timing_record "$log" phase gh-auth '' "the GitHub auth probe was not timed"
1005
+ assert_timing_record "$log" phase secondmate-liveness '' "the dead-secondmate relaunch sweep was not timed"
1006
+ assert_timing_record "$log" phase secondmate-sync '' "the secondmate convergence sweep was not timed"
1007
+ assert_timing_record "$log" phase handoff-delivery '' "the pending handoff sweep was not timed"
1008
+ assert_timing_record "$log" phase fleet-sync '' "the project clone refresh was not timed"
1009
+ assert_timing_record "$log" secondmate liveness mate-a \
1010
+ "the liveness sweep was not attributed to the individual secondmate it checked"
1011
+ assert_timing_record "$log" clone sync alpha \
1012
+ "the clone refresh was not attributed to the individual clone it refreshed"
1013
+
1014
+ # Every record carries a start offset and an elapsed time, both numeric, so the
1015
+ # artifact can be read as a timeline rather than a bag of durations.
1016
+ fields=$(awk -F'\t' '$4 ~ /^[0-9]+$/ && $5 ~ /^[0-9]+$/ { n++ } END { print n+0 }' "$log")
1017
+ [ "$fields" = "$(grep -c . "$log")" ] \
1018
+ || fail "some records lack a numeric start offset and elapsed time: $(cat "$log")"
1019
+
1020
+ # And an ordinary run - the local half, or any caller that never asked for
1021
+ # timings - writes nothing anywhere.
1022
+ rm -f "$log"
1023
+ PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$ROOT" \
1024
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 FM_BOOTSTRAP_NETWORK=only \
1025
+ FM_BOOTSTRAP_NETWORK_LOCK_PID=$$ \
1026
+ "$ROOT/bin/fm-bootstrap.sh" >/dev/null 2>&1
1027
+ assert_absent "$log" "a run that never asked for timings recorded them anyway"
1028
+ pass "bootstrap: each deferred network phase, secondmate, and clone records its own elapsed time"
1029
+ }
1030
+
1031
+ test_tasks_axi_verdict_handoff_is_consumed_once() {
1032
+ local case_dir fakebin log out
1033
+ case_dir="$TMP_ROOT/tasks-axi-handoff"
1034
+ mkdir -p "$case_dir/home/config"
1035
+ fakebin=$(make_fake_toolchain "$case_dir")
1036
+ log="$case_dir/tasks-axi.log"
1037
+ cat > "$fakebin/tasks-axi" <<'SH'
1038
+ #!/usr/bin/env bash
1039
+ printf '%s\n' "$*" >> "${FM_FAKE_TASKS_AXI_LOG:?}"
1040
+ printf '0.0.1\n'
1041
+ exit 0
1042
+ SH
1043
+ chmod +x "$fakebin/tasks-axi"
1044
+
1045
+ # Without the handoff, the incompatible stub is probed and reported.
1046
+ : > "$log"
1047
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
1048
+ FM_FAKE_TASKS_AXI_LOG="$log" FM_FAKE_TREEHOUSE_LEASE_HELP=1 "$ROOT/bin/fm-bootstrap.sh")
1049
+ assert_contains "$out" "MISSING: tasks-axi (install:" "the unaided run did not probe tasks-axi"
1050
+ assert_grep '--version' "$log" "the unaided run never ran the probe"
1051
+
1052
+ # With it, the probe is skipped entirely and the handed-in verdict is used.
1053
+ : > "$log"
1054
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
1055
+ FM_FAKE_TASKS_AXI_LOG="$log" FM_FAKE_TREEHOUSE_LEASE_HELP=1 \
1056
+ FM_TASKS_AXI_COMPATIBLE=1 "$ROOT/bin/fm-bootstrap.sh")
1057
+ assert_not_contains "$out" "MISSING: tasks-axi" "the handed-in verdict was ignored"
1058
+ [ ! -s "$log" ] || fail "the handed-in verdict did not save the probe: $(cat "$log")"
1059
+
1060
+ # A malformed value is not a verdict.
1061
+ : > "$log"
1062
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
1063
+ FM_FAKE_TASKS_AXI_LOG="$log" FM_FAKE_TREEHOUSE_LEASE_HELP=1 \
1064
+ FM_TASKS_AXI_COMPATIBLE=yes "$ROOT/bin/fm-bootstrap.sh")
1065
+ assert_contains "$out" "MISSING: tasks-axi (install:" "a malformed handoff value was trusted"
1066
+
1067
+ # And the handoff never reaches a grandchild: bootstrap spawns agents, and a
1068
+ # verdict cached into an agent's environment would outlive the tool it describes.
1069
+ out=$(FM_TASKS_AXI_COMPATIBLE=1 bash -c '. "$1"; printf "%s\n" "${FM_TASKS_AXI_COMPATIBLE-unset}"' \
1070
+ _ "$ROOT/bin/fm-tasks-axi-lib.sh")
1071
+ [ "$out" = unset ] || fail "sourcing the library left the handoff in the environment: $out"
1072
+ pass "bootstrap: the tasks-axi compatibility verdict travels exactly one process hop"
1073
+ }
1074
+
1075
+ test_crew_dispatch_active_rules_are_verbose_bootstrap_info() {
1076
+ local case_dir fakebin out expect
1077
+ case_dir="$TMP_ROOT/dispatch-active"
1078
+ mkdir -p "$case_dir/home/config"
1079
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
1080
+ printf '%s\n' '{"rules":[{"when":"fresh news","use":{"harness":"grok"},"why":"current context"},{"when":"big feature","use":[{"harness":"claude","model":"claude-sonnet-5","effort":"high"},{"harness":"codex","model":"gpt-5.5","effort":"high"}]},{"when":"legacy feature","use":[{"harness":"claude"},{"harness":"codex"}],"select":"quota-balanced"}],"default":[{"harness":"pi","model":"anthropic/claude-sonnet-5","effort":"high"},{"harness":"grok","model":"grok-4.5","effort":"high"}]}' > "$case_dir/home/config/crew-dispatch.json"
1081
+ fakebin=$(make_fake_toolchain "$case_dir")
1082
+ add_real_jq "$fakebin"
1083
+
1084
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
1085
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 "$ROOT/bin/fm-bootstrap.sh")
1086
+ [ -z "$out" ] || fail "active dispatch profile should be silent by default, got: $out"
1087
+
1088
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
1089
+ FM_BOOTSTRAP_VERBOSE_FACTS=1 FM_FAKE_TREEHOUSE_LEASE_HELP=1 "$ROOT/bin/fm-bootstrap.sh")
1090
+
1091
+ expect=$'BOOTSTRAP_INFO: crew dispatch active config/crew-dispatch.json\nBOOTSTRAP_INFO: crew dispatch rule: fresh news -> grok\nBOOTSTRAP_INFO: crew dispatch rule: big feature -> quota-balanced[claude/claude-sonnet-5/high, codex/gpt-5.5/high]\nBOOTSTRAP_INFO: crew dispatch rule: legacy feature -> quota-balanced[claude, codex]\nBOOTSTRAP_INFO: crew dispatch default: quota-balanced[pi/anthropic/claude-sonnet-5/high, grok/grok-4.5/high]'
1092
+ [ "$out" = "$expect" ] || fail "active dispatch verbose info block mismatch"$'\n'"expected: $expect"$'\n'"actual: $out"
1093
+ pass "bootstrap surfaces active crew-dispatch rules only as verbose BOOTSTRAP_INFO"
1094
+ }
1095
+
1096
+ test_crew_dispatch_validation() {
1097
+ local label body expect mode case_dir fakebin out n
1098
+ n=0
1099
+ while IFS='^' read -r label body mode expect; do
1100
+ [ -n "$label" ] || continue
1101
+ n=$((n + 1))
1102
+ case_dir="$TMP_ROOT/dispatch-$n"
1103
+ mkdir -p "$case_dir/home/config"
1104
+ printf '%s\n' manual > "$case_dir/home/config/backlog-backend"
1105
+ printf '%s\n' "$body" > "$case_dir/home/config/crew-dispatch.json"
1106
+ fakebin=$(make_fake_toolchain "$case_dir")
1107
+ add_real_jq "$fakebin"
1108
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$case_dir/home" FM_ROOT_OVERRIDE="$case_dir/home" \
1109
+ FM_FAKE_TREEHOUSE_LEASE_HELP=1 "$ROOT/bin/fm-bootstrap.sh")
1110
+ case "$mode" in
1111
+ empty)
1112
+ [ -z "$out" ] || fail "$label: expected silence, got: $out" ;;
1113
+ exact)
1114
+ [ "$out" = "$expect" ] || fail "$label: expected '$expect', got: $out" ;;
1115
+ grep)
1116
+ printf '%s\n' "$out" | grep -Fx "$expect" >/dev/null || fail "$label: missing '$expect' (got: $out)" ;;
1117
+ esac
1118
+ done <<'ROWS'
1119
+ malformed dispatch config is flagged^{"rules":[^exact^CREW_DISPATCH: invalid config/crew-dispatch.json - malformed JSON
1120
+ unverified dispatch harness is flagged^{"rules":[{"when":"anything","use":{"harness":"spaceship"}}],"default":{"harness":"codex"}}^exact^CREW_DISPATCH: invalid config/crew-dispatch.json - unverified harness: spaceship
1121
+ unsupported codex max effort is flagged^{"rules":[{"when":"big feature","use":{"harness":"codex","model":"gpt-5","effort":"max"}}]}^exact^CREW_DISPATCH: invalid config/crew-dispatch.json - invalid effort: codex:max
1122
+ unsupported grok max effort is flagged^{"rules":[{"when":"deep current work","use":{"harness":"grok","model":"grok-4","effort":"max"}}]}^exact^CREW_DISPATCH: invalid config/crew-dispatch.json - invalid effort: grok:max
1123
+ unsupported grok xhigh effort is flagged^{"rules":[{"when":"deep current work","use":{"harness":"grok","model":"grok-4","effort":"xhigh"}}]}^exact^CREW_DISPATCH: invalid config/crew-dispatch.json - invalid effort: grok:xhigh
1124
+ pi max effort is accepted^{"rules":[{"when":"deep coding","use":{"harness":"pi","model":"openai-codex/gpt-5.6-sol","effort":"max"}}]}^empty^
1125
+ pi-signed max effort is accepted^{"rules":[{"when":"signed coding","use":{"harness":"pi-signed","model":"openai-codex/gpt-5.6-sol","effort":"max"}}]}^empty^
1126
+ muse shared efforts are accepted^{"rules":[{"when":"muse low","use":{"harness":"muse","effort":"low"}},{"when":"muse medium","use":{"harness":"muse","effort":"medium"}},{"when":"muse high","use":{"harness":"muse","effort":"high"}},{"when":"muse xhigh","use":{"harness":"muse","effort":"xhigh"}},{"when":"muse max","use":{"harness":"muse","effort":"max"}}]}^empty^
1127
+ unsupported muse ultra effort is flagged^{"rules":[{"when":"muse ultra","use":{"harness":"muse","effort":"ultra"}}]}^exact^CREW_DISPATCH: invalid config/crew-dispatch.json - invalid effort: muse:ultra
1128
+ unsupported opencode effort is flagged^{"rules":[{"when":"opencode work","use":{"harness":"opencode","model":"anthropic/claude-sonnet-4-5","effort":"high"}}]}^exact^CREW_DISPATCH: invalid config/crew-dispatch.json - invalid effort: opencode:high
1129
+ kimi model profile is accepted^{"rules":[{"when":"kimi work","use":{"harness":"kimi","model":"kimi-code/k3"}}]}^empty^
1130
+ unsupported kimi effort is flagged^{"rules":[{"when":"kimi work","use":{"harness":"kimi","model":"kimi-code/k3","effort":"high"}}]}^exact^CREW_DISPATCH: invalid config/crew-dispatch.json - invalid effort: kimi:high
1131
+ cursor model profile is accepted^{"rules":[{"when":"cursor work","use":{"harness":"cursor","model":"cursor-grok-4.5-high"}}]}^empty^
1132
+ unsupported cursor effort is flagged^{"rules":[{"when":"cursor work","use":{"harness":"cursor","model":"cursor-grok-4.5-high","effort":"high"}}]}^exact^CREW_DISPATCH: invalid config/crew-dispatch.json - invalid effort: cursor:high
1133
+ array use with quota-balanced is accepted^{"rules":[{"when":"big feature","use":[{"harness":"claude","model":"claude-sonnet-5","effort":"high"},{"harness":"codex","model":"gpt-5.5","effort":"high"}],"select":"quota-balanced"}]}^empty^
1134
+ array use without select is accepted^{"rules":[{"when":"big feature","use":[{"harness":"claude"},{"harness":"codex"}]}]}^empty^
1135
+ one-element array use is accepted^{"rules":[{"when":"focused feature","use":[{"harness":"claude"}]}]}^empty^
1136
+ default array is accepted^{"default":[{"harness":"pi","model":"anthropic/claude-sonnet-5"},{"harness":"grok"}]}^empty^
1137
+ one-element default array is accepted^{"default":[{"harness":"codex"}]}^empty^
1138
+ empty array use is flagged^{"rules":[{"when":"big feature","use":[]}]}^exact^CREW_DISPATCH: invalid config/crew-dispatch.json - each rule needs at least one use profile
1139
+ array profile without harness is flagged^{"rules":[{"when":"big feature","use":[{"model":"gpt-5.5"}]}]}^exact^CREW_DISPATCH: invalid config/crew-dispatch.json - each use profile needs harness
1140
+ array profile with malformed model is flagged^{"rules":[{"when":"big feature","use":[{"harness":"codex","model":5}]}]}^exact^CREW_DISPATCH: invalid config/crew-dispatch.json - use profile model and effort must be non-empty strings when present
1141
+ unknown select is flagged^{"rules":[{"when":"big feature","use":[{"harness":"claude"},{"harness":"codex"}],"select":"mystery"}]}^exact^CREW_DISPATCH: invalid config/crew-dispatch.json - unknown select: mystery
1142
+ array profile unsupported effort is flagged^{"rules":[{"when":"big feature","use":[{"harness":"codex","effort":"max"}]}]}^exact^CREW_DISPATCH: invalid config/crew-dispatch.json - invalid effort: codex:max
1143
+ empty default array is flagged^{"default":[]}^exact^CREW_DISPATCH: invalid config/crew-dispatch.json - default needs at least one profile
1144
+ non-object default array entry is flagged^{"default":["codex"]}^exact^CREW_DISPATCH: invalid config/crew-dispatch.json - each default profile must be an object
1145
+ default array profile without harness is flagged^{"default":[{"model":"gpt-5.5"}]}^exact^CREW_DISPATCH: invalid config/crew-dispatch.json - each default profile needs harness
1146
+ default array malformed effort is flagged^{"default":[{"harness":"codex","effort":3}]}^exact^CREW_DISPATCH: invalid config/crew-dispatch.json - default profile model and effort must be non-empty strings when present
1147
+ ROWS
1148
+ pass "bootstrap validates crew-dispatch.json and reports malformed or unverified configs"
1149
+ }
1150
+
1151
+ test_bootstrap_reporting
1152
+ test_no_mistakes_min_version
1153
+ test_gh_axi_min_version
1154
+ test_lavish_axi_min_version
1155
+ test_tasks_axi_min_version
1156
+ test_quota_axi_min_version
1157
+ test_git_is_required_with_supported_install_instruction
1158
+ test_orca_backend_gates_orca_tool_only_when_selected
1159
+ test_session_provider_backends_do_not_require_tmux
1160
+ test_session_provider_backends_gate_own_cli_not_tmux
1161
+ test_herdr_install_requires_manual_action
1162
+ test_cmux_bundled_cli_satisfies_dependency
1163
+ test_unknown_backend_reports_invalid_configuration
1164
+ test_json_backends_require_jq_not_tmux
1165
+ test_treehouse_lease_check_follows_resolved_backend
1166
+ test_fleet_sync_timeout_scales_with_origin_backed_project_count
1167
+ test_fleet_sync_timeout_floor_preserves_small_fleets
1168
+ test_fleet_sync_timeout_explicit_override_wins
1169
+ test_fleet_sync_timeout_empty_override_uses_default
1170
+ test_fleet_sync_timeout_is_computed_before_launch
1171
+ test_routine_bootstrap_confirmations_are_silent
1172
+ test_routine_bootstrap_contract_runs_under_system_bash
1173
+ test_network_phase_partitions_the_run
1174
+ test_network_sweeps_recheck_lock_ownership
1175
+ test_network_phases_record_per_step_elapsed_times
1176
+ test_tasks_axi_verdict_handoff_is_consumed_once
1177
+ test_crew_dispatch_active_rules_are_verbose_bootstrap_info
1178
+ test_crew_dispatch_validation