@zerwiz/ymir 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (850) hide show
  1. package/.agents/README.md +291 -0
  2. package/.agents/agents/bragi-marketer.md +111 -0
  3. package/.agents/agents/brokk.md +352 -0
  4. package/.agents/agents/forseti-reviewer.md +93 -0
  5. package/.agents/agents/frigg-consent.md +77 -0
  6. package/.agents/agents/groa-update.md +77 -0
  7. package/.agents/agents/hnoss-designer.md +69 -0
  8. package/.agents/agents/huginn-researcher.md +99 -0
  9. package/.agents/agents/jord-projects.md +78 -0
  10. package/.agents/agents/kvasir-scout.md +91 -0
  11. package/.agents/agents/mimir-planner.md +92 -0
  12. package/.agents/agents/muninn-stow.md +77 -0
  13. package/.agents/agents/saga-bearings.md +78 -0
  14. package/.agents/agents/sindri-developer.md +114 -0
  15. package/.agents/agents/snotra-documenter.md +105 -0
  16. package/.agents/agents/syn-recovery.md +78 -0
  17. package/.agents/agents/tyr-check.md +78 -0
  18. package/.agents/agents/urdh-hold.md +77 -0
  19. package/.agents/agents/volundr-smith.md +80 -0
  20. package/.agents/agents/vor-diagnostics.md +77 -0
  21. package/.agents/assets/agents/naming.md +50 -0
  22. package/.agents/assets/agents/registry.md +93 -0
  23. package/.agents/assets/agents/runtime.md +36 -0
  24. package/.agents/assets/agents/toon-tasks-cli.md +42 -0
  25. package/.agents/assets/templates/company_entity.template.md +37 -0
  26. package/.agents/backend/backends/cmux.sh +649 -0
  27. package/.agents/backend/backends/herdr-eventwait.py +157 -0
  28. package/.agents/backend/backends/herdr-workspace-move.py +114 -0
  29. package/.agents/backend/backends/herdr.sh +3386 -0
  30. package/.agents/backend/backends/orca.sh +290 -0
  31. package/.agents/backend/backends/tmux.sh +357 -0
  32. package/.agents/backend/backends/zellij.sh +700 -0
  33. package/.agents/backend/fm-afk-launch.sh +654 -0
  34. package/.agents/backend/fm-afk-return.sh +241 -0
  35. package/.agents/backend/fm-afk-start.sh +172 -0
  36. package/.agents/backend/fm-arm-command-policy.mjs +960 -0
  37. package/.agents/backend/fm-arm-pretool-check.sh +200 -0
  38. package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
  39. package/.agents/backend/fm-backend.sh +981 -0
  40. package/.agents/backend/fm-backlog-handoff.sh +909 -0
  41. package/.agents/backend/fm-backlog-receive.sh +187 -0
  42. package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
  43. package/.agents/backend/fm-bearings-board.sh +204 -0
  44. package/.agents/backend/fm-bearings-snapshot.sh +546 -0
  45. package/.agents/backend/fm-bootstrap.sh +1568 -0
  46. package/.agents/backend/fm-branch-outcome.sh +203 -0
  47. package/.agents/backend/fm-branch-prompt.sh +101 -0
  48. package/.agents/backend/fm-brief.sh +452 -0
  49. package/.agents/backend/fm-busy-event.sh +234 -0
  50. package/.agents/backend/fm-busy-lib.sh +986 -0
  51. package/.agents/backend/fm-captain-hold.sh +1001 -0
  52. package/.agents/backend/fm-cd-command-policy.mjs +153 -0
  53. package/.agents/backend/fm-cd-pretool-check.sh +190 -0
  54. package/.agents/backend/fm-check-lib.sh +74 -0
  55. package/.agents/backend/fm-check-register.sh +42 -0
  56. package/.agents/backend/fm-check-unregister.sh +52 -0
  57. package/.agents/backend/fm-classify-lib.sh +1770 -0
  58. package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
  59. package/.agents/backend/fm-composer-lib.sh +1416 -0
  60. package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
  61. package/.agents/backend/fm-config-push.sh +254 -0
  62. package/.agents/backend/fm-control-lib.sh +251 -0
  63. package/.agents/backend/fm-control.sh +878 -0
  64. package/.agents/backend/fm-crew-state.sh +625 -0
  65. package/.agents/backend/fm-cursor-lib.sh +243 -0
  66. package/.agents/backend/fm-decision-hold.sh +232 -0
  67. package/.agents/backend/fm-doc-audience-check.sh +269 -0
  68. package/.agents/backend/fm-dod-lib.sh +67 -0
  69. package/.agents/backend/fm-ensure-agents-md.sh +253 -0
  70. package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
  71. package/.agents/backend/fm-extension.mjs +2577 -0
  72. package/.agents/backend/fm-extension.sh +16 -0
  73. package/.agents/backend/fm-ff-lib.sh +420 -0
  74. package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
  75. package/.agents/backend/fm-fleet-sync.sh +458 -0
  76. package/.agents/backend/fm-fleet-view.sh +96 -0
  77. package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
  78. package/.agents/backend/fm-guard.sh +242 -0
  79. package/.agents/backend/fm-harness.sh +195 -0
  80. package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
  81. package/.agents/backend/fm-herdr-lab.sh +346 -0
  82. package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
  83. package/.agents/backend/fm-home-seed.sh +984 -0
  84. package/.agents/backend/fm-home-summary-refresh.sh +255 -0
  85. package/.agents/backend/fm-hook-host-lib.sh +36 -0
  86. package/.agents/backend/fm-inactive-reconcile.sh +531 -0
  87. package/.agents/backend/fm-inbox.sh +399 -0
  88. package/.agents/backend/fm-install-actionlint.sh +84 -0
  89. package/.agents/backend/fm-install-herdr.sh +96 -0
  90. package/.agents/backend/fm-install-shellcheck.sh +84 -0
  91. package/.agents/backend/fm-install-treehouse.sh +95 -0
  92. package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
  93. package/.agents/backend/fm-lease-lib.sh +218 -0
  94. package/.agents/backend/fm-lease.sh +189 -0
  95. package/.agents/backend/fm-line-cap-lib.sh +51 -0
  96. package/.agents/backend/fm-lint-workflows.sh +137 -0
  97. package/.agents/backend/fm-lint.sh +591 -0
  98. package/.agents/backend/fm-lock-lib.sh +104 -0
  99. package/.agents/backend/fm-lock.sh +107 -0
  100. package/.agents/backend/fm-marker-lib.sh +12 -0
  101. package/.agents/backend/fm-merge-local.sh +74 -0
  102. package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
  103. package/.agents/backend/fm-nm-run-lib.sh +124 -0
  104. package/.agents/backend/fm-on.sh +125 -0
  105. package/.agents/backend/fm-operational-input.sh +253 -0
  106. package/.agents/backend/fm-peek.sh +46 -0
  107. package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
  108. package/.agents/backend/fm-pr-check.sh +135 -0
  109. package/.agents/backend/fm-pr-lib.sh +1016 -0
  110. package/.agents/backend/fm-pr-merge.sh +707 -0
  111. package/.agents/backend/fm-pr-poll.sh +110 -0
  112. package/.agents/backend/fm-primary-scope-lib.sh +33 -0
  113. package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
  114. package/.agents/backend/fm-procevent-lavish.sh +628 -0
  115. package/.agents/backend/fm-procevent-lib.sh +892 -0
  116. package/.agents/backend/fm-procevent-quota.sh +290 -0
  117. package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
  118. package/.agents/backend/fm-procevent-when.sh +504 -0
  119. package/.agents/backend/fm-procevent.sh +1466 -0
  120. package/.agents/backend/fm-project-mode.sh +92 -0
  121. package/.agents/backend/fm-project-origin-lib.sh +180 -0
  122. package/.agents/backend/fm-promote.sh +282 -0
  123. package/.agents/backend/fm-public-followup-emit.sh +260 -0
  124. package/.agents/backend/fm-public-followup-lib.sh +398 -0
  125. package/.agents/backend/fm-public-followup.sh +1330 -0
  126. package/.agents/backend/fm-push-transition-lib.sh +168 -0
  127. package/.agents/backend/fm-quota-axi-lib.sh +93 -0
  128. package/.agents/backend/fm-quota-choose.sh +384 -0
  129. package/.agents/backend/fm-remote-delta-read.sh +189 -0
  130. package/.agents/backend/fm-remote-doctor.sh +800 -0
  131. package/.agents/backend/fm-remote-entrypoint.sh +193 -0
  132. package/.agents/backend/fm-remote-file.sh +234 -0
  133. package/.agents/backend/fm-remote-home-provision.sh +259 -0
  134. package/.agents/backend/fm-remote-home-seed.sh +259 -0
  135. package/.agents/backend/fm-remote-inherit-push.sh +90 -0
  136. package/.agents/backend/fm-remote-inherit.sh +179 -0
  137. package/.agents/backend/fm-remote-job-lib.sh +1241 -0
  138. package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
  139. package/.agents/backend/fm-remote-job-worker.sh +1124 -0
  140. package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
  141. package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
  142. package/.agents/backend/fm-review-diff.sh +158 -0
  143. package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
  144. package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
  145. package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
  146. package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
  147. package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
  148. package/.agents/backend/fm-secondmate-report.sh +88 -0
  149. package/.agents/backend/fm-send.sh +1059 -0
  150. package/.agents/backend/fm-session-lock-lib.sh +176 -0
  151. package/.agents/backend/fm-session-start.sh +970 -0
  152. package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
  153. package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
  154. package/.agents/backend/fm-sessionstart-run.sh +145 -0
  155. package/.agents/backend/fm-spawn.sh +3153 -0
  156. package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
  157. package/.agents/backend/fm-startup-memory-budget.sh +94 -0
  158. package/.agents/backend/fm-startup-network.sh +642 -0
  159. package/.agents/backend/fm-stow-cascade.sh +251 -0
  160. package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
  161. package/.agents/backend/fm-supervise-daemon.sh +1732 -0
  162. package/.agents/backend/fm-supervision-instructions.sh +215 -0
  163. package/.agents/backend/fm-supervision-lib.sh +90 -0
  164. package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
  165. package/.agents/backend/fm-tangle-lib.sh +53 -0
  166. package/.agents/backend/fm-task-inbox-lib.sh +402 -0
  167. package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
  168. package/.agents/backend/fm-teardown.sh +2922 -0
  169. package/.agents/backend/fm-test-isolation-proof.sh +588 -0
  170. package/.agents/backend/fm-test-run.sh +2191 -0
  171. package/.agents/backend/fm-timeout-lib.sh +141 -0
  172. package/.agents/backend/fm-timing-lib.sh +179 -0
  173. package/.agents/backend/fm-tmux-lib.sh +291 -0
  174. package/.agents/backend/fm-tool-update-check.sh +898 -0
  175. package/.agents/backend/fm-trace-context-lib.sh +227 -0
  176. package/.agents/backend/fm-transition-lib.sh +103 -0
  177. package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
  178. package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
  179. package/.agents/backend/fm-turnend-guard.sh +432 -0
  180. package/.agents/backend/fm-update.sh +110 -0
  181. package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
  182. package/.agents/backend/fm-voice-client.py +1373 -0
  183. package/.agents/backend/fm-voice-relay.py +1256 -0
  184. package/.agents/backend/fm-wake-drain.sh +592 -0
  185. package/.agents/backend/fm-wake-grant.sh +127 -0
  186. package/.agents/backend/fm-wake-lib.sh +1849 -0
  187. package/.agents/backend/fm-watch-arm.sh +602 -0
  188. package/.agents/backend/fm-watch-checkpoint.sh +109 -0
  189. package/.agents/backend/fm-watch.sh +1962 -0
  190. package/.agents/backend/fm-x-dismiss.sh +116 -0
  191. package/.agents/backend/fm-x-followup.sh +286 -0
  192. package/.agents/backend/fm-x-lib.sh +1000 -0
  193. package/.agents/backend/fm-x-link.sh +234 -0
  194. package/.agents/backend/fm-x-poll.sh +185 -0
  195. package/.agents/backend/fm-x-reply.sh +385 -0
  196. package/.agents/backend/fm_voice_frame.py +166 -0
  197. package/.agents/backend/fm_voice_records.py +574 -0
  198. package/.agents/backend/model-bridge.py +290 -0
  199. package/.agents/backend/opencode-go-bridge.py +30 -0
  200. package/.agents/bus/README.md +12 -0
  201. package/.agents/bus/messages.json +1 -0
  202. package/.agents/config/agents.machine.example.yaml +16 -0
  203. package/.agents/config/agents.yaml +50 -0
  204. package/.agents/config/agents.yaml.example +72 -0
  205. package/.agents/config/crew-dispatch.json +22 -0
  206. package/.agents/config/cron.yaml +9 -0
  207. package/.agents/config/eindri-dispatch.json +67 -0
  208. package/.agents/config/eindri-harness +1 -0
  209. package/.agents/config/startup-memory-budget +1 -0
  210. package/.agents/config/tailscale-sync.yaml +27 -0
  211. package/.agents/filebrowser/.filebrowser.json +6 -0
  212. package/.agents/filebrowser/README.md +11 -0
  213. package/.agents/gateway/README.md +12 -0
  214. package/.agents/github/README.md +15 -0
  215. package/.agents/github/webhooks/.gitkeep +0 -0
  216. package/.agents/github/workflows/.gitkeep +0 -0
  217. package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
  218. package/.agents/harness/opencode/plugins/package.json +4 -0
  219. package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
  220. package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
  221. package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
  222. package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
  223. package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
  224. package/.agents/memory/README.md +46 -0
  225. package/.agents/memory/kaia.engram +0 -0
  226. package/.agents/memory/kaia.engram-shm +0 -0
  227. package/.agents/memory/kaia.engram-wal +0 -0
  228. package/.agents/memory/runes_audit.md +5 -0
  229. package/.agents/memory/well/episodes.jsonl +367 -0
  230. package/.agents/memory/well/workspace.jsonl +27 -0
  231. package/.agents/migrations/0001-hodd-layout.sh +38 -0
  232. package/.agents/migrations/0002-a2a-mcp.sh +27 -0
  233. package/.agents/migrations/0003-private-data-separation.sh +111 -0
  234. package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
  235. package/.agents/sandbox/Dockerfile.utgard +19 -0
  236. package/.agents/sandbox/README.md +14 -0
  237. package/.agents/sandbox/utgard.config.json +14 -0
  238. package/.agents/skills/README.md +59 -0
  239. package/.agents/skills/bragi-marketing/README.md +3 -0
  240. package/.agents/skills/bragi-marketing/SKILL.md +82 -0
  241. package/.agents/skills/eindri-homes/README.md +3 -0
  242. package/.agents/skills/eindri-homes/SKILL.md +291 -0
  243. package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
  244. package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
  245. package/.agents/skills/frigg-consent/README.md +3 -0
  246. package/.agents/skills/frigg-consent/SKILL.md +56 -0
  247. package/.agents/skills/galdr-ymirsystem/README.md +3 -0
  248. package/.agents/skills/galdr-ymirsystem/SKILL.md +158 -0
  249. package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
  250. package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
  251. package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
  252. package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
  253. package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
  254. package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
  255. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +673 -0
  256. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
  257. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
  258. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
  259. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
  260. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
  261. package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +496 -0
  262. package/.agents/skills/galdr-ymirsystem/assets/installation.md +571 -0
  263. package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
  264. package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
  265. package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +161 -0
  266. package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +377 -0
  267. package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
  268. package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +129 -0
  269. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
  270. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
  271. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +96 -0
  272. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
  273. package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
  274. package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
  275. package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
  276. package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
  277. package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
  278. package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
  279. package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
  280. package/.agents/skills/galdr-ymirsystem/assets/smidja.md +175 -0
  281. package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
  282. package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
  283. package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
  284. package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +360 -0
  285. package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
  286. package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
  287. package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
  288. package/.agents/skills/gjallarhorn-relay/README.md +3 -0
  289. package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
  290. package/.agents/skills/groa-update/SKILL.md +70 -0
  291. package/.agents/skills/hamr-adapters/README.md +3 -0
  292. package/.agents/skills/hamr-adapters/SKILL.md +123 -0
  293. package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
  294. package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
  295. package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
  296. package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
  297. package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
  298. package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
  299. package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
  300. package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
  301. package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
  302. package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
  303. package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
  304. package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
  305. package/.agents/skills/herdr-panes/README.md +3 -0
  306. package/.agents/skills/herdr-panes/SKILL.md +92 -0
  307. package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
  308. package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
  309. package/.agents/skills/hnoss-design/DESIGN.md +19 -0
  310. package/.agents/skills/hnoss-design/README.md +3 -0
  311. package/.agents/skills/hnoss-design/SKILL.md +83 -0
  312. package/.agents/skills/hvild-afk/README.md +3 -0
  313. package/.agents/skills/hvild-afk/SKILL.md +241 -0
  314. package/.agents/skills/jord-projects/README.md +3 -0
  315. package/.agents/skills/jord-projects/SKILL.md +92 -0
  316. package/.agents/skills/lifecycle/SKILL.md +56 -0
  317. package/.agents/skills/lifecycle/smoke_test.sh +52 -0
  318. package/.agents/skills/lifecycle/start.sh +21 -0
  319. package/.agents/skills/lifecycle/status.sh +35 -0
  320. package/.agents/skills/lifecycle/stop.sh +16 -0
  321. package/.agents/skills/muninn-stow/README.md +3 -0
  322. package/.agents/skills/muninn-stow/SKILL.md +309 -0
  323. package/.agents/skills/no-mistakes/SKILL.md +379 -0
  324. package/.agents/skills/nornir-schedule/README.md +3 -0
  325. package/.agents/skills/nornir-schedule/SKILL.md +25 -0
  326. package/.agents/skills/nornir-schedule/assets/events.md +137 -0
  327. package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
  328. package/.agents/skills/nsr-compliance/README.md +3 -0
  329. package/.agents/skills/nsr-compliance/SKILL.md +29 -0
  330. package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
  331. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
  332. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
  333. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
  334. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
  335. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
  336. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
  337. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
  338. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
  339. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
  340. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
  341. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
  342. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
  343. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
  344. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
  345. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
  346. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
  347. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
  348. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
  349. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
  350. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
  351. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
  352. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
  353. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  354. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
  355. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
  356. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
  357. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
  358. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
  359. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
  360. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
  361. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
  362. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
  363. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
  364. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
  365. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
  366. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
  367. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
  368. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
  369. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
  370. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
  371. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
  372. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
  373. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
  374. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
  375. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
  376. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
  377. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
  378. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
  379. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
  380. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
  381. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
  382. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
  383. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
  384. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
  385. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
  386. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
  387. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
  388. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
  389. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
  390. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
  391. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
  392. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
  393. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
  394. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
  395. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
  396. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
  397. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
  398. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
  399. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
  400. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
  401. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
  402. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
  403. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
  404. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
  405. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
  406. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
  407. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
  408. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
  409. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
  410. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
  411. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
  412. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
  413. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
  414. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
  415. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
  416. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
  417. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
  418. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
  419. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
  420. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
  421. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
  422. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
  423. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
  424. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
  425. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
  426. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
  427. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
  428. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
  429. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
  430. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
  431. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
  432. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
  433. package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
  434. package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
  435. package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
  436. package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
  437. package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
  438. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
  439. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
  440. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
  441. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
  442. package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
  443. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
  444. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
  445. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
  446. package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
  447. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
  448. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
  449. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
  450. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
  451. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
  452. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
  453. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
  454. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  455. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
  456. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
  457. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
  458. package/.agents/skills/pr-ops/README.md +3 -0
  459. package/.agents/skills/pr-ops/SKILL.md +109 -0
  460. package/.agents/skills/ratatoskr-a2a/README.md +3 -0
  461. package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
  462. package/.agents/skills/rules-check-drift/SKILL.md +81 -0
  463. package/.agents/skills/saga-bearings/README.md +3 -0
  464. package/.agents/skills/saga-bearings/SKILL.md +26 -0
  465. package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
  466. package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
  467. package/.agents/skills/saga-bearings/assets/recap.md +56 -0
  468. package/.agents/skills/syn-recovery/README.md +3 -0
  469. package/.agents/skills/syn-recovery/SKILL.md +54 -0
  470. package/.agents/skills/tyr-check/README.md +3 -0
  471. package/.agents/skills/tyr-check/SKILL.md +218 -0
  472. package/.agents/skills/urdh-hold/README.md +3 -0
  473. package/.agents/skills/urdh-hold/SKILL.md +25 -0
  474. package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
  475. package/.agents/skills/urdh-hold/assets/hold.md +54 -0
  476. package/.agents/skills/vor-diagnostics/README.md +3 -0
  477. package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
  478. package/.agents/skills/ymir-host/README.md +3 -0
  479. package/.agents/skills/ymir-host/SKILL.md +34 -0
  480. package/.agents/skills/ymir-host/assets/desktop.md +36 -0
  481. package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
  482. package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
  483. package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
  484. package/.agents/skills/ymir-host/assets/update.md +64 -0
  485. package/.agents/tests/assets/board-render-harness.mjs +117 -0
  486. package/.agents/tests/cmux-test-safety.sh +51 -0
  487. package/.agents/tests/fixtures.sh +291 -0
  488. package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
  489. package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
  490. package/.agents/tests/fm-afk-launch.test.sh +961 -0
  491. package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
  492. package/.agents/tests/fm-afk-return.test.sh +283 -0
  493. package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
  494. package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
  495. package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
  496. package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
  497. package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
  498. package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
  499. package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
  500. package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
  501. package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
  502. package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
  503. package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
  504. package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
  505. package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
  506. package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
  507. package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
  508. package/.agents/tests/fm-backend-orca.test.sh +1357 -0
  509. package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
  510. package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
  511. package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
  512. package/.agents/tests/fm-backend.test.sh +1142 -0
  513. package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
  514. package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
  515. package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
  516. package/.agents/tests/fm-bearings-board.test.sh +414 -0
  517. package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
  518. package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
  519. package/.agents/tests/fm-bootstrap.test.sh +1178 -0
  520. package/.agents/tests/fm-branch-supervision.test.sh +552 -0
  521. package/.agents/tests/fm-brief.test.sh +785 -0
  522. package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
  523. package/.agents/tests/fm-busy-state.test.sh +465 -0
  524. package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
  525. package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
  526. package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
  527. package/.agents/tests/fm-check-unregister.test.sh +198 -0
  528. package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
  529. package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
  530. package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
  531. package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
  532. package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
  533. package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
  534. package/.agents/tests/fm-composer-ghost.test.sh +716 -0
  535. package/.agents/tests/fm-composer-lib.test.sh +666 -0
  536. package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
  537. package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
  538. package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
  539. package/.agents/tests/fm-control.test.sh +910 -0
  540. package/.agents/tests/fm-crew-state.test.sh +1610 -0
  541. package/.agents/tests/fm-cursor-harness.test.sh +404 -0
  542. package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
  543. package/.agents/tests/fm-cursor-primary.test.sh +708 -0
  544. package/.agents/tests/fm-daemon.test.sh +2740 -0
  545. package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
  546. package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
  547. package/.agents/tests/fm-extension-binding.test.sh +2187 -0
  548. package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
  549. package/.agents/tests/fm-fleet-sync.test.sh +721 -0
  550. package/.agents/tests/fm-gate-refuse.test.sh +356 -0
  551. package/.agents/tests/fm-gitignore-config.test.sh +89 -0
  552. package/.agents/tests/fm-gotmp.test.sh +230 -0
  553. package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
  554. package/.agents/tests/fm-grok-harness.test.sh +117 -0
  555. package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
  556. package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
  557. package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
  558. package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
  559. package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
  560. package/.agents/tests/fm-herdr-lab.test.sh +243 -0
  561. package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
  562. package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
  563. package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
  564. package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
  565. package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
  566. package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
  567. package/.agents/tests/fm-kimi-harness.test.sh +687 -0
  568. package/.agents/tests/fm-lint-workflows.test.sh +532 -0
  569. package/.agents/tests/fm-lint.test.sh +1023 -0
  570. package/.agents/tests/fm-muse-harness.test.sh +932 -0
  571. package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
  572. package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
  573. package/.agents/tests/fm-on.test.sh +507 -0
  574. package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
  575. package/.agents/tests/fm-operational-input.test.sh +160 -0
  576. package/.agents/tests/fm-peek-remote.test.sh +110 -0
  577. package/.agents/tests/fm-pending-reply.test.sh +1288 -0
  578. package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
  579. package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
  580. package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
  581. package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
  582. package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
  583. package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
  584. package/.agents/tests/fm-pr-merge.test.sh +2135 -0
  585. package/.agents/tests/fm-procevent-quota.test.sh +225 -0
  586. package/.agents/tests/fm-procevent-when.test.sh +406 -0
  587. package/.agents/tests/fm-procevent.test.sh +1745 -0
  588. package/.agents/tests/fm-project-origin.test.sh +110 -0
  589. package/.agents/tests/fm-public-followup.test.sh +2272 -0
  590. package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
  591. package/.agents/tests/fm-quota-choose.test.sh +626 -0
  592. package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
  593. package/.agents/tests/fm-remote-doctor.test.sh +625 -0
  594. package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
  595. package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
  596. package/.agents/tests/fm-remote-job.test.sh +768 -0
  597. package/.agents/tests/fm-remote-reply.test.sh +520 -0
  598. package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
  599. package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
  600. package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
  601. package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
  602. package/.agents/tests/fm-review-diff.test.sh +176 -0
  603. package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
  604. package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
  605. package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
  606. package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
  607. package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
  608. package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
  609. package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
  610. package/.agents/tests/fm-send-inbox.test.sh +352 -0
  611. package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
  612. package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
  613. package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
  614. package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
  615. package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
  616. package/.agents/tests/fm-send-settle.test.sh +147 -0
  617. package/.agents/tests/fm-send-strict.test.sh +241 -0
  618. package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
  619. package/.agents/tests/fm-session-start.test.sh +2515 -0
  620. package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
  621. package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
  622. package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
  623. package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
  624. package/.agents/tests/fm-spawn-batch.test.sh +148 -0
  625. package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
  626. package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
  627. package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
  628. package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
  629. package/.agents/tests/fm-startup-network.test.sh +712 -0
  630. package/.agents/tests/fm-stow-cascade.test.sh +370 -0
  631. package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
  632. package/.agents/tests/fm-supervision-events.test.sh +156 -0
  633. package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
  634. package/.agents/tests/fm-tangle-guard.test.sh +277 -0
  635. package/.agents/tests/fm-task-delivery.test.sh +420 -0
  636. package/.agents/tests/fm-task-inbox.test.sh +513 -0
  637. package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
  638. package/.agents/tests/fm-teardown.test.sh +2665 -0
  639. package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
  640. package/.agents/tests/fm-test-fixtures.test.sh +132 -0
  641. package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
  642. package/.agents/tests/fm-test-run.test.sh +1196 -0
  643. package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
  644. package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
  645. package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
  646. package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
  647. package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
  648. package/.agents/tests/fm-transition-lib.test.sh +54 -0
  649. package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
  650. package/.agents/tests/fm-update.test.sh +304 -0
  651. package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
  652. package/.agents/tests/fm-voice-relay.test.sh +4741 -0
  653. package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
  654. package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
  655. package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
  656. package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
  657. package/.agents/tests/fm-wake-queue.test.sh +1224 -0
  658. package/.agents/tests/fm-watch-arm.test.sh +821 -0
  659. package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
  660. package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
  661. package/.agents/tests/fm-watch-triage.test.sh +3889 -0
  662. package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
  663. package/.agents/tests/fm-x-mode.test.sh +3042 -0
  664. package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
  665. package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
  666. package/.agents/tests/herdr-test-safety.sh +42 -0
  667. package/.agents/tests/lib.sh +315 -0
  668. package/.agents/tests/remote-herdr-fixture.sh +125 -0
  669. package/.agents/tests/secondmate-helpers.sh +193 -0
  670. package/.agents/tests/smoke.test.sh +93 -0
  671. package/.agents/tests/wake-helpers.sh +338 -0
  672. package/.agents/tests/zellij-test-safety.sh +60 -0
  673. package/.agents/tools/README.md +10 -0
  674. package/.agents/tools/bin/tasks-cli +267 -0
  675. package/.agents/tools/tasks-cli.ts +216 -0
  676. package/AGENTS.md +454 -0
  677. package/CHANGELOG.md +881 -0
  678. package/LICENSE +1 -1
  679. package/NOTICE +63 -0
  680. package/README.md +113 -59
  681. package/RULES/01-domains.md +51 -0
  682. package/RULES/02-agents.md +78 -0
  683. package/RULES/03-houses.md +61 -0
  684. package/RULES/04-hoard.md +65 -0
  685. package/RULES/05-platforms.md +67 -0
  686. package/RULES/06-append-only.md +58 -0
  687. package/RULES/07-config.md +45 -0
  688. package/RULES/08-delivery-gate.md +75 -0
  689. package/RULES/README.md +20 -0
  690. package/bin/README.md +107 -0
  691. package/bin/a2a-mcp.sh +98 -0
  692. package/bin/a2a-serve.py +103 -0
  693. package/bin/a2a-serve.sh +8 -0
  694. package/bin/a2a-talk.sh +50 -0
  695. package/bin/a2abridge-ensure.sh +108 -0
  696. package/bin/agent-run.sh +43 -0
  697. package/bin/agents-config.sh +233 -0
  698. package/bin/apodex-smoke-test.sh +92 -0
  699. package/bin/bifrost-bridge.sh +112 -0
  700. package/bin/bifrost-ingress.sh +77 -0
  701. package/bin/bootstrap-macos.sh +107 -0
  702. package/bin/bootstrap-windows.ps1 +112 -0
  703. package/bin/branch-guard.sh +64 -0
  704. package/bin/brokk +73 -0
  705. package/bin/brokk-classify-lib.sh +1770 -0
  706. package/bin/brokk-lease-lib.sh +222 -0
  707. package/bin/brokk-lease.sh +193 -0
  708. package/bin/brokk-lint.sh +65 -0
  709. package/bin/brokk-send.sh +38 -0
  710. package/bin/brokk-update.sh +9 -0
  711. package/bin/brokk-wake-grant.sh +127 -0
  712. package/bin/brokk-wake-lib.sh +1849 -0
  713. package/bin/changelog-guard.sh +165 -0
  714. package/bin/crash-sense.sh +160 -0
  715. package/bin/desktop-place.sh +224 -0
  716. package/bin/docs-guard.sh +39 -0
  717. package/bin/editor-place.sh +207 -0
  718. package/bin/eindri-acclaim.sh +34 -0
  719. package/bin/eindri-control.sh +28 -0
  720. package/bin/eindri-role.sh +83 -0
  721. package/bin/eindri-seen.sh +41 -0
  722. package/bin/eindri-send.sh +15 -0
  723. package/bin/eindri-start.sh +85 -0
  724. package/bin/eindri-watch.sh +57 -0
  725. package/bin/einherjar-spawn.sh +576 -0
  726. package/bin/eir-doctor.sh +112 -0
  727. package/bin/erindi-brief.sh +271 -0
  728. package/bin/fleet-apply.sh +68 -0
  729. package/bin/github-deploy.sh +81 -0
  730. package/bin/gjallarhorn-expose.sh +102 -0
  731. package/bin/gjallarhorn-notify.sh +58 -0
  732. package/bin/gjallarhorn-purge.sh +54 -0
  733. package/bin/gjallarhorn-tunnel.sh +72 -0
  734. package/bin/gleipnir-lock-lib.sh +284 -0
  735. package/bin/groa-update.sh +122 -0
  736. package/bin/hall-snapshot.sh +92 -0
  737. package/bin/hamr-harness.sh +246 -0
  738. package/bin/heimdall-ssh-keys.sh +374 -0
  739. package/bin/herdr-agents.py +44 -0
  740. package/bin/herdr-ensure.sh +104 -0
  741. package/bin/herdr-run.sh +431 -0
  742. package/bin/hermes-ensure.sh +88 -0
  743. package/bin/hoard-lib.sh +25 -0
  744. package/bin/hodd.sh +78 -0
  745. package/bin/host-sense.sh +199 -0
  746. package/bin/huginn-research-worker.sh +108 -0
  747. package/bin/local-model-lock.sh +51 -0
  748. package/bin/mimir-bridge.py +226 -0
  749. package/bin/mimir-bridge.sh +87 -0
  750. package/bin/mimir-ingest.sh +131 -0
  751. package/bin/mimir.sh +113 -0
  752. package/bin/mjollnir-webhook.sh +59 -0
  753. package/bin/mjollnir.sh +85 -0
  754. package/bin/model-hardware.sh +82 -0
  755. package/bin/model-resolve.sh +147 -0
  756. package/bin/models-detect.sh +178 -0
  757. package/bin/nornir-cron-start.sh +162 -0
  758. package/bin/nornir-job-daily-briefing.sh +191 -0
  759. package/bin/nornir-job-git-sync.sh +152 -0
  760. package/bin/nornir-job-memory-housekeeping.sh +114 -0
  761. package/bin/nornir-job-observer.sh +166 -0
  762. package/bin/nornir-job-skillopt-sleep.sh +76 -0
  763. package/bin/omarchy-hook-install.sh +60 -0
  764. package/bin/omarchy-install.sh +148 -0
  765. package/bin/omarchy-plugins.sh +174 -0
  766. package/bin/omarchy-sense.sh +149 -0
  767. package/bin/perm-guard.sh +30 -0
  768. package/bin/pi-agent.sh +68 -0
  769. package/bin/pi-ensure.sh +77 -0
  770. package/bin/pi-local.sh +37 -0
  771. package/bin/pi-seat.sh +63 -0
  772. package/bin/prereq-ensure.sh +166 -0
  773. package/bin/private-guard.sh +38 -0
  774. package/bin/project-git.sh +55 -0
  775. package/bin/public-guard.sh +70 -0
  776. package/bin/ratatoskr.sh +49 -0
  777. package/bin/realm-lib.sh +32 -0
  778. package/bin/repo-scrub.sh +62 -0
  779. package/bin/rodd-operational-input.sh +250 -0
  780. package/bin/runes-append.sh +198 -0
  781. package/bin/saga-session-start.sh +172 -0
  782. package/bin/saga-sessionstart-run.sh +79 -0
  783. package/bin/saga-wake-drain.sh +41 -0
  784. package/bin/secret-guard.sh +63 -0
  785. package/bin/sessrumnir-ensure.sh +100 -0
  786. package/bin/sessrumnir-sync.sh +237 -0
  787. package/bin/sessrumnir.sh +91 -0
  788. package/bin/skillopt-setup.sh +76 -0
  789. package/bin/skuld-branch-outcome.sh +203 -0
  790. package/bin/skuld-branch-prompt.sh +101 -0
  791. package/bin/smidja-bootstrap.sh +90 -0
  792. package/bin/smidja-observe.sh +86 -0
  793. package/bin/syn-arm-pretool-check.sh +57 -0
  794. package/bin/syn-asset-pretool-check.sh +76 -0
  795. package/bin/syn-cd-pretool-check.sh +28 -0
  796. package/bin/syn-guard-pretool-check.sh +83 -0
  797. package/bin/syn-turnend-guard.sh +36 -0
  798. package/bin/syn-watch-arm.sh +99 -0
  799. package/bin/tailscale-sync.sh +190 -0
  800. package/bin/telegram-bot.sh +69 -0
  801. package/bin/toolchain.sh +74 -0
  802. package/bin/utgard.sh +111 -0
  803. package/bin/valhalla.sh +115 -0
  804. package/bin/valknut-load.sh +260 -0
  805. package/bin/vor-crew-state.sh +124 -0
  806. package/bin/wedge-notify.sh +47 -0
  807. package/bin/workspace-provision.sh +80 -0
  808. package/bin/workspace-rag.sh +150 -0
  809. package/bin/wyrd-db.sh +47 -0
  810. package/bin/yggdrasil.sh +152 -0
  811. package/bin/ymir-edit.sh +145 -0
  812. package/bin/ymir-install.sh +641 -0
  813. package/bin/ymir-invite.sh +93 -0
  814. package/bin/ymir-isolation.sh +88 -0
  815. package/bin/ymir-migrate.sh +61 -0
  816. package/bin/ymir-platform.sh +348 -0
  817. package/bin/ymir-say.sh +97 -0
  818. package/bin/ymir-setup-auth.sh +108 -0
  819. package/bin/ymir-validate.sh +173 -0
  820. package/bin/ymir.js +74 -16
  821. package/docs/Architecture.md +339 -0
  822. package/docs/design.md +351 -0
  823. package/docs/installations/README.md +63 -0
  824. package/docs/installations/ymir-home.md +93 -0
  825. package/docs/integration.md +105 -0
  826. package/docs/lore.md +1056 -0
  827. package/docs/research/agent-harnesses.md +290 -0
  828. package/docs/runbooks/README.md +19 -0
  829. package/docs/runbooks/a2a.md +91 -0
  830. package/docs/runbooks/agent-permissions.md +99 -0
  831. package/docs/runbooks/agents.md +57 -0
  832. package/docs/runbooks/models.md +69 -0
  833. package/docs/runbooks/realm-onboarding.md +60 -0
  834. package/docs/runbooks/secrets-and-hoard.md +60 -0
  835. package/docs/runbooks/updates-and-migrations.md +110 -0
  836. package/docs/runbooks/worktrees-isolation.md +72 -0
  837. package/docs/session-start.md +105 -0
  838. package/docs/skillopt-integration.md +321 -0
  839. package/docs/sync-system.md +66 -0
  840. package/docs/workspaces.md +104 -0
  841. package/docs/ymir-rut.md +260 -0
  842. package/package.json +44 -14
  843. package/packaging/build.sh +74 -0
  844. package/packaging/macos/Ymir Installer.command +43 -0
  845. package/packaging/windows/ymir-setup.nsi +74 -0
  846. package/scripts/README.md +22 -0
  847. package/scripts/electron.sh +210 -0
  848. package/scripts/start.sh +167 -0
  849. package/scripts/stop.sh +113 -0
  850. package/install.sh +0 -35
@@ -0,0 +1,1039 @@
1
+ #!/usr/bin/env bash
2
+ # Behavior tests for both native session-open tiers: the nudge wrapper that
3
+ # only asks the agent to take the helm, and the run wrapper that takes it.
4
+ #
5
+ # The run-wrapper cases drive the REAL bin/fm-session-start.sh against a
6
+ # throwaway home, so they prove routing by the digest that actually appears,
7
+ # not by inspecting the wrapper's source. docs/sessionstart-nudge.md owns the
8
+ # tier assignment and the source table these pin.
9
+ set -u
10
+
11
+ # Run the whole suite beneath one long-lived fixture harness, matching the real
12
+ # lifecycle in which startup and later clear/compact hooks share one harness
13
+ # ancestor. This also prevents a developer's ambient harness from making the
14
+ # portable regression pass locally while failing on a harness-free CI runner.
15
+ if [ "${FM_SESSIONSTART_TEST_HARNESS:-0}" != 1 ]; then
16
+ HARNESS_FIXTURE=$(mktemp -d "${TMPDIR:-/tmp}/fm-sessionstart-harness.XXXXXX") || exit 1
17
+ ln -s /bin/bash "$HARNESS_FIXTURE/codex" || exit 1
18
+ # shellcheck disable=SC2016 # Expand in the fixture shell, not this parent.
19
+ FM_SESSIONSTART_TEST_HARNESS=1 "$HARNESS_FIXTURE/codex" \
20
+ -c '"$@"; rc=$?; :; exit "$rc"' _ "$0" "$@"
21
+ HARNESS_STATUS=$?
22
+ rm -rf "$HARNESS_FIXTURE"
23
+ exit "$HARNESS_STATUS"
24
+ fi
25
+
26
+ # shellcheck source=tests/lib.sh
27
+ . "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
28
+
29
+ unset NO_MISTAKES_GATE
30
+
31
+ TMP_ROOT=$(fm_test_tmproot fm-sessionstart-nudge)
32
+ NUDGE="$ROOT/bin/fm-sessionstart-nudge.sh"
33
+ RUN="$ROOT/bin/fm-sessionstart-run.sh"
34
+ # shellcheck source=/dev/null
35
+ . "$ROOT/bin/fm-operational-input.sh"
36
+ NUDGE_TEXT="Run \`bin/fm-session-start.sh\` now, exactly once, before executing any other instructions."
37
+ fm_operational_input_encode session-start "$NUDGE_TEXT" NUDGE_LINE \
38
+ || fail "could not construct expected session-start nudge"
39
+ fm_git_identity fmtest fmtest@example.invalid
40
+
41
+ make_primary() {
42
+ local dir=$1
43
+ mkdir -p "$dir/bin" "$dir/state"
44
+ git init -q "$dir"
45
+ git -C "$dir" commit -q --allow-empty -m init
46
+ : > "$dir/AGENTS.md"
47
+ }
48
+
49
+ run_nudge() {
50
+ local root=$1
51
+ FM_GATE_REFUSE_BYPASS=0 FM_ROOT_OVERRIDE="$root" FM_HOME="$root" "$NUDGE"
52
+ }
53
+
54
+ expect_silent_zero() {
55
+ local label=$1
56
+ shift
57
+ local out status=0
58
+ out=$("$@" 2>&1) || status=$?
59
+ expect_code 0 "$status" "$label must exit 0"
60
+ [ -z "$out" ] || fail "$label must be silent, got: $out"
61
+ }
62
+
63
+ test_genuine_primary_nudges() {
64
+ local root="$TMP_ROOT/primary" out prefix_hex status=0
65
+ make_primary "$root"
66
+ out=$(run_nudge "$root") || status=$?
67
+ expect_code 0 "$status" "genuine primary nudge"
68
+ [ "$out" = "$NUDGE_LINE" ] || fail "genuine primary printed unexpected output: $out"
69
+ prefix_hex=$(printf '%s' "$out" | head -c 3 | od -An -tx1 | tr -d ' \n')
70
+ [ "$prefix_hex" = e281a3 ] || fail "genuine primary nudge lost its U+2063 operational marker: $prefix_hex"
71
+ pass "fm-sessionstart-nudge: a genuine primary gets one explicitly marked instruction line"
72
+ }
73
+
74
+ test_gate_env_is_silent() {
75
+ local root="$TMP_ROOT/gate-env"
76
+ make_primary "$root"
77
+ expect_silent_zero "gate env nudge" env NO_MISTAKES_GATE=1 FM_GATE_REFUSE_BYPASS=0 \
78
+ FM_ROOT_OVERRIDE="$root" FM_HOME="$root" "$NUDGE"
79
+ pass "fm-sessionstart-nudge: NO_MISTAKES_GATE is silent"
80
+ }
81
+
82
+ test_gate_common_dir_is_silent() {
83
+ local source="$TMP_ROOT/gate-source" bare="$TMP_ROOT/.no-mistakes/repos/gate.git"
84
+ local root="$TMP_ROOT/gate-worktree"
85
+ fm_git_init_commit "$source"
86
+ mkdir -p "$(dirname "$bare")"
87
+ git clone --quiet --bare "$source" "$bare"
88
+ git --git-dir="$bare" worktree add --quiet -b gate-test "$root" HEAD
89
+ mkdir -p "$root/bin" "$root/state"
90
+ : > "$root/AGENTS.md"
91
+ printf 'gate-test\n' > "$root/.fm-secondmate-home"
92
+ expect_silent_zero "gate common-dir nudge" env FM_GATE_REFUSE_BYPASS=0 \
93
+ FM_ROOT_OVERRIDE="$root" FM_HOME="$root" "$NUDGE"
94
+ pass "fm-sessionstart-nudge: .no-mistakes gate common-dir is silent"
95
+ }
96
+
97
+ test_unmarked_linked_worktree_is_silent() {
98
+ local base="$TMP_ROOT/worktree-base" root="$TMP_ROOT/worktree-child"
99
+ fm_git_worktree "$base" "$root" fm/sessionstart-linked
100
+ mkdir -p "$root/bin" "$root/state"
101
+ : > "$root/AGENTS.md"
102
+ expect_silent_zero "linked worktree nudge" run_nudge "$root"
103
+ pass "fm-sessionstart-nudge: an unmarked linked task worktree is silent"
104
+ }
105
+
106
+ test_linked_secondmate_primary_nudges() {
107
+ local base="$TMP_ROOT/secondmate-base" root="$TMP_ROOT/secondmate-home" out status=0
108
+ fm_git_worktree "$base" "$root" fm/sessionstart-secondmate
109
+ mkdir -p "$root/bin" "$root/state"
110
+ : > "$root/AGENTS.md"
111
+ printf 'sessionstart-sm\n' > "$root/.fm-secondmate-home"
112
+ out=$(run_nudge "$root") || status=$?
113
+ expect_code 0 "$status" "linked secondmate nudge"
114
+ [ "$out" = "$NUDGE_LINE" ] || fail "linked secondmate printed unexpected output: $out"
115
+ pass "fm-sessionstart-nudge: a marked linked secondmate home is a primary"
116
+ }
117
+
118
+ test_missing_state_is_silent() {
119
+ local root="$TMP_ROOT/missing-state"
120
+ make_primary "$root"
121
+ rmdir "$root/state"
122
+ expect_silent_zero "missing state nudge" run_nudge "$root"
123
+ pass "fm-sessionstart-nudge: a checkout without state is silent"
124
+ }
125
+
126
+ test_owned_lock_is_silent() {
127
+ local root="$TMP_ROOT/already-ran"
128
+ make_primary "$root"
129
+ printf '%s\n' "$$" > "$root/state/.lock"
130
+ expect_silent_zero "owned lock nudge" run_nudge "$root"
131
+ pass "fm-sessionstart-nudge: a lock holder in process ancestry is already run"
132
+ }
133
+
134
+ test_opencode_plugin_delivers_exact_nudge_once() {
135
+ local root="$TMP_ROOT/opencode-primary" out status=0
136
+ make_primary "$root"
137
+ cp "$ROOT/bin/fm-sessionstart-nudge.sh" "$ROOT/bin/fm-primary-scope-lib.sh" \
138
+ "$ROOT/bin/fm-gate-refuse-lib.sh" "$ROOT/bin/fm-operational-input.sh" "$root/bin/"
139
+ chmod +x "$root/bin/fm-sessionstart-nudge.sh"
140
+ out=$(PLUGIN="$ROOT/.opencode/plugins/fm-primary-sessionstart-nudge.js" \
141
+ WORKTREE="$root" EXPECTED="$NUDGE_LINE" node --input-type=module 2>&1 <<'EOF'
142
+ import { pathToFileURL } from "node:url";
143
+
144
+ const prompts = [];
145
+ const client = {
146
+ session: {
147
+ promptAsync: async (request) => {
148
+ prompts.push(request.body.parts[0].text);
149
+ },
150
+ },
151
+ };
152
+ const mod = await import(pathToFileURL(process.env.PLUGIN).href);
153
+ const hooks = await mod.FmPrimarySessionstartNudge({
154
+ client,
155
+ directory: process.env.WORKTREE,
156
+ worktree: process.env.WORKTREE,
157
+ });
158
+ const event = {
159
+ type: "session.created",
160
+ properties: { sessionID: "session-nudge-test", info: { id: "session-nudge-test" } },
161
+ };
162
+ await hooks.event({ event });
163
+ await hooks.event({ event });
164
+ if (prompts.length !== 1) throw new Error(`expected one prompt, got ${prompts.length}`);
165
+ if (prompts[0] !== process.env.EXPECTED) throw new Error(`unexpected prompt: ${prompts[0]}`);
166
+ EOF
167
+ ) || status=$?
168
+ expect_code 0 "$status" "OpenCode exact nudge delivery"
169
+ [ -z "$out" ] || fail "OpenCode exact nudge delivery printed output: $out"
170
+ pass "OpenCode session.created delivers the exact wrapper nudge once per session"
171
+ }
172
+
173
+ # --- run tier ----------------------------------------------------------------
174
+ #
175
+ # make_run_primary builds a primary the run wrapper accepts and the REAL
176
+ # fm-session-start.sh can execute: a git repo on main so the tangle check
177
+ # behaves, plus the home directories the digest reads. The deliberately bare
178
+ # PATH keeps every bootstrap probe fast and hermetic - it reports missing tools
179
+ # instead of reaching the host's real gh/tmux/tasks-axi.
180
+ RUN_PATH=${FM_TEST_BASE_PATH:-/usr/bin:/bin:/usr/sbin:/sbin}
181
+
182
+ make_run_primary() {
183
+ local dir=$1
184
+ mkdir -p "$dir/bin" "$dir/state" "$dir/data" "$dir/config"
185
+ git init -q -b main "$dir"
186
+ git -C "$dir" commit -q --allow-empty -m init
187
+ : > "$dir/AGENTS.md"
188
+ }
189
+
190
+ run_hook() { # <root> [args...]
191
+ local root=$1
192
+ shift
193
+ env -u CLAUDECODE -u PI_CODING_AGENT -u FM_PI_HARNESS -u GROK_AGENT \
194
+ FM_GATE_REFUSE_BYPASS=0 FM_ROOT_OVERRIDE="$root" FM_HOME="$root" PATH="$RUN_PATH" "$RUN" "$@"
195
+ }
196
+
197
+ run_hook_pi() { # <root> [args...]
198
+ local root=$1
199
+ shift
200
+ env -u CLAUDECODE -u GROK_AGENT PI_CODING_AGENT=true FM_PI_HARNESS=pi \
201
+ FM_GATE_REFUSE_BYPASS=0 FM_ROOT_OVERRIDE="$root" FM_HOME="$root" PATH="$RUN_PATH" "$RUN" "$@"
202
+ }
203
+
204
+ # Every run-tier assertion keys off the digest banner, which fm-session-start.sh
205
+ # prints before the lock result, so routing is proven whether or not the lock
206
+ # was won in the test environment.
207
+ FULL_BANNER="SESSION START - "
208
+ REEMIT_BANNER="SESSION START (CONTEXT RE-EMIT) - "
209
+
210
+ test_run_startup_runs_the_full_digest() {
211
+ local root="$TMP_ROOT/run-startup" out status=0
212
+ make_run_primary "$root"
213
+ out=$(run_hook "$root" --source startup </dev/null) || status=$?
214
+ expect_code 0 "$status" "run wrapper startup"
215
+ assert_contains "$out" "$FULL_BANNER$root" "startup did not run the full digest"
216
+ assert_contains "$out" "lock acquired: harness pid" \
217
+ "the portable startup fixture did not supply a real harness process"
218
+ assert_not_contains "$out" "$REEMIT_BANNER" "startup was misrouted to a context re-emit"
219
+ assert_not_contains "$out" "FIRSTMATE_OP" "a run-tier open also emitted the nudge instruction"
220
+ assert_contains "$out" "NEXT STEP" "the run wrapper did not deliver a complete digest"
221
+ pass "run wrapper: startup runs the full digest and never also nudges"
222
+ }
223
+
224
+ test_run_clear_and_compact_reemit() {
225
+ local root out source status
226
+ for source in clear compact; do
227
+ root="$TMP_ROOT/run-$source"
228
+ make_run_primary "$root"
229
+ run_hook "$root" --source startup </dev/null >/dev/null
230
+ assert_present "$root/state/.session-start-complete" \
231
+ "startup did not publish the completion proof needed by $source"
232
+ status=0
233
+ out=$(run_hook "$root" --source "$source" </dev/null) || status=$?
234
+ expect_code 0 "$status" "run wrapper $source"
235
+ assert_contains "$out" "$REEMIT_BANNER$root" "$source did not re-emit the digest"
236
+ assert_contains "$out" "are NOT repeated" "$source did not report the skipped startup sweeps"
237
+ assert_contains "$out" "Queued wakes ARE still drained" "$source did not preserve the wake-queue drain"
238
+ assert_not_contains "$out" "FIRSTMATE_OP" "a $source open also emitted the nudge instruction"
239
+ done
240
+ pass "run wrapper: clear and compact re-emit the digest without repeating startup sweeps"
241
+ }
242
+
243
+ test_run_rebuild_forwards_source_to_drifted_instruction_refresh() {
244
+ local root="$TMP_ROOT/run-instruction-refresh" baseline compact_out clear_out resume_out
245
+ make_run_primary "$root"
246
+ printf '%s\n' 'RUN_TIER_AGENTS=original' > "$root/AGENTS.md"
247
+ run_hook_pi "$root" --source startup </dev/null >/dev/null
248
+ assert_present "$root/state/.session-start-agents-baseline" \
249
+ "run-tier startup did not record an instruction baseline"
250
+ baseline=$(cat "$root/state/.session-start-agents-baseline")
251
+
252
+ printf '%s\n' 'RUN_TIER_AGENTS=updated' > "$root/AGENTS.md"
253
+ compact_out=$(run_hook_pi "$root" --source compact </dev/null)
254
+ clear_out=$(run_hook_pi "$root" --source clear </dev/null)
255
+ resume_out=$(run_hook_pi "$root" --source resume </dev/null)
256
+
257
+ assert_contains "$compact_out" "RUN_TIER_AGENTS=updated" \
258
+ "the compact run wrapper did not forward its source to instruction refresh"
259
+ assert_not_contains "$clear_out" "CURRENT AGENTS.md - INSTRUCTION REFRESH" \
260
+ "the clear run wrapper emitted a replacement contract despite Pi's fresh runtime"
261
+ [ "$baseline" = "$(cat "$root/state/.session-start-agents-baseline")" ] \
262
+ || fail "a run-tier rebuild rewrote the true-start instruction baseline"
263
+ [ -z "$resume_out" ] \
264
+ || fail "an already-owned resume should preserve context without re-running the digest"
265
+
266
+ pass "run wrapper forwards only stale-cache rebuild sources to immutable-baseline instruction refresh"
267
+ }
268
+
269
+ test_run_compact_without_completion_refreshes_before_finishing_startup() {
270
+ local root="$TMP_ROOT/run-compact-incomplete" out status=0 refresh_line bootstrap_line
271
+ make_run_primary "$root"
272
+ printf '%s\n' 'INCOMPLETE_START_AGENTS=current' > "$root/AGENTS.md"
273
+
274
+ out=$(run_hook_pi "$root" --source compact </dev/null) || status=$?
275
+ expect_code 0 "$status" "run wrapper compact without completion proof"
276
+ assert_contains "$out" "$FULL_BANNER$root" \
277
+ "compact skipped full startup when no completed startup could be proven"
278
+ assert_contains "$out" "INCOMPLETE_START_AGENTS=current" \
279
+ "compact after an incomplete startup did not conservatively inject current instructions"
280
+ refresh_line=$(printf '%s\n' "$out" | grep -n '^CURRENT AGENTS.md - INSTRUCTION REFRESH$' | head -1 | cut -d: -f1)
281
+ bootstrap_line=$(printf '%s\n' "$out" | grep -n '^BOOTSTRAP$' | head -1 | cut -d: -f1)
282
+ [ -n "$refresh_line" ] && [ -n "$bootstrap_line" ] && [ "$refresh_line" -lt "$bootstrap_line" ] \
283
+ || fail "compact recovery did not emit current instructions before the bulky digest"
284
+ assert_absent "$root/state/.session-start-agents-baseline" \
285
+ "compact recovery fabricated a true-start instruction baseline"
286
+
287
+ pass "run wrapper refreshes a compact even when startup completion is unproven"
288
+ }
289
+
290
+ test_run_clear_without_completion_finishes_startup() {
291
+ local root="$TMP_ROOT/run-clear-incomplete" out status=0
292
+ make_run_primary "$root"
293
+ out=$(run_hook "$root" --source clear </dev/null) || status=$?
294
+ expect_code 0 "$status" "run wrapper clear without completion proof"
295
+ assert_contains "$out" "$FULL_BANNER$root" \
296
+ "clear skipped full startup when no completed startup could be proven"
297
+ assert_not_contains "$out" "$REEMIT_BANNER" \
298
+ "clear trusted lock ownership as proof that startup completed"
299
+ assert_present "$root/state/.session-start-complete" \
300
+ "the recovery full startup did not publish completion proof"
301
+ pass "run wrapper: clear falls back to full startup when completion is unproven"
302
+ }
303
+
304
+ test_run_clear_rejects_previous_owner_completion() {
305
+ local root="$TMP_ROOT/run-clear-previous-owner" out status=0 previous_pid
306
+ make_run_primary "$root"
307
+ sleep 0 &
308
+ previous_pid=$!
309
+ wait "$previous_pid"
310
+ printf '%s\n' "$previous_pid" > "$root/state/.lock"
311
+ printf '%s\n' "$previous_pid" > "$root/state/.session-start-complete"
312
+
313
+ out=$(run_hook "$root" --source clear </dev/null) || status=$?
314
+ expect_code 0 "$status" "run wrapper clear with previous owner completion"
315
+ assert_contains "$out" "$FULL_BANNER$root" \
316
+ "clear treated a previous session's completion as current"
317
+ assert_not_contains "$out" "$REEMIT_BANNER" \
318
+ "clear skipped startup sweeps completed only by a previous session"
319
+ [ "$(cat "$root/state/.lock")" != "$previous_pid" ] \
320
+ || fail "the recovery startup did not replace the previous session's stale lock"
321
+ pass "run wrapper: clear accepts completion only from the current harness"
322
+ }
323
+
324
+ test_pi_startup_classifies_cli_continuations() {
325
+ local fixture out expected actual status=0
326
+ command -v node >/dev/null 2>&1 || {
327
+ echo "skip: node not found for Pi continuation classification test"
328
+ return 0
329
+ }
330
+ fixture="$TMP_ROOT/pi-continuation-source"
331
+ mkdir -p "$fixture/.pi/extensions/lib" "$fixture/bin" "$fixture/state"
332
+ cp "$ROOT/.pi/extensions/fm-primary-turnend-guard.ts" "$fixture/.pi/extensions/"
333
+ cp "$ROOT/.pi/extensions/lib/fm-operational-input.ts" \
334
+ "$ROOT/.pi/extensions/lib/fm-sessionstart-supervisor.mjs" "$fixture/.pi/extensions/lib/"
335
+ cat > "$fixture/bin/fm-sessionstart-run.sh" <<'SH'
336
+ #!/usr/bin/env bash
337
+ source_name=
338
+ while [ $# -gt 0 ]; do
339
+ case "$1" in
340
+ --source) source_name=${2:-}; shift 2 ;;
341
+ *) shift ;;
342
+ esac
343
+ done
344
+ printf '%s\n' "$source_name" >> "${FM_HOME:?}/state/sources"
345
+ SH
346
+ cat > "$fixture/bin/fm-turnend-guard.sh" <<'SH'
347
+ #!/usr/bin/env bash
348
+ exit 0
349
+ SH
350
+ chmod +x "$fixture/bin/"*.sh
351
+
352
+ out=$(EXT="$fixture/.pi/extensions/fm-primary-turnend-guard.ts" \
353
+ FM_HOME="$fixture" FM_ROOT_OVERRIDE="$fixture" \
354
+ node --input-type=module 2>&1 <<'JS'
355
+ import { pathToFileURL } from "node:url";
356
+ const handlers = new Map();
357
+ const pi = {
358
+ on(event, handler) { handlers.set(event, handler); },
359
+ sendMessage() {},
360
+ };
361
+ const extension = await import(`${pathToFileURL(process.env.EXT).href}?continuation=${Date.now()}`);
362
+ extension.default(pi);
363
+ let sessionNumber = 0;
364
+ const fire = async (args, entries = [], timestamp = new Date().toISOString()) => {
365
+ process.argv.splice(1, process.argv.length, "pi", ...args);
366
+ const sessionId = `continuation-${++sessionNumber}`;
367
+ const ctx = {
368
+ sessionManager: {
369
+ getEntries: () => entries,
370
+ getHeader: () => ({ timestamp }),
371
+ getSessionId: () => sessionId,
372
+ },
373
+ };
374
+ handlers.get("session_start")({ reason: "startup" }, ctx);
375
+ await handlers.get("before_agent_start")({ prompt: "test" }, ctx);
376
+ };
377
+ const oldTimestamp = "2000-01-01T00:00:00.000Z";
378
+ const nameEntry = [{ type: "session_info", name: "named" }];
379
+ await fire([]);
380
+ await fire(["-c"]);
381
+ await fire(["--continue"], [{ type: "message" }], oldTimestamp);
382
+ await fire(["--resume"]);
383
+ await fire(["-r"], [{ type: "message" }], oldTimestamp);
384
+ await fire(["--session", "new-session"]);
385
+ await fire(["--session=existing-session"], [{ type: "message" }], oldTimestamp);
386
+ await fire(["--session-id", "new-id"]);
387
+ await fire(["--session-id=existing-id"], [{ type: "message" }], oldTimestamp);
388
+ await fire(["--session-id", "empty-existing-id"], [], oldTimestamp);
389
+ await fire(["-c", "--name", "new-named"], nameEntry);
390
+ await fire(["-c", "--name", "restored-named"], nameEntry, oldTimestamp);
391
+ await fire(["--session-id", "new-named-id", "--name", "new-named"], nameEntry);
392
+ await fire(["--session", "existing-named", "--name", "restored-named"], nameEntry, oldTimestamp);
393
+ await fire(["--fork=session-id"]);
394
+ await fire([], [{ type: "message" }], oldTimestamp);
395
+ JS
396
+ ) || status=$?
397
+ expect_code 0 "$status" "Pi continuation classification"
398
+ [ -z "$out" ] || fail "Pi continuation classification printed output: $out"
399
+ expected=$(printf '%s\n' \
400
+ 'startup' \
401
+ 'startup' \
402
+ 'resume' \
403
+ 'startup' \
404
+ 'resume' \
405
+ 'startup' \
406
+ 'resume' \
407
+ 'startup' \
408
+ 'resume' \
409
+ 'resume' \
410
+ 'startup' \
411
+ 'resume' \
412
+ 'startup' \
413
+ 'resume' \
414
+ 'fork' \
415
+ 'startup')
416
+ actual=$(cat "$fixture/state/sources")
417
+ [ "$actual" = "$expected" ] \
418
+ || fail "Pi continuation classification produced unexpected sources: $actual"
419
+ pass "Pi distinguishes header-proven restored CLI sessions from named create-if-missing startups"
420
+ }
421
+
422
+ test_pi_sessionstart_generation_prerequisite() {
423
+ local fixture out status=0
424
+ command -v node >/dev/null 2>&1 || {
425
+ echo "skip: node not found for Pi session-start generation prerequisite test"
426
+ return 0
427
+ }
428
+ fixture="$TMP_ROOT/pi-sessionstart-generation"
429
+ mkdir -p "$fixture/.pi/extensions/lib" "$fixture/bin" "$fixture/state"
430
+ cp "$ROOT/.pi/extensions/fm-primary-turnend-guard.ts" "$fixture/.pi/extensions/"
431
+ cp "$ROOT/.pi/extensions/lib/fm-operational-input.ts" \
432
+ "$ROOT/.pi/extensions/lib/fm-sessionstart-supervisor.mjs" "$fixture/.pi/extensions/lib/"
433
+ cp "$ROOT/bin/fm-operational-input.sh" "$fixture/bin/"
434
+ cat > "$fixture/bin/fm-turnend-guard.sh" <<'SH'
435
+ #!/usr/bin/env bash
436
+ exit 0
437
+ SH
438
+ cat > "$fixture/bin/fm-sessionstart-run.sh" <<'SH'
439
+ #!/usr/bin/env bash
440
+ set -u
441
+ state=${FM_HOME:?}/state
442
+ source_name=
443
+ while [ $# -gt 0 ]; do
444
+ case "$1" in
445
+ --source) source_name=${2:-}; shift 2 ;;
446
+ *) shift ;;
447
+ esac
448
+ done
449
+ count=$(( $(wc -l < "$state/launches" 2>/dev/null || printf '0') + 1 ))
450
+ behavior=$(cat "$state/behavior-$count")
451
+ printf 'launch:%s:%s:%s:%s:%s\n' \
452
+ "$count" "$behavior" "$source_name" "$$" "${FM_SESSIONSTART_SUPERVISOR_PID:-}" >> "$state/launches"
453
+ printf 'launch:%s\n' "$count" >> "$state/events"
454
+ case "$behavior" in
455
+ success)
456
+ printf 'GENERATION_DIGEST_%s source=%s\n' "$count" "$source_name"
457
+ : > "$state/completed-$count"
458
+ ;;
459
+ slow|timeout)
460
+ (
461
+ trap '' TERM INT
462
+ while :; do sleep 30; done
463
+ ) </dev/null >/dev/null 2>&1 &
464
+ grandchild=$!
465
+ printf '%s\n' "$grandchild" > "$state/grandchild-$count"
466
+ trap 'printf "term:'"$count"'\n" >> "$state/events"; exit 143' TERM INT
467
+ : > "$state/started-$count"
468
+ while [ ! -f "$state/release-$count" ]; do sleep 0.02; done
469
+ if [ "$behavior" = timeout ]; then
470
+ printf 'STARTUP TRUNCATED - stage=bootstrap generation=%s\n' "$count"
471
+ else
472
+ printf 'GENERATION_DIGEST_%s source=%s\n' "$count" "$source_name"
473
+ fi
474
+ : > "$state/completed-$count"
475
+ kill -KILL "$grandchild" 2>/dev/null || true
476
+ wait "$grandchild" 2>/dev/null || true
477
+ ;;
478
+ stale)
479
+ trap 'printf "stale-close:'"$count"'\n" >> "$state/events"; printf "STALE_DIGEST_'"$count"'\n"; : > "$state/completed-'"$count"'"; exit 0' TERM INT
480
+ : > "$state/started-$count"
481
+ while [ ! -f "$state/release-$count" ]; do sleep 0.02; done
482
+ printf 'STALE_DIGEST_%s\n' "$count"
483
+ : > "$state/completed-$count"
484
+ ;;
485
+ empty)
486
+ : > "$state/completed-$count"
487
+ ;;
488
+ ineligible)
489
+ : > "$state/completed-$count"
490
+ exit 3
491
+ ;;
492
+ error)
493
+ : > "$state/completed-$count"
494
+ exit 7
495
+ ;;
496
+ truncate)
497
+ printf 'TRUNCATION_PREFIX_%s\n' "$count"
498
+ i=0
499
+ while [ "$i" -lt 700 ]; do
500
+ printf '%01024d' 0
501
+ i=$((i + 1))
502
+ done
503
+ printf '\nTRUNCATION_SUFFIX_%s\n' "$count"
504
+ : > "$state/completed-$count"
505
+ ;;
506
+ *)
507
+ exit 9
508
+ ;;
509
+ esac
510
+ SH
511
+ chmod +x "$fixture/bin/"*.sh
512
+ : > "$fixture/state/launches"
513
+ : > "$fixture/state/events"
514
+
515
+ out=$(EXT="$fixture/.pi/extensions/fm-primary-turnend-guard.ts" \
516
+ FM_HOME="$fixture" FM_ROOT_OVERRIDE="$fixture" \
517
+ node --input-type=module 2>&1 <<'JS'
518
+ import {
519
+ existsSync,
520
+ readFileSync,
521
+ renameSync,
522
+ writeFileSync,
523
+ } from "node:fs";
524
+ import { pathToFileURL } from "node:url";
525
+
526
+ const state = `${process.env.FM_HOME}/state`;
527
+ const runner = `${process.env.FM_HOME}/bin/fm-sessionstart-run.sh`;
528
+ const handlers = new Map();
529
+ const sent = [];
530
+ const pi = {
531
+ on(event, handler) { handlers.set(event, handler); },
532
+ sendMessage(message) { sent.push(message); },
533
+ };
534
+ const extension = await import(`${pathToFileURL(process.env.EXT).href}?generation=${Date.now()}`);
535
+ extension.default(pi);
536
+ process.argv.splice(1, process.argv.length, "pi");
537
+
538
+ const assert = (condition, message) => {
539
+ if (!condition) throw new Error(message);
540
+ };
541
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
542
+ const waitFor = async (predicate, message) => {
543
+ for (let i = 0; i < 400; i += 1) {
544
+ if (predicate()) return;
545
+ await delay(5);
546
+ }
547
+ throw new Error(message);
548
+ };
549
+ const alive = (pid) => {
550
+ try { process.kill(Number(pid), 0); return true; } catch { return false; }
551
+ };
552
+ const waitDead = async (pid, message) => {
553
+ await waitFor(() => !alive(pid), message);
554
+ };
555
+ const ctx = (sessionId) => ({
556
+ sessionManager: {
557
+ getHeader: () => ({ timestamp: new Date().toISOString() }),
558
+ getSessionId: () => sessionId,
559
+ },
560
+ });
561
+ const begin = (reason, sessionId) => {
562
+ const current = ctx(sessionId);
563
+ handlers.get("session_start")({ reason }, current);
564
+ return current;
565
+ };
566
+ const providerCalls = [];
567
+ const providerCall = async (current, prompt) => {
568
+ const preflight = await handlers.get("before_agent_start")({ prompt }, current);
569
+ providerCalls.push({ prompt, message: preflight?.message });
570
+ return preflight;
571
+ };
572
+ const plan = (index, behavior) => {
573
+ writeFileSync(`${state}/behavior-${index}`, `${behavior}\n`);
574
+ };
575
+ const release = (index) => writeFileSync(`${state}/release-${index}`, "\n");
576
+ const launchLines = () => readFileSync(`${state}/launches`, "utf8").trim().split("\n").filter(Boolean);
577
+ const pidFor = (index) => Number(launchLines().find((line) => line.startsWith(`launch:${index}:`))?.split(":")[4]);
578
+ const supervisorFor = (index) => Number(launchLines().find((line) => line.startsWith(`launch:${index}:`))?.split(":")[5]);
579
+ const grandchildFor = (index) => Number(readFileSync(`${state}/grandchild-${index}`, "utf8").trim());
580
+ const startupMessages = () => providerCalls.map((call) => call.message).filter(Boolean);
581
+
582
+ // The failing immediate-prompt path is now a prerequisite: no provider call is
583
+ // observable until the matching slow native generation settles.
584
+ plan(1, "slow");
585
+ const immediate = begin("startup", "session-immediate");
586
+ await waitFor(() => existsSync(`${state}/started-1`), "immediate generation never started");
587
+ const immediateCall = providerCall(immediate, "immediate prompt");
588
+ await delay(100);
589
+ assert(providerCalls.length === 0, "provider call escaped before the startup prerequisite settled");
590
+ release(1);
591
+ const immediateResult = await immediateCall;
592
+ assert(immediateResult?.message?.content.includes("GENERATION_DIGEST_1"), "first payload lost matching startup context");
593
+ assert(launchLines().length === 1, "immediate generation executed more than once");
594
+ assert((await handlers.get("before_agent_start")({ prompt: "second prompt" }, immediate)) === undefined,
595
+ "one generation delivered startup context more than once");
596
+ assert(startupMessages().length === 1, "immediate generation produced more than one model-visible startup context");
597
+ assert(sent.length === 0, "session start still used asynchronous sendMessage delivery");
598
+
599
+ // Proven non-racing path: native completion before submission produces the same
600
+ // first-payload guarantee without a manual fallback.
601
+ plan(2, "success");
602
+ const proven = begin("new", "session-proven");
603
+ await waitFor(() => existsSync(`${state}/completed-2`), "proven generation never completed");
604
+ const provenResult = await providerCall(proven, "proven prompt");
605
+ assert(provenResult?.message?.content.includes("GENERATION_DIGEST_2"), "proven path lost startup context");
606
+ assert(!provenResult.message.content.includes("Run `bin/fm-session-start.sh`"), "proven path used manual fallback");
607
+ const provenSupervisor = supervisorFor(2);
608
+ assert(alive(provenSupervisor), "completed generation lost its stable supervisor owner");
609
+ const originalKill = process.kill;
610
+ let ownedGroupSignalCount = 0;
611
+ process.kill = (pid, signal) => {
612
+ if (pid === -provenSupervisor && signal !== 0) ownedGroupSignalCount += 1;
613
+ return Reflect.apply(originalKill, process, [pid, signal]);
614
+ };
615
+
616
+ // Shutdown owns interruption and the whole child process group. The pending
617
+ // preflight settles without stale delivery.
618
+ plan(3, "slow");
619
+ const interrupted = begin("new", "session-interrupted");
620
+ process.kill = originalKill;
621
+ assert(ownedGroupSignalCount > 0, "replacement did not retire the completed generation's stable owner");
622
+ await waitFor(() => existsSync(`${state}/started-3`) && existsSync(`${state}/grandchild-3`),
623
+ "interrupted generation never started its process tree");
624
+ const interruptedCall = handlers.get("before_agent_start")({ prompt: "interrupted" }, interrupted);
625
+ const interruptedPid = pidFor(3);
626
+ const interruptedGrandchild = grandchildFor(3);
627
+ await handlers.get("session_shutdown")({ reason: "new" }, interrupted);
628
+ assert((await interruptedCall) === undefined, "shutdown delivered cancelled startup context");
629
+ await waitDead(interruptedPid, "shutdown left the startup child alive");
630
+ await waitDead(interruptedGrandchild, "shutdown left a startup grandchild alive");
631
+
632
+ // Two rapid replacements serialize retirement. The middle generation never
633
+ // starts, stale completion is ignored, and only the newest generation delivers.
634
+ plan(4, "stale");
635
+ plan(5, "success");
636
+ const stale = begin("new", "session-stale");
637
+ await waitFor(() => existsSync(`${state}/started-4`), "stale generation never started");
638
+ const staleCall = handlers.get("before_agent_start")({ prompt: "stale" }, stale);
639
+ begin("new", "session-replaced-once");
640
+ const newest = begin("new", "session-replaced-twice");
641
+ const newestResult = await providerCall(newest, "newest");
642
+ assert(newestResult?.message?.content.includes("GENERATION_DIGEST_5"), "newest replacement lost its context");
643
+ assert((await staleCall) === undefined, "stale generation delivered after replacement");
644
+ assert(launchLines().length === 5, "rapid replacements launched the cancelled middle generation");
645
+ assert(!startupMessages().some((message) => message.content.includes("STALE_DIGEST_4")),
646
+ "stale completion reached model context");
647
+ const events = readFileSync(`${state}/events`, "utf8");
648
+ assert(events.indexOf("stale-close:4") < events.indexOf("launch:5"),
649
+ "newest generation started before stale child retirement completed");
650
+
651
+ // Empty output and spawn failure settle first, then inject the existing exact
652
+ // manual instruction. Intentional ineligibility remains silent.
653
+ plan(6, "empty");
654
+ const empty = begin("new", "session-empty");
655
+ const emptyResult = await providerCall(empty, "empty");
656
+ assert(existsSync(`${state}/completed-6`), "empty attempt had not settled before fallback delivery");
657
+ assert(emptyResult?.message?.content.includes("Run `bin/fm-session-start.sh` now, exactly once, before executing any other instructions."),
658
+ "empty output lost the exact manual fallback");
659
+
660
+ renameSync(runner, `${runner}.missing`);
661
+ const spawnError = begin("new", "session-spawn-error");
662
+ const spawnErrorResult = await providerCall(spawnError, "spawn error");
663
+ renameSync(`${runner}.missing`, runner);
664
+ assert(spawnErrorResult?.message?.content.includes("Run `bin/fm-session-start.sh` now, exactly once"),
665
+ "spawn error lost the manual fallback");
666
+
667
+ plan(7, "ineligible");
668
+ const ineligible = begin("new", "session-ineligible");
669
+ assert((await providerCall(ineligible, "ineligible")) === undefined,
670
+ "intentional gate/scope stand-down injected a manual fallback");
671
+
672
+ plan(8, "error");
673
+ const failed = begin("new", "session-failed");
674
+ const failedResult = await providerCall(failed, "failed");
675
+ assert(failedResult?.message?.content.includes("Run `bin/fm-session-start.sh` now, exactly once"),
676
+ "failed eligible attempt lost the manual fallback");
677
+
678
+ plan(9, "error");
679
+ const failedResume = begin("resume", "session-failed-resume");
680
+ const failedResumeResult = await providerCall(failedResume, "failed resume");
681
+ assert(failedResumeResult?.message?.content.includes("Run `bin/fm-session-start.sh` now, exactly once"),
682
+ "failed eligible resume lost the manual fallback");
683
+
684
+ plan(10, "error");
685
+ const failedFork = begin("fork", "session-failed-fork");
686
+ const failedForkResult = await providerCall(failedFork, "failed fork");
687
+ assert(failedForkResult?.message?.content.includes("Run `bin/fm-session-start.sh` now, exactly once"),
688
+ "failed eligible fork lost the manual fallback");
689
+
690
+ plan(11, "empty");
691
+ const emptyResume = begin("resume", "session-empty-resume");
692
+ assert((await providerCall(emptyResume, "empty resume")) === undefined,
693
+ "empty context-restored resume injected a manual fallback");
694
+
695
+ plan(12, "empty");
696
+ const emptyFork = begin("fork", "session-empty-fork");
697
+ assert((await providerCall(emptyFork, "empty fork")) === undefined,
698
+ "empty context-restored fork injected a manual fallback");
699
+
700
+ // The wrapper's bounded timeout result and the extension's 512 KiB containment
701
+ // remain loud provider prerequisites rather than falling back or going stale.
702
+ plan(13, "timeout");
703
+ const timed = begin("new", "session-timeout");
704
+ await waitFor(() => existsSync(`${state}/started-13`), "timeout generation never started");
705
+ const timedCall = providerCall(timed, "timeout");
706
+ await delay(100);
707
+ assert(!providerCalls.some((call) => call.prompt === "timeout"), "timeout provider call escaped before settlement");
708
+ release(13);
709
+ const timedResult = await timedCall;
710
+ assert(timedResult?.message?.content.includes("STARTUP TRUNCATED - stage=bootstrap"), "timeout banner was lost");
711
+ assert(!timedResult.message.content.includes("Run `bin/fm-session-start.sh`"), "bounded timeout incorrectly used manual fallback");
712
+
713
+ plan(14, "truncate");
714
+ const truncated = begin("new", "session-truncated");
715
+ const truncatedResult = await providerCall(truncated, "truncated");
716
+ assert(truncatedResult?.message?.content.includes("TRUNCATION_PREFIX_14"), "truncated digest lost its prefix");
717
+ assert(truncatedResult.message.content.includes("PI SESSION-START DELIVERY TRUNCATED"), "truncated digest was not loud");
718
+ assert(!truncatedResult.message.content.includes("TRUNCATION_SUFFIX_14"), "truncated digest exceeded its bound");
719
+
720
+ // Compaction keeps its supported asynchronous transport for retry-without-
721
+ // preflight, but it shares the same generation cancellation and exactly-once claim.
722
+ plan(15, "success");
723
+ const compactContext = ctx("session-truncated");
724
+ await handlers.get("session_compact")({}, compactContext);
725
+ assert(sent.length === 1 && sent[0].content.includes("GENERATION_DIGEST_15"),
726
+ "compaction lost its existing persistent delivery");
727
+ assert((await handlers.get("before_agent_start")({ prompt: "post compact" }, compactContext)) === undefined,
728
+ "compaction delivered the same context twice");
729
+
730
+ plan(16, "slow");
731
+ const compactPending = handlers.get("session_compact")({}, compactContext);
732
+ await waitFor(() => existsSync(`${state}/started-16`) && existsSync(`${state}/grandchild-16`),
733
+ "cancelled compaction generation never started");
734
+ const compactPid = pidFor(16);
735
+ const compactGrandchild = grandchildFor(16);
736
+ await handlers.get("session_shutdown")({ reason: "quit" }, compactContext);
737
+ await compactPending;
738
+ assert(sent.length === 1, "cancelled compaction delivered stale context");
739
+ await waitDead(compactPid, "shutdown left the compaction child alive");
740
+ await waitDead(compactGrandchild, "shutdown left a compaction grandchild alive");
741
+ JS
742
+ ) || status=$?
743
+ expect_code 0 "$status" "Pi session-start generation prerequisite"
744
+ [ -z "$out" ] || fail "Pi session-start generation prerequisite printed output: $out"
745
+ pass "Pi provider preflight owns one generation-bound startup prerequisite with deterministic fallback, replacement, cancellation, timeout, and truncation"
746
+ }
747
+
748
+ test_pi_reload_releases_sessionstart_exit_listener() {
749
+ local fixture out status=0
750
+ command -v node >/dev/null 2>&1 || {
751
+ echo "skip: node not found for Pi reload exit-listener test"
752
+ return 0
753
+ }
754
+ fixture="$TMP_ROOT/pi-reload-exit-listener"
755
+ mkdir -p "$fixture/.pi/extensions/lib" "$fixture/bin" "$fixture/state"
756
+ cp "$ROOT/.pi/extensions/fm-primary-turnend-guard.ts" "$fixture/.pi/extensions/"
757
+ cp "$ROOT/.pi/extensions/lib/fm-operational-input.ts" \
758
+ "$ROOT/.pi/extensions/lib/fm-sessionstart-supervisor.mjs" "$fixture/.pi/extensions/lib/"
759
+ cp "$ROOT/bin/fm-operational-input.sh" "$fixture/bin/"
760
+ cat > "$fixture/bin/fm-turnend-guard.sh" <<'SH'
761
+ #!/usr/bin/env bash
762
+ exit 0
763
+ SH
764
+ cat > "$fixture/bin/fm-sessionstart-run.sh" <<'SH'
765
+ #!/usr/bin/env bash
766
+ set -u
767
+ state=${FM_HOME:?}/state
768
+ index=$(( $(wc -l < "$state/launches" 2>/dev/null || printf '0') + 1 ))
769
+ printf '%s:%s:%s\n' "$index" "$$" "${FM_SESSIONSTART_SUPERVISOR_PID:-}" >> "$state/launches"
770
+ (
771
+ trap '' TERM INT HUP
772
+ while :; do sleep 30; done
773
+ ) </dev/null >/dev/null 2>&1 &
774
+ grandchild=$!
775
+ printf '%s\n' "$grandchild" > "$state/grandchild-$index"
776
+ : > "$state/started-$index"
777
+ if [ "$index" -eq 3 ]; then
778
+ exit 0
779
+ fi
780
+ trap 'exit 143' TERM INT
781
+ while :; do sleep 30; done
782
+ SH
783
+ chmod +x "$fixture/bin/"*.sh
784
+ : > "$fixture/state/launches"
785
+
786
+ out=$(EXT="$fixture/.pi/extensions/fm-primary-turnend-guard.ts" \
787
+ FM_HOME="$fixture" FM_ROOT_OVERRIDE="$fixture" \
788
+ node --input-type=module 2>&1 <<'JS'
789
+ import { existsSync, readFileSync } from "node:fs";
790
+ import { spawnSync } from "node:child_process";
791
+ import { pathToFileURL } from "node:url";
792
+
793
+ const state = `${process.env.FM_HOME}/state`;
794
+ const baselineListeners = process.listeners("exit");
795
+ const baselineCount = baselineListeners.length;
796
+ const assert = (condition, message) => {
797
+ if (!condition) throw new Error(message);
798
+ };
799
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
800
+ const waitFor = async (predicate, message) => {
801
+ for (let i = 0; i < 600; i += 1) {
802
+ if (predicate()) return;
803
+ await delay(5);
804
+ }
805
+ throw new Error(message);
806
+ };
807
+ const alive = (pid) => {
808
+ try { process.kill(Number(pid), 0); } catch { return false; }
809
+ const status = spawnSync("ps", ["-o", "stat=", "-p", String(pid)], { encoding: "utf8" });
810
+ return status.status === 0 && !status.stdout.trim().startsWith("Z");
811
+ };
812
+ const ctx = (sessionId) => ({
813
+ sessionManager: {
814
+ getHeader: () => ({ timestamp: new Date().toISOString() }),
815
+ getSessionId: () => sessionId,
816
+ },
817
+ });
818
+
819
+ let launchIndex = 0;
820
+ for (let instance = 1; instance <= 2; instance += 1) {
821
+ const handlers = new Map();
822
+ const pi = {
823
+ on(event, handler) { handlers.set(event, handler); },
824
+ sendMessage() {},
825
+ };
826
+ const extension = await import(
827
+ `${pathToFileURL(process.env.EXT).href}?reload=${instance}-${Date.now()}`
828
+ );
829
+ extension.default(pi);
830
+ assert(process.listenerCount("exit") === baselineCount + 1,
831
+ `reload ${instance} did not own exactly one exit listener`);
832
+ const sessionCount = instance === 1 ? 2 : 1;
833
+ for (let session = 1; session <= sessionCount; session += 1) {
834
+ launchIndex += 1;
835
+ const current = ctx(`reload-${instance}-session-${session}`);
836
+ handlers.get("session_start")({ reason: "new" }, current);
837
+ assert(process.listenerCount("exit") === baselineCount + 1,
838
+ `reload ${instance} session ${session} did not own exactly one exit listener`);
839
+ await waitFor(() => existsSync(`${state}/started-${launchIndex}`),
840
+ `reload ${instance} session ${session} startup generation never started`);
841
+ const launch = readFileSync(`${state}/launches`, "utf8").trim().split("\n")[launchIndex - 1];
842
+ const child = Number(launch.split(":")[1]);
843
+ const supervisor = Number(launch.split(":")[2]);
844
+ const grandchild = Number(readFileSync(`${state}/grandchild-${launchIndex}`, "utf8").trim());
845
+ if (launchIndex === 3) {
846
+ await waitFor(() => !alive(child), "leader did not close before process-exit cleanup");
847
+ assert(alive(grandchild), "TERM-resistant descendant exited with its leader");
848
+ assert(alive(supervisor), "leader close lost the stable supervisor owner");
849
+ assert(process.listenerCount("exit") === baselineCount + 1,
850
+ "leader close removed the active instance exit listener");
851
+ const ownedListener = process.listeners("exit").find(
852
+ (listener) => !baselineListeners.includes(listener)
853
+ );
854
+ assert(ownedListener, "active reload had no process-exit cleanup listener");
855
+ ownedListener(0);
856
+ await waitFor(() => !alive(grandchild),
857
+ "active process-exit cleanup left the leaderless process group alive");
858
+ }
859
+ await handlers.get("session_shutdown")({ reason: "reload" }, current);
860
+ assert(process.listenerCount("exit") === baselineCount,
861
+ `reload ${instance} session ${session} retained its exit listener after shutdown`);
862
+ assert((await handlers.get("before_agent_start")({ prompt: "stale" }, current)) === undefined,
863
+ `reload ${instance} session ${session} retained model-visible startup context after shutdown`);
864
+ assert(!alive(child) && !alive(grandchild),
865
+ `reload ${instance} session ${session} retained its stale startup process group`);
866
+ if (session < sessionCount) {
867
+ assert(process.listenerCount("exit") === baselineCount,
868
+ "same-instance replacement started with a stale exit listener");
869
+ }
870
+ }
871
+ }
872
+ JS
873
+ ) || status=$?
874
+ [ -z "$out" ] || fail "Pi reload exit-listener ownership printed output: $out"
875
+ expect_code 0 "$status" "Pi reload exit-listener ownership"
876
+ pass "Pi reload shutdown releases its exit listener and stale startup generation"
877
+ }
878
+
879
+ test_pi_large_sessionstart_digest_is_delivered_loudly() {
880
+ local fixture out status=0
881
+ command -v node >/dev/null 2>&1 || {
882
+ echo "skip: node not found for Pi large session-start delivery test"
883
+ return 0
884
+ }
885
+ fixture="$TMP_ROOT/pi-large-digest"
886
+ mkdir -p "$fixture/.pi/extensions/lib" "$fixture/bin" "$fixture/state" "$fixture/data" "$fixture/config"
887
+ git init -q -b main "$fixture"
888
+ git -C "$fixture" commit -q --allow-empty -m init
889
+ : > "$fixture/AGENTS.md"
890
+ cp "$ROOT/.pi/extensions/fm-primary-turnend-guard.ts" "$fixture/.pi/extensions/"
891
+ cp "$ROOT/.pi/extensions/lib/fm-operational-input.ts" \
892
+ "$ROOT/.pi/extensions/lib/fm-sessionstart-supervisor.mjs" "$fixture/.pi/extensions/lib/"
893
+ cp "$ROOT/bin/fm-sessionstart-run.sh" "$ROOT/bin/fm-sessionstart-nudge.sh" \
894
+ "$ROOT/bin/fm-primary-scope-lib.sh" "$ROOT/bin/fm-gate-refuse-lib.sh" \
895
+ "$ROOT/bin/fm-hook-host-lib.sh" \
896
+ "$ROOT/bin/fm-operational-input.sh" "$fixture/bin/"
897
+ cat > "$fixture/bin/fm-session-start.sh" <<'SH'
898
+ #!/usr/bin/env bash
899
+ printf 'PI_LARGE_DIGEST_PREFIX\n'
900
+ i=0
901
+ while [ "$i" -lt 700 ]; do
902
+ printf '%01024d' 0
903
+ i=$((i + 1))
904
+ done
905
+ printf '\nPI_LARGE_DIGEST_SUFFIX\n'
906
+ SH
907
+ chmod +x "$fixture/bin/"*.sh
908
+
909
+ out=$(EXT="$fixture/.pi/extensions/fm-primary-turnend-guard.ts" \
910
+ FM_HOME="$fixture" FM_ROOT_OVERRIDE="$fixture" FM_GATE_REFUSE_BYPASS=1 \
911
+ node --input-type=module 2>&1 <<'JS'
912
+ import { pathToFileURL } from "node:url";
913
+ const handlers = new Map();
914
+ const pi = {
915
+ on(event, handler) { handlers.set(event, handler); },
916
+ sendMessage() { throw new Error("session start must use provider preflight"); },
917
+ };
918
+ const extension = await import(`${pathToFileURL(process.env.EXT).href}?large=${Date.now()}`);
919
+ extension.default(pi);
920
+ const ctx = { sessionManager: { getEntries: () => [], getSessionId: () => "large-digest" } };
921
+ handlers.get("session_start")({ reason: "startup" }, ctx);
922
+ const result = await handlers.get("before_agent_start")({ prompt: "test" }, ctx);
923
+ if (!result?.message) throw new Error("expected one persistent preflight message");
924
+ const content = result.message.content;
925
+ if (!content.includes("PI_LARGE_DIGEST_PREFIX")) throw new Error("digest prefix was lost");
926
+ if (!content.includes("PI SESSION-START DELIVERY TRUNCATED")) throw new Error("truncation marker was lost");
927
+ if (content.includes("PI_LARGE_DIGEST_SUFFIX")) throw new Error("delivery exceeded its declared bound");
928
+ if (!content.includes("FIRSTMATE_OP: v1 session-start:")) throw new Error("operational provenance was lost");
929
+ JS
930
+ ) || status=$?
931
+ expect_code 0 "$status" "Pi large session-start delivery"
932
+ [ -z "$out" ] || fail "Pi large session-start delivery printed output: $out"
933
+ pass "Pi retains a bounded digest prefix and loudly marks oversized preflight delivery"
934
+ }
935
+
936
+ test_run_resume_delegates_to_the_nudge() {
937
+ local root="$TMP_ROOT/run-resume" out status=0
938
+ make_run_primary "$root"
939
+ out=$(run_hook "$root" --source resume </dev/null) || status=$?
940
+ expect_code 0 "$status" "run wrapper resume"
941
+ [ "$out" = "$NUDGE_LINE" ] || fail "resume did not delegate to the exact nudge line, got: $out"
942
+ assert_absent "$root/state/.lock" "resume acquired the fleet lock instead of delegating"
943
+ pass "run wrapper: resume delegates to the nudge instead of re-running the digest"
944
+ }
945
+
946
+ test_run_reads_source_from_the_hook_payload() {
947
+ local root="$TMP_ROOT/run-payload" out status=0
948
+ make_run_primary "$root"
949
+ run_hook "$root" --source startup </dev/null >/dev/null
950
+ out=$(printf '{"session_id":"s1","hook_event_name":"SessionStart","source":"compact"}' |
951
+ run_hook "$root") || status=$?
952
+ expect_code 0 "$status" "run wrapper payload compact"
953
+ assert_contains "$out" "$REEMIT_BANNER$root" "a compact hook payload was not routed to a re-emit"
954
+
955
+ # A fresh root, because the compact case above legitimately took the lock and
956
+ # an owned lock is exactly when the nudge is supposed to stay silent.
957
+ root="$TMP_ROOT/run-payload-resume"
958
+ make_run_primary "$root"
959
+ status=0
960
+ out=$(printf '{"source":"resume","cwd":"/nowhere"}' | run_hook "$root") || status=$?
961
+ expect_code 0 "$status" "run wrapper payload resume"
962
+ assert_contains "$out" "FIRSTMATE_OP" "a resume hook payload did not delegate to the nudge"
963
+ assert_not_contains "$out" "SESSION START" "a resume hook payload still ran the digest"
964
+ pass "run wrapper: the hook payload's source field drives routing with no explicit argument"
965
+ }
966
+
967
+ test_run_unknown_source_takes_the_helm() {
968
+ local root="$TMP_ROOT/run-unknown" out status=0
969
+ make_run_primary "$root"
970
+ out=$(run_hook "$root" --source somethingnew </dev/null) || status=$?
971
+ expect_code 0 "$status" "run wrapper unknown source"
972
+ assert_contains "$out" "$FULL_BANNER$root" "an unrecognized source did not fall through to the full digest"
973
+
974
+ status=0
975
+ out=$(printf '{"hook_event_name":"SessionStart"}' | run_hook "$root") || status=$?
976
+ expect_code 0 "$status" "run wrapper sourceless payload"
977
+ assert_contains "$out" "$FULL_BANNER$root" "a payload with no source did not fall through to the full digest"
978
+ pass "run wrapper: an unrecognized or absent source takes the helm rather than skipping it"
979
+ }
980
+
981
+ test_run_gate_and_scope_are_silent() {
982
+ local root="$TMP_ROOT/run-gate" base="$TMP_ROOT/run-linked-base" linked="$TMP_ROOT/run-linked"
983
+ local out status=0
984
+ make_run_primary "$root"
985
+ expect_silent_zero "gate env run" env NO_MISTAKES_GATE=1 FM_GATE_REFUSE_BYPASS=0 \
986
+ FM_ROOT_OVERRIDE="$root" FM_HOME="$root" PATH="$RUN_PATH" "$RUN" --source startup
987
+ assert_absent "$root/state/.lock" "a gate agent's session open still took the fleet lock"
988
+ out=$(env NO_MISTAKES_GATE=1 FM_GATE_REFUSE_BYPASS=0 \
989
+ FM_ROOT_OVERRIDE="$root" FM_HOME="$root" PATH="$RUN_PATH" \
990
+ "$RUN" --source startup --pi-prerequisite 2>&1) || status=$?
991
+ expect_code 3 "$status" "gate env Pi prerequisite stand-down"
992
+ [ -z "$out" ] || fail "gate env Pi prerequisite stand-down must be silent, got: $out"
993
+
994
+ fm_git_worktree "$base" "$linked" fm/run-linked
995
+ mkdir -p "$linked/bin" "$linked/state"
996
+ : > "$linked/AGENTS.md"
997
+ expect_silent_zero "linked worktree run" run_hook "$linked" --source startup
998
+ status=0
999
+ out=$(run_hook "$linked" --source startup --pi-prerequisite 2>&1) || status=$?
1000
+ expect_code 3 "$status" "linked worktree Pi prerequisite stand-down"
1001
+ [ -z "$out" ] || fail "linked worktree Pi prerequisite stand-down must be silent, got: $out"
1002
+ assert_absent "$linked/state/.lock" "an unmarked task worktree still took the fleet lock"
1003
+ pass "run wrapper: ordinary ineligible opens stay silent-zero and Pi preflight gets an explicit silent stand-down"
1004
+ }
1005
+
1006
+ test_run_reports_a_failed_session_start_as_digest_text() {
1007
+ local root="$TMP_ROOT/run-unwritable" out status=0
1008
+ make_run_primary "$root"
1009
+ chmod 0500 "$root/state"
1010
+ out=$(run_hook "$root" --source startup </dev/null) || status=$?
1011
+ chmod 0700 "$root/state"
1012
+ expect_code 0 "$status" "run wrapper with an unwritable state directory"
1013
+ assert_contains "$out" "READ-ONLY SESSION" "a failed lock did not reach the agent as digest text"
1014
+ pass "run wrapper: a session start that cannot take the lock still opens the session and says so"
1015
+ }
1016
+
1017
+ test_genuine_primary_nudges
1018
+ test_gate_env_is_silent
1019
+ test_gate_common_dir_is_silent
1020
+ test_unmarked_linked_worktree_is_silent
1021
+ test_linked_secondmate_primary_nudges
1022
+ test_missing_state_is_silent
1023
+ test_owned_lock_is_silent
1024
+ test_opencode_plugin_delivers_exact_nudge_once
1025
+ test_run_startup_runs_the_full_digest
1026
+ test_run_clear_and_compact_reemit
1027
+ test_run_rebuild_forwards_source_to_drifted_instruction_refresh
1028
+ test_run_compact_without_completion_refreshes_before_finishing_startup
1029
+ test_run_clear_without_completion_finishes_startup
1030
+ test_run_clear_rejects_previous_owner_completion
1031
+ test_run_resume_delegates_to_the_nudge
1032
+ test_run_reads_source_from_the_hook_payload
1033
+ test_run_unknown_source_takes_the_helm
1034
+ test_run_gate_and_scope_are_silent
1035
+ test_run_reports_a_failed_session_start_as_digest_text
1036
+ test_pi_startup_classifies_cli_continuations
1037
+ test_pi_sessionstart_generation_prerequisite
1038
+ test_pi_reload_releases_sessionstart_exit_listener
1039
+ test_pi_large_sessionstart_digest_is_delivered_loudly