@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,732 @@
1
+ #!/usr/bin/env bash
2
+ # tests/fm-secondmate-reconcile.test.sh - the cooldown-limited reconcile ask.
3
+ #
4
+ # A backlog-vs-metadata inventory mismatch inside a secondmate home no longer
5
+ # blanks that home in the fleet snapshot, so the parent asks the home that owns
6
+ # those books to fix them. This suite pins that ask: it lands as a real durable
7
+ # steering record, a home is asked at most once per cooldown window however
8
+ # often the snapshot runs, a mismatch still sitting there after the window
9
+ # earns one gentle re-nudge, and the parent never touches the mate's own files.
10
+ set -u
11
+
12
+ # shellcheck source=tests/secondmate-helpers.sh disable=SC1091
13
+ . "$(dirname "${BASH_SOURCE[0]}")/secondmate-helpers.sh"
14
+
15
+ RECONCILE="$ROOT/bin/fm-secondmate-reconcile.sh"
16
+ TMP_ROOT=$(fm_test_tmproot fm-secondmate-reconcile)
17
+
18
+ command -v jq >/dev/null 2>&1 || { echo "skip: jq not found"; exit 0; }
19
+
20
+ export FM_SEND_SETTLE=0 FM_SEND_SLEEP=0 FM_SEND_RETRIES=1
21
+
22
+ # A main home with one registered, live, local secondmate reachable through the
23
+ # fake tmux backend, so fm-send's real inbox plane is exercised end to end.
24
+ make_main_home() { # <name> <mate-id>
25
+ local home="$TMP_ROOT/$1" mate="$TMP_ROOT/$1-mate" id=$2 abs fakebin
26
+ mkdir -p "$home/data" "$home/state"
27
+ seed_secondmate_home_marker "$mate" "$id"
28
+ abs=$(cd "$mate" && pwd -P)
29
+ printf -- '- %s - fixture domain (home: %s; scope: fixture; projects: sample; added 2026-08-26)\n' \
30
+ "$id" "$abs" > "$home/data/secondmates.md"
31
+ cat > "$home/state/$id.meta" <<META
32
+ window=firstmate:fm-$id
33
+ kind=secondmate
34
+ harness=claude
35
+ backend=tmux
36
+ spawn_gen=spawn-$id
37
+ home=$abs
38
+ worktree=$abs
39
+ META
40
+ fakebin=$(make_fake_tmux "$TMP_ROOT/$1-fake")
41
+ printf '%s\n' "$home" "$mate" "$fakebin"
42
+ }
43
+
44
+ # A minimal but schema-true fleet snapshot carrying one structured-home record.
45
+ write_snapshot() { # <path> <mate-id> <invalidity-json> [state]
46
+ jq -n --arg id "$2" --argjson inv "$3" --arg state "${4:-captain_decision}" '{
47
+ schema:"fm-fleet-snapshot.v1", generated:"2026-08-26T00:00:00Z",
48
+ secondmate_current:{records:[{
49
+ id:$id, home:("/tmp/" + $id), spawn_gen:("spawn-" + $id),
50
+ current:{state:$state, reason:null},
51
+ invalidity:$inv, reconcile_inventory:($inv // {kind:null,ids:[]}),
52
+ provenance:{selected:"structured-home", trust:"partial-structured"}}]}}' > "$1"
53
+ }
54
+
55
+ # The same shape, but for a persistent REMOTE secondmate: no spawn_gen (its
56
+ # parent metadata never carries one), host instead.
57
+ write_remote_snapshot() { # <path> <mate-id> <host> <invalidity-json> [state]
58
+ jq -n --arg id "$2" --arg host "$3" --argjson inv "$4" --arg state "${5:-captain_decision}" '{
59
+ schema:"fm-fleet-snapshot.v1", generated:"2026-08-26T00:00:00Z",
60
+ secondmate_current:{records:[{
61
+ id:$id, home:("/tmp/" + $id), host:$host, spawn_gen:null,
62
+ current:{state:$state, reason:null},
63
+ invalidity:$inv, reconcile_inventory:($inv // {kind:null,ids:[]}),
64
+ provenance:{selected:"structured-home", trust:"partial-structured"}}]}}' > "$1"
65
+ }
66
+
67
+ # A fake ssh that decodes fm-on.sh's base64 remote-home/argv payload and
68
+ # EXECUTES the real host-local leg (fm-remote-secondmate-control.sh) against a
69
+ # genuinely seeded remote-home fixture, exactly like the ssh stub proven in
70
+ # tests/fm-send-remote-delivery.test.sh.
71
+ make_remote_ssh_stub() { # <dir> -> echoes fakebin dir
72
+ local dir=$1 fb="$1/fakebin"
73
+ mkdir -p "$fb"
74
+ cat > "$fb/fake-ssh" <<'SH'
75
+ #!/usr/bin/env bash
76
+ set -u
77
+ cat > /dev/null
78
+ while [ "$#" -gt 0 ]; do
79
+ case "$1" in -o) shift 2 ;; --) shift; break ;; *) exit 90 ;; esac
80
+ done
81
+ shift 2 # host, fm-remote-entrypoint.sh
82
+ home_b64=$3
83
+ argv_b64=$4
84
+ remote_home=$(perl -MMIME::Base64=decode_base64 -e 'print decode_base64($ARGV[0])' "$home_b64")
85
+ rargs=()
86
+ while IFS= read -r -d '' a; do rargs+=("$a"); done \
87
+ < <(perl -MMIME::Base64=decode_base64 -e 'print decode_base64($ARGV[0])' "$argv_b64")
88
+ cmd=${rargs[0]}
89
+ rc=0
90
+ env FM_HOME="$remote_home" FM_ROOT_OVERRIDE="$FM_REMOTE_CODE_ROOT" \
91
+ "$FM_REMOTE_CODE_ROOT/bin/$cmd" "${rargs[@]:1}" || rc=$?
92
+ exit "$rc"
93
+ SH
94
+ chmod +x "$fb/fake-ssh"
95
+ printf '%s\n' "$fb"
96
+ }
97
+
98
+ # A seeded remote secondmate home the real host-local leg validates and writes
99
+ # into (identity marker, Firstmate-checkout shape, parent-route endpoint meta).
100
+ make_remote_secondmate_home() { # <name> -> echoes remote home dir
101
+ local rh="$TMP_ROOT/$1-rhome"
102
+ mkdir -p "$rh/state/parent-route" "$rh/bin"
103
+ printf '%s\n' "$1" > "$rh/.fm-secondmate-home"
104
+ printf '# remote secondmate home fixture\n' > "$rh/AGENTS.md"
105
+ cat > "$rh/state/parent-route/$1.meta" <<META
106
+ window=fm-remote:p1
107
+ worktree=-
108
+ project=-
109
+ backend=herdr
110
+ endpoint_task_id=$1
111
+ harness=claude
112
+ herdr_session=fm-remote
113
+ herdr_workspace_id=w1
114
+ herdr_tab_id=t1
115
+ herdr_pane_id=p1
116
+ META
117
+ printf '%s\n' "$rh"
118
+ }
119
+
120
+ # A parent home whose secondmate is a persistent REMOTE route (as
121
+ # spawn_remote_secondmate() writes it: no spawn_gen, remote_host instead).
122
+ make_remote_parent_home() { # <name> <mate-id> <remote-home> <host> -> echoes home dir
123
+ local home="$TMP_ROOT/$1" id=$2 rhome host=$4
124
+ mkdir -p "$home/data" "$home/state"
125
+ # Canonicalize: fm-on.sh's registry route parser rejects an empty path
126
+ # component, and $TMP_ROOT can carry one (a raw mktemp base under a
127
+ # trailing-slash TMPDIR), same as make_main_home's $abs above.
128
+ rhome=$(cd "$3" && pwd -P)
129
+ cat > "$home/state/$id.meta" <<META
130
+ window=remote:$id
131
+ endpoint_task_id=$id
132
+ harness=claude
133
+ kind=secondmate
134
+ mode=secondmate
135
+ yolo=off
136
+ remote_host=$host
137
+ remote_root=/remote/root
138
+ remote_backend=herdr
139
+ remote_herdr_session=fm-remote
140
+ remote_target=fm-remote:p1
141
+ META
142
+ cat > "$home/data/secondmates.md" <<EOF
143
+ - $id - remote fixture domain (host: $host; root: /remote/root; home: $rhome; scope: remote fixture; projects: sample; added 2026-08-26)
144
+ EOF
145
+ printf '%s\n' "$home"
146
+ }
147
+
148
+ remote_inbox_records() { # <remote-home> <mate-id>
149
+ find "$1/state/parent-route/$2.inbox" -maxdepth 1 -type f -name '*.msg' 2>/dev/null
150
+ }
151
+
152
+ run_remote_notify() { # <home> <fakebin> <snapshot>
153
+ local home=$1 fakebin=$2 snap=$3
154
+ FM_SSH_BIN="$fakebin/fake-ssh" FM_REMOTE_CODE_ROOT="$ROOT" \
155
+ PATH="$fakebin:$PATH" FM_HOME="$home" FM_ROOT_OVERRIDE="$ROOT" \
156
+ FM_STATE_OVERRIDE="$home/state" \
157
+ "$RECONCILE" notify --snapshot "$snap"
158
+ }
159
+
160
+ # Age the home's cooldown record so the next run sees the window as elapsed.
161
+ age_cooldown() { # <state-dir> <mate-id> <seconds-ago>
162
+ printf '%s\n' "$(( $(date +%s) - $3 ))" > "$1/$2.reconcile-nudged"
163
+ }
164
+
165
+ run_notify() { # <home> <fakebin> <name> <snapshot> [extra args...]
166
+ local home=$1 fakebin=$2 name=$3 snap=$4
167
+ shift 4
168
+ PATH="$fakebin:$PATH" FM_HOME="$home" FM_ROOT_OVERRIDE="$ROOT" \
169
+ FM_STATE_OVERRIDE="$home/state" \
170
+ FM_FAKE_TMUX_WINDOW="firstmate:fm-mate" \
171
+ FM_FAKE_TMUX_LOG="$TMP_ROOT/$name-tmux.log" \
172
+ FM_FAKE_TMUX_CAPTURE="$TMP_ROOT/$name-fake/pane.txt" \
173
+ "$RECONCILE" notify --snapshot "$snap" "$@"
174
+ }
175
+
176
+ inbox_records() { # <state-dir> <task-id>
177
+ find "$1/$2.inbox" -maxdepth 1 -type f -name '*.msg' 2>/dev/null | wc -l | tr -d '[:space:]'
178
+ }
179
+
180
+ # Content-and-name fingerprint of a whole home, so any parent-side write shows up.
181
+ fingerprint_tree() { # <dir>
182
+ find "$1" -type f -print0 2>/dev/null | LC_ALL=C sort -z \
183
+ | while IFS= read -r -d '' f; do printf '%s %s\n' "${f#"$1"}" "$(cksum < "$f")"; done
184
+ }
185
+
186
+ inbox_text() { # <state-dir> <task-id>
187
+ local rec
188
+ for rec in "$1/$2.inbox"/*.msg; do
189
+ [ -f "$rec" ] || continue
190
+ bash -c '. "$1"; fm_task_inbox_body "$2"' _ "$ROOT/bin/fm-task-inbox-lib.sh" "$rec"
191
+ done
192
+ }
193
+
194
+ hold_lock_until_released() { # <lock> <ready> <release>
195
+ bash -c '
196
+ . "$1"
197
+ fm_lock_acquire_wait "$2"
198
+ : > "$3"
199
+ while [ ! -f "$4" ]; do sleep 0.01; done
200
+ fm_lock_release "$2"
201
+ ' _ "$ROOT/bin/fm-wake-lib.sh" "$1" "$2" "$3" &
202
+ }
203
+
204
+
205
+ test_an_inventory_mismatch_asks_the_mate_once_per_window() {
206
+ local home mate fakebin snap out
207
+ { read -r home; read -r mate; read -r fakebin; } < <(make_main_home once mate)
208
+ snap="$home/snapshot.json"
209
+ write_snapshot "$snap" mate '{"kind":"orphan_in_flight","ids":["stale-scout","watch-row"]}'
210
+
211
+ out=$(run_notify "$home" "$fakebin" once "$snap") || fail "the first reconcile ask failed: $out"
212
+ assert_contains "$out" "sent: mate orphan_in_flight" \
213
+ "the first ask did not report what it sent: $out"
214
+ [ "$(inbox_records "$home/state" mate)" -eq 1 ] \
215
+ || fail "the ask did not land as exactly one durable steering record"
216
+ assert_contains "$(inbox_text "$home/state" mate)" "check your current books" \
217
+ "the instruction did not ask the mate to inspect its current state"
218
+ if printf '%s' "$(inbox_text "$home/state" mate)" | grep -Fq 'stale-scout'; then
219
+ fail "the instruction prescribed a repair from sampled details that can become stale"
220
+ fi
221
+
222
+ # Every later recap sees the same mismatch; none of them may nag.
223
+ out=$(run_notify "$home" "$fakebin" once "$snap") || fail "the repeat run failed: $out"
224
+ assert_contains "$out" "cooldown: mate" "a repeated snapshot did not report the cooldown: $out"
225
+ run_notify "$home" "$fakebin" once "$snap" >/dev/null
226
+ run_notify "$home" "$fakebin" once "$snap" >/dev/null
227
+ [ "$(inbox_records "$home/state" mate)" -eq 1 ] \
228
+ || fail "repeated snapshots asked the mate more than once inside the cooldown"
229
+ pass "a home in mismatch is asked once, and later recaps stay silent"
230
+ }
231
+
232
+ test_a_mismatch_still_there_after_the_window_earns_one_more_nudge() {
233
+ local home mate fakebin snap out
234
+ { read -r home; read -r mate; read -r fakebin; } < <(make_main_home window mate)
235
+ snap="$home/snapshot.json"
236
+ write_snapshot "$snap" mate '{"kind":"orphan_in_flight","ids":["ghost"]}'
237
+ run_notify "$home" "$fakebin" window "$snap" >/dev/null || fail "the first ask failed"
238
+
239
+ # Just inside four hours: still silent.
240
+ age_cooldown "$home/state" mate 14000
241
+ out=$(run_notify "$home" "$fakebin" window "$snap") || fail "the in-window run failed: $out"
242
+ assert_contains "$out" "cooldown: mate" "an ask inside the window was not suppressed: $out"
243
+ [ "$(inbox_records "$home/state" mate)" -eq 1 ] || fail "an in-window ask was sent anyway"
244
+
245
+ # Past four hours: exactly one gentle re-nudge, then silent again.
246
+ age_cooldown "$home/state" mate 14500
247
+ out=$(run_notify "$home" "$fakebin" window "$snap") || fail "the past-window run failed: $out"
248
+ assert_contains "$out" "sent: mate orphan_in_flight" \
249
+ "a mismatch outliving the window did not earn a re-nudge: $out"
250
+ [ "$(inbox_records "$home/state" mate)" -eq 2 ] \
251
+ || fail "the re-nudge did not send exactly one more instruction"
252
+ run_notify "$home" "$fakebin" window "$snap" >/dev/null
253
+ [ "$(inbox_records "$home/state" mate)" -eq 2 ] \
254
+ || fail "the re-nudge did not restart the cooldown"
255
+ pass "a mismatch outliving the cooldown earns one re-nudge, then goes quiet again"
256
+ }
257
+
258
+ test_the_cooldown_starts_when_delivery_finishes() {
259
+ local home mate fakebin snap started nudged
260
+ { read -r home; read -r mate; read -r fakebin; } < <(make_main_home deliverytime mate)
261
+ snap="$home/snapshot.json"
262
+ write_snapshot "$snap" mate '{"kind":"orphan_in_flight","ids":["ghost"]}'
263
+ mv "$fakebin/tmux" "$fakebin/tmux-real"
264
+ cat > "$fakebin/tmux" <<'SH'
265
+ #!/usr/bin/env bash
266
+ set -u
267
+ if [ "${1:-}" = send-keys ]; then sleep 2; fi
268
+ exec "$(dirname "$0")/tmux-real" "$@"
269
+ SH
270
+ chmod +x "$fakebin/tmux"
271
+
272
+ started=$(date +%s)
273
+ run_notify "$home" "$fakebin" deliverytime "$snap" >/dev/null \
274
+ || fail "the delayed reconcile ask failed"
275
+ nudged=$(cat "$home/state/mate.reconcile-nudged")
276
+ [ "$nudged" -ge "$((started + 2))" ] \
277
+ || fail "the cooldown began before delivery finished: start=$started nudged=$nudged"
278
+ pass "the cooldown begins when delivery finishes"
279
+ }
280
+
281
+ test_the_window_is_four_hours() {
282
+ local home mate fakebin snap out now
283
+ { read -r home; read -r mate; read -r fakebin; } < <(make_main_home fourhours mate)
284
+ snap="$home/snapshot.json"
285
+ write_snapshot "$snap" mate '{"kind":"terminal_in_flight","ids":["done-row"]}'
286
+ run_notify "$home" "$fakebin" fourhours "$snap" >/dev/null || fail "the first ask failed"
287
+ now=$(date +%s)
288
+ cat > "$fakebin/date" <<'SH'
289
+ #!/usr/bin/env bash
290
+ if [ -n "${FM_TEST_DATE_NOW:-}" ] && [ "${1:-}" = +%s ]; then
291
+ printf '%s\n' "$FM_TEST_DATE_NOW"
292
+ exit 0
293
+ fi
294
+ exec /bin/date "$@"
295
+ SH
296
+ chmod +x "$fakebin/date"
297
+ # One second short of four hours is still inside; one second past is not.
298
+ printf '%s\n' "$((now - 14399))" > "$home/state/mate.reconcile-nudged"
299
+ out=$(FM_TEST_DATE_NOW=$now run_notify "$home" "$fakebin" fourhours "$snap")
300
+ assert_contains "$out" "cooldown: mate" "the window was shorter than four hours: $out"
301
+ printf '%s\n' "$((now - 14401))" > "$home/state/mate.reconcile-nudged"
302
+ out=$(FM_TEST_DATE_NOW=$now run_notify "$home" "$fakebin" fourhours "$snap")
303
+ assert_contains "$out" "sent: mate" "the window was longer than four hours: $out"
304
+ pass "the cooldown window is four hours"
305
+ }
306
+
307
+ test_each_home_carries_its_own_cooldown() {
308
+ local home mate fakebin snap out
309
+ { read -r home; read -r mate; read -r fakebin; } < <(make_main_home perhome mate)
310
+ # A second registered mate in the same home, so one nudge cannot silence the other.
311
+ cp "$home/state/mate.meta" "$home/state/other.meta"
312
+ sed -i.bak 's/fm-mate/fm-other/' "$home/state/other.meta" && rm -f "$home/state/other.meta.bak"
313
+ snap="$home/snapshot.json"
314
+ jq -n '{schema:"fm-fleet-snapshot.v1", generated:"2026-08-26T00:00:00Z",
315
+ secondmate_current:{records:[
316
+ {id:"mate", home:"/tmp/mate", spawn_gen:"spawn-mate", current:{state:"captain_decision",reason:null},
317
+ invalidity:{kind:"orphan_in_flight",ids:["a"]},
318
+ reconcile_inventory:{kind:"orphan_in_flight",ids:["a"]},
319
+ provenance:{selected:"structured-home",trust:"partial-structured"}},
320
+ {id:"other", home:"/tmp/other", spawn_gen:"spawn-mate", current:{state:"captain_decision",reason:null},
321
+ invalidity:{kind:"unowned_current",ids:["b"]},
322
+ reconcile_inventory:{kind:"unowned_current",ids:["b"]},
323
+ provenance:{selected:"structured-home",trust:"partial-structured"}}]}}' > "$snap"
324
+ out=$(run_notify "$home" "$fakebin" perhome "$snap") || fail "the first run failed: $out"
325
+ assert_contains "$out" "sent: mate" "the first home was not asked: $out"
326
+ assert_contains "$out" "sent: other" "the second home was not asked: $out"
327
+ [ "$(inbox_records "$home/state" mate)" -eq 1 ] || fail "the first home got the wrong count"
328
+ [ "$(inbox_records "$home/state" other)" -eq 1 ] || fail "the second home got the wrong count"
329
+ # Only one home's window elapses; the other must stay quiet.
330
+ age_cooldown "$home/state" mate 14500
331
+ out=$(run_notify "$home" "$fakebin" perhome "$snap")
332
+ assert_contains "$out" "sent: mate" "an elapsed window did not re-nudge its own home: $out"
333
+ assert_contains "$out" "cooldown: other" "one home's nudge reset another home's window: $out"
334
+ pass "the cooldown is per home, not fleet-wide"
335
+ }
336
+
337
+ test_the_ask_never_arms_a_reply_expectation_or_a_re_ring() {
338
+ local home mate fakebin snap ladder
339
+ { read -r home; read -r mate; read -r fakebin; } < <(make_main_home fireforget mate)
340
+ snap="$home/snapshot.json"
341
+ write_snapshot "$snap" mate '{"kind":"orphan_in_flight","ids":["ghost"]}'
342
+ run_notify "$home" "$fakebin" fireforget "$snap" >/dev/null || fail "the ask failed"
343
+ [ "$(inbox_records "$home/state" mate)" -eq 1 ] || fail "the ask was not durably recorded"
344
+
345
+ # The parent expects no answer, so nothing may chase one.
346
+ [ "$(find "$home/state/pending-replies" -type f 2>/dev/null | wc -l | tr -d '[:space:]')" -eq 0 ] \
347
+ || fail "the reconcile ask armed a pending-reply expectation"
348
+
349
+ # The record stays unhandled. With the ladder's grace elapsed, an ordinary
350
+ # steer in that position is due for a re-ring; this one must stay invisible.
351
+ ladder=$(FM_TASK_INBOX_GRACE_SECS=0 bash -c '. "$1"; fm_task_inbox_due_action "$2" "$3"' _ \
352
+ "$ROOT/bin/fm-task-inbox-lib.sh" "$home/state" mate 2>&1 || true)
353
+ [ "$ladder" = quiet ] \
354
+ || fail "the unacknowledged reconcile record entered the re-ring ladder: $ladder"
355
+
356
+ # Divergence check, so the assertion above cannot pass for the wrong reason:
357
+ # the same inbox, same grace, with an ordinary unhandled steer added.
358
+ PATH="$fakebin:$PATH" FM_HOME="$home" FM_ROOT_OVERRIDE="$ROOT" \
359
+ FM_STATE_OVERRIDE="$home/state" \
360
+ FM_FAKE_TMUX_WINDOW="firstmate:fm-mate" \
361
+ FM_FAKE_TMUX_LOG="$TMP_ROOT/fireforget-tmux.log" \
362
+ FM_FAKE_TMUX_CAPTURE="$TMP_ROOT/fireforget-fake/pane.txt" \
363
+ "$ROOT/bin/fm-send.sh" mate "an ordinary steer that does expect handling" >/dev/null 2>&1 \
364
+ || fail "the control steer could not be recorded"
365
+ ladder=$(FM_TASK_INBOX_GRACE_SECS=0 bash -c '. "$1"; fm_task_inbox_due_action "$2" "$3"' _ \
366
+ "$ROOT/bin/fm-task-inbox-lib.sh" "$home/state" mate 2>&1 || true)
367
+ case "$ladder" in
368
+ ring\ *) ;;
369
+ *) fail "the ladder ignored an ordinary steer too, so the quiet verdict proved nothing: $ladder" ;;
370
+ esac
371
+ pass "the reconcile ask expects no reply and stays out of a ladder that still rings ordinary steers"
372
+ }
373
+
374
+ test_a_readable_home_without_a_mismatch_is_never_asked() {
375
+ local home mate fakebin snap out
376
+ { read -r home; read -r mate; read -r fakebin; } < <(make_main_home quiet mate)
377
+ snap="$home/snapshot.json"
378
+ write_snapshot "$snap" mate '{"kind":"child_current_unavailable","ids":["x"]}' unknown
379
+ out=$(run_notify "$home" "$fakebin" quiet "$snap") || fail "notify failed: $out"
380
+ [ "$(inbox_records "$home/state" mate)" -eq 0 ] \
381
+ || fail "an unavailable child state was mistaken for a books problem"
382
+ write_snapshot "$snap" mate '{"kind":null,"ids":[]}' no_active_work
383
+ out=$(run_notify "$home" "$fakebin" quiet "$snap") || fail "notify failed: $out"
384
+ [ "$(inbox_records "$home/state" mate)" -eq 0 ] || fail "a healthy home was asked to reconcile"
385
+ pass "only a backlog-vs-metadata mismatch produces an ask"
386
+ }
387
+
388
+ test_the_parent_never_changes_the_mates_own_files() {
389
+ local home mate fakebin snap before after
390
+ { read -r home; read -r mate; read -r fakebin; } < <(make_main_home readonly mate)
391
+ snap="$home/snapshot.json"
392
+ write_snapshot "$snap" mate '{"kind":"orphan_in_flight","ids":["ghost"]}'
393
+ before=$(fingerprint_tree "$mate")
394
+ [ -n "$before" ] || fail "the mate fixture has no files to compare"
395
+ run_notify "$home" "$fakebin" readonly "$snap" >/dev/null || fail "the ask failed"
396
+ after=$(fingerprint_tree "$mate")
397
+ [ "$before" = "$after" ] \
398
+ || fail "asking for a reconcile changed the mate's own files: $before / $after"
399
+ pass "the parent asks and changes nothing inside the mate's home"
400
+ }
401
+
402
+ test_a_failed_send_is_retried_on_the_next_run() {
403
+ local home mate fakebin snap out rc
404
+ { read -r home; read -r mate; read -r fakebin; } < <(make_main_home retry mate)
405
+ snap="$home/snapshot.json"
406
+ write_snapshot "$snap" absent-mate '{"kind":"orphan_in_flight","ids":["ghost"]}'
407
+ set +e
408
+ out=$(run_notify "$home" "$fakebin" retry "$snap"); rc=$?
409
+ set -e
410
+ [ "$rc" -ne 0 ] || fail "an unroutable ask reported success: $out"
411
+ assert_contains "$out" "failed: absent-mate" "the failure was not reported: $out"
412
+ assert_absent "$home/state/absent-mate.reconcile-nudged" \
413
+ "a failed ask started a cooldown and would never be retried"
414
+ pass "a failed ask starts no cooldown, so the next run retries it"
415
+ }
416
+
417
+ test_busy_lifecycle_locks_never_hold_up_the_digest() {
418
+ local label home mate fakebin snap lock ready release holder notify out
419
+ for label in reconcile control meta; do
420
+ { read -r home; read -r mate; read -r fakebin; } < <(make_main_home "busy-$label" mate)
421
+ snap="$home/snapshot.json"
422
+ write_snapshot "$snap" mate '{"kind":"orphan_in_flight","ids":["ghost"]}'
423
+ case "$label" in
424
+ reconcile) lock="$home/state/.mate.reconcile.lock" ;;
425
+ control) lock="$home/state/.control-mate.lock" ;;
426
+ meta) lock="$home/state/.meta-mate.lock" ;;
427
+ esac
428
+ ready="$home/lock-ready"
429
+ release="$home/lock-release"
430
+ hold_lock_until_released "$lock" "$ready" "$release"
431
+ holder=$!
432
+ while [ ! -f "$ready" ]; do sleep 0.01; done
433
+ run_notify "$home" "$fakebin" "busy-$label" "$snap" > "$home/notify.out" 2>&1 &
434
+ notify=$!
435
+ sleep 0.2
436
+ if kill -0 "$notify" 2>/dev/null; then
437
+ : > "$release"
438
+ wait "$notify" 2>/dev/null || true
439
+ wait "$holder" 2>/dev/null || true
440
+ fail "a busy $label lock blocked the reconcile path"
441
+ fi
442
+ wait "$notify" || fail "a busy $label lock made notify fail"
443
+ : > "$release"
444
+ wait "$holder" || fail "the $label lock holder failed"
445
+ out=$(cat "$home/notify.out")
446
+ assert_contains "$out" "skipped: mate lock" \
447
+ "a busy $label lock was not reported as a skipped nudge: $out"
448
+ assert_absent "$home/state/mate.reconcile-nudged" \
449
+ "a skipped $label-lock nudge started the cooldown"
450
+ done
451
+ pass "busy reconcile lifecycle locks never block the digest or start cooldown"
452
+ }
453
+
454
+ test_concurrent_recaps_send_one_instruction() {
455
+ local home mate fakebin snap
456
+ { read -r home; read -r mate; read -r fakebin; } < <(make_main_home concurrent mate)
457
+ snap="$home/snapshot.json"
458
+ write_snapshot "$snap" mate '{"kind":"orphan_in_flight","ids":["ghost"]}'
459
+ run_notify "$home" "$fakebin" concurrent "$snap" >/dev/null 2>&1 &
460
+ run_notify "$home" "$fakebin" concurrent "$snap" >/dev/null 2>&1 &
461
+ wait
462
+ [ "$(inbox_records "$home/state" mate)" -eq 1 ] \
463
+ || fail "two simultaneous recaps asked the mate twice"
464
+ pass "simultaneous recaps still ask the mate only once"
465
+ }
466
+
467
+ test_a_delayed_snapshot_never_prescribes_a_stale_repair() {
468
+ local home mate fakebin old_snap new_snap out text
469
+ { read -r home; read -r mate; read -r fakebin; } < <(make_main_home delayed mate)
470
+ old_snap="$home/old-snapshot.json"
471
+ new_snap="$home/new-snapshot.json"
472
+ write_snapshot "$old_snap" mate '{"kind":"orphan_in_flight","ids":["already-repaired"]}'
473
+ write_snapshot "$new_snap" mate '{"kind":"unowned_current","ids":["current-row"]}'
474
+
475
+ out=$(run_notify "$home" "$fakebin" delayed "$old_snap") \
476
+ || fail "the delayed reconcile ask failed: $out"
477
+ assert_contains "$out" "sent: mate orphan_in_flight" \
478
+ "the delayed snapshot did not produce the cooldown-limited check: $out"
479
+ out=$(run_notify "$home" "$fakebin" delayed "$new_snap") \
480
+ || fail "the current snapshot reconcile failed: $out"
481
+ assert_contains "$out" "cooldown: mate" \
482
+ "the per-home cooldown did not deduplicate the newer observation: $out"
483
+ [ "$(inbox_records "$home/state" mate)" -eq 1 ] \
484
+ || fail "the old and new snapshots produced more than one ask inside the cooldown"
485
+ text=$(inbox_text "$home/state" mate)
486
+ assert_contains "$text" "check your current books" \
487
+ "the delayed ask did not direct the mate to current state"
488
+ if printf '%s' "$text" | grep -Eq 'already-repaired|current-row'; then
489
+ fail "the delayed ask embedded sampled row details and could prescribe a stale repair: $text"
490
+ fi
491
+ pass "a delayed snapshot asks for a current check instead of prescribing a stale repair"
492
+ }
493
+
494
+ test_a_stale_snapshot_never_targets_a_replacement_mate() {
495
+ local home mate fakebin snap out
496
+ { read -r home; read -r mate; read -r fakebin; } < <(make_main_home stale mate)
497
+ snap="$home/snapshot.json"
498
+ write_snapshot "$snap" mate '{"kind":"orphan_in_flight","ids":["old-ghost"]}'
499
+ awk '{ sub(/^spawn_gen=.*/, "spawn_gen=spawn-replacement"); print }' \
500
+ "$home/state/mate.meta" > "$home/state/mate.meta.tmp"
501
+ mv "$home/state/mate.meta.tmp" "$home/state/mate.meta"
502
+
503
+ out=$(run_notify "$home" "$fakebin" stale "$snap") \
504
+ || fail "a stale snapshot made reconcile fail: $out"
505
+ assert_contains "$out" "stale: mate orphan_in_flight" \
506
+ "the stale snapshot was not identified: $out"
507
+ [ "$(inbox_records "$home/state" mate)" -eq 0 ] \
508
+ || fail "a replacement mate received its predecessor's reconcile ask"
509
+ assert_absent "$home/state/mate.reconcile-nudged" \
510
+ "a replacement mate inherited cooldown from a stale snapshot"
511
+ pass "a stale snapshot cannot ask or silence a replacement mate"
512
+ }
513
+
514
+ test_teardown_cannot_leave_its_replacement_in_cooldown() {
515
+ local home mate fakebin snap signal release lifecycle_done cooldown notify_pid lifecycle_pid
516
+ { read -r home; read -r mate; read -r fakebin; } < <(make_main_home lifecycle mate)
517
+ snap="$home/snapshot.json"
518
+ signal="$home/send-ringing"
519
+ release="$home/release-ring"
520
+ lifecycle_done="$home/lifecycle-done"
521
+ cooldown="$home/state/mate.reconcile-nudged"
522
+ write_snapshot "$snap" mate '{"kind":"orphan_in_flight","ids":["ghost"]}'
523
+ mv "$fakebin/tmux" "$fakebin/tmux-real"
524
+ cat > "$fakebin/tmux" <<'SH'
525
+ #!/usr/bin/env bash
526
+ set -u
527
+ if [ "${1:-}" = send-keys ]; then
528
+ : > "$FM_FAKE_TMUX_SEND_SIGNAL"
529
+ while [ ! -f "$FM_FAKE_TMUX_SEND_RELEASE" ]; do sleep 0.01; done
530
+ fi
531
+ exec "$(dirname "$0")/tmux-real" "$@"
532
+ SH
533
+ chmod +x "$fakebin/tmux"
534
+
535
+ FM_FAKE_TMUX_SEND_SIGNAL="$signal" FM_FAKE_TMUX_SEND_RELEASE="$release" \
536
+ run_notify "$home" "$fakebin" lifecycle "$snap" >/dev/null 2>&1 &
537
+ notify_pid=$!
538
+ while [ ! -f "$signal" ]; do sleep 0.01; done
539
+
540
+ (
541
+ . "$ROOT/bin/fm-wake-lib.sh"
542
+ fm_lock_acquire_wait "$home/state/.control-mate.lock"
543
+ fm_lock_acquire_wait "$home/state/.meta-mate.lock"
544
+ rm -rf "$home/state/mate.inbox"
545
+ rm -f "$home/state/mate.meta" "$home/state/mate.reconcile-nudged"
546
+ cat > "$home/state/mate.meta" <<META
547
+ window=firstmate:fm-mate
548
+ kind=secondmate
549
+ harness=claude
550
+ backend=tmux
551
+ spawn_gen=spawn-replacement
552
+ home=$mate
553
+ worktree=$mate
554
+ META
555
+ fm_lock_release "$home/state/.meta-mate.lock"
556
+ fm_lock_release "$home/state/.control-mate.lock"
557
+ : > "$lifecycle_done"
558
+ ) &
559
+ lifecycle_pid=$!
560
+
561
+ sleep 0.1
562
+ : > "$release"
563
+ wait "$notify_pid" 2>/dev/null || true
564
+ wait "$lifecycle_pid" || fail "the simulated teardown and reseed failed"
565
+ [ -f "$lifecycle_done" ] || fail "the simulated lifecycle transition did not finish"
566
+ assert_absent "$cooldown" \
567
+ "a retired mate's cooldown was recreated after its replacement was seeded"
568
+ pass "teardown retires the cooldown before a replacement can inherit it"
569
+ }
570
+
571
+ # A persistent REMOTE secondmate's parent metadata never carries spawn_gen
572
+ # (bin/fm-spawn.sh's spawn_remote_secondmate() never writes one). This is the
573
+ # proven marker-bearing path's counterpart: same durable fire-and-forget
574
+ # delivery and cooldown behavior, driven end to end through the real remote
575
+ # transport, for a mate that legitimately has no generation marker at all.
576
+ test_a_markerless_remote_secondmate_is_nudged_once_per_window() {
577
+ local home rhome fakebin snap out
578
+ fakebin=$(make_remote_ssh_stub "$TMP_ROOT/remote-once")
579
+ rhome=$(make_remote_secondmate_home remote-once-mate)
580
+ home=$(make_remote_parent_home remote-once remote-once-mate "$rhome" remote-once-host)
581
+ snap="$home/snapshot.json"
582
+ write_remote_snapshot "$snap" remote-once-mate remote-once-host \
583
+ '{"kind":"orphan_in_flight","ids":["stale-scout"]}'
584
+
585
+ out=$(run_remote_notify "$home" "$fakebin" "$snap") \
586
+ || fail "the first markerless reconcile ask failed: $out"
587
+ assert_contains "$out" "sent: remote-once-mate orphan_in_flight" \
588
+ "a legitimately markerless remote mate got no ask: $out"
589
+ [ -n "$(remote_inbox_records "$rhome" remote-once-mate)" ] \
590
+ || fail "the ask did not land as a durable record in the remote steering inbox"
591
+ [ -f "$home/state/remote-once-mate.reconcile-nudged" ] \
592
+ || fail "a successful markerless ask did not start the cooldown"
593
+
594
+ out=$(run_remote_notify "$home" "$fakebin" "$snap") \
595
+ || fail "the repeat markerless run failed: $out"
596
+ assert_contains "$out" "cooldown: remote-once-mate" \
597
+ "a repeated markerless snapshot did not report the cooldown: $out"
598
+ [ "$(remote_inbox_records "$rhome" remote-once-mate | grep -c . || true)" -eq 1 ] \
599
+ || fail "repeated markerless snapshots asked the mate more than once inside the cooldown"
600
+ pass "a legitimately markerless persistent remote secondmate is nudged once per window"
601
+ }
602
+
603
+ # The safety boundary the spawn_gen check protects for local mates has a
604
+ # host-keyed counterpart for markerless remote mates: a snapshot sampled
605
+ # before the route moved to a different host must never reach the new host's
606
+ # mate or arm its cooldown.
607
+ test_a_stale_remote_route_is_refused() {
608
+ local home rhome fakebin snap out
609
+ fakebin=$(make_remote_ssh_stub "$TMP_ROOT/remote-stale")
610
+ rhome=$(make_remote_secondmate_home remote-stale-mate)
611
+ home=$(make_remote_parent_home remote-stale remote-stale-mate "$rhome" old-host)
612
+ snap="$home/snapshot.json"
613
+ write_remote_snapshot "$snap" remote-stale-mate old-host \
614
+ '{"kind":"orphan_in_flight","ids":["old-ghost"]}'
615
+ # The route was re-seeded to a different host since the snapshot was taken.
616
+ sed -i.bak 's/^remote_host=.*/remote_host=new-host/' "$home/state/remote-stale-mate.meta" \
617
+ && rm -f "$home/state/remote-stale-mate.meta.bak"
618
+
619
+ out=$(run_remote_notify "$home" "$fakebin" "$snap") \
620
+ || fail "a stale remote-route snapshot made reconcile fail: $out"
621
+ assert_contains "$out" "stale: remote-stale-mate orphan_in_flight" \
622
+ "a snapshot sampled from a retired remote route was not identified: $out"
623
+ [ -z "$(remote_inbox_records "$rhome" remote-stale-mate)" ] \
624
+ || fail "a replacement remote route received its predecessor's reconcile ask"
625
+ assert_absent "$home/state/remote-stale-mate.reconcile-nudged" \
626
+ "a replacement remote route inherited cooldown from a stale snapshot"
627
+ pass "a stale remote-route snapshot cannot ask or silence a replacement mate"
628
+ }
629
+
630
+ test_route_replacement_during_send_is_refused() {
631
+ local home rhome fakebin snap signal release out rc notify_pid
632
+ fakebin=$(make_remote_ssh_stub "$TMP_ROOT/remote-send-race")
633
+ rhome=$(make_remote_secondmate_home remote-send-race-mate)
634
+ home=$(make_remote_parent_home remote-send-race remote-send-race-mate "$rhome" old-host)
635
+ snap="$home/snapshot.json"
636
+ signal="$home/fm-send-started"
637
+ release="$home/release-fm-send"
638
+ write_remote_snapshot "$snap" remote-send-race-mate old-host \
639
+ '{"kind":"orphan_in_flight","ids":["old-ghost"]}'
640
+ cat > "$fakebin/dirname" <<'SH'
641
+ #!/usr/bin/env bash
642
+ set -u
643
+ case "${1:-}" in
644
+ */fm-send.sh)
645
+ : > "$FM_RECONCILE_RACE_SIGNAL"
646
+ while [ ! -f "$FM_RECONCILE_RACE_RELEASE" ]; do sleep 0.01; done
647
+ ;;
648
+ esac
649
+ case "${1:-}" in
650
+ */*) printf '%s\n' "${1%/*}" ;;
651
+ *) printf '.\n' ;;
652
+ esac
653
+ SH
654
+ chmod +x "$fakebin/dirname"
655
+
656
+ rc=0
657
+ FM_RECONCILE_RACE_SIGNAL="$signal" FM_RECONCILE_RACE_RELEASE="$release" \
658
+ run_remote_notify "$home" "$fakebin" "$snap" > "$home/notify.out" 2>&1 &
659
+ notify_pid=$!
660
+ while [ ! -f "$signal" ]; do
661
+ kill -0 "$notify_pid" 2>/dev/null || fail "reconcile exited before entering fm-send"
662
+ sleep 0.01
663
+ done
664
+
665
+ . "$ROOT/bin/fm-wake-lib.sh"
666
+ fm_lock_acquire_wait "$home/state/.control-remote-send-race-mate.lock"
667
+ fm_lock_acquire_wait "$home/state/.meta-remote-send-race-mate.lock"
668
+ sed 's/^remote_host=.*/remote_host=new-host/' \
669
+ "$home/state/remote-send-race-mate.meta" > "$home/state/remote-send-race-mate.meta.tmp"
670
+ mv "$home/state/remote-send-race-mate.meta.tmp" "$home/state/remote-send-race-mate.meta"
671
+ sed 's/host: old-host/host: new-host/' \
672
+ "$home/data/secondmates.md" > "$home/data/secondmates.md.tmp"
673
+ mv "$home/data/secondmates.md.tmp" "$home/data/secondmates.md"
674
+ fm_lock_release "$home/state/.meta-remote-send-race-mate.lock"
675
+ fm_lock_release "$home/state/.control-remote-send-race-mate.lock"
676
+ : > "$release"
677
+ wait "$notify_pid" || rc=$?
678
+ out=$(cat "$home/notify.out")
679
+
680
+ [ "$rc" -ne 0 ] || fail "a route replacement during send reported success: $out"
681
+ assert_contains "$out" "failed: remote-send-race-mate orphan_in_flight" \
682
+ "a route replacement during send was not refused: $out"
683
+ [ -z "$(remote_inbox_records "$rhome" remote-send-race-mate)" ] \
684
+ || fail "a replacement route received its predecessor's reconcile ask"
685
+ assert_absent "$home/state/remote-send-race-mate.reconcile-nudged" \
686
+ "a refused route replacement started the cooldown"
687
+ pass "a route replacement between reconcile and fm-send cannot receive a stale ask"
688
+ }
689
+
690
+ # A row with neither a spawn generation nor a host carries no safe identity at
691
+ # all - the markerless path must not swallow that case the way the original
692
+ # bug swallowed every markerless row.
693
+ test_a_row_with_no_identity_at_all_fails_loudly() {
694
+ local home rhome fakebin snap out
695
+ fakebin=$(make_remote_ssh_stub "$TMP_ROOT/remote-noid")
696
+ rhome=$(make_remote_secondmate_home remote-noid-mate)
697
+ home=$(make_remote_parent_home remote-noid remote-noid-mate "$rhome" remote-noid-host)
698
+ snap="$home/snapshot.json"
699
+ write_remote_snapshot "$snap" remote-noid-mate "" \
700
+ '{"kind":"orphan_in_flight","ids":["ghost"]}'
701
+
702
+ set +e
703
+ out=$(run_remote_notify "$home" "$fakebin" "$snap"); rc=$?
704
+ set -e
705
+ [ "$rc" -ne 0 ] || fail "a row with no sampled identity at all reported success: $out"
706
+ assert_contains "$out" "failed: remote-noid-mate orphan_in_flight" \
707
+ "an unidentifiable row was not reported as failed: $out"
708
+ [ -z "$(remote_inbox_records "$rhome" remote-noid-mate)" ] \
709
+ || fail "an unidentifiable row still received a reconcile ask"
710
+ assert_absent "$home/state/remote-noid-mate.reconcile-nudged" \
711
+ "an unidentifiable row started a cooldown"
712
+ pass "a row with neither a spawn generation nor a host fails loudly instead of vanishing"
713
+ }
714
+
715
+ test_an_inventory_mismatch_asks_the_mate_once_per_window
716
+ test_a_mismatch_still_there_after_the_window_earns_one_more_nudge
717
+ test_the_cooldown_starts_when_delivery_finishes
718
+ test_the_window_is_four_hours
719
+ test_each_home_carries_its_own_cooldown
720
+ test_the_ask_never_arms_a_reply_expectation_or_a_re_ring
721
+ test_a_readable_home_without_a_mismatch_is_never_asked
722
+ test_the_parent_never_changes_the_mates_own_files
723
+ test_a_failed_send_is_retried_on_the_next_run
724
+ test_busy_lifecycle_locks_never_hold_up_the_digest
725
+ test_concurrent_recaps_send_one_instruction
726
+ test_a_delayed_snapshot_never_prescribes_a_stale_repair
727
+ test_a_stale_snapshot_never_targets_a_replacement_mate
728
+ test_teardown_cannot_leave_its_replacement_in_cooldown
729
+ test_a_markerless_remote_secondmate_is_nudged_once_per_window
730
+ test_a_stale_remote_route_is_refused
731
+ test_route_replacement_during_send_is_refused
732
+ test_a_row_with_no_identity_at_all_fails_loudly