@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,986 @@
1
+ #!/usr/bin/env bash
2
+ # fm-busy-lib.sh - the ONE owner of firstmate's semantic busy-state contract.
3
+ #
4
+ # Design source: the captain-approved semantic busy-state redesign
5
+ # (2026-07-28): each harness adapter reports turn lifecycle through a
6
+ # machine-readable semantic source it owns, classification always exposes
7
+ # which source produced it, and missing, malformed, stale, unsupported, or
8
+ # unverified semantic data is UNKNOWN - never idle. Endpoint death is the only
9
+ # process-level override and yields dead, never busy. Child processes, CPU,
10
+ # process sleep state, marker mtimes, and the old global UI-regex OR are not
11
+ # state signals here; state/<id>.turn-ended files remain wake NOTIFICATIONS
12
+ # owned by the watcher, not current-state truth.
13
+ #
14
+ # Record file: state/<id>.busy-state - exactly one line, atomically replaced
15
+ # by bin/fm-busy-event.sh (the only writer):
16
+ #
17
+ # v1 gen=<token> seq=<uint> state=<busy|idle|unknown> source=<token> event=<token> ts=<epoch>
18
+ #
19
+ # Gen sidecar: state/<id>.busy-gen - one token minted when the task's busy
20
+ # wiring is armed (fm-spawn, or a documented recovery re-arm). Every event
21
+ # must present the current gen; an event or record carrying any other gen is
22
+ # a stale incarnation and is rejected (written events) or classified unknown
23
+ # (read records). seq is a strictly increasing integer per gen, advanced
24
+ # under the writer's lock, so an out-of-order apply can never regress a
25
+ # newer record.
26
+ #
27
+ # Semantic sources written by adapters (fm_busy_sources_for_harness owns the
28
+ # per-harness trust table; a record whose source is not trusted for the
29
+ # task's recorded harness classifies unknown, so one adapter's writer can
30
+ # never classify another adapter):
31
+ # pi-ext Pi/pi-signed per-task extension (agent_start/agent_settled)
32
+ # opencode-plugin OpenCode per-task plugin (session.status)
33
+ # claude-hook Claude lifecycle hooks (UserPromptSubmit/Stop/StopFailure/SessionEnd)
34
+ # codex-hook, codex-appserver reserved: Codex, gated by
35
+ # fm_busy_codex_semantic_source
36
+ # kimi-wire, kimi-hook reserved: standalone Kimi, gated by fm_busy_kimi_verified
37
+ # Firstmate-owned sources accepted for every converted adapter:
38
+ # fm-spawn the launch-brief turn seeded at spawn
39
+ # fm-interrupt the legacy Claude fm-send --key Escape idle event
40
+ # fm-recovery a documented recovery reset after relaunch
41
+ # Classifier-only sources (never written into a record):
42
+ # endpoint-gone, herdr-native, grok-regex, muse-session-log,
43
+ # cursor-transcript, missing, malformed, gen-mismatch, source-mismatch,
44
+ # kimi-unverified, codex-unverified, capture-failed, no-target
45
+ #
46
+ # Classification (fm_busy_classify): busy | idle | unknown | dead, always
47
+ # with the producing source as the second token. Precedence:
48
+ # 1. dead endpoint (fm_busy_classify_live only) -> dead endpoint-gone
49
+ # 2. standalone Kimi before verification -> unknown kimi-unverified
50
+ # 3. a valid, gen-matching, source-trusted record -> its state and source
51
+ # 4. no record at all: herdr's native busy verdict is trusted as busy
52
+ # (generation state is sufficient for busy, not for idle), then the
53
+ # muse session-log and cursor transcript pull sources, then the Grok-only
54
+ # temporary regex fallback classifies a grok task from its rendered tail,
55
+ # then unknown missing
56
+ # 5. malformed, stale, or untrusted records -> unknown, never a fallback
57
+ # The Grok arm is the ONLY rendered-text classification that survives the
58
+ # redesign, because Grok's structured lifecycle was not credited-live-verified
59
+ # in the approved audit; it is scoped to harness=grok and can never classify
60
+ # another adapter. The delivery guards in bin/fm-composer-lib.sh match rendered
61
+ # footers for submit acknowledgement and away-mode supervisor injection only;
62
+ # neither is a recorded worker state source.
63
+ #
64
+ # The muse pull source is semantic, not rendered: it folds muse's own durable
65
+ # session event log. It has no writer, no arm, and no gen, because
66
+ # muse's default build ships no hook or plugin surface that could push events
67
+ # (its plugin engine reports "plugins are not available in this build" without
68
+ # MUSE_EXPERIMENTAL_PLUGINS). Nothing is armed for muse for the same reason
69
+ # standalone Kimi is not: a seeded record with no writer could never be
70
+ # cleared. See fm_busy_muse_run_state for the fold.
71
+ #
72
+ # The cursor pull source works the same way and for the same reason: it folds
73
+ # cursor's own durable per-conversation transcript, which brackets each turn
74
+ # with a role:user open and a typed turn_ended close that covers aborts. It has
75
+ # no writer, no arm, and no gen, so nothing is seeded that could never be
76
+ # cleared. See fm_busy_cursor_turn_state for the fold. Cursor's rendered
77
+ # `ctrl+c to stop` footer is deliberately not a state source here.
78
+ #
79
+ # Codex negotiation (fm_busy_codex_appserver_observable,
80
+ # fm_busy_codex_hooks_verified): the approved contract prefers Codex's
81
+ # app-server turn lifecycle with capability negotiation, and sanctions its
82
+ # stable lifecycle hooks as the intermediate. Neither is usable on the
83
+ # installed binary, so Codex classifies unknown codex-unverified rather than
84
+ # falling back to idle, and fm-spawn installs no Codex busy wiring.
85
+ # docs/verification/supervision.md owns the evidence for both probes.
86
+ #
87
+ # Sourcing: set -u and set -e safe; no subshell-unfriendly globals.
88
+
89
+ FM_BUSY_LIB_VERSION=v1
90
+
91
+ # Standalone-Kimi verification gate. Empty means no installed Kimi version
92
+ # has passed live verification, so every standalone Kimi task classifies
93
+ # unknown kimi-unverified and fm-spawn wires no Kimi busy events. Kimi's
94
+ # rendered moon-phase spinner is deliberately NOT a state source here: the
95
+ # approved redesign forbids inventing a Kimi UI signature, and that spinner
96
+ # is locale- and emoji-font-sensitive.
97
+ #
98
+ # Preferred source, in order: Wire mode's JSON-RPC `prompt` request lifetime,
99
+ # whose outstanding request exactly brackets a turn and returns finished,
100
+ # cancelled, or max_steps_reached (so it covers interruption, which `Stop`
101
+ # does not); then the documented lifecycle hooks, which must include
102
+ # `Interrupt` because Kimi documents that `Stop` does not fire on interrupts.
103
+ #
104
+ # To open the gate: install Kimi, live-verify the chosen source brackets a
105
+ # real turn on a firstmate-launched worker including the interrupt path,
106
+ # record the version, exact commands, and observed output in
107
+ # docs/verification/supervision.md, add the verified version string(s) here,
108
+ # and land the wiring in fm-spawn behind this same gate in the same change.
109
+ FM_BUSY_KIMI_VERIFIED_VERSIONS=""
110
+
111
+ fm_busy_kimi_verified() {
112
+ [ -n "$FM_BUSY_KIMI_VERIFIED_VERSIONS" ]
113
+ }
114
+
115
+ # fm_busy_codex_appserver_observable: capability/version negotiation for the
116
+ # Codex app-server turn lifecycle. Returns 0 only when a pane worker's turns
117
+ # are observable through the app-server protocol on the installed binary.
118
+ # codex-cli 0.145.0 verdict (live, 2026-07-28): NOT observable. The v2
119
+ # protocol does define the needed turn lifecycle (turn/started plus a
120
+ # turn/completed status of completed, interrupted, failed, or inProgress),
121
+ # but an interactive TUI worker neither starts nor attaches to the
122
+ # app-server daemon, and `codex app-server daemon start` refuses outside the
123
+ # managed standalone install, so no client can observe a pane worker's turns.
124
+ fm_busy_codex_appserver_observable() {
125
+ return 1
126
+ }
127
+
128
+ # fm_busy_codex_hooks_verified: the sanctioned intermediate - Codex's stable
129
+ # hooks engine (UserPromptSubmit to open a turn, Stop and SessionEnd to close
130
+ # it). Returns 0 only once those hooks are live-verified to fire for a
131
+ # firstmate-launched worker. codex-cli 0.145.0 verdict (live, 2026-07-28):
132
+ # NOT verified. Firstmate-written project hooks under <worktree>/.codex/
133
+ # never fired in an interactive pane whose directory trust was granted, nor
134
+ # under `codex exec`, in either case with --dangerously-bypass-hook-trust,
135
+ # while global hooks fired in the same runs. Codex additionally exposes no
136
+ # StopFailure hook, so an API-error turn end would need separate coverage
137
+ # even after the discovery problem is solved.
138
+ fm_busy_codex_hooks_verified() {
139
+ return 1
140
+ }
141
+
142
+ # fm_busy_codex_semantic_source: 0 when ANY verified Codex semantic source
143
+ # exists. fm-spawn arms and wires Codex only behind this gate, and the
144
+ # classifier reports unknown codex-unverified until it opens.
145
+ fm_busy_codex_semantic_source() {
146
+ fm_busy_codex_appserver_observable || fm_busy_codex_hooks_verified
147
+ }
148
+
149
+ fm_busy_record_path() { # <state-dir> <id>
150
+ printf '%s/%s.busy-state' "$1" "$2"
151
+ }
152
+
153
+ fm_busy_gen_path() { # <state-dir> <id>
154
+ printf '%s/%s.busy-gen' "$1" "$2"
155
+ }
156
+
157
+ # fm_busy_token_valid: conservative token charset shared by gen, source, and
158
+ # event fields. Anything else is malformed.
159
+ fm_busy_token_valid() { # <value>
160
+ case "${1:-}" in
161
+ ''|*[!A-Za-z0-9._-]*) return 1 ;;
162
+ esac
163
+ return 0
164
+ }
165
+
166
+ # fm_busy_current_gen: the task's armed gen token, or failure when the busy
167
+ # contract has never been armed for this task.
168
+ fm_busy_current_gen() { # <state-dir> <id>
169
+ local gen_file gen
170
+ gen_file=$(fm_busy_gen_path "$1" "$2")
171
+ [ -f "$gen_file" ] || return 1
172
+ IFS= read -r gen < "$gen_file" 2>/dev/null || gen=
173
+ fm_busy_token_valid "$gen" || return 1
174
+ printf '%s' "$gen"
175
+ }
176
+
177
+ # fm_busy_sources_for_harness: the semantic sources trusted to classify a
178
+ # task recorded with <harness>. One line, space-separated, possibly empty.
179
+ # The firstmate-owned sources are appended for every converted adapter.
180
+ # Grok and muse deliberately trust nothing: neither has a semantic WRITER, so
181
+ # neither is armed, and both read their live source on demand in the classifier
182
+ # (grok's rendered tail, muse's session log) rather than through a stored
183
+ # record. Listing a source here without a writer that can clear it would seed a
184
+ # busy record nothing could ever settle.
185
+ fm_busy_sources_for_harness() { # <harness>
186
+ local adapter=
187
+ case "${1:-}" in
188
+ claude*) adapter=claude-hook ;;
189
+ codex*)
190
+ fm_busy_codex_semantic_source || { printf ''; return 0; }
191
+ adapter='codex-hook codex-appserver'
192
+ ;;
193
+ opencode*) adapter=opencode-plugin ;;
194
+ pi|pi-signed) adapter=pi-ext ;;
195
+ kimi*)
196
+ fm_busy_kimi_verified || { printf ''; return 0; }
197
+ adapter='kimi-wire kimi-hook'
198
+ ;;
199
+ *) printf ''; return 0 ;;
200
+ esac
201
+ printf '%s fm-spawn fm-interrupt fm-recovery' "$adapter"
202
+ }
203
+
204
+ fm_busy_source_trusted() { # <harness> <source>
205
+ local trusted
206
+ trusted=$(fm_busy_sources_for_harness "$1")
207
+ case " $trusted " in
208
+ *" $2 "*) return 0 ;;
209
+ esac
210
+ return 1
211
+ }
212
+
213
+ # fm_busy_record_read: parse and validate state/<id>.busy-state against the
214
+ # armed gen. Prints "<state> <source> <event> <seq>" for a valid record.
215
+ # Non-zero returns name the reason on stdout instead:
216
+ # missing no record file (or no armed gen and no record)
217
+ # malformed unparseable line, bad tokens, or a missing armed gen for an
218
+ # existing record
219
+ # gen-mismatch a record from a stale incarnation
220
+ fm_busy_record_read() { # <state-dir> <id>
221
+ local state=$1 id=$2 rec gen line extra ver f
222
+ local r_gen='' r_seq='' r_state='' r_source='' r_event='' r_ts=''
223
+ rec=$(fm_busy_record_path "$state" "$id")
224
+ if [ ! -f "$rec" ]; then
225
+ printf 'missing'
226
+ return 1
227
+ fi
228
+ if ! gen=$(fm_busy_current_gen "$state" "$id"); then
229
+ # A record without an armed gen has no incarnation to bind to.
230
+ printf 'malformed'
231
+ return 1
232
+ fi
233
+ # shellcheck disable=SC2034 # extra exists only to prove the record is one line
234
+ { IFS= read -r line && ! IFS= read -r extra; } < "$rec" 2>/dev/null || {
235
+ printf 'malformed'
236
+ return 1
237
+ }
238
+ # `read -a` rather than `set --`: it never glob-expands a field and never
239
+ # touches the caller's positional parameters or shell options.
240
+ local -a fields
241
+ IFS=' ' read -r -a fields <<< "$line"
242
+ ver=${fields[0]:-}
243
+ [ "$ver" = "$FM_BUSY_LIB_VERSION" ] || { printf 'malformed'; return 1; }
244
+ for f in "${fields[@]:1}"; do
245
+ case "$f" in
246
+ gen=*) r_gen=${f#gen=} ;;
247
+ seq=*) r_seq=${f#seq=} ;;
248
+ state=*) r_state=${f#state=} ;;
249
+ source=*) r_source=${f#source=} ;;
250
+ event=*) r_event=${f#event=} ;;
251
+ ts=*) r_ts=${f#ts=} ;;
252
+ *) printf 'malformed'; return 1 ;;
253
+ esac
254
+ done
255
+ fm_busy_token_valid "$r_gen" || { printf 'malformed'; return 1; }
256
+ fm_busy_token_valid "$r_source" || { printf 'malformed'; return 1; }
257
+ fm_busy_token_valid "$r_event" || { printf 'malformed'; return 1; }
258
+ case "$r_seq" in ''|*[!0-9]*) printf 'malformed'; return 1 ;; esac
259
+ case "$r_ts" in ''|*[!0-9]*) printf 'malformed'; return 1 ;; esac
260
+ case "$r_state" in busy|idle|unknown) : ;; *) printf 'malformed'; return 1 ;; esac
261
+ if [ "$r_gen" != "$gen" ]; then
262
+ printf 'gen-mismatch'
263
+ return 1
264
+ fi
265
+ printf '%s %s %s %s' "$r_state" "$r_source" "$r_event" "$r_seq"
266
+ }
267
+
268
+ # ---------------------------------------------------------------------------
269
+ # muse session-log busy source
270
+ #
271
+ # muse persists an append-only session event log per session at
272
+ # <sessions-root>/YYYY/MM/DD/<session-uuid>/session.jsonl, and brackets every
273
+ # submitted turn with one run lifecycle pair. Verified live on muse
274
+ # 0.1.0-R708.1 across completed, interrupted, and killed-mid-turn turns:
275
+ # {"payload":{"kind":"run","run_id":"<uuid>","event":{"kind":"started",...
276
+ # {"payload":{"kind":"run","run_id":"<uuid>","event":{"kind":"terminal",
277
+ # "terminal":"completed"|"cancelled",...
278
+ # An Escape interrupt closes its run with terminal=cancelled, so unlike Claude's
279
+ # Stop hook this source covers the interrupt path itself. Any later
280
+ # run_retracted records follow the terminal rather than replacing it.
281
+ #
282
+ # Both halves of the fold are trusted. An open run is positive proof a turn is
283
+ # in flight, and a settled log is idle: the credentialed multi-step smoke showed
284
+ # one run pair spans a whole multi-step turn, including an Escape interrupt that
285
+ # closes the run with terminal=cancelled instead of continuing the turn in
286
+ # another run. This gives the settled log the same idle trust as the Claude and
287
+ # Pi push sources. A version allowlist would be false precision and a maintenance
288
+ # treadmill for an auto-updating vendor binary: busy classification receives
289
+ # only the normalized muse harness identity, while session metadata records
290
+ # semver 0.1.0 plus a build SHA that cannot be matched against it. Resolution
291
+ # failures - no sidecar, no matching log, an unreadable or run-free log - remain
292
+ # unknown because those prove nothing about the turn either way. See
293
+ # docs/verification/muse.md for the evidence.
294
+ # fm_busy_muse_binding_path: the per-task sidecar fm-spawn writes so the
295
+ # classifier binds a pane to its session log without re-deriving muse's data
296
+ # directory. It records sessions_root=<abs>, workspace_root=<abs>, one
297
+ # binding_id=<token>, and one prior_log=<abs> for each matching main log that
298
+ # predates this pane.
299
+ fm_busy_muse_binding_path() { # <state-dir> <id>
300
+ printf '%s/%s.muse-session' "$1" "$2"
301
+ }
302
+
303
+ fm_busy_muse_cache_path() { # <state-dir> <id>
304
+ printf '%s/%s.muse-session-current' "$1" "$2"
305
+ }
306
+
307
+ # fm_busy_muse_binding_field: read one field from the sidecar, or fail.
308
+ fm_busy_muse_binding_field() { # <state-dir> <id> <key>
309
+ local path line key=$3
310
+ path=$(fm_busy_muse_binding_path "$1" "$2")
311
+ [ -f "$path" ] || return 1
312
+ while IFS= read -r line || [ -n "$line" ]; do
313
+ case "$line" in
314
+ "$key="*)
315
+ line=${line#"$key="}
316
+ [ -n "$line" ] || return 1
317
+ printf '%s' "$line"
318
+ return 0
319
+ ;;
320
+ esac
321
+ done < "$path"
322
+ return 1
323
+ }
324
+
325
+ # fm_busy_muse_matching_logs: every MAIN session log whose recorded
326
+ # workspace_root is this task's worktree. The depth bounds are what exclude
327
+ # muse's own native sub-agent logs, which live one directory deeper under
328
+ # subagent/<child-session-id>/session.jsonl and carry their own independent run
329
+ # lifecycle - folding a child's log would report the parent busy long after the
330
+ # parent's turn ended.
331
+ fm_busy_muse_matching_logs() { # <sessions-root> <workspace-root>
332
+ local root=$1 ws=$2
333
+ [ -d "$root" ] || return 1
334
+ command -v node >/dev/null 2>&1 || return 1
335
+ node - "$root" "$ws" <<'NODE'
336
+ const fs = require("fs");
337
+ const path = require("path");
338
+ const [root, workspace] = process.argv.slice(2);
339
+
340
+ function directories(parent) {
341
+ try {
342
+ return fs.readdirSync(parent, { withFileTypes: true })
343
+ .filter((entry) => entry.isDirectory())
344
+ .map((entry) => path.join(parent, entry.name));
345
+ } catch {
346
+ return [];
347
+ }
348
+ }
349
+
350
+ function metadataWorkspace(file) {
351
+ let descriptor;
352
+ try {
353
+ descriptor = fs.openSync(file, "r");
354
+ const buffer = Buffer.alloc(65536);
355
+ const length = fs.readSync(descriptor, buffer, 0, buffer.length, 0);
356
+ const newline = buffer.indexOf(10, 0);
357
+ if (newline < 0 || newline >= length) return null;
358
+ const record = JSON.parse(buffer.subarray(0, newline).toString("utf8"));
359
+ return record?.payload?.record?.workspace_root ?? null;
360
+ } catch {
361
+ return null;
362
+ } finally {
363
+ if (descriptor !== undefined) fs.closeSync(descriptor);
364
+ }
365
+ }
366
+
367
+ for (const year of directories(root)) {
368
+ for (const month of directories(year)) {
369
+ for (const day of directories(month)) {
370
+ for (const session of directories(day)) {
371
+ const file = path.join(session, "session.jsonl");
372
+ try {
373
+ if (!fs.lstatSync(file).isFile()) continue;
374
+ } catch {
375
+ continue;
376
+ }
377
+ if (metadataWorkspace(file) === workspace) process.stdout.write(`${file}\n`);
378
+ }
379
+ }
380
+ }
381
+ }
382
+ NODE
383
+ }
384
+
385
+ fm_busy_muse_binding_has_prior_log() { # <state-dir> <id> <session-log>
386
+ local path line
387
+ path=$(fm_busy_muse_binding_path "$1" "$2")
388
+ [ -f "$path" ] || return 1
389
+ while IFS= read -r line || [ -n "$line" ]; do
390
+ [ "$line" = "prior_log=$3" ] && return 0
391
+ done < "$path"
392
+ return 1
393
+ }
394
+
395
+ fm_busy_muse_cache_field() { # <state-dir> <id> <key>
396
+ local path line key=$3
397
+ path=$(fm_busy_muse_cache_path "$1" "$2")
398
+ [ -f "$path" ] || return 1
399
+ while IFS= read -r line || [ -n "$line" ]; do
400
+ case "$line" in
401
+ "$key="*)
402
+ line=${line#"$key="}
403
+ [ -n "$line" ] || return 1
404
+ printf '%s' "$line"
405
+ return 0
406
+ ;;
407
+ esac
408
+ done < "$path"
409
+ return 1
410
+ }
411
+
412
+ fm_busy_muse_main_log_path_valid() { # <sessions-root> <session-log>
413
+ local root=${1%/} log=$2 rel year month day session leaf
414
+ while :; do
415
+ case "$root" in
416
+ *'//'*) root=${root//\/\//\/} ;;
417
+ *) break ;;
418
+ esac
419
+ done
420
+ [ -n "$root" ] && [ -f "$log" ] && [ ! -L "$log" ] || return 1
421
+ case "$log" in
422
+ "$root"/*) rel=${log#"$root"/} ;;
423
+ *) return 1 ;;
424
+ esac
425
+ year=${rel%%/*}; rel=${rel#*/}
426
+ month=${rel%%/*}; rel=${rel#*/}
427
+ day=${rel%%/*}; rel=${rel#*/}
428
+ session=${rel%%/*}; leaf=${rel#*/}
429
+ [ -n "$year" ] && [ -n "$month" ] && [ -n "$day" ] && [ -n "$session" ] \
430
+ && [ "$leaf" = session.jsonl ]
431
+ }
432
+
433
+ fm_busy_muse_namespace_day() { # <sessions-root>
434
+ printf '%s/%s' "${1%/}" "$(date '+%Y/%m/%d')"
435
+ }
436
+
437
+ fm_busy_muse_namespace_signature() { # <day-directory>
438
+ local first first_signature manifest='' path paths signature
439
+ if [ ! -d "$1" ]; then
440
+ printf '%s' missing
441
+ return 0
442
+ fi
443
+ paths=$(find "$1" -mindepth 2 -maxdepth 2 -type f -name session.jsonl -print 2>/dev/null) \
444
+ || return 1
445
+ paths=$(printf '%s\n' "$paths" | LC_ALL=C sort) || return 1
446
+ while IFS= read -r path; do
447
+ [ -n "$path" ] || continue
448
+ first=$(sed -n '1p' "$path") || return 1
449
+ first_signature=$(printf '%s' "$first" | cksum | awk '{ print $1 ":" $2 }') || return 1
450
+ manifest="${manifest}${path}:${first_signature}
451
+ "
452
+ done <<EOF
453
+ $paths
454
+ EOF
455
+ signature=$(printf '%s' "$manifest" | cksum | awk '{ print $1 ":" $2 }') || return 1
456
+ [ -n "$signature" ] || return 1
457
+ printf '%s' "$signature"
458
+ }
459
+
460
+ fm_busy_muse_cached_session_log() { # <state-dir> <id> <root> <binding-id>
461
+ local cache_binding log cache_day cache_signature day signature
462
+ [ -n "$4" ] || return 1
463
+ cache_binding=$(fm_busy_muse_cache_field "$1" "$2" binding_id) || return 1
464
+ [ "$cache_binding" = "$4" ] || return 1
465
+ log=$(fm_busy_muse_cache_field "$1" "$2" session_log) || return 1
466
+ fm_busy_muse_main_log_path_valid "$3" "$log" || return 1
467
+ fm_busy_muse_binding_has_prior_log "$1" "$2" "$log" && return 1
468
+ cache_day=$(fm_busy_muse_cache_field "$1" "$2" namespace_day) || return 1
469
+ cache_signature=$(fm_busy_muse_cache_field "$1" "$2" namespace_signature) || return 1
470
+ day=$(fm_busy_muse_namespace_day "$3") || return 1
471
+ [ "$cache_day" = "$day" ] || return 1
472
+ signature=$(fm_busy_muse_namespace_signature "$day") || return 1
473
+ [ "$cache_signature" = "$signature" ] || return 1
474
+ printf '%s' "$log"
475
+ }
476
+
477
+ fm_busy_muse_cache_session_log() { # <state-dir> <id> <binding-id> <session-log> <namespace-day> <namespace-signature>
478
+ local cache tmp current
479
+ [ -n "$3" ] || return 0
480
+ current=$(fm_busy_muse_binding_field "$1" "$2" binding_id) || return 1
481
+ [ "$current" = "$3" ] || return 1
482
+ cache=$(fm_busy_muse_cache_path "$1" "$2")
483
+ tmp="$cache.tmp.$$"
484
+ {
485
+ printf 'binding_id=%s\n' "$3"
486
+ printf 'session_log=%s\n' "$4"
487
+ printf 'namespace_day=%s\n' "$5"
488
+ printf 'namespace_signature=%s\n' "$6"
489
+ } > "$tmp" || { rm -f "$tmp"; return 1; }
490
+ mv -f -- "$tmp" "$cache"
491
+ }
492
+
493
+ # fm_busy_muse_session_log: the one matching MAIN session log that did not
494
+ # exist when fm-spawn created this pane's binding. Multiple candidates are
495
+ # ambiguous and fail closed rather than guessing which pane owns either log.
496
+ fm_busy_muse_session_log() { # <state-dir> <id>
497
+ local root ws binding_id='' candidate selected='' cache namespace_day namespace_before namespace_after
498
+ root=$(fm_busy_muse_binding_field "$1" "$2" sessions_root) || return 1
499
+ ws=$(fm_busy_muse_binding_field "$1" "$2" workspace_root) || return 1
500
+ binding_id=$(fm_busy_muse_binding_field "$1" "$2" binding_id 2>/dev/null || true)
501
+ if cache=$(fm_busy_muse_cached_session_log "$1" "$2" "$root" "$binding_id"); then
502
+ printf '%s' "$cache"
503
+ return 0
504
+ fi
505
+ rm -f "$(fm_busy_muse_cache_path "$1" "$2")"
506
+ namespace_day=$(fm_busy_muse_namespace_day "$root") || return 1
507
+ namespace_before=$(fm_busy_muse_namespace_signature "$namespace_day") || return 1
508
+ while IFS= read -r candidate; do
509
+ [ -n "$candidate" ] || continue
510
+ fm_busy_muse_binding_has_prior_log "$1" "$2" "$candidate" && continue
511
+ [ -z "$selected" ] || return 1
512
+ selected=$candidate
513
+ done <<EOF
514
+ $(fm_busy_muse_matching_logs "$root" "$ws")
515
+ EOF
516
+ [ -n "$selected" ] || return 1
517
+ namespace_after=$(fm_busy_muse_namespace_signature "$namespace_day") || return 1
518
+ [ "$namespace_before" = "$namespace_after" ] || return 1
519
+ fm_busy_muse_cache_session_log "$1" "$2" "$binding_id" "$selected" \
520
+ "$namespace_day" "$namespace_after" || return 1
521
+ printf '%s' "$selected"
522
+ }
523
+
524
+ fm_busy_muse_run_events() { # <session-log>
525
+ [ -f "$1" ] || return 1
526
+ LC_ALL=C awk '
527
+ BEGIN { OFS = "\t"; pre = "\"payload\":{\"kind\":\"run\",\"run_id\":\"" }
528
+ {
529
+ p = index($0, pre)
530
+ if (p == 0) next
531
+ rest = substr($0, p + length(pre))
532
+ q = index(rest, "\"")
533
+ if (q == 0) next
534
+ rid = substr(rest, 1, q - 1)
535
+ rest = substr(rest, q)
536
+ head = "\",\"event\":{\"kind\":\""
537
+ if (substr(rest, 1, length(head)) != head) next
538
+ rest = substr(rest, length(head) + 1)
539
+ q = index(rest, "\"")
540
+ if (q == 0) next
541
+ ev = substr(rest, 1, q - 1)
542
+ terminal = ""
543
+ if (ev == "terminal") {
544
+ marker = "\"terminal\":\""
545
+ p = index(rest, marker)
546
+ if (p != 0) {
547
+ value = substr(rest, p + length(marker))
548
+ q = index(value, "\"")
549
+ if (q != 0) terminal = substr(value, 1, q - 1)
550
+ }
551
+ }
552
+ if (ev == "started" || ev == "terminal") print rid, ev, terminal
553
+ }
554
+ ' "$1"
555
+ }
556
+
557
+ # fm_busy_muse_run_state: fold one session log to busy|settled|none.
558
+ # busy at least one run started with no matching terminal
559
+ # settled every started run reached a terminal
560
+ # none the log holds no run lifecycle records at all
561
+ # The match is anchored on the exact structural prefix rather than a bare
562
+ # "kind":"terminal" search, because muse also emits nested "record":{"kind":
563
+ # "terminal"} cleanup-effect payloads that are NOT run terminals and would
564
+ # otherwise close a run that is still in flight.
565
+ fm_busy_muse_run_state() { # <session-log>
566
+ [ -f "$1" ] || return 1
567
+ fm_busy_muse_run_events "$1" | LC_ALL=C awk -F '\t' '
568
+ $2 == "started" { open[$1] = 1; seen = 1 }
569
+ $2 == "terminal" { open[$1] = 0 }
570
+ END {
571
+ if (!seen) { print "none"; exit }
572
+ for (rid in open) if (open[rid] == 1) { print "busy"; exit }
573
+ print "settled"
574
+ }
575
+ '
576
+ }
577
+
578
+ fm_busy_muse_active_run_id() { # <session-log>
579
+ [ -f "$1" ] || return 1
580
+ fm_busy_muse_run_events "$1" | LC_ALL=C awk -F '\t' '
581
+ $2 == "started" { open[$1] = 1 }
582
+ $2 == "terminal" { open[$1] = 0 }
583
+ END {
584
+ for (rid in open) {
585
+ if (open[rid] != 1) continue
586
+ active = rid
587
+ count++
588
+ }
589
+ if (count != 1) exit 1
590
+ print active
591
+ }
592
+ '
593
+ }
594
+
595
+ fm_busy_muse_run_terminal() { # <session-log> <run-id>
596
+ [ -f "$1" ] && [ -n "${2:-}" ] || return 1
597
+ fm_busy_muse_run_events "$1" | LC_ALL=C awk -F '\t' -v wanted="$2" '
598
+ $1 == wanted && $2 == "terminal" && $3 != "" { terminal = $3 }
599
+ END {
600
+ if (terminal == "") exit 1
601
+ print terminal
602
+ }
603
+ '
604
+ }
605
+
606
+ # cursor conversation-transcript busy source
607
+ #
608
+ # cursor-agent persists an append-only JSONL transcript per conversation at
609
+ # <projects-root>/<workspace-slug>/agent-transcripts/<conversation-id>/<id>.jsonl
610
+ # and brackets every submitted turn. Verified live on cursor-agent
611
+ # 2026.08.11-e8db854:
612
+ # {"role":"user", ...} <- turn opens
613
+ # {"role":"assistant", ...} <- work
614
+ # {"type":"turn_ended","status":"success"} <- turn closes
615
+ # An Escape interrupt closes the turn with status "aborted", so like muse's
616
+ # session log - and unlike Claude's Stop hook - this source covers the manual
617
+ # interrupt path. Nothing is installed and no trust grant is needed: cursor
618
+ # writes this transcript on its own.
619
+ #
620
+ # Resolution deliberately does NOT reconstruct cursor's workspace-slug directory
621
+ # name. That slug is a lossy transformation of the workspace path (separators
622
+ # collapse), so rebuilding it would be a guess that silently binds the wrong
623
+ # pane. cursor writes the exact absolute path into each project directory's
624
+ # .workspace-trusted, so the binding matches on that recorded value instead.
625
+ #
626
+ # fm_busy_cursor_binding_path: the per-task sidecar fm-spawn writes. It records
627
+ # projects_root=<abs>, workspace_root=<abs>, and one prior_conversation=<id> for
628
+ # each conversation that already existed for that workspace when this pane
629
+ # launched, so a relaunched task cannot fold its predecessor's transcript.
630
+ fm_busy_cursor_binding_path() { # <state-dir> <id>
631
+ printf '%s/%s.cursor-session' "$1" "$2"
632
+ }
633
+
634
+ fm_busy_cursor_binding_field() { # <state-dir> <id> <key>
635
+ local path value
636
+ path=$(fm_busy_cursor_binding_path "$1" "$2")
637
+ [ -f "$path" ] || return 1
638
+ value=$(LC_ALL=C awk -F= -v k="$3" '$1 == k { sub(/^[^=]*=/, ""); print; exit }' "$path")
639
+ [ -n "$value" ] || return 1
640
+ printf '%s' "$value"
641
+ }
642
+
643
+ # fm_busy_cursor_project_dir: the project directory whose recorded
644
+ # .workspace-trusted workspacePath is exactly <workspace-root>. Exact-match
645
+ # only: a prefix or slug comparison would bind a nested worktree to its parent.
646
+ fm_busy_cursor_project_dir() { # <projects-root> <workspace-root>
647
+ local root=$1 want=$2 marker dir path
648
+ [ -d "$root" ] || return 1
649
+ for marker in "$root"/*/.workspace-trusted; do
650
+ [ -f "$marker" ] || continue
651
+ path=$(LC_ALL=C sed -n 's/.*"workspacePath"[[:space:]]*:[[:space:]]*"\(.*\)".*/\1/p' "$marker" | head -1)
652
+ [ -n "$path" ] || continue
653
+ [ "$path" = "$want" ] || continue
654
+ dir=${marker%/.workspace-trusted}
655
+ printf '%s' "$dir"
656
+ return 0
657
+ done
658
+ return 1
659
+ }
660
+
661
+ # fm_busy_cursor_transcript: the ONE transcript this pane owns, or failure.
662
+ # A conversation recorded as prior_conversation is excluded, so a relaunch in a
663
+ # reused worktree folds its own turn rather than the previous pane's. Requiring
664
+ # a UNIQUE remaining conversation is what keeps the binding honest: zero means
665
+ # no turn has been submitted yet and several means the pane cannot be told
666
+ # apart, and neither proves anything about the current turn.
667
+ fm_busy_cursor_transcript() { # <state-dir> <id>
668
+ local root workspace project dir conv found='' count=0 prior
669
+ root=$(fm_busy_cursor_binding_field "$1" "$2" projects_root) || return 1
670
+ workspace=$(fm_busy_cursor_binding_field "$1" "$2" workspace_root) || return 1
671
+ project=$(fm_busy_cursor_project_dir "$root" "$workspace") || return 1
672
+ prior=$(LC_ALL=C awk -F= '$1 == "prior_conversation" { sub(/^[^=]*=/, ""); print }' \
673
+ "$(fm_busy_cursor_binding_path "$1" "$2")" 2>/dev/null)
674
+ for dir in "$project"/agent-transcripts/*/; do
675
+ [ -d "$dir" ] || continue
676
+ conv=$(basename -- "${dir%/}")
677
+ printf '%s\n' "$prior" | grep -Fqx "$conv" && continue
678
+ [ -f "$dir$conv.jsonl" ] || continue
679
+ found="$dir$conv.jsonl"
680
+ count=$((count + 1))
681
+ done
682
+ [ "$count" = 1 ] && [ -n "$found" ] || return 1
683
+ printf '%s' "$found"
684
+ }
685
+
686
+ # fm_busy_cursor_turn_state: fold the transcript into busy | settled | none.
687
+ # Lifecycle records are matched on top-level fields of structurally valid JSON,
688
+ # so a turn whose own text mentions turn_ended cannot close it.
689
+ fm_busy_cursor_turn_state() { # <transcript>
690
+ [ -f "$1" ] || return 1
691
+ if command -v jq >/dev/null 2>&1; then
692
+ LC_ALL=C jq -Rr '
693
+ try (
694
+ fromjson
695
+ | if type == "object" and .type? == "turn_ended" then "close"
696
+ elif type == "object" and .role? == "user" then "open"
697
+ else "other"
698
+ end
699
+ ) catch "malformed"
700
+ ' "$1"
701
+ else
702
+ LC_ALL=C awk '
703
+ function ws( c) {
704
+ while (p <= n) {
705
+ c = substr(line, p, 1)
706
+ if (c != " " && c != "\t" && c != "\r") break
707
+ p++
708
+ }
709
+ }
710
+ function hex(c) {
711
+ if (c >= "0" && c <= "9") return c + 0
712
+ c = tolower(c)
713
+ return index("abcdef", c) + 9
714
+ }
715
+ function string( c, e, h, i, code, out) {
716
+ if (substr(line, p, 1) != "\"") return 0
717
+ p++; out = ""
718
+ while (p <= n) {
719
+ c = substr(line, p++, 1)
720
+ if (c == "\"") { value = out; kind = "string"; return 1 }
721
+ if (c ~ /[[:cntrl:]]/) return 0
722
+ if (c != "\\") { out = out c; continue }
723
+ if (p > n) return 0
724
+ e = substr(line, p++, 1)
725
+ if (e == "\"" || e == "\\" || e == "/") out = out e
726
+ else if (e ~ /^[bfnrt]$/) out = out "?"
727
+ else if (e == "u") {
728
+ h = substr(line, p, 4)
729
+ if (length(h) != 4 || h !~ /^[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]$/) return 0
730
+ code = 0
731
+ for (i = 1; i <= 4; i++) code = code * 16 + hex(substr(h, i, 1))
732
+ out = out (code < 128 ? sprintf("%c", code) : "?")
733
+ p += 4
734
+ } else return 0
735
+ }
736
+ return 0
737
+ }
738
+ function number( c) {
739
+ if (substr(line, p, 1) == "-") p++
740
+ c = substr(line, p, 1)
741
+ if (c == "0") {
742
+ p++
743
+ if (substr(line, p, 1) ~ /^[0-9]$/) return 0
744
+ } else if (c ~ /^[1-9]$/) {
745
+ do { p++; c = substr(line, p, 1) } while (c ~ /^[0-9]$/)
746
+ } else return 0
747
+ if (substr(line, p, 1) == ".") {
748
+ p++
749
+ if (substr(line, p, 1) !~ /^[0-9]$/) return 0
750
+ while (substr(line, p, 1) ~ /^[0-9]$/) p++
751
+ }
752
+ c = substr(line, p, 1)
753
+ if (c == "e" || c == "E") {
754
+ p++; c = substr(line, p, 1)
755
+ if (c == "+" || c == "-") p++
756
+ if (substr(line, p, 1) !~ /^[0-9]$/) return 0
757
+ while (substr(line, p, 1) ~ /^[0-9]$/) p++
758
+ }
759
+ kind = "number"; value = ""
760
+ return 1
761
+ }
762
+ function array(depth, c) {
763
+ p++; ws()
764
+ if (substr(line, p, 1) == "]") { p++; return 1 }
765
+ while (p <= n) {
766
+ if (!json(depth + 1)) return 0
767
+ ws(); c = substr(line, p, 1)
768
+ if (c == "]") { p++; return 1 }
769
+ if (c != ",") return 0
770
+ p++; ws()
771
+ }
772
+ return 0
773
+ }
774
+ function object(depth, c, key, vkind, vvalue, is_close, is_open) {
775
+ p++; ws()
776
+ if (substr(line, p, 1) == "}") { p++; kind = "object"; return 1 }
777
+ while (p <= n) {
778
+ if (!string()) return 0
779
+ key = value; ws()
780
+ if (substr(line, p, 1) != ":") return 0
781
+ p++; ws()
782
+ if (!json(depth + 1)) return 0
783
+ vkind = kind; vvalue = value
784
+ if (depth == 0 && key == "type") is_close = (vkind == "string" && vvalue == "turn_ended")
785
+ if (depth == 0 && key == "role") is_open = (vkind == "string" && vvalue == "user")
786
+ ws(); c = substr(line, p, 1)
787
+ if (c == "}") {
788
+ p++; kind = "object"; value = ""
789
+ if (depth == 0) event = (is_close ? "close" : (is_open ? "open" : "other"))
790
+ return 1
791
+ }
792
+ if (c != ",") return 0
793
+ p++; ws()
794
+ }
795
+ return 0
796
+ }
797
+ function json(depth, c, word) {
798
+ ws(); c = substr(line, p, 1)
799
+ if (c == "\"") return string()
800
+ if (c == "{") return object(depth)
801
+ if (c == "[") { kind = "array"; value = ""; return array(depth) }
802
+ if (c == "-" || c ~ /^[0-9]$/) return number()
803
+ word = substr(line, p)
804
+ if (substr(word, 1, 4) == "true" || substr(word, 1, 4) == "null") { p += 4; kind = "literal"; value = ""; return 1 }
805
+ if (substr(word, 1, 5) == "false") { p += 5; kind = "literal"; value = ""; return 1 }
806
+ return 0
807
+ }
808
+ {
809
+ line = $0; p = 1; n = length(line); event = "other"; kind = ""; value = ""
810
+ valid = json(0); ws()
811
+ print (valid && p > n ? event : "malformed")
812
+ }
813
+ ' "$1"
814
+ fi | LC_ALL=C awk '
815
+ $0 == "close" { open = 0; seen = 1; malformed = 0; next }
816
+ $0 == "open" { open = 1; seen = 1; next }
817
+ $0 == "malformed" { if (!open) malformed = 1; next }
818
+ END {
819
+ if (!seen || (!open && malformed)) { print "none"; exit }
820
+ print (open ? "busy" : "settled")
821
+ }
822
+ '
823
+ }
824
+
825
+ # fm_busy_grok_tail_busy: the Grok-only temporary rendered-tail fallback.
826
+ # Consumes the tail on stdin; 0 when Grok's verified busy signature matches.
827
+ # FM_BUSY_REGEX still globally overrides the signature, mirroring the
828
+ # historical operator escape hatch.
829
+ fm_busy_grok_tail_busy() {
830
+ grep -v '^[[:space:]]*$' | tail -12 \
831
+ | grep -qiE "${FM_BUSY_REGEX:-${FM_DELIVERY_GROK_BUSY_REGEX_DEFAULT:-Ctrl\\+c:cancel}}"
832
+ }
833
+
834
+ # fm_busy_classify: semantic classification for a task whose endpoint the
835
+ # caller has already established as present. Prints "<verdict> <source>":
836
+ # busy|idle|unknown plus the producing source (see header). Never probes
837
+ # process state. <tail40> is optional pre-captured plain output used only by
838
+ # the Grok arm; when absent the Grok arm captures through fm_backend_capture
839
+ # if available, else reports unknown capture-failed.
840
+ fm_busy_classify() { # <backend> <target> <harness> <id> <state-dir> [tail40]
841
+ local backend=$1 target=$2 harness=$3 id=$4 state=$5 tail40=${6-}
842
+ local out rc r_state r_source native log
843
+ case "$harness" in
844
+ kimi*)
845
+ if ! fm_busy_kimi_verified; then
846
+ printf 'unknown kimi-unverified'
847
+ return 0
848
+ fi
849
+ ;;
850
+ codex*)
851
+ if ! fm_busy_codex_semantic_source; then
852
+ printf 'unknown codex-unverified'
853
+ return 0
854
+ fi
855
+ ;;
856
+ cursor*)
857
+ # Semantic, on demand: fold this task's bound conversation transcript. A
858
+ # turn open past its last close is positive proof of a turn in flight and
859
+ # a trailing turn_ended is a finished turn. Every other outcome - no
860
+ # sidecar, no resolvable transcript, an unreadable or record-free file -
861
+ # is unknown, never idle. The rendered `ctrl+c to stop` footer is
862
+ # deliberately NOT consulted here; see the source note above.
863
+ if ! log=$(fm_busy_cursor_transcript "$state" "$id"); then
864
+ printf 'unknown cursor-transcript'
865
+ return 0
866
+ fi
867
+ case "$(fm_busy_cursor_turn_state "$log" 2>/dev/null)" in
868
+ busy) printf 'busy cursor-transcript' ;;
869
+ settled) printf 'idle cursor-transcript' ;;
870
+ *) printf 'unknown cursor-transcript' ;;
871
+ esac
872
+ return 0
873
+ ;;
874
+ esac
875
+ out=$(fm_busy_record_read "$state" "$id") && rc=0 || rc=$?
876
+ if [ "$rc" = 0 ]; then
877
+ r_state=${out%% *}
878
+ out=${out#* }
879
+ r_source=${out%% *}
880
+ if fm_busy_source_trusted "$harness" "$r_source"; then
881
+ printf '%s %s' "$r_state" "$r_source"
882
+ else
883
+ printf 'unknown source-mismatch'
884
+ fi
885
+ return 0
886
+ fi
887
+ case "$out" in
888
+ malformed|gen-mismatch)
889
+ printf 'unknown %s' "$out"
890
+ return 0
891
+ ;;
892
+ esac
893
+ # No record at all. A native herdr busy verdict is semantic enough to trust
894
+ # for BUSY (streaming means a turn is running); native idle is narrower
895
+ # than turn state (a long foreground tool call reads idle) and stays
896
+ # unknown here.
897
+ if [ "$backend" = herdr ] && command -v fm_backend_busy_state >/dev/null 2>&1; then
898
+ native=$(fm_backend_busy_state "$backend" "$target" 2>/dev/null || true)
899
+ if [ "$native" = busy ]; then
900
+ printf 'busy herdr-native'
901
+ return 0
902
+ fi
903
+ fi
904
+ case "$harness" in
905
+ muse*)
906
+ # Semantic, on demand: fold this task's bound session log. An open run is
907
+ # positive proof of a turn in flight and a settled log is a finished turn.
908
+ # Every other outcome - no sidecar, no matching log, an unreadable or
909
+ # run-free log - is unknown, never idle.
910
+ if ! log=$(fm_busy_muse_session_log "$state" "$id"); then
911
+ printf 'unknown muse-session-log'
912
+ return 0
913
+ fi
914
+ case "$(fm_busy_muse_run_state "$log" 2>/dev/null)" in
915
+ busy) printf 'busy muse-session-log' ;;
916
+ settled) printf 'idle muse-session-log' ;;
917
+ *) printf 'unknown muse-session-log' ;;
918
+ esac
919
+ return 0
920
+ ;;
921
+ grok*)
922
+ if [ -z "$tail40" ]; then
923
+ if command -v fm_backend_capture >/dev/null 2>&1; then
924
+ tail40=$(fm_backend_capture "$backend" "$target" 40 2>/dev/null) || {
925
+ printf 'unknown capture-failed'
926
+ return 0
927
+ }
928
+ else
929
+ printf 'unknown capture-failed'
930
+ return 0
931
+ fi
932
+ fi
933
+ if printf '%s' "$tail40" | fm_busy_grok_tail_busy; then
934
+ printf 'busy grok-regex'
935
+ else
936
+ printf 'idle grok-regex'
937
+ fi
938
+ return 0
939
+ ;;
940
+ esac
941
+ printf 'unknown missing'
942
+ }
943
+
944
+ # fm_busy_classify_live: fm_busy_classify behind the one process-level
945
+ # override - a gone endpoint is dead, never busy. Requires fm-backend.sh to
946
+ # be sourced for fm_backend_target_exists.
947
+ fm_busy_classify_live() { # <backend> <target> <harness> <id> <state-dir> [expected-label]
948
+ local backend=$1 target=$2 harness=$3 id=$4 state=$5 label=${6-}
949
+ if [ -z "$target" ]; then
950
+ printf 'unknown no-target'
951
+ return 0
952
+ fi
953
+ if ! fm_backend_target_exists "$backend" "$target" "$label" 2>/dev/null; then
954
+ printf 'dead endpoint-gone'
955
+ return 0
956
+ fi
957
+ fm_busy_classify "$backend" "$target" "$harness" "$id" "$state"
958
+ }
959
+
960
+ # fm_busy_classify_meta: classify a task from its recorded metadata, so every
961
+ # consumer resolves backend, target, and harness the same way instead of
962
+ # re-deriving them. Requires fm-backend.sh to be sourced. <tail40> is
963
+ # optional pre-captured plain output reused by the Grok arm.
964
+ fm_busy_classify_meta() { # <meta-file> <id> <state-dir> [tail40]
965
+ local meta=$1 id=$2 state=$3 tail40=${4-} backend target harness
966
+ [ -f "$meta" ] || { printf 'unknown missing'; return 0; }
967
+ backend=$(fm_backend_of_meta "$meta")
968
+ target=$(fm_backend_target_of_meta "$meta")
969
+ harness=$(fm_meta_get "$meta" harness)
970
+ if [ -z "$target" ]; then
971
+ printf 'unknown no-target'
972
+ return 0
973
+ fi
974
+ fm_busy_classify "$backend" "$target" "$harness" "$id" "$state" "$tail40"
975
+ }
976
+
977
+ # fm_busy_is_busy: boolean view for callers that only gate on provable
978
+ # activity. 0 iff the classification verdict is exactly busy; idle, unknown,
979
+ # and dead all return 1, so an unknown can never be silently promoted to
980
+ # either boolean pole - callers that must distinguish idle from unknown read
981
+ # the full classification instead.
982
+ fm_busy_is_busy() { # <backend> <target> <harness> <id> <state-dir> [tail40]
983
+ local verdict
984
+ verdict=$(fm_busy_classify "$@")
985
+ [ "${verdict%% *}" = busy ]
986
+ }