@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.
- package/.agents/README.md +291 -0
- package/.agents/agents/bragi-marketer.md +111 -0
- package/.agents/agents/brokk.md +352 -0
- package/.agents/agents/forseti-reviewer.md +93 -0
- package/.agents/agents/frigg-consent.md +77 -0
- package/.agents/agents/groa-update.md +77 -0
- package/.agents/agents/hnoss-designer.md +69 -0
- package/.agents/agents/huginn-researcher.md +99 -0
- package/.agents/agents/jord-projects.md +78 -0
- package/.agents/agents/kvasir-scout.md +91 -0
- package/.agents/agents/mimir-planner.md +92 -0
- package/.agents/agents/muninn-stow.md +77 -0
- package/.agents/agents/saga-bearings.md +78 -0
- package/.agents/agents/sindri-developer.md +114 -0
- package/.agents/agents/snotra-documenter.md +105 -0
- package/.agents/agents/syn-recovery.md +78 -0
- package/.agents/agents/tyr-check.md +78 -0
- package/.agents/agents/urdh-hold.md +77 -0
- package/.agents/agents/volundr-smith.md +80 -0
- package/.agents/agents/vor-diagnostics.md +77 -0
- package/.agents/assets/agents/naming.md +50 -0
- package/.agents/assets/agents/registry.md +93 -0
- package/.agents/assets/agents/runtime.md +36 -0
- package/.agents/assets/agents/toon-tasks-cli.md +42 -0
- package/.agents/assets/templates/company_entity.template.md +37 -0
- package/.agents/backend/README.md +74 -0
- package/.agents/backend/backends/cmux.sh +649 -0
- package/.agents/backend/backends/herdr-eventwait.py +157 -0
- package/.agents/backend/backends/herdr-workspace-move.py +114 -0
- package/.agents/backend/backends/herdr.sh +3386 -0
- package/.agents/backend/backends/orca.sh +290 -0
- package/.agents/backend/backends/tmux.sh +357 -0
- package/.agents/backend/backends/zellij.sh +700 -0
- package/.agents/backend/fm-afk-launch.sh +654 -0
- package/.agents/backend/fm-afk-return.sh +241 -0
- package/.agents/backend/fm-afk-start.sh +172 -0
- package/.agents/backend/fm-arm-command-policy.mjs +960 -0
- package/.agents/backend/fm-arm-pretool-check.sh +200 -0
- package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
- package/.agents/backend/fm-backend.sh +981 -0
- package/.agents/backend/fm-backlog-handoff.sh +909 -0
- package/.agents/backend/fm-backlog-receive.sh +187 -0
- package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
- package/.agents/backend/fm-bearings-board.sh +204 -0
- package/.agents/backend/fm-bearings-snapshot.sh +546 -0
- package/.agents/backend/fm-bootstrap.sh +1568 -0
- package/.agents/backend/fm-branch-outcome.sh +203 -0
- package/.agents/backend/fm-branch-prompt.sh +101 -0
- package/.agents/backend/fm-brief.sh +452 -0
- package/.agents/backend/fm-busy-event.sh +234 -0
- package/.agents/backend/fm-busy-lib.sh +986 -0
- package/.agents/backend/fm-captain-hold.sh +1001 -0
- package/.agents/backend/fm-cd-command-policy.mjs +153 -0
- package/.agents/backend/fm-cd-pretool-check.sh +190 -0
- package/.agents/backend/fm-check-lib.sh +74 -0
- package/.agents/backend/fm-check-register.sh +42 -0
- package/.agents/backend/fm-check-unregister.sh +52 -0
- package/.agents/backend/fm-classify-lib.sh +1770 -0
- package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
- package/.agents/backend/fm-composer-lib.sh +1416 -0
- package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
- package/.agents/backend/fm-config-push.sh +254 -0
- package/.agents/backend/fm-control-lib.sh +251 -0
- package/.agents/backend/fm-control.sh +878 -0
- package/.agents/backend/fm-crew-state.sh +625 -0
- package/.agents/backend/fm-cursor-lib.sh +243 -0
- package/.agents/backend/fm-decision-hold.sh +232 -0
- package/.agents/backend/fm-doc-audience-check.sh +269 -0
- package/.agents/backend/fm-dod-lib.sh +67 -0
- package/.agents/backend/fm-ensure-agents-md.sh +253 -0
- package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
- package/.agents/backend/fm-extension.mjs +2577 -0
- package/.agents/backend/fm-extension.sh +16 -0
- package/.agents/backend/fm-ff-lib.sh +420 -0
- package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
- package/.agents/backend/fm-fleet-sync.sh +458 -0
- package/.agents/backend/fm-fleet-view.sh +96 -0
- package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
- package/.agents/backend/fm-guard.sh +242 -0
- package/.agents/backend/fm-harness.sh +195 -0
- package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
- package/.agents/backend/fm-herdr-lab.sh +346 -0
- package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
- package/.agents/backend/fm-home-seed.sh +984 -0
- package/.agents/backend/fm-home-summary-refresh.sh +255 -0
- package/.agents/backend/fm-hook-host-lib.sh +36 -0
- package/.agents/backend/fm-inactive-reconcile.sh +531 -0
- package/.agents/backend/fm-inbox.sh +399 -0
- package/.agents/backend/fm-install-actionlint.sh +84 -0
- package/.agents/backend/fm-install-herdr.sh +96 -0
- package/.agents/backend/fm-install-shellcheck.sh +84 -0
- package/.agents/backend/fm-install-treehouse.sh +95 -0
- package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
- package/.agents/backend/fm-lease-lib.sh +218 -0
- package/.agents/backend/fm-lease.sh +189 -0
- package/.agents/backend/fm-line-cap-lib.sh +51 -0
- package/.agents/backend/fm-lint-workflows.sh +137 -0
- package/.agents/backend/fm-lint.sh +591 -0
- package/.agents/backend/fm-lock-lib.sh +104 -0
- package/.agents/backend/fm-lock.sh +107 -0
- package/.agents/backend/fm-marker-lib.sh +12 -0
- package/.agents/backend/fm-merge-local.sh +74 -0
- package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
- package/.agents/backend/fm-nm-run-lib.sh +124 -0
- package/.agents/backend/fm-on.sh +125 -0
- package/.agents/backend/fm-operational-input.sh +253 -0
- package/.agents/backend/fm-peek.sh +46 -0
- package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
- package/.agents/backend/fm-pr-check.sh +135 -0
- package/.agents/backend/fm-pr-lib.sh +1016 -0
- package/.agents/backend/fm-pr-merge.sh +707 -0
- package/.agents/backend/fm-pr-poll.sh +110 -0
- package/.agents/backend/fm-primary-scope-lib.sh +33 -0
- package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
- package/.agents/backend/fm-procevent-lavish.sh +628 -0
- package/.agents/backend/fm-procevent-lib.sh +892 -0
- package/.agents/backend/fm-procevent-quota.sh +290 -0
- package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
- package/.agents/backend/fm-procevent-when.sh +504 -0
- package/.agents/backend/fm-procevent.sh +1466 -0
- package/.agents/backend/fm-project-mode.sh +92 -0
- package/.agents/backend/fm-project-origin-lib.sh +180 -0
- package/.agents/backend/fm-promote.sh +282 -0
- package/.agents/backend/fm-public-followup-emit.sh +260 -0
- package/.agents/backend/fm-public-followup-lib.sh +398 -0
- package/.agents/backend/fm-public-followup.sh +1330 -0
- package/.agents/backend/fm-push-transition-lib.sh +168 -0
- package/.agents/backend/fm-quota-axi-lib.sh +93 -0
- package/.agents/backend/fm-quota-choose.sh +384 -0
- package/.agents/backend/fm-remote-delta-read.sh +189 -0
- package/.agents/backend/fm-remote-doctor.sh +800 -0
- package/.agents/backend/fm-remote-entrypoint.sh +193 -0
- package/.agents/backend/fm-remote-file.sh +234 -0
- package/.agents/backend/fm-remote-home-provision.sh +259 -0
- package/.agents/backend/fm-remote-home-seed.sh +259 -0
- package/.agents/backend/fm-remote-inherit-push.sh +90 -0
- package/.agents/backend/fm-remote-inherit.sh +179 -0
- package/.agents/backend/fm-remote-job-lib.sh +1241 -0
- package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
- package/.agents/backend/fm-remote-job-worker.sh +1124 -0
- package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
- package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
- package/.agents/backend/fm-review-diff.sh +158 -0
- package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
- package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
- package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
- package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
- package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
- package/.agents/backend/fm-secondmate-report.sh +88 -0
- package/.agents/backend/fm-send.sh +1059 -0
- package/.agents/backend/fm-session-lock-lib.sh +176 -0
- package/.agents/backend/fm-session-start.sh +970 -0
- package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
- package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
- package/.agents/backend/fm-sessionstart-run.sh +145 -0
- package/.agents/backend/fm-spawn.sh +3153 -0
- package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
- package/.agents/backend/fm-startup-memory-budget.sh +94 -0
- package/.agents/backend/fm-startup-network.sh +642 -0
- package/.agents/backend/fm-stow-cascade.sh +251 -0
- package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
- package/.agents/backend/fm-supervise-daemon.sh +1732 -0
- package/.agents/backend/fm-supervision-instructions.sh +215 -0
- package/.agents/backend/fm-supervision-lib.sh +90 -0
- package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
- package/.agents/backend/fm-tangle-lib.sh +53 -0
- package/.agents/backend/fm-task-inbox-lib.sh +402 -0
- package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
- package/.agents/backend/fm-teardown.sh +2922 -0
- package/.agents/backend/fm-test-isolation-proof.sh +588 -0
- package/.agents/backend/fm-test-run.sh +2191 -0
- package/.agents/backend/fm-timeout-lib.sh +141 -0
- package/.agents/backend/fm-timing-lib.sh +179 -0
- package/.agents/backend/fm-tmux-lib.sh +291 -0
- package/.agents/backend/fm-tool-update-check.sh +898 -0
- package/.agents/backend/fm-trace-context-lib.sh +227 -0
- package/.agents/backend/fm-transition-lib.sh +103 -0
- package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
- package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
- package/.agents/backend/fm-turnend-guard.sh +432 -0
- package/.agents/backend/fm-update.sh +110 -0
- package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
- package/.agents/backend/fm-voice-client.py +1373 -0
- package/.agents/backend/fm-voice-relay.py +1256 -0
- package/.agents/backend/fm-wake-drain.sh +592 -0
- package/.agents/backend/fm-wake-grant.sh +127 -0
- package/.agents/backend/fm-wake-lib.sh +1849 -0
- package/.agents/backend/fm-watch-arm.sh +602 -0
- package/.agents/backend/fm-watch-checkpoint.sh +109 -0
- package/.agents/backend/fm-watch.sh +1962 -0
- package/.agents/backend/fm-x-dismiss.sh +116 -0
- package/.agents/backend/fm-x-followup.sh +286 -0
- package/.agents/backend/fm-x-lib.sh +1000 -0
- package/.agents/backend/fm-x-link.sh +234 -0
- package/.agents/backend/fm-x-poll.sh +185 -0
- package/.agents/backend/fm-x-reply.sh +385 -0
- package/.agents/backend/fm_voice_frame.py +166 -0
- package/.agents/backend/fm_voice_records.py +574 -0
- package/.agents/backend/model-bridge.py +360 -0
- package/.agents/backend/opencode-go-bridge.py +30 -0
- package/.agents/bus/README.md +12 -0
- package/.agents/bus/messages.json +1 -0
- package/.agents/config/agents.machine.example.yaml +16 -0
- package/.agents/config/agents.yaml.example +72 -0
- package/.agents/config/crew-dispatch.json +22 -0
- package/.agents/config/cron.yaml +11 -0
- package/.agents/config/eindri-dispatch.json +67 -0
- package/.agents/config/eindri-harness +1 -0
- package/.agents/config/startup-memory-budget +1 -0
- package/.agents/filebrowser/.filebrowser.json +6 -0
- package/.agents/filebrowser/README.md +11 -0
- package/.agents/gateway/README.md +12 -0
- package/.agents/github/README.md +15 -0
- package/.agents/github/webhooks/.gitkeep +0 -0
- package/.agents/github/workflows/.gitkeep +0 -0
- package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
- package/.agents/harness/opencode/plugins/package.json +4 -0
- package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
- package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
- package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
- package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
- package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
- package/.agents/memory/README.md +46 -0
- package/.agents/memory/runes_audit.md +5 -0
- package/.agents/migrations/0001-hodd-layout.sh +38 -0
- package/.agents/migrations/0002-a2a-mcp.sh +27 -0
- package/.agents/migrations/0003-private-data-separation.sh +121 -0
- package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
- package/.agents/migrations/0005-roots-out-of-tree.sh +158 -0
- package/.agents/sandbox/Dockerfile.utgard +19 -0
- package/.agents/sandbox/README.md +14 -0
- package/.agents/sandbox/utgard.config.json +14 -0
- package/.agents/skills/README.md +59 -0
- package/.agents/skills/bragi-marketing/README.md +3 -0
- package/.agents/skills/bragi-marketing/SKILL.md +82 -0
- package/.agents/skills/eindri-homes/README.md +3 -0
- package/.agents/skills/eindri-homes/SKILL.md +291 -0
- package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
- package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
- package/.agents/skills/frigg-consent/README.md +3 -0
- package/.agents/skills/frigg-consent/SKILL.md +56 -0
- package/.agents/skills/galdr-ymirsystem/README.md +3 -0
- package/.agents/skills/galdr-ymirsystem/SKILL.md +159 -0
- package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
- package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +736 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
- package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +539 -0
- package/.agents/skills/galdr-ymirsystem/assets/installation.md +763 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
- package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +163 -0
- package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +415 -0
- package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
- package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +133 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +101 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
- package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
- package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
- package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
- package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
- package/.agents/skills/galdr-ymirsystem/assets/smidja.md +180 -0
- package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
- package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
- package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
- package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +503 -0
- package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
- package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
- package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
- package/.agents/skills/gjallarhorn-relay/README.md +3 -0
- package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
- package/.agents/skills/groa-update/SKILL.md +90 -0
- package/.agents/skills/hamr-adapters/README.md +3 -0
- package/.agents/skills/hamr-adapters/SKILL.md +123 -0
- package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
- package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
- package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
- package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
- package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
- package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
- package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
- package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
- package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
- package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
- package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
- package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
- package/.agents/skills/herdr-panes/README.md +3 -0
- package/.agents/skills/herdr-panes/SKILL.md +92 -0
- package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
- package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
- package/.agents/skills/hnoss-design/DESIGN.md +19 -0
- package/.agents/skills/hnoss-design/README.md +3 -0
- package/.agents/skills/hnoss-design/SKILL.md +83 -0
- package/.agents/skills/hvild-afk/README.md +3 -0
- package/.agents/skills/hvild-afk/SKILL.md +241 -0
- package/.agents/skills/jord-projects/README.md +3 -0
- package/.agents/skills/jord-projects/SKILL.md +92 -0
- package/.agents/skills/lifecycle/SKILL.md +56 -0
- package/.agents/skills/lifecycle/smoke_test.sh +57 -0
- package/.agents/skills/lifecycle/start.sh +21 -0
- package/.agents/skills/lifecycle/status.sh +35 -0
- package/.agents/skills/lifecycle/stop.sh +16 -0
- package/.agents/skills/muninn-stow/README.md +3 -0
- package/.agents/skills/muninn-stow/SKILL.md +309 -0
- package/.agents/skills/no-mistakes/SKILL.md +379 -0
- package/.agents/skills/nornir-schedule/README.md +3 -0
- package/.agents/skills/nornir-schedule/SKILL.md +25 -0
- package/.agents/skills/nornir-schedule/assets/events.md +137 -0
- package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
- package/.agents/skills/nsr-compliance/README.md +3 -0
- package/.agents/skills/nsr-compliance/SKILL.md +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/pr-ops/README.md +3 -0
- package/.agents/skills/pr-ops/SKILL.md +109 -0
- package/.agents/skills/ratatoskr-a2a/README.md +3 -0
- package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
- package/.agents/skills/rules-check-drift/SKILL.md +81 -0
- package/.agents/skills/saga-bearings/README.md +3 -0
- package/.agents/skills/saga-bearings/SKILL.md +26 -0
- package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
- package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
- package/.agents/skills/saga-bearings/assets/recap.md +56 -0
- package/.agents/skills/syn-recovery/README.md +3 -0
- package/.agents/skills/syn-recovery/SKILL.md +54 -0
- package/.agents/skills/tyr-check/README.md +3 -0
- package/.agents/skills/tyr-check/SKILL.md +218 -0
- package/.agents/skills/urdh-hold/README.md +3 -0
- package/.agents/skills/urdh-hold/SKILL.md +25 -0
- package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
- package/.agents/skills/urdh-hold/assets/hold.md +54 -0
- package/.agents/skills/vor-diagnostics/README.md +3 -0
- package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
- package/.agents/skills/ymir-host/README.md +3 -0
- package/.agents/skills/ymir-host/SKILL.md +35 -0
- package/.agents/skills/ymir-host/assets/desktop.md +36 -0
- package/.agents/skills/ymir-host/assets/install.md +69 -0
- package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
- package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
- package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
- package/.agents/skills/ymir-host/assets/update.md +64 -0
- package/.agents/tests/assets/board-render-harness.mjs +117 -0
- package/.agents/tests/cmux-test-safety.sh +51 -0
- package/.agents/tests/fixtures.sh +291 -0
- package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
- package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
- package/.agents/tests/fm-afk-launch.test.sh +961 -0
- package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
- package/.agents/tests/fm-afk-return.test.sh +283 -0
- package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
- package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
- package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
- package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
- package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
- package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
- package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
- package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
- package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
- package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
- package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
- package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
- package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
- package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
- package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
- package/.agents/tests/fm-backend-orca.test.sh +1357 -0
- package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
- package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
- package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
- package/.agents/tests/fm-backend.test.sh +1142 -0
- package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
- package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
- package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
- package/.agents/tests/fm-bearings-board.test.sh +414 -0
- package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
- package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
- package/.agents/tests/fm-bootstrap.test.sh +1178 -0
- package/.agents/tests/fm-branch-supervision.test.sh +552 -0
- package/.agents/tests/fm-brief.test.sh +785 -0
- package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
- package/.agents/tests/fm-busy-state.test.sh +465 -0
- package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
- package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
- package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
- package/.agents/tests/fm-check-unregister.test.sh +198 -0
- package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
- package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
- package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
- package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
- package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
- package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
- package/.agents/tests/fm-composer-ghost.test.sh +716 -0
- package/.agents/tests/fm-composer-lib.test.sh +666 -0
- package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
- package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
- package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
- package/.agents/tests/fm-control.test.sh +910 -0
- package/.agents/tests/fm-crew-state.test.sh +1610 -0
- package/.agents/tests/fm-cursor-harness.test.sh +404 -0
- package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
- package/.agents/tests/fm-cursor-primary.test.sh +708 -0
- package/.agents/tests/fm-daemon.test.sh +2740 -0
- package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
- package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
- package/.agents/tests/fm-extension-binding.test.sh +2187 -0
- package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
- package/.agents/tests/fm-fleet-sync.test.sh +721 -0
- package/.agents/tests/fm-gate-refuse.test.sh +356 -0
- package/.agents/tests/fm-gitignore-config.test.sh +89 -0
- package/.agents/tests/fm-gotmp.test.sh +230 -0
- package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
- package/.agents/tests/fm-grok-harness.test.sh +117 -0
- package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
- package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
- package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
- package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
- package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
- package/.agents/tests/fm-herdr-lab.test.sh +243 -0
- package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
- package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
- package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
- package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
- package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
- package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
- package/.agents/tests/fm-kimi-harness.test.sh +687 -0
- package/.agents/tests/fm-lint-workflows.test.sh +532 -0
- package/.agents/tests/fm-lint.test.sh +1023 -0
- package/.agents/tests/fm-muse-harness.test.sh +932 -0
- package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
- package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
- package/.agents/tests/fm-on.test.sh +507 -0
- package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
- package/.agents/tests/fm-operational-input.test.sh +160 -0
- package/.agents/tests/fm-peek-remote.test.sh +110 -0
- package/.agents/tests/fm-pending-reply.test.sh +1288 -0
- package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
- package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
- package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
- package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
- package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
- package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
- package/.agents/tests/fm-pr-merge.test.sh +2135 -0
- package/.agents/tests/fm-procevent-quota.test.sh +225 -0
- package/.agents/tests/fm-procevent-when.test.sh +406 -0
- package/.agents/tests/fm-procevent.test.sh +1745 -0
- package/.agents/tests/fm-project-origin.test.sh +110 -0
- package/.agents/tests/fm-public-followup.test.sh +2272 -0
- package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
- package/.agents/tests/fm-quota-choose.test.sh +626 -0
- package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
- package/.agents/tests/fm-remote-doctor.test.sh +625 -0
- package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
- package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
- package/.agents/tests/fm-remote-job.test.sh +768 -0
- package/.agents/tests/fm-remote-reply.test.sh +520 -0
- package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
- package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
- package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
- package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
- package/.agents/tests/fm-review-diff.test.sh +176 -0
- package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
- package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
- package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
- package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
- package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
- package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
- package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
- package/.agents/tests/fm-send-inbox.test.sh +352 -0
- package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
- package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
- package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
- package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
- package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
- package/.agents/tests/fm-send-settle.test.sh +147 -0
- package/.agents/tests/fm-send-strict.test.sh +241 -0
- package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
- package/.agents/tests/fm-session-start.test.sh +2515 -0
- package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
- package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
- package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
- package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
- package/.agents/tests/fm-spawn-batch.test.sh +148 -0
- package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
- package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
- package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
- package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
- package/.agents/tests/fm-startup-network.test.sh +712 -0
- package/.agents/tests/fm-stow-cascade.test.sh +370 -0
- package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
- package/.agents/tests/fm-supervision-events.test.sh +156 -0
- package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
- package/.agents/tests/fm-tangle-guard.test.sh +277 -0
- package/.agents/tests/fm-task-delivery.test.sh +420 -0
- package/.agents/tests/fm-task-inbox.test.sh +513 -0
- package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
- package/.agents/tests/fm-teardown.test.sh +2665 -0
- package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
- package/.agents/tests/fm-test-fixtures.test.sh +132 -0
- package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
- package/.agents/tests/fm-test-run.test.sh +1196 -0
- package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
- package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
- package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
- package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
- package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
- package/.agents/tests/fm-transition-lib.test.sh +54 -0
- package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
- package/.agents/tests/fm-update.test.sh +304 -0
- package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
- package/.agents/tests/fm-voice-relay.test.sh +4741 -0
- package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
- package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
- package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
- package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
- package/.agents/tests/fm-wake-queue.test.sh +1224 -0
- package/.agents/tests/fm-watch-arm.test.sh +821 -0
- package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
- package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
- package/.agents/tests/fm-watch-triage.test.sh +3889 -0
- package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
- package/.agents/tests/fm-x-mode.test.sh +3042 -0
- package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
- package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
- package/.agents/tests/herdr-test-safety.sh +42 -0
- package/.agents/tests/lib.sh +315 -0
- package/.agents/tests/remote-herdr-fixture.sh +125 -0
- package/.agents/tests/secondmate-helpers.sh +193 -0
- package/.agents/tests/smoke.test.sh +93 -0
- package/.agents/tests/wake-helpers.sh +338 -0
- package/.agents/tests/zellij-test-safety.sh +60 -0
- package/.agents/tools/README.md +10 -0
- package/.agents/tools/bin/tasks-cli +267 -0
- package/.agents/tools/tasks-cli.ts +216 -0
- package/AGENTS.md +498 -0
- package/CHANGELOG.md +1913 -0
- package/LICENSE +1 -1
- package/NOTICE +63 -0
- package/README.md +114 -60
- package/RULES/01-domains.md +51 -0
- package/RULES/02-agents.md +78 -0
- package/RULES/03-houses.md +61 -0
- package/RULES/04-hoard.md +97 -0
- package/RULES/05-platforms.md +67 -0
- package/RULES/06-append-only.md +108 -0
- package/RULES/07-config.md +101 -0
- package/RULES/08-delivery-gate.md +75 -0
- package/RULES/09-electron.md +58 -0
- package/RULES/README.md +22 -0
- package/bin/README.md +107 -0
- package/bin/a2a-mcp.sh +133 -0
- package/bin/a2a-serve.py +103 -0
- package/bin/a2a-serve.sh +8 -0
- package/bin/a2a-talk.sh +50 -0
- package/bin/a2abridge-ensure.sh +108 -0
- package/bin/agent-run.sh +43 -0
- package/bin/agents-config.sh +256 -0
- package/bin/apodex-smoke-test.sh +105 -0
- package/bin/bifrost-bridge.sh +131 -0
- package/bin/bifrost-ingress.sh +77 -0
- package/bin/bootstrap-macos.sh +112 -0
- package/bin/bootstrap-windows.ps1 +112 -0
- package/bin/branch-guard.sh +64 -0
- package/bin/brokk +73 -0
- package/bin/brokk-classify-lib.sh +1770 -0
- package/bin/brokk-lease-lib.sh +222 -0
- package/bin/brokk-lease.sh +193 -0
- package/bin/brokk-lint.sh +65 -0
- package/bin/brokk-send.sh +51 -0
- package/bin/brokk-update.sh +9 -0
- package/bin/brokk-wake-grant.sh +127 -0
- package/bin/brokk-wake-lib.sh +1849 -0
- package/bin/changelog-assemble.sh +154 -0
- package/bin/changelog-guard.sh +197 -0
- package/bin/crash-sense.sh +173 -0
- package/bin/design-check.sh +80 -0
- package/bin/design-icon.sh +153 -0
- package/bin/desktop-place.sh +224 -0
- package/bin/docs-guard.sh +39 -0
- package/bin/editor-place.sh +225 -0
- package/bin/eindri-acclaim.sh +47 -0
- package/bin/eindri-control.sh +28 -0
- package/bin/eindri-role.sh +83 -0
- package/bin/eindri-seat.sh +83 -0
- package/bin/eindri-seen.sh +54 -0
- package/bin/eindri-send.sh +15 -0
- package/bin/eindri-start.sh +98 -0
- package/bin/eindri-watch.sh +57 -0
- package/bin/einherjar-spawn.sh +576 -0
- package/bin/eir-doctor.sh +158 -0
- package/bin/erindi-brief.sh +271 -0
- package/bin/feature-inventory.sh +73 -0
- package/bin/fleet-apply.sh +81 -0
- package/bin/github-deploy.sh +94 -0
- package/bin/gjallarhorn-expose.sh +136 -0
- package/bin/gjallarhorn-notify.sh +71 -0
- package/bin/gjallarhorn-purge.sh +67 -0
- package/bin/gjallarhorn-tunnel.sh +85 -0
- package/bin/gleipnir-lock-lib.sh +284 -0
- package/bin/groa-update.sh +130 -0
- package/bin/hall-snapshot.sh +111 -0
- package/bin/hamr-harness.sh +246 -0
- package/bin/heimdall-ssh-keys.sh +374 -0
- package/bin/herdr-agents.py +44 -0
- package/bin/herdr-ensure.sh +104 -0
- package/bin/herdr-run.sh +452 -0
- package/bin/hermes-ensure.sh +88 -0
- package/bin/hlidskjalf-agents.sh +172 -0
- package/bin/hlidskjalf-usage.sh +196 -0
- package/bin/hoard-guard.sh +135 -0
- package/bin/hoard-lib.sh +105 -0
- package/bin/hodd.sh +102 -0
- package/bin/host-sense.sh +199 -0
- package/bin/huginn-research-worker.sh +121 -0
- package/bin/local-model-lock.sh +70 -0
- package/bin/mimir-bridge.py +259 -0
- package/bin/mimir-bridge.sh +100 -0
- package/bin/mimir-ingest.sh +131 -0
- package/bin/mimir-reflect.sh +27 -0
- package/bin/mimir.sh +113 -0
- package/bin/mjollnir-webhook.sh +72 -0
- package/bin/mjollnir.sh +104 -0
- package/bin/model-hardware.sh +96 -0
- package/bin/model-resolve.sh +160 -0
- package/bin/models-detect.sh +178 -0
- package/bin/nornir-cron-start.sh +162 -0
- package/bin/nornir-job-daily-briefing.sh +191 -0
- package/bin/nornir-job-git-sync.sh +152 -0
- package/bin/nornir-job-hall-snapshot.sh +28 -0
- package/bin/nornir-job-memory-housekeeping.sh +114 -0
- package/bin/nornir-job-nsr-compliance.sh +54 -0
- package/bin/nornir-job-observer.sh +166 -0
- package/bin/nornir-job-skillopt-sleep.sh +76 -0
- package/bin/npm-publish.sh +136 -0
- package/bin/omarchy-hook-install.sh +60 -0
- package/bin/omarchy-install.sh +161 -0
- package/bin/omarchy-plugins.sh +187 -0
- package/bin/omarchy-sense.sh +162 -0
- package/bin/perm-guard.sh +30 -0
- package/bin/pi-agent.sh +68 -0
- package/bin/pi-ensure.sh +77 -0
- package/bin/pi-local.sh +37 -0
- package/bin/pi-model-agnostic.sh +71 -0
- package/bin/pi-seat.sh +63 -0
- package/bin/prereq-ensure.sh +166 -0
- package/bin/private-guard.sh +58 -0
- package/bin/project-git.sh +69 -0
- package/bin/public-guard.sh +71 -0
- package/bin/ratatoskr.sh +49 -0
- package/bin/realm-lib.sh +32 -0
- package/bin/repo-scrub.sh +62 -0
- package/bin/rodd-operational-input.sh +250 -0
- package/bin/runes-append.sh +198 -0
- package/bin/saga-session-start.sh +172 -0
- package/bin/saga-sessionstart-run.sh +79 -0
- package/bin/saga-wake-drain.sh +41 -0
- package/bin/secret-guard.sh +63 -0
- package/bin/sessrumnir-ensure.sh +100 -0
- package/bin/sessrumnir-sync.sh +237 -0
- package/bin/sessrumnir.sh +104 -0
- package/bin/skillopt-setup.sh +76 -0
- package/bin/skuld-branch-outcome.sh +203 -0
- package/bin/skuld-branch-prompt.sh +101 -0
- package/bin/smidja-bootstrap.sh +94 -0
- package/bin/smidja-observe.sh +86 -0
- package/bin/syn-arm-pretool-check.sh +57 -0
- package/bin/syn-asset-pretool-check.sh +89 -0
- package/bin/syn-cd-pretool-check.sh +28 -0
- package/bin/syn-guard-pretool-check.sh +83 -0
- package/bin/syn-spawn-pretool-check.sh +58 -0
- package/bin/syn-turnend-guard.sh +36 -0
- package/bin/syn-watch-arm.sh +99 -0
- package/bin/tailscale-sync.sh +203 -0
- package/bin/telegram-bot.sh +82 -0
- package/bin/toolchain.sh +74 -0
- package/bin/ui-truth-check.sh +75 -0
- package/bin/utgard.sh +111 -0
- package/bin/valhalla.sh +115 -0
- package/bin/valknut-load.sh +298 -0
- package/bin/vor-crew-state.sh +124 -0
- package/bin/wedge-notify.sh +60 -0
- package/bin/workspace-provision.sh +80 -0
- package/bin/workspace-rag.sh +163 -0
- package/bin/wyrd-db.sh +47 -0
- package/bin/yggdrasil.sh +152 -0
- package/bin/ymir-edit.sh +145 -0
- package/bin/ymir-install.sh +806 -0
- package/bin/ymir-invite.sh +93 -0
- package/bin/ymir-isolation.sh +88 -0
- package/bin/ymir-migrate.sh +61 -0
- package/bin/ymir-plan.sh +356 -0
- package/bin/ymir-platform.sh +348 -0
- package/bin/ymir-say.sh +110 -0
- package/bin/ymir-setup-auth.sh +119 -0
- package/bin/ymir-validate.sh +193 -0
- package/bin/ymir.js +74 -16
- package/docs/Architecture.md +339 -0
- package/docs/design.md +351 -0
- package/docs/installations/README.md +63 -0
- package/docs/installations/ymir-home.md +93 -0
- package/docs/integration.md +105 -0
- package/docs/lore.md +1060 -0
- package/docs/research/agent-harnesses.md +290 -0
- package/docs/runbooks/README.md +19 -0
- package/docs/runbooks/a2a.md +91 -0
- package/docs/runbooks/agent-permissions.md +99 -0
- package/docs/runbooks/agents.md +57 -0
- package/docs/runbooks/models.md +69 -0
- package/docs/runbooks/realm-onboarding.md +60 -0
- package/docs/runbooks/secrets-and-hoard.md +60 -0
- package/docs/runbooks/updates-and-migrations.md +110 -0
- package/docs/runbooks/worktrees-isolation.md +72 -0
- package/docs/session-start.md +105 -0
- package/docs/skillopt-integration.md +321 -0
- package/docs/sync-system.md +66 -0
- package/docs/workspaces.md +104 -0
- package/docs/ymir-rut.md +260 -0
- package/package.json +54 -14
- package/packaging/build.sh +74 -0
- package/packaging/macos/Ymir Installer.command +43 -0
- package/packaging/windows/ymir-setup.nsi +74 -0
- package/scripts/README.md +22 -0
- package/scripts/electron.sh +269 -0
- package/scripts/lower.sh +34 -0
- package/scripts/raise.sh +42 -0
- package/scripts/start.sh +217 -0
- package/scripts/stop.sh +113 -0
- package/install.sh +0 -35
|
@@ -0,0 +1,1142 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# tests/fm-backend.test.sh - P1 runtime-backend extraction conformance
|
|
3
|
+
# (data/fm-backend-design-d7/report.md, herdr-addendum.md "events as the core
|
|
4
|
+
# abstraction"). bin/fm-backend.sh and bin/backends/tmux.sh move the tmux
|
|
5
|
+
# command sequences that fm-send.sh, fm-peek.sh, fm-spawn.sh, and
|
|
6
|
+
# fm-teardown.sh used to run inline into named adapter functions. This suite:
|
|
7
|
+
#
|
|
8
|
+
# 1. Unit-tests bin/fm-backend.sh's selection, meta, and dispatch helpers.
|
|
9
|
+
# 2. Runs the PRE-REFACTOR versions of fm-send.sh, fm-peek.sh, fm-spawn.sh,
|
|
10
|
+
# and fm-teardown.sh (checked out from the merge-base with `main`, the
|
|
11
|
+
# commit this branch started from) against the SAME fake tmux/treehouse
|
|
12
|
+
# binaries and fixtures as the REFACTORED versions in this checkout, then
|
|
13
|
+
# diffs the two command logs byte-for-byte - the report's P1 checklist
|
|
14
|
+
# item "run current main scripts and refactored scripts against the same
|
|
15
|
+
# fake tools and compare command logs". The teardown old-vs-new case also
|
|
16
|
+
# overlays a content-historical permissive tmux kill fixture: after the
|
|
17
|
+
# exact-selector change lands on the default branch, merge-base with main
|
|
18
|
+
# collapses to HEAD and can no longer supply that baseline.
|
|
19
|
+
# 3. Asserts the `--backend`/`FM_BACKEND` selection refuses unknown backends
|
|
20
|
+
# and the blocked `codex-app` backend loudly.
|
|
21
|
+
#
|
|
22
|
+
# fm-watch.sh's signal/stale/check/heartbeat wake-string contract is already
|
|
23
|
+
# exercised end-to-end against this refactor by tests/fm-watch-triage.test.sh
|
|
24
|
+
# and tests/wake-helpers.sh (same fake-tmux convention, run against the
|
|
25
|
+
# now-refactored bin/fm-watch.sh); this suite adds one direct old-vs-new
|
|
26
|
+
# diff for the stale-pane path specifically, since that is the one wake path
|
|
27
|
+
# that now calls through fm_backend_capture instead of tmux directly.
|
|
28
|
+
# The real tmux smoke test (create session, send text + Enter, capture, list,
|
|
29
|
+
# kill) lives in tests/fm-backend-tmux-smoke.test.sh.
|
|
30
|
+
set -u
|
|
31
|
+
|
|
32
|
+
# shellcheck source=tests/lib.sh
|
|
33
|
+
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
|
34
|
+
fm_git_identity fmtest fmtest@example.invalid
|
|
35
|
+
|
|
36
|
+
# shellcheck source=/dev/null
|
|
37
|
+
. "$ROOT/bin/fm-backend.sh"
|
|
38
|
+
|
|
39
|
+
TMP_ROOT=$(fm_test_tmproot fm-backend-tests)
|
|
40
|
+
|
|
41
|
+
# fm_backend_detect's cmux fallback (bundle id + process ancestry,
|
|
42
|
+
# docs/cmux-backend.md "Runtime auto-detection") consults uname, lsappinfo,
|
|
43
|
+
# and ps. FAKE_NONDARWIN_BIN pins uname to Linux so the whole fallback is
|
|
44
|
+
# deterministically inert for every assertion that expects NO detection,
|
|
45
|
+
# regardless of the ambient runtime this suite itself executes inside (a real
|
|
46
|
+
# cmux tab would otherwise leak a bundle-id or ancestry match into results).
|
|
47
|
+
FAKE_NONDARWIN_BIN="$TMP_ROOT/fake-nondarwin-bin"
|
|
48
|
+
mkdir -p "$FAKE_NONDARWIN_BIN"
|
|
49
|
+
printf '#!/bin/sh\necho Linux\n' > "$FAKE_NONDARWIN_BIN/uname"
|
|
50
|
+
chmod +x "$FAKE_NONDARWIN_BIN/uname"
|
|
51
|
+
|
|
52
|
+
# make_cmux_fallback_fakebin: PATH fakes for the DETECTING side of the cmux
|
|
53
|
+
# fallback - uname pinned to Darwin, lsappinfo echoing $FM_FAKE_LSAPPINFO_OUT
|
|
54
|
+
# (empty output mirrors the real lsappinfo's app-not-running behavior: prints
|
|
55
|
+
# nothing, exit 0), and a ps answering `-o ppid=/-o comm= -p <pid>` from the
|
|
56
|
+
# tab-separated "pid ppid comm" table file named by $FM_FAKE_PS_TABLE.
|
|
57
|
+
make_cmux_fallback_fakebin() { # <dir> -> echoes fakebin dir
|
|
58
|
+
local fb="$1/fakebin-cmux-fallback"
|
|
59
|
+
mkdir -p "$fb"
|
|
60
|
+
printf '#!/bin/sh\necho Darwin\n' > "$fb/uname"
|
|
61
|
+
cat > "$fb/lsappinfo" <<'SH'
|
|
62
|
+
#!/bin/sh
|
|
63
|
+
[ -n "${FM_FAKE_LSAPPINFO_OUT:-}" ] && printf '%s\n' "$FM_FAKE_LSAPPINFO_OUT"
|
|
64
|
+
exit 0
|
|
65
|
+
SH
|
|
66
|
+
cat > "$fb/ps" <<'SH'
|
|
67
|
+
#!/bin/sh
|
|
68
|
+
# supports exactly: ps -o ppid= -p <pid> / ps -o comm= -p <pid>
|
|
69
|
+
field=${2:-} pid=${4:-}
|
|
70
|
+
while IFS=" " read -r tpid tppid tcomm; do
|
|
71
|
+
if [ "$tpid" = "$pid" ]; then
|
|
72
|
+
case "$field" in
|
|
73
|
+
ppid=) printf '%s\n' "$tppid" ;;
|
|
74
|
+
comm=) printf '%s\n' "$tcomm" ;;
|
|
75
|
+
esac
|
|
76
|
+
exit 0
|
|
77
|
+
fi
|
|
78
|
+
done < "${FM_FAKE_PS_TABLE:?}"
|
|
79
|
+
exit 1
|
|
80
|
+
SH
|
|
81
|
+
chmod +x "$fb/uname" "$fb/lsappinfo" "$fb/ps"
|
|
82
|
+
printf '%s\n' "$fb"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# The commit this branch started from - the P1 "current main" baseline.
|
|
86
|
+
# Suitable for byte-identical old-vs-new checks while a branch still diverges
|
|
87
|
+
# from main. After a squash lands, merge-base(HEAD, main) collapses to HEAD, so
|
|
88
|
+
# callers that need a true pre-change fixture must not rely on this alone.
|
|
89
|
+
resolve_base_ref() {
|
|
90
|
+
local ref base
|
|
91
|
+
for ref in main refs/heads/main origin/main refs/remotes/origin/main origin/HEAD refs/remotes/origin/HEAD; do
|
|
92
|
+
if git -C "$ROOT" rev-parse --verify -q "$ref^{commit}" >/dev/null; then
|
|
93
|
+
base=$(git -C "$ROOT" merge-base HEAD "$ref" 2>/dev/null) || continue
|
|
94
|
+
[ -n "$base" ] || continue
|
|
95
|
+
printf '%s\n' "$base"
|
|
96
|
+
return 0
|
|
97
|
+
fi
|
|
98
|
+
done
|
|
99
|
+
return 1
|
|
100
|
+
}
|
|
101
|
+
BASE_REF=$(resolve_base_ref) \
|
|
102
|
+
|| fail "fm-backend baseline requires local main or origin/main; fetch the default branch before running this test"
|
|
103
|
+
|
|
104
|
+
# Newest first-parent revision whose bin/backends/tmux.sh still uses the
|
|
105
|
+
# pre-exact permissive kill-window target. Content-addressed from history so the
|
|
106
|
+
# fixture stays historical on default-branch CI and on branches cut after the
|
|
107
|
+
# exact-selector change, where merge-base with main is self-referential.
|
|
108
|
+
resolve_permissive_tmux_kill_ref() {
|
|
109
|
+
local commit body
|
|
110
|
+
while IFS= read -r commit; do
|
|
111
|
+
[ -n "$commit" ] || continue
|
|
112
|
+
body=$(git -C "$ROOT" show "$commit:bin/backends/tmux.sh" 2>/dev/null) || continue
|
|
113
|
+
# shellcheck disable=SC2016
|
|
114
|
+
case "$body" in
|
|
115
|
+
*'tmux kill-window -t "=$session:=$window"'*) continue ;;
|
|
116
|
+
esac
|
|
117
|
+
# shellcheck disable=SC2016
|
|
118
|
+
case "$body" in
|
|
119
|
+
*'tmux kill-window -t "$1"'*|*'tmux kill-window -t "$target"'*)
|
|
120
|
+
printf '%s\n' "$commit"
|
|
121
|
+
return 0
|
|
122
|
+
;;
|
|
123
|
+
esac
|
|
124
|
+
done < <(git -C "$ROOT" log --first-parent --format='%H' HEAD -- bin/backends/tmux.sh)
|
|
125
|
+
return 1
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# --- shared: a pre-refactor bin/ shim --------------------------------------
|
|
129
|
+
#
|
|
130
|
+
# build_old_bin echoes a directory whose bin/ subdir is the complete bin/ tree
|
|
131
|
+
# from BASE_REF.
|
|
132
|
+
# Materializing the whole historical tree keeps every entrypoint and sourced
|
|
133
|
+
# sibling on the same revision, while avoiding a hand-maintained dependency
|
|
134
|
+
# list that can omit a newly sourced helper and make the old process abort
|
|
135
|
+
# before it reaches the behavior under test.
|
|
136
|
+
# FM_ROOT_OVERRIDE pointed at this dir's root makes
|
|
137
|
+
# "$FM_ROOT/bin/fm-project-mode.sh" (etc.) resolve correctly.
|
|
138
|
+
# The teardown conformance case applies its explicitly historical tmux adapter
|
|
139
|
+
# after this complete baseline has been materialized.
|
|
140
|
+
|
|
141
|
+
build_old_bin() { # <name> -> echoes root dir (root/bin/<script> is the entry point)
|
|
142
|
+
local name=$1 root archive
|
|
143
|
+
root="$TMP_ROOT/$name"
|
|
144
|
+
archive="$root/bin.tar"
|
|
145
|
+
mkdir -p "$root"
|
|
146
|
+
git -C "$ROOT" archive --format=tar "$BASE_REF" bin > "$archive" \
|
|
147
|
+
|| fail "old-bin shim: could not archive bin/ from $BASE_REF"
|
|
148
|
+
tar -xf "$archive" -C "$root" \
|
|
149
|
+
|| fail "old-bin shim: could not extract bin/ from $BASE_REF"
|
|
150
|
+
rm -f "$archive"
|
|
151
|
+
printf '%s\n' "$root"
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# --- fm-backend.sh unit tests ------------------------------------------------
|
|
155
|
+
|
|
156
|
+
test_backend_name_precedence() {
|
|
157
|
+
local dir cfg
|
|
158
|
+
dir="$TMP_ROOT/name-precedence"; cfg="$dir/config"
|
|
159
|
+
mkdir -p "$cfg"
|
|
160
|
+
|
|
161
|
+
# TMUX/HERDR_ENV/CMUX_WORKSPACE_ID explicitly unset in a subshell so this
|
|
162
|
+
# stays deterministic regardless of the runtime this test suite itself
|
|
163
|
+
# happens to execute inside (e.g. a real tmux pane, which is the normal case
|
|
164
|
+
# for a captain's session).
|
|
165
|
+
# fm_backend_name reads FM_BACKEND_CONFIG_DIR (bound once, at fm-backend.sh
|
|
166
|
+
# source time, from FM_CONFIG_OVERRIDE); a later FM_CONFIG_OVERRIDE=... prefix
|
|
167
|
+
# on the function call itself does not re-bind it, so these calls set
|
|
168
|
+
# FM_BACKEND_CONFIG_DIR directly.
|
|
169
|
+
[ "$(unset TMUX HERDR_ENV CMUX_WORKSPACE_ID __CFBundleIdentifier; PATH="$FAKE_NONDARWIN_BIN:$PATH" FM_BACKEND='' FM_BACKEND_CONFIG_DIR="$cfg" fm_backend_name)" = tmux ] \
|
|
170
|
+
|| fail "fm_backend_name should default to tmux with no env/config/detection markers"
|
|
171
|
+
|
|
172
|
+
printf 'tmux\n' > "$cfg/backend"
|
|
173
|
+
[ "$(unset TMUX HERDR_ENV CMUX_WORKSPACE_ID; FM_BACKEND='' FM_BACKEND_CONFIG_DIR="$cfg" fm_backend_name)" = tmux ] \
|
|
174
|
+
|| fail "fm_backend_name should read config/backend"
|
|
175
|
+
|
|
176
|
+
[ "$(unset TMUX HERDR_ENV CMUX_WORKSPACE_ID; FM_BACKEND=tmux FM_BACKEND_CONFIG_DIR="$cfg" fm_backend_name)" = tmux ] \
|
|
177
|
+
|| fail "FM_BACKEND env should win over config/backend"
|
|
178
|
+
|
|
179
|
+
pass "fm_backend_name: FM_BACKEND env > config/backend > default tmux"
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
# fm_backend_detect: environment-marker runtime auto-detection (mirrors
|
|
183
|
+
# fm-harness.sh's detect_own layer). Every case explicitly controls TMUX,
|
|
184
|
+
# HERDR_ENV, and CMUX_WORKSPACE_ID - and, where no detection is expected, the
|
|
185
|
+
# cmux fallback inputs (__CFBundleIdentifier plus a non-Darwin uname fake) -
|
|
186
|
+
# so results never depend on the ambient shell this suite runs inside (a real
|
|
187
|
+
# tmux pane or cmux tab, both normal cases for a captain's session).
|
|
188
|
+
test_backend_detect_precedence() {
|
|
189
|
+
local out
|
|
190
|
+
|
|
191
|
+
if out=$(unset TMUX HERDR_ENV CMUX_WORKSPACE_ID __CFBundleIdentifier; PATH="$FAKE_NONDARWIN_BIN:$PATH" fm_backend_detect); then
|
|
192
|
+
fail "fm_backend_detect should return 1 (undetected) with no markers set, got '$out'"
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
out=$(unset TMUX CMUX_WORKSPACE_ID; HERDR_ENV=1 fm_backend_detect) \
|
|
196
|
+
|| fail "fm_backend_detect should succeed when HERDR_ENV=1"
|
|
197
|
+
[ "$out" = herdr ] || fail "fm_backend_detect should report herdr for HERDR_ENV=1 alone, got '$out'"
|
|
198
|
+
|
|
199
|
+
out=$(unset HERDR_ENV CMUX_WORKSPACE_ID; TMUX='fake,1,0' fm_backend_detect) \
|
|
200
|
+
|| fail "fm_backend_detect should succeed when \$TMUX is set"
|
|
201
|
+
[ "$out" = tmux ] || fail "fm_backend_detect should report tmux for \$TMUX alone, got '$out'"
|
|
202
|
+
|
|
203
|
+
out=$(unset TMUX HERDR_ENV; CMUX_WORKSPACE_ID='fake-uuid' fm_backend_detect) \
|
|
204
|
+
|| fail "fm_backend_detect should succeed when CMUX_WORKSPACE_ID is set"
|
|
205
|
+
[ "$out" = cmux ] || fail "fm_backend_detect should report cmux for CMUX_WORKSPACE_ID alone, got '$out'"
|
|
206
|
+
|
|
207
|
+
# Nesting: tmux started inside a herdr pane carries BOTH markers. Innermost
|
|
208
|
+
# (tmux) must win, since that is the surface firstmate is actually running on.
|
|
209
|
+
out=$(unset CMUX_WORKSPACE_ID; TMUX='fake,1,0' HERDR_ENV=1 fm_backend_detect) \
|
|
210
|
+
|| fail "fm_backend_detect should succeed with both markers present"
|
|
211
|
+
[ "$out" = tmux ] || fail "fm_backend_detect should resolve nesting innermost-first (tmux over herdr), got '$out'"
|
|
212
|
+
|
|
213
|
+
# Nesting: tmux started inside a cmux-provided shell carries BOTH markers.
|
|
214
|
+
# cmux is a terminal application, not a nestable multiplexer, so the
|
|
215
|
+
# innermost multiplexer (tmux) must still win.
|
|
216
|
+
out=$(unset HERDR_ENV; TMUX='fake,1,0' CMUX_WORKSPACE_ID='fake-uuid' fm_backend_detect) \
|
|
217
|
+
|| fail "fm_backend_detect should succeed with tmux and cmux markers present"
|
|
218
|
+
[ "$out" = tmux ] || fail "fm_backend_detect should resolve nesting innermost-first (tmux over cmux), got '$out'"
|
|
219
|
+
|
|
220
|
+
# Nesting: herdr started inside a cmux-provided shell carries BOTH markers.
|
|
221
|
+
# Same reasoning: herdr (the innermost multiplexer) must win over cmux.
|
|
222
|
+
out=$(unset TMUX; HERDR_ENV=1 CMUX_WORKSPACE_ID='fake-uuid' fm_backend_detect) \
|
|
223
|
+
|| fail "fm_backend_detect should succeed with herdr and cmux markers present"
|
|
224
|
+
[ "$out" = herdr ] || fail "fm_backend_detect should resolve nesting innermost-first (herdr over cmux), got '$out'"
|
|
225
|
+
|
|
226
|
+
# Pathological: all three markers present. tmux still wins (innermost of all).
|
|
227
|
+
out=$(TMUX='fake,1,0' HERDR_ENV=1 CMUX_WORKSPACE_ID='fake-uuid' fm_backend_detect) \
|
|
228
|
+
|| fail "fm_backend_detect should succeed with all three markers present"
|
|
229
|
+
[ "$out" = tmux ] || fail "fm_backend_detect should resolve nesting innermost-first with all three markers (tmux wins), got '$out'"
|
|
230
|
+
|
|
231
|
+
pass "fm_backend_detect: no markers -> undetected, HERDR_ENV=1 -> herdr, \$TMUX -> tmux, CMUX_WORKSPACE_ID -> cmux, nested combinations resolve innermost-first"
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
# fm_backend_detect's cmux FALLBACK signals (docs/cmux-backend.md "Runtime
|
|
235
|
+
# auto-detection"): cmux's bundled claude wrapper strips every CMUX_* env var
|
|
236
|
+
# on its passthrough path, so a claude-under-cmux firstmate has no
|
|
237
|
+
# CMUX_WORKSPACE_ID; detection then falls back to __CFBundleIdentifier and,
|
|
238
|
+
# after that, a process-ancestry walk - macOS-only, and never outranking the
|
|
239
|
+
# $TMUX/HERDR_ENV innermost-first checks.
|
|
240
|
+
test_backend_detect_cmux_fallback_bundle_id() {
|
|
241
|
+
local dir fb out
|
|
242
|
+
dir="$TMP_ROOT/detect-fallback-bundle"; mkdir -p "$dir"
|
|
243
|
+
fb=$(make_cmux_fallback_fakebin "$dir")
|
|
244
|
+
|
|
245
|
+
out=$(unset TMUX HERDR_ENV CMUX_WORKSPACE_ID; PATH="$fb:$PATH" __CFBundleIdentifier='com.cmuxterm.app' fm_backend_detect) \
|
|
246
|
+
|| fail "fm_backend_detect should fall back to the cmux bundle id when CMUX_WORKSPACE_ID is absent"
|
|
247
|
+
[ "$out" = cmux ] || fail "bundle-id fallback should report cmux, got '$out'"
|
|
248
|
+
|
|
249
|
+
(
|
|
250
|
+
unset TMUX HERDR_ENV CMUX_WORKSPACE_ID
|
|
251
|
+
PATH="$fb:$PATH" __CFBundleIdentifier='com.cmuxterm.app' fm_backend_detect >/dev/null || exit 1
|
|
252
|
+
[ "$FM_BACKEND_DETECT_SIGNAL" = bundle-id ] || exit 2
|
|
253
|
+
) || fail "bundle-id fallback should set FM_BACKEND_DETECT_SIGNAL=bundle-id (subshell exit $?)"
|
|
254
|
+
|
|
255
|
+
# A foreign bundle id (an ordinary terminal app) must not match.
|
|
256
|
+
if out=$(unset TMUX HERDR_ENV CMUX_WORKSPACE_ID; PATH="$fb:$PATH" FM_FAKE_PS_TABLE="$dir/no-table" __CFBundleIdentifier='com.apple.Terminal' fm_backend_detect); then
|
|
257
|
+
fail "a non-cmux __CFBundleIdentifier should not detect cmux, got '$out'"
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
pass "fm_backend_detect: falls back to __CFBundleIdentifier=com.cmuxterm.app when CMUX_WORKSPACE_ID is absent (signal bundle-id; foreign bundle ids rejected)"
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
test_backend_detect_cmux_fallback_requires_darwin() {
|
|
264
|
+
local out
|
|
265
|
+
if out=$(unset TMUX HERDR_ENV CMUX_WORKSPACE_ID; PATH="$FAKE_NONDARWIN_BIN:$PATH" __CFBundleIdentifier='com.cmuxterm.app' fm_backend_detect); then
|
|
266
|
+
fail "the cmux fallback must be macOS-only (cmux itself is), got '$out' on a non-Darwin uname"
|
|
267
|
+
fi
|
|
268
|
+
pass "fm_backend_detect: the cmux fallback signals are macOS-only (inert on a non-Darwin uname)"
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
# The false positive the innermost-first ordering must keep absorbing: a tmux
|
|
272
|
+
# server started from a cmux tab inherits __CFBundleIdentifier=com.cmuxterm.app
|
|
273
|
+
# into every pane (verified live, docs/cmux-backend.md), so the bundle-id
|
|
274
|
+
# fallback WILL match inside such panes - $TMUX winning first is what keeps
|
|
275
|
+
# the result correct. Same for a herdr pane whose server was started from a
|
|
276
|
+
# cmux tab.
|
|
277
|
+
test_backend_detect_cmux_fallback_tmux_nested_false_positive() {
|
|
278
|
+
local dir fb out
|
|
279
|
+
dir="$TMP_ROOT/detect-fallback-nested"; mkdir -p "$dir"
|
|
280
|
+
fb=$(make_cmux_fallback_fakebin "$dir")
|
|
281
|
+
|
|
282
|
+
out=$(unset HERDR_ENV CMUX_WORKSPACE_ID; PATH="$fb:$PATH" TMUX='fake,1,0' __CFBundleIdentifier='com.cmuxterm.app' fm_backend_detect) \
|
|
283
|
+
|| fail "fm_backend_detect should still succeed with \$TMUX plus an inherited cmux bundle id"
|
|
284
|
+
[ "$out" = tmux ] || fail "\$TMUX must win over an inherited cmux bundle id (tmux-inside-cmux pane), got '$out'"
|
|
285
|
+
|
|
286
|
+
out=$(unset TMUX CMUX_WORKSPACE_ID; PATH="$fb:$PATH" HERDR_ENV=1 __CFBundleIdentifier='com.cmuxterm.app' fm_backend_detect) \
|
|
287
|
+
|| fail "fm_backend_detect should still succeed with HERDR_ENV=1 plus an inherited cmux bundle id"
|
|
288
|
+
[ "$out" = herdr ] || fail "HERDR_ENV=1 must win over an inherited cmux bundle id (herdr-inside-cmux pane), got '$out'"
|
|
289
|
+
|
|
290
|
+
pass "fm_backend_detect: an inherited cmux bundle id never outranks \$TMUX or HERDR_ENV (tmux/herdr-inside-cmux false positive absorbed)"
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
test_backend_detect_cmux_fallback_ancestry_pid_match() {
|
|
294
|
+
local dir fb table
|
|
295
|
+
dir="$TMP_ROOT/detect-ancestry-pid"; mkdir -p "$dir"
|
|
296
|
+
fb=$(make_cmux_fallback_fakebin "$dir")
|
|
297
|
+
table="$dir/ps-table"
|
|
298
|
+
# $$ is this test script's own pid - the walk starts there. The cmux app
|
|
299
|
+
# pid (66666) is matched via the lsappinfo bundle-id resolution, with a
|
|
300
|
+
# deliberately non-standard install path so only the pid can match.
|
|
301
|
+
printf '%s\t77777\t/bin/zsh\n77777\t66666\t/usr/bin/login\n66666\t1\t/home/x/Custom.app/Contents/MacOS/custom\n' "$$" > "$table"
|
|
302
|
+
|
|
303
|
+
(
|
|
304
|
+
unset TMUX HERDR_ENV CMUX_WORKSPACE_ID __CFBundleIdentifier
|
|
305
|
+
PATH="$fb:$PATH" FM_FAKE_PS_TABLE="$table" FM_FAKE_LSAPPINFO_OUT='"pid"=66666' fm_backend_detect >/dev/null || exit 1
|
|
306
|
+
[ "$FM_BACKEND_DETECTED" = cmux ] || exit 2
|
|
307
|
+
[ "$FM_BACKEND_DETECT_SIGNAL" = ancestry ] || exit 3
|
|
308
|
+
) || fail "ancestry fallback should detect cmux via the lsappinfo-resolved app pid (subshell exit $?)"
|
|
309
|
+
|
|
310
|
+
pass "fm_backend_detect: ancestry fallback matches the lsappinfo-resolved (bundle-id) cmux app pid in the parent chain"
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
test_backend_detect_cmux_fallback_ancestry_comm_match() {
|
|
314
|
+
local dir fb table
|
|
315
|
+
dir="$TMP_ROOT/detect-ancestry-comm"; mkdir -p "$dir"
|
|
316
|
+
fb=$(make_cmux_fallback_fakebin "$dir")
|
|
317
|
+
table="$dir/ps-table"
|
|
318
|
+
# lsappinfo resolves nothing (empty output, like the real one for a
|
|
319
|
+
# non-running or non-GUI-visible app); the bundle-shaped comm path is the
|
|
320
|
+
# remaining match, at a non-/Applications install location on purpose.
|
|
321
|
+
printf '%s\t77777\t/bin/zsh\n77777\t66666\t/usr/bin/login\n66666\t1\t/home/x/Applications/cmux.app/Contents/MacOS/cmux\n' "$$" > "$table"
|
|
322
|
+
|
|
323
|
+
(
|
|
324
|
+
unset TMUX HERDR_ENV CMUX_WORKSPACE_ID __CFBundleIdentifier FM_FAKE_LSAPPINFO_OUT
|
|
325
|
+
PATH="$fb:$PATH" FM_FAKE_PS_TABLE="$table" fm_backend_detect >/dev/null || exit 1
|
|
326
|
+
[ "$FM_BACKEND_DETECTED" = cmux ] || exit 2
|
|
327
|
+
[ "$FM_BACKEND_DETECT_SIGNAL" = ancestry ] || exit 3
|
|
328
|
+
) || fail "ancestry fallback should detect cmux via a bundle-shaped comm path when lsappinfo resolves nothing (subshell exit $?)"
|
|
329
|
+
|
|
330
|
+
pass "fm_backend_detect: ancestry fallback matches a bundle-shaped cmux comm path at any install location when lsappinfo cannot resolve a pid"
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
# From inside tmux, ancestry can never reach cmux: the tmux server reparents
|
|
334
|
+
# to launchd (verified live - the reference machine's own tmux server, started
|
|
335
|
+
# from a cmux tab, has ppid 1), so the walk stops at ppid 1 undetected. This
|
|
336
|
+
# pins the walk's launchd stop as the structural guarantee behind that.
|
|
337
|
+
test_backend_detect_cmux_fallback_ancestry_stops_at_launchd() {
|
|
338
|
+
local dir fb table out
|
|
339
|
+
dir="$TMP_ROOT/detect-ancestry-stop"; mkdir -p "$dir"
|
|
340
|
+
fb=$(make_cmux_fallback_fakebin "$dir")
|
|
341
|
+
table="$dir/ps-table"
|
|
342
|
+
printf '%s\t77777\t/bin/zsh\n77777\t1\ttmux\n' "$$" > "$table"
|
|
343
|
+
|
|
344
|
+
if out=$(unset TMUX HERDR_ENV CMUX_WORKSPACE_ID __CFBundleIdentifier FM_FAKE_LSAPPINFO_OUT; PATH="$fb:$PATH" FM_FAKE_PS_TABLE="$table" fm_backend_detect); then
|
|
345
|
+
fail "ancestry fallback should stop undetected at a launchd-reparented chain, got '$out'"
|
|
346
|
+
fi
|
|
347
|
+
pass "fm_backend_detect: ancestry fallback stops undetected at launchd (a reparented tmux server never reaches cmux)"
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
# The auto-detect NOTICE must say when cmux was selected via a fallback
|
|
351
|
+
# signal, so a captain can tell a wrapper-stripped claude-under-cmux spawn
|
|
352
|
+
# apart from the primary-marker case.
|
|
353
|
+
test_backend_name_cmux_fallback_notice() {
|
|
354
|
+
local dir cfg fb out errfile
|
|
355
|
+
dir="$TMP_ROOT/name-fallback-notice"; cfg="$dir/config-empty"; mkdir -p "$cfg"
|
|
356
|
+
fb=$(make_cmux_fallback_fakebin "$dir")
|
|
357
|
+
errfile="$dir/err.txt"
|
|
358
|
+
|
|
359
|
+
: > "$errfile"
|
|
360
|
+
out=$(unset TMUX HERDR_ENV CMUX_WORKSPACE_ID; PATH="$fb:$PATH" __CFBundleIdentifier='com.cmuxterm.app' FM_BACKEND='' FM_BACKEND_CONFIG_DIR="$cfg" fm_backend_name 2>"$errfile")
|
|
361
|
+
[ "$out" = cmux ] || fail "fm_backend_name should auto-detect cmux via the bundle-id fallback, got '$out'"
|
|
362
|
+
assert_contains "$(cat "$errfile")" "FALLBACK signal __CFBundleIdentifier" \
|
|
363
|
+
"the fallback-detected cmux notice did not name the bundle-id fallback signal"
|
|
364
|
+
assert_contains "$(cat "$errfile")" "EXPERIMENTAL cmux backend" \
|
|
365
|
+
"the fallback-detected cmux notice lost the experimental warning"
|
|
366
|
+
assert_contains "$(cat "$errfile")" "--backend tmux" \
|
|
367
|
+
"the fallback-detected cmux notice lost the opt-out"
|
|
368
|
+
|
|
369
|
+
# The primary-marker notice is unchanged: it names CMUX_WORKSPACE_ID and
|
|
370
|
+
# carries no FALLBACK wording.
|
|
371
|
+
: > "$errfile"
|
|
372
|
+
out=$(unset TMUX HERDR_ENV; CMUX_WORKSPACE_ID='fake-uuid' FM_BACKEND='' FM_BACKEND_CONFIG_DIR="$cfg" fm_backend_name 2>"$errfile")
|
|
373
|
+
[ "$out" = cmux ] || fail "fm_backend_name should auto-detect cmux from CMUX_WORKSPACE_ID, got '$out'"
|
|
374
|
+
assert_contains "$(cat "$errfile")" "(CMUX_WORKSPACE_ID)" \
|
|
375
|
+
"the primary-marker cmux notice no longer names CMUX_WORKSPACE_ID"
|
|
376
|
+
case "$(cat "$errfile")" in
|
|
377
|
+
*FALLBACK*) fail "the primary-marker cmux notice must not carry FALLBACK wording" ;;
|
|
378
|
+
esac
|
|
379
|
+
|
|
380
|
+
pass "fm_backend_name: a fallback-detected cmux prints a NOTICE naming the fallback signal; the primary-marker notice is unchanged"
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
# fm_backend_name's auto-detect step: fires only when FM_BACKEND/config/backend
|
|
384
|
+
# are both absent, selects between the three markers exactly as
|
|
385
|
+
# fm_backend_detect does, and is loud only when it selects herdr or cmux -
|
|
386
|
+
# never when it selects tmux (today's default-path behavior must stay
|
|
387
|
+
# byte-for-byte silent).
|
|
388
|
+
test_backend_name_autodetect_notice() {
|
|
389
|
+
local dir cfg out errfile
|
|
390
|
+
|
|
391
|
+
dir="$TMP_ROOT/name-autodetect"; cfg="$dir/config-empty"; mkdir -p "$cfg"
|
|
392
|
+
errfile="$dir/err.txt"
|
|
393
|
+
|
|
394
|
+
: > "$errfile"
|
|
395
|
+
out=$(unset TMUX HERDR_ENV CMUX_WORKSPACE_ID __CFBundleIdentifier; PATH="$FAKE_NONDARWIN_BIN:$PATH" FM_BACKEND='' FM_BACKEND_CONFIG_DIR="$cfg" fm_backend_name 2>"$errfile")
|
|
396
|
+
[ "$out" = tmux ] || fail "fm_backend_name should default to tmux with no detection markers, got '$out'"
|
|
397
|
+
[ -s "$errfile" ] && fail "fm_backend_name must stay silent with no detection markers"$'\n'"$(cat "$errfile")"
|
|
398
|
+
|
|
399
|
+
: > "$errfile"
|
|
400
|
+
out=$(unset TMUX CMUX_WORKSPACE_ID; HERDR_ENV=1 FM_BACKEND='' FM_BACKEND_CONFIG_DIR="$cfg" fm_backend_name 2>"$errfile")
|
|
401
|
+
[ "$out" = herdr ] || fail "fm_backend_name should auto-detect herdr from HERDR_ENV=1, got '$out'"
|
|
402
|
+
assert_contains "$(cat "$errfile")" "EXPERIMENTAL herdr backend" \
|
|
403
|
+
"fm_backend_name did not print a loud notice when auto-detecting herdr"
|
|
404
|
+
assert_contains "$(cat "$errfile")" "config/backend" \
|
|
405
|
+
"fm_backend_name's auto-detect notice did not name the opt-out"
|
|
406
|
+
|
|
407
|
+
: > "$errfile"
|
|
408
|
+
out=$(unset HERDR_ENV CMUX_WORKSPACE_ID; TMUX='fake,1,0' FM_BACKEND='' FM_BACKEND_CONFIG_DIR="$cfg" fm_backend_name 2>"$errfile")
|
|
409
|
+
[ "$out" = tmux ] || fail "fm_backend_name should auto-detect tmux from \$TMUX, got '$out'"
|
|
410
|
+
[ -s "$errfile" ] && fail "auto-detecting tmux must stay silent (today's unchanged default-path behavior)"$'\n'"$(cat "$errfile")"
|
|
411
|
+
|
|
412
|
+
: > "$errfile"
|
|
413
|
+
out=$(unset TMUX HERDR_ENV; CMUX_WORKSPACE_ID='fake-uuid' FM_BACKEND='' FM_BACKEND_CONFIG_DIR="$cfg" fm_backend_name 2>"$errfile")
|
|
414
|
+
[ "$out" = cmux ] || fail "fm_backend_name should auto-detect cmux from CMUX_WORKSPACE_ID, got '$out'"
|
|
415
|
+
assert_contains "$(cat "$errfile")" "EXPERIMENTAL cmux backend" \
|
|
416
|
+
"fm_backend_name did not print a loud notice when auto-detecting cmux"
|
|
417
|
+
assert_contains "$(cat "$errfile")" "config/backend" \
|
|
418
|
+
"fm_backend_name's cmux auto-detect notice did not name the opt-out"
|
|
419
|
+
assert_contains "$(cat "$errfile")" "--backend tmux" \
|
|
420
|
+
"fm_backend_name's cmux auto-detect notice did not name the --backend tmux opt-out"
|
|
421
|
+
|
|
422
|
+
: > "$errfile"
|
|
423
|
+
out=$(unset CMUX_WORKSPACE_ID; TMUX='fake,1,0' HERDR_ENV=1 FM_BACKEND='' FM_BACKEND_CONFIG_DIR="$cfg" fm_backend_name 2>"$errfile")
|
|
424
|
+
[ "$out" = tmux ] || fail "nested tmux-in-herdr should auto-detect tmux (innermost first), got '$out'"
|
|
425
|
+
[ -s "$errfile" ] && fail "nested tmux-in-herdr auto-detect (result tmux) must stay silent"$'\n'"$(cat "$errfile")"
|
|
426
|
+
|
|
427
|
+
: > "$errfile"
|
|
428
|
+
out=$(unset HERDR_ENV; TMUX='fake,1,0' CMUX_WORKSPACE_ID='fake-uuid' FM_BACKEND='' FM_BACKEND_CONFIG_DIR="$cfg" fm_backend_name 2>"$errfile")
|
|
429
|
+
[ "$out" = tmux ] || fail "nested tmux-in-cmux should auto-detect tmux (innermost first), got '$out'"
|
|
430
|
+
[ -s "$errfile" ] && fail "nested tmux-in-cmux auto-detect (result tmux) must stay silent"$'\n'"$(cat "$errfile")"
|
|
431
|
+
|
|
432
|
+
pass "fm_backend_name: auto-detect selects herdr or cmux (loud notice) or tmux (silent, including nested tmux-in-herdr/tmux-in-cmux)"
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
# Explicit configuration (FM_BACKEND env or config/backend) always wins over
|
|
436
|
+
# runtime auto-detection, even when a detection marker points the other way.
|
|
437
|
+
test_backend_name_explicit_beats_detection() {
|
|
438
|
+
local dir cfg out
|
|
439
|
+
|
|
440
|
+
dir="$TMP_ROOT/name-explicit-beats-detect"
|
|
441
|
+
cfg="$dir/config-tmux"; mkdir -p "$cfg"; printf 'tmux\n' > "$cfg/backend"
|
|
442
|
+
mkdir -p "$dir/config-empty"
|
|
443
|
+
|
|
444
|
+
# fm_backend_name reads FM_BACKEND_CONFIG_DIR (bound once, at fm-backend.sh
|
|
445
|
+
# source time, from FM_CONFIG_OVERRIDE); a later FM_CONFIG_OVERRIDE=... prefix
|
|
446
|
+
# on the function call itself does not re-bind it, so these calls set
|
|
447
|
+
# FM_BACKEND_CONFIG_DIR directly to control which config dir is checked.
|
|
448
|
+
out=$(unset TMUX; HERDR_ENV=1 FM_BACKEND=tmux FM_BACKEND_CONFIG_DIR="$dir/config-empty" fm_backend_name)
|
|
449
|
+
[ "$out" = tmux ] || fail "FM_BACKEND=tmux should win over an ambient HERDR_ENV=1 auto-detect marker, got '$out'"
|
|
450
|
+
|
|
451
|
+
out=$(unset TMUX; HERDR_ENV=1 FM_BACKEND='' FM_BACKEND_CONFIG_DIR="$cfg" fm_backend_name)
|
|
452
|
+
[ "$out" = tmux ] || fail "config/backend=tmux should win over an ambient HERDR_ENV=1 auto-detect marker, got '$out'"
|
|
453
|
+
|
|
454
|
+
# The same opt-out must work for an ambient cmux auto-detect marker: a
|
|
455
|
+
# captain who is running firstmate inside a cmux terminal but explicitly
|
|
456
|
+
# wants tmux is never overridden by CMUX_WORKSPACE_ID.
|
|
457
|
+
out=$(unset TMUX HERDR_ENV; CMUX_WORKSPACE_ID='fake-uuid' FM_BACKEND=tmux FM_BACKEND_CONFIG_DIR="$dir/config-empty" fm_backend_name)
|
|
458
|
+
[ "$out" = tmux ] || fail "FM_BACKEND=tmux should win over an ambient CMUX_WORKSPACE_ID auto-detect marker, got '$out'"
|
|
459
|
+
|
|
460
|
+
out=$(unset TMUX HERDR_ENV; CMUX_WORKSPACE_ID='fake-uuid' FM_BACKEND='' FM_BACKEND_CONFIG_DIR="$cfg" fm_backend_name)
|
|
461
|
+
[ "$out" = tmux ] || fail "config/backend=tmux should win over an ambient CMUX_WORKSPACE_ID auto-detect marker, got '$out'"
|
|
462
|
+
|
|
463
|
+
pass "fm_backend_name: an explicit FM_BACKEND or config/backend setting always wins over runtime auto-detection, including an ambient cmux marker"
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
test_backend_validate_refuses_unknown() {
|
|
467
|
+
fm_backend_validate tmux 2>/dev/null || fail "fm_backend_validate should accept tmux"
|
|
468
|
+
fm_backend_validate orca 2>/dev/null || fail "fm_backend_validate should accept orca"
|
|
469
|
+
local out
|
|
470
|
+
# bogus names a backend with no adapter at all; tmux, herdr, zellij, orca,
|
|
471
|
+
# and cmux are all known adapters and spawn-supported.
|
|
472
|
+
out=$(fm_backend_validate bogus 2>&1) && fail "fm_backend_validate should refuse bogus (no such adapter)"
|
|
473
|
+
assert_contains "$out" "unknown backend 'bogus'" "fm_backend_validate did not name the rejected backend"
|
|
474
|
+
out=$(fm_backend_validate codex-app 2>&1) && fail "fm_backend_validate should refuse codex-app"
|
|
475
|
+
assert_contains "$out" "unknown backend 'codex-app'" "fm_backend_validate accepted codex-app"
|
|
476
|
+
out=$(fm_backend_validate "tmux herdr" 2>&1) && fail "fm_backend_validate should refuse a multi-token backend name"
|
|
477
|
+
assert_contains "$out" "unknown backend 'tmux herdr'" "fm_backend_validate accepted a multi-token backend name"
|
|
478
|
+
pass "fm_backend_validate: implemented adapters accepted, unknown and blocked codex-app backends refused loudly"
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
test_backend_source_shell_portable() {
|
|
482
|
+
local out status
|
|
483
|
+
# zsh does not word-split unquoted expansions; sourcing fm-backend.sh from
|
|
484
|
+
# an interactive zsh session must still recognize known backend names.
|
|
485
|
+
if command -v zsh >/dev/null 2>&1; then
|
|
486
|
+
zsh -c "cd '$ROOT' && source bin/fm-backend.sh && fm_backend_source herdr && whence -w fm_backend_herdr_capture >/dev/null" 2>/dev/null \
|
|
487
|
+
|| fail "zsh: fm_backend_source herdr should load the adapter when sourced"
|
|
488
|
+
out=$(zsh -c "cd '$ROOT' && source bin/fm-backend.sh && fm_backend_source bogus" 2>&1) \
|
|
489
|
+
&& fail "zsh: fm_backend_source bogus should fail"
|
|
490
|
+
assert_contains "$out" "unknown backend 'bogus'" \
|
|
491
|
+
"zsh: fm_backend_source did not reject bogus with the expected error"
|
|
492
|
+
pass "zsh: fm_backend_source recognizes known backends and rejects unknown ones"
|
|
493
|
+
else
|
|
494
|
+
pass "zsh: shell-portable backend matching skipped (zsh not found)"
|
|
495
|
+
fi
|
|
496
|
+
|
|
497
|
+
bash -c "cd '$ROOT' && source bin/fm-backend.sh && fm_backend_source herdr && declare -F fm_backend_herdr_capture >/dev/null" 2>/dev/null \
|
|
498
|
+
|| fail "bash: fm_backend_source herdr should load the adapter when sourced"
|
|
499
|
+
out=$(bash -c "cd '$ROOT' && source bin/fm-backend.sh && fm_backend_source bogus" 2>&1) \
|
|
500
|
+
&& fail "bash: fm_backend_source bogus should fail"
|
|
501
|
+
assert_contains "$out" "unknown backend 'bogus'" \
|
|
502
|
+
"bash: fm_backend_source did not reject bogus with the expected error"
|
|
503
|
+
pass "bash: fm_backend_source recognizes known backends and rejects unknown ones"
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
test_backend_validate_spawn_accepts_orca() {
|
|
507
|
+
local out
|
|
508
|
+
fm_backend_validate_spawn tmux 2>/dev/null || fail "fm_backend_validate_spawn should accept tmux"
|
|
509
|
+
fm_backend_validate_spawn herdr 2>/dev/null || fail "fm_backend_validate_spawn should accept herdr"
|
|
510
|
+
fm_backend_validate_spawn zellij 2>/dev/null || fail "fm_backend_validate_spawn should accept zellij"
|
|
511
|
+
fm_backend_validate_spawn orca 2>/dev/null || fail "fm_backend_validate_spawn should accept orca"
|
|
512
|
+
fm_backend_validate_spawn cmux 2>/dev/null || fail "fm_backend_validate_spawn should accept cmux"
|
|
513
|
+
out=$(fm_backend_validate_spawn bogus 2>&1) && fail "fm_backend_validate_spawn should still refuse unknown backends"
|
|
514
|
+
assert_contains "$out" "unknown backend 'bogus'" "fm_backend_validate_spawn did not preserve unknown-backend validation"
|
|
515
|
+
out=$(fm_backend_validate_spawn codex-app 2>&1) && fail "fm_backend_validate_spawn should refuse codex-app"
|
|
516
|
+
assert_contains "$out" "unknown backend 'codex-app'" "fm_backend_validate_spawn accepted codex-app"
|
|
517
|
+
out=$(fm_backend_validate_spawn "tmux herdr" 2>&1) && fail "fm_backend_validate_spawn should refuse a multi-token backend name"
|
|
518
|
+
assert_contains "$out" "unknown backend 'tmux herdr'" "fm_backend_validate_spawn accepted a multi-token backend name"
|
|
519
|
+
pass "fm_backend_validate_spawn: all implemented lifecycle backends are spawn-supported"
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
test_meta_get_and_backend_of_meta() {
|
|
523
|
+
local meta=$TMP_ROOT/meta-get.meta
|
|
524
|
+
fm_write_meta "$meta" "window=firstmate:fm-x1" "harness=claude"
|
|
525
|
+
[ "$(fm_meta_get "$meta" window)" = "firstmate:fm-x1" ] || fail "fm_meta_get did not read window="
|
|
526
|
+
[ "$(fm_meta_get "$meta" missing)" = "" ] || fail "fm_meta_get should print nothing for an absent key"
|
|
527
|
+
[ "$(fm_backend_of_meta "$meta")" = tmux ] || fail "fm_backend_of_meta should default absent backend= to tmux"
|
|
528
|
+
|
|
529
|
+
printf 'backend=tmux\n' >> "$meta"
|
|
530
|
+
[ "$(fm_backend_of_meta "$meta")" = tmux ] || fail "fm_backend_of_meta should read an explicit backend=tmux"
|
|
531
|
+
|
|
532
|
+
pass "fm_meta_get / fm_backend_of_meta: read key=value, default backend to tmux"
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
test_resolve_selector_three_forms() {
|
|
536
|
+
local state=$TMP_ROOT/resolve-state fakebin out
|
|
537
|
+
mkdir -p "$state"
|
|
538
|
+
fm_write_meta "$state/task1.meta" "window=firstmate:fm-task1"
|
|
539
|
+
fm_write_meta "$state/dotfiles-d6.meta" "window=default:wA:p2" "backend=herdr"
|
|
540
|
+
fm_write_meta "$state/fm-turnend-all-harnesses-v9.meta" "window=default:wB:p3" "backend=herdr"
|
|
541
|
+
|
|
542
|
+
[ "$(fm_backend_resolve_selector 'sess:win' "$state")" = "sess:win" ] \
|
|
543
|
+
|| fail "explicit session:window should be used as-is"
|
|
544
|
+
|
|
545
|
+
[ "$(fm_backend_resolve_selector 'dotfiles-d6' "$state")" = "default:wA:p2" ] \
|
|
546
|
+
|| fail "bare non-fm task id should resolve through exact metadata"
|
|
547
|
+
[ "$(fm_backend_of_selector 'dotfiles-d6' 'default:wA:p2' "$state")" = herdr ] \
|
|
548
|
+
|| fail "bare non-fm task id should use its recorded backend"
|
|
549
|
+
[ "$(fm_backend_expected_label_of_selector 'dotfiles-d6' "$state")" = "fm-dotfiles-d6" ] \
|
|
550
|
+
|| fail "bare non-fm task id should report the spawned fm-<id> label"
|
|
551
|
+
|
|
552
|
+
[ "$(fm_backend_resolve_selector 'fm-turnend-all-harnesses-v9' "$state")" = "default:wB:p3" ] \
|
|
553
|
+
|| fail "exact fm-* task id should resolve through its exact metadata"
|
|
554
|
+
[ "$(fm_backend_of_selector 'fm-turnend-all-harnesses-v9' 'default:wB:p3' "$state")" = herdr ] \
|
|
555
|
+
|| fail "exact fm-* task id should use exact metadata without stripping fm-"
|
|
556
|
+
[ "$(fm_backend_expected_label_of_selector 'fm-turnend-all-harnesses-v9' "$state")" = "fm-fm-turnend-all-harnesses-v9" ] \
|
|
557
|
+
|| fail "exact fm-* task id should report the spawned fm-<id> label"
|
|
558
|
+
|
|
559
|
+
[ "$(fm_backend_resolve_selector 'fm-task1' "$state")" = "firstmate:fm-task1" ] \
|
|
560
|
+
|| fail "legacy fm-<id> label should resolve through <id>.meta's window="
|
|
561
|
+
[ "$(fm_backend_expected_label_of_selector 'fm-task1' "$state")" = "fm-task1" ] \
|
|
562
|
+
|| fail "legacy fm-<id> label should preserve its backend label"
|
|
563
|
+
|
|
564
|
+
out=$(fm_backend_resolve_selector 'fm-missing' "$state" 2>&1) && fail "fm-<id> with no meta should fail"
|
|
565
|
+
assert_contains "$out" "no metadata for fm-missing" "missing-meta error text changed"
|
|
566
|
+
|
|
567
|
+
fakebin="$TMP_ROOT/resolve-fakebin"; mkdir -p "$fakebin"
|
|
568
|
+
cat > "$fakebin/tmux" <<'SH'
|
|
569
|
+
#!/usr/bin/env bash
|
|
570
|
+
case "${1:-}" in
|
|
571
|
+
list-windows) printf 'firstmate:adhoc\nother:otherwin\n' ;;
|
|
572
|
+
esac
|
|
573
|
+
exit 0
|
|
574
|
+
SH
|
|
575
|
+
chmod +x "$fakebin/tmux"
|
|
576
|
+
out=$(PATH="$fakebin:$PATH" fm_backend_resolve_selector 'fm-adhoc' "$state" 2>&1) || true
|
|
577
|
+
# fm-adhoc carries no meta file, so it is NOT the bare-name fallback path - it
|
|
578
|
+
# is the fm-* meta-miss error path after exact-id and legacy-label metadata
|
|
579
|
+
# lookup both miss.
|
|
580
|
+
# Only a NON fm-* bare name falls through to the live-window search.
|
|
581
|
+
assert_contains "$out" "no metadata for fm-adhoc" "an fm-* selector must always require meta, not silently fall back to a live search"
|
|
582
|
+
|
|
583
|
+
out=$(PATH="$fakebin:$PATH" fm_backend_resolve_selector 'adhoc' "$state")
|
|
584
|
+
[ "$out" = "firstmate:adhoc" ] || fail "an ad hoc bare name should resolve via the tmux live-window fallback, got '$out'"
|
|
585
|
+
|
|
586
|
+
pass "fm_backend_resolve_selector: session:window literal, exact task id first, legacy fm-<id> label fallback, ad hoc bare name via tmux list-windows"
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
test_backend_of_selector_matches_explicit_target_meta() {
|
|
590
|
+
local state=$TMP_ROOT/backend-selector-state
|
|
591
|
+
mkdir -p "$state"
|
|
592
|
+
fm_write_meta "$state/herdr-task.meta" "window=default:w1:p2" "backend=herdr"
|
|
593
|
+
fm_write_meta "$state/dotfiles-d6.meta" "window=default:wA:p2" "backend=herdr"
|
|
594
|
+
fm_write_meta "$state/fm-turnend-all-harnesses-v9.meta" "window=default:wB:p3" "backend=herdr"
|
|
595
|
+
fm_write_meta "$state/tmux-task.meta" "window=firstmate:fm-tmux-task"
|
|
596
|
+
fm_write_meta "$state/custom-window-task.meta" "window=custom-window"
|
|
597
|
+
fm_write_meta "$state/orca-task.meta" "window=fm-orca-task" "terminal=term-orca-task" "backend=orca"
|
|
598
|
+
|
|
599
|
+
[ "$(fm_backend_of_selector 'dotfiles-d6' 'default:wA:p2' "$state")" = herdr ] \
|
|
600
|
+
|| fail "bare non-fm task id selector should use its recorded backend"
|
|
601
|
+
[ "$(fm_backend_of_selector 'fm-turnend-all-harnesses-v9' 'default:wB:p3' "$state")" = herdr ] \
|
|
602
|
+
|| fail "exact fm-* task id selector should use exact metadata before legacy stripping"
|
|
603
|
+
[ "$(fm_backend_of_selector 'fm-herdr-task' 'default:w1:p2' "$state")" = herdr ] \
|
|
604
|
+
|| fail "legacy fm-<id> selector should use its recorded backend"
|
|
605
|
+
[ "$(fm_backend_resolve_selector 'fm-orca-task' "$state")" = term-orca-task ] \
|
|
606
|
+
|| fail "Orca fm-<id> selector should resolve to terminal=, not window="
|
|
607
|
+
[ "$(fm_backend_resolve_selector 'term-orca-task' "$state")" = term-orca-task ] \
|
|
608
|
+
|| fail "raw Orca terminal selector should resolve through metadata"
|
|
609
|
+
[ "$(fm_backend_resolve_selector 'custom-window' "$state")" = custom-window ] \
|
|
610
|
+
|| fail "raw window selector matching metadata should not require tmux fallback"
|
|
611
|
+
[ "$(fm_backend_of_selector 'term-orca-task' 'term-orca-task' "$state")" = orca ] \
|
|
612
|
+
|| fail "matching an explicit Orca terminal handle should inherit metadata backend"
|
|
613
|
+
[ "$(fm_backend_of_selector 'default:w1:p2' 'default:w1:p2' "$state")" = herdr ] \
|
|
614
|
+
|| fail "explicit backend target matching metadata should use that task's backend"
|
|
615
|
+
[ "$(fm_backend_of_selector 'firstmate:fm-tmux-task' 'firstmate:fm-tmux-task' "$state")" = tmux ] \
|
|
616
|
+
|| fail "explicit tmux-shaped target with absent backend= should default to tmux"
|
|
617
|
+
[ "$(fm_backend_of_selector 'manual:outside' 'manual:outside' "$state")" = tmux ] \
|
|
618
|
+
|| fail "explicit target with no matching metadata should keep the tmux compatibility default"
|
|
619
|
+
|
|
620
|
+
pass "fm_backend_of_selector: exact task ids, legacy fm-<id> labels, and matching explicit targets inherit metadata backend"
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
# --- old vs new: fm-send.sh --------------------------------------------------
|
|
624
|
+
|
|
625
|
+
make_send_fakebin() { # <dir> -> echoes fakebin dir; logs every tmux call to $FM_TMUX_LOG
|
|
626
|
+
local dir=$1 fb="$1/fakebin"
|
|
627
|
+
mkdir -p "$fb"
|
|
628
|
+
cat > "$fb/tmux" <<'SH'
|
|
629
|
+
#!/usr/bin/env bash
|
|
630
|
+
set -u
|
|
631
|
+
{ printf 'tmux'; for a in "$@"; do printf '\x1f%s' "$a"; done; printf '\n'; } >> "${FM_TMUX_LOG:?}"
|
|
632
|
+
case "${1:-}" in
|
|
633
|
+
send-keys) exit 0 ;;
|
|
634
|
+
display-message)
|
|
635
|
+
for a in "$@"; do case "$a" in *cursor_y*) printf '1\n'; exit 0 ;; esac; done
|
|
636
|
+
printf 'fakepane\n'; exit 0 ;;
|
|
637
|
+
capture-pane)
|
|
638
|
+
start= end=
|
|
639
|
+
while [ $# -gt 0 ]; do
|
|
640
|
+
case "$1" in
|
|
641
|
+
-S) start=$2; shift 2 ;;
|
|
642
|
+
-E) end=$2; shift 2 ;;
|
|
643
|
+
*) shift ;;
|
|
644
|
+
esac
|
|
645
|
+
done
|
|
646
|
+
if [ "$start" = 1 ] && [ "$end" = 1 ]; then
|
|
647
|
+
printf '│ │\n'
|
|
648
|
+
else
|
|
649
|
+
printf '╭────╮\n│ │\n╰────╯\n'
|
|
650
|
+
fi
|
|
651
|
+
exit 0 ;;
|
|
652
|
+
list-windows) exit 0 ;;
|
|
653
|
+
esac
|
|
654
|
+
exit 0
|
|
655
|
+
SH
|
|
656
|
+
chmod +x "$fb/tmux"
|
|
657
|
+
printf '%s\n' "$fb"
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
run_send_case() { # <bin-root> <fakebin> <log> <home> -- <send args...>
|
|
661
|
+
local bin=$1 fb=$2 log=$3 home=$4; shift 4
|
|
662
|
+
[ "${1:-}" = -- ] && shift
|
|
663
|
+
: > "$log"
|
|
664
|
+
env PATH="$fb:$PATH" FM_ROOT_OVERRIDE="$bin" FM_HOME="$home" FM_TMUX_LOG="$log" \
|
|
665
|
+
FM_SEND_SETTLE=0 FM_SEND_SLEEP=0 \
|
|
666
|
+
"$bin/bin/fm-send.sh" "$@" >/dev/null 2>&1
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
strip_send_preflight() { # <log>
|
|
670
|
+
local preflight
|
|
671
|
+
preflight=$'tmux\x1fdisplay-message\x1f-p\x1f-t\x1fsess:win\x1f#{pane_id}'
|
|
672
|
+
awk -v preflight="$preflight" '$0 != preflight { print }' "$1"
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
# The byte-identical old-vs-new tmux log comparison this test used to run
|
|
676
|
+
# covered the P1 backend extraction, which promised an unchanged command
|
|
677
|
+
# sequence. The composer consolidation (fm-composer-thin-adapter-refactor-r1)
|
|
678
|
+
# deliberately changed that sequence - the submit core reads a busy baseline
|
|
679
|
+
# before typing (its idle-to-busy turn-started confirmation) and the composer
|
|
680
|
+
# verdict comes from one full styled capture instead of a second band capture -
|
|
681
|
+
# so the current contract is asserted directly instead.
|
|
682
|
+
test_send_tmux_contract() {
|
|
683
|
+
local fb log home rc
|
|
684
|
+
fb=$(make_send_fakebin "$TMP_ROOT/send-fake")
|
|
685
|
+
home="$TMP_ROOT/send-home"; mkdir -p "$home/state"
|
|
686
|
+
log="$TMP_ROOT/send-new.log"
|
|
687
|
+
|
|
688
|
+
# Case 1: --key path - target verified, named key sent, no typing.
|
|
689
|
+
run_send_case "$ROOT" "$fb" "$log" "$home" -- "sess:win" --key Escape
|
|
690
|
+
rc=$?
|
|
691
|
+
expect_code 0 "$rc" "fm-send --key should succeed against a live fake pane"
|
|
692
|
+
assert_contains "$(cat "$log")" $'\x1f''display-message'$'\x1f''-p'$'\x1f''-t'$'\x1f''sess:win'$'\x1f''#{pane_id}' \
|
|
693
|
+
"fm-send --key did not verify the explicit tmux target before sending"
|
|
694
|
+
assert_contains "$(cat "$log")" $'\x1f''Escape' "fm-send --key did not send the named key"
|
|
695
|
+
assert_not_contains "$(cat "$log")" $'\x1f''-l'$'\x1f' "fm-send --key must not type literal text"
|
|
696
|
+
|
|
697
|
+
# Case 2: plain text - typed literally exactly once, submitted with Enter,
|
|
698
|
+
# confirmed against the bordered-empty fake composer.
|
|
699
|
+
run_send_case "$ROOT" "$fb" "$log" "$home" -- "sess:win" hello captain
|
|
700
|
+
rc=$?
|
|
701
|
+
expect_code 0 "$rc" "fm-send plain text should confirm against the empty fake composer"
|
|
702
|
+
assert_contains "$(cat "$log")" $'\x1f''send-keys'$'\x1f''-t'$'\x1f''sess:win'$'\x1f''-l'$'\x1f''hello captain' \
|
|
703
|
+
"fm-send did not send the literal text with send-keys -l"
|
|
704
|
+
[ "$(grep -c $'\x1f''-l'$'\x1f' "$log")" -eq 1 ] \
|
|
705
|
+
|| fail "fm-send must type the text exactly once (Enter-only retries, never a retype)"
|
|
706
|
+
assert_contains "$(cat "$log")" $'\x1f''Enter' "fm-send did not submit with Enter"
|
|
707
|
+
|
|
708
|
+
# Case 3: a slash command still opens the popup-settle path (verified in
|
|
709
|
+
# tests/fm-send-popup-settle.test.sh) and ends in the same command shape:
|
|
710
|
+
# one literal type, then Enter.
|
|
711
|
+
run_send_case "$ROOT" "$fb" "$log" "$home" -- "sess:win" /some-skill
|
|
712
|
+
rc=$?
|
|
713
|
+
expect_code 0 "$rc" "fm-send /skill should confirm against the empty fake composer"
|
|
714
|
+
assert_contains "$(cat "$log")" $'\x1f''send-keys'$'\x1f''-t'$'\x1f''sess:win'$'\x1f''-l'$'\x1f''/some-skill' \
|
|
715
|
+
"fm-send /skill did not type the literal slash command"
|
|
716
|
+
[ "$(grep -c $'\x1f''-l'$'\x1f' "$log")" -eq 1 ] \
|
|
717
|
+
|| fail "fm-send /skill must type the text exactly once"
|
|
718
|
+
|
|
719
|
+
pass "fm-send.sh: explicit tmux targets are verified; text types once and submits with Enter"
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
# --- old vs new: fm-peek.sh --------------------------------------------------
|
|
723
|
+
|
|
724
|
+
make_peek_fakebin() { # <dir> <capture-output> -> echoes fakebin dir
|
|
725
|
+
local dir=$1 payload=$2 fb="$1/fakebin"
|
|
726
|
+
mkdir -p "$fb"
|
|
727
|
+
printf '%s' "$payload" > "$dir/capture.out"
|
|
728
|
+
cat > "$fb/tmux" <<SH
|
|
729
|
+
#!/usr/bin/env bash
|
|
730
|
+
set -u
|
|
731
|
+
{ printf 'tmux'; for a in "\$@"; do printf '\\x1f%s' "\$a"; done; printf '\\n'; } >> "\${FM_TMUX_LOG:?}"
|
|
732
|
+
case "\${1:-}" in
|
|
733
|
+
capture-pane) cat "$dir/capture.out" ;;
|
|
734
|
+
esac
|
|
735
|
+
exit 0
|
|
736
|
+
SH
|
|
737
|
+
chmod +x "$fb/tmux"
|
|
738
|
+
printf '%s\n' "$fb"
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
test_peek_conformance_old_vs_new() {
|
|
742
|
+
local old_bin fb log_old log_new home out_old out_new payload neutral_root
|
|
743
|
+
payload=$'line one\nline two\ncaptain on deck'
|
|
744
|
+
old_bin=$(build_old_bin peek-old)
|
|
745
|
+
fb=$(make_peek_fakebin "$TMP_ROOT/peek-fake" "$payload")
|
|
746
|
+
home="$TMP_ROOT/peek-home"; mkdir -p "$home/state"
|
|
747
|
+
log_old="$TMP_ROOT/peek-old.log"; log_new="$TMP_ROOT/peek-new.log"
|
|
748
|
+
# A fresh non-git dir keeps fm-guard.sh's worktree-tangle check inert (it warns
|
|
749
|
+
# to stderr, discarded below) - neither run needs FM_ROOT for anything beyond
|
|
750
|
+
# that guard, since STATE/HOME are already overridden directly.
|
|
751
|
+
neutral_root="$TMP_ROOT/peek-neutral-root"; mkdir -p "$neutral_root"
|
|
752
|
+
|
|
753
|
+
: > "$log_old"
|
|
754
|
+
out_old=$(PATH="$fb:$PATH" FM_ROOT_OVERRIDE="$neutral_root" FM_HOME="$home" FM_TMUX_LOG="$log_old" \
|
|
755
|
+
"$old_bin/bin/fm-peek.sh" "sess:win" 25 2>/dev/null)
|
|
756
|
+
: > "$log_new"
|
|
757
|
+
out_new=$(PATH="$fb:$PATH" FM_ROOT_OVERRIDE="$neutral_root" FM_HOME="$home" FM_TMUX_LOG="$log_new" \
|
|
758
|
+
"$ROOT/bin/fm-peek.sh" "sess:win" 25 2>/dev/null)
|
|
759
|
+
|
|
760
|
+
[ "$out_old" = "$out_new" ] || fail "fm-peek output differs old vs new"$'\n'"--- old ---"$'\n'"$out_old"$'\n'"--- new ---"$'\n'"$out_new"
|
|
761
|
+
[ "$out_new" = "$payload" ] || fail "fm-peek did not pass through the fake capture-pane output exactly"
|
|
762
|
+
diff -u "$log_old" "$log_new" > "$TMP_ROOT/peek-diff.txt" 2>&1 \
|
|
763
|
+
|| fail "fm-peek: tmux command log differs old vs new"$'\n'"$(cat "$TMP_ROOT/peek-diff.txt")"
|
|
764
|
+
assert_contains "$(cat "$log_new")" $'\x1f''capture-pane'$'\x1f''-p'$'\x1f''-t'$'\x1f''sess:win'$'\x1f''-S'$'\x1f''-25' \
|
|
765
|
+
"fm-peek did not call capture-pane -p -t <target> -S -<lines> exactly"
|
|
766
|
+
|
|
767
|
+
pass "fm-peek.sh: capture-pane invocation and output are byte-identical old vs new"
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
# --- old vs new: fm-spawn.sh --------------------------------------------------
|
|
771
|
+
|
|
772
|
+
make_spawn_fakebin() { # <dir> <fake-worktree-path> -> echoes fakebin dir
|
|
773
|
+
local dir=$1 wt=$2 fb="$1/fakebin"
|
|
774
|
+
mkdir -p "$fb"
|
|
775
|
+
cat > "$fb/tmux" <<SH
|
|
776
|
+
#!/usr/bin/env bash
|
|
777
|
+
set -u
|
|
778
|
+
{ printf 'tmux'; for a in "\$@"; do printf '\\x1f%s' "\$a"; done; printf '\\n'; } >> "\${FM_TMUX_LOG:?}"
|
|
779
|
+
case "\${1:-}" in
|
|
780
|
+
display-message)
|
|
781
|
+
for a in "\$@"; do case "\$a" in *pane_current_path*) printf '%s\\n' "$wt"; exit 0 ;; esac; done
|
|
782
|
+
printf 'firstmate\\n'; exit 0 ;;
|
|
783
|
+
list-windows) exit 0 ;;
|
|
784
|
+
esac
|
|
785
|
+
exit 0
|
|
786
|
+
SH
|
|
787
|
+
chmod +x "$fb/tmux"
|
|
788
|
+
fm_fake_exit0 "$fb" treehouse
|
|
789
|
+
printf '%s\n' "$fb"
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
run_spawn_case() { # <bin-root> <fakebin> <log> <state> <data> <config> <proj> -- <spawn args...>
|
|
793
|
+
local bin=$1 fb=$2 log=$3 state=$4 data=$5 config=$6 proj=$7; shift 7
|
|
794
|
+
[ "${1:-}" = -- ] && shift
|
|
795
|
+
: > "$log"
|
|
796
|
+
env PATH="$fb:$PATH" FM_ROOT_OVERRIDE="$bin" \
|
|
797
|
+
FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
|
|
798
|
+
FM_PROJECTS_OVERRIDE="$TMP_ROOT/unused-projects" \
|
|
799
|
+
FM_SPAWN_NO_GUARD=1 TMUX="fake,1,0" FM_TMUX_LOG="$log" \
|
|
800
|
+
"$bin/bin/fm-spawn.sh" "$@"
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
# NOTE: the old-vs-new spawn command-log conformance test that used to live here
|
|
804
|
+
# was retired. It asserted the P1 backend refactor was a byte-for-byte pure
|
|
805
|
+
# extraction of the spawn window-creation/targeting sequence, but that sequence
|
|
806
|
+
# is now DELIBERATELY changed: fm-spawn drives the tmux backend to capture a
|
|
807
|
+
# stable window id, pin the window name (automatic-rename/allow-rename off), and
|
|
808
|
+
# target that id for the rename-critical spawn steps (robustness under a
|
|
809
|
+
# captain's non-default tmux config). A byte-identical old-vs-new diff can no
|
|
810
|
+
# longer hold there by design. That intended sequence is now authoritatively and
|
|
811
|
+
# comprehensively verified - via a recording fake-tmux - by
|
|
812
|
+
# tests/fm-tangle-guard.test.sh ("fm-spawn: appends windows by session-colon,
|
|
813
|
+
# pins the name, and targets the window id"), and the real tmux create/kill path
|
|
814
|
+
# by tests/fm-backend-tmux-smoke.test.sh. The send/peek/teardown conformance
|
|
815
|
+
# tests below remain pure extractions and stay. (make_spawn_fakebin and
|
|
816
|
+
# run_spawn_case are retained: test_spawn_default_backend_writes_no_meta_field
|
|
817
|
+
# uses make_spawn_fakebin, and #294's run_spawn_symlink_case uses run_spawn_case.)
|
|
818
|
+
|
|
819
|
+
# --- symlinked project prefix must not false-refuse the isolation guard -----
|
|
820
|
+
#
|
|
821
|
+
# docs/herdr-backend.md "Known gaps": a real backend's pane_current_path read
|
|
822
|
+
# (tmux, herdr) reports the OS-level PHYSICALLY-resolved cwd. When the project
|
|
823
|
+
# itself lives under a symlinked prefix (e.g. macOS's /tmp -> /private/tmp),
|
|
824
|
+
# fm-spawn.sh's PROJ_ABS - a logical `cd && pwd` - differs string-for-string
|
|
825
|
+
# from that physical read even before treehouse moves the pane at all, so the
|
|
826
|
+
# worktree-discovery poll used to mistake an UNMOVED pane for one that had
|
|
827
|
+
# already left the project, handing validate_spawn_worktree the project's own
|
|
828
|
+
# directory as "the worktree" and tripping its false isolation refusal.
|
|
829
|
+
# make_spawn_symlink_fakebin's tmux stub returns an unmoved project path on the
|
|
830
|
+
# first pane_current_path poll, then the real worktree path from the second poll
|
|
831
|
+
# onward, so this test fails loudly if the PROJ_ABS/PROJ_ABS_REAL
|
|
832
|
+
# canonicalization in bin/fm-spawn.sh ever regresses.
|
|
833
|
+
make_spawn_symlink_fakebin() { # <dir> <initial-project-path> <worktree-path> -> echoes fakebin dir
|
|
834
|
+
local dir=$1 initial_path=$2 wt=$3 fb="$1/fakebin" counter="$1/poll-count"
|
|
835
|
+
mkdir -p "$fb"
|
|
836
|
+
: > "$counter"
|
|
837
|
+
cat > "$fb/tmux" <<SH
|
|
838
|
+
#!/usr/bin/env bash
|
|
839
|
+
set -u
|
|
840
|
+
{ printf 'tmux'; for a in "\$@"; do printf '\\x1f%s' "\$a"; done; printf '\\n'; } >> "\${FM_TMUX_LOG:?}"
|
|
841
|
+
case "\${1:-}" in
|
|
842
|
+
display-message)
|
|
843
|
+
for a in "\$@"; do case "\$a" in *pane_current_path*)
|
|
844
|
+
printf x >> "$counter"
|
|
845
|
+
if [ "\$(wc -c < "$counter")" -le 1 ]; then
|
|
846
|
+
printf '%s\\n' "$initial_path"
|
|
847
|
+
else
|
|
848
|
+
printf '%s\\n' "$wt"
|
|
849
|
+
fi
|
|
850
|
+
exit 0
|
|
851
|
+
;; esac; done
|
|
852
|
+
printf 'firstmate\\n'; exit 0 ;;
|
|
853
|
+
list-windows) exit 0 ;;
|
|
854
|
+
esac
|
|
855
|
+
exit 0
|
|
856
|
+
SH
|
|
857
|
+
chmod +x "$fb/tmux"
|
|
858
|
+
fm_fake_exit0 "$fb" treehouse
|
|
859
|
+
printf '%s\n' "$fb"
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
run_spawn_symlink_case() { # <label> <physical|logical>
|
|
863
|
+
local label=$1 first_reply=$2 real_root link_root proj wt id fb data state config log out rc proj_phys initial_path
|
|
864
|
+
real_root="$TMP_ROOT/symlink-real-$label"; link_root="$TMP_ROOT/symlink-link-$label"
|
|
865
|
+
mkdir -p "$real_root"
|
|
866
|
+
ln -s "$real_root" "$link_root"
|
|
867
|
+
proj="$link_root/proj"
|
|
868
|
+
wt="$TMP_ROOT/symlink-wt-$label"
|
|
869
|
+
id="spawnsymlink$label"
|
|
870
|
+
fm_git_worktree "$real_root/proj" "$wt" "fm/$id"
|
|
871
|
+
# TMP_ROOT itself can already sit behind an OS-level symlink (e.g. macOS's
|
|
872
|
+
# /var -> /private/var), so resolve the fakebin's "physical" reply with
|
|
873
|
+
# pwd -P rather than string concatenation - it must match exactly what
|
|
874
|
+
# fm-spawn.sh's own PROJ_ABS_REAL computes, including any symlink layers
|
|
875
|
+
# ABOVE this test's own synthetic real_root/link_root pair.
|
|
876
|
+
proj_phys=$(cd "$real_root/proj" && pwd -P)
|
|
877
|
+
case "$first_reply" in
|
|
878
|
+
physical) initial_path=$proj_phys ;;
|
|
879
|
+
logical) initial_path=$proj ;;
|
|
880
|
+
*) fail "unknown symlink first-reply mode: $first_reply" ;;
|
|
881
|
+
esac
|
|
882
|
+
fb=$(make_spawn_symlink_fakebin "$TMP_ROOT/symlink-fake-$label" "$initial_path" "$wt")
|
|
883
|
+
data="$TMP_ROOT/symlink-data-$label"
|
|
884
|
+
mkdir -p "$data/$id"
|
|
885
|
+
printf 'test brief content\n' > "$data/$id/brief.md"
|
|
886
|
+
state="$TMP_ROOT/symlink-state-$label"; config="$TMP_ROOT/symlink-config-$label"
|
|
887
|
+
mkdir -p "$state" "$config"
|
|
888
|
+
log="$TMP_ROOT/symlink-spawn-$label.log"
|
|
889
|
+
|
|
890
|
+
out=$(run_spawn_case "$ROOT" "$fb" "$log" "$state" "$data" "$config" "$proj" -- "$id" "$proj" claude --mode no-mistakes --yolo off 2>&1)
|
|
891
|
+
rc=$?
|
|
892
|
+
expect_code 0 "$rc" "fm-spawn.sh should succeed for a project reached through a symlinked prefix when the backend reports $first_reply cwd"$'\n'"$out"
|
|
893
|
+
assert_contains "$out" "worktree=$wt" \
|
|
894
|
+
"fm-spawn.sh did not resolve a symlinked-prefix project to its real worktree when the backend reports $first_reply cwd"
|
|
895
|
+
|
|
896
|
+
rm -rf "/tmp/fm-$id"
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
test_spawn_symlinked_project_prefix_avoids_false_refusal() {
|
|
900
|
+
run_spawn_symlink_case physical physical
|
|
901
|
+
run_spawn_symlink_case logical logical
|
|
902
|
+
pass "fm-spawn.sh: a project reached through a symlinked prefix (e.g. macOS /tmp -> /private/tmp) does not trip the isolation guard's false refusal"
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
# --- old vs new: fm-teardown.sh ----------------------------------------------
|
|
906
|
+
|
|
907
|
+
make_teardown_fakebin() { # <dir> -> echoes fakebin dir; logs tmux+treehouse calls
|
|
908
|
+
local dir=$1 fb="$1/fakebin"
|
|
909
|
+
mkdir -p "$fb"
|
|
910
|
+
cat > "$fb/tmux" <<'SH'
|
|
911
|
+
#!/usr/bin/env bash
|
|
912
|
+
set -u
|
|
913
|
+
{ printf 'tmux'; for a in "$@"; do printf '\x1f%s' "$a"; done; printf '\n'; } >> "${FM_TMUX_LOG:?}"
|
|
914
|
+
exit 0
|
|
915
|
+
SH
|
|
916
|
+
cat > "$fb/treehouse" <<'SH'
|
|
917
|
+
#!/usr/bin/env bash
|
|
918
|
+
set -u
|
|
919
|
+
{ printf 'treehouse'; for a in "$@"; do printf '\x1f%s' "$a"; done; printf '\n'; } >> "${FM_TMUX_LOG:?}"
|
|
920
|
+
exit 0
|
|
921
|
+
SH
|
|
922
|
+
chmod +x "$fb/tmux" "$fb/treehouse"
|
|
923
|
+
printf '%s\n' "$fb"
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
# run_teardown_case <script> <fm-root-override> <fakebin> <log> <state> <data> <config> <id>
|
|
927
|
+
# FM_ROOT_OVERRIDE is passed separately from <script> so both the old and new
|
|
928
|
+
# runs can point it at the SAME neutral (non-git) shim root - that root's
|
|
929
|
+
# bin/fm-guard.sh is a symlink to the real, unchanged script, so the
|
|
930
|
+
# worktree-tangle check runs identically (and silently) for both, regardless
|
|
931
|
+
# of which fm-teardown.sh (old or new) is actually being invoked.
|
|
932
|
+
run_teardown_case() {
|
|
933
|
+
local script=$1 fmroot=$2 fb=$3 log=$4 state=$5 data=$6 config=$7 id=$8
|
|
934
|
+
: > "$log"
|
|
935
|
+
env PATH="$fb:$PATH" FM_ROOT_OVERRIDE="$fmroot" \
|
|
936
|
+
FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
|
|
937
|
+
FM_TMUX_LOG="$log" \
|
|
938
|
+
"$script" "$id"
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
test_teardown_conformance_old_vs_new() {
|
|
942
|
+
local old_bin fb proj wt id old_tmux_ref saved_base_ref
|
|
943
|
+
local state_old state_new config_old config_new data log_old log_new out_old out_new rc_old rc_new
|
|
944
|
+
# Force the post-squash topology inside this case: merge-base with main may
|
|
945
|
+
# equal HEAD on default-branch CI, and that must not make the legacy kill
|
|
946
|
+
# fixture self-referential. build_old_bin still uses BASE_REF for entrypoints;
|
|
947
|
+
# only the tmux kill adapter is pinned to the content-historical permissive ref.
|
|
948
|
+
saved_base_ref=$BASE_REF
|
|
949
|
+
BASE_REF=$(git -C "$ROOT" rev-parse HEAD)
|
|
950
|
+
old_tmux_ref=$(resolve_permissive_tmux_kill_ref) \
|
|
951
|
+
|| { BASE_REF=$saved_base_ref; fail "unable to locate a historical bin/backends/tmux.sh with permissive kill-window selectors"; }
|
|
952
|
+
old_bin=$(build_old_bin teardown-old)
|
|
953
|
+
git -C "$ROOT" show "$old_tmux_ref:bin/backends/tmux.sh" > "$old_bin/bin/backends/tmux.sh" \
|
|
954
|
+
|| { BASE_REF=$saved_base_ref; fail "could not materialize historical tmux adapter from $old_tmux_ref"; }
|
|
955
|
+
BASE_REF=$saved_base_ref
|
|
956
|
+
proj="$TMP_ROOT/teardown-project"; wt="$TMP_ROOT/teardown-wt"
|
|
957
|
+
id="teardownconform1"
|
|
958
|
+
fm_git_worktree "$proj" "$wt" "fm/$id"
|
|
959
|
+
fb=$(make_teardown_fakebin "$TMP_ROOT/teardown-fake")
|
|
960
|
+
|
|
961
|
+
data="$TMP_ROOT/teardown-data"
|
|
962
|
+
mkdir -p "$data/$id"
|
|
963
|
+
printf 'scout findings\n' > "$data/$id/report.md"
|
|
964
|
+
|
|
965
|
+
state_old="$TMP_ROOT/teardown-state-old"; state_new="$TMP_ROOT/teardown-state-new"
|
|
966
|
+
config_old="$TMP_ROOT/teardown-config-old"; config_new="$TMP_ROOT/teardown-config-new"
|
|
967
|
+
mkdir -p "$state_old" "$state_new" "$config_old" "$config_new"
|
|
968
|
+
|
|
969
|
+
fm_write_meta "$state_old/$id.meta" \
|
|
970
|
+
"window=firstmate:fm-$id" "worktree=$wt" "project=$proj" "harness=claude" "kind=scout" "mode=no-mistakes" "yolo=off" \
|
|
971
|
+
"decisions_reviewed=1" "decision_keys="
|
|
972
|
+
fm_write_meta "$state_new/$id.meta" \
|
|
973
|
+
"window=firstmate:fm-$id" "worktree=$wt" "project=$proj" "harness=claude" "kind=scout" "mode=no-mistakes" "yolo=off" \
|
|
974
|
+
"decisions_reviewed=1" "decision_keys="
|
|
975
|
+
touch "$state_old/.last-watcher-beat" "$state_new/.last-watcher-beat"
|
|
976
|
+
|
|
977
|
+
log_old="$TMP_ROOT/teardown-old.log"; log_new="$TMP_ROOT/teardown-new.log"
|
|
978
|
+
out_old=$(run_teardown_case "$old_bin/bin/fm-teardown.sh" "$old_bin" "$fb" "$log_old" "$state_old" "$data" "$config_old" "$id" 2>&1)
|
|
979
|
+
rc_old=$?
|
|
980
|
+
out_new=$(run_teardown_case "$ROOT/bin/fm-teardown.sh" "$old_bin" "$fb" "$log_new" "$state_new" "$data" "$config_new" "$id" 2>&1)
|
|
981
|
+
rc_new=$?
|
|
982
|
+
|
|
983
|
+
expect_code 0 "$rc_old" "old fm-teardown.sh (scout, report present) should succeed"$'\n'"$out_old"
|
|
984
|
+
expect_code 0 "$rc_new" "new fm-teardown.sh (scout, report present) should succeed"$'\n'"$out_new"
|
|
985
|
+
assert_contains "$(cat "$log_new")" "treehouse"$'\x1f''return'$'\x1f''--force'$'\x1f'"$wt" \
|
|
986
|
+
"teardown did not call treehouse return --force <worktree>"
|
|
987
|
+
# The legacy fixture's adapter comes from BASE_REF, so its selector form is
|
|
988
|
+
# whatever the merge-base carried: permissive while the exact-selector change
|
|
989
|
+
# was still on a branch, exact for every branch cut after it landed on main.
|
|
990
|
+
# Pinning the old form here would make this case pass once and then fail
|
|
991
|
+
# forever, so the '=' exactness markers are normalized away and the legacy run
|
|
992
|
+
# is only required to have reached tmux window cleanup for this task. The
|
|
993
|
+
# exact-selector contract belongs to the current script, asserted below.
|
|
994
|
+
assert_contains "$(tr -d '=' < "$log_old")" "tmux"$'\x1f''kill-window'$'\x1f''-t'$'\x1f'"firstmate:fm-$id" \
|
|
995
|
+
"legacy teardown fixture did not exercise tmux window cleanup for the task"
|
|
996
|
+
assert_contains "$(cat "$log_new")" "tmux"$'\x1f''kill-window'$'\x1f''-t'$'\x1f'"=firstmate:=fm-$id" \
|
|
997
|
+
"teardown did not call tmux kill-window with exact session and window selectors"
|
|
998
|
+
|
|
999
|
+
pass "fm-teardown.sh: treehouse return remains compatible while tmux cleanup uses exact selectors"
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
# --- backend selection loudly refuses an unknown backend --------------------
|
|
1003
|
+
|
|
1004
|
+
test_spawn_refuses_unknown_backend_flag() {
|
|
1005
|
+
local out status
|
|
1006
|
+
# bogus names a backend with no adapter at all; zellij and orca both
|
|
1007
|
+
# graduated to real adapters and have their own spawn tests.
|
|
1008
|
+
out=$(FM_ROOT_OVERRIDE='' FM_HOME='' FM_STATE_OVERRIDE='' FM_DATA_OVERRIDE='' \
|
|
1009
|
+
FM_PROJECTS_OVERRIDE='' FM_CONFIG_OVERRIDE='' FM_SPAWN_NO_GUARD=1 \
|
|
1010
|
+
"$ROOT/bin/fm-spawn.sh" nope-backend-z1 projects/none claude --mode no-mistakes --yolo off --backend bogus 2>&1)
|
|
1011
|
+
status=$?
|
|
1012
|
+
[ "$status" -ne 0 ] || fail "fm-spawn --backend bogus should refuse"
|
|
1013
|
+
assert_contains "$out" "unknown backend 'bogus'" "fm-spawn did not name the rejected backend"
|
|
1014
|
+
pass "fm-spawn.sh --backend bogus is refused loudly"
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
test_spawn_refuses_codex_app_backend_flag() {
|
|
1018
|
+
local out status
|
|
1019
|
+
out=$(FM_ROOT_OVERRIDE='' FM_HOME='' FM_STATE_OVERRIDE='' FM_DATA_OVERRIDE='' \
|
|
1020
|
+
FM_PROJECTS_OVERRIDE='' FM_CONFIG_OVERRIDE='' FM_SPAWN_NO_GUARD=1 \
|
|
1021
|
+
"$ROOT/bin/fm-spawn.sh" nope-codex-app-z1 projects/none claude --mode no-mistakes --yolo off --backend codex-app 2>&1)
|
|
1022
|
+
status=$?
|
|
1023
|
+
[ "$status" -ne 0 ] || fail "fm-spawn --backend codex-app should refuse"
|
|
1024
|
+
assert_contains "$out" "unknown backend 'codex-app'" "fm-spawn did not preserve the blocked codex-app contract"
|
|
1025
|
+
pass "fm-spawn.sh --backend codex-app is refused"
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
test_spawn_refuses_unknown_fm_backend_env() {
|
|
1029
|
+
local out status
|
|
1030
|
+
out=$(FM_ROOT_OVERRIDE='' FM_HOME='' FM_STATE_OVERRIDE='' FM_DATA_OVERRIDE='' \
|
|
1031
|
+
FM_PROJECTS_OVERRIDE='' FM_CONFIG_OVERRIDE='' FM_SPAWN_NO_GUARD=1 FM_BACKEND=bogus \
|
|
1032
|
+
"$ROOT/bin/fm-spawn.sh" nope-backend-z2 projects/none claude --mode no-mistakes --yolo off 2>&1)
|
|
1033
|
+
status=$?
|
|
1034
|
+
[ "$status" -ne 0 ] || fail "FM_BACKEND=bogus should refuse"
|
|
1035
|
+
assert_contains "$out" "unknown backend 'bogus'" "fm-spawn did not name the rejected FM_BACKEND"
|
|
1036
|
+
pass "fm-spawn.sh honors FM_BACKEND and refuses an unimplemented value loudly"
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
test_spawn_default_backend_writes_no_meta_field() {
|
|
1040
|
+
local proj wt data id state config out
|
|
1041
|
+
proj="$TMP_ROOT/nobackend-project"; wt="$TMP_ROOT/nobackend-wt"; data="$TMP_ROOT/nobackend-data"
|
|
1042
|
+
id="nobackendz3"
|
|
1043
|
+
fm_git_worktree "$proj" "$wt" "fm/$id"
|
|
1044
|
+
local fb
|
|
1045
|
+
fb=$(make_spawn_fakebin "$TMP_ROOT/nobackend-fake" "$wt")
|
|
1046
|
+
mkdir -p "$data/$id"; printf 'brief\n' > "$data/$id/brief.md"
|
|
1047
|
+
state="$TMP_ROOT/nobackend-state"; config="$TMP_ROOT/nobackend-config"
|
|
1048
|
+
mkdir -p "$state" "$config"
|
|
1049
|
+
|
|
1050
|
+
out=$(PATH="$fb:$PATH" FM_ROOT_OVERRIDE="$ROOT" \
|
|
1051
|
+
FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
|
|
1052
|
+
FM_PROJECTS_OVERRIDE="$TMP_ROOT/unused-projects" FM_SPAWN_NO_GUARD=1 TMUX="fake,1,0" \
|
|
1053
|
+
FM_TMUX_LOG="$TMP_ROOT/nobackend.log" \
|
|
1054
|
+
"$ROOT/bin/fm-spawn.sh" "$id" "$proj" claude --mode no-mistakes --yolo off --backend tmux 2>&1)
|
|
1055
|
+
expect_code 0 $? "explicit --backend tmux should spawn successfully"$'\n'"$out"
|
|
1056
|
+
assert_no_grep 'backend=' "$state/$id.meta" \
|
|
1057
|
+
"an explicit --backend tmux (the default) must not write backend= to meta (P1 compatibility contract)"
|
|
1058
|
+
rm -rf "/tmp/fm-$id"
|
|
1059
|
+
pass "fm-spawn.sh: an explicit --backend tmux resolves silently and writes no backend= (missing means tmux)"
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
test_spawn_explicit_backend_flag_beats_autodetect_herdr_env() {
|
|
1063
|
+
local proj wt data id state config out fb
|
|
1064
|
+
proj="$TMP_ROOT/explicit-backend-project"; wt="$TMP_ROOT/explicit-backend-wt"; data="$TMP_ROOT/explicit-backend-data"
|
|
1065
|
+
id="explicitbackendz4"
|
|
1066
|
+
fm_git_worktree "$proj" "$wt" "fm/$id"
|
|
1067
|
+
fb=$(make_spawn_fakebin "$TMP_ROOT/explicit-backend-fake" "$wt")
|
|
1068
|
+
mkdir -p "$data/$id"; printf 'brief\n' > "$data/$id/brief.md"
|
|
1069
|
+
state="$TMP_ROOT/explicit-backend-state"; config="$TMP_ROOT/explicit-backend-config"
|
|
1070
|
+
mkdir -p "$state" "$config"
|
|
1071
|
+
|
|
1072
|
+
# HERDR_ENV=1 is present (as if firstmate itself were running under herdr),
|
|
1073
|
+
# but an explicit --backend tmux flag must still win outright.
|
|
1074
|
+
out=$(PATH="$fb:$PATH" FM_ROOT_OVERRIDE="$ROOT" \
|
|
1075
|
+
FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
|
|
1076
|
+
FM_PROJECTS_OVERRIDE="$TMP_ROOT/unused-projects" FM_SPAWN_NO_GUARD=1 TMUX="fake,1,0" HERDR_ENV=1 \
|
|
1077
|
+
FM_TMUX_LOG="$TMP_ROOT/explicit-backend.log" \
|
|
1078
|
+
"$ROOT/bin/fm-spawn.sh" "$id" "$proj" claude --mode no-mistakes --yolo off --backend tmux 2>&1)
|
|
1079
|
+
expect_code 0 $? "explicit --backend tmux should spawn successfully even with HERDR_ENV=1 set"$'\n'"$out"
|
|
1080
|
+
assert_no_grep 'backend=' "$state/$id.meta" \
|
|
1081
|
+
"an explicit --backend tmux must win over an ambient HERDR_ENV=1 auto-detect marker"
|
|
1082
|
+
rm -rf "/tmp/fm-$id"
|
|
1083
|
+
pass "fm-spawn.sh: explicit --backend tmux wins over an ambient HERDR_ENV=1 auto-detect marker"
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
test_spawn_autodetect_nesting_resolves_tmux_silently() {
|
|
1087
|
+
local proj wt data id state config out fb
|
|
1088
|
+
proj="$TMP_ROOT/nest-project"; wt="$TMP_ROOT/nest-wt"; data="$TMP_ROOT/nest-data"
|
|
1089
|
+
id="nestbackendz5"
|
|
1090
|
+
fm_git_worktree "$proj" "$wt" "fm/$id"
|
|
1091
|
+
fb=$(make_spawn_fakebin "$TMP_ROOT/nest-fake" "$wt")
|
|
1092
|
+
mkdir -p "$data/$id"; printf 'brief\n' > "$data/$id/brief.md"
|
|
1093
|
+
state="$TMP_ROOT/nest-state"; config="$TMP_ROOT/nest-config"
|
|
1094
|
+
mkdir -p "$state" "$config"
|
|
1095
|
+
|
|
1096
|
+
# No --backend, no FM_BACKEND, no config/backend: nothing is explicitly
|
|
1097
|
+
# configured, so auto-detect runs. $TMUX and HERDR_ENV=1 are both present
|
|
1098
|
+
# (tmux nested inside a herdr pane) - the full fm-spawn.sh pipeline, not just
|
|
1099
|
+
# fm_backend_name, must resolve this to tmux and stay completely silent about
|
|
1100
|
+
# it (today's default path, byte-identical).
|
|
1101
|
+
out=$(PATH="$fb:$PATH" FM_ROOT_OVERRIDE="$ROOT" \
|
|
1102
|
+
FM_STATE_OVERRIDE="$state" FM_DATA_OVERRIDE="$data" FM_CONFIG_OVERRIDE="$config" \
|
|
1103
|
+
FM_PROJECTS_OVERRIDE="$TMP_ROOT/unused-projects" FM_SPAWN_NO_GUARD=1 TMUX="fake,1,0" HERDR_ENV=1 \
|
|
1104
|
+
FM_TMUX_LOG="$TMP_ROOT/nest.log" \
|
|
1105
|
+
"$ROOT/bin/fm-spawn.sh" "$id" "$proj" claude --mode no-mistakes --yolo off 2>&1)
|
|
1106
|
+
expect_code 0 $? "fm-spawn.sh should auto-detect tmux and spawn successfully for nested tmux-in-herdr"$'\n'"$out"
|
|
1107
|
+
assert_no_grep 'backend=' "$state/$id.meta" \
|
|
1108
|
+
"auto-detected nested tmux-in-herdr must resolve to tmux (missing backend= means tmux)"
|
|
1109
|
+
case "$out" in
|
|
1110
|
+
*NOTICE*) fail "auto-detecting tmux (even nested inside herdr) must stay silent, no NOTICE expected"$'\n'"$out" ;;
|
|
1111
|
+
esac
|
|
1112
|
+
rm -rf "/tmp/fm-$id"
|
|
1113
|
+
pass "fm-spawn.sh: auto-detect resolves nested tmux-in-herdr to tmux and stays silent end to end"
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
test_backend_name_precedence
|
|
1117
|
+
test_backend_detect_precedence
|
|
1118
|
+
test_backend_detect_cmux_fallback_bundle_id
|
|
1119
|
+
test_backend_detect_cmux_fallback_requires_darwin
|
|
1120
|
+
test_backend_detect_cmux_fallback_tmux_nested_false_positive
|
|
1121
|
+
test_backend_detect_cmux_fallback_ancestry_pid_match
|
|
1122
|
+
test_backend_detect_cmux_fallback_ancestry_comm_match
|
|
1123
|
+
test_backend_detect_cmux_fallback_ancestry_stops_at_launchd
|
|
1124
|
+
test_backend_name_cmux_fallback_notice
|
|
1125
|
+
test_backend_name_autodetect_notice
|
|
1126
|
+
test_backend_name_explicit_beats_detection
|
|
1127
|
+
test_backend_validate_refuses_unknown
|
|
1128
|
+
test_backend_source_shell_portable
|
|
1129
|
+
test_backend_validate_spawn_accepts_orca
|
|
1130
|
+
test_meta_get_and_backend_of_meta
|
|
1131
|
+
test_resolve_selector_three_forms
|
|
1132
|
+
test_backend_of_selector_matches_explicit_target_meta
|
|
1133
|
+
test_send_tmux_contract
|
|
1134
|
+
test_peek_conformance_old_vs_new
|
|
1135
|
+
test_spawn_symlinked_project_prefix_avoids_false_refusal
|
|
1136
|
+
test_teardown_conformance_old_vs_new
|
|
1137
|
+
test_spawn_refuses_unknown_backend_flag
|
|
1138
|
+
test_spawn_refuses_codex_app_backend_flag
|
|
1139
|
+
test_spawn_refuses_unknown_fm_backend_env
|
|
1140
|
+
test_spawn_default_backend_writes_no_meta_field
|
|
1141
|
+
test_spawn_explicit_backend_flag_beats_autodetect_herdr_env
|
|
1142
|
+
test_spawn_autodetect_nesting_resolves_tmux_silently
|