@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,910 @@
1
+ #!/usr/bin/env bash
2
+ # fm-control.sh: the agent lifecycle CONTROL plane.
3
+ #
4
+ # These tests pin the control plane's observable behavior hermetically - a
5
+ # stubbed session provider, no real agent - through the executable interface
6
+ # firstmate actually calls:
7
+ # 1. Adapter contract: every verified harness gets its own verified exit
8
+ # command and interrupt key, delivered as bytes to the endpoint.
9
+ # 2. Backend capability: a backend that cannot deliver the harness's
10
+ # interrupt key, and a backend with no recovery-grade agent-state
11
+ # classifier, both refuse instead of acting blind.
12
+ # 3. Exact-id scoping: a window label, an explicit endpoint, an unknown id,
13
+ # and a record bound to another task are all refused.
14
+ # 4. Verb allowlist: no arbitrary text, no raw keys, no resume.
15
+ # 5. Lifecycle states: busy interrupts first, idle does not, already-stopped
16
+ # is idempotent success, and an agent that does not stop fails closed.
17
+ # 6. Marker non-regression: a control command to a kind=secondmate task
18
+ # carries NO from-firstmate marker and opens no pending-reply expectation,
19
+ # while fm-send's marking of the same task is untouched.
20
+ set -u
21
+
22
+ # shellcheck source=tests/lib.sh
23
+ . "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
24
+ # shellcheck source=/dev/null
25
+ . "$ROOT/bin/fm-control-lib.sh"
26
+ # shellcheck source=/dev/null
27
+ . "$ROOT/bin/fm-marker-lib.sh"
28
+
29
+ CONTROL="$ROOT/bin/fm-control.sh"
30
+ SEND="$ROOT/bin/fm-send.sh"
31
+ # fm_test_tmproot's own cleanup trap fires when its command substitution exits,
32
+ # so recreate the root before resolving it and clean it up from this file's trap.
33
+ TMP_ROOT=$(fm_test_tmproot fm-control)
34
+ mkdir -p "$TMP_ROOT"
35
+ TMP_ROOT=$(cd "$TMP_ROOT" && pwd)
36
+ trap 'rm -rf "$TMP_ROOT"' EXIT
37
+
38
+ VERIFIED_HARNESSES="claude codex opencode pi pi-signed grok kimi cursor muse"
39
+
40
+ # The expectation table, written out independently of the implementation so a
41
+ # silent change to either side shows up here. The fourth field is the composer
42
+ # clear that must FOLLOW the interrupt key, empty for every adapter that leaves
43
+ # its composer empty on cancel.
44
+ verified_adapter_contract() { # <harness> -> exit command, interrupt key, repeat, clear key
45
+ case "$1" in
46
+ claude) printf '/exit\tEscape\t1\t\n' ;;
47
+ codex) printf '/quit\tEscape\t1\t\n' ;;
48
+ opencode) printf '/exit\tEscape\t2\t\n' ;;
49
+ pi) printf '/quit\tEscape\t1\t\n' ;;
50
+ pi-signed) printf '/quit\tEscape\t1\t\n' ;;
51
+ grok) printf '/exit\tC-c\t1\t\n' ;;
52
+ kimi) printf '/exit\tEscape\t1\t\n' ;;
53
+ cursor) printf '/exit\tEscape\t1\t\n' ;;
54
+ muse) printf '/exit\tEscape\t1\tC-u\n' ;;
55
+ *) return 1 ;;
56
+ esac
57
+ }
58
+
59
+ # --- fake session provider --------------------------------------------------
60
+ #
61
+ # A tmux stub whose whole model is four files under $FM_FAKE_DIR:
62
+ # command the pane's foreground process name, which IS the agent-state
63
+ # classifier's input (bin/backends/tmux.sh).
64
+ # cwd the pane's current path.
65
+ # literal every `send-keys -l` payload, one per line - exactly what was
66
+ # typed into the composer.
67
+ # keys every named key send, one per line.
68
+ # pane optional capture-pane override, for an adapter whose busy verdict
69
+ # is read from the rendered tail.
70
+ # Two transitions make it a lifecycle model rather than a recorder: a literal
71
+ # that is the harness's exit command flips `command` to a shell (the agent
72
+ # stopped), and a literal carrying a launch brief flips it to the value in
73
+ # `becomes` (a new agent came up). FM_FAKE_NEVER_DIES suppresses the first, so
74
+ # a stubborn agent can be tested too.
75
+ make_tmux_stub() { # <dir> -> echoes fakebin dir
76
+ local dir=$1 fb="$1/fakebin"
77
+ mkdir -p "$fb"
78
+ cat > "$fb/tmux" <<'SH'
79
+ #!/usr/bin/env bash
80
+ set -u
81
+ D=$FM_FAKE_DIR
82
+ case "${1:-}" in
83
+ send-keys)
84
+ shift
85
+ literal=0
86
+ while [ $# -gt 0 ]; do
87
+ case "$1" in
88
+ -t) shift 2 ;;
89
+ -l) literal=1; shift ;;
90
+ *) break ;;
91
+ esac
92
+ done
93
+ payload=${1:-}
94
+ if [ "$literal" = 1 ]; then
95
+ printf '%s\n' "$payload" >> "$D/literal"
96
+ if [ -z "${FM_FAKE_NEVER_DIES:-}" ] \
97
+ && { [ "$payload" = /exit ] || [ "$payload" = /quit ]; }; then
98
+ printf 'zsh' > "$D/command"
99
+ fi
100
+ case "$payload" in
101
+ *'encode launch-brief'*) cat "$D/becomes" > "$D/command" ;;
102
+ esac
103
+ else
104
+ printf '%s\n' "$payload" >> "$D/keys"
105
+ if [ -n "${FM_FAKE_INTERRUPT_STOPS_AGENT:-}" ] \
106
+ && { [ "$payload" = Escape ] || [ "$payload" = C-c ]; }; then
107
+ printf 'zsh' > "$D/command"
108
+ fi
109
+ if [ "$payload" = Escape ] && [ -n "${FM_FAKE_MUSE_LOG:-}" ]; then
110
+ if [ -n "${FM_FAKE_MUSE_DISAPPEAR_BEFORE_ACK:-}" ]; then
111
+ : > "$D/muse-ack-pending"
112
+ else
113
+ printf '%s\n' '{"schema_version":1,"payload_type":"runtime.session","payload":{"kind":"run","run_id":"run-1","event":{"kind":"terminal","terminal":"cancelled","reason":null}}}' >> "$FM_FAKE_MUSE_LOG"
114
+ fi
115
+ fi
116
+ fi
117
+ exit 0 ;;
118
+ display-message)
119
+ for a in "$@"; do
120
+ case "$a" in
121
+ *cursor_y*) printf '1\n'; exit 0 ;;
122
+ *pane_current_command*) cat "$D/command"; printf '\n'; exit 0 ;;
123
+ *pane_current_path*) cat "$D/cwd"; printf '\n'; exit 0 ;;
124
+ esac
125
+ done
126
+ printf 'fakepane\n'; exit 0 ;;
127
+ capture-pane)
128
+ if [ -f "$D/pane" ]; then cat "$D/pane"; else printf '╭────╮\n│ │\n╰────╯\n'; fi
129
+ exit 0 ;;
130
+ list-windows)
131
+ if [ -f "$D/windows" ]; then cat "$D/windows"; fi
132
+ exit 0 ;;
133
+ esac
134
+ exit 0
135
+ SH
136
+ chmod +x "$fb/tmux"
137
+ cat > "$fb/sleep" <<'SH'
138
+ #!/usr/bin/env bash
139
+ if [ -n "${FM_FAKE_MUSE_DISAPPEAR_BEFORE_ACK:-}" ] \
140
+ && [ -e "$FM_FAKE_DIR/muse-ack-pending" ]; then
141
+ rm -f "$FM_FAKE_DIR/muse-ack-pending"
142
+ printf 'zsh' > "$FM_FAKE_DIR/command"
143
+ printf '%s\n' '{"schema_version":1,"payload_type":"runtime.session","payload":{"kind":"run","run_id":"run-1","event":{"kind":"terminal","terminal":"cancelled","reason":null}}}' >> "$FM_FAKE_MUSE_LOG"
144
+ fi
145
+ exit 0
146
+ SH
147
+ chmod +x "$fb/sleep"
148
+ printf '%s\n' "$fb"
149
+ }
150
+
151
+ # new_case <name> -> echoes a case dir holding home/, fake/, and fakebin.
152
+ new_case() {
153
+ local dir="$TMP_ROOT/$1-$RANDOM"
154
+ mkdir -p "$dir/home/state" "$dir/home/data" "$dir/fake"
155
+ : > "$dir/fake/literal"
156
+ : > "$dir/fake/keys"
157
+ printf 'zsh' > "$dir/fake/command"
158
+ printf 'claude' > "$dir/fake/becomes"
159
+ make_tmux_stub "$dir" >/dev/null
160
+ printf '%s\n' "$dir"
161
+ }
162
+
163
+ # add_task <case-dir> <id> <harness> [kind] [backend] [window]
164
+ # Builds the task's worktree (a real git worktree so the relaunch checkpoint
165
+ # has something to account for), its brief, and its state/<id>.meta.
166
+ add_task() {
167
+ local dir=$1 id=$2 harness=$3 kind=${4:-ship} backend=${5:-tmux}
168
+ local window=${6:-fmses:fm-$id}
169
+ local home="$dir/home" proj="$dir/proj-$id" wt="$dir/wt-$id"
170
+ fm_git_worktree "$proj" "$wt" "task-$id"
171
+ mkdir -p "$home/data/$id"
172
+ printf '# brief for %s\n' "$id" > "$home/data/$id/brief.md"
173
+ {
174
+ echo "window=$window"
175
+ echo "endpoint_task_id=$id"
176
+ echo "worktree=$wt"
177
+ echo "project=$proj"
178
+ echo "harness=$harness"
179
+ echo "kind=$kind"
180
+ echo "mode=no-mistakes"
181
+ echo "yolo=off"
182
+ echo "model=default"
183
+ echo "effort=default"
184
+ [ "$backend" = tmux ] || echo "backend=$backend"
185
+ } > "$home/state/$id.meta"
186
+ printf '%s\n' "fm-$id" > "$dir/fake/windows"
187
+ printf '%s' "$wt" > "$dir/fake/cwd"
188
+ }
189
+
190
+ # run_control <case-dir> <args...>: run fm-control against the case's home with
191
+ # the stubbed provider on PATH. Echoes combined output; returns its exit code.
192
+ run_control() {
193
+ local dir=$1; shift
194
+ env PATH="$dir/fakebin:$PATH" FM_HOME="$dir/home" FM_FAKE_DIR="$dir/fake" \
195
+ FM_CONTROL_POLL=0.01 FM_CONTROL_SETTLE_WAIT=0.05 \
196
+ FM_CONTROL_EXIT_WAIT=0.05 FM_CONTROL_LAUNCH_WAIT=0.05 \
197
+ FM_FAKE_MUSE_LOG="${FM_FAKE_MUSE_LOG:-}" \
198
+ FM_FAKE_MUSE_DISAPPEAR_BEFORE_ACK="${FM_FAKE_MUSE_DISAPPEAR_BEFORE_ACK:-}" \
199
+ FM_FAKE_INTERRUPT_STOPS_AGENT="${FM_FAKE_INTERRUPT_STOPS_AGENT:-}" \
200
+ "$CONTROL" "$@" 2>&1
201
+ }
202
+
203
+ alive_as() { # <case-dir> <command-name>
204
+ printf '%s' "$2" > "$1/fake/command"
205
+ }
206
+
207
+ literals() { # <case-dir>
208
+ cat "$1/fake/literal"
209
+ }
210
+
211
+ # Every named key EXCEPT Enter, which is submission mechanics shared with every
212
+ # text send rather than a control-plane key.
213
+ keys_sent() { # <case-dir>
214
+ grep -v '^Enter$' "$1/fake/keys" || true
215
+ }
216
+
217
+ # --- 1. adapter contract across every verified harness -----------------------
218
+
219
+ test_exit_types_each_harness_verified_command() {
220
+ local dir out rc harness expected key repeat clear
221
+ for harness in $VERIFIED_HARNESSES; do
222
+ dir=$(new_case "exit-$harness")
223
+ add_task "$dir" t1 "$harness"
224
+ if [ "$harness" = cursor ]; then
225
+ alive_as "$dir" cursor-agent
226
+ else
227
+ alive_as "$dir" "$harness"
228
+ fi
229
+ out=$(run_control "$dir" t1 exit); rc=$?
230
+ expect_code 0 "$rc" "exit on $harness should succeed"$'\n'"$out"
231
+ IFS=$'\t' read -r expected key repeat clear <<< "$(verified_adapter_contract "$harness")"
232
+ [ "$(literals "$dir")" = "$expected" ] \
233
+ || fail "exit on $harness should type exactly '$expected', got: $(literals "$dir")"
234
+ assert_contains "$out" "stopped t1 harness=$harness" "exit should report the stop for $harness"
235
+ done
236
+ pass "fm-control exit: every verified harness gets its own verified exit command"
237
+ }
238
+
239
+ test_interrupt_sends_each_harness_verified_key() {
240
+ local dir out rc harness expected key repeat clear got want
241
+ for harness in $VERIFIED_HARNESSES; do
242
+ dir=$(new_case "int-$harness")
243
+ add_task "$dir" t1 "$harness"
244
+ if [ "$harness" = cursor ]; then
245
+ alive_as "$dir" cursor-agent
246
+ else
247
+ alive_as "$dir" "$harness"
248
+ fi
249
+ out=$(run_control "$dir" t1 interrupt); rc=$?
250
+ expect_code 0 "$rc" "interrupt on $harness should succeed"$'\n'"$out"
251
+ IFS=$'\t' read -r expected key repeat clear <<< "$(verified_adapter_contract "$harness")"
252
+ want=$(for _ in $(seq 1 "$repeat"); do printf '%s\n' "$key"; done)
253
+ [ -z "$clear" ] || want="$want"$'\n'"$clear"
254
+ got=$(keys_sent "$dir")
255
+ [ "$got" = "$want" ] \
256
+ || fail "interrupt on $harness should send $repeat x $key${clear:+ then $clear}, got: $got"
257
+ [ -z "$(literals "$dir")" ] \
258
+ || fail "interrupt on $harness must type no text, got: $(literals "$dir")"
259
+ done
260
+ pass "fm-control interrupt: every verified harness gets its own verified key and repeat count"
261
+ }
262
+
263
+ # A recorded harness can carry a raw launch command's basename, so the tables
264
+ # are reached through one prefix rule rather than an exact string match.
265
+ test_harness_family_resolution() {
266
+ local pair recorded want got
267
+ for pair in claude:claude claude-latest:claude codex:codex codex-cli:codex \
268
+ opencode:opencode grok:grok grok-2:grok kimi:kimi cursor:cursor \
269
+ cursor-agent:cursor muse:muse muse-bin-0.1.0:muse pi:pi \
270
+ pi-signed:pi-signed; do
271
+ recorded=${pair%%:*}
272
+ want=${pair#*:}
273
+ got=$(fm_control_harness_family "$recorded") \
274
+ || fail "'$recorded' should resolve to the $want adapter"
275
+ [ "$got" = "$want" ] || fail "'$recorded' should resolve to $want, got '$got'"
276
+ done
277
+ fm_control_harness_family someagent \
278
+ && fail "an unrecognized launch command must not be guessed into an adapter family"
279
+ fm_control_harness_family '' \
280
+ && fail "an empty harness must not resolve to an adapter family"
281
+ # The signed adapter is a distinct launch profile, not a pi variant.
282
+ [ "$(fm_control_harness_family pi-signed)" != "$(fm_control_harness_family pi)" ] \
283
+ || fail "pi-signed must not collapse into pi"
284
+ pass "fm-control-lib: a recorded harness resolves to its verified adapter without guessing"
285
+ }
286
+
287
+ test_prefixed_recorded_harness_reaches_each_control_verb() {
288
+ local dir out rc
289
+ dir=$(new_case prefixed-interrupt)
290
+ add_task "$dir" t1 grok-2
291
+ alive_as "$dir" grok-2
292
+ out=$(run_control "$dir" t1 interrupt); rc=$?
293
+ expect_code 0 "$rc" "interrupt should resolve a prefixed recorded harness"$'\n'"$out"
294
+ [ "$(keys_sent "$dir")" = C-c ] \
295
+ || fail "a grok-prefixed task should receive grok's interrupt key"
296
+ assert_contains "$out" "harness=grok" \
297
+ "interrupt should report the verified adapter that supplied its mechanics"
298
+
299
+ dir=$(new_case prefixed-exit)
300
+ add_task "$dir" t1 grok-2
301
+ alive_as "$dir" grok-2
302
+ out=$(run_control "$dir" t1 exit); rc=$?
303
+ expect_code 0 "$rc" "exit should resolve a prefixed recorded harness"$'\n'"$out"
304
+ [ "$(literals "$dir")" = /exit ] \
305
+ || fail "a grok-prefixed task should receive grok's exit command"
306
+ assert_contains "$out" "stopped t1 harness=grok" \
307
+ "exit should report the verified adapter that supplied its mechanics"
308
+ pass "fm-control: prefixed recorded harnesses reach interrupt and exit mechanics"
309
+ }
310
+
311
+ test_opencode_interrupts_twice_and_others_once() {
312
+ # The one adapter that differs, asserted through the delivered keys rather
313
+ # than the table, so a regression in either shows up here.
314
+ local dir
315
+ dir=$(new_case int-double)
316
+ add_task "$dir" t1 opencode
317
+ alive_as "$dir" opencode
318
+ run_control "$dir" t1 interrupt >/dev/null
319
+ [ "$(keys_sent "$dir" | wc -l | tr -d ' ')" = 2 ] \
320
+ || fail "opencode should receive a double Escape"
321
+ dir=$(new_case int-single)
322
+ add_task "$dir" t1 claude
323
+ alive_as "$dir" claude
324
+ run_control "$dir" t1 interrupt >/dev/null
325
+ [ "$(keys_sent "$dir" | wc -l | tr -d ' ')" = 1 ] \
326
+ || fail "claude should receive a single Escape"
327
+ pass "fm-control interrupt: opencode needs a double Escape, claude a single one"
328
+ }
329
+
330
+ test_unverified_harness_is_refused() {
331
+ local dir out rc
332
+ dir=$(new_case unverified)
333
+ add_task "$dir" t1 someagent
334
+ alive_as "$dir" someagent
335
+ out=$(run_control "$dir" t1 exit); rc=$?
336
+ expect_code 1 "$rc" "an unverified harness should refuse"
337
+ assert_contains "$out" "no verified control mechanics" "refusal should name the missing verification"
338
+ [ -z "$(literals "$dir")" ] || fail "an unverified harness must receive no bytes"
339
+ pass "fm-control: a harness with no verified control mechanics is refused, not guessed at"
340
+ }
341
+
342
+ # --- 2. backend capability matrix -------------------------------------------
343
+
344
+ test_backend_key_capability_matrix() {
345
+ local backend key
346
+ for backend in tmux herdr zellij cmux; do
347
+ # C-u is the composer clear muse's interrupt needs; every session provider
348
+ # but Orca normalizes it (bin/backends/*.sh).
349
+ for key in Escape Enter C-c C-u; do
350
+ fm_control_backend_supports_key "$backend" "$key" \
351
+ || fail "$backend should be able to deliver $key"
352
+ done
353
+ done
354
+ fm_control_backend_supports_key orca Escape \
355
+ && fail "orca's terminal API has no Escape and must not claim it"
356
+ fm_control_backend_supports_key orca C-u \
357
+ && fail "orca's terminal API has no composer clear and must not claim one"
358
+ fm_control_backend_supports_key orca C-c || fail "orca should deliver C-c"
359
+ fm_control_backend_supports_key orca Enter || fail "orca should deliver Enter"
360
+ pass "fm-control-lib: the backend key matrix matches each adapter's real send-key surface"
361
+ }
362
+
363
+ # A verified adapter is not automatically verified for every task kind, and the
364
+ # check has to sit on the pre-stop side of a relaunch: muse has no primary
365
+ # supervision protocol, so bin/fm-spawn.sh refuses it for a secondmate, and
366
+ # discovering that only after the running agent was stopped would strand the
367
+ # secondmate with no agent at all.
368
+ test_harness_kind_capability() {
369
+ local harness
370
+ for harness in $VERIFIED_HARNESSES; do
371
+ fm_control_harness_supports_kind "$harness" ship \
372
+ || fail "$harness should be able to run a ship task"
373
+ fm_control_harness_supports_kind "$harness" scout \
374
+ || fail "$harness should be able to run a scout task"
375
+ done
376
+ fm_control_harness_supports_kind muse secondmate \
377
+ && fail "muse has no primary supervision protocol and must not claim a secondmate"
378
+ for harness in claude codex opencode pi pi-signed grok kimi; do
379
+ fm_control_harness_supports_kind "$harness" secondmate \
380
+ || fail "$harness should be able to run a secondmate"
381
+ done
382
+ fm_control_harness_supports_kind someagent ship \
383
+ && fail "an unverified harness must not claim any kind"
384
+ pass "fm-control-lib: adapter capability is per task kind, not per adapter alone"
385
+ }
386
+
387
+ test_orca_refuses_an_escape_harness_interrupt() {
388
+ local dir out rc
389
+ dir=$(new_case orca-escape)
390
+ add_task "$dir" t1 claude ship orca "term-1"
391
+ # Orca records its endpoint as terminal=, which endpoint validation requires.
392
+ {
393
+ cat "$dir/home/state/t1.meta"
394
+ echo "terminal=term-1"
395
+ echo "orca_worktree_id=wt-1"
396
+ } > "$dir/home/state/t1.meta.new"
397
+ sed 's|^window=.*|window=fm-t1|' "$dir/home/state/t1.meta.new" > "$dir/home/state/t1.meta"
398
+ out=$(run_control "$dir" t1 interrupt); rc=$?
399
+ expect_code 1 "$rc" "an Escape harness on orca should refuse"
400
+ assert_contains "$out" "cannot deliver" "refusal should name the undeliverable key"
401
+ pass "fm-control interrupt: a backend that cannot deliver the harness's key refuses instead of sending another"
402
+ }
403
+
404
+ test_unverified_state_backends_refuse_stop_verbs() {
405
+ local dir out rc backend
406
+ for backend in zellij cmux; do
407
+ dir=$(new_case "nostate-$backend")
408
+ if [ "$backend" = zellij ]; then
409
+ add_task "$dir" t1 claude ship zellij "sess:7"
410
+ {
411
+ echo "zellij_session=sess"
412
+ echo "zellij_tab_id=1"
413
+ echo "zellij_pane_id=7"
414
+ } >> "$dir/home/state/t1.meta"
415
+ else
416
+ add_task "$dir" t1 claude ship cmux "ws1:surface1"
417
+ {
418
+ echo "cmux_workspace_id=ws1"
419
+ echo "cmux_surface_id=surface1"
420
+ } >> "$dir/home/state/t1.meta"
421
+ fi
422
+ out=$(run_control "$dir" t1 exit); rc=$?
423
+ expect_code 1 "$rc" "exit on $backend should refuse"$'\n'"$out"
424
+ assert_contains "$out" "no recovery-grade agent-state classifier" \
425
+ "the $backend refusal should name the missing stop proof"
426
+ [ -z "$(literals "$dir")" ] || fail "$backend must receive no exit command"
427
+ out=$(run_control "$dir" t1 relaunch --note x); rc=$?
428
+ expect_code 1 "$rc" "relaunch on $backend should refuse"$'\n'"$out"
429
+ assert_contains "$out" "no recovery-grade agent-state classifier" \
430
+ "the $backend relaunch refusal should name the missing stop proof"
431
+ done
432
+ pass "fm-control: a backend that cannot prove an agent stopped refuses exit and relaunch"
433
+ }
434
+
435
+ test_state_verified_backends_are_exactly_tmux_and_herdr() {
436
+ fm_control_backend_state_verified tmux || fail "tmux has a recovery-grade classifier"
437
+ fm_control_backend_state_verified herdr || fail "herdr has a recovery-grade classifier"
438
+ local backend
439
+ for backend in zellij orca cmux; do
440
+ fm_control_backend_state_verified "$backend" \
441
+ && fail "$backend has no recovery-grade classifier and must not claim one"
442
+ done
443
+ pass "fm-control-lib: stop-proving verbs are gated on the backends that really classify agent state"
444
+ }
445
+
446
+ # --- 3. exact-id scoping ----------------------------------------------------
447
+
448
+ test_window_label_is_refused_with_the_exact_id() {
449
+ local dir out rc
450
+ dir=$(new_case label)
451
+ add_task "$dir" t1 claude
452
+ alive_as "$dir" claude
453
+ out=$(run_control "$dir" fm-t1 exit); rc=$?
454
+ expect_code 1 "$rc" "a window label should refuse"
455
+ assert_contains "$out" "pass the exact task id 't1'" "the refusal should name the exact id"
456
+ [ -z "$(literals "$dir")" ] || fail "a refused target must receive no bytes"
457
+ pass "fm-control: a legacy window label is refused and the exact task id is named"
458
+ }
459
+
460
+ test_explicit_endpoint_is_refused() {
461
+ local dir out rc
462
+ dir=$(new_case endpoint)
463
+ add_task "$dir" t1 claude
464
+ alive_as "$dir" claude
465
+ out=$(run_control "$dir" "fmses:fm-t1" exit); rc=$?
466
+ expect_code 1 "$rc" "an explicit endpoint should refuse"
467
+ assert_contains "$out" "exact task id only" "the refusal should name the exact-id rule"
468
+ [ -z "$(literals "$dir")" ] || fail "a refused target must receive no bytes"
469
+ pass "fm-control: an explicit backend endpoint is never a control target"
470
+ }
471
+
472
+ test_unknown_task_is_refused() {
473
+ local dir out rc
474
+ dir=$(new_case unknown)
475
+ add_task "$dir" t1 claude
476
+ out=$(run_control "$dir" t2 exit); rc=$?
477
+ expect_code 1 "$rc" "an unknown task should refuse"
478
+ assert_contains "$out" "no task 't2'" "the refusal should name the missing task"
479
+ pass "fm-control: an unrecorded task id is refused"
480
+ }
481
+
482
+ test_record_bound_to_another_task_is_refused() {
483
+ local dir out rc
484
+ dir=$(new_case foreign)
485
+ add_task "$dir" t1 claude
486
+ alive_as "$dir" claude
487
+ sed 's/^endpoint_task_id=t1$/endpoint_task_id=other/' "$dir/home/state/t1.meta" \
488
+ > "$dir/home/state/t1.meta.tmp"
489
+ mv "$dir/home/state/t1.meta.tmp" "$dir/home/state/t1.meta"
490
+ out=$(run_control "$dir" t1 exit); rc=$?
491
+ expect_code 1 "$rc" "a record bound to another task should refuse"
492
+ assert_contains "$out" "belongs to task other" "the refusal should name the conflicting binding"
493
+ [ -z "$(literals "$dir")" ] || fail "a foreign record must receive no bytes"
494
+ pass "fm-control: a record whose endpoint identity names another task is refused"
495
+ }
496
+
497
+ # A remotely placed secondmate's agent runs on another host, so none of the
498
+ # postconditions this plane verifies could be read for it here. Endpoint
499
+ # validation would refuse the record anyway - `window=remote:<id>` can never
500
+ # match a local backend's shape - but it would blame malformed metadata for a
501
+ # correctly configured route, so the placement is named instead. Every verb
502
+ # refuses, and none of them reaches a local endpoint.
503
+ test_remote_secondmate_is_refused_by_placement() {
504
+ local dir out rc verb
505
+ for verb in interrupt exit relaunch; do
506
+ dir=$(new_case "remote-$verb")
507
+ add_task "$dir" t1 claude secondmate
508
+ alive_as "$dir" claude
509
+ {
510
+ grep -v '^window=' "$dir/home/state/t1.meta"
511
+ echo "window=remote:t1"
512
+ echo "home=$dir/wt-t1"
513
+ echo "remote_host=example.invalid"
514
+ echo "remote_root=/srv/fm"
515
+ echo "remote_backend=herdr"
516
+ echo "remote_target=fm:pane-1"
517
+ } > "$dir/home/state/t1.meta.tmp"
518
+ mv "$dir/home/state/t1.meta.tmp" "$dir/home/state/t1.meta"
519
+ if [ "$verb" = relaunch ]; then
520
+ out=$(run_control "$dir" t1 "$verb" --note "x"); rc=$?
521
+ else
522
+ out=$(run_control "$dir" t1 "$verb"); rc=$?
523
+ fi
524
+ expect_code 1 "$rc" "$verb on a remotely placed secondmate should refuse"
525
+ assert_contains "$out" "remotely placed secondmate on example.invalid" \
526
+ "the $verb refusal should name the remote placement, not blame the record"
527
+ assert_not_contains "$out" "malformed" \
528
+ "a correctly configured remote route must not be reported as malformed"
529
+ [ -z "$(literals "$dir")" ] && [ -z "$(keys_sent "$dir")" ] \
530
+ || fail "$verb on a remote secondmate must reach no local endpoint"
531
+ done
532
+ pass "fm-control: a remotely placed secondmate is refused by placement, not by a metadata complaint"
533
+ }
534
+
535
+ hold_lifecycle_lock() { # <lock-path>
536
+ local lifecycle_lock_path=$1
537
+ . "$ROOT/bin/fm-wake-lib.sh"
538
+ fm_lock_try_acquire "$lifecycle_lock_path" || return 1
539
+ sleep 30
540
+ }
541
+
542
+ test_interrupt_and_exit_lock_before_task_state_resolution() {
543
+ local case_dir out rc verb lifecycle_lock_path holder i
544
+ for verb in interrupt exit; do
545
+ case_dir=$(new_case "locked-$verb")
546
+ add_task "$case_dir" t1 claude
547
+ alive_as "$case_dir" claude
548
+ lifecycle_lock_path="$case_dir/home/state/.control-t1.lock"
549
+ hold_lifecycle_lock "$lifecycle_lock_path" &
550
+ holder=$!
551
+ i=0
552
+ while [ ! -e "$lifecycle_lock_path" ] && [ "$i" -lt 100 ]; do
553
+ sleep 0.1
554
+ i=$((i + 1))
555
+ done
556
+ [ -e "$lifecycle_lock_path" ] || fail "could not stage the lifecycle lock for $verb"
557
+ sed 's/^endpoint_task_id=t1$/endpoint_task_id=other/' "$case_dir/home/state/t1.meta" \
558
+ > "$case_dir/home/state/t1.meta.tmp"
559
+ mv "$case_dir/home/state/t1.meta.tmp" "$case_dir/home/state/t1.meta"
560
+ out=$(run_control "$case_dir" t1 "$verb"); rc=$?
561
+ kill "$holder" 2>/dev/null || true
562
+ wait "$holder" 2>/dev/null || true
563
+ expect_code 1 "$rc" "$verb should refuse a held lifecycle lock"
564
+ assert_contains "$out" "another lifecycle action is already running" \
565
+ "$verb should serialize before reading mutable task state"
566
+ [ -z "$(literals "$case_dir")" ] || fail "contended $verb must type no command"
567
+ [ -z "$(keys_sent "$case_dir")" ] || fail "contended $verb must send no control key"
568
+ done
569
+ pass "fm-control: interrupt and exit lock before task-state resolution"
570
+ }
571
+
572
+ # --- 4. verb allowlist ------------------------------------------------------
573
+
574
+ test_verb_allowlist_is_closed() {
575
+ local dir out rc
576
+ dir=$(new_case verbs)
577
+ add_task "$dir" t1 claude
578
+ alive_as "$dir" claude
579
+ out=$(run_control "$dir" t1 restart); rc=$?
580
+ expect_code 2 "$rc" "an unknown verb should be a usage error"
581
+ assert_contains "$out" "is not a control verb" "the refusal should say so"
582
+ assert_contains "$out" "interrupt" "the refusal should list the allowed verbs"
583
+ out=$(run_control "$dir" t1 --key); rc=$?
584
+ expect_code 2 "$rc" "a raw key is not a control verb"
585
+ out=$(run_control "$dir" t1 clear); rc=$?
586
+ expect_code 2 "$rc" "clear is not a control verb"
587
+ out=$(run_control "$dir" t1 "please stop what you are doing"); rc=$?
588
+ expect_code 2 "$rc" "arbitrary text is not a control verb"
589
+ [ -z "$(literals "$dir")" ] || fail "a refused verb must send nothing"
590
+ [ -z "$(keys_sent "$dir")" ] || fail "a refused verb must send no keys"
591
+ pass "fm-control: the verb list is closed - no raw keys, arbitrary text, or clear verb"
592
+ }
593
+
594
+ test_resume_is_refused_with_its_reason() {
595
+ local dir out rc
596
+ dir=$(new_case resume)
597
+ add_task "$dir" t1 claude
598
+ out=$(run_control "$dir" t1 resume); rc=$?
599
+ expect_code 2 "$rc" "resume should be refused"
600
+ assert_contains "$out" "not deterministic across the verified adapters" \
601
+ "the refusal should explain why resume is excluded"
602
+ assert_contains "$out" "relaunch" "the refusal should point at the deterministic alternative"
603
+ pass "fm-control: resume is refused with the determinism reason and the alternative"
604
+ }
605
+
606
+ test_relaunch_only_flags_are_rejected_on_other_verbs() {
607
+ local dir out rc
608
+ dir=$(new_case flags)
609
+ add_task "$dir" t1 claude
610
+ alive_as "$dir" claude
611
+ out=$(run_control "$dir" t1 exit --harness codex); rc=$?
612
+ expect_code 1 "$rc" "--harness should not apply to exit"
613
+ assert_contains "$out" "apply to 'relaunch' only" "the refusal should scope the flags"
614
+ pass "fm-control: profile and note flags belong to relaunch only"
615
+ }
616
+
617
+ # --- 5. lifecycle states ----------------------------------------------------
618
+
619
+ test_already_stopped_exit_is_idempotent() {
620
+ local dir out rc
621
+ dir=$(new_case idempotent)
622
+ add_task "$dir" t1 claude
623
+ alive_as "$dir" zsh
624
+ out=$(run_control "$dir" t1 exit); rc=$?
625
+ expect_code 0 "$rc" "exiting an already-stopped agent should succeed"
626
+ assert_contains "$out" "already-stopped t1" "the outcome should say it was already stopped"
627
+ [ -z "$(literals "$dir")" ] || fail "an already-stopped agent must not be sent an exit command"
628
+ pass "fm-control exit: an already-stopped agent is idempotent success with no bytes sent"
629
+ }
630
+
631
+ test_missing_endpoint_refuses() {
632
+ local dir out rc
633
+ dir=$(new_case gone)
634
+ add_task "$dir" t1 claude
635
+ : > "$dir/fake/windows"
636
+ out=$(run_control "$dir" t1 exit); rc=$?
637
+ expect_code 1 "$rc" "a missing endpoint should refuse"
638
+ assert_contains "$out" "recorded endpoint is gone" "the refusal should name the missing endpoint"
639
+ pass "fm-control exit: a vanished endpoint refuses instead of silently succeeding"
640
+ }
641
+
642
+ test_interrupt_refuses_when_no_agent_runs() {
643
+ local dir out rc
644
+ dir=$(new_case nointerrupt)
645
+ add_task "$dir" t1 claude
646
+ alive_as "$dir" zsh
647
+ out=$(run_control "$dir" t1 interrupt); rc=$?
648
+ expect_code 1 "$rc" "interrupting a stopped agent should refuse"
649
+ assert_contains "$out" "nothing to interrupt" "the refusal should say there is no agent"
650
+ [ -z "$(keys_sent "$dir")" ] || fail "no key should reach a stopped agent"
651
+ pass "fm-control interrupt: refuses when no agent is running rather than keying a shell"
652
+ }
653
+
654
+ test_ambiguous_endpoint_refuses() {
655
+ local dir out rc
656
+ dir=$(new_case ambiguous)
657
+ add_task "$dir" t1 claude
658
+ alive_as "$dir" some-unrelated-process
659
+ out=$(run_control "$dir" t1 exit); rc=$?
660
+ expect_code 1 "$rc" "an unattributed endpoint should refuse"
661
+ assert_contains "$out" "positively classified" "the refusal should name the missing attribution"
662
+ [ -z "$(literals "$dir")" ] || fail "an unattributed endpoint must receive no bytes"
663
+ pass "fm-control exit: an endpoint whose process cannot be attributed refuses"
664
+ }
665
+
666
+ test_busy_agent_is_interrupted_before_the_exit_command() {
667
+ local dir out rc
668
+ dir=$(new_case busy)
669
+ add_task "$dir" t1 claude
670
+ alive_as "$dir" claude
671
+ # Arm the semantic busy contract and record a busy turn, exactly as the
672
+ # harness's own lifecycle hook would.
673
+ gen=$("$ROOT/bin/fm-busy-event.sh" arm "$dir/home/state" t1)
674
+ printf 'busy_gen=%s\n' "$gen" >> "$dir/home/state/t1.meta"
675
+ out=$(run_control "$dir" t1 exit); rc=$?
676
+ expect_code 0 "$rc" "exiting a busy agent should succeed"$'\n'"$out"
677
+ [ "$(keys_sent "$dir")" = "Escape" ] \
678
+ || fail "a busy agent should be interrupted once before its exit command, got: $(keys_sent "$dir")"
679
+ [ "$(literals "$dir")" = "/exit" ] || fail "the exit command should follow the interrupt"
680
+ pass "fm-control exit: a busy agent receives interrupt delivery before the exit command"
681
+ }
682
+
683
+ test_idle_agent_is_not_interrupted() {
684
+ local dir out rc gen
685
+ dir=$(new_case idle)
686
+ add_task "$dir" t1 claude
687
+ alive_as "$dir" claude
688
+ gen=$("$ROOT/bin/fm-busy-event.sh" arm "$dir/home/state" t1 --state idle --source fm-spawn --event seed)
689
+ printf 'busy_gen=%s\n' "$gen" >> "$dir/home/state/t1.meta"
690
+ out=$(run_control "$dir" t1 exit); rc=$?
691
+ expect_code 0 "$rc" "exiting an idle agent should succeed"$'\n'"$out"
692
+ [ -z "$(keys_sent "$dir")" ] \
693
+ || fail "an idle agent needs no interrupt, got keys: $(keys_sent "$dir")"
694
+ [ "$(literals "$dir")" = "/exit" ] || fail "the exit command should still be sent"
695
+ pass "fm-control exit: an idle agent goes straight to its exit command"
696
+ }
697
+
698
+ test_interrupt_without_acknowledgement_preserves_busy_state() {
699
+ local dir gen before after out rc
700
+ dir=$(new_case unconfirmed)
701
+ add_task "$dir" t1 claude
702
+ alive_as "$dir" claude
703
+ gen=$("$ROOT/bin/fm-busy-event.sh" arm "$dir/home/state" t1)
704
+ printf 'busy_gen=%s\n' "$gen" >> "$dir/home/state/t1.meta"
705
+ before=$(cat "$dir/home/state/t1.busy-state")
706
+ out=$(run_control "$dir" t1 interrupt); rc=$?
707
+ expect_code 0 "$rc" "an interrupt without acknowledgement should still deliver"$'\n'"$out"
708
+ after=$(cat "$dir/home/state/t1.busy-state")
709
+ [ "$after" = "$before" ] || fail "an unconfirmed interrupt must preserve adapter-owned busy state"
710
+ assert_contains "$out" "verified=agent-alive cancel=unconfirmed" \
711
+ "the result should distinguish delivery proof from unconfirmed cancellation"
712
+ assert_not_contains "$out" "cancel=confirmed" \
713
+ "an adapter without acknowledgement must not report cancellation"
714
+ pass "fm-control interrupt: unconfirmed delivery preserves observed busy state"
715
+ }
716
+
717
+ test_muse_interrupt_confirms_adapter_acknowledgement() {
718
+ local dir root log out rc
719
+ dir=$(new_case confirmed)
720
+ add_task "$dir" t1 muse
721
+ alive_as "$dir" muse
722
+ root="$dir/muse-sessions"
723
+ log="$root/2026/08/08/session-1/session.jsonl"
724
+ mkdir -p "$(dirname "$log")"
725
+ printf '%s\n' \
726
+ "{\"schema_version\":1,\"payload_type\":\"runtime.session.metadata\",\"payload\":{\"kind\":\"metadata\",\"record\":{\"workspace_root\":\"$dir/wt-t1\"}}}" \
727
+ '{"schema_version":1,"payload_type":"runtime.session","payload":{"kind":"run","run_id":"run-1","event":{"kind":"started","prompt":"work"}}}' > "$log"
728
+ printf 'sessions_root=%s\nworkspace_root=%s\nbinding_id=test\n' \
729
+ "$root" "$dir/wt-t1" > "$dir/home/state/t1.muse-session"
730
+ out=$(FM_FAKE_MUSE_LOG="$log" run_control "$dir" t1 interrupt); rc=$?
731
+ expect_code 0 "$rc" "muse interrupt should observe its adapter acknowledgement"$'\n'"$out"
732
+ assert_contains "$out" "verified=agent-alive cancel=confirmed" \
733
+ "the result should report muse's cancelled terminal acknowledgement"
734
+ pass "fm-control interrupt: muse confirms cancellation from its session log"
735
+ }
736
+
737
+ test_interrupt_revalidates_agent_after_acknowledgement_wait() {
738
+ local dir root log out rc
739
+ dir=$(new_case ack-race)
740
+ add_task "$dir" t1 muse
741
+ alive_as "$dir" muse
742
+ root="$dir/muse-sessions"
743
+ log="$root/2026/08/08/session-1/session.jsonl"
744
+ mkdir -p "$(dirname "$log")"
745
+ printf '%s\n' \
746
+ "{\"schema_version\":1,\"payload_type\":\"runtime.session.metadata\",\"payload\":{\"kind\":\"metadata\",\"record\":{\"workspace_root\":\"$dir/wt-t1\"}}}" \
747
+ '{"schema_version":1,"payload_type":"runtime.session","payload":{"kind":"run","run_id":"run-1","event":{"kind":"started","prompt":"work"}}}' > "$log"
748
+ printf 'sessions_root=%s\nworkspace_root=%s\nbinding_id=test\n' \
749
+ "$root" "$dir/wt-t1" > "$dir/home/state/t1.muse-session"
750
+ out=$(FM_FAKE_MUSE_LOG="$log" FM_FAKE_MUSE_DISAPPEAR_BEFORE_ACK=1 \
751
+ run_control "$dir" t1 interrupt); rc=$?
752
+ expect_code 1 "$rc" "interrupt should fail when the agent stops during acknowledgement polling"
753
+ assert_contains "$out" "agent is 'dead' after its interrupt key" \
754
+ "the final postcondition should observe the agent after acknowledgement polling"
755
+ assert_not_contains "$out" "interrupt-delivered" \
756
+ "a stale pre-wait liveness proof must not be published"
757
+ pass "fm-control interrupt: postconditions are revalidated after acknowledgement polling"
758
+ }
759
+
760
+ test_exit_accepts_agent_stopped_by_busy_interrupt() {
761
+ local dir out rc gen
762
+ dir=$(new_case interrupt-stops)
763
+ add_task "$dir" t1 claude
764
+ alive_as "$dir" claude
765
+ gen=$("$ROOT/bin/fm-busy-event.sh" arm "$dir/home/state" t1)
766
+ printf 'busy_gen=%s\n' "$gen" >> "$dir/home/state/t1.meta"
767
+ out=$(FM_FAKE_INTERRUPT_STOPS_AGENT=1 run_control "$dir" t1 exit); rc=$?
768
+ expect_code 0 "$rc" "exit should accept a busy agent stopped by interrupt"$'\n'"$out"
769
+ assert_contains "$out" "stopped t1 harness=claude" \
770
+ "the authoritative gone-state should complete exit successfully"
771
+ [ "$(keys_sent "$dir")" = Escape ] \
772
+ || fail "exit should deliver the busy agent's interrupt sequence"
773
+ [ -z "$(literals "$dir")" ] \
774
+ || fail "exit should not type a command after interrupt already stopped the agent"
775
+ [ ! -e "$dir/home/state/t1.busy-gen" ] && [ ! -e "$dir/home/state/t1.busy-state" ] \
776
+ || fail "exit should retire busy wiring for an agent stopped by interrupt"
777
+ pass "fm-control exit: an interrupt-stopped agent satisfies the gone-state postcondition"
778
+ }
779
+
780
+ test_agent_that_does_not_stop_fails_closed() {
781
+ local dir out rc gen
782
+ dir=$(new_case stubborn)
783
+ add_task "$dir" t1 claude
784
+ alive_as "$dir" claude
785
+ gen=$("$ROOT/bin/fm-busy-event.sh" arm "$dir/home/state" t1)
786
+ printf 'busy_gen=%s\n' "$gen" >> "$dir/home/state/t1.meta"
787
+ out=$(env FM_FAKE_NEVER_DIES=1 PATH="$dir/fakebin:$PATH" FM_HOME="$dir/home" \
788
+ FM_FAKE_DIR="$dir/fake" FM_CONTROL_POLL=0.01 FM_CONTROL_EXIT_WAIT=0.05 \
789
+ "$CONTROL" t1 exit 2>&1); rc=$?
790
+ expect_code 1 "$rc" "an agent that ignores its exit command should fail closed"
791
+ assert_contains "$out" "did not stop" "the failure should say the agent did not stop"
792
+ assert_contains "$out" "exit-delivered t1 interrupt=delivered verified=agent-alive cancel=unconfirmed exit-command=delivered agent-state=alive exit=unconfirmed" \
793
+ "the failure should distinguish delivered lifecycle input from the unconfirmed exit"
794
+ assert_not_contains "$out" "nothing was changed" \
795
+ "the failure must not deny the lifecycle input that was delivered"
796
+ [ "$(keys_sent "$dir")" = Escape ] \
797
+ || fail "a stubborn busy agent should receive its interrupt sequence"
798
+ [ "$(literals "$dir")" = /exit ] \
799
+ || fail "a stubborn busy agent should receive its exit command"
800
+ pass "fm-control exit: a stubborn agent reports delivered input and an unconfirmed exit"
801
+ }
802
+
803
+ test_grok_interrupt_without_acknowledgement_reports_unconfirmed() {
804
+ local dir out rc
805
+ dir=$(new_case nosettle)
806
+ add_task "$dir" t1 grok
807
+ alive_as "$dir" grok
808
+ printf '╭────╮\n│ │\n╰────╯\n Ctrl+c:cancel\n' > "$dir/fake/pane"
809
+ out=$(run_control "$dir" t1 interrupt); rc=$?
810
+ expect_code 0 "$rc" "grok interrupt delivery should not depend on inferred cancellation"$'\n'"$out"
811
+ assert_contains "$out" "verified=agent-alive cancel=unconfirmed" \
812
+ "a rendered busy hint is not a cancellation acknowledgement"
813
+ pass "fm-control interrupt: grok reports delivery without claiming cancellation"
814
+ }
815
+
816
+ test_grok_idle_footer_does_not_confirm_cancellation() {
817
+ local dir out rc
818
+ dir=$(new_case settles)
819
+ add_task "$dir" t1 grok
820
+ alive_as "$dir" grok
821
+ printf '╭────╮\n│ │\n╰────╯\n Shift+Tab:mode │ Ctrl+.:shortcuts\n' > "$dir/fake/pane"
822
+ out=$(run_control "$dir" t1 interrupt); rc=$?
823
+ expect_code 0 "$rc" "grok interrupt delivery should succeed"$'\n'"$out"
824
+ assert_contains "$out" "verified=agent-alive cancel=unconfirmed" \
825
+ "an idle footer is not an explicit cancellation acknowledgement"
826
+ [ "$(keys_sent "$dir")" = "C-c" ] || fail "grok should receive C-c, got: $(keys_sent "$dir")"
827
+ pass "fm-control interrupt: grok's idle footer does not confirm cancellation"
828
+ }
829
+
830
+ # --- 6. marker non-regression -----------------------------------------------
831
+
832
+ test_secondmate_control_command_carries_no_marker() {
833
+ local dir out rc typed home
834
+ dir=$(new_case sm-marker)
835
+ home="$dir/home"
836
+ add_task "$dir" domain claude secondmate
837
+ # A secondmate's worktree IS its home; give it the marker its records need.
838
+ printf '%s\n' domain > "$dir/wt-domain/.fm-secondmate-home"
839
+ alive_as "$dir" claude
840
+ out=$(run_control "$dir" domain exit); rc=$?
841
+ expect_code 0 "$rc" "exiting a secondmate's agent should succeed"$'\n'"$out"
842
+ typed=$(literals "$dir")
843
+ [ "$typed" = "/exit" ] \
844
+ || fail "a secondmate control command must be the bare exit command, got: $typed"
845
+ case "$typed" in
846
+ *"$FM_FROMFIRST_MARK"*) fail "a control command must never carry the from-firstmate marker" ;;
847
+ esac
848
+ case "$typed" in
849
+ *corr=*) fail "a control command must never carry a pending-reply correlation id" ;;
850
+ esac
851
+ [ -z "$(find "$home/state/pending-replies" -type f 2>/dev/null | head -n 1)" ] \
852
+ || fail "a control command must not open a pending-reply expectation"
853
+ pass "fm-control: a lifecycle command to a secondmate is unmarked and opens no reply expectation"
854
+ }
855
+
856
+ test_fm_send_still_marks_the_same_secondmate_task() {
857
+ local dir log out rc
858
+ dir=$(new_case sm-send)
859
+ add_task "$dir" domain claude secondmate
860
+ log="$dir/fake/sendlog"
861
+ : > "$log"
862
+ out=$(env PATH="$dir/fakebin:$PATH" FM_HOME="$dir/home" FM_FAKE_DIR="$dir/fake" \
863
+ FM_SEND_SETTLE=0 FM_ROOT_OVERRIDE="$dir/home" \
864
+ "$SEND" domain "audit the build" 2>&1); rc=$?
865
+ expect_code 0 "$rc" "fm-send to a secondmate should still succeed"$'\n'"$out"
866
+ # The marked steer rides fm-send's durable inbox plane; only the doorbell is
867
+ # typed, so the marker is asserted on the recorded body.
868
+ case "$(bash -c '. "$1"; fm_task_inbox_body "$2"' _ "$ROOT/bin/fm-task-inbox-lib.sh" \
869
+ "$dir/home/state/domain.inbox/001.msg")" in
870
+ "$FM_FROMFIRST_MARK"*) : ;;
871
+ *) fail "fm-send must still mark a kind=secondmate target: $(literals "$dir")" ;;
872
+ esac
873
+ pass "fm-control's arrival leaves fm-send's from-firstmate marking untouched"
874
+ }
875
+
876
+ test_exit_types_each_harness_verified_command
877
+ test_interrupt_sends_each_harness_verified_key
878
+ test_opencode_interrupts_twice_and_others_once
879
+ test_unverified_harness_is_refused
880
+ test_harness_family_resolution
881
+ test_prefixed_recorded_harness_reaches_each_control_verb
882
+ test_backend_key_capability_matrix
883
+ test_harness_kind_capability
884
+ test_orca_refuses_an_escape_harness_interrupt
885
+ test_unverified_state_backends_refuse_stop_verbs
886
+ test_state_verified_backends_are_exactly_tmux_and_herdr
887
+ test_window_label_is_refused_with_the_exact_id
888
+ test_explicit_endpoint_is_refused
889
+ test_unknown_task_is_refused
890
+ test_record_bound_to_another_task_is_refused
891
+ test_remote_secondmate_is_refused_by_placement
892
+ test_interrupt_and_exit_lock_before_task_state_resolution
893
+ test_verb_allowlist_is_closed
894
+ test_resume_is_refused_with_its_reason
895
+ test_relaunch_only_flags_are_rejected_on_other_verbs
896
+ test_already_stopped_exit_is_idempotent
897
+ test_missing_endpoint_refuses
898
+ test_interrupt_refuses_when_no_agent_runs
899
+ test_ambiguous_endpoint_refuses
900
+ test_busy_agent_is_interrupted_before_the_exit_command
901
+ test_idle_agent_is_not_interrupted
902
+ test_interrupt_without_acknowledgement_preserves_busy_state
903
+ test_muse_interrupt_confirms_adapter_acknowledgement
904
+ test_interrupt_revalidates_agent_after_acknowledgement_wait
905
+ test_exit_accepts_agent_stopped_by_busy_interrupt
906
+ test_agent_that_does_not_stop_fails_closed
907
+ test_grok_interrupt_without_acknowledgement_reports_unconfirmed
908
+ test_grok_idle_footer_does_not_confirm_cancellation
909
+ test_secondmate_control_command_carries_no_marker
910
+ test_fm_send_still_marks_the_same_secondmate_task