@zerwiz/ymir 0.1.4 → 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 -59
  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,1187 @@
1
+ #!/usr/bin/env bash
2
+ # End-to-end tests for captain-held tasks: the one primitive behind "a decision
3
+ # is simply a task waiting on the captain", its completion gate, its recorded
4
+ # answers, the record-divergence guard over its two records, and the legacy
5
+ # compatibility for pre-collapse decision identities.
6
+ set -u
7
+
8
+ # shellcheck source=tests/lib.sh
9
+ # shellcheck disable=SC1091
10
+ . "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
11
+
12
+ TEARDOWN="$ROOT/bin/fm-teardown.sh"
13
+ BEARINGS="$ROOT/bin/fm-bearings-snapshot.sh"
14
+ TMP_ROOT=$(fm_test_tmproot fm-captain-hold)
15
+ TASKS_AXI_BIN=$(command -v tasks-axi || true)
16
+
17
+ command -v jq >/dev/null 2>&1 || { echo "skip: jq not found"; exit 0; }
18
+ command -v tasks-axi >/dev/null 2>&1 || { echo "skip: tasks-axi not found"; exit 0; }
19
+
20
+ make_home() { # <name>
21
+ local home="$TMP_ROOT/$1" fakebin
22
+ mkdir -p "$home/data" "$home/state" "$home/config" "$home/projects"
23
+ cp "$ROOT/.tasks.toml" "$home/.tasks.toml"
24
+ cat > "$home/data/backlog.md" <<'EOF'
25
+ ## In flight
26
+
27
+ ## Queued
28
+
29
+ ## Done
30
+ EOF
31
+ fakebin=$(fm_fakebin "$home")
32
+ fm_fake_exit0 "$fakebin" tmux treehouse no-mistakes gh gh-axi
33
+ printf '%s\n' "$home"
34
+ }
35
+
36
+ # The Lavish review adapter, run against this suite's isolated home. The
37
+ # machine-wide process-event claim root is redirected into the fixture so arming
38
+ # a review here can never contend with a real one on this machine.
39
+ run_lavish() { # <home> <command args...>
40
+ local home=$1
41
+ shift
42
+ PATH="$home/fakebin:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$home" \
43
+ FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
44
+ FM_PROCEVENT_CLAIM_ROOT="$home/procevent-claims" \
45
+ "$ROOT/bin/fm-procevent-lavish.sh" "$@"
46
+ }
47
+
48
+ run_bearings() { # <home>
49
+ local home=$1
50
+ PATH="$home/fakebin:$PATH" FM_HOME="$home" FM_BEARINGS_NOW=2026-07-14T12:00:00Z \
51
+ "$BEARINGS" --json
52
+ }
53
+
54
+ run_teardown() { # <home> <id>
55
+ local home=$1 id=$2
56
+ PATH="$home/fakebin:$PATH" FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$home" \
57
+ FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
58
+ FM_CONFIG_OVERRIDE="$home/config" "$TEARDOWN" "$id"
59
+ }
60
+
61
+ tasks_in() { # <home> <tasks-axi args...>
62
+ local home=$1
63
+ shift
64
+ (cd "$home" && tasks-axi "$@")
65
+ }
66
+
67
+ run_captain() { # <home> <command args...>
68
+ local home=$1
69
+ shift
70
+ PATH="$home/fakebin:$PATH" REAL_TASKS_AXI="$TASKS_AXI_BIN" \
71
+ FM_HOME="$home" FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
72
+ FM_CONFIG_OVERRIDE="$home/config" "$ROOT/bin/fm-captain-hold.sh" "$@"
73
+ }
74
+
75
+ # The retired command surface, kept for one release as a shim; in-flight
76
+ # pre-collapse work still drives the lifecycle through these spellings.
77
+ run_shim() { # <home> <command args...>
78
+ local home=$1
79
+ shift
80
+ PATH="$home/fakebin:$PATH" REAL_TASKS_AXI="$TASKS_AXI_BIN" \
81
+ FM_HOME="$home" FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
82
+ FM_CONFIG_OVERRIDE="$home/config" "$ROOT/bin/fm-decision-hold.sh" "$@"
83
+ }
84
+
85
+ write_origin_meta() { # <home> <id> [kind]
86
+ local home=$1 id=$2 kind=${3:-scout}
87
+ fm_write_meta "$home/state/$id.meta" \
88
+ "window=firstmate:fm-$id" \
89
+ "worktree=$home/projects/missing-$id" \
90
+ "project=$home/projects/sample" \
91
+ "harness=codex" \
92
+ "kind=$kind" \
93
+ "mode=$kind" \
94
+ "spawn_gen=fixture-$id"
95
+ }
96
+
97
+ # Reproduces the loss exactly with privacy-safe synthetic names: the investigation
98
+ # and visual review have ended, the only genuine unresolved captain call is report
99
+ # prose, no held backlog item or open status exists, and the authoritative
100
+ # Bearings view correctly omits it. Completion must now refuse before teardown can
101
+ # erase the source.
102
+ test_uninventoried_report_decision_refuses_completion() {
103
+ local home id json rc
104
+ home=$(make_home omitted-decision)
105
+ id=sample-route-review
106
+ mkdir -p "$home/data/$id"
107
+ cat > "$home/data/backlog.md" <<EOF
108
+ ## In flight
109
+ - [ ] $id - Investigate sample routing (repo: sample) (kind: scout) (since 2026-07-14)
110
+
111
+ ## Queued
112
+
113
+ ## Done
114
+ EOF
115
+ write_origin_meta "$home" "$id"
116
+ printf 'done: report and visual review complete\n' > "$home/state/$id.status"
117
+ cat > "$home/data/$id/report.md" <<'EOF'
118
+ # Sample route review
119
+
120
+ The evidence is complete.
121
+ The captain still needs to choose route north or route south before follow-up work starts.
122
+ EOF
123
+
124
+ json=$(run_bearings "$home") || fail "Bearings failed for unresolved-call regression"
125
+ printf '%s' "$json" | jq -e '
126
+ (.decisions_open | length) == 0
127
+ and (.gates | length) == 0
128
+ and (.reports | any(.id == "sample-route-review"))
129
+ ' >/dev/null || fail "the pre-policy omission shape was not reproduced: $json"
130
+
131
+ set +e
132
+ run_teardown "$home" "$id" > "$home/teardown.out" 2> "$home/teardown.err"
133
+ rc=$?
134
+ set -e
135
+ [ "$rc" -ne 0 ] || fail "completed investigation teardown erased a report-only unresolved captain call"
136
+ assert_present "$home/state/$id.meta" "refused completion must preserve investigation metadata"
137
+ assert_grep "REFUSED" "$home/teardown.err" "refusal must be explicit"
138
+ pass "report-only unresolved captain call is reproduced and completion refuses before loss"
139
+ }
140
+
141
+ # The completion gate on the collapsed primitive: an origin with open keyed
142
+ # status decisions refuses --none, refuses an inventory naming absent tasks,
143
+ # attests a verified inventory of captain-held task ids, and transfers every
144
+ # still-open status decision to that durable inventory.
145
+ test_completion_gate_attests_and_transfers() {
146
+ local home id json open before after
147
+ home=$(make_home completion-gate)
148
+ id=sample-systems-review
149
+ mkdir -p "$home/data/$id"
150
+ tasks_in "$home" add "$id" "Investigate sample systems" --kind scout --repo sample --start >/dev/null \
151
+ || fail "could not create investigation backlog fixture"
152
+ write_origin_meta "$home" "$id"
153
+ cat > "$home/state/$id.status" <<'EOF'
154
+ working: report drafted
155
+ needs-decision [key=route]: choose route north or route south
156
+ needs-decision [key=access]: choose open or restricted sample access
157
+ EOF
158
+ cat > "$home/data/$id/report.md" <<'EOF'
159
+ # Sample systems review
160
+
161
+ Two choices remain unresolved: the route and the sample access level.
162
+ A separate recommendation is already resolved and requires no captain action.
163
+ EOF
164
+
165
+ if run_captain "$home" complete "$id" --none > "$home/none.out" 2> "$home/none.err"; then
166
+ fail "--none attested while captain calls were still open in the status stream"
167
+ fi
168
+ assert_no_grep "decisions_reviewed=1" "$home/state/$id.meta" \
169
+ "failed completion recorded a false completion attestation"
170
+ if run_captain "$home" complete "$id" sample-route-call > "$home/absent.out" 2> "$home/absent.err"; then
171
+ fail "completion accepted an inventory entry that names no task"
172
+ fi
173
+
174
+ run_captain "$home" hold sample-route-call \
175
+ --title "Choose route: north, south" --reason "captain route and access choices pending" \
176
+ --repo sample --origin "$id" >/dev/null \
177
+ || fail "could not register the captain-held task"
178
+ run_captain "$home" hold sample-route-call \
179
+ --title "Choose route: north, south" --reason "captain route and access choices pending" \
180
+ --repo sample >/dev/null \
181
+ || fail "idempotent hold retry failed"
182
+ [ "$(grep -cE "^- \[ \] sample-route-call -" "$home/data/backlog.md")" = 1 ] \
183
+ || fail "idempotent retry duplicated the captain-held task"
184
+ if run_captain "$home" hold sample-route-call --title "A different title" \
185
+ --reason "captain route and access choices pending" > "$home/title.out" 2> "$home/title.err"; then
186
+ fail "hold accepted a changed title on an existing task"
187
+ fi
188
+
189
+ FM_STATE_OVERRIDE="$home/state" bash -c '
190
+ . "$1"
191
+ fm_wake_status_mark_current "$2" "$3"
192
+ ' _ "$ROOT/bin/fm-wake-lib.sh" "$home/state" "$home/state/$id.status" \
193
+ || fail "could not prime the announced decision baseline"
194
+ run_captain "$home" complete "$id" sample-route-call >/dev/null \
195
+ || fail "shared investigation completion gate failed"
196
+ FM_STATE_OVERRIDE="$home/state" bash -c '
197
+ . "$1"; fm_wake_signal_seen_current "$2" "$3"
198
+ ' _ "$ROOT/bin/fm-wake-lib.sh" "$home/state" "$home/state/$id.status" \
199
+ || fail "captain-held bookkeeping closes re-woke their own home"
200
+ assert_grep "decisions_reviewed=1" "$home/state/$id.meta" "completion attestation missing"
201
+ assert_grep "decision_keys=sample-route-call" "$home/state/$id.meta" "inventory was not recorded as task ids"
202
+ open=$(bash -c '. "$1"; status_open_decisions "$2"' _ \
203
+ "$ROOT/bin/fm-classify-lib.sh" "$home/state/$id.status")
204
+ [ -z "$open" ] || fail "captain-held transfer did not close the live status decisions: $open"
205
+ grep -F 'captain-held [key=route]: tracked by sample-route-call' "$home/state/$id.status" >/dev/null \
206
+ || fail "the transfer line does not name the tracking inventory"
207
+
208
+ before=$(shasum -a 256 "$home/data/backlog.md" | awk '{print $1}')
209
+ json=$(run_bearings "$home") || fail "Bearings failed with a captain-held task"
210
+ after=$(shasum -a 256 "$home/data/backlog.md" | awk '{print $1}')
211
+ [ "$before" = "$after" ] || fail "Bearings mutated the authoritative backlog"
212
+ printf '%s' "$json" | jq -e '
213
+ (.decisions_open | any(.id == "sample-route-call" and .verb == "captain-hold" and .owner == "(main)"))
214
+ and (.gates | any(.id == "sample-route-call") | not)
215
+ ' >/dev/null || fail "Bearings did not surface the captain-held task: $json"
216
+
217
+ run_teardown "$home" "$id" >/dev/null 2> "$home/teardown.err" \
218
+ || fail "reviewed investigation teardown failed: $(cat "$home/teardown.err")"
219
+ tasks_in "$home" "done" "$id" --report "data/$id/report.md" --keep 0 >/dev/null \
220
+ || fail "could not archive completed investigation"
221
+ json=$(run_bearings "$home") || fail "Bearings failed after source teardown and archival"
222
+ printf '%s' "$json" | jq -e '
223
+ (.decisions_open | any(.id == "sample-route-call" and .verb == "captain-hold"))
224
+ and (.in_flight | any(.id == "sample-systems-review") | not)
225
+ ' >/dev/null || fail "teardown or archival erased a captain-held task: $json"
226
+ pass "the completion gate attests captain-held inventory and transfers open status decisions"
227
+ }
228
+
229
+ # The recorded-answer rule: answering closes with the captain's exact words, an
230
+ # exact retry is idempotent, a drifted retry is rejected, dependent work routed
231
+ # behind the answered task is released by the close, and the completion gate is
232
+ # satisfied only by a recorded answer.
233
+ test_answer_records_and_closes() {
234
+ local home id json show
235
+ home=$(make_home answer-close)
236
+ id=sample-guard-review
237
+ mkdir -p "$home/data/$id"
238
+ tasks_in "$home" add "$id" "Guard the answer path" --kind scout --repo sample --start >/dev/null \
239
+ || fail "could not create the answer-guard origin"
240
+ write_origin_meta "$home" "$id"
241
+ printf 'done: report complete\n' > "$home/state/$id.status"
242
+ printf '# Guard review\n\nOne captain choice remains.\n' > "$home/data/$id/report.md"
243
+ run_captain "$home" hold sample-guard-call \
244
+ --title "Choose the guard option" --reason "captain guard choice pending" --repo sample >/dev/null \
245
+ || fail "could not register the captain-held task"
246
+ run_captain "$home" complete "$id" sample-guard-call >/dev/null \
247
+ || fail "completion failed for the held inventory"
248
+ tasks_in "$home" add sample-guard-work "Apply the guard option" \
249
+ --kind ship --repo sample --blocked-by sample-guard-call >/dev/null \
250
+ || fail "could not route work behind the captain-held task"
251
+
252
+ printf '' > "$home/empty.txt"
253
+ if run_captain "$home" answer sample-guard-call --decision-file "$home/empty.txt" \
254
+ > "$home/empty-answer.out" 2> "$home/empty-answer.err"; then
255
+ fail "answer accepted an empty captain decision"
256
+ fi
257
+ if run_captain "$home" answer sample-guard-call > "$home/bare-answer.out" 2> "$home/bare-answer.err"; then
258
+ fail "answer accepted a close with no captain decision file at all"
259
+ fi
260
+ printf 'An answer the captain never gave.\n' > "$home/invented.txt"
261
+ if run_captain "$home" answer sample-absent-call --decision-file "$home/invented.txt" \
262
+ > "$home/absent-answer.out" 2> "$home/absent-answer.err"; then
263
+ fail "answer invented a resolution for a task that does not exist"
264
+ fi
265
+ if run_captain "$home" answer sample-guard-work --decision-file "$home/invented.txt" \
266
+ > "$home/unheld-answer.out" 2> "$home/unheld-answer.err"; then
267
+ fail "answer closed a task that is not held for the captain"
268
+ fi
269
+ show=$(tasks_in "$home" show sample-guard-call --full)
270
+ assert_contains "$show" "state: queued" "a refused answer closed the captain-held task"
271
+ assert_contains "$show" "held: yes" "a refused answer released the captain-held task"
272
+
273
+ printf 'Captain chose the guard option.\n' > "$home/guard-decision.txt"
274
+ run_captain "$home" answer sample-guard-call --decision-file "$home/guard-decision.txt" >/dev/null \
275
+ || fail "answer could not close the captain-held task"
276
+ show=$(tasks_in "$home" show sample-guard-call --full)
277
+ assert_contains "$show" "state: done" "an answered captain-held task did not close"
278
+ assert_contains "$show" "Resolution recorded by fm-captain-hold" "the answered task lost the decision record"
279
+ assert_contains "$show" "Resolution mode: answered" "the answered task did not record its close path"
280
+ assert_contains "$show" "Captain chose the guard option." \
281
+ "the answered task did not record the captain decision text"
282
+ run_captain "$home" answer sample-guard-call --decision-file "$home/guard-decision.txt" >/dev/null \
283
+ || fail "identical answer retry was not idempotent"
284
+ printf 'Captain chose something else entirely.\n' > "$home/drifted.txt"
285
+ if run_captain "$home" answer sample-guard-call --decision-file "$home/drifted.txt" \
286
+ > "$home/drifted-answer.out" 2> "$home/drifted-answer.err"; then
287
+ fail "answer retry accepted a different captain decision"
288
+ fi
289
+ # The answered call releases the work routed behind it: a Done blocker reads
290
+ # as resolved everywhere.
291
+ show=$(tasks_in "$home" show sample-guard-work --full)
292
+ assert_contains "$show" "blocked: no" "the recorded answer did not release dependent work"
293
+ run_captain "$home" verify "$id" >/dev/null \
294
+ || fail "an answered captain call did not satisfy the completion gate"
295
+ json=$(run_bearings "$home") || fail "Bearings failed after the answer"
296
+ printf '%s' "$json" | jq -e '
297
+ (.decisions_open | any(.id == "sample-guard-call") | not)
298
+ and (.gates | any(.id == "sample-guard-call") | not)
299
+ and (.landed | any(.id == "sample-guard-call") | not)
300
+ ' >/dev/null || fail "an answered captain call still renders somewhere it should not: $json"
301
+ pass "answer records the captain's words, closes idempotently, and releases routed work"
302
+ }
303
+
304
+ # --release lifts the hold instead of closing, preserving the work item's own
305
+ # body under the record; a re-held task later accepts a new answer.
306
+ test_release_frees_held_work() {
307
+ local home show out
308
+ home=$(make_home release-work)
309
+ tasks_in "$home" add sample-widget "Ship the sample widget" --kind ship --repo sample \
310
+ --body 'The widget plan body. Literal escape: \n. Unicode: café.' >/dev/null \
311
+ || fail "could not create the held work item"
312
+ run_captain "$home" hold sample-widget --reason "captain go needed before shipping" >/dev/null \
313
+ || fail "could not hold the work item for the captain"
314
+ printf 'Go: ship it as planned.\n' > "$home/go.txt"
315
+ run_captain "$home" answer sample-widget --decision-file "$home/go.txt" --release >/dev/null \
316
+ || fail "answer --release failed on the held work item"
317
+ show=$(tasks_in "$home" show sample-widget --full)
318
+ assert_contains "$show" "state: queued" "a released work item did not stay queued"
319
+ assert_contains "$show" "held: no" "a released work item kept its hold"
320
+ assert_contains "$show" "Resolution mode: released" "the release did not record its close path"
321
+ assert_contains "$show" "Go: ship it as planned." "the release lost the captain's words"
322
+ assert_contains "$show" "The widget plan body." "the release destroyed the work item body"
323
+ assert_contains "$show" 'Literal escape: \\n. Unicode: café.' \
324
+ "the release corrupted escaped or Unicode body text"
325
+ run_captain "$home" answer sample-widget --decision-file "$home/go.txt" --release >/dev/null \
326
+ || fail "identical release retry was not idempotent"
327
+ if run_captain "$home" answer sample-widget --decision-file "$home/go.txt" \
328
+ > "$home/wrong-mode.out" 2> "$home/wrong-mode.err"; then
329
+ fail "a released answer replay without --release reported completion"
330
+ fi
331
+ assert_grep "mode released" "$home/wrong-mode.err" \
332
+ "the mismatched replay did not name the recorded release mode"
333
+ show=$(tasks_in "$home" show sample-widget --full)
334
+ assert_contains "$show" "state: queued" "a mismatched release replay closed the work item"
335
+ assert_contains "$show" "held: no" "a mismatched release replay re-held the work item"
336
+
337
+ tasks_in "$home" add sample-empty-label-widget "Ship without a display label" \
338
+ --kind ship --repo sample >/dev/null
339
+ run_captain "$home" hold sample-empty-label-widget --reason "captain go needed" >/dev/null
340
+ out=$(printf 'sample-empty-label-widget\tgo\t\trelease\n' \
341
+ | run_captain "$home" answers --source "empty-label release fixture") \
342
+ || fail "an empty answer label shifted the release close mode"
343
+ assert_contains "$out" "closed: sample-empty-label-widget" \
344
+ "the empty-label release was not accepted"
345
+ show=$(tasks_in "$home" show sample-empty-label-widget --full)
346
+ assert_contains "$show" "state: queued" "an empty-label release completed its work item"
347
+ assert_contains "$show" "held: no" "an empty-label release did not lift the hold"
348
+ assert_contains "$show" "Resolution mode: released" \
349
+ "an empty-label release recorded the wrong close mode"
350
+
351
+ # A NEW captain gate on the same task later takes a NEW answer.
352
+ run_captain "$home" hold sample-widget --reason "captain pricing call needed" >/dev/null \
353
+ || fail "could not re-hold the released work item"
354
+ printf 'Price it at nine dollars.\n' > "$home/price.txt"
355
+ run_captain "$home" answer sample-widget --decision-file "$home/price.txt" --release >/dev/null \
356
+ || fail "a re-held task refused a new answer"
357
+ show=$(tasks_in "$home" show sample-widget --full)
358
+ assert_contains "$show" "Price it at nine dollars." "the new answer was not recorded"
359
+ assert_contains "$show" "Go: ship it as planned." "the new answer erased the earlier record"
360
+
361
+ tasks_in "$home" "done" sample-widget >/dev/null \
362
+ || fail "could not complete the released work item normally"
363
+ if run_captain "$home" answer sample-widget --decision-file "$home/price.txt" \
364
+ > "$home/closed-wrong-mode.out" 2> "$home/closed-wrong-mode.err"; then
365
+ fail "a completed release replay without --release reported an answer"
366
+ fi
367
+ assert_grep "mode released" "$home/closed-wrong-mode.err" \
368
+ "the completed replay did not name the recorded release mode"
369
+ show=$(tasks_in "$home" show sample-widget --full)
370
+ assert_contains "$show" "state: done" "a refused completed replay changed task state"
371
+ pass "release frees held work with the captain's words recorded and the body preserved"
372
+ }
373
+
374
+ # Deferral is a date, not a live card: hold --until keeps the task out of
375
+ # captain_actionable until due, tasks-axi's own date-gate expiry keeps the task
376
+ # answerable, and Bearings renders the wait as a dated gate.
377
+ test_deferral_leaves_captains_call_until_due() {
378
+ local home json snap show
379
+ home=$(make_home deferral)
380
+ run_captain "$home" hold sample-later-call --title "Revisit the sample plan" \
381
+ --reason "captain deferred revisit later" --repo sample --until 2026-08-01 >/dev/null \
382
+ || fail "could not register the deferred captain call"
383
+ run_captain "$home" hold sample-now-call --title "Decide the sample cut" \
384
+ --reason "captain cut choice pending" --repo sample >/dev/null \
385
+ || fail "could not register the live captain call"
386
+ if run_captain "$home" hold sample-bad-date --title "Bad date" \
387
+ --reason "captain choice" --until 2026-8-1 > "$home/bad-date.out" 2> "$home/bad-date.err"; then
388
+ fail "hold accepted a malformed --until date"
389
+ fi
390
+
391
+ snap=$(PATH="$home/fakebin:$PATH" FM_HOME="$home" FM_STATE_OVERRIDE="$home/state" \
392
+ FM_DATA_OVERRIDE="$home/data" FM_CONFIG_OVERRIDE="$home/config" \
393
+ FM_PROJECTS_OVERRIDE="$home/projects" FM_SNAPSHOT_NOW=2026-07-14T12:00:00Z \
394
+ "$ROOT/bin/fm-fleet-snapshot.sh" --json) || fail "fleet snapshot failed"
395
+ printf '%s' "$snap" | jq -e '
396
+ ([.backlog.records[] | select(.id == "sample-later-call")][0]) as $later
397
+ | ([.backlog.records[] | select(.id == "sample-now-call")][0]) as $now
398
+ | $later.captain_actionable == false and $later.hold_until == "2026-08-01"
399
+ and $now.captain_actionable == true and $now.hold_until == null
400
+ and ($later.title | contains("hold-until") | not)
401
+ ' >/dev/null || fail "the due gate or hold-until parsing is wrong: $snap"
402
+
403
+ json=$(run_bearings "$home") || fail "Bearings failed with a deferred call"
404
+ printf '%s' "$json" | jq -e '
405
+ (.decisions_open | any(.id == "sample-now-call"))
406
+ and (.decisions_open | any(.id == "sample-later-call") | not)
407
+ and (.gates | any(.id == "sample-later-call" and (.reason | startswith("until 2026-08-01"))))
408
+ ' >/dev/null || fail "the deferred call did not render as a dated gate: $json"
409
+
410
+ # On its date the call is due again - and still answerable even though
411
+ # tasks-axi reports the expired hold as no longer held.
412
+ snap=$(PATH="$home/fakebin:$PATH" FM_HOME="$home" FM_STATE_OVERRIDE="$home/state" \
413
+ FM_DATA_OVERRIDE="$home/data" FM_CONFIG_OVERRIDE="$home/config" \
414
+ FM_PROJECTS_OVERRIDE="$home/projects" FM_SNAPSHOT_NOW=2026-08-01T12:00:00Z \
415
+ "$ROOT/bin/fm-fleet-snapshot.sh" --json) || fail "fleet snapshot failed at the due date"
416
+ printf '%s' "$snap" | jq -e '
417
+ [.backlog.records[] | select(.id == "sample-later-call")][0].captain_actionable == true
418
+ ' >/dev/null || fail "a due deferral did not resurface as captain-actionable"
419
+ show=$(tasks_in "$home" show sample-later-call --full)
420
+ assert_contains "$show" "hold_kind: captain" "the expired deferral lost its captain-hold annotations"
421
+ printf 'Answered on the due date.\n' > "$home/due.txt"
422
+ run_captain "$home" answer sample-later-call --decision-file "$home/due.txt" >/dev/null \
423
+ || fail "an expired deferral was not answerable"
424
+ pass "a deferred captain call leaves the live Captain's Call until its date and stays answerable"
425
+ }
426
+
427
+ # The recorded-answer guard survives an out-of-band close: a bare tasks-axi done
428
+ # fails verify until answer records the captain's word, and an ordinary finished
429
+ # task can never be dressed up as an answered captain call.
430
+ test_out_of_band_close_is_recordable() {
431
+ local home id show
432
+ home=$(make_home out-of-band)
433
+ id=sample-fullrun-review
434
+ mkdir -p "$home/data/$id"
435
+ tasks_in "$home" add "$id" "Investigate the sample full run" --kind scout --repo sample --start >/dev/null \
436
+ || fail "could not create out-of-band origin"
437
+ write_origin_meta "$home" "$id"
438
+ printf 'done: report complete\n' > "$home/state/$id.status"
439
+ printf '# Sample full run review\n\nOne captain choice remains.\n' > "$home/data/$id/report.md"
440
+ run_captain "$home" hold sample-submission-call --title "Choose the sample submission" \
441
+ --reason "captain submission choice pending" --repo sample --origin "$id" >/dev/null \
442
+ || fail "could not register the captain-held task"
443
+ run_captain "$home" complete "$id" sample-submission-call >/dev/null \
444
+ || fail "completion failed before the out-of-band close"
445
+
446
+ tasks_in "$home" "done" sample-submission-call >/dev/null \
447
+ || fail "could not reproduce the direct out-of-band close"
448
+ if run_captain "$home" verify "$id" > "$home/broken-verify.out" 2> "$home/broken-verify.err"; then
449
+ fail "verification passed a captain call closed with no recorded answer"
450
+ fi
451
+ if run_teardown "$home" "$id" > "$home/broken-teardown.out" 2> "$home/broken-teardown.err"; then
452
+ fail "teardown proceeded while a captain call had no recorded answer"
453
+ fi
454
+ assert_present "$home/state/$id.meta" "refused teardown removed investigation metadata"
455
+
456
+ printf 'Declined: do not submit the sample full run upstream.\n' > "$home/submission.txt"
457
+ run_captain "$home" answer sample-submission-call --decision-file "$home/submission.txt" >/dev/null \
458
+ || fail "answer could not record the missing captain decision on the closed task"
459
+ show=$(tasks_in "$home" show sample-submission-call --full)
460
+ assert_contains "$show" "state: done" "recording the answer reopened the closed task"
461
+ assert_contains "$show" "Resolution mode: repaired" "the retroactive record did not name its path"
462
+ assert_contains "$show" "Declined: do not submit the sample full run upstream." \
463
+ "the retroactive record lost the captain decision text"
464
+ run_captain "$home" verify "$id" >/dev/null \
465
+ || fail "the recorded answer did not satisfy the completion gate"
466
+ run_captain "$home" answer sample-submission-call --decision-file "$home/submission.txt" >/dev/null \
467
+ || fail "identical retroactive retry was not idempotent"
468
+ printf 'A different answer entirely.\n' > "$home/drifted.txt"
469
+ if run_captain "$home" answer sample-submission-call --decision-file "$home/drifted.txt" \
470
+ > "$home/drifted.out" 2> "$home/drifted.err"; then
471
+ fail "a drifted retry overwrote the recorded captain decision"
472
+ fi
473
+ run_teardown "$home" "$id" >/dev/null 2> "$home/teardown.err" \
474
+ || fail "teardown still refused after the answer was recorded: $(cat "$home/teardown.err")"
475
+
476
+ # An ordinary finished task was never the captain's item; recording an
477
+ # invented answer on it must be refused.
478
+ tasks_in "$home" add sample-ordinary-work "Ordinary finished work" --kind ship --repo sample >/dev/null
479
+ tasks_in "$home" "done" sample-ordinary-work >/dev/null
480
+ printf 'An answer the captain never gave.\n' > "$home/invented.txt"
481
+ if run_captain "$home" answer sample-ordinary-work --decision-file "$home/invented.txt" \
482
+ > "$home/never-held.out" 2> "$home/never-held.err"; then
483
+ fail "an ordinary finished task was dressed up as an answered captain call"
484
+ fi
485
+ assert_grep "never held for the captain" "$home/never-held.err" \
486
+ "the refusal must say the task carries no captain-hold provenance"
487
+ pass "an out-of-band close is recordable with the captain's word and nothing else"
488
+ }
489
+
490
+ # A post-teardown visual review completes against the surviving report and
491
+ # durable tasks, with no volatile task metadata and no second decision database.
492
+ test_visual_review_uses_shared_completion_owner() {
493
+ local home id json
494
+ home=$(make_home visual-review)
495
+ id=sample-board-review
496
+ mkdir -p "$home/data/$id"
497
+ tasks_in "$home" add "$id" "Review the sample board" --kind scout --repo sample --start >/dev/null
498
+ write_origin_meta "$home" "$id"
499
+ printf 'done: investigation complete\n' > "$home/state/$id.status"
500
+ printf '# Sample board investigation\n\nThe initial findings need no captain choice.\n' > "$home/data/$id/report.md"
501
+ run_captain "$home" complete "$id" --none >/dev/null \
502
+ || fail "initial investigation could not pass the shared completion owner"
503
+ run_teardown "$home" "$id" >/dev/null 2> "$home/visual-teardown.err" \
504
+ || fail "completed investigation teardown failed: $(cat "$home/visual-teardown.err")"
505
+ tasks_in "$home" "done" "$id" --report "data/$id/report.md" --keep 0 >/dev/null
506
+
507
+ mkdir -p "$home/.lavish"
508
+ printf '<html><body>Synthetic sample board</body></html>\n' > "$home/.lavish/sample-board.html"
509
+ run_captain "$home" hold sample-layout-call --title "Choose the sample layout" \
510
+ --reason "captain layout choice pending" --repo sample --origin "$id" >/dev/null \
511
+ || fail "post-teardown visual review could not use the shared hold owner"
512
+ run_captain "$home" complete "$id" sample-layout-call >/dev/null \
513
+ || fail "post-teardown visual review could not use the shared completion owner"
514
+ json=$(run_bearings "$home") || fail "Bearings failed after the ended visual review"
515
+ printf '%s' "$json" | jq -e '
516
+ .decisions_open | any(.id == "sample-layout-call" and .verb == "captain-hold")
517
+ ' >/dev/null || fail "ended visual review did not leave its durable Captain Call: $json"
518
+ [ ! -e "$home/data/visual-review-decisions.json" ] \
519
+ || fail "visual review created a second decision database"
520
+ pass "ended visual review follows the same captain-hold completion owner"
521
+ }
522
+
523
+ test_none_inventory_and_resolved_prose_do_not_create_holds() {
524
+ local home id json
525
+ home=$(make_home no-false-holds)
526
+ id=sample-resolved-review
527
+ mkdir -p "$home/data/$id"
528
+ tasks_in "$home" add "$id" "Review a resolved sample finding" --kind scout --repo sample --start >/dev/null
529
+ write_origin_meta "$home" "$id"
530
+ printf 'resolved [key=old-choice]: the sample choice was already recorded\ndone: report complete\n' \
531
+ > "$home/state/$id.status"
532
+ cat > "$home/data/$id/report.md" <<'EOF'
533
+ # Resolved sample finding
534
+
535
+ Decision record: the earlier choice is resolved.
536
+ The recommendation is informational and needs no captain action.
537
+ EOF
538
+ run_captain "$home" complete "$id" --none >/dev/null \
539
+ || fail "explicit no-call inventory failed"
540
+ json=$(run_bearings "$home") || fail "Bearings failed for no-call inventory"
541
+ printf '%s' "$json" | jq -e '
542
+ (.decisions_open | any(.id | startswith("sample-resolved-review")) | not)
543
+ ' >/dev/null || fail "resolved findings or decision-like prose created a false captain call: $json"
544
+ pass "resolved findings and decision-like prose do not create captain-held tasks"
545
+ }
546
+
547
+ test_terminal_single_owner_status_decision_does_not_block_empty_inventory() {
548
+ local home id open secondmate
549
+ home=$(make_home stale-terminal-decision)
550
+ id=sample-terminal-review
551
+ mkdir -p "$home/data/$id"
552
+ tasks_in "$home" add "$id" "Review a terminal sample finding" --kind scout --repo sample --start >/dev/null
553
+ write_origin_meta "$home" "$id"
554
+ printf 'needs-decision [key=default]: choose route A or route B\ndone: report complete\n' \
555
+ > "$home/state/$id.status"
556
+ printf '# Terminal sample review\n\nNo unresolved captain choice remains.\n' > "$home/data/$id/report.md"
557
+ open=$(bash -c '. "$1"; status_open_decisions "$2"' _ \
558
+ "$ROOT/bin/fm-classify-lib.sh" "$home/state/$id.status")
559
+ assert_contains "$open" "default" "fixture must retain the raw stale status decision"
560
+ run_captain "$home" complete "$id" --none >/dev/null \
561
+ || fail "terminal single-owner stale status decision blocked empty inventory completion"
562
+ run_captain "$home" verify "$id" >/dev/null \
563
+ || fail "terminal single-owner stale status decision blocked inventory verification"
564
+ run_teardown "$home" "$id" >/dev/null 2> "$home/terminal-teardown.err" \
565
+ || fail "terminal single-owner stale status decision blocked teardown: $(cat "$home/terminal-teardown.err")"
566
+
567
+ secondmate=sample-secondmate
568
+ write_origin_meta "$home" "$secondmate" secondmate
569
+ printf 'needs-decision [key=route]: choose route A or route B\ndone: heartbeat complete\n' \
570
+ > "$home/state/$secondmate.status"
571
+ if run_captain "$home" complete "$secondmate" --none \
572
+ > "$home/secondmate-terminal.out" 2> "$home/secondmate-terminal.err"; then
573
+ fail "secondmate terminal status decision was incorrectly cleared"
574
+ fi
575
+ pass "terminal single-owner stale status decisions do not block empty inventory"
576
+ }
577
+
578
+ test_secondmate_hold_stays_in_authoritative_home() {
579
+ local parent mate fakebin origin json
580
+ parent=$(make_home main-routing)
581
+ mate="$TMP_ROOT/sample-mate-home"
582
+ mkdir -p "$mate/data" "$mate/state" "$mate/config" "$mate/projects" "$mate/bin"
583
+ cp "$ROOT/.tasks.toml" "$mate/.tasks.toml"
584
+ printf '# Synthetic secondmate home\n' > "$mate/AGENTS.md"
585
+ printf 'sample-mate\n' > "$mate/.fm-secondmate-home"
586
+ cat > "$mate/data/backlog.md" <<'EOF'
587
+ ## In flight
588
+
589
+ ## Queued
590
+
591
+ ## Done
592
+ EOF
593
+ fakebin=$(fm_fakebin "$mate")
594
+ fm_fake_exit0 "$fakebin" tmux treehouse no-mistakes gh gh-axi
595
+ origin=sample-mate-review
596
+ mkdir -p "$mate/data/$origin"
597
+ tasks_in "$mate" add "$origin" "Investigate secondmate sample" --kind scout --repo sample --start >/dev/null
598
+ write_origin_meta "$mate" "$origin"
599
+ printf 'done: report and visual review complete\n' > "$mate/state/$origin.status"
600
+ printf '# Sample secondmate review\n\nOne captain choice remains.\n' > "$mate/data/$origin/report.md"
601
+ run_captain "$mate" hold sample-release-call --title "Choose the sample release" \
602
+ --reason "captain release choice pending" --repo sample --origin "$origin" >/dev/null \
603
+ || fail "secondmate-owned hold creation failed"
604
+ run_captain "$mate" complete "$origin" sample-release-call >/dev/null \
605
+ || fail "secondmate-owned completion failed"
606
+ run_teardown "$mate" "$origin" >/dev/null 2> "$mate/teardown.err" \
607
+ || fail "secondmate investigation teardown failed: $(cat "$mate/teardown.err")"
608
+ tasks_in "$mate" "done" "$origin" --report "data/$origin/report.md" --keep 0 >/dev/null
609
+
610
+ printf -- '- sample-mate - synthetic scope (home: %s; scope: sample reviews; projects: sample; added 2026-07-14)\n' \
611
+ "$mate" > "$parent/data/secondmates.md"
612
+ fm_write_secondmate_meta "$parent/state/sample-mate.meta" "$mate" \
613
+ "firstmate:fm-sample-mate" sample
614
+ json=$(run_bearings "$parent") || fail "parent Bearings could not read the secondmate captain call"
615
+ printf '%s' "$json" | jq -e '
616
+ .decisions_open | any(.owner == "sample-mate" and .verb == "captain-hold"
617
+ and (.id | endswith("sample-release-call")))
618
+ ' >/dev/null || fail "secondmate captain call did not surface with authoritative owner: $json"
619
+ assert_no_grep "sample-release-call" "$parent/data/backlog.md" "secondmate call leaked into the main backlog"
620
+ assert_grep "sample-release-call" "$mate/data/backlog.md" "secondmate call left its authoritative backlog"
621
+ pass "main-home and secondmate-home captain calls remain correctly routed"
622
+ }
623
+
624
+ # The one keyed-answer intake, fed through the real process-event runner by a
625
+ # fixture channel that knows nothing about captain holds: task-id keys close at
626
+ # answer time, a card-declared release mode frees held work, freeform prose can
627
+ # forge nothing, and a replayed capture is idempotent.
628
+ test_bound_channel_answers_close_at_answer_time() {
629
+ local home id sid artifact result out show rc
630
+ home=$(make_home channel-answer-closure)
631
+ id=sample-eval-proposal
632
+ mkdir -p "$home/data/$id"
633
+ tasks_in "$home" add "$id" "Propose sample eval changes" --kind scout --repo sample --start >/dev/null \
634
+ || fail "could not create the review origin"
635
+ write_origin_meta "$home" "$id"
636
+ printf 'done: proposal deck ready for the captain\n' > "$home/state/$id.status"
637
+ printf '# Sample eval proposal\n\nThree captain choices remain.\n' > "$home/data/$id/report.md"
638
+ run_captain "$home" hold sample-membership-call --title "Captain call: membership" \
639
+ --reason "captain membership choice pending" --repo sample --origin "$id" >/dev/null
640
+ run_captain "$home" hold sample-headline-call --title "Captain call: headline" \
641
+ --reason "captain headline choice pending" --repo sample --origin "$id" >/dev/null
642
+ run_captain "$home" hold sample-forged-call --title "Captain call: forged" \
643
+ --reason "captain forged choice pending" --repo sample --origin "$id" >/dev/null
644
+ run_captain "$home" hold sample-invalid-close-call --title "Captain call: invalid close" \
645
+ --reason "captain close mode validation pending" --repo sample --origin "$id" >/dev/null
646
+ tasks_in "$home" add sample-gated-work "Gated sample work" --kind ship --repo sample \
647
+ --body 'Gated work plan.' >/dev/null
648
+ run_captain "$home" hold sample-gated-work --reason "captain go needed" >/dev/null
649
+ run_captain "$home" complete "$id" \
650
+ sample-membership-call sample-headline-call sample-forged-call sample-invalid-close-call \
651
+ sample-gated-work >/dev/null \
652
+ || fail "completion failed for the deck's inventoried calls"
653
+
654
+ artifact="$home/data/$id/review.html"
655
+ printf '<h1>Sample eval proposal</h1>\n' > "$artifact"
656
+ fm_fake_exit0 "$home/fakebin" lavish-axi
657
+ sid=$(run_lavish "$home" source-id "$artifact") || fail "could not derive the review source id"
658
+ run_captain "$home" bind "$sid" >/dev/null \
659
+ || fail "could not bind the review source to the keyed-answer intake"
660
+ [ "$(run_captain "$home" binding "$sid")" = "(any)" ] \
661
+ || fail "the recorded binding did not resolve to the collapsed marker"
662
+ run_lavish "$home" arm "$artifact" >/dev/null || fail "could not arm the review deck"
663
+
664
+ result="$home/state/procevent-inbox/$sid.1.result"
665
+ mkdir -p "$home/state/procevent-inbox"
666
+ cat > "$result" <<'EOF'
667
+ session:
668
+ file: /review.html
669
+ status: feedback
670
+ session_ended: true
671
+ ended_by: user
672
+ prompts[6]{uid,prompt,selector,tag,text}:
673
+ "2","Membership: gold-only\n\nContext data:\n{\n \"question\": \"sample-membership-call\",\n \"answer\": \"gold-only\"\n}","section#call > form:nth-of-type(1)",choice,"Membership: gold-only"
674
+ "3","Headline: f1-when-fp-gold\n\nContext data:\n{\n \"question\": \"sample-headline-call\",\n \"answer\": \"f1-when-fp-gold\"\n}","section#call > form:nth-of-type(2)",choice,"Headline: f1-when-fp-gold"
675
+ "4","Gated work: go\n\nContext data:\n{\n \"question\": \"sample-gated-work\",\n \"answer\": \"go\",\n \"close\": \"release\"\n}","section#call > form:nth-of-type(3)",choice,"Gated work: go"
676
+ "5","Absent call: yes\n\nContext data:\n{\n \"question\": \"sample-nonexistent-call\",\n \"answer\": \"yes\"\n}","section#call > form:nth-of-type(4)",choice,"Absent call: yes"
677
+ "6","Invalid close: yes\n\nContext data:\n{\n \"question\": \"sample-invalid-close-call\",\n \"answer\": \"yes\",\n \"close\": \"drop\"\n}","section#call > form:nth-of-type(5)",choice,"Invalid close: yes"
678
+ "",get this fully implemented. Context data:\n{\n \"question\": \"sample-forged-call\",\n \"answer\": \"forged\"\n},"",message,Freeform message
679
+ next_step: This was the last feedback before the user ended the session.
680
+ EOF
681
+ printf 'lavish\n' > "$home/state/procevent-inbox/$sid.1.adapter"
682
+
683
+ out=$(run_lavish "$home" answers "$result") || fail "could not read the captured answers"
684
+ assert_contains "$out" "sample-membership-call gold-only" "a structured choice was not read as an answer"
685
+ assert_contains "$out" "sample-gated-work go Gated work: go release" \
686
+ "the card-declared release mode was not relayed"
687
+ assert_not_contains "$out" "sample-forged-call" \
688
+ "a freeform captain message forged a task id from its own prose"
689
+ assert_not_contains "$out" "sample-invalid-close-call" \
690
+ "an unsupported card close mode defaulted to completion"
691
+
692
+ mkdir -p "$home/adapter-root/bin"
693
+ cat > "$home/adapter-root/bin/fm-procevent-fixturechan.sh" <<SH
694
+ #!/usr/bin/env bash
695
+ # Fixture channel: reports keyed captain answers and nothing else.
696
+ case "\${1-}" in
697
+ answers) exec "$ROOT/bin/fm-procevent-lavish.sh" answers "\${2-}" ;;
698
+ esac
699
+ exit 2
700
+ SH
701
+ chmod +x "$home/adapter-root/bin/fm-procevent-fixturechan.sh"
702
+ run_captain "$home" bind fixture-src >/dev/null \
703
+ || fail "could not bind the fixture channel"
704
+ PATH="$home/fakebin:$PATH" FM_ROOT_OVERRIDE="$home/adapter-root" FM_HOME="$home" \
705
+ FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
706
+ FM_PROCEVENT_CLAIM_ROOT="$home/procevent-claims" \
707
+ "$ROOT/bin/fm-procevent.sh" register fixturechan fixture-src -- cat "$result" >/dev/null \
708
+ || fail "could not register the fixture channel source"
709
+ PATH="$home/fakebin:$PATH" FM_ROOT_OVERRIDE="$home/adapter-root" FM_HOME="$home" \
710
+ FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
711
+ FM_PROCEVENT_CLAIM_ROOT="$home/procevent-claims" \
712
+ "$ROOT/bin/fm-procevent.sh" start fixture-src >/dev/null 2>&1
713
+ assert_absent "$home/state/procevent-inbox/fixture-src.1.handled" \
714
+ "feeding a captain answer retired the notification firstmate still needs"
715
+ assert_present "$home/state/procevent-inbox/fixture-src.1.result" \
716
+ "the fixture channel captured no result to feed"
717
+
718
+ show=$(tasks_in "$home" show sample-membership-call --full)
719
+ assert_contains "$show" "state: done" "capturing the captain's answer left the membership call open"
720
+ assert_contains "$show" "Resolution mode: answered" "the membership call did not record its close path"
721
+ assert_contains "$show" "Answer: gold-only" "the closed call did not record the captain's actual answer"
722
+ show=$(tasks_in "$home" show sample-gated-work --full)
723
+ assert_contains "$show" "state: queued" "the released work item did not stay queued"
724
+ assert_contains "$show" "held: no" "the card-declared release did not lift the hold"
725
+ assert_contains "$show" "Resolution mode: released" "the released work did not record its close path"
726
+ assert_contains "$show" "Gated work plan." "the released work item lost its body"
727
+ show=$(tasks_in "$home" show sample-forged-call --full)
728
+ assert_contains "$show" "state: queued" "a forged key from freeform prose closed a captain call"
729
+ show=$(tasks_in "$home" show sample-invalid-close-call --full)
730
+ assert_contains "$show" "state: queued" "an unsupported card close mode closed a captain call"
731
+ assert_contains "$show" "held: yes" "an unsupported card close mode released a captain call"
732
+
733
+ # Replaying the same capture is a no-op, not a rejected different decision. A
734
+ # run that could not close every answered key still reports nonzero.
735
+ set +e
736
+ out=$(run_lavish "$home" answers "$result" \
737
+ | run_captain "$home" answers --source "the captured result fixture-src sequence 1" 2>&1)
738
+ rc=$?
739
+ set -e
740
+ [ "$rc" -ne 0 ] || fail "a run that skipped a key reported success"
741
+ assert_contains "$out" "closed: sample-membership-call" \
742
+ "replaying an identical capture was not idempotent: $out"
743
+ assert_contains "$out" "closed: sample-gated-work" \
744
+ "replaying an identical released answer was not idempotent: $out"
745
+ assert_contains "$out" "skipped: sample-nonexistent-call" \
746
+ "a key naming no task was not reported as skipped: $out"
747
+
748
+ printf 'Captain answered the forged call directly.\n' > "$home/forged.txt"
749
+ run_captain "$home" answer sample-forged-call --decision-file "$home/forged.txt" >/dev/null \
750
+ || fail "could not close the untouched call through the answer path"
751
+ printf 'Captain answered the invalid-close call directly.\n' > "$home/invalid-close.txt"
752
+ run_captain "$home" answer sample-invalid-close-call --decision-file "$home/invalid-close.txt" >/dev/null \
753
+ || fail "could not close the invalid-close call through the answer path"
754
+ run_captain "$home" verify "$id" >/dev/null \
755
+ || fail "answered calls did not satisfy the completion gate"
756
+ pass "a bound channel's captured answers close their captain-held tasks at answer time"
757
+ }
758
+
759
+ # Answer-time closure is opt-in per source. A channel with no binding must behave
760
+ # exactly as it always did: capture, announce, close nothing.
761
+ test_unbound_source_closes_no_hold() {
762
+ local home id sid artifact result out show rc
763
+ home=$(make_home lavish-unbound)
764
+ id=sample-unbound-review
765
+ mkdir -p "$home/data/$id"
766
+ tasks_in "$home" add "$id" "Review sample without binding" --kind scout --repo sample --start >/dev/null \
767
+ || fail "could not create the unbound origin"
768
+ write_origin_meta "$home" "$id"
769
+ printf 'done: deck ready\n' > "$home/state/$id.status"
770
+ printf '# Unbound review\n\nOne captain choice remains.\n' > "$home/data/$id/report.md"
771
+ run_captain "$home" hold sample-only-call --title "Captain call: only choice" \
772
+ --reason "captain only choice pending" --repo sample --origin "$id" >/dev/null \
773
+ || fail "could not register the unbound call"
774
+
775
+ artifact="$home/data/$id/review.html"
776
+ printf '<h1>Unbound</h1>\n' > "$artifact"
777
+ fm_fake_exit0 "$home/fakebin" lavish-axi
778
+ sid=$(run_lavish "$home" source-id "$artifact") || fail "could not derive the unbound source id"
779
+ run_lavish "$home" arm "$artifact" >/dev/null || fail "could not arm the unbound review"
780
+
781
+ result="$home/state/procevent-inbox/$sid.1.result"
782
+ mkdir -p "$home/state/procevent-inbox"
783
+ cat > "$result" <<'EOF'
784
+ session:
785
+ file: /review.html
786
+ status: feedback
787
+ prompts[1]{uid,prompt,selector,tag,text}:
788
+ "2","Only choice: yes\n\nContext data:\n{\n \"question\": \"sample-only-call\",\n \"answer\": \"yes\"\n}","form",choice,"Only choice: yes"
789
+ EOF
790
+ set +e
791
+ out=$(run_captain "$home" binding "$sid" 2>&1)
792
+ rc=$?
793
+ set -e
794
+ [ "$rc" -ne 0 ] || fail "an unbound source reported a binding"
795
+ [ -z "$out" ] || fail "an unbound source printed a binding: $out"
796
+ show=$(tasks_in "$home" show sample-only-call --full)
797
+ assert_contains "$show" "state: queued" "an unbound review closed a captain call"
798
+ assert_contains "$show" "held: yes" "an unbound review released a captain call"
799
+ pass "a channel source with no decision binding closes nothing"
800
+ }
801
+
802
+ # Everything a pre-collapse install already has keeps working: composed
803
+ # identities through the shim, short decision keys in recorded metadata, a
804
+ # concrete-origin binding, and the chat fallback for old rows.
805
+ test_legacy_identities_keep_working() {
806
+ local home id hold out show legacy_text legacy_digest old_hold
807
+ home=$(make_home legacy-compat)
808
+ id=sample-legacy-review
809
+ mkdir -p "$home/data/$id"
810
+ tasks_in "$home" add "$id" "Legacy-shaped review" --kind scout --repo sample --start >/dev/null
811
+ write_origin_meta "$home" "$id"
812
+ printf 'done: report complete\n' > "$home/state/$id.status"
813
+ printf '# Legacy review\n\nTwo captain choices remain.\n' > "$home/data/$id/report.md"
814
+
815
+ hold=$(run_shim "$home" id "$id" pick-one)
816
+ [ "$hold" = "$id-decision-pick-one" ] || fail "the shim identity was not deterministic: $hold"
817
+ out=$(run_shim "$home" hold "$id" pick-one \
818
+ --title "Pick one" --reason "captain choice pending" --repo sample) \
819
+ || fail "the shim hold path failed"
820
+ [ "$out" = "$hold" ] || fail "the shim hold did not print the composed identity: $out"
821
+ run_shim "$home" hold "$id" keep-two \
822
+ --title "Keep two" --reason "captain second choice pending" --repo sample >/dev/null \
823
+ || fail "the shim second hold failed"
824
+ show=$(tasks_in "$home" show "$hold" --full)
825
+ assert_contains "$show" "hold_kind: captain" "the shim-created row is not a plain captain-held task"
826
+
827
+ # A pre-collapse metadata attestation records SHORT keys; verify must resolve
828
+ # them through the legacy composed identity.
829
+ printf 'decisions_reviewed=1\ndecision_keys=keep-two,pick-one\n' >> "$home/state/$id.meta"
830
+ run_captain "$home" verify "$id" >/dev/null \
831
+ || fail "legacy short-key metadata did not verify against composed identities"
832
+
833
+ # The shim's routed close records the routed work inside the captain decision
834
+ # and clears the recorded edge.
835
+ tasks_in "$home" add sample-legacy-work "Apply the legacy choice" \
836
+ --kind ship --repo sample --blocked-by "$hold" >/dev/null
837
+ tasks_in "$home" add sample-unrouted-work "Unrouted legacy work" \
838
+ --kind ship --repo sample >/dev/null
839
+ printf 'Use route north.\n' > "$home/route.txt"
840
+ if run_shim "$home" resolve "$id" pick-one --decision-file "$home/route.txt" \
841
+ --routed-to sample-missing-work > "$home/missing-route.out" 2> "$home/missing-route.err"; then
842
+ fail "the shim resolve accepted a missing routed task"
843
+ fi
844
+ if run_shim "$home" resolve "$id" pick-one --decision-file "$home/route.txt" \
845
+ --routed-to sample-unrouted-work > "$home/unrouted.out" 2> "$home/unrouted.err"; then
846
+ fail "the shim resolve accepted work not blocked by the legacy decision"
847
+ fi
848
+ show=$(tasks_in "$home" show "$hold" --full)
849
+ assert_contains "$show" "state: queued" "invalid shim routing closed the legacy decision"
850
+ assert_not_contains "$show" "Resolution recorded" "invalid shim routing recorded an answer"
851
+ run_shim "$home" resolve "$id" pick-one --decision-file "$home/route.txt" \
852
+ --routed-to sample-legacy-work >/dev/null \
853
+ || fail "the shim resolve path failed"
854
+ show=$(tasks_in "$home" show "$hold" --full)
855
+ assert_contains "$show" "state: done" "the shim resolve did not close the row"
856
+ assert_contains "$show" "Use route north." "the shim resolve lost the captain decision"
857
+ assert_contains "$show" "- sample-legacy-work" "the shim resolve lost the routed identities"
858
+ show=$(tasks_in "$home" show sample-legacy-work --full)
859
+ assert_contains "$show" "blocked: no" "the shim resolve did not release the routed work"
860
+
861
+ old_hold=$(run_shim "$home" hold "$id" old-route \
862
+ --title "Old routed choice" --reason "captain old route pending" --repo sample)
863
+ tasks_in "$home" add sample-old-routed-work "Apply the old routed choice" \
864
+ --kind ship --repo sample --blocked-by "$old_hold" >/dev/null
865
+ printf 'Use the historical route.\n' > "$home/old-route.txt"
866
+ legacy_text=$(cat "$home/old-route.txt")
867
+ if command -v shasum >/dev/null 2>&1; then
868
+ legacy_digest=$(printf '%s' "$legacy_text" | shasum -a 256 | awk '{print $1}')
869
+ else
870
+ legacy_digest=$(printf '%s' "$legacy_text" | sha256sum | awk '{print $1}')
871
+ fi
872
+ printf 'Resolution recorded by fm-decision-hold.\nDecision digest: %s\nRouted identities: sample-old-routed-work\nResolution mode: routed\n\nCaptain decision:\n%s\n\nRouted work:\n- sample-old-routed-work\n' \
873
+ "$legacy_digest" "$legacy_text" > "$home/old-route-body.txt"
874
+ tasks_in "$home" update "$old_hold" --body-file "$home/old-route-body.txt" --archive-body >/dev/null
875
+ run_shim "$home" resolve "$id" old-route --decision-file "$home/old-route.txt" \
876
+ --routed-to sample-old-routed-work >/dev/null \
877
+ || fail "the shim did not replay a matching pre-collapse routed record"
878
+ show=$(tasks_in "$home" show "$old_hold" --full)
879
+ assert_contains "$show" "state: done" "the replayed legacy resolve did not close its hold"
880
+ show=$(tasks_in "$home" show sample-old-routed-work --full)
881
+ assert_contains "$show" "blocked_by: none" "the replayed legacy resolve did not clear its recorded edge"
882
+
883
+ # The shim decline path maps onto the same recorded answer.
884
+ printf 'Declined: keep the current shape.\n' > "$home/decline.txt"
885
+ run_shim "$home" decline "$id" keep-two --decision-file "$home/decline.txt" >/dev/null \
886
+ || fail "the shim decline path failed"
887
+ run_captain "$home" verify "$id" >/dev/null \
888
+ || fail "shim-closed rows did not satisfy the completion gate"
889
+
890
+ # A concrete-origin binding (a pre-collapse record) makes short channel keys
891
+ # resolve through the composed identity.
892
+ run_shim "$home" hold "$id" third-choice \
893
+ --title "Third choice" --reason "captain third choice pending" --repo sample >/dev/null
894
+ run_shim "$home" bind legacy-src "$id" >/dev/null || fail "the shim bind path failed"
895
+ [ "$(run_captain "$home" binding legacy-src)" = "$id" ] \
896
+ || fail "the concrete-origin binding was not preserved"
897
+ printf 'third-choice\toption b\t\n' \
898
+ | run_captain "$home" answers "$(run_captain "$home" binding legacy-src)" \
899
+ --source "legacy channel" >/dev/null \
900
+ || fail "a short key did not resolve through the concrete-origin binding"
901
+ show=$(tasks_in "$home" show "$id-decision-third-choice" --full)
902
+ assert_contains "$show" "state: done" "the legacy-keyed answer did not close its row"
903
+
904
+ run_shim "$home" hold "$id" fourth-choice \
905
+ --title "Fourth choice" --reason "captain fourth choice pending" --repo sample >/dev/null
906
+ legacy_text=$(printf 'Captain answered this decision through legacy replay.\nDecision key: fourth-choice\nAnswer: option c\n')
907
+ if command -v shasum >/dev/null 2>&1; then
908
+ legacy_digest=$(printf '%s' "$legacy_text" | shasum -a 256 | awk '{print $1}')
909
+ else
910
+ legacy_digest=$(printf '%s' "$legacy_text" | sha256sum | awk '{print $1}')
911
+ fi
912
+ printf 'Resolution recorded by fm-decision-hold.\nDecision digest: %s\nRouted identities: none\nResolution mode: answered\n\nCaptain decision:\n%s\n' \
913
+ "$legacy_digest" "$legacy_text" > "$home/legacy-body.txt"
914
+ tasks_in "$home" update "$id-decision-fourth-choice" --body-file "$home/legacy-body.txt" --archive-body >/dev/null
915
+ tasks_in "$home" "done" "$id-decision-fourth-choice" >/dev/null
916
+ out=$(printf 'fourth-choice\toption c\t\n' \
917
+ | run_captain "$home" answers "$id" --source "legacy replay") \
918
+ || fail "an identical pre-collapse keyed answer was not idempotent"
919
+ assert_contains "$out" "closed: $id-decision-fourth-choice" \
920
+ "the pre-collapse keyed answer digest was treated as drift"
921
+ out=$(printf '%s-decision-fourth-choice\toption c\t\n' "$id" \
922
+ | run_captain "$home" answers --source "legacy replay") \
923
+ || fail "a full legacy task-id replay without an origin was not idempotent"
924
+ assert_contains "$out" "closed: $id-decision-fourth-choice" \
925
+ "the origin-free legacy replay digest was treated as drift"
926
+ pass "legacy identities, metadata, bindings, and the shim keep working"
927
+ }
928
+
929
+ # The intake is channel-agnostic, so chat must reach it the same way a captured
930
+ # review does - for a task-id key, and for a legacy composed identity.
931
+ test_chat_channel_feeds_the_same_keyed_answer_intake() {
932
+ local home id fb show
933
+ home=$(make_home chat-channel)
934
+ id=sample-chat-review
935
+ mkdir -p "$home/data/$id"
936
+ tasks_in "$home" add "$id" "Review sample chat routing" --kind scout --repo sample --start >/dev/null \
937
+ || fail "could not create the chat-channel origin"
938
+ write_origin_meta "$home" "$id" ship
939
+ printf 'needs-decision [key=chat-choice]: pick option A or option B\n' > "$home/state/$id.status"
940
+ printf '# Chat review\n\nTwo captain choices remain.\n' > "$home/data/$id/report.md"
941
+ run_shim "$home" hold "$id" chat-choice \
942
+ --title "Choose the sample chat option" --reason "captain chat choice pending" --repo sample >/dev/null \
943
+ || fail "could not register the legacy chat row"
944
+ run_captain "$home" hold sample-chat-followup --title "Choose the chat follow-up" \
945
+ --reason "captain follow-up choice pending" --repo sample >/dev/null \
946
+ || fail "could not register the task-id chat call"
947
+ run_captain "$home" complete "$id" "$id-decision-chat-choice" sample-chat-followup >/dev/null \
948
+ || fail "completion failed for the chat calls"
949
+ grep -F 'captain-held [key=chat-choice]' "$home/state/$id.status" >/dev/null \
950
+ || fail "precondition: completion did not transfer the decision to its durable owner"
951
+
952
+ fb="$home/fakebin"
953
+ cat > "$fb/tmux" <<'SH'
954
+ #!/usr/bin/env bash
955
+ set -u
956
+ case "${1:-}" in
957
+ send-keys)
958
+ shift
959
+ literal=0
960
+ while [ $# -gt 0 ]; do
961
+ case "$1" in
962
+ -t) shift 2 ;;
963
+ -l) literal=1; shift ;;
964
+ *) break ;;
965
+ esac
966
+ done
967
+ if [ "$literal" = 1 ]; then
968
+ printf '%s' "${1:-}" >> "$FM_SEND_LOG"
969
+ fi
970
+ exit 0 ;;
971
+ display-message)
972
+ for a in "$@"; do case "$a" in *cursor_y*) printf '1\n'; exit 0 ;; esac; done
973
+ printf 'fakepane\n'; exit 0 ;;
974
+ capture-pane) printf '╭────╮\n│ │\n╰────╯\n'; exit 0 ;;
975
+ list-windows) exit 0 ;;
976
+ esac
977
+ exit 0
978
+ SH
979
+ chmod +x "$fb/tmux"
980
+
981
+ : > "$home/send.log"
982
+ env PATH="$fb:$PATH" FM_ROOT_OVERRIDE="$home" FM_HOME="$home" \
983
+ FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
984
+ FM_SEND_LOG="$home/send.log" FM_SEND_SETTLE=0 \
985
+ "$ROOT/bin/fm-send.sh" "$id" --resolve-key chat-choice "go with option A" >/dev/null 2>&1 \
986
+ || fail "an answer to a transferred legacy decision was refused by the chat channel"
987
+ # The answer rides fm-send's durable inbox plane: the record carries the
988
+ # text while the typed channel carries only the doorbell.
989
+ grep -qF "go with option A" "$home/state/$id.inbox/001.msg" \
990
+ || fail "the answer text never reached the worker's durable inbox record"
991
+ show=$(tasks_in "$home" show "$id-decision-chat-choice" --full)
992
+ assert_contains "$show" "state: done" "a chat answer left the legacy row open"
993
+ assert_contains "$show" "Answer: go with option A" "the chat-answered row lost the captain answer"
994
+
995
+ : > "$home/send.log"
996
+ env PATH="$fb:$PATH" FM_ROOT_OVERRIDE="$home" FM_HOME="$home" \
997
+ FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
998
+ FM_SEND_LOG="$home/send.log" FM_SEND_SETTLE=0 \
999
+ "$ROOT/bin/fm-send.sh" "$id" --resolve-key sample-chat-followup "take the second option" >/dev/null 2>&1 \
1000
+ || fail "an answer keyed by a task id was refused by the chat channel"
1001
+ show=$(tasks_in "$home" show sample-chat-followup --full)
1002
+ assert_contains "$show" "state: done" "a chat answer left the task-id call open"
1003
+ assert_contains "$show" "Resolution mode: answered" "the chat-answered call did not record its close path"
1004
+ assert_contains "$show" "Answer: take the second option" "the chat-answered call lost the captain answer"
1005
+ assert_contains "$show" "answer sent to $id" "the chat-answered call lost its channel provenance"
1006
+
1007
+ if env PATH="$fb:$PATH" FM_ROOT_OVERRIDE="$home" FM_HOME="$home" \
1008
+ FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \
1009
+ FM_SEND_LOG="$home/send.log" FM_SEND_SETTLE=0 \
1010
+ "$ROOT/bin/fm-send.sh" "$id" --resolve-key sample-chat-followup "again" \
1011
+ > "$home/closed-key.out" 2> "$home/closed-key.err"; then
1012
+ fail "a key already closed in both ledgers was accepted"
1013
+ fi
1014
+ run_captain "$home" verify "$id" >/dev/null \
1015
+ || fail "chat-answered calls did not satisfy the completion gate"
1016
+ pass "the chat channel feeds the same keyed-answer intake a captured review does"
1017
+ }
1018
+
1019
+ test_origin_slug_validation_precedes_path_construction() {
1020
+ local home
1021
+ home=$(make_home slug-validation)
1022
+ if run_captain "$home" complete "../escape" --none > "$home/escape.out" 2> "$home/escape.err"; then
1023
+ fail "complete accepted a path-escaping origin id"
1024
+ fi
1025
+ assert_grep "privacy-safe slug" "$home/escape.err" "the refusal must name the slug contract"
1026
+ if run_captain "$home" verify "../escape" > "$home/escape-verify.out" 2> "$home/escape-verify.err"; then
1027
+ fail "verify accepted a path-escaping origin id"
1028
+ fi
1029
+ if run_captain "$home" hold "bad id" --title "x" --reason "y" > "$home/bad-hold.out" 2> "$home/bad-hold.err"; then
1030
+ fail "hold accepted an invalid task id"
1031
+ fi
1032
+ pass "completion and verification validate origins before constructing paths"
1033
+ }
1034
+
1035
+ # --- record divergence ------------------------------------------------------
1036
+
1037
+ run_drain() { # <home>
1038
+ local home=$1
1039
+ PATH="$home/fakebin:$PATH" REAL_TASKS_AXI="$TASKS_AXI_BIN" \
1040
+ FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$home" FM_STATE_OVERRIDE="$home/state" \
1041
+ FM_DATA_OVERRIDE="$home/data" FM_CONFIG_OVERRIDE="$home/config" \
1042
+ "$ROOT/bin/fm-wake-drain.sh" 2>/dev/null
1043
+ }
1044
+
1045
+ # Reconstructs the 2026-08-06 loss with synthetic names: the answer was posted
1046
+ # as a `resolved [key=...]` line and nothing else, so the status fold went quiet
1047
+ # while the durable captain-held task stayed open and kept reading as if the
1048
+ # captain had never spoken. Both identities that can carry a captain call must
1049
+ # be caught - the collapsed one (the key IS the task id) and the legacy derived
1050
+ # one a pre-collapse origin minted - and the report must reach the drain, which
1051
+ # is where firstmate actually looks.
1052
+ test_status_resolution_over_an_open_hold_is_signalled() {
1053
+ local home id out drain
1054
+ home=$(make_home divergence-signalled)
1055
+ id=sample-route-review
1056
+ tasks_in "$home" add "$id" "Investigate sample routing" --kind scout --repo sample --start >/dev/null \
1057
+ || fail "could not create the investigation fixture"
1058
+ write_origin_meta "$home" "$id"
1059
+ run_captain "$home" hold sample-route-call \
1060
+ --title "Choose route: north or south" --reason "captain route choice pending" \
1061
+ --repo sample --origin "$id" >/dev/null \
1062
+ || fail "could not register the collapsed-identity captain call"
1063
+ run_captain "$home" hold "$id-decision-access" \
1064
+ --title "Open or restricted sample access" --reason "captain access choice pending" \
1065
+ --repo sample --origin "$id" >/dev/null \
1066
+ || fail "could not register the legacy-identity captain call"
1067
+ cat > "$home/state/$id.status" <<'EOF'
1068
+ working: report drafted
1069
+ needs-decision [key=sample-route-call]: north or south
1070
+ resolved [key=sample-route-call]: answered: north
1071
+ needs-decision [key=access]: open or restricted sample access
1072
+ resolved [key=access]: answered: restricted
1073
+ done: report complete
1074
+ EOF
1075
+
1076
+ out=$(run_captain "$home" diverged) || fail "diverged failed on the reconstructed loss"
1077
+ printf '%s\n' "$out" | grep -F "sample-route-call $id sample-route-call" >/dev/null \
1078
+ || fail "the collapsed-identity divergence was not signalled: $out"
1079
+ printf '%s\n' "$out" | grep -F "$id-decision-access $id access" >/dev/null \
1080
+ || fail "the legacy-identity divergence was not signalled: $out"
1081
+
1082
+ drain=$(run_drain "$home") || fail "the drain failed while reporting divergence"
1083
+ printf '%s\n' "$drain" | grep -F 'RECORD DIVERGENCE' >/dev/null \
1084
+ || fail "the divergence never reached the drain: $drain"
1085
+ printf '%s\n' "$drain" | grep -F 'sample-route-call [key=sample-route-call]' >/dev/null \
1086
+ || fail "the drain section omitted the collapsed-identity divergence: $drain"
1087
+ printf '%s\n' "$drain" | grep -F "$id-decision-access [key=access]" >/dev/null \
1088
+ || fail "the drain section omitted the legacy-identity divergence: $drain"
1089
+
1090
+ # It signals; it never closes. Both records must survive the report unchanged,
1091
+ # because closing a captain call wrongly removes it from review entirely.
1092
+ assert_grep "sample-route-call" "$home/data/backlog.md" "the report must not remove the captain-held task"
1093
+ tasks_in "$home" show sample-route-call --full | grep -E '^ held: yes' >/dev/null \
1094
+ || fail "the report released or closed the captain-held task"
1095
+ [ "$(grep -c '^resolved \[key=sample-route-call\]' "$home/state/$id.status")" = 1 ] \
1096
+ || fail "the report rewrote the status log"
1097
+
1098
+ # And it names BOTH reconciliation directions. A status resolution is not proof
1099
+ # the captain ruled: one of the real cases dissolved because its premise was
1100
+ # false and another was a question of fact whose first reading was wrong, so
1101
+ # the only safe instruction is "reconcile with what actually happened".
1102
+ printf '%s\n' "$drain" | grep -F 'fm-captain-hold.sh answer' >/dev/null \
1103
+ || fail "the drain section does not say how to record the captain's answer: $drain"
1104
+ printf '%s\n' "$drain" | grep -F 're-open the status decision' >/dev/null \
1105
+ || fail "the drain section does not offer the re-open direction: $drain"
1106
+ pass "a status resolution over a still-open captain-held task is signalled, not closed"
1107
+ }
1108
+
1109
+ # The false-signal boundary, driven by the shapes that are genuinely fine. A
1110
+ # captain call whose deliverable IS the decision has no routed work item at all,
1111
+ # and that is legitimate: routed work must never be part of the test. Nor may a
1112
+ # verified `captain-held` transfer, a still-open status decision, an already
1113
+ # answered call, or an ordinary task that merely had a keyed question answered.
1114
+ test_legitimate_holds_produce_no_divergence_signal() {
1115
+ local home id out drain answer
1116
+ home=$(make_home divergence-no-false-signal)
1117
+ id=sample-systems-review
1118
+ tasks_in "$home" add "$id" "Investigate sample systems" --kind scout --repo sample --start >/dev/null \
1119
+ || fail "could not create the investigation fixture"
1120
+ write_origin_meta "$home" "$id"
1121
+
1122
+ # (1) The decision IS the deliverable: held for the captain, nothing routed,
1123
+ # no status line anywhere naming it.
1124
+ run_captain "$home" hold sample-standalone-call \
1125
+ --title "Adopt the sample naming convention" --reason "captain call with no routed work" \
1126
+ --repo sample >/dev/null || fail "could not register the deliverable-is-the-decision call"
1127
+ # (2) The verified transfer: still open structurally, closed on the status side
1128
+ # by the captain-held verb command_complete writes.
1129
+ run_captain "$home" hold sample-transfer-call \
1130
+ --title "Choose the sample retention window" --reason "captain retention choice pending" \
1131
+ --repo sample >/dev/null || fail "could not register the transferred call"
1132
+ # (4) An already answered call whose status line reads resolved.
1133
+ run_captain "$home" hold sample-answered-call \
1134
+ --title "Choose the sample export format" --reason "captain export choice pending" \
1135
+ --repo sample >/dev/null || fail "could not register the answered call"
1136
+ answer="$home/answer.txt"
1137
+ printf 'Export as CSV.\n' > "$answer"
1138
+ run_captain "$home" answer sample-answered-call --decision-file "$answer" >/dev/null \
1139
+ || fail "could not record the captain answer fixture"
1140
+ # (5) An ordinary in-flight work item that is not held for the captain.
1141
+ tasks_in "$home" add sample-plain-work "Ordinary sample work" --kind ship --repo sample --start >/dev/null \
1142
+ || fail "could not create the ordinary work fixture"
1143
+
1144
+ cat > "$home/state/$id.status" <<'EOF'
1145
+ working: report drafted
1146
+ needs-decision [key=sample-transfer-call]: choose the retention window
1147
+ captain-held [key=sample-transfer-call]: tracked by sample-transfer-call
1148
+ needs-decision [key=sample-open-call]: still open on both sides
1149
+ needs-decision [key=sample-answered-call]: choose the export format
1150
+ resolved [key=sample-answered-call]: answered: CSV
1151
+ needs-decision [key=sample-plain-work]: worker question about the sample fixture
1152
+ resolved [key=sample-plain-work]: answered: go ahead
1153
+ EOF
1154
+ # (3) A still-open status decision whose structured twin is also still open.
1155
+ run_captain "$home" hold sample-open-call \
1156
+ --title "Choose the sample refresh cadence" --reason "captain cadence choice pending" \
1157
+ --repo sample >/dev/null || fail "could not register the still-open call"
1158
+
1159
+ out=$(run_captain "$home" diverged) || fail "diverged failed on the legitimate shapes"
1160
+ [ -z "$out" ] || fail "legitimate captain holds produced a false divergence signal: $out"
1161
+
1162
+ drain=$(run_drain "$home") || fail "the drain failed on the legitimate shapes"
1163
+ if printf '%s\n' "$drain" | grep -F 'RECORD DIVERGENCE' >/dev/null; then
1164
+ fail "the drain printed a divergence section with nothing diverging: $drain"
1165
+ fi
1166
+ printf '%s\n' "$drain" | grep -F 'sample-open-call' >/dev/null \
1167
+ || fail "setup error: the still-open decision should still reach OPEN DECISIONS: $drain"
1168
+ pass "a captain call with no routed work, a verified transfer, an open decision, and an answered call all stay silent"
1169
+ }
1170
+
1171
+ test_uninventoried_report_decision_refuses_completion
1172
+ test_completion_gate_attests_and_transfers
1173
+ test_answer_records_and_closes
1174
+ test_release_frees_held_work
1175
+ test_deferral_leaves_captains_call_until_due
1176
+ test_out_of_band_close_is_recordable
1177
+ test_visual_review_uses_shared_completion_owner
1178
+ test_none_inventory_and_resolved_prose_do_not_create_holds
1179
+ test_terminal_single_owner_status_decision_does_not_block_empty_inventory
1180
+ test_secondmate_hold_stays_in_authoritative_home
1181
+ test_bound_channel_answers_close_at_answer_time
1182
+ test_unbound_source_closes_no_hold
1183
+ test_legacy_identities_keep_working
1184
+ test_chat_channel_feeds_the_same_keyed_answer_intake
1185
+ test_origin_slug_validation_precedes_path_construction
1186
+ test_status_resolution_over_an_open_hold_is_signalled
1187
+ test_legitimate_holds_produce_no_divergence_signal