@zerwiz/ymir 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (850) hide show
  1. package/.agents/README.md +291 -0
  2. package/.agents/agents/bragi-marketer.md +111 -0
  3. package/.agents/agents/brokk.md +352 -0
  4. package/.agents/agents/forseti-reviewer.md +93 -0
  5. package/.agents/agents/frigg-consent.md +77 -0
  6. package/.agents/agents/groa-update.md +77 -0
  7. package/.agents/agents/hnoss-designer.md +69 -0
  8. package/.agents/agents/huginn-researcher.md +99 -0
  9. package/.agents/agents/jord-projects.md +78 -0
  10. package/.agents/agents/kvasir-scout.md +91 -0
  11. package/.agents/agents/mimir-planner.md +92 -0
  12. package/.agents/agents/muninn-stow.md +77 -0
  13. package/.agents/agents/saga-bearings.md +78 -0
  14. package/.agents/agents/sindri-developer.md +114 -0
  15. package/.agents/agents/snotra-documenter.md +105 -0
  16. package/.agents/agents/syn-recovery.md +78 -0
  17. package/.agents/agents/tyr-check.md +78 -0
  18. package/.agents/agents/urdh-hold.md +77 -0
  19. package/.agents/agents/volundr-smith.md +80 -0
  20. package/.agents/agents/vor-diagnostics.md +77 -0
  21. package/.agents/assets/agents/naming.md +50 -0
  22. package/.agents/assets/agents/registry.md +93 -0
  23. package/.agents/assets/agents/runtime.md +36 -0
  24. package/.agents/assets/agents/toon-tasks-cli.md +42 -0
  25. package/.agents/assets/templates/company_entity.template.md +37 -0
  26. package/.agents/backend/backends/cmux.sh +649 -0
  27. package/.agents/backend/backends/herdr-eventwait.py +157 -0
  28. package/.agents/backend/backends/herdr-workspace-move.py +114 -0
  29. package/.agents/backend/backends/herdr.sh +3386 -0
  30. package/.agents/backend/backends/orca.sh +290 -0
  31. package/.agents/backend/backends/tmux.sh +357 -0
  32. package/.agents/backend/backends/zellij.sh +700 -0
  33. package/.agents/backend/fm-afk-launch.sh +654 -0
  34. package/.agents/backend/fm-afk-return.sh +241 -0
  35. package/.agents/backend/fm-afk-start.sh +172 -0
  36. package/.agents/backend/fm-arm-command-policy.mjs +960 -0
  37. package/.agents/backend/fm-arm-pretool-check.sh +200 -0
  38. package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
  39. package/.agents/backend/fm-backend.sh +981 -0
  40. package/.agents/backend/fm-backlog-handoff.sh +909 -0
  41. package/.agents/backend/fm-backlog-receive.sh +187 -0
  42. package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
  43. package/.agents/backend/fm-bearings-board.sh +204 -0
  44. package/.agents/backend/fm-bearings-snapshot.sh +546 -0
  45. package/.agents/backend/fm-bootstrap.sh +1568 -0
  46. package/.agents/backend/fm-branch-outcome.sh +203 -0
  47. package/.agents/backend/fm-branch-prompt.sh +101 -0
  48. package/.agents/backend/fm-brief.sh +452 -0
  49. package/.agents/backend/fm-busy-event.sh +234 -0
  50. package/.agents/backend/fm-busy-lib.sh +986 -0
  51. package/.agents/backend/fm-captain-hold.sh +1001 -0
  52. package/.agents/backend/fm-cd-command-policy.mjs +153 -0
  53. package/.agents/backend/fm-cd-pretool-check.sh +190 -0
  54. package/.agents/backend/fm-check-lib.sh +74 -0
  55. package/.agents/backend/fm-check-register.sh +42 -0
  56. package/.agents/backend/fm-check-unregister.sh +52 -0
  57. package/.agents/backend/fm-classify-lib.sh +1770 -0
  58. package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
  59. package/.agents/backend/fm-composer-lib.sh +1416 -0
  60. package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
  61. package/.agents/backend/fm-config-push.sh +254 -0
  62. package/.agents/backend/fm-control-lib.sh +251 -0
  63. package/.agents/backend/fm-control.sh +878 -0
  64. package/.agents/backend/fm-crew-state.sh +625 -0
  65. package/.agents/backend/fm-cursor-lib.sh +243 -0
  66. package/.agents/backend/fm-decision-hold.sh +232 -0
  67. package/.agents/backend/fm-doc-audience-check.sh +269 -0
  68. package/.agents/backend/fm-dod-lib.sh +67 -0
  69. package/.agents/backend/fm-ensure-agents-md.sh +253 -0
  70. package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
  71. package/.agents/backend/fm-extension.mjs +2577 -0
  72. package/.agents/backend/fm-extension.sh +16 -0
  73. package/.agents/backend/fm-ff-lib.sh +420 -0
  74. package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
  75. package/.agents/backend/fm-fleet-sync.sh +458 -0
  76. package/.agents/backend/fm-fleet-view.sh +96 -0
  77. package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
  78. package/.agents/backend/fm-guard.sh +242 -0
  79. package/.agents/backend/fm-harness.sh +195 -0
  80. package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
  81. package/.agents/backend/fm-herdr-lab.sh +346 -0
  82. package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
  83. package/.agents/backend/fm-home-seed.sh +984 -0
  84. package/.agents/backend/fm-home-summary-refresh.sh +255 -0
  85. package/.agents/backend/fm-hook-host-lib.sh +36 -0
  86. package/.agents/backend/fm-inactive-reconcile.sh +531 -0
  87. package/.agents/backend/fm-inbox.sh +399 -0
  88. package/.agents/backend/fm-install-actionlint.sh +84 -0
  89. package/.agents/backend/fm-install-herdr.sh +96 -0
  90. package/.agents/backend/fm-install-shellcheck.sh +84 -0
  91. package/.agents/backend/fm-install-treehouse.sh +95 -0
  92. package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
  93. package/.agents/backend/fm-lease-lib.sh +218 -0
  94. package/.agents/backend/fm-lease.sh +189 -0
  95. package/.agents/backend/fm-line-cap-lib.sh +51 -0
  96. package/.agents/backend/fm-lint-workflows.sh +137 -0
  97. package/.agents/backend/fm-lint.sh +591 -0
  98. package/.agents/backend/fm-lock-lib.sh +104 -0
  99. package/.agents/backend/fm-lock.sh +107 -0
  100. package/.agents/backend/fm-marker-lib.sh +12 -0
  101. package/.agents/backend/fm-merge-local.sh +74 -0
  102. package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
  103. package/.agents/backend/fm-nm-run-lib.sh +124 -0
  104. package/.agents/backend/fm-on.sh +125 -0
  105. package/.agents/backend/fm-operational-input.sh +253 -0
  106. package/.agents/backend/fm-peek.sh +46 -0
  107. package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
  108. package/.agents/backend/fm-pr-check.sh +135 -0
  109. package/.agents/backend/fm-pr-lib.sh +1016 -0
  110. package/.agents/backend/fm-pr-merge.sh +707 -0
  111. package/.agents/backend/fm-pr-poll.sh +110 -0
  112. package/.agents/backend/fm-primary-scope-lib.sh +33 -0
  113. package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
  114. package/.agents/backend/fm-procevent-lavish.sh +628 -0
  115. package/.agents/backend/fm-procevent-lib.sh +892 -0
  116. package/.agents/backend/fm-procevent-quota.sh +290 -0
  117. package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
  118. package/.agents/backend/fm-procevent-when.sh +504 -0
  119. package/.agents/backend/fm-procevent.sh +1466 -0
  120. package/.agents/backend/fm-project-mode.sh +92 -0
  121. package/.agents/backend/fm-project-origin-lib.sh +180 -0
  122. package/.agents/backend/fm-promote.sh +282 -0
  123. package/.agents/backend/fm-public-followup-emit.sh +260 -0
  124. package/.agents/backend/fm-public-followup-lib.sh +398 -0
  125. package/.agents/backend/fm-public-followup.sh +1330 -0
  126. package/.agents/backend/fm-push-transition-lib.sh +168 -0
  127. package/.agents/backend/fm-quota-axi-lib.sh +93 -0
  128. package/.agents/backend/fm-quota-choose.sh +384 -0
  129. package/.agents/backend/fm-remote-delta-read.sh +189 -0
  130. package/.agents/backend/fm-remote-doctor.sh +800 -0
  131. package/.agents/backend/fm-remote-entrypoint.sh +193 -0
  132. package/.agents/backend/fm-remote-file.sh +234 -0
  133. package/.agents/backend/fm-remote-home-provision.sh +259 -0
  134. package/.agents/backend/fm-remote-home-seed.sh +259 -0
  135. package/.agents/backend/fm-remote-inherit-push.sh +90 -0
  136. package/.agents/backend/fm-remote-inherit.sh +179 -0
  137. package/.agents/backend/fm-remote-job-lib.sh +1241 -0
  138. package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
  139. package/.agents/backend/fm-remote-job-worker.sh +1124 -0
  140. package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
  141. package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
  142. package/.agents/backend/fm-review-diff.sh +158 -0
  143. package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
  144. package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
  145. package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
  146. package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
  147. package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
  148. package/.agents/backend/fm-secondmate-report.sh +88 -0
  149. package/.agents/backend/fm-send.sh +1059 -0
  150. package/.agents/backend/fm-session-lock-lib.sh +176 -0
  151. package/.agents/backend/fm-session-start.sh +970 -0
  152. package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
  153. package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
  154. package/.agents/backend/fm-sessionstart-run.sh +145 -0
  155. package/.agents/backend/fm-spawn.sh +3153 -0
  156. package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
  157. package/.agents/backend/fm-startup-memory-budget.sh +94 -0
  158. package/.agents/backend/fm-startup-network.sh +642 -0
  159. package/.agents/backend/fm-stow-cascade.sh +251 -0
  160. package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
  161. package/.agents/backend/fm-supervise-daemon.sh +1732 -0
  162. package/.agents/backend/fm-supervision-instructions.sh +215 -0
  163. package/.agents/backend/fm-supervision-lib.sh +90 -0
  164. package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
  165. package/.agents/backend/fm-tangle-lib.sh +53 -0
  166. package/.agents/backend/fm-task-inbox-lib.sh +402 -0
  167. package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
  168. package/.agents/backend/fm-teardown.sh +2922 -0
  169. package/.agents/backend/fm-test-isolation-proof.sh +588 -0
  170. package/.agents/backend/fm-test-run.sh +2191 -0
  171. package/.agents/backend/fm-timeout-lib.sh +141 -0
  172. package/.agents/backend/fm-timing-lib.sh +179 -0
  173. package/.agents/backend/fm-tmux-lib.sh +291 -0
  174. package/.agents/backend/fm-tool-update-check.sh +898 -0
  175. package/.agents/backend/fm-trace-context-lib.sh +227 -0
  176. package/.agents/backend/fm-transition-lib.sh +103 -0
  177. package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
  178. package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
  179. package/.agents/backend/fm-turnend-guard.sh +432 -0
  180. package/.agents/backend/fm-update.sh +110 -0
  181. package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
  182. package/.agents/backend/fm-voice-client.py +1373 -0
  183. package/.agents/backend/fm-voice-relay.py +1256 -0
  184. package/.agents/backend/fm-wake-drain.sh +592 -0
  185. package/.agents/backend/fm-wake-grant.sh +127 -0
  186. package/.agents/backend/fm-wake-lib.sh +1849 -0
  187. package/.agents/backend/fm-watch-arm.sh +602 -0
  188. package/.agents/backend/fm-watch-checkpoint.sh +109 -0
  189. package/.agents/backend/fm-watch.sh +1962 -0
  190. package/.agents/backend/fm-x-dismiss.sh +116 -0
  191. package/.agents/backend/fm-x-followup.sh +286 -0
  192. package/.agents/backend/fm-x-lib.sh +1000 -0
  193. package/.agents/backend/fm-x-link.sh +234 -0
  194. package/.agents/backend/fm-x-poll.sh +185 -0
  195. package/.agents/backend/fm-x-reply.sh +385 -0
  196. package/.agents/backend/fm_voice_frame.py +166 -0
  197. package/.agents/backend/fm_voice_records.py +574 -0
  198. package/.agents/backend/model-bridge.py +290 -0
  199. package/.agents/backend/opencode-go-bridge.py +30 -0
  200. package/.agents/bus/README.md +12 -0
  201. package/.agents/bus/messages.json +1 -0
  202. package/.agents/config/agents.machine.example.yaml +16 -0
  203. package/.agents/config/agents.yaml +50 -0
  204. package/.agents/config/agents.yaml.example +72 -0
  205. package/.agents/config/crew-dispatch.json +22 -0
  206. package/.agents/config/cron.yaml +9 -0
  207. package/.agents/config/eindri-dispatch.json +67 -0
  208. package/.agents/config/eindri-harness +1 -0
  209. package/.agents/config/startup-memory-budget +1 -0
  210. package/.agents/config/tailscale-sync.yaml +27 -0
  211. package/.agents/filebrowser/.filebrowser.json +6 -0
  212. package/.agents/filebrowser/README.md +11 -0
  213. package/.agents/gateway/README.md +12 -0
  214. package/.agents/github/README.md +15 -0
  215. package/.agents/github/webhooks/.gitkeep +0 -0
  216. package/.agents/github/workflows/.gitkeep +0 -0
  217. package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
  218. package/.agents/harness/opencode/plugins/package.json +4 -0
  219. package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
  220. package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
  221. package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
  222. package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
  223. package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
  224. package/.agents/memory/README.md +46 -0
  225. package/.agents/memory/kaia.engram +0 -0
  226. package/.agents/memory/kaia.engram-shm +0 -0
  227. package/.agents/memory/kaia.engram-wal +0 -0
  228. package/.agents/memory/runes_audit.md +5 -0
  229. package/.agents/memory/well/episodes.jsonl +367 -0
  230. package/.agents/memory/well/workspace.jsonl +27 -0
  231. package/.agents/migrations/0001-hodd-layout.sh +38 -0
  232. package/.agents/migrations/0002-a2a-mcp.sh +27 -0
  233. package/.agents/migrations/0003-private-data-separation.sh +111 -0
  234. package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
  235. package/.agents/sandbox/Dockerfile.utgard +19 -0
  236. package/.agents/sandbox/README.md +14 -0
  237. package/.agents/sandbox/utgard.config.json +14 -0
  238. package/.agents/skills/README.md +59 -0
  239. package/.agents/skills/bragi-marketing/README.md +3 -0
  240. package/.agents/skills/bragi-marketing/SKILL.md +82 -0
  241. package/.agents/skills/eindri-homes/README.md +3 -0
  242. package/.agents/skills/eindri-homes/SKILL.md +291 -0
  243. package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
  244. package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
  245. package/.agents/skills/frigg-consent/README.md +3 -0
  246. package/.agents/skills/frigg-consent/SKILL.md +56 -0
  247. package/.agents/skills/galdr-ymirsystem/README.md +3 -0
  248. package/.agents/skills/galdr-ymirsystem/SKILL.md +158 -0
  249. package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
  250. package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
  251. package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
  252. package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
  253. package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
  254. package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
  255. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +673 -0
  256. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
  257. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
  258. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
  259. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
  260. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
  261. package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +496 -0
  262. package/.agents/skills/galdr-ymirsystem/assets/installation.md +571 -0
  263. package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
  264. package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
  265. package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +161 -0
  266. package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +377 -0
  267. package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
  268. package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +129 -0
  269. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
  270. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
  271. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +96 -0
  272. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
  273. package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
  274. package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
  275. package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
  276. package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
  277. package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
  278. package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
  279. package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
  280. package/.agents/skills/galdr-ymirsystem/assets/smidja.md +175 -0
  281. package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
  282. package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
  283. package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
  284. package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +360 -0
  285. package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
  286. package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
  287. package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
  288. package/.agents/skills/gjallarhorn-relay/README.md +3 -0
  289. package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
  290. package/.agents/skills/groa-update/SKILL.md +70 -0
  291. package/.agents/skills/hamr-adapters/README.md +3 -0
  292. package/.agents/skills/hamr-adapters/SKILL.md +123 -0
  293. package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
  294. package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
  295. package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
  296. package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
  297. package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
  298. package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
  299. package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
  300. package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
  301. package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
  302. package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
  303. package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
  304. package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
  305. package/.agents/skills/herdr-panes/README.md +3 -0
  306. package/.agents/skills/herdr-panes/SKILL.md +92 -0
  307. package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
  308. package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
  309. package/.agents/skills/hnoss-design/DESIGN.md +19 -0
  310. package/.agents/skills/hnoss-design/README.md +3 -0
  311. package/.agents/skills/hnoss-design/SKILL.md +83 -0
  312. package/.agents/skills/hvild-afk/README.md +3 -0
  313. package/.agents/skills/hvild-afk/SKILL.md +241 -0
  314. package/.agents/skills/jord-projects/README.md +3 -0
  315. package/.agents/skills/jord-projects/SKILL.md +92 -0
  316. package/.agents/skills/lifecycle/SKILL.md +56 -0
  317. package/.agents/skills/lifecycle/smoke_test.sh +52 -0
  318. package/.agents/skills/lifecycle/start.sh +21 -0
  319. package/.agents/skills/lifecycle/status.sh +35 -0
  320. package/.agents/skills/lifecycle/stop.sh +16 -0
  321. package/.agents/skills/muninn-stow/README.md +3 -0
  322. package/.agents/skills/muninn-stow/SKILL.md +309 -0
  323. package/.agents/skills/no-mistakes/SKILL.md +379 -0
  324. package/.agents/skills/nornir-schedule/README.md +3 -0
  325. package/.agents/skills/nornir-schedule/SKILL.md +25 -0
  326. package/.agents/skills/nornir-schedule/assets/events.md +137 -0
  327. package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
  328. package/.agents/skills/nsr-compliance/README.md +3 -0
  329. package/.agents/skills/nsr-compliance/SKILL.md +29 -0
  330. package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
  331. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
  332. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
  333. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
  334. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
  335. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
  336. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
  337. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
  338. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
  339. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
  340. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
  341. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
  342. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
  343. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
  344. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
  345. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
  346. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
  347. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
  348. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
  349. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
  350. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
  351. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
  352. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
  353. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  354. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
  355. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
  356. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
  357. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
  358. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
  359. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
  360. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
  361. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
  362. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
  363. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
  364. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
  365. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
  366. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
  367. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
  368. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
  369. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
  370. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
  371. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
  372. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
  373. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
  374. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
  375. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
  376. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
  377. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
  378. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
  379. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
  380. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
  381. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
  382. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
  383. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
  384. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
  385. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
  386. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
  387. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
  388. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
  389. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
  390. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
  391. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
  392. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
  393. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
  394. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
  395. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
  396. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
  397. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
  398. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
  399. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
  400. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
  401. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
  402. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
  403. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
  404. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
  405. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
  406. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
  407. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
  408. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
  409. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
  410. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
  411. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
  412. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
  413. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
  414. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
  415. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
  416. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
  417. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
  418. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
  419. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
  420. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
  421. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
  422. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
  423. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
  424. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
  425. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
  426. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
  427. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
  428. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
  429. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
  430. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
  431. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
  432. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
  433. package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
  434. package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
  435. package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
  436. package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
  437. package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
  438. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
  439. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
  440. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
  441. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
  442. package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
  443. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
  444. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
  445. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
  446. package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
  447. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
  448. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
  449. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
  450. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
  451. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
  452. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
  453. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
  454. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  455. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
  456. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
  457. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
  458. package/.agents/skills/pr-ops/README.md +3 -0
  459. package/.agents/skills/pr-ops/SKILL.md +109 -0
  460. package/.agents/skills/ratatoskr-a2a/README.md +3 -0
  461. package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
  462. package/.agents/skills/rules-check-drift/SKILL.md +81 -0
  463. package/.agents/skills/saga-bearings/README.md +3 -0
  464. package/.agents/skills/saga-bearings/SKILL.md +26 -0
  465. package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
  466. package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
  467. package/.agents/skills/saga-bearings/assets/recap.md +56 -0
  468. package/.agents/skills/syn-recovery/README.md +3 -0
  469. package/.agents/skills/syn-recovery/SKILL.md +54 -0
  470. package/.agents/skills/tyr-check/README.md +3 -0
  471. package/.agents/skills/tyr-check/SKILL.md +218 -0
  472. package/.agents/skills/urdh-hold/README.md +3 -0
  473. package/.agents/skills/urdh-hold/SKILL.md +25 -0
  474. package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
  475. package/.agents/skills/urdh-hold/assets/hold.md +54 -0
  476. package/.agents/skills/vor-diagnostics/README.md +3 -0
  477. package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
  478. package/.agents/skills/ymir-host/README.md +3 -0
  479. package/.agents/skills/ymir-host/SKILL.md +34 -0
  480. package/.agents/skills/ymir-host/assets/desktop.md +36 -0
  481. package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
  482. package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
  483. package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
  484. package/.agents/skills/ymir-host/assets/update.md +64 -0
  485. package/.agents/tests/assets/board-render-harness.mjs +117 -0
  486. package/.agents/tests/cmux-test-safety.sh +51 -0
  487. package/.agents/tests/fixtures.sh +291 -0
  488. package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
  489. package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
  490. package/.agents/tests/fm-afk-launch.test.sh +961 -0
  491. package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
  492. package/.agents/tests/fm-afk-return.test.sh +283 -0
  493. package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
  494. package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
  495. package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
  496. package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
  497. package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
  498. package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
  499. package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
  500. package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
  501. package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
  502. package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
  503. package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
  504. package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
  505. package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
  506. package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
  507. package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
  508. package/.agents/tests/fm-backend-orca.test.sh +1357 -0
  509. package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
  510. package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
  511. package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
  512. package/.agents/tests/fm-backend.test.sh +1142 -0
  513. package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
  514. package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
  515. package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
  516. package/.agents/tests/fm-bearings-board.test.sh +414 -0
  517. package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
  518. package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
  519. package/.agents/tests/fm-bootstrap.test.sh +1178 -0
  520. package/.agents/tests/fm-branch-supervision.test.sh +552 -0
  521. package/.agents/tests/fm-brief.test.sh +785 -0
  522. package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
  523. package/.agents/tests/fm-busy-state.test.sh +465 -0
  524. package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
  525. package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
  526. package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
  527. package/.agents/tests/fm-check-unregister.test.sh +198 -0
  528. package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
  529. package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
  530. package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
  531. package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
  532. package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
  533. package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
  534. package/.agents/tests/fm-composer-ghost.test.sh +716 -0
  535. package/.agents/tests/fm-composer-lib.test.sh +666 -0
  536. package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
  537. package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
  538. package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
  539. package/.agents/tests/fm-control.test.sh +910 -0
  540. package/.agents/tests/fm-crew-state.test.sh +1610 -0
  541. package/.agents/tests/fm-cursor-harness.test.sh +404 -0
  542. package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
  543. package/.agents/tests/fm-cursor-primary.test.sh +708 -0
  544. package/.agents/tests/fm-daemon.test.sh +2740 -0
  545. package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
  546. package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
  547. package/.agents/tests/fm-extension-binding.test.sh +2187 -0
  548. package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
  549. package/.agents/tests/fm-fleet-sync.test.sh +721 -0
  550. package/.agents/tests/fm-gate-refuse.test.sh +356 -0
  551. package/.agents/tests/fm-gitignore-config.test.sh +89 -0
  552. package/.agents/tests/fm-gotmp.test.sh +230 -0
  553. package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
  554. package/.agents/tests/fm-grok-harness.test.sh +117 -0
  555. package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
  556. package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
  557. package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
  558. package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
  559. package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
  560. package/.agents/tests/fm-herdr-lab.test.sh +243 -0
  561. package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
  562. package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
  563. package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
  564. package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
  565. package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
  566. package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
  567. package/.agents/tests/fm-kimi-harness.test.sh +687 -0
  568. package/.agents/tests/fm-lint-workflows.test.sh +532 -0
  569. package/.agents/tests/fm-lint.test.sh +1023 -0
  570. package/.agents/tests/fm-muse-harness.test.sh +932 -0
  571. package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
  572. package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
  573. package/.agents/tests/fm-on.test.sh +507 -0
  574. package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
  575. package/.agents/tests/fm-operational-input.test.sh +160 -0
  576. package/.agents/tests/fm-peek-remote.test.sh +110 -0
  577. package/.agents/tests/fm-pending-reply.test.sh +1288 -0
  578. package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
  579. package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
  580. package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
  581. package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
  582. package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
  583. package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
  584. package/.agents/tests/fm-pr-merge.test.sh +2135 -0
  585. package/.agents/tests/fm-procevent-quota.test.sh +225 -0
  586. package/.agents/tests/fm-procevent-when.test.sh +406 -0
  587. package/.agents/tests/fm-procevent.test.sh +1745 -0
  588. package/.agents/tests/fm-project-origin.test.sh +110 -0
  589. package/.agents/tests/fm-public-followup.test.sh +2272 -0
  590. package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
  591. package/.agents/tests/fm-quota-choose.test.sh +626 -0
  592. package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
  593. package/.agents/tests/fm-remote-doctor.test.sh +625 -0
  594. package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
  595. package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
  596. package/.agents/tests/fm-remote-job.test.sh +768 -0
  597. package/.agents/tests/fm-remote-reply.test.sh +520 -0
  598. package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
  599. package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
  600. package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
  601. package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
  602. package/.agents/tests/fm-review-diff.test.sh +176 -0
  603. package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
  604. package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
  605. package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
  606. package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
  607. package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
  608. package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
  609. package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
  610. package/.agents/tests/fm-send-inbox.test.sh +352 -0
  611. package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
  612. package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
  613. package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
  614. package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
  615. package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
  616. package/.agents/tests/fm-send-settle.test.sh +147 -0
  617. package/.agents/tests/fm-send-strict.test.sh +241 -0
  618. package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
  619. package/.agents/tests/fm-session-start.test.sh +2515 -0
  620. package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
  621. package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
  622. package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
  623. package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
  624. package/.agents/tests/fm-spawn-batch.test.sh +148 -0
  625. package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
  626. package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
  627. package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
  628. package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
  629. package/.agents/tests/fm-startup-network.test.sh +712 -0
  630. package/.agents/tests/fm-stow-cascade.test.sh +370 -0
  631. package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
  632. package/.agents/tests/fm-supervision-events.test.sh +156 -0
  633. package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
  634. package/.agents/tests/fm-tangle-guard.test.sh +277 -0
  635. package/.agents/tests/fm-task-delivery.test.sh +420 -0
  636. package/.agents/tests/fm-task-inbox.test.sh +513 -0
  637. package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
  638. package/.agents/tests/fm-teardown.test.sh +2665 -0
  639. package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
  640. package/.agents/tests/fm-test-fixtures.test.sh +132 -0
  641. package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
  642. package/.agents/tests/fm-test-run.test.sh +1196 -0
  643. package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
  644. package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
  645. package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
  646. package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
  647. package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
  648. package/.agents/tests/fm-transition-lib.test.sh +54 -0
  649. package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
  650. package/.agents/tests/fm-update.test.sh +304 -0
  651. package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
  652. package/.agents/tests/fm-voice-relay.test.sh +4741 -0
  653. package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
  654. package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
  655. package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
  656. package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
  657. package/.agents/tests/fm-wake-queue.test.sh +1224 -0
  658. package/.agents/tests/fm-watch-arm.test.sh +821 -0
  659. package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
  660. package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
  661. package/.agents/tests/fm-watch-triage.test.sh +3889 -0
  662. package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
  663. package/.agents/tests/fm-x-mode.test.sh +3042 -0
  664. package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
  665. package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
  666. package/.agents/tests/herdr-test-safety.sh +42 -0
  667. package/.agents/tests/lib.sh +315 -0
  668. package/.agents/tests/remote-herdr-fixture.sh +125 -0
  669. package/.agents/tests/secondmate-helpers.sh +193 -0
  670. package/.agents/tests/smoke.test.sh +93 -0
  671. package/.agents/tests/wake-helpers.sh +338 -0
  672. package/.agents/tests/zellij-test-safety.sh +60 -0
  673. package/.agents/tools/README.md +10 -0
  674. package/.agents/tools/bin/tasks-cli +267 -0
  675. package/.agents/tools/tasks-cli.ts +216 -0
  676. package/AGENTS.md +454 -0
  677. package/CHANGELOG.md +881 -0
  678. package/LICENSE +1 -1
  679. package/NOTICE +63 -0
  680. package/README.md +113 -39
  681. package/RULES/01-domains.md +51 -0
  682. package/RULES/02-agents.md +78 -0
  683. package/RULES/03-houses.md +61 -0
  684. package/RULES/04-hoard.md +65 -0
  685. package/RULES/05-platforms.md +67 -0
  686. package/RULES/06-append-only.md +58 -0
  687. package/RULES/07-config.md +45 -0
  688. package/RULES/08-delivery-gate.md +75 -0
  689. package/RULES/README.md +20 -0
  690. package/bin/README.md +107 -0
  691. package/bin/a2a-mcp.sh +98 -0
  692. package/bin/a2a-serve.py +103 -0
  693. package/bin/a2a-serve.sh +8 -0
  694. package/bin/a2a-talk.sh +50 -0
  695. package/bin/a2abridge-ensure.sh +108 -0
  696. package/bin/agent-run.sh +43 -0
  697. package/bin/agents-config.sh +233 -0
  698. package/bin/apodex-smoke-test.sh +92 -0
  699. package/bin/bifrost-bridge.sh +112 -0
  700. package/bin/bifrost-ingress.sh +77 -0
  701. package/bin/bootstrap-macos.sh +107 -0
  702. package/bin/bootstrap-windows.ps1 +112 -0
  703. package/bin/branch-guard.sh +64 -0
  704. package/bin/brokk +73 -0
  705. package/bin/brokk-classify-lib.sh +1770 -0
  706. package/bin/brokk-lease-lib.sh +222 -0
  707. package/bin/brokk-lease.sh +193 -0
  708. package/bin/brokk-lint.sh +65 -0
  709. package/bin/brokk-send.sh +38 -0
  710. package/bin/brokk-update.sh +9 -0
  711. package/bin/brokk-wake-grant.sh +127 -0
  712. package/bin/brokk-wake-lib.sh +1849 -0
  713. package/bin/changelog-guard.sh +165 -0
  714. package/bin/crash-sense.sh +160 -0
  715. package/bin/desktop-place.sh +224 -0
  716. package/bin/docs-guard.sh +39 -0
  717. package/bin/editor-place.sh +207 -0
  718. package/bin/eindri-acclaim.sh +34 -0
  719. package/bin/eindri-control.sh +28 -0
  720. package/bin/eindri-role.sh +83 -0
  721. package/bin/eindri-seen.sh +41 -0
  722. package/bin/eindri-send.sh +15 -0
  723. package/bin/eindri-start.sh +85 -0
  724. package/bin/eindri-watch.sh +57 -0
  725. package/bin/einherjar-spawn.sh +576 -0
  726. package/bin/eir-doctor.sh +112 -0
  727. package/bin/erindi-brief.sh +271 -0
  728. package/bin/fleet-apply.sh +68 -0
  729. package/bin/github-deploy.sh +81 -0
  730. package/bin/gjallarhorn-expose.sh +102 -0
  731. package/bin/gjallarhorn-notify.sh +58 -0
  732. package/bin/gjallarhorn-purge.sh +54 -0
  733. package/bin/gjallarhorn-tunnel.sh +72 -0
  734. package/bin/gleipnir-lock-lib.sh +284 -0
  735. package/bin/groa-update.sh +122 -0
  736. package/bin/hall-snapshot.sh +92 -0
  737. package/bin/hamr-harness.sh +246 -0
  738. package/bin/heimdall-ssh-keys.sh +374 -0
  739. package/bin/herdr-agents.py +44 -0
  740. package/bin/herdr-ensure.sh +104 -0
  741. package/bin/herdr-run.sh +431 -0
  742. package/bin/hermes-ensure.sh +88 -0
  743. package/bin/hoard-lib.sh +25 -0
  744. package/bin/hodd.sh +78 -0
  745. package/bin/host-sense.sh +199 -0
  746. package/bin/huginn-research-worker.sh +108 -0
  747. package/bin/local-model-lock.sh +51 -0
  748. package/bin/mimir-bridge.py +226 -0
  749. package/bin/mimir-bridge.sh +87 -0
  750. package/bin/mimir-ingest.sh +131 -0
  751. package/bin/mimir.sh +113 -0
  752. package/bin/mjollnir-webhook.sh +59 -0
  753. package/bin/mjollnir.sh +85 -0
  754. package/bin/model-hardware.sh +82 -0
  755. package/bin/model-resolve.sh +147 -0
  756. package/bin/models-detect.sh +178 -0
  757. package/bin/nornir-cron-start.sh +162 -0
  758. package/bin/nornir-job-daily-briefing.sh +191 -0
  759. package/bin/nornir-job-git-sync.sh +152 -0
  760. package/bin/nornir-job-memory-housekeeping.sh +114 -0
  761. package/bin/nornir-job-observer.sh +166 -0
  762. package/bin/nornir-job-skillopt-sleep.sh +76 -0
  763. package/bin/omarchy-hook-install.sh +60 -0
  764. package/bin/omarchy-install.sh +148 -0
  765. package/bin/omarchy-plugins.sh +174 -0
  766. package/bin/omarchy-sense.sh +149 -0
  767. package/bin/perm-guard.sh +30 -0
  768. package/bin/pi-agent.sh +68 -0
  769. package/bin/pi-ensure.sh +77 -0
  770. package/bin/pi-local.sh +37 -0
  771. package/bin/pi-seat.sh +63 -0
  772. package/bin/prereq-ensure.sh +166 -0
  773. package/bin/private-guard.sh +38 -0
  774. package/bin/project-git.sh +55 -0
  775. package/bin/public-guard.sh +70 -0
  776. package/bin/ratatoskr.sh +49 -0
  777. package/bin/realm-lib.sh +32 -0
  778. package/bin/repo-scrub.sh +62 -0
  779. package/bin/rodd-operational-input.sh +250 -0
  780. package/bin/runes-append.sh +198 -0
  781. package/bin/saga-session-start.sh +172 -0
  782. package/bin/saga-sessionstart-run.sh +79 -0
  783. package/bin/saga-wake-drain.sh +41 -0
  784. package/bin/secret-guard.sh +63 -0
  785. package/bin/sessrumnir-ensure.sh +100 -0
  786. package/bin/sessrumnir-sync.sh +237 -0
  787. package/bin/sessrumnir.sh +91 -0
  788. package/bin/skillopt-setup.sh +76 -0
  789. package/bin/skuld-branch-outcome.sh +203 -0
  790. package/bin/skuld-branch-prompt.sh +101 -0
  791. package/bin/smidja-bootstrap.sh +90 -0
  792. package/bin/smidja-observe.sh +86 -0
  793. package/bin/syn-arm-pretool-check.sh +57 -0
  794. package/bin/syn-asset-pretool-check.sh +76 -0
  795. package/bin/syn-cd-pretool-check.sh +28 -0
  796. package/bin/syn-guard-pretool-check.sh +83 -0
  797. package/bin/syn-turnend-guard.sh +36 -0
  798. package/bin/syn-watch-arm.sh +99 -0
  799. package/bin/tailscale-sync.sh +190 -0
  800. package/bin/telegram-bot.sh +69 -0
  801. package/bin/toolchain.sh +74 -0
  802. package/bin/utgard.sh +111 -0
  803. package/bin/valhalla.sh +115 -0
  804. package/bin/valknut-load.sh +260 -0
  805. package/bin/vor-crew-state.sh +124 -0
  806. package/bin/wedge-notify.sh +47 -0
  807. package/bin/workspace-provision.sh +80 -0
  808. package/bin/workspace-rag.sh +150 -0
  809. package/bin/wyrd-db.sh +47 -0
  810. package/bin/yggdrasil.sh +152 -0
  811. package/bin/ymir-edit.sh +145 -0
  812. package/bin/ymir-install.sh +641 -0
  813. package/bin/ymir-invite.sh +93 -0
  814. package/bin/ymir-isolation.sh +88 -0
  815. package/bin/ymir-migrate.sh +61 -0
  816. package/bin/ymir-platform.sh +348 -0
  817. package/bin/ymir-say.sh +97 -0
  818. package/bin/ymir-setup-auth.sh +108 -0
  819. package/bin/ymir-validate.sh +173 -0
  820. package/bin/ymir.js +74 -16
  821. package/docs/Architecture.md +339 -0
  822. package/docs/design.md +351 -0
  823. package/docs/installations/README.md +63 -0
  824. package/docs/installations/ymir-home.md +93 -0
  825. package/docs/integration.md +105 -0
  826. package/docs/lore.md +1056 -0
  827. package/docs/research/agent-harnesses.md +290 -0
  828. package/docs/runbooks/README.md +19 -0
  829. package/docs/runbooks/a2a.md +91 -0
  830. package/docs/runbooks/agent-permissions.md +99 -0
  831. package/docs/runbooks/agents.md +57 -0
  832. package/docs/runbooks/models.md +69 -0
  833. package/docs/runbooks/realm-onboarding.md +60 -0
  834. package/docs/runbooks/secrets-and-hoard.md +60 -0
  835. package/docs/runbooks/updates-and-migrations.md +110 -0
  836. package/docs/runbooks/worktrees-isolation.md +72 -0
  837. package/docs/session-start.md +105 -0
  838. package/docs/skillopt-integration.md +321 -0
  839. package/docs/sync-system.md +66 -0
  840. package/docs/workspaces.md +104 -0
  841. package/docs/ymir-rut.md +260 -0
  842. package/package.json +44 -14
  843. package/packaging/build.sh +74 -0
  844. package/packaging/macos/Ymir Installer.command +43 -0
  845. package/packaging/windows/ymir-setup.nsi +74 -0
  846. package/scripts/README.md +22 -0
  847. package/scripts/electron.sh +210 -0
  848. package/scripts/start.sh +167 -0
  849. package/scripts/stop.sh +113 -0
  850. package/install.sh +0 -35
@@ -0,0 +1,1202 @@
1
+ # shellcheck shell=bash
2
+ # Inheritance propagation: the PRIMARY firstmate pushes a declared, extensible
3
+ # set of LOCAL (gitignored) config items down into each secondmate home's
4
+ # config/, so a secondmate's OWN crewmates inherit the primary's settings
5
+ # (e.g. primary config/crew-dispatch.json makes a secondmate use the same dispatch
6
+ # profile rules, primary config/crew-harness=codex makes a secondmate's crewmates
7
+ # spawn on codex too, primary config/backlog-backend=manual makes that home
8
+ # hand-edit backlog files too, primary config/backend pins that home's local
9
+ # runtime-backend default for future spawns, primary config/startup-memory-budget
10
+ # bounds that home's startup-memory curation, and primary
11
+ # config/herdr-presentation-spaces carries the same Herdr presentation-projection
12
+ # preference - an absent primary file and an absent destination file both mean
13
+ # the same unconfigured default, so the generic absence mirror below converges
14
+ # a secondmate without deciding the release-dependent floor; explicit "on" and
15
+ # "off" preferences propagate as files. Primary
16
+ # config/trace-context is copied at the launch convergence point as part of the
17
+ # default-off W3C trace-context setup, while live convergence leaves it unchanged.
18
+ # The primary passes its frozen home-session decision into a newly launched
19
+ # Secondmate; see docs/trace-context.md.
20
+ # It also pushes
21
+ # the one primary-authoritative shared captain-preference file,
22
+ # data/captain-shared.md, into each secondmate home's data/ as a read-only copy.
23
+ #
24
+ # Usage: . bin/fm-config-inherit-lib.sh (no FM_* setup required)
25
+ #
26
+ # Why this is separate from the tracked-files fast-forward (fm-ff-lib.sh): config/
27
+ # is gitignored, so a tracked-files fast-forward never carries these items. This
28
+ # is an explicit copy run at the convergence points the primary owns - a
29
+ # secondmate spawn (bin/fm-spawn.sh), the bootstrap secondmate sweep
30
+ # (bin/fm-bootstrap.sh), and the focused mid-session config push
31
+ # (bin/fm-config-push.sh). It is PRIMARY-AUTHORITATIVE: the primary's value wins
32
+ # and is re-pushed on every convergence, so the fleet stays converged on the
33
+ # primary; an item the primary does not set is mirrored as absence downstream.
34
+ # After successful config/* changes under an already-running secondmate, callers
35
+ # invoke fm_config_send_reread_nudge so the live agent re-reads exact post-write
36
+ # bytes (spawn/respawn already re-reads at launch and needs no redundant nudge).
37
+ #
38
+ # Extensible by design: FM_INHERITABLE_CONFIG is the single declared list of
39
+ # config-dir-relative items the primary propagates. Add an item there and every
40
+ # convergence point inherits it - no other change needed. config/secondmate-harness
41
+ # is deliberately NOT in the list: it is the primary's own setting for launching
42
+ # secondmates, and a secondmate never spawns secondmates, so it must not flow
43
+ # downstream.
44
+ #
45
+ # That single declaration is also the ONE owner of the inherited-material
46
+ # allowlist for remote routes: bin/fm-remote-inherit-push.sh (sender) and
47
+ # bin/fm-remote-inherit.sh (receiver, executing inside the remote home) both
48
+ # derive their item set from fm_config_inherit_items rather than restating it,
49
+ # so a new inheritable item cannot be accepted by one side and refused by the
50
+ # other. A local and remote code root that disagree about this list must be
51
+ # reconciled by the ordinary remote sync/update path before the transfer
52
+ # succeeds; there is no separate allowlist version negotiation.
53
+ #
54
+ # shellcheck source=bin/fm-startup-memory-budget-lib.sh
55
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/fm-startup-memory-budget-lib.sh"
56
+
57
+ # The one shared data file in this inheritance contract. There is deliberately
58
+ # no shared learnings file.
59
+ FM_SHARED_CAPTAIN_FILE="captain-shared.md"
60
+ FM_SHARED_CAPTAIN_REL="data/$FM_SHARED_CAPTAIN_FILE"
61
+ FM_SHARED_CAPTAIN_MODE="444"
62
+
63
+ # The declared inheritable set (space-separated, config-dir-relative item paths).
64
+ # Extend here to inherit more of the primary's local config; override via the
65
+ # environment only in tests. Items must not contain whitespace.
66
+ FM_INHERITABLE_CONFIG="${FM_INHERITABLE_CONFIG:-crew-dispatch.json crew-harness backlog-backend backend herdr-presentation-spaces startup-memory-budget trace-context}"
67
+
68
+ # Items whose value is a home-SESSION enablement decision rather than durable
69
+ # local configuration. They are inherited at the launch convergence point, where
70
+ # the primary also hands the new process its frozen on/off decision, and left
71
+ # untouched by live convergence into an already-running home, whose decision is
72
+ # already frozen for its current session (bin/fm-trace-context-lib.sh).
73
+ FM_SESSION_SCOPED_INHERITABLE_CONFIG="trace-context"
74
+
75
+ # True when <item> is session-scoped in the sense above.
76
+ fm_config_inherit_item_session_scoped() { # <item>
77
+ local item=$1 candidate
78
+ for candidate in $FM_SESSION_SCOPED_INHERITABLE_CONFIG; do
79
+ [ "$candidate" = "$item" ] && return 0
80
+ done
81
+ return 1
82
+ }
83
+
84
+ # The complete declared inherited-material set as home-relative paths, one per
85
+ # line, in propagation order: every FM_INHERITABLE_CONFIG item under config/,
86
+ # then the one shared data file. This is what remote senders and receivers
87
+ # derive from, so both ends of a transfer agree by construction.
88
+ fm_config_inherit_items() {
89
+ local item
90
+ for item in $FM_INHERITABLE_CONFIG; do
91
+ printf 'config/%s\n' "$item"
92
+ done
93
+ printf '%s\n' "$FM_SHARED_CAPTAIN_REL"
94
+ }
95
+
96
+ fm_inherit_file_mode() {
97
+ if [ "$(uname)" = Darwin ]; then
98
+ stat -f %Lp "$1" 2>/dev/null
99
+ else
100
+ stat -c %a "$1" 2>/dev/null
101
+ fi
102
+ }
103
+
104
+ fm_inherit_file_device() {
105
+ if [ "$(uname)" = Darwin ]; then
106
+ stat -f %d "$1" 2>/dev/null
107
+ else
108
+ stat -c %d "$1" 2>/dev/null
109
+ fi
110
+ }
111
+
112
+ fm_inherit_file_link_count() {
113
+ if [ "$(uname)" = Darwin ]; then
114
+ stat -f %l "$1" 2>/dev/null
115
+ else
116
+ stat -c %h "$1" 2>/dev/null
117
+ fi
118
+ }
119
+
120
+ fm_inherit_sha256() {
121
+ if command -v shasum >/dev/null 2>&1; then
122
+ shasum -a 256 "$1" 2>/dev/null | awk '{print $1}'
123
+ elif command -v sha256sum >/dev/null 2>&1; then
124
+ sha256sum "$1" 2>/dev/null | awk '{print $1}'
125
+ else
126
+ return 1
127
+ fi
128
+ }
129
+
130
+ copy_inheritable_file() {
131
+ local src=$1 dest=$2 dest_parent tmp
132
+ if [ -e "$dest" ] && [ ! -f "$dest" ] && [ ! -L "$dest" ]; then
133
+ return 1
134
+ fi
135
+ dest_parent=${dest%/*}
136
+ [ -n "$dest_parent" ] && [ "$dest_parent" != "$dest" ] || return 1
137
+ mkdir -p "$dest_parent" 2>/dev/null || return 1
138
+ tmp=$(mktemp "$dest_parent/.fm-inherit.XXXXXX" 2>/dev/null) || return 1
139
+ if ! cp "$src" "$tmp" 2>/dev/null; then
140
+ rm -f "$tmp" 2>/dev/null || true
141
+ return 1
142
+ fi
143
+ if [ -L "$dest" ] && ! rm -f "$dest" 2>/dev/null; then
144
+ rm -f "$tmp" 2>/dev/null || true
145
+ return 1
146
+ fi
147
+ if mv -f "$tmp" "$dest" 2>/dev/null; then
148
+ return 0
149
+ fi
150
+ rm -f "$tmp" 2>/dev/null || true
151
+ return 1
152
+ }
153
+
154
+ destination_allows_inherited_item() {
155
+ local dest_config=$1 item=$2 dest_parent dest_name dest_parent_abs top dest_path rel_path
156
+ dest_parent=${dest_config%/*}
157
+ dest_name=${dest_config##*/}
158
+ [ -n "$dest_parent" ] && [ "$dest_parent" != "$dest_config" ] || return 1
159
+ dest_parent_abs=$(cd "$dest_parent" 2>/dev/null && pwd -P) || return 1
160
+ if ! git -C "$dest_parent_abs" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
161
+ return 0
162
+ fi
163
+ top=$(git -C "$dest_parent_abs" rev-parse --show-toplevel 2>/dev/null) || return 1
164
+ dest_path="$dest_parent_abs/$dest_name/$item"
165
+ case "$dest_path" in
166
+ "$top"/*) rel_path=${dest_path#"$top"/} ;;
167
+ *) return 1 ;;
168
+ esac
169
+ git -C "$top" check-ignore -q -- "$rel_path" 2>/dev/null
170
+ }
171
+
172
+ # propagate_inheritable_config <src-config-dir> <dest-config-dir>
173
+ # Copy each declared inheritable item from the primary's config dir (src) into a
174
+ # secondmate home's config dir (dest). SILENT on stdout - callers parse stdout,
175
+ # so this writes nothing there. It emits concise stderr diagnostics only for
176
+ # notable events: a guard skip or a copy/remove error. A source item that is
177
+ # present is copied only when its content differs (idempotent: a re-run never
178
+ # churns mtimes). A source item that is absent is mirrored as a missing
179
+ # destination item, so clearing the primary's value clears it downstream too
180
+ # (primary-authoritative). The destination dir is created lazily, only when there
181
+ # is actually something to write, so a primary with no inherited config item set is a
182
+ # complete no-op (it leaves the secondmate home exactly as it was - the
183
+ # backward-compatible path). When FM_CONFIG_INHERIT_REPORT points at a writable
184
+ # file, one tab-separated line per item is appended there:
185
+ # <item> <status> <reason>
186
+ # Status is pushed, unchanged, skipped, or error. Skipped items are warnings and
187
+ # do not affect the exit code. Returns non-zero only when a real propagation
188
+ # error, such as copy or remove failure, occurs.
189
+ record_inheritable_config_result() {
190
+ local item=$1 status=$2 reason=${3:-}
191
+ [ -n "${FM_CONFIG_INHERIT_REPORT:-}" ] || return 0
192
+ printf '%s\t%s\t%s\n' "$item" "$status" "$reason" >> "$FM_CONFIG_INHERIT_REPORT" 2>/dev/null || true
193
+ }
194
+
195
+ inheritable_config_skip_reason() {
196
+ printf '%s' "destination does not allow inherited item (not gitignored or guard failed)"
197
+ }
198
+
199
+ warn_inheritable_config_skip() {
200
+ local item=$1 dest_config=$2 reason=$3
201
+ echo "fm-config-inherit: warning: skipped $item for $dest_config: $reason" >&2
202
+ }
203
+
204
+ warn_inheritable_config_error() {
205
+ local item=$1 dest=$2 reason=$3
206
+ echo "fm-config-inherit: error: $reason $item at $dest" >&2
207
+ }
208
+
209
+ shared_captain_header_valid() {
210
+ local src=$1 head
211
+ head=$(sed -n '1,12p' "$src" 2>/dev/null) || return 1
212
+ case "$head" in *main-authoritative*) ;; *) return 1 ;; esac
213
+ case "$head" in *"read-only in secondmate homes"*) ;; *) return 1 ;; esac
214
+ case "$head" in *"must not be edited there"*) ;; *) return 1 ;; esac
215
+ case "$head" in *"main firstmate"*) ;; *) return 1 ;; esac
216
+ case "$head" in *"marked status"*|*"document pointer"*) ;; *) return 1 ;; esac
217
+ }
218
+
219
+ shared_captain_dir_safe() {
220
+ local dir=$1
221
+ [ -n "$dir" ] || return 1
222
+ if [ -e "$dir" ] || [ -L "$dir" ]; then
223
+ [ -d "$dir" ] && [ ! -L "$dir" ] || return 1
224
+ else
225
+ mkdir -p "$dir" 2>/dev/null || return 1
226
+ fi
227
+ [ -d "$dir" ] && [ ! -L "$dir" ] || return 1
228
+ }
229
+
230
+ shared_captain_file_safe_existing() {
231
+ local path=$1
232
+ [ -f "$path" ] && [ ! -L "$path" ] || return 1
233
+ [ "$(fm_inherit_file_link_count "$path")" = 1 ]
234
+ }
235
+
236
+ restore_shared_captain_readonly() {
237
+ local dest=$1
238
+ [ -e "$dest" ] || [ -L "$dest" ] || return 0
239
+ shared_captain_file_safe_existing "$dest" || return 1
240
+ chmod "$FM_SHARED_CAPTAIN_MODE" "$dest" 2>/dev/null || return 1
241
+ }
242
+
243
+ shared_captain_quarantine_existing_for_hash() {
244
+ local parent=$1 hash=$2 artifact artifact_hash
245
+ for artifact in "$parent"/."$FM_SHARED_CAPTAIN_FILE".quarantine.*."$hash" "$parent"/."$FM_SHARED_CAPTAIN_FILE".quarantine.*."$hash".[0-9]*; do
246
+ [ -e "$artifact" ] || [ -L "$artifact" ] || continue
247
+ shared_captain_file_safe_existing "$artifact" || return 1
248
+ artifact_hash=$(fm_inherit_sha256 "$artifact") || return 1
249
+ [ "$artifact_hash" = "$hash" ] || continue
250
+ printf '%s\n' "$artifact"
251
+ return 0
252
+ done
253
+ return 1
254
+ }
255
+
256
+ shared_captain_quarantine_name() {
257
+ local parent=$1 hash=$2 stamp base candidate n
258
+ stamp=$(date -u +%Y%m%dT%H%M%SZ 2>/dev/null) || return 1
259
+ base="$parent/.$FM_SHARED_CAPTAIN_FILE.quarantine.$stamp.$hash"
260
+ candidate=$base
261
+ n=0
262
+ while [ -e "$candidate" ] || [ -L "$candidate" ]; do
263
+ n=$((n + 1))
264
+ candidate="$base.$n"
265
+ done
266
+ printf '%s\n' "$candidate"
267
+ }
268
+
269
+ quarantine_shared_captain_dest() {
270
+ local dest=$1 dest_parent=$2 dest_hash artifact existing
271
+ shared_captain_file_safe_existing "$dest" || return 1
272
+ dest_hash=$(fm_inherit_sha256 "$dest") || return 1
273
+ if existing=$(shared_captain_quarantine_existing_for_hash "$dest_parent" "$dest_hash" 2>/dev/null); then
274
+ chmod u+w "$dest" 2>/dev/null || return 1
275
+ if rm -f -- "$dest" 2>/dev/null; then
276
+ printf '%s\n' "$existing"
277
+ return 0
278
+ fi
279
+ restore_shared_captain_readonly "$dest" || true
280
+ return 1
281
+ fi
282
+ artifact=$(shared_captain_quarantine_name "$dest_parent" "$dest_hash") || return 1
283
+ chmod u+w "$dest" 2>/dev/null || return 1
284
+ if mv -- "$dest" "$artifact" 2>/dev/null; then
285
+ chmod 0600 "$artifact" 2>/dev/null || return 1
286
+ shared_captain_file_safe_existing "$artifact" || return 1
287
+ printf '%s\n' "$artifact"
288
+ return 0
289
+ fi
290
+ restore_shared_captain_readonly "$dest" || true
291
+ return 1
292
+ }
293
+
294
+ copy_shared_captain_file() {
295
+ local src=$1 dest=$2 dest_parent tmp
296
+ dest_parent=${dest%/*}
297
+ shared_captain_dir_safe "$dest_parent" || return 1
298
+ tmp=$(mktemp "$dest_parent/.fm-captain-shared.XXXXXX" 2>/dev/null) || return 1
299
+ if ! cp "$src" "$tmp" 2>/dev/null; then
300
+ rm -f "$tmp" 2>/dev/null || true
301
+ return 1
302
+ fi
303
+ chmod 0600 "$tmp" 2>/dev/null || { rm -f "$tmp" 2>/dev/null || true; return 1; }
304
+ shared_captain_file_safe_existing "$tmp" || { rm -f "$tmp" 2>/dev/null || true; return 1; }
305
+ if mv -f -- "$tmp" "$dest" 2>/dev/null; then
306
+ chmod "$FM_SHARED_CAPTAIN_MODE" "$dest" 2>/dev/null || return 1
307
+ shared_captain_file_safe_existing "$dest" || return 1
308
+ return 0
309
+ fi
310
+ rm -f "$tmp" 2>/dev/null || true
311
+ return 1
312
+ }
313
+
314
+ propagate_shared_captain_preferences() {
315
+ local src_data=$1 dest_data=$2 src dest src_hash dest_hash dest_parent dest_home quarantine reason rc
316
+ [ -n "$src_data" ] || return 1
317
+ [ -n "$dest_data" ] || return 1
318
+ src="$src_data/$FM_SHARED_CAPTAIN_FILE"
319
+ dest="$dest_data/$FM_SHARED_CAPTAIN_FILE"
320
+ dest_parent=${dest%/*}
321
+ dest_home=${dest_data%/data}
322
+ rc=0
323
+
324
+ if [ -e "$src" ] || [ -L "$src" ]; then
325
+ if ! shared_captain_file_safe_existing "$src"; then
326
+ reason="unsafe primary source"
327
+ warn_inheritable_config_error "$FM_SHARED_CAPTAIN_REL" "$src" "$reason"
328
+ record_inheritable_config_result "$FM_SHARED_CAPTAIN_REL" error "$reason"
329
+ return 1
330
+ fi
331
+ if ! shared_captain_header_valid "$src"; then
332
+ reason="primary source header missing required main-authoritative warning"
333
+ warn_inheritable_config_error "$FM_SHARED_CAPTAIN_REL" "$src" "$reason"
334
+ record_inheritable_config_result "$FM_SHARED_CAPTAIN_REL" error "$reason"
335
+ return 1
336
+ fi
337
+ src_hash=$(fm_inherit_sha256 "$src") || {
338
+ reason="failed to hash primary source"
339
+ warn_inheritable_config_error "$FM_SHARED_CAPTAIN_REL" "$src" "$reason"
340
+ record_inheritable_config_result "$FM_SHARED_CAPTAIN_REL" error "$reason"
341
+ return 1
342
+ }
343
+ if [ -e "$dest" ] || [ -L "$dest" ]; then
344
+ if ! shared_captain_file_safe_existing "$dest"; then
345
+ reason="unsafe destination"
346
+ warn_inheritable_config_error "$FM_SHARED_CAPTAIN_REL" "$dest" "$reason"
347
+ record_inheritable_config_result "$FM_SHARED_CAPTAIN_REL" error "$reason"
348
+ return 1
349
+ fi
350
+ dest_hash=$(fm_inherit_sha256 "$dest") || {
351
+ reason="failed to hash destination"
352
+ warn_inheritable_config_error "$FM_SHARED_CAPTAIN_REL" "$dest" "$reason"
353
+ record_inheritable_config_result "$FM_SHARED_CAPTAIN_REL" error "$reason"
354
+ restore_shared_captain_readonly "$dest" || true
355
+ return 1
356
+ }
357
+ if [ "$src_hash" = "$dest_hash" ]; then
358
+ if restore_shared_captain_readonly "$dest"; then
359
+ record_inheritable_config_result "$FM_SHARED_CAPTAIN_REL" unchanged ""
360
+ return 0
361
+ fi
362
+ reason="failed to restore read-only mode"
363
+ warn_inheritable_config_error "$FM_SHARED_CAPTAIN_REL" "$dest" "$reason"
364
+ record_inheritable_config_result "$FM_SHARED_CAPTAIN_REL" error "$reason"
365
+ return 1
366
+ fi
367
+ if ! shared_captain_dir_safe "$dest_parent"; then
368
+ reason="unsafe destination directory"
369
+ warn_inheritable_config_error "$FM_SHARED_CAPTAIN_REL" "$dest_parent" "$reason"
370
+ record_inheritable_config_result "$FM_SHARED_CAPTAIN_REL" error "$reason"
371
+ restore_shared_captain_readonly "$dest" || true
372
+ return 1
373
+ fi
374
+ if ! quarantine=$(quarantine_shared_captain_dest "$dest" "$dest_parent"); then
375
+ reason="failed to quarantine divergent destination"
376
+ warn_inheritable_config_error "$FM_SHARED_CAPTAIN_REL" "$dest" "$reason"
377
+ record_inheritable_config_result "$FM_SHARED_CAPTAIN_REL" error "$reason"
378
+ restore_shared_captain_readonly "$dest" || true
379
+ return 1
380
+ fi
381
+ printf 'SECONDMATE_SYNC: secondmate home %s: quarantined %s drift at %s\n' "$dest_home" "$FM_SHARED_CAPTAIN_REL" "$quarantine"
382
+ elif ! shared_captain_dir_safe "$dest_parent"; then
383
+ reason="unsafe destination directory"
384
+ warn_inheritable_config_error "$FM_SHARED_CAPTAIN_REL" "$dest_parent" "$reason"
385
+ record_inheritable_config_result "$FM_SHARED_CAPTAIN_REL" error "$reason"
386
+ return 1
387
+ fi
388
+ if copy_shared_captain_file "$src" "$dest"; then
389
+ if [ -n "${quarantine:-}" ]; then
390
+ record_inheritable_config_result "$FM_SHARED_CAPTAIN_REL" pushed "quarantined local drift at $quarantine"
391
+ else
392
+ record_inheritable_config_result "$FM_SHARED_CAPTAIN_REL" pushed ""
393
+ fi
394
+ else
395
+ reason="failed to copy"
396
+ warn_inheritable_config_error "$FM_SHARED_CAPTAIN_REL" "$dest" "$reason"
397
+ record_inheritable_config_result "$FM_SHARED_CAPTAIN_REL" error "$reason"
398
+ rc=1
399
+ fi
400
+ elif [ -e "$dest" ] || [ -L "$dest" ]; then
401
+ if ! shared_captain_file_safe_existing "$dest"; then
402
+ reason="unsafe destination"
403
+ warn_inheritable_config_error "$FM_SHARED_CAPTAIN_REL" "$dest" "$reason"
404
+ record_inheritable_config_result "$FM_SHARED_CAPTAIN_REL" error "$reason"
405
+ return 1
406
+ fi
407
+ if ! shared_captain_dir_safe "$dest_parent"; then
408
+ reason="unsafe destination directory"
409
+ warn_inheritable_config_error "$FM_SHARED_CAPTAIN_REL" "$dest_parent" "$reason"
410
+ record_inheritable_config_result "$FM_SHARED_CAPTAIN_REL" error "$reason"
411
+ restore_shared_captain_readonly "$dest" || true
412
+ return 1
413
+ fi
414
+ if quarantine=$(quarantine_shared_captain_dest "$dest" "$dest_parent"); then
415
+ printf 'SECONDMATE_SYNC: secondmate home %s: quarantined %s drift at %s\n' "$dest_home" "$FM_SHARED_CAPTAIN_REL" "$quarantine"
416
+ record_inheritable_config_result "$FM_SHARED_CAPTAIN_REL" pushed "mirrored primary absence after quarantining local copy at $quarantine"
417
+ else
418
+ reason="failed to quarantine destination before mirroring primary absence"
419
+ warn_inheritable_config_error "$FM_SHARED_CAPTAIN_REL" "$dest" "$reason"
420
+ record_inheritable_config_result "$FM_SHARED_CAPTAIN_REL" error "$reason"
421
+ restore_shared_captain_readonly "$dest" || true
422
+ rc=1
423
+ fi
424
+ else
425
+ record_inheritable_config_result "$FM_SHARED_CAPTAIN_REL" unchanged ""
426
+ fi
427
+ return "$rc"
428
+ }
429
+
430
+ propagate_secondmate_inheritance() {
431
+ local src_home=$1 dest_home=$2 src_config=${3:-} src_data=${4:-} rc
432
+ [ -n "$src_home" ] || return 1
433
+ [ -n "$dest_home" ] || return 1
434
+ [ -n "$src_config" ] || src_config="$src_home/config"
435
+ [ -n "$src_data" ] || src_data="$src_home/data"
436
+ rc=0
437
+ propagate_inheritable_config "$src_config" "$dest_home/config" || rc=1
438
+ propagate_shared_captain_preferences "$src_data" "$dest_home/data" || rc=1
439
+ return "$rc"
440
+ }
441
+
442
+ propagate_inheritable_config() {
443
+ local src_config=$1 dest_config=$2 item src dest reason rc
444
+ [ -n "$src_config" ] || return 1
445
+ [ -n "$dest_config" ] || return 1
446
+ rc=0
447
+ for item in $FM_INHERITABLE_CONFIG; do
448
+ case "$item" in
449
+ ''|/*|.|..|../*|*/../*|*/..) return 1 ;;
450
+ esac
451
+ if [ "${FM_CONFIG_INHERIT_LIVE:-0}" = 1 ] && fm_config_inherit_item_session_scoped "$item"; then
452
+ record_inheritable_config_result "$item" unchanged "session-scoped"
453
+ continue
454
+ fi
455
+ src="$src_config/$item"
456
+ dest="$dest_config/$item"
457
+ # This one scalar config is consumed as a local safety boundary, so reject
458
+ # every unsafe or malformed source/destination artifact before the generic
459
+ # byte-copy behavior below can treat it as ordinary inherited material.
460
+ if [ "$item" = "$FM_STARTUP_MEMORY_BUDGET_FILE" ]; then
461
+ if [ -e "$src_config" ] || [ -L "$src_config" ]; then
462
+ if ! fm_startup_memory_budget_config_dir_safe "$src_config"; then
463
+ reason="unsafe primary config directory: $FM_STARTUP_MEMORY_BUDGET_ERROR"
464
+ warn_inheritable_config_error "$item" "$src_config" "$reason"
465
+ record_inheritable_config_result "$item" error "$reason"
466
+ rc=1
467
+ continue
468
+ fi
469
+ fi
470
+ if [ -e "$dest_config" ] || [ -L "$dest_config" ]; then
471
+ if ! fm_startup_memory_budget_config_dir_safe "$dest_config"; then
472
+ reason="unsafe destination config directory: $FM_STARTUP_MEMORY_BUDGET_ERROR"
473
+ warn_inheritable_config_error "$item" "$dest_config" "$reason"
474
+ record_inheritable_config_result "$item" error "$reason"
475
+ rc=1
476
+ continue
477
+ fi
478
+ fi
479
+ if [ -e "$src" ] || [ -L "$src" ]; then
480
+ if ! fm_startup_memory_budget_file_valid "$src"; then
481
+ reason="unsafe or invalid primary source: $FM_STARTUP_MEMORY_BUDGET_ERROR"
482
+ warn_inheritable_config_error "$item" "$src" "$reason"
483
+ record_inheritable_config_result "$item" error "$reason"
484
+ rc=1
485
+ continue
486
+ fi
487
+ fi
488
+ if [ -e "$dest" ] || [ -L "$dest" ]; then
489
+ if ! fm_startup_memory_budget_file_valid "$dest"; then
490
+ reason="unsafe or invalid destination: $FM_STARTUP_MEMORY_BUDGET_ERROR"
491
+ warn_inheritable_config_error "$item" "$dest" "$reason"
492
+ record_inheritable_config_result "$item" error "$reason"
493
+ rc=1
494
+ continue
495
+ fi
496
+ fi
497
+ fi
498
+ if [ -f "$src" ]; then
499
+ if ! destination_allows_inherited_item "$dest_config" "$item"; then
500
+ reason=$(inheritable_config_skip_reason)
501
+ warn_inheritable_config_skip "$item" "$dest_config" "$reason"
502
+ record_inheritable_config_result "$item" skipped "$reason"
503
+ continue
504
+ fi
505
+ if [ -L "$dest" ] || [ ! -f "$dest" ] || ! cmp -s "$src" "$dest"; then
506
+ if copy_inheritable_file "$src" "$dest"; then
507
+ record_inheritable_config_result "$item" pushed ""
508
+ else
509
+ reason="failed to copy"
510
+ warn_inheritable_config_error "$item" "$dest" "$reason"
511
+ record_inheritable_config_result "$item" error "$reason"
512
+ rc=1
513
+ fi
514
+ else
515
+ record_inheritable_config_result "$item" unchanged ""
516
+ fi
517
+ elif [ -e "$dest" ] || [ -L "$dest" ]; then
518
+ if ! destination_allows_inherited_item "$dest_config" "$item"; then
519
+ reason=$(inheritable_config_skip_reason)
520
+ warn_inheritable_config_skip "$item" "$dest_config" "$reason"
521
+ record_inheritable_config_result "$item" skipped "$reason"
522
+ continue
523
+ fi
524
+ # Primary has no value for this item: mirror the absence downstream.
525
+ if rm -f "$dest" 2>/dev/null; then
526
+ record_inheritable_config_result "$item" pushed "mirrored primary absence"
527
+ else
528
+ reason="failed to remove"
529
+ warn_inheritable_config_error "$item" "$dest" "$reason"
530
+ record_inheritable_config_result "$item" error "$reason"
531
+ rc=1
532
+ fi
533
+ else
534
+ record_inheritable_config_result "$item" unchanged ""
535
+ fi
536
+ done
537
+ return "$rc"
538
+ }
539
+
540
+ # Relative prefix of per-home instruction files written after a successful
541
+ # config push so the live secondmate can re-read exact post-write bytes.
542
+ # Kept under state/ (gitignored operational dir) so it never dirties the home.
543
+ FM_CONFIG_REREAD_INSTRUCTION_PREFIX_REL="state/.fm-inherited-config-reread"
544
+ FM_CONFIG_REREAD_MAX_SENT=16
545
+ FM_CONFIG_REREAD_RETRY_ROOT_REL="state/.fm-inherited-config-reread-retry"
546
+ FM_CONFIG_REREAD_MAX_PENDING=16
547
+ FM_CONFIG_REREAD_MAX_QUARANTINE=16
548
+ FM_CONFIG_INHERIT_LOCK_REL="state/.fm-inherited-config.lock"
549
+
550
+ # Framing lines for the config-reread instruction. Defaults/rules only - never
551
+ # an enforcement claim, and never a parsed summary of file contents.
552
+ FM_CONFIG_REREAD_FRAMING='These inherited config files changed. Re-read and apply their exact contents at every future intake. They are defaults/rules and do not remove your judgment to choose differently when warranted.'
553
+
554
+ # fm_config_reread_is_allowlisted_item <item>
555
+ # True only for the declared inheritable config allowlist (bare item name as
556
+ # recorded in FM_CONFIG_INHERIT_REPORT). data/captain-shared.md is never
557
+ # allowlisted here and must never be inlined into a reread instruction.
558
+ fm_config_reread_is_allowlisted_item() {
559
+ local item=$1 candidate
560
+ for candidate in $FM_INHERITABLE_CONFIG; do
561
+ [ "$candidate" = "$item" ] && return 0
562
+ done
563
+ return 1
564
+ }
565
+
566
+ # fm_config_reread_changed_items <report>
567
+ # Print bare allowlisted config item names whose report status is "pushed",
568
+ # in FM_INHERITABLE_CONFIG order (deterministic path order). Empty when none.
569
+ fm_config_reread_changed_items() {
570
+ local report=$1 item status
571
+ [ -n "$report" ] && [ -f "$report" ] || return 0
572
+ for item in $FM_INHERITABLE_CONFIG; do
573
+ status=$(awk -F '\t' -v item="$item" '$1 == item { print $2; exit }' "$report" 2>/dev/null) || status=""
574
+ [ "$status" = pushed ] || continue
575
+ printf '%s\n' "$item"
576
+ done
577
+ }
578
+
579
+ fm_config_inherit_lock_path() {
580
+ local dest_home=$1
581
+ [ -n "$dest_home" ] || return 1
582
+ printf '%s/%s\n' "$dest_home" "$FM_CONFIG_INHERIT_LOCK_REL"
583
+ }
584
+
585
+ fm_config_reread_retry_dir() {
586
+ local source_home=$1 id=$2 token
587
+ [ -n "$source_home" ] && [ -n "$id" ] || return 1
588
+ token=${id//[^a-zA-Z0-9_.-]/_}
589
+ [ -n "$token" ] || token=unknown
590
+ printf '%s/%s/%s\n' "$source_home" "$FM_CONFIG_REREAD_RETRY_ROOT_REL" "$token"
591
+ }
592
+
593
+ fm_config_reread_pending_stages() {
594
+ local source_home=$1 id=$2 retry_dir stage
595
+ retry_dir=$(fm_config_reread_retry_dir "$source_home" "$id") || return 1
596
+ for stage in "$retry_dir"/.fm-inherited-config-reread.*; do
597
+ case "$stage" in
598
+ *.report) continue ;;
599
+ esac
600
+ [ -f "$stage" ] && [ ! -L "$stage" ] || continue
601
+ [ -s "$stage" ] || continue
602
+ printf '%s\n' "$stage"
603
+ done | LC_ALL=C sort
604
+ }
605
+
606
+ fm_config_reread_pending_reports() {
607
+ local source_home=$1 id=$2 retry_dir report
608
+ retry_dir=$(fm_config_reread_retry_dir "$source_home" "$id") || return 1
609
+ for report in "$retry_dir"/.fm-inherited-config-reread.*.report; do
610
+ [ -f "$report" ] && [ ! -L "$report" ] || continue
611
+ printf '%s\n' "$report"
612
+ done | LC_ALL=C sort
613
+ }
614
+
615
+ fm_config_reread_has_staged() {
616
+ local source_home=$1 id=$2 stage
617
+ while IFS= read -r stage; do
618
+ [ -n "$stage" ] && return 0
619
+ done < <(fm_config_reread_pending_stages "$source_home" "$id")
620
+ while IFS= read -r stage; do
621
+ [ -n "$stage" ] && return 0
622
+ done < <(fm_config_reread_pending_reports "$source_home" "$id")
623
+ return 1
624
+ }
625
+
626
+ fm_config_reread_retry_queue_is_full() {
627
+ local source_home=$1 id=$2 count report_count
628
+ count=$(fm_config_reread_pending_stages "$source_home" "$id" | wc -l | tr -d ' ')
629
+ report_count=$(fm_config_reread_pending_reports "$source_home" "$id" | wc -l | tr -d ' ')
630
+ count=$((count + report_count))
631
+ [ "$count" -ge "$FM_CONFIG_REREAD_MAX_PENDING" ]
632
+ }
633
+
634
+ fm_config_reread_retry_pending() {
635
+ local id=$1 dest_home=$2 report retry_out rc
636
+ report=$(mktemp "${TMPDIR:-/tmp}/fm-config-reread-retry.XXXXXX" 2>/dev/null) || {
637
+ printf 'CONFIG_REREAD: secondmate %s: send failed: could not create retry report\n' "$id"
638
+ return 1
639
+ }
640
+ retry_out=$(fm_config_send_reread_nudge "$id" "$dest_home" "$report" 2>&1)
641
+ rc=$?
642
+ rm -f "$report"
643
+ [ -z "$retry_out" ] || printf '%s\n' "$retry_out"
644
+ return "$rc"
645
+ }
646
+
647
+ fm_config_reread_new_retry_stage_path() {
648
+ local source_home=$1 id=$2 retry_dir sequence sequence_file sequence_tmp generation stage
649
+ retry_dir=$(fm_config_reread_retry_dir "$source_home" "$id") || return 1
650
+ mkdir -p "$retry_dir" 2>/dev/null || return 1
651
+ chmod 0700 "$retry_dir" 2>/dev/null || return 1
652
+ sequence=$(cat "$retry_dir/.sequence" 2>/dev/null || true)
653
+ case "$sequence" in
654
+ ''|*[!0-9]*) sequence=0 ;;
655
+ esac
656
+ sequence=$((sequence + 1))
657
+ sequence_file="$retry_dir/.sequence"
658
+ sequence_tmp=$(umask 077; mktemp "$retry_dir/.sequence.XXXXXX" 2>/dev/null) || return 1
659
+ if ! printf '%s\n' "$sequence" > "$sequence_tmp" || ! chmod 0600 "$sequence_tmp" 2>/dev/null || ! mv -f "$sequence_tmp" "$sequence_file" 2>/dev/null; then
660
+ rm -f "$sequence_tmp"
661
+ return 1
662
+ fi
663
+ generation=$(date -u +%Y%m%dT%H%M%S 2>/dev/null) || return 1
664
+ generation="$generation.$(printf '%08d' "$sequence")"
665
+ stage=$(umask 077; mktemp "$retry_dir/.fm-inherited-config-reread.$generation.XXXXXX" 2>/dev/null) || return 1
666
+ printf '%s\n' "$stage"
667
+ }
668
+
669
+ fm_config_reread_save_retry_report() {
670
+ local report=$1 stage_path=$2 report_path tmp parent
671
+ parent=${stage_path%/*}
672
+ report_path="$stage_path.report"
673
+ tmp=$(umask 077; mktemp "$parent/.fm-config-reread-report.XXXXXX" 2>/dev/null) || return 1
674
+ if ! cat "$report" > "$tmp" || ! chmod 0600 "$tmp" 2>/dev/null || ! mv -f "$tmp" "$report_path" 2>/dev/null; then
675
+ rm -f "$tmp"
676
+ return 1
677
+ fi
678
+ printf '%s\n' "$report_path"
679
+ }
680
+
681
+ # fm_config_write_reread_instruction <dest-home> <report> <instruction-path>
682
+ # After successful propagation, write one instruction from the validated
683
+ # destination state. Includes only changed allowlisted config files, each with
684
+ # relative path, begin/end delimiters, and either the destination file's full
685
+ # exact post-write bytes (streamed unparsed) or the literal token ABSENT when
686
+ # the destination copy was removed. Returns 1 when no allowlisted config item
687
+ # changed (or on write failure). Never inlines data/captain-shared.md, SHA
688
+ # values, selected profiles, or any generated interpretation.
689
+ fm_config_write_reread_instruction() {
690
+ local dest_home=$1 report=$2 instruction_path=$3 item rel dest parent tmp first=1
691
+ FM_CONFIG_REREAD_FAILED_TEMP=""
692
+ [ -n "$dest_home" ] || return 1
693
+ [ -n "$report" ] && [ -f "$report" ] || return 1
694
+ [ -n "$instruction_path" ] || return 1
695
+ parent=${instruction_path%/*}
696
+ [ -n "$parent" ] && [ "$parent" != "$instruction_path" ] || return 1
697
+ mkdir -p "$parent" 2>/dev/null || return 1
698
+ tmp=$(umask 077; mktemp "$instruction_path.tmp.XXXXXX" 2>/dev/null) || return 1
699
+ chmod 0600 "$tmp" 2>/dev/null || { rm -f "$tmp"; return 1; }
700
+ while IFS= read -r item; do
701
+ [ -n "$item" ] || continue
702
+ fm_config_reread_is_allowlisted_item "$item" || continue
703
+ rel="config/$item"
704
+ dest="$dest_home/config/$item"
705
+ if [ "$first" = 1 ]; then
706
+ printf '%s\n' "$FM_CONFIG_REREAD_FRAMING" >> "$tmp" || { rm -f "$tmp"; return 1; }
707
+ first=0
708
+ fi
709
+ {
710
+ printf '\n'
711
+ printf '%s\n' "$rel"
712
+ printf '%s\n' "-----BEGIN $rel-----"
713
+ } >> "$tmp" || { rm -f "$tmp"; return 1; }
714
+ if [ -f "$dest" ] && [ ! -L "$dest" ]; then
715
+ # Stream destination post-write bytes only - never re-read the primary.
716
+ cat "$dest" >> "$tmp" || { rm -f "$tmp"; return 1; }
717
+ else
718
+ printf '%s\n' "ABSENT" >> "$tmp" || { rm -f "$tmp"; return 1; }
719
+ fi
720
+ printf '%s\n' "-----END $rel-----" >> "$tmp" || { rm -f "$tmp"; return 1; }
721
+ done < <(fm_config_reread_changed_items "$report")
722
+ if [ "$first" = 1 ]; then
723
+ rm -f "$tmp"
724
+ return 1
725
+ fi
726
+ if ! mv -f "$tmp" "$instruction_path" 2>/dev/null; then
727
+ FM_CONFIG_REREAD_FAILED_TEMP="$tmp"
728
+ return 1
729
+ fi
730
+ return 0
731
+ }
732
+
733
+ fm_config_reread_adopt_exact_temp() {
734
+ local exact_tmp=$1 stage_path=$2
735
+ [ -f "$exact_tmp" ] && [ ! -L "$exact_tmp" ] || return 1
736
+ [ ! -L "$stage_path" ] || return 1
737
+ if mv -f "$exact_tmp" "$stage_path" 2>/dev/null; then
738
+ return 0
739
+ fi
740
+ if cp "$exact_tmp" "$stage_path" 2>/dev/null \
741
+ && chmod 0600 "$stage_path" 2>/dev/null \
742
+ && cmp -s "$exact_tmp" "$stage_path"; then
743
+ rm -f "$exact_tmp" 2>/dev/null || true
744
+ return 0
745
+ fi
746
+ rm -f "$stage_path" 2>/dev/null || true
747
+ return 1
748
+ }
749
+
750
+ fm_config_reread_pending_instructions() {
751
+ local state=$1 pending instruction
752
+ for pending in "$state"/.fm-inherited-config-reread.*.pending; do
753
+ [ -f "$pending" ] && [ ! -L "$pending" ] || continue
754
+ instruction=${pending%.pending}
755
+ printf '%s\n' "$instruction"
756
+ done | LC_ALL=C sort
757
+ }
758
+
759
+ fm_config_reread_has_pending() {
760
+ local dest_home=$1 state pending
761
+ state="$dest_home/${FM_CONFIG_REREAD_INSTRUCTION_PREFIX_REL%/*}"
762
+ for pending in "$state"/.fm-inherited-config-reread.*.pending; do
763
+ [ -f "$pending" ] && [ ! -L "$pending" ] || continue
764
+ return 0
765
+ done
766
+ return 1
767
+ }
768
+
769
+ fm_config_reread_cleanup_sent() {
770
+ local dest_home=$1 state path paths sorted total remove
771
+ state="$dest_home/${FM_CONFIG_REREAD_INSTRUCTION_PREFIX_REL%/*}"
772
+ [ -d "$state" ] || return 0
773
+ paths=""
774
+ for path in "$state"/.fm-inherited-config-reread.*; do
775
+ case "$path" in
776
+ *.pending) continue ;;
777
+ esac
778
+ [ -f "$path" ] && [ ! -L "$path" ] || continue
779
+ [ -e "$path.pending" ] || [ -L "$path.pending" ] && continue
780
+ if [ -n "$paths" ]; then
781
+ paths+=$'\n'
782
+ fi
783
+ paths+="$path"
784
+ done
785
+ [ -n "$paths" ] || return 0
786
+ sorted=$(printf '%s\n' "$paths" | LC_ALL=C sort)
787
+ total=$(printf '%s\n' "$sorted" | wc -l | tr -d ' ')
788
+ remove=$((total - FM_CONFIG_REREAD_MAX_SENT))
789
+ [ "$remove" -gt 0 ] || return 0
790
+ while IFS= read -r path; do
791
+ [ "$remove" -gt 0 ] || break
792
+ [ -n "$path" ] || continue
793
+ [ -e "$path.pending" ] || [ -L "$path.pending" ] && continue
794
+ rm -f "$path" 2>/dev/null || continue
795
+ remove=$((remove - 1))
796
+ done <<EOF
797
+ $sorted
798
+ EOF
799
+ }
800
+
801
+ fm_config_reread_mark_pending() {
802
+ local instruction_path=$1 pending_path=$2 parent tmp
803
+ parent=${pending_path%/*}
804
+ [ -n "$parent" ] && [ "$parent" != "$pending_path" ] || return 1
805
+ mkdir -p "$parent" 2>/dev/null || return 1
806
+ tmp=$(umask 077; mktemp "$parent/.fm-config-reread-pending.XXXXXX" 2>/dev/null) || return 1
807
+ if ! printf '%s\n' "$instruction_path" > "$tmp"; then
808
+ rm -f "$tmp"
809
+ return 1
810
+ fi
811
+ if ! chmod 0600 "$tmp" 2>/dev/null; then
812
+ rm -f "$tmp"
813
+ return 1
814
+ fi
815
+ if ! mv -f "$tmp" "$pending_path" 2>/dev/null; then
816
+ rm -f "$tmp"
817
+ return 1
818
+ fi
819
+ return 0
820
+ }
821
+
822
+ fm_config_reread_publish_stage() {
823
+ local dest_home=$1 stage=$2 state final pending_pointer tmp
824
+ [ -f "$stage" ] && [ ! -L "$stage" ] || return 1
825
+ state="$dest_home/${FM_CONFIG_REREAD_INSTRUCTION_PREFIX_REL%/*}"
826
+ mkdir -p "$state" 2>/dev/null || return 1
827
+ final="$state/${stage##*/}"
828
+ if [ -f "$final.pending" ] && [ ! -L "$final.pending" ]; then
829
+ pending_pointer=$(cat "$final.pending" 2>/dev/null || true)
830
+ [ "$pending_pointer" = "$final" ] || return 1
831
+ [ -f "$final" ] && [ ! -L "$final" ] || return 1
832
+ printf '%s\n' "$final"
833
+ return 0
834
+ fi
835
+ tmp=$(umask 077; mktemp "$state/.fm-config-reread-publish.XXXXXX" 2>/dev/null) || return 1
836
+ if ! cat "$stage" > "$tmp" || ! chmod 0600 "$tmp" 2>/dev/null || ! mv -f "$tmp" "$final" 2>/dev/null; then
837
+ rm -f "$tmp"
838
+ return 1
839
+ fi
840
+ if ! fm_config_reread_mark_pending "$final" "$final.pending"; then
841
+ rm -f "$final"
842
+ return 1
843
+ fi
844
+ printf '%s\n' "$final"
845
+ }
846
+
847
+ fm_config_reread_send_failure() {
848
+ local id=$1 instruction_path=$2 pending_path=$3 detail=$4
849
+ if ! fm_config_reread_mark_pending "$instruction_path" "$pending_path"; then
850
+ detail="$detail; could not record retry marker"
851
+ fi
852
+ printf 'CONFIG_REREAD: secondmate %s: send failed: %s\n' "$id" "$detail"
853
+ return 1
854
+ }
855
+
856
+ # fm_config_reread_send_pointer <id> <instruction-path>
857
+ fm_config_reread_send_pointer() {
858
+ local id=$1 instruction_path=$2 pending_path selector out rc send_bin message pending_pointer
859
+ pending_path="$instruction_path.pending"
860
+ if [ ! -f "$instruction_path" ] || [ -L "$instruction_path" ]; then
861
+ printf 'CONFIG_REREAD: secondmate %s: send failed: pending instruction file is missing\n' "$id"
862
+ return 1
863
+ fi
864
+ pending_pointer=$(cat "$pending_path" 2>/dev/null || true)
865
+ if [ "$pending_pointer" != "$instruction_path" ]; then
866
+ printf 'CONFIG_REREAD: secondmate %s: send failed: pending instruction file is mismatched\n' "$id"
867
+ return 1
868
+ fi
869
+ selector="fm-$id"
870
+ send_bin="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/fm-send.sh"
871
+ if [ ! -x "$send_bin" ]; then
872
+ fm_config_reread_send_failure "$id" "$instruction_path" "$pending_path" "fm-send.sh not executable at $send_bin"
873
+ return 1
874
+ fi
875
+ if [ -z "${FM_HOME:-}" ]; then
876
+ fm_config_reread_send_failure "$id" "$instruction_path" "$pending_path" "FM_HOME is not set"
877
+ return 1
878
+ fi
879
+ message="CONFIG_REREAD: $instruction_path"
880
+ out=$(FM_HOME="$FM_HOME" \
881
+ FM_ROOT_OVERRIDE="${FM_ROOT_OVERRIDE:-}" \
882
+ FM_STATE_OVERRIDE="${FM_STATE_OVERRIDE:-}" \
883
+ FM_SEND_SETTLE="${FM_SEND_SETTLE:-0}" \
884
+ "$send_bin" "$selector" "$message" 2>&1) && rc=0 || rc=$?
885
+ if [ "$rc" -eq 0 ]; then
886
+ rm -f "$pending_path"
887
+ return 0
888
+ fi
889
+ out=${out%%$'\n'*}
890
+ [ -n "$out" ] || out="fm-send exited $rc"
891
+ fm_config_reread_send_failure "$id" "$instruction_path" "$pending_path" "$out"
892
+ return 1
893
+ }
894
+
895
+ # fm_config_reread_discard_pending <dest-home>
896
+ fm_config_reread_discard_pending() {
897
+ local dest_home=$1 id=${2:-} source_home=${3:-} state pending instruction retry_dir retry_stage rc=0
898
+ state="$dest_home/${FM_CONFIG_REREAD_INSTRUCTION_PREFIX_REL%/*}"
899
+ for pending in "$state"/.fm-inherited-config-reread.*.pending; do
900
+ [ -f "$pending" ] && [ ! -L "$pending" ] || continue
901
+ instruction=${pending%.pending}
902
+ rm -f "$pending" 2>/dev/null || rc=1
903
+ rm -f "$instruction" 2>/dev/null || rc=1
904
+ done
905
+ if [ -n "$id" ] && [ -n "$source_home" ]; then
906
+ retry_dir=$(fm_config_reread_retry_dir "$source_home" "$id") || rc=1
907
+ if [ -d "$retry_dir" ]; then
908
+ for retry_stage in "$retry_dir"/.fm-inherited-config-reread.*; do
909
+ [ -f "$retry_stage" ] && [ ! -L "$retry_stage" ] || continue
910
+ rm -f "$retry_stage" 2>/dev/null || rc=1
911
+ done
912
+ rm -f "$retry_dir/.sequence" 2>/dev/null || true
913
+ rmdir "$retry_dir" 2>/dev/null || true
914
+ fi
915
+ fi
916
+ return "$rc"
917
+ }
918
+
919
+ fm_config_reread_quarantine_prune() {
920
+ local root=$1 keep=${2:-$FM_CONFIG_REREAD_MAX_QUARANTINE} dirs dir oldest path remove
921
+ case "$keep" in
922
+ ''|*[!0-9]*) keep=$FM_CONFIG_REREAD_MAX_QUARANTINE ;;
923
+ esac
924
+ [ -d "$root" ] || return 0
925
+ dirs=""
926
+ for dir in "$root"/generation.*; do
927
+ [ -d "$dir" ] && [ ! -L "$dir" ] || continue
928
+ [ -n "$dirs" ] && dirs+=$'\n'
929
+ dirs+="$dir"
930
+ done
931
+ dirs=$(printf '%s\n' "$dirs" | LC_ALL=C sort)
932
+ [ -n "$dirs" ] || return 0
933
+ remove=$(printf '%s\n' "$dirs" | wc -l | tr -d ' ')
934
+ remove=$((remove - keep))
935
+ while [ "$remove" -gt 0 ]; do
936
+ oldest=${dirs%%$'\n'*}
937
+ if [ "$oldest" = "$dirs" ]; then
938
+ dirs=""
939
+ else
940
+ dirs=${dirs#*$'\n'}
941
+ fi
942
+ for path in "$oldest"/.[!.]* "$oldest"/..?* "$oldest"/*; do
943
+ [ -e "$path" ] || [ -L "$path" ] || continue
944
+ if [ -d "$path" ] && [ ! -L "$path" ]; then
945
+ rmdir "$path" 2>/dev/null || return 1
946
+ else
947
+ rm -f "$path" 2>/dev/null || return 1
948
+ fi
949
+ done
950
+ rmdir "$oldest" 2>/dev/null || return 1
951
+ remove=$((remove - 1))
952
+ done
953
+ }
954
+
955
+ fm_config_reread_quarantine_dir() {
956
+ local home=$1 state root quarantine
957
+ state="$home/${FM_CONFIG_REREAD_INSTRUCTION_PREFIX_REL%/*}"
958
+ root="$state/.fm-inherited-config-reread-quarantine"
959
+ mkdir -p "$root" 2>/dev/null || return 1
960
+ chmod 0700 "$root" 2>/dev/null || return 1
961
+ fm_config_reread_quarantine_prune "$root" $((FM_CONFIG_REREAD_MAX_QUARANTINE - 1)) || return 1
962
+ quarantine=$(umask 077; mktemp -d "$root/generation.XXXXXX" 2>/dev/null) || return 1
963
+ chmod 0700 "$quarantine" 2>/dev/null || return 1
964
+ printf '%s\n' "$quarantine"
965
+ }
966
+
967
+ fm_config_reread_quarantine_pending() {
968
+ local dest_home=$1 id=${2:-} source_home=${3:-}
969
+ local state pending instruction retry_dir retry_stage dest_quarantine source_quarantine
970
+ local dest_has_artifacts source_has_artifacts rc=0
971
+ state="$dest_home/${FM_CONFIG_REREAD_INSTRUCTION_PREFIX_REL%/*}"
972
+ dest_has_artifacts=0
973
+ for pending in "$state"/.fm-inherited-config-reread.*.pending; do
974
+ [ -f "$pending" ] && [ ! -L "$pending" ] || continue
975
+ dest_has_artifacts=1
976
+ break
977
+ done
978
+ dest_quarantine=""
979
+ if [ "$dest_has_artifacts" -eq 1 ]; then
980
+ dest_quarantine=$(fm_config_reread_quarantine_dir "$dest_home" 2>/dev/null || true)
981
+ fi
982
+ for pending in "$state"/.fm-inherited-config-reread.*.pending; do
983
+ [ -f "$pending" ] && [ ! -L "$pending" ] || continue
984
+ instruction=${pending%.pending}
985
+ if [ -n "$dest_quarantine" ] && mv -f "$pending" "$dest_quarantine/${pending##*/}" 2>/dev/null; then
986
+ if [ -e "$instruction" ] || [ -L "$instruction" ]; then
987
+ mv -f "$instruction" "$dest_quarantine/${instruction##*/}" 2>/dev/null || {
988
+ rm -f "$instruction" 2>/dev/null || true
989
+ rc=1
990
+ }
991
+ fi
992
+ else
993
+ rm -f "$pending" 2>/dev/null || rc=1
994
+ rm -f "$instruction" 2>/dev/null || rc=1
995
+ rc=1
996
+ fi
997
+ done
998
+ if [ -n "$id" ] && [ -n "$source_home" ]; then
999
+ retry_dir=$(fm_config_reread_retry_dir "$source_home" "$id") || retry_dir=
1000
+ source_has_artifacts=0
1001
+ if [ -d "$retry_dir" ]; then
1002
+ for retry_stage in "$retry_dir"/.fm-inherited-config-reread.*; do
1003
+ [ -f "$retry_stage" ] && [ ! -L "$retry_stage" ] || continue
1004
+ source_has_artifacts=1
1005
+ break
1006
+ done
1007
+ fi
1008
+ source_quarantine=""
1009
+ if [ "$source_has_artifacts" -eq 1 ]; then
1010
+ source_quarantine=$(fm_config_reread_quarantine_dir "$source_home" 2>/dev/null || true)
1011
+ fi
1012
+ if [ -d "$retry_dir" ]; then
1013
+ for retry_stage in "$retry_dir"/.fm-inherited-config-reread.*; do
1014
+ [ -f "$retry_stage" ] && [ ! -L "$retry_stage" ] || continue
1015
+ if [ -n "$source_quarantine" ] && mv -f "$retry_stage" "$source_quarantine/${retry_stage##*/}" 2>/dev/null; then
1016
+ :
1017
+ else
1018
+ rm -f "$retry_stage" 2>/dev/null || rc=1
1019
+ rc=1
1020
+ fi
1021
+ done
1022
+ if [ -f "$retry_dir/.sequence" ] && [ ! -L "$retry_dir/.sequence" ]; then
1023
+ if [ -n "$source_quarantine" ] && mv -f "$retry_dir/.sequence" "$source_quarantine/.sequence" 2>/dev/null; then
1024
+ :
1025
+ else
1026
+ rm -f "$retry_dir/.sequence" 2>/dev/null || rc=1
1027
+ rc=1
1028
+ fi
1029
+ fi
1030
+ rmdir "$retry_dir" 2>/dev/null || true
1031
+ fi
1032
+ fi
1033
+ return "$rc"
1034
+ }
1035
+
1036
+ # fm_config_send_reread_nudge <id> <dest-home> <report>
1037
+ # After successful propagation, if any allowlisted config item changed for this
1038
+ # home, write the exact-byte instruction under the destination home and send a
1039
+ # single-line pointers to those files through the routed secondmate path
1040
+ # (fm-send). The files contain only changed config paths, clear delimiters, and
1041
+ # the destination's full exact post-write bytes (or ABSENT) - never summaries,
1042
+ # SHA values, selected profiles, or data/captain-shared.md. No-op (return 0) when
1043
+ # nothing changed and no pending delivery exists. On publication or send
1044
+ # failure, print a concrete CONFIG_REREAD retry diagnostic to stdout and return
1045
+ # non-zero - never claim the live agent reread the values.
1046
+ fm_config_send_reread_nudge() {
1047
+ local id=$1 dest_home=$2 report=$3
1048
+ local dest_home_abs state source_home_abs changed_items pending_paths stage_paths delivery_paths
1049
+ local stage_path instruction_path current_stage_path exact_tmp
1050
+ local send_failures retry_report_paths retry_report_path retry_stage_path retry_record_path
1051
+ [ -n "$id" ] || return 1
1052
+ [ -n "$dest_home" ] || return 1
1053
+ [ -n "$report" ] && [ -f "$report" ] || return 1
1054
+ dest_home_abs=$(cd "$dest_home" 2>/dev/null && pwd -P) || {
1055
+ printf 'CONFIG_REREAD: secondmate %s: send failed: destination home is not readable\n' "$id"
1056
+ return 1
1057
+ }
1058
+ state="$dest_home_abs/${FM_CONFIG_REREAD_INSTRUCTION_PREFIX_REL%/*}"
1059
+ changed_items=$(fm_config_reread_changed_items "$report")
1060
+ pending_paths=""
1061
+ stage_paths=""
1062
+ retry_report_paths=""
1063
+ if [ "${FM_CONFIG_REREAD_SKIP_PENDING:-0}" != 1 ]; then
1064
+ pending_paths=$(fm_config_reread_pending_instructions "$state")
1065
+ source_home_abs=$(cd "${FM_HOME:-}" 2>/dev/null && pwd -P || true)
1066
+ if [ -n "$source_home_abs" ]; then
1067
+ stage_paths=$(fm_config_reread_pending_stages "$source_home_abs" "$id")
1068
+ retry_report_paths=$(fm_config_reread_pending_reports "$source_home_abs" "$id")
1069
+ fi
1070
+ fi
1071
+ send_failures=0
1072
+ while IFS= read -r retry_report_path; do
1073
+ [ -n "$retry_report_path" ] || continue
1074
+ retry_stage_path=${retry_report_path%.report}
1075
+ exact_tmp=""
1076
+ for stage_path in "$retry_stage_path".tmp.*; do
1077
+ [ -f "$stage_path" ] && [ ! -L "$stage_path" ] || continue
1078
+ exact_tmp="$stage_path"
1079
+ break
1080
+ done
1081
+ if [ -n "$exact_tmp" ]; then
1082
+ rm -f "$retry_report_path" 2>/dev/null || send_failures=1
1083
+ continue
1084
+ fi
1085
+ if fm_config_write_reread_instruction "$dest_home_abs" "$retry_report_path" "$retry_stage_path"; then
1086
+ rm -f "$retry_report_path" 2>/dev/null || send_failures=1
1087
+ if [ -n "$stage_paths" ]; then
1088
+ stage_paths+=$'\n'
1089
+ fi
1090
+ stage_paths+="$retry_stage_path"
1091
+ else
1092
+ exact_tmp=${FM_CONFIG_REREAD_FAILED_TEMP:-}
1093
+ if [ -n "$exact_tmp" ] \
1094
+ && fm_config_reread_adopt_exact_temp "$exact_tmp" "$retry_stage_path"; then
1095
+ rm -f "$retry_report_path" 2>/dev/null || send_failures=1
1096
+ if [ -n "$stage_paths" ]; then
1097
+ stage_paths+=$'\n'
1098
+ fi
1099
+ stage_paths+="$retry_stage_path"
1100
+ elif [ -n "$exact_tmp" ] && [ -f "$exact_tmp" ]; then
1101
+ printf 'CONFIG_REREAD: secondmate %s: send failed: retained exact retry temporary %s\n' "$id" "$exact_tmp"
1102
+ send_failures=1
1103
+ break
1104
+ else
1105
+ printf 'CONFIG_REREAD: secondmate %s: send failed: could not rebuild retry instruction\n' "$id"
1106
+ send_failures=1
1107
+ break
1108
+ fi
1109
+ fi
1110
+ done <<EOF
1111
+ $retry_report_paths
1112
+ EOF
1113
+ if [ "$send_failures" -ne 0 ]; then
1114
+ fm_config_reread_cleanup_sent "$dest_home_abs"
1115
+ return 1
1116
+ fi
1117
+ if [ -n "$changed_items" ]; then
1118
+ source_home_abs=$(cd "${FM_HOME:-}" 2>/dev/null && pwd -P || true)
1119
+ if [ -z "$source_home_abs" ]; then
1120
+ printf 'CONFIG_REREAD: secondmate %s: send failed: could not reserve retry instruction\n' "$id"
1121
+ return 1
1122
+ fi
1123
+ if fm_config_reread_retry_queue_is_full "$source_home_abs" "$id"; then
1124
+ printf 'CONFIG_REREAD: secondmate %s: send failed: retry instruction queue is full\n' "$id"
1125
+ return 1
1126
+ fi
1127
+ current_stage_path=$(fm_config_reread_new_retry_stage_path "$source_home_abs" "$id") || {
1128
+ printf 'CONFIG_REREAD: secondmate %s: send failed: could not reserve retry instruction\n' "$id"
1129
+ return 1
1130
+ }
1131
+ if ! fm_config_write_reread_instruction "$dest_home_abs" "$report" "$current_stage_path"; then
1132
+ exact_tmp=${FM_CONFIG_REREAD_FAILED_TEMP:-}
1133
+ if [ -n "$exact_tmp" ] \
1134
+ && fm_config_reread_adopt_exact_temp "$exact_tmp" "$current_stage_path"; then
1135
+ printf 'CONFIG_REREAD: secondmate %s: send failed: could not publish retry instruction; retained exact retry generation %s\n' "$id" "$current_stage_path"
1136
+ elif [ -n "$exact_tmp" ] && [ -f "$exact_tmp" ]; then
1137
+ rm -f "$current_stage_path" 2>/dev/null || true
1138
+ printf 'CONFIG_REREAD: secondmate %s: send failed: retained exact retry temporary %s\n' "$id" "$exact_tmp"
1139
+ elif retry_record_path=$(fm_config_reread_save_retry_report "$report" "$current_stage_path"); then
1140
+ rm -f "$current_stage_path" 2>/dev/null || true
1141
+ printf 'CONFIG_REREAD: secondmate %s: send failed: could not write retry instruction; retained retry report %s\n' "$id" "$retry_record_path"
1142
+ else
1143
+ rm -f "$current_stage_path" 2>/dev/null || true
1144
+ printf 'CONFIG_REREAD: secondmate %s: send failed: could not write retry instruction or retain retry report\n' "$id"
1145
+ fi
1146
+ return 1
1147
+ fi
1148
+ if [ -n "$stage_paths" ]; then
1149
+ stage_paths+=$'\n'
1150
+ fi
1151
+ stage_paths+="$current_stage_path"
1152
+ fi
1153
+ delivery_paths="$pending_paths"
1154
+ while IFS= read -r stage_path; do
1155
+ [ -n "$stage_path" ] || continue
1156
+ if instruction_path=$(fm_config_reread_publish_stage "$dest_home_abs" "$stage_path"); then
1157
+ if [ -n "$delivery_paths" ]; then
1158
+ case $'\n'"$delivery_paths"$'\n' in
1159
+ *$'\n'"$instruction_path"$'\n'*) ;;
1160
+ *) delivery_paths+=$'\n'; delivery_paths+="$instruction_path" ;;
1161
+ esac
1162
+ else
1163
+ delivery_paths="$instruction_path"
1164
+ fi
1165
+ else
1166
+ printf 'CONFIG_REREAD: secondmate %s: send failed: could not publish retry instruction\n' "$id"
1167
+ send_failures=1
1168
+ break
1169
+ fi
1170
+ done <<EOF
1171
+ $stage_paths
1172
+ EOF
1173
+ if [ -z "$delivery_paths" ]; then
1174
+ fm_config_reread_cleanup_sent "$dest_home_abs"
1175
+ [ "${send_failures:-0}" = 1 ] && return 1
1176
+ return 0
1177
+ fi
1178
+ delivery_paths=$(printf '%s\n' "$delivery_paths" | LC_ALL=C sort)
1179
+ while IFS= read -r instruction_path; do
1180
+ [ -n "$instruction_path" ] || continue
1181
+ if fm_config_reread_send_pointer "$id" "$instruction_path"; then
1182
+ while IFS= read -r stage_path; do
1183
+ [ -n "$stage_path" ] || continue
1184
+ [ "${stage_path##*/}" = "${instruction_path##*/}" ] || continue
1185
+ rm -f "$stage_path" 2>/dev/null || true
1186
+ done <<EOF
1187
+ $stage_paths
1188
+ EOF
1189
+ else
1190
+ send_failures=1
1191
+ break
1192
+ fi
1193
+ done <<EOF
1194
+ $delivery_paths
1195
+ EOF
1196
+ if [ "$send_failures" -ne 0 ]; then
1197
+ fm_config_reread_cleanup_sent "$dest_home_abs"
1198
+ return 1
1199
+ fi
1200
+ fm_config_reread_cleanup_sent "$dest_home_abs"
1201
+ return 0
1202
+ }