@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,961 @@
1
+ #!/usr/bin/env bash
2
+ # tests/fm-afk-launch.test.sh - the script-owned, backend-aware away-daemon
3
+ # launch (bin/fm-afk-launch.sh) and the away-mode stale-artifact lifecycle fixes
4
+ # (bin/fm-afk-start.sh). Two layers:
5
+ #
6
+ # UNIT (always run, no backend): the session-scoped stale-artifact clear on a
7
+ # fresh entry vs a refresh, and the correct-ordered stop (daemon SIGTERM'd
8
+ # while state/.afk is still present, .afk cleared last).
9
+ #
10
+ # E2E TOPOLOGY (per backend, skipped when its tool is absent): the anti-
11
+ # regression for the pane split/shrink - entering AND exiting away mode leaves
12
+ # the captain's active tab topology UNCHANGED, because the daemon lands in a
13
+ # NON-VISIBLE separate terminal (a herdr dedicated workspace, a detached tmux
14
+ # session), never a split of the captain's pane. The herdr path runs on a
15
+ # throwaway, NEVER-default HERDR_SESSION and asserts the default session is
16
+ # byte-identical via the fm-herdr-lab.sh fleet-state tripwire; the tmux path
17
+ # uses uniquely-named throwaway sessions killed by exact name. A harmless
18
+ # sleeper replaces the real daemon (FM_AFK_LAUNCH_ENTRY) so the test observes
19
+ # only the terminal lifecycle.
20
+ set -u
21
+
22
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
23
+ LAUNCH="$ROOT/bin/fm-afk-launch.sh"
24
+ START="$ROOT/bin/fm-afk-start.sh"
25
+
26
+ FAILED=0
27
+ fail() { printf 'not ok - %s\n' "$1" >&2; FAILED=1; }
28
+ pass() { printf 'ok - %s\n' "$1"; }
29
+
30
+ SLEEPER=$(mktemp "${TMPDIR:-/tmp}/fm-afk-sleeper.XXXXXX")
31
+ printf '#!/usr/bin/env bash\nexec sleep 600\n' > "$SLEEPER"
32
+ chmod +x "$SLEEPER"
33
+ TRACK_TMUX_SESSIONS=""
34
+ GLOBAL_CLEANUP() {
35
+ rm -f "$SLEEPER" 2>/dev/null || true
36
+ local s
37
+ for s in $TRACK_TMUX_SESSIONS; do
38
+ tmux kill-session -t "$s" 2>/dev/null || true
39
+ done
40
+ }
41
+ trap GLOBAL_CLEANUP EXIT
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # UNIT 1: fm_afk_clear_stale_artifacts removes exactly the three stale artifacts.
45
+ # ---------------------------------------------------------------------------
46
+ unit_clear_stale() {
47
+ local st
48
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-clear.XXXXXX")
49
+ mkdir -p "$st/state"
50
+ : > "$st/state/.subsuper-escalations"
51
+ : > "$st/state/.subsuper-escalations.since"
52
+ : > "$st/state/.subsuper-inject-wedged"
53
+ : > "$st/state/.wake-queue" # durable queue must be untouched
54
+ # Source fm-afk-start.sh inside a child bash (it sets `set -eu` and would
55
+ # otherwise leak that into this test shell) and call the clear helper.
56
+ FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" \
57
+ bash -c '. "$1"; fm_afk_clear_stale_artifacts "$2"' _ "$START" "$st/state"
58
+ if [ ! -e "$st/state/.subsuper-escalations" ] \
59
+ && [ ! -e "$st/state/.subsuper-escalations.since" ] \
60
+ && [ ! -e "$st/state/.subsuper-inject-wedged" ]; then
61
+ pass "clear-stale: removes escalations buffer, sidecar, and wedge marker"
62
+ else
63
+ fail "clear-stale: stale artifacts survived"
64
+ fi
65
+ if [ -e "$st/state/.wake-queue" ]; then
66
+ pass "clear-stale: leaves the durable wake-queue intact (no pending work dropped)"
67
+ else
68
+ fail "clear-stale: removed the durable wake-queue"
69
+ fi
70
+ rm -rf "$st"
71
+ }
72
+
73
+ unit_relative_paths_are_absolute_before_daemon_launch() {
74
+ local root home state out status linked_home
75
+ root=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-relative-home.XXXXXX")
76
+ mkdir -p "$root/home/state" "$root/cdpath/home/state"
77
+ home=$(cd "$root/home" && pwd -P)
78
+ state="$home/state"
79
+ out=$(
80
+ cd "$root" || exit 1
81
+ CDPATH="$root/cdpath" FM_HOME=home FM_STATE_OVERRIDE=home/state \
82
+ bash -c '. "$1"; printf "%s\n%s\n" "$FM_HOME" "$FM_AFK_LAUNCH_STATE"' _ "$LAUNCH"
83
+ )
84
+ if [ "$out" = "$home"$'\n'"$state" ]; then
85
+ pass "launcher paths: relative home and state ignore CDPATH before daemon command construction"
86
+ else
87
+ fail "launcher paths: relative home or state remained cwd-dependent ($out)"
88
+ fi
89
+ linked_home="$root/home-link"
90
+ ln -s "$root/home" "$linked_home"
91
+ out=$(FM_HOME="$linked_home" FM_STATE_OVERRIDE="$linked_home/state" \
92
+ bash -c '. "$1"; printf "%s\n%s\n" "$FM_HOME" "$FM_AFK_LAUNCH_STATE"' _ "$LAUNCH")
93
+ if [ "$out" = "$linked_home"$'\n'"$linked_home/state" ]; then
94
+ pass "launcher paths: absolute symlink spellings are preserved"
95
+ else
96
+ fail "launcher paths: absolute symlink spelling changed ($out)"
97
+ fi
98
+ out=$(
99
+ cd "$root" || exit 1
100
+ FM_HOME=missing-home "$LAUNCH" help 2>&1
101
+ )
102
+ status=$?
103
+ if [ "$status" -ne 0 ] && printf '%s\n' "$out" | grep -F "FM_HOME directory cannot be resolved: missing-home" >/dev/null; then
104
+ pass "launcher paths: unresolved relative FM_HOME fails loudly"
105
+ else
106
+ fail "launcher paths: unresolved relative FM_HOME did not name the bad input ($out)"
107
+ fi
108
+ out=$(
109
+ cd "$root" || exit 1
110
+ FM_HOME=home FM_STATE_OVERRIDE=missing-state "$LAUNCH" help 2>&1
111
+ )
112
+ status=$?
113
+ if [ "$status" -ne 0 ] && printf '%s\n' "$out" | grep -F "FM_STATE_OVERRIDE directory cannot be resolved: missing-state" >/dev/null; then
114
+ pass "launcher paths: unresolved relative FM_STATE_OVERRIDE fails loudly"
115
+ else
116
+ fail "launcher paths: unresolved relative FM_STATE_OVERRIDE did not name the bad input ($out)"
117
+ fi
118
+ rm -rf "$root"
119
+ }
120
+
121
+ # ---------------------------------------------------------------------------
122
+ # UNIT 2: a FRESH entry clears; a REFRESH (daemon already alive) preserves the
123
+ # current session's buffered escalations.
124
+ # ---------------------------------------------------------------------------
125
+ unit_fresh_vs_refresh() {
126
+ local st sleep_pid lock
127
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-refresh.XXXXXX")
128
+ mkdir -p "$st/state"
129
+ : > "$st/state/.subsuper-escalations"
130
+ : > "$st/state/.subsuper-inject-wedged"
131
+ # A live "daemon": a real process whose identity the lock records, so
132
+ # daemon_lock_held_by_live_daemon returns true (a refresh).
133
+ sleep 600 &
134
+ sleep_pid=$!
135
+ lock="$st/state/.supervise-daemon.lock"
136
+ mkdir -p "$lock"
137
+ printf '%s' "$sleep_pid" > "$lock/pid"
138
+ ( . "$ROOT/bin/fm-wake-lib.sh"; fm_pid_identity "$sleep_pid" > "$lock/pid-identity" 2>/dev/null ) || true
139
+ FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" "$START" >/dev/null 2>&1
140
+ if [ -e "$st/state/.subsuper-escalations" ] && [ -e "$st/state/.subsuper-inject-wedged" ]; then
141
+ pass "refresh: daemon already alive - stale artifacts preserved (current session's buffer kept)"
142
+ else
143
+ fail "refresh: incorrectly cleared the current session's buffered escalations"
144
+ fi
145
+ kill "$sleep_pid" 2>/dev/null || true
146
+ wait "$sleep_pid" 2>/dev/null || true
147
+ rm -rf "$st"
148
+ }
149
+
150
+ # ---------------------------------------------------------------------------
151
+ # UNIT 3: exit ordering - fm_afk_launch_stop SIGTERMs the daemon WHILE .afk is
152
+ # still present (so its flush is not a no-op), and clears .afk last.
153
+ # ---------------------------------------------------------------------------
154
+ unit_stop_ordering() {
155
+ local st lock marker daemon_pid
156
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-stop.XXXXXX")
157
+ mkdir -p "$st/state"
158
+ date '+%s' > "$st/state/.afk"
159
+ marker="$st/afk-at-term"
160
+ # A fake daemon: on SIGTERM, record whether .afk was still present, then exit.
161
+ bash -c '
162
+ trap "if [ -f \"$1/state/.afk\" ]; then echo present > \"$2\"; else echo absent > \"$2\"; fi; exit 0" TERM
163
+ while :; do sleep 0.2; done
164
+ ' _ "$st" "$marker" &
165
+ daemon_pid=$!
166
+ lock="$st/state/.supervise-daemon.lock"
167
+ mkdir -p "$lock"
168
+ printf '%s' "$daemon_pid" > "$lock/pid"
169
+ ( . "$ROOT/bin/fm-wake-lib.sh"; fm_pid_identity "$daemon_pid" > "$lock/pid-identity" 2>/dev/null ) || true
170
+ printf 'none\t-\tnative\n' > "$st/state/.afk-daemon-terminal"
171
+ FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" "$LAUNCH" stop >/dev/null 2>&1
172
+ # shellcheck disable=SC2031 # The background daemon writes this shared file; no shell variable is reassigned.
173
+ if [ "$(cat "$marker" 2>/dev/null || echo missing)" = present ]; then
174
+ pass "stop-ordering: daemon SIGTERM'd while .afk still present (flush is not a no-op)"
175
+ else
176
+ fail "stop-ordering: .afk was already cleared when the daemon got SIGTERM"
177
+ fi
178
+ if [ ! -e "$st/state/.afk" ]; then
179
+ pass "stop-ordering: .afk cleared last"
180
+ else
181
+ fail "stop-ordering: .afk not cleared"
182
+ fi
183
+ if [ ! -e "$st/state/.afk-daemon-terminal" ]; then
184
+ pass "stop-ordering: daemon-terminal record removed"
185
+ else
186
+ fail "stop-ordering: record not removed"
187
+ fi
188
+ kill "$daemon_pid" 2>/dev/null || true
189
+ wait "$daemon_pid" 2>/dev/null || true
190
+ rm -rf "$st"
191
+ }
192
+
193
+ unit_stop_rejects_reused_pid() {
194
+ local st lock sleeper_pid
195
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-pid-reuse.XXXXXX")
196
+ mkdir -p "$st/state"
197
+ date '+%s' > "$st/state/.afk"
198
+ sleep 600 &
199
+ sleeper_pid=$!
200
+ lock="$st/state/.supervise-daemon.lock"
201
+ mkdir -p "$lock"
202
+ printf '%s' "$sleeper_pid" > "$lock/pid"
203
+ printf 'different-process-identity' > "$lock/pid-identity"
204
+ FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" "$LAUNCH" stop >/dev/null 2>&1
205
+ if kill -0 "$sleeper_pid" 2>/dev/null; then
206
+ pass "stop identity: stale lock cannot signal an unrelated live process"
207
+ else
208
+ fail "stop identity: stale lock signaled an unrelated live process"
209
+ fi
210
+ kill "$sleeper_pid" 2>/dev/null || true
211
+ wait "$sleeper_pid" 2>/dev/null || true
212
+ rm -rf "$st"
213
+ }
214
+
215
+ unit_failed_start_rolls_back_state() {
216
+ local st
217
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-failed-start.XXXXXX")
218
+ mkdir -p "$st/state"
219
+ printf 'pending\n' > "$st/state/.subsuper-escalations"
220
+ printf 'wedged\n' > "$st/state/.subsuper-inject-wedged"
221
+ if FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" FM_SUPERVISOR_TARGET=unused \
222
+ FM_SUPERVISOR_BACKEND=unsupported "$LAUNCH" start >/dev/null 2>&1; then
223
+ fail "failed start: unsupported backend unexpectedly succeeded"
224
+ elif [ ! -e "$st/state/.afk" ] \
225
+ && [ "$(cat "$st/state/.subsuper-escalations")" = pending ] \
226
+ && [ "$(cat "$st/state/.subsuper-inject-wedged")" = wedged ]; then
227
+ pass "failed start: away flag and delivery artifacts roll back"
228
+ else
229
+ fail "failed start: left false away state or discarded delivery artifacts"
230
+ fi
231
+ rm -rf "$st"
232
+ }
233
+
234
+ unit_concurrent_start_serialized() {
235
+ command -v tmux >/dev/null 2>&1 || { echo "skip: tmux not found (concurrent start)"; return 0; }
236
+ local st cap_session cap_pane first second rec count
237
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-concurrent.XXXXXX")
238
+ cap_session="fm-afk-concurrent-cap-$$"
239
+ tmux new-session -d -s "$cap_session" 2>/dev/null || { fail "concurrent start: captain session creation failed"; rm -rf "$st"; return 0; }
240
+ TRACK_TMUX_SESSIONS="$TRACK_TMUX_SESSIONS $cap_session"
241
+ cap_pane=$(tmux display-message -p -t "$cap_session" '#{pane_id}')
242
+ FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" FM_SUPERVISOR_TARGET="$cap_pane" \
243
+ FM_SUPERVISOR_BACKEND=tmux FM_AFK_LAUNCH_ENTRY="$SLEEPER" "$LAUNCH" start >/dev/null 2>&1 & first=$!
244
+ FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" FM_SUPERVISOR_TARGET="$cap_pane" \
245
+ FM_SUPERVISOR_BACKEND=tmux FM_AFK_LAUNCH_ENTRY="$SLEEPER" "$LAUNCH" start >/dev/null 2>&1 & second=$!
246
+ wait "$first"; wait "$second"
247
+ rec=$(cut -f2 "$st/state/.afk-daemon-terminal" 2>/dev/null || true)
248
+ count=$(tmux list-sessions -F '#{session_name}' 2>/dev/null | awk -v expected="$rec" '$0 == expected {n++} END{print n+0}')
249
+ TRACK_TMUX_SESSIONS="$TRACK_TMUX_SESSIONS $rec"
250
+ if [ -n "$rec" ] && tmux has-session -t "$rec" 2>/dev/null && [ "$count" -eq 1 ]; then
251
+ pass "concurrent start: one serialized daemon terminal remains tracked"
252
+ else
253
+ fail "concurrent start: leaked or lost daemon terminal (count $count, record $rec)"
254
+ fi
255
+ FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" "$LAUNCH" stop >/dev/null 2>&1
256
+ tmux kill-session -t "$cap_session" 2>/dev/null || true
257
+ rm -rf "$st"
258
+ }
259
+
260
+ unit_lock_initialization_grace() {
261
+ local st marker initializer
262
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-lock-init.XXXXXX")
263
+ marker="$st/initialized"
264
+ mkdir -p "$st/state/.afk-launch.lock"
265
+ (
266
+ sleep 0.15
267
+ if [ -d "$st/state/.afk-launch.lock" ]; then
268
+ printf '%s' "$$" > "$st/state/.afk-launch.lock/pid"
269
+ ( . "$ROOT/bin/fm-wake-lib.sh"; fm_pid_identity "$$" > "$st/state/.afk-launch.lock/pid-identity" 2>/dev/null ) || true
270
+ # shellcheck disable=SC2031 # The subshell writes the path value; it does not reassign the variable.
271
+ : > "$marker"
272
+ sleep 0.15
273
+ rm -rf "$st/state/.afk-launch.lock"
274
+ fi
275
+ ) &
276
+ initializer=$!
277
+ # shellcheck disable=SC2031 # The initializer communicates through this shared file path.
278
+ if FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" bash -c '
279
+ . "$1"
280
+ fm_afk_launch_lock_acquire
281
+ fm_afk_launch_lock_release
282
+ ' _ "$LAUNCH" && [ -e "$marker" ]; then
283
+ pass "launcher lock: incomplete publication receives initialization grace"
284
+ else
285
+ fail "launcher lock: contender removed a lock during initialization"
286
+ fi
287
+ wait "$initializer" 2>/dev/null || true
288
+ rm -rf "$st"
289
+ }
290
+
291
+ unit_signal_exits_with_lock_cleanup() {
292
+ local st marker child
293
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-signal.XXXXXX")
294
+ marker="$st/resumed"
295
+ FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" bash -c '
296
+ . "$1"
297
+ fm_afk_launch_start() { sleep 30; }
298
+ fm_afk_launch_main start
299
+ : > "$2"
300
+ ' _ "$LAUNCH" "$marker" &
301
+ child=$!
302
+ # Signal only once the lifecycle actually holds its lock. Killing before the
303
+ # lock exists tests nothing, and on a loaded machine it used to race: the
304
+ # lock could be created just after the kill and outlive the process.
305
+ local locked=0 _
306
+ for _ in $(seq 1 100); do
307
+ if [ -d "$st/state/.afk-launch.lock" ]; then locked=1; break; fi
308
+ sleep 0.05
309
+ done
310
+ [ "$locked" = 1 ] || fail "launcher signal: lifecycle never acquired its lock to interrupt"
311
+ kill -TERM "$child" 2>/dev/null || true
312
+ wait "$child" 2>/dev/null || true
313
+ # The signal handler releases the lock as it exits; give that removal a
314
+ # bounded settle rather than sampling the instant `wait` returns.
315
+ for _ in $(seq 1 100); do
316
+ [ -e "$st/state/.afk-launch.lock" ] || break
317
+ sleep 0.05
318
+ done
319
+ if [ ! -e "$marker" ] && [ ! -e "$st/state/.afk-launch.lock" ]; then
320
+ pass "launcher signal: TERM exits and releases the lifecycle lock"
321
+ else
322
+ fail "launcher signal: interrupted lifecycle resumed or retained its lock"
323
+ fi
324
+ rm -rf "$st"
325
+ }
326
+
327
+ unit_herdr_partial_create_recovery() {
328
+ local st recorded
329
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-herdr-partial.XXXXXX")
330
+ recorded="$st/recorded"
331
+ FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" FM_AFK_LAUNCH_ENTRY=/bin/true \
332
+ FM_AFK_LAUNCH_LABEL=afk-exact-label RECORDED="$recorded" bash -c '
333
+ . "$1"
334
+ fm_backend_source() { return 0; }
335
+ fm_backend_herdr_server_ensure() { return 0; }
336
+ fm_backend_herdr_cli() {
337
+ if [ "$2 $3" = "workspace create" ]; then
338
+ printf %s '\''truncated'\''
339
+ return 1
340
+ elif [ "$2 $3" = "workspace list" ]; then
341
+ printf %s '\''{"result":{"workspaces":[{"workspace_id":"ws-partial","label":"afk-exact-label"}]}}'\''
342
+ else
343
+ printf %s '\''{"result":{"panes":[{"pane_id":"pane-exact"}]}}'\''
344
+ fi
345
+ }
346
+ fm_afk_launch_record_write() { printf "%s:%s:%s" "$1" "$2" "$3" > "$RECORDED"; }
347
+ fm_afk_launch_create_herdr lab:captain herdr
348
+ ' _ "$LAUNCH"
349
+ if [ "$(cat "$recorded" 2>/dev/null || true)" = "herdr:lab:pane-exact:ws-partial" ]; then
350
+ pass "herdr create: malformed response recovers durable exact ownership"
351
+ else
352
+ fail "herdr create: malformed response left terminal ownership unknown"
353
+ fi
354
+ rm -rf "$st"
355
+ }
356
+
357
+ unit_herdr_error_with_exact_ids_closes_exact() {
358
+ local st
359
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-herdr-error-exact.XXXXXX")
360
+ FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" bash -c '
361
+ . "$1"
362
+ fm_backend_source() { return 0; }
363
+ fm_backend_herdr_server_ensure() { return 0; }
364
+ fm_backend_herdr_cli() {
365
+ if [ "$2 $3" = "workspace create" ]; then
366
+ printf %s '\''{"result":{"workspace":{"workspace_id":"ws-exact"},"root_pane":{"pane_id":"pane-exact"}}}'\''
367
+ return 1
368
+ elif [ "$2 $3" = "pane get" ]; then
369
+ printf %s '\''{"error":{"code":"transport_error"}}'\''
370
+ return 2
371
+ fi
372
+ return 2
373
+ }
374
+ ! fm_afk_launch_create_herdr lab:captain herdr
375
+ ' _ "$LAUNCH"
376
+ if [ "$(cut -f2 "$st/state/.afk-daemon-terminal" 2>/dev/null || true)" = "lab:pane-exact" ]; then
377
+ pass "herdr create error: unconfirmed exact id is persisted for reconciliation"
378
+ else
379
+ fail "herdr create error: unconfirmed exact cleanup id was discarded"
380
+ fi
381
+ rm -rf "$st"
382
+ }
383
+
384
+ unit_herdr_run_failure_preserves_unconfirmed_record() {
385
+ local st
386
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-herdr-run-fail.XXXXXX")
387
+ FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" bash -c '
388
+ . "$1"
389
+ fm_backend_source() { return 0; }
390
+ fm_backend_herdr_server_ensure() { return 0; }
391
+ fm_backend_herdr_cli() {
392
+ if [ "$2 $3" = "workspace create" ]; then
393
+ printf %s '\''{"result":{"workspace":{"workspace_id":"ws-exact"},"root_pane":{"pane_id":"pane-exact"}}}'\''
394
+ return 0
395
+ elif [ "$2 $3" = "pane run" ]; then
396
+ return 1
397
+ elif [ "$2 $3" = "pane get" ]; then
398
+ printf %s '\''{"error":{"code":"transport_error"}}'\''
399
+ return 2
400
+ fi
401
+ return 2
402
+ }
403
+ ! fm_afk_launch_create_herdr lab:captain herdr
404
+ ' _ "$LAUNCH"
405
+ if [ "$(cut -f2 "$st/state/.afk-daemon-terminal" 2>/dev/null || true)" = "lab:pane-exact" ]; then
406
+ pass "herdr run failure: unconfirmed exact id remains reconcilable"
407
+ else
408
+ fail "herdr run failure: unconfirmed exact id was discarded"
409
+ fi
410
+ rm -rf "$st"
411
+ }
412
+
413
+ unit_record_failure_closes_terminal() {
414
+ local st closed
415
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-record-fail.XXXXXX")
416
+ closed="$st/closed"
417
+ FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" CLOSED="$closed" bash -c '
418
+ . "$1"
419
+ fm_afk_launch_record_write() { return 1; }
420
+ fm_afk_launch_close_terminal() { printf "%s:%s" "$1" "$2" > "$CLOSED"; }
421
+ ! fm_afk_launch_commit_terminal tmux exact-session ""
422
+ ' _ "$LAUNCH"
423
+ if [ "$(cat "$closed" 2>/dev/null || true)" = "tmux:exact-session" ]; then
424
+ pass "record failure: newly created terminal is closed by exact id"
425
+ else
426
+ fail "record failure: newly created terminal leaked"
427
+ fi
428
+ rm -rf "$st"
429
+ }
430
+
431
+ unit_readiness_failure_rolls_back_terminal() {
432
+ local st closed
433
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-not-ready.XXXXXX")
434
+ closed="$st/closed"
435
+ FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" CLOSED="$closed" bash -c '
436
+ . "$1"
437
+ fm_afk_launch_wait_ready() { return 1; }
438
+ fm_afk_launch_close_terminal() { printf "%s:%s" "$1" "$2" > "$CLOSED"; }
439
+ fm_afk_launch_terminal_absent() { [ -e "$CLOSED" ]; }
440
+ ! fm_afk_launch_commit_terminal tmux exact-session ""
441
+ ' _ "$LAUNCH"
442
+ if [ "$(cat "$closed" 2>/dev/null || true)" = "tmux:exact-session" ] \
443
+ && [ ! -e "$st/state/.afk-daemon-terminal" ]; then
444
+ pass "readiness failure: exact terminal and durable record roll back"
445
+ else
446
+ fail "readiness failure: terminal or record survived"
447
+ fi
448
+ rm -rf "$st"
449
+ }
450
+
451
+ unit_readiness_failure_preserves_unconfirmed_record() {
452
+ local st
453
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-not-ready-unconfirmed.XXXXXX")
454
+ FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" bash -c '
455
+ . "$1"
456
+ fm_afk_launch_wait_ready() { return 1; }
457
+ fm_afk_launch_close_terminal() { return 1; }
458
+ fm_afk_launch_terminal_absent() { return 1; }
459
+ ! fm_afk_launch_commit_terminal tmux exact-session ""
460
+ ' _ "$LAUNCH"
461
+ if [ "$(cut -f2 "$st/state/.afk-daemon-terminal" 2>/dev/null || true)" = exact-session ]; then
462
+ pass "readiness failure: unconfirmed terminal retains its reconciliation id"
463
+ else
464
+ fail "readiness failure: unconfirmed terminal lost its reconciliation id"
465
+ fi
466
+ rm -rf "$st"
467
+ }
468
+
469
+ unit_tmux_absence_distinguishes_probe_failure() {
470
+ local st
471
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-tmux-probe.XXXXXX")
472
+ if FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" bash -c '
473
+ . "$1"
474
+ tmux() { printf "%s" "can'\''t find session: exact-session" >&2; return 1; }
475
+ fm_afk_launch_terminal_absent tmux exact-session
476
+ tmux() { printf "%s" "error connecting to /tmp/tmux.sock" >&2; return 1; }
477
+ ! fm_afk_launch_terminal_absent tmux exact-session
478
+ ' _ "$LAUNCH"; then
479
+ pass "tmux absence: clean missing differs from transport probe failure"
480
+ else
481
+ fail "tmux absence: probe failure was treated as confirmed absence"
482
+ fi
483
+ rm -rf "$st"
484
+ }
485
+
486
+ unit_native_lifecycle() {
487
+ local st
488
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-native.XXXXXX")
489
+ mkdir -p "$st/state"
490
+ : > "$st/state/.subsuper-escalations"
491
+ if FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" "$LAUNCH" start-native >/dev/null 2>&1 \
492
+ && [ "$(cut -f1 "$st/state/.afk-daemon-terminal")" = none ] \
493
+ && [ -e "$st/state/.afk" ] \
494
+ && [ ! -e "$st/state/.subsuper-escalations" ]; then
495
+ pass "native lifecycle: launcher owns state with no terminal"
496
+ else
497
+ fail "native lifecycle: state preparation or no-terminal record failed"
498
+ fi
499
+ FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" "$LAUNCH" stop >/dev/null 2>&1
500
+ if [ ! -e "$st/state/.afk" ] && [ ! -e "$st/state/.afk-daemon-terminal" ]; then
501
+ pass "native lifecycle: uniform stop clears state without closing a terminal"
502
+ else
503
+ fail "native lifecycle: uniform stop retained state"
504
+ fi
505
+ rm -rf "$st"
506
+ }
507
+
508
+ unit_native_entry_preserves_prepared_state() {
509
+ local st
510
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-native-entry.XXXXXX")
511
+ mkdir -p "$st/state"
512
+ : > "$st/state/.afk"
513
+ : > "$st/state/.subsuper-escalations"
514
+ FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" FM_AFK_STATE_PREPARED=1 bash -c '
515
+ . "$1"
516
+ FM_AFK_DAEMON=/bin/true
517
+ fm_afk_start_main
518
+ ' _ "$START" >/dev/null 2>&1
519
+ if [ -e "$st/state/.afk" ] && [ -e "$st/state/.subsuper-escalations" ]; then
520
+ pass "native entry: launcher-prepared lifecycle state is not rewritten"
521
+ else
522
+ fail "native entry: launcher-prepared lifecycle state was mutated"
523
+ fi
524
+ rm -rf "$st"
525
+ }
526
+
527
+ unit_close_failure_preserves_record() {
528
+ local st
529
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-close-fail.XXXXXX")
530
+ mkdir -p "$st/state"
531
+ printf 'tmux\texact-session\towned\n' > "$st/state/.afk-daemon-terminal"
532
+ FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" bash -c '
533
+ . "$1"
534
+ fm_afk_launch_close_terminal() { return 1; }
535
+ fm_afk_launch_terminal_absent() { return 1; }
536
+ ! fm_afk_launch_reconcile
537
+ ' _ "$LAUNCH"
538
+ if [ -e "$st/state/.afk-daemon-terminal" ]; then
539
+ pass "teardown failure: exact terminal record is preserved"
540
+ else
541
+ fail "teardown failure: exact terminal record was discarded"
542
+ fi
543
+ rm -rf "$st"
544
+ }
545
+
546
+ unit_record_publication_atomic() {
547
+ local st
548
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-record-atomic.XXXXXX")
549
+ mkdir -p "$st/state"
550
+ printf 'tmux\told-session\towned\n' > "$st/state/.afk-daemon-terminal"
551
+ if FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" bash -c '
552
+ . "$1"
553
+ mv() { return 1; }
554
+ ! fm_afk_launch_record_write tmux new-session owned
555
+ ' _ "$LAUNCH" \
556
+ && [ "$(cat "$st/state/.afk-daemon-terminal")" = $'tmux\told-session\towned' ] \
557
+ && ! find "$st/state" -name '.afk-daemon-terminal.pending.*' -print -quit | grep -q .; then
558
+ pass "record publication: failed atomic rename preserves the complete prior record"
559
+ else
560
+ fail "record publication: failed write truncated or replaced the prior record"
561
+ fi
562
+ rm -rf "$st"
563
+ }
564
+
565
+ unit_malformed_record_fails_closed() {
566
+ local st acted
567
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-record-malformed.XXXXXX")
568
+ mkdir -p "$st/state"
569
+ printf 'tmux\tonly-two-fields\n' > "$st/state/.afk-daemon-terminal"
570
+ acted="$st/acted"
571
+ if FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" ACTED="$acted" bash -c '
572
+ . "$1"
573
+ fm_afk_launch_close_terminal() { : > "$ACTED"; }
574
+ ! fm_afk_launch_reconcile
575
+ ' _ "$LAUNCH" \
576
+ && [ ! -e "$acted" ] && [ -e "$st/state/.afk-daemon-terminal" ]; then
577
+ pass "record read: malformed record fails closed without acting on a partial id"
578
+ else
579
+ fail "record read: malformed record was acted on or discarded"
580
+ fi
581
+ rm -rf "$st"
582
+ }
583
+
584
+ unit_stop_malformed_record_fails_closed() {
585
+ local st
586
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-stop-malformed.XXXXXX")
587
+ mkdir -p "$st/state"
588
+ : > "$st/state/.afk"
589
+ printf 'tmux\tonly-two-fields\n' > "$st/state/.afk-daemon-terminal"
590
+ if FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" bash -c '
591
+ . "$1"
592
+ ! fm_afk_launch_stop
593
+ ' _ "$LAUNCH" && [ -e "$st/state/.afk" ] && [ -e "$st/state/.afk-daemon-terminal" ]; then
594
+ pass "stop: malformed terminal record preserves away state and fails closed"
595
+ else
596
+ fail "stop: malformed terminal record cleared protected lifecycle state"
597
+ fi
598
+ rm -rf "$st"
599
+ }
600
+
601
+ unit_tmux_planned_record_and_collision() {
602
+ local st first second
603
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-tmux-plan.XXXXXX")
604
+ mkdir -p "$st/state"
605
+ if FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" bash -c '
606
+ . "$1"
607
+ tmux() {
608
+ if [ "$1" = new-session ]; then
609
+ [ -s "$FM_AFK_LAUNCH_RECORD" ] || return 9
610
+ printf "%s" "$4" > "$FM_HOME/created-name"
611
+ return 1
612
+ fi
613
+ [ "$1" != kill-session ] || : > "$FM_HOME/killed"
614
+ return 1
615
+ }
616
+ ! fm_afk_launch_create_tmux captain:0 tmux
617
+ ' _ "$LAUNCH" && [ ! -e "$st/state/.afk-daemon-terminal" ] && [ ! -e "$st/killed" ]; then
618
+ pass "tmux launch: planned exact target is recorded before creation and removed on failure"
619
+ else
620
+ fail "tmux launch: creation began before exact target publication"
621
+ fi
622
+ first=$(cat "$st/created-name")
623
+ rm -rf "$st"
624
+
625
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-tmux-unique.XXXXXX")
626
+ mkdir -p "$st/state"
627
+ if FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" bash -c '
628
+ . "$1"
629
+ tmux() {
630
+ [ "$1" != new-session ] || { printf "%s" "$4" > "$FM_HOME/created-name"; return 1; }
631
+ [ "$1" != kill-session ] || : > "$FM_HOME/killed"
632
+ return 1
633
+ }
634
+ ! fm_afk_launch_create_tmux captain:0 tmux
635
+ ' _ "$LAUNCH" && [ ! -e "$st/killed" ]; then
636
+ second=$(cat "$st/created-name")
637
+ if [ "$first" != "$second" ]; then
638
+ pass "tmux launch: unique names eliminate collision teardown"
639
+ else
640
+ fail "tmux launch: consecutive launches reused a session name"
641
+ fi
642
+ else
643
+ fail "tmux launch: creation failure attempted session teardown"
644
+ fi
645
+ rm -rf "$st"
646
+ }
647
+
648
+ unit_stop_validates_before_signal() {
649
+ local st sleeper_pid
650
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-stop-validate.XXXXXX")
651
+ mkdir -p "$st/state"
652
+ : > "$st/state/.afk"
653
+ printf 'tmux\tonly-two-fields\n' > "$st/state/.afk-daemon-terminal"
654
+ sleep 30 & sleeper_pid=$!
655
+ mkdir -p "$st/state/.supervise-daemon.lock"
656
+ printf '%s' "$sleeper_pid" > "$st/state/.supervise-daemon.lock/pid"
657
+ ( . "$ROOT/bin/fm-wake-lib.sh"; fm_pid_identity "$sleeper_pid" > "$st/state/.supervise-daemon.lock/pid-identity" )
658
+ FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" "$LAUNCH" stop >/dev/null 2>&1 || true
659
+ if kill -0 "$sleeper_pid" 2>/dev/null && [ -e "$st/state/.afk" ]; then
660
+ pass "stop validation: malformed record causes no daemon or state side effects"
661
+ else
662
+ fail "stop validation: malformed record signaled daemon or cleared state"
663
+ fi
664
+ kill "$sleeper_pid" 2>/dev/null || true
665
+ wait "$sleeper_pid" 2>/dev/null || true
666
+ rm -rf "$st"
667
+ }
668
+
669
+ unit_lock_requires_complete_metadata() {
670
+ local st
671
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-lock-metadata.XXXXXX")
672
+ mkdir -p "$st/state"
673
+ if FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" bash -c '
674
+ . "$1"
675
+ fm_pid_identity() { return 1; }
676
+ ! fm_afk_launch_lock_acquire
677
+ ' _ "$LAUNCH" && [ ! -e "$st/state/.afk-launch.lock" ]; then
678
+ pass "launcher lock: incomplete metadata fails acquisition and releases lock"
679
+ else
680
+ fail "launcher lock: incomplete metadata was accepted"
681
+ fi
682
+ rm -rf "$st"
683
+ }
684
+
685
+ unit_stop_surfaces_afk_removal_failure() {
686
+ local st
687
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-stop-remove.XXXXXX")
688
+ mkdir -p "$st/state"
689
+ : > "$st/state/.afk"
690
+ if FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" bash -c '
691
+ . "$1"
692
+ rm() { local last=${!#}; [ "$last" != "$FM_AFK_LAUNCH_STATE/.afk" ]; }
693
+ ! fm_afk_launch_stop
694
+ ' _ "$LAUNCH"; then
695
+ pass "stop state: away-flag removal failure is surfaced"
696
+ else
697
+ fail "stop state: away-flag removal failure reported success"
698
+ fi
699
+ rm -rf "$st"
700
+ }
701
+
702
+ unit_stop_confirms_daemon_exit() {
703
+ local st daemon_pid
704
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-stop-live.XXXXXX")
705
+ mkdir -p "$st/state/.supervise-daemon.lock"
706
+ : > "$st/state/.afk"
707
+ printf 'none\t-\tnative\n' > "$st/state/.afk-daemon-terminal"
708
+ bash -c 'trap "" TERM; while :; do sleep 1; done' &
709
+ daemon_pid=$!
710
+ printf '%s' "$daemon_pid" > "$st/state/.supervise-daemon.lock/pid"
711
+ ( . "$ROOT/bin/fm-wake-lib.sh"; fm_pid_identity "$daemon_pid" > "$st/state/.supervise-daemon.lock/pid-identity" )
712
+ if FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" bash -c '
713
+ . "$1"
714
+ seq() { printf "1\n"; }
715
+ sleep() { :; }
716
+ kill() {
717
+ command kill "$@"
718
+ if [ "$1" = -TERM ]; then
719
+ rm -rf "$FM_AFK_LAUNCH_STATE/.supervise-daemon.lock"
720
+ fi
721
+ }
722
+ ! fm_afk_launch_stop
723
+ ' _ "$LAUNCH" && kill -0 "$daemon_pid" 2>/dev/null \
724
+ && [ ! -e "$st/state/.supervise-daemon.lock" ] \
725
+ && [ -e "$st/state/.afk" ] && [ -e "$st/state/.afk-daemon-terminal" ]; then
726
+ pass "stop liveness: captured live daemon preserves lifecycle state after lock release"
727
+ else
728
+ fail "stop liveness: lock release was mistaken for captured daemon exit"
729
+ fi
730
+ kill -KILL "$daemon_pid" 2>/dev/null || true
731
+ wait "$daemon_pid" 2>/dev/null || true
732
+ rm -rf "$st"
733
+ }
734
+
735
+ unit_refresh_validates_record() {
736
+ local st daemon_pid
737
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-refresh-record.XXXXXX")
738
+ mkdir -p "$st/state/.supervise-daemon.lock"
739
+ printf 'tmux\tonly-two-fields\n' > "$st/state/.afk-daemon-terminal"
740
+ sleep 30 & daemon_pid=$!
741
+ printf '%s' "$daemon_pid" > "$st/state/.supervise-daemon.lock/pid"
742
+ ( . "$ROOT/bin/fm-wake-lib.sh"; fm_pid_identity "$daemon_pid" > "$st/state/.supervise-daemon.lock/pid-identity" )
743
+ if FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" FM_SUPERVISOR_TARGET=unused \
744
+ FM_SUPERVISOR_BACKEND=tmux bash -c '
745
+ . "$1"
746
+ ! fm_afk_launch_start && ! fm_afk_launch_start_native
747
+ ' _ "$LAUNCH" && [ ! -e "$st/state/.afk" ]; then
748
+ pass "refresh record: malformed terminal identity fails closed"
749
+ else
750
+ fail "refresh record: malformed terminal identity was accepted"
751
+ fi
752
+ kill "$daemon_pid" 2>/dev/null || true
753
+ wait "$daemon_pid" 2>/dev/null || true
754
+ rm -rf "$st"
755
+ }
756
+
757
+ unit_clear_failure_aborts_entry() {
758
+ local st
759
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-clear-fail.XXXXXX")
760
+ mkdir -p "$st/state"
761
+ : > "$st/state/.subsuper-escalations"
762
+ if FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" bash -c '
763
+ . "$1"
764
+ fm_afk_launch_reconcile() { return 0; }
765
+ fm_afk_clear_stale_artifacts() { return 1; }
766
+ ! fm_afk_launch_start_native
767
+ ' _ "$LAUNCH" && [ ! -e "$st/state/.afk" ] && [ -e "$st/state/.subsuper-escalations" ]; then
768
+ pass "clear failure: native entry aborts and restores prior state"
769
+ else
770
+ fail "clear failure: native entry proceeded or lost prior state"
771
+ fi
772
+ rm -rf "$st"
773
+ }
774
+
775
+ unit_confirmed_absence_succeeds() {
776
+ local st
777
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-confirmed-absent.XXXXXX")
778
+ mkdir -p "$st/state"
779
+ printf 'tmux\texact-session\towned\n' > "$st/state/.afk-daemon-terminal"
780
+ if FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" bash -c '
781
+ . "$1"
782
+ fm_afk_launch_close_terminal() { return 1; }
783
+ fm_afk_launch_terminal_absent() { return 0; }
784
+ fm_afk_launch_reconcile
785
+ ' _ "$LAUNCH" && [ ! -e "$st/state/.afk-daemon-terminal" ]; then
786
+ pass "confirmed absence: cleanup succeeds and removes the stale record"
787
+ else
788
+ fail "confirmed absence: close error incorrectly failed reconciliation"
789
+ fi
790
+ rm -rf "$st"
791
+ }
792
+
793
+ unit_incomplete_restore_retains_backup() {
794
+ local st backup
795
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-restore-fail.XXXXXX")
796
+ mkdir -p "$st/state"
797
+ backup=$(mktemp -d "$st/state/.afk-launch-backup.XXXXXX")
798
+ printf 'prior\n' > "$backup/.afk"
799
+ if FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" bash -c '
800
+ . "$1"
801
+ cp() { return 1; }
802
+ ! fm_afk_launch_restore_backup "$2" 1
803
+ ' _ "$LAUNCH" "$backup" && [ -d "$backup" ] && [ -e "$backup/.afk" ]; then
804
+ pass "rollback restore: incomplete restoration retains its recovery backup"
805
+ else
806
+ fail "rollback restore: incomplete restoration discarded its backup"
807
+ fi
808
+ rm -rf "$st"
809
+ }
810
+
811
+ unit_flag_write_failure_aborts() {
812
+ local st
813
+ st=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-flag-fail.XXXXXX")
814
+ mkdir -p "$st/state"
815
+ FM_HOME="$st" FM_STATE_OVERRIDE="$st/state" bash -c '
816
+ . "$1"
817
+ fm_afk_launch_flag_write() { return 1; }
818
+ ! fm_afk_launch_start_native
819
+ ' _ "$LAUNCH"
820
+ if [ ! -e "$st/state/.afk" ] && [ ! -e "$st/state/.afk-daemon-terminal" ]; then
821
+ pass "flag failure: lifecycle aborts without active state"
822
+ else
823
+ fail "flag failure: lifecycle reported active state"
824
+ fi
825
+ rm -rf "$st"
826
+ }
827
+
828
+ # ---------------------------------------------------------------------------
829
+ # E2E herdr: topology invariant.
830
+ # ---------------------------------------------------------------------------
831
+ e2e_herdr() {
832
+ command -v herdr >/dev/null 2>&1 || { echo "skip: herdr not found (herdr e2e)"; return 0; }
833
+ command -v jq >/dev/null 2>&1 || { echo "skip: jq not found (herdr e2e)"; return 0; }
834
+ # shellcheck source=tests/herdr-test-safety.sh
835
+ . "$ROOT/tests/herdr-test-safety.sh"
836
+ # shellcheck source=/dev/null
837
+ . "$ROOT/bin/fm-backend.sh"
838
+
839
+ local SESSION home_tmp cap_ws cap_tab cap_pane target
840
+ local before during after ws_before ws_during ws_after out dtgt dtab
841
+ SESSION="fm-lab-afk-launch-e2e-$$"
842
+ export HERDR_SESSION="$SESSION"
843
+ home_tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-e2e-home.XXXXXX")
844
+ E2E_HERDR_CLEANUP() {
845
+ # shellcheck disable=SC2031 # Cleanup reads the caller's resolved target; it does not reassign it.
846
+ FM_HOME="$home_tmp" FM_STATE_OVERRIDE="$home_tmp/state" \
847
+ FM_SUPERVISOR_TARGET="$target" FM_SUPERVISOR_BACKEND=herdr "$LAUNCH" stop >/dev/null 2>&1 || true
848
+ herdr_safe_stop_and_delete "$SESSION" >/dev/null 2>&1 || true
849
+ rm -rf "$home_tmp" 2>/dev/null || true
850
+ }
851
+ fm_herdr_lab_prepare "$SESSION" || { fail "herdr e2e: could not prepare isolated lab session"; return 0; }
852
+ fm_backend_source herdr || { E2E_HERDR_CLEANUP; fail "herdr e2e: fm_backend_source herdr failed"; return 0; }
853
+ fm_backend_herdr_server_ensure "$SESSION" || { E2E_HERDR_CLEANUP; fail "herdr e2e: lab server did not start"; return 0; }
854
+
855
+ out=$(fm_backend_herdr_cli "$SESSION" workspace create --cwd "$ROOT" --label captain --no-focus 2>/dev/null)
856
+ cap_ws=$(printf '%s' "$out" | jq -r '.result.workspace.workspace_id // empty')
857
+ cap_tab=$(printf '%s' "$out" | jq -r '.result.tab.tab_id // empty')
858
+ cap_pane=$(printf '%s' "$out" | jq -r '.result.root_pane.pane_id // empty')
859
+ if [ -z "$cap_ws" ] || [ -z "$cap_pane" ]; then E2E_HERDR_CLEANUP; fail "herdr e2e: could not create captain workspace"; return 0; fi
860
+ target="$SESSION:$cap_pane"
861
+ before=$(fm_backend_herdr_cli "$SESSION" pane list --workspace "$cap_ws" 2>/dev/null | jq --arg t "$cap_tab" '[.result.panes[]?|select(.tab_id==$t)]|length')
862
+ ws_before=$(fm_backend_herdr_cli "$SESSION" workspace list 2>/dev/null | jq '[.result.workspaces[]?]|length')
863
+
864
+ FM_HOME="$home_tmp" FM_STATE_OVERRIDE="$home_tmp/state" \
865
+ FM_SUPERVISOR_TARGET="$target" FM_SUPERVISOR_BACKEND=herdr FM_AFK_LAUNCH_ENTRY="$SLEEPER" \
866
+ "$LAUNCH" start >/dev/null 2>&1
867
+
868
+ during=$(fm_backend_herdr_cli "$SESSION" pane list --workspace "$cap_ws" 2>/dev/null | jq --arg t "$cap_tab" '[.result.panes[]?|select(.tab_id==$t)]|length')
869
+ ws_during=$(fm_backend_herdr_cli "$SESSION" workspace list 2>/dev/null | jq '[.result.workspaces[]?]|length')
870
+ dtgt=$(cut -f2 "$home_tmp/state/.afk-daemon-terminal" 2>/dev/null || true)
871
+ dtab=$(fm_backend_herdr_cli "$SESSION" pane get "${dtgt#*:}" 2>/dev/null | jq -r '.result.pane.tab_id // empty')
872
+
873
+ if [ "$before" = "$during" ]; then pass "herdr e2e: captain tab pane count unchanged after start (no split)"; else fail "herdr e2e: captain tab pane count changed ($before -> $during)"; fi
874
+ if [ "$ws_during" -gt "$ws_before" ]; then pass "herdr e2e: daemon launched in a separate non-visible workspace"; else fail "herdr e2e: no separate daemon workspace created"; fi
875
+ if [ -n "$dtab" ] && [ "$dtab" != "$cap_tab" ]; then pass "herdr e2e: daemon pane is NOT in the captain's tab"; else fail "herdr e2e: daemon pane shares the captain tab ($dtab)"; fi
876
+ case "$dtgt" in "$SESSION":*) pass "herdr e2e: daemon terminal scoped to the lab session" ;; *) fail "herdr e2e: daemon terminal not in the lab session ($dtgt)" ;; esac
877
+
878
+ FM_HOME="$home_tmp" FM_STATE_OVERRIDE="$home_tmp/state" \
879
+ FM_SUPERVISOR_TARGET="$target" FM_SUPERVISOR_BACKEND=herdr "$LAUNCH" stop >/dev/null 2>&1
880
+
881
+ after=$(fm_backend_herdr_cli "$SESSION" pane list --workspace "$cap_ws" 2>/dev/null | jq --arg t "$cap_tab" '[.result.panes[]?|select(.tab_id==$t)]|length')
882
+ ws_after=$(fm_backend_herdr_cli "$SESSION" workspace list 2>/dev/null | jq '[.result.workspaces[]?]|length')
883
+ if [ "$after" = "$before" ]; then pass "herdr e2e: captain tab pane count restored after stop"; else fail "herdr e2e: captain tab pane count not restored ($before -> $after)"; fi
884
+ if [ "$ws_after" = "$ws_before" ]; then pass "herdr e2e: daemon workspace removed by exact id on stop"; else fail "herdr e2e: daemon workspace leaked ($ws_before -> $ws_after)"; fi
885
+ if [ ! -e "$home_tmp/state/.afk-daemon-terminal" ] && [ ! -e "$home_tmp/state/.afk" ]; then pass "herdr e2e: record + .afk cleared on stop"; else fail "herdr e2e: record or .afk not cleared"; fi
886
+
887
+ E2E_HERDR_CLEANUP
888
+ }
889
+
890
+ # ---------------------------------------------------------------------------
891
+ # E2E tmux: topology invariant (captain window untouched; daemon in a separate
892
+ # detached session).
893
+ # ---------------------------------------------------------------------------
894
+ e2e_tmux() {
895
+ command -v tmux >/dev/null 2>&1 || { echo "skip: tmux not found (tmux e2e)"; return 0; }
896
+ local cap_session home_tmp cap_pane before during after rec
897
+ cap_session="fm-afk-launch-cap-$$"
898
+ home_tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-afk-tmux-home.XXXXXX")
899
+ tmux new-session -d -s "$cap_session" 2>/dev/null || { fail "tmux e2e: could not create captain session"; rm -rf "$home_tmp"; return 0; }
900
+ TRACK_TMUX_SESSIONS="$TRACK_TMUX_SESSIONS $cap_session"
901
+ cap_pane=$(tmux display-message -p -t "$cap_session" '#{pane_id}')
902
+ before=$(tmux list-panes -t "$cap_session" | wc -l | tr -d ' ')
903
+
904
+ FM_HOME="$home_tmp" FM_STATE_OVERRIDE="$home_tmp/state" \
905
+ FM_SUPERVISOR_TARGET="$cap_pane" FM_SUPERVISOR_BACKEND=tmux FM_AFK_LAUNCH_ENTRY="$SLEEPER" \
906
+ "$LAUNCH" start >/dev/null 2>&1
907
+
908
+ during=$(tmux list-panes -t "$cap_session" | wc -l | tr -d ' ')
909
+ rec=$(cut -f2 "$home_tmp/state/.afk-daemon-terminal" 2>/dev/null || true)
910
+ TRACK_TMUX_SESSIONS="$TRACK_TMUX_SESSIONS $rec"
911
+ if [ "$before" = "$during" ]; then pass "tmux e2e: captain window pane count unchanged after start (no split-window)"; else fail "tmux e2e: captain window pane count changed ($before -> $during)"; fi
912
+ if [ -n "$rec" ] && tmux has-session -t "$rec" 2>/dev/null && [ "$rec" != "$cap_session" ]; then pass "tmux e2e: daemon launched in a separate detached session"; else fail "tmux e2e: no separate daemon session ($rec)"; fi
913
+
914
+ FM_HOME="$home_tmp" FM_STATE_OVERRIDE="$home_tmp/state" \
915
+ FM_SUPERVISOR_TARGET="$cap_pane" FM_SUPERVISOR_BACKEND=tmux "$LAUNCH" stop >/dev/null 2>&1
916
+
917
+ after=$(tmux list-panes -t "$cap_session" | wc -l | tr -d ' ')
918
+ if [ "$after" = "$before" ]; then pass "tmux e2e: captain window pane count unchanged after stop"; else fail "tmux e2e: captain window changed ($before -> $after)"; fi
919
+ if [ -n "$rec" ] && ! tmux has-session -t "$rec" 2>/dev/null; then pass "tmux e2e: daemon session killed by exact id on stop"; else fail "tmux e2e: daemon session leaked ($rec)"; fi
920
+ if [ ! -e "$home_tmp/state/.afk-daemon-terminal" ] && [ ! -e "$home_tmp/state/.afk" ]; then pass "tmux e2e: record + .afk cleared on stop"; else fail "tmux e2e: record or .afk not cleared"; fi
921
+
922
+ tmux kill-session -t "$cap_session" 2>/dev/null || true
923
+ rm -rf "$home_tmp" 2>/dev/null || true
924
+ }
925
+
926
+ unit_clear_stale
927
+ unit_relative_paths_are_absolute_before_daemon_launch
928
+ unit_fresh_vs_refresh
929
+ unit_stop_ordering
930
+ unit_stop_rejects_reused_pid
931
+ unit_failed_start_rolls_back_state
932
+ unit_concurrent_start_serialized
933
+ unit_lock_initialization_grace
934
+ unit_signal_exits_with_lock_cleanup
935
+ unit_herdr_partial_create_recovery
936
+ unit_herdr_error_with_exact_ids_closes_exact
937
+ unit_herdr_run_failure_preserves_unconfirmed_record
938
+ unit_record_failure_closes_terminal
939
+ unit_readiness_failure_rolls_back_terminal
940
+ unit_readiness_failure_preserves_unconfirmed_record
941
+ unit_tmux_absence_distinguishes_probe_failure
942
+ unit_native_lifecycle
943
+ unit_native_entry_preserves_prepared_state
944
+ unit_close_failure_preserves_record
945
+ unit_record_publication_atomic
946
+ unit_malformed_record_fails_closed
947
+ unit_stop_malformed_record_fails_closed
948
+ unit_tmux_planned_record_and_collision
949
+ unit_stop_validates_before_signal
950
+ unit_lock_requires_complete_metadata
951
+ unit_stop_surfaces_afk_removal_failure
952
+ unit_stop_confirms_daemon_exit
953
+ unit_refresh_validates_record
954
+ unit_clear_failure_aborts_entry
955
+ unit_confirmed_absence_succeeds
956
+ unit_incomplete_restore_retains_backup
957
+ unit_flag_write_failure_aborts
958
+ e2e_herdr
959
+ e2e_tmux
960
+
961
+ [ "$FAILED" -eq 0 ] || exit 1