@zerwiz/ymir 0.1.4 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (865) hide show
  1. package/.agents/README.md +291 -0
  2. package/.agents/agents/bragi-marketer.md +111 -0
  3. package/.agents/agents/brokk.md +352 -0
  4. package/.agents/agents/forseti-reviewer.md +93 -0
  5. package/.agents/agents/frigg-consent.md +77 -0
  6. package/.agents/agents/groa-update.md +77 -0
  7. package/.agents/agents/hnoss-designer.md +69 -0
  8. package/.agents/agents/huginn-researcher.md +99 -0
  9. package/.agents/agents/jord-projects.md +78 -0
  10. package/.agents/agents/kvasir-scout.md +91 -0
  11. package/.agents/agents/mimir-planner.md +92 -0
  12. package/.agents/agents/muninn-stow.md +77 -0
  13. package/.agents/agents/saga-bearings.md +78 -0
  14. package/.agents/agents/sindri-developer.md +114 -0
  15. package/.agents/agents/snotra-documenter.md +105 -0
  16. package/.agents/agents/syn-recovery.md +78 -0
  17. package/.agents/agents/tyr-check.md +78 -0
  18. package/.agents/agents/urdh-hold.md +77 -0
  19. package/.agents/agents/volundr-smith.md +80 -0
  20. package/.agents/agents/vor-diagnostics.md +77 -0
  21. package/.agents/assets/agents/naming.md +50 -0
  22. package/.agents/assets/agents/registry.md +93 -0
  23. package/.agents/assets/agents/runtime.md +36 -0
  24. package/.agents/assets/agents/toon-tasks-cli.md +42 -0
  25. package/.agents/assets/templates/company_entity.template.md +37 -0
  26. package/.agents/backend/README.md +74 -0
  27. package/.agents/backend/backends/cmux.sh +649 -0
  28. package/.agents/backend/backends/herdr-eventwait.py +157 -0
  29. package/.agents/backend/backends/herdr-workspace-move.py +114 -0
  30. package/.agents/backend/backends/herdr.sh +3386 -0
  31. package/.agents/backend/backends/orca.sh +290 -0
  32. package/.agents/backend/backends/tmux.sh +357 -0
  33. package/.agents/backend/backends/zellij.sh +700 -0
  34. package/.agents/backend/fm-afk-launch.sh +654 -0
  35. package/.agents/backend/fm-afk-return.sh +241 -0
  36. package/.agents/backend/fm-afk-start.sh +172 -0
  37. package/.agents/backend/fm-arm-command-policy.mjs +960 -0
  38. package/.agents/backend/fm-arm-pretool-check.sh +200 -0
  39. package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
  40. package/.agents/backend/fm-backend.sh +981 -0
  41. package/.agents/backend/fm-backlog-handoff.sh +909 -0
  42. package/.agents/backend/fm-backlog-receive.sh +187 -0
  43. package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
  44. package/.agents/backend/fm-bearings-board.sh +204 -0
  45. package/.agents/backend/fm-bearings-snapshot.sh +546 -0
  46. package/.agents/backend/fm-bootstrap.sh +1568 -0
  47. package/.agents/backend/fm-branch-outcome.sh +203 -0
  48. package/.agents/backend/fm-branch-prompt.sh +101 -0
  49. package/.agents/backend/fm-brief.sh +452 -0
  50. package/.agents/backend/fm-busy-event.sh +234 -0
  51. package/.agents/backend/fm-busy-lib.sh +986 -0
  52. package/.agents/backend/fm-captain-hold.sh +1001 -0
  53. package/.agents/backend/fm-cd-command-policy.mjs +153 -0
  54. package/.agents/backend/fm-cd-pretool-check.sh +190 -0
  55. package/.agents/backend/fm-check-lib.sh +74 -0
  56. package/.agents/backend/fm-check-register.sh +42 -0
  57. package/.agents/backend/fm-check-unregister.sh +52 -0
  58. package/.agents/backend/fm-classify-lib.sh +1770 -0
  59. package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
  60. package/.agents/backend/fm-composer-lib.sh +1416 -0
  61. package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
  62. package/.agents/backend/fm-config-push.sh +254 -0
  63. package/.agents/backend/fm-control-lib.sh +251 -0
  64. package/.agents/backend/fm-control.sh +878 -0
  65. package/.agents/backend/fm-crew-state.sh +625 -0
  66. package/.agents/backend/fm-cursor-lib.sh +243 -0
  67. package/.agents/backend/fm-decision-hold.sh +232 -0
  68. package/.agents/backend/fm-doc-audience-check.sh +269 -0
  69. package/.agents/backend/fm-dod-lib.sh +67 -0
  70. package/.agents/backend/fm-ensure-agents-md.sh +253 -0
  71. package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
  72. package/.agents/backend/fm-extension.mjs +2577 -0
  73. package/.agents/backend/fm-extension.sh +16 -0
  74. package/.agents/backend/fm-ff-lib.sh +420 -0
  75. package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
  76. package/.agents/backend/fm-fleet-sync.sh +458 -0
  77. package/.agents/backend/fm-fleet-view.sh +96 -0
  78. package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
  79. package/.agents/backend/fm-guard.sh +242 -0
  80. package/.agents/backend/fm-harness.sh +195 -0
  81. package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
  82. package/.agents/backend/fm-herdr-lab.sh +346 -0
  83. package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
  84. package/.agents/backend/fm-home-seed.sh +984 -0
  85. package/.agents/backend/fm-home-summary-refresh.sh +255 -0
  86. package/.agents/backend/fm-hook-host-lib.sh +36 -0
  87. package/.agents/backend/fm-inactive-reconcile.sh +531 -0
  88. package/.agents/backend/fm-inbox.sh +399 -0
  89. package/.agents/backend/fm-install-actionlint.sh +84 -0
  90. package/.agents/backend/fm-install-herdr.sh +96 -0
  91. package/.agents/backend/fm-install-shellcheck.sh +84 -0
  92. package/.agents/backend/fm-install-treehouse.sh +95 -0
  93. package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
  94. package/.agents/backend/fm-lease-lib.sh +218 -0
  95. package/.agents/backend/fm-lease.sh +189 -0
  96. package/.agents/backend/fm-line-cap-lib.sh +51 -0
  97. package/.agents/backend/fm-lint-workflows.sh +137 -0
  98. package/.agents/backend/fm-lint.sh +591 -0
  99. package/.agents/backend/fm-lock-lib.sh +104 -0
  100. package/.agents/backend/fm-lock.sh +107 -0
  101. package/.agents/backend/fm-marker-lib.sh +12 -0
  102. package/.agents/backend/fm-merge-local.sh +74 -0
  103. package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
  104. package/.agents/backend/fm-nm-run-lib.sh +124 -0
  105. package/.agents/backend/fm-on.sh +125 -0
  106. package/.agents/backend/fm-operational-input.sh +253 -0
  107. package/.agents/backend/fm-peek.sh +46 -0
  108. package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
  109. package/.agents/backend/fm-pr-check.sh +135 -0
  110. package/.agents/backend/fm-pr-lib.sh +1016 -0
  111. package/.agents/backend/fm-pr-merge.sh +707 -0
  112. package/.agents/backend/fm-pr-poll.sh +110 -0
  113. package/.agents/backend/fm-primary-scope-lib.sh +33 -0
  114. package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
  115. package/.agents/backend/fm-procevent-lavish.sh +628 -0
  116. package/.agents/backend/fm-procevent-lib.sh +892 -0
  117. package/.agents/backend/fm-procevent-quota.sh +290 -0
  118. package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
  119. package/.agents/backend/fm-procevent-when.sh +504 -0
  120. package/.agents/backend/fm-procevent.sh +1466 -0
  121. package/.agents/backend/fm-project-mode.sh +92 -0
  122. package/.agents/backend/fm-project-origin-lib.sh +180 -0
  123. package/.agents/backend/fm-promote.sh +282 -0
  124. package/.agents/backend/fm-public-followup-emit.sh +260 -0
  125. package/.agents/backend/fm-public-followup-lib.sh +398 -0
  126. package/.agents/backend/fm-public-followup.sh +1330 -0
  127. package/.agents/backend/fm-push-transition-lib.sh +168 -0
  128. package/.agents/backend/fm-quota-axi-lib.sh +93 -0
  129. package/.agents/backend/fm-quota-choose.sh +384 -0
  130. package/.agents/backend/fm-remote-delta-read.sh +189 -0
  131. package/.agents/backend/fm-remote-doctor.sh +800 -0
  132. package/.agents/backend/fm-remote-entrypoint.sh +193 -0
  133. package/.agents/backend/fm-remote-file.sh +234 -0
  134. package/.agents/backend/fm-remote-home-provision.sh +259 -0
  135. package/.agents/backend/fm-remote-home-seed.sh +259 -0
  136. package/.agents/backend/fm-remote-inherit-push.sh +90 -0
  137. package/.agents/backend/fm-remote-inherit.sh +179 -0
  138. package/.agents/backend/fm-remote-job-lib.sh +1241 -0
  139. package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
  140. package/.agents/backend/fm-remote-job-worker.sh +1124 -0
  141. package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
  142. package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
  143. package/.agents/backend/fm-review-diff.sh +158 -0
  144. package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
  145. package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
  146. package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
  147. package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
  148. package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
  149. package/.agents/backend/fm-secondmate-report.sh +88 -0
  150. package/.agents/backend/fm-send.sh +1059 -0
  151. package/.agents/backend/fm-session-lock-lib.sh +176 -0
  152. package/.agents/backend/fm-session-start.sh +970 -0
  153. package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
  154. package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
  155. package/.agents/backend/fm-sessionstart-run.sh +145 -0
  156. package/.agents/backend/fm-spawn.sh +3153 -0
  157. package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
  158. package/.agents/backend/fm-startup-memory-budget.sh +94 -0
  159. package/.agents/backend/fm-startup-network.sh +642 -0
  160. package/.agents/backend/fm-stow-cascade.sh +251 -0
  161. package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
  162. package/.agents/backend/fm-supervise-daemon.sh +1732 -0
  163. package/.agents/backend/fm-supervision-instructions.sh +215 -0
  164. package/.agents/backend/fm-supervision-lib.sh +90 -0
  165. package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
  166. package/.agents/backend/fm-tangle-lib.sh +53 -0
  167. package/.agents/backend/fm-task-inbox-lib.sh +402 -0
  168. package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
  169. package/.agents/backend/fm-teardown.sh +2922 -0
  170. package/.agents/backend/fm-test-isolation-proof.sh +588 -0
  171. package/.agents/backend/fm-test-run.sh +2191 -0
  172. package/.agents/backend/fm-timeout-lib.sh +141 -0
  173. package/.agents/backend/fm-timing-lib.sh +179 -0
  174. package/.agents/backend/fm-tmux-lib.sh +291 -0
  175. package/.agents/backend/fm-tool-update-check.sh +898 -0
  176. package/.agents/backend/fm-trace-context-lib.sh +227 -0
  177. package/.agents/backend/fm-transition-lib.sh +103 -0
  178. package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
  179. package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
  180. package/.agents/backend/fm-turnend-guard.sh +432 -0
  181. package/.agents/backend/fm-update.sh +110 -0
  182. package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
  183. package/.agents/backend/fm-voice-client.py +1373 -0
  184. package/.agents/backend/fm-voice-relay.py +1256 -0
  185. package/.agents/backend/fm-wake-drain.sh +592 -0
  186. package/.agents/backend/fm-wake-grant.sh +127 -0
  187. package/.agents/backend/fm-wake-lib.sh +1849 -0
  188. package/.agents/backend/fm-watch-arm.sh +602 -0
  189. package/.agents/backend/fm-watch-checkpoint.sh +109 -0
  190. package/.agents/backend/fm-watch.sh +1962 -0
  191. package/.agents/backend/fm-x-dismiss.sh +116 -0
  192. package/.agents/backend/fm-x-followup.sh +286 -0
  193. package/.agents/backend/fm-x-lib.sh +1000 -0
  194. package/.agents/backend/fm-x-link.sh +234 -0
  195. package/.agents/backend/fm-x-poll.sh +185 -0
  196. package/.agents/backend/fm-x-reply.sh +385 -0
  197. package/.agents/backend/fm_voice_frame.py +166 -0
  198. package/.agents/backend/fm_voice_records.py +574 -0
  199. package/.agents/backend/model-bridge.py +360 -0
  200. package/.agents/backend/opencode-go-bridge.py +30 -0
  201. package/.agents/bus/README.md +12 -0
  202. package/.agents/bus/messages.json +1 -0
  203. package/.agents/config/agents.machine.example.yaml +16 -0
  204. package/.agents/config/agents.yaml.example +72 -0
  205. package/.agents/config/crew-dispatch.json +22 -0
  206. package/.agents/config/cron.yaml +11 -0
  207. package/.agents/config/eindri-dispatch.json +67 -0
  208. package/.agents/config/eindri-harness +1 -0
  209. package/.agents/config/startup-memory-budget +1 -0
  210. package/.agents/filebrowser/.filebrowser.json +6 -0
  211. package/.agents/filebrowser/README.md +11 -0
  212. package/.agents/gateway/README.md +12 -0
  213. package/.agents/github/README.md +15 -0
  214. package/.agents/github/webhooks/.gitkeep +0 -0
  215. package/.agents/github/workflows/.gitkeep +0 -0
  216. package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
  217. package/.agents/harness/opencode/plugins/package.json +4 -0
  218. package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
  219. package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
  220. package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
  221. package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
  222. package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
  223. package/.agents/memory/README.md +46 -0
  224. package/.agents/memory/runes_audit.md +5 -0
  225. package/.agents/migrations/0001-hodd-layout.sh +38 -0
  226. package/.agents/migrations/0002-a2a-mcp.sh +27 -0
  227. package/.agents/migrations/0003-private-data-separation.sh +121 -0
  228. package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
  229. package/.agents/migrations/0005-roots-out-of-tree.sh +158 -0
  230. package/.agents/sandbox/Dockerfile.utgard +19 -0
  231. package/.agents/sandbox/README.md +14 -0
  232. package/.agents/sandbox/utgard.config.json +14 -0
  233. package/.agents/skills/README.md +59 -0
  234. package/.agents/skills/bragi-marketing/README.md +3 -0
  235. package/.agents/skills/bragi-marketing/SKILL.md +82 -0
  236. package/.agents/skills/eindri-homes/README.md +3 -0
  237. package/.agents/skills/eindri-homes/SKILL.md +291 -0
  238. package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
  239. package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
  240. package/.agents/skills/frigg-consent/README.md +3 -0
  241. package/.agents/skills/frigg-consent/SKILL.md +56 -0
  242. package/.agents/skills/galdr-ymirsystem/README.md +3 -0
  243. package/.agents/skills/galdr-ymirsystem/SKILL.md +159 -0
  244. package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
  245. package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
  246. package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
  247. package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
  248. package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
  249. package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
  250. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +736 -0
  251. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
  252. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
  253. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
  254. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
  255. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
  256. package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +539 -0
  257. package/.agents/skills/galdr-ymirsystem/assets/installation.md +763 -0
  258. package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
  259. package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
  260. package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +163 -0
  261. package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +415 -0
  262. package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
  263. package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +133 -0
  264. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
  265. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
  266. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +101 -0
  267. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
  268. package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
  269. package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
  270. package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
  271. package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
  272. package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
  273. package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
  274. package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
  275. package/.agents/skills/galdr-ymirsystem/assets/smidja.md +180 -0
  276. package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
  277. package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
  278. package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
  279. package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +503 -0
  280. package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
  281. package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
  282. package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
  283. package/.agents/skills/gjallarhorn-relay/README.md +3 -0
  284. package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
  285. package/.agents/skills/groa-update/SKILL.md +90 -0
  286. package/.agents/skills/hamr-adapters/README.md +3 -0
  287. package/.agents/skills/hamr-adapters/SKILL.md +123 -0
  288. package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
  289. package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
  290. package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
  291. package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
  292. package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
  293. package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
  294. package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
  295. package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
  296. package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
  297. package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
  298. package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
  299. package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
  300. package/.agents/skills/herdr-panes/README.md +3 -0
  301. package/.agents/skills/herdr-panes/SKILL.md +92 -0
  302. package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
  303. package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
  304. package/.agents/skills/hnoss-design/DESIGN.md +19 -0
  305. package/.agents/skills/hnoss-design/README.md +3 -0
  306. package/.agents/skills/hnoss-design/SKILL.md +83 -0
  307. package/.agents/skills/hvild-afk/README.md +3 -0
  308. package/.agents/skills/hvild-afk/SKILL.md +241 -0
  309. package/.agents/skills/jord-projects/README.md +3 -0
  310. package/.agents/skills/jord-projects/SKILL.md +92 -0
  311. package/.agents/skills/lifecycle/SKILL.md +56 -0
  312. package/.agents/skills/lifecycle/smoke_test.sh +57 -0
  313. package/.agents/skills/lifecycle/start.sh +21 -0
  314. package/.agents/skills/lifecycle/status.sh +35 -0
  315. package/.agents/skills/lifecycle/stop.sh +16 -0
  316. package/.agents/skills/muninn-stow/README.md +3 -0
  317. package/.agents/skills/muninn-stow/SKILL.md +309 -0
  318. package/.agents/skills/no-mistakes/SKILL.md +379 -0
  319. package/.agents/skills/nornir-schedule/README.md +3 -0
  320. package/.agents/skills/nornir-schedule/SKILL.md +25 -0
  321. package/.agents/skills/nornir-schedule/assets/events.md +137 -0
  322. package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
  323. package/.agents/skills/nsr-compliance/README.md +3 -0
  324. package/.agents/skills/nsr-compliance/SKILL.md +29 -0
  325. package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
  326. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
  327. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
  328. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
  329. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
  330. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
  331. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
  332. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
  333. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
  334. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
  335. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
  336. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
  337. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
  338. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
  339. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
  340. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
  341. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
  342. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
  343. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
  344. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
  345. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
  346. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
  347. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
  348. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  349. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
  350. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
  351. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
  352. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
  353. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
  354. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
  355. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
  356. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
  357. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
  358. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
  359. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
  360. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
  361. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
  362. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
  363. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
  364. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
  365. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
  366. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
  367. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
  368. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
  369. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
  370. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
  371. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
  372. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
  373. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
  374. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
  375. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
  376. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
  377. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
  378. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
  379. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
  380. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
  381. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
  382. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
  383. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
  384. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
  385. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
  386. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
  387. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
  388. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
  389. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
  390. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
  391. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
  392. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
  393. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
  394. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
  395. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
  396. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
  397. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
  398. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
  399. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
  400. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
  401. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
  402. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
  403. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
  404. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
  405. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
  406. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
  407. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
  408. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
  409. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
  410. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
  411. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
  412. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
  413. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
  414. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
  415. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
  416. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
  417. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
  418. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
  419. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
  420. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
  421. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
  422. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
  423. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
  424. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
  425. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
  426. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
  427. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
  428. package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
  429. package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
  430. package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
  431. package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
  432. package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
  433. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
  434. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
  435. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
  436. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
  437. package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
  438. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
  439. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
  440. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
  441. package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
  442. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
  443. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
  444. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
  445. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
  446. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
  447. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
  448. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
  449. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  450. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
  451. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
  452. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
  453. package/.agents/skills/pr-ops/README.md +3 -0
  454. package/.agents/skills/pr-ops/SKILL.md +109 -0
  455. package/.agents/skills/ratatoskr-a2a/README.md +3 -0
  456. package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
  457. package/.agents/skills/rules-check-drift/SKILL.md +81 -0
  458. package/.agents/skills/saga-bearings/README.md +3 -0
  459. package/.agents/skills/saga-bearings/SKILL.md +26 -0
  460. package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
  461. package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
  462. package/.agents/skills/saga-bearings/assets/recap.md +56 -0
  463. package/.agents/skills/syn-recovery/README.md +3 -0
  464. package/.agents/skills/syn-recovery/SKILL.md +54 -0
  465. package/.agents/skills/tyr-check/README.md +3 -0
  466. package/.agents/skills/tyr-check/SKILL.md +218 -0
  467. package/.agents/skills/urdh-hold/README.md +3 -0
  468. package/.agents/skills/urdh-hold/SKILL.md +25 -0
  469. package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
  470. package/.agents/skills/urdh-hold/assets/hold.md +54 -0
  471. package/.agents/skills/vor-diagnostics/README.md +3 -0
  472. package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
  473. package/.agents/skills/ymir-host/README.md +3 -0
  474. package/.agents/skills/ymir-host/SKILL.md +35 -0
  475. package/.agents/skills/ymir-host/assets/desktop.md +36 -0
  476. package/.agents/skills/ymir-host/assets/install.md +69 -0
  477. package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
  478. package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
  479. package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
  480. package/.agents/skills/ymir-host/assets/update.md +64 -0
  481. package/.agents/tests/assets/board-render-harness.mjs +117 -0
  482. package/.agents/tests/cmux-test-safety.sh +51 -0
  483. package/.agents/tests/fixtures.sh +291 -0
  484. package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
  485. package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
  486. package/.agents/tests/fm-afk-launch.test.sh +961 -0
  487. package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
  488. package/.agents/tests/fm-afk-return.test.sh +283 -0
  489. package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
  490. package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
  491. package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
  492. package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
  493. package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
  494. package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
  495. package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
  496. package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
  497. package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
  498. package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
  499. package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
  500. package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
  501. package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
  502. package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
  503. package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
  504. package/.agents/tests/fm-backend-orca.test.sh +1357 -0
  505. package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
  506. package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
  507. package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
  508. package/.agents/tests/fm-backend.test.sh +1142 -0
  509. package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
  510. package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
  511. package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
  512. package/.agents/tests/fm-bearings-board.test.sh +414 -0
  513. package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
  514. package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
  515. package/.agents/tests/fm-bootstrap.test.sh +1178 -0
  516. package/.agents/tests/fm-branch-supervision.test.sh +552 -0
  517. package/.agents/tests/fm-brief.test.sh +785 -0
  518. package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
  519. package/.agents/tests/fm-busy-state.test.sh +465 -0
  520. package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
  521. package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
  522. package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
  523. package/.agents/tests/fm-check-unregister.test.sh +198 -0
  524. package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
  525. package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
  526. package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
  527. package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
  528. package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
  529. package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
  530. package/.agents/tests/fm-composer-ghost.test.sh +716 -0
  531. package/.agents/tests/fm-composer-lib.test.sh +666 -0
  532. package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
  533. package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
  534. package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
  535. package/.agents/tests/fm-control.test.sh +910 -0
  536. package/.agents/tests/fm-crew-state.test.sh +1610 -0
  537. package/.agents/tests/fm-cursor-harness.test.sh +404 -0
  538. package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
  539. package/.agents/tests/fm-cursor-primary.test.sh +708 -0
  540. package/.agents/tests/fm-daemon.test.sh +2740 -0
  541. package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
  542. package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
  543. package/.agents/tests/fm-extension-binding.test.sh +2187 -0
  544. package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
  545. package/.agents/tests/fm-fleet-sync.test.sh +721 -0
  546. package/.agents/tests/fm-gate-refuse.test.sh +356 -0
  547. package/.agents/tests/fm-gitignore-config.test.sh +89 -0
  548. package/.agents/tests/fm-gotmp.test.sh +230 -0
  549. package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
  550. package/.agents/tests/fm-grok-harness.test.sh +117 -0
  551. package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
  552. package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
  553. package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
  554. package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
  555. package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
  556. package/.agents/tests/fm-herdr-lab.test.sh +243 -0
  557. package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
  558. package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
  559. package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
  560. package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
  561. package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
  562. package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
  563. package/.agents/tests/fm-kimi-harness.test.sh +687 -0
  564. package/.agents/tests/fm-lint-workflows.test.sh +532 -0
  565. package/.agents/tests/fm-lint.test.sh +1023 -0
  566. package/.agents/tests/fm-muse-harness.test.sh +932 -0
  567. package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
  568. package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
  569. package/.agents/tests/fm-on.test.sh +507 -0
  570. package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
  571. package/.agents/tests/fm-operational-input.test.sh +160 -0
  572. package/.agents/tests/fm-peek-remote.test.sh +110 -0
  573. package/.agents/tests/fm-pending-reply.test.sh +1288 -0
  574. package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
  575. package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
  576. package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
  577. package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
  578. package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
  579. package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
  580. package/.agents/tests/fm-pr-merge.test.sh +2135 -0
  581. package/.agents/tests/fm-procevent-quota.test.sh +225 -0
  582. package/.agents/tests/fm-procevent-when.test.sh +406 -0
  583. package/.agents/tests/fm-procevent.test.sh +1745 -0
  584. package/.agents/tests/fm-project-origin.test.sh +110 -0
  585. package/.agents/tests/fm-public-followup.test.sh +2272 -0
  586. package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
  587. package/.agents/tests/fm-quota-choose.test.sh +626 -0
  588. package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
  589. package/.agents/tests/fm-remote-doctor.test.sh +625 -0
  590. package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
  591. package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
  592. package/.agents/tests/fm-remote-job.test.sh +768 -0
  593. package/.agents/tests/fm-remote-reply.test.sh +520 -0
  594. package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
  595. package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
  596. package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
  597. package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
  598. package/.agents/tests/fm-review-diff.test.sh +176 -0
  599. package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
  600. package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
  601. package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
  602. package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
  603. package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
  604. package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
  605. package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
  606. package/.agents/tests/fm-send-inbox.test.sh +352 -0
  607. package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
  608. package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
  609. package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
  610. package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
  611. package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
  612. package/.agents/tests/fm-send-settle.test.sh +147 -0
  613. package/.agents/tests/fm-send-strict.test.sh +241 -0
  614. package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
  615. package/.agents/tests/fm-session-start.test.sh +2515 -0
  616. package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
  617. package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
  618. package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
  619. package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
  620. package/.agents/tests/fm-spawn-batch.test.sh +148 -0
  621. package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
  622. package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
  623. package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
  624. package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
  625. package/.agents/tests/fm-startup-network.test.sh +712 -0
  626. package/.agents/tests/fm-stow-cascade.test.sh +370 -0
  627. package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
  628. package/.agents/tests/fm-supervision-events.test.sh +156 -0
  629. package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
  630. package/.agents/tests/fm-tangle-guard.test.sh +277 -0
  631. package/.agents/tests/fm-task-delivery.test.sh +420 -0
  632. package/.agents/tests/fm-task-inbox.test.sh +513 -0
  633. package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
  634. package/.agents/tests/fm-teardown.test.sh +2665 -0
  635. package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
  636. package/.agents/tests/fm-test-fixtures.test.sh +132 -0
  637. package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
  638. package/.agents/tests/fm-test-run.test.sh +1196 -0
  639. package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
  640. package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
  641. package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
  642. package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
  643. package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
  644. package/.agents/tests/fm-transition-lib.test.sh +54 -0
  645. package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
  646. package/.agents/tests/fm-update.test.sh +304 -0
  647. package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
  648. package/.agents/tests/fm-voice-relay.test.sh +4741 -0
  649. package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
  650. package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
  651. package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
  652. package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
  653. package/.agents/tests/fm-wake-queue.test.sh +1224 -0
  654. package/.agents/tests/fm-watch-arm.test.sh +821 -0
  655. package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
  656. package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
  657. package/.agents/tests/fm-watch-triage.test.sh +3889 -0
  658. package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
  659. package/.agents/tests/fm-x-mode.test.sh +3042 -0
  660. package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
  661. package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
  662. package/.agents/tests/herdr-test-safety.sh +42 -0
  663. package/.agents/tests/lib.sh +315 -0
  664. package/.agents/tests/remote-herdr-fixture.sh +125 -0
  665. package/.agents/tests/secondmate-helpers.sh +193 -0
  666. package/.agents/tests/smoke.test.sh +93 -0
  667. package/.agents/tests/wake-helpers.sh +338 -0
  668. package/.agents/tests/zellij-test-safety.sh +60 -0
  669. package/.agents/tools/README.md +10 -0
  670. package/.agents/tools/bin/tasks-cli +267 -0
  671. package/.agents/tools/tasks-cli.ts +216 -0
  672. package/AGENTS.md +498 -0
  673. package/CHANGELOG.md +1913 -0
  674. package/LICENSE +1 -1
  675. package/NOTICE +63 -0
  676. package/README.md +114 -60
  677. package/RULES/01-domains.md +51 -0
  678. package/RULES/02-agents.md +78 -0
  679. package/RULES/03-houses.md +61 -0
  680. package/RULES/04-hoard.md +97 -0
  681. package/RULES/05-platforms.md +67 -0
  682. package/RULES/06-append-only.md +108 -0
  683. package/RULES/07-config.md +101 -0
  684. package/RULES/08-delivery-gate.md +75 -0
  685. package/RULES/09-electron.md +58 -0
  686. package/RULES/README.md +22 -0
  687. package/bin/README.md +107 -0
  688. package/bin/a2a-mcp.sh +133 -0
  689. package/bin/a2a-serve.py +103 -0
  690. package/bin/a2a-serve.sh +8 -0
  691. package/bin/a2a-talk.sh +50 -0
  692. package/bin/a2abridge-ensure.sh +108 -0
  693. package/bin/agent-run.sh +43 -0
  694. package/bin/agents-config.sh +256 -0
  695. package/bin/apodex-smoke-test.sh +105 -0
  696. package/bin/bifrost-bridge.sh +131 -0
  697. package/bin/bifrost-ingress.sh +77 -0
  698. package/bin/bootstrap-macos.sh +112 -0
  699. package/bin/bootstrap-windows.ps1 +112 -0
  700. package/bin/branch-guard.sh +64 -0
  701. package/bin/brokk +73 -0
  702. package/bin/brokk-classify-lib.sh +1770 -0
  703. package/bin/brokk-lease-lib.sh +222 -0
  704. package/bin/brokk-lease.sh +193 -0
  705. package/bin/brokk-lint.sh +65 -0
  706. package/bin/brokk-send.sh +51 -0
  707. package/bin/brokk-update.sh +9 -0
  708. package/bin/brokk-wake-grant.sh +127 -0
  709. package/bin/brokk-wake-lib.sh +1849 -0
  710. package/bin/changelog-assemble.sh +154 -0
  711. package/bin/changelog-guard.sh +197 -0
  712. package/bin/crash-sense.sh +173 -0
  713. package/bin/design-check.sh +80 -0
  714. package/bin/design-icon.sh +153 -0
  715. package/bin/desktop-place.sh +224 -0
  716. package/bin/docs-guard.sh +39 -0
  717. package/bin/editor-place.sh +225 -0
  718. package/bin/eindri-acclaim.sh +47 -0
  719. package/bin/eindri-control.sh +28 -0
  720. package/bin/eindri-role.sh +83 -0
  721. package/bin/eindri-seat.sh +83 -0
  722. package/bin/eindri-seen.sh +54 -0
  723. package/bin/eindri-send.sh +15 -0
  724. package/bin/eindri-start.sh +98 -0
  725. package/bin/eindri-watch.sh +57 -0
  726. package/bin/einherjar-spawn.sh +576 -0
  727. package/bin/eir-doctor.sh +158 -0
  728. package/bin/erindi-brief.sh +271 -0
  729. package/bin/feature-inventory.sh +73 -0
  730. package/bin/fleet-apply.sh +81 -0
  731. package/bin/github-deploy.sh +94 -0
  732. package/bin/gjallarhorn-expose.sh +136 -0
  733. package/bin/gjallarhorn-notify.sh +71 -0
  734. package/bin/gjallarhorn-purge.sh +67 -0
  735. package/bin/gjallarhorn-tunnel.sh +85 -0
  736. package/bin/gleipnir-lock-lib.sh +284 -0
  737. package/bin/groa-update.sh +130 -0
  738. package/bin/hall-snapshot.sh +111 -0
  739. package/bin/hamr-harness.sh +246 -0
  740. package/bin/heimdall-ssh-keys.sh +374 -0
  741. package/bin/herdr-agents.py +44 -0
  742. package/bin/herdr-ensure.sh +104 -0
  743. package/bin/herdr-run.sh +452 -0
  744. package/bin/hermes-ensure.sh +88 -0
  745. package/bin/hlidskjalf-agents.sh +172 -0
  746. package/bin/hlidskjalf-usage.sh +196 -0
  747. package/bin/hoard-guard.sh +135 -0
  748. package/bin/hoard-lib.sh +105 -0
  749. package/bin/hodd.sh +102 -0
  750. package/bin/host-sense.sh +199 -0
  751. package/bin/huginn-research-worker.sh +121 -0
  752. package/bin/local-model-lock.sh +70 -0
  753. package/bin/mimir-bridge.py +259 -0
  754. package/bin/mimir-bridge.sh +100 -0
  755. package/bin/mimir-ingest.sh +131 -0
  756. package/bin/mimir-reflect.sh +27 -0
  757. package/bin/mimir.sh +113 -0
  758. package/bin/mjollnir-webhook.sh +72 -0
  759. package/bin/mjollnir.sh +104 -0
  760. package/bin/model-hardware.sh +96 -0
  761. package/bin/model-resolve.sh +160 -0
  762. package/bin/models-detect.sh +178 -0
  763. package/bin/nornir-cron-start.sh +162 -0
  764. package/bin/nornir-job-daily-briefing.sh +191 -0
  765. package/bin/nornir-job-git-sync.sh +152 -0
  766. package/bin/nornir-job-hall-snapshot.sh +28 -0
  767. package/bin/nornir-job-memory-housekeeping.sh +114 -0
  768. package/bin/nornir-job-nsr-compliance.sh +54 -0
  769. package/bin/nornir-job-observer.sh +166 -0
  770. package/bin/nornir-job-skillopt-sleep.sh +76 -0
  771. package/bin/npm-publish.sh +136 -0
  772. package/bin/omarchy-hook-install.sh +60 -0
  773. package/bin/omarchy-install.sh +161 -0
  774. package/bin/omarchy-plugins.sh +187 -0
  775. package/bin/omarchy-sense.sh +162 -0
  776. package/bin/perm-guard.sh +30 -0
  777. package/bin/pi-agent.sh +68 -0
  778. package/bin/pi-ensure.sh +77 -0
  779. package/bin/pi-local.sh +37 -0
  780. package/bin/pi-model-agnostic.sh +71 -0
  781. package/bin/pi-seat.sh +63 -0
  782. package/bin/prereq-ensure.sh +166 -0
  783. package/bin/private-guard.sh +58 -0
  784. package/bin/project-git.sh +69 -0
  785. package/bin/public-guard.sh +71 -0
  786. package/bin/ratatoskr.sh +49 -0
  787. package/bin/realm-lib.sh +32 -0
  788. package/bin/repo-scrub.sh +62 -0
  789. package/bin/rodd-operational-input.sh +250 -0
  790. package/bin/runes-append.sh +198 -0
  791. package/bin/saga-session-start.sh +172 -0
  792. package/bin/saga-sessionstart-run.sh +79 -0
  793. package/bin/saga-wake-drain.sh +41 -0
  794. package/bin/secret-guard.sh +63 -0
  795. package/bin/sessrumnir-ensure.sh +100 -0
  796. package/bin/sessrumnir-sync.sh +237 -0
  797. package/bin/sessrumnir.sh +104 -0
  798. package/bin/skillopt-setup.sh +76 -0
  799. package/bin/skuld-branch-outcome.sh +203 -0
  800. package/bin/skuld-branch-prompt.sh +101 -0
  801. package/bin/smidja-bootstrap.sh +94 -0
  802. package/bin/smidja-observe.sh +86 -0
  803. package/bin/syn-arm-pretool-check.sh +57 -0
  804. package/bin/syn-asset-pretool-check.sh +89 -0
  805. package/bin/syn-cd-pretool-check.sh +28 -0
  806. package/bin/syn-guard-pretool-check.sh +83 -0
  807. package/bin/syn-spawn-pretool-check.sh +58 -0
  808. package/bin/syn-turnend-guard.sh +36 -0
  809. package/bin/syn-watch-arm.sh +99 -0
  810. package/bin/tailscale-sync.sh +203 -0
  811. package/bin/telegram-bot.sh +82 -0
  812. package/bin/toolchain.sh +74 -0
  813. package/bin/ui-truth-check.sh +75 -0
  814. package/bin/utgard.sh +111 -0
  815. package/bin/valhalla.sh +115 -0
  816. package/bin/valknut-load.sh +298 -0
  817. package/bin/vor-crew-state.sh +124 -0
  818. package/bin/wedge-notify.sh +60 -0
  819. package/bin/workspace-provision.sh +80 -0
  820. package/bin/workspace-rag.sh +163 -0
  821. package/bin/wyrd-db.sh +47 -0
  822. package/bin/yggdrasil.sh +152 -0
  823. package/bin/ymir-edit.sh +145 -0
  824. package/bin/ymir-install.sh +806 -0
  825. package/bin/ymir-invite.sh +93 -0
  826. package/bin/ymir-isolation.sh +88 -0
  827. package/bin/ymir-migrate.sh +61 -0
  828. package/bin/ymir-plan.sh +356 -0
  829. package/bin/ymir-platform.sh +348 -0
  830. package/bin/ymir-say.sh +110 -0
  831. package/bin/ymir-setup-auth.sh +119 -0
  832. package/bin/ymir-validate.sh +193 -0
  833. package/bin/ymir.js +74 -16
  834. package/docs/Architecture.md +339 -0
  835. package/docs/design.md +351 -0
  836. package/docs/installations/README.md +63 -0
  837. package/docs/installations/ymir-home.md +93 -0
  838. package/docs/integration.md +105 -0
  839. package/docs/lore.md +1060 -0
  840. package/docs/research/agent-harnesses.md +290 -0
  841. package/docs/runbooks/README.md +19 -0
  842. package/docs/runbooks/a2a.md +91 -0
  843. package/docs/runbooks/agent-permissions.md +99 -0
  844. package/docs/runbooks/agents.md +57 -0
  845. package/docs/runbooks/models.md +69 -0
  846. package/docs/runbooks/realm-onboarding.md +60 -0
  847. package/docs/runbooks/secrets-and-hoard.md +60 -0
  848. package/docs/runbooks/updates-and-migrations.md +110 -0
  849. package/docs/runbooks/worktrees-isolation.md +72 -0
  850. package/docs/session-start.md +105 -0
  851. package/docs/skillopt-integration.md +321 -0
  852. package/docs/sync-system.md +66 -0
  853. package/docs/workspaces.md +104 -0
  854. package/docs/ymir-rut.md +260 -0
  855. package/package.json +54 -14
  856. package/packaging/build.sh +74 -0
  857. package/packaging/macos/Ymir Installer.command +43 -0
  858. package/packaging/windows/ymir-setup.nsi +74 -0
  859. package/scripts/README.md +22 -0
  860. package/scripts/electron.sh +269 -0
  861. package/scripts/lower.sh +34 -0
  862. package/scripts/raise.sh +42 -0
  863. package/scripts/start.sh +217 -0
  864. package/scripts/stop.sh +113 -0
  865. package/install.sh +0 -35
@@ -0,0 +1,1001 @@
1
+ #!/usr/bin/env bash
2
+ # fm-captain-hold.sh - deterministic mechanics for tasks held for the captain.
3
+ #
4
+ # The semantic policy is owned once by
5
+ # .agents/skills/captain-hold-lifecycle/SKILL.md. This script never reads
6
+ # report, visual-review, chat, or terminal prose to guess whether the captain
7
+ # owes an answer. The invoking agent decides what is genuinely waiting on the
8
+ # captain; this script supplies guarded creation, a durable record of what the
9
+ # captain actually said, the investigation completion gate, and the one
10
+ # keyed-answer intake every channel feeds.
11
+ #
12
+ # There is no separate decision type. A captain call is an ordinary backlog
13
+ # task held for the captain (`tasks-axi hold <id> --kind captain`), and its
14
+ # identity is simply the task id. Older installs created derived
15
+ # `<origin>-decision-<key>` identities through bin/fm-decision-hold.sh; those
16
+ # rows are already plain task ids, so they keep working here unchanged, and
17
+ # the legacy inputs noted below resolve them without a migration.
18
+ # All backlog mutations run in the active FM_HOME, which keeps main-home and
19
+ # secondmate-home ownership aligned with the work that discovered the call.
20
+ #
21
+ # Usage:
22
+ # fm-captain-hold.sh hold <task-id> --reason <reason> \
23
+ # [--title <title>] [--repo <repo>] [--origin <origin-id>] [--until YYYY-MM-DD]
24
+ # fm-captain-hold.sh answer <task-id> --decision-file <path> [--release]
25
+ # fm-captain-hold.sh answers [<legacy-origin> | --any-origin] --source <provenance> (keyed answers on stdin)
26
+ # fm-captain-hold.sh bind <source-id> [<legacy-origin> | --any-origin]
27
+ # fm-captain-hold.sh unbind <source-id>
28
+ # fm-captain-hold.sh binding <source-id>
29
+ # fm-captain-hold.sh complete <origin-id> (--none | <task-id>...)
30
+ # fm-captain-hold.sh verify <origin-id>
31
+ # fm-captain-hold.sh diverged
32
+ #
33
+ # `hold` places an existing task under an active captain hold, or creates the
34
+ # task first when no work item exists to hold (--title required to create; the
35
+ # optional --origin records provenance in the new task's body and supplies the
36
+ # default repo from that origin's metadata). Prefer holding the work item the
37
+ # question gates over minting a new row. Repeating `hold` with the same id is
38
+ # idempotent; a task already closed is refused rather than reopened. `--until`
39
+ # records the captain's own deferral date through `tasks-axi hold --until`, so
40
+ # a "revisit later" answer is stored as a date instead of a live card.
41
+ #
42
+ # `answer` records the captain's exact words and closes the call in the same
43
+ # act. It requires a non-empty captain decision file of at most 8192 bytes,
44
+ # writes a resolution block at the top of the task body (the previous body is
45
+ # preserved below the block and archived through tasks-axi --archive-body),
46
+ # then closes the task with `tasks-axi done` - or, with `--release`, lifts the
47
+ # hold with `tasks-axi unhold` so a captain-gated WORK item resumes instead of
48
+ # closing. An exact retry is idempotent only when its requested close mode
49
+ # matches the newest record; a changed decision or a mode mismatch is rejected.
50
+ # A re-held task may record a new answer on top. On a task already closed outside this script,
51
+ # `answer` records the missing resolution block (the old `repair` path) only
52
+ # when the task still carries the captain-hold provenance tasks-axi preserves
53
+ # through a close, so an ordinary finished task cannot be dressed up as an
54
+ # answered captain call. A hold that expired by date (`--until` in the past) is
55
+ # still answerable: the surviving hold annotations, not tasks-axi's live
56
+ # `held:` bit, prove the captain owned it.
57
+ #
58
+ # ONE KEYED-ANSWER INTAKE, FED BY EVERY CHANNEL.
59
+ # "A keyed answer closes its matching captain-held task" is a single
60
+ # capability, owned here and nowhere else. `answers` reads
61
+ # `<task-id>\t<answer>\t<label>[\t<mode>]` lines on stdin and closes each named
62
+ # task through the very same `answer` path above, so every guard applies
63
+ # identically no matter which channel the answer arrived on. The key IS the
64
+ # task id - no identity arithmetic. The optional fourth field selects the close:
65
+ # empty or `done` completes the task, `release` lifts the hold so held work
66
+ # resumes; anything else is skipped. A key that names no task, a task that is
67
+ # not held for the captain, or a task already closed is reported as `skipped:`
68
+ # and feeds nothing. A replayed delivery whose answer digest and requested
69
+ # close mode both match the newest record is reported `closed:` and is a no-op;
70
+ # a mode mismatch is skipped. The command exits nonzero when any key was
71
+ # skipped. `--source` is provenance text recorded in the
72
+ # durable decision, never a behavior switch: this command has no per-channel
73
+ # branch and no knowledge of chat, review decks, or any transport.
74
+ # Legacy input: an optional positional origin (or a stored concrete-origin
75
+ # binding) makes a key that names no task fall back to the old
76
+ # `<origin>-decision-<key>` identity, so an in-flight pre-collapse channel
77
+ # keeps closing its rows; `--any-origin` and the stored `(any)` marker mean
78
+ # what an absent origin means and are accepted for the same reason.
79
+ #
80
+ # A channel's ONLY job is to turn whatever it received into those keyed lines
81
+ # and pipe them here. It must never map keys to tasks, build decision records,
82
+ # choose a close mode beyond what its card declared, or close anything itself.
83
+ #
84
+ # `bind`, `unbind`, and `binding` record that a captured-answer SOURCE feeds
85
+ # this intake, for any channel whose answers arrive detached from their origin
86
+ # (a process-event source id, for example). The binding is a private record
87
+ # under `state/decision-bindings/`; a source with no binding feeds nothing, so
88
+ # this whole path is opt-in per source and an unbound source behaves as if it
89
+ # did not exist. `bind` deliberately does not require the source to exist yet,
90
+ # so a channel can be bound BEFORE it is armed. The optional second argument
91
+ # exists only for legacy pre-collapse records and callers: a concrete origin is
92
+ # stored verbatim and used as the composition fallback above, and
93
+ # `--any-origin` stores the same `(any)` marker a plain `bind <source-id>`
94
+ # stores. `binding` prints the stored value verbatim and `answers` accepts it,
95
+ # so the process-event runner's feed seam is unchanged.
96
+ #
97
+ # `complete` is the shared investigation and visual-review completion gate.
98
+ # It attests, in the origin task's metadata, the reviewed inventory of
99
+ # captain-held tasks that carry the origin's unresolved captain calls.
100
+ # `--none` is an explicit semantic attestation that the just-reviewed surface
101
+ # has no unresolved captain call, and is refused while the origin still has an
102
+ # open keyed status decision. With a non-empty inventory, every listed task is
103
+ # verified durable (actively captain-held, or closed with a recorded answer),
104
+ # the inventory is unioned idempotently into the metadata, and every still-open
105
+ # keyed status decision is transferred to its durable owner with a
106
+ # `captain-held [key=...]` status close naming the inventory. Later review
107
+ # passes may add ids. A post-teardown visual review can complete against the
108
+ # surviving report and tasks without recreating task state.
109
+ # `verify` is read-only and is called by scout teardown, so teardown cannot
110
+ # erase a source before this gate has succeeded: every recorded inventory
111
+ # entry must still be durable and no keyed status decision may be open.
112
+ # Metadata compatibility: the attestation keeps the historical
113
+ # `decisions_reviewed=1` and `decision_keys=` keys, and an inventory entry that
114
+ # names no existing task resolves through the legacy `<origin>-decision-<entry>`
115
+ # identity, so pre-collapse metadata written by fm-decision-hold.sh verifies
116
+ # unchanged. An entry that exists as a task id is always that task.
117
+ #
118
+ # `diverged` is the read-only guard over the seam between the two records of
119
+ # one captain call. See "record divergence" beside command_diverged below.
120
+ #
121
+ # Resolution records: the block written into the body names this script, the
122
+ # decision digest, and a `Resolution mode:` of answered, released, or repaired.
123
+ # Records written by the retired fm-decision-hold.sh (routed, declined,
124
+ # answered, repaired) are recognized everywhere a record is read, so nothing
125
+ # already closed needs rewriting.
126
+ set -eu
127
+
128
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
129
+ FM_ROOT="${FM_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
130
+ FM_HOME="${FM_HOME:-${FM_ROOT_OVERRIDE:-$FM_ROOT}}"
131
+ STATE="${FM_STATE_OVERRIDE:-$FM_HOME/state}"
132
+ DATA="${FM_DATA_OVERRIDE:-$FM_HOME/data}"
133
+
134
+ # shellcheck source=bin/fm-classify-lib.sh
135
+ # shellcheck disable=SC1091
136
+ . "$SCRIPT_DIR/fm-classify-lib.sh"
137
+ # shellcheck source=bin/fm-tasks-axi-lib.sh
138
+ # shellcheck disable=SC1091
139
+ . "$SCRIPT_DIR/fm-tasks-axi-lib.sh"
140
+ # shellcheck source=bin/fm-wake-lib.sh
141
+ # shellcheck disable=SC1091
142
+ . "$SCRIPT_DIR/fm-wake-lib.sh"
143
+
144
+ CAPTAIN_META_LOCK=
145
+ CAPTAIN_META_LOCK_HELD=0
146
+ captain_hold_cleanup() {
147
+ if [ "$CAPTAIN_META_LOCK_HELD" = 1 ]; then
148
+ fm_lock_release "$CAPTAIN_META_LOCK" || true
149
+ CAPTAIN_META_LOCK_HELD=0
150
+ fi
151
+ }
152
+ trap captain_hold_cleanup EXIT
153
+
154
+ usage() {
155
+ awk '
156
+ NR == 1 { next }
157
+ /^#/ { sub(/^# ?/, ""); print; next }
158
+ { exit }
159
+ ' "$0"
160
+ }
161
+
162
+ fail() {
163
+ printf 'fm-captain-hold: %s\n' "$*" >&2
164
+ exit 1
165
+ }
166
+
167
+ validate_slug() { # <label> <value>
168
+ local label=$1 value=$2
169
+ case "$value" in
170
+ ''|*[!A-Za-z0-9._-]*) fail "$label must be a non-empty privacy-safe slug: $value" ;;
171
+ esac
172
+ }
173
+
174
+ validate_one_line() { # <label> <value>
175
+ local label=$1 value=$2
176
+ [ -n "$value" ] || fail "$label must not be empty"
177
+ case "$value" in
178
+ *$'\n'*|*$'\r'*) fail "$label must be one line" ;;
179
+ esac
180
+ }
181
+
182
+ sha256_text() { # <text>
183
+ if command -v shasum >/dev/null 2>&1; then
184
+ printf '%s' "$1" | shasum -a 256 | awk '{print $1}'
185
+ elif command -v sha256sum >/dev/null 2>&1; then
186
+ printf '%s' "$1" | sha256sum | awk '{print $1}'
187
+ else
188
+ fail "shasum or sha256sum is required"
189
+ fi
190
+ }
191
+
192
+ # The legacy derived identity older installs minted for a captain call.
193
+ # Kept only to resolve pre-collapse rows, metadata entries, and channel keys.
194
+ legacy_hold_id() { # <origin-id> <key>
195
+ printf '%s-decision-%s' "$1" "$2"
196
+ }
197
+
198
+ # The legacy any-origin binding marker. Slug validation rejects parentheses, so
199
+ # no real origin id or task id can collide with it.
200
+ BINDING_ANY='(any)'
201
+
202
+ DECISION_TEXT=''
203
+ DECISION_DIGEST=''
204
+
205
+ load_decision() { # <path>; sets DECISION_TEXT and DECISION_DIGEST
206
+ local path=$1 decision
207
+ [ -n "$path" ] || fail "--decision-file is required"
208
+ [ -f "$path" ] || fail "decision file does not exist: $path"
209
+ decision=$(cat "$path")
210
+ [ -n "$decision" ] || fail "decision file must not be empty"
211
+ [ "$(printf '%s' "$decision" | LC_ALL=C wc -c | tr -d ' ')" -le 8192 ] \
212
+ || fail "decision file exceeds 8192 bytes"
213
+ DECISION_TEXT=$decision
214
+ DECISION_DIGEST=$(sha256_text "$decision")
215
+ }
216
+
217
+ tasks_axi() {
218
+ (cd "$FM_HOME" && tasks-axi "$@")
219
+ }
220
+
221
+ require_tasks_axi() {
222
+ fm_tasks_axi_compatible || fail "compatible tasks-axi is required"
223
+ tasks-axi hold --help 2>&1 | grep -F -- '--kind captain' >/dev/null \
224
+ || fail "tasks-axi does not expose the captain-hold contract"
225
+ }
226
+
227
+ task_show() { # <id>
228
+ tasks_axi show "$1" --full 2>/dev/null
229
+ }
230
+
231
+ show_field() { # <show-output> <field>
232
+ local output=$1 field=$2
233
+ printf '%s\n' "$output" | sed -n "s/^ $field: //p" | head -1
234
+ }
235
+
236
+ decode_shown_value() { # <shown-field>
237
+ local value=$1
238
+ case "$value" in
239
+ \"*\")
240
+ printf '%s' "$value" | perl -MJSON::PP -e '
241
+ local $/;
242
+ my $value = decode_json(<STDIN>);
243
+ binmode STDOUT, ":raw";
244
+ utf8::encode($value) if utf8::is_utf8($value);
245
+ print $value;
246
+ '
247
+ ;;
248
+ *) printf '%s' "$value" ;;
249
+ esac
250
+ }
251
+
252
+ # Decode show-encoded scalar fields and normalize the empty marker.
253
+ show_field_value() { # <show-output> <field>
254
+ local value
255
+ value=$(decode_shown_value "$(show_field "$1" "$2")")
256
+ [ "$value" != '-' ] || value=''
257
+ printf '%s' "$value"
258
+ }
259
+
260
+ origin_exists_here() { # <origin-id>
261
+ [ -f "$STATE/$1.meta" ] && return 0
262
+ [ -f "$DATA/$1/report.md" ] && return 0
263
+ task_show "$1" >/dev/null 2>&1
264
+ }
265
+
266
+ list_has_key() { # <comma-list> <key>
267
+ case ",$1," in
268
+ *",$2,"*) return 0 ;;
269
+ *) return 1 ;;
270
+ esac
271
+ }
272
+
273
+ sorted_key_union() { # <comma-list> <newline-or-space-separated-new-keys>
274
+ local existing=$1 new=$2
275
+ {
276
+ printf '%s\n' "$existing" | tr ',' '\n'
277
+ printf '%s\n' "$new" | tr ' ' '\n'
278
+ } | sed '/^$/d' | LC_ALL=C sort -u | paste -sd, -
279
+ }
280
+
281
+ meta_value() { # <meta> <key>
282
+ grep "^$2=" "$1" 2>/dev/null | tail -1 | cut -d= -f2- || true
283
+ }
284
+
285
+ origin_open_decisions() { # <origin-id>
286
+ local origin=$1 meta="$STATE/$1.meta" status_file="$STATE/$1.status" open kind last verb
287
+ open=$(status_open_decisions "$status_file")
288
+ [ -n "$open" ] || return 0
289
+ [ -f "$meta" ] || { printf '%s' "$open"; return 0; }
290
+ kind=$(meta_value "$meta" kind)
291
+ [ -n "$kind" ] || kind=ship
292
+ if [ "$kind" != secondmate ]; then
293
+ last=$(last_status_line "$status_file")
294
+ verb=$(status_line_verb "$last")
295
+ case "$verb" in
296
+ done|failed) return 0 ;;
297
+ esac
298
+ fi
299
+ printf '%s' "$open"
300
+ }
301
+
302
+ # A resolution record written by this script or by the retired
303
+ # fm-decision-hold.sh. Both carry the same leader-then-captain-decision shape.
304
+ body_has_resolution_record() { # <task-body>
305
+ case "$1" in
306
+ *"Resolution recorded by fm-captain-hold."*"Captain decision:"*) return 0 ;;
307
+ *"Resolution recorded by fm-decision-hold."*"Captain decision:"*) return 0 ;;
308
+ esac
309
+ return 1
310
+ }
311
+
312
+ # The recorded decision digest of either record format, from the show-escaped
313
+ # body (multi-line bodies print as one quoted line with \n escapes). Records
314
+ # are prepended, so the first match is the newest record.
315
+ recorded_decision_digest() { # <task-body>
316
+ local rest=$1
317
+ case "$rest" in
318
+ *"Decision digest: "*) rest=${rest#*"Decision digest: "} ;;
319
+ *) return 1 ;;
320
+ esac
321
+ rest=${rest%%\\n*}
322
+ rest=${rest%%$'\n'*}
323
+ printf '%s' "$rest"
324
+ }
325
+
326
+ # The newest record's `Resolution mode:` value; empty for a record predating it.
327
+ recorded_resolution_mode() { # <task-body>
328
+ local rest=$1
329
+ case "$rest" in
330
+ *"Resolution mode: "*) rest=${rest#*"Resolution mode: "} ;;
331
+ *) return 1 ;;
332
+ esac
333
+ rest=${rest%%\\n*}
334
+ rest=${rest%%$'\n'*}
335
+ printf '%s' "$rest"
336
+ }
337
+
338
+ resolution_block() { # <mode>
339
+ printf 'Resolution recorded by fm-captain-hold.\nDecision digest: %s\nResolution mode: %s\n\nCaptain decision:\n%s\n' \
340
+ "$DECISION_DIGEST" "$1" "$DECISION_TEXT"
341
+ }
342
+
343
+ # Durable state of one captain call: an active captain hold (annotations
344
+ # surviving even when a date gate has expired) or a recorded captain answer.
345
+ verify_hold_durable() { # <task-id>
346
+ local id=$1 show state hold_kind body
347
+ show=$(task_show "$id") || fail "captain-held task $id is absent from $FM_HOME/data/backlog.md"
348
+ state=$(show_field "$show" state)
349
+ hold_kind=$(show_field_value "$show" hold_kind)
350
+ body=$(show_field "$show" body)
351
+ if body_has_resolution_record "$body"; then
352
+ return 0
353
+ fi
354
+ if [ "$state" != "done" ] && [ "$hold_kind" = captain ]; then
355
+ return 0
356
+ fi
357
+ fail "captain-held task $id is neither held for the captain nor closed with a recorded captain answer"
358
+ }
359
+
360
+ # Resolve one inventory entry or channel key to the task that carries it: the
361
+ # exact task id when it exists, else the legacy derived identity.
362
+ resolve_entry() { # <origin-or-empty> <entry>; prints the resolved id or fails
363
+ local origin=$1 entry=$2 legacy
364
+ if task_show "$entry" >/dev/null 2>&1; then
365
+ printf '%s' "$entry"
366
+ return 0
367
+ fi
368
+ if [ -n "$origin" ] && [ "$origin" != "$BINDING_ANY" ]; then
369
+ legacy=$(legacy_hold_id "$origin" "$entry")
370
+ if task_show "$legacy" >/dev/null 2>&1; then
371
+ printf '%s' "$legacy"
372
+ return 0
373
+ fi
374
+ fail "no captain-held task $entry and no legacy identity $legacy in $FM_HOME/data/backlog.md"
375
+ fi
376
+ fail "no captain-held task $entry in $FM_HOME/data/backlog.md"
377
+ }
378
+
379
+ command_hold() {
380
+ local id=${1:-} title='' reason='' repo='' origin='' until='' show state existing_title body='' hold_kind
381
+ [ "$#" -ge 1 ] || { usage >&2; exit 2; }
382
+ shift
383
+ while [ "$#" -gt 0 ]; do
384
+ case "$1" in
385
+ --title) shift; title=${1:-} ;;
386
+ --reason) shift; reason=${1:-} ;;
387
+ --repo) shift; repo=${1:-} ;;
388
+ --origin) shift; origin=${1:-} ;;
389
+ --until) shift; until=${1:-} ;;
390
+ *) usage >&2; exit 2 ;;
391
+ esac
392
+ shift
393
+ done
394
+ validate_slug task-id "$id"
395
+ validate_one_line reason "$reason"
396
+ case "$reason" in *'('*|*')'*) fail "reason must not contain parentheses (tasks-axi hold contract)" ;; esac
397
+ if [ -n "$origin" ]; then
398
+ validate_slug origin-id "$origin"
399
+ fi
400
+ if [ -n "$until" ]; then
401
+ case "$until" in
402
+ [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]) : ;;
403
+ *) fail "--until must be a YYYY-MM-DD date: $until" ;;
404
+ esac
405
+ fi
406
+ require_tasks_axi
407
+ if show=$(task_show "$id"); then
408
+ state=$(show_field "$show" state)
409
+ [ "$state" != "done" ] \
410
+ || fail "task $id is already closed; a new captain call needs its own task"
411
+ if [ -n "$title" ]; then
412
+ existing_title=$(show_field_value "$show" title)
413
+ [ "$existing_title" = "$title" ] || fail "existing task $id has a different title"
414
+ fi
415
+ else
416
+ [ -n "$title" ] || fail "--title is required to create task $id"
417
+ validate_one_line title "$title"
418
+ if [ -z "$repo" ] && [ -n "$origin" ] && [ -f "$STATE/$origin.meta" ]; then
419
+ repo=$(meta_value "$STATE/$origin.meta" project)
420
+ repo=${repo%/}
421
+ repo=${repo##*/}
422
+ fi
423
+ [ -n "$repo" ] || repo=firstmate
424
+ validate_one_line repo "$repo"
425
+ [ -z "$origin" ] || body=$(printf 'Origin: %s' "$origin")
426
+ if [ -n "$body" ]; then
427
+ tasks_axi add "$id" "$title" --repo "$repo" --body "$body" >/dev/null \
428
+ || fail "could not create task $id"
429
+ else
430
+ tasks_axi add "$id" "$title" --repo "$repo" >/dev/null \
431
+ || fail "could not create task $id"
432
+ fi
433
+ fi
434
+ if [ -n "$until" ]; then
435
+ tasks_axi hold "$id" --reason "$reason" --kind captain --until "$until" >/dev/null \
436
+ || fail "could not hold task $id for the captain"
437
+ else
438
+ tasks_axi hold "$id" --reason "$reason" --kind captain >/dev/null \
439
+ || fail "could not hold task $id for the captain"
440
+ fi
441
+ show=$(task_show "$id") || fail "task $id disappeared while holding it"
442
+ hold_kind=$(show_field_value "$show" hold_kind)
443
+ [ "$hold_kind" = captain ] || fail "task $id did not retain its captain hold"
444
+ printf '%s\n' "$id"
445
+ }
446
+
447
+ # Record a resolution block at the top of the task body, preserving the
448
+ # previous body below it and archiving the pristine original.
449
+ write_resolution_record() { # <task-id> <mode> <shown-body>
450
+ local id=$1 mode=$2 body=$3 new_body tmp
451
+ new_body=$(resolution_block "$mode")
452
+ body=$(decode_shown_value "$body") \
453
+ || fail "could not decode the existing body for $id"
454
+ if [ -n "$body" ]; then
455
+ new_body=$(printf '%s\n\n%s' "$new_body" "$body")
456
+ fi
457
+ tmp=$(umask 077; mktemp "${TMPDIR:-/tmp}/fm-captain-hold-body.XXXXXX") \
458
+ || fail "cannot stage the resolution record"
459
+ if ! printf '%s\n' "$new_body" > "$tmp"; then
460
+ rm -f -- "$tmp"
461
+ fail "cannot stage the resolution record for $id"
462
+ fi
463
+ if ! tasks_axi update "$id" --body-file "$tmp" --archive-body >/dev/null; then
464
+ rm -f -- "$tmp"
465
+ fail "could not record the captain decision on $id"
466
+ fi
467
+ rm -f -- "$tmp"
468
+ }
469
+
470
+ close_answered() { # <task-id> <release-0-or-1>
471
+ if [ "$2" = 1 ]; then
472
+ tasks_axi unhold "$1" >/dev/null || fail "could not release captain-held task $1"
473
+ else
474
+ tasks_axi "done" "$1" >/dev/null || fail "could not close answered captain-held task $1"
475
+ fi
476
+ }
477
+
478
+ command_answer() {
479
+ local id=${1:-} decision_file='' release=0 show state hold_kind body outcome recorded_mode
480
+ [ "$#" -ge 1 ] || { usage >&2; exit 2; }
481
+ shift
482
+ while [ "$#" -gt 0 ]; do
483
+ case "$1" in
484
+ --decision-file) shift; decision_file=${1:-} ;;
485
+ --release) release=1 ;;
486
+ *) usage >&2; exit 2 ;;
487
+ esac
488
+ shift
489
+ done
490
+ validate_slug task-id "$id"
491
+ load_decision "$decision_file"
492
+ require_tasks_axi
493
+ show=$(task_show "$id") || fail "captain-held task $id is absent from $FM_HOME/data/backlog.md"
494
+ state=$(show_field "$show" state)
495
+ hold_kind=$(show_field_value "$show" hold_kind)
496
+ body=$(show_field "$show" body)
497
+ if [ "$release" = 1 ]; then outcome=released; else outcome=answered; fi
498
+
499
+ if [ "$state" = "done" ]; then
500
+ if body_has_resolution_record "$body"; then
501
+ # An exact compatible retry is an idempotent no-op; drift is rejected.
502
+ [ "$(recorded_decision_digest "$body" || true)" = "$DECISION_DIGEST" ] \
503
+ || fail "captain-held task $id records a different captain decision"
504
+ recorded_mode=$(recorded_resolution_mode "$body" || true)
505
+ [ "$recorded_mode" != released ] \
506
+ || fail "task $id records this answer with mode released; a closed task cannot replay that release"
507
+ [ "$release" = 0 ] \
508
+ || fail "task $id records this answer with mode ${recorded_mode:-unknown}; --release cannot reopen a closed task"
509
+ printf 'answered: %s\n' "$id"
510
+ return 0
511
+ fi
512
+ [ "$release" = 0 ] || fail "task $id is already closed; --release cannot reopen it"
513
+ # Closed outside this script: record the captain's answer retroactively.
514
+ # tasks-axi keeps hold_kind through a close, so it is the surviving proof
515
+ # this really was the captain's item rather than ordinary finished work.
516
+ [ "$hold_kind" = captain ] \
517
+ || fail "task $id was never held for the captain; nothing to record an answer on"
518
+ write_resolution_record "$id" repaired "$body"
519
+ show=$(task_show "$id") || fail "task $id disappeared while recording the answer"
520
+ [ "$(show_field "$show" state)" = "done" ] || fail "recording the answer reopened closed task $id"
521
+ body_has_resolution_record "$(show_field "$show" body)" \
522
+ || fail "captain-held task $id did not retain its durable resolution record"
523
+ printf 'repaired: %s\n' "$id"
524
+ return 0
525
+ fi
526
+
527
+ if [ "$hold_kind" = captain ]; then
528
+ # Actively the captain's item (a date-expired hold keeps its annotations
529
+ # and stays answerable). A matching record means an interrupted close to
530
+ # finish; a different digest is a NEW answer on a re-held task and gets
531
+ # its own record on top. Either way the close mode is the caller's flag,
532
+ # checked against an interrupted close's recorded mode so a retry cannot
533
+ # silently flip a release into a close.
534
+ if body_has_resolution_record "$body" \
535
+ && [ "$(recorded_decision_digest "$body" || true)" = "$DECISION_DIGEST" ]; then
536
+ recorded_mode=$(recorded_resolution_mode "$body" || true)
537
+ case "$recorded_mode" in
538
+ released) [ "$release" = 1 ] || fail "task $id records this answer as a release; retry with --release" ;;
539
+ answered) [ "$release" = 0 ] || fail "task $id records this answer as a close; retry without --release" ;;
540
+ esac
541
+ close_answered "$id" "$release"
542
+ printf '%s: %s\n' "$outcome" "$id"
543
+ return 0
544
+ fi
545
+ write_resolution_record "$id" "$outcome" "$body"
546
+ close_answered "$id" "$release"
547
+ show=$(task_show "$id") || fail "task $id disappeared after closing"
548
+ body_has_resolution_record "$(show_field "$show" body)" \
549
+ || fail "captain-held task $id did not retain its durable resolution record"
550
+ printf '%s: %s\n' "$outcome" "$id"
551
+ return 0
552
+ fi
553
+
554
+ # Not held and not closed: only an already-recorded release replays cleanly.
555
+ if body_has_resolution_record "$body"; then
556
+ recorded_mode=$(recorded_resolution_mode "$body" || true)
557
+ [ "$(recorded_decision_digest "$body" || true)" = "$DECISION_DIGEST" ] \
558
+ || fail "task $id records a different captain decision with mode ${recorded_mode:-unknown}"
559
+ [ "$recorded_mode" = released ] && [ "$release" = 1 ] \
560
+ || fail "task $id records this answer with mode ${recorded_mode:-unknown}; replay requires matching --release"
561
+ printf 'released: %s\n' "$id"
562
+ return 0
563
+ fi
564
+ fail "task $id is not held for the captain; hold it first or name the right task"
565
+ }
566
+
567
+ # --- the one keyed-answer intake, and the source bindings that feed it --------
568
+
569
+ BINDING_DIR="$STATE/decision-bindings"
570
+ BINDING_SCHEMA=fm-decision-binding.v1
571
+
572
+ validate_source_id() { # <source-id>
573
+ validate_slug source-id "$1"
574
+ [ "${#1}" -le 64 ] || fail "source-id must be at most 64 characters: $1"
575
+ }
576
+
577
+ binding_path() { printf '%s/%s.origin\n' "$BINDING_DIR" "$1"; }
578
+
579
+ # The stored binding value, or empty when the source is unbound. An unreadable
580
+ # or wrong-schema record is a hard error rather than a silent "unbound":
581
+ # feeding nothing is the safe direction only when it is a deliberate choice,
582
+ # never when it is a corrupted record.
583
+ read_binding() { # <source-id>
584
+ local path origin schema
585
+ path=$(binding_path "$1")
586
+ [ -e "$path" ] || return 0
587
+ [ -f "$path" ] && [ ! -L "$path" ] || fail "decision binding is unsafe: $path"
588
+ schema=$(sed -n 's/^schema=//p' "$path" | head -1)
589
+ [ "$schema" = "$BINDING_SCHEMA" ] || fail "decision binding has an incompatible schema: $path"
590
+ origin=$(sed -n 's/^origin=//p' "$path" | head -1)
591
+ if [ "$origin" != "$BINDING_ANY" ]; then
592
+ case "$origin" in
593
+ ''|*[!A-Za-z0-9._-]*) fail "decision binding has an invalid origin id: $path" ;;
594
+ esac
595
+ fi
596
+ printf '%s\n' "$origin"
597
+ }
598
+
599
+ command_bind() {
600
+ local source=${1:-} origin=${2:-} dest tmp
601
+ [ "$#" -ge 1 ] && [ "$#" -le 2 ] || { usage >&2; exit 2; }
602
+ validate_source_id "$source"
603
+ if [ -z "$origin" ] || [ "$origin" = --any-origin ]; then
604
+ origin=$BINDING_ANY
605
+ else
606
+ validate_slug legacy-origin "$origin"
607
+ fi
608
+ (umask 077; mkdir -p "$BINDING_DIR") || fail "cannot create $BINDING_DIR"
609
+ [ -d "$BINDING_DIR" ] && [ ! -L "$BINDING_DIR" ] || fail "decision binding dir is unsafe: $BINDING_DIR"
610
+ dest=$(binding_path "$source")
611
+ tmp=$(umask 077; mktemp "$BINDING_DIR/.origin.XXXXXX") || fail "cannot stage the decision binding"
612
+ if ! { printf 'schema=%s\norigin=%s\n' "$BINDING_SCHEMA" "$origin" > "$tmp" \
613
+ && chmod 0600 "$tmp" && mv -f -- "$tmp" "$dest"; }; then
614
+ rm -f -- "$tmp"
615
+ fail "cannot record the decision binding for $source"
616
+ fi
617
+ printf 'bound: %s -> %s\n' "$source" "$origin"
618
+ }
619
+
620
+ command_unbind() {
621
+ local source=${1:-}
622
+ [ "$#" -eq 1 ] || { usage >&2; exit 2; }
623
+ validate_source_id "$source"
624
+ rm -f -- "$(binding_path "$source")"
625
+ printf 'unbound: %s\n' "$source"
626
+ }
627
+
628
+ command_binding() {
629
+ local source=${1:-} origin
630
+ [ "$#" -eq 1 ] || { usage >&2; exit 2; }
631
+ validate_source_id "$source"
632
+ origin=$(read_binding "$source") || exit 1
633
+ [ -n "$origin" ] || return 1
634
+ printf '%s\n' "$origin"
635
+ }
636
+
637
+ # The durable captain decision one keyed answer records. Pure function of its
638
+ # inputs, so the same answer delivered twice is idempotent rather than a
639
+ # conflicting decision.
640
+ keyed_decision_text() { # <source> <task-id> <answer> <label>
641
+ printf 'Captain answered this call through %s.\n' "$1"
642
+ printf 'Task: %s\n' "$2"
643
+ printf 'Answer: %s\n' "$3"
644
+ [ -z "$4" ] || printf 'Answer as shown to the captain: %s\n' "$4"
645
+ }
646
+
647
+ legacy_keyed_decision_text() { # <source> <key> <answer> <label>
648
+ printf 'Captain answered this decision through %s.\n' "$1"
649
+ printf 'Decision key: %s\n' "$2"
650
+ printf 'Answer: %s\n' "$3"
651
+ [ -z "$4" ] || printf 'Answer as shown to the captain: %s\n' "$4"
652
+ }
653
+
654
+ sanitize_field() { # <text>
655
+ printf '%s' "$1" | tr '\n\r\t' ' ' | LC_ALL=C tr -d '\000-\037\177' | cut -c1-512
656
+ }
657
+
658
+ command_answers() {
659
+ local origin='' source='' row rest key answer label mode id show state hold_kind body digest legacy_digest legacy_key
660
+ local recorded_digest recorded_mode tmp err closed=0 skipped=0 reason release_flag tab=$'\t'
661
+ while [ "$#" -gt 0 ]; do
662
+ case "$1" in
663
+ --source) shift; source=${1:-} ;;
664
+ --any-origin) origin=$BINDING_ANY ;;
665
+ --*) usage >&2; exit 2 ;;
666
+ *)
667
+ [ -z "$origin" ] || { usage >&2; exit 2; }
668
+ origin=$1
669
+ ;;
670
+ esac
671
+ shift
672
+ done
673
+ if [ -n "$origin" ] && [ "$origin" != "$BINDING_ANY" ]; then
674
+ validate_slug legacy-origin "$origin"
675
+ fi
676
+ [ -n "$source" ] || fail "--source provenance is required so the durable decision records where the answer came from"
677
+ source=$(sanitize_field "$source")
678
+ require_tasks_axi
679
+ tmp=$(umask 077; mktemp "${TMPDIR:-/tmp}/fm-keyed-decision.XXXXXX") || fail "cannot stage the captain decision"
680
+ err=$(umask 077; mktemp "${TMPDIR:-/tmp}/fm-keyed-decision-err.XXXXXX") \
681
+ || { rm -f -- "$tmp"; fail "cannot stage the captain decision diagnostics"; }
682
+ while IFS= read -r row; do
683
+ key=${row%%"$tab"*}
684
+ rest=''
685
+ case "$row" in *"$tab"*) rest=${row#*"$tab"} ;; esac
686
+ answer=${rest%%"$tab"*}
687
+ case "$rest" in *"$tab"*) rest=${rest#*"$tab"} ;; *) rest='' ;; esac
688
+ label=${rest%%"$tab"*}
689
+ case "$rest" in *"$tab"*) mode=${rest#*"$tab"} ;; *) mode='' ;; esac
690
+ [ -n "${key:-}" ] || continue
691
+ case "$key" in *[!A-Za-z0-9._-]*) continue ;; esac
692
+ [ "${#key}" -le 128 ] || continue
693
+ answer=$(sanitize_field "${answer:-}")
694
+ [ -n "$answer" ] || continue
695
+ label=$(sanitize_field "${label:-}")
696
+ release_flag=''
697
+ case "${mode:-}" in
698
+ ''|done) : ;;
699
+ release) release_flag=--release ;;
700
+ *)
701
+ printf 'skipped: %s (unknown close mode %s)\n' "$key" "$(sanitize_field "$mode")"
702
+ skipped=$((skipped + 1))
703
+ continue
704
+ ;;
705
+ esac
706
+ if ! id=$(resolve_entry "$origin" "$key" 2>/dev/null); then
707
+ printf 'skipped: %s (no captain-held task with that id)\n' "$key"
708
+ skipped=$((skipped + 1))
709
+ continue
710
+ fi
711
+ keyed_decision_text "$source" "$id" "$answer" "$label" > "$tmp" \
712
+ || fail "cannot stage the captain decision for $id"
713
+ digest=$(sha256_text "$(cat "$tmp")")
714
+ legacy_digest=''
715
+ if [ "$id" != "$key" ]; then
716
+ legacy_key=$key
717
+ elif { [ -z "$origin" ] || [ "$origin" = "$BINDING_ANY" ]; } \
718
+ && [ "${id#*-decision-}" != "$id" ]; then
719
+ legacy_key=${id#*-decision-}
720
+ else
721
+ legacy_key=''
722
+ fi
723
+ if [ -n "$legacy_key" ]; then
724
+ legacy_digest=$(sha256_text "$(legacy_keyed_decision_text "$source" "$legacy_key" "$answer" "$label")")
725
+ fi
726
+ show=$(task_show "$id") || { printf 'skipped: %s (absent)\n' "$id"; skipped=$((skipped + 1)); continue; }
727
+ state=$(show_field "$show" state)
728
+ hold_kind=$(show_field_value "$show" hold_kind)
729
+ body=$(show_field "$show" body)
730
+ recorded_digest=$(recorded_decision_digest "$body" || true)
731
+ recorded_mode=$(recorded_resolution_mode "$body" || true)
732
+ if body_has_resolution_record "$body" \
733
+ && { [ "$recorded_digest" = "$digest" ] \
734
+ || { case "$body" in *"Resolution recorded by fm-decision-hold."*) true ;; *) false ;; esac \
735
+ && [ -n "$legacy_digest" ] && [ "$recorded_digest" = "$legacy_digest" ]; }; }; then
736
+ if { [ -z "$release_flag" ] && [ "$state" = "done" ] && [ "$recorded_mode" != released ]; } \
737
+ || { [ "$release_flag" = --release ] && [ "$state" != "done" ] \
738
+ && [ "$hold_kind" != captain ] && [ "$recorded_mode" = released ]; }; then
739
+ printf 'closed: %s\n' "$id"
740
+ closed=$((closed + 1))
741
+ continue
742
+ fi
743
+ fi
744
+ if [ "$state" = "done" ]; then
745
+ printf 'skipped: %s (already closed)\n' "$id"
746
+ skipped=$((skipped + 1))
747
+ continue
748
+ fi
749
+ if [ "$hold_kind" != captain ]; then
750
+ printf 'skipped: %s (not held for the captain)\n' "$id"
751
+ skipped=$((skipped + 1))
752
+ continue
753
+ fi
754
+ # shellcheck disable=SC2086 # release_flag is empty or a single literal flag.
755
+ if "$0" answer "$id" --decision-file "$tmp" $release_flag </dev/null >/dev/null 2>"$err"; then
756
+ printf 'closed: %s\n' "$id"
757
+ closed=$((closed + 1))
758
+ else
759
+ reason=$(tr -d '\n' < "$err" | sed 's/^fm-captain-hold: //')
760
+ printf 'skipped: %s (%s)\n' "$id" "$reason"
761
+ skipped=$((skipped + 1))
762
+ fi
763
+ done
764
+ rm -f -- "$tmp" "$err"
765
+ printf 'answers: closed=%s skipped=%s\n' "$closed" "$skipped"
766
+ [ "$skipped" -eq 0 ]
767
+ }
768
+
769
+ command_complete() {
770
+ local origin=${1:-} meta previous='' supplied='' keys='' entry key status_file open raw_open has_meta=0 transfer_rc
771
+ [ "$#" -ge 2 ] || { usage >&2; exit 2; }
772
+ validate_slug origin-id "$origin"
773
+ shift
774
+ meta="$STATE/$origin.meta"
775
+ [ -f "$meta" ] && has_meta=1
776
+ if [ "$has_meta" = 1 ]; then
777
+ CAPTAIN_META_LOCK=$(fm_meta_lock_path "$meta") || fail "could not resolve task metadata lock"
778
+ fm_lock_acquire_wait "$CAPTAIN_META_LOCK"
779
+ CAPTAIN_META_LOCK_HELD=1
780
+ [ -f "$meta" ] || fail "task metadata disappeared while recording completion"
781
+ fi
782
+ require_tasks_axi
783
+ origin_exists_here "$origin" || fail "origin $origin is not owned by the active home $FM_HOME"
784
+ if [ "$#" -eq 1 ] && [ "$1" = --none ]; then
785
+ supplied=''
786
+ else
787
+ while [ "$#" -gt 0 ]; do
788
+ [ "$1" != --none ] || fail "--none cannot be combined with task ids"
789
+ validate_slug task-id "$1"
790
+ supplied="${supplied}${supplied:+ }$1"
791
+ shift
792
+ done
793
+ fi
794
+ if [ "$has_meta" = 1 ]; then
795
+ previous=$(meta_value "$meta" decision_keys)
796
+ fi
797
+ keys=$(sorted_key_union "$previous" "$supplied")
798
+ if [ -n "$keys" ]; then
799
+ while IFS= read -r entry; do
800
+ [ -n "$entry" ] || continue
801
+ verify_hold_durable "$(resolve_entry "$origin" "$entry")"
802
+ done <<EOF
803
+ $(printf '%s\n' "$keys" | tr ',' '\n')
804
+ EOF
805
+ fi
806
+
807
+ status_file="$STATE/$origin.status"
808
+ raw_open=$(status_open_decisions "$status_file")
809
+ open=$(origin_open_decisions "$origin")
810
+ if [ -n "$open" ] && [ -z "$keys" ]; then
811
+ fail "origin $origin still has open captain decisions in its status stream; hold a captain task for what remains, or answer them, before attesting --none"
812
+ fi
813
+
814
+ if [ "$has_meta" = 1 ]; then
815
+ if [ "$(meta_value "$meta" decisions_reviewed)" != 1 ] || [ "$previous" != "$keys" ]; then
816
+ printf 'decisions_reviewed=1\ndecision_keys=%s\n' "$keys" >> "$meta"
817
+ fi
818
+ fm_lock_release "$CAPTAIN_META_LOCK"
819
+ CAPTAIN_META_LOCK_HELD=0
820
+
821
+ # Transfer every still-open status decision to the durable captain-held
822
+ # inventory so the live status fold does not duplicate the same Captain's
823
+ # Call item. The transfer line is this home's own bookkeeping close,
824
+ # written by the turn that just reviewed the inventory, so it uses the
825
+ # guarded self-announced append (bin/fm-wake-lib.sh) and does not wake this
826
+ # same session; an append failure still fails this command loudly.
827
+ if [ -n "$keys" ]; then
828
+ while IFS=$'\t' read -r key _verb _summary; do
829
+ [ -n "$key" ] || continue
830
+ transfer_rc=0
831
+ fm_wake_status_append_self_announced "$STATE" "$status_file" \
832
+ "captain-held [key=$key]: tracked by $keys" || transfer_rc=$?
833
+ [ "$transfer_rc" -ne 2 ] || fail "cannot append the captain-held transfer for $origin/$key"
834
+ done <<EOF
835
+ $raw_open
836
+ EOF
837
+ fi
838
+ fi
839
+ printf 'complete: %s captain-call inventory reviewed%s\n' "$origin" "${keys:+ ($keys)}"
840
+ }
841
+
842
+ command_verify() {
843
+ local origin=${1:-} meta reviewed keys entry key open
844
+ [ "$#" -eq 1 ] || { usage >&2; exit 2; }
845
+ validate_slug origin-id "$origin"
846
+ meta="$STATE/$origin.meta"
847
+ [ -f "$meta" ] || fail "origin metadata is absent: $meta"
848
+ require_tasks_axi
849
+ reviewed=$(meta_value "$meta" decisions_reviewed)
850
+ [ "$reviewed" = 1 ] || fail "origin $origin has no completed captain-call inventory"
851
+ keys=$(meta_value "$meta" decision_keys)
852
+ if [ -n "$keys" ]; then
853
+ while IFS= read -r entry; do
854
+ [ -n "$entry" ] || continue
855
+ verify_hold_durable "$(resolve_entry "$origin" "$entry")"
856
+ done <<EOF
857
+ $(printf '%s\n' "$keys" | tr ',' '\n')
858
+ EOF
859
+ fi
860
+ open=$(origin_open_decisions "$origin")
861
+ while IFS=$'\t' read -r key _verb _summary; do
862
+ [ -n "$key" ] || continue
863
+ fail "open captain decision $origin/$key is not transferred to the captain-held inventory; re-run complete"
864
+ done <<EOF
865
+ $open
866
+ EOF
867
+ printf 'verified: %s captain-call inventory\n' "$origin"
868
+ }
869
+
870
+ # --- record divergence ------------------------------------------------------
871
+ #
872
+ # A captain call can be written down twice, and until now nothing said when
873
+ # those two records disagreed. A `resolved [key=...]` line closes the status-log
874
+ # fold outright; the structured captain-held task is closed by a SEPARATE act
875
+ # (`answer` above). Closing only on the status side therefore looks complete
876
+ # there while the durable record still says the captain owes an answer and
877
+ # keeps resurfacing it. The defect was never the separation; it was the silence.
878
+ #
879
+ # `diverged` is a read-only report of that contradiction and nothing else. It
880
+ # closes NOTHING. A captain call closed wrongly disappears without review, which
881
+ # is strictly worse than the noise this prints, so reconciling a divergence stays
882
+ # a human-owned act - and it runs in either direction: record what the captain
883
+ # actually said with `answer`, or re-open the status decision when that
884
+ # resolution was not the captain's word.
885
+ #
886
+ # What it flags, and only this: a task that is still open and still carries the
887
+ # captain-hold annotations, whose key was closed on the status side by the
888
+ # RESOLVE verb. The other closing verb is not a divergence: a `captain-held`
889
+ # close is the VERIFIED transfer to that very task, written by command_complete
890
+ # only after verifying it, so the structured row staying open behind it is the
891
+ # correct state. Neither is a still-open status decision - the OPEN DECISIONS
892
+ # fold already owns that one.
893
+ #
894
+ # Routed work is deliberately irrelevant. When the decision IS the deliverable
895
+ # there is nothing to route, so the test is only whether the status side already
896
+ # declared this task's key resolved.
897
+ # Nor does the report interpret why that resolution exists. A call can turn out
898
+ # not to be a captain arbitration at all - a premise can dissolve, or a question
899
+ # of fact can prove its first reading wrong - so the report says only that the
900
+ # two records disagree and names both reconciliation directions above.
901
+ #
902
+ # Cost stays flat on a healthy home: one `tasks-axi list`, one key scan per
903
+ # status log, and the precise per-key fold only for a key that already names a
904
+ # still-open task. If tasks-axi is unavailable or its listing cannot be parsed,
905
+ # the guard cannot read the structured record and prints nothing.
906
+ #
907
+ # Output: one `<task-id>\t<origin>\t<key>\t<title>` line per divergence, in
908
+ # status-log then key order; nothing when the two records agree.
909
+
910
+ # Every still-open task id in this home's backlog, one per line. Only the first
911
+ # two comma-separated listing fields are read - both are slugs that precede any
912
+ # quoted title - so a title containing commas or quotes cannot shift them.
913
+ open_task_ids() {
914
+ tasks_axi list 2>/dev/null | awk -F, '
915
+ /^ [A-Za-z0-9._-]+,/ {
916
+ id = $1
917
+ sub(/^ +/, "", id)
918
+ if ($2 != "done") print id
919
+ }
920
+ '
921
+ }
922
+
923
+ # Every key token stated anywhere in a status log. A cheap candidate scan: it
924
+ # over-includes tokens that are only prose, and status_key_closing_verb below is
925
+ # what actually decides what the stream says about a key.
926
+ status_log_key_tokens() { # <status-file>
927
+ grep -o '\[key=[A-Za-z0-9._-]*\]' "$1" 2>/dev/null |
928
+ sed 's/^\[key=//; s/\]$//' | LC_ALL=C sort -u
929
+ }
930
+
931
+ list_has_line() { # <newline-separated-list> <value>
932
+ case $'\n'"$1"$'\n' in
933
+ *$'\n'"$2"$'\n'*) return 0 ;;
934
+ *) return 1 ;;
935
+ esac
936
+ }
937
+
938
+ command_diverged() {
939
+ local ids resolve f origin tokens id keys key show title
940
+ [ "$#" -eq 0 ] || { usage >&2; exit 2; }
941
+ # Both records must belong to the SAME home or the comparison is meaningless:
942
+ # tasks-axi reads $FM_HOME's backlog, so a state dir pointed somewhere else
943
+ # would report one home's status logs against another home's tasks. Every
944
+ # production caller pairs the two; a mismatch stays silent rather than
945
+ # inventing a cross-home divergence.
946
+ [ "$STATE" = "$FM_HOME/state" ] || return 0
947
+ # A read-only listing on a per-wake path, so it skips the mutation-oriented
948
+ # compatibility floor and its extra probes: a listing this parser cannot read
949
+ # simply yields no candidates and the report stays silent.
950
+ command -v tasks-axi >/dev/null 2>&1 || return 0
951
+ ids=$(open_task_ids) || return 0
952
+ [ -n "$ids" ] || return 0
953
+ resolve=${FM_CLASSIFY_RESOLVE_VERB:-$FM_CLASSIFY_RESOLVE_VERB_DEFAULT}
954
+ for f in "$STATE"/*.status; do
955
+ [ -f "$f" ] && [ -r "$f" ] && [ ! -L "$f" ] || continue
956
+ origin=$(basename "$f"); origin=${origin%.status}
957
+ tokens=$(status_log_key_tokens "$f")
958
+ [ -n "$tokens" ] || continue
959
+ while IFS= read -r id; do
960
+ [ -n "$id" ] || continue
961
+ # The keys that could name this task in THIS log: the collapsed identity
962
+ # (the key IS the task id) and, for a pre-collapse row, the legacy derived
963
+ # one this origin would have minted.
964
+ keys=$id
965
+ case "$id" in
966
+ "$origin-decision-"?*) keys="$keys"$'\n'"${id#"$origin-decision-"}" ;;
967
+ esac
968
+ while IFS= read -r key; do
969
+ list_has_line "$tokens" "$key" || continue
970
+ [ "$(status_key_closing_verb "$f" "$key")" = "$resolve" ] || continue
971
+ show=$(task_show "$id") || continue
972
+ [ "$(show_field "$show" state)" != "done" ] || continue
973
+ [ "$(show_field_value "$show" hold_kind)" = captain ] || continue
974
+ # The title is the only free-text field here, and the report is
975
+ # TAB-separated, so it goes through the same sanitizer every other
976
+ # emitted field uses rather than being trusted to stay one clean line.
977
+ title=$(sanitize_field "$(show_field_value "$show" title)")
978
+ printf '%s\t%s\t%s\t%s\n' "$id" "$origin" "$key" "$title"
979
+ break
980
+ done <<INNER
981
+ $keys
982
+ INNER
983
+ done <<EOF
984
+ $ids
985
+ EOF
986
+ done
987
+ }
988
+
989
+ case "${1:-}" in
990
+ hold) shift; command_hold "$@" ;;
991
+ answer) shift; command_answer "$@" ;;
992
+ answers) shift; command_answers "$@" ;;
993
+ bind) shift; command_bind "$@" ;;
994
+ unbind) shift; command_unbind "$@" ;;
995
+ binding) shift; command_binding "$@" ;;
996
+ complete) shift; command_complete "$@" ;;
997
+ verify) shift; command_verify "$@" ;;
998
+ diverged) shift; command_diverged "$@" ;;
999
+ -h|--help) usage ;;
1000
+ *) usage >&2; exit 2 ;;
1001
+ esac