@zerwiz/ymir 0.1.4 → 0.1.7

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 (865) 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/README.md +74 -0
  27. package/.agents/backend/backends/cmux.sh +649 -0
  28. package/.agents/backend/backends/herdr-eventwait.py +157 -0
  29. package/.agents/backend/backends/herdr-workspace-move.py +114 -0
  30. package/.agents/backend/backends/herdr.sh +3386 -0
  31. package/.agents/backend/backends/orca.sh +290 -0
  32. package/.agents/backend/backends/tmux.sh +357 -0
  33. package/.agents/backend/backends/zellij.sh +700 -0
  34. package/.agents/backend/fm-afk-launch.sh +654 -0
  35. package/.agents/backend/fm-afk-return.sh +241 -0
  36. package/.agents/backend/fm-afk-start.sh +172 -0
  37. package/.agents/backend/fm-arm-command-policy.mjs +960 -0
  38. package/.agents/backend/fm-arm-pretool-check.sh +200 -0
  39. package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
  40. package/.agents/backend/fm-backend.sh +981 -0
  41. package/.agents/backend/fm-backlog-handoff.sh +909 -0
  42. package/.agents/backend/fm-backlog-receive.sh +187 -0
  43. package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
  44. package/.agents/backend/fm-bearings-board.sh +204 -0
  45. package/.agents/backend/fm-bearings-snapshot.sh +546 -0
  46. package/.agents/backend/fm-bootstrap.sh +1568 -0
  47. package/.agents/backend/fm-branch-outcome.sh +203 -0
  48. package/.agents/backend/fm-branch-prompt.sh +101 -0
  49. package/.agents/backend/fm-brief.sh +452 -0
  50. package/.agents/backend/fm-busy-event.sh +234 -0
  51. package/.agents/backend/fm-busy-lib.sh +986 -0
  52. package/.agents/backend/fm-captain-hold.sh +1001 -0
  53. package/.agents/backend/fm-cd-command-policy.mjs +153 -0
  54. package/.agents/backend/fm-cd-pretool-check.sh +190 -0
  55. package/.agents/backend/fm-check-lib.sh +74 -0
  56. package/.agents/backend/fm-check-register.sh +42 -0
  57. package/.agents/backend/fm-check-unregister.sh +52 -0
  58. package/.agents/backend/fm-classify-lib.sh +1770 -0
  59. package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
  60. package/.agents/backend/fm-composer-lib.sh +1416 -0
  61. package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
  62. package/.agents/backend/fm-config-push.sh +254 -0
  63. package/.agents/backend/fm-control-lib.sh +251 -0
  64. package/.agents/backend/fm-control.sh +878 -0
  65. package/.agents/backend/fm-crew-state.sh +625 -0
  66. package/.agents/backend/fm-cursor-lib.sh +243 -0
  67. package/.agents/backend/fm-decision-hold.sh +232 -0
  68. package/.agents/backend/fm-doc-audience-check.sh +269 -0
  69. package/.agents/backend/fm-dod-lib.sh +67 -0
  70. package/.agents/backend/fm-ensure-agents-md.sh +253 -0
  71. package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
  72. package/.agents/backend/fm-extension.mjs +2577 -0
  73. package/.agents/backend/fm-extension.sh +16 -0
  74. package/.agents/backend/fm-ff-lib.sh +420 -0
  75. package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
  76. package/.agents/backend/fm-fleet-sync.sh +458 -0
  77. package/.agents/backend/fm-fleet-view.sh +96 -0
  78. package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
  79. package/.agents/backend/fm-guard.sh +242 -0
  80. package/.agents/backend/fm-harness.sh +195 -0
  81. package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
  82. package/.agents/backend/fm-herdr-lab.sh +346 -0
  83. package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
  84. package/.agents/backend/fm-home-seed.sh +984 -0
  85. package/.agents/backend/fm-home-summary-refresh.sh +255 -0
  86. package/.agents/backend/fm-hook-host-lib.sh +36 -0
  87. package/.agents/backend/fm-inactive-reconcile.sh +531 -0
  88. package/.agents/backend/fm-inbox.sh +399 -0
  89. package/.agents/backend/fm-install-actionlint.sh +84 -0
  90. package/.agents/backend/fm-install-herdr.sh +96 -0
  91. package/.agents/backend/fm-install-shellcheck.sh +84 -0
  92. package/.agents/backend/fm-install-treehouse.sh +95 -0
  93. package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
  94. package/.agents/backend/fm-lease-lib.sh +218 -0
  95. package/.agents/backend/fm-lease.sh +189 -0
  96. package/.agents/backend/fm-line-cap-lib.sh +51 -0
  97. package/.agents/backend/fm-lint-workflows.sh +137 -0
  98. package/.agents/backend/fm-lint.sh +591 -0
  99. package/.agents/backend/fm-lock-lib.sh +104 -0
  100. package/.agents/backend/fm-lock.sh +107 -0
  101. package/.agents/backend/fm-marker-lib.sh +12 -0
  102. package/.agents/backend/fm-merge-local.sh +74 -0
  103. package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
  104. package/.agents/backend/fm-nm-run-lib.sh +124 -0
  105. package/.agents/backend/fm-on.sh +125 -0
  106. package/.agents/backend/fm-operational-input.sh +253 -0
  107. package/.agents/backend/fm-peek.sh +46 -0
  108. package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
  109. package/.agents/backend/fm-pr-check.sh +135 -0
  110. package/.agents/backend/fm-pr-lib.sh +1016 -0
  111. package/.agents/backend/fm-pr-merge.sh +707 -0
  112. package/.agents/backend/fm-pr-poll.sh +110 -0
  113. package/.agents/backend/fm-primary-scope-lib.sh +33 -0
  114. package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
  115. package/.agents/backend/fm-procevent-lavish.sh +628 -0
  116. package/.agents/backend/fm-procevent-lib.sh +892 -0
  117. package/.agents/backend/fm-procevent-quota.sh +290 -0
  118. package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
  119. package/.agents/backend/fm-procevent-when.sh +504 -0
  120. package/.agents/backend/fm-procevent.sh +1466 -0
  121. package/.agents/backend/fm-project-mode.sh +92 -0
  122. package/.agents/backend/fm-project-origin-lib.sh +180 -0
  123. package/.agents/backend/fm-promote.sh +282 -0
  124. package/.agents/backend/fm-public-followup-emit.sh +260 -0
  125. package/.agents/backend/fm-public-followup-lib.sh +398 -0
  126. package/.agents/backend/fm-public-followup.sh +1330 -0
  127. package/.agents/backend/fm-push-transition-lib.sh +168 -0
  128. package/.agents/backend/fm-quota-axi-lib.sh +93 -0
  129. package/.agents/backend/fm-quota-choose.sh +384 -0
  130. package/.agents/backend/fm-remote-delta-read.sh +189 -0
  131. package/.agents/backend/fm-remote-doctor.sh +800 -0
  132. package/.agents/backend/fm-remote-entrypoint.sh +193 -0
  133. package/.agents/backend/fm-remote-file.sh +234 -0
  134. package/.agents/backend/fm-remote-home-provision.sh +259 -0
  135. package/.agents/backend/fm-remote-home-seed.sh +259 -0
  136. package/.agents/backend/fm-remote-inherit-push.sh +90 -0
  137. package/.agents/backend/fm-remote-inherit.sh +179 -0
  138. package/.agents/backend/fm-remote-job-lib.sh +1241 -0
  139. package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
  140. package/.agents/backend/fm-remote-job-worker.sh +1124 -0
  141. package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
  142. package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
  143. package/.agents/backend/fm-review-diff.sh +158 -0
  144. package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
  145. package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
  146. package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
  147. package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
  148. package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
  149. package/.agents/backend/fm-secondmate-report.sh +88 -0
  150. package/.agents/backend/fm-send.sh +1059 -0
  151. package/.agents/backend/fm-session-lock-lib.sh +176 -0
  152. package/.agents/backend/fm-session-start.sh +970 -0
  153. package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
  154. package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
  155. package/.agents/backend/fm-sessionstart-run.sh +145 -0
  156. package/.agents/backend/fm-spawn.sh +3153 -0
  157. package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
  158. package/.agents/backend/fm-startup-memory-budget.sh +94 -0
  159. package/.agents/backend/fm-startup-network.sh +642 -0
  160. package/.agents/backend/fm-stow-cascade.sh +251 -0
  161. package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
  162. package/.agents/backend/fm-supervise-daemon.sh +1732 -0
  163. package/.agents/backend/fm-supervision-instructions.sh +215 -0
  164. package/.agents/backend/fm-supervision-lib.sh +90 -0
  165. package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
  166. package/.agents/backend/fm-tangle-lib.sh +53 -0
  167. package/.agents/backend/fm-task-inbox-lib.sh +402 -0
  168. package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
  169. package/.agents/backend/fm-teardown.sh +2922 -0
  170. package/.agents/backend/fm-test-isolation-proof.sh +588 -0
  171. package/.agents/backend/fm-test-run.sh +2191 -0
  172. package/.agents/backend/fm-timeout-lib.sh +141 -0
  173. package/.agents/backend/fm-timing-lib.sh +179 -0
  174. package/.agents/backend/fm-tmux-lib.sh +291 -0
  175. package/.agents/backend/fm-tool-update-check.sh +898 -0
  176. package/.agents/backend/fm-trace-context-lib.sh +227 -0
  177. package/.agents/backend/fm-transition-lib.sh +103 -0
  178. package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
  179. package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
  180. package/.agents/backend/fm-turnend-guard.sh +432 -0
  181. package/.agents/backend/fm-update.sh +110 -0
  182. package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
  183. package/.agents/backend/fm-voice-client.py +1373 -0
  184. package/.agents/backend/fm-voice-relay.py +1256 -0
  185. package/.agents/backend/fm-wake-drain.sh +592 -0
  186. package/.agents/backend/fm-wake-grant.sh +127 -0
  187. package/.agents/backend/fm-wake-lib.sh +1849 -0
  188. package/.agents/backend/fm-watch-arm.sh +602 -0
  189. package/.agents/backend/fm-watch-checkpoint.sh +109 -0
  190. package/.agents/backend/fm-watch.sh +1962 -0
  191. package/.agents/backend/fm-x-dismiss.sh +116 -0
  192. package/.agents/backend/fm-x-followup.sh +286 -0
  193. package/.agents/backend/fm-x-lib.sh +1000 -0
  194. package/.agents/backend/fm-x-link.sh +234 -0
  195. package/.agents/backend/fm-x-poll.sh +185 -0
  196. package/.agents/backend/fm-x-reply.sh +385 -0
  197. package/.agents/backend/fm_voice_frame.py +166 -0
  198. package/.agents/backend/fm_voice_records.py +574 -0
  199. package/.agents/backend/model-bridge.py +360 -0
  200. package/.agents/backend/opencode-go-bridge.py +30 -0
  201. package/.agents/bus/README.md +12 -0
  202. package/.agents/bus/messages.json +1 -0
  203. package/.agents/config/agents.machine.example.yaml +16 -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 +11 -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/filebrowser/.filebrowser.json +6 -0
  211. package/.agents/filebrowser/README.md +11 -0
  212. package/.agents/gateway/README.md +12 -0
  213. package/.agents/github/README.md +15 -0
  214. package/.agents/github/webhooks/.gitkeep +0 -0
  215. package/.agents/github/workflows/.gitkeep +0 -0
  216. package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
  217. package/.agents/harness/opencode/plugins/package.json +4 -0
  218. package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
  219. package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
  220. package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
  221. package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
  222. package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
  223. package/.agents/memory/README.md +46 -0
  224. package/.agents/memory/runes_audit.md +5 -0
  225. package/.agents/migrations/0001-hodd-layout.sh +38 -0
  226. package/.agents/migrations/0002-a2a-mcp.sh +27 -0
  227. package/.agents/migrations/0003-private-data-separation.sh +121 -0
  228. package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
  229. package/.agents/migrations/0005-roots-out-of-tree.sh +158 -0
  230. package/.agents/sandbox/Dockerfile.utgard +19 -0
  231. package/.agents/sandbox/README.md +14 -0
  232. package/.agents/sandbox/utgard.config.json +14 -0
  233. package/.agents/skills/README.md +59 -0
  234. package/.agents/skills/bragi-marketing/README.md +3 -0
  235. package/.agents/skills/bragi-marketing/SKILL.md +82 -0
  236. package/.agents/skills/eindri-homes/README.md +3 -0
  237. package/.agents/skills/eindri-homes/SKILL.md +291 -0
  238. package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
  239. package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
  240. package/.agents/skills/frigg-consent/README.md +3 -0
  241. package/.agents/skills/frigg-consent/SKILL.md +56 -0
  242. package/.agents/skills/galdr-ymirsystem/README.md +3 -0
  243. package/.agents/skills/galdr-ymirsystem/SKILL.md +159 -0
  244. package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
  245. package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
  246. package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
  247. package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
  248. package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
  249. package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
  250. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +736 -0
  251. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
  252. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
  253. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
  254. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
  255. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
  256. package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +539 -0
  257. package/.agents/skills/galdr-ymirsystem/assets/installation.md +763 -0
  258. package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
  259. package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
  260. package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +163 -0
  261. package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +415 -0
  262. package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
  263. package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +133 -0
  264. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
  265. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
  266. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +101 -0
  267. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
  268. package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
  269. package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
  270. package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
  271. package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
  272. package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
  273. package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
  274. package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
  275. package/.agents/skills/galdr-ymirsystem/assets/smidja.md +180 -0
  276. package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
  277. package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
  278. package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
  279. package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +503 -0
  280. package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
  281. package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
  282. package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
  283. package/.agents/skills/gjallarhorn-relay/README.md +3 -0
  284. package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
  285. package/.agents/skills/groa-update/SKILL.md +90 -0
  286. package/.agents/skills/hamr-adapters/README.md +3 -0
  287. package/.agents/skills/hamr-adapters/SKILL.md +123 -0
  288. package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
  289. package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
  290. package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
  291. package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
  292. package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
  293. package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
  294. package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
  295. package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
  296. package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
  297. package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
  298. package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
  299. package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
  300. package/.agents/skills/herdr-panes/README.md +3 -0
  301. package/.agents/skills/herdr-panes/SKILL.md +92 -0
  302. package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
  303. package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
  304. package/.agents/skills/hnoss-design/DESIGN.md +19 -0
  305. package/.agents/skills/hnoss-design/README.md +3 -0
  306. package/.agents/skills/hnoss-design/SKILL.md +83 -0
  307. package/.agents/skills/hvild-afk/README.md +3 -0
  308. package/.agents/skills/hvild-afk/SKILL.md +241 -0
  309. package/.agents/skills/jord-projects/README.md +3 -0
  310. package/.agents/skills/jord-projects/SKILL.md +92 -0
  311. package/.agents/skills/lifecycle/SKILL.md +56 -0
  312. package/.agents/skills/lifecycle/smoke_test.sh +57 -0
  313. package/.agents/skills/lifecycle/start.sh +21 -0
  314. package/.agents/skills/lifecycle/status.sh +35 -0
  315. package/.agents/skills/lifecycle/stop.sh +16 -0
  316. package/.agents/skills/muninn-stow/README.md +3 -0
  317. package/.agents/skills/muninn-stow/SKILL.md +309 -0
  318. package/.agents/skills/no-mistakes/SKILL.md +379 -0
  319. package/.agents/skills/nornir-schedule/README.md +3 -0
  320. package/.agents/skills/nornir-schedule/SKILL.md +25 -0
  321. package/.agents/skills/nornir-schedule/assets/events.md +137 -0
  322. package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
  323. package/.agents/skills/nsr-compliance/README.md +3 -0
  324. package/.agents/skills/nsr-compliance/SKILL.md +29 -0
  325. package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
  326. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
  327. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
  328. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
  329. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
  330. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
  331. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
  332. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
  333. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
  334. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
  335. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
  336. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
  337. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
  338. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
  339. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
  340. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
  341. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
  342. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
  343. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
  344. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
  345. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
  346. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
  347. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
  348. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  349. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
  350. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
  351. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
  352. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
  353. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
  354. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
  355. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
  356. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
  357. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
  358. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
  359. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
  360. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
  361. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
  362. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
  363. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
  364. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
  365. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
  366. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
  367. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
  368. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
  369. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
  370. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
  371. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
  372. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
  373. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
  374. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
  375. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
  376. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
  377. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
  378. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
  379. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
  380. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
  381. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
  382. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
  383. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
  384. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
  385. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
  386. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
  387. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
  388. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
  389. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
  390. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
  391. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
  392. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
  393. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
  394. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
  395. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
  396. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
  397. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
  398. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
  399. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
  400. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
  401. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
  402. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
  403. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
  404. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
  405. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
  406. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
  407. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
  408. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
  409. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
  410. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
  411. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
  412. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
  413. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
  414. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
  415. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
  416. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
  417. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
  418. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
  419. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
  420. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
  421. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
  422. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
  423. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
  424. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
  425. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
  426. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
  427. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
  428. package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
  429. package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
  430. package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
  431. package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
  432. package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
  433. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
  434. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
  435. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
  436. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
  437. package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
  438. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
  439. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
  440. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
  441. package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
  442. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
  443. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
  444. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
  445. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
  446. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
  447. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
  448. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
  449. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  450. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
  451. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
  452. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
  453. package/.agents/skills/pr-ops/README.md +3 -0
  454. package/.agents/skills/pr-ops/SKILL.md +109 -0
  455. package/.agents/skills/ratatoskr-a2a/README.md +3 -0
  456. package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
  457. package/.agents/skills/rules-check-drift/SKILL.md +81 -0
  458. package/.agents/skills/saga-bearings/README.md +3 -0
  459. package/.agents/skills/saga-bearings/SKILL.md +26 -0
  460. package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
  461. package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
  462. package/.agents/skills/saga-bearings/assets/recap.md +56 -0
  463. package/.agents/skills/syn-recovery/README.md +3 -0
  464. package/.agents/skills/syn-recovery/SKILL.md +54 -0
  465. package/.agents/skills/tyr-check/README.md +3 -0
  466. package/.agents/skills/tyr-check/SKILL.md +218 -0
  467. package/.agents/skills/urdh-hold/README.md +3 -0
  468. package/.agents/skills/urdh-hold/SKILL.md +25 -0
  469. package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
  470. package/.agents/skills/urdh-hold/assets/hold.md +54 -0
  471. package/.agents/skills/vor-diagnostics/README.md +3 -0
  472. package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
  473. package/.agents/skills/ymir-host/README.md +3 -0
  474. package/.agents/skills/ymir-host/SKILL.md +35 -0
  475. package/.agents/skills/ymir-host/assets/desktop.md +36 -0
  476. package/.agents/skills/ymir-host/assets/install.md +69 -0
  477. package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
  478. package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
  479. package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
  480. package/.agents/skills/ymir-host/assets/update.md +64 -0
  481. package/.agents/tests/assets/board-render-harness.mjs +117 -0
  482. package/.agents/tests/cmux-test-safety.sh +51 -0
  483. package/.agents/tests/fixtures.sh +291 -0
  484. package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
  485. package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
  486. package/.agents/tests/fm-afk-launch.test.sh +961 -0
  487. package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
  488. package/.agents/tests/fm-afk-return.test.sh +283 -0
  489. package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
  490. package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
  491. package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
  492. package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
  493. package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
  494. package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
  495. package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
  496. package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
  497. package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
  498. package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
  499. package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
  500. package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
  501. package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
  502. package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
  503. package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
  504. package/.agents/tests/fm-backend-orca.test.sh +1357 -0
  505. package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
  506. package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
  507. package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
  508. package/.agents/tests/fm-backend.test.sh +1142 -0
  509. package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
  510. package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
  511. package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
  512. package/.agents/tests/fm-bearings-board.test.sh +414 -0
  513. package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
  514. package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
  515. package/.agents/tests/fm-bootstrap.test.sh +1178 -0
  516. package/.agents/tests/fm-branch-supervision.test.sh +552 -0
  517. package/.agents/tests/fm-brief.test.sh +785 -0
  518. package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
  519. package/.agents/tests/fm-busy-state.test.sh +465 -0
  520. package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
  521. package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
  522. package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
  523. package/.agents/tests/fm-check-unregister.test.sh +198 -0
  524. package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
  525. package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
  526. package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
  527. package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
  528. package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
  529. package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
  530. package/.agents/tests/fm-composer-ghost.test.sh +716 -0
  531. package/.agents/tests/fm-composer-lib.test.sh +666 -0
  532. package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
  533. package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
  534. package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
  535. package/.agents/tests/fm-control.test.sh +910 -0
  536. package/.agents/tests/fm-crew-state.test.sh +1610 -0
  537. package/.agents/tests/fm-cursor-harness.test.sh +404 -0
  538. package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
  539. package/.agents/tests/fm-cursor-primary.test.sh +708 -0
  540. package/.agents/tests/fm-daemon.test.sh +2740 -0
  541. package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
  542. package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
  543. package/.agents/tests/fm-extension-binding.test.sh +2187 -0
  544. package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
  545. package/.agents/tests/fm-fleet-sync.test.sh +721 -0
  546. package/.agents/tests/fm-gate-refuse.test.sh +356 -0
  547. package/.agents/tests/fm-gitignore-config.test.sh +89 -0
  548. package/.agents/tests/fm-gotmp.test.sh +230 -0
  549. package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
  550. package/.agents/tests/fm-grok-harness.test.sh +117 -0
  551. package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
  552. package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
  553. package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
  554. package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
  555. package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
  556. package/.agents/tests/fm-herdr-lab.test.sh +243 -0
  557. package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
  558. package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
  559. package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
  560. package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
  561. package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
  562. package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
  563. package/.agents/tests/fm-kimi-harness.test.sh +687 -0
  564. package/.agents/tests/fm-lint-workflows.test.sh +532 -0
  565. package/.agents/tests/fm-lint.test.sh +1023 -0
  566. package/.agents/tests/fm-muse-harness.test.sh +932 -0
  567. package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
  568. package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
  569. package/.agents/tests/fm-on.test.sh +507 -0
  570. package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
  571. package/.agents/tests/fm-operational-input.test.sh +160 -0
  572. package/.agents/tests/fm-peek-remote.test.sh +110 -0
  573. package/.agents/tests/fm-pending-reply.test.sh +1288 -0
  574. package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
  575. package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
  576. package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
  577. package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
  578. package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
  579. package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
  580. package/.agents/tests/fm-pr-merge.test.sh +2135 -0
  581. package/.agents/tests/fm-procevent-quota.test.sh +225 -0
  582. package/.agents/tests/fm-procevent-when.test.sh +406 -0
  583. package/.agents/tests/fm-procevent.test.sh +1745 -0
  584. package/.agents/tests/fm-project-origin.test.sh +110 -0
  585. package/.agents/tests/fm-public-followup.test.sh +2272 -0
  586. package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
  587. package/.agents/tests/fm-quota-choose.test.sh +626 -0
  588. package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
  589. package/.agents/tests/fm-remote-doctor.test.sh +625 -0
  590. package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
  591. package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
  592. package/.agents/tests/fm-remote-job.test.sh +768 -0
  593. package/.agents/tests/fm-remote-reply.test.sh +520 -0
  594. package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
  595. package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
  596. package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
  597. package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
  598. package/.agents/tests/fm-review-diff.test.sh +176 -0
  599. package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
  600. package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
  601. package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
  602. package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
  603. package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
  604. package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
  605. package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
  606. package/.agents/tests/fm-send-inbox.test.sh +352 -0
  607. package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
  608. package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
  609. package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
  610. package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
  611. package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
  612. package/.agents/tests/fm-send-settle.test.sh +147 -0
  613. package/.agents/tests/fm-send-strict.test.sh +241 -0
  614. package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
  615. package/.agents/tests/fm-session-start.test.sh +2515 -0
  616. package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
  617. package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
  618. package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
  619. package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
  620. package/.agents/tests/fm-spawn-batch.test.sh +148 -0
  621. package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
  622. package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
  623. package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
  624. package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
  625. package/.agents/tests/fm-startup-network.test.sh +712 -0
  626. package/.agents/tests/fm-stow-cascade.test.sh +370 -0
  627. package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
  628. package/.agents/tests/fm-supervision-events.test.sh +156 -0
  629. package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
  630. package/.agents/tests/fm-tangle-guard.test.sh +277 -0
  631. package/.agents/tests/fm-task-delivery.test.sh +420 -0
  632. package/.agents/tests/fm-task-inbox.test.sh +513 -0
  633. package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
  634. package/.agents/tests/fm-teardown.test.sh +2665 -0
  635. package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
  636. package/.agents/tests/fm-test-fixtures.test.sh +132 -0
  637. package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
  638. package/.agents/tests/fm-test-run.test.sh +1196 -0
  639. package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
  640. package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
  641. package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
  642. package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
  643. package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
  644. package/.agents/tests/fm-transition-lib.test.sh +54 -0
  645. package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
  646. package/.agents/tests/fm-update.test.sh +304 -0
  647. package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
  648. package/.agents/tests/fm-voice-relay.test.sh +4741 -0
  649. package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
  650. package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
  651. package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
  652. package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
  653. package/.agents/tests/fm-wake-queue.test.sh +1224 -0
  654. package/.agents/tests/fm-watch-arm.test.sh +821 -0
  655. package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
  656. package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
  657. package/.agents/tests/fm-watch-triage.test.sh +3889 -0
  658. package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
  659. package/.agents/tests/fm-x-mode.test.sh +3042 -0
  660. package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
  661. package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
  662. package/.agents/tests/herdr-test-safety.sh +42 -0
  663. package/.agents/tests/lib.sh +315 -0
  664. package/.agents/tests/remote-herdr-fixture.sh +125 -0
  665. package/.agents/tests/secondmate-helpers.sh +193 -0
  666. package/.agents/tests/smoke.test.sh +93 -0
  667. package/.agents/tests/wake-helpers.sh +338 -0
  668. package/.agents/tests/zellij-test-safety.sh +60 -0
  669. package/.agents/tools/README.md +10 -0
  670. package/.agents/tools/bin/tasks-cli +267 -0
  671. package/.agents/tools/tasks-cli.ts +216 -0
  672. package/AGENTS.md +498 -0
  673. package/CHANGELOG.md +1913 -0
  674. package/LICENSE +1 -1
  675. package/NOTICE +63 -0
  676. package/README.md +114 -60
  677. package/RULES/01-domains.md +51 -0
  678. package/RULES/02-agents.md +78 -0
  679. package/RULES/03-houses.md +61 -0
  680. package/RULES/04-hoard.md +97 -0
  681. package/RULES/05-platforms.md +67 -0
  682. package/RULES/06-append-only.md +108 -0
  683. package/RULES/07-config.md +101 -0
  684. package/RULES/08-delivery-gate.md +75 -0
  685. package/RULES/09-electron.md +58 -0
  686. package/RULES/README.md +22 -0
  687. package/bin/README.md +107 -0
  688. package/bin/a2a-mcp.sh +133 -0
  689. package/bin/a2a-serve.py +103 -0
  690. package/bin/a2a-serve.sh +8 -0
  691. package/bin/a2a-talk.sh +50 -0
  692. package/bin/a2abridge-ensure.sh +108 -0
  693. package/bin/agent-run.sh +43 -0
  694. package/bin/agents-config.sh +256 -0
  695. package/bin/apodex-smoke-test.sh +105 -0
  696. package/bin/bifrost-bridge.sh +131 -0
  697. package/bin/bifrost-ingress.sh +77 -0
  698. package/bin/bootstrap-macos.sh +112 -0
  699. package/bin/bootstrap-windows.ps1 +112 -0
  700. package/bin/branch-guard.sh +64 -0
  701. package/bin/brokk +73 -0
  702. package/bin/brokk-classify-lib.sh +1770 -0
  703. package/bin/brokk-lease-lib.sh +222 -0
  704. package/bin/brokk-lease.sh +193 -0
  705. package/bin/brokk-lint.sh +65 -0
  706. package/bin/brokk-send.sh +51 -0
  707. package/bin/brokk-update.sh +9 -0
  708. package/bin/brokk-wake-grant.sh +127 -0
  709. package/bin/brokk-wake-lib.sh +1849 -0
  710. package/bin/changelog-assemble.sh +154 -0
  711. package/bin/changelog-guard.sh +197 -0
  712. package/bin/crash-sense.sh +173 -0
  713. package/bin/design-check.sh +80 -0
  714. package/bin/design-icon.sh +153 -0
  715. package/bin/desktop-place.sh +224 -0
  716. package/bin/docs-guard.sh +39 -0
  717. package/bin/editor-place.sh +225 -0
  718. package/bin/eindri-acclaim.sh +47 -0
  719. package/bin/eindri-control.sh +28 -0
  720. package/bin/eindri-role.sh +83 -0
  721. package/bin/eindri-seat.sh +83 -0
  722. package/bin/eindri-seen.sh +54 -0
  723. package/bin/eindri-send.sh +15 -0
  724. package/bin/eindri-start.sh +98 -0
  725. package/bin/eindri-watch.sh +57 -0
  726. package/bin/einherjar-spawn.sh +576 -0
  727. package/bin/eir-doctor.sh +158 -0
  728. package/bin/erindi-brief.sh +271 -0
  729. package/bin/feature-inventory.sh +73 -0
  730. package/bin/fleet-apply.sh +81 -0
  731. package/bin/github-deploy.sh +94 -0
  732. package/bin/gjallarhorn-expose.sh +136 -0
  733. package/bin/gjallarhorn-notify.sh +71 -0
  734. package/bin/gjallarhorn-purge.sh +67 -0
  735. package/bin/gjallarhorn-tunnel.sh +85 -0
  736. package/bin/gleipnir-lock-lib.sh +284 -0
  737. package/bin/groa-update.sh +130 -0
  738. package/bin/hall-snapshot.sh +111 -0
  739. package/bin/hamr-harness.sh +246 -0
  740. package/bin/heimdall-ssh-keys.sh +374 -0
  741. package/bin/herdr-agents.py +44 -0
  742. package/bin/herdr-ensure.sh +104 -0
  743. package/bin/herdr-run.sh +452 -0
  744. package/bin/hermes-ensure.sh +88 -0
  745. package/bin/hlidskjalf-agents.sh +172 -0
  746. package/bin/hlidskjalf-usage.sh +196 -0
  747. package/bin/hoard-guard.sh +135 -0
  748. package/bin/hoard-lib.sh +105 -0
  749. package/bin/hodd.sh +102 -0
  750. package/bin/host-sense.sh +199 -0
  751. package/bin/huginn-research-worker.sh +121 -0
  752. package/bin/local-model-lock.sh +70 -0
  753. package/bin/mimir-bridge.py +259 -0
  754. package/bin/mimir-bridge.sh +100 -0
  755. package/bin/mimir-ingest.sh +131 -0
  756. package/bin/mimir-reflect.sh +27 -0
  757. package/bin/mimir.sh +113 -0
  758. package/bin/mjollnir-webhook.sh +72 -0
  759. package/bin/mjollnir.sh +104 -0
  760. package/bin/model-hardware.sh +96 -0
  761. package/bin/model-resolve.sh +160 -0
  762. package/bin/models-detect.sh +178 -0
  763. package/bin/nornir-cron-start.sh +162 -0
  764. package/bin/nornir-job-daily-briefing.sh +191 -0
  765. package/bin/nornir-job-git-sync.sh +152 -0
  766. package/bin/nornir-job-hall-snapshot.sh +28 -0
  767. package/bin/nornir-job-memory-housekeeping.sh +114 -0
  768. package/bin/nornir-job-nsr-compliance.sh +54 -0
  769. package/bin/nornir-job-observer.sh +166 -0
  770. package/bin/nornir-job-skillopt-sleep.sh +76 -0
  771. package/bin/npm-publish.sh +136 -0
  772. package/bin/omarchy-hook-install.sh +60 -0
  773. package/bin/omarchy-install.sh +161 -0
  774. package/bin/omarchy-plugins.sh +187 -0
  775. package/bin/omarchy-sense.sh +162 -0
  776. package/bin/perm-guard.sh +30 -0
  777. package/bin/pi-agent.sh +68 -0
  778. package/bin/pi-ensure.sh +77 -0
  779. package/bin/pi-local.sh +37 -0
  780. package/bin/pi-model-agnostic.sh +71 -0
  781. package/bin/pi-seat.sh +63 -0
  782. package/bin/prereq-ensure.sh +166 -0
  783. package/bin/private-guard.sh +58 -0
  784. package/bin/project-git.sh +69 -0
  785. package/bin/public-guard.sh +71 -0
  786. package/bin/ratatoskr.sh +49 -0
  787. package/bin/realm-lib.sh +32 -0
  788. package/bin/repo-scrub.sh +62 -0
  789. package/bin/rodd-operational-input.sh +250 -0
  790. package/bin/runes-append.sh +198 -0
  791. package/bin/saga-session-start.sh +172 -0
  792. package/bin/saga-sessionstart-run.sh +79 -0
  793. package/bin/saga-wake-drain.sh +41 -0
  794. package/bin/secret-guard.sh +63 -0
  795. package/bin/sessrumnir-ensure.sh +100 -0
  796. package/bin/sessrumnir-sync.sh +237 -0
  797. package/bin/sessrumnir.sh +104 -0
  798. package/bin/skillopt-setup.sh +76 -0
  799. package/bin/skuld-branch-outcome.sh +203 -0
  800. package/bin/skuld-branch-prompt.sh +101 -0
  801. package/bin/smidja-bootstrap.sh +94 -0
  802. package/bin/smidja-observe.sh +86 -0
  803. package/bin/syn-arm-pretool-check.sh +57 -0
  804. package/bin/syn-asset-pretool-check.sh +89 -0
  805. package/bin/syn-cd-pretool-check.sh +28 -0
  806. package/bin/syn-guard-pretool-check.sh +83 -0
  807. package/bin/syn-spawn-pretool-check.sh +58 -0
  808. package/bin/syn-turnend-guard.sh +36 -0
  809. package/bin/syn-watch-arm.sh +99 -0
  810. package/bin/tailscale-sync.sh +203 -0
  811. package/bin/telegram-bot.sh +82 -0
  812. package/bin/toolchain.sh +74 -0
  813. package/bin/ui-truth-check.sh +75 -0
  814. package/bin/utgard.sh +111 -0
  815. package/bin/valhalla.sh +115 -0
  816. package/bin/valknut-load.sh +298 -0
  817. package/bin/vor-crew-state.sh +124 -0
  818. package/bin/wedge-notify.sh +60 -0
  819. package/bin/workspace-provision.sh +80 -0
  820. package/bin/workspace-rag.sh +163 -0
  821. package/bin/wyrd-db.sh +47 -0
  822. package/bin/yggdrasil.sh +152 -0
  823. package/bin/ymir-edit.sh +145 -0
  824. package/bin/ymir-install.sh +806 -0
  825. package/bin/ymir-invite.sh +93 -0
  826. package/bin/ymir-isolation.sh +88 -0
  827. package/bin/ymir-migrate.sh +61 -0
  828. package/bin/ymir-plan.sh +356 -0
  829. package/bin/ymir-platform.sh +348 -0
  830. package/bin/ymir-say.sh +110 -0
  831. package/bin/ymir-setup-auth.sh +119 -0
  832. package/bin/ymir-validate.sh +193 -0
  833. package/bin/ymir.js +74 -16
  834. package/docs/Architecture.md +339 -0
  835. package/docs/design.md +351 -0
  836. package/docs/installations/README.md +63 -0
  837. package/docs/installations/ymir-home.md +93 -0
  838. package/docs/integration.md +105 -0
  839. package/docs/lore.md +1060 -0
  840. package/docs/research/agent-harnesses.md +290 -0
  841. package/docs/runbooks/README.md +19 -0
  842. package/docs/runbooks/a2a.md +91 -0
  843. package/docs/runbooks/agent-permissions.md +99 -0
  844. package/docs/runbooks/agents.md +57 -0
  845. package/docs/runbooks/models.md +69 -0
  846. package/docs/runbooks/realm-onboarding.md +60 -0
  847. package/docs/runbooks/secrets-and-hoard.md +60 -0
  848. package/docs/runbooks/updates-and-migrations.md +110 -0
  849. package/docs/runbooks/worktrees-isolation.md +72 -0
  850. package/docs/session-start.md +105 -0
  851. package/docs/skillopt-integration.md +321 -0
  852. package/docs/sync-system.md +66 -0
  853. package/docs/workspaces.md +104 -0
  854. package/docs/ymir-rut.md +260 -0
  855. package/package.json +54 -14
  856. package/packaging/build.sh +74 -0
  857. package/packaging/macos/Ymir Installer.command +43 -0
  858. package/packaging/windows/ymir-setup.nsi +74 -0
  859. package/scripts/README.md +22 -0
  860. package/scripts/electron.sh +269 -0
  861. package/scripts/lower.sh +34 -0
  862. package/scripts/raise.sh +42 -0
  863. package/scripts/start.sh +217 -0
  864. package/scripts/stop.sh +113 -0
  865. package/install.sh +0 -35
@@ -0,0 +1,1190 @@
1
+ #!/usr/bin/env bash
2
+ # Behavior tests for the Claude Stop-owned watcher auto-arm
3
+ # (bin/fm-claude-stop-autoarm.sh, docs/watcher-continuity.md).
4
+ #
5
+ # The hook fires as a Claude asyncRewake Stop hook. These tests run it hermetically
6
+ # as a child of a fake harness (a bash symlink named "claude") whose pid is
7
+ # written into the fixture home's state/.lock for ordinary owned-lock cases.
8
+ # Stale-owner cases instead leave a dead recorded pid for the hook to reclaim
9
+ # through the real fm-lock.sh path. The arm wrapper is a per-test fixture, so no
10
+ # real watcher, model, or fleet state is touched.
11
+ # shellcheck disable=SC2016 # single quotes are deliberate: $FM_HOME expands inside the fake harness child, and grep needles are literal strings
12
+ set -u
13
+
14
+ # shellcheck source=tests/lib.sh
15
+ . "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
16
+
17
+ TMP_ROOT=$(fm_test_tmproot fm-claude-stop-autoarm)
18
+ fm_git_identity fmtest fmtest@example.invalid
19
+
20
+ FAKEBIN=$(fm_fakebin "$TMP_ROOT/fakebin")
21
+ ln -s /bin/bash "$FAKEBIN/claude"
22
+ FAKE_CLAUDE="$FAKEBIN/claude"
23
+ export FAKE_CLAUDE
24
+
25
+ # Copy the hook and its sourced dependencies into a fixture checkout.
26
+ install_autoarm_scripts() {
27
+ local dir=$1
28
+ mkdir -p "$dir/bin"
29
+ cp "$ROOT/bin/fm-claude-stop-autoarm.sh" "$dir/bin/fm-claude-stop-autoarm.sh"
30
+ cp "$ROOT/bin/fm-primary-scope-lib.sh" "$dir/bin/fm-primary-scope-lib.sh"
31
+ cp "$ROOT/bin/fm-supervision-lib.sh" "$dir/bin/fm-supervision-lib.sh"
32
+ cp "$ROOT/bin/fm-wake-lib.sh" "$dir/bin/fm-wake-lib.sh"
33
+ cp "$ROOT/bin/fm-session-lock-lib.sh" "$dir/bin/fm-session-lock-lib.sh"
34
+ cp "$ROOT/bin/fm-cursor-lib.sh" "$dir/bin/fm-cursor-lib.sh"
35
+ cp "$ROOT/bin/fm-hook-host-lib.sh" "$dir/bin/fm-hook-host-lib.sh"
36
+ cp "$ROOT/bin/fm-lock.sh" "$dir/bin/fm-lock.sh"
37
+ chmod +x "$dir/bin/fm-claude-stop-autoarm.sh" "$dir/bin/fm-lock.sh"
38
+ }
39
+
40
+ make_primary_dir() {
41
+ local dir=$1
42
+ mkdir -p "$dir/state"
43
+ git init -q "$dir"
44
+ git -C "$dir" commit -q --allow-empty -m init
45
+ : > "$dir/AGENTS.md"
46
+ install_autoarm_scripts "$dir"
47
+ printf '%s\n' "$dir"
48
+ }
49
+
50
+ make_secondmate_dir() {
51
+ local dir=$1
52
+ make_primary_dir "$dir" >/dev/null
53
+ printf 'sm-autoarm-1\n' > "$dir/.fm-secondmate-home"
54
+ printf '%s\n' "$dir"
55
+ }
56
+
57
+ # A genuine linked git worktree: the shape every crewmate/scout task worktree
58
+ # has (git-dir != git-common-dir), which must keep the hook inert.
59
+ make_crewmate_worktree_dir() {
60
+ local base=$1 dir=$2
61
+ fm_git_worktree "$base" "$dir" fm/autoarm-test-branch
62
+ mkdir -p "$dir/state"
63
+ : > "$dir/AGENTS.md"
64
+ install_autoarm_scripts "$dir"
65
+ printf '%s\n' "$dir"
66
+ }
67
+
68
+ # Run the hook as a child of the fake harness holding the fixture home's
69
+ # session lock. $1 = fixture dir. Any extra env assignments must be exported
70
+ # before invocation. Captures stdout+stderr; exit code on stdout of the caller.
71
+ run_autoarm() {
72
+ local dir=$1 rc=0
73
+ printf '%s\n' '{"session_id":"sess-autoarm","stop_hook_active":false}' \
74
+ | FM_HOME="$dir" "$FAKE_CLAUDE" -c '
75
+ printf "%s\n" "$$" > "$FM_HOME/state/.lock"
76
+ "$FM_HOME/bin/fm-claude-stop-autoarm.sh"
77
+ ' 2>&1 || rc=$?
78
+ printf 'RC=%s\n' "$rc" >&2
79
+ return "$rc"
80
+ }
81
+
82
+ # Arm fixture variants, installed per test as <dir>/bin/fm-watch-arm.sh.
83
+ write_arm_fixture() {
84
+ local dir=$1 kind=$2
85
+ case "$kind" in
86
+ actionable)
87
+ cat > "$dir/bin/fm-watch-arm.sh" <<'SH'
88
+ #!/usr/bin/env bash
89
+ echo "$$" >> "$FM_HOME/state/arm-ran"
90
+ printf 'watcher: started pid=%s (beacon fresh)\n' "$$"
91
+ printf 'stale: fixture-win actionable\n'
92
+ exit 0
93
+ SH
94
+ ;;
95
+ failed)
96
+ cat > "$dir/bin/fm-watch-arm.sh" <<'SH'
97
+ #!/usr/bin/env bash
98
+ echo "$$" >> "$FM_HOME/state/arm-ran"
99
+ printf 'watcher: FAILED - no live watcher with a fresh beacon\n'
100
+ exit 1
101
+ SH
102
+ ;;
103
+ clean)
104
+ cat > "$dir/bin/fm-watch-arm.sh" <<'SH'
105
+ #!/usr/bin/env bash
106
+ echo "$$" >> "$FM_HOME/state/arm-ran"
107
+ printf 'watcher: attached pid=%s (beacon 2s)\n' "$$"
108
+ exit 0
109
+ SH
110
+ ;;
111
+ benign-live)
112
+ cat > "$dir/bin/fm-watch-arm.sh" <<'SH'
113
+ #!/usr/bin/env bash
114
+ echo "$$" >> "$FM_HOME/state/arm-ran"
115
+ printf 'watcher: FAILED - cycle ended without an actionable reason\n'
116
+ exit 1
117
+ SH
118
+ ;;
119
+ reset-boundary)
120
+ cat > "$dir/bin/fm-watch-arm.sh" <<'SH'
121
+ #!/usr/bin/env bash
122
+ echo "$$" >> "$FM_HOME/state/arm-ran"
123
+ : > "$FM_HOME/state/arm-waiting"
124
+ while [ ! -e "$FM_HOME/state/arm-release" ]; do sleep 0.02; done
125
+ printf 'watcher: FAILED - cycle ended without an actionable reason\n'
126
+ exit 1
127
+ SH
128
+ ;;
129
+ slow-actionable)
130
+ cat > "$dir/bin/fm-watch-arm.sh" <<'SH'
131
+ #!/usr/bin/env bash
132
+ echo "$$" >> "$FM_HOME/state/arm-ran"
133
+ sleep 2
134
+ printf 'watcher: started pid=%s (beacon fresh)\n' "$$"
135
+ printf 'signal: task.status done: slow fixture\n'
136
+ exit 0
137
+ SH
138
+ ;;
139
+ blocking-actionable)
140
+ cat > "$dir/bin/fm-watch-arm.sh" <<'SH'
141
+ #!/usr/bin/env bash
142
+ echo "$$" >> "$FM_HOME/state/arm-ran"
143
+ sleep 6
144
+ printf 'watcher: started pid=%s (beacon fresh)\n' "$$"
145
+ printf 'stale: fixture-win actionable\n'
146
+ exit 0
147
+ SH
148
+ ;;
149
+ supersede-then-fail)
150
+ cat > "$dir/bin/fm-watch-arm.sh" <<'SH'
151
+ #!/usr/bin/env bash
152
+ echo "$$" >> "$FM_HOME/state/arm-ran"
153
+ printf 'epoch=999 owner_pid=1 outcome=arming updated_at=%s\nfixture-superseder-identity\n' "$(date +%s)" \
154
+ > "$FM_HOME/state/.claude-autoarm-epoch"
155
+ printf 'watcher: FAILED - no live watcher with a fresh beacon\n'
156
+ exit 1
157
+ SH
158
+ ;;
159
+ meta-vanishes)
160
+ cat > "$dir/bin/fm-watch-arm.sh" <<'SH'
161
+ #!/usr/bin/env bash
162
+ echo "$$" >> "$FM_HOME/state/arm-ran"
163
+ rm -f "$FM_HOME/state/task.meta"
164
+ printf 'watcher: started pid=%s (beacon fresh)\n' "$$"
165
+ printf 'signal: task.status done: fixture\n'
166
+ exit 0
167
+ SH
168
+ ;;
169
+ afk-appears)
170
+ cat > "$dir/bin/fm-watch-arm.sh" <<'SH'
171
+ #!/usr/bin/env bash
172
+ echo "$$" >> "$FM_HOME/state/arm-ran"
173
+ : > "$FM_HOME/state/.afk"
174
+ printf 'watcher: started pid=%s (beacon fresh)\n' "$$"
175
+ printf 'stale: fixture-win actionable\n'
176
+ exit 0
177
+ SH
178
+ ;;
179
+ *)
180
+ echo "unknown arm fixture: $kind" >&2
181
+ return 2
182
+ ;;
183
+ esac
184
+ chmod +x "$dir/bin/fm-watch-arm.sh"
185
+ }
186
+
187
+ epoch_outcome() {
188
+ sed -n '1s/^.*outcome=\([a-z][a-z-]*\) .*$/\1/p' "$1/state/.claude-autoarm-epoch" 2>/dev/null || true
189
+ }
190
+
191
+ # Run the hook in the background under the fake harness, output captured to a
192
+ # file. Sets RUN_AUTOARM_BG_PID (a direct child of the calling shell, so the
193
+ # caller can `wait` on it for the hook's exit status).
194
+ RUN_AUTOARM_BG_PID=
195
+ run_autoarm_bg() {
196
+ local dir=$1 out=$2
197
+ printf '%s\n' '{"session_id":"sess-autoarm","stop_hook_active":false}' \
198
+ | FM_HOME="$dir" "$FAKE_CLAUDE" -c '
199
+ printf "%s\n" "$$" > "$FM_HOME/state/.lock"
200
+ "$FM_HOME/bin/fm-claude-stop-autoarm.sh"
201
+ ' > "$out" 2>&1 &
202
+ RUN_AUTOARM_BG_PID=$!
203
+ }
204
+
205
+ watcher_identity() {
206
+ local dir=$1 pid=$2
207
+ FM_STATE_OVERRIDE="$dir/state" bash -c '. "$1"; fm_pid_identity "$2"' _ "$dir/bin/fm-wake-lib.sh" "$pid"
208
+ }
209
+
210
+ record_watcher_lock() {
211
+ local dir=$1 pid=$2 identity=$3 root bin_dir
212
+ root=$dir
213
+ bin_dir=$(cd "$dir/bin" && pwd)
214
+ mkdir -p "$dir/state/.watch.lock"
215
+ printf '%s\n' "$pid" > "$dir/state/.watch.lock/pid"
216
+ printf '%s\n' "$root" > "$dir/state/.watch.lock/fm-home"
217
+ printf '%s\n' "$bin_dir/fm-watch.sh" > "$dir/state/.watch.lock/watcher-path"
218
+ printf '%s\n' "$identity" > "$dir/state/.watch.lock/pid-identity"
219
+ }
220
+
221
+ # --- registration contract ----------------------------------------------------
222
+
223
+ # --- scope and gates ----------------------------------------------------------
224
+
225
+ test_inert_in_child_worktree() {
226
+ local base dir out status
227
+ base="$TMP_ROOT/crew-base"
228
+ dir="$TMP_ROOT/crew-wt"
229
+ make_crewmate_worktree_dir "$base" "$dir" >/dev/null
230
+ : > "$dir/state/task.meta"
231
+ write_arm_fixture "$dir" actionable
232
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
233
+ expect_code 0 "$status" "hook must stay inert in a child task worktree"
234
+ [ ! -e "$dir/state/arm-ran" ] || fail "hook armed inside a child worktree"
235
+ [ ! -e "$dir/state/.claude-autoarm-epoch" ] || fail "hook wrote an epoch inside a child worktree"
236
+ pass "auto-arm: inert in a linked child worktree even when in-flight"
237
+ }
238
+
239
+ test_inert_without_session_lock() {
240
+ local dir out status
241
+ dir=$(make_primary_dir "$TMP_ROOT/no-lock")
242
+ : > "$dir/state/task.meta"
243
+ write_arm_fixture "$dir" actionable
244
+ # No state/.lock: run the hook directly (no fake harness, no lock file).
245
+ out=$(printf '%s\n' '{"session_id":"s"}' | FM_HOME="$dir" bash "$dir/bin/fm-claude-stop-autoarm.sh" 2>&1); status=$?
246
+ expect_code 0 "$status" "hook must stay inert when no session holds the home lock"
247
+ [ ! -e "$dir/state/arm-ran" ] || fail "hook armed without a session lock"
248
+ pass "auto-arm: inert with no session lock"
249
+ }
250
+
251
+ test_reclaims_stale_session_lock_before_arming() {
252
+ local dir out status expected_owner actual_owner
253
+ dir=$(make_primary_dir "$TMP_ROOT/stale-lock")
254
+ : > "$dir/state/task.meta"
255
+ printf '9999999\n' > "$dir/state/.lock"
256
+ write_arm_fixture "$dir" actionable
257
+ out=$(printf '%s\n' '{"session_id":"stale"}' \
258
+ | FM_HOME="$dir" "$FAKE_CLAUDE" -c '
259
+ printf "%s\n" "$$" > "$FM_HOME/state/expected-owner"
260
+ "$FM_HOME/bin/fm-claude-stop-autoarm.sh"
261
+ ' 2>&1); status=$?
262
+ expect_code 2 "$status" "a dead recorded session owner must be reclaimed before the actionable rewake"
263
+ expected_owner=$(cat "$dir/state/expected-owner")
264
+ actual_owner=$(cat "$dir/state/.lock")
265
+ [ "$actual_owner" = "$expected_owner" ] || fail "stale session lock was not claimed by the current harness: expected $expected_owner, got $actual_owner"
266
+ [ -e "$dir/state/arm-ran" ] || fail "hook did not arm after reclaiming the stale session lock"
267
+ [ "$(epoch_outcome "$dir")" = rewake ] || fail "stale-lock recovery must record outcome=rewake"
268
+ pass "auto-arm: a demonstrably dead recorded session owner is reclaimed through fm-lock.sh before arming"
269
+ }
270
+
271
+ test_inert_when_lock_held_by_other_harness() {
272
+ local dir other out status owner_after
273
+ dir=$(make_primary_dir "$TMP_ROOT/other-lock")
274
+ : > "$dir/state/task.meta"
275
+ write_arm_fixture "$dir" actionable
276
+ # The trailing no-op keeps the fake harness process alive instead of allowing
277
+ # bash to exec the final sleep into a non-harness process.
278
+ "$FAKE_CLAUDE" -c 'sleep 60; :' &
279
+ other=$!
280
+ printf '%s\n' "$other" > "$dir/state/.lock"
281
+ out=$(printf '%s\n' '{"session_id":"s"}' | FM_HOME="$dir" "$FAKE_CLAUDE" -c '"$FM_HOME/bin/fm-claude-stop-autoarm.sh"' 2>&1); status=$?
282
+ owner_after=$(cat "$dir/state/.lock")
283
+ kill "$other" 2>/dev/null || true
284
+ wait "$other" 2>/dev/null || true
285
+ expect_code 0 "$status" "hook must stay inert when another live harness holds the session lock"
286
+ [ "$owner_after" = "$other" ] || fail "hook replaced another live harness owner: expected $other, got $owner_after"
287
+ [ ! -e "$dir/state/arm-ran" ] || fail "hook armed while another session owned the lock"
288
+ [ ! -e "$dir/state/.claude-autoarm-epoch" ] || fail "hook wrote an epoch while another session owned the lock"
289
+ pass "auto-arm: inert without arm, rewake, or lock replacement when another live harness owns the home"
290
+ }
291
+
292
+ test_inert_when_afk() {
293
+ local dir out status
294
+ dir=$(make_primary_dir "$TMP_ROOT/afk")
295
+ : > "$dir/state/task.meta"
296
+ : > "$dir/state/.afk"
297
+ : > "$dir/state/.claude-autoarm-failure-notified"
298
+ : > "$dir/state/.claude-autoarm-failure-alarmed"
299
+ write_arm_fixture "$dir" actionable
300
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
301
+ expect_code 0 "$status" "hook must never arm or rewake while away mode owns triage"
302
+ [ ! -e "$dir/state/arm-ran" ] || fail "hook armed while state/.afk existed"
303
+ assert_present "$dir/state/.claude-autoarm-failure-notified" "AFK without positive recovery reset the failure notice"
304
+ assert_present "$dir/state/.claude-autoarm-failure-alarmed" "AFK without positive recovery reset the attended alarm"
305
+ pass "auto-arm: inert while AFK owns supervision"
306
+ }
307
+
308
+ test_stale_lock_recovery_preserves_afk_and_need_gates() {
309
+ local afk_dir idle_dir out status
310
+ afk_dir=$(make_primary_dir "$TMP_ROOT/stale-afk")
311
+ : > "$afk_dir/state/task.meta"
312
+ : > "$afk_dir/state/.afk"
313
+ printf '9999999\n' > "$afk_dir/state/.lock"
314
+ write_arm_fixture "$afk_dir" actionable
315
+ out=$(printf '%s\n' '{"session_id":"stale-afk"}' | FM_HOME="$afk_dir" "$FAKE_CLAUDE" -c '"$FM_HOME/bin/fm-claude-stop-autoarm.sh"' 2>&1); status=$?
316
+ expect_code 0 "$status" "a stale owner must not widen the AFK gate"
317
+ [ "$(cat "$afk_dir/state/.lock")" = 9999999 ] || fail "AFK stale lock was reclaimed despite away ownership"
318
+ [ ! -e "$afk_dir/state/arm-ran" ] || fail "stale AFK home armed"
319
+
320
+ idle_dir=$(make_primary_dir "$TMP_ROOT/stale-idle")
321
+ printf '9999999\n' > "$idle_dir/state/.lock"
322
+ write_arm_fixture "$idle_dir" actionable
323
+ out=$(printf '%s\n' '{"session_id":"stale-idle"}' | FM_HOME="$idle_dir" "$FAKE_CLAUDE" -c '"$FM_HOME/bin/fm-claude-stop-autoarm.sh"' 2>&1); status=$?
324
+ expect_code 0 "$status" "a stale owner must not widen the supervision-need gate"
325
+ [ "$(cat "$idle_dir/state/.lock")" = 9999999 ] || fail "idle stale lock was reclaimed without supervision need"
326
+ [ ! -e "$idle_dir/state/arm-ran" ] || fail "stale idle home armed"
327
+ pass "auto-arm: stale-owner recovery leaves the AFK and supervision-need gates unchanged"
328
+ }
329
+
330
+ test_resolves_outermost_claude_pid_in_nested_bgspare_chain() {
331
+ local dir out status inner_pid lock_pid
332
+ dir=$(make_primary_dir "$TMP_ROOT/nested-chain")
333
+ : > "$dir/state/task.meta"
334
+ write_arm_fixture "$dir" actionable
335
+ # A genuine multi-level contiguous claude-named ancestry: the hook fires
336
+ # inside an inner fake-claude process (its recorded pid is distinct from its
337
+ # own parent, a second, outer fake-claude process holding the session lock -
338
+ # the bg-spare shape). Only the outer pid may own the lock; a
339
+ # first-match-wins walk would resolve to the inner pid instead and leave the
340
+ # hook inert. The inner process records its own pid before running the hook
341
+ # so bash cannot tail-exec-collapse it into the outer pid, which would
342
+ # collapse the two-hop chain this test depends on down to one hop.
343
+ out=$(printf '%s\n' '{"session_id":"nested"}' \
344
+ | FM_HOME="$dir" "$FAKE_CLAUDE" -c '
345
+ printf "%s\n" "$$" > "$FM_HOME/state/.lock"
346
+ "$FAKE_CLAUDE" -c "
347
+ printf \"%s\n\" \"\$\$\" > \"\$FM_HOME/state/inner-pid\"
348
+ \"\$FM_HOME/bin/fm-claude-stop-autoarm.sh\"
349
+ "
350
+ ' 2>&1); status=$?
351
+ inner_pid=$(cat "$dir/state/inner-pid" 2>/dev/null || true)
352
+ lock_pid=$(cat "$dir/state/.lock" 2>/dev/null || true)
353
+ [ -n "$inner_pid" ] && [ "$inner_pid" != "$lock_pid" ] \
354
+ || fail "test setup did not produce a genuine two-hop claude chain: inner=$inner_pid lock=$lock_pid"
355
+ expect_code 2 "$status" "a nested contiguous claude ancestry must resolve to the outer lock-owning pid and arm"
356
+ [ -e "$dir/state/arm-ran" ] || fail "hook did not resolve past the inner claude-named process to the outer lock owner"
357
+ [ "$(epoch_outcome "$dir")" = rewake ] || fail "nested-chain arm must record outcome=rewake"
358
+ pass "auto-arm: resolves the outermost pid of a nested contiguous claude ancestry (bg-spare chain)"
359
+ }
360
+
361
+ test_inert_when_fleet_idle() {
362
+ local dir out status
363
+ dir=$(make_primary_dir "$TMP_ROOT/idle")
364
+ : > "$dir/state/.claude-autoarm-failure-notified"
365
+ : > "$dir/state/.claude-autoarm-failure-alarmed"
366
+ write_arm_fixture "$dir" actionable
367
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
368
+ expect_code 0 "$status" "hook must exit 0 in an idle home with no X-mode poll"
369
+ [ ! -e "$dir/state/arm-ran" ] || fail "hook armed an idle home"
370
+ assert_present "$dir/state/.claude-autoarm-failure-notified" "idle state without positive recovery reset the failure notice"
371
+ assert_present "$dir/state/.claude-autoarm-failure-alarmed" "idle state without positive recovery reset the attended alarm"
372
+ pass "auto-arm: inert with nothing in flight and no X-mode need"
373
+ }
374
+
375
+ # --- the armed cycle ----------------------------------------------------------
376
+
377
+ test_actionable_close_rewakes_with_reason() {
378
+ local dir out status
379
+ dir=$(make_primary_dir "$TMP_ROOT/actionable")
380
+ : > "$dir/state/task.meta"
381
+ write_arm_fixture "$dir" actionable
382
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
383
+ expect_code 2 "$status" "an actionable arm close must exit 2 so Claude rewakes"
384
+ assert_contains "$out" "firstmate watcher wake" "rewake must carry the wake banner"
385
+ assert_contains "$out" "stale: fixture-win actionable" "rewake must carry the arm's reason line"
386
+ assert_contains "$out" "bin/fm-wake-drain.sh" "rewake must direct the drain-first protocol"
387
+ assert_contains "$out" "do NOT run bin/fm-watch-arm.sh" "rewake must forbid a duplicate model re-arm"
388
+ [ "$(epoch_outcome "$dir")" = rewake ] || fail "epoch must record outcome=rewake, got: $(epoch_outcome "$dir")"
389
+ [ ! -e "$dir/state/.claude-autoarm.lock" ] || fail "owner lock must be released after the cycle"
390
+ [ -e "$dir/state/arm-ran" ] || fail "hook never foregrounded the arm wrapper"
391
+ pass "auto-arm: actionable close translates to exactly one exit-2 rewake with reason"
392
+ }
393
+
394
+ test_actionable_close_with_live_successor_rewakes_once() {
395
+ local dir out out2 status status2 pid identity
396
+ dir=$(make_primary_dir "$TMP_ROOT/actionable-live-successor")
397
+ : > "$dir/state/task.meta"
398
+ write_arm_fixture "$dir" actionable
399
+ sleep 60 &
400
+ pid=$!
401
+ identity=$(watcher_identity "$dir" "$pid") || fail "could not identify live successor for actionable close"
402
+ record_watcher_lock "$dir" "$pid" "$identity"
403
+ touch "$dir/state/.last-watcher-beat"
404
+
405
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
406
+ write_arm_fixture "$dir" benign-live
407
+ out2=$(run_autoarm "$dir" 2>/dev/null); status2=$?
408
+
409
+ expect_code 2 "$status" "an actionable close must rewake when a live successor already exists"
410
+ expect_code 0 "$status2" "a repeated non-actionable close with the live successor must stay quiet"
411
+ [ "$(printf '%s\n' "$out" | grep -c '^firstmate watcher wake')" -eq 1 ] \
412
+ || fail "actionable close with a live successor did not emit exactly one wake banner: $out"
413
+ [ "$(printf '%s\n' "$out" | grep -c '^stale: fixture-win actionable')" -eq 1 ] \
414
+ || fail "actionable close with a live successor did not surface its reason exactly once: $out"
415
+ [ -z "$out2" ] || fail "repeated hook duplicated the delivered actionable result: $out2"
416
+ kill -0 "$pid" 2>/dev/null || fail "actionable delivery stopped or replaced the live successor"
417
+ [ "$(epoch_outcome "$dir")" = clean ] || fail "the later benign close must record outcome=clean"
418
+
419
+ kill "$pid" 2>/dev/null || true
420
+ wait "$pid" 2>/dev/null || true
421
+ pass "auto-arm: actionable close survives a healthy successor without duplicate delivery"
422
+ }
423
+
424
+ test_failed_close_rewakes_with_failure_banner() {
425
+ local dir out status
426
+ dir=$(make_primary_dir "$TMP_ROOT/failed")
427
+ : > "$dir/state/task.meta"
428
+ write_arm_fixture "$dir" failed
429
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
430
+ expect_code 2 "$status" "a typed watcher failure must rewake as an alarm"
431
+ assert_contains "$out" "automatic supervision mechanism is broken" "failure rewake must describe the automatic mechanism failure"
432
+ assert_contains "$out" "watcher: FAILED" "failure rewake must carry the arm's typed failure"
433
+ assert_not_contains "$out" "bin/fm-watch-arm.sh" "failure rewake must not create a manual arm loop"
434
+ [ "$(epoch_outcome "$dir")" = failed ] || fail "epoch must record outcome=failed, got: $(epoch_outcome "$dir")"
435
+ [ "$(wc -l < "$dir/state/arm-ran" | tr -d ' ')" -eq 2 ] || fail "failure must exhaust exactly two bounded arm attempts"
436
+ pass "auto-arm: bounded failure verification emits one automatic-mechanism alarm"
437
+ }
438
+
439
+ test_failed_cycles_notify_once_and_keep_retrying() {
440
+ local dir out1 out2 status1 status2
441
+ dir=$(make_primary_dir "$TMP_ROOT/failed-dedup")
442
+ : > "$dir/state/task.meta"
443
+ write_arm_fixture "$dir" failed
444
+ out1=$(run_autoarm "$dir" 2>/dev/null); status1=$?
445
+ out2=$(run_autoarm "$dir" 2>/dev/null); status2=$?
446
+ expect_code 2 "$status1" "the first exhausted failure must notify"
447
+ expect_code 2 "$status2" "a consecutive exhausted failure must force another Stop-owned retry"
448
+ [ -n "$out1" ] || fail "the first exhausted failure did not notify"
449
+ [ -z "$out2" ] || fail "consecutive exhausted failure repeated an operator notice: $out2"
450
+ [ "$(wc -l < "$dir/state/arm-ran" | tr -d ' ')" -eq 4 ] || fail "each cycle must retain bounded automatic retries"
451
+ assert_present "$dir/state/.claude-autoarm-failure-notified" "failure episode marker was not recorded"
452
+ [ "$(epoch_outcome "$dir")" = failed-suppressed ] || fail "second failure must record failed-suppressed"
453
+ pass "auto-arm: consecutive failures keep Stop-owned retry without repeating notice"
454
+ }
455
+
456
+ test_failure_notice_marker_write_refuses_delivery_and_retries() {
457
+ local dir marker out1 out2 out3 status1 status2 status3 gen1 delivered
458
+ dir=$(make_primary_dir "$TMP_ROOT/failed-marker-refusal")
459
+ : > "$dir/state/task.meta"
460
+ write_arm_fixture "$dir" failed
461
+ marker="$dir/state/.claude-autoarm-failure-notified"
462
+ ln -s "$dir/state/missing/notice" "$marker"
463
+
464
+ out1=$(run_autoarm "$dir" 2>/dev/null); status1=$?
465
+ expect_code 0 "$status1" "an unrecordable failure notice must refuse delivery"
466
+ [ -L "$marker" ] || fail "the failed marker write unexpectedly replaced its dangling symlink"
467
+ [ "$(epoch_outcome "$dir")" = failed ] || fail "the refused generation must leave its terminal ledger outcome"
468
+ gen1=$(epoch_field "$dir" epoch)
469
+
470
+ rm -f "$marker"
471
+ out2=$(run_autoarm "$dir" 2>/dev/null); status2=$?
472
+ out3=$(run_autoarm "$dir" 2>/dev/null); status3=$?
473
+ expect_code 2 "$status2" "a successor must retry and deliver after the marker path is restored"
474
+ expect_code 2 "$status3" "a later failure must retain the Stop-owned retry"
475
+ [ "$(epoch_field "$dir" epoch)" -gt "$gen1" ] || fail "the successor did not supersede the refused terminal entry"
476
+ assert_present "$marker" "the successful successor did not record the failure notice"
477
+ assert_contains "$out2" "automatic supervision mechanism is broken" "the successful successor did not deliver the failure notice"
478
+ [ -z "$out3" ] || fail "the firing after the successful marker commit repeated the notice: $out3"
479
+ delivered=$(printf '%s\n%s\n' "$out2" "$out3" | grep -c 'automatic supervision mechanism is broken' || true)
480
+ [ "$delivered" -eq 1 ] || fail "the restored episode delivered $delivered failure notices instead of one"
481
+ pass "auto-arm: marker-write refusal defers delivery until one successor commits the notice"
482
+ }
483
+
484
+ test_unverified_clean_close_exhausts_retries() {
485
+ local dir out status
486
+ dir=$(make_primary_dir "$TMP_ROOT/clean")
487
+ : > "$dir/state/task.meta"
488
+ write_arm_fixture "$dir" clean
489
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
490
+ expect_code 2 "$status" "a non-actionable close without a healthy watcher must fail closed"
491
+ assert_contains "$out" "automatic supervision mechanism is broken" "unverified close must report automatic failure"
492
+ [ "$(wc -l < "$dir/state/arm-ran" | tr -d ' ')" -eq 2 ] || fail "unverified close must exhaust exactly two bounded attempts"
493
+ [ "$(epoch_outcome "$dir")" = failed ] || fail "epoch must record outcome=failed, got: $(epoch_outcome "$dir")"
494
+ pass "auto-arm: unverified clean close exhausts retries and fails closed"
495
+ }
496
+
497
+ test_post_alarm_actionable_close_is_suppressed() {
498
+ local dir out status
499
+ dir=$(make_primary_dir "$TMP_ROOT/post-alarm-actionable")
500
+ : > "$dir/state/task.meta"
501
+ : > "$dir/state/.claude-autoarm-failure-notified"
502
+ : > "$dir/state/.claude-autoarm-failure-alarmed"
503
+ write_arm_fixture "$dir" actionable
504
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
505
+ expect_code 0 "$status" "an actionable result after attended fail-open must not continue"
506
+ [ -z "$out" ] || fail "post-alarm actionable result produced continuation output: $out"
507
+ assert_present "$dir/state/.claude-autoarm-failure-notified" "post-alarm actionable result cleared the failure notice"
508
+ assert_present "$dir/state/.claude-autoarm-failure-alarmed" "post-alarm actionable result cleared the attended alarm"
509
+ [ "$(epoch_outcome "$dir")" = failed-suppressed ] || fail "post-alarm actionable result must record failed-suppressed"
510
+ pass "auto-arm: post-alarm actionable outcomes cannot continue or reset failure state"
511
+ }
512
+
513
+ test_benign_cycle_end_with_live_watcher_is_silent() {
514
+ local dir out out2 status status2 pid identity
515
+ dir=$(make_primary_dir "$TMP_ROOT/benign-live")
516
+ : > "$dir/state/task.meta"
517
+ write_arm_fixture "$dir" benign-live
518
+ sleep 60 &
519
+ pid=$!
520
+ identity=$(watcher_identity "$dir" "$pid") || fail "could not identify live watcher holder for benign close"
521
+ record_watcher_lock "$dir" "$pid" "$identity"
522
+ touch "$dir/state/.last-watcher-beat"
523
+ printf 'session=sess-autoarm\ncount=3\nepoch=9\n' > "$dir/state/.turnend-claude-blocks"
524
+ : > "$dir/state/.claude-autoarm-failure-notified"
525
+ : > "$dir/state/.claude-autoarm-failure-alarmed"
526
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
527
+ out2=$(run_autoarm "$dir" 2>/dev/null); status2=$?
528
+ kill "$pid" 2>/dev/null || true
529
+ wait "$pid" 2>/dev/null || true
530
+ expect_code 0 "$status" "a failed-looking cycle with a live fresh watcher must be benign"
531
+ expect_code 0 "$status2" "the next Stop-owned cycle must remain benign with the live watcher"
532
+ [ -z "$out" ] || fail "benign live cycle produced an operator notice: $out"
533
+ [ -z "$out2" ] || fail "next benign live cycle produced an operator notice: $out2"
534
+ [ "$(epoch_outcome "$dir")" = clean ] || fail "benign live cycle must record outcome=clean, got: $(epoch_outcome "$dir")"
535
+ [ "$(wc -l < "$dir/state/arm-ran" | tr -d ' ')" -eq 2 ] || fail "the next Stop-owned cycle must run its own bounded arm"
536
+ [ ! -e "$dir/state/.turnend-claude-blocks" ] || fail "benign live cycle must clear the prior block budget"
537
+ [ ! -e "$dir/state/.claude-autoarm-failure-notified" ] || fail "benign live cycle must not leave a failure-notice marker"
538
+ [ ! -e "$dir/state/.claude-autoarm-failure-alarmed" ] || fail "benign live cycle must not leave an attended-alarm marker"
539
+ pass "auto-arm: benign cycle end with a live watcher and fresh beacon stays silent across the next cycle"
540
+ }
541
+
542
+ test_positive_recovery_budget_contention_preserves_episode() {
543
+ local dir out status pid identity holder
544
+ dir=$(make_primary_dir "$TMP_ROOT/recovery-budget-contention")
545
+ : > "$dir/state/task.meta"
546
+ write_arm_fixture "$dir" benign-live
547
+ sleep 60 &
548
+ pid=$!
549
+ identity=$(watcher_identity "$dir" "$pid") || fail "could not identify live watcher holder for recovery contention"
550
+ record_watcher_lock "$dir" "$pid" "$identity"
551
+ touch "$dir/state/.last-watcher-beat"
552
+ printf 'session=sess-autoarm\ncount=3\nepoch=9\n' > "$dir/state/.turnend-claude-blocks"
553
+ : > "$dir/state/.claude-autoarm-failure-notified"
554
+ sleep 60 &
555
+ holder=$!
556
+ mkdir -p "$dir/state/.turnend-claude-blocks.lock"
557
+ printf '%s\n' "$holder" > "$dir/state/.turnend-claude-blocks.lock/pid"
558
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
559
+ expect_code 2 "$status" "a healthy auto-arm must continue when the episode reset lock is busy"
560
+ [ -z "$out" ] || fail "recovery contention produced an operator notice: $out"
561
+ [ "$(epoch_outcome "$dir")" = failed-suppressed ] || fail "recovery contention must not record ordinary clean recovery"
562
+ assert_present "$dir/state/.turnend-claude-blocks" "recovery contention partially cleared the block budget"
563
+ assert_present "$dir/state/.claude-autoarm-failure-notified" "recovery contention partially cleared the failure notice"
564
+ kill "$holder" 2>/dev/null || true
565
+ wait "$holder" 2>/dev/null || true
566
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
567
+ kill "$pid" 2>/dev/null || true
568
+ wait "$pid" 2>/dev/null || true
569
+ expect_code 0 "$status" "a later healthy auto-arm must complete the episode reset"
570
+ assert_absent "$dir/state/.turnend-claude-blocks" "successful retry left the block budget"
571
+ assert_absent "$dir/state/.claude-autoarm-failure-notified" "successful retry left the failure notice"
572
+ pass "auto-arm: budget contention preserves the episode and forces a reset retry"
573
+ }
574
+
575
+ test_owner_mutex_contention_preserves_failure_episode_reset() {
576
+ local dir out hook_pid status watcher watcher_id holder i
577
+ dir=$(make_primary_dir "$TMP_ROOT/reset-owner-contention")
578
+ : > "$dir/state/task.meta"
579
+ : > "$dir/state/.turnend-claude-blocks"
580
+ : > "$dir/state/.claude-autoarm-failure-notified"
581
+ : > "$dir/state/.claude-autoarm-failure-alarmed"
582
+ write_arm_fixture "$dir" reset-boundary
583
+ sleep 60 &
584
+ watcher=$!
585
+ watcher_id=$(watcher_identity "$dir" "$watcher") || fail "could not identify reset-contention watcher"
586
+ record_watcher_lock "$dir" "$watcher" "$watcher_id"
587
+ touch "$dir/state/.last-watcher-beat"
588
+ out="$dir/state/hook.out"
589
+ run_autoarm_bg "$dir" "$out"
590
+ hook_pid=$RUN_AUTOARM_BG_PID
591
+ i=0
592
+ while [ ! -e "$dir/state/arm-waiting" ]; do
593
+ [ "$i" -lt 50 ] || fail "healthy owner never reached the reset boundary"
594
+ sleep 0.05
595
+ i=$((i + 1))
596
+ done
597
+ sleep 60 &
598
+ holder=$!
599
+ mkdir -p "$dir/state/.claude-autoarm.lock"
600
+ printf '%s\n' "$holder" > "$dir/state/.claude-autoarm.lock/pid"
601
+ : > "$dir/state/arm-release"
602
+ wait "$hook_pid"; status=$?
603
+ expect_code 0 "$status" "owner-mutex contention at reset must close quietly"
604
+ [ ! -s "$out" ] || fail "owner-mutex contention at reset produced output: $(cat "$out")"
605
+ assert_present "$dir/state/.turnend-claude-blocks" "contended reset deleted the block budget"
606
+ assert_present "$dir/state/.claude-autoarm-failure-notified" "contended reset deleted the failure notice"
607
+ assert_present "$dir/state/.claude-autoarm-failure-alarmed" "contended reset deleted the attended alarm"
608
+ kill "$holder" "$watcher" 2>/dev/null || true
609
+ wait "$holder" 2>/dev/null || true
610
+ wait "$watcher" 2>/dev/null || true
611
+ rm -rf "$dir/state/.claude-autoarm.lock"
612
+ pass "auto-arm: owner-mutex contention preserves successor episode state"
613
+ }
614
+
615
+ test_arms_for_x_mode_poll_need_without_inflight() {
616
+ local dir out status
617
+ dir=$(make_primary_dir "$TMP_ROOT/x-need")
618
+ printf '#!/usr/bin/env bash\nexit 0\n' > "$dir/state/x-watch.check.sh"
619
+ write_arm_fixture "$dir" actionable
620
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
621
+ expect_code 2 "$status" "an X-mode relay poll need must keep the auto-arm active with zero tasks in flight"
622
+ [ -e "$dir/state/arm-ran" ] || fail "hook did not arm for the X-mode poll need"
623
+ pass "auto-arm: X-mode poll need arms the cycle even with no tasks in flight"
624
+ }
625
+
626
+ test_single_flight_admits_exactly_one_owner() {
627
+ local dir rc1 rc2 count
628
+ dir=$(make_primary_dir "$TMP_ROOT/single-flight")
629
+ : > "$dir/state/task.meta"
630
+ write_arm_fixture "$dir" slow-actionable
631
+ FM_HOME="$dir" "$FAKE_CLAUDE" -c '
632
+ printf "%s\n" "$$" > "$FM_HOME/state/.lock"
633
+ printf "%s\n" "{\"session_id\":\"s\"}" | "$FM_HOME/bin/fm-claude-stop-autoarm.sh" >/dev/null 2>"$FM_HOME/state/err1" &
634
+ p1=$!
635
+ printf "%s\n" "{\"session_id\":\"s\"}" | "$FM_HOME/bin/fm-claude-stop-autoarm.sh" >/dev/null 2>"$FM_HOME/state/err2" &
636
+ p2=$!
637
+ wait "$p1"; echo $? > "$FM_HOME/state/rc1"
638
+ wait "$p2"; echo $? > "$FM_HOME/state/rc2"
639
+ '
640
+ rc1=$(cat "$dir/state/rc1")
641
+ rc2=$(cat "$dir/state/rc2")
642
+ count=$(wc -l < "$dir/state/arm-ran" | tr -d ' ')
643
+ [ "$count" -eq 1 ] || fail "concurrent firings must foreground exactly one arm, saw $count"
644
+ { [ "$rc1" = 2 ] && [ "$rc2" = 0 ]; } || { [ "$rc1" = 0 ] && [ "$rc2" = 2 ]; } \
645
+ || fail "exactly one firing must translate the close (rc 2) and the other must no-op (rc 0), got rc1=$rc1 rc2=$rc2"
646
+ pass "auto-arm: concurrent firings admit one owner and one rewake translation"
647
+ }
648
+
649
+ # --- abandoned single-flight claim recovery (legacy shim) ----------------------
650
+ # The 2026-08-14 lapse: one cycle armed, beat its beacon, delivered a single
651
+ # rewake, and exited, leaving its owner lock behind with a live pid. The single
652
+ # flight gate then turned every later firing into exit 0, so with two tasks in
653
+ # flight and a beacon 40 minutes cold nothing re-armed and both workers' reports
654
+ # sat unread until an operator drained the queue by hand. The lock alone is not
655
+ # enough to prove that: the ledger naming that same pid with a finished outcome,
656
+ # or a recorded pid-identity the live pid no longer matches, is what distinguishes
657
+ # an abandoned claim from one still deciding.
658
+ #
659
+ # These fixtures fabricate the LOCK-HOLDING claim shape a pre-generation build
660
+ # leaves behind, so this section pins the legacy shim: a live legacy owner
661
+ # still defers the gate, and an abandoned one is reclaimed once so the home
662
+ # re-arms - with an identity-verified live owner retired via TERM first, and
663
+ # an identityless one reclaimed without any signalling. The generation-claim
664
+ # section below pins the current contract.
665
+
666
+ # Fabricate a held owner lock: <dir> <pid> <role>. Plain-dir shape on purpose -
667
+ # the hook must reclaim whatever a crashed or blocked owner left behind.
668
+ record_autoarm_owner() {
669
+ local dir=$1 pid=$2 role=${3:-autoarm}
670
+ mkdir -p "$dir/state/.claude-autoarm.lock"
671
+ printf '%s\n' "$pid" > "$dir/state/.claude-autoarm.lock/pid"
672
+ printf '%s\n' "$role" > "$dir/state/.claude-autoarm.lock/role"
673
+ }
674
+
675
+ # Record the pid-identity a claim leaves inside its own lock: <dir> <pid>. The
676
+ # claim writes the identity of the process that took the lock, so passing a pid
677
+ # OTHER than the lock's own reproduces pid reuse - the recorded claimant is gone
678
+ # and an unrelated live process now answers to its number.
679
+ record_autoarm_owner_identity() {
680
+ local dir=$1 pid=$2 identity
681
+ identity=$(fm_test_pid_identity "$pid") || return 1
682
+ [ -n "$identity" ] || return 1
683
+ printf '%s\n' "$identity" > "$dir/state/.claude-autoarm.lock/pid-identity"
684
+ }
685
+
686
+ # <dir> <epoch-seq> <owner-pid> <outcome>, aged well past any freshness window.
687
+ record_autoarm_epoch() {
688
+ local dir=$1 seq=$2 owner=$3 outcome=$4
689
+ printf 'epoch=%s owner_pid=%s outcome=%s updated_at=1\n' "$seq" "$owner" "$outcome" \
690
+ > "$dir/state/.claude-autoarm-epoch"
691
+ touch -t 202001010000 "$dir/state/.claude-autoarm-epoch"
692
+ }
693
+
694
+ epoch_field() {
695
+ local dir=$1 field=$2
696
+ sed -n "s/^.*[[:space:]]\{0,1\}$field=\([A-Za-z0-9_-]*\).*\$/\1/p" \
697
+ "$dir/state/.claude-autoarm-epoch" 2>/dev/null || true
698
+ }
699
+
700
+ test_abandoned_owner_claim_is_reclaimed_and_rearms() {
701
+ local dir out status pid
702
+ dir=$(make_primary_dir "$TMP_ROOT/abandoned-claim")
703
+ : > "$dir/state/task1.meta"
704
+ : > "$dir/state/task2.meta"
705
+ write_arm_fixture "$dir" actionable
706
+ sleep 60 &
707
+ pid=$!
708
+ record_autoarm_owner "$dir" "$pid"
709
+ record_autoarm_epoch "$dir" 464 "$pid" rewake
710
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
711
+ kill -0 "$pid" 2>/dev/null || fail "an identityless abandoned owner must be reclaimed without being signalled"
712
+ kill "$pid" 2>/dev/null || true
713
+ wait "$pid" 2>/dev/null || true
714
+ expect_code 2 "$status" "a claim whose ledger outcome is already terminal must be reclaimed, not deferred to forever"
715
+ [ -e "$dir/state/arm-ran" ] || fail "abandoned claim left the home unarmed with work in flight"
716
+ assert_contains "$out" "firstmate watcher wake" "the reclaimed cycle must still translate its wake"
717
+ [ "$(epoch_field "$dir" epoch)" -gt 464 ] || fail "reclaimed cycle did not advance the frozen ledger: $(epoch_field "$dir" epoch)"
718
+ [ "$(epoch_outcome "$dir")" = rewake ] || fail "reclaimed cycle did not record its own outcome: $(epoch_outcome "$dir")"
719
+ [ "$(epoch_field "$dir" owner_pid)" != "$pid" ] || fail "reclaimed ledger still names the abandoned owner"
720
+ assert_absent "$dir/state/.claude-autoarm.lock" "reclaimed cycle left an owner lock behind"
721
+ assert_absent "$dir/state/.claude-autoarm.lock.steal" "reclaim left its serialization mutex behind"
722
+ pass "auto-arm: an abandoned owner claim is reclaimed so a lapsed cycle re-arms"
723
+ }
724
+
725
+ test_arming_claim_with_fresh_beacon_is_never_reclaimed() {
726
+ local dir out status pid
727
+ dir=$(make_primary_dir "$TMP_ROOT/arming-claim")
728
+ : > "$dir/state/task1.meta"
729
+ write_arm_fixture "$dir" actionable
730
+ sleep 60 &
731
+ pid=$!
732
+ record_autoarm_owner "$dir" "$pid"
733
+ # An owner foregrounds the arm for the whole watcher cycle, so an old "arming"
734
+ # entry is still in progress while its watcher keeps beating the beacon.
735
+ record_autoarm_epoch "$dir" 464 "$pid" arming
736
+ : > "$dir/state/.last-watcher-beat"
737
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
738
+ kill "$pid" 2>/dev/null || true
739
+ wait "$pid" 2>/dev/null || true
740
+ expect_code 0 "$status" "a legacy claim still arming under a fresh beacon must keep the single-flight gate closed"
741
+ [ -z "$out" ] || fail "deferring to an arming claim produced output: $out"
742
+ assert_absent "$dir/state/arm-ran" "an arming claim was stolen and double-armed"
743
+ [ "$(epoch_field "$dir" epoch)" = 464 ] || fail "deferred firing rewrote the arming ledger entry"
744
+ assert_present "$dir/state/.claude-autoarm.lock" "an arming claim lost its owner lock"
745
+ pass "auto-arm: a legacy owner still arming is never reclaimed while its watcher keeps beating"
746
+ }
747
+
748
+ # The other legitimate legacy arming shape: a claim that JUST started arming
749
+ # after a real lapse, so the beacon is long stale but the entry is fresh. The
750
+ # arm's bounded startup window must never be stolen out from under it.
751
+ test_fresh_arming_claim_with_stale_beacon_is_never_reclaimed() {
752
+ local dir out status pid
753
+ dir=$(make_primary_dir "$TMP_ROOT/fresh-arming-claim")
754
+ : > "$dir/state/task1.meta"
755
+ write_arm_fixture "$dir" actionable
756
+ sleep 60 &
757
+ pid=$!
758
+ record_autoarm_owner "$dir" "$pid"
759
+ record_autoarm_owner_identity "$dir" "$pid" || fail "could not record a claim pid-identity"
760
+ printf 'epoch=464 owner_pid=%s outcome=arming updated_at=%s\n' "$pid" "$(date +%s)" \
761
+ > "$dir/state/.claude-autoarm-epoch"
762
+ touch -t 202001010000 "$dir/state/.last-watcher-beat"
763
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
764
+ kill "$pid" 2>/dev/null || true
765
+ wait "$pid" 2>/dev/null || true
766
+ expect_code 0 "$status" "a freshly arming legacy claim must keep the single-flight gate closed even after a long lapse"
767
+ [ -z "$out" ] || fail "deferring to a fresh arming claim produced output: $out"
768
+ assert_absent "$dir/state/arm-ran" "a fresh arming claim was stolen and double-armed"
769
+ assert_present "$dir/state/.claude-autoarm.lock" "a fresh arming claim lost its owner lock"
770
+ pass "auto-arm: a fresh legacy arming claim is never reclaimed while its startup window is still open"
771
+ }
772
+
773
+ test_claim_not_named_by_the_ledger_is_never_reclaimed() {
774
+ local dir out status pid
775
+ dir=$(make_primary_dir "$TMP_ROOT/unnamed-claim")
776
+ : > "$dir/state/task1.meta"
777
+ write_arm_fixture "$dir" actionable
778
+ sleep 60 &
779
+ pid=$!
780
+ record_autoarm_owner "$dir" "$pid"
781
+ # A fresh claimant holds the lock before it writes "arming", so until it does
782
+ # the ledger still names the PREVIOUS owner. Requiring the two pids to match is
783
+ # what keeps that window from being mistaken for abandonment.
784
+ record_autoarm_epoch "$dir" 464 999 rewake
785
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
786
+ kill "$pid" 2>/dev/null || true
787
+ wait "$pid" 2>/dev/null || true
788
+ expect_code 0 "$status" "a live claim the ledger does not name is unproven and must be left alone"
789
+ [ -z "$out" ] || fail "deferring to an unnamed claim produced output: $out"
790
+ assert_absent "$dir/state/arm-ran" "a claim the ledger does not name was stolen and double-armed"
791
+ assert_present "$dir/state/.claude-autoarm.lock" "an unproven claim lost its owner lock"
792
+ pass "auto-arm: a live claim the ledger does not name is never reclaimed"
793
+ }
794
+
795
+ # The same unrecoverable lapse, reached where the ledger cannot prove it: a session
796
+ # teardown kills the claim's whole process group before it records any outcome, so
797
+ # the entry still reads "arming" while the recorded pid is later handed to an
798
+ # unrelated live process. Only the identity the claim recorded inside its own lock
799
+ # separates that from a real arm in progress, so keep the beacon fresh here: this
800
+ # case must reclaim on the identity leg alone, not the stuck-arming leg. The
801
+ # reclaim must not signal the unrelated live process that inherited the number.
802
+ test_pid_reused_arming_claim_is_reclaimed_and_rearms() {
803
+ local dir out status pid
804
+ dir=$(make_primary_dir "$TMP_ROOT/reused-pid-arming")
805
+ : > "$dir/state/task1.meta"
806
+ : > "$dir/state/task2.meta"
807
+ write_arm_fixture "$dir" actionable
808
+ sleep 60 &
809
+ pid=$!
810
+ record_autoarm_owner "$dir" "$pid"
811
+ record_autoarm_owner_identity "$dir" "$$" || fail "could not record a claim pid-identity"
812
+ record_autoarm_epoch "$dir" 464 "$pid" arming
813
+ : > "$dir/state/.last-watcher-beat"
814
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
815
+ kill -0 "$pid" 2>/dev/null || fail "the unrelated live process inheriting the number must never be signalled"
816
+ kill "$pid" 2>/dev/null || true
817
+ wait "$pid" 2>/dev/null || true
818
+ expect_code 2 "$status" "a claim whose recorded identity no longer matches its live pid must be reclaimed, arming entry or not"
819
+ [ -e "$dir/state/arm-ran" ] || fail "a reused-pid claim left the home unarmed with work in flight"
820
+ assert_contains "$out" "firstmate watcher wake" "the reclaimed cycle must still translate its wake"
821
+ [ "$(epoch_field "$dir" epoch)" -gt 464 ] || fail "reclaimed cycle did not advance the frozen ledger: $(epoch_field "$dir" epoch)"
822
+ assert_absent "$dir/state/.claude-autoarm.lock" "reclaimed cycle left an owner lock behind"
823
+ assert_absent "$dir/state/.claude-autoarm.lock.steal" "reclaim left its serialization mutex behind"
824
+ pass "auto-arm: a claim whose pid was reused is reclaimed even while its ledger entry still reads arming"
825
+ }
826
+
827
+ # The other ledger-blind shape: no ledger at all (a fresh or hand-cleared home)
828
+ # plus a reused pid. Without the recorded identity nothing proves abandonment, so
829
+ # every later firing exits at the lock and the home never re-arms.
830
+ test_pid_reused_claim_with_no_ledger_is_reclaimed_and_rearms() {
831
+ local dir out status pid
832
+ dir=$(make_primary_dir "$TMP_ROOT/reused-pid-no-ledger")
833
+ : > "$dir/state/task1.meta"
834
+ write_arm_fixture "$dir" actionable
835
+ sleep 60 &
836
+ pid=$!
837
+ record_autoarm_owner "$dir" "$pid"
838
+ record_autoarm_owner_identity "$dir" "$$" || fail "could not record a claim pid-identity"
839
+ assert_absent "$dir/state/.claude-autoarm-epoch" "this case must start with no ledger at all"
840
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
841
+ kill "$pid" 2>/dev/null || true
842
+ wait "$pid" 2>/dev/null || true
843
+ expect_code 2 "$status" "a reused-pid claim with no ledger to consult must still be reclaimed"
844
+ [ -e "$dir/state/arm-ran" ] || fail "a reused-pid claim with no ledger left the home unarmed"
845
+ assert_contains "$out" "firstmate watcher wake" "the reclaimed cycle must still translate its wake"
846
+ [ "$(epoch_outcome "$dir")" = rewake ] || fail "reclaimed cycle did not record its own outcome: $(epoch_outcome "$dir")"
847
+ assert_absent "$dir/state/.claude-autoarm.lock" "reclaimed cycle left an owner lock behind"
848
+ pass "auto-arm: a reused-pid claim is reclaimed even with no ledger entry to prove it"
849
+ }
850
+
851
+ # The negative control for the identity leg: a claim whose recorded identity still
852
+ # matches the process holding the lock is genuinely in flight, so an arm that has
853
+ # legitimately been running for hours - its watcher beating the whole time - must
854
+ # keep the single-flight gate closed.
855
+ test_identity_matched_arming_claim_is_never_reclaimed() {
856
+ local dir out status pid
857
+ dir=$(make_primary_dir "$TMP_ROOT/identity-matched-arming")
858
+ : > "$dir/state/task1.meta"
859
+ write_arm_fixture "$dir" actionable
860
+ sleep 60 &
861
+ pid=$!
862
+ record_autoarm_owner "$dir" "$pid"
863
+ record_autoarm_owner_identity "$dir" "$pid" || fail "could not record a claim pid-identity"
864
+ record_autoarm_epoch "$dir" 464 "$pid" arming
865
+ : > "$dir/state/.last-watcher-beat"
866
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
867
+ kill "$pid" 2>/dev/null || true
868
+ wait "$pid" 2>/dev/null || true
869
+ expect_code 0 "$status" "an identity-matched claim still arming must keep the single-flight gate closed"
870
+ [ -z "$out" ] || fail "deferring to an identity-matched arming claim produced output: $out"
871
+ assert_absent "$dir/state/arm-ran" "an identity-matched arming claim was stolen and double-armed"
872
+ [ "$(epoch_field "$dir" epoch)" = 464 ] || fail "deferred firing rewrote the arming ledger entry"
873
+ assert_present "$dir/state/.claude-autoarm.lock" "an identity-matched arming claim lost its owner lock"
874
+ pass "auto-arm: an identity-matched owner still arming is never reclaimed"
875
+ }
876
+
877
+ test_terminal_check_claim_is_never_reclaimed() {
878
+ local dir out status pid
879
+ dir=$(make_primary_dir "$TMP_ROOT/terminal-check-claim")
880
+ : > "$dir/state/task1.meta"
881
+ write_arm_fixture "$dir" actionable
882
+ sleep 60 &
883
+ pid=$!
884
+ # The synchronous guard takes the same lock under its own role while it decides
885
+ # the attended fail-open. Reclaiming that would race the guard's own decision.
886
+ record_autoarm_owner "$dir" "$pid" terminal-check
887
+ record_autoarm_epoch "$dir" 464 "$pid" failed-suppressed
888
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
889
+ kill "$pid" 2>/dev/null || true
890
+ wait "$pid" 2>/dev/null || true
891
+ expect_code 0 "$status" "the guard's own terminal-check claim must never be reclaimed by the arm hook"
892
+ [ -z "$out" ] || fail "deferring to a terminal-check claim produced output: $out"
893
+ assert_absent "$dir/state/arm-ran" "a terminal-check claim was stolen and double-armed"
894
+ assert_present "$dir/state/.claude-autoarm.lock" "a terminal-check claim lost its owner lock"
895
+ pass "auto-arm: the guard's terminal-check claim is never reclaimed"
896
+ }
897
+
898
+ # A proven-stuck legacy owner that is still ALIVE and identity-verified is
899
+ # retired with TERM before its lock is removed, because old-build code cannot
900
+ # re-check generations and would otherwise resume and act after supersession.
901
+ test_stuck_live_legacy_owner_is_retired_and_reclaimed() {
902
+ local dir out status pid
903
+ dir=$(make_primary_dir "$TMP_ROOT/legacy-term")
904
+ : > "$dir/state/task1.meta"
905
+ write_arm_fixture "$dir" actionable
906
+ sleep 60 &
907
+ pid=$!
908
+ record_autoarm_owner "$dir" "$pid"
909
+ record_autoarm_owner_identity "$dir" "$pid" || fail "could not record a claim pid-identity"
910
+ record_autoarm_epoch "$dir" 464 "$pid" arming
911
+ touch -t 202001010000 "$dir/state/.last-watcher-beat"
912
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
913
+ expect_code 2 "$status" "a proven-stuck identity-verified live legacy owner must be retired and reclaimed"
914
+ kill -0 "$pid" 2>/dev/null && fail "the stuck legacy owner was reclaimed without being retired"
915
+ wait "$pid" 2>/dev/null || true
916
+ [ -e "$dir/state/arm-ran" ] || fail "the reclaimed home did not re-arm"
917
+ assert_contains "$out" "firstmate watcher wake" "the reclaimed cycle must still translate its wake"
918
+ assert_absent "$dir/state/.claude-autoarm.lock" "reclaim left the legacy owner lock behind"
919
+ pass "auto-arm: a stuck live legacy owner is retired via TERM and its lock reclaimed"
920
+ }
921
+
922
+ # The SIGSTOP counterfactual: a stopped legacy owner survives the bounded
923
+ # retirement wait with TERM queued, and the reclaim must proceed anyway - a
924
+ # pending TERM on the verified owner is retirement-safe because delivery
925
+ # precedes any further user code when the process continues.
926
+ test_stopped_legacy_owner_is_reclaimed_with_term_pending() {
927
+ local dir out status pid i
928
+ dir=$(make_primary_dir "$TMP_ROOT/legacy-term-stopped")
929
+ : > "$dir/state/task1.meta"
930
+ write_arm_fixture "$dir" actionable
931
+ sleep 60 &
932
+ pid=$!
933
+ record_autoarm_owner "$dir" "$pid"
934
+ record_autoarm_owner_identity "$dir" "$pid" || fail "could not record a claim pid-identity"
935
+ record_autoarm_epoch "$dir" 464 "$pid" arming
936
+ touch -t 202001010000 "$dir/state/.last-watcher-beat"
937
+ kill -STOP "$pid" 2>/dev/null || fail "could not stop the legacy owner fixture"
938
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
939
+ expect_code 2 "$status" "a stopped legacy owner with TERM queued must not block the reclaim forever"
940
+ [ -e "$dir/state/arm-ran" ] || fail "the reclaimed home did not re-arm past the stopped owner"
941
+ assert_absent "$dir/state/.claude-autoarm.lock" "reclaim left the stopped owner's lock behind"
942
+ kill -CONT "$pid" 2>/dev/null || true
943
+ i=0
944
+ while [ "$i" -lt 40 ] && kill -0 "$pid" 2>/dev/null; do
945
+ sleep 0.05
946
+ i=$((i + 1))
947
+ done
948
+ kill -0 "$pid" 2>/dev/null && fail "the queued TERM did not retire the owner on continue"
949
+ wait "$pid" 2>/dev/null || true
950
+ pass "auto-arm: a SIGSTOPped legacy owner is reclaimed with TERM pending and dies on continue"
951
+ }
952
+
953
+ # --- generation claims: optimistic single-flight and supersession --------------
954
+ # The current claim is the two-line ledger entry itself (line 1 the classic
955
+ # epoch record, line 2 the owner's MANDATORY pid-identity); no lock is held
956
+ # across arming or output. A live open claim defers every firing; a stuck,
957
+ # dead, identity-mismatched, identityless, or finished claim is superseded by
958
+ # taking the next generation; a superseded owner goes completely silent.
959
+
960
+ # Fabricate a v2 generation claim: <dir> <gen> <owner-pid> <outcome>
961
+ # <identity-pid>. The identity of <identity-pid> is recorded as line 2 (the
962
+ # claim's own pid for a matched claim, another pid to reproduce pid reuse).
963
+ record_autoarm_v2_claim() {
964
+ local dir=$1 gen=$2 owner=$3 outcome=$4 identity_pid=$5 identity
965
+ identity=$(fm_test_pid_identity "$identity_pid") || return 1
966
+ [ -n "$identity" ] || return 1
967
+ printf 'epoch=%s owner_pid=%s outcome=%s updated_at=1\n%s\n' \
968
+ "$gen" "$owner" "$outcome" "$identity" > "$dir/state/.claude-autoarm-epoch"
969
+ }
970
+
971
+ # A live open generation claim needs no lock to keep the gate closed: the
972
+ # ledger alone defers a concurrent firing, however old the entry, while the
973
+ # watcher keeps beating the beacon.
974
+ test_open_generation_claim_defers_without_any_lock() {
975
+ local dir out status pid
976
+ dir=$(make_primary_dir "$TMP_ROOT/v2-open-claim")
977
+ : > "$dir/state/task1.meta"
978
+ write_arm_fixture "$dir" actionable
979
+ sleep 60 &
980
+ pid=$!
981
+ record_autoarm_v2_claim "$dir" 464 "$pid" arming "$pid" || fail "could not record a v2 claim"
982
+ touch -t 202001010000 "$dir/state/.claude-autoarm-epoch"
983
+ : > "$dir/state/.last-watcher-beat"
984
+ assert_absent "$dir/state/.claude-autoarm.lock" "this case must start with no owner lock at all"
985
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
986
+ kill "$pid" 2>/dev/null || true
987
+ wait "$pid" 2>/dev/null || true
988
+ expect_code 0 "$status" "a live open generation claim must keep the single-flight gate closed with no lock held"
989
+ [ -z "$out" ] || fail "deferring to an open generation claim produced output: $out"
990
+ assert_absent "$dir/state/arm-ran" "an open generation claim was superseded and double-armed"
991
+ [ "$(epoch_field "$dir" epoch)" = 464 ] || fail "deferred firing rewrote the open claim's ledger entry"
992
+ pass "auto-arm: a live open generation claim defers concurrent firings with no lock held"
993
+ }
994
+
995
+ # The 2026-08-26 watcher flap in the generation model: a live, identity-matched
996
+ # owner whose ledger entry and watcher beacon are both older than grace is
997
+ # stuck, and the next firing supersedes it by taking the next generation.
998
+ test_stuck_generation_claim_is_superseded_and_rearms() {
999
+ local dir out status pid
1000
+ dir=$(make_primary_dir "$TMP_ROOT/v2-stuck-claim")
1001
+ : > "$dir/state/task1.meta"
1002
+ : > "$dir/state/task2.meta"
1003
+ write_arm_fixture "$dir" actionable
1004
+ sleep 60 &
1005
+ pid=$!
1006
+ record_autoarm_v2_claim "$dir" 464 "$pid" arming "$pid" || fail "could not record a v2 claim"
1007
+ touch -t 202001010000 "$dir/state/.claude-autoarm-epoch"
1008
+ touch -t 202001010000 "$dir/state/.last-watcher-beat"
1009
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
1010
+ kill "$pid" 2>/dev/null || true
1011
+ wait "$pid" 2>/dev/null || true
1012
+ expect_code 2 "$status" "a live owner stuck arming past grace with a beacon just as stale must be superseded, not deferred to forever"
1013
+ [ -e "$dir/state/arm-ran" ] || fail "a stuck generation claim left the home unarmed with work in flight"
1014
+ assert_contains "$out" "firstmate watcher wake" "the superseding generation must still translate its wake"
1015
+ [ "$(epoch_field "$dir" epoch)" -gt 464 ] || fail "superseding claim did not advance the frozen ledger: $(epoch_field "$dir" epoch)"
1016
+ [ "$(epoch_field "$dir" owner_pid)" != "$pid" ] || fail "superseding claim left the stuck owner on the ledger"
1017
+ assert_absent "$dir/state/.claude-autoarm.lock" "the generation claim left a lock held after finishing"
1018
+ pass "auto-arm: a hung generation owner with no watcher beat is superseded so re-arming self-heals"
1019
+ }
1020
+
1021
+ # Identity is mandatory at read time: a bare identityless one-line arming
1022
+ # ledger naming an unrelated live pid is NOT an open claim - it must neither
1023
+ # defer the hook nor survive as the current entry, whatever the beacon says.
1024
+ test_identityless_ledger_never_defers() {
1025
+ local dir out status pid
1026
+ dir=$(make_primary_dir "$TMP_ROOT/v2-identityless-ledger")
1027
+ : > "$dir/state/task1.meta"
1028
+ write_arm_fixture "$dir" actionable
1029
+ sleep 60 &
1030
+ pid=$!
1031
+ printf 'epoch=464 owner_pid=%s outcome=arming updated_at=1\n' "$pid" \
1032
+ > "$dir/state/.claude-autoarm-epoch"
1033
+ touch -t 202001010000 "$dir/state/.claude-autoarm-epoch"
1034
+ : > "$dir/state/.last-watcher-beat"
1035
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
1036
+ kill -0 "$pid" 2>/dev/null || fail "the unrelated live pid on an identityless ledger must never be signalled"
1037
+ kill "$pid" 2>/dev/null || true
1038
+ wait "$pid" 2>/dev/null || true
1039
+ expect_code 2 "$status" "an identityless arming ledger must be superseded, never deferred to"
1040
+ [ -e "$dir/state/arm-ran" ] || fail "an identityless ledger left the home unarmed"
1041
+ [ "$(epoch_field "$dir" epoch)" -gt 464 ] || fail "the identityless entry was not superseded: $(epoch_field "$dir" epoch)"
1042
+ pass "auto-arm: an identityless arming ledger never defers the gate (reused-pid loophole closed)"
1043
+ }
1044
+
1045
+ # A superseded owner must not start or attach another watcher: when its claim
1046
+ # is superseded between arm attempts, the retry boundary goes silent instead
1047
+ # of invoking the arm again.
1048
+ test_superseded_owner_never_reinvokes_the_arm() {
1049
+ local dir out status count
1050
+ dir=$(make_primary_dir "$TMP_ROOT/v2-superseded-arm-boundary")
1051
+ : > "$dir/state/task1.meta"
1052
+ write_arm_fixture "$dir" supersede-then-fail
1053
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
1054
+ expect_code 0 "$status" "an owner superseded between arm attempts must exit 0 silently"
1055
+ [ -z "$out" ] || fail "a superseded owner produced output at the arm boundary: $out"
1056
+ count=$(wc -l < "$dir/state/arm-ran" | tr -d ' ')
1057
+ [ "$count" -eq 1 ] || fail "a superseded owner re-invoked the arm, saw $count arms"
1058
+ [ "$(epoch_field "$dir" epoch)" = 999 ] || fail "a superseded owner rewrote its successor's ledger entry: $(epoch_field "$dir" epoch)"
1059
+ pass "auto-arm: a superseded owner never re-invokes the arm and leaves its successor's claim untouched"
1060
+ }
1061
+
1062
+ # End-to-end regression for all three concurrency edge classes at once, with a
1063
+ # REAL hook process hung mid-arm:
1064
+ # 1. no mutex across blocking steps - while owner A is mid-arm, a concurrent
1065
+ # firing B defers promptly instead of queueing on any lock;
1066
+ # 2. stuck-owner supersession - once A's claim and the beacon age past grace
1067
+ # while A is still alive arming, firing C takes the next generation and
1068
+ # translates its own close (exit 2);
1069
+ # 3. no double-translation - when A's arm finally returns, A finds itself
1070
+ # superseded and goes completely silent (exit 0, no banner, no ledger
1071
+ # write), so one supersession episode produces exactly one translation.
1072
+ test_superseded_owner_goes_silent_and_never_double_translates() {
1073
+ local dir a_out a_pid b_out b_status c_out c_status a_status i count
1074
+ dir=$(make_primary_dir "$TMP_ROOT/v2-superseded-silence")
1075
+ : > "$dir/state/task1.meta"
1076
+ write_arm_fixture "$dir" blocking-actionable
1077
+ a_out="$dir/state/a.out"
1078
+ run_autoarm_bg "$dir" "$a_out"
1079
+ a_pid=$RUN_AUTOARM_BG_PID
1080
+ i=0
1081
+ while [ "$(epoch_outcome "$dir")" != arming ] || [ ! -e "$dir/state/arm-ran" ]; do
1082
+ [ "$i" -lt 50 ] || fail "owner A never published its arming claim"
1083
+ sleep 0.1
1084
+ i=$((i + 1))
1085
+ done
1086
+ b_out=$(run_autoarm "$dir" 2>/dev/null); b_status=$?
1087
+ expect_code 0 "$b_status" "a firing during a live open claim must defer promptly (no mutex is held across arming)"
1088
+ [ -z "$b_out" ] || fail "deferring firing produced output: $b_out"
1089
+ count=$(wc -l < "$dir/state/arm-ran" | tr -d ' ')
1090
+ [ "$count" -eq 1 ] || fail "deferring firing must not arm, saw $count arms"
1091
+ # A is still alive mid-arm; make its claim stuck-shaped.
1092
+ kill -0 "$a_pid" 2>/dev/null || fail "owner A finished before the supersession could be exercised"
1093
+ touch -t 202001010000 "$dir/state/.claude-autoarm-epoch"
1094
+ touch -t 202001010000 "$dir/state/.last-watcher-beat"
1095
+ c_out=$(run_autoarm "$dir" 2>/dev/null); c_status=$?
1096
+ expect_code 2 "$c_status" "the superseding generation must translate its own close"
1097
+ assert_contains "$c_out" "firstmate watcher wake" "the superseding generation must carry the rewake banner"
1098
+ wait "$a_pid"
1099
+ a_status=$?
1100
+ expect_code 0 "$a_status" "the superseded owner must exit 0 instead of double-translating"
1101
+ [ ! -s "$a_out" ] || fail "the superseded owner emitted output after losing its generation: $(cat "$a_out")"
1102
+ [ "$(epoch_field "$dir" epoch)" = 2 ] || fail "the superseded owner advanced the ledger past its successor: $(epoch_field "$dir" epoch)"
1103
+ [ "$(epoch_outcome "$dir")" = rewake ] || fail "the superseding generation's outcome was overwritten: $(epoch_outcome "$dir")"
1104
+ count=$(wc -l < "$dir/state/arm-ran" | tr -d ' ')
1105
+ [ "$count" -eq 2 ] || fail "expected exactly the owner and superseder arms, saw $count"
1106
+ pass "auto-arm: a superseded owner goes silent - one supersession episode, one translation, no held mutex"
1107
+ }
1108
+
1109
+ test_need_vanished_mid_cycle_closes_quietly() {
1110
+ local dir out status
1111
+ dir=$(make_primary_dir "$TMP_ROOT/vanished")
1112
+ : > "$dir/state/task.meta"
1113
+ write_arm_fixture "$dir" meta-vanishes
1114
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
1115
+ expect_code 0 "$status" "an actionable close after the fleet went idle must not rewake"
1116
+ [ -z "$out" ] || fail "vanished-need close produced output: $out"
1117
+ [ "$(epoch_outcome "$dir")" = clean ] || fail "epoch must record outcome=clean, got: $(epoch_outcome "$dir")"
1118
+ pass "auto-arm: need vanishing mid-cycle closes without a rewake"
1119
+ }
1120
+
1121
+ test_afk_mid_cycle_suppresses_rewake() {
1122
+ local dir out status
1123
+ dir=$(make_primary_dir "$TMP_ROOT/afk-mid")
1124
+ : > "$dir/state/task.meta"
1125
+ write_arm_fixture "$dir" afk-appears
1126
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
1127
+ expect_code 0 "$status" "AFK appearing mid-cycle must suppress the primary rewake"
1128
+ [ -z "$out" ] || fail "AFK-suppressed close produced output: $out"
1129
+ [ "$(epoch_outcome "$dir")" = afk ] || fail "epoch must record outcome=afk, got: $(epoch_outcome "$dir")"
1130
+ pass "auto-arm: mid-cycle AFK hands triage to the daemon with no rewake"
1131
+ }
1132
+
1133
+ test_active_in_marked_secondmate_home() {
1134
+ local dir out status
1135
+ dir=$(make_secondmate_dir "$TMP_ROOT/secondmate")
1136
+ : > "$dir/state/task.meta"
1137
+ write_arm_fixture "$dir" actionable
1138
+ out=$(run_autoarm "$dir" 2>/dev/null); status=$?
1139
+ expect_code 2 "$status" "a marked secondmate home must get the same active auto-arm as the main primary"
1140
+ [ -e "$dir/state/arm-ran" ] || fail "hook did not arm in a marked secondmate home"
1141
+ [ "$(epoch_outcome "$dir")" = rewake ] || fail "secondmate epoch must record outcome=rewake"
1142
+ pass "auto-arm: active in a marked secondmate home"
1143
+ }
1144
+
1145
+ test_fm_lock_status_still_works_with_shared_lib() {
1146
+ local out
1147
+ out=$(FM_HOME="$TMP_ROOT/lock-status-home" bash "$ROOT/bin/fm-lock.sh" status 2>&1)
1148
+ assert_contains "$out" "lock: free" "fm-lock.sh status must keep working after the session-lock lib extraction"
1149
+ pass "fm-lock: shared session-lock lib preserves the status path"
1150
+ }
1151
+
1152
+ test_inert_in_child_worktree
1153
+ test_inert_without_session_lock
1154
+ test_reclaims_stale_session_lock_before_arming
1155
+ test_inert_when_lock_held_by_other_harness
1156
+ test_inert_when_afk
1157
+ test_stale_lock_recovery_preserves_afk_and_need_gates
1158
+ test_resolves_outermost_claude_pid_in_nested_bgspare_chain
1159
+ test_inert_when_fleet_idle
1160
+ test_actionable_close_rewakes_with_reason
1161
+ test_actionable_close_with_live_successor_rewakes_once
1162
+ test_failed_close_rewakes_with_failure_banner
1163
+ test_failed_cycles_notify_once_and_keep_retrying
1164
+ test_failure_notice_marker_write_refuses_delivery_and_retries
1165
+ test_unverified_clean_close_exhausts_retries
1166
+ test_post_alarm_actionable_close_is_suppressed
1167
+ test_benign_cycle_end_with_live_watcher_is_silent
1168
+ test_positive_recovery_budget_contention_preserves_episode
1169
+ test_owner_mutex_contention_preserves_failure_episode_reset
1170
+ test_arms_for_x_mode_poll_need_without_inflight
1171
+ test_single_flight_admits_exactly_one_owner
1172
+ test_abandoned_owner_claim_is_reclaimed_and_rearms
1173
+ test_arming_claim_with_fresh_beacon_is_never_reclaimed
1174
+ test_fresh_arming_claim_with_stale_beacon_is_never_reclaimed
1175
+ test_claim_not_named_by_the_ledger_is_never_reclaimed
1176
+ test_pid_reused_arming_claim_is_reclaimed_and_rearms
1177
+ test_pid_reused_claim_with_no_ledger_is_reclaimed_and_rearms
1178
+ test_identity_matched_arming_claim_is_never_reclaimed
1179
+ test_terminal_check_claim_is_never_reclaimed
1180
+ test_stuck_live_legacy_owner_is_retired_and_reclaimed
1181
+ test_stopped_legacy_owner_is_reclaimed_with_term_pending
1182
+ test_open_generation_claim_defers_without_any_lock
1183
+ test_stuck_generation_claim_is_superseded_and_rearms
1184
+ test_identityless_ledger_never_defers
1185
+ test_superseded_owner_never_reinvokes_the_arm
1186
+ test_superseded_owner_goes_silent_and_never_double_translates
1187
+ test_need_vanished_mid_cycle_closes_quietly
1188
+ test_afk_mid_cycle_suppresses_rewake
1189
+ test_active_in_marked_secondmate_home
1190
+ test_fm_lock_status_still_works_with_shared_lib