@zerwiz/ymir 0.1.4 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (865) hide show
  1. package/.agents/README.md +291 -0
  2. package/.agents/agents/bragi-marketer.md +111 -0
  3. package/.agents/agents/brokk.md +352 -0
  4. package/.agents/agents/forseti-reviewer.md +93 -0
  5. package/.agents/agents/frigg-consent.md +77 -0
  6. package/.agents/agents/groa-update.md +77 -0
  7. package/.agents/agents/hnoss-designer.md +69 -0
  8. package/.agents/agents/huginn-researcher.md +99 -0
  9. package/.agents/agents/jord-projects.md +78 -0
  10. package/.agents/agents/kvasir-scout.md +91 -0
  11. package/.agents/agents/mimir-planner.md +92 -0
  12. package/.agents/agents/muninn-stow.md +77 -0
  13. package/.agents/agents/saga-bearings.md +78 -0
  14. package/.agents/agents/sindri-developer.md +114 -0
  15. package/.agents/agents/snotra-documenter.md +105 -0
  16. package/.agents/agents/syn-recovery.md +78 -0
  17. package/.agents/agents/tyr-check.md +78 -0
  18. package/.agents/agents/urdh-hold.md +77 -0
  19. package/.agents/agents/volundr-smith.md +80 -0
  20. package/.agents/agents/vor-diagnostics.md +77 -0
  21. package/.agents/assets/agents/naming.md +50 -0
  22. package/.agents/assets/agents/registry.md +93 -0
  23. package/.agents/assets/agents/runtime.md +36 -0
  24. package/.agents/assets/agents/toon-tasks-cli.md +42 -0
  25. package/.agents/assets/templates/company_entity.template.md +37 -0
  26. package/.agents/backend/README.md +74 -0
  27. package/.agents/backend/backends/cmux.sh +649 -0
  28. package/.agents/backend/backends/herdr-eventwait.py +157 -0
  29. package/.agents/backend/backends/herdr-workspace-move.py +114 -0
  30. package/.agents/backend/backends/herdr.sh +3386 -0
  31. package/.agents/backend/backends/orca.sh +290 -0
  32. package/.agents/backend/backends/tmux.sh +357 -0
  33. package/.agents/backend/backends/zellij.sh +700 -0
  34. package/.agents/backend/fm-afk-launch.sh +654 -0
  35. package/.agents/backend/fm-afk-return.sh +241 -0
  36. package/.agents/backend/fm-afk-start.sh +172 -0
  37. package/.agents/backend/fm-arm-command-policy.mjs +960 -0
  38. package/.agents/backend/fm-arm-pretool-check.sh +200 -0
  39. package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
  40. package/.agents/backend/fm-backend.sh +981 -0
  41. package/.agents/backend/fm-backlog-handoff.sh +909 -0
  42. package/.agents/backend/fm-backlog-receive.sh +187 -0
  43. package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
  44. package/.agents/backend/fm-bearings-board.sh +204 -0
  45. package/.agents/backend/fm-bearings-snapshot.sh +546 -0
  46. package/.agents/backend/fm-bootstrap.sh +1568 -0
  47. package/.agents/backend/fm-branch-outcome.sh +203 -0
  48. package/.agents/backend/fm-branch-prompt.sh +101 -0
  49. package/.agents/backend/fm-brief.sh +452 -0
  50. package/.agents/backend/fm-busy-event.sh +234 -0
  51. package/.agents/backend/fm-busy-lib.sh +986 -0
  52. package/.agents/backend/fm-captain-hold.sh +1001 -0
  53. package/.agents/backend/fm-cd-command-policy.mjs +153 -0
  54. package/.agents/backend/fm-cd-pretool-check.sh +190 -0
  55. package/.agents/backend/fm-check-lib.sh +74 -0
  56. package/.agents/backend/fm-check-register.sh +42 -0
  57. package/.agents/backend/fm-check-unregister.sh +52 -0
  58. package/.agents/backend/fm-classify-lib.sh +1770 -0
  59. package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
  60. package/.agents/backend/fm-composer-lib.sh +1416 -0
  61. package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
  62. package/.agents/backend/fm-config-push.sh +254 -0
  63. package/.agents/backend/fm-control-lib.sh +251 -0
  64. package/.agents/backend/fm-control.sh +878 -0
  65. package/.agents/backend/fm-crew-state.sh +625 -0
  66. package/.agents/backend/fm-cursor-lib.sh +243 -0
  67. package/.agents/backend/fm-decision-hold.sh +232 -0
  68. package/.agents/backend/fm-doc-audience-check.sh +269 -0
  69. package/.agents/backend/fm-dod-lib.sh +67 -0
  70. package/.agents/backend/fm-ensure-agents-md.sh +253 -0
  71. package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
  72. package/.agents/backend/fm-extension.mjs +2577 -0
  73. package/.agents/backend/fm-extension.sh +16 -0
  74. package/.agents/backend/fm-ff-lib.sh +420 -0
  75. package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
  76. package/.agents/backend/fm-fleet-sync.sh +458 -0
  77. package/.agents/backend/fm-fleet-view.sh +96 -0
  78. package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
  79. package/.agents/backend/fm-guard.sh +242 -0
  80. package/.agents/backend/fm-harness.sh +195 -0
  81. package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
  82. package/.agents/backend/fm-herdr-lab.sh +346 -0
  83. package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
  84. package/.agents/backend/fm-home-seed.sh +984 -0
  85. package/.agents/backend/fm-home-summary-refresh.sh +255 -0
  86. package/.agents/backend/fm-hook-host-lib.sh +36 -0
  87. package/.agents/backend/fm-inactive-reconcile.sh +531 -0
  88. package/.agents/backend/fm-inbox.sh +399 -0
  89. package/.agents/backend/fm-install-actionlint.sh +84 -0
  90. package/.agents/backend/fm-install-herdr.sh +96 -0
  91. package/.agents/backend/fm-install-shellcheck.sh +84 -0
  92. package/.agents/backend/fm-install-treehouse.sh +95 -0
  93. package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
  94. package/.agents/backend/fm-lease-lib.sh +218 -0
  95. package/.agents/backend/fm-lease.sh +189 -0
  96. package/.agents/backend/fm-line-cap-lib.sh +51 -0
  97. package/.agents/backend/fm-lint-workflows.sh +137 -0
  98. package/.agents/backend/fm-lint.sh +591 -0
  99. package/.agents/backend/fm-lock-lib.sh +104 -0
  100. package/.agents/backend/fm-lock.sh +107 -0
  101. package/.agents/backend/fm-marker-lib.sh +12 -0
  102. package/.agents/backend/fm-merge-local.sh +74 -0
  103. package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
  104. package/.agents/backend/fm-nm-run-lib.sh +124 -0
  105. package/.agents/backend/fm-on.sh +125 -0
  106. package/.agents/backend/fm-operational-input.sh +253 -0
  107. package/.agents/backend/fm-peek.sh +46 -0
  108. package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
  109. package/.agents/backend/fm-pr-check.sh +135 -0
  110. package/.agents/backend/fm-pr-lib.sh +1016 -0
  111. package/.agents/backend/fm-pr-merge.sh +707 -0
  112. package/.agents/backend/fm-pr-poll.sh +110 -0
  113. package/.agents/backend/fm-primary-scope-lib.sh +33 -0
  114. package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
  115. package/.agents/backend/fm-procevent-lavish.sh +628 -0
  116. package/.agents/backend/fm-procevent-lib.sh +892 -0
  117. package/.agents/backend/fm-procevent-quota.sh +290 -0
  118. package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
  119. package/.agents/backend/fm-procevent-when.sh +504 -0
  120. package/.agents/backend/fm-procevent.sh +1466 -0
  121. package/.agents/backend/fm-project-mode.sh +92 -0
  122. package/.agents/backend/fm-project-origin-lib.sh +180 -0
  123. package/.agents/backend/fm-promote.sh +282 -0
  124. package/.agents/backend/fm-public-followup-emit.sh +260 -0
  125. package/.agents/backend/fm-public-followup-lib.sh +398 -0
  126. package/.agents/backend/fm-public-followup.sh +1330 -0
  127. package/.agents/backend/fm-push-transition-lib.sh +168 -0
  128. package/.agents/backend/fm-quota-axi-lib.sh +93 -0
  129. package/.agents/backend/fm-quota-choose.sh +384 -0
  130. package/.agents/backend/fm-remote-delta-read.sh +189 -0
  131. package/.agents/backend/fm-remote-doctor.sh +800 -0
  132. package/.agents/backend/fm-remote-entrypoint.sh +193 -0
  133. package/.agents/backend/fm-remote-file.sh +234 -0
  134. package/.agents/backend/fm-remote-home-provision.sh +259 -0
  135. package/.agents/backend/fm-remote-home-seed.sh +259 -0
  136. package/.agents/backend/fm-remote-inherit-push.sh +90 -0
  137. package/.agents/backend/fm-remote-inherit.sh +179 -0
  138. package/.agents/backend/fm-remote-job-lib.sh +1241 -0
  139. package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
  140. package/.agents/backend/fm-remote-job-worker.sh +1124 -0
  141. package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
  142. package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
  143. package/.agents/backend/fm-review-diff.sh +158 -0
  144. package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
  145. package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
  146. package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
  147. package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
  148. package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
  149. package/.agents/backend/fm-secondmate-report.sh +88 -0
  150. package/.agents/backend/fm-send.sh +1059 -0
  151. package/.agents/backend/fm-session-lock-lib.sh +176 -0
  152. package/.agents/backend/fm-session-start.sh +970 -0
  153. package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
  154. package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
  155. package/.agents/backend/fm-sessionstart-run.sh +145 -0
  156. package/.agents/backend/fm-spawn.sh +3153 -0
  157. package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
  158. package/.agents/backend/fm-startup-memory-budget.sh +94 -0
  159. package/.agents/backend/fm-startup-network.sh +642 -0
  160. package/.agents/backend/fm-stow-cascade.sh +251 -0
  161. package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
  162. package/.agents/backend/fm-supervise-daemon.sh +1732 -0
  163. package/.agents/backend/fm-supervision-instructions.sh +215 -0
  164. package/.agents/backend/fm-supervision-lib.sh +90 -0
  165. package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
  166. package/.agents/backend/fm-tangle-lib.sh +53 -0
  167. package/.agents/backend/fm-task-inbox-lib.sh +402 -0
  168. package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
  169. package/.agents/backend/fm-teardown.sh +2922 -0
  170. package/.agents/backend/fm-test-isolation-proof.sh +588 -0
  171. package/.agents/backend/fm-test-run.sh +2191 -0
  172. package/.agents/backend/fm-timeout-lib.sh +141 -0
  173. package/.agents/backend/fm-timing-lib.sh +179 -0
  174. package/.agents/backend/fm-tmux-lib.sh +291 -0
  175. package/.agents/backend/fm-tool-update-check.sh +898 -0
  176. package/.agents/backend/fm-trace-context-lib.sh +227 -0
  177. package/.agents/backend/fm-transition-lib.sh +103 -0
  178. package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
  179. package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
  180. package/.agents/backend/fm-turnend-guard.sh +432 -0
  181. package/.agents/backend/fm-update.sh +110 -0
  182. package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
  183. package/.agents/backend/fm-voice-client.py +1373 -0
  184. package/.agents/backend/fm-voice-relay.py +1256 -0
  185. package/.agents/backend/fm-wake-drain.sh +592 -0
  186. package/.agents/backend/fm-wake-grant.sh +127 -0
  187. package/.agents/backend/fm-wake-lib.sh +1849 -0
  188. package/.agents/backend/fm-watch-arm.sh +602 -0
  189. package/.agents/backend/fm-watch-checkpoint.sh +109 -0
  190. package/.agents/backend/fm-watch.sh +1962 -0
  191. package/.agents/backend/fm-x-dismiss.sh +116 -0
  192. package/.agents/backend/fm-x-followup.sh +286 -0
  193. package/.agents/backend/fm-x-lib.sh +1000 -0
  194. package/.agents/backend/fm-x-link.sh +234 -0
  195. package/.agents/backend/fm-x-poll.sh +185 -0
  196. package/.agents/backend/fm-x-reply.sh +385 -0
  197. package/.agents/backend/fm_voice_frame.py +166 -0
  198. package/.agents/backend/fm_voice_records.py +574 -0
  199. package/.agents/backend/model-bridge.py +360 -0
  200. package/.agents/backend/opencode-go-bridge.py +30 -0
  201. package/.agents/bus/README.md +12 -0
  202. package/.agents/bus/messages.json +1 -0
  203. package/.agents/config/agents.machine.example.yaml +16 -0
  204. package/.agents/config/agents.yaml.example +72 -0
  205. package/.agents/config/crew-dispatch.json +22 -0
  206. package/.agents/config/cron.yaml +11 -0
  207. package/.agents/config/eindri-dispatch.json +67 -0
  208. package/.agents/config/eindri-harness +1 -0
  209. package/.agents/config/startup-memory-budget +1 -0
  210. package/.agents/filebrowser/.filebrowser.json +6 -0
  211. package/.agents/filebrowser/README.md +11 -0
  212. package/.agents/gateway/README.md +12 -0
  213. package/.agents/github/README.md +15 -0
  214. package/.agents/github/webhooks/.gitkeep +0 -0
  215. package/.agents/github/workflows/.gitkeep +0 -0
  216. package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
  217. package/.agents/harness/opencode/plugins/package.json +4 -0
  218. package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
  219. package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
  220. package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
  221. package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
  222. package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
  223. package/.agents/memory/README.md +46 -0
  224. package/.agents/memory/runes_audit.md +5 -0
  225. package/.agents/migrations/0001-hodd-layout.sh +38 -0
  226. package/.agents/migrations/0002-a2a-mcp.sh +27 -0
  227. package/.agents/migrations/0003-private-data-separation.sh +121 -0
  228. package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
  229. package/.agents/migrations/0005-roots-out-of-tree.sh +158 -0
  230. package/.agents/sandbox/Dockerfile.utgard +19 -0
  231. package/.agents/sandbox/README.md +14 -0
  232. package/.agents/sandbox/utgard.config.json +14 -0
  233. package/.agents/skills/README.md +59 -0
  234. package/.agents/skills/bragi-marketing/README.md +3 -0
  235. package/.agents/skills/bragi-marketing/SKILL.md +82 -0
  236. package/.agents/skills/eindri-homes/README.md +3 -0
  237. package/.agents/skills/eindri-homes/SKILL.md +291 -0
  238. package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
  239. package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
  240. package/.agents/skills/frigg-consent/README.md +3 -0
  241. package/.agents/skills/frigg-consent/SKILL.md +56 -0
  242. package/.agents/skills/galdr-ymirsystem/README.md +3 -0
  243. package/.agents/skills/galdr-ymirsystem/SKILL.md +159 -0
  244. package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
  245. package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
  246. package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
  247. package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
  248. package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
  249. package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
  250. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +736 -0
  251. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
  252. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
  253. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
  254. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
  255. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
  256. package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +539 -0
  257. package/.agents/skills/galdr-ymirsystem/assets/installation.md +763 -0
  258. package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
  259. package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
  260. package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +163 -0
  261. package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +415 -0
  262. package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
  263. package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +133 -0
  264. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
  265. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
  266. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +101 -0
  267. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
  268. package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
  269. package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
  270. package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
  271. package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
  272. package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
  273. package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
  274. package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
  275. package/.agents/skills/galdr-ymirsystem/assets/smidja.md +180 -0
  276. package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
  277. package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
  278. package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
  279. package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +503 -0
  280. package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
  281. package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
  282. package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
  283. package/.agents/skills/gjallarhorn-relay/README.md +3 -0
  284. package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
  285. package/.agents/skills/groa-update/SKILL.md +90 -0
  286. package/.agents/skills/hamr-adapters/README.md +3 -0
  287. package/.agents/skills/hamr-adapters/SKILL.md +123 -0
  288. package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
  289. package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
  290. package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
  291. package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
  292. package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
  293. package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
  294. package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
  295. package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
  296. package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
  297. package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
  298. package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
  299. package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
  300. package/.agents/skills/herdr-panes/README.md +3 -0
  301. package/.agents/skills/herdr-panes/SKILL.md +92 -0
  302. package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
  303. package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
  304. package/.agents/skills/hnoss-design/DESIGN.md +19 -0
  305. package/.agents/skills/hnoss-design/README.md +3 -0
  306. package/.agents/skills/hnoss-design/SKILL.md +83 -0
  307. package/.agents/skills/hvild-afk/README.md +3 -0
  308. package/.agents/skills/hvild-afk/SKILL.md +241 -0
  309. package/.agents/skills/jord-projects/README.md +3 -0
  310. package/.agents/skills/jord-projects/SKILL.md +92 -0
  311. package/.agents/skills/lifecycle/SKILL.md +56 -0
  312. package/.agents/skills/lifecycle/smoke_test.sh +57 -0
  313. package/.agents/skills/lifecycle/start.sh +21 -0
  314. package/.agents/skills/lifecycle/status.sh +35 -0
  315. package/.agents/skills/lifecycle/stop.sh +16 -0
  316. package/.agents/skills/muninn-stow/README.md +3 -0
  317. package/.agents/skills/muninn-stow/SKILL.md +309 -0
  318. package/.agents/skills/no-mistakes/SKILL.md +379 -0
  319. package/.agents/skills/nornir-schedule/README.md +3 -0
  320. package/.agents/skills/nornir-schedule/SKILL.md +25 -0
  321. package/.agents/skills/nornir-schedule/assets/events.md +137 -0
  322. package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
  323. package/.agents/skills/nsr-compliance/README.md +3 -0
  324. package/.agents/skills/nsr-compliance/SKILL.md +29 -0
  325. package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
  326. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
  327. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
  328. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
  329. package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
  330. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
  331. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
  332. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
  333. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
  334. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
  335. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
  336. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
  337. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
  338. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
  339. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
  340. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
  341. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
  342. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
  343. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
  344. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
  345. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
  346. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
  347. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
  348. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  349. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
  350. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
  351. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
  352. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
  353. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
  354. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
  355. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
  356. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
  357. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
  358. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
  359. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
  360. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
  361. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
  362. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
  363. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
  364. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
  365. package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
  366. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
  367. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
  368. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
  369. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
  370. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
  371. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
  372. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
  373. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
  374. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
  375. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
  376. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
  377. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
  378. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
  379. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
  380. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
  381. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
  382. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
  383. package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
  384. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
  385. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
  386. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
  387. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
  388. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
  389. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
  390. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
  391. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
  392. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
  393. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
  394. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
  395. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
  396. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
  397. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
  398. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
  399. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
  400. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
  401. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
  402. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
  403. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
  404. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
  405. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
  406. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
  407. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
  408. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
  409. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
  410. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
  411. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
  412. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
  413. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
  414. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
  415. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
  416. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
  417. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
  418. package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
  419. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
  420. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
  421. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
  422. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
  423. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
  424. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
  425. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
  426. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
  427. package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
  428. package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
  429. package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
  430. package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
  431. package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
  432. package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
  433. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
  434. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
  435. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
  436. package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
  437. package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
  438. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
  439. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
  440. package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
  441. package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
  442. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
  443. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
  444. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
  445. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
  446. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
  447. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
  448. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
  449. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
  450. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
  451. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
  452. package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
  453. package/.agents/skills/pr-ops/README.md +3 -0
  454. package/.agents/skills/pr-ops/SKILL.md +109 -0
  455. package/.agents/skills/ratatoskr-a2a/README.md +3 -0
  456. package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
  457. package/.agents/skills/rules-check-drift/SKILL.md +81 -0
  458. package/.agents/skills/saga-bearings/README.md +3 -0
  459. package/.agents/skills/saga-bearings/SKILL.md +26 -0
  460. package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
  461. package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
  462. package/.agents/skills/saga-bearings/assets/recap.md +56 -0
  463. package/.agents/skills/syn-recovery/README.md +3 -0
  464. package/.agents/skills/syn-recovery/SKILL.md +54 -0
  465. package/.agents/skills/tyr-check/README.md +3 -0
  466. package/.agents/skills/tyr-check/SKILL.md +218 -0
  467. package/.agents/skills/urdh-hold/README.md +3 -0
  468. package/.agents/skills/urdh-hold/SKILL.md +25 -0
  469. package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
  470. package/.agents/skills/urdh-hold/assets/hold.md +54 -0
  471. package/.agents/skills/vor-diagnostics/README.md +3 -0
  472. package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
  473. package/.agents/skills/ymir-host/README.md +3 -0
  474. package/.agents/skills/ymir-host/SKILL.md +35 -0
  475. package/.agents/skills/ymir-host/assets/desktop.md +36 -0
  476. package/.agents/skills/ymir-host/assets/install.md +69 -0
  477. package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
  478. package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
  479. package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
  480. package/.agents/skills/ymir-host/assets/update.md +64 -0
  481. package/.agents/tests/assets/board-render-harness.mjs +117 -0
  482. package/.agents/tests/cmux-test-safety.sh +51 -0
  483. package/.agents/tests/fixtures.sh +291 -0
  484. package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
  485. package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
  486. package/.agents/tests/fm-afk-launch.test.sh +961 -0
  487. package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
  488. package/.agents/tests/fm-afk-return.test.sh +283 -0
  489. package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
  490. package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
  491. package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
  492. package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
  493. package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
  494. package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
  495. package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
  496. package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
  497. package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
  498. package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
  499. package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
  500. package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
  501. package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
  502. package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
  503. package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
  504. package/.agents/tests/fm-backend-orca.test.sh +1357 -0
  505. package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
  506. package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
  507. package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
  508. package/.agents/tests/fm-backend.test.sh +1142 -0
  509. package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
  510. package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
  511. package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
  512. package/.agents/tests/fm-bearings-board.test.sh +414 -0
  513. package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
  514. package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
  515. package/.agents/tests/fm-bootstrap.test.sh +1178 -0
  516. package/.agents/tests/fm-branch-supervision.test.sh +552 -0
  517. package/.agents/tests/fm-brief.test.sh +785 -0
  518. package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
  519. package/.agents/tests/fm-busy-state.test.sh +465 -0
  520. package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
  521. package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
  522. package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
  523. package/.agents/tests/fm-check-unregister.test.sh +198 -0
  524. package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
  525. package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
  526. package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
  527. package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
  528. package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
  529. package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
  530. package/.agents/tests/fm-composer-ghost.test.sh +716 -0
  531. package/.agents/tests/fm-composer-lib.test.sh +666 -0
  532. package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
  533. package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
  534. package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
  535. package/.agents/tests/fm-control.test.sh +910 -0
  536. package/.agents/tests/fm-crew-state.test.sh +1610 -0
  537. package/.agents/tests/fm-cursor-harness.test.sh +404 -0
  538. package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
  539. package/.agents/tests/fm-cursor-primary.test.sh +708 -0
  540. package/.agents/tests/fm-daemon.test.sh +2740 -0
  541. package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
  542. package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
  543. package/.agents/tests/fm-extension-binding.test.sh +2187 -0
  544. package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
  545. package/.agents/tests/fm-fleet-sync.test.sh +721 -0
  546. package/.agents/tests/fm-gate-refuse.test.sh +356 -0
  547. package/.agents/tests/fm-gitignore-config.test.sh +89 -0
  548. package/.agents/tests/fm-gotmp.test.sh +230 -0
  549. package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
  550. package/.agents/tests/fm-grok-harness.test.sh +117 -0
  551. package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
  552. package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
  553. package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
  554. package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
  555. package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
  556. package/.agents/tests/fm-herdr-lab.test.sh +243 -0
  557. package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
  558. package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
  559. package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
  560. package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
  561. package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
  562. package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
  563. package/.agents/tests/fm-kimi-harness.test.sh +687 -0
  564. package/.agents/tests/fm-lint-workflows.test.sh +532 -0
  565. package/.agents/tests/fm-lint.test.sh +1023 -0
  566. package/.agents/tests/fm-muse-harness.test.sh +932 -0
  567. package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
  568. package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
  569. package/.agents/tests/fm-on.test.sh +507 -0
  570. package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
  571. package/.agents/tests/fm-operational-input.test.sh +160 -0
  572. package/.agents/tests/fm-peek-remote.test.sh +110 -0
  573. package/.agents/tests/fm-pending-reply.test.sh +1288 -0
  574. package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
  575. package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
  576. package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
  577. package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
  578. package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
  579. package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
  580. package/.agents/tests/fm-pr-merge.test.sh +2135 -0
  581. package/.agents/tests/fm-procevent-quota.test.sh +225 -0
  582. package/.agents/tests/fm-procevent-when.test.sh +406 -0
  583. package/.agents/tests/fm-procevent.test.sh +1745 -0
  584. package/.agents/tests/fm-project-origin.test.sh +110 -0
  585. package/.agents/tests/fm-public-followup.test.sh +2272 -0
  586. package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
  587. package/.agents/tests/fm-quota-choose.test.sh +626 -0
  588. package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
  589. package/.agents/tests/fm-remote-doctor.test.sh +625 -0
  590. package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
  591. package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
  592. package/.agents/tests/fm-remote-job.test.sh +768 -0
  593. package/.agents/tests/fm-remote-reply.test.sh +520 -0
  594. package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
  595. package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
  596. package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
  597. package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
  598. package/.agents/tests/fm-review-diff.test.sh +176 -0
  599. package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
  600. package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
  601. package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
  602. package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
  603. package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
  604. package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
  605. package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
  606. package/.agents/tests/fm-send-inbox.test.sh +352 -0
  607. package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
  608. package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
  609. package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
  610. package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
  611. package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
  612. package/.agents/tests/fm-send-settle.test.sh +147 -0
  613. package/.agents/tests/fm-send-strict.test.sh +241 -0
  614. package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
  615. package/.agents/tests/fm-session-start.test.sh +2515 -0
  616. package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
  617. package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
  618. package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
  619. package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
  620. package/.agents/tests/fm-spawn-batch.test.sh +148 -0
  621. package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
  622. package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
  623. package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
  624. package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
  625. package/.agents/tests/fm-startup-network.test.sh +712 -0
  626. package/.agents/tests/fm-stow-cascade.test.sh +370 -0
  627. package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
  628. package/.agents/tests/fm-supervision-events.test.sh +156 -0
  629. package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
  630. package/.agents/tests/fm-tangle-guard.test.sh +277 -0
  631. package/.agents/tests/fm-task-delivery.test.sh +420 -0
  632. package/.agents/tests/fm-task-inbox.test.sh +513 -0
  633. package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
  634. package/.agents/tests/fm-teardown.test.sh +2665 -0
  635. package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
  636. package/.agents/tests/fm-test-fixtures.test.sh +132 -0
  637. package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
  638. package/.agents/tests/fm-test-run.test.sh +1196 -0
  639. package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
  640. package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
  641. package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
  642. package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
  643. package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
  644. package/.agents/tests/fm-transition-lib.test.sh +54 -0
  645. package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
  646. package/.agents/tests/fm-update.test.sh +304 -0
  647. package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
  648. package/.agents/tests/fm-voice-relay.test.sh +4741 -0
  649. package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
  650. package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
  651. package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
  652. package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
  653. package/.agents/tests/fm-wake-queue.test.sh +1224 -0
  654. package/.agents/tests/fm-watch-arm.test.sh +821 -0
  655. package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
  656. package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
  657. package/.agents/tests/fm-watch-triage.test.sh +3889 -0
  658. package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
  659. package/.agents/tests/fm-x-mode.test.sh +3042 -0
  660. package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
  661. package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
  662. package/.agents/tests/herdr-test-safety.sh +42 -0
  663. package/.agents/tests/lib.sh +315 -0
  664. package/.agents/tests/remote-herdr-fixture.sh +125 -0
  665. package/.agents/tests/secondmate-helpers.sh +193 -0
  666. package/.agents/tests/smoke.test.sh +93 -0
  667. package/.agents/tests/wake-helpers.sh +338 -0
  668. package/.agents/tests/zellij-test-safety.sh +60 -0
  669. package/.agents/tools/README.md +10 -0
  670. package/.agents/tools/bin/tasks-cli +267 -0
  671. package/.agents/tools/tasks-cli.ts +216 -0
  672. package/AGENTS.md +498 -0
  673. package/CHANGELOG.md +1913 -0
  674. package/LICENSE +1 -1
  675. package/NOTICE +63 -0
  676. package/README.md +114 -60
  677. package/RULES/01-domains.md +51 -0
  678. package/RULES/02-agents.md +78 -0
  679. package/RULES/03-houses.md +61 -0
  680. package/RULES/04-hoard.md +97 -0
  681. package/RULES/05-platforms.md +67 -0
  682. package/RULES/06-append-only.md +108 -0
  683. package/RULES/07-config.md +101 -0
  684. package/RULES/08-delivery-gate.md +75 -0
  685. package/RULES/09-electron.md +58 -0
  686. package/RULES/README.md +22 -0
  687. package/bin/README.md +107 -0
  688. package/bin/a2a-mcp.sh +133 -0
  689. package/bin/a2a-serve.py +103 -0
  690. package/bin/a2a-serve.sh +8 -0
  691. package/bin/a2a-talk.sh +50 -0
  692. package/bin/a2abridge-ensure.sh +108 -0
  693. package/bin/agent-run.sh +43 -0
  694. package/bin/agents-config.sh +256 -0
  695. package/bin/apodex-smoke-test.sh +105 -0
  696. package/bin/bifrost-bridge.sh +131 -0
  697. package/bin/bifrost-ingress.sh +77 -0
  698. package/bin/bootstrap-macos.sh +112 -0
  699. package/bin/bootstrap-windows.ps1 +112 -0
  700. package/bin/branch-guard.sh +64 -0
  701. package/bin/brokk +73 -0
  702. package/bin/brokk-classify-lib.sh +1770 -0
  703. package/bin/brokk-lease-lib.sh +222 -0
  704. package/bin/brokk-lease.sh +193 -0
  705. package/bin/brokk-lint.sh +65 -0
  706. package/bin/brokk-send.sh +51 -0
  707. package/bin/brokk-update.sh +9 -0
  708. package/bin/brokk-wake-grant.sh +127 -0
  709. package/bin/brokk-wake-lib.sh +1849 -0
  710. package/bin/changelog-assemble.sh +154 -0
  711. package/bin/changelog-guard.sh +197 -0
  712. package/bin/crash-sense.sh +173 -0
  713. package/bin/design-check.sh +80 -0
  714. package/bin/design-icon.sh +153 -0
  715. package/bin/desktop-place.sh +224 -0
  716. package/bin/docs-guard.sh +39 -0
  717. package/bin/editor-place.sh +225 -0
  718. package/bin/eindri-acclaim.sh +47 -0
  719. package/bin/eindri-control.sh +28 -0
  720. package/bin/eindri-role.sh +83 -0
  721. package/bin/eindri-seat.sh +83 -0
  722. package/bin/eindri-seen.sh +54 -0
  723. package/bin/eindri-send.sh +15 -0
  724. package/bin/eindri-start.sh +98 -0
  725. package/bin/eindri-watch.sh +57 -0
  726. package/bin/einherjar-spawn.sh +576 -0
  727. package/bin/eir-doctor.sh +158 -0
  728. package/bin/erindi-brief.sh +271 -0
  729. package/bin/feature-inventory.sh +73 -0
  730. package/bin/fleet-apply.sh +81 -0
  731. package/bin/github-deploy.sh +94 -0
  732. package/bin/gjallarhorn-expose.sh +136 -0
  733. package/bin/gjallarhorn-notify.sh +71 -0
  734. package/bin/gjallarhorn-purge.sh +67 -0
  735. package/bin/gjallarhorn-tunnel.sh +85 -0
  736. package/bin/gleipnir-lock-lib.sh +284 -0
  737. package/bin/groa-update.sh +130 -0
  738. package/bin/hall-snapshot.sh +111 -0
  739. package/bin/hamr-harness.sh +246 -0
  740. package/bin/heimdall-ssh-keys.sh +374 -0
  741. package/bin/herdr-agents.py +44 -0
  742. package/bin/herdr-ensure.sh +104 -0
  743. package/bin/herdr-run.sh +452 -0
  744. package/bin/hermes-ensure.sh +88 -0
  745. package/bin/hlidskjalf-agents.sh +172 -0
  746. package/bin/hlidskjalf-usage.sh +196 -0
  747. package/bin/hoard-guard.sh +135 -0
  748. package/bin/hoard-lib.sh +105 -0
  749. package/bin/hodd.sh +102 -0
  750. package/bin/host-sense.sh +199 -0
  751. package/bin/huginn-research-worker.sh +121 -0
  752. package/bin/local-model-lock.sh +70 -0
  753. package/bin/mimir-bridge.py +259 -0
  754. package/bin/mimir-bridge.sh +100 -0
  755. package/bin/mimir-ingest.sh +131 -0
  756. package/bin/mimir-reflect.sh +27 -0
  757. package/bin/mimir.sh +113 -0
  758. package/bin/mjollnir-webhook.sh +72 -0
  759. package/bin/mjollnir.sh +104 -0
  760. package/bin/model-hardware.sh +96 -0
  761. package/bin/model-resolve.sh +160 -0
  762. package/bin/models-detect.sh +178 -0
  763. package/bin/nornir-cron-start.sh +162 -0
  764. package/bin/nornir-job-daily-briefing.sh +191 -0
  765. package/bin/nornir-job-git-sync.sh +152 -0
  766. package/bin/nornir-job-hall-snapshot.sh +28 -0
  767. package/bin/nornir-job-memory-housekeeping.sh +114 -0
  768. package/bin/nornir-job-nsr-compliance.sh +54 -0
  769. package/bin/nornir-job-observer.sh +166 -0
  770. package/bin/nornir-job-skillopt-sleep.sh +76 -0
  771. package/bin/npm-publish.sh +136 -0
  772. package/bin/omarchy-hook-install.sh +60 -0
  773. package/bin/omarchy-install.sh +161 -0
  774. package/bin/omarchy-plugins.sh +187 -0
  775. package/bin/omarchy-sense.sh +162 -0
  776. package/bin/perm-guard.sh +30 -0
  777. package/bin/pi-agent.sh +68 -0
  778. package/bin/pi-ensure.sh +77 -0
  779. package/bin/pi-local.sh +37 -0
  780. package/bin/pi-model-agnostic.sh +71 -0
  781. package/bin/pi-seat.sh +63 -0
  782. package/bin/prereq-ensure.sh +166 -0
  783. package/bin/private-guard.sh +58 -0
  784. package/bin/project-git.sh +69 -0
  785. package/bin/public-guard.sh +71 -0
  786. package/bin/ratatoskr.sh +49 -0
  787. package/bin/realm-lib.sh +32 -0
  788. package/bin/repo-scrub.sh +62 -0
  789. package/bin/rodd-operational-input.sh +250 -0
  790. package/bin/runes-append.sh +198 -0
  791. package/bin/saga-session-start.sh +172 -0
  792. package/bin/saga-sessionstart-run.sh +79 -0
  793. package/bin/saga-wake-drain.sh +41 -0
  794. package/bin/secret-guard.sh +63 -0
  795. package/bin/sessrumnir-ensure.sh +100 -0
  796. package/bin/sessrumnir-sync.sh +237 -0
  797. package/bin/sessrumnir.sh +104 -0
  798. package/bin/skillopt-setup.sh +76 -0
  799. package/bin/skuld-branch-outcome.sh +203 -0
  800. package/bin/skuld-branch-prompt.sh +101 -0
  801. package/bin/smidja-bootstrap.sh +94 -0
  802. package/bin/smidja-observe.sh +86 -0
  803. package/bin/syn-arm-pretool-check.sh +57 -0
  804. package/bin/syn-asset-pretool-check.sh +89 -0
  805. package/bin/syn-cd-pretool-check.sh +28 -0
  806. package/bin/syn-guard-pretool-check.sh +83 -0
  807. package/bin/syn-spawn-pretool-check.sh +58 -0
  808. package/bin/syn-turnend-guard.sh +36 -0
  809. package/bin/syn-watch-arm.sh +99 -0
  810. package/bin/tailscale-sync.sh +203 -0
  811. package/bin/telegram-bot.sh +82 -0
  812. package/bin/toolchain.sh +74 -0
  813. package/bin/ui-truth-check.sh +75 -0
  814. package/bin/utgard.sh +111 -0
  815. package/bin/valhalla.sh +115 -0
  816. package/bin/valknut-load.sh +298 -0
  817. package/bin/vor-crew-state.sh +124 -0
  818. package/bin/wedge-notify.sh +60 -0
  819. package/bin/workspace-provision.sh +80 -0
  820. package/bin/workspace-rag.sh +163 -0
  821. package/bin/wyrd-db.sh +47 -0
  822. package/bin/yggdrasil.sh +152 -0
  823. package/bin/ymir-edit.sh +145 -0
  824. package/bin/ymir-install.sh +806 -0
  825. package/bin/ymir-invite.sh +93 -0
  826. package/bin/ymir-isolation.sh +88 -0
  827. package/bin/ymir-migrate.sh +61 -0
  828. package/bin/ymir-plan.sh +356 -0
  829. package/bin/ymir-platform.sh +348 -0
  830. package/bin/ymir-say.sh +110 -0
  831. package/bin/ymir-setup-auth.sh +119 -0
  832. package/bin/ymir-validate.sh +193 -0
  833. package/bin/ymir.js +74 -16
  834. package/docs/Architecture.md +339 -0
  835. package/docs/design.md +351 -0
  836. package/docs/installations/README.md +63 -0
  837. package/docs/installations/ymir-home.md +93 -0
  838. package/docs/integration.md +105 -0
  839. package/docs/lore.md +1060 -0
  840. package/docs/research/agent-harnesses.md +290 -0
  841. package/docs/runbooks/README.md +19 -0
  842. package/docs/runbooks/a2a.md +91 -0
  843. package/docs/runbooks/agent-permissions.md +99 -0
  844. package/docs/runbooks/agents.md +57 -0
  845. package/docs/runbooks/models.md +69 -0
  846. package/docs/runbooks/realm-onboarding.md +60 -0
  847. package/docs/runbooks/secrets-and-hoard.md +60 -0
  848. package/docs/runbooks/updates-and-migrations.md +110 -0
  849. package/docs/runbooks/worktrees-isolation.md +72 -0
  850. package/docs/session-start.md +105 -0
  851. package/docs/skillopt-integration.md +321 -0
  852. package/docs/sync-system.md +66 -0
  853. package/docs/workspaces.md +104 -0
  854. package/docs/ymir-rut.md +260 -0
  855. package/package.json +54 -14
  856. package/packaging/build.sh +74 -0
  857. package/packaging/macos/Ymir Installer.command +43 -0
  858. package/packaging/windows/ymir-setup.nsi +74 -0
  859. package/scripts/README.md +22 -0
  860. package/scripts/electron.sh +269 -0
  861. package/scripts/lower.sh +34 -0
  862. package/scripts/raise.sh +42 -0
  863. package/scripts/start.sh +217 -0
  864. package/scripts/stop.sh +113 -0
  865. package/install.sh +0 -35
@@ -0,0 +1,1023 @@
1
+ #!/usr/bin/env bash
2
+ # Parity guard for firstmate's shell-lint definition.
3
+ #
4
+ # bin/fm-lint.sh must be the single owner that BOTH CI
5
+ # (.github/workflows/ci.yml) and the pre-push gate (.no-mistakes.yaml
6
+ # commands.lint) invoke, so the local lint can never diverge from CI again.
7
+ # Regression origin: with no commands.lint configured, the local no-mistakes
8
+ # lint step never ran the deterministic
9
+ # `shellcheck bin/*.sh bin/backends/*.sh tests/*.sh`, so PRs passed local
10
+ # validation yet failed that exact check in CI on info/warning findings such as
11
+ # SC2015, SC1007, and SC2034. A second axis was tool-version skew: CI's
12
+ # ShellCheck floated with the runner image and still emitted SC2015, which
13
+ # ShellCheck retired in 0.11.0. fm-lint.sh now pins one exact version and both
14
+ # gates resolve it, so command, file set, config, AND version all match.
15
+ set -u
16
+
17
+ # shellcheck source=tests/lib.sh
18
+ . "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
19
+
20
+ LINT="$ROOT/bin/fm-lint.sh"
21
+ INSTALLER="$ROOT/bin/fm-install-shellcheck.sh"
22
+ # The pinned version, read from the single source (the one owner itself).
23
+ REQUIRED=$("$LINT" --required-version)
24
+
25
+ # Official GitHub release asset sha256 values for shellcheck v0.11.0 .tar.xz
26
+ # archives (https://github.com/koalaman/shellcheck/releases/tag/v0.11.0). Tests
27
+ # compare installer behavior against these published digests, not script source.
28
+ SHELLCHECK_SHA_LINUX_X86_64=8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198
29
+ SHELLCHECK_SHA_LINUX_AARCH64=12b331c1d2db6b9eb13cfca64306b1b157a86eb69db83023e261eaa7e7c14588
30
+ SHELLCHECK_SHA_DARWIN_X86_64=3c89db4edcab7cf1c27bff178882e0f6f27f7afdf54e859fa041fca10febe4c6
31
+ SHELLCHECK_SHA_DARWIN_AARCH64=56affdd8de5527894dca6dc3d7e0a99a873b0f004d7aabc30ae407d3f48b0a79
32
+
33
+ # fm_install_stub_uname <fakebin>: uname -s / uname -m from FM_TEST_UNAME_S/M.
34
+ fm_install_stub_uname() {
35
+ local fakebin=$1
36
+ cat > "$fakebin/uname" <<'SH'
37
+ #!/usr/bin/env bash
38
+ case "${1:-}" in
39
+ -s) printf '%s\n' "${FM_TEST_UNAME_S:-Linux}" ;;
40
+ -m) printf '%s\n' "${FM_TEST_UNAME_M:-x86_64}" ;;
41
+ *) printf '%s\n' "${FM_TEST_UNAME_S:-Linux}" ;;
42
+ esac
43
+ SH
44
+ chmod +x "$fakebin/uname"
45
+ }
46
+
47
+ # fm_install_stub_curl <fakebin>: log the URL, fail CURL_FAIL_UNTIL times, then
48
+ # write an empty file at -o. CURL_COUNT and CURL_URL_LOG are paths the stub
49
+ # updates when invoked.
50
+ fm_install_stub_curl() {
51
+ local fakebin=$1
52
+ cat > "$fakebin/curl" <<'SH'
53
+ #!/usr/bin/env bash
54
+ count=0
55
+ [ ! -f "${CURL_COUNT:-}" ] || count=$(cat "$CURL_COUNT")
56
+ count=$((count + 1))
57
+ [ -z "${CURL_COUNT:-}" ] || printf '%s\n' "$count" > "$CURL_COUNT"
58
+ url=
59
+ out=
60
+ while [ "$#" -gt 0 ]; do
61
+ case "$1" in
62
+ -o)
63
+ out=$2
64
+ shift 2
65
+ ;;
66
+ -*)
67
+ shift
68
+ ;;
69
+ *)
70
+ url=$1
71
+ shift
72
+ ;;
73
+ esac
74
+ done
75
+ [ -z "${CURL_URL_LOG:-}" ] || printf '%s\n' "$url" >> "$CURL_URL_LOG"
76
+ fail_until=${CURL_FAIL_UNTIL:-0}
77
+ [ "$count" -gt "$fail_until" ] || exit 22
78
+ : > "$out"
79
+ exit 0
80
+ SH
81
+ chmod +x "$fakebin/curl"
82
+ }
83
+
84
+ # fm_install_stub_hasher <fakebin> <name>: sha256sum or shasum stub that prints
85
+ # SHA256_STUB_HASH and records the invocation on HASHER_LOG. shasum requires -a 256.
86
+ fm_install_stub_hasher() {
87
+ local fakebin=$1 name=$2
88
+ cat > "$fakebin/$name" <<'SH'
89
+ #!/usr/bin/env bash
90
+ self=${0##*/}
91
+ if [ -n "${HASHER_LOG:-}" ]; then
92
+ printf '%s\n' "$self $*" >> "$HASHER_LOG"
93
+ fi
94
+ file=$1
95
+ if [ "$self" = shasum ]; then
96
+ algo=
97
+ file=
98
+ while [ "$#" -gt 0 ]; do
99
+ case "$1" in
100
+ -a)
101
+ algo=$2
102
+ shift 2
103
+ ;;
104
+ *)
105
+ file=$1
106
+ shift
107
+ ;;
108
+ esac
109
+ done
110
+ [ "$algo" = 256 ] || exit 1
111
+ fi
112
+ printf '%s %s\n' "${SHA256_STUB_HASH:?}" "$file"
113
+ SH
114
+ chmod +x "$fakebin/$name"
115
+ }
116
+
117
+ fm_install_stub_tar_shellcheck() {
118
+ local fakebin=$1
119
+ cat > "$fakebin/tar" <<'SH'
120
+ #!/usr/bin/env bash
121
+ while [ "$#" -gt 0 ]; do
122
+ if [ "$1" = "-C" ]; then
123
+ mkdir -p "$2/shellcheck-v0.11.0"
124
+ cat > "$2/shellcheck-v0.11.0/shellcheck" <<'EOF'
125
+ #!/usr/bin/env bash
126
+ printf 'ShellCheck - shell script analysis tool\nversion: 0.11.0\n'
127
+ EOF
128
+ chmod +x "$2/shellcheck-v0.11.0/shellcheck"
129
+ exit 0
130
+ fi
131
+ shift
132
+ done
133
+ exit 2
134
+ SH
135
+ chmod +x "$fakebin/tar"
136
+ }
137
+
138
+ fm_install_stub_sleep() {
139
+ local fakebin=$1
140
+ cat > "$fakebin/sleep" <<'SH'
141
+ #!/usr/bin/env bash
142
+ exit 0
143
+ SH
144
+ chmod +x "$fakebin/sleep"
145
+ }
146
+
147
+ # True only when the resolved shellcheck is exactly the pinned version, so the
148
+ # lint-running tests below match what CI enforces instead of a runner default.
149
+ pinned_ready() {
150
+ command -v shellcheck >/dev/null 2>&1 || return 1
151
+ [ "$(shellcheck --version | awk '/^version:/ {print $2; exit}')" = "$REQUIRED" ]
152
+ }
153
+
154
+ test_help_reports_the_complete_interface() {
155
+ local help
156
+ help=$("$LINT" --help) || fail "fm-lint.sh --help failed"
157
+ assert_contains "$help" "--telemetry" "fm-lint.sh --help omitted --telemetry"
158
+ assert_contains "$help" "--required-version" "fm-lint.sh --help omitted --required-version"
159
+ assert_contains "$help" "--list-files" "fm-lint.sh --help omitted --list-files"
160
+ assert_contains "$help" "--help" "fm-lint.sh --help omitted --help"
161
+ assert_contains "$help" "--fast" "fm-lint.sh --help omitted --fast"
162
+ pass "fm-lint.sh --help reports the complete executable interface"
163
+ }
164
+
165
+ test_list_files_reports_the_shell_inventory() {
166
+ local listed expected
167
+ # CI=true forces the full canonical set regardless of the ambient branch or
168
+ # working-tree diff a local test run happens to have, so this stays a pure
169
+ # inventory check independent of fm-lint.sh's own changed-file mode below.
170
+ listed=$(CI=true "$LINT" --list-files)
171
+ expected=$(find bin bin/backends tests -maxdepth 1 -type f -name '*.sh' -print | LC_ALL=C sort)
172
+ [ "$(printf '%s\n' "$listed" | LC_ALL=C sort)" = "$expected" ] \
173
+ || fail "fm-lint.sh --list-files did not return the complete shell inventory"
174
+ pass "fm-lint.sh --list-files reports the complete shell inventory"
175
+ }
176
+
177
+ # fm_lint_stub_git <fakebin-dir>: install a git stub for the changed-file mode
178
+ # tests below. Its answers are driven by env vars the caller sets before
179
+ # invoking fm-lint.sh, so those tests can steer git state without depending on
180
+ # this worktree's actual branch, remotes, or history:
181
+ # FM_TEST_GIT_INSIDE_WORKTREE 1 (default) or 0
182
+ # FM_TEST_GIT_BRANCH branch name for `rev-parse --abbrev-ref HEAD`
183
+ # FM_TEST_GIT_HAS_ORIGIN_MAIN 1 (default) or 0
184
+ # FM_TEST_GIT_HAS_MAIN 1 (default) or 0
185
+ # FM_TEST_GIT_MERGE_BASE_OK 1 (default) or 0
186
+ # FM_TEST_GIT_MERGE_BASE merge-base value to print when OK
187
+ # FM_TEST_GIT_DIFF_FILE path to a file of NUL-separated changed paths
188
+ fm_lint_stub_git() {
189
+ local fakebin=$1
190
+ cat > "$fakebin/git" <<'SH'
191
+ #!/usr/bin/env bash
192
+ case "$*" in
193
+ "rev-parse --is-inside-work-tree")
194
+ [ "${FM_TEST_GIT_INSIDE_WORKTREE:-1}" = 1 ] || exit 1
195
+ printf 'true\n'
196
+ exit 0
197
+ ;;
198
+ "rev-parse --abbrev-ref HEAD")
199
+ printf '%s\n' "${FM_TEST_GIT_BRANCH:-feature}"
200
+ exit 0
201
+ ;;
202
+ "rev-parse --verify -q origin/main")
203
+ [ "${FM_TEST_GIT_HAS_ORIGIN_MAIN:-1}" = 1 ] && exit 0 || exit 1
204
+ ;;
205
+ "rev-parse --verify -q main")
206
+ [ "${FM_TEST_GIT_HAS_MAIN:-1}" = 1 ] && exit 0 || exit 1
207
+ ;;
208
+ "merge-base "*)
209
+ if [ "${FM_TEST_GIT_MERGE_BASE_OK:-1}" = 1 ]; then
210
+ printf '%s\n' "${FM_TEST_GIT_MERGE_BASE:-fakebase123}"
211
+ exit 0
212
+ fi
213
+ exit 1
214
+ ;;
215
+ "diff --name-only --diff-filter=ACMR -z "*)
216
+ if [ -n "${FM_TEST_GIT_DIFF_FILE:-}" ] && [ -f "$FM_TEST_GIT_DIFF_FILE" ]; then
217
+ cat "$FM_TEST_GIT_DIFF_FILE"
218
+ fi
219
+ exit 0
220
+ ;;
221
+ *)
222
+ exit 1
223
+ ;;
224
+ esac
225
+ SH
226
+ chmod +x "$fakebin/git"
227
+ }
228
+
229
+ # fm_lint_write_diff_file <file> <path>...: writes NUL-separated changed paths
230
+ # in the shape `git diff --name-only -z` produces, for FM_TEST_GIT_DIFF_FILE.
231
+ fm_lint_write_diff_file() {
232
+ local file=$1
233
+ shift
234
+ printf '%s\0' "$@" > "$file"
235
+ }
236
+
237
+ # fm_lint_stub_shellcheck <fakebin-dir> <log-file>: install a ShellCheck stub
238
+ # that answers --version with the pinned version and otherwise logs the file
239
+ # roots it was asked to check (one per line) instead of actually analyzing
240
+ # them, so changed-file mode tests can assert exactly which files fm-lint.sh
241
+ # selected without depending on real ShellCheck findings. When
242
+ # FM_TEST_MODE_LOG is set, it records the effective analysis mode, treating
243
+ # ShellCheck's default as full analysis.
244
+ fm_lint_stub_shellcheck() {
245
+ local fakebin=$1 log=$2
246
+ : > "$log"
247
+ cat > "$fakebin/shellcheck" <<SH
248
+ #!/usr/bin/env bash
249
+ if [ "\${1:-}" = --version ]; then
250
+ printf 'ShellCheck - shell script analysis tool\nversion: 0.11.0\n'
251
+ exit 0
252
+ fi
253
+ mode=on
254
+ while [ "\$#" -gt 0 ] && [ "\$1" != -- ]; do
255
+ [ "\$1" = --extended-analysis=false ] && mode=off
256
+ shift
257
+ done
258
+ if [ -n "\${FM_TEST_MODE_LOG:-}" ]; then
259
+ printf '%s\n' "\$mode" >> "\$FM_TEST_MODE_LOG"
260
+ fi
261
+ [ "\$#" -eq 0 ] || shift
262
+ printf '%s\n' "\$@" >> "$log"
263
+ exit 0
264
+ SH
265
+ chmod +x "$fakebin/shellcheck"
266
+ }
267
+
268
+ test_fast_mode_disables_extended_analysis() {
269
+ local tmp fakebin log mode_log telemetry fixture out
270
+ tmp=$(fm_test_tmproot fm-lint-fast-mode)
271
+ fakebin=$(fm_fakebin "$tmp")
272
+ fixture="$tmp/fixture.sh"
273
+ log="$tmp/shellcheck.log"
274
+ mode_log="$tmp/mode.log"
275
+ telemetry="$tmp/telemetry.tsv"
276
+ cat > "$fixture" <<'SH'
277
+ #!/usr/bin/env bash
278
+ printf '%s\n' "${1:-ok}"
279
+ SH
280
+ chmod +x "$fixture"
281
+ fm_lint_stub_shellcheck "$fakebin" "$log"
282
+
283
+ out=$(PATH="$fakebin:$PATH" GITHUB_ACTIONS='' CI='' FM_LINT_JOBS=1 \
284
+ FM_TEST_MODE_LOG="$mode_log" "$LINT" --fast --telemetry "$telemetry" "$fixture" 2>&1) \
285
+ || fail "fast lint mode failed"$'\n'"$out"
286
+ [ "$(cat "$mode_log")" = off ] \
287
+ || fail "fast lint mode did not disable extended analysis"
288
+ [ "$(cat "$log")" = "$fixture" ] \
289
+ || fail "fast lint mode did not lint the requested root"
290
+ assert_grep $'analysis_mode\tfast' "$telemetry" "telemetry did not record fast analysis mode"
291
+ pass "fm-lint.sh --fast disables ShellCheck extended analysis"
292
+ }
293
+
294
+ test_ci_defaults_to_full_analysis() {
295
+ local tmp fakebin log mode_log fixture out
296
+ tmp=$(fm_test_tmproot fm-lint-ci-analysis)
297
+ fakebin=$(fm_fakebin "$tmp")
298
+ fixture="$tmp/fixture.sh"
299
+ log="$tmp/shellcheck.log"
300
+ mode_log="$tmp/mode.log"
301
+ cat > "$fixture" <<'SH'
302
+ #!/usr/bin/env bash
303
+ printf '%s\n' "${1:-ok}"
304
+ SH
305
+ chmod +x "$fixture"
306
+ fm_lint_stub_shellcheck "$fakebin" "$log"
307
+
308
+ out=$(PATH="$fakebin:$PATH" CI=true GITHUB_ACTIONS=true FM_LINT_FAST=1 FM_LINT_JOBS=1 \
309
+ FM_TEST_MODE_LOG="$mode_log" "$LINT" "$fixture" 2>&1) \
310
+ || fail "CI full lint mode failed"$'\n'"$out"
311
+ [ "$(cat "$mode_log")" = on ] \
312
+ || fail "CI default did not keep full ShellCheck analysis"
313
+ pass "fm-lint.sh keeps full ShellCheck analysis by default in CI"
314
+ }
315
+
316
+ test_ci_rejects_explicit_fast_mode() {
317
+ local tmp fakebin log fixture out rc
318
+ tmp=$(fm_test_tmproot fm-lint-ci-reject-fast)
319
+ fakebin=$(fm_fakebin "$tmp")
320
+ fixture="$tmp/fixture.sh"
321
+ log="$tmp/shellcheck.log"
322
+ cat > "$fixture" <<'SH'
323
+ #!/usr/bin/env bash
324
+ printf '%s\n' "${1:-ok}"
325
+ SH
326
+ chmod +x "$fixture"
327
+ fm_lint_stub_shellcheck "$fakebin" "$log"
328
+
329
+ rc=0
330
+ out=$(PATH="$fakebin:$PATH" CI=true GITHUB_ACTIONS=true FM_LINT_JOBS=1 \
331
+ "$LINT" --fast "$fixture" 2>&1) || rc=$?
332
+ [ "$rc" -eq 2 ] \
333
+ || fail "CI accepted explicit fast lint mode (exit $rc)"$'\n'"$out"
334
+ assert_contains "$out" "--fast is local-only" \
335
+ "CI fast-mode rejection did not explain the policy"
336
+ [ ! -s "$log" ] || fail "CI invoked ShellCheck after rejecting fast mode"
337
+ pass "fm-lint.sh rejects explicit --fast mode in CI"
338
+ }
339
+
340
+ test_fast_mode_catches_a_real_lint_defect() {
341
+ if ! pinned_ready; then
342
+ pass "SKIP (ShellCheck $REQUIRED not resolved): fast lint-defect regression check"
343
+ return
344
+ fi
345
+ local tmp bad out rc
346
+ tmp=$(fm_test_tmproot fm-lint-fast-bad)
347
+ bad="$tmp/bad.sh"
348
+ cat > "$bad" <<'SH'
349
+ #!/usr/bin/env bash
350
+ foo() {
351
+ local a= b=
352
+ echo "$a$b"
353
+ }
354
+ foo
355
+ SH
356
+ rc=0
357
+ out=$(GITHUB_ACTIONS='' CI='' "$LINT" --fast "$bad" 2>&1) || rc=$?
358
+ [ "$rc" -ne 0 ] || fail "fast lint mode passed a known-bad fixture"$'\n'"$out"
359
+ assert_contains "$out" "SC1007" "fast lint mode did not report the expected ShellCheck finding"
360
+ pass "fm-lint.sh --fast catches an ordinary shell lint defect"
361
+ }
362
+
363
+ test_changed_mode_lints_only_the_changed_file() {
364
+ local tmp fakebin log diff_file out target
365
+ tmp=$(fm_test_tmproot fm-lint-changed)
366
+ fakebin=$(fm_fakebin "$tmp")
367
+ fm_lint_stub_git "$fakebin"
368
+ log="$tmp/shellcheck.log"
369
+ fm_lint_stub_shellcheck "$fakebin" "$log"
370
+ diff_file="$tmp/diff.nul"
371
+ target="bin/fm-install-shellcheck.sh"
372
+ fm_lint_write_diff_file "$diff_file" "$target" "README.md"
373
+
374
+ # Clear the ambient CI/GITHUB_ACTIONS signals so changed-file mode is actually
375
+ # exercised: a CI run sets them and would otherwise force the full lint here.
376
+ out=$(PATH="$fakebin:$PATH" GITHUB_ACTIONS='' CI='' FM_LINT_JOBS=1 \
377
+ FM_TEST_GIT_BRANCH=feature \
378
+ FM_TEST_GIT_DIFF_FILE="$diff_file" "$LINT" 2>&1) \
379
+ || fail "changed-mode lint run failed"$'\n'"$out"
380
+ [ "$(cat "$log")" = "$target" ] \
381
+ || fail "changed-mode lint did not run ShellCheck on exactly the changed file"$'\n'"logged: $(cat "$log")"
382
+ pass "fm-lint.sh changed mode lints only the changed canonical file"
383
+ }
384
+
385
+ test_ci_forces_full_lint_even_with_empty_diff() {
386
+ local listed expected
387
+ # No git stub: CI=true must short-circuit fm-lint.sh's mode selection before
388
+ # it ever consults git, so this proves CI wins regardless of local diff state.
389
+ listed=$(CI=true "$LINT" --list-files)
390
+ expected=$(find bin bin/backends tests -maxdepth 1 -type f -name '*.sh' -print | LC_ALL=C sort)
391
+ [ "$(printf '%s\n' "$listed" | LC_ALL=C sort)" = "$expected" ] \
392
+ || fail "CI=true did not force the full canonical file set"
393
+ pass "fm-lint.sh forces a full lint in CI even when the local diff would be empty"
394
+ }
395
+
396
+ test_main_branch_forces_full_lint() {
397
+ local tmp fakebin listed expected
398
+ tmp=$(fm_test_tmproot fm-lint-main-full)
399
+ fakebin=$(fm_fakebin "$tmp")
400
+ fm_lint_stub_git "$fakebin"
401
+
402
+ # Clear CI/GITHUB_ACTIONS so the on-main branch is what forces the full lint,
403
+ # not the ambient CI signal a real CI run would otherwise supply.
404
+ listed=$(PATH="$fakebin:$PATH" GITHUB_ACTIONS='' CI='' \
405
+ FM_TEST_GIT_BRANCH=main "$LINT" --list-files)
406
+ expected=$(find bin bin/backends tests -maxdepth 1 -type f -name '*.sh' -print | LC_ALL=C sort)
407
+ [ "$(printf '%s\n' "$listed" | LC_ALL=C sort)" = "$expected" ] \
408
+ || fail "fm-lint.sh did not force a full lint when HEAD is on main"
409
+ pass "fm-lint.sh forces a full lint when HEAD is on main"
410
+ }
411
+
412
+ test_explicit_path_bypasses_changed_logic() {
413
+ local tmp fakebin log out target
414
+ tmp=$(fm_test_tmproot fm-lint-explicit-override)
415
+ fakebin=$(fm_fakebin "$tmp")
416
+ fm_lint_stub_git "$fakebin"
417
+ log="$tmp/shellcheck.log"
418
+ fm_lint_stub_shellcheck "$fakebin" "$log"
419
+ target="bin/fm-install-shellcheck.sh"
420
+
421
+ # The git stub reports a broken merge-base, which would force a full lint
422
+ # under the no-args default. Clearing CI/GITHUB_ACTIONS keeps changed-file
423
+ # selection live so this proves the explicit path bypasses it, not that CI
424
+ # already forced full mode. An explicit path must never even consult git.
425
+ out=$(PATH="$fakebin:$PATH" GITHUB_ACTIONS='' CI='' FM_LINT_JOBS=1 \
426
+ FM_TEST_GIT_MERGE_BASE_OK=0 \
427
+ "$LINT" "$target" 2>&1) || fail "explicit-path lint failed"$'\n'"$out"
428
+ [ "$(cat "$log")" = "$target" ] \
429
+ || fail "explicit path lint did not run on exactly the requested file"$'\n'"logged: $(cat "$log")"
430
+ pass "fm-lint.sh explicit paths bypass changed-file mode selection"
431
+ }
432
+
433
+ test_zero_changed_files_exits_clean() {
434
+ local tmp fakebin diff_file out rc
435
+ tmp=$(fm_test_tmproot fm-lint-zero-changed)
436
+ fakebin=$(fm_fakebin "$tmp")
437
+ fm_lint_stub_git "$fakebin"
438
+ diff_file="$tmp/diff.nul"
439
+ : > "$diff_file"
440
+
441
+ rc=0
442
+ # Clear CI/GITHUB_ACTIONS so changed-file mode runs and can reach the empty
443
+ # target set; a CI run would otherwise force a full lint instead.
444
+ out=$(PATH="$fakebin:$PATH" GITHUB_ACTIONS='' CI='' FM_TEST_GIT_BRANCH=feature \
445
+ FM_TEST_GIT_DIFF_FILE="$diff_file" "$LINT" 2>&1) || rc=$?
446
+ [ "$rc" -eq 0 ] || fail "zero changed lint targets must exit 0, got $rc"$'\n'"$out"
447
+ assert_contains "$out" "ShellCheck 0.11.0" "zero-changed run did not print the ShellCheck version line"
448
+ assert_contains "$out" "no changed lint targets" "zero-changed run did not note the empty target set"
449
+ assert_contains "$out" "workflow files valid" \
450
+ "zero-changed run skipped workflow YAML validation"
451
+ pass "fm-lint.sh exits 0 with a note when the local branch has no changed lint targets"
452
+ }
453
+
454
+ test_list_files_respects_changed_mode() {
455
+ local tmp fakebin diff_file listed
456
+ tmp=$(fm_test_tmproot fm-lint-list-changed)
457
+ fakebin=$(fm_fakebin "$tmp")
458
+ fm_lint_stub_git "$fakebin"
459
+ diff_file="$tmp/diff.nul"
460
+ # A real canonical file, a non-canonical file, and a canonical-looking path
461
+ # that does not exist: only the first should survive into the listed set.
462
+ fm_lint_write_diff_file "$diff_file" \
463
+ "tests/fm-lint.test.sh" "docs/README.md" "bin/definitely-not-real-file.sh"
464
+
465
+ # Clear CI/GITHUB_ACTIONS so --list-files reflects the changed set rather than
466
+ # the full canonical set a CI run's ambient signals would otherwise force.
467
+ listed=$(PATH="$fakebin:$PATH" GITHUB_ACTIONS='' CI='' FM_TEST_GIT_BRANCH=feature \
468
+ FM_TEST_GIT_DIFF_FILE="$diff_file" "$LINT" --list-files)
469
+ [ "$listed" = "tests/fm-lint.test.sh" ] \
470
+ || fail "--list-files did not report the would-be changed set in changed mode"$'\n'"got: $listed"
471
+ pass "fm-lint.sh --list-files reports the would-be changed set in changed mode"
472
+ }
473
+
474
+ test_pins_an_explicit_version() {
475
+ [ -n "$REQUIRED" ] || fail "fm-lint.sh --required-version printed nothing"
476
+ # The captain-agreed pin: adopt ShellCheck 0.11.0's rule set consistently,
477
+ # which is also what drops the upstream-retired, false-positive-prone SC2015.
478
+ assert_contains "$REQUIRED" "0.11.0" "fm-lint.sh must pin ShellCheck 0.11.0"
479
+ pass "fm-lint.sh pins an explicit ShellCheck version ($REQUIRED)"
480
+ }
481
+
482
+ test_installer_retries_transient_download_failure() {
483
+ local tmp fakebin destination out
484
+ tmp=$(fm_test_tmproot fm-shellcheck-download)
485
+ fakebin=$(fm_fakebin "$tmp")
486
+ destination="$tmp/bin"
487
+
488
+ fm_install_stub_uname "$fakebin"
489
+ fm_install_stub_curl "$fakebin"
490
+ fm_install_stub_hasher "$fakebin" sha256sum
491
+ fm_install_stub_tar_shellcheck "$fakebin"
492
+ fm_install_stub_sleep "$fakebin"
493
+
494
+ # Reproduce the CI incident: the release endpoint returned 503 for all three
495
+ # formerly configured attempts before recovering. Force linux/x86_64 so the
496
+ # retry path stays the CI archive even when this suite runs on macOS.
497
+ out=$(CURL_COUNT="$tmp/curl-count" CURL_FAIL_UNTIL=3 \
498
+ SHA256_STUB_HASH="$SHELLCHECK_SHA_LINUX_X86_64" \
499
+ FM_TEST_UNAME_S=Linux FM_TEST_UNAME_M=x86_64 \
500
+ PATH="$fakebin:$PATH" "$INSTALLER" "$destination" 2>&1) \
501
+ || fail "installer did not recover from a transient download failure"$'\n'"$out"
502
+ [ "$(cat "$tmp/curl-count")" -eq 4 ] || fail "installer did not recover after three failed downloads"
503
+ assert_contains "$out" "download attempt 3 failed; retrying" "installer did not disclose its third retry"
504
+ [ -x "$destination/shellcheck" ] || fail "installer did not install ShellCheck after retrying"
505
+ pass "ShellCheck installer retries a transient download failure"
506
+ }
507
+
508
+ test_installer_selects_platform_archive_url_and_checksum() {
509
+ local tmp fakebin destination out url_log uname_s uname_m archive sha
510
+ tmp=$(fm_test_tmproot fm-shellcheck-platform)
511
+ fakebin=$(fm_fakebin "$tmp")
512
+ destination="$tmp/bin"
513
+ url_log="$tmp/curl-url.log"
514
+
515
+ fm_install_stub_uname "$fakebin"
516
+ fm_install_stub_curl "$fakebin"
517
+ fm_install_stub_hasher "$fakebin" sha256sum
518
+ fm_install_stub_tar_shellcheck "$fakebin"
519
+ fm_install_stub_sleep "$fakebin"
520
+
521
+ while IFS=$'\t' read -r uname_s uname_m archive sha; do
522
+ [ -n "$uname_s" ] || continue
523
+ rm -rf "$destination"
524
+ : > "$url_log"
525
+ out=$(CURL_URL_LOG="$url_log" SHA256_STUB_HASH="$sha" \
526
+ FM_TEST_UNAME_S="$uname_s" FM_TEST_UNAME_M="$uname_m" \
527
+ PATH="$fakebin:$PATH" "$INSTALLER" "$destination" 2>&1) \
528
+ || fail "installer failed for ${uname_s}/${uname_m}"$'\n'"$out"
529
+ assert_contains "$(cat "$url_log")" "$archive" \
530
+ "installer did not download $archive for ${uname_s}/${uname_m}"
531
+ assert_contains "$(cat "$url_log")" \
532
+ "https://github.com/koalaman/shellcheck/releases/download/v${REQUIRED}/${archive}" \
533
+ "installer used the wrong URL for ${uname_s}/${uname_m}"
534
+ [ -x "$destination/shellcheck" ] || fail "installer did not install ShellCheck for ${uname_s}/${uname_m}"
535
+ done <<EOF
536
+ Linux x86_64 shellcheck-v${REQUIRED}.linux.x86_64.tar.xz $SHELLCHECK_SHA_LINUX_X86_64
537
+ Linux amd64 shellcheck-v${REQUIRED}.linux.x86_64.tar.xz $SHELLCHECK_SHA_LINUX_X86_64
538
+ Linux aarch64 shellcheck-v${REQUIRED}.linux.aarch64.tar.xz $SHELLCHECK_SHA_LINUX_AARCH64
539
+ Linux arm64 shellcheck-v${REQUIRED}.linux.aarch64.tar.xz $SHELLCHECK_SHA_LINUX_AARCH64
540
+ Darwin x86_64 shellcheck-v${REQUIRED}.darwin.x86_64.tar.xz $SHELLCHECK_SHA_DARWIN_X86_64
541
+ Darwin amd64 shellcheck-v${REQUIRED}.darwin.x86_64.tar.xz $SHELLCHECK_SHA_DARWIN_X86_64
542
+ Darwin arm64 shellcheck-v${REQUIRED}.darwin.aarch64.tar.xz $SHELLCHECK_SHA_DARWIN_AARCH64
543
+ Darwin aarch64 shellcheck-v${REQUIRED}.darwin.aarch64.tar.xz $SHELLCHECK_SHA_DARWIN_AARCH64
544
+ EOF
545
+ pass "ShellCheck installer selects the official archive, URL, and checksum per OS/arch"
546
+ }
547
+
548
+ test_installer_rejects_wrong_checksum() {
549
+ local tmp fakebin destination out rc
550
+ tmp=$(fm_test_tmproot fm-shellcheck-badsum)
551
+ fakebin=$(fm_fakebin "$tmp")
552
+ destination="$tmp/bin"
553
+
554
+ fm_install_stub_uname "$fakebin"
555
+ fm_install_stub_curl "$fakebin"
556
+ fm_install_stub_hasher "$fakebin" sha256sum
557
+ fm_install_stub_tar_shellcheck "$fakebin"
558
+ fm_install_stub_sleep "$fakebin"
559
+
560
+ rc=0
561
+ out=$(SHA256_STUB_HASH=0000000000000000000000000000000000000000000000000000000000000000 \
562
+ FM_TEST_UNAME_S=Linux FM_TEST_UNAME_M=x86_64 \
563
+ PATH="$fakebin:$PATH" "$INSTALLER" "$destination" 2>&1) || rc=$?
564
+ [ "$rc" -ne 0 ] || fail "installer accepted a wrong checksum"$'\n'"$out"
565
+ assert_contains "$out" "checksum mismatch" "installer did not report a checksum mismatch"
566
+ assert_contains "$out" "shellcheck-v${REQUIRED}.linux.x86_64.tar.xz" \
567
+ "mismatch did not name the selected archive"
568
+ assert_contains "$out" "$SHELLCHECK_SHA_LINUX_X86_64" \
569
+ "mismatch did not name the pinned linux/x86_64 checksum"
570
+ [ ! -e "$destination/shellcheck" ] || fail "installer installed ShellCheck after a checksum mismatch"
571
+ pass "ShellCheck installer rejects a wrong checksum"
572
+ }
573
+
574
+ test_installer_falls_back_to_shasum() {
575
+ local tmp fakebin destination out hasher_log tool
576
+ tmp=$(fm_test_tmproot fm-shellcheck-shasum)
577
+ fakebin=$(fm_fakebin "$tmp")
578
+ destination="$tmp/bin"
579
+ hasher_log="$tmp/hasher.log"
580
+
581
+ for tool in bash dirname mktemp rm awk mkdir install cat chmod; do
582
+ ln -s "$(command -v "$tool")" "$fakebin/$tool"
583
+ done
584
+ fm_install_stub_uname "$fakebin"
585
+ fm_install_stub_curl "$fakebin"
586
+ fm_install_stub_hasher "$fakebin" shasum
587
+ fm_install_stub_tar_shellcheck "$fakebin"
588
+ fm_install_stub_sleep "$fakebin"
589
+
590
+ # Restricted PATH: shasum is present, sha256sum is not.
591
+ : > "$hasher_log"
592
+ out=$(CURL_URL_LOG="$tmp/curl-url.log" HASHER_LOG="$hasher_log" \
593
+ SHA256_STUB_HASH="$SHELLCHECK_SHA_LINUX_X86_64" \
594
+ FM_TEST_UNAME_S=Linux FM_TEST_UNAME_M=x86_64 \
595
+ PATH="$fakebin" "$INSTALLER" "$destination" 2>&1) \
596
+ || fail "installer did not fall back to shasum -a 256"$'\n'"$out"
597
+ assert_grep 'shasum -a 256' "$hasher_log" "installer did not invoke shasum -a 256"
598
+ [ -x "$destination/shellcheck" ] || fail "installer did not install ShellCheck via shasum"
599
+ pass "ShellCheck installer falls back to shasum -a 256 when sha256sum is absent"
600
+ }
601
+
602
+ test_installer_prefers_sha256sum_over_shasum() {
603
+ local tmp fakebin destination hasher_log
604
+ tmp=$(fm_test_tmproot fm-shellcheck-sha256sum-pref)
605
+ fakebin=$(fm_fakebin "$tmp")
606
+ destination="$tmp/bin"
607
+ hasher_log="$tmp/hasher.log"
608
+
609
+ fm_install_stub_uname "$fakebin"
610
+ fm_install_stub_curl "$fakebin"
611
+ fm_install_stub_hasher "$fakebin" sha256sum
612
+ fm_install_stub_hasher "$fakebin" shasum
613
+ fm_install_stub_tar_shellcheck "$fakebin"
614
+ fm_install_stub_sleep "$fakebin"
615
+
616
+ : > "$hasher_log"
617
+ PATH="$fakebin:$PATH" HASHER_LOG="$hasher_log" \
618
+ SHA256_STUB_HASH="$SHELLCHECK_SHA_LINUX_X86_64" \
619
+ FM_TEST_UNAME_S=Linux FM_TEST_UNAME_M=x86_64 \
620
+ "$INSTALLER" "$destination" >/dev/null \
621
+ || fail "installer failed when both hashers were present"
622
+ assert_grep 'sha256sum' "$hasher_log" "installer did not prefer sha256sum"
623
+ if grep -q 'shasum' "$hasher_log"; then
624
+ fail "installer invoked shasum even though sha256sum was present"$'\n'"$(cat "$hasher_log")"
625
+ fi
626
+ pass "ShellCheck installer prefers sha256sum when both hashers are present"
627
+ }
628
+
629
+ test_installer_rejects_unsupported_platform() {
630
+ local tmp fakebin destination out rc
631
+ tmp=$(fm_test_tmproot fm-shellcheck-unsupported)
632
+ fakebin=$(fm_fakebin "$tmp")
633
+ destination="$tmp/bin"
634
+
635
+ fm_install_stub_uname "$fakebin"
636
+ fm_install_stub_curl "$fakebin"
637
+
638
+ rc=0
639
+ out=$(FM_TEST_UNAME_S=FreeBSD FM_TEST_UNAME_M=amd64 \
640
+ PATH="$fakebin:$PATH" "$INSTALLER" "$destination" 2>&1) || rc=$?
641
+ [ "$rc" -ne 0 ] || fail "installer accepted an unsupported OS"$'\n'"$out"
642
+ assert_contains "$out" "unsupported platform" "installer did not name the unsupported platform"
643
+ assert_contains "$out" "FreeBSD-amd64" "installer did not report the detected OS/arch"
644
+
645
+ rc=0
646
+ out=$(FM_TEST_UNAME_S=Linux FM_TEST_UNAME_M=ppc64le \
647
+ PATH="$fakebin:$PATH" "$INSTALLER" "$destination" 2>&1) || rc=$?
648
+ [ "$rc" -ne 0 ] || fail "installer accepted an unsupported architecture"$'\n'"$out"
649
+ assert_contains "$out" "unsupported platform" "installer did not reject linux/ppc64le"
650
+ pass "ShellCheck installer rejects an unsupported OS or architecture"
651
+ }
652
+
653
+ test_missing_shellcheck_fails_closed() {
654
+ local tmp fakebin out rc tool
655
+ tmp=$(fm_test_tmproot fm-lint-noshellcheck)
656
+ fakebin=$(fm_fakebin "$tmp")
657
+ for tool in bash dirname; do
658
+ ln -s "$(command -v "$tool")" "$fakebin/$tool"
659
+ done
660
+ rc=0
661
+ out=$(PATH="$fakebin" CI=true GITHUB_ACTIONS=true "$LINT" 2>&1) || rc=$?
662
+ [ "$rc" -eq 1 ] || fail "missing ShellCheck expected exit 1, got $rc"$'\n'"$out"
663
+ assert_contains "$out" "ShellCheck not found" \
664
+ "missing ShellCheck did not name the required linter"
665
+ assert_contains "$out" "$REQUIRED" \
666
+ "missing ShellCheck did not name the pinned version"
667
+ assert_contains "$out" "fm-install-shellcheck.sh" \
668
+ "missing ShellCheck did not name the pinned installer"
669
+ pass "missing ShellCheck fails closed"
670
+ }
671
+
672
+ test_rejects_wrong_shellcheck_version() {
673
+ # Version-independent: a fake shellcheck reporting a different version must be
674
+ # refused before any lint, proving local and CI cannot silently diverge.
675
+ local tmp fakebin out rc
676
+ tmp=$(fm_test_tmproot fm-lint-ver)
677
+ fakebin=$(fm_fakebin "$tmp")
678
+ cat > "$fakebin/shellcheck" <<'SH'
679
+ #!/usr/bin/env bash
680
+ if [ "$1" = "--version" ]; then
681
+ printf 'ShellCheck - shell script analysis tool\nversion: 0.9.9\nlicense: x\nwebsite: y\n'
682
+ exit 0
683
+ fi
684
+ exit 0
685
+ SH
686
+ chmod +x "$fakebin/shellcheck"
687
+ rc=0
688
+ out=$(PATH="$fakebin:$PATH" "$LINT" 2>&1) || rc=$?
689
+ [ "$rc" -ne 0 ] || fail "fm-lint.sh accepted a shellcheck version other than the pin"$'\n'"$out"
690
+ assert_contains "$out" "$REQUIRED" "fm-lint.sh did not name the required version on mismatch"
691
+ assert_contains "$out" "0.9.9" "fm-lint.sh did not report the resolved (wrong) version"
692
+ pass "fm-lint.sh refuses to lint under a non-pinned ShellCheck version"
693
+ }
694
+
695
+ test_catches_a_real_lint_defect() {
696
+ if ! pinned_ready; then
697
+ pass "SKIP (ShellCheck $REQUIRED not resolved): lint-defect regression check"
698
+ return
699
+ fi
700
+ # A script with a genuine ShellCheck finding must make the one owner exit
701
+ # non-zero, proving local now runs real shellcheck instead of the old no-op
702
+ # lint step. We deliberately do NOT assert SC2015 (PR 475's actual failure):
703
+ # ShellCheck removed SC2015 in the pinned 0.11.0, so asserting it would make
704
+ # this test itself version-fragile - the very trap being fixed. SC1007 is a
705
+ # warning present at default severity (and is itself one of the recurring
706
+ # classes that slipped through, PR 474).
707
+ local tmp bad out rc
708
+ tmp=$(fm_test_tmproot fm-lint-bad)
709
+ mkdir -p "$tmp"
710
+ bad="$tmp/bad.sh"
711
+ cat > "$bad" <<'SH'
712
+ #!/usr/bin/env bash
713
+ foo() {
714
+ local a= b=
715
+ echo "$a$b"
716
+ }
717
+ foo
718
+ SH
719
+ rc=0
720
+ out=$("$LINT" "$bad" 2>&1) || rc=$?
721
+ [ "$rc" -ne 0 ] || fail "fm-lint.sh passed a known-bad fixture"$'\n'"$out"
722
+ assert_contains "$out" "SC1007" "fm-lint.sh did not report the expected ShellCheck finding"
723
+ pass "fm-lint.sh catches a real lint defect the old no-op gate passed"
724
+ }
725
+
726
+ test_ignores_ambient_shellcheck_opts() {
727
+ if ! pinned_ready; then
728
+ pass "SKIP (ShellCheck $REQUIRED not resolved): ambient options regression check"
729
+ return
730
+ fi
731
+ local tmp bad out rc
732
+ tmp=$(fm_test_tmproot fm-lint-opts)
733
+ mkdir -p "$tmp"
734
+ bad="$tmp/bad.sh"
735
+ cat > "$bad" <<'SH'
736
+ #!/usr/bin/env bash
737
+ foo() {
738
+ local a= b=
739
+ echo "$a$b"
740
+ }
741
+ foo
742
+ SH
743
+ rc=0
744
+ out=$(SHELLCHECK_OPTS='--exclude=SC1007' "$LINT" "$bad" 2>&1) || rc=$?
745
+ [ "$rc" -ne 0 ] || fail "fm-lint.sh allowed ambient SHELLCHECK_OPTS to hide a finding"$'\n'"$out"
746
+ assert_contains "$out" "SC1007" "fm-lint.sh did not neutralize ambient SHELLCHECK_OPTS"
747
+ pass "fm-lint.sh ignores ambient ShellCheck options"
748
+ }
749
+
750
+ test_clean_fixture_passes() {
751
+ if ! pinned_ready; then
752
+ pass "SKIP (ShellCheck $REQUIRED not resolved): clean fixture check"
753
+ return
754
+ fi
755
+ local tmp good rc
756
+ tmp=$(fm_test_tmproot fm-lint-good)
757
+ mkdir -p "$tmp"
758
+ good="$tmp/good.sh"
759
+ cat > "$good" <<'SH'
760
+ #!/usr/bin/env bash
761
+ set -eu
762
+ if [ -n "${1:-}" ] && [ -d "$1" ]; then
763
+ printf 'ok\n'
764
+ fi
765
+ SH
766
+ rc=0
767
+ "$LINT" "$good" >/dev/null 2>&1 || rc=$?
768
+ [ "$rc" -eq 0 ] || fail "fm-lint.sh flagged a clean fixture (exit $rc)"
769
+ pass "fm-lint.sh passes a clean fixture"
770
+ }
771
+
772
+ test_jobs_are_deterministic_and_complete() {
773
+ if ! pinned_ready; then
774
+ pass "SKIP (ShellCheck $REQUIRED not resolved): deterministic bounded jobs check"
775
+ return
776
+ fi
777
+ local tmp good bad_a bad_b out_clean_1 out_clean_2 out_fail_1 out_fail_2 out_fail_2b
778
+ local telemetry telemetry_out cleanup_tmp cleanup_out rc_clean_1 rc_clean_2 rc_fail_1 rc_fail_2 rc_fail_2b rc_bad_jobs
779
+ tmp=$(fm_test_tmproot fm-lint-jobs)
780
+ mkdir -p "$tmp"
781
+ good="$tmp/good.sh"
782
+ bad_a="$tmp/bad-a.sh"
783
+ bad_b="$tmp/bad-b.sh"
784
+ telemetry="$tmp/telemetry.tsv"
785
+ cat > "$good" <<'SH'
786
+ #!/usr/bin/env bash
787
+ printf '%s\n' "${1:-ok}"
788
+ SH
789
+ cat > "$bad_a" <<'SH'
790
+ #!/usr/bin/env bash
791
+ bad_a() {
792
+ local a= b=
793
+ printf '%s\n' "$a$b"
794
+ }
795
+ SH
796
+ cat > "$bad_b" <<'SH'
797
+ #!/usr/bin/env bash
798
+ bad_b() {
799
+ printf '%s\n' $1
800
+ }
801
+ SH
802
+
803
+ rc_clean_1=0
804
+ out_clean_1=$(FM_LINT_JOBS=1 "$LINT" "$good" 2>&1) || rc_clean_1=$?
805
+ rc_clean_2=0
806
+ out_clean_2=$(FM_LINT_JOBS=2 "$LINT" "$good" 2>&1) || rc_clean_2=$?
807
+ [ "$rc_clean_1" -eq 0 ] && [ "$rc_clean_2" -eq 0 ] || fail "clean jobs=1/jobs=2 paths must both pass"
808
+ [ "$out_clean_1" = "$out_clean_2" ] || fail "clean jobs=1/jobs=2 output differs"
809
+
810
+ rc_fail_1=0
811
+ out_fail_1=$(FM_LINT_JOBS=1 "$LINT" "$bad_a" "$bad_b" 2>&1) || rc_fail_1=$?
812
+ rc_fail_2=0
813
+ out_fail_2=$(FM_LINT_JOBS=2 "$LINT" "$bad_a" "$bad_b" 2>&1) || rc_fail_2=$?
814
+ rc_fail_2b=0
815
+ out_fail_2b=$(FM_LINT_JOBS=2 "$LINT" "$bad_a" "$bad_b" 2>&1) || rc_fail_2b=$?
816
+ [ "$rc_fail_1" -ne 0 ] && [ "$rc_fail_1" -eq "$rc_fail_2" ] && [ "$rc_fail_2" -eq "$rc_fail_2b" ] \
817
+ || fail "failing jobs=1/jobs=2 exit results differ: $rc_fail_1/$rc_fail_2/$rc_fail_2b"
818
+ [ "$out_fail_1" = "$out_fail_2" ] && [ "$out_fail_2" = "$out_fail_2b" ] \
819
+ || fail "failing diagnostics are not byte-identical and deterministic across jobs"
820
+ assert_contains "$out_fail_1" "SC1007" "the first failing root diagnostic was lost"
821
+ assert_contains "$out_fail_1" "SC2086" "the later failing root diagnostic was lost"
822
+ rc_bad_jobs=0
823
+ FM_LINT_JOBS=3 "$LINT" "$good" >/dev/null 2>&1 || rc_bad_jobs=$?
824
+ [ "$rc_bad_jobs" -eq 2 ] || fail "the lint owner must reject unbounded worker counts"
825
+
826
+ telemetry_out=$(FM_LINT_JOBS=2 FM_LINT_TELEMETRY="$telemetry" "$LINT" "$good" 2>&1) \
827
+ || fail "telemetry-enabled clean lint failed"
828
+ [ "$telemetry_out" = "$out_clean_2" ] || fail "quiet telemetry changed routine lint output"
829
+ assert_grep $'format\tfm-lint-telemetry-v1' "$telemetry" "telemetry format marker is missing"
830
+ assert_grep $'analysis_mode\tfull' "$telemetry" "telemetry did not record full analysis mode"
831
+ assert_grep $'jobs\t2' "$telemetry" "telemetry did not record bounded jobs"
832
+ assert_grep $'root_count\t1' "$telemetry" "telemetry did not record root count"
833
+ assert_grep $'wall_seconds\t' "$telemetry" "telemetry did not record wall time"
834
+ assert_grep $'user_seconds\t' "$telemetry" "telemetry did not record user CPU"
835
+ assert_grep $'system_seconds\t' "$telemetry" "telemetry did not record system CPU"
836
+ assert_grep $'max_worker_rss_kib\t' "$telemetry" "telemetry did not record maximum RSS"
837
+ assert_grep $'source_boundary_directives\t' "$telemetry" "telemetry did not record source-graph boundaries"
838
+ assert_grep $'shellcheck_processes_start\t' "$telemetry" "telemetry did not record competing ShellCheck conditions"
839
+
840
+ cleanup_tmp="$tmp/lint-tmp"
841
+ mkdir -p "$cleanup_tmp"
842
+ cleanup_out=$(TMPDIR="$cleanup_tmp" FM_LINT_JOBS=2 "$LINT" "$good" 2>&1) \
843
+ || fail "cleanup fixture lint failed"
844
+ [ "$cleanup_out" = "$out_clean_2" ] || fail "cleanup fixture changed routine diagnostics"
845
+ [ -z "$(find "$cleanup_tmp" -mindepth 1 -maxdepth 1 -name 'fm-lint.*' -print -quit)" ] \
846
+ || fail "bounded lint left temporary worker state behind"
847
+ pass "jobs=1 and jobs=2 preserve deterministic diagnostics, failures, cleanup bounds, and quiet telemetry"
848
+ }
849
+
850
+ test_worker_trees_stop_on_signal() {
851
+ local tmp fakebin fixture jobs telemetry lint_tmp pid_file out_file telemetry_file
852
+ local parent_pid shellcheck_pid i parent_rc survivor
853
+ tmp=$(fm_test_tmproot fm-lint-signal)
854
+ mkdir -p "$tmp"
855
+ fakebin=$(fm_fakebin "$tmp")
856
+ fixture="$tmp/good.sh"
857
+ cat > "$fixture" <<'SH'
858
+ #!/usr/bin/env bash
859
+ printf '%s\n' "${1:-ok}"
860
+ SH
861
+ cat > "$fakebin/shellcheck" <<'SH'
862
+ #!/usr/bin/env bash
863
+ if [ "${1:-}" = "--version" ]; then
864
+ printf 'ShellCheck - shell script analysis tool\nversion: 0.11.0\n'
865
+ exit 0
866
+ fi
867
+ printf '%s\n' "$$" > "$FM_TEST_SHELLCHECK_PID"
868
+ trap 'exit 143' HUP INT TERM
869
+ while :; do
870
+ sleep 1
871
+ done
872
+ SH
873
+ chmod +x "$fakebin/shellcheck"
874
+
875
+ for jobs in 1 2; do
876
+ for telemetry in off on; do
877
+ lint_tmp="$tmp/lint-$jobs-$telemetry"
878
+ pid_file="$tmp/shellcheck-$jobs-$telemetry.pid"
879
+ out_file="$tmp/output-$jobs-$telemetry"
880
+ telemetry_file=
881
+ mkdir -p "$lint_tmp"
882
+ if [ "$telemetry" = on ]; then
883
+ telemetry_file="$tmp/telemetry-$jobs.tsv"
884
+ fi
885
+ PATH="$fakebin:$PATH" TMPDIR="$lint_tmp" FM_LINT_JOBS="$jobs" \
886
+ FM_LINT_TELEMETRY="$telemetry_file" FM_TEST_SHELLCHECK_PID="$pid_file" \
887
+ "$LINT" "$fixture" > "$out_file" 2>&1 &
888
+ parent_pid=$!
889
+ i=0
890
+ while [ "$i" -lt 500 ] && [ ! -s "$pid_file" ]; do
891
+ kill -0 "$parent_pid" 2>/dev/null || break
892
+ sleep 0.01
893
+ i=$((i + 1))
894
+ done
895
+ [ -s "$pid_file" ] || {
896
+ kill -TERM "$parent_pid" 2>/dev/null || true
897
+ wait "$parent_pid" 2>/dev/null || true
898
+ fail "jobs=$jobs telemetry=$telemetry did not start ShellCheck"
899
+ }
900
+ shellcheck_pid=$(cat "$pid_file")
901
+ kill -TERM "$parent_pid" 2>/dev/null \
902
+ || fail "jobs=$jobs telemetry=$telemetry parent could not be interrupted"
903
+ parent_rc=0
904
+ wait "$parent_pid" 2>/dev/null || parent_rc=$?
905
+ survivor=0
906
+ i=0
907
+ while [ "$i" -lt 100 ] && kill -0 "$shellcheck_pid" 2>/dev/null; do
908
+ sleep 0.01
909
+ i=$((i + 1))
910
+ done
911
+ if kill -0 "$shellcheck_pid" 2>/dev/null; then
912
+ survivor=1
913
+ kill -KILL "$shellcheck_pid" 2>/dev/null || true
914
+ fi
915
+ [ "$parent_rc" -eq 143 ] \
916
+ || fail "jobs=$jobs telemetry=$telemetry signal exit was $parent_rc, expected 143"
917
+ [ "$survivor" -eq 0 ] \
918
+ || fail "jobs=$jobs telemetry=$telemetry left ShellCheck running"
919
+ [ -z "$(find "$lint_tmp" -mindepth 1 -maxdepth 1 -name 'fm-lint.*' -print -quit)" ] \
920
+ || fail "jobs=$jobs telemetry=$telemetry left temporary worker state"
921
+ done
922
+ done
923
+ pass "jobs=1 and jobs=2 stop complete worker trees with and without telemetry"
924
+ }
925
+
926
+ test_seeded_module_boundary_parity() {
927
+ if ! pinned_ready; then
928
+ pass "SKIP (ShellCheck $REQUIRED not resolved): seeded source-boundary parity check"
929
+ return
930
+ fi
931
+ local tmp rel adapter dispatcher dep owner test_root out rc
932
+ tmp=$(mktemp -d "$ROOT/.fm-lint-parity.XXXXXX")
933
+ if [ "${#FM_TEST_CLEANUP_DIRS[@]}" -eq 0 ]; then
934
+ trap fm_test_cleanup EXIT
935
+ fi
936
+ FM_TEST_CLEANUP_DIRS+=("$tmp")
937
+ rel=${tmp#"$ROOT/"}
938
+ adapter="$tmp/adapter.sh"
939
+ dispatcher="$tmp/dispatcher.sh"
940
+ dep="$tmp/owner-dep.sh"
941
+ owner="$tmp/owner.sh"
942
+ test_root="$tmp/test-local.sh"
943
+
944
+ cat > "$adapter" <<'SH'
945
+ #!/usr/bin/env bash
946
+ adapter_bad() {
947
+ rm $1
948
+ }
949
+ SH
950
+ cat > "$dispatcher" <<SH
951
+ #!/usr/bin/env bash
952
+ # shellcheck source=/dev/null
953
+ . "$adapter"
954
+ dispatcher_bad() {
955
+ local a= b=
956
+ printf '%s\n' "\$a\$b"
957
+ }
958
+ SH
959
+ cat > "$dep" <<'SH'
960
+ #!/usr/bin/env bash
961
+ owner_dependency_value=ok
962
+ SH
963
+ cat > "$owner" <<SH
964
+ #!/usr/bin/env bash
965
+ # shellcheck source=$rel/owner-dep.sh
966
+ . "$dep"
967
+ owner_bad() {
968
+ printf '%s\n' "\$owner_dependency_value"
969
+ cd "\$1"
970
+ }
971
+ SH
972
+ cat > "$test_root" <<SH
973
+ #!/usr/bin/env bash
974
+ # shellcheck source=/dev/null
975
+ . "$owner"
976
+ test_local_bad() {
977
+ local output=\$(printf ok)
978
+ printf '%s\n' "\$output"
979
+ }
980
+ SH
981
+
982
+ rc=0
983
+ out=$(FM_LINT_JOBS=2 "$LINT" "$dispatcher" "$adapter" "$owner" "$test_root" 2>&1) || rc=$?
984
+ [ "$rc" -ne 0 ] || fail "seeded module-boundary defects unexpectedly passed"
985
+ assert_contains "$out" "SC1007" "representative dispatcher defect was hidden"
986
+ assert_contains "$out" "SC2086" "representative canonical adapter defect was hidden"
987
+ assert_contains "$out" "SC2164" "representative production-owner defect was hidden"
988
+ assert_contains "$out" "SC2155" "representative test-local defect was hidden"
989
+ assert_not_contains "$out" "SC2154" "the production owner lost source-aware dependency context"
990
+ [ "$(printf '%s\n' "$out" | grep -Fc 'SC2086 (info)')" -eq 1 ] \
991
+ || fail "the dispatcher boundary re-imported the adapter diagnostic"
992
+ [ "$(printf '%s\n' "$out" | grep -Fc 'SC2164 (warning)')" -eq 1 ] \
993
+ || fail "the test boundary re-imported the production-owner diagnostic"
994
+ pass "seeded dispatcher, adapter, production-owner, and test-local diagnostics preserve parity"
995
+ }
996
+
997
+ test_help_reports_the_complete_interface
998
+ test_list_files_reports_the_shell_inventory
999
+ test_fast_mode_disables_extended_analysis
1000
+ test_ci_defaults_to_full_analysis
1001
+ test_ci_rejects_explicit_fast_mode
1002
+ test_fast_mode_catches_a_real_lint_defect
1003
+ test_pins_an_explicit_version
1004
+ test_installer_retries_transient_download_failure
1005
+ test_installer_selects_platform_archive_url_and_checksum
1006
+ test_installer_rejects_wrong_checksum
1007
+ test_installer_falls_back_to_shasum
1008
+ test_installer_prefers_sha256sum_over_shasum
1009
+ test_installer_rejects_unsupported_platform
1010
+ test_missing_shellcheck_fails_closed
1011
+ test_rejects_wrong_shellcheck_version
1012
+ test_catches_a_real_lint_defect
1013
+ test_ignores_ambient_shellcheck_opts
1014
+ test_clean_fixture_passes
1015
+ test_jobs_are_deterministic_and_complete
1016
+ test_worker_trees_stop_on_signal
1017
+ test_seeded_module_boundary_parity
1018
+ test_changed_mode_lints_only_the_changed_file
1019
+ test_ci_forces_full_lint_even_with_empty_diff
1020
+ test_main_branch_forces_full_lint
1021
+ test_explicit_path_bypasses_changed_logic
1022
+ test_zero_changed_files_exits_clean
1023
+ test_list_files_respects_changed_mode