@zerwiz/ymir 0.1.4 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (865) hide show
  1. package/.agents/README.md +291 -0
  2. package/.agents/agents/bragi-marketer.md +111 -0
  3. package/.agents/agents/brokk.md +352 -0
  4. package/.agents/agents/forseti-reviewer.md +93 -0
  5. package/.agents/agents/frigg-consent.md +77 -0
  6. package/.agents/agents/groa-update.md +77 -0
  7. package/.agents/agents/hnoss-designer.md +69 -0
  8. package/.agents/agents/huginn-researcher.md +99 -0
  9. package/.agents/agents/jord-projects.md +78 -0
  10. package/.agents/agents/kvasir-scout.md +91 -0
  11. package/.agents/agents/mimir-planner.md +92 -0
  12. package/.agents/agents/muninn-stow.md +77 -0
  13. package/.agents/agents/saga-bearings.md +78 -0
  14. package/.agents/agents/sindri-developer.md +114 -0
  15. package/.agents/agents/snotra-documenter.md +105 -0
  16. package/.agents/agents/syn-recovery.md +78 -0
  17. package/.agents/agents/tyr-check.md +78 -0
  18. package/.agents/agents/urdh-hold.md +77 -0
  19. package/.agents/agents/volundr-smith.md +80 -0
  20. package/.agents/agents/vor-diagnostics.md +77 -0
  21. package/.agents/assets/agents/naming.md +50 -0
  22. package/.agents/assets/agents/registry.md +93 -0
  23. package/.agents/assets/agents/runtime.md +36 -0
  24. package/.agents/assets/agents/toon-tasks-cli.md +42 -0
  25. package/.agents/assets/templates/company_entity.template.md +37 -0
  26. package/.agents/backend/README.md +74 -0
  27. package/.agents/backend/backends/cmux.sh +649 -0
  28. package/.agents/backend/backends/herdr-eventwait.py +157 -0
  29. package/.agents/backend/backends/herdr-workspace-move.py +114 -0
  30. package/.agents/backend/backends/herdr.sh +3386 -0
  31. package/.agents/backend/backends/orca.sh +290 -0
  32. package/.agents/backend/backends/tmux.sh +357 -0
  33. package/.agents/backend/backends/zellij.sh +700 -0
  34. package/.agents/backend/fm-afk-launch.sh +654 -0
  35. package/.agents/backend/fm-afk-return.sh +241 -0
  36. package/.agents/backend/fm-afk-start.sh +172 -0
  37. package/.agents/backend/fm-arm-command-policy.mjs +960 -0
  38. package/.agents/backend/fm-arm-pretool-check.sh +200 -0
  39. package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
  40. package/.agents/backend/fm-backend.sh +981 -0
  41. package/.agents/backend/fm-backlog-handoff.sh +909 -0
  42. package/.agents/backend/fm-backlog-receive.sh +187 -0
  43. package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
  44. package/.agents/backend/fm-bearings-board.sh +204 -0
  45. package/.agents/backend/fm-bearings-snapshot.sh +546 -0
  46. package/.agents/backend/fm-bootstrap.sh +1568 -0
  47. package/.agents/backend/fm-branch-outcome.sh +203 -0
  48. package/.agents/backend/fm-branch-prompt.sh +101 -0
  49. package/.agents/backend/fm-brief.sh +452 -0
  50. package/.agents/backend/fm-busy-event.sh +234 -0
  51. package/.agents/backend/fm-busy-lib.sh +986 -0
  52. package/.agents/backend/fm-captain-hold.sh +1001 -0
  53. package/.agents/backend/fm-cd-command-policy.mjs +153 -0
  54. package/.agents/backend/fm-cd-pretool-check.sh +190 -0
  55. package/.agents/backend/fm-check-lib.sh +74 -0
  56. package/.agents/backend/fm-check-register.sh +42 -0
  57. package/.agents/backend/fm-check-unregister.sh +52 -0
  58. package/.agents/backend/fm-classify-lib.sh +1770 -0
  59. package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
  60. package/.agents/backend/fm-composer-lib.sh +1416 -0
  61. package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
  62. package/.agents/backend/fm-config-push.sh +254 -0
  63. package/.agents/backend/fm-control-lib.sh +251 -0
  64. package/.agents/backend/fm-control.sh +878 -0
  65. package/.agents/backend/fm-crew-state.sh +625 -0
  66. package/.agents/backend/fm-cursor-lib.sh +243 -0
  67. package/.agents/backend/fm-decision-hold.sh +232 -0
  68. package/.agents/backend/fm-doc-audience-check.sh +269 -0
  69. package/.agents/backend/fm-dod-lib.sh +67 -0
  70. package/.agents/backend/fm-ensure-agents-md.sh +253 -0
  71. package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
  72. package/.agents/backend/fm-extension.mjs +2577 -0
  73. package/.agents/backend/fm-extension.sh +16 -0
  74. package/.agents/backend/fm-ff-lib.sh +420 -0
  75. package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
  76. package/.agents/backend/fm-fleet-sync.sh +458 -0
  77. package/.agents/backend/fm-fleet-view.sh +96 -0
  78. package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
  79. package/.agents/backend/fm-guard.sh +242 -0
  80. package/.agents/backend/fm-harness.sh +195 -0
  81. package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
  82. package/.agents/backend/fm-herdr-lab.sh +346 -0
  83. package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
  84. package/.agents/backend/fm-home-seed.sh +984 -0
  85. package/.agents/backend/fm-home-summary-refresh.sh +255 -0
  86. package/.agents/backend/fm-hook-host-lib.sh +36 -0
  87. package/.agents/backend/fm-inactive-reconcile.sh +531 -0
  88. package/.agents/backend/fm-inbox.sh +399 -0
  89. package/.agents/backend/fm-install-actionlint.sh +84 -0
  90. package/.agents/backend/fm-install-herdr.sh +96 -0
  91. package/.agents/backend/fm-install-shellcheck.sh +84 -0
  92. package/.agents/backend/fm-install-treehouse.sh +95 -0
  93. package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
  94. package/.agents/backend/fm-lease-lib.sh +218 -0
  95. package/.agents/backend/fm-lease.sh +189 -0
  96. package/.agents/backend/fm-line-cap-lib.sh +51 -0
  97. package/.agents/backend/fm-lint-workflows.sh +137 -0
  98. package/.agents/backend/fm-lint.sh +591 -0
  99. package/.agents/backend/fm-lock-lib.sh +104 -0
  100. package/.agents/backend/fm-lock.sh +107 -0
  101. package/.agents/backend/fm-marker-lib.sh +12 -0
  102. package/.agents/backend/fm-merge-local.sh +74 -0
  103. package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
  104. package/.agents/backend/fm-nm-run-lib.sh +124 -0
  105. package/.agents/backend/fm-on.sh +125 -0
  106. package/.agents/backend/fm-operational-input.sh +253 -0
  107. package/.agents/backend/fm-peek.sh +46 -0
  108. package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
  109. package/.agents/backend/fm-pr-check.sh +135 -0
  110. package/.agents/backend/fm-pr-lib.sh +1016 -0
  111. package/.agents/backend/fm-pr-merge.sh +707 -0
  112. package/.agents/backend/fm-pr-poll.sh +110 -0
  113. package/.agents/backend/fm-primary-scope-lib.sh +33 -0
  114. package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
  115. package/.agents/backend/fm-procevent-lavish.sh +628 -0
  116. package/.agents/backend/fm-procevent-lib.sh +892 -0
  117. package/.agents/backend/fm-procevent-quota.sh +290 -0
  118. package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
  119. package/.agents/backend/fm-procevent-when.sh +504 -0
  120. package/.agents/backend/fm-procevent.sh +1466 -0
  121. package/.agents/backend/fm-project-mode.sh +92 -0
  122. package/.agents/backend/fm-project-origin-lib.sh +180 -0
  123. package/.agents/backend/fm-promote.sh +282 -0
  124. package/.agents/backend/fm-public-followup-emit.sh +260 -0
  125. package/.agents/backend/fm-public-followup-lib.sh +398 -0
  126. package/.agents/backend/fm-public-followup.sh +1330 -0
  127. package/.agents/backend/fm-push-transition-lib.sh +168 -0
  128. package/.agents/backend/fm-quota-axi-lib.sh +93 -0
  129. package/.agents/backend/fm-quota-choose.sh +384 -0
  130. package/.agents/backend/fm-remote-delta-read.sh +189 -0
  131. package/.agents/backend/fm-remote-doctor.sh +800 -0
  132. package/.agents/backend/fm-remote-entrypoint.sh +193 -0
  133. package/.agents/backend/fm-remote-file.sh +234 -0
  134. package/.agents/backend/fm-remote-home-provision.sh +259 -0
  135. package/.agents/backend/fm-remote-home-seed.sh +259 -0
  136. package/.agents/backend/fm-remote-inherit-push.sh +90 -0
  137. package/.agents/backend/fm-remote-inherit.sh +179 -0
  138. package/.agents/backend/fm-remote-job-lib.sh +1241 -0
  139. package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
  140. package/.agents/backend/fm-remote-job-worker.sh +1124 -0
  141. package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
  142. package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
  143. package/.agents/backend/fm-review-diff.sh +158 -0
  144. package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
  145. package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
  146. package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
  147. package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
  148. package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
  149. package/.agents/backend/fm-secondmate-report.sh +88 -0
  150. package/.agents/backend/fm-send.sh +1059 -0
  151. package/.agents/backend/fm-session-lock-lib.sh +176 -0
  152. package/.agents/backend/fm-session-start.sh +970 -0
  153. package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
  154. package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
  155. package/.agents/backend/fm-sessionstart-run.sh +145 -0
  156. package/.agents/backend/fm-spawn.sh +3153 -0
  157. package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
  158. package/.agents/backend/fm-startup-memory-budget.sh +94 -0
  159. package/.agents/backend/fm-startup-network.sh +642 -0
  160. package/.agents/backend/fm-stow-cascade.sh +251 -0
  161. package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
  162. package/.agents/backend/fm-supervise-daemon.sh +1732 -0
  163. package/.agents/backend/fm-supervision-instructions.sh +215 -0
  164. package/.agents/backend/fm-supervision-lib.sh +90 -0
  165. package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
  166. package/.agents/backend/fm-tangle-lib.sh +53 -0
  167. package/.agents/backend/fm-task-inbox-lib.sh +402 -0
  168. package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
  169. package/.agents/backend/fm-teardown.sh +2922 -0
  170. package/.agents/backend/fm-test-isolation-proof.sh +588 -0
  171. package/.agents/backend/fm-test-run.sh +2191 -0
  172. package/.agents/backend/fm-timeout-lib.sh +141 -0
  173. package/.agents/backend/fm-timing-lib.sh +179 -0
  174. package/.agents/backend/fm-tmux-lib.sh +291 -0
  175. package/.agents/backend/fm-tool-update-check.sh +898 -0
  176. package/.agents/backend/fm-trace-context-lib.sh +227 -0
  177. package/.agents/backend/fm-transition-lib.sh +103 -0
  178. package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
  179. package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
  180. package/.agents/backend/fm-turnend-guard.sh +432 -0
  181. package/.agents/backend/fm-update.sh +110 -0
  182. package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
  183. package/.agents/backend/fm-voice-client.py +1373 -0
  184. package/.agents/backend/fm-voice-relay.py +1256 -0
  185. package/.agents/backend/fm-wake-drain.sh +592 -0
  186. package/.agents/backend/fm-wake-grant.sh +127 -0
  187. package/.agents/backend/fm-wake-lib.sh +1849 -0
  188. package/.agents/backend/fm-watch-arm.sh +602 -0
  189. package/.agents/backend/fm-watch-checkpoint.sh +109 -0
  190. package/.agents/backend/fm-watch.sh +1962 -0
  191. package/.agents/backend/fm-x-dismiss.sh +116 -0
  192. package/.agents/backend/fm-x-followup.sh +286 -0
  193. package/.agents/backend/fm-x-lib.sh +1000 -0
  194. package/.agents/backend/fm-x-link.sh +234 -0
  195. package/.agents/backend/fm-x-poll.sh +185 -0
  196. package/.agents/backend/fm-x-reply.sh +385 -0
  197. package/.agents/backend/fm_voice_frame.py +166 -0
  198. package/.agents/backend/fm_voice_records.py +574 -0
  199. package/.agents/backend/model-bridge.py +360 -0
  200. package/.agents/backend/opencode-go-bridge.py +30 -0
  201. package/.agents/bus/README.md +12 -0
  202. package/.agents/bus/messages.json +1 -0
  203. package/.agents/config/agents.machine.example.yaml +16 -0
  204. package/.agents/config/agents.yaml.example +72 -0
  205. package/.agents/config/crew-dispatch.json +22 -0
  206. package/.agents/config/cron.yaml +11 -0
  207. package/.agents/config/eindri-dispatch.json +67 -0
  208. package/.agents/config/eindri-harness +1 -0
  209. package/.agents/config/startup-memory-budget +1 -0
  210. package/.agents/filebrowser/.filebrowser.json +6 -0
  211. package/.agents/filebrowser/README.md +11 -0
  212. package/.agents/gateway/README.md +12 -0
  213. package/.agents/github/README.md +15 -0
  214. package/.agents/github/webhooks/.gitkeep +0 -0
  215. package/.agents/github/workflows/.gitkeep +0 -0
  216. package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
  217. package/.agents/harness/opencode/plugins/package.json +4 -0
  218. package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
  219. package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
  220. package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
  221. package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
  222. package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
  223. package/.agents/memory/README.md +46 -0
  224. package/.agents/memory/runes_audit.md +5 -0
  225. package/.agents/migrations/0001-hodd-layout.sh +38 -0
  226. package/.agents/migrations/0002-a2a-mcp.sh +27 -0
  227. package/.agents/migrations/0003-private-data-separation.sh +121 -0
  228. package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
  229. package/.agents/migrations/0005-roots-out-of-tree.sh +158 -0
  230. package/.agents/sandbox/Dockerfile.utgard +19 -0
  231. package/.agents/sandbox/README.md +14 -0
  232. package/.agents/sandbox/utgard.config.json +14 -0
  233. package/.agents/skills/README.md +59 -0
  234. package/.agents/skills/bragi-marketing/README.md +3 -0
  235. package/.agents/skills/bragi-marketing/SKILL.md +82 -0
  236. package/.agents/skills/eindri-homes/README.md +3 -0
  237. package/.agents/skills/eindri-homes/SKILL.md +291 -0
  238. package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
  239. package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
  240. package/.agents/skills/frigg-consent/README.md +3 -0
  241. package/.agents/skills/frigg-consent/SKILL.md +56 -0
  242. package/.agents/skills/galdr-ymirsystem/README.md +3 -0
  243. package/.agents/skills/galdr-ymirsystem/SKILL.md +159 -0
  244. package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
  245. package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
  246. package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
  247. package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
  248. package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
  249. package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
  250. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +736 -0
  251. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
  252. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
  253. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
  254. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
  255. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
  256. package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +539 -0
  257. package/.agents/skills/galdr-ymirsystem/assets/installation.md +763 -0
  258. package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
  259. package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
  260. package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +163 -0
  261. package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +415 -0
  262. package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
  263. package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +133 -0
  264. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
  265. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
  266. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +101 -0
  267. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
  268. package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
  269. package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
  270. package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
  271. package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
  272. package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
  273. package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
  274. package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
  275. package/.agents/skills/galdr-ymirsystem/assets/smidja.md +180 -0
  276. package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
  277. package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
  278. package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
  279. package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +503 -0
  280. package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
  281. package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
  282. package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
  283. package/.agents/skills/gjallarhorn-relay/README.md +3 -0
  284. package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
  285. package/.agents/skills/groa-update/SKILL.md +90 -0
  286. package/.agents/skills/hamr-adapters/README.md +3 -0
  287. package/.agents/skills/hamr-adapters/SKILL.md +123 -0
  288. package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
  289. package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
  290. package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
  291. package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
  292. package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
  293. package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
  294. package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
  295. package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
  296. package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
  297. package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
  298. package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
  299. package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
  300. package/.agents/skills/herdr-panes/README.md +3 -0
  301. package/.agents/skills/herdr-panes/SKILL.md +92 -0
  302. package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
  303. package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
  304. package/.agents/skills/hnoss-design/DESIGN.md +19 -0
  305. package/.agents/skills/hnoss-design/README.md +3 -0
  306. package/.agents/skills/hnoss-design/SKILL.md +83 -0
  307. package/.agents/skills/hvild-afk/README.md +3 -0
  308. package/.agents/skills/hvild-afk/SKILL.md +241 -0
  309. package/.agents/skills/jord-projects/README.md +3 -0
  310. package/.agents/skills/jord-projects/SKILL.md +92 -0
  311. package/.agents/skills/lifecycle/SKILL.md +56 -0
  312. package/.agents/skills/lifecycle/smoke_test.sh +57 -0
  313. package/.agents/skills/lifecycle/start.sh +21 -0
  314. package/.agents/skills/lifecycle/status.sh +35 -0
  315. package/.agents/skills/lifecycle/stop.sh +16 -0
  316. package/.agents/skills/muninn-stow/README.md +3 -0
  317. package/.agents/skills/muninn-stow/SKILL.md +309 -0
  318. package/.agents/skills/no-mistakes/SKILL.md +379 -0
  319. package/.agents/skills/nornir-schedule/README.md +3 -0
  320. package/.agents/skills/nornir-schedule/SKILL.md +25 -0
  321. package/.agents/skills/nornir-schedule/assets/events.md +137 -0
  322. package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
  323. package/.agents/skills/nsr-compliance/README.md +3 -0
  324. package/.agents/skills/nsr-compliance/SKILL.md +29 -0
  325. package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
  326. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
  327. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
  328. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
  329. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
  330. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
  331. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
  332. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
  333. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
  334. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
  335. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
  336. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
  337. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
  338. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
  339. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
  340. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
  341. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
  342. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
  343. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
  344. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
  345. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
  346. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
  347. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
  348. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  349. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
  350. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
  351. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
  352. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
  353. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
  354. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
  355. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
  356. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
  357. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
  358. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
  359. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
  360. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
  361. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
  362. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
  363. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
  364. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
  365. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
  366. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
  367. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
  368. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
  369. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
  370. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
  371. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
  372. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
  373. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
  374. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
  375. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
  376. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
  377. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
  378. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
  379. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
  380. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
  381. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
  382. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
  383. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
  384. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
  385. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
  386. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
  387. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
  388. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
  389. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
  390. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
  391. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
  392. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
  393. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
  394. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
  395. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
  396. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
  397. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
  398. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
  399. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
  400. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
  401. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
  402. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
  403. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
  404. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
  405. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
  406. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
  407. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
  408. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
  409. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
  410. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
  411. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
  412. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
  413. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
  414. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
  415. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
  416. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
  417. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
  418. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
  419. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
  420. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
  421. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
  422. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
  423. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
  424. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
  425. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
  426. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
  427. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
  428. package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
  429. package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
  430. package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
  431. package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
  432. package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
  433. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
  434. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
  435. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
  436. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
  437. package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
  438. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
  439. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
  440. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
  441. package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
  442. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
  443. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
  444. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
  445. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
  446. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
  447. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
  448. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
  449. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  450. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
  451. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
  452. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
  453. package/.agents/skills/pr-ops/README.md +3 -0
  454. package/.agents/skills/pr-ops/SKILL.md +109 -0
  455. package/.agents/skills/ratatoskr-a2a/README.md +3 -0
  456. package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
  457. package/.agents/skills/rules-check-drift/SKILL.md +81 -0
  458. package/.agents/skills/saga-bearings/README.md +3 -0
  459. package/.agents/skills/saga-bearings/SKILL.md +26 -0
  460. package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
  461. package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
  462. package/.agents/skills/saga-bearings/assets/recap.md +56 -0
  463. package/.agents/skills/syn-recovery/README.md +3 -0
  464. package/.agents/skills/syn-recovery/SKILL.md +54 -0
  465. package/.agents/skills/tyr-check/README.md +3 -0
  466. package/.agents/skills/tyr-check/SKILL.md +218 -0
  467. package/.agents/skills/urdh-hold/README.md +3 -0
  468. package/.agents/skills/urdh-hold/SKILL.md +25 -0
  469. package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
  470. package/.agents/skills/urdh-hold/assets/hold.md +54 -0
  471. package/.agents/skills/vor-diagnostics/README.md +3 -0
  472. package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
  473. package/.agents/skills/ymir-host/README.md +3 -0
  474. package/.agents/skills/ymir-host/SKILL.md +35 -0
  475. package/.agents/skills/ymir-host/assets/desktop.md +36 -0
  476. package/.agents/skills/ymir-host/assets/install.md +69 -0
  477. package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
  478. package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
  479. package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
  480. package/.agents/skills/ymir-host/assets/update.md +64 -0
  481. package/.agents/tests/assets/board-render-harness.mjs +117 -0
  482. package/.agents/tests/cmux-test-safety.sh +51 -0
  483. package/.agents/tests/fixtures.sh +291 -0
  484. package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
  485. package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
  486. package/.agents/tests/fm-afk-launch.test.sh +961 -0
  487. package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
  488. package/.agents/tests/fm-afk-return.test.sh +283 -0
  489. package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
  490. package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
  491. package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
  492. package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
  493. package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
  494. package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
  495. package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
  496. package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
  497. package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
  498. package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
  499. package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
  500. package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
  501. package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
  502. package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
  503. package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
  504. package/.agents/tests/fm-backend-orca.test.sh +1357 -0
  505. package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
  506. package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
  507. package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
  508. package/.agents/tests/fm-backend.test.sh +1142 -0
  509. package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
  510. package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
  511. package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
  512. package/.agents/tests/fm-bearings-board.test.sh +414 -0
  513. package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
  514. package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
  515. package/.agents/tests/fm-bootstrap.test.sh +1178 -0
  516. package/.agents/tests/fm-branch-supervision.test.sh +552 -0
  517. package/.agents/tests/fm-brief.test.sh +785 -0
  518. package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
  519. package/.agents/tests/fm-busy-state.test.sh +465 -0
  520. package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
  521. package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
  522. package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
  523. package/.agents/tests/fm-check-unregister.test.sh +198 -0
  524. package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
  525. package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
  526. package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
  527. package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
  528. package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
  529. package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
  530. package/.agents/tests/fm-composer-ghost.test.sh +716 -0
  531. package/.agents/tests/fm-composer-lib.test.sh +666 -0
  532. package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
  533. package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
  534. package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
  535. package/.agents/tests/fm-control.test.sh +910 -0
  536. package/.agents/tests/fm-crew-state.test.sh +1610 -0
  537. package/.agents/tests/fm-cursor-harness.test.sh +404 -0
  538. package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
  539. package/.agents/tests/fm-cursor-primary.test.sh +708 -0
  540. package/.agents/tests/fm-daemon.test.sh +2740 -0
  541. package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
  542. package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
  543. package/.agents/tests/fm-extension-binding.test.sh +2187 -0
  544. package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
  545. package/.agents/tests/fm-fleet-sync.test.sh +721 -0
  546. package/.agents/tests/fm-gate-refuse.test.sh +356 -0
  547. package/.agents/tests/fm-gitignore-config.test.sh +89 -0
  548. package/.agents/tests/fm-gotmp.test.sh +230 -0
  549. package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
  550. package/.agents/tests/fm-grok-harness.test.sh +117 -0
  551. package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
  552. package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
  553. package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
  554. package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
  555. package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
  556. package/.agents/tests/fm-herdr-lab.test.sh +243 -0
  557. package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
  558. package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
  559. package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
  560. package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
  561. package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
  562. package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
  563. package/.agents/tests/fm-kimi-harness.test.sh +687 -0
  564. package/.agents/tests/fm-lint-workflows.test.sh +532 -0
  565. package/.agents/tests/fm-lint.test.sh +1023 -0
  566. package/.agents/tests/fm-muse-harness.test.sh +932 -0
  567. package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
  568. package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
  569. package/.agents/tests/fm-on.test.sh +507 -0
  570. package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
  571. package/.agents/tests/fm-operational-input.test.sh +160 -0
  572. package/.agents/tests/fm-peek-remote.test.sh +110 -0
  573. package/.agents/tests/fm-pending-reply.test.sh +1288 -0
  574. package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
  575. package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
  576. package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
  577. package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
  578. package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
  579. package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
  580. package/.agents/tests/fm-pr-merge.test.sh +2135 -0
  581. package/.agents/tests/fm-procevent-quota.test.sh +225 -0
  582. package/.agents/tests/fm-procevent-when.test.sh +406 -0
  583. package/.agents/tests/fm-procevent.test.sh +1745 -0
  584. package/.agents/tests/fm-project-origin.test.sh +110 -0
  585. package/.agents/tests/fm-public-followup.test.sh +2272 -0
  586. package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
  587. package/.agents/tests/fm-quota-choose.test.sh +626 -0
  588. package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
  589. package/.agents/tests/fm-remote-doctor.test.sh +625 -0
  590. package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
  591. package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
  592. package/.agents/tests/fm-remote-job.test.sh +768 -0
  593. package/.agents/tests/fm-remote-reply.test.sh +520 -0
  594. package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
  595. package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
  596. package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
  597. package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
  598. package/.agents/tests/fm-review-diff.test.sh +176 -0
  599. package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
  600. package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
  601. package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
  602. package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
  603. package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
  604. package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
  605. package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
  606. package/.agents/tests/fm-send-inbox.test.sh +352 -0
  607. package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
  608. package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
  609. package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
  610. package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
  611. package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
  612. package/.agents/tests/fm-send-settle.test.sh +147 -0
  613. package/.agents/tests/fm-send-strict.test.sh +241 -0
  614. package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
  615. package/.agents/tests/fm-session-start.test.sh +2515 -0
  616. package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
  617. package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
  618. package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
  619. package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
  620. package/.agents/tests/fm-spawn-batch.test.sh +148 -0
  621. package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
  622. package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
  623. package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
  624. package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
  625. package/.agents/tests/fm-startup-network.test.sh +712 -0
  626. package/.agents/tests/fm-stow-cascade.test.sh +370 -0
  627. package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
  628. package/.agents/tests/fm-supervision-events.test.sh +156 -0
  629. package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
  630. package/.agents/tests/fm-tangle-guard.test.sh +277 -0
  631. package/.agents/tests/fm-task-delivery.test.sh +420 -0
  632. package/.agents/tests/fm-task-inbox.test.sh +513 -0
  633. package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
  634. package/.agents/tests/fm-teardown.test.sh +2665 -0
  635. package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
  636. package/.agents/tests/fm-test-fixtures.test.sh +132 -0
  637. package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
  638. package/.agents/tests/fm-test-run.test.sh +1196 -0
  639. package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
  640. package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
  641. package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
  642. package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
  643. package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
  644. package/.agents/tests/fm-transition-lib.test.sh +54 -0
  645. package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
  646. package/.agents/tests/fm-update.test.sh +304 -0
  647. package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
  648. package/.agents/tests/fm-voice-relay.test.sh +4741 -0
  649. package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
  650. package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
  651. package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
  652. package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
  653. package/.agents/tests/fm-wake-queue.test.sh +1224 -0
  654. package/.agents/tests/fm-watch-arm.test.sh +821 -0
  655. package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
  656. package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
  657. package/.agents/tests/fm-watch-triage.test.sh +3889 -0
  658. package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
  659. package/.agents/tests/fm-x-mode.test.sh +3042 -0
  660. package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
  661. package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
  662. package/.agents/tests/herdr-test-safety.sh +42 -0
  663. package/.agents/tests/lib.sh +315 -0
  664. package/.agents/tests/remote-herdr-fixture.sh +125 -0
  665. package/.agents/tests/secondmate-helpers.sh +193 -0
  666. package/.agents/tests/smoke.test.sh +93 -0
  667. package/.agents/tests/wake-helpers.sh +338 -0
  668. package/.agents/tests/zellij-test-safety.sh +60 -0
  669. package/.agents/tools/README.md +10 -0
  670. package/.agents/tools/bin/tasks-cli +267 -0
  671. package/.agents/tools/tasks-cli.ts +216 -0
  672. package/AGENTS.md +498 -0
  673. package/CHANGELOG.md +1913 -0
  674. package/LICENSE +1 -1
  675. package/NOTICE +63 -0
  676. package/README.md +114 -60
  677. package/RULES/01-domains.md +51 -0
  678. package/RULES/02-agents.md +78 -0
  679. package/RULES/03-houses.md +61 -0
  680. package/RULES/04-hoard.md +97 -0
  681. package/RULES/05-platforms.md +67 -0
  682. package/RULES/06-append-only.md +108 -0
  683. package/RULES/07-config.md +101 -0
  684. package/RULES/08-delivery-gate.md +75 -0
  685. package/RULES/09-electron.md +58 -0
  686. package/RULES/README.md +22 -0
  687. package/bin/README.md +107 -0
  688. package/bin/a2a-mcp.sh +133 -0
  689. package/bin/a2a-serve.py +103 -0
  690. package/bin/a2a-serve.sh +8 -0
  691. package/bin/a2a-talk.sh +50 -0
  692. package/bin/a2abridge-ensure.sh +108 -0
  693. package/bin/agent-run.sh +43 -0
  694. package/bin/agents-config.sh +256 -0
  695. package/bin/apodex-smoke-test.sh +105 -0
  696. package/bin/bifrost-bridge.sh +131 -0
  697. package/bin/bifrost-ingress.sh +77 -0
  698. package/bin/bootstrap-macos.sh +112 -0
  699. package/bin/bootstrap-windows.ps1 +112 -0
  700. package/bin/branch-guard.sh +64 -0
  701. package/bin/brokk +73 -0
  702. package/bin/brokk-classify-lib.sh +1770 -0
  703. package/bin/brokk-lease-lib.sh +222 -0
  704. package/bin/brokk-lease.sh +193 -0
  705. package/bin/brokk-lint.sh +65 -0
  706. package/bin/brokk-send.sh +51 -0
  707. package/bin/brokk-update.sh +9 -0
  708. package/bin/brokk-wake-grant.sh +127 -0
  709. package/bin/brokk-wake-lib.sh +1849 -0
  710. package/bin/changelog-assemble.sh +154 -0
  711. package/bin/changelog-guard.sh +197 -0
  712. package/bin/crash-sense.sh +173 -0
  713. package/bin/design-check.sh +80 -0
  714. package/bin/design-icon.sh +153 -0
  715. package/bin/desktop-place.sh +224 -0
  716. package/bin/docs-guard.sh +39 -0
  717. package/bin/editor-place.sh +225 -0
  718. package/bin/eindri-acclaim.sh +47 -0
  719. package/bin/eindri-control.sh +28 -0
  720. package/bin/eindri-role.sh +83 -0
  721. package/bin/eindri-seat.sh +83 -0
  722. package/bin/eindri-seen.sh +54 -0
  723. package/bin/eindri-send.sh +15 -0
  724. package/bin/eindri-start.sh +98 -0
  725. package/bin/eindri-watch.sh +57 -0
  726. package/bin/einherjar-spawn.sh +576 -0
  727. package/bin/eir-doctor.sh +158 -0
  728. package/bin/erindi-brief.sh +271 -0
  729. package/bin/feature-inventory.sh +73 -0
  730. package/bin/fleet-apply.sh +81 -0
  731. package/bin/github-deploy.sh +94 -0
  732. package/bin/gjallarhorn-expose.sh +136 -0
  733. package/bin/gjallarhorn-notify.sh +71 -0
  734. package/bin/gjallarhorn-purge.sh +67 -0
  735. package/bin/gjallarhorn-tunnel.sh +85 -0
  736. package/bin/gleipnir-lock-lib.sh +284 -0
  737. package/bin/groa-update.sh +130 -0
  738. package/bin/hall-snapshot.sh +111 -0
  739. package/bin/hamr-harness.sh +246 -0
  740. package/bin/heimdall-ssh-keys.sh +374 -0
  741. package/bin/herdr-agents.py +44 -0
  742. package/bin/herdr-ensure.sh +104 -0
  743. package/bin/herdr-run.sh +452 -0
  744. package/bin/hermes-ensure.sh +88 -0
  745. package/bin/hlidskjalf-agents.sh +172 -0
  746. package/bin/hlidskjalf-usage.sh +196 -0
  747. package/bin/hoard-guard.sh +135 -0
  748. package/bin/hoard-lib.sh +105 -0
  749. package/bin/hodd.sh +102 -0
  750. package/bin/host-sense.sh +199 -0
  751. package/bin/huginn-research-worker.sh +121 -0
  752. package/bin/local-model-lock.sh +70 -0
  753. package/bin/mimir-bridge.py +259 -0
  754. package/bin/mimir-bridge.sh +100 -0
  755. package/bin/mimir-ingest.sh +131 -0
  756. package/bin/mimir-reflect.sh +27 -0
  757. package/bin/mimir.sh +113 -0
  758. package/bin/mjollnir-webhook.sh +72 -0
  759. package/bin/mjollnir.sh +104 -0
  760. package/bin/model-hardware.sh +96 -0
  761. package/bin/model-resolve.sh +160 -0
  762. package/bin/models-detect.sh +178 -0
  763. package/bin/nornir-cron-start.sh +162 -0
  764. package/bin/nornir-job-daily-briefing.sh +191 -0
  765. package/bin/nornir-job-git-sync.sh +152 -0
  766. package/bin/nornir-job-hall-snapshot.sh +28 -0
  767. package/bin/nornir-job-memory-housekeeping.sh +114 -0
  768. package/bin/nornir-job-nsr-compliance.sh +54 -0
  769. package/bin/nornir-job-observer.sh +166 -0
  770. package/bin/nornir-job-skillopt-sleep.sh +76 -0
  771. package/bin/npm-publish.sh +136 -0
  772. package/bin/omarchy-hook-install.sh +60 -0
  773. package/bin/omarchy-install.sh +161 -0
  774. package/bin/omarchy-plugins.sh +187 -0
  775. package/bin/omarchy-sense.sh +162 -0
  776. package/bin/perm-guard.sh +30 -0
  777. package/bin/pi-agent.sh +68 -0
  778. package/bin/pi-ensure.sh +77 -0
  779. package/bin/pi-local.sh +37 -0
  780. package/bin/pi-model-agnostic.sh +71 -0
  781. package/bin/pi-seat.sh +63 -0
  782. package/bin/prereq-ensure.sh +166 -0
  783. package/bin/private-guard.sh +58 -0
  784. package/bin/project-git.sh +69 -0
  785. package/bin/public-guard.sh +71 -0
  786. package/bin/ratatoskr.sh +49 -0
  787. package/bin/realm-lib.sh +32 -0
  788. package/bin/repo-scrub.sh +62 -0
  789. package/bin/rodd-operational-input.sh +250 -0
  790. package/bin/runes-append.sh +198 -0
  791. package/bin/saga-session-start.sh +172 -0
  792. package/bin/saga-sessionstart-run.sh +79 -0
  793. package/bin/saga-wake-drain.sh +41 -0
  794. package/bin/secret-guard.sh +63 -0
  795. package/bin/sessrumnir-ensure.sh +100 -0
  796. package/bin/sessrumnir-sync.sh +237 -0
  797. package/bin/sessrumnir.sh +104 -0
  798. package/bin/skillopt-setup.sh +76 -0
  799. package/bin/skuld-branch-outcome.sh +203 -0
  800. package/bin/skuld-branch-prompt.sh +101 -0
  801. package/bin/smidja-bootstrap.sh +94 -0
  802. package/bin/smidja-observe.sh +86 -0
  803. package/bin/syn-arm-pretool-check.sh +57 -0
  804. package/bin/syn-asset-pretool-check.sh +89 -0
  805. package/bin/syn-cd-pretool-check.sh +28 -0
  806. package/bin/syn-guard-pretool-check.sh +83 -0
  807. package/bin/syn-spawn-pretool-check.sh +58 -0
  808. package/bin/syn-turnend-guard.sh +36 -0
  809. package/bin/syn-watch-arm.sh +99 -0
  810. package/bin/tailscale-sync.sh +203 -0
  811. package/bin/telegram-bot.sh +82 -0
  812. package/bin/toolchain.sh +74 -0
  813. package/bin/ui-truth-check.sh +75 -0
  814. package/bin/utgard.sh +111 -0
  815. package/bin/valhalla.sh +115 -0
  816. package/bin/valknut-load.sh +298 -0
  817. package/bin/vor-crew-state.sh +124 -0
  818. package/bin/wedge-notify.sh +60 -0
  819. package/bin/workspace-provision.sh +80 -0
  820. package/bin/workspace-rag.sh +163 -0
  821. package/bin/wyrd-db.sh +47 -0
  822. package/bin/yggdrasil.sh +152 -0
  823. package/bin/ymir-edit.sh +145 -0
  824. package/bin/ymir-install.sh +806 -0
  825. package/bin/ymir-invite.sh +93 -0
  826. package/bin/ymir-isolation.sh +88 -0
  827. package/bin/ymir-migrate.sh +61 -0
  828. package/bin/ymir-plan.sh +356 -0
  829. package/bin/ymir-platform.sh +348 -0
  830. package/bin/ymir-say.sh +110 -0
  831. package/bin/ymir-setup-auth.sh +119 -0
  832. package/bin/ymir-validate.sh +193 -0
  833. package/bin/ymir.js +74 -16
  834. package/docs/Architecture.md +339 -0
  835. package/docs/design.md +351 -0
  836. package/docs/installations/README.md +63 -0
  837. package/docs/installations/ymir-home.md +93 -0
  838. package/docs/integration.md +105 -0
  839. package/docs/lore.md +1060 -0
  840. package/docs/research/agent-harnesses.md +290 -0
  841. package/docs/runbooks/README.md +19 -0
  842. package/docs/runbooks/a2a.md +91 -0
  843. package/docs/runbooks/agent-permissions.md +99 -0
  844. package/docs/runbooks/agents.md +57 -0
  845. package/docs/runbooks/models.md +69 -0
  846. package/docs/runbooks/realm-onboarding.md +60 -0
  847. package/docs/runbooks/secrets-and-hoard.md +60 -0
  848. package/docs/runbooks/updates-and-migrations.md +110 -0
  849. package/docs/runbooks/worktrees-isolation.md +72 -0
  850. package/docs/session-start.md +105 -0
  851. package/docs/skillopt-integration.md +321 -0
  852. package/docs/sync-system.md +66 -0
  853. package/docs/workspaces.md +104 -0
  854. package/docs/ymir-rut.md +260 -0
  855. package/package.json +54 -14
  856. package/packaging/build.sh +74 -0
  857. package/packaging/macos/Ymir Installer.command +43 -0
  858. package/packaging/windows/ymir-setup.nsi +74 -0
  859. package/scripts/README.md +22 -0
  860. package/scripts/electron.sh +269 -0
  861. package/scripts/lower.sh +34 -0
  862. package/scripts/raise.sh +42 -0
  863. package/scripts/start.sh +217 -0
  864. package/scripts/stop.sh +113 -0
  865. package/install.sh +0 -35
@@ -0,0 +1,913 @@
1
+ #!/usr/bin/env bash
2
+ # Tests for the local-HEAD secondmate sync: every secondmate home tracks the
3
+ # PRIMARY firstmate checkout's current default-branch commit by a purely LOCAL
4
+ # fast-forward (no origin fetch). Two hook points drive it - bin/fm-spawn.sh
5
+ # (before launching a secondmate) and bin/fm-bootstrap.sh (a startup sweep of
6
+ # every live secondmate home) - and both share the ff machinery in
7
+ # bin/fm-ff-lib.sh.
8
+ #
9
+ # The guarantees under test:
10
+ # - The shared ff helper, driven with a LOCAL commit base, advances a behind
11
+ # home (updated), is a no-op on an already-current home (current, no nudge),
12
+ # and refuses - leaving work untouched - on a dirty, diverged, or
13
+ # in-flight (feature-branch) home.
14
+ # - No origin fetch happens in the local-HEAD sync path.
15
+ # - The bootstrap sweep fast-forwards every live secondmate home and sends a
16
+ # reread nudge ONLY for a running secondmate whose instruction surface
17
+ # actually changed; a successful send is reported as BOOTSTRAP_INFO:, a
18
+ # failed send is reported as NUDGE_SECONDMATES:, an already-current or
19
+ # readme-only home is never nudged, a skipped home is reported as
20
+ # SECONDMATE_SYNC:, and a home with no live metadata is never swept.
21
+ # - Spawning a secondmate fast-forwards its worktree to the primary's HEAD
22
+ # before launch, or warns and launches unchanged when the sync is skipped.
23
+ set -u
24
+
25
+ # shellcheck source=tests/lib.sh
26
+ . "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
27
+
28
+ # shellcheck source=bin/fm-ff-lib.sh
29
+ . "$ROOT/bin/fm-ff-lib.sh"
30
+
31
+ BASE_PATH=${FM_TEST_BASE_PATH:-/usr/bin:/bin:/usr/sbin:/sbin}
32
+
33
+ # Deterministic, isolated git identity for fixture commits.
34
+ fm_git_identity fmtest fmtest@example.com
35
+
36
+ TMP_ROOT=$(fm_test_tmproot fm-secondmate-sync)
37
+ export FM_BACKEND=tmux
38
+
39
+ # --- world builders --------------------------------------------------------
40
+
41
+ # new_world <name>: a PRIMARY firstmate repo on `main` with one commit (the
42
+ # instruction surface seeded) and a home dir with state/ and data/. NO origin
43
+ # remote: the local-HEAD sync never needs one. Echoes the world dir.
44
+ new_world() {
45
+ local name=$1 w
46
+ w="$TMP_ROOT/$name"
47
+ mkdir -p "$w/home/state" "$w/home/data"
48
+ # Fresh watcher beacon keeps fm-guard quiet for the spawn path.
49
+ touch "$w/home/state/.last-watcher-beat"
50
+
51
+ git init -q -b main "$w/main"
52
+ # Mirror the real repo: the gitignored operational dirs never dirty a worktree,
53
+ # so a secondmate home's data/state/projects can never block its fast-forward.
54
+ printf 'projects/\nstate/\ndata/\n.no-mistakes/\nconfig/crew-harness\nscratchpad*\n' > "$w/main/.gitignore"
55
+ printf 'v1\n' > "$w/main/AGENTS.md"
56
+ printf 'r1\n' > "$w/main/README.md"
57
+ mkdir -p "$w/main/bin" "$w/main/.agents/skills"
58
+ printf 'echo a\n' > "$w/main/bin/tool.sh"
59
+ printf 's1\n' > "$w/main/.agents/skills/note.md"
60
+ git -C "$w/main" add -A
61
+ git -C "$w/main" commit -qm c1
62
+ printf '%s\n' "$w"
63
+ }
64
+
65
+ # add_sm_worktree <w> <id> <commit>: a secondmate home as a DETACHED worktree of
66
+ # the primary at <commit>, plus its seed marker and a LIVE kind=secondmate meta
67
+ # (a window= makes it a running direct report).
68
+ add_sm_worktree() {
69
+ local w=$1 id=$2 commit=$3
70
+ git -C "$w/main" worktree add -q --detach "$w/$id" "$commit"
71
+ printf '%s\n' "$id" > "$w/$id/.fm-secondmate-home"
72
+ {
73
+ printf 'window=firstmate:fm-%s\n' "$id"
74
+ printf 'kind=secondmate\n'
75
+ printf 'harness=codex\n'
76
+ printf 'home=%s/%s\n' "$w" "$id"
77
+ } > "$w/home/state/$id.meta"
78
+ }
79
+
80
+ # bump_primary <w> <mode>: advance the PRIMARY's main branch by one local commit.
81
+ # instr changes the instruction surface (AGENTS.md, bin, .agents/skills) plus README;
82
+ # readme changes only README. No push - the sync follows the primary's local HEAD.
83
+ bump_primary() {
84
+ local w=$1 mode=$2
85
+ printf 'r-%s\n' "$mode" >> "$w/main/README.md"
86
+ if [ "$mode" = instr ]; then
87
+ printf 'v-%s\n' "$mode" > "$w/main/AGENTS.md"
88
+ printf 'echo %s\n' "$mode" > "$w/main/bin/tool.sh"
89
+ printf 's-%s\n' "$mode" > "$w/main/.agents/skills/note.md"
90
+ fi
91
+ git -C "$w/main" add -A
92
+ git -C "$w/main" commit -qm "bump-$mode"
93
+ }
94
+
95
+ head_of() { git -C "$1" rev-parse HEAD; }
96
+
97
+ # ignore_marker_commit <w>: land THE FIX in the primary - add the seed marker to
98
+ # the tracked .gitignore and commit it on main. The marker (.fm-secondmate-home)
99
+ # is firstmate-generic, written by bin/fm-home-seed.sh into every seeded home; once
100
+ # a home fast-forwards past this commit the marker is git-ignored and can no longer
101
+ # read as a dirty working tree to any `git status --porcelain` dirtiness check.
102
+ ignore_marker_commit() {
103
+ local w=$1
104
+ printf '.fm-secondmate-home\n' >> "$w/main/.gitignore"
105
+ git -C "$w/main" add -A
106
+ git -C "$w/main" commit -qm "gitignore seed marker"
107
+ }
108
+
109
+ # seed_marked_home <w> <id> <commit>: a secondmate home matching what
110
+ # bin/fm-home-seed.sh actually lays down - a detached worktree at <commit>, the
111
+ # seed marker, a live kind=secondmate meta, and the gitignored operational dirs
112
+ # with a charter. The ONLY unignored extra file is the seed marker, which is
113
+ # exactly what this fix must keep from dirtying the home.
114
+ seed_marked_home() {
115
+ local w=$1 id=$2 commit=$3
116
+ add_sm_worktree "$w" "$id" "$commit"
117
+ mkdir -p "$w/$id/data" "$w/$id/state" "$w/$id/config" "$w/$id/projects"
118
+ printf 'charter\n' > "$w/$id/data/charter.md"
119
+ }
120
+
121
+ # run_ff <dir> <base>: drive the shared ff helper in THIS shell (output to a file,
122
+ # not a subshell, so FF_STATUS / FF_INSTR propagate). Sets FF_OUT to the printed
123
+ # status line. Uses allow_detached=yes, ignore_seed_marker=yes (the secondmate
124
+ # home contract).
125
+ FF_OUT=""
126
+ run_ff() {
127
+ local dir=$1 base=$2 outfile="$TMP_ROOT/ff.out"
128
+ ff_target "$dir" "secondmate sm" "$base" yes yes >"$outfile" 2>&1
129
+ FF_OUT=$(cat "$outfile")
130
+ }
131
+
132
+ # --- T1: updated - a behind home fast-forwards to the primary's local HEAD ---
133
+ test_ff_updated() {
134
+ local w c1 base
135
+ w=$(new_world ff-updated)
136
+ c1=$(head_of "$w/main")
137
+ git -C "$w/main" worktree add -q --detach "$w/sm" "$c1"
138
+ bump_primary "$w" instr
139
+ base=$(primary_head_commit "$w/main")
140
+
141
+ run_ff "$w/sm" "$base"
142
+
143
+ [ "$FF_STATUS" = updated ] || fail "FF_STATUS: expected updated, got '$FF_STATUS'"
144
+ assert_contains "$FF_OUT" "secondmate sm: updated " "updated home prints an advance line"
145
+ assert_contains "$FF_INSTR" "AGENTS.md" "instruction change is recorded in FF_INSTR"
146
+ [ "$(head_of "$w/sm")" = "$base" ] || fail "home did not advance to the primary's local HEAD"
147
+ git -C "$w/sm" symbolic-ref -q HEAD >/dev/null && fail "home is no longer detached"
148
+ # A fast-forwarded tip has exactly one parent; a merge would have two.
149
+ [ "$(git -C "$w/sm" rev-list --parents -n1 HEAD | wc -w | tr -d ' ')" -eq 2 ] \
150
+ || fail "home tip is not a single-parent fast-forward"
151
+ pass "T1 updated: a behind home fast-forwards to the primary's local HEAD"
152
+ }
153
+
154
+ # --- T2: current - already on the primary's HEAD is a no-op (no nudge) -------
155
+ test_ff_current() {
156
+ local w base
157
+ w=$(new_world ff-current)
158
+ bump_primary "$w" instr
159
+ base=$(primary_head_commit "$w/main")
160
+ git -C "$w/main" worktree add -q --detach "$w/sm" "$base"
161
+
162
+ run_ff "$w/sm" "$base"
163
+
164
+ [ "$FF_STATUS" = current ] || fail "FF_STATUS: expected current, got '$FF_STATUS'"
165
+ assert_contains "$FF_OUT" "secondmate sm: already current" "current home reports already current"
166
+ [ -z "$FF_INSTR" ] || fail "a no-op must not report instruction changes (would trigger a nudge)"
167
+ [ "$(head_of "$w/sm")" = "$base" ] || fail "current home HEAD moved"
168
+ pass "T2 current: an already-current home is a no-op and reports no instruction change"
169
+ }
170
+
171
+ # --- T3: dirty - a home with uncommitted edits is skipped, edit preserved ----
172
+ test_ff_dirty() {
173
+ local w c1 base before
174
+ w=$(new_world ff-dirty)
175
+ c1=$(head_of "$w/main")
176
+ git -C "$w/main" worktree add -q --detach "$w/sm" "$c1"
177
+ bump_primary "$w" instr
178
+ base=$(primary_head_commit "$w/main")
179
+ printf 'uncommitted local edit\n' >> "$w/sm/AGENTS.md"
180
+ before=$(head_of "$w/sm")
181
+
182
+ run_ff "$w/sm" "$base"
183
+
184
+ [ "$FF_STATUS" = skipped ] || fail "FF_STATUS: expected skipped, got '$FF_STATUS'"
185
+ assert_contains "$FF_OUT" "secondmate sm: skipped: dirty working tree" "dirty home is skipped"
186
+ [ "$(head_of "$w/sm")" = "$before" ] || fail "dirty home HEAD moved"
187
+ grep -q 'uncommitted local edit' "$w/sm/AGENTS.md" || fail "dirty edit was discarded"
188
+ pass "T3 dirty: an uncommitted home is skipped, its edit preserved"
189
+ }
190
+
191
+ # --- scratchpad2 must not trip the dirty-home sync guard ----------------------
192
+ test_scratchpad2_does_not_dirty_home() {
193
+ local w c1 base
194
+ w=$(new_world scratchpad2-clean)
195
+ c1=$(head_of "$w/main")
196
+ git -C "$w/main" worktree add -q --detach "$w/sm" "$c1"
197
+ mkdir -p "$w/sm/scratchpad2"
198
+ printf 'notes\n' > "$w/sm/scratchpad2/notes.txt"
199
+ bump_primary "$w" instr
200
+ base=$(primary_head_commit "$w/main")
201
+
202
+ run_ff "$w/sm" "$base"
203
+
204
+ [ "$FF_STATUS" = updated ] || fail "FF_STATUS: expected updated, got '$FF_STATUS' (scratchpad2/ must not dirty the home)"
205
+ [ "$(head_of "$w/sm")" = "$base" ] || fail "home did not fast-forward past an ignored scratchpad2/ directory"
206
+ grep -q notes "$w/sm/scratchpad2/notes.txt" || fail "scratchpad2 contents were discarded"
207
+ pass "scratchpad2/ does not mark a secondmate home dirty for the sync guard"
208
+ }
209
+
210
+ # --- T4: diverged - a home with its own commit is skipped, commit preserved --
211
+ test_ff_diverged() {
212
+ local w c1 base before
213
+ w=$(new_world ff-diverged)
214
+ c1=$(head_of "$w/main")
215
+ git -C "$w/main" worktree add -q --detach "$w/sm" "$c1"
216
+ printf 'fork work\n' > "$w/sm/AGENTS.md"
217
+ git -C "$w/sm" add -A
218
+ git -C "$w/sm" commit -qm local-work
219
+ before=$(head_of "$w/sm")
220
+ bump_primary "$w" instr
221
+ base=$(primary_head_commit "$w/main")
222
+
223
+ run_ff "$w/sm" "$base"
224
+
225
+ [ "$FF_STATUS" = skipped ] || fail "FF_STATUS: expected skipped, got '$FF_STATUS'"
226
+ assert_contains "$FF_OUT" "secondmate sm: skipped: diverged from $base" "diverged home is skipped"
227
+ [ "$(head_of "$w/sm")" = "$before" ] || fail "diverged home HEAD moved (unlanded work at risk)"
228
+ pass "T4 diverged: a home that is not an ancestor of the primary's HEAD is skipped"
229
+ }
230
+
231
+ # --- T5: in-flight - a home on a feature branch is skipped, work preserved ----
232
+ # A secondmate home carrying its own in-flight work sits on a named feature
233
+ # branch, not a detached default-branch HEAD; the ff helper refuses to move it.
234
+ test_ff_inflight_feature_branch() {
235
+ local w c1 base before
236
+ w=$(new_world ff-inflight)
237
+ c1=$(head_of "$w/main")
238
+ git -C "$w/main" worktree add -q -b feature/wip "$w/sm" "$c1"
239
+ printf 'work in progress\n' >> "$w/sm/README.md"
240
+ git -C "$w/sm" add -A
241
+ git -C "$w/sm" commit -qm wip
242
+ before=$(head_of "$w/sm")
243
+ bump_primary "$w" instr
244
+ base=$(primary_head_commit "$w/main")
245
+
246
+ run_ff "$w/sm" "$base"
247
+
248
+ [ "$FF_STATUS" = skipped ] || fail "FF_STATUS: expected skipped, got '$FF_STATUS'"
249
+ assert_contains "$FF_OUT" "secondmate sm: skipped: on feature/wip, expected main" \
250
+ "a home on a feature branch is skipped"
251
+ [ "$(head_of "$w/sm")" = "$before" ] || fail "in-flight home HEAD moved (work at risk)"
252
+ pass "T5 in-flight: a home on a feature branch is skipped, its work preserved"
253
+ }
254
+
255
+ # --- T6: no origin fetch happens in the local-HEAD sync path -----------------
256
+ # A bare `git fetch` would need the network; the sync must never reach for it.
257
+ # Shadow git with a wrapper that records any `fetch` invocation, then drive the
258
+ # updated path and confirm the wrapper saw none.
259
+ test_no_fetch_in_local_path() {
260
+ local w c1 base fakebin log real_git
261
+ w=$(new_world ff-nofetch)
262
+ c1=$(head_of "$w/main")
263
+ git -C "$w/main" worktree add -q --detach "$w/sm" "$c1"
264
+ bump_primary "$w" instr
265
+ base=$(primary_head_commit "$w/main")
266
+
267
+ fakebin="$w/fakebin"
268
+ log="$w/fetch.log"
269
+ real_git=$(command -v git)
270
+ mkdir -p "$fakebin"
271
+ cat > "$fakebin/git" <<SH
272
+ #!/usr/bin/env bash
273
+ for a in "\$@"; do
274
+ if [ "\$a" = fetch ]; then printf 'FETCH\n' >> '$log'; fi
275
+ done
276
+ exec '$real_git' "\$@"
277
+ SH
278
+ chmod +x "$fakebin/git"
279
+
280
+ PATH="$fakebin:$BASE_PATH" run_ff "$w/sm" "$base"
281
+
282
+ [ "$FF_STATUS" = updated ] || fail "FF_STATUS: expected updated, got '$FF_STATUS'"
283
+ [ ! -f "$log" ] || fail "git fetch was invoked in the local-HEAD sync path: $(cat "$log")"
284
+ pass "T6 no fetch: the local-HEAD sync never invokes git fetch"
285
+ }
286
+
287
+ # --- T7: sweep advances a readme-only home but does NOT nudge it -------------
288
+ test_sweep_nudge_requires_instruction_change() {
289
+ local w c1 base
290
+ w=$(new_world sweep-gate)
291
+ c1=$(head_of "$w/main")
292
+ add_sm_worktree "$w" sm-r "$c1"
293
+ bump_primary "$w" readme
294
+ base=$(primary_head_commit "$w/main")
295
+
296
+ FM_ROOT="$w/main" FM_HOME="$w/home"
297
+ FF_NUDGE_WINDOWS=""
298
+ FF_SEEN_HOMES=""
299
+ sweep_live_secondmate_metas "$w/home/state" "$base" yes >/dev/null
300
+
301
+ [ -z "$FF_NUDGE_WINDOWS" ] \
302
+ || fail "readme-only advance must not nudge, got: '$FF_NUDGE_WINDOWS'"
303
+ [ "$(head_of "$w/sm-r")" = "$base" ] \
304
+ || fail "home should still fast-forward even when it is not nudged"
305
+ pass "T7 sweep nudges on a real instruction change only, but still fast-forwards"
306
+ }
307
+
308
+ # --- T8: bootstrap sweeps live homes, nudges only the real instruction change -
309
+ make_fake_toolchain() {
310
+ local dir=$1 fakebin
311
+ fakebin="$dir/fakebin"
312
+ mkdir -p "$fakebin"
313
+ fm_fake_exit0 "$fakebin" node chrome-devtools-axi
314
+ fm_fake_version_tool "$fakebin" lavish-axi FM_FAKE_LAVISH_AXI_VERSION 0.1.46
315
+ cat > "$fakebin/gh-axi" <<'SH'
316
+ #!/usr/bin/env bash
317
+ if [ "${1:-}" = --version ]; then
318
+ printf '%s\n' '0.1.29'
319
+ exit 0
320
+ fi
321
+ exit 0
322
+ SH
323
+ chmod +x "$fakebin/gh-axi"
324
+ cat > "$fakebin/tmux" <<'SH'
325
+ #!/usr/bin/env bash
326
+ if [ -n "${FM_FAKE_TMUX_LOG:-}" ]; then
327
+ printf '%s\n' "$*" >> "$FM_FAKE_TMUX_LOG"
328
+ fi
329
+ case "$*" in
330
+ list-windows*)
331
+ sed -n 's/^window=[^:]*://p' "${FM_HOME:?}"/state/*.meta
332
+ exit 0
333
+ ;;
334
+ *display-message*'#{pane_current_command}'*) printf '%s\n' codex; exit 0 ;;
335
+ *display-message*'#{pane_id}'*) printf '%s\n' '%1'; exit 0 ;;
336
+ *display-message*'#{cursor_y}'*) printf '%s\n' 0; exit 0 ;;
337
+ *capture-pane*) printf '❯\n'; exit 0 ;;
338
+ *'send-keys'*' -l '*)
339
+ [ "${FM_FAKE_TMUX_FAIL_LITERAL:-0}" = 1 ] && exit 1
340
+ exit 0
341
+ ;;
342
+ esac
343
+ exit 0
344
+ SH
345
+ chmod +x "$fakebin/tmux"
346
+ cat > "$fakebin/gh" <<'SH'
347
+ #!/usr/bin/env bash
348
+ exit 0
349
+ SH
350
+ chmod +x "$fakebin/gh"
351
+ cat > "$fakebin/treehouse" <<'SH'
352
+ #!/usr/bin/env bash
353
+ if [ "${1:-}" = get ] && [ "${2:-}" = --help ]; then
354
+ printf '%s\n' 'Usage: treehouse get [--lease]'
355
+ fi
356
+ exit 0
357
+ SH
358
+ chmod +x "$fakebin/treehouse"
359
+ cat > "$fakebin/no-mistakes" <<'SH'
360
+ #!/usr/bin/env bash
361
+ if [ "${1:-}" = --version ]; then
362
+ printf '%s\n' 'no-mistakes version v1.46.0 (fake)'
363
+ exit 0
364
+ fi
365
+ exit 0
366
+ SH
367
+ chmod +x "$fakebin/no-mistakes"
368
+ cat > "$fakebin/tasks-axi" <<'SH'
369
+ #!/usr/bin/env bash
370
+ case "${1:-} ${2:-}" in
371
+ "--version ") printf '%s\n' '0.2.4' ;;
372
+ "update --help") printf '%s\n' 'usage: tasks-axi update <id> [flags]' ' --archive-body' ;;
373
+ "mv --help") printf '%s\n' 'usage: tasks-axi mv <id> [<id>...] --to <path-or-dir>' ;;
374
+ esac
375
+ exit 0
376
+ SH
377
+ chmod +x "$fakebin/tasks-axi"
378
+ cat > "$fakebin/quota-axi" <<'SH'
379
+ #!/usr/bin/env bash
380
+ if [ "${1:-}" = --version ]; then
381
+ printf '%s\n' 'quota-axi 0.1.29 (fake)'
382
+ fi
383
+ exit 0
384
+ SH
385
+ chmod +x "$fakebin/quota-axi"
386
+ printf '%s\n' "$fakebin"
387
+ }
388
+
389
+ add_real_jq() {
390
+ local fakebin=$1 real_jq
391
+ real_jq=$(command -v jq 2>/dev/null) || return 1
392
+ cat > "$fakebin/jq" <<SH
393
+ #!/usr/bin/env bash
394
+ exec '$real_jq' "\$@"
395
+ SH
396
+ chmod +x "$fakebin/jq"
397
+ }
398
+
399
+ test_bootstrap_sweep_nudges_only_instruction_change() {
400
+ local w c1 c2 c3 fakebin out info_line log marker_dir
401
+ w=$(new_world boot-sweep)
402
+ c1=$(head_of "$w/main")
403
+ add_sm_worktree "$w" sm-instr "$c1" # behind by an instruction change
404
+ bump_primary "$w" instr
405
+ c2=$(head_of "$w/main")
406
+ add_sm_worktree "$w" sm-readme "$c2" # behind by a readme-only change
407
+ bump_primary "$w" readme
408
+ c3=$(head_of "$w/main")
409
+ add_sm_worktree "$w" sm-current "$c3" # already on the primary's HEAD
410
+ # A home with NO live meta must never be swept (live = a running direct report).
411
+ git -C "$w/main" worktree add -q --detach "$w/sm-nonlive" "$c1"
412
+ printf 'sm-nonlive\n' > "$w/sm-nonlive/.fm-secondmate-home"
413
+
414
+ fakebin=$(make_fake_toolchain "$w")
415
+ log="$w/tmux.log"
416
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$w/home" FM_ROOT_OVERRIDE="$w/main" \
417
+ FM_SEND_SETTLE=0 FM_FAKE_TMUX_LOG="$log" \
418
+ "$ROOT/bin/fm-bootstrap.sh" 2>/dev/null)
419
+
420
+ info_line=$(printf '%s\n' "$out" | grep '^BOOTSTRAP_INFO: nudged fm-sm-instr ' || true)
421
+ [ -n "$info_line" ] || fail "no BOOTSTRAP_INFO nudge line emitted (got: $out)"
422
+ assert_contains "$info_line" "firstmate was updated to the latest - please re-read your AGENTS.md to pick up the new instructions." \
423
+ "successful nudge report should include the exact message sent"
424
+ assert_not_contains "$out" "NUDGE_SECONDMATES:" "successful nudge must not leave a firstmate action item"
425
+ assert_not_contains "$out" "sm-readme" "readme-only advance is not nudged"
426
+ assert_not_contains "$out" "sm-current" "already-current secondmate is not nudged"
427
+ # The nudge rides fm-send's durable inbox plane: the marked message lands in
428
+ # the secondmate task's steering-inbox record while only the doorbell is typed.
429
+ assert_contains "$(cat "$w/home/state/sm-instr.inbox/001.msg")" "[fm-from-firstmate]" \
430
+ "nudge send should use the marked fm-send secondmate path"
431
+ assert_contains "$(cat "$w/home/state/sm-instr.inbox/001.msg")" "firstmate was updated to the latest - please re-read your AGENTS.md" \
432
+ "nudge send should enqueue the exact re-read message"
433
+ assert_contains "$(cat "$log")" "Firstmate instruction waiting" \
434
+ "nudge send should ring the doorbell at the secondmate pane"
435
+ marker_dir="$w/home/state/.secondmate-nudge-pending"
436
+ [ ! -e "$marker_dir/sm-instr.pending" ] || fail "successful nudge should clear its retry marker"
437
+
438
+ # Every live home advanced to the primary's HEAD; the already-current one stayed.
439
+ [ "$(head_of "$w/sm-instr")" = "$c3" ] || fail "sm-instr not at primary HEAD"
440
+ [ "$(head_of "$w/sm-readme")" = "$c3" ] || fail "sm-readme not at primary HEAD"
441
+ [ "$(head_of "$w/sm-current")" = "$c3" ] || fail "sm-current moved off primary HEAD"
442
+ # The non-live home is never touched by the bootstrap sweep.
443
+ [ "$(head_of "$w/sm-nonlive")" = "$c1" ] || fail "a home with no live meta was swept"
444
+ pass "T8 bootstrap sweeps live homes and sends exactly one marked nudge for the instruction change"
445
+ }
446
+
447
+ test_bootstrap_nudge_send_uses_state_override() {
448
+ local w c1 fakebin out log override_state marker
449
+ w=$(new_world nudge-state-override)
450
+ c1=$(head_of "$w/main")
451
+ add_sm_worktree "$w" sm-instr "$c1"
452
+ bump_primary "$w" instr
453
+ override_state="$w/override-state"
454
+ mkdir -p "$override_state"
455
+ mv "$w/home/state/sm-instr.meta" "$override_state/sm-instr.meta"
456
+ touch "$override_state/.last-watcher-beat"
457
+ fakebin=$(make_fake_toolchain "$w")
458
+ log="$w/tmux.log"
459
+
460
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$w/home" FM_ROOT_OVERRIDE="$w/main" \
461
+ FM_STATE_OVERRIDE="$override_state" FM_SEND_SETTLE=0 FM_FAKE_TMUX_LOG="$log" \
462
+ "$ROOT/bin/fm-bootstrap.sh" 2>/dev/null)
463
+
464
+ assert_contains "$out" "BOOTSTRAP_INFO: nudged fm-sm-instr with" \
465
+ "nudge send should resolve fm-sm-instr through the effective state dir"
466
+ assert_not_contains "$out" "NUDGE_SECONDMATES:" \
467
+ "effective-state nudge should not fail through FM_HOME/state"
468
+ assert_contains "$(cat "$override_state/sm-instr.inbox/001.msg")" "[fm-from-firstmate]" \
469
+ "effective-state nudge should still use secondmate marker metadata"
470
+ marker="$override_state/.secondmate-nudge-pending/sm-instr.pending"
471
+ assert_absent "$marker" "successful effective-state nudge should clear its retry marker"
472
+ pass "T8a bootstrap nudge send respects FM_STATE_OVERRIDE"
473
+ }
474
+
475
+ test_bootstrap_nudge_retry_rejects_malformed_marker_id() {
476
+ local w c1 fakebin out marker log evil
477
+ w=$(new_world nudge-malformed-id)
478
+ c1=$(head_of "$w/main")
479
+ evil="$w/evil"
480
+ git -C "$w/main" worktree add -q --detach "$evil" "$c1"
481
+ printf '../escape\n' > "$evil/.fm-secondmate-home"
482
+ mkdir -p "$w/home/state/.secondmate-nudge-pending"
483
+ marker="$w/home/state/.secondmate-nudge-pending/bad.pending"
484
+ {
485
+ printf 'id=../escape\n'
486
+ printf 'selector=fm-../escape\n'
487
+ printf 'home=%s\n' "$evil"
488
+ printf 'commit=%s\n' "$c1"
489
+ printf 'instructions=AGENTS.md\n'
490
+ printf 'message=firstmate was updated to the latest - please re-read your AGENTS.md to pick up the new instructions.\n'
491
+ } > "$marker"
492
+ {
493
+ printf 'window=firstmate:fm-evil\n'
494
+ printf 'kind=secondmate\n'
495
+ printf 'home=%s\n' "$evil"
496
+ } > "$w/home/escape.meta"
497
+ fakebin=$(make_fake_toolchain "$w")
498
+ log="$w/tmux.log"
499
+
500
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$w/home" FM_ROOT_OVERRIDE="$w/main" \
501
+ FM_SEND_SETTLE=0 FM_FAKE_TMUX_LOG="$log" \
502
+ "$ROOT/bin/fm-bootstrap.sh" 2>/dev/null)
503
+
504
+ assert_contains "$out" "NUDGE_SECONDMATES: secondmate ../escape: send failed: retry marker has unsafe id" \
505
+ "malformed retry marker id should be rejected before target resolution"
506
+ assert_not_contains "$out" "BOOTSTRAP_INFO: nudged fm-../escape" \
507
+ "malformed retry marker id must never send through a path-traversed selector"
508
+ assert_present "$marker" "malformed retry marker should remain for operator inspection"
509
+ assert_absent "$log" "malformed retry marker should not invoke fm-send"
510
+ pass "T8f bootstrap nudge retry rejects malformed marker ids"
511
+ }
512
+
513
+ test_bootstrap_nudge_failure_records_retry_marker() {
514
+ local w c1 fakebin out marker
515
+ w=$(new_world nudge-failure)
516
+ c1=$(head_of "$w/main")
517
+ add_sm_worktree "$w" sm-instr "$c1"
518
+ bump_primary "$w" instr
519
+ fakebin=$(make_fake_toolchain "$w")
520
+ # A real local send failure on the inbox plane is an unwritable steer record
521
+ # (a keystroke failure alone no longer fails a durably enqueued nudge).
522
+ : > "$w/home/state/sm-instr.inbox"
523
+
524
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$w/home" FM_ROOT_OVERRIDE="$w/main" \
525
+ FM_SEND_SETTLE=0 \
526
+ "$ROOT/bin/fm-bootstrap.sh" 2>/dev/null)
527
+
528
+ assert_contains "$out" "NUDGE_SECONDMATES: secondmate sm-instr: send failed:" \
529
+ "failed nudge send should be surfaced as actionable bootstrap output"
530
+ marker="$w/home/state/.secondmate-nudge-pending/sm-instr.pending"
531
+ assert_present "$marker" "failed nudge should leave a retry marker"
532
+ assert_grep "selector=fm-sm-instr" "$marker" "retry marker should pin the stable selector"
533
+ assert_grep "message=firstmate was updated to the latest - please re-read your AGENTS.md to pick up the new instructions." \
534
+ "$marker" "retry marker should pin the exact message"
535
+ pass "T8c failed bootstrap nudge is surfaced and recorded for retry"
536
+ }
537
+
538
+ test_bootstrap_nudge_retry_is_idempotent() {
539
+ local w c1 fakebin out marker out2
540
+ w=$(new_world nudge-retry)
541
+ c1=$(head_of "$w/main")
542
+ add_sm_worktree "$w" sm-instr "$c1"
543
+ bump_primary "$w" instr
544
+ fakebin=$(make_fake_toolchain "$w")
545
+ : > "$w/home/state/sm-instr.inbox" # block the steer record: a real local failure
546
+
547
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$w/home" FM_ROOT_OVERRIDE="$w/main" \
548
+ FM_SEND_SETTLE=0 \
549
+ "$ROOT/bin/fm-bootstrap.sh" 2>/dev/null)
550
+ assert_contains "$out" "NUDGE_SECONDMATES: secondmate sm-instr: send failed:" \
551
+ "precondition: first nudge should fail"
552
+ marker="$w/home/state/.secondmate-nudge-pending/sm-instr.pending"
553
+ assert_present "$marker" "precondition: failed nudge should leave marker"
554
+
555
+ rm -f "$w/home/state/sm-instr.inbox" # unblock: the steer record can be written again
556
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$w/home" FM_ROOT_OVERRIDE="$w/main" \
557
+ FM_SEND_SETTLE=0 "$ROOT/bin/fm-bootstrap.sh" 2>/dev/null)
558
+ assert_contains "$out" "BOOTSTRAP_INFO: nudged fm-sm-instr with" \
559
+ "retry should send the pending nudge once the endpoint works"
560
+ assert_absent "$marker" "successful retry should clear the marker"
561
+
562
+ out2=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$w/home" FM_ROOT_OVERRIDE="$w/main" \
563
+ FM_SEND_SETTLE=0 "$ROOT/bin/fm-bootstrap.sh" 2>/dev/null)
564
+ [ -z "$out2" ] || fail "idempotent retry should converge to silence, got: $out2"
565
+ pass "T8d bootstrap nudge retry is idempotent after success"
566
+ }
567
+
568
+ test_bootstrap_nudge_retry_refuses_changed_home() {
569
+ local w c1 fakebin marker out other
570
+ w=$(new_world nudge-retry-home-change)
571
+ c1=$(head_of "$w/main")
572
+ add_sm_worktree "$w" sm-instr "$c1"
573
+ bump_primary "$w" instr
574
+ fakebin=$(make_fake_toolchain "$w")
575
+ : > "$w/home/state/sm-instr.inbox" # block the steer record: a real local failure
576
+
577
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$w/home" FM_ROOT_OVERRIDE="$w/main" \
578
+ FM_SEND_SETTLE=0 \
579
+ "$ROOT/bin/fm-bootstrap.sh" 2>/dev/null)
580
+ assert_contains "$out" "NUDGE_SECONDMATES: secondmate sm-instr: send failed:" \
581
+ "precondition: first nudge should fail"
582
+ marker="$w/home/state/.secondmate-nudge-pending/sm-instr.pending"
583
+ assert_present "$marker" "precondition: failed nudge should leave marker"
584
+
585
+ other="$w/sm-other"
586
+ git -C "$w/main" worktree add -q --detach "$other" "$(head_of "$w/sm-instr")"
587
+ printf '%s\n' sm-instr > "$other/.fm-secondmate-home"
588
+ sed -i.bak "s|^home=.*|home=$other|" "$w/home/state/sm-instr.meta" 2>/dev/null || \
589
+ sed -i "s|^home=.*|home=$other|" "$w/home/state/sm-instr.meta"
590
+ rm -f "$w/home/state/sm-instr.meta.bak"
591
+
592
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$w/home" FM_ROOT_OVERRIDE="$w/main" \
593
+ FM_SEND_SETTLE=0 "$ROOT/bin/fm-bootstrap.sh" 2>/dev/null)
594
+ assert_contains "$out" "NUDGE_SECONDMATES: secondmate sm-instr: send failed: retry target home changed" \
595
+ "retry must not infer a nudge target outside the recorded failed home"
596
+ assert_present "$marker" "ambiguous retry should keep marker for operator inspection"
597
+ pass "T8e bootstrap nudge retry refuses a changed home instead of guessing"
598
+ }
599
+
600
+ # --- T8b: stale herdr nudge failures retry through current fm-<id> metadata ---
601
+ # Reproduces the 2026-07-07 session-start bug: secondmate_sync used to print raw
602
+ # backend targets (default:w9:pY) that liveness respawn immediately replaced
603
+ # (default:wA:p2), so fm-send with the printed target fell back to tmux and failed
604
+ # while fm-<id> resolved through current meta.
605
+ make_nudge_herdr_fake() {
606
+ local dir=$1 stale=$2 fresh=$3 fakebin
607
+ fakebin=$(fm_fakebin "$dir")
608
+ cat > "$fakebin/herdr" <<SH
609
+ #!/usr/bin/env bash
610
+ set -u
611
+ cmd=\${1:-}; sub=\${2:-}; arg=\${3:-}
612
+ case "\$cmd \$sub" in
613
+ "status --json")
614
+ printf '{"client":{"version":"0.7.1","protocol":14},"server":{"running":true}}\n'
615
+ ;;
616
+ "pane get")
617
+ if [ "\$arg" = "${stale#*:}" ]; then
618
+ printf '{"result":{"pane":{"pane_id":"${stale#*:}"}}}\n'
619
+ elif [ "\$arg" = "${fresh#*:}" ]; then
620
+ printf '{"result":{"pane":{"pane_id":"${fresh#*:}"}}}\n'
621
+ else
622
+ printf '{"error":{"code":"pane_not_found","message":"missing"}}\n' >&2
623
+ exit 0
624
+ fi
625
+ ;;
626
+ "agent get")
627
+ if [ "\$arg" = "${stale#*:}" ]; then
628
+ printf '{"error":{"code":"agent_not_found","message":"gone"}}\n' >&2
629
+ elif [ "\$arg" = "${fresh#*:}" ]; then
630
+ printf '{"result":{"agent":{"agent_status":"idle"}}}\n'
631
+ else
632
+ printf '{"error":{"code":"agent_not_found","message":"gone"}}\n' >&2
633
+ fi
634
+ ;;
635
+ "pane send-text"|"pane run"|"pane send-keys")
636
+ if [ "\$arg" = "${stale#*:}" ]; then
637
+ exit 1
638
+ fi
639
+ exit 0
640
+ ;;
641
+ esac
642
+ exit 0
643
+ SH
644
+ chmod +x "$fakebin/herdr"
645
+ printf '%s\n' "$fakebin"
646
+ }
647
+
648
+ test_nudge_retry_uses_fresh_herdr_endpoint_after_respawn() {
649
+ local w c1 stale fresh fakebin herdrfb toolchain out meta window resolved stale_send fresh_send spawn_stub marker
650
+ stale=default:w9:pY
651
+ fresh=default:wA:p2
652
+ w=$(new_world nudge-herdr-rotate)
653
+ c1=$(head_of "$w/main")
654
+ add_sm_worktree "$w" sm-instr "$c1"
655
+ bump_primary "$w" instr
656
+
657
+ meta="$w/home/state/sm-instr.meta"
658
+ {
659
+ printf 'window=%s\n' "$stale"
660
+ printf 'backend=herdr\n'
661
+ printf 'kind=secondmate\n'
662
+ printf 'harness=claude\n'
663
+ printf 'home=%s/sm-instr\n' "$w"
664
+ } > "$meta"
665
+
666
+ spawn_stub="$w/spawn-stub.sh"
667
+ cat > "$spawn_stub" <<SH
668
+ #!/usr/bin/env bash
669
+ set -u
670
+ id=\${1:-}
671
+ meta="\$FM_HOME/state/\$id.meta"
672
+ [ -f "\$meta" ] || exit 1
673
+ sed -i.bak "s/^window=.*/window=$fresh/" "\$meta" 2>/dev/null || \
674
+ sed -i "s/^window=.*/window=$fresh/" "\$meta"
675
+ rm -f "\$meta.bak"
676
+ exit 0
677
+ SH
678
+ chmod +x "$spawn_stub"
679
+ cp "$spawn_stub" "$w/main/bin/fm-spawn.sh"
680
+
681
+ herdrfb=$(make_nudge_herdr_fake "$w/herdr" "$stale" "$fresh")
682
+ toolchain=$(make_fake_toolchain "$w")
683
+ if ! add_real_jq "$toolchain"; then
684
+ pass "T8b nudge selector herdr respawn skipped without jq"
685
+ return
686
+ fi
687
+ out=$(PATH="$herdrfb:$toolchain:$BASE_PATH" HERDR_ENV=1 FM_BACKEND=herdr \
688
+ FM_SEND_SETTLE=0 \
689
+ FM_HOME="$w/home" FM_ROOT_OVERRIDE="$w/main" \
690
+ "$ROOT/bin/fm-bootstrap.sh" 2>/dev/null)
691
+
692
+ # The nudge now rides the durable inbox: a stale endpoint can only swallow
693
+ # the best-effort doorbell, never the steer itself, so the nudge is SENT
694
+ # (recorded) rather than failed, no retry marker is owed, and the watcher's
695
+ # re-ring ladder owns delivery against the respawned fresh endpoint.
696
+ assert_contains "$out" "BOOTSTRAP_INFO: nudged fm-sm-instr" \
697
+ "a stale herdr endpoint must not fail a durably enqueued nudge"
698
+ assert_contains "$(cat "$w/home/state/sm-instr.inbox/001.msg")" \
699
+ "please re-read your AGENTS.md" \
700
+ "the nudge should be durably recorded despite the stale endpoint"
701
+
702
+ window=$(grep '^window=' "$meta" | tail -1 | cut -d= -f2-)
703
+ [ "$window" = "$fresh" ] || fail "respawn stub did not rotate meta window to '$fresh' (got '$window')"
704
+ marker="$w/home/state/.secondmate-nudge-pending/sm-instr.pending"
705
+ assert_absent "$marker" "a durably enqueued nudge owes no retry marker"
706
+
707
+ # shellcheck disable=SC2016 # $0/$1 belong to the inner bash -c process.
708
+ resolved=$(bash -c '. "$0/bin/fm-backend.sh"; fm_backend_resolve_selector fm-sm-instr "$1"' "$ROOT" "$w/home/state")
709
+ [ "$resolved" = "$fresh" ] || fail "fm-<id> should resolve through post-respawn meta, got '$resolved'"
710
+
711
+ # shellcheck disable=SC2016 # $0/$1 belong to the inner bash -c process.
712
+ stale_send=$(PATH="$herdrfb:$toolchain:$BASE_PATH" bash -c \
713
+ '. "$0/bin/fm-backend.sh"; fm_backend_source herdr; fm_backend_herdr_send_literal "$1" "nudge"' "$ROOT" "$stale" 2>/dev/null; printf '%s' "$?")
714
+ [ "$stale_send" != 0 ] || fail "explicit stale herdr endpoint send should fail"
715
+
716
+ # shellcheck disable=SC2016 # $0/$1 belong to the inner bash -c process.
717
+ fresh_send=$(PATH="$herdrfb:$toolchain:$BASE_PATH" bash -c \
718
+ '. "$0/bin/fm-backend.sh"; fm_backend_source herdr; fm_backend_herdr_send_literal "$1" "nudge"' "$ROOT" "$fresh" 2>/dev/null; printf '%s' "$?")
719
+ [ "$fresh_send" = 0 ] || fail "send through fm-<id>-resolved fresh endpoint should succeed"
720
+
721
+ pass "T8b a stale herdr endpoint cannot lose a durably enqueued nudge, and fm-<id> resolves through post-respawn metadata"
722
+ }
723
+
724
+ # --- T9: bootstrap surfaces a skipped dirty live secondmate home --------------
725
+ test_bootstrap_sweep_surfaces_skipped_home() {
726
+ local w c1 base before fakebin out skip_line
727
+ w=$(new_world boot-skip)
728
+ c1=$(head_of "$w/main")
729
+ add_sm_worktree "$w" sm-dirty "$c1"
730
+ bump_primary "$w" instr
731
+ base=$(primary_head_commit "$w/main")
732
+ printf 'uncommitted local edit\n' >> "$w/sm-dirty/AGENTS.md"
733
+ before=$(head_of "$w/sm-dirty")
734
+
735
+ fakebin=$(make_fake_toolchain "$w")
736
+ out=$(PATH="$fakebin:$BASE_PATH" FM_HOME="$w/home" FM_ROOT_OVERRIDE="$w/main" \
737
+ "$ROOT/bin/fm-bootstrap.sh" 2>/dev/null)
738
+
739
+ skip_line=$(printf '%s\n' "$out" | grep '^SECONDMATE_SYNC: secondmate sm-dirty: skipped:' || true)
740
+ [ -n "$skip_line" ] || fail "no SECONDMATE_SYNC skip line emitted (got: $out)"
741
+ assert_contains "$skip_line" "dirty working tree" "dirty skipped home reports the actionable reason"
742
+ [ "$(head_of "$w/sm-dirty")" = "$before" ] || fail "dirty home HEAD moved"
743
+ [ "$(head_of "$w/main")" = "$base" ] || fail "primary HEAD changed during bootstrap"
744
+ grep -q 'uncommitted local edit' "$w/sm-dirty/AGENTS.md" || fail "dirty edit was discarded"
745
+ pass "T9 bootstrap surfaces a skipped dirty live secondmate home"
746
+ }
747
+
748
+ # --- T10: spawning a secondmate fast-forwards its worktree before launch ------
749
+ test_spawn_fast_forwards_before_launch() {
750
+ local w c1 c2 fakebin
751
+ w=$(new_world spawn-ff)
752
+ c1=$(head_of "$w/main")
753
+ git -C "$w/main" worktree add -q --detach "$w/sm" "$c1"
754
+ printf 'sm\n' > "$w/sm/.fm-secondmate-home"
755
+ mkdir -p "$w/sm/data"
756
+ printf 'charter\n' > "$w/sm/data/charter.md"
757
+ bump_primary "$w" instr
758
+ c2=$(head_of "$w/main")
759
+ [ "$(head_of "$w/sm")" = "$c1" ] || fail "precondition: home should start behind the primary"
760
+
761
+ # tmux stub: accept every subcommand, print nothing (so no window pre-exists).
762
+ fakebin="$w/fakebin"
763
+ mkdir -p "$fakebin"
764
+ cat > "$fakebin/tmux" <<'SH'
765
+ #!/usr/bin/env bash
766
+ exit 0
767
+ SH
768
+ chmod +x "$fakebin/tmux"
769
+
770
+ PATH="$fakebin:$BASE_PATH" TMUX='' \
771
+ FM_ROOT_OVERRIDE="$w/main" FM_HOME="$w/home" \
772
+ FM_STATE_OVERRIDE="$w/home/state" FM_DATA_OVERRIDE="$w/home/data" \
773
+ FM_PROJECTS_OVERRIDE="$w/home/projects" FM_CONFIG_OVERRIDE="$w/home/config" \
774
+ FM_SPAWN_NO_GUARD=1 \
775
+ "$ROOT/bin/fm-spawn.sh" sm "$w/sm" codex --secondmate >/dev/null 2>&1 || true
776
+
777
+ [ "$(head_of "$w/sm")" = "$c2" ] \
778
+ || fail "spawn did not fast-forward the secondmate worktree to the primary's HEAD"
779
+ pass "T10 spawn fast-forwards a secondmate worktree to the primary's local HEAD before launch"
780
+ }
781
+
782
+ # --- T11: spawn warns when pre-launch sync is skipped ------------------------
783
+ test_spawn_warns_when_sync_skipped_before_launch() {
784
+ local w c1 before fakebin err
785
+ w=$(new_world spawn-skip)
786
+ c1=$(head_of "$w/main")
787
+ git -C "$w/main" worktree add -q --detach "$w/sm" "$c1"
788
+ printf 'sm\n' > "$w/sm/.fm-secondmate-home"
789
+ mkdir -p "$w/sm/data"
790
+ printf 'charter\n' > "$w/sm/data/charter.md"
791
+ bump_primary "$w" instr
792
+ printf 'uncommitted local edit\n' >> "$w/sm/AGENTS.md"
793
+ before=$(head_of "$w/sm")
794
+
795
+ fakebin="$w/fakebin"
796
+ err="$w/spawn.err"
797
+ mkdir -p "$fakebin"
798
+ cat > "$fakebin/tmux" <<'SH'
799
+ #!/usr/bin/env bash
800
+ exit 0
801
+ SH
802
+ chmod +x "$fakebin/tmux"
803
+
804
+ PATH="$fakebin:$BASE_PATH" TMUX='' \
805
+ FM_ROOT_OVERRIDE="$w/main" FM_HOME="$w/home" \
806
+ FM_STATE_OVERRIDE="$w/home/state" FM_DATA_OVERRIDE="$w/home/data" \
807
+ FM_PROJECTS_OVERRIDE="$w/home/projects" FM_CONFIG_OVERRIDE="$w/home/config" \
808
+ FM_SPAWN_NO_GUARD=1 \
809
+ "$ROOT/bin/fm-spawn.sh" sm "$w/sm" codex --secondmate >/dev/null 2>"$err" || true
810
+
811
+ assert_contains "$(cat "$err")" \
812
+ "warning: secondmate sm sync skipped before launch: dirty working tree" \
813
+ "spawn warning reports the skipped sync reason"
814
+ [ "$(head_of "$w/sm")" = "$before" ] || fail "dirty spawn home HEAD moved"
815
+ grep -q 'uncommitted local edit' "$w/sm/AGENTS.md" || fail "dirty spawn edit was discarded"
816
+ pass "T11 spawn warns when pre-launch sync is skipped"
817
+ }
818
+
819
+ # --- T12: a freshly seeded home reads clean once the primary ignores the marker -
820
+ # The seed marker used to leave every home permanently dirty: bin/fm-fleet-sync.sh
821
+ # and any other plain `git status --porcelain` check counts the untracked marker,
822
+ # so a seeded home reported STUCK/dirty forever. With the marker in .gitignore, a
823
+ # home seeded from a primary that carries the fix reads clean to that exact signal.
824
+ test_seed_marker_clean_when_gitignored() {
825
+ local w base
826
+ w=$(new_world marker-clean)
827
+ ignore_marker_commit "$w" # primary now ignores the marker
828
+ base=$(primary_head_commit "$w/main")
829
+ seed_marked_home "$w" sm "$base" # fresh home at the post-fix HEAD
830
+
831
+ # The exact dirtiness signal bin/fm-fleet-sync.sh reads (its line: dirty=yes when
832
+ # `git status --porcelain | head -1` is non-empty).
833
+ [ -z "$(git -C "$w/sm" status --porcelain)" ] \
834
+ || fail "seed marker still dirties a fresh home: $(git -C "$w/sm" status --porcelain)"
835
+ # And the secondmate ff sweep sees no dirt: an at-HEAD home is a clean no-op.
836
+ run_ff "$w/sm" "$base"
837
+ [ "$FF_STATUS" = current ] || fail "fresh home not read as clean/current, got '$FF_STATUS': $FF_OUT"
838
+ pass "T12 gitignored marker: a freshly seeded home reads clean to fleet-sync and the ff sweep"
839
+ }
840
+
841
+ # --- T13: an existing marker-only-dirty home converges on the next sweep --------
842
+ # The convergence chicken-and-egg: existing homes predate the fix, so their marker
843
+ # is still untracked-and-unignored, and the fix itself only arrives by fast-forward.
844
+ # The marker-tolerant ff-skip (ignore_seed_marker=yes) bridges the gap for
845
+ # linked-worktree homes, which bootstrap/spawn fast-forward from the primary's local HEAD.
846
+ # Standalone-clone homes converge through /updatefirstmate's origin fetch instead.
847
+ # Once advanced, the now-ignored marker reads clean with no hand intervention.
848
+ test_seed_marker_converges_existing_home() {
849
+ local w c0 base
850
+ w=$(new_world marker-converge) # primary does NOT ignore the marker yet
851
+ c0=$(head_of "$w/main")
852
+ seed_marked_home "$w" sm "$c0" # existing home predates the fix
853
+ [ -n "$(git -C "$w/sm" status --porcelain)" ] \
854
+ || fail "precondition: the untracked marker should dirty a pre-fix home"
855
+ ignore_marker_commit "$w" # THE FIX lands as a later commit
856
+ base=$(primary_head_commit "$w/main")
857
+
858
+ run_ff "$w/sm" "$base" # the marker-tolerant convergence sweep
859
+
860
+ [ "$FF_STATUS" = updated ] || fail "existing marker-only home did not converge, got '$FF_STATUS': $FF_OUT"
861
+ [ "$(head_of "$w/sm")" = "$base" ] || fail "home did not fast-forward to the fix commit"
862
+ [ -z "$(git -C "$w/sm" status --porcelain)" ] \
863
+ || fail "marker still dirty after convergence: $(git -C "$w/sm" status --porcelain)"
864
+ pass "T13 gitignored marker: an existing marker-only-dirty home converges, then reads clean"
865
+ }
866
+
867
+ # --- T14: marker tolerance does not mask a genuinely dirty home -----------------
868
+ # The ff-skip only forgives the seed marker; a real uncommitted change alongside the
869
+ # marker must still refuse the fast-forward and leave the work untouched, exactly as
870
+ # before this fix.
871
+ test_seed_marker_does_not_mask_real_dirt() {
872
+ local w c0 base before
873
+ w=$(new_world marker-real-dirt)
874
+ c0=$(head_of "$w/main")
875
+ seed_marked_home "$w" sm "$c0"
876
+ printf 'real local change\n' >> "$w/sm/AGENTS.md" # genuine tracked-file edit + the marker
877
+ before=$(head_of "$w/sm")
878
+ ignore_marker_commit "$w"
879
+ base=$(primary_head_commit "$w/main")
880
+
881
+ run_ff "$w/sm" "$base"
882
+
883
+ [ "$FF_STATUS" = skipped ] || fail "a genuinely dirty home must skip, got '$FF_STATUS'"
884
+ assert_contains "$FF_OUT" "secondmate sm: skipped: dirty working tree" \
885
+ "a genuinely dirty home is skipped even with the marker present"
886
+ [ "$(head_of "$w/sm")" = "$before" ] || fail "genuinely dirty home HEAD moved (work at risk)"
887
+ grep -q 'real local change' "$w/sm/AGENTS.md" || fail "genuine local edit was discarded"
888
+ pass "T14 marker tolerance does not mask a genuinely dirty home"
889
+ }
890
+
891
+ test_ff_updated
892
+ test_ff_current
893
+ test_ff_dirty
894
+ test_scratchpad2_does_not_dirty_home
895
+ test_ff_diverged
896
+ test_ff_inflight_feature_branch
897
+ test_no_fetch_in_local_path
898
+ test_sweep_nudge_requires_instruction_change
899
+ test_bootstrap_sweep_nudges_only_instruction_change
900
+ test_bootstrap_nudge_send_uses_state_override
901
+ test_bootstrap_nudge_retry_rejects_malformed_marker_id
902
+ test_bootstrap_nudge_failure_records_retry_marker
903
+ test_bootstrap_nudge_retry_is_idempotent
904
+ test_bootstrap_nudge_retry_refuses_changed_home
905
+ test_nudge_retry_uses_fresh_herdr_endpoint_after_respawn
906
+ test_bootstrap_sweep_surfaces_skipped_home
907
+ test_spawn_fast_forwards_before_launch
908
+ test_spawn_warns_when_sync_skipped_before_launch
909
+ test_seed_marker_clean_when_gitignored
910
+ test_seed_marker_converges_existing_home
911
+ test_seed_marker_does_not_mask_real_dirt
912
+
913
+ echo "# all fm-secondmate-sync tests passed"