@zerwiz/ymir 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (850) hide show
  1. package/.agents/README.md +291 -0
  2. package/.agents/agents/bragi-marketer.md +111 -0
  3. package/.agents/agents/brokk.md +352 -0
  4. package/.agents/agents/forseti-reviewer.md +93 -0
  5. package/.agents/agents/frigg-consent.md +77 -0
  6. package/.agents/agents/groa-update.md +77 -0
  7. package/.agents/agents/hnoss-designer.md +69 -0
  8. package/.agents/agents/huginn-researcher.md +99 -0
  9. package/.agents/agents/jord-projects.md +78 -0
  10. package/.agents/agents/kvasir-scout.md +91 -0
  11. package/.agents/agents/mimir-planner.md +92 -0
  12. package/.agents/agents/muninn-stow.md +77 -0
  13. package/.agents/agents/saga-bearings.md +78 -0
  14. package/.agents/agents/sindri-developer.md +114 -0
  15. package/.agents/agents/snotra-documenter.md +105 -0
  16. package/.agents/agents/syn-recovery.md +78 -0
  17. package/.agents/agents/tyr-check.md +78 -0
  18. package/.agents/agents/urdh-hold.md +77 -0
  19. package/.agents/agents/volundr-smith.md +80 -0
  20. package/.agents/agents/vor-diagnostics.md +77 -0
  21. package/.agents/assets/agents/naming.md +50 -0
  22. package/.agents/assets/agents/registry.md +93 -0
  23. package/.agents/assets/agents/runtime.md +36 -0
  24. package/.agents/assets/agents/toon-tasks-cli.md +42 -0
  25. package/.agents/assets/templates/company_entity.template.md +37 -0
  26. package/.agents/backend/backends/cmux.sh +649 -0
  27. package/.agents/backend/backends/herdr-eventwait.py +157 -0
  28. package/.agents/backend/backends/herdr-workspace-move.py +114 -0
  29. package/.agents/backend/backends/herdr.sh +3386 -0
  30. package/.agents/backend/backends/orca.sh +290 -0
  31. package/.agents/backend/backends/tmux.sh +357 -0
  32. package/.agents/backend/backends/zellij.sh +700 -0
  33. package/.agents/backend/fm-afk-launch.sh +654 -0
  34. package/.agents/backend/fm-afk-return.sh +241 -0
  35. package/.agents/backend/fm-afk-start.sh +172 -0
  36. package/.agents/backend/fm-arm-command-policy.mjs +960 -0
  37. package/.agents/backend/fm-arm-pretool-check.sh +200 -0
  38. package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
  39. package/.agents/backend/fm-backend.sh +981 -0
  40. package/.agents/backend/fm-backlog-handoff.sh +909 -0
  41. package/.agents/backend/fm-backlog-receive.sh +187 -0
  42. package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
  43. package/.agents/backend/fm-bearings-board.sh +204 -0
  44. package/.agents/backend/fm-bearings-snapshot.sh +546 -0
  45. package/.agents/backend/fm-bootstrap.sh +1568 -0
  46. package/.agents/backend/fm-branch-outcome.sh +203 -0
  47. package/.agents/backend/fm-branch-prompt.sh +101 -0
  48. package/.agents/backend/fm-brief.sh +452 -0
  49. package/.agents/backend/fm-busy-event.sh +234 -0
  50. package/.agents/backend/fm-busy-lib.sh +986 -0
  51. package/.agents/backend/fm-captain-hold.sh +1001 -0
  52. package/.agents/backend/fm-cd-command-policy.mjs +153 -0
  53. package/.agents/backend/fm-cd-pretool-check.sh +190 -0
  54. package/.agents/backend/fm-check-lib.sh +74 -0
  55. package/.agents/backend/fm-check-register.sh +42 -0
  56. package/.agents/backend/fm-check-unregister.sh +52 -0
  57. package/.agents/backend/fm-classify-lib.sh +1770 -0
  58. package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
  59. package/.agents/backend/fm-composer-lib.sh +1416 -0
  60. package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
  61. package/.agents/backend/fm-config-push.sh +254 -0
  62. package/.agents/backend/fm-control-lib.sh +251 -0
  63. package/.agents/backend/fm-control.sh +878 -0
  64. package/.agents/backend/fm-crew-state.sh +625 -0
  65. package/.agents/backend/fm-cursor-lib.sh +243 -0
  66. package/.agents/backend/fm-decision-hold.sh +232 -0
  67. package/.agents/backend/fm-doc-audience-check.sh +269 -0
  68. package/.agents/backend/fm-dod-lib.sh +67 -0
  69. package/.agents/backend/fm-ensure-agents-md.sh +253 -0
  70. package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
  71. package/.agents/backend/fm-extension.mjs +2577 -0
  72. package/.agents/backend/fm-extension.sh +16 -0
  73. package/.agents/backend/fm-ff-lib.sh +420 -0
  74. package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
  75. package/.agents/backend/fm-fleet-sync.sh +458 -0
  76. package/.agents/backend/fm-fleet-view.sh +96 -0
  77. package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
  78. package/.agents/backend/fm-guard.sh +242 -0
  79. package/.agents/backend/fm-harness.sh +195 -0
  80. package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
  81. package/.agents/backend/fm-herdr-lab.sh +346 -0
  82. package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
  83. package/.agents/backend/fm-home-seed.sh +984 -0
  84. package/.agents/backend/fm-home-summary-refresh.sh +255 -0
  85. package/.agents/backend/fm-hook-host-lib.sh +36 -0
  86. package/.agents/backend/fm-inactive-reconcile.sh +531 -0
  87. package/.agents/backend/fm-inbox.sh +399 -0
  88. package/.agents/backend/fm-install-actionlint.sh +84 -0
  89. package/.agents/backend/fm-install-herdr.sh +96 -0
  90. package/.agents/backend/fm-install-shellcheck.sh +84 -0
  91. package/.agents/backend/fm-install-treehouse.sh +95 -0
  92. package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
  93. package/.agents/backend/fm-lease-lib.sh +218 -0
  94. package/.agents/backend/fm-lease.sh +189 -0
  95. package/.agents/backend/fm-line-cap-lib.sh +51 -0
  96. package/.agents/backend/fm-lint-workflows.sh +137 -0
  97. package/.agents/backend/fm-lint.sh +591 -0
  98. package/.agents/backend/fm-lock-lib.sh +104 -0
  99. package/.agents/backend/fm-lock.sh +107 -0
  100. package/.agents/backend/fm-marker-lib.sh +12 -0
  101. package/.agents/backend/fm-merge-local.sh +74 -0
  102. package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
  103. package/.agents/backend/fm-nm-run-lib.sh +124 -0
  104. package/.agents/backend/fm-on.sh +125 -0
  105. package/.agents/backend/fm-operational-input.sh +253 -0
  106. package/.agents/backend/fm-peek.sh +46 -0
  107. package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
  108. package/.agents/backend/fm-pr-check.sh +135 -0
  109. package/.agents/backend/fm-pr-lib.sh +1016 -0
  110. package/.agents/backend/fm-pr-merge.sh +707 -0
  111. package/.agents/backend/fm-pr-poll.sh +110 -0
  112. package/.agents/backend/fm-primary-scope-lib.sh +33 -0
  113. package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
  114. package/.agents/backend/fm-procevent-lavish.sh +628 -0
  115. package/.agents/backend/fm-procevent-lib.sh +892 -0
  116. package/.agents/backend/fm-procevent-quota.sh +290 -0
  117. package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
  118. package/.agents/backend/fm-procevent-when.sh +504 -0
  119. package/.agents/backend/fm-procevent.sh +1466 -0
  120. package/.agents/backend/fm-project-mode.sh +92 -0
  121. package/.agents/backend/fm-project-origin-lib.sh +180 -0
  122. package/.agents/backend/fm-promote.sh +282 -0
  123. package/.agents/backend/fm-public-followup-emit.sh +260 -0
  124. package/.agents/backend/fm-public-followup-lib.sh +398 -0
  125. package/.agents/backend/fm-public-followup.sh +1330 -0
  126. package/.agents/backend/fm-push-transition-lib.sh +168 -0
  127. package/.agents/backend/fm-quota-axi-lib.sh +93 -0
  128. package/.agents/backend/fm-quota-choose.sh +384 -0
  129. package/.agents/backend/fm-remote-delta-read.sh +189 -0
  130. package/.agents/backend/fm-remote-doctor.sh +800 -0
  131. package/.agents/backend/fm-remote-entrypoint.sh +193 -0
  132. package/.agents/backend/fm-remote-file.sh +234 -0
  133. package/.agents/backend/fm-remote-home-provision.sh +259 -0
  134. package/.agents/backend/fm-remote-home-seed.sh +259 -0
  135. package/.agents/backend/fm-remote-inherit-push.sh +90 -0
  136. package/.agents/backend/fm-remote-inherit.sh +179 -0
  137. package/.agents/backend/fm-remote-job-lib.sh +1241 -0
  138. package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
  139. package/.agents/backend/fm-remote-job-worker.sh +1124 -0
  140. package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
  141. package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
  142. package/.agents/backend/fm-review-diff.sh +158 -0
  143. package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
  144. package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
  145. package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
  146. package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
  147. package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
  148. package/.agents/backend/fm-secondmate-report.sh +88 -0
  149. package/.agents/backend/fm-send.sh +1059 -0
  150. package/.agents/backend/fm-session-lock-lib.sh +176 -0
  151. package/.agents/backend/fm-session-start.sh +970 -0
  152. package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
  153. package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
  154. package/.agents/backend/fm-sessionstart-run.sh +145 -0
  155. package/.agents/backend/fm-spawn.sh +3153 -0
  156. package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
  157. package/.agents/backend/fm-startup-memory-budget.sh +94 -0
  158. package/.agents/backend/fm-startup-network.sh +642 -0
  159. package/.agents/backend/fm-stow-cascade.sh +251 -0
  160. package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
  161. package/.agents/backend/fm-supervise-daemon.sh +1732 -0
  162. package/.agents/backend/fm-supervision-instructions.sh +215 -0
  163. package/.agents/backend/fm-supervision-lib.sh +90 -0
  164. package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
  165. package/.agents/backend/fm-tangle-lib.sh +53 -0
  166. package/.agents/backend/fm-task-inbox-lib.sh +402 -0
  167. package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
  168. package/.agents/backend/fm-teardown.sh +2922 -0
  169. package/.agents/backend/fm-test-isolation-proof.sh +588 -0
  170. package/.agents/backend/fm-test-run.sh +2191 -0
  171. package/.agents/backend/fm-timeout-lib.sh +141 -0
  172. package/.agents/backend/fm-timing-lib.sh +179 -0
  173. package/.agents/backend/fm-tmux-lib.sh +291 -0
  174. package/.agents/backend/fm-tool-update-check.sh +898 -0
  175. package/.agents/backend/fm-trace-context-lib.sh +227 -0
  176. package/.agents/backend/fm-transition-lib.sh +103 -0
  177. package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
  178. package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
  179. package/.agents/backend/fm-turnend-guard.sh +432 -0
  180. package/.agents/backend/fm-update.sh +110 -0
  181. package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
  182. package/.agents/backend/fm-voice-client.py +1373 -0
  183. package/.agents/backend/fm-voice-relay.py +1256 -0
  184. package/.agents/backend/fm-wake-drain.sh +592 -0
  185. package/.agents/backend/fm-wake-grant.sh +127 -0
  186. package/.agents/backend/fm-wake-lib.sh +1849 -0
  187. package/.agents/backend/fm-watch-arm.sh +602 -0
  188. package/.agents/backend/fm-watch-checkpoint.sh +109 -0
  189. package/.agents/backend/fm-watch.sh +1962 -0
  190. package/.agents/backend/fm-x-dismiss.sh +116 -0
  191. package/.agents/backend/fm-x-followup.sh +286 -0
  192. package/.agents/backend/fm-x-lib.sh +1000 -0
  193. package/.agents/backend/fm-x-link.sh +234 -0
  194. package/.agents/backend/fm-x-poll.sh +185 -0
  195. package/.agents/backend/fm-x-reply.sh +385 -0
  196. package/.agents/backend/fm_voice_frame.py +166 -0
  197. package/.agents/backend/fm_voice_records.py +574 -0
  198. package/.agents/backend/model-bridge.py +290 -0
  199. package/.agents/backend/opencode-go-bridge.py +30 -0
  200. package/.agents/bus/README.md +12 -0
  201. package/.agents/bus/messages.json +1 -0
  202. package/.agents/config/agents.machine.example.yaml +16 -0
  203. package/.agents/config/agents.yaml +50 -0
  204. package/.agents/config/agents.yaml.example +72 -0
  205. package/.agents/config/crew-dispatch.json +22 -0
  206. package/.agents/config/cron.yaml +9 -0
  207. package/.agents/config/eindri-dispatch.json +67 -0
  208. package/.agents/config/eindri-harness +1 -0
  209. package/.agents/config/startup-memory-budget +1 -0
  210. package/.agents/config/tailscale-sync.yaml +27 -0
  211. package/.agents/filebrowser/.filebrowser.json +6 -0
  212. package/.agents/filebrowser/README.md +11 -0
  213. package/.agents/gateway/README.md +12 -0
  214. package/.agents/github/README.md +15 -0
  215. package/.agents/github/webhooks/.gitkeep +0 -0
  216. package/.agents/github/workflows/.gitkeep +0 -0
  217. package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
  218. package/.agents/harness/opencode/plugins/package.json +4 -0
  219. package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
  220. package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
  221. package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
  222. package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
  223. package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
  224. package/.agents/memory/README.md +46 -0
  225. package/.agents/memory/kaia.engram +0 -0
  226. package/.agents/memory/kaia.engram-shm +0 -0
  227. package/.agents/memory/kaia.engram-wal +0 -0
  228. package/.agents/memory/runes_audit.md +5 -0
  229. package/.agents/memory/well/episodes.jsonl +367 -0
  230. package/.agents/memory/well/workspace.jsonl +27 -0
  231. package/.agents/migrations/0001-hodd-layout.sh +38 -0
  232. package/.agents/migrations/0002-a2a-mcp.sh +27 -0
  233. package/.agents/migrations/0003-private-data-separation.sh +111 -0
  234. package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
  235. package/.agents/sandbox/Dockerfile.utgard +19 -0
  236. package/.agents/sandbox/README.md +14 -0
  237. package/.agents/sandbox/utgard.config.json +14 -0
  238. package/.agents/skills/README.md +59 -0
  239. package/.agents/skills/bragi-marketing/README.md +3 -0
  240. package/.agents/skills/bragi-marketing/SKILL.md +82 -0
  241. package/.agents/skills/eindri-homes/README.md +3 -0
  242. package/.agents/skills/eindri-homes/SKILL.md +291 -0
  243. package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
  244. package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
  245. package/.agents/skills/frigg-consent/README.md +3 -0
  246. package/.agents/skills/frigg-consent/SKILL.md +56 -0
  247. package/.agents/skills/galdr-ymirsystem/README.md +3 -0
  248. package/.agents/skills/galdr-ymirsystem/SKILL.md +158 -0
  249. package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
  250. package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
  251. package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
  252. package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
  253. package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
  254. package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
  255. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +673 -0
  256. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
  257. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
  258. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
  259. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
  260. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
  261. package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +496 -0
  262. package/.agents/skills/galdr-ymirsystem/assets/installation.md +571 -0
  263. package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
  264. package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
  265. package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +161 -0
  266. package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +377 -0
  267. package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
  268. package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +129 -0
  269. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
  270. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
  271. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +96 -0
  272. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
  273. package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
  274. package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
  275. package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
  276. package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
  277. package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
  278. package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
  279. package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
  280. package/.agents/skills/galdr-ymirsystem/assets/smidja.md +175 -0
  281. package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
  282. package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
  283. package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
  284. package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +360 -0
  285. package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
  286. package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
  287. package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
  288. package/.agents/skills/gjallarhorn-relay/README.md +3 -0
  289. package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
  290. package/.agents/skills/groa-update/SKILL.md +70 -0
  291. package/.agents/skills/hamr-adapters/README.md +3 -0
  292. package/.agents/skills/hamr-adapters/SKILL.md +123 -0
  293. package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
  294. package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
  295. package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
  296. package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
  297. package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
  298. package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
  299. package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
  300. package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
  301. package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
  302. package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
  303. package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
  304. package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
  305. package/.agents/skills/herdr-panes/README.md +3 -0
  306. package/.agents/skills/herdr-panes/SKILL.md +92 -0
  307. package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
  308. package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
  309. package/.agents/skills/hnoss-design/DESIGN.md +19 -0
  310. package/.agents/skills/hnoss-design/README.md +3 -0
  311. package/.agents/skills/hnoss-design/SKILL.md +83 -0
  312. package/.agents/skills/hvild-afk/README.md +3 -0
  313. package/.agents/skills/hvild-afk/SKILL.md +241 -0
  314. package/.agents/skills/jord-projects/README.md +3 -0
  315. package/.agents/skills/jord-projects/SKILL.md +92 -0
  316. package/.agents/skills/lifecycle/SKILL.md +56 -0
  317. package/.agents/skills/lifecycle/smoke_test.sh +52 -0
  318. package/.agents/skills/lifecycle/start.sh +21 -0
  319. package/.agents/skills/lifecycle/status.sh +35 -0
  320. package/.agents/skills/lifecycle/stop.sh +16 -0
  321. package/.agents/skills/muninn-stow/README.md +3 -0
  322. package/.agents/skills/muninn-stow/SKILL.md +309 -0
  323. package/.agents/skills/no-mistakes/SKILL.md +379 -0
  324. package/.agents/skills/nornir-schedule/README.md +3 -0
  325. package/.agents/skills/nornir-schedule/SKILL.md +25 -0
  326. package/.agents/skills/nornir-schedule/assets/events.md +137 -0
  327. package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
  328. package/.agents/skills/nsr-compliance/README.md +3 -0
  329. package/.agents/skills/nsr-compliance/SKILL.md +29 -0
  330. package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
  331. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
  332. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
  333. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
  334. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
  335. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
  336. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
  337. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
  338. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
  339. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
  340. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
  341. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
  342. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
  343. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
  344. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
  345. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
  346. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
  347. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
  348. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
  349. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
  350. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
  351. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
  352. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
  353. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  354. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
  355. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
  356. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
  357. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
  358. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
  359. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
  360. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
  361. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
  362. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
  363. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
  364. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
  365. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
  366. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
  367. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
  368. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
  369. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
  370. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
  371. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
  372. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
  373. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
  374. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
  375. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
  376. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
  377. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
  378. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
  379. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
  380. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
  381. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
  382. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
  383. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
  384. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
  385. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
  386. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
  387. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
  388. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
  389. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
  390. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
  391. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
  392. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
  393. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
  394. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
  395. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
  396. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
  397. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
  398. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
  399. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
  400. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
  401. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
  402. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
  403. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
  404. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
  405. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
  406. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
  407. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
  408. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
  409. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
  410. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
  411. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
  412. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
  413. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
  414. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
  415. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
  416. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
  417. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
  418. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
  419. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
  420. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
  421. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
  422. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
  423. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
  424. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
  425. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
  426. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
  427. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
  428. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
  429. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
  430. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
  431. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
  432. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
  433. package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
  434. package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
  435. package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
  436. package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
  437. package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
  438. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
  439. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
  440. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
  441. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
  442. package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
  443. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
  444. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
  445. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
  446. package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
  447. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
  448. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
  449. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
  450. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
  451. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
  452. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
  453. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
  454. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  455. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
  456. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
  457. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
  458. package/.agents/skills/pr-ops/README.md +3 -0
  459. package/.agents/skills/pr-ops/SKILL.md +109 -0
  460. package/.agents/skills/ratatoskr-a2a/README.md +3 -0
  461. package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
  462. package/.agents/skills/rules-check-drift/SKILL.md +81 -0
  463. package/.agents/skills/saga-bearings/README.md +3 -0
  464. package/.agents/skills/saga-bearings/SKILL.md +26 -0
  465. package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
  466. package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
  467. package/.agents/skills/saga-bearings/assets/recap.md +56 -0
  468. package/.agents/skills/syn-recovery/README.md +3 -0
  469. package/.agents/skills/syn-recovery/SKILL.md +54 -0
  470. package/.agents/skills/tyr-check/README.md +3 -0
  471. package/.agents/skills/tyr-check/SKILL.md +218 -0
  472. package/.agents/skills/urdh-hold/README.md +3 -0
  473. package/.agents/skills/urdh-hold/SKILL.md +25 -0
  474. package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
  475. package/.agents/skills/urdh-hold/assets/hold.md +54 -0
  476. package/.agents/skills/vor-diagnostics/README.md +3 -0
  477. package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
  478. package/.agents/skills/ymir-host/README.md +3 -0
  479. package/.agents/skills/ymir-host/SKILL.md +34 -0
  480. package/.agents/skills/ymir-host/assets/desktop.md +36 -0
  481. package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
  482. package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
  483. package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
  484. package/.agents/skills/ymir-host/assets/update.md +64 -0
  485. package/.agents/tests/assets/board-render-harness.mjs +117 -0
  486. package/.agents/tests/cmux-test-safety.sh +51 -0
  487. package/.agents/tests/fixtures.sh +291 -0
  488. package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
  489. package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
  490. package/.agents/tests/fm-afk-launch.test.sh +961 -0
  491. package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
  492. package/.agents/tests/fm-afk-return.test.sh +283 -0
  493. package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
  494. package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
  495. package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
  496. package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
  497. package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
  498. package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
  499. package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
  500. package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
  501. package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
  502. package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
  503. package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
  504. package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
  505. package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
  506. package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
  507. package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
  508. package/.agents/tests/fm-backend-orca.test.sh +1357 -0
  509. package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
  510. package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
  511. package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
  512. package/.agents/tests/fm-backend.test.sh +1142 -0
  513. package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
  514. package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
  515. package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
  516. package/.agents/tests/fm-bearings-board.test.sh +414 -0
  517. package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
  518. package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
  519. package/.agents/tests/fm-bootstrap.test.sh +1178 -0
  520. package/.agents/tests/fm-branch-supervision.test.sh +552 -0
  521. package/.agents/tests/fm-brief.test.sh +785 -0
  522. package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
  523. package/.agents/tests/fm-busy-state.test.sh +465 -0
  524. package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
  525. package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
  526. package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
  527. package/.agents/tests/fm-check-unregister.test.sh +198 -0
  528. package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
  529. package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
  530. package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
  531. package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
  532. package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
  533. package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
  534. package/.agents/tests/fm-composer-ghost.test.sh +716 -0
  535. package/.agents/tests/fm-composer-lib.test.sh +666 -0
  536. package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
  537. package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
  538. package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
  539. package/.agents/tests/fm-control.test.sh +910 -0
  540. package/.agents/tests/fm-crew-state.test.sh +1610 -0
  541. package/.agents/tests/fm-cursor-harness.test.sh +404 -0
  542. package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
  543. package/.agents/tests/fm-cursor-primary.test.sh +708 -0
  544. package/.agents/tests/fm-daemon.test.sh +2740 -0
  545. package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
  546. package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
  547. package/.agents/tests/fm-extension-binding.test.sh +2187 -0
  548. package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
  549. package/.agents/tests/fm-fleet-sync.test.sh +721 -0
  550. package/.agents/tests/fm-gate-refuse.test.sh +356 -0
  551. package/.agents/tests/fm-gitignore-config.test.sh +89 -0
  552. package/.agents/tests/fm-gotmp.test.sh +230 -0
  553. package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
  554. package/.agents/tests/fm-grok-harness.test.sh +117 -0
  555. package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
  556. package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
  557. package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
  558. package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
  559. package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
  560. package/.agents/tests/fm-herdr-lab.test.sh +243 -0
  561. package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
  562. package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
  563. package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
  564. package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
  565. package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
  566. package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
  567. package/.agents/tests/fm-kimi-harness.test.sh +687 -0
  568. package/.agents/tests/fm-lint-workflows.test.sh +532 -0
  569. package/.agents/tests/fm-lint.test.sh +1023 -0
  570. package/.agents/tests/fm-muse-harness.test.sh +932 -0
  571. package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
  572. package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
  573. package/.agents/tests/fm-on.test.sh +507 -0
  574. package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
  575. package/.agents/tests/fm-operational-input.test.sh +160 -0
  576. package/.agents/tests/fm-peek-remote.test.sh +110 -0
  577. package/.agents/tests/fm-pending-reply.test.sh +1288 -0
  578. package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
  579. package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
  580. package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
  581. package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
  582. package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
  583. package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
  584. package/.agents/tests/fm-pr-merge.test.sh +2135 -0
  585. package/.agents/tests/fm-procevent-quota.test.sh +225 -0
  586. package/.agents/tests/fm-procevent-when.test.sh +406 -0
  587. package/.agents/tests/fm-procevent.test.sh +1745 -0
  588. package/.agents/tests/fm-project-origin.test.sh +110 -0
  589. package/.agents/tests/fm-public-followup.test.sh +2272 -0
  590. package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
  591. package/.agents/tests/fm-quota-choose.test.sh +626 -0
  592. package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
  593. package/.agents/tests/fm-remote-doctor.test.sh +625 -0
  594. package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
  595. package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
  596. package/.agents/tests/fm-remote-job.test.sh +768 -0
  597. package/.agents/tests/fm-remote-reply.test.sh +520 -0
  598. package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
  599. package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
  600. package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
  601. package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
  602. package/.agents/tests/fm-review-diff.test.sh +176 -0
  603. package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
  604. package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
  605. package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
  606. package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
  607. package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
  608. package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
  609. package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
  610. package/.agents/tests/fm-send-inbox.test.sh +352 -0
  611. package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
  612. package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
  613. package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
  614. package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
  615. package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
  616. package/.agents/tests/fm-send-settle.test.sh +147 -0
  617. package/.agents/tests/fm-send-strict.test.sh +241 -0
  618. package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
  619. package/.agents/tests/fm-session-start.test.sh +2515 -0
  620. package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
  621. package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
  622. package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
  623. package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
  624. package/.agents/tests/fm-spawn-batch.test.sh +148 -0
  625. package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
  626. package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
  627. package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
  628. package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
  629. package/.agents/tests/fm-startup-network.test.sh +712 -0
  630. package/.agents/tests/fm-stow-cascade.test.sh +370 -0
  631. package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
  632. package/.agents/tests/fm-supervision-events.test.sh +156 -0
  633. package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
  634. package/.agents/tests/fm-tangle-guard.test.sh +277 -0
  635. package/.agents/tests/fm-task-delivery.test.sh +420 -0
  636. package/.agents/tests/fm-task-inbox.test.sh +513 -0
  637. package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
  638. package/.agents/tests/fm-teardown.test.sh +2665 -0
  639. package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
  640. package/.agents/tests/fm-test-fixtures.test.sh +132 -0
  641. package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
  642. package/.agents/tests/fm-test-run.test.sh +1196 -0
  643. package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
  644. package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
  645. package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
  646. package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
  647. package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
  648. package/.agents/tests/fm-transition-lib.test.sh +54 -0
  649. package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
  650. package/.agents/tests/fm-update.test.sh +304 -0
  651. package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
  652. package/.agents/tests/fm-voice-relay.test.sh +4741 -0
  653. package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
  654. package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
  655. package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
  656. package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
  657. package/.agents/tests/fm-wake-queue.test.sh +1224 -0
  658. package/.agents/tests/fm-watch-arm.test.sh +821 -0
  659. package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
  660. package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
  661. package/.agents/tests/fm-watch-triage.test.sh +3889 -0
  662. package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
  663. package/.agents/tests/fm-x-mode.test.sh +3042 -0
  664. package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
  665. package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
  666. package/.agents/tests/herdr-test-safety.sh +42 -0
  667. package/.agents/tests/lib.sh +315 -0
  668. package/.agents/tests/remote-herdr-fixture.sh +125 -0
  669. package/.agents/tests/secondmate-helpers.sh +193 -0
  670. package/.agents/tests/smoke.test.sh +93 -0
  671. package/.agents/tests/wake-helpers.sh +338 -0
  672. package/.agents/tests/zellij-test-safety.sh +60 -0
  673. package/.agents/tools/README.md +10 -0
  674. package/.agents/tools/bin/tasks-cli +267 -0
  675. package/.agents/tools/tasks-cli.ts +216 -0
  676. package/AGENTS.md +454 -0
  677. package/CHANGELOG.md +881 -0
  678. package/LICENSE +1 -1
  679. package/NOTICE +63 -0
  680. package/README.md +113 -59
  681. package/RULES/01-domains.md +51 -0
  682. package/RULES/02-agents.md +78 -0
  683. package/RULES/03-houses.md +61 -0
  684. package/RULES/04-hoard.md +65 -0
  685. package/RULES/05-platforms.md +67 -0
  686. package/RULES/06-append-only.md +58 -0
  687. package/RULES/07-config.md +45 -0
  688. package/RULES/08-delivery-gate.md +75 -0
  689. package/RULES/README.md +20 -0
  690. package/bin/README.md +107 -0
  691. package/bin/a2a-mcp.sh +98 -0
  692. package/bin/a2a-serve.py +103 -0
  693. package/bin/a2a-serve.sh +8 -0
  694. package/bin/a2a-talk.sh +50 -0
  695. package/bin/a2abridge-ensure.sh +108 -0
  696. package/bin/agent-run.sh +43 -0
  697. package/bin/agents-config.sh +233 -0
  698. package/bin/apodex-smoke-test.sh +92 -0
  699. package/bin/bifrost-bridge.sh +112 -0
  700. package/bin/bifrost-ingress.sh +77 -0
  701. package/bin/bootstrap-macos.sh +107 -0
  702. package/bin/bootstrap-windows.ps1 +112 -0
  703. package/bin/branch-guard.sh +64 -0
  704. package/bin/brokk +73 -0
  705. package/bin/brokk-classify-lib.sh +1770 -0
  706. package/bin/brokk-lease-lib.sh +222 -0
  707. package/bin/brokk-lease.sh +193 -0
  708. package/bin/brokk-lint.sh +65 -0
  709. package/bin/brokk-send.sh +38 -0
  710. package/bin/brokk-update.sh +9 -0
  711. package/bin/brokk-wake-grant.sh +127 -0
  712. package/bin/brokk-wake-lib.sh +1849 -0
  713. package/bin/changelog-guard.sh +165 -0
  714. package/bin/crash-sense.sh +160 -0
  715. package/bin/desktop-place.sh +224 -0
  716. package/bin/docs-guard.sh +39 -0
  717. package/bin/editor-place.sh +207 -0
  718. package/bin/eindri-acclaim.sh +34 -0
  719. package/bin/eindri-control.sh +28 -0
  720. package/bin/eindri-role.sh +83 -0
  721. package/bin/eindri-seen.sh +41 -0
  722. package/bin/eindri-send.sh +15 -0
  723. package/bin/eindri-start.sh +85 -0
  724. package/bin/eindri-watch.sh +57 -0
  725. package/bin/einherjar-spawn.sh +576 -0
  726. package/bin/eir-doctor.sh +112 -0
  727. package/bin/erindi-brief.sh +271 -0
  728. package/bin/fleet-apply.sh +68 -0
  729. package/bin/github-deploy.sh +81 -0
  730. package/bin/gjallarhorn-expose.sh +102 -0
  731. package/bin/gjallarhorn-notify.sh +58 -0
  732. package/bin/gjallarhorn-purge.sh +54 -0
  733. package/bin/gjallarhorn-tunnel.sh +72 -0
  734. package/bin/gleipnir-lock-lib.sh +284 -0
  735. package/bin/groa-update.sh +122 -0
  736. package/bin/hall-snapshot.sh +92 -0
  737. package/bin/hamr-harness.sh +246 -0
  738. package/bin/heimdall-ssh-keys.sh +374 -0
  739. package/bin/herdr-agents.py +44 -0
  740. package/bin/herdr-ensure.sh +104 -0
  741. package/bin/herdr-run.sh +431 -0
  742. package/bin/hermes-ensure.sh +88 -0
  743. package/bin/hoard-lib.sh +25 -0
  744. package/bin/hodd.sh +78 -0
  745. package/bin/host-sense.sh +199 -0
  746. package/bin/huginn-research-worker.sh +108 -0
  747. package/bin/local-model-lock.sh +51 -0
  748. package/bin/mimir-bridge.py +226 -0
  749. package/bin/mimir-bridge.sh +87 -0
  750. package/bin/mimir-ingest.sh +131 -0
  751. package/bin/mimir.sh +113 -0
  752. package/bin/mjollnir-webhook.sh +59 -0
  753. package/bin/mjollnir.sh +85 -0
  754. package/bin/model-hardware.sh +82 -0
  755. package/bin/model-resolve.sh +147 -0
  756. package/bin/models-detect.sh +178 -0
  757. package/bin/nornir-cron-start.sh +162 -0
  758. package/bin/nornir-job-daily-briefing.sh +191 -0
  759. package/bin/nornir-job-git-sync.sh +152 -0
  760. package/bin/nornir-job-memory-housekeeping.sh +114 -0
  761. package/bin/nornir-job-observer.sh +166 -0
  762. package/bin/nornir-job-skillopt-sleep.sh +76 -0
  763. package/bin/omarchy-hook-install.sh +60 -0
  764. package/bin/omarchy-install.sh +148 -0
  765. package/bin/omarchy-plugins.sh +174 -0
  766. package/bin/omarchy-sense.sh +149 -0
  767. package/bin/perm-guard.sh +30 -0
  768. package/bin/pi-agent.sh +68 -0
  769. package/bin/pi-ensure.sh +77 -0
  770. package/bin/pi-local.sh +37 -0
  771. package/bin/pi-seat.sh +63 -0
  772. package/bin/prereq-ensure.sh +166 -0
  773. package/bin/private-guard.sh +38 -0
  774. package/bin/project-git.sh +55 -0
  775. package/bin/public-guard.sh +70 -0
  776. package/bin/ratatoskr.sh +49 -0
  777. package/bin/realm-lib.sh +32 -0
  778. package/bin/repo-scrub.sh +62 -0
  779. package/bin/rodd-operational-input.sh +250 -0
  780. package/bin/runes-append.sh +198 -0
  781. package/bin/saga-session-start.sh +172 -0
  782. package/bin/saga-sessionstart-run.sh +79 -0
  783. package/bin/saga-wake-drain.sh +41 -0
  784. package/bin/secret-guard.sh +63 -0
  785. package/bin/sessrumnir-ensure.sh +100 -0
  786. package/bin/sessrumnir-sync.sh +237 -0
  787. package/bin/sessrumnir.sh +91 -0
  788. package/bin/skillopt-setup.sh +76 -0
  789. package/bin/skuld-branch-outcome.sh +203 -0
  790. package/bin/skuld-branch-prompt.sh +101 -0
  791. package/bin/smidja-bootstrap.sh +90 -0
  792. package/bin/smidja-observe.sh +86 -0
  793. package/bin/syn-arm-pretool-check.sh +57 -0
  794. package/bin/syn-asset-pretool-check.sh +76 -0
  795. package/bin/syn-cd-pretool-check.sh +28 -0
  796. package/bin/syn-guard-pretool-check.sh +83 -0
  797. package/bin/syn-turnend-guard.sh +36 -0
  798. package/bin/syn-watch-arm.sh +99 -0
  799. package/bin/tailscale-sync.sh +190 -0
  800. package/bin/telegram-bot.sh +69 -0
  801. package/bin/toolchain.sh +74 -0
  802. package/bin/utgard.sh +111 -0
  803. package/bin/valhalla.sh +115 -0
  804. package/bin/valknut-load.sh +260 -0
  805. package/bin/vor-crew-state.sh +124 -0
  806. package/bin/wedge-notify.sh +47 -0
  807. package/bin/workspace-provision.sh +80 -0
  808. package/bin/workspace-rag.sh +150 -0
  809. package/bin/wyrd-db.sh +47 -0
  810. package/bin/yggdrasil.sh +152 -0
  811. package/bin/ymir-edit.sh +145 -0
  812. package/bin/ymir-install.sh +641 -0
  813. package/bin/ymir-invite.sh +93 -0
  814. package/bin/ymir-isolation.sh +88 -0
  815. package/bin/ymir-migrate.sh +61 -0
  816. package/bin/ymir-platform.sh +348 -0
  817. package/bin/ymir-say.sh +97 -0
  818. package/bin/ymir-setup-auth.sh +108 -0
  819. package/bin/ymir-validate.sh +173 -0
  820. package/bin/ymir.js +74 -16
  821. package/docs/Architecture.md +339 -0
  822. package/docs/design.md +351 -0
  823. package/docs/installations/README.md +63 -0
  824. package/docs/installations/ymir-home.md +93 -0
  825. package/docs/integration.md +105 -0
  826. package/docs/lore.md +1056 -0
  827. package/docs/research/agent-harnesses.md +290 -0
  828. package/docs/runbooks/README.md +19 -0
  829. package/docs/runbooks/a2a.md +91 -0
  830. package/docs/runbooks/agent-permissions.md +99 -0
  831. package/docs/runbooks/agents.md +57 -0
  832. package/docs/runbooks/models.md +69 -0
  833. package/docs/runbooks/realm-onboarding.md +60 -0
  834. package/docs/runbooks/secrets-and-hoard.md +60 -0
  835. package/docs/runbooks/updates-and-migrations.md +110 -0
  836. package/docs/runbooks/worktrees-isolation.md +72 -0
  837. package/docs/session-start.md +105 -0
  838. package/docs/skillopt-integration.md +321 -0
  839. package/docs/sync-system.md +66 -0
  840. package/docs/workspaces.md +104 -0
  841. package/docs/ymir-rut.md +260 -0
  842. package/package.json +44 -14
  843. package/packaging/build.sh +74 -0
  844. package/packaging/macos/Ymir Installer.command +43 -0
  845. package/packaging/windows/ymir-setup.nsi +74 -0
  846. package/scripts/README.md +22 -0
  847. package/scripts/electron.sh +210 -0
  848. package/scripts/start.sh +167 -0
  849. package/scripts/stop.sh +113 -0
  850. package/install.sh +0 -35
@@ -0,0 +1,1568 @@
1
+ #!/usr/bin/env bash
2
+ # Bootstrap detection, best-effort fleet refresh/prune, and installs.
3
+ # Usage: fm-bootstrap.sh
4
+ # Detect: prints one line per actionable problem, or an explicit
5
+ # BOOTSTRAP_INFO no-action fact for completed benign bootstrap work, and
6
+ # exits 0.
7
+ # Silent = all good.
8
+ # Lines: "MISSING: <tool> (install: <command>)",
9
+ # "MISSING_MANUAL: <tool> (instructions: <url>)", "NEEDS_GH_AUTH",
10
+ # "BACKEND_INVALID: <name> (known: <names>)",
11
+ # "STARTUP_MEMORY_BUDGET: invalid config/startup-memory-budget - <reason>",
12
+ # "CREW_DISPATCH: invalid config/crew-dispatch.json - <reason>",
13
+ # "FLEET_SYNC: <repo>: skipped|recovered|STUCK: <detail>",
14
+ # "HOME_SUMMARY: <ledger never published|not republished since
15
+ # <stamp>>; <n> failed attempt(s) ... last: <recorded failure>",
16
+ # "BACKLOG_RECONCILE: <id>: <what this home could not reconcile>",
17
+ # "TANGLE: <remediation>",
18
+ # "SECONDMATE_SYNC: secondmate <id>: skipped: <reason>",
19
+ # "NUDGE_SECONDMATES: secondmate <id>: send failed: <reason>",
20
+ # "BOOTSTRAP_INFO: nudged fm-<id> with '<message>'",
21
+ # "SECONDMATE_LIVENESS: secondmate <id>: skipped: <reason>|respawn failed after <cause>: <reason>",
22
+ # "SECONDMATE_HANDOFF: secondmate <id>: pending delivery: <n> item(s)",
23
+ # "FMX: X mode on ..." or "FMX: X mode off ...".
24
+ # When a RUNNING local secondmate worktree is fast-forwarded to
25
+ # firstmate's own current default-branch commit, that update is a
26
+ # purely local fast-forward and never an origin fetch. Remote routes
27
+ # instead converge the persistent home to their configured remote code
28
+ # root. If either placement changes its loaded instruction surface
29
+ # (AGENTS.md, bin/, or .agents/skills/), bootstrap immediately nudges it
30
+ # via FM_HOME=<active-home> bin/fm-send.sh fm-<id> so meta resolves the
31
+ # current route and the standard from-firstmate marker is applied. A
32
+ # successful send prints one BOOTSTRAP_INFO line with the exact target
33
+ # and message sent; a failed send leaves an idempotent retry marker
34
+ # under state/.secondmate-nudge-pending/ and prints an actionable
35
+ # NUDGE_SECONDMATES line.
36
+ # Already-current or no-instruction-change homes are silently left alone.
37
+ # The secondmate sweep also propagates declared inherited local material
38
+ # into each validated live secondmate home.
39
+ # SECONDMATE_SYNC lines report actionable skipped placement-specific
40
+ # syncs or inheritance failures for live secondmate homes, plus
41
+ # quarantine diagnostics for divergent shared captain-preference
42
+ # copies; no-op/current and successful updates stay quiet.
43
+ # SECONDMATE_LIVENESS lines report only actionable failures from the
44
+ # recovery-grade state owned by bin/fm-backend.sh's
45
+ # fm_backend_agent_state: skipped distinguishes an existing ambiguous
46
+ # process, an unreadable target, and an unverified backend; respawn
47
+ # failed names whether the endpoint was missing or agent-less.
48
+ # Already-live and successfully relaunched secondmates are silent
49
+ # unless FM_BOOTSTRAP_VERBOSE_FACTS=1 requests BOOTSTRAP_INFO facts.
50
+ # A TANGLE line means the firstmate primary checkout (FM_ROOT) is stranded
51
+ # on a feature branch instead of its default branch - a crewmate's work
52
+ # landed in the primary instead of its own worktree; restore it per the line.
53
+ # treehouse is also MISSING when its installed version lacks
54
+ # "treehouse get --lease" support.
55
+ # no-mistakes is also MISSING when its installed version is older than
56
+ # 1.46.0 (structured pipeline attestation floor; see CONTRIBUTING.md).
57
+ # The AXI-family floor policy is owned beside GH_AXI_MIN and
58
+ # LAVISH_AXI_MIN below; the per-tool owners point there. An installed
59
+ # build below its floor reports MISSING like no-mistakes, so the operator
60
+ # is asked to upgrade rather than silently running an older tool.
61
+ # tasks-axi feature probes remain a separate defense-in-depth check.
62
+ # tasks-axi and quota-axi are required bootstrap tools (same class as
63
+ # lavish-axi). A compatible tasks-axi default backend is silent.
64
+ # quota-axi is required for the agent-owned dispatch-profile array
65
+ # procedure in AGENTS.md section 4 and
66
+ # .agents/skills/quota-array-dispatch/SKILL.md.
67
+ # On a primary home, the locked mutable path materializes the visible
68
+ # default config/startup-memory-budget=7500 when absent. It never
69
+ # guesses at malformed or unsafe existing files, and secondmate homes
70
+ # await the primary-authoritative inherited value instead of creating
71
+ # their own.
72
+ # X mode is OPTIONAL and inert unless FM_HOME/.env has a non-empty
73
+ # FMX_PAIRING_TOKEN. When opted in, bootstrap requires curl+jq, writes
74
+ # the relay poll shim and 30s cadence config, and prints an FMX line.
75
+ # Fleet sync fetches, fast-forwards safe default-branch states, reports
76
+ # recovered and STUCK clone drift, and prunes gone local branches; it is
77
+ # bounded by FM_FLEET_SYNC_BOOTSTRAP_TIMEOUT when it is a non-empty
78
+ # numeric override, while non-numeric values fall back to 20s.
79
+ # When the override is unset or blank, the timeout is
80
+ # max(20, 5 + 3 * origin-backed project clone count). A timed-out
81
+ # refresh relays any completed fm-fleet-sync.sh output before the
82
+ # aggregate timeout skip line with timeout and elapsed seconds.
83
+ # Set FM_FLEET_PRUNE=0 to skip branch pruning during that refresh.
84
+ # BACKLOG_RECONCILE lines report what backlog_record_reconcile could not
85
+ # settle in THIS home. Every ordinary dispatch and completion now moves
86
+ # the backlog row inside the script that moves the task's record
87
+ # (bin/fm-backlog-transition-lib.sh), so this sweep exists for the
88
+ # crash window inside those scripts and for drift a home was already
89
+ # carrying: it finishes the authoritative close an interrupted cleanup
90
+ # recorded, and marks In flight any item this home already owns a worker
91
+ # for. The worker-record sweep never starts a captain-held or closed
92
+ # item, and reconciliation never reads or writes another home; the fleet
93
+ # snapshot's classifier and
94
+ # bin/fm-secondmate-reconcile.sh's nudge stay as backstops. Replayed
95
+ # closes and restored In-flight rows print BOOTSTRAP_INFO facts.
96
+ # Set FM_BOOTSTRAP_DETECT_ONLY=1 to skip the six MUTATING sweeps
97
+ # (backlog_record_reconcile, secondmate_sync,
98
+ # secondmate_liveness_sweep, secondmate_handoff_resume, x_mode_setup,
99
+ # fleet_sync) while still
100
+ # printing every read-only detect line
101
+ # above; the TANGLE line switches to advisory-only wording with no
102
+ # checkout command. Used by
103
+ # fm-session-start.sh's read-only path when another live session holds
104
+ # the fleet lock, so a second concurrent session never race-mutates
105
+ # secondmate homes, pending handoff outboxes,
106
+ # X-mode artifacts, project clones, or repair instructions.
107
+ # Unset/0 (the default) runs all six sweeps - this flag is purely
108
+ # additive.
109
+ # Set FM_BOOTSTRAP_NETWORK to split this run by whether a step talks to
110
+ # the network, so a session start can print its digest from local reads
111
+ # alone and run the network half off the digest's blocking path:
112
+ # all (default, and any unrecognized value) - every local and network
113
+ # step. Unrecognized values fall back here on purpose: a typo
114
+ # must never silently skip a safety sweep.
115
+ # skip - every LOCAL step, and none of the network ones. Skips
116
+ # `gh auth status`, secondmate_liveness_sweep, secondmate_sync,
117
+ # secondmate_handoff_resume, and fleet_sync.
118
+ # only - ONLY those network steps and nothing else. No tool detection,
119
+ # no version floors, no tangle check, no backlog
120
+ # reconciliation, no x_mode_setup: those already ran on the
121
+ # local pass.
122
+ # FM_BOOTSTRAP_DETECT_ONLY composes with it unchanged, so `only` plus
123
+ # detect-only is the read-only `gh auth status` probe on its own.
124
+ # bin/fm-startup-network.sh owns the deferral: it runs the `only` phase
125
+ # in a detached bounded worker and publishes the result. This file stays
126
+ # the single owner of every sweep, and the split changes only WHEN each
127
+ # runs, never WHETHER. During the network phase, project clone refresh
128
+ # overlaps the independent secondmate work. Per-secondmate remote
129
+ # liveness workers run concurrently and finish before per-secondmate
130
+ # remote convergence workers run concurrently, because convergence
131
+ # consumes respawned ids. Worker output is captured separately and
132
+ # replayed in spawn order; failure to create that private capture
133
+ # directory selects the sequential fallback.
134
+ # A relaunch that the liveness sweep performs during an `only` run is
135
+ # always reported, because a digest composed before that run already
136
+ # printed the superseded endpoint record.
137
+ # Set FM_BOOTSTRAP_LOCKED=1 alongside it when the sweeps are skipped
138
+ # because THIS session already ran them while holding the fleet lock,
139
+ # rather than because it has no lock at all. The two cases differ in
140
+ # exactly one place: repair ownership. A locked session is told to
141
+ # restore a tangled primary checkout itself, while an unlocked one is
142
+ # told to leave that work to the lock holder. Unset/0 (the default)
143
+ # keeps detect-only meaning unlocked, exactly as before.
144
+ # fm-bootstrap.sh install <tool>...
145
+ # Install the named tools (only ones the captain approved).
146
+ set -u
147
+
148
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
149
+ FM_ROOT="${FM_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
150
+ FM_HOME="${FM_HOME:-${FM_ROOT_OVERRIDE:-$FM_ROOT}}"
151
+ PROJECTS="${FM_PROJECTS_OVERRIDE:-$FM_HOME/projects}"
152
+ CONFIG="${FM_CONFIG_OVERRIDE:-$FM_HOME/config}"
153
+ STATE="${FM_STATE_OVERRIDE:-$FM_HOME/state}"
154
+ DATA="${FM_DATA_OVERRIDE:-$FM_HOME/data}"
155
+ # shellcheck source=bin/fm-tasks-axi-lib.sh disable=SC1091
156
+ . "$SCRIPT_DIR/fm-tasks-axi-lib.sh"
157
+ # shellcheck source=bin/fm-backlog-transition-lib.sh disable=SC1091
158
+ . "$SCRIPT_DIR/fm-backlog-transition-lib.sh"
159
+ # shellcheck source=bin/fm-quota-axi-lib.sh disable=SC1091
160
+ . "$SCRIPT_DIR/fm-quota-axi-lib.sh"
161
+ # shellcheck source=bin/fm-tangle-lib.sh disable=SC1091
162
+ . "$SCRIPT_DIR/fm-tangle-lib.sh"
163
+ # shellcheck source=bin/fm-ff-lib.sh disable=SC1091
164
+ . "$SCRIPT_DIR/fm-ff-lib.sh"
165
+ # shellcheck source=bin/fm-cursor-lib.sh disable=SC1091
166
+ . "$SCRIPT_DIR/fm-cursor-lib.sh"
167
+ # shellcheck source=bin/fm-config-inherit-lib.sh disable=SC1091
168
+ . "$SCRIPT_DIR/fm-config-inherit-lib.sh"
169
+ # shellcheck source=bin/fm-secondmate-nudge-lib.sh disable=SC1091
170
+ . "$SCRIPT_DIR/fm-secondmate-nudge-lib.sh"
171
+ # shellcheck source=bin/fm-startup-memory-budget-lib.sh disable=SC1091
172
+ . "$SCRIPT_DIR/fm-startup-memory-budget-lib.sh"
173
+ # shellcheck source=bin/fm-x-lib.sh disable=SC1091
174
+ . "$SCRIPT_DIR/fm-x-lib.sh"
175
+ # shellcheck source=bin/fm-backend.sh disable=SC1091
176
+ . "$SCRIPT_DIR/fm-backend.sh"
177
+ # shellcheck source=bin/fm-remote-readiness-lib.sh disable=SC1091
178
+ . "$SCRIPT_DIR/fm-remote-readiness-lib.sh"
179
+ # fm-timing-lib.sh is inert unless FM_TIMING_LOG names a file, which only the
180
+ # deferred network stage sets, so an ordinary bootstrap run records nothing.
181
+ # shellcheck source=bin/fm-timing-lib.sh disable=SC1091
182
+ . "$SCRIPT_DIR/fm-timing-lib.sh"
183
+
184
+ # Network-phase selection (see the header). An unrecognized value resolves to
185
+ # `all` so a malformed override runs every step rather than silently dropping a
186
+ # safety sweep.
187
+ case "${FM_BOOTSTRAP_NETWORK:-all}" in
188
+ skip|only) FM_BOOTSTRAP_NETWORK_PHASE=${FM_BOOTSTRAP_NETWORK:-all} ;;
189
+ *) FM_BOOTSTRAP_NETWORK_PHASE=all ;;
190
+ esac
191
+ local_phase() { [ "$FM_BOOTSTRAP_NETWORK_PHASE" != only ]; }
192
+ network_phase() { [ "$FM_BOOTSTRAP_NETWORK_PHASE" != skip ]; }
193
+
194
+ network_mutation_authorized() {
195
+ local expected=${FM_BOOTSTRAP_NETWORK_LOCK_PID:-} current
196
+ [ -n "$expected" ] || return 0
197
+ case "$expected" in *[!0-9]*) return 1 ;; esac
198
+ [ -f "$STATE/.lock" ] && [ ! -L "$STATE/.lock" ] || return 1
199
+ current=$(cat "$STATE/.lock" 2>/dev/null) || return 1
200
+ [ "$current" = "$expected" ]
201
+ }
202
+
203
+ network_sweep_authorized() {
204
+ local label=$1
205
+ if network_mutation_authorized; then
206
+ return 0
207
+ fi
208
+ echo "NETWORK_CHECKS: fleet lock ownership changed before $label, so this stale worker skipped that sweep"
209
+ return 1
210
+ }
211
+
212
+ # Concurrent per-item runner for the deferred network sweeps. Each worker's
213
+ # stdout and stderr are captured to private files and replayed in original
214
+ # order after every worker finishes, so concurrent probes cannot interleave
215
+ # or mis-attribute SECONDMATE_LIVENESS / SECONDMATE_SYNC lines. Respawned ids
216
+ # are collected from per-id files because background workers cannot mutate
217
+ # the parent's SECONDMATE_RESPAWNED_IDS.
218
+ bootstrap_parallel_begin() {
219
+ BOOTSTRAP_PAR_DIR=$(mktemp -d "${TMPDIR:-/tmp}/fm-bootstrap-par.XXXXXX") || return 1
220
+ BOOTSTRAP_PAR_N=0
221
+ FM_BOOTSTRAP_PARALLEL_DIR=$BOOTSTRAP_PAR_DIR
222
+ export FM_BOOTSTRAP_PARALLEL_DIR
223
+ }
224
+
225
+ bootstrap_parallel_spawn() {
226
+ BOOTSTRAP_PAR_N=$((BOOTSTRAP_PAR_N + 1))
227
+ (
228
+ "$@"
229
+ ) >"$BOOTSTRAP_PAR_DIR/$BOOTSTRAP_PAR_N.out" 2>"$BOOTSTRAP_PAR_DIR/$BOOTSTRAP_PAR_N.err" &
230
+ printf '%s\n' "$!" > "$BOOTSTRAP_PAR_DIR/$BOOTSTRAP_PAR_N.pid"
231
+ }
232
+
233
+ bootstrap_parallel_finish() {
234
+ local i pid f
235
+ i=1
236
+ while [ "$i" -le "$BOOTSTRAP_PAR_N" ]; do
237
+ pid=$(cat "$BOOTSTRAP_PAR_DIR/$i.pid")
238
+ wait "$pid" || true
239
+ i=$((i + 1))
240
+ done
241
+ i=1
242
+ while [ "$i" -le "$BOOTSTRAP_PAR_N" ]; do
243
+ cat "$BOOTSTRAP_PAR_DIR/$i.out"
244
+ cat "$BOOTSTRAP_PAR_DIR/$i.err" >&2
245
+ i=$((i + 1))
246
+ done
247
+ for f in "$BOOTSTRAP_PAR_DIR"/respawned.*; do
248
+ [ -f "$f" ] || continue
249
+ SECONDMATE_RESPAWNED_IDS="$SECONDMATE_RESPAWNED_IDS $(tr -d '\n' < "$f")"
250
+ done
251
+ rm -rf "$BOOTSTRAP_PAR_DIR"
252
+ unset FM_BOOTSTRAP_PARALLEL_DIR BOOTSTRAP_PAR_DIR BOOTSTRAP_PAR_N
253
+ }
254
+
255
+ secondmate_note_respawned() { # <id>
256
+ SECONDMATE_RESPAWNED_IDS="$SECONDMATE_RESPAWNED_IDS $1"
257
+ [ -n "${FM_BOOTSTRAP_PARALLEL_DIR:-}" ] || return 0
258
+ printf '%s\n' "$1" > "$FM_BOOTSTRAP_PARALLEL_DIR/respawned.$1"
259
+ }
260
+
261
+ fleet_sync_origin_backed_project_count() {
262
+ local count proj
263
+ count=0
264
+ [ -d "$PROJECTS" ] || { echo 0; return 0; }
265
+ for proj in "$PROJECTS"/*; do
266
+ [ -d "$proj" ] || continue
267
+ git -C "$proj" rev-parse --git-dir >/dev/null 2>&1 || continue
268
+ git -C "$proj" remote get-url origin >/dev/null 2>&1 || continue
269
+ count=$((count + 1))
270
+ done
271
+ echo "$count"
272
+ }
273
+
274
+ fleet_sync_bootstrap_timeout() {
275
+ local count timeout
276
+ if [ -n "${FM_FLEET_SYNC_BOOTSTRAP_TIMEOUT:-}" ]; then
277
+ case "$FM_FLEET_SYNC_BOOTSTRAP_TIMEOUT" in
278
+ *[!0-9]*) echo 20 ;;
279
+ *) echo "$FM_FLEET_SYNC_BOOTSTRAP_TIMEOUT" ;;
280
+ esac
281
+ return 0
282
+ fi
283
+
284
+ count=$(fleet_sync_origin_backed_project_count)
285
+ timeout=$((5 + (3 * count)))
286
+ [ "$timeout" -ge 20 ] || timeout=20
287
+ echo "$timeout"
288
+ }
289
+
290
+ fleet_sync_relay_filtered_output() {
291
+ local tmp=$1 line
292
+ while IFS= read -r line; do
293
+ case "$line" in
294
+ *': skipped: local-only project') ;;
295
+ *': skipped: no origin remote') ;;
296
+ *': skipped:'*) echo "FLEET_SYNC: $line" ;;
297
+ *': STUCK:'*) echo "FLEET_SYNC: $line" ;;
298
+ *': recovered:'*) echo "FLEET_SYNC: $line" ;;
299
+ esac
300
+ done < "$tmp"
301
+ }
302
+
303
+ fleet_sync_relay_all_output() {
304
+ local tmp=$1 line
305
+ while IFS= read -r line; do
306
+ [ -n "$line" ] || continue
307
+ echo "FLEET_SYNC: $line"
308
+ done < "$tmp"
309
+ }
310
+
311
+ fleet_sync() {
312
+ [ -x "$FM_ROOT/bin/fm-fleet-sync.sh" ] || return 0
313
+ [ -d "$PROJECTS" ] || return 0
314
+
315
+ tmp=$(mktemp "${TMPDIR:-/tmp}/fm-fleet-sync.XXXXXX" 2>/dev/null) || return 0
316
+ timeout=$(fleet_sync_bootstrap_timeout)
317
+ monitor_was_on=0
318
+ case $- in *m*) monitor_was_on=1 ;; esac
319
+ set -m 2>/dev/null || true
320
+ "$FM_ROOT/bin/fm-fleet-sync.sh" >"$tmp" 2>/dev/null &
321
+ pid=$!
322
+
323
+ start=$SECONDS
324
+ while jobs -r -p | grep -qx "$pid"; do
325
+ elapsed=$((SECONDS - start))
326
+ if [ "$elapsed" -ge "$timeout" ]; then
327
+ kill -TERM "-$pid" 2>/dev/null || kill "$pid" 2>/dev/null || true
328
+ wait "$pid" 2>/dev/null || true
329
+ [ "$monitor_was_on" -eq 1 ] || set +m 2>/dev/null || true
330
+ fleet_sync_relay_all_output "$tmp"
331
+ echo "FLEET_SYNC: fleet: skipped: bootstrap refresh timed out (timeout=${timeout}s elapsed=${elapsed}s)"
332
+ rm -f "$tmp"
333
+ return 0
334
+ fi
335
+ sleep 1
336
+ done
337
+ wait "$pid" 2>/dev/null || true
338
+ [ "$monitor_was_on" -eq 1 ] || set +m 2>/dev/null || true
339
+
340
+ fleet_sync_relay_filtered_output "$tmp"
341
+ rm -f "$tmp"
342
+ }
343
+
344
+ secondmate_sync() {
345
+ # shellcheck source=bin/fm-wake-lib.sh disable=SC1091
346
+ . "$SCRIPT_DIR/fm-wake-lib.sh"
347
+ # Placement-specific secondmate sync: local homes fast-forward to the primary
348
+ # checkout's current default-branch commit. That path is purely LOCAL - no
349
+ # fetch, no origin dependency: a linked-worktree home already holds the primary's
350
+ # commit (fm-ff-lib.sh), while a standalone clone without it is skipped until
351
+ # /updatefirstmate refreshes it from origin. Startup sends reread nudges only
352
+ # for RUNNING secondmates whose instruction surface (AGENTS.md, bin/, or
353
+ # .agents/skills/) actually changed, so a secondmate already on the primary's
354
+ # version is never disturbed (AGENTS.md bootstrap + supervision). Unlike
355
+ # /updatefirstmate, startup owns the live-convergence send itself because it is
356
+ # a deterministic locked sweep and can report success as BOOTSTRAP_INFO while
357
+ # preserving failed sends as NUDGE_SECONDMATES retry markers.
358
+ [ -d "$STATE" ] || return 0
359
+ local primary_head
360
+ if ! primary_head=$(primary_head_commit "$FM_ROOT"); then
361
+ local meta id
362
+ for meta in "$STATE"/*.meta; do
363
+ [ -f "$meta" ] || continue
364
+ grep -q '^kind=secondmate' "$meta" 2>/dev/null || continue
365
+ id=$(basename "$meta" .meta)
366
+ echo "SECONDMATE_SYNC: secondmate $id: skipped: primary default-branch commit cannot be resolved"
367
+ done
368
+ return 0
369
+ fi
370
+ FF_NUDGE_WINDOWS=""
371
+ FF_SEEN_HOMES=""
372
+ SECOND_MATE_NUDGE_MESSAGE=$FM_SECOND_MATE_NUDGE_MESSAGE
373
+ REMOTE_SECOND_MATE_NUDGE_MESSAGE=$FM_REMOTE_SECOND_MATE_NUDGE_MESSAGE
374
+ SECOND_MATE_NUDGE_PENDING_DIR="$STATE/.secondmate-nudge-pending"
375
+
376
+ secondmate_nudge_marker_path() {
377
+ fm_secondmate_nudge_marker_path "$STATE" "$1"
378
+ }
379
+
380
+ secondmate_write_nudge_marker() {
381
+ local id=$1 home=$2 commit=$3 instr=$4 message=${5:-$SECOND_MATE_NUDGE_MESSAGE} remote=${6:-0}
382
+ fm_secondmate_nudge_write "$STATE" "$id" "$home" "$commit" "$instr" "$message" "$remote"
383
+ }
384
+
385
+ secondmate_send_nudge() {
386
+ local id=$1 home=$2 commit=$3 instr=$4 selector marker out
387
+ selector="fm-$id"
388
+ marker=$(secondmate_nudge_marker_path "$id") || {
389
+ echo "NUDGE_SECONDMATES: secondmate $id: send failed: unsafe id"
390
+ return 0
391
+ }
392
+ if ! secondmate_write_nudge_marker "$id" "$home" "$commit" "$instr"; then
393
+ echo "NUDGE_SECONDMATES: secondmate $id: send failed: cannot record retry marker"
394
+ return 0
395
+ fi
396
+ if out=$(FM_HOME="$FM_HOME" FM_ROOT_OVERRIDE="$FM_ROOT" FM_STATE_OVERRIDE="$STATE" "$SCRIPT_DIR/fm-send.sh" "$selector" "$SECOND_MATE_NUDGE_MESSAGE" 2>&1); then
397
+ rm -f "$marker"
398
+ echo "BOOTSTRAP_INFO: nudged $selector with '$SECOND_MATE_NUDGE_MESSAGE'"
399
+ else
400
+ echo "NUDGE_SECONDMATES: secondmate $id: send failed: $(first_line "$out")"
401
+ fi
402
+ }
403
+
404
+ fm_ff_after_instruction_update() {
405
+ local id=$1 home=$2 _window=$3 instr=$4
406
+ secondmate_send_nudge "$id" "$home" "$primary_head" "$instr"
407
+ }
408
+
409
+ secondmate_retry_pending_nudges() {
410
+ local marker id selector home commit message remote expected_marker meta meta_home home_real head out
411
+ [ -d "$SECOND_MATE_NUDGE_PENDING_DIR" ] || return 0
412
+ for marker in "$SECOND_MATE_NUDGE_PENDING_DIR"/*.pending; do
413
+ [ -f "$marker" ] || continue
414
+ id=$(fm_meta_get "$marker" id)
415
+ if ! expected_marker=$(secondmate_nudge_marker_path "$id"); then
416
+ echo "NUDGE_SECONDMATES: secondmate ${id:-unknown}: send failed: retry marker has unsafe id"
417
+ continue
418
+ fi
419
+ [ "$expected_marker" = "$marker" ] || {
420
+ echo "NUDGE_SECONDMATES: secondmate $id: send failed: retry marker filename mismatch"
421
+ continue
422
+ }
423
+ selector=$(fm_meta_get "$marker" selector)
424
+ home=$(fm_meta_get "$marker" home)
425
+ commit=$(fm_meta_get "$marker" commit)
426
+ message=$(fm_meta_get "$marker" message)
427
+ remote=$(fm_meta_get "$marker" remote)
428
+ [ -n "$remote" ] || remote=0
429
+ [ "$selector" = "fm-$id" ] || {
430
+ echo "NUDGE_SECONDMATES: secondmate ${id:-unknown}: send failed: retry marker selector mismatch"
431
+ continue
432
+ }
433
+ case "$remote" in
434
+ 0) [ "$message" = "$SECOND_MATE_NUDGE_MESSAGE" ] || {
435
+ echo "NUDGE_SECONDMATES: secondmate ${id:-unknown}: send failed: retry marker message mismatch"
436
+ continue
437
+ } ;;
438
+ 1) [ "$message" = "$REMOTE_SECOND_MATE_NUDGE_MESSAGE" ] || {
439
+ echo "NUDGE_SECONDMATES: secondmate ${id:-unknown}: send failed: remote retry marker message mismatch"
440
+ continue
441
+ } ;;
442
+ *)
443
+ echo "NUDGE_SECONDMATES: secondmate ${id:-unknown}: send failed: retry marker placement is invalid"
444
+ continue
445
+ ;;
446
+ esac
447
+ [ "$remote" -ne 1 ] || continue
448
+ meta="$STATE/$id.meta"
449
+ [ -f "$meta" ] && [ "$(fm_meta_get "$meta" kind)" = secondmate ] || {
450
+ echo "NUDGE_SECONDMATES: secondmate ${id:-unknown}: send failed: retry target has no live secondmate metadata"
451
+ continue
452
+ }
453
+ meta_home=$(fm_meta_get "$meta" home)
454
+ [ -n "$meta_home" ] || meta_home=$(secondmate_registry_field "$DATA/secondmates.md" "$id" home || true)
455
+ if ! validate_secondmate_home "$id" "$meta_home"; then
456
+ echo "NUDGE_SECONDMATES: secondmate $id: send failed: retry target home unsafe: $VALIDATION_ERROR"
457
+ continue
458
+ fi
459
+ home_real="$VALIDATED_HOME"
460
+ [ "$home_real" = "$home" ] || {
461
+ echo "NUDGE_SECONDMATES: secondmate $id: send failed: retry target home changed"
462
+ continue
463
+ }
464
+ head=$(git -C "$home_real" rev-parse HEAD 2>/dev/null || true)
465
+ [ -n "$head" ] && [ "$head" = "$commit" ] || {
466
+ echo "NUDGE_SECONDMATES: secondmate $id: send failed: retry target is not at recorded instruction commit"
467
+ continue
468
+ }
469
+ if out=$(FM_HOME="$FM_HOME" FM_ROOT_OVERRIDE="$FM_ROOT" FM_STATE_OVERRIDE="$STATE" "$SCRIPT_DIR/fm-send.sh" "$selector" "$SECOND_MATE_NUDGE_MESSAGE" 2>&1); then
470
+ rm -f "$marker"
471
+ echo "BOOTSTRAP_INFO: nudged $selector with '$SECOND_MATE_NUDGE_MESSAGE'"
472
+ else
473
+ echo "NUDGE_SECONDMATES: secondmate $id: send failed: $(first_line "$out")"
474
+ fi
475
+ done
476
+ }
477
+
478
+ local tmp line
479
+ secondmate_retry_pending_nudges
480
+ tmp=$(mktemp "${TMPDIR:-/tmp}/fm-secondmate-sync.XXXXXX" 2>/dev/null) || return 0
481
+ sweep_live_secondmate_metas "$STATE" "$primary_head" yes "$DATA/secondmates.md" >"$tmp"
482
+ while IFS= read -r line; do
483
+ case "$line" in
484
+ secondmate\ *': skipped:'*) echo "SECONDMATE_SYNC: $line" ;;
485
+ BOOTSTRAP_INFO:\ *) echo "$line" ;;
486
+ NUDGE_SECONDMATES:\ *) echo "$line" ;;
487
+ esac
488
+ done < "$tmp"
489
+ rm -f "$tmp"
490
+ unset -f fm_ff_after_instruction_update
491
+ # Inheritance propagation: push the primary-authoritative local inheritance
492
+ # surface into every VALIDATED live secondmate home swept above.
493
+ # FF_SEEN_HOMES is exactly that set, and fm-config-inherit-lib.sh owns the
494
+ # declared config items plus data/captain-shared.md.
495
+ # After a successful push that changes allowlisted config/* for an already-
496
+ # running home, send its literal-content reread instruction pointer so the
497
+ # live agent does not keep applying stale defaults. Spawn/respawn already
498
+ # re-reads at launch and needs no redundant nudge unless files changed after launch.
499
+ local id home home_real home_lock propagated_homes report reread_out reread_skip_pending
500
+ propagated_homes=""
501
+ SECONDMATE_RESPAWNED_IDS=${SECONDMATE_RESPAWNED_IDS:-}
502
+ while IFS='|' read -r id home _window _meta; do
503
+ validate_secondmate_home "$id" "$home" || continue
504
+ home_real="$VALIDATED_HOME"
505
+ case " $FF_SEEN_HOMES " in
506
+ *" $home_real "*) ;;
507
+ *) continue ;;
508
+ esac
509
+ case " $propagated_homes " in
510
+ *" $home_real "*) continue ;;
511
+ esac
512
+ propagated_homes="$propagated_homes $home_real"
513
+ mkdir -p "$home_real/state" || {
514
+ echo "CONFIG_REREAD: secondmate $id: send failed: could not create state directory"
515
+ continue
516
+ }
517
+ home_lock=$(fm_config_inherit_lock_path "$home_real") || {
518
+ echo "CONFIG_REREAD: secondmate $id: send failed: could not resolve per-home lock"
519
+ continue
520
+ }
521
+ fm_lock_acquire_wait "$home_lock" || {
522
+ echo "CONFIG_REREAD: secondmate $id: send failed: could not acquire per-home lock"
523
+ continue
524
+ }
525
+ reread_skip_pending=0
526
+ case " $SECONDMATE_RESPAWNED_IDS " in
527
+ *" $id "*) reread_skip_pending=1 ;;
528
+ esac
529
+ if [ "$reread_skip_pending" -eq 0 ] \
530
+ && fm_config_reread_retry_queue_is_full "$FM_HOME" "$id"; then
531
+ fm_config_reread_retry_pending "$id" "$home_real" || true
532
+ if fm_config_reread_retry_queue_is_full "$FM_HOME" "$id"; then
533
+ echo "CONFIG_REREAD: secondmate $id: send failed: retry instruction queue is full"
534
+ fm_lock_release "$home_lock" || true
535
+ continue
536
+ fi
537
+ fi
538
+ report=$(mktemp "${TMPDIR:-/tmp}/fm-bootstrap-inherit.XXXXXX" 2>/dev/null) || {
539
+ echo "SECONDMATE_SYNC: secondmate $id: skipped: inheritance failed"
540
+ fm_lock_release "$home_lock" || true
541
+ continue
542
+ }
543
+ if FM_CONFIG_INHERIT_REPORT="$report" FM_CONFIG_INHERIT_LIVE=1 \
544
+ propagate_secondmate_inheritance "$FM_HOME" "$home_real" "$CONFIG" "$DATA"; then
545
+ :
546
+ else
547
+ echo "SECONDMATE_SYNC: secondmate $id: skipped: inheritance failed"
548
+ fi
549
+ if ! reread_out=$(FM_HOME="$FM_HOME" FM_ROOT_OVERRIDE="$FM_ROOT" \
550
+ FM_STATE_OVERRIDE="$STATE" \
551
+ FM_CONFIG_REREAD_SKIP_PENDING="$reread_skip_pending" \
552
+ fm_config_send_reread_nudge "$id" "$home_real" "$report" 2>&1); then
553
+ if [ -n "$reread_out" ]; then
554
+ printf '%s\n' "$reread_out"
555
+ else
556
+ echo "CONFIG_REREAD: secondmate $id: send failed: unknown error"
557
+ fi
558
+ elif [ -n "$reread_out" ]; then
559
+ printf '%s\n' "$reread_out"
560
+ fi
561
+ rm -f "$report"
562
+ fm_lock_release "$home_lock" || true
563
+ done < <(live_secondmate_meta_records "$STATE" "$DATA/secondmates.md")
564
+
565
+ # One remote secondmate's convergence, split out of the loop so each host is
566
+ # individually timed; every `return` here was a `continue` and still means
567
+ # "move on to the next secondmate".
568
+ secondmate_sync_remote_one() { # <id> <home> <remote-host>
569
+ local id=$1 _home=$2 remote_host=$3
570
+ local sync_out inherit_out nudge_needed remote_marker remote_pending converged out remote_lock remote_generation
571
+ remote_lock=$(fm_remote_inherit_transaction_lock_path "$STATE" "$id" 2>/dev/null || true)
572
+ if [ -z "$remote_lock" ] || ! fm_lock_acquire_wait "$remote_lock"; then
573
+ echo "NUDGE_SECONDMATES: secondmate $id: send failed: cannot lock remote inheritance transaction"
574
+ return 0
575
+ fi
576
+ if ! "$SCRIPT_DIR/fm-procevent-remote-reply.sh" arm "$id" >/dev/null 2>&1; then
577
+ echo "SECONDMATE_LIVENESS: secondmate $id: skipped: remote reply source could not be registered"
578
+ fi
579
+ remote_generation=$(fm_remote_inherit_generation_next "$STATE" "$id" 2>/dev/null || true)
580
+ if [ -z "$remote_generation" ]; then
581
+ echo "SECONDMATE_SYNC: secondmate $id: skipped: remote inheritance generation could not be published"
582
+ fm_lock_release "$remote_lock" || true
583
+ return 0
584
+ fi
585
+ remote_marker=$(secondmate_nudge_marker_path "$id" 2>/dev/null || true)
586
+ remote_pending=0
587
+ if [ -f "$remote_marker" ] && [ "$(fm_meta_get "$remote_marker" remote)" = 1 ]; then remote_pending=1; fi
588
+ if ! secondmate_write_nudge_marker "$id" "$_home" "" remote \
589
+ "$REMOTE_SECOND_MATE_NUDGE_MESSAGE" 1; then
590
+ echo "NUDGE_SECONDMATES: secondmate $id: send failed: cannot record remote retry marker"
591
+ fm_lock_release "$remote_lock" || true
592
+ return 0
593
+ fi
594
+ nudge_needed=0
595
+ converged=1
596
+ if sync_out=$("$SCRIPT_DIR/fm-on.sh" "$id" fm-remote-secondmate-control.sh sync "$id" < /dev/null 2>&1); then
597
+ case "$sync_out" in synced:*) nudge_needed=1 ;; esac
598
+ else
599
+ echo "SECONDMATE_SYNC: secondmate $id: skipped: remote tracked-file sync failed on $remote_host: $(first_line "$sync_out")"
600
+ converged=0
601
+ fi
602
+ if inherit_out=$(FM_CONFIG_INHERIT_LIVE=1 \
603
+ "$SCRIPT_DIR/fm-remote-inherit-push.sh" "$id" "$remote_generation" 2>&1); then
604
+ if printf '%s\n' "$inherit_out" | grep -Eq '^(pushed|removed):'; then nudge_needed=1; fi
605
+ else
606
+ echo "SECONDMATE_SYNC: secondmate $id: skipped: remote inheritance failed on $remote_host: $(first_line "$inherit_out")"
607
+ converged=0
608
+ fi
609
+ [ "$remote_pending" -eq 0 ] || nudge_needed=1
610
+ if [ "$converged" -eq 1 ] && [ "$nudge_needed" -eq 1 ]; then
611
+ if out=$(FM_HOME="$FM_HOME" FM_ROOT_OVERRIDE="$FM_ROOT" FM_STATE_OVERRIDE="$STATE" \
612
+ "$SCRIPT_DIR/fm-send.sh" "fm-$id" "$REMOTE_SECOND_MATE_NUDGE_MESSAGE" 2>&1); then
613
+ rm -f "$remote_marker"
614
+ [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" != 1 ] || echo "BOOTSTRAP_INFO: nudged remote fm-$id after convergence"
615
+ else
616
+ echo "NUDGE_SECONDMATES: secondmate $id: send failed: $(first_line "$out")"
617
+ fi
618
+ elif [ "$converged" -eq 1 ]; then
619
+ rm -f "$remote_marker"
620
+ fi
621
+ fm_lock_release "$remote_lock" || true
622
+ return 0
623
+ }
624
+
625
+ secondmate_sync_remote_one_timed() { # <id> <home> <remote-host>
626
+ local id=$1 home=$2 remote_host=$3 __fm_timing_stamp
627
+ __fm_timing_stamp=$(fm_timing_now_ms)
628
+ secondmate_sync_remote_one "$id" "$home" "$remote_host"
629
+ fm_timing_record secondmate convergence "$__fm_timing_stamp" "$id@$remote_host"
630
+ }
631
+
632
+ # Remote routes converge through the generic transport. Their code root and
633
+ # inherited files are authoritative on that host; no local path probe or
634
+ # local fast-forward is attempted for them.
635
+ local remote_host __fm_timing_stamp parallel=0
636
+ if bootstrap_parallel_begin; then
637
+ parallel=1
638
+ fi
639
+ while IFS='|' read -r id _home _window meta; do
640
+ remote_host=$(fm_meta_get "$meta" remote_host)
641
+ [ -n "$remote_host" ] || continue
642
+ if [ "$parallel" -eq 1 ]; then
643
+ bootstrap_parallel_spawn secondmate_sync_remote_one_timed "$id" "$_home" "$remote_host"
644
+ else
645
+ secondmate_sync_remote_one_timed "$id" "$_home" "$remote_host"
646
+ fi
647
+ done < <(live_secondmate_meta_records "$STATE" "$DATA/secondmates.md")
648
+ [ "$parallel" -eq 0 ] || bootstrap_parallel_finish
649
+ return 0
650
+ }
651
+
652
+ # A relaunch replaces the endpoint record a digest may already have printed. On
653
+ # the local pass that digest has not been composed yet, so the fact stays behind
654
+ # FM_BOOTSTRAP_VERBOSE_FACTS as before; on the deferred network pass the digest
655
+ # is already out, so reporting it is what keeps the superseded record from being
656
+ # acted on.
657
+ report_relaunch() { # <id> <cause> <where>
658
+ [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" = 1 ] || ! local_phase || return 0
659
+ echo "BOOTSTRAP_INFO: secondmate $1 relaunched after $2 ($3)"
660
+ }
661
+
662
+ secondmate_liveness_sweep() {
663
+ # Idempotent secondmate liveness guarantee - SESSION START ONLY. The detailed
664
+ # state machine and its only recovery-authorizing states are owned by
665
+ # fm_backend_agent_state. A missing tmux pane is not enough: tmux must prove
666
+ # the window or session absent. This preserves duplicate prevention for
667
+ # existing ambiguous processes and every transiently unreadable target while
668
+ # adding the missing-session path the original bare-shell and Herdr-husk sweep
669
+ # lacked.
670
+ # A meta with no window remains owned by secondmate-provisioning recovery.
671
+ # Secondmate homes never contain kind=secondmate meta, so this is naturally a
672
+ # primary-only no-op there. Mid-session liveness remains explicitly out of
673
+ # scope and requires a separate periodic signal.
674
+ [ -d "$STATE" ] || return 0
675
+ local meta id remote_host label __fm_timing_stamp parallel=0
676
+ SECONDMATE_RESPAWNED_IDS=""
677
+ if bootstrap_parallel_begin; then
678
+ parallel=1
679
+ fi
680
+ for meta in "$STATE"/*.meta; do
681
+ [ -f "$meta" ] || continue
682
+ grep -q '^kind=secondmate$' "$meta" 2>/dev/null || continue
683
+ # Identity for the timing record is read here, in the loop, so the per-meta
684
+ # body below keeps its single-exit-per-outcome shape.
685
+ id=$(basename "$meta" .meta)
686
+ remote_host=$(fm_meta_get "$meta" remote_host)
687
+ label=$id
688
+ [ -z "$remote_host" ] || label="$id@$remote_host"
689
+ if [ "$parallel" -eq 1 ]; then
690
+ bootstrap_parallel_spawn secondmate_liveness_one_timed "$meta" "$id" "$label"
691
+ else
692
+ secondmate_liveness_one_timed "$meta" "$id" "$label"
693
+ fi
694
+ done
695
+ [ "$parallel" -eq 0 ] || bootstrap_parallel_finish
696
+ return 0
697
+ }
698
+
699
+ secondmate_liveness_one_timed() { # <meta> <id> <label>
700
+ local meta=$1 id=$2 label=$3 __fm_timing_stamp
701
+ __fm_timing_stamp=$(fm_timing_now_ms)
702
+ secondmate_liveness_one "$meta" "$id"
703
+ fm_timing_record secondmate liveness "$__fm_timing_stamp" "$label"
704
+ }
705
+
706
+ # One secondmate's liveness check. Split out of the sweep so each is individually
707
+ # timed; every `return` here was a `continue` in the loop and means exactly the
708
+ # same thing - move on to the next secondmate. Respawned ids are recorded through
709
+ # secondmate_note_respawned so a concurrent sweep can collect them after wait.
710
+ secondmate_liveness_one() { # <meta> <id>
711
+ local meta=$1 id=$2
712
+ local window harness backend target agent_state out cause remote_host remote_rc readiness_reason route_out remote_backend
713
+ window=$(fm_meta_get "$meta" window)
714
+ [ -n "$window" ] || return 0
715
+ harness=$(fm_meta_get "$meta" harness)
716
+ remote_host=$(fm_meta_get "$meta" remote_host)
717
+ if [ -n "$remote_host" ]; then
718
+ remote_rc=0
719
+ fm_remote_readiness_ensure "$SCRIPT_DIR" "$id" || remote_rc=$?
720
+ if [ "$remote_rc" -eq 255 ]; then
721
+ echo "SECONDMATE_LIVENESS: secondmate $id: skipped: remote host unavailable or endpoint state unknown; route preserved on $remote_host"
722
+ return 0
723
+ fi
724
+ if [ "$remote_rc" -ne 0 ]; then
725
+ readiness_reason=$(printf '%s\n' "$FM_REMOTE_READINESS_OUT" \
726
+ | awk '/^check [^=]+=(fixable|human):|^action:|^error:/ { print; exit }')
727
+ [ -n "$readiness_reason" ] || readiness_reason=$(first_line "$FM_REMOTE_READINESS_OUT")
728
+ [ -n "$readiness_reason" ] || readiness_reason="unknown readiness failure"
729
+ echo "SECONDMATE_LIVENESS: secondmate $id: skipped: remote readiness failed on $remote_host: $readiness_reason"
730
+ return 0
731
+ fi
732
+ if out=$("$SCRIPT_DIR/fm-on.sh" "$id" fm-remote-secondmate-control.sh state "$id" < /dev/null 2>/dev/null); then
733
+ remote_rc=0
734
+ else
735
+ remote_rc=$?
736
+ fi
737
+ if [ "$remote_rc" -eq 255 ]; then
738
+ echo "SECONDMATE_LIVENESS: secondmate $id: skipped: remote host unavailable or endpoint state unknown; route preserved on $remote_host"
739
+ return 0
740
+ fi
741
+ if [ "$remote_rc" -ne 0 ]; then
742
+ echo "SECONDMATE_LIVENESS: secondmate $id: skipped: remote endpoint probe unreadable on $remote_host"
743
+ return 0
744
+ fi
745
+ agent_state=$(printf '%s\n' "$out" | tail -1)
746
+ case "$agent_state" in
747
+ alive)
748
+ if route_out=$("$SCRIPT_DIR/fm-on.sh" "$id" fm-remote-secondmate-control.sh route "$id" < /dev/null 2>/dev/null); then
749
+ remote_rc=0
750
+ else
751
+ remote_rc=$?
752
+ fi
753
+ if [ "$remote_rc" -eq 255 ]; then
754
+ echo "SECONDMATE_LIVENESS: secondmate $id: skipped: remote host unavailable or endpoint route unknown; route preserved on $remote_host"
755
+ return 0
756
+ fi
757
+ if [ "$remote_rc" -ne 0 ]; then
758
+ echo "SECONDMATE_LIVENESS: secondmate $id: skipped: alive remote endpoint route is unreadable on $remote_host; inspect and migrate or retire it explicitly"
759
+ return 0
760
+ fi
761
+ remote_backend=$(printf '%s\n' "$route_out" | sed -n 's/^backend=//p' | tail -1)
762
+ if [ "$remote_backend" != herdr ]; then
763
+ echo "SECONDMATE_LIVENESS: secondmate $id: skipped: alive remote endpoint is recorded on backend '${remote_backend:-missing}'; migrate or retire it explicitly"
764
+ return 0
765
+ fi
766
+ [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" != 1 ] || echo "BOOTSTRAP_INFO: remote secondmate $id already live (host=$remote_host)"
767
+ ;;
768
+ dead|missing)
769
+ cause="remote endpoint $agent_state on its configured host"
770
+ if out=$(FM_SPAWN_NO_GUARD=1 "$FM_ROOT/bin/fm-spawn.sh" "$id" --secondmate 2>&1); then
771
+ secondmate_note_respawned "$id"
772
+ report_relaunch "$id" "$cause" "host=$remote_host"
773
+ else
774
+ echo "SECONDMATE_LIVENESS: secondmate $id: respawn failed after $cause: $(first_line "$out")"
775
+ fi
776
+ ;;
777
+ ambiguous|unreadable|unverified)
778
+ echo "SECONDMATE_LIVENESS: secondmate $id: skipped: remote endpoint state is $agent_state on $remote_host"
779
+ ;;
780
+ *) echo "SECONDMATE_LIVENESS: secondmate $id: skipped: remote endpoint returned an invalid state" ;;
781
+ esac
782
+ return 0
783
+ fi
784
+ backend=$(fm_backend_of_meta "$meta")
785
+ target=$(fm_backend_target_of_meta "$meta")
786
+ [ -n "$target" ] || target="$window"
787
+ agent_state=$(fm_backend_agent_state "$backend" "$target" 2>/dev/null) || agent_state=unreadable
788
+ case "$harness" in
789
+ claude|codex|opencode|pi|pi-signed|grok|kimi) ;;
790
+ *)
791
+ case "$agent_state" in dead|missing) agent_state=unverified-harness ;; esac
792
+ ;;
793
+ esac
794
+ case "$agent_state" in
795
+ alive)
796
+ if [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" = 1 ]; then
797
+ echo "BOOTSTRAP_INFO: secondmate $id already live (backend=$backend)"
798
+ fi
799
+ ;;
800
+ dead|missing)
801
+ if [ "$agent_state" = dead ]; then
802
+ cause="confirmed agent absence on existing endpoint"
803
+ fm_backend_kill "$backend" "$target" 2>/dev/null || true
804
+ else
805
+ cause="recorded endpoint confidently missing"
806
+ fi
807
+ if out=$(FM_SPAWN_NO_GUARD=1 "$FM_ROOT/bin/fm-spawn.sh" "$id" --secondmate 2>&1); then
808
+ secondmate_note_respawned "$id"
809
+ report_relaunch "$id" "$cause" "backend=$backend"
810
+ else
811
+ echo "SECONDMATE_LIVENESS: secondmate $id: respawn failed after $cause: $(first_line "$out")"
812
+ fi
813
+ ;;
814
+ ambiguous)
815
+ echo "SECONDMATE_LIVENESS: secondmate $id: skipped: existing endpoint has ambiguous agent process (backend=$backend)"
816
+ ;;
817
+ unreadable)
818
+ echo "SECONDMATE_LIVENESS: secondmate $id: skipped: endpoint probe unreadable (backend=$backend)"
819
+ ;;
820
+ unverified-harness)
821
+ echo "SECONDMATE_LIVENESS: secondmate $id: skipped: recorded harness '$harness' is unverified for recovery (backend=$backend)"
822
+ ;;
823
+ *)
824
+ echo "SECONDMATE_LIVENESS: secondmate $id: skipped: agent recovery classifier unverified (backend=$backend)"
825
+ ;;
826
+ esac
827
+ return 0
828
+ }
829
+
830
+ secondmate_handoff_resume() {
831
+ [ -d "$DATA/handoff" ] || return 0
832
+ "$SCRIPT_DIR/fm-backlog-handoff.sh" --resume-pending >/dev/null 2>&1 || true
833
+ }
834
+
835
+ secondmate_handoff_detect() {
836
+ local outbox id count
837
+ [ -d "$DATA/handoff" ] || return 0
838
+ for outbox in "$DATA/handoff"/*.outbox.md; do
839
+ [ -e "$outbox" ] || continue
840
+ id=$(basename "$outbox" .outbox.md)
841
+ case "$id" in ''|*[!A-Za-z0-9._-]*) id=unknown ;; esac
842
+ if [ ! -f "$outbox" ] || [ -L "$outbox" ]; then
843
+ echo "SECONDMATE_HANDOFF: secondmate $id: pending delivery: unsafe outbox"
844
+ continue
845
+ fi
846
+ count=$(awk '/^- \[[ x]\] / { count++ } END { print count + 0 }' "$outbox" 2>/dev/null || printf unknown)
847
+ echo "SECONDMATE_HANDOFF: secondmate $id: pending delivery: $count item(s)"
848
+ done
849
+ }
850
+
851
+ install_cmd() {
852
+ case "$1" in
853
+ tmux|node|git|gh|curl|jq|orca|zellij) echo "brew install $1 # or the platform's package manager" ;;
854
+ cmux) echo "brew install --cask cmux # or see https://cmux.com" ;;
855
+ treehouse) echo "curl -fsSL https://kunchenguid.github.io/treehouse/install.sh | sh" ;;
856
+ no-mistakes) echo "curl -fsSL https://raw.githubusercontent.com/kunchenguid/no-mistakes/main/docs/install.sh | sh" ;;
857
+ gh-axi|chrome-devtools-axi|lavish-axi) echo "npm install -g $1 && $1 setup hooks" ;;
858
+ tasks-axi|quota-axi) echo "npm install -g $1" ;;
859
+ *) return 1 ;;
860
+ esac
861
+ }
862
+
863
+ manual_install_url() {
864
+ case "$1" in
865
+ herdr) echo "https://herdr.dev" ;;
866
+ cursor-agent) echo "https://cursor.com/cli" ;;
867
+ *) return 1 ;;
868
+ esac
869
+ }
870
+
871
+ missing_tool_diagnostic() {
872
+ local tool=$1 instructions
873
+ if instructions=$(manual_install_url "$tool"); then
874
+ echo "MISSING_MANUAL: $tool (instructions: $instructions)"
875
+ return 0
876
+ fi
877
+ echo "MISSING: $tool (install: $(install_cmd "$tool"))"
878
+ }
879
+
880
+ # Required-tool detection follows the RESOLVED backend, not a one-size default:
881
+ # a universal toolchain every home needs plus the backend-specific delta owned by
882
+ # fm_backend_required_tools (bin/fm-backend.sh). So a herdr/zellij/cmux home is
883
+ # never told tmux is missing, and only orca drops treehouse. A backend value with
884
+ # no verified dependency set is reported before the universal checks continue.
885
+ COMMON_TOOLS="node git gh no-mistakes gh-axi chrome-devtools-axi lavish-axi tasks-axi quota-axi"
886
+ BACKEND=$(fm_backend_name)
887
+ BACKEND_VALID=1
888
+ if ! BACKEND_TOOLS=$(fm_backend_required_tools "$BACKEND"); then
889
+ BACKEND_VALID=0
890
+ BACKEND_TOOLS=""
891
+ fi
892
+ TOOLS="$BACKEND_TOOLS $COMMON_TOOLS"
893
+ NO_MISTAKES_MIN=1.46.0
894
+ # AXI-FAMILY FLOOR POLICY. Every axi-family floor is the CURRENT LATEST published
895
+ # version of that tool, captain-bumped periodically to keep the whole fleet on the
896
+ # newest axi tools. It is NOT the minimum feature-introduced version. These floors
897
+ # are expected to drift upward as new versions ship. Never lower a floor to the
898
+ # earliest release that happens to satisfy some depended-on behavior. The
899
+ # tasks-axi feature probes are an independent defense-in-depth concern, not part
900
+ # of its floor.
901
+ GH_AXI_MIN=0.1.29
902
+ LAVISH_AXI_MIN=0.1.46
903
+
904
+ treehouse_supports_lease() {
905
+ treehouse get --help 2>&1 | grep -Eq '(^|[^[:alnum:]_-])--lease([^[:alnum:]_-]|$)'
906
+ }
907
+
908
+ # Shared semantic-version floor for the tool gates below. A version string that
909
+ # cannot be parsed into exactly one major.minor.patch triple is incompatible,
910
+ # never assumed current, so a development or vendored build cannot pass a floor
911
+ # it was never checked against.
912
+ tool_version_at_least() { # <tool> <min-version>
913
+ local tool=$1 min=$2 output parts major minor patch extra
914
+ local min_major min_minor min_patch min_extra
915
+ command -v "$tool" >/dev/null 2>&1 || return 1
916
+ output=$("$tool" --version 2>/dev/null) || return 1
917
+ parts=$(printf '%s\n' "$output" | sed -nE 's/.*[vV]?([0-9]+)\.([0-9]+)\.([0-9]+).*/\1 \2 \3/p' | head -n 1)
918
+ IFS=' ' read -r major minor patch extra <<< "$parts"
919
+ [ -n "$major" ] && [ -n "$minor" ] && [ -n "$patch" ] && [ -z "$extra" ] || return 1
920
+ IFS='.' read -r min_major min_minor min_patch min_extra <<< "$min"
921
+ [ -n "$min_major" ] && [ -n "$min_minor" ] && [ -n "$min_patch" ] && [ -z "$min_extra" ] || return 1
922
+ [ "$major" -gt "$min_major" ] && return 0
923
+ [ "$major" -eq "$min_major" ] || return 1
924
+ [ "$minor" -gt "$min_minor" ] && return 0
925
+ [ "$minor" -eq "$min_minor" ] || return 1
926
+ [ "$patch" -ge "$min_patch" ]
927
+ }
928
+
929
+ x_mode_write_if_changed() {
930
+ local dest=$1 content=$2 mode=$3 parent tmp parent_device current_mode
931
+ parent=${dest%/*}
932
+ [ "$parent" != "$dest" ] || return 1
933
+ [ -d "$parent" ] && [ ! -L "$parent" ] || return 1
934
+ if [ "$(uname)" = Darwin ]; then
935
+ parent_device=$(stat -f %d "$parent" 2>/dev/null) || return 1
936
+ else
937
+ parent_device=$(stat -c %d "$parent" 2>/dev/null) || return 1
938
+ fi
939
+ if [ -e "$dest" ] || [ -L "$dest" ]; then
940
+ fmx_single_link_file_valid "$dest" "$parent_device" || return 1
941
+ if [ "$(uname)" = Darwin ]; then
942
+ current_mode=$(stat -f %Lp "$dest" 2>/dev/null) || return 1
943
+ else
944
+ current_mode=$(stat -c %a "$dest" 2>/dev/null) || return 1
945
+ fi
946
+ if [ "$current_mode" = "$mode" ] && cmp -s "$dest" <(printf '%s\n' "$content"); then
947
+ return 0
948
+ fi
949
+ fi
950
+ tmp=$(umask 077; mktemp "$parent/.fm-x-mode.XXXXXX" 2>/dev/null) || return 1
951
+ if ! printf '%s\n' "$content" > "$tmp" \
952
+ || ! chmod "$mode" "$tmp" \
953
+ || ! fmx_single_link_file_mode_valid "$tmp" "$mode" "$parent_device"; then
954
+ rm -f -- "$tmp"
955
+ return 1
956
+ fi
957
+ if { [ -e "$dest" ] || [ -L "$dest" ]; } \
958
+ && ! fmx_single_link_file_valid "$dest" "$parent_device"; then
959
+ rm -f -- "$tmp"
960
+ return 1
961
+ fi
962
+ if ! mv -f -- "$tmp" "$dest"; then
963
+ rm -f -- "$tmp"
964
+ return 1
965
+ fi
966
+ if ! fmx_single_link_file_mode_valid "$dest" "$mode" "$parent_device" \
967
+ || ! cmp -s "$dest" <(printf '%s\n' "$content"); then
968
+ rm -f -- "$dest"
969
+ return 1
970
+ fi
971
+ }
972
+
973
+ x_mode_artifact_present() {
974
+ [ -e "$1" ] || [ -L "$1" ]
975
+ }
976
+
977
+ x_mode_remove_artifact() {
978
+ local artifact=$1 parent=${1%/*}
979
+ x_mode_artifact_present "$artifact" || return 0
980
+ [ -d "$parent" ] && [ ! -L "$parent" ] || return 1
981
+ rm -f -- "$artifact" 2>/dev/null || return 1
982
+ ! x_mode_artifact_present "$artifact"
983
+ }
984
+
985
+ # X mode (opt-in): when this home's .env carries a non-empty FMX_PAIRING_TOKEN,
986
+ # wire the relay poll into the existing authenticated watcher dispatch.
987
+ # Drops two idempotent, gitignored artifacts:
988
+ # state/x-watch.check.sh - byte-static identity shim; the watcher validates
989
+ # its bytes and invokes bin/fm-x-poll.sh directly
990
+ # config/x-mode.env - exports FM_CHECK_INTERVAL=30, sourced by the watcher
991
+ # arm so only an X instance polls at the 30s cadence
992
+ # On opt-out (no token, or empty) it removes any such artifacts so the instance
993
+ # reverts to the default 300s no-poll behavior. Absent a token AND with no leftover
994
+ # artifacts it is a complete no-op (nothing written, nothing printed), so a non-X
995
+ # user sees zero change. Prints one confirmation line on opt-in, and one on opt-out
996
+ # only when it actually removed artifacts. It never touches the watcher itself;
997
+ # applying a cadence transition to a running watcher is the caller's job via
998
+ # the emitted harness-aware supervision repair instruction.
999
+ x_mode_setup() {
1000
+ local env_file token shim cadence shim_body cadence_body tool missing shim_home
1001
+ env_file="$FM_HOME/.env"
1002
+ shim="$STATE/x-watch.check.sh"
1003
+ cadence="$CONFIG/x-mode.env"
1004
+
1005
+ token=
1006
+ [ -f "$env_file" ] && token=$(fmx_env_get FMX_PAIRING_TOKEN "$env_file")
1007
+
1008
+ x_mode_remove_artifacts() {
1009
+ local failed=0
1010
+ x_mode_remove_artifact "$shim" || failed=1
1011
+ x_mode_remove_artifact "$cadence" || failed=1
1012
+ [ "$failed" -eq 0 ]
1013
+ }
1014
+
1015
+ x_mode_supervision_repair() {
1016
+ local out
1017
+ out=$("$SCRIPT_DIR/fm-supervision-instructions.sh" --repair-line 2>/dev/null) \
1018
+ || out='repair missing watcher supervision according to the session-start operating block.'
1019
+ printf '%s\n' "$out"
1020
+ }
1021
+
1022
+ if [ -z "$token" ]; then
1023
+ # Opt-out (or never opted in): drop any X artifacts; stay silent unless we
1024
+ # actually removed something.
1025
+ if x_mode_artifact_present "$shim" || x_mode_artifact_present "$cadence"; then
1026
+ if x_mode_remove_artifacts; then
1027
+ echo "FMX: X mode off - removed relay poll shim and 30s cadence; default cadence applies on the next supervision cycle; $(x_mode_supervision_repair)"
1028
+ else
1029
+ echo "FMX: X mode off - failed to remove relay poll shim or 30s cadence"
1030
+ fi
1031
+ fi
1032
+ return 0
1033
+ fi
1034
+
1035
+ missing=0
1036
+ for tool in curl jq; do
1037
+ if ! command -v "$tool" >/dev/null 2>&1; then
1038
+ echo "MISSING: $tool (install: $(install_cmd "$tool"))"
1039
+ missing=1
1040
+ fi
1041
+ done
1042
+ if [ "$missing" -ne 0 ]; then
1043
+ if x_mode_artifact_present "$shim" || x_mode_artifact_present "$cadence"; then
1044
+ if x_mode_remove_artifacts; then
1045
+ echo "FMX: X mode off - missing relay poll dependencies; install them and rerun bootstrap"
1046
+ else
1047
+ echo "FMX: X mode off - failed to remove relay poll shim or 30s cadence after missing relay poll dependencies"
1048
+ fi
1049
+ fi
1050
+ return 0
1051
+ fi
1052
+
1053
+ fmx_arm_failed() {
1054
+ if x_mode_remove_artifacts; then
1055
+ echo "FMX: X mode off - failed to arm relay poll shim or 30s cadence"
1056
+ else
1057
+ echo "FMX: X mode off - failed to arm relay poll shim or 30s cadence; stale artifacts remain"
1058
+ fi
1059
+ }
1060
+
1061
+ mkdir -p "$STATE" "$CONFIG" 2>/dev/null || { fmx_arm_failed; return 0; }
1062
+
1063
+ case "$FM_HOME" in
1064
+ /*) shim_home=$FM_HOME ;;
1065
+ *)
1066
+ shim_home=$(CDPATH='' cd -- "$FM_HOME" 2>/dev/null && pwd -P) \
1067
+ || { fmx_arm_failed; return 0; }
1068
+ ;;
1069
+ esac
1070
+ shim_body=$(fmx_poll_shim_content "$shim_home" "$FM_ROOT")
1071
+ x_mode_write_if_changed "$shim" "$shim_body" 700 || { fmx_arm_failed; return 0; }
1072
+ fmx_poll_shim_valid "$shim" "$shim_home" "$FM_ROOT" \
1073
+ || { fmx_arm_failed; return 0; }
1074
+
1075
+ cadence_body=$(cat <<'EOF'
1076
+ # Auto-generated by fm-bootstrap.sh - X mode watcher cadence.
1077
+ # Source this before the active harness protocol starts a watcher process so
1078
+ # fm-watch.sh polls the X check every 30s. Non-X instances have no such file and
1079
+ # keep the default 300s cadence.
1080
+ export FM_CHECK_INTERVAL=30
1081
+ EOF
1082
+ )
1083
+ x_mode_write_if_changed "$cadence" "$cadence_body" 600 || { fmx_arm_failed; return 0; }
1084
+
1085
+ echo "FMX: X mode on - relay poll armed via state/x-watch.check.sh; 30s watcher cadence in config/x-mode.env"
1086
+ }
1087
+
1088
+ crew_dispatch_validate() {
1089
+ local file err
1090
+ file="$CONFIG/crew-dispatch.json"
1091
+ [ -f "$file" ] || return 0
1092
+ if ! command -v jq >/dev/null 2>&1; then
1093
+ echo "MISSING: jq (install: $(install_cmd jq))"
1094
+ return 0
1095
+ fi
1096
+ if ! jq -e . "$file" >/dev/null 2>&1; then
1097
+ echo "CREW_DISPATCH: invalid config/crew-dispatch.json - malformed JSON"
1098
+ return 0
1099
+ fi
1100
+ err=$(jq -r '
1101
+ def verified($h): ["claude","codex","opencode","pi","pi-signed","grok","kimi","cursor","muse"] | index($h);
1102
+ def effort_ok($h; $e):
1103
+ if $e == null then true
1104
+ elif ($e | type) != "string" then false
1105
+ elif $h == "claude" then (["low","medium","high","xhigh","max"] | index($e))
1106
+ elif $h == "codex" then (["low","medium","high","xhigh"] | index($e))
1107
+ elif $h == "grok" then (["low","medium","high"] | index($e))
1108
+ elif $h == "pi" or $h == "pi-signed" then (["low","medium","high","xhigh","max"] | index($e))
1109
+ elif $h == "muse" then (["low","medium","high","xhigh","max"] | index($e))
1110
+ elif $h == "opencode" or $h == "kimi" or $h == "cursor" then false
1111
+ else true
1112
+ end;
1113
+ def profiles($value):
1114
+ if ($value | type) == "array" then $value
1115
+ elif ($value | type) == "object" then [$value]
1116
+ else []
1117
+ end;
1118
+ def configured_profiles:
1119
+ ([(.rules // [])[]? | profiles(.use?)[]?]
1120
+ + (if has("default") then [profiles(.default)[]?] else [] end));
1121
+ def malformed_optional_fields($items):
1122
+ ($items | any(has("model") and (((.model | type) != "string") or (.model | length) == 0)))
1123
+ or ($items | any(has("effort") and (((.effort | type) != "string") or (.effort | length) == 0)));
1124
+ def bad_efforts:
1125
+ configured_profiles
1126
+ | map({h: .harness, e: .effort})
1127
+ | map(select(.e != null))
1128
+ | map(select((.h | type) == "string" and verified(.h)))
1129
+ | map(select(. as $p | effort_ok($p.h; $p.e) | not))
1130
+ | map("\(.h):\(.e)")
1131
+ | unique;
1132
+ if type != "object" then "top-level value must be an object"
1133
+ elif has("rules") and (.rules | type) != "array" then "rules must be an array"
1134
+ elif [(.rules // [])[]? | select(type != "object")] | length > 0 then "each rule must be an object"
1135
+ elif [(.rules // [])[]? | select((.when? | type) != "string" or (.when | length) == 0)] | length > 0 then "each rule needs non-empty when"
1136
+ elif [(.rules // [])[]? | select((.use? | type) != "object" and (.use? | type) != "array")] | length > 0 then "each rule needs use"
1137
+ elif [(.rules // [])[]? | select((.use? | type) == "array" and (.use | length) == 0)] | length > 0 then "each rule needs at least one use profile"
1138
+ elif [(.rules // [])[]? | profiles(.use?)[]? | select(type != "object")] | length > 0 then "each use profile must be an object"
1139
+ elif [(.rules // [])[]? | profiles(.use?)[]? | select((.harness? | type) != "string" or (.harness | length) == 0)] | length > 0 then "each use profile needs harness"
1140
+ elif malformed_optional_fields([(.rules // [])[]? | profiles(.use?)[]?]) then "use profile model and effort must be non-empty strings when present"
1141
+ elif [(.rules // [])[]? | select(has("select") and ((.select? | type) != "string" or (.select | length) == 0))] | length > 0 then "select must be a non-empty string"
1142
+ elif [(.rules // [])[]? | .select? // empty | select(. != "quota-balanced")] | length > 0 then
1143
+ "unknown select: " + ([ (.rules // [])[]? | .select? // empty | select(. != "quota-balanced") ] | unique | join(", "))
1144
+ elif has("default") and ((.default | type) != "object" and (.default | type) != "array") then "default must be a profile object or non-empty profile array"
1145
+ elif has("default") and ((.default | type) == "array" and (.default | length) == 0) then "default needs at least one profile"
1146
+ elif has("default") and ([profiles(.default)[]? | select(type != "object")] | length) > 0 then "each default profile must be an object"
1147
+ elif has("default") and ([profiles(.default)[]? | select((.harness? | type) != "string" or (.harness | length) == 0)] | length) > 0 then "each default profile needs harness"
1148
+ elif has("default") and malformed_optional_fields([profiles(.default)[]?]) then "default profile model and effort must be non-empty strings when present"
1149
+ else
1150
+ (configured_profiles
1151
+ | map(.harness)
1152
+ | map(select(. != null))
1153
+ | map(select(. as $h | verified($h) | not))
1154
+ | unique) as $bad_harnesses
1155
+ | if ($bad_harnesses | length) > 0 then "unverified harness: " + ($bad_harnesses | join(", "))
1156
+ elif (bad_efforts | length) > 0 then "invalid effort: " + (bad_efforts | join(", "))
1157
+ else empty
1158
+ end
1159
+ end
1160
+ ' "$file" 2>/dev/null || true)
1161
+ if [ -n "$err" ]; then
1162
+ echo "CREW_DISPATCH: invalid config/crew-dispatch.json - $err"
1163
+ return 0
1164
+ fi
1165
+ if [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" = 1 ]; then
1166
+ jq -r '
1167
+ def profile($p):
1168
+ ($p.harness | tostring)
1169
+ + (if ($p.model? != null) then "/" + ($p.model | tostring)
1170
+ elif ($p.effort? != null) then "/default"
1171
+ else "" end)
1172
+ + (if ($p.effort? != null) then "/" + ($p.effort | tostring) else "" end);
1173
+ def profile_set($value; $selector):
1174
+ if ($value | type) == "array" then
1175
+ (($selector // "quota-balanced") + "[" + ([$value[] | profile(.)] | join(", ")) + "]")
1176
+ else profile($value)
1177
+ end;
1178
+ (["BOOTSTRAP_INFO: crew dispatch active config/crew-dispatch.json"]
1179
+ + [(.rules // [])[]? | "BOOTSTRAP_INFO: crew dispatch rule: " + (.when | tostring) + " -> " + profile_set(.use; .select?)]
1180
+ + (if has("default") then ["BOOTSTRAP_INFO: crew dispatch default: " + profile_set(.default; null)] else [] end))
1181
+ | .[]
1182
+ ' "$file"
1183
+ fi
1184
+ }
1185
+
1186
+ # Same-home record reconciliation. Every ordinary dispatch and completion now
1187
+ # moves the backlog row inside the script that moves the task's record
1188
+ # (bin/fm-backlog-transition-lib.sh), so remaining recovery cases include a
1189
+ # process killed mid-transition and drift this home was already carrying. Heal
1190
+ # this home's OWN books on its own
1191
+ # restart rather than waiting for a parent's cross-home nudge; the fleet
1192
+ # snapshot's classifier and bin/fm-secondmate-reconcile.sh's nudge stay as
1193
+ # backstops for what this cannot see. Never reads or writes another home.
1194
+ backlog_record_reconcile() {
1195
+ local marker meta meta_lock id row label has_record=0 gate_status
1196
+ # A fresh home with no state directory has no physical task records to pair.
1197
+ # Keep bootstrap diagnostics working without creating state just for a no-op.
1198
+ [ -e "$STATE" ] || [ -L "$STATE" ] || return 0
1199
+ if ! fm_backlog_directory_present "$STATE" "state directory"; then
1200
+ echo "error: backlog reconciliation refused: $FM_BACKLOG_TRANSITION_ERROR" >&2
1201
+ return 2
1202
+ fi
1203
+ if fm_backlog_transition_applies "$CONFIG" "$DATA" "$BOOTSTRAP_BACKLOG_GATE_KIND"; then
1204
+ :
1205
+ else
1206
+ gate_status=$?
1207
+ if [ "$gate_status" -eq 2 ]; then
1208
+ echo "error: backlog reconciliation cannot access configured data directory $DATA ($FM_BACKLOG_TRANSITION_ERROR)" >&2
1209
+ return 2
1210
+ fi
1211
+ return 0
1212
+ fi
1213
+ # Keep the wake/lock library's source-time state-directory creation inside
1214
+ # this mutating sweep, so FM_BOOTSTRAP_DETECT_ONLY remains read-only.
1215
+ # shellcheck source=bin/fm-wake-lib.sh disable=SC1091
1216
+ . "$SCRIPT_DIR/fm-wake-lib.sh"
1217
+
1218
+ # Finish any close an interrupted cleanup recorded but never landed.
1219
+ for marker in "$STATE"/*.backlog-close; do
1220
+ [ -e "$marker" ] || [ -L "$marker" ] || continue
1221
+ if ! fm_backlog_record_present "$marker" "pending-close record" "$STATE"; then
1222
+ echo "BACKLOG_RECONCILE: unsafe pending close refused: $FM_BACKLOG_TRANSITION_ERROR"
1223
+ return 2
1224
+ fi
1225
+ label=$(basename "$marker" .backlog-close)
1226
+ meta_lock=$(fm_meta_lock_path "$STATE/$label.meta") || continue
1227
+ fm_lock_try_acquire "$meta_lock" || continue
1228
+ if fm_backlog_close_marker_replay "$STATE" "$marker" "$DATA"; then
1229
+ case "$FM_BACKLOG_CLOSE_REPLAY_RESULT" in
1230
+ closed)
1231
+ echo "BOOTSTRAP_INFO: closed the backlog item for $label that an interrupted cleanup left open"
1232
+ ;;
1233
+ closed_incomplete)
1234
+ echo "BOOTSTRAP_INFO: closed the backlog item for $label after interrupted cleanup; its endpoint or local copy may remain and should be reconciled"
1235
+ ;;
1236
+ esac
1237
+ else
1238
+ echo "BACKLOG_RECONCILE: $label: recorded backlog close could not be replayed: $FM_BACKLOG_TRANSITION_ERROR"
1239
+ fi
1240
+ fm_lock_release "$meta_lock"
1241
+ done
1242
+
1243
+ # A home that owns no records has nothing to pair, so it never pays for a
1244
+ # backlog read. A pending close remains authoritative even when replay failed:
1245
+ # the record sweep below must not start that item while its marker survives.
1246
+ for meta in "$STATE"/*.meta; do
1247
+ [ -e "$meta" ] || [ -L "$meta" ] || continue
1248
+ if ! fm_backlog_record_present "$meta" "task record" "$STATE"; then
1249
+ echo "BACKLOG_RECONCILE: unsafe worker record refused: $FM_BACKLOG_TRANSITION_ERROR"
1250
+ return 2
1251
+ fi
1252
+ has_record=1
1253
+ break
1254
+ done
1255
+ [ "$has_record" = 1 ] || return 0
1256
+ for meta in "$STATE"/*.meta; do
1257
+ [ -e "$meta" ] || [ -L "$meta" ] || continue
1258
+ if ! fm_backlog_record_present "$meta" "task record" "$STATE"; then
1259
+ echo "BACKLOG_RECONCILE: unsafe worker record refused: $FM_BACKLOG_TRANSITION_ERROR"
1260
+ return 2
1261
+ fi
1262
+ id=$(basename "$meta" .meta)
1263
+ meta_lock=$(fm_meta_lock_path "$meta") || continue
1264
+ fm_lock_try_acquire "$meta_lock" || continue
1265
+ if [ -e "$STATE/$id.backlog-close" ] || [ -L "$STATE/$id.backlog-close" ]; then
1266
+ fm_lock_release "$meta_lock"
1267
+ continue
1268
+ fi
1269
+ if ! fm_backlog_record_present "$meta" "task record" "$STATE"; then
1270
+ echo "BACKLOG_RECONCILE: $id: post-lock worker record check refused: $FM_BACKLOG_TRANSITION_ERROR"
1271
+ fm_lock_release "$meta_lock"
1272
+ return 2
1273
+ fi
1274
+ if [ "$(fm_meta_get "$meta" kind)" != secondmate ] \
1275
+ && [ "$(fm_meta_get "$meta" cleanup_recovery)" != orca ]; then
1276
+ row=
1277
+ if fm_backlog_row_probe "$DATA" "$id"; then
1278
+ row=$FM_BACKLOG_ROW_STATE
1279
+ elif [ "$FM_BACKLOG_ROW_RESULT" != not_found ]; then
1280
+ echo "BACKLOG_RECONCILE: $id: worker record exists but its backlog item could not be read: $FM_BACKLOG_ROW_ERROR"
1281
+ fi
1282
+ # Heal only the unambiguous case: a queued row for a record this home
1283
+ # already owns. A held row is the captain's to move, and a closed row is a
1284
+ # contradiction this sweep must not resolve by resurrecting the item.
1285
+ if [ "$row" = "queued no no" ]; then
1286
+ if fm_backlog_start "$DATA" "$id"; then
1287
+ echo "BOOTSTRAP_INFO: marked $id in flight to match the worker this home already owns"
1288
+ else
1289
+ echo "BACKLOG_RECONCILE: $id: worker record exists but its backlog item could not be moved to In flight: $FM_BACKLOG_TRANSITION_ERROR"
1290
+ fi
1291
+ fi
1292
+ fi
1293
+ fm_lock_release "$meta_lock"
1294
+ done
1295
+ }
1296
+
1297
+ startup_memory_budget_setup() {
1298
+ # Primary bootstrap owns default publication. A secondmate is deliberately
1299
+ # passive here because its setting must converge from the primary through the
1300
+ # inherited-local-material contract rather than becoming a local authority.
1301
+ if [ -e "$FM_HOME/.fm-secondmate-home" ] || [ -L "$FM_HOME/.fm-secondmate-home" ]; then
1302
+ return 0
1303
+ fi
1304
+ if ! fm_startup_memory_budget_materialize "$CONFIG"; then
1305
+ echo "STARTUP_MEMORY_BUDGET: invalid config/$FM_STARTUP_MEMORY_BUDGET_FILE - $FM_STARTUP_MEMORY_BUDGET_ERROR"
1306
+ fi
1307
+ }
1308
+
1309
+ if [ "${1:-}" = "install" ]; then
1310
+ shift
1311
+ [ $# -gt 0 ] || { echo "usage: fm-bootstrap.sh install <tool>..." >&2; exit 1; }
1312
+ for t in "$@"; do
1313
+ if ! cmd=$(install_cmd "$t"); then
1314
+ instructions=$(manual_install_url "$t") || { echo "error: unknown tool $t" >&2; exit 1; }
1315
+ echo "error: $t requires manual installation (instructions: $instructions)" >&2
1316
+ exit 1
1317
+ fi
1318
+ cmd=${cmd%% #*}
1319
+ echo "installing $t: $cmd"
1320
+ eval "$cmd"
1321
+ done
1322
+ exit 0
1323
+ fi
1324
+
1325
+ # This is the first mutating sweep at a locked session boundary. Detect-only
1326
+ # sessions never touch state, and the deferred network pass never repeats it:
1327
+ # the local pass that ran first already closed that window.
1328
+ if [ "${FM_BOOTSTRAP_DETECT_ONLY:-0}" != 1 ] && local_phase; then
1329
+ BOOTSTRAP_BACKLOG_GATE_KIND=secondmate
1330
+ if [ -e "$STATE" ] || [ -L "$STATE" ]; then
1331
+ if ! fm_backlog_directory_present "$STATE" "state directory"; then
1332
+ echo "error: bootstrap cannot reconcile task state ($FM_BACKLOG_TRANSITION_ERROR)" >&2
1333
+ exit 1
1334
+ fi
1335
+ for BOOTSTRAP_BACKLOG_MARKER in "$STATE"/*.backlog-close; do
1336
+ [ -e "$BOOTSTRAP_BACKLOG_MARKER" ] || [ -L "$BOOTSTRAP_BACKLOG_MARKER" ] || continue
1337
+ if ! fm_backlog_record_present "$BOOTSTRAP_BACKLOG_MARKER" "pending-close record" "$STATE"; then
1338
+ echo "error: bootstrap refused unsafe pending close ($FM_BACKLOG_TRANSITION_ERROR)" >&2
1339
+ exit 1
1340
+ fi
1341
+ BOOTSTRAP_BACKLOG_GATE_KIND=ship
1342
+ break
1343
+ done
1344
+ if [ "$BOOTSTRAP_BACKLOG_GATE_KIND" = secondmate ]; then
1345
+ for BOOTSTRAP_BACKLOG_META in "$STATE"/*.meta; do
1346
+ [ -e "$BOOTSTRAP_BACKLOG_META" ] || [ -L "$BOOTSTRAP_BACKLOG_META" ] || continue
1347
+ if ! fm_backlog_record_present "$BOOTSTRAP_BACKLOG_META" "task record" "$STATE"; then
1348
+ echo "error: bootstrap refused unsafe worker record ($FM_BACKLOG_TRANSITION_ERROR)" >&2
1349
+ exit 1
1350
+ fi
1351
+ if [ "$(fm_meta_get "$BOOTSTRAP_BACKLOG_META" kind)" != secondmate ] \
1352
+ && [ "$(fm_meta_get "$BOOTSTRAP_BACKLOG_META" cleanup_recovery)" != orca ]; then
1353
+ BOOTSTRAP_BACKLOG_GATE_KIND=ship
1354
+ break
1355
+ fi
1356
+ done
1357
+ fi
1358
+ fi
1359
+ if fm_backlog_transition_applies "$CONFIG" "$DATA" "$BOOTSTRAP_BACKLOG_GATE_KIND"; then
1360
+ :
1361
+ else
1362
+ BOOTSTRAP_BACKLOG_GATE_STATUS=$?
1363
+ if [ "$BOOTSTRAP_BACKLOG_GATE_STATUS" -eq 2 ]; then
1364
+ echo "error: bootstrap cannot access configured backlog data directory $DATA ($FM_BACKLOG_TRANSITION_ERROR)" >&2
1365
+ exit 1
1366
+ fi
1367
+ fi
1368
+ startup_memory_budget_setup
1369
+ if backlog_record_reconcile; then
1370
+ :
1371
+ else
1372
+ BOOTSTRAP_BACKLOG_RECONCILE_STATUS=$?
1373
+ if [ "$BOOTSTRAP_BACKLOG_RECONCILE_STATUS" -eq 2 ]; then
1374
+ exit 1
1375
+ fi
1376
+ fi
1377
+ fi
1378
+
1379
+ # Local detection: presence, version floors, and configuration. Nothing here
1380
+ # leaves this machine, so it stays on the session-start critical path.
1381
+ detect_local_tools() {
1382
+ if [ "$BACKEND_VALID" -eq 0 ]; then
1383
+ echo "BACKEND_INVALID: $BACKEND (known: $FM_BACKEND_KNOWN)"
1384
+ fi
1385
+ for t in $BACKEND_TOOLS; do
1386
+ fm_backend_required_tool_available "$BACKEND" "$t" \
1387
+ || missing_tool_diagnostic "$t"
1388
+ done
1389
+ for t in $COMMON_TOOLS; do
1390
+ command -v "$t" >/dev/null || missing_tool_diagnostic "$t"
1391
+ done
1392
+ # The treehouse lease-support upgrade check is only relevant when the resolved
1393
+ # backend actually requires treehouse (every backend except orca, which owns its
1394
+ # own worktrees); an orca home must not be told to upgrade a provider it never uses.
1395
+ if fm_backend_list_contains "$TOOLS" treehouse \
1396
+ && command -v treehouse >/dev/null 2>&1 && ! treehouse_supports_lease; then
1397
+ echo "MISSING: treehouse (install: $(install_cmd treehouse))"
1398
+ fi
1399
+ if command -v no-mistakes >/dev/null 2>&1 && ! tool_version_at_least no-mistakes "$NO_MISTAKES_MIN"; then
1400
+ echo "MISSING: no-mistakes (install: $(install_cmd no-mistakes))"
1401
+ fi
1402
+ if command -v gh-axi >/dev/null 2>&1 && ! tool_version_at_least gh-axi "$GH_AXI_MIN"; then
1403
+ echo "MISSING: gh-axi (install: $(install_cmd gh-axi))"
1404
+ fi
1405
+ if command -v lavish-axi >/dev/null 2>&1 && ! tool_version_at_least lavish-axi "$LAVISH_AXI_MIN"; then
1406
+ echo "MISSING: lavish-axi (install: $(install_cmd lavish-axi))"
1407
+ fi
1408
+ if command -v quota-axi >/dev/null 2>&1 && ! fm_quota_axi_compatible; then
1409
+ echo "MISSING: quota-axi (install: $(install_cmd quota-axi))"
1410
+ fi
1411
+ if command -v tasks-axi >/dev/null 2>&1 && ! fm_tasks_axi_compatible; then
1412
+ echo "MISSING: tasks-axi (install: $(install_cmd tasks-axi))"
1413
+ fi
1414
+ }
1415
+
1416
+ detect_local_config() {
1417
+ # Worktree-tangle check: the firstmate primary checkout (FM_ROOT) must sit on its
1418
+ # default branch, not a feature branch (see fm-tangle-lib.sh). Scoped to the
1419
+ # primary only; detached-HEAD worktrees and secondmate homes never trip it.
1420
+ tangle_branch=$(fm_primary_tangle_branch "$FM_ROOT" 2>/dev/null || true)
1421
+ if [ -n "$tangle_branch" ]; then
1422
+ tangle_default=$(fm_default_branch "$FM_ROOT" 2>/dev/null || echo main)
1423
+ if [ "${FM_BOOTSTRAP_DETECT_ONLY:-0}" = 1 ] && [ "${FM_BOOTSTRAP_LOCKED:-0}" != 1 ]; then
1424
+ echo "TANGLE: primary checkout on feature branch '$tangle_branch' (expected '$tangle_default'); the work is safe on that ref - read-only session must leave restore work to the session holding the fleet lock"
1425
+ else
1426
+ echo "TANGLE: primary checkout on feature branch '$tangle_branch' (expected '$tangle_default'); the work is safe on that ref - restore the primary with: git -C $FM_ROOT checkout $tangle_default, then re-validate the branch in a proper worktree"
1427
+ fi
1428
+ fi
1429
+ crew=
1430
+ [ -f "$CONFIG/crew-harness" ] && crew=$(tr -d '[:space:]' < "$CONFIG/crew-harness" || true)
1431
+ if [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" = 1 ] && [ -n "$crew" ] && [ "$crew" != "default" ]; then
1432
+ echo "BOOTSTRAP_INFO: crew harness override active: $crew"
1433
+ fi
1434
+ # A configured cursor crew harness needs a cursor executable present, and
1435
+ # cursor ships under EITHER installed name. Resolution runs through the
1436
+ # verified owner rather than a bare `command -v`, so a home that merely has
1437
+ # some unrelated executable named `agent` on PATH is still reported missing
1438
+ # instead of failing at the first spawn.
1439
+ if [ "$crew" = cursor ] && ! fm_cursor_resolve_binary >/dev/null 2>&1; then
1440
+ echo "MISSING_MANUAL: cursor-agent (instructions: $(manual_install_url cursor-agent))"
1441
+ fi
1442
+ crew_dispatch_validate
1443
+ if [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" = 1 ] \
1444
+ && ! fm_backlog_backend_manual "$CONFIG" && fm_tasks_axi_compatible; then
1445
+ echo "BOOTSTRAP_INFO: tasks-axi available"
1446
+ fi
1447
+ detect_home_summary_publication
1448
+ }
1449
+
1450
+ # This home's ledger publication is deliberately best-effort: every lifecycle
1451
+ # trigger calls it with --best-effort so a failure can never change the result
1452
+ # of a session start, a spawn, a teardown, or a watcher poll. That is correct,
1453
+ # and it also means a home that never manages to publish says nothing at all -
1454
+ # the failures land only in the bounded home-local record nobody reads.
1455
+ #
1456
+ # So read that same record here, where a session start already looks, and say so
1457
+ # once when the evidence is a pattern rather than a blip: the ledger has not
1458
+ # been (re)published, and at least FM_HOME_SUMMARY_FAILURE_REPORT attempts have
1459
+ # failed since whenever it last was. No new record, no new state, no retry
1460
+ # policy - just the existing evidence, surfaced.
1461
+ detect_home_summary_publication() {
1462
+ local log="$STATE/.home-summary-refresh.log" ledger="$STATE/home-summary.json"
1463
+ local since='' counted failures last threshold
1464
+ threshold=${FM_HOME_SUMMARY_FAILURE_REPORT:-2}
1465
+ case "$threshold" in ''|*[!0-9]*|0) threshold=2 ;; esac
1466
+ [ -f "$log" ] && [ -r "$log" ] && [ ! -L "$log" ] || return 0
1467
+ if [ -f "$ledger" ] && [ -r "$ledger" ] && [ ! -L "$ledger" ]; then
1468
+ since=$(LC_ALL=C sed -n 's/.*"generated"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
1469
+ "$ledger" 2>/dev/null | head -1)
1470
+ fi
1471
+ # Publication and failure stamps have whole-second precision, so failures in
1472
+ # the publication's own second remain quiet until a later failure advances
1473
+ # the record. That bounded delay avoids a precision dependency in bootstrap.
1474
+ counted=$(LC_ALL=C awk -v since="$since" '
1475
+ match($0, /^\[[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]Z\]/) {
1476
+ stamp = substr($0, 2, RLENGTH - 2)
1477
+ if (since == "" || stamp > since) {
1478
+ n += 1
1479
+ last = substr($0, RLENGTH + 2)
1480
+ } else if (stamp == since) {
1481
+ same_second += 1
1482
+ }
1483
+ }
1484
+ END {
1485
+ if (since != "" && n > 0) n += same_second
1486
+ printf "%d\t%s", n + 0, last
1487
+ }' "$log" 2>/dev/null) || return 0
1488
+ failures=${counted%%$'\t'*}
1489
+ last=${counted#*$'\t'}
1490
+ case "$failures" in ''|*[!0-9]*) return 0 ;; esac
1491
+ [ "$failures" -ge "$threshold" ] || return 0
1492
+ last=$(printf '%s' "$last" | cut -c1-200)
1493
+ if [ -z "$since" ]; then
1494
+ echo "HOME_SUMMARY: this home has never published state/home-summary.json; $failures failed attempt(s) recorded in state/.home-summary-refresh.log, last: $last"
1495
+ else
1496
+ echo "HOME_SUMMARY: state/home-summary.json has not been republished since $since; $failures failed attempt(s) recorded in state/.home-summary-refresh.log, last: $last"
1497
+ fi
1498
+ }
1499
+
1500
+ # The order below is the order the diagnostics have always printed in, so a
1501
+ # `skip` run is the same output with the network lines removed rather than a
1502
+ # reshuffle. `gh auth status` sits between the two local blocks because that is
1503
+ # where it has always been.
1504
+ # Each network owner below is bracketed by an elapsed-time record, so a deferred
1505
+ # stage that ran long can be attributed to the phase that spent the time.
1506
+ # fm-timing-lib.sh discards the record unless the caller asked for timings, and
1507
+ # every sweep is still called directly. Per-secondmate remote probes run
1508
+ # concurrently; clone refresh overlaps them. Diagnostic lines are replayed in
1509
+ # original order so attribution is unchanged.
1510
+ # The stamp variable is named for the library rather than `start` on purpose:
1511
+ # fleet_sync and others assign plain names like `start` without `local`, and
1512
+ # bash's dynamic scoping would let them overwrite a stamp held by a caller.
1513
+ local_phase && detect_local_tools
1514
+ if network_phase; then
1515
+ __fm_timing_stamp=$(fm_timing_now_ms)
1516
+ gh auth status >/dev/null 2>&1 || echo "NEEDS_GH_AUTH"
1517
+ fm_timing_record phase gh-auth "$__fm_timing_stamp"
1518
+ fi
1519
+ local_phase && detect_local_config
1520
+
1521
+ if [ "${FM_BOOTSTRAP_DETECT_ONLY:-0}" != 1 ]; then
1522
+ # secondmate_sync consumes SECONDMATE_RESPAWNED_IDS from the liveness sweep, so
1523
+ # those two always run together in the same phase. Clone refresh does not
1524
+ # depend on them, so it starts in the background and overlaps their wall clock.
1525
+ fleet_sync_pid=
1526
+ fleet_sync_out=
1527
+ if network_phase && network_sweep_authorized 'project clone refresh'; then
1528
+ fleet_sync_out=$(mktemp "${TMPDIR:-/tmp}/fm-bootstrap-fleet.XXXXXX") || fleet_sync_out=
1529
+ if [ -n "$fleet_sync_out" ]; then
1530
+ (
1531
+ __fm_timing_stamp=$(fm_timing_now_ms)
1532
+ fleet_sync
1533
+ fm_timing_record phase fleet-sync "$__fm_timing_stamp"
1534
+ ) >"$fleet_sync_out" 2>&1 &
1535
+ fleet_sync_pid=$!
1536
+ else
1537
+ __fm_timing_stamp=$(fm_timing_now_ms)
1538
+ fleet_sync
1539
+ fm_timing_record phase fleet-sync "$__fm_timing_stamp"
1540
+ fi
1541
+ fi
1542
+ if network_phase; then
1543
+ if network_sweep_authorized 'dead-secondmate relaunch'; then
1544
+ __fm_timing_stamp=$(fm_timing_now_ms)
1545
+ secondmate_liveness_sweep
1546
+ fm_timing_record phase secondmate-liveness "$__fm_timing_stamp"
1547
+ fi
1548
+ if network_sweep_authorized 'secondmate convergence'; then
1549
+ __fm_timing_stamp=$(fm_timing_now_ms)
1550
+ secondmate_sync
1551
+ fm_timing_record phase secondmate-sync "$__fm_timing_stamp"
1552
+ fi
1553
+ if network_sweep_authorized 'pending handoff delivery'; then
1554
+ __fm_timing_stamp=$(fm_timing_now_ms)
1555
+ secondmate_handoff_resume
1556
+ fm_timing_record phase handoff-delivery "$__fm_timing_stamp"
1557
+ fi
1558
+ fi
1559
+ # x_mode_setup writes local Relay artifacts only and never leaves the machine.
1560
+ local_phase && x_mode_setup
1561
+ if [ -n "$fleet_sync_pid" ]; then
1562
+ wait "$fleet_sync_pid" || true
1563
+ cat "$fleet_sync_out"
1564
+ rm -f "$fleet_sync_out"
1565
+ fi
1566
+ fi
1567
+ local_phase && secondmate_handoff_detect
1568
+ exit 0