@zerwiz/ymir 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (850) hide show
  1. package/.agents/README.md +291 -0
  2. package/.agents/agents/bragi-marketer.md +111 -0
  3. package/.agents/agents/brokk.md +352 -0
  4. package/.agents/agents/forseti-reviewer.md +93 -0
  5. package/.agents/agents/frigg-consent.md +77 -0
  6. package/.agents/agents/groa-update.md +77 -0
  7. package/.agents/agents/hnoss-designer.md +69 -0
  8. package/.agents/agents/huginn-researcher.md +99 -0
  9. package/.agents/agents/jord-projects.md +78 -0
  10. package/.agents/agents/kvasir-scout.md +91 -0
  11. package/.agents/agents/mimir-planner.md +92 -0
  12. package/.agents/agents/muninn-stow.md +77 -0
  13. package/.agents/agents/saga-bearings.md +78 -0
  14. package/.agents/agents/sindri-developer.md +114 -0
  15. package/.agents/agents/snotra-documenter.md +105 -0
  16. package/.agents/agents/syn-recovery.md +78 -0
  17. package/.agents/agents/tyr-check.md +78 -0
  18. package/.agents/agents/urdh-hold.md +77 -0
  19. package/.agents/agents/volundr-smith.md +80 -0
  20. package/.agents/agents/vor-diagnostics.md +77 -0
  21. package/.agents/assets/agents/naming.md +50 -0
  22. package/.agents/assets/agents/registry.md +93 -0
  23. package/.agents/assets/agents/runtime.md +36 -0
  24. package/.agents/assets/agents/toon-tasks-cli.md +42 -0
  25. package/.agents/assets/templates/company_entity.template.md +37 -0
  26. package/.agents/backend/backends/cmux.sh +649 -0
  27. package/.agents/backend/backends/herdr-eventwait.py +157 -0
  28. package/.agents/backend/backends/herdr-workspace-move.py +114 -0
  29. package/.agents/backend/backends/herdr.sh +3386 -0
  30. package/.agents/backend/backends/orca.sh +290 -0
  31. package/.agents/backend/backends/tmux.sh +357 -0
  32. package/.agents/backend/backends/zellij.sh +700 -0
  33. package/.agents/backend/fm-afk-launch.sh +654 -0
  34. package/.agents/backend/fm-afk-return.sh +241 -0
  35. package/.agents/backend/fm-afk-start.sh +172 -0
  36. package/.agents/backend/fm-arm-command-policy.mjs +960 -0
  37. package/.agents/backend/fm-arm-pretool-check.sh +200 -0
  38. package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
  39. package/.agents/backend/fm-backend.sh +981 -0
  40. package/.agents/backend/fm-backlog-handoff.sh +909 -0
  41. package/.agents/backend/fm-backlog-receive.sh +187 -0
  42. package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
  43. package/.agents/backend/fm-bearings-board.sh +204 -0
  44. package/.agents/backend/fm-bearings-snapshot.sh +546 -0
  45. package/.agents/backend/fm-bootstrap.sh +1568 -0
  46. package/.agents/backend/fm-branch-outcome.sh +203 -0
  47. package/.agents/backend/fm-branch-prompt.sh +101 -0
  48. package/.agents/backend/fm-brief.sh +452 -0
  49. package/.agents/backend/fm-busy-event.sh +234 -0
  50. package/.agents/backend/fm-busy-lib.sh +986 -0
  51. package/.agents/backend/fm-captain-hold.sh +1001 -0
  52. package/.agents/backend/fm-cd-command-policy.mjs +153 -0
  53. package/.agents/backend/fm-cd-pretool-check.sh +190 -0
  54. package/.agents/backend/fm-check-lib.sh +74 -0
  55. package/.agents/backend/fm-check-register.sh +42 -0
  56. package/.agents/backend/fm-check-unregister.sh +52 -0
  57. package/.agents/backend/fm-classify-lib.sh +1770 -0
  58. package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
  59. package/.agents/backend/fm-composer-lib.sh +1416 -0
  60. package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
  61. package/.agents/backend/fm-config-push.sh +254 -0
  62. package/.agents/backend/fm-control-lib.sh +251 -0
  63. package/.agents/backend/fm-control.sh +878 -0
  64. package/.agents/backend/fm-crew-state.sh +625 -0
  65. package/.agents/backend/fm-cursor-lib.sh +243 -0
  66. package/.agents/backend/fm-decision-hold.sh +232 -0
  67. package/.agents/backend/fm-doc-audience-check.sh +269 -0
  68. package/.agents/backend/fm-dod-lib.sh +67 -0
  69. package/.agents/backend/fm-ensure-agents-md.sh +253 -0
  70. package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
  71. package/.agents/backend/fm-extension.mjs +2577 -0
  72. package/.agents/backend/fm-extension.sh +16 -0
  73. package/.agents/backend/fm-ff-lib.sh +420 -0
  74. package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
  75. package/.agents/backend/fm-fleet-sync.sh +458 -0
  76. package/.agents/backend/fm-fleet-view.sh +96 -0
  77. package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
  78. package/.agents/backend/fm-guard.sh +242 -0
  79. package/.agents/backend/fm-harness.sh +195 -0
  80. package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
  81. package/.agents/backend/fm-herdr-lab.sh +346 -0
  82. package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
  83. package/.agents/backend/fm-home-seed.sh +984 -0
  84. package/.agents/backend/fm-home-summary-refresh.sh +255 -0
  85. package/.agents/backend/fm-hook-host-lib.sh +36 -0
  86. package/.agents/backend/fm-inactive-reconcile.sh +531 -0
  87. package/.agents/backend/fm-inbox.sh +399 -0
  88. package/.agents/backend/fm-install-actionlint.sh +84 -0
  89. package/.agents/backend/fm-install-herdr.sh +96 -0
  90. package/.agents/backend/fm-install-shellcheck.sh +84 -0
  91. package/.agents/backend/fm-install-treehouse.sh +95 -0
  92. package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
  93. package/.agents/backend/fm-lease-lib.sh +218 -0
  94. package/.agents/backend/fm-lease.sh +189 -0
  95. package/.agents/backend/fm-line-cap-lib.sh +51 -0
  96. package/.agents/backend/fm-lint-workflows.sh +137 -0
  97. package/.agents/backend/fm-lint.sh +591 -0
  98. package/.agents/backend/fm-lock-lib.sh +104 -0
  99. package/.agents/backend/fm-lock.sh +107 -0
  100. package/.agents/backend/fm-marker-lib.sh +12 -0
  101. package/.agents/backend/fm-merge-local.sh +74 -0
  102. package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
  103. package/.agents/backend/fm-nm-run-lib.sh +124 -0
  104. package/.agents/backend/fm-on.sh +125 -0
  105. package/.agents/backend/fm-operational-input.sh +253 -0
  106. package/.agents/backend/fm-peek.sh +46 -0
  107. package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
  108. package/.agents/backend/fm-pr-check.sh +135 -0
  109. package/.agents/backend/fm-pr-lib.sh +1016 -0
  110. package/.agents/backend/fm-pr-merge.sh +707 -0
  111. package/.agents/backend/fm-pr-poll.sh +110 -0
  112. package/.agents/backend/fm-primary-scope-lib.sh +33 -0
  113. package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
  114. package/.agents/backend/fm-procevent-lavish.sh +628 -0
  115. package/.agents/backend/fm-procevent-lib.sh +892 -0
  116. package/.agents/backend/fm-procevent-quota.sh +290 -0
  117. package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
  118. package/.agents/backend/fm-procevent-when.sh +504 -0
  119. package/.agents/backend/fm-procevent.sh +1466 -0
  120. package/.agents/backend/fm-project-mode.sh +92 -0
  121. package/.agents/backend/fm-project-origin-lib.sh +180 -0
  122. package/.agents/backend/fm-promote.sh +282 -0
  123. package/.agents/backend/fm-public-followup-emit.sh +260 -0
  124. package/.agents/backend/fm-public-followup-lib.sh +398 -0
  125. package/.agents/backend/fm-public-followup.sh +1330 -0
  126. package/.agents/backend/fm-push-transition-lib.sh +168 -0
  127. package/.agents/backend/fm-quota-axi-lib.sh +93 -0
  128. package/.agents/backend/fm-quota-choose.sh +384 -0
  129. package/.agents/backend/fm-remote-delta-read.sh +189 -0
  130. package/.agents/backend/fm-remote-doctor.sh +800 -0
  131. package/.agents/backend/fm-remote-entrypoint.sh +193 -0
  132. package/.agents/backend/fm-remote-file.sh +234 -0
  133. package/.agents/backend/fm-remote-home-provision.sh +259 -0
  134. package/.agents/backend/fm-remote-home-seed.sh +259 -0
  135. package/.agents/backend/fm-remote-inherit-push.sh +90 -0
  136. package/.agents/backend/fm-remote-inherit.sh +179 -0
  137. package/.agents/backend/fm-remote-job-lib.sh +1241 -0
  138. package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
  139. package/.agents/backend/fm-remote-job-worker.sh +1124 -0
  140. package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
  141. package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
  142. package/.agents/backend/fm-review-diff.sh +158 -0
  143. package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
  144. package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
  145. package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
  146. package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
  147. package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
  148. package/.agents/backend/fm-secondmate-report.sh +88 -0
  149. package/.agents/backend/fm-send.sh +1059 -0
  150. package/.agents/backend/fm-session-lock-lib.sh +176 -0
  151. package/.agents/backend/fm-session-start.sh +970 -0
  152. package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
  153. package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
  154. package/.agents/backend/fm-sessionstart-run.sh +145 -0
  155. package/.agents/backend/fm-spawn.sh +3153 -0
  156. package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
  157. package/.agents/backend/fm-startup-memory-budget.sh +94 -0
  158. package/.agents/backend/fm-startup-network.sh +642 -0
  159. package/.agents/backend/fm-stow-cascade.sh +251 -0
  160. package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
  161. package/.agents/backend/fm-supervise-daemon.sh +1732 -0
  162. package/.agents/backend/fm-supervision-instructions.sh +215 -0
  163. package/.agents/backend/fm-supervision-lib.sh +90 -0
  164. package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
  165. package/.agents/backend/fm-tangle-lib.sh +53 -0
  166. package/.agents/backend/fm-task-inbox-lib.sh +402 -0
  167. package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
  168. package/.agents/backend/fm-teardown.sh +2922 -0
  169. package/.agents/backend/fm-test-isolation-proof.sh +588 -0
  170. package/.agents/backend/fm-test-run.sh +2191 -0
  171. package/.agents/backend/fm-timeout-lib.sh +141 -0
  172. package/.agents/backend/fm-timing-lib.sh +179 -0
  173. package/.agents/backend/fm-tmux-lib.sh +291 -0
  174. package/.agents/backend/fm-tool-update-check.sh +898 -0
  175. package/.agents/backend/fm-trace-context-lib.sh +227 -0
  176. package/.agents/backend/fm-transition-lib.sh +103 -0
  177. package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
  178. package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
  179. package/.agents/backend/fm-turnend-guard.sh +432 -0
  180. package/.agents/backend/fm-update.sh +110 -0
  181. package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
  182. package/.agents/backend/fm-voice-client.py +1373 -0
  183. package/.agents/backend/fm-voice-relay.py +1256 -0
  184. package/.agents/backend/fm-wake-drain.sh +592 -0
  185. package/.agents/backend/fm-wake-grant.sh +127 -0
  186. package/.agents/backend/fm-wake-lib.sh +1849 -0
  187. package/.agents/backend/fm-watch-arm.sh +602 -0
  188. package/.agents/backend/fm-watch-checkpoint.sh +109 -0
  189. package/.agents/backend/fm-watch.sh +1962 -0
  190. package/.agents/backend/fm-x-dismiss.sh +116 -0
  191. package/.agents/backend/fm-x-followup.sh +286 -0
  192. package/.agents/backend/fm-x-lib.sh +1000 -0
  193. package/.agents/backend/fm-x-link.sh +234 -0
  194. package/.agents/backend/fm-x-poll.sh +185 -0
  195. package/.agents/backend/fm-x-reply.sh +385 -0
  196. package/.agents/backend/fm_voice_frame.py +166 -0
  197. package/.agents/backend/fm_voice_records.py +574 -0
  198. package/.agents/backend/model-bridge.py +290 -0
  199. package/.agents/backend/opencode-go-bridge.py +30 -0
  200. package/.agents/bus/README.md +12 -0
  201. package/.agents/bus/messages.json +1 -0
  202. package/.agents/config/agents.machine.example.yaml +16 -0
  203. package/.agents/config/agents.yaml +50 -0
  204. package/.agents/config/agents.yaml.example +72 -0
  205. package/.agents/config/crew-dispatch.json +22 -0
  206. package/.agents/config/cron.yaml +9 -0
  207. package/.agents/config/eindri-dispatch.json +67 -0
  208. package/.agents/config/eindri-harness +1 -0
  209. package/.agents/config/startup-memory-budget +1 -0
  210. package/.agents/config/tailscale-sync.yaml +27 -0
  211. package/.agents/filebrowser/.filebrowser.json +6 -0
  212. package/.agents/filebrowser/README.md +11 -0
  213. package/.agents/gateway/README.md +12 -0
  214. package/.agents/github/README.md +15 -0
  215. package/.agents/github/webhooks/.gitkeep +0 -0
  216. package/.agents/github/workflows/.gitkeep +0 -0
  217. package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
  218. package/.agents/harness/opencode/plugins/package.json +4 -0
  219. package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
  220. package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
  221. package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
  222. package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
  223. package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
  224. package/.agents/memory/README.md +46 -0
  225. package/.agents/memory/kaia.engram +0 -0
  226. package/.agents/memory/kaia.engram-shm +0 -0
  227. package/.agents/memory/kaia.engram-wal +0 -0
  228. package/.agents/memory/runes_audit.md +5 -0
  229. package/.agents/memory/well/episodes.jsonl +367 -0
  230. package/.agents/memory/well/workspace.jsonl +27 -0
  231. package/.agents/migrations/0001-hodd-layout.sh +38 -0
  232. package/.agents/migrations/0002-a2a-mcp.sh +27 -0
  233. package/.agents/migrations/0003-private-data-separation.sh +111 -0
  234. package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
  235. package/.agents/sandbox/Dockerfile.utgard +19 -0
  236. package/.agents/sandbox/README.md +14 -0
  237. package/.agents/sandbox/utgard.config.json +14 -0
  238. package/.agents/skills/README.md +59 -0
  239. package/.agents/skills/bragi-marketing/README.md +3 -0
  240. package/.agents/skills/bragi-marketing/SKILL.md +82 -0
  241. package/.agents/skills/eindri-homes/README.md +3 -0
  242. package/.agents/skills/eindri-homes/SKILL.md +291 -0
  243. package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
  244. package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
  245. package/.agents/skills/frigg-consent/README.md +3 -0
  246. package/.agents/skills/frigg-consent/SKILL.md +56 -0
  247. package/.agents/skills/galdr-ymirsystem/README.md +3 -0
  248. package/.agents/skills/galdr-ymirsystem/SKILL.md +158 -0
  249. package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
  250. package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
  251. package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
  252. package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
  253. package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
  254. package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
  255. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +673 -0
  256. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
  257. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
  258. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
  259. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
  260. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
  261. package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +496 -0
  262. package/.agents/skills/galdr-ymirsystem/assets/installation.md +571 -0
  263. package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
  264. package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
  265. package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +161 -0
  266. package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +377 -0
  267. package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
  268. package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +129 -0
  269. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
  270. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
  271. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +96 -0
  272. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
  273. package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
  274. package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
  275. package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
  276. package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
  277. package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
  278. package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
  279. package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
  280. package/.agents/skills/galdr-ymirsystem/assets/smidja.md +175 -0
  281. package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
  282. package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
  283. package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
  284. package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +360 -0
  285. package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
  286. package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
  287. package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
  288. package/.agents/skills/gjallarhorn-relay/README.md +3 -0
  289. package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
  290. package/.agents/skills/groa-update/SKILL.md +70 -0
  291. package/.agents/skills/hamr-adapters/README.md +3 -0
  292. package/.agents/skills/hamr-adapters/SKILL.md +123 -0
  293. package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
  294. package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
  295. package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
  296. package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
  297. package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
  298. package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
  299. package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
  300. package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
  301. package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
  302. package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
  303. package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
  304. package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
  305. package/.agents/skills/herdr-panes/README.md +3 -0
  306. package/.agents/skills/herdr-panes/SKILL.md +92 -0
  307. package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
  308. package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
  309. package/.agents/skills/hnoss-design/DESIGN.md +19 -0
  310. package/.agents/skills/hnoss-design/README.md +3 -0
  311. package/.agents/skills/hnoss-design/SKILL.md +83 -0
  312. package/.agents/skills/hvild-afk/README.md +3 -0
  313. package/.agents/skills/hvild-afk/SKILL.md +241 -0
  314. package/.agents/skills/jord-projects/README.md +3 -0
  315. package/.agents/skills/jord-projects/SKILL.md +92 -0
  316. package/.agents/skills/lifecycle/SKILL.md +56 -0
  317. package/.agents/skills/lifecycle/smoke_test.sh +52 -0
  318. package/.agents/skills/lifecycle/start.sh +21 -0
  319. package/.agents/skills/lifecycle/status.sh +35 -0
  320. package/.agents/skills/lifecycle/stop.sh +16 -0
  321. package/.agents/skills/muninn-stow/README.md +3 -0
  322. package/.agents/skills/muninn-stow/SKILL.md +309 -0
  323. package/.agents/skills/no-mistakes/SKILL.md +379 -0
  324. package/.agents/skills/nornir-schedule/README.md +3 -0
  325. package/.agents/skills/nornir-schedule/SKILL.md +25 -0
  326. package/.agents/skills/nornir-schedule/assets/events.md +137 -0
  327. package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
  328. package/.agents/skills/nsr-compliance/README.md +3 -0
  329. package/.agents/skills/nsr-compliance/SKILL.md +29 -0
  330. package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
  331. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
  332. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
  333. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
  334. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
  335. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
  336. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
  337. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
  338. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
  339. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
  340. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
  341. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
  342. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
  343. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
  344. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
  345. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
  346. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
  347. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
  348. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
  349. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
  350. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
  351. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
  352. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
  353. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  354. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
  355. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
  356. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
  357. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
  358. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
  359. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
  360. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
  361. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
  362. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
  363. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
  364. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
  365. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
  366. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
  367. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
  368. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
  369. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
  370. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
  371. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
  372. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
  373. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
  374. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
  375. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
  376. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
  377. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
  378. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
  379. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
  380. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
  381. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
  382. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
  383. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
  384. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
  385. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
  386. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
  387. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
  388. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
  389. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
  390. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
  391. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
  392. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
  393. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
  394. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
  395. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
  396. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
  397. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
  398. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
  399. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
  400. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
  401. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
  402. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
  403. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
  404. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
  405. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
  406. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
  407. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
  408. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
  409. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
  410. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
  411. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
  412. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
  413. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
  414. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
  415. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
  416. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
  417. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
  418. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
  419. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
  420. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
  421. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
  422. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
  423. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
  424. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
  425. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
  426. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
  427. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
  428. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
  429. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
  430. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
  431. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
  432. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
  433. package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
  434. package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
  435. package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
  436. package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
  437. package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
  438. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
  439. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
  440. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
  441. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
  442. package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
  443. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
  444. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
  445. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
  446. package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
  447. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
  448. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
  449. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
  450. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
  451. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
  452. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
  453. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
  454. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  455. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
  456. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
  457. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
  458. package/.agents/skills/pr-ops/README.md +3 -0
  459. package/.agents/skills/pr-ops/SKILL.md +109 -0
  460. package/.agents/skills/ratatoskr-a2a/README.md +3 -0
  461. package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
  462. package/.agents/skills/rules-check-drift/SKILL.md +81 -0
  463. package/.agents/skills/saga-bearings/README.md +3 -0
  464. package/.agents/skills/saga-bearings/SKILL.md +26 -0
  465. package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
  466. package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
  467. package/.agents/skills/saga-bearings/assets/recap.md +56 -0
  468. package/.agents/skills/syn-recovery/README.md +3 -0
  469. package/.agents/skills/syn-recovery/SKILL.md +54 -0
  470. package/.agents/skills/tyr-check/README.md +3 -0
  471. package/.agents/skills/tyr-check/SKILL.md +218 -0
  472. package/.agents/skills/urdh-hold/README.md +3 -0
  473. package/.agents/skills/urdh-hold/SKILL.md +25 -0
  474. package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
  475. package/.agents/skills/urdh-hold/assets/hold.md +54 -0
  476. package/.agents/skills/vor-diagnostics/README.md +3 -0
  477. package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
  478. package/.agents/skills/ymir-host/README.md +3 -0
  479. package/.agents/skills/ymir-host/SKILL.md +34 -0
  480. package/.agents/skills/ymir-host/assets/desktop.md +36 -0
  481. package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
  482. package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
  483. package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
  484. package/.agents/skills/ymir-host/assets/update.md +64 -0
  485. package/.agents/tests/assets/board-render-harness.mjs +117 -0
  486. package/.agents/tests/cmux-test-safety.sh +51 -0
  487. package/.agents/tests/fixtures.sh +291 -0
  488. package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
  489. package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
  490. package/.agents/tests/fm-afk-launch.test.sh +961 -0
  491. package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
  492. package/.agents/tests/fm-afk-return.test.sh +283 -0
  493. package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
  494. package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
  495. package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
  496. package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
  497. package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
  498. package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
  499. package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
  500. package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
  501. package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
  502. package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
  503. package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
  504. package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
  505. package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
  506. package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
  507. package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
  508. package/.agents/tests/fm-backend-orca.test.sh +1357 -0
  509. package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
  510. package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
  511. package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
  512. package/.agents/tests/fm-backend.test.sh +1142 -0
  513. package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
  514. package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
  515. package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
  516. package/.agents/tests/fm-bearings-board.test.sh +414 -0
  517. package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
  518. package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
  519. package/.agents/tests/fm-bootstrap.test.sh +1178 -0
  520. package/.agents/tests/fm-branch-supervision.test.sh +552 -0
  521. package/.agents/tests/fm-brief.test.sh +785 -0
  522. package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
  523. package/.agents/tests/fm-busy-state.test.sh +465 -0
  524. package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
  525. package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
  526. package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
  527. package/.agents/tests/fm-check-unregister.test.sh +198 -0
  528. package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
  529. package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
  530. package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
  531. package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
  532. package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
  533. package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
  534. package/.agents/tests/fm-composer-ghost.test.sh +716 -0
  535. package/.agents/tests/fm-composer-lib.test.sh +666 -0
  536. package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
  537. package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
  538. package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
  539. package/.agents/tests/fm-control.test.sh +910 -0
  540. package/.agents/tests/fm-crew-state.test.sh +1610 -0
  541. package/.agents/tests/fm-cursor-harness.test.sh +404 -0
  542. package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
  543. package/.agents/tests/fm-cursor-primary.test.sh +708 -0
  544. package/.agents/tests/fm-daemon.test.sh +2740 -0
  545. package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
  546. package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
  547. package/.agents/tests/fm-extension-binding.test.sh +2187 -0
  548. package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
  549. package/.agents/tests/fm-fleet-sync.test.sh +721 -0
  550. package/.agents/tests/fm-gate-refuse.test.sh +356 -0
  551. package/.agents/tests/fm-gitignore-config.test.sh +89 -0
  552. package/.agents/tests/fm-gotmp.test.sh +230 -0
  553. package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
  554. package/.agents/tests/fm-grok-harness.test.sh +117 -0
  555. package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
  556. package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
  557. package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
  558. package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
  559. package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
  560. package/.agents/tests/fm-herdr-lab.test.sh +243 -0
  561. package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
  562. package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
  563. package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
  564. package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
  565. package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
  566. package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
  567. package/.agents/tests/fm-kimi-harness.test.sh +687 -0
  568. package/.agents/tests/fm-lint-workflows.test.sh +532 -0
  569. package/.agents/tests/fm-lint.test.sh +1023 -0
  570. package/.agents/tests/fm-muse-harness.test.sh +932 -0
  571. package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
  572. package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
  573. package/.agents/tests/fm-on.test.sh +507 -0
  574. package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
  575. package/.agents/tests/fm-operational-input.test.sh +160 -0
  576. package/.agents/tests/fm-peek-remote.test.sh +110 -0
  577. package/.agents/tests/fm-pending-reply.test.sh +1288 -0
  578. package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
  579. package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
  580. package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
  581. package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
  582. package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
  583. package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
  584. package/.agents/tests/fm-pr-merge.test.sh +2135 -0
  585. package/.agents/tests/fm-procevent-quota.test.sh +225 -0
  586. package/.agents/tests/fm-procevent-when.test.sh +406 -0
  587. package/.agents/tests/fm-procevent.test.sh +1745 -0
  588. package/.agents/tests/fm-project-origin.test.sh +110 -0
  589. package/.agents/tests/fm-public-followup.test.sh +2272 -0
  590. package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
  591. package/.agents/tests/fm-quota-choose.test.sh +626 -0
  592. package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
  593. package/.agents/tests/fm-remote-doctor.test.sh +625 -0
  594. package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
  595. package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
  596. package/.agents/tests/fm-remote-job.test.sh +768 -0
  597. package/.agents/tests/fm-remote-reply.test.sh +520 -0
  598. package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
  599. package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
  600. package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
  601. package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
  602. package/.agents/tests/fm-review-diff.test.sh +176 -0
  603. package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
  604. package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
  605. package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
  606. package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
  607. package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
  608. package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
  609. package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
  610. package/.agents/tests/fm-send-inbox.test.sh +352 -0
  611. package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
  612. package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
  613. package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
  614. package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
  615. package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
  616. package/.agents/tests/fm-send-settle.test.sh +147 -0
  617. package/.agents/tests/fm-send-strict.test.sh +241 -0
  618. package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
  619. package/.agents/tests/fm-session-start.test.sh +2515 -0
  620. package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
  621. package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
  622. package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
  623. package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
  624. package/.agents/tests/fm-spawn-batch.test.sh +148 -0
  625. package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
  626. package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
  627. package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
  628. package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
  629. package/.agents/tests/fm-startup-network.test.sh +712 -0
  630. package/.agents/tests/fm-stow-cascade.test.sh +370 -0
  631. package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
  632. package/.agents/tests/fm-supervision-events.test.sh +156 -0
  633. package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
  634. package/.agents/tests/fm-tangle-guard.test.sh +277 -0
  635. package/.agents/tests/fm-task-delivery.test.sh +420 -0
  636. package/.agents/tests/fm-task-inbox.test.sh +513 -0
  637. package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
  638. package/.agents/tests/fm-teardown.test.sh +2665 -0
  639. package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
  640. package/.agents/tests/fm-test-fixtures.test.sh +132 -0
  641. package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
  642. package/.agents/tests/fm-test-run.test.sh +1196 -0
  643. package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
  644. package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
  645. package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
  646. package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
  647. package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
  648. package/.agents/tests/fm-transition-lib.test.sh +54 -0
  649. package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
  650. package/.agents/tests/fm-update.test.sh +304 -0
  651. package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
  652. package/.agents/tests/fm-voice-relay.test.sh +4741 -0
  653. package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
  654. package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
  655. package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
  656. package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
  657. package/.agents/tests/fm-wake-queue.test.sh +1224 -0
  658. package/.agents/tests/fm-watch-arm.test.sh +821 -0
  659. package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
  660. package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
  661. package/.agents/tests/fm-watch-triage.test.sh +3889 -0
  662. package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
  663. package/.agents/tests/fm-x-mode.test.sh +3042 -0
  664. package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
  665. package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
  666. package/.agents/tests/herdr-test-safety.sh +42 -0
  667. package/.agents/tests/lib.sh +315 -0
  668. package/.agents/tests/remote-herdr-fixture.sh +125 -0
  669. package/.agents/tests/secondmate-helpers.sh +193 -0
  670. package/.agents/tests/smoke.test.sh +93 -0
  671. package/.agents/tests/wake-helpers.sh +338 -0
  672. package/.agents/tests/zellij-test-safety.sh +60 -0
  673. package/.agents/tools/README.md +10 -0
  674. package/.agents/tools/bin/tasks-cli +267 -0
  675. package/.agents/tools/tasks-cli.ts +216 -0
  676. package/AGENTS.md +454 -0
  677. package/CHANGELOG.md +881 -0
  678. package/LICENSE +1 -1
  679. package/NOTICE +63 -0
  680. package/README.md +113 -39
  681. package/RULES/01-domains.md +51 -0
  682. package/RULES/02-agents.md +78 -0
  683. package/RULES/03-houses.md +61 -0
  684. package/RULES/04-hoard.md +65 -0
  685. package/RULES/05-platforms.md +67 -0
  686. package/RULES/06-append-only.md +58 -0
  687. package/RULES/07-config.md +45 -0
  688. package/RULES/08-delivery-gate.md +75 -0
  689. package/RULES/README.md +20 -0
  690. package/bin/README.md +107 -0
  691. package/bin/a2a-mcp.sh +98 -0
  692. package/bin/a2a-serve.py +103 -0
  693. package/bin/a2a-serve.sh +8 -0
  694. package/bin/a2a-talk.sh +50 -0
  695. package/bin/a2abridge-ensure.sh +108 -0
  696. package/bin/agent-run.sh +43 -0
  697. package/bin/agents-config.sh +233 -0
  698. package/bin/apodex-smoke-test.sh +92 -0
  699. package/bin/bifrost-bridge.sh +112 -0
  700. package/bin/bifrost-ingress.sh +77 -0
  701. package/bin/bootstrap-macos.sh +107 -0
  702. package/bin/bootstrap-windows.ps1 +112 -0
  703. package/bin/branch-guard.sh +64 -0
  704. package/bin/brokk +73 -0
  705. package/bin/brokk-classify-lib.sh +1770 -0
  706. package/bin/brokk-lease-lib.sh +222 -0
  707. package/bin/brokk-lease.sh +193 -0
  708. package/bin/brokk-lint.sh +65 -0
  709. package/bin/brokk-send.sh +38 -0
  710. package/bin/brokk-update.sh +9 -0
  711. package/bin/brokk-wake-grant.sh +127 -0
  712. package/bin/brokk-wake-lib.sh +1849 -0
  713. package/bin/changelog-guard.sh +165 -0
  714. package/bin/crash-sense.sh +160 -0
  715. package/bin/desktop-place.sh +224 -0
  716. package/bin/docs-guard.sh +39 -0
  717. package/bin/editor-place.sh +207 -0
  718. package/bin/eindri-acclaim.sh +34 -0
  719. package/bin/eindri-control.sh +28 -0
  720. package/bin/eindri-role.sh +83 -0
  721. package/bin/eindri-seen.sh +41 -0
  722. package/bin/eindri-send.sh +15 -0
  723. package/bin/eindri-start.sh +85 -0
  724. package/bin/eindri-watch.sh +57 -0
  725. package/bin/einherjar-spawn.sh +576 -0
  726. package/bin/eir-doctor.sh +112 -0
  727. package/bin/erindi-brief.sh +271 -0
  728. package/bin/fleet-apply.sh +68 -0
  729. package/bin/github-deploy.sh +81 -0
  730. package/bin/gjallarhorn-expose.sh +102 -0
  731. package/bin/gjallarhorn-notify.sh +58 -0
  732. package/bin/gjallarhorn-purge.sh +54 -0
  733. package/bin/gjallarhorn-tunnel.sh +72 -0
  734. package/bin/gleipnir-lock-lib.sh +284 -0
  735. package/bin/groa-update.sh +122 -0
  736. package/bin/hall-snapshot.sh +92 -0
  737. package/bin/hamr-harness.sh +246 -0
  738. package/bin/heimdall-ssh-keys.sh +374 -0
  739. package/bin/herdr-agents.py +44 -0
  740. package/bin/herdr-ensure.sh +104 -0
  741. package/bin/herdr-run.sh +431 -0
  742. package/bin/hermes-ensure.sh +88 -0
  743. package/bin/hoard-lib.sh +25 -0
  744. package/bin/hodd.sh +78 -0
  745. package/bin/host-sense.sh +199 -0
  746. package/bin/huginn-research-worker.sh +108 -0
  747. package/bin/local-model-lock.sh +51 -0
  748. package/bin/mimir-bridge.py +226 -0
  749. package/bin/mimir-bridge.sh +87 -0
  750. package/bin/mimir-ingest.sh +131 -0
  751. package/bin/mimir.sh +113 -0
  752. package/bin/mjollnir-webhook.sh +59 -0
  753. package/bin/mjollnir.sh +85 -0
  754. package/bin/model-hardware.sh +82 -0
  755. package/bin/model-resolve.sh +147 -0
  756. package/bin/models-detect.sh +178 -0
  757. package/bin/nornir-cron-start.sh +162 -0
  758. package/bin/nornir-job-daily-briefing.sh +191 -0
  759. package/bin/nornir-job-git-sync.sh +152 -0
  760. package/bin/nornir-job-memory-housekeeping.sh +114 -0
  761. package/bin/nornir-job-observer.sh +166 -0
  762. package/bin/nornir-job-skillopt-sleep.sh +76 -0
  763. package/bin/omarchy-hook-install.sh +60 -0
  764. package/bin/omarchy-install.sh +148 -0
  765. package/bin/omarchy-plugins.sh +174 -0
  766. package/bin/omarchy-sense.sh +149 -0
  767. package/bin/perm-guard.sh +30 -0
  768. package/bin/pi-agent.sh +68 -0
  769. package/bin/pi-ensure.sh +77 -0
  770. package/bin/pi-local.sh +37 -0
  771. package/bin/pi-seat.sh +63 -0
  772. package/bin/prereq-ensure.sh +166 -0
  773. package/bin/private-guard.sh +38 -0
  774. package/bin/project-git.sh +55 -0
  775. package/bin/public-guard.sh +70 -0
  776. package/bin/ratatoskr.sh +49 -0
  777. package/bin/realm-lib.sh +32 -0
  778. package/bin/repo-scrub.sh +62 -0
  779. package/bin/rodd-operational-input.sh +250 -0
  780. package/bin/runes-append.sh +198 -0
  781. package/bin/saga-session-start.sh +172 -0
  782. package/bin/saga-sessionstart-run.sh +79 -0
  783. package/bin/saga-wake-drain.sh +41 -0
  784. package/bin/secret-guard.sh +63 -0
  785. package/bin/sessrumnir-ensure.sh +100 -0
  786. package/bin/sessrumnir-sync.sh +237 -0
  787. package/bin/sessrumnir.sh +91 -0
  788. package/bin/skillopt-setup.sh +76 -0
  789. package/bin/skuld-branch-outcome.sh +203 -0
  790. package/bin/skuld-branch-prompt.sh +101 -0
  791. package/bin/smidja-bootstrap.sh +90 -0
  792. package/bin/smidja-observe.sh +86 -0
  793. package/bin/syn-arm-pretool-check.sh +57 -0
  794. package/bin/syn-asset-pretool-check.sh +76 -0
  795. package/bin/syn-cd-pretool-check.sh +28 -0
  796. package/bin/syn-guard-pretool-check.sh +83 -0
  797. package/bin/syn-turnend-guard.sh +36 -0
  798. package/bin/syn-watch-arm.sh +99 -0
  799. package/bin/tailscale-sync.sh +190 -0
  800. package/bin/telegram-bot.sh +69 -0
  801. package/bin/toolchain.sh +74 -0
  802. package/bin/utgard.sh +111 -0
  803. package/bin/valhalla.sh +115 -0
  804. package/bin/valknut-load.sh +260 -0
  805. package/bin/vor-crew-state.sh +124 -0
  806. package/bin/wedge-notify.sh +47 -0
  807. package/bin/workspace-provision.sh +80 -0
  808. package/bin/workspace-rag.sh +150 -0
  809. package/bin/wyrd-db.sh +47 -0
  810. package/bin/yggdrasil.sh +152 -0
  811. package/bin/ymir-edit.sh +145 -0
  812. package/bin/ymir-install.sh +641 -0
  813. package/bin/ymir-invite.sh +93 -0
  814. package/bin/ymir-isolation.sh +88 -0
  815. package/bin/ymir-migrate.sh +61 -0
  816. package/bin/ymir-platform.sh +348 -0
  817. package/bin/ymir-say.sh +97 -0
  818. package/bin/ymir-setup-auth.sh +108 -0
  819. package/bin/ymir-validate.sh +173 -0
  820. package/bin/ymir.js +74 -16
  821. package/docs/Architecture.md +339 -0
  822. package/docs/design.md +351 -0
  823. package/docs/installations/README.md +63 -0
  824. package/docs/installations/ymir-home.md +93 -0
  825. package/docs/integration.md +105 -0
  826. package/docs/lore.md +1056 -0
  827. package/docs/research/agent-harnesses.md +290 -0
  828. package/docs/runbooks/README.md +19 -0
  829. package/docs/runbooks/a2a.md +91 -0
  830. package/docs/runbooks/agent-permissions.md +99 -0
  831. package/docs/runbooks/agents.md +57 -0
  832. package/docs/runbooks/models.md +69 -0
  833. package/docs/runbooks/realm-onboarding.md +60 -0
  834. package/docs/runbooks/secrets-and-hoard.md +60 -0
  835. package/docs/runbooks/updates-and-migrations.md +110 -0
  836. package/docs/runbooks/worktrees-isolation.md +72 -0
  837. package/docs/session-start.md +105 -0
  838. package/docs/skillopt-integration.md +321 -0
  839. package/docs/sync-system.md +66 -0
  840. package/docs/workspaces.md +104 -0
  841. package/docs/ymir-rut.md +260 -0
  842. package/package.json +44 -14
  843. package/packaging/build.sh +74 -0
  844. package/packaging/macos/Ymir Installer.command +43 -0
  845. package/packaging/windows/ymir-setup.nsi +74 -0
  846. package/scripts/README.md +22 -0
  847. package/scripts/electron.sh +210 -0
  848. package/scripts/start.sh +167 -0
  849. package/scripts/stop.sh +113 -0
  850. package/install.sh +0 -35
@@ -0,0 +1,666 @@
1
+ #!/usr/bin/env bash
2
+ # tests/fm-composer-lib.test.sh - the shared composer-content classifier
3
+ # (bin/fm-composer-lib.sh), the ONE fleet-wide owner every backend adapter
4
+ # delegates its empty|pending|unknown verdict to.
5
+ #
6
+ # The load-bearing contract, task fm-composer-shellglyph-safety:
7
+ # 1. A BARE shell prompt glyph (`>`/`$`/`%`/`#`) on an unstructured row is a
8
+ # dead shell, NOT an empty agent composer - it must read `unknown`
9
+ # (unsafe-for-injection), never `empty`. This is the safety fix.
10
+ # 2. The SAME shell glyph INSIDE a bordered composer box is the harness's own
11
+ # prompt and still reads `empty` (existing behavior preserved).
12
+ # 3. The AGENT prompt glyphs `❯` (claude), `›` (codex), `⟩` (muse), and `→`
13
+ # (cursor) are a genuine empty agent composer either way, bordered or bare.
14
+ # 4. Real unsubmitted text reads `pending`; a known idle placeholder reads
15
+ # `empty`.
16
+ set -u
17
+
18
+ # shellcheck source=tests/lib.sh
19
+ . "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
20
+
21
+ # shellcheck source=/dev/null
22
+ . "$ROOT/bin/fm-composer-lib.sh"
23
+
24
+ # classify <bordered> <content> [idle_re] -> echoes the verdict.
25
+ classify() { fm_composer_classify_content "$@"; }
26
+
27
+ # --- Safety fix: bare shell prompt is NOT an empty agent composer -----------
28
+
29
+ test_bare_shell_glyphs_are_unknown() {
30
+ local g out
31
+ for g in '>' '$' '%' '#'; do
32
+ out=$(classify 0 "$g")
33
+ [ "$out" = unknown ] \
34
+ || fail "bare shell glyph '$g' must read unknown (dead shell, unsafe), got '$out'"
35
+ done
36
+ pass "fm_composer_classify_content: a bare shell prompt glyph (>/\$/%/#) reads unknown, never empty"
37
+ }
38
+
39
+ test_stripped_unbordered_content_uses_plain_content() {
40
+ local plain out
41
+ for plain in '$' 'user@host $'; do
42
+ out=$(classify 0 '' '' sensitive "$plain")
43
+ [ "$out" = unknown ] \
44
+ || fail "stripped unbordered content '$plain' must retain its unknown safety verdict, got '$out'"
45
+ done
46
+ # muse draws `⟩` at luminance ~150, the tightest margin over the 128 ghost
47
+ # threshold in the fleet, so a raised threshold really can strip it to empty
48
+ # and leave only the plain row. This branch is what keeps that pane readable.
49
+ for plain in '❯' '›' '⟩'; do
50
+ out=$(classify 0 '' '' sensitive "$plain")
51
+ [ "$out" = empty ] \
52
+ || fail "a stripped agent glyph '$plain' must remain empty, got '$out'"
53
+ done
54
+ pass "fm_composer_classify_content: stripped unbordered content is unknown except verified agent glyphs"
55
+ }
56
+
57
+ test_bare_shell_prompt_with_command_is_not_empty() {
58
+ local out
59
+ # A dead shell showing a typed command must not read empty either.
60
+ out=$(classify 0 '$ ls -la')
61
+ [ "$out" != empty ] || fail "a bare shell prompt with a command must not read empty, got '$out'"
62
+ pass "fm_composer_classify_content: a bare shell prompt carrying a command is not empty"
63
+ }
64
+
65
+ # --- Preserved: shell glyph inside a composer box is the harness prompt ------
66
+
67
+ test_bordered_shell_glyph_is_empty() {
68
+ local g out
69
+ for g in '>' '$' '%' '#'; do
70
+ out=$(classify 1 "$g")
71
+ [ "$out" = empty ] \
72
+ || fail "a shell glyph '$g' inside a bordered composer box must read empty, got '$out'"
73
+ done
74
+ pass "fm_composer_classify_content: a bare prompt glyph inside a bordered composer box reads empty (claude's own idle composer)"
75
+ }
76
+
77
+ # --- Agent glyphs are empty either way --------------------------------------
78
+
79
+ test_agent_glyphs_are_empty_bordered_and_bare() {
80
+ local out
81
+ out=$(classify 0 '❯'); [ "$out" = empty ] || fail "bare claude '❯' should read empty, got '$out'"
82
+ out=$(classify 0 '›'); [ "$out" = empty ] || fail "bare codex '›' should read empty, got '$out'"
83
+ out=$(classify 1 '❯'); [ "$out" = empty ] || fail "bordered claude '❯' should read empty, got '$out'"
84
+ out=$(classify 1 '›'); [ "$out" = empty ] || fail "bordered codex '›' should read empty, got '$out'"
85
+ out=$(classify 0 '⟩'); [ "$out" = empty ] || fail "bare muse '⟩' should read empty, got '$out'"
86
+ out=$(classify 1 '⟩'); [ "$out" = empty ] || fail "bordered muse '⟩' should read empty, got '$out'"
87
+ pass "fm_composer_classify_content: agent prompt glyphs (❯ claude, › codex, ⟩ muse) read empty bordered or bare"
88
+ }
89
+
90
+ # --- Empty content and idle placeholder -------------------------------------
91
+
92
+ test_empty_content_is_empty() {
93
+ local out
94
+ out=$(classify 0 ''); [ "$out" = empty ] || fail "empty bare content should read empty, got '$out'"
95
+ out=$(classify 1 ''); [ "$out" = empty ] || fail "empty bordered content should read empty, got '$out'"
96
+ pass "fm_composer_classify_content: an empty composer reads empty"
97
+ }
98
+
99
+ test_idle_placeholder_is_empty() {
100
+ local idle='^Type a message\.\.\.$' out
101
+ out=$(classify 1 'Type a message...' "$idle" sensitive 'Type a message...' 1 1)
102
+ [ "$out" = pending ] || fail "placeholder-like text surviving a styled box capture should read pending, got '$out'"
103
+ out=$(classify 1 '❯ Type a message...' "$idle" sensitive '❯ Type a message...' 1 0)
104
+ [ "$out" = empty ] || fail "a glyph-bearing plain box placeholder should read empty, got '$out'"
105
+ out=$(classify 0 '❯ Type a message...' "$idle" sensitive '❯ Type a message...' 0 1)
106
+ [ "$out" = pending ] || fail "placeholder text on a styled bare input row must be pending, got '$out'"
107
+ out=$(classify 0 '❯ Type a message...' "$idle" sensitive '❯ Type a message...' 0 0)
108
+ [ "$out" = unknown ] || fail "placeholder text on a plain bare input row must be unknown, got '$out'"
109
+ out=$(classify 1 'Type a message...')
110
+ [ "$out" = pending ] || fail "without an idle regex the placeholder text is pending, got '$out'"
111
+ pass "fm_composer_classify_content: idle matching is limited to proven placeholder positions"
112
+ }
113
+
114
+ test_idle_placeholder_case_mode_is_explicit() {
115
+ local idle='^Type a message\.\.\.$' out
116
+ out=$(classify 1 'type a message...' "$idle" sensitive 'type a message...' 1 0)
117
+ [ "$out" = pending ] || fail "a case-variant idle placeholder should remain pending by default, got '$out'"
118
+ out=$(classify 1 'type a message...' "$idle" insensitive 'type a message...' 1 0)
119
+ [ "$out" = empty ] || fail "an explicitly insensitive plain placeholder should read empty, got '$out'"
120
+ pass "fm_composer_classify_content: idle matching preserves the caller's case mode"
121
+ }
122
+
123
+ # --- Real text is pending ---------------------------------------------------
124
+
125
+ test_real_text_is_pending() {
126
+ local out
127
+ out=$(classify 0 '❯ fix findings 1 and 3'); [ "$out" = pending ] || fail "bare '❯ <text>' should be pending, got '$out'"
128
+ out=$(classify 1 '> deploy staging now'); [ "$out" = pending ] || fail "bordered '> <text>' should be pending, got '$out'"
129
+ # muse restores the interrupted prompt into its composer after Escape, as real
130
+ # bright text. Reading that as pending is correct - it really is unsubmitted.
131
+ out=$(classify 0 '⟩ second turn to interrupt'); [ "$out" = pending ] || fail "bare '⟩ <text>' should be pending, got '$out'"
132
+ # A slash-command popup argument-hint placeholder is still unsubmitted text.
133
+ out=$(classify 1 '/compact compaction instructions'); [ "$out" = pending ] || fail "a popup placeholder fill should be pending, got '$out'"
134
+ pass "fm_composer_classify_content: real unsubmitted text reads pending (including a popup argument-hint fill)"
135
+ }
136
+
137
+ # =============================================================================
138
+ # fm_composer_classify_screen: the adapter-facing screen classifier and the
139
+ # correctness matrix (audit data/fm-composer-consolidation-audit-s1, task
140
+ # fm-composer-thin-adapter-refactor-r1).
141
+ #
142
+ # Fixtures are the audit's byte-level captures of six REAL idle harnesses:
143
+ # claude 2.1.226 (bare `❯` + U+00A0 NO-BREAK SPACE), codex 0.146.0 (bold `›`
144
+ # + SGR-2 dim hint), muse (truecolor `⟩`, 38;2;90;160;255), pi (blank row
145
+ # between solid `─` rules), opencode 1.14.46 (left-bar `┃` rows), and grok
146
+ # 1.0.0 (bordered box with a TITLED bottom border), plus claude captured
147
+ # inside zellij through `dump-screen --ansi` (`ESC[m` `❯` U+00A0).
148
+ #
149
+ # Capability profiles mirror the real adapters' descriptors: tmux
150
+ # (styled+cursor+identity), herdr/zellij (styled), cmux/orca (plain). Every
151
+ # emptiness verdict is asserted under the ambient UTF-8 locale AND LC_ALL=C,
152
+ # pinning the locale-safe Unicode-space normalization (issue #1988).
153
+ # =============================================================================
154
+
155
+ ESC=$(printf '\033')
156
+ NBSP=$(printf '\302\240')
157
+ CAPS_TMUX=$'styled=1\ncursor=1\nidentity=1\nrows=0'
158
+ CAPS_STYLED=$'styled=1\ncursor=0\nidentity=1\nrows=20' # herdr
159
+ CAPS_STYLED_NOID=$'styled=1\ncursor=0\nidentity=0\nrows=20' # zellij
160
+ CAPS_PLAIN=$'styled=0\ncursor=0\nidentity=0\nrows=20' # cmux, orca
161
+
162
+ # assert_screen <label> <want> <caps> <screen> [cursor] [identity]: one
163
+ # verdict, asserted under the ambient locale AND LC_ALL=C.
164
+ assert_screen() {
165
+ local label=$1 want=$2 out
166
+ shift 2
167
+ out=$(fm_composer_classify_screen "$@")
168
+ [ "$out" = "$want" ] || fail "$label: expected $want, got '$out'"
169
+ out=$(LC_ALL=C fm_composer_classify_screen "$@")
170
+ [ "$out" = "$want" ] || fail "$label under LC_ALL=C: expected $want, got '$out'"
171
+ }
172
+
173
+ test_matrix_claude_bare_nbsp_row() {
174
+ # Real idle claude: `❯` + U+00A0, borderless, between horizontal rules.
175
+ # The audit's headline defect: this row read `pending` under LC_ALL=C
176
+ # (issue #1988), deferring every away-mode escalation in daemon contexts.
177
+ local screen typed
178
+ screen=$'transcript line\n────────────────────────\n❯'"$NBSP"$'\n────────────────────────\n bypass permissions'
179
+ assert_screen "claude idle on tmux" empty "$CAPS_TMUX" "$screen" 2 probe-absent
180
+ assert_screen "claude idle on herdr" empty "$CAPS_STYLED" "$screen" '' probe-absent
181
+ assert_screen "claude idle on zellij" empty "$CAPS_STYLED_NOID" "$screen"
182
+ assert_screen "claude idle on cmux/orca" empty "$CAPS_PLAIN" "$screen"
183
+ typed=$'────────────────────────\n❯ fix the login bug\n────────────────────────'
184
+ assert_screen "claude typed on tmux" pending "$CAPS_TMUX" "$typed" 1 probe-absent
185
+ # Plain capture cannot tell typed text from claude's rotating suggestion:
186
+ # the styled=0 degradation defers instead of fabricating pending.
187
+ assert_screen "claude typed on plain backends" unknown "$CAPS_PLAIN" "$typed"
188
+ pass "matrix: claude's ❯+NBSP row reads empty on every profile in both locales (#1988)"
189
+ }
190
+
191
+ test_matrix_codex_dim_hint_row() {
192
+ # Real idle codex: bold `›`, reset, then an SGR-2 dim hint. Styled captures
193
+ # strip the ghost and prove empty; plain captures must defer as unknown -
194
+ # NEVER the old false `pending` that read the hint as unsent text.
195
+ local styled plain
196
+ styled=$'banner\n'"${ESC}[1m›${ESC}[0m ${ESC}[2mUse /skills to list available skills${ESC}[0m"
197
+ plain=$'banner\n› Use /skills to list available skills'
198
+ assert_screen "codex idle on tmux" empty "$CAPS_TMUX" "$styled" 1
199
+ assert_screen "codex idle on herdr" empty "$CAPS_STYLED" "$styled"
200
+ assert_screen "codex idle on zellij" empty "$CAPS_STYLED_NOID" "$styled"
201
+ assert_screen "codex idle on plain backends" unknown "$CAPS_PLAIN" "$plain"
202
+ pass "matrix: codex's dim hint is empty when styling proves it, unknown (never pending) when it cannot"
203
+ }
204
+
205
+ test_matrix_muse_truecolor_glyph_survives_signal_loss() {
206
+ # Real idle muse: truecolor `⟩` (38;2;90;160;255, luminance ~149.9) under a
207
+ # TITLED rule. Two independent signals prove emptiness: the glyph surviving
208
+ # the ghost strip, and the UNSTRIPPED plain row carrying an agent glyph.
209
+ # Drive them apart: with the luma threshold raised past the glyph's
210
+ # luminance, the ghost strip erases it, and the verdict must survive on the
211
+ # plain-row signal alone.
212
+ local screen plain out
213
+ screen=$'── Voice input (⌥ + v to start) ─────\n'"${ESC}[0m${ESC}[38;2;90;160;255m⟩${ESC}[0m"
214
+ plain=$'── Voice input (⌥ + v to start) ─────\n⟩'
215
+ assert_screen "muse idle on tmux" empty "$CAPS_TMUX" "$screen" 1
216
+ assert_screen "muse idle on herdr" empty "$CAPS_STYLED" "$screen"
217
+ assert_screen "muse idle on zellij" empty "$CAPS_STYLED_NOID" "$screen"
218
+ assert_screen "muse idle on cmux/orca" empty "$CAPS_PLAIN" "$plain"
219
+ out=$(FM_COMPOSER_GHOST_LUMA_MAX=200 fm_composer_classify_screen "$CAPS_STYLED" "$screen")
220
+ [ "$out" = empty ] || fail "muse must stay empty when the ghost strip eats its glyph (plain-row signal), got '$out'"
221
+ pass "matrix: muse's ⟩ reads empty everywhere and survives losing the styled-glyph signal"
222
+ }
223
+
224
+ test_matrix_cursor_reverse_video_placeholder_remnant() {
225
+ # Real idle cursor-agent (2026.08.11-e8db854), captured byte-for-byte from a
226
+ # live pane: the `→ ` glyph and the placeholder tail are dim (SGR 2), but the
227
+ # cell under the terminal cursor is REVERSE VIDEO (SGR 0;7). Reverse video is
228
+ # neither dim nor a dark foreground, so the ghost stripper keeps that one
229
+ # character and an idle composer reduces to a lone `P`.
230
+ local row screen plain out stripped
231
+ row="${ESC}[48;2;21;21;21m ${ESC}[2m→ ${ESC}[0;7m${ESC}[48;2;21;21;21mP"
232
+ row="${row}${ESC}[0;2m${ESC}[48;2;21;21;21mlan, search, build anything${ESC}[0m"
233
+ screen=$'transcript\n\n'"$row"
234
+ plain=$'transcript\n\n → Plan, search, build anything'
235
+
236
+ # NON-VACUOUSNESS: prove the remnant really survives stripping. If the ghost
237
+ # stripper ever learned SGR 7, `stripped` would be empty and the verdict below
238
+ # would come from the empty-content path instead, silently retiring the
239
+ # plain-row branch this case exists to cover.
240
+ stripped=$(printf '%s' "$row" | fm_composer_strip_ghost)
241
+ fm_composer_normalize_trim_var stripped
242
+ [ "$stripped" = P ] \
243
+ || fail "cursor's reverse-video remnant must survive ghost stripping as 'P', got '$stripped'"
244
+
245
+ assert_screen "cursor idle on herdr" empty "$CAPS_STYLED" "$screen"
246
+ assert_screen "cursor idle on zellij" empty "$CAPS_STYLED_NOID" "$screen"
247
+ # An UNSTYLED capture carries no ghost-strip proof, so a bare row matching a
248
+ # placeholder is indistinguishable from typed text and must stay unknown -
249
+ # the same degradation every other bare-row placeholder already takes.
250
+ assert_screen "cursor idle on cmux/orca" unknown "$CAPS_PLAIN" "$plain"
251
+
252
+ # The dangerous direction: text a user actually TYPED is uniformly bright, so
253
+ # stripping leaves it EQUAL to the plain row. Even when that text is exactly
254
+ # the placeholder, it must stay pending - never a false empty.
255
+ local typed typed_plain
256
+ typed="${ESC}[48;2;21;21;21m ${ESC}[2m→ ${ESC}[0m${ESC}[38;2;224;222;244mAdd a follow-up${ESC}[0m"
257
+ typed_plain=$'transcript\n\n → Add a follow-up'
258
+ assert_screen "cursor typed placeholder text stays pending" pending \
259
+ "$CAPS_STYLED" $'transcript\n\n'"$typed"
260
+ # Without styling there is no proof either way, so it must not read empty.
261
+ out=$(fm_composer_classify_screen "$CAPS_PLAIN" "$typed_plain")
262
+ [ "$out" != empty ] \
263
+ || fail "an unstyled cursor row matching the placeholder must not read empty, got '$out'"
264
+ pass "matrix: cursor's reverse-video placeholder remnant reads empty; real typed text stays pending"
265
+ }
266
+
267
+ test_matrix_herdr_halfblock_rule_bounds_bare_wrap() {
268
+ # Herdr draws a composer's rules with half-block glyphs (▄ above, ▀ below)
269
+ # rather than the box-drawing family. Without treating those as edges, a bare
270
+ # composer's WRAP region walks through its own closing rule and swallows the
271
+ # footer, whose real content turns an idle pane into a false `pending`.
272
+ # Captured live from a herdr cursor pane.
273
+ local screen plain out
274
+ plain=$'transcript\n \u2584\u2584\u2584\u2584\u2584\u2584\u2584\u2584\n \u2192 Add a follow-up\n \u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\n Cursor Grok 4.5 High \u00b7 6.7% Run Everything\n ~/wt \u00b7 64cdd3a'
275
+ # The closing rule must bound the region, so the footer below is not input.
276
+ fm_composer_row_has_edge " $(printf '\u2580\u2580\u2580')" \
277
+ || fail "a half-block rule row must count as a structural edge"
278
+ fm_composer_row_has_edge " $(printf '\u2584\u2584\u2584')" \
279
+ || fail "the upper half-block rule must count as a structural edge"
280
+ # Non-vacuousness: the footer rows really are non-blank content that would be
281
+ # swallowed if the rule did not bound the region.
282
+ case "$plain" in *"Run Everything"*) : ;; *) fail "fixture lost its footer content" ;; esac
283
+ ESC_LOCAL=$(printf '\033')
284
+ screen=$'transcript\n \u2584\u2584\u2584\u2584\u2584\u2584\u2584\u2584\n'" ${ESC_LOCAL}[2m\u2192 ${ESC_LOCAL}[0;7mA${ESC_LOCAL}[0;2mdd a follow-up${ESC_LOCAL}[0m"$'\n \u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\n Cursor Grok 4.5 High \u00b7 6.7% Run Everything\n ~/wt \u00b7 64cdd3a'
285
+ out=$(fm_composer_classify_screen "$CAPS_STYLED" "$(printf '%b' "$screen")")
286
+ [ "$out" = empty ] \
287
+ || fail "an idle cursor composer inside herdr half-block rules must read empty, got '$out'"
288
+ pass "matrix: herdr half-block rules bound a bare composer's wrap region"
289
+ }
290
+
291
+ test_matrix_pi_separated_needs_identity() {
292
+ # Real idle pi: a blank row between two solid rules. The blank row alone is
293
+ # exactly what the strict rule refuses; only structure PLUS a live
294
+ # idle/done pi identity proves the composer (herdr's rule, now
295
+ # fleet-wide; tmux supplies identity from its foreground-process probe).
296
+ local screen typed pi_idle pi_working pi_blocked none
297
+ screen=$'transcript\n────────────────────────\n\n────────────────────────\n footer'
298
+ pi_idle=$(printf 'pi\tidle'); pi_working=$(printf 'pi\tworking'); none=$(printf 'zsh\t')
299
+ pi_blocked=$(printf 'pi\tblocked')
300
+ assert_screen "pi idle with identity" empty "$CAPS_STYLED" "$screen" '' "$pi_idle"
301
+ assert_screen "pi idle on tmux with identity" empty "$CAPS_TMUX" "$screen" 2 "$pi_idle"
302
+ assert_screen "pi idle on zellij" unknown "$CAPS_STYLED_NOID" "$screen"
303
+ # Identity-capable but unfetched: the adapter is asked to probe lazily.
304
+ [ "$(fm_composer_classify_screen "$CAPS_STYLED" "$screen")" = need-identity ] \
305
+ || fail "an identity-capable profile should request the lazy identity probe"
306
+ # No identity capability (cmux/orca/zellij): the shape is unprovable.
307
+ assert_screen "pi pair without identity capability" unknown "$CAPS_PLAIN" "$screen"
308
+ # A working pi cannot authorize injection into the blank region.
309
+ assert_screen "working pi defers" unknown "$CAPS_STYLED" "$screen" '' "$pi_working"
310
+ # A pi parked on an interactive prompt reports `blocked`: it is waiting on a
311
+ # human keystroke, so the blank region is a menu's, not a free composer's.
312
+ # Typing there answers the prompt and the text is discarded (issue #2797).
313
+ assert_screen "blocked pi defers" unknown "$CAPS_STYLED" "$screen" '' "$pi_blocked"
314
+ # The audit's live counterexample: a plain shell running sleep, cursor
315
+ # parked on a blank line between two rules, NO pi process. The permissive
316
+ # rule read this `empty`; identity+structure refuses it.
317
+ assert_screen "sleep-pane counterexample" unknown "$CAPS_TMUX" "$screen" 2 "$none"
318
+ assert_screen "absent identity cannot prove blank pi pair" unknown "$CAPS_TMUX" "$screen" 2 probe-absent
319
+ typed=$'────────────────────────\nfix the flaky test\n────────────────────────'
320
+ assert_screen "pi typed" pending "$CAPS_STYLED" "$typed" '' "$pi_idle"
321
+ typed=$'────────────────────────\n❯\n────────────────────────'
322
+ assert_screen "pi lone-glyph draft with identity" pending "$CAPS_STYLED" "$typed" '' "$pi_idle"
323
+ assert_screen "pi lone-glyph draft on tmux" pending "$CAPS_TMUX" "$typed" 1 "$pi_idle"
324
+ assert_screen "lone glyph without identity capability" empty "$CAPS_STYLED_NOID" "$typed"
325
+ assert_screen "lone glyph on plain backend" empty "$CAPS_PLAIN" "$typed"
326
+ assert_screen "lone glyph with non-pi identity" empty "$CAPS_STYLED" "$typed" '' "$none"
327
+ pass "matrix: pi's separated composer needs identity + structure; the blank row alone never proves it"
328
+ }
329
+
330
+ test_matrix_opencode_leftbar_signals() {
331
+ # Real idle opencode: `┃`-prefixed rows holding the "Ask anything..." hint,
332
+ # blanks, and a Build-mode footer. Two independent idle signals: the shared
333
+ # idle-placeholder pattern (works on plain captures) and the ghost strip
334
+ # (works on styled captures even if the pattern is overridden away).
335
+ local screen typed dim_screen out
336
+ screen=$' ┃\n ┃ Ask anything... "What is the tech stack?"\n ┃\n ┃ Build · GPT-5.5 Fast OpenAI · high\n ╹▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀'
337
+ dim_screen=$' ┃\n ┃ '"${ESC}[2mAsk anything...${ESC}[0m"$'\n ┃\n ┃ Build · GPT-5.5 Fast OpenAI · high\n ╹▀▀▀▀'
338
+ assert_screen "opencode idle on tmux (cursor on hint)" empty "$CAPS_TMUX" "$dim_screen" 1
339
+ assert_screen "opencode idle on herdr" empty "$CAPS_STYLED" "$dim_screen"
340
+ assert_screen "opencode idle on zellij" empty "$CAPS_STYLED_NOID" "$dim_screen"
341
+ assert_screen "opencode idle on cmux/orca" empty "$CAPS_PLAIN" "$screen"
342
+ # Signal separation: with the idle pattern overridden to something that
343
+ # cannot match, a DIM-styled hint still proves empty through the ghost strip.
344
+ out=$(FM_COMPOSER_IDLE_RE='^NEVER-MATCHES$' fm_composer_classify_screen "$CAPS_TMUX" "$dim_screen" 1)
345
+ [ "$out" = empty ] || fail "a dim opencode hint must stay empty via the ghost strip alone, got '$out'"
346
+ typed=$'┃\n┃ refactor the parser please\n┃\n┃ Build · GPT-5.5 Fast OpenAI · high\n╹▀▀▀▀'
347
+ assert_screen "opencode typed on tmux" pending "$CAPS_TMUX" "$typed" 1
348
+ assert_screen "opencode typed on plain backends" unknown "$CAPS_PLAIN" "$typed"
349
+ typed=$'┃ Ask anything... please investigate\n┃\n┃ Build · GPT-5.5 Fast OpenAI · high\n╹▀▀▀▀'
350
+ assert_screen "opencode placeholder-like input on tmux" pending "$CAPS_TMUX" "$typed" 0
351
+ assert_screen "opencode placeholder-like input on plain backends" unknown "$CAPS_PLAIN" "$typed"
352
+ typed=$'┃ refactor the parser please\n┃\n┃ Build · GPT-5.5 Fast OpenAI · high'
353
+ assert_screen "opencode multiline draft above blank cursor row" pending "$CAPS_TMUX" "$typed" 1
354
+ pass "matrix: opencode's left-bar composer reads empty everywhere and scans the full active run"
355
+ }
356
+
357
+ test_matrix_grok_titled_bottom_border() {
358
+ # Real idle grok: a bordered box whose BOTTOM border carries the model name.
359
+ # The audit showed the title alone flipped tmux's geometry check to
360
+ # ambiguous and the verdict to unknown, stranding every grok steer.
361
+ local titled plain_border typed placeholder_draft
362
+ titled=$' ╭──────────────────────────────────────╮\n │ ❯ │\n ╰──────────────────── Grok 4.5 (high) ─╯'
363
+ plain_border=$' ╭──────────────────────────────────────╮\n │ ❯ │\n ╰──────────────────────────────────────╯'
364
+ assert_screen "grok titled on tmux" empty "$CAPS_TMUX" "$titled" 1
365
+ assert_screen "grok titled on tmux bottom-border cursor" empty "$CAPS_TMUX" "$titled" 2
366
+ assert_screen "grok titled on herdr" empty "$CAPS_STYLED" "$titled"
367
+ placeholder_draft=$' ╭──────────────────────────────────────╮\n │ ❯ Type a message... │\n ╰──────────────────── Grok 4.5 (high) ─╯'
368
+ assert_screen "grok bright placeholder-like draft on tmux" pending "$CAPS_TMUX" "$placeholder_draft" 1
369
+ assert_screen "grok placeholder on plain backends" empty "$CAPS_PLAIN" "$placeholder_draft"
370
+ assert_screen "grok titled on cmux/orca" empty "$CAPS_PLAIN" "$titled"
371
+ assert_screen "grok titled on zellij" empty "$CAPS_STYLED_NOID" "$titled"
372
+ # The tolerance is additive: an untitled border still proves the same box.
373
+ assert_screen "grok untitled border" empty "$CAPS_TMUX" "$plain_border" 1
374
+ typed=$' ╭──────────────────────────────────────╮\n │ ❯ deploy the fix │\n ╰──────────────────── Grok 4.5 (high) ─╯'
375
+ assert_screen "grok typed on tmux" pending "$CAPS_TMUX" "$typed" 1
376
+ pass "matrix: grok's titled bottom border is tolerated as a title, not read as ambiguity"
377
+ }
378
+
379
+ test_matrix_kimi_bordered_shell_glyph_box() {
380
+ # Kimi's bordered `│ > │` composer - the shape fm-spawn.sh's retired
381
+ # spawn-local regex used to own. Now the shared owner proves it everywhere,
382
+ # which is what kimi launch-readiness and delivery route through.
383
+ local screen
384
+ screen=$'╭────────────────────────╮\n│ > │\n╰────────────────────────╯'
385
+ assert_screen "kimi idle on tmux" empty "$CAPS_TMUX" "$screen" 1
386
+ assert_screen "kimi idle on cmux/orca" empty "$CAPS_PLAIN" "$screen"
387
+ assert_screen "kimi idle on herdr" empty "$CAPS_STYLED" "$screen"
388
+ assert_screen "kimi idle on zellij" empty "$CAPS_STYLED_NOID" "$screen"
389
+ pass "matrix: kimi's bordered shell-glyph box reads empty through the shared owner (spawn's fourth copy retired)"
390
+ }
391
+
392
+ test_matrix_claude_inside_zellij_ansi_dump() {
393
+ # Real claude captured through `zellij action dump-screen --ansi`
394
+ # (capability established by the audit): `ESC[m` `❯` U+00A0.
395
+ local screen plain
396
+ screen=$'zellij pane transcript\n'"${ESC}[m❯${NBSP}"
397
+ plain=$'zellij pane transcript\n❯'"$NBSP"
398
+ assert_screen "claude-in-zellij on tmux" empty "$CAPS_TMUX" "$screen" 1
399
+ assert_screen "claude-in-zellij on herdr" empty "$CAPS_STYLED" "$screen"
400
+ assert_screen "claude-in-zellij on zellij" empty "$CAPS_STYLED_NOID" "$screen"
401
+ assert_screen "claude-in-zellij on plain backends" empty "$CAPS_PLAIN" "$plain"
402
+ pass "matrix: the real claude-in-zellij --ansi dump reads empty in both locales"
403
+ }
404
+
405
+ test_strict_blank_row_divergence() {
406
+ # THE STRICT POSTURE PIN (captain decision blank-row-injection-posture,
407
+ # 2026-08-09): a blank or otherwise unidentified input row with no positive
408
+ # container proof is `unknown`. Each case below read `empty` (or `pending`)
409
+ # under the replaced permissive rule; if any of them drifts back, the
410
+ # permissive posture has silently returned and away-mode injection would
411
+ # again type escalations into unproven panes.
412
+ local out
413
+ # Permissive read this blank cursor row as empty = safe to inject.
414
+ out=$(fm_composer_classify_screen "$CAPS_TMUX" $'some output\nmore output\n' 2)
415
+ [ "$out" = unknown ] || fail "a blank unidentified cursor row must be unknown (was permissive empty), got '$out'"
416
+ # A dead shell's prompt row.
417
+ out=$(fm_composer_classify_screen "$CAPS_TMUX" $'output\n$ ' 1)
418
+ [ "$out" = unknown ] || fail "a dead-shell prompt row must be unknown, got '$out'"
419
+ # A bare busy-footer row is not a composer container.
420
+ out=$(fm_composer_classify_screen "$CAPS_TMUX" $'Working...' 0)
421
+ [ "$out" = unknown ] || fail "a bare busy-footer row must be unknown (was permissive empty), got '$out'"
422
+ # An unidentified free-text cursor row carries no container proof either.
423
+ out=$(fm_composer_classify_screen "$CAPS_TMUX" $'output\nhuman draft text' 1)
424
+ [ "$out" = unknown ] || fail "an unidentified text row must be unknown under strict, got '$out'"
425
+ # A blank screen with no cursor capability.
426
+ out=$(fm_composer_classify_screen "$CAPS_PLAIN" $'\n\n')
427
+ [ "$out" = unknown ] || fail "a blank screen must be unknown, got '$out'"
428
+ pass "strict posture: blank and unidentified rows are unknown, never injectable empty"
429
+ }
430
+
431
+ test_bare_wrap_region_classifies() {
432
+ # Long typed input wraps below the glyph row; the cursor rides the wrapped
433
+ # continuation. The region is IDENTIFIED (glyph row + contiguous non-blank,
434
+ # non-structural rows), so a swallowed Enter still reads pending and earns
435
+ # its retry; a wrapped GHOST suggestion still proves empty.
436
+ local wrapped ghost_wrapped out
437
+ wrapped=$'❯ a very long steer message that\nwraps onto the following line'
438
+ assert_screen "wrapped typed input" pending "$CAPS_TMUX" "$wrapped" 1
439
+ wrapped=$'❯ wrapped typed input\ncontinues without a terminal-inserted glyph'
440
+ assert_screen "ordinary wrapped input" pending "$CAPS_TMUX" "$wrapped" 1
441
+ ghost_wrapped=$'❯ '"${ESC}[2ma long rotating suggestion that${ESC}[0m"$'\n'"${ESC}[2mwraps onto the next line${ESC}[0m"
442
+ out=$(fm_composer_classify_screen "$CAPS_TMUX" "$ghost_wrapped" 1)
443
+ [ "$out" = empty ] || fail "a wrapped ghost suggestion should still prove empty, got '$out'"
444
+ # A structural row between the glyph and the cursor breaks the wrap claim.
445
+ out=$(fm_composer_classify_screen "$CAPS_TMUX" $'❯ text\n────────────────\nbelow the rule' 2)
446
+ [ "$out" = unknown ] || fail "a rule between glyph and cursor must break the wrap region, got '$out'"
447
+ out=$(fm_composer_classify_screen "$CAPS_TMUX" $'❯ text\n$ live shell' 1)
448
+ [ "$out" = unknown ] || fail "a shell prompt below a glyph row must not become wrapped input, got '$out'"
449
+ pass "fm_composer_classify_screen: the bare composer's wrap region stays identified; structure breaks it"
450
+ }
451
+
452
+ test_contiguous_transcript_reanchors_on_live_prompt() {
453
+ local screen
454
+ screen=$'❯ hi\nHello!\n❯'
455
+ assert_screen "contiguous transcript live prompt on cursorless styled backend" empty "$CAPS_STYLED_NOID" "$screen"
456
+ assert_screen "contiguous transcript live prompt on cursorless plain backend" empty "$CAPS_PLAIN" "$screen"
457
+ assert_screen "contiguous transcript live prompt with cursor" empty "$CAPS_TMUX" "$screen" 2
458
+ pass "fm_composer_classify_screen: a row-leading agent glyph reanchors the live composer"
459
+ }
460
+
461
+ test_lower_dead_shell_invalidates_cursorless_candidate() {
462
+ local stale live out
463
+ stale=$'old transcript\n❯\nprocess exited\n$'
464
+ assert_screen "stale composer above dead shell on herdr" unknown "$CAPS_STYLED" "$stale"
465
+ assert_screen "stale composer above dead shell on zellij" unknown "$CAPS_STYLED_NOID" "$stale"
466
+ assert_screen "stale composer above dead shell on cmux/orca" unknown "$CAPS_PLAIN" "$stale"
467
+ out=$(fm_composer_classify_screen "$CAPS_TMUX" "$stale" 1)
468
+ [ "$out" = empty ] \
469
+ || fail "cursor mode must keep the cursor-anchored composer verdict, got '$out'"
470
+
471
+ live=$'transcript shell snippet\n$ echo old output\nmore transcript\n❯'
472
+ assert_screen "shell transcript above live composer on herdr" empty "$CAPS_STYLED" "$live"
473
+ assert_screen "shell transcript above live composer on zellij" empty "$CAPS_STYLED_NOID" "$live"
474
+ assert_screen "shell transcript above live composer on cmux/orca" empty "$CAPS_PLAIN" "$live"
475
+ pass "fm_composer_classify_screen: a lower dead shell invalidates only cursorless stale composers"
476
+ }
477
+
478
+ test_cursorless_bare_wrap_region_classifies() {
479
+ local activity status bounded ghost out
480
+ activity=$'❯\nWorking on request...'
481
+ assert_screen "cursorless activity below bare row on herdr" pending "$CAPS_STYLED" "$activity"
482
+ assert_screen "cursorless activity below bare row on zellij" pending "$CAPS_STYLED_NOID" "$activity"
483
+ assert_screen "cursorless activity below bare row on cmux/orca" unknown "$CAPS_PLAIN" "$activity"
484
+
485
+ status=$'›\n\ncodex status line'
486
+ assert_screen "blank-separated codex status on herdr" empty "$CAPS_STYLED" "$status"
487
+ assert_screen "blank-separated codex status on zellij" empty "$CAPS_STYLED_NOID" "$status"
488
+ assert_screen "blank-separated codex status on cmux/orca" empty "$CAPS_PLAIN" "$status"
489
+
490
+ bounded=$'────────────────────────\n❯\n────────────────────────\nClaude 4.1'
491
+ assert_screen "rule-bounded claude footer on herdr" empty "$CAPS_STYLED" "$bounded" '' probe-absent
492
+ assert_screen "rule-bounded claude footer on zellij" empty "$CAPS_STYLED_NOID" "$bounded"
493
+ assert_screen "rule-bounded claude footer on cmux/orca" empty "$CAPS_PLAIN" "$bounded"
494
+
495
+ ghost=$'❯ '"${ESC}[2ma long rotating suggestion that${ESC}[0m"$'\n'"${ESC}[2mwraps onto the next line${ESC}[0m"
496
+ out=$(fm_composer_classify_screen "$CAPS_STYLED" "$ghost")
497
+ [ "$out" = empty ] || fail "cursorless ghost wrap on herdr should be empty, got '$out'"
498
+ out=$(fm_composer_classify_screen "$CAPS_STYLED_NOID" "$ghost")
499
+ [ "$out" = empty ] || fail "cursorless ghost wrap on zellij should be empty, got '$out'"
500
+ pass "fm_composer_classify_screen: cursorless bare wrap regions participate in verdicts"
501
+ }
502
+
503
+ test_cursorless_container_rejects_contiguous_lower_activity() {
504
+ local box leftbar grok kimi opencode
505
+ box=$'╭────────────────────────╮\n│ ❯ │\n╰────────────────────────╯\nWorking on request...'
506
+ assert_screen "stale box above activity on herdr" unknown "$CAPS_STYLED" "$box"
507
+ assert_screen "stale box above activity on zellij" unknown "$CAPS_STYLED_NOID" "$box"
508
+ assert_screen "stale box above activity on cmux/orca" unknown "$CAPS_PLAIN" "$box"
509
+
510
+ leftbar=$'┃\n┃ Ask anything...\n┃\n┃ Build · GPT-5.5 Fast OpenAI · high\n╹▀▀▀▀▀▀▀▀\nWorking on request...'
511
+ assert_screen "stale left-bar above activity on herdr" unknown "$CAPS_STYLED" "$leftbar"
512
+ assert_screen "stale left-bar above activity on zellij" unknown "$CAPS_STYLED_NOID" "$leftbar"
513
+ assert_screen "stale left-bar above activity on cmux/orca" unknown "$CAPS_PLAIN" "$leftbar"
514
+
515
+ grok=$'╭────────────────────────╮\n│ ❯ │\n╰──────── Grok 4.5 ──────╯\n\nGrok status'
516
+ kimi=$'╭────────────────────────╮\n│ > │\n╰────────────────────────╯\n\nKimi status'
517
+ opencode=$'┃\n┃ Ask anything...\n┃\n┃ Build · GPT-5.5 Fast OpenAI · high\n╹▀▀▀▀▀▀▀▀\n\nOpenCode status'
518
+ assert_screen "blank-separated grok footer" empty "$CAPS_STYLED_NOID" "$grok"
519
+ assert_screen "blank-separated kimi footer" empty "$CAPS_PLAIN" "$kimi"
520
+ assert_screen "left-bar floor and blank-separated footer" empty "$CAPS_STYLED_NOID" "$opencode"
521
+ pass "fm_composer_classify_screen: cursorless containers reject only contiguous unclaimed activity"
522
+ }
523
+
524
+ test_bottom_most_candidate_wins() {
525
+ # The one ranking rule: the live composer is bottom-anchored, so a stale
526
+ # decorative box (codex's startup banner) can never outrank the real row
527
+ # below it - the confidently-wrong orca case from the audit.
528
+ local screen out
529
+ screen=$'╭────────────────────────╮\n│ permissions: YOLO mode │\n╰────────────────────────╯\n❯'"$NBSP"
530
+ assert_screen "banner above live claude row" empty "$CAPS_PLAIN" "$screen"
531
+ out=$(fm_composer_classify_screen "$CAPS_PLAIN" $'╭────────────────────────╮\n│ permissions: YOLO mode │\n╰────────────────────────╯\n› Use /skills to list available skills')
532
+ [ "$out" != pending ] || fail "a stale banner must never classify as pending composer text"
533
+ screen=$'❯ old draft\n\n❯'
534
+ assert_screen "blank-separated newer bare composer" empty "$CAPS_STYLED_NOID" "$screen"
535
+ pass "fm_composer_classify_screen: the bottom-most candidate wins; stale banners cannot"
536
+ }
537
+
538
+ test_incomplete_lower_box_invalidates_stale_candidate() {
539
+ local screen out
540
+ screen=$'╭────────────────────────╮\n│ ❯ │\n╰────────────────────────╯\nstartup complete\n╭────────────────────────╮\n│ ❯ clipped live draft '
541
+ out=$(fm_composer_classify_screen "$CAPS_PLAIN" "$screen")
542
+ [ "$out" = unknown ] \
543
+ || fail "an incomplete lower box must invalidate an earlier empty box, got '$out'"
544
+ pass "fm_composer_classify_screen: incomplete lower structure invalidates stale boxes"
545
+ }
546
+
547
+ test_titled_bottom_requires_matching_width() {
548
+ local screen out
549
+ screen=$'╭────────────────────────╮\n│ ❯ │\n╰─ Grok ─╯'
550
+ out=$(fm_composer_classify_screen "$CAPS_TMUX" "$screen" 1)
551
+ [ "$out" = unknown ] \
552
+ || fail "a short titled bottom must not prove an empty box, got '$out'"
553
+ pass "fm_composer_classify_screen: titled bottoms retain full box geometry"
554
+ }
555
+
556
+ test_cursor_on_proven_box_bottom_classifies_content() {
557
+ local screen out
558
+ screen=$'╭────────────────────────╮\n│ ❯ │\n╰────────────────────────╯'
559
+ out=$(fm_composer_classify_screen "$CAPS_TMUX" "$screen" 2)
560
+ [ "$out" = empty ] \
561
+ || fail "a cursor on a proven box bottom must classify its content, got '$out'"
562
+ pass "fm_composer_classify_screen: a proven box tolerates a bottom-border cursor"
563
+ }
564
+
565
+ test_selected_content_is_composer_scoped_and_wrap_normalized() {
566
+ local screen out
567
+ screen=$'hello captain in transcript\n╭────────────────────╮\n│ unrelated │\n│ draft │\n╰────────────────────╯'
568
+ out=$(fm_composer_extract_selected_content "$CAPS_STYLED_NOID" "$screen")
569
+ [ "$out" = 'unrelated draft' ] \
570
+ || fail "box extraction should contain only normalized selected composer rows, got '$out'"
571
+ screen=$'hello captain in transcript\n┃ hello\n┃ captain\n┃ Build · GPT-5.5 Fast OpenAI · high'
572
+ out=$(fm_composer_extract_selected_content "$CAPS_STYLED_NOID" "$screen")
573
+ [ "$out" = 'hello captain' ] \
574
+ || fail "left-bar extraction should join user rows without footer furniture, got '$out'"
575
+ screen=$'╭────────────────────╮\n│ ❯ '"${ESC}[2mType a message...${ESC}[0m"$'│\n╰────────────────────╯'
576
+ out=$(fm_composer_extract_selected_content "$CAPS_STYLED_NOID" "$screen")
577
+ [ -z "$out" ] \
578
+ || fail "ghost agent-prompt placeholders should be excluded from extracted user content, got '$out'"
579
+ screen=$'╭────────────────────╮\n│ > '"${ESC}[2mType a message...${ESC}[0m"$'│\n╰────────────────────╯'
580
+ out=$(fm_composer_extract_selected_content "$CAPS_STYLED_NOID" "$screen")
581
+ [ -z "$out" ] \
582
+ || fail "ghost shell-prompt placeholders should be excluded from boxed user content, got '$out'"
583
+ screen=$'╭────────────────────╮\n│ ❯ Type a message...│\n╰────────────────────╯'
584
+ out=$(fm_composer_extract_selected_content "$CAPS_STYLED_NOID" "$screen")
585
+ [ "$out" = 'Type a message...' ] \
586
+ || fail "surviving placeholder-like input should remain extracted user content, got '$out'"
587
+ screen=$'❯ a legitimately long steer that\nwraps across the next bare row\n\ntranscript below the break'
588
+ out=$(fm_composer_extract_selected_content "$CAPS_STYLED_NOID" "$screen")
589
+ [ "$out" = 'a legitimately long steer that wraps across the next bare row' ] \
590
+ || fail "bare extraction should include only its contiguous wrap region, got '$out'"
591
+ screen=$'❯ wrapped user content\ncontinuation preserves a mid-row ❯ glyph'
592
+ out=$(fm_composer_extract_selected_content "$CAPS_STYLED_NOID" "$screen")
593
+ [ "$out" = 'wrapped user content continuation preserves a mid-row ❯ glyph' ] \
594
+ || fail "bare extraction should preserve mid-row agent glyph bytes, got '$out'"
595
+ screen=$'❯ stale composer\n$ live shell'
596
+ if out=$(fm_composer_extract_selected_content "$CAPS_STYLED_NOID" "$screen"); then
597
+ fail "a lower live shell must invalidate composer extraction, got '$out'"
598
+ fi
599
+ screen=$'╭──────────────────────────────╮\n│ > wrapped user content │\n│ ❯ preserves its leading glyph│\n╰──────────────────────────────╯'
600
+ out=$(fm_composer_extract_selected_content "$CAPS_STYLED_NOID" "$screen")
601
+ [ "$out" = 'wrapped user content ❯ preserves its leading glyph' ] \
602
+ || fail "box extraction should strip only its actual prompt-row glyph, got '$out'"
603
+ pass "fm_composer_extract_selected_content: scopes user content and excludes furniture"
604
+ }
605
+
606
+ test_bare_shell_glyphs_are_unknown
607
+ test_stripped_unbordered_content_uses_plain_content
608
+ test_bare_shell_prompt_with_command_is_not_empty
609
+ test_bordered_shell_glyph_is_empty
610
+ test_agent_glyphs_are_empty_bordered_and_bare
611
+ test_empty_content_is_empty
612
+ test_idle_placeholder_is_empty
613
+ test_idle_placeholder_case_mode_is_explicit
614
+ test_real_text_is_pending
615
+ test_matrix_claude_bare_nbsp_row
616
+ test_matrix_codex_dim_hint_row
617
+ test_matrix_muse_truecolor_glyph_survives_signal_loss
618
+ test_matrix_cursor_reverse_video_placeholder_remnant
619
+ test_matrix_herdr_halfblock_rule_bounds_bare_wrap
620
+ test_matrix_pi_separated_needs_identity
621
+ test_matrix_opencode_leftbar_signals
622
+ test_matrix_grok_titled_bottom_border
623
+ test_matrix_kimi_bordered_shell_glyph_box
624
+ test_matrix_claude_inside_zellij_ansi_dump
625
+ test_strict_blank_row_divergence
626
+ test_bare_wrap_region_classifies
627
+ test_contiguous_transcript_reanchors_on_live_prompt
628
+ test_lower_dead_shell_invalidates_cursorless_candidate
629
+ test_cursorless_bare_wrap_region_classifies
630
+ test_cursorless_container_rejects_contiguous_lower_activity
631
+ test_bottom_most_candidate_wins
632
+ test_incomplete_lower_box_invalidates_stale_candidate
633
+ test_titled_bottom_requires_matching_width
634
+ test_cursor_on_proven_box_bottom_classifies_content
635
+ test_selected_content_is_composer_scoped_and_wrap_normalized
636
+
637
+ test_queued_enter_verdict_busy_pending_is_empty() {
638
+ local out
639
+ out=$(fm_composer_queued_enter_verdict pending busy)
640
+ [ "$out" = empty ] || fail "busy + proven pending must be queued delivery (empty), got '$out'"
641
+ pass "fm_composer_queued_enter_verdict: pending + busy returns empty (queued Enter)"
642
+ }
643
+
644
+ test_queued_enter_verdict_idle_pending_stays_pending() {
645
+ local out
646
+ out=$(fm_composer_queued_enter_verdict pending idle)
647
+ [ "$out" = pending ] || fail "idle + proven pending must stay a genuine swallow, got '$out'"
648
+ out=$(fm_composer_queued_enter_verdict pending unknown)
649
+ [ "$out" = pending ] || fail "unknown busy is not proof of a queue, got '$out'"
650
+ pass "fm_composer_queued_enter_verdict: pending + idle/unknown stays pending"
651
+ }
652
+
653
+ test_queued_enter_verdict_does_not_convert_other_states() {
654
+ local state out
655
+ for state in empty pending-unproven unknown send-failed future-state; do
656
+ out=$(fm_composer_queued_enter_verdict "$state" busy)
657
+ [ "$out" = "$state" ] || fail "busy must not convert '$state', got '$out'"
658
+ out=$(fm_composer_queued_enter_verdict "$state" idle)
659
+ [ "$out" = "$state" ] || fail "idle must not convert '$state', got '$out'"
660
+ done
661
+ pass "fm_composer_queued_enter_verdict: only proven pending is converted"
662
+ }
663
+
664
+ test_queued_enter_verdict_busy_pending_is_empty
665
+ test_queued_enter_verdict_idle_pending_stays_pending
666
+ test_queued_enter_verdict_does_not_convert_other_states