@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,1530 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# fm-control.sh relaunch: the transactional replace-the-agent verb.
|
|
3
|
+
#
|
|
4
|
+
# Relaunch is the only control verb that changes durable records, so these
|
|
5
|
+
# tests pin the transaction itself, hermetically (stubbed session provider, no
|
|
6
|
+
# real agent):
|
|
7
|
+
# 1. A same-harness relaunch keeps every identity axis and reuses the SAME
|
|
8
|
+
# endpoint and worktree - it replaces an agent, it never forks a task.
|
|
9
|
+
# 2. A harness switch is one ordinary relaunch: the record follows, the
|
|
10
|
+
# previous harness's per-task wiring is cleared, and profile axes chosen
|
|
11
|
+
# for the old harness do not silently carry to the new one.
|
|
12
|
+
# 3. The progress note is required where the replacement needs it, lands in
|
|
13
|
+
# the instructions the replacement reads, and never rewrites a charter.
|
|
14
|
+
# 4. A refusal before the agent is stopped changes nothing.
|
|
15
|
+
# 5. A launch failure after the agent is stopped keeps the prior record,
|
|
16
|
+
# reports the concrete state, and preserves the work.
|
|
17
|
+
# 6. fm-spawn --relaunch refuses on its own: a live agent, a contradicting
|
|
18
|
+
# flag, an extra positional, or a backend that cannot prove the previous
|
|
19
|
+
# agent exited.
|
|
20
|
+
set -u
|
|
21
|
+
|
|
22
|
+
# shellcheck source=tests/lib.sh
|
|
23
|
+
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
|
24
|
+
# shellcheck source=/dev/null
|
|
25
|
+
. "$ROOT/bin/fm-control-lib.sh"
|
|
26
|
+
# shellcheck source=/dev/null
|
|
27
|
+
. "$ROOT/bin/fm-trace-context-lib.sh"
|
|
28
|
+
|
|
29
|
+
CONTROL="$ROOT/bin/fm-control.sh"
|
|
30
|
+
SPAWN="$ROOT/bin/fm-spawn.sh"
|
|
31
|
+
PROMOTE="$ROOT/bin/fm-promote.sh"
|
|
32
|
+
X_LINK="$ROOT/bin/fm-x-link.sh"
|
|
33
|
+
# fm_test_tmproot's own cleanup trap fires when its command substitution exits,
|
|
34
|
+
# so recreate the root before resolving it and clean it up from this file's trap.
|
|
35
|
+
TMP_ROOT=$(fm_test_tmproot fm-control-relaunch)
|
|
36
|
+
mkdir -p "$TMP_ROOT"
|
|
37
|
+
TMP_ROOT=$(cd "$TMP_ROOT" && pwd)
|
|
38
|
+
TASK_TMPS=()
|
|
39
|
+
|
|
40
|
+
relaunch_cleanup() {
|
|
41
|
+
local d
|
|
42
|
+
for d in "${TASK_TMPS[@]:-}"; do
|
|
43
|
+
[ -n "$d" ] && rm -rf "$d"
|
|
44
|
+
done
|
|
45
|
+
rm -rf "$TMP_ROOT"
|
|
46
|
+
}
|
|
47
|
+
trap relaunch_cleanup EXIT
|
|
48
|
+
|
|
49
|
+
# The same lifecycle-modelling tmux stub as tests/fm-control.test.sh: the
|
|
50
|
+
# harness's exit command stops the agent, and a launch-brief literal starts the
|
|
51
|
+
# harness named in `becomes`.
|
|
52
|
+
make_tmux_stub() { # <dir>
|
|
53
|
+
local fb="$1/fakebin"
|
|
54
|
+
mkdir -p "$fb"
|
|
55
|
+
cat > "$fb/tmux" <<'SH'
|
|
56
|
+
#!/usr/bin/env bash
|
|
57
|
+
set -u
|
|
58
|
+
D=$FM_FAKE_DIR
|
|
59
|
+
case "${1:-}" in
|
|
60
|
+
send-keys)
|
|
61
|
+
shift
|
|
62
|
+
literal=0
|
|
63
|
+
while [ $# -gt 0 ]; do
|
|
64
|
+
case "$1" in
|
|
65
|
+
-t) shift 2 ;;
|
|
66
|
+
-l) literal=1; shift ;;
|
|
67
|
+
*) break ;;
|
|
68
|
+
esac
|
|
69
|
+
done
|
|
70
|
+
payload=${1:-}
|
|
71
|
+
if [ "$literal" = 1 ]; then
|
|
72
|
+
printf '%s\n' "$payload" >> "$D/literal"
|
|
73
|
+
case "$payload" in
|
|
74
|
+
/exit|/quit)
|
|
75
|
+
printf 'zsh' > "$D/command"
|
|
76
|
+
[ -z "${FM_FAKE_EXIT_TRANSPORT_FAIL_AFTER_STOP:-}" ] || exit 1
|
|
77
|
+
;;
|
|
78
|
+
*'encode launch-brief'*)
|
|
79
|
+
cat "$D/becomes" > "$D/command"
|
|
80
|
+
[ -z "${FM_FAKE_LAUNCH_TRANSPORT_FAIL_AFTER_START:-}" ] || exit 1
|
|
81
|
+
;;
|
|
82
|
+
esac
|
|
83
|
+
else
|
|
84
|
+
printf '%s\n' "$payload" >> "$D/keys"
|
|
85
|
+
case "$payload" in
|
|
86
|
+
'export GOTMPDIR='*)
|
|
87
|
+
if [ -n "${FM_FAKE_TRACE_PREPARE:-}" ]; then
|
|
88
|
+
: > "$FM_FAKE_TRACE_PREPARE"
|
|
89
|
+
while [ ! -e "$FM_FAKE_TRACE_RELEASE" ]; do /bin/sleep 0.01; done
|
|
90
|
+
fi
|
|
91
|
+
;;
|
|
92
|
+
'export TRACEPARENT='*)
|
|
93
|
+
[ -z "${FM_FAKE_TRACE_EXPORTED:-}" ] || : > "$FM_FAKE_TRACE_EXPORTED"
|
|
94
|
+
;;
|
|
95
|
+
esac
|
|
96
|
+
fi
|
|
97
|
+
exit 0 ;;
|
|
98
|
+
display-message)
|
|
99
|
+
for a in "$@"; do
|
|
100
|
+
case "$a" in
|
|
101
|
+
*cursor_y*) printf '1\n'; exit 0 ;;
|
|
102
|
+
*pane_current_command*) cat "$D/command"; printf '\n'; exit 0 ;;
|
|
103
|
+
*pane_current_path*)
|
|
104
|
+
if [ -n "${FM_FAKE_CWD_RACE_READY:-}" ]; then
|
|
105
|
+
: > "$FM_FAKE_CWD_RACE_READY"
|
|
106
|
+
/bin/sleep 1
|
|
107
|
+
fi
|
|
108
|
+
cat "$D/cwd"; printf '\n'; exit 0 ;;
|
|
109
|
+
esac
|
|
110
|
+
done
|
|
111
|
+
printf 'fakepane\n'; exit 0 ;;
|
|
112
|
+
capture-pane) printf '╭────╮\n│ │\n╰────╯\n'; exit 0 ;;
|
|
113
|
+
list-windows) [ -f "$D/windows" ] && cat "$D/windows"; exit 0 ;;
|
|
114
|
+
esac
|
|
115
|
+
exit 0
|
|
116
|
+
SH
|
|
117
|
+
chmod +x "$fb/tmux"
|
|
118
|
+
cat > "$fb/sleep" <<'SH'
|
|
119
|
+
#!/usr/bin/env bash
|
|
120
|
+
[ -z "${FM_FAKE_LOCK_WAITING:-}" ] || : > "$FM_FAKE_LOCK_WAITING"
|
|
121
|
+
exit 0
|
|
122
|
+
SH
|
|
123
|
+
chmod +x "$fb/sleep"
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# new_case <name> [id] -> echoes a case dir with a live claude ship task.
|
|
127
|
+
new_case() {
|
|
128
|
+
local id=${2:-t1} dir="$TMP_ROOT/$1-$RANDOM"
|
|
129
|
+
mkdir -p "$dir/home/state" "$dir/home/data" "$dir/fake"
|
|
130
|
+
: > "$dir/fake/literal"
|
|
131
|
+
: > "$dir/fake/keys"
|
|
132
|
+
printf 'claude' > "$dir/fake/command"
|
|
133
|
+
printf 'claude' > "$dir/fake/becomes"
|
|
134
|
+
printf '%s\n' "fm-$id" > "$dir/fake/windows"
|
|
135
|
+
make_tmux_stub "$dir"
|
|
136
|
+
printf '%s\n' "$dir"
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# add_ship_task <case-dir> <id> [harness]
|
|
140
|
+
add_ship_task() {
|
|
141
|
+
local dir=$1 id=$2 harness=${3:-claude}
|
|
142
|
+
local home="$dir/home" proj="$dir/proj" wt="$dir/wt"
|
|
143
|
+
fm_git_worktree "$proj" "$wt" "task-$id"
|
|
144
|
+
mkdir -p "$home/data/$id"
|
|
145
|
+
printf '# brief for %s\n\nDo the thing.\n' "$id" > "$home/data/$id/brief.md"
|
|
146
|
+
{
|
|
147
|
+
echo "window=fmses:fm-$id"
|
|
148
|
+
echo "endpoint_task_id=$id"
|
|
149
|
+
echo "worktree=$wt"
|
|
150
|
+
echo "project=$proj"
|
|
151
|
+
echo "harness=$harness"
|
|
152
|
+
echo "kind=ship"
|
|
153
|
+
echo "mode=no-mistakes"
|
|
154
|
+
echo "yolo=off"
|
|
155
|
+
echo "tasktmp=/tmp/fm-$id"
|
|
156
|
+
echo "model=default"
|
|
157
|
+
echo "effort=default"
|
|
158
|
+
} > "$home/state/$id.meta"
|
|
159
|
+
printf '%s\n' "fm-$id" > "$dir/fake/windows"
|
|
160
|
+
printf '%s' "$wt" > "$dir/fake/cwd"
|
|
161
|
+
TASK_TMPS+=("/tmp/fm-$id")
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
run_control() { # <case-dir> <args...>
|
|
165
|
+
local dir=$1; shift
|
|
166
|
+
env PATH="$dir/fakebin:$PATH" FM_HOME="$dir/home" FM_FAKE_DIR="$dir/fake" \
|
|
167
|
+
FM_SPAWN_NO_GUARD=1 GROK_HOME="$dir/grokhome" \
|
|
168
|
+
FM_CONTROL_POLL=0.01 FM_CONTROL_EXIT_WAIT=0.05 FM_CONTROL_LAUNCH_WAIT=0.05 \
|
|
169
|
+
FM_REAL_GIT="${FM_REAL_GIT:-}" FM_FAKE_GIT_FAILURE="${FM_FAKE_GIT_FAILURE:-}" \
|
|
170
|
+
FM_REAL_MV="${FM_REAL_MV:-}" FM_FAKE_COMPLETE_JOURNAL_MV_FAIL="${FM_FAKE_COMPLETE_JOURNAL_MV_FAIL:-}" \
|
|
171
|
+
FM_FAKE_META_PUBLISH_MV_FAIL="${FM_FAKE_META_PUBLISH_MV_FAIL:-}" \
|
|
172
|
+
FM_FAKE_TRACE_PREPARE="${FM_FAKE_TRACE_PREPARE:-}" \
|
|
173
|
+
FM_FAKE_TRACE_RELEASE="${FM_FAKE_TRACE_RELEASE:-}" \
|
|
174
|
+
FM_FAKE_META_WRITER_READY="${FM_FAKE_META_WRITER_READY:-}" \
|
|
175
|
+
FM_FAKE_TRACE_EXPORTED="${FM_FAKE_TRACE_EXPORTED:-}" \
|
|
176
|
+
"$CONTROL" "$@" 2>&1
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
run_spawn() { # <case-dir> <args...>
|
|
180
|
+
local dir=$1; shift
|
|
181
|
+
env PATH="$dir/fakebin:$PATH" FM_HOME="$dir/home" FM_FAKE_DIR="$dir/fake" \
|
|
182
|
+
FM_SPAWN_NO_GUARD=1 GROK_HOME="$dir/grokhome" \
|
|
183
|
+
"$SPAWN" "$@" 2>&1
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
meta_field() { # <case-dir> <id> <key>
|
|
187
|
+
grep "^$3=" "$1/home/state/$2.meta" | tail -1 | cut -d= -f2-
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
journal_field() { # <case-dir> <id> <key>
|
|
191
|
+
grep "^$3=" "$1/home/state/$2.control-relaunch" | tail -1 | cut -d= -f2-
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
make_git_failure_stub() { # <case-dir>
|
|
195
|
+
cat > "$1/fakebin/git" <<'SH'
|
|
196
|
+
#!/usr/bin/env bash
|
|
197
|
+
case "${FM_FAKE_GIT_FAILURE:-}:$*" in
|
|
198
|
+
head:*' rev-parse --verify HEAD'|head:*' symbolic-ref -q HEAD') exit 128 ;;
|
|
199
|
+
status:*' status --porcelain') exit 128 ;;
|
|
200
|
+
esac
|
|
201
|
+
exec "$FM_REAL_GIT" "$@"
|
|
202
|
+
SH
|
|
203
|
+
chmod +x "$1/fakebin/git"
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
make_mv_failure_stub() { # <case-dir>
|
|
207
|
+
cat > "$1/fakebin/mv" <<'SH'
|
|
208
|
+
#!/usr/bin/env bash
|
|
209
|
+
if [ -n "${FM_FAKE_COMPLETE_JOURNAL_MV_FAIL:-}" ]; then
|
|
210
|
+
for path in "$@"; do
|
|
211
|
+
if [ -f "$path" ] && grep -Fqx 'phase=complete' "$path"; then
|
|
212
|
+
exit 1
|
|
213
|
+
fi
|
|
214
|
+
done
|
|
215
|
+
fi
|
|
216
|
+
if [ -n "${FM_FAKE_META_PUBLISH_MV_FAIL:-}" ]; then
|
|
217
|
+
for path in "$@"; do
|
|
218
|
+
[ "$path" != "$FM_FAKE_META_PUBLISH_MV_FAIL" ] || exit 1
|
|
219
|
+
done
|
|
220
|
+
fi
|
|
221
|
+
source_path=
|
|
222
|
+
target_path=
|
|
223
|
+
for path in "$@"; do
|
|
224
|
+
source_path=$target_path
|
|
225
|
+
target_path=$path
|
|
226
|
+
done
|
|
227
|
+
if [ -n "${FM_FAKE_META_WRITER_TARGET:-}" ] \
|
|
228
|
+
&& [ "$target_path" = "$FM_FAKE_META_WRITER_TARGET" ] \
|
|
229
|
+
&& grep -q '^x_request=' "$source_path" 2>/dev/null; then
|
|
230
|
+
: > "$FM_FAKE_META_WRITER_READY"
|
|
231
|
+
while [ ! -e "$FM_FAKE_META_WRITER_RELEASE" ]; do /bin/sleep 0.01; done
|
|
232
|
+
fi
|
|
233
|
+
exec "$FM_REAL_MV" "$@"
|
|
234
|
+
SH
|
|
235
|
+
chmod +x "$1/fakebin/mv"
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
make_rm_failure_stub() { # <case-dir>
|
|
239
|
+
cat > "$1/fakebin/rm" <<'SH'
|
|
240
|
+
#!/usr/bin/env bash
|
|
241
|
+
for arg in "$@"; do
|
|
242
|
+
if [ -n "${FM_FAKE_RM_FAIL_PATH:-}" ] && [ "$arg" = "$FM_FAKE_RM_FAIL_PATH" ]; then
|
|
243
|
+
exit 1
|
|
244
|
+
fi
|
|
245
|
+
done
|
|
246
|
+
exec "$FM_REAL_RM" "$@"
|
|
247
|
+
SH
|
|
248
|
+
chmod +x "$1/fakebin/rm"
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
# Give a case home a real backlog carrying <id>, so the relaunch path's paired
|
|
252
|
+
# backlog transition (bin/fm-backlog-transition-lib.sh) is live rather than
|
|
253
|
+
# skipped for want of a backlog file.
|
|
254
|
+
seed_backlog() { # <case-dir> <id> <queued|in_flight>
|
|
255
|
+
local dir=$1 id=$2 want=$3 file="$1/home/data/backlog.md"
|
|
256
|
+
printf '%s\n' '# Backlog' '' '## In flight' '' '## Queued' '' '## Done' > "$file"
|
|
257
|
+
tasks-axi add "$id" "relaunch fixture task" --kind ship --file "$file" >/dev/null
|
|
258
|
+
[ "$want" != in_flight ] || tasks-axi start "$id" --file "$file" >/dev/null
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
backlog_state() { # <case-dir> <id>
|
|
262
|
+
tasks-axi show "$2" --file "$1/home/data/backlog.md" 2>/dev/null |
|
|
263
|
+
sed -n 's/^ state: *//p' | head -1
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# Shadow tasks-axi so every `start` fails and every other verb is real. A
|
|
267
|
+
# relaunch that re-reads the row before acting never calls it; one that assumes
|
|
268
|
+
# it must re-run the transition trips over it.
|
|
269
|
+
break_tasks_axi_start() { # <case-dir>
|
|
270
|
+
local dir=$1 real
|
|
271
|
+
real=$(command -v tasks-axi)
|
|
272
|
+
cat > "$dir/fakebin/tasks-axi" <<SH
|
|
273
|
+
#!/usr/bin/env bash
|
|
274
|
+
if [ "\${1:-}" = start ]; then
|
|
275
|
+
echo 'error: "start refused"' >&2
|
|
276
|
+
exit 1
|
|
277
|
+
fi
|
|
278
|
+
exec "$real" "\$@"
|
|
279
|
+
SH
|
|
280
|
+
chmod +x "$dir/fakebin/tasks-axi"
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
# --- 1. same-harness relaunch -----------------------------------------------
|
|
284
|
+
|
|
285
|
+
test_same_harness_relaunch_keeps_identity_and_reuses_the_endpoint() {
|
|
286
|
+
local dir out rc gen_before gen_after
|
|
287
|
+
dir=$(new_case same rl1)
|
|
288
|
+
add_ship_task "$dir" rl1 claude
|
|
289
|
+
gen_before=$("$ROOT/bin/fm-busy-event.sh" arm "$dir/home/state" rl1)
|
|
290
|
+
printf 'busy_gen=%s\n' "$gen_before" >> "$dir/home/state/rl1.meta"
|
|
291
|
+
out=$(run_control "$dir" rl1 relaunch --note "stopped mid-refactor"); rc=$?
|
|
292
|
+
expect_code 0 "$rc" "a same-harness relaunch should succeed"$'\n'"$out"
|
|
293
|
+
assert_contains "$out" "relaunched rl1 harness=claude from=claude" "the outcome should name the transition"
|
|
294
|
+
[ "$(meta_field "$dir" rl1 window)" = "fmses:fm-rl1" ] \
|
|
295
|
+
|| fail "the endpoint must be reused, not recreated"
|
|
296
|
+
[ "$(meta_field "$dir" rl1 worktree)" = "$dir/wt" ] \
|
|
297
|
+
|| fail "the worktree must be reused, not reallocated"
|
|
298
|
+
[ "$(meta_field "$dir" rl1 kind)" = ship ] || fail "kind must survive the relaunch"
|
|
299
|
+
[ "$(meta_field "$dir" rl1 project)" = "$dir/proj" ] || fail "project must survive the relaunch"
|
|
300
|
+
gen_after=$(meta_field "$dir" rl1 busy_gen)
|
|
301
|
+
[ -n "$gen_after" ] && [ "$gen_after" != "$gen_before" ] \
|
|
302
|
+
|| fail "a relaunch must arm a fresh busy generation, got '$gen_after'"
|
|
303
|
+
[ "$(journal_field "$dir" rl1 phase)" = complete ] \
|
|
304
|
+
|| fail "the transaction journal should end complete"
|
|
305
|
+
assert_grep "/exit" "$dir/fake/literal" "the previous agent should have been exited"
|
|
306
|
+
assert_grep "encode launch-brief" "$dir/fake/literal" "the replacement should have been launched"
|
|
307
|
+
pass "fm-control relaunch: a same-harness relaunch replaces the agent in the same endpoint and worktree"
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
test_relaunch_preserves_durable_task_metadata() {
|
|
311
|
+
local dir out rc
|
|
312
|
+
dir=$(new_case durable-meta rl19)
|
|
313
|
+
add_ship_task "$dir" rl19 claude
|
|
314
|
+
{
|
|
315
|
+
printf '%s\n' 'pr=https://github.com/example/repo/pull/19'
|
|
316
|
+
printf '%s\n' 'pr_head=feature/relaunch'
|
|
317
|
+
printf '%s\n' 'x_request=request-19'
|
|
318
|
+
printf '%s\n' 'decisions_reviewed=1'
|
|
319
|
+
} >> "$dir/home/state/rl19.meta"
|
|
320
|
+
|
|
321
|
+
out=$(run_control "$dir" rl19 relaunch --note "continuing review work"); rc=$?
|
|
322
|
+
expect_code 0 "$rc" "relaunch should preserve durable metadata"$'\n'"$out"
|
|
323
|
+
[ "$(meta_field "$dir" rl19 pr)" = "https://github.com/example/repo/pull/19" ] \
|
|
324
|
+
|| fail "the task PR must survive relaunch"
|
|
325
|
+
[ "$(meta_field "$dir" rl19 pr_head)" = "feature/relaunch" ] \
|
|
326
|
+
|| fail "the task PR head must survive relaunch"
|
|
327
|
+
[ "$(meta_field "$dir" rl19 x_request)" = "request-19" ] \
|
|
328
|
+
|| fail "the task X request must survive relaunch"
|
|
329
|
+
[ "$(meta_field "$dir" rl19 decisions_reviewed)" = 1 ] \
|
|
330
|
+
|| fail "the task decision state must survive relaunch"
|
|
331
|
+
pass "fm-control relaunch: durable task metadata survives replacement launch publication"
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
test_relaunch_serializes_concurrent_durable_metadata_publication() {
|
|
335
|
+
local dir control_pid link_pid rc i=0 traceparent prepare launch_release waiting ready release
|
|
336
|
+
dir=$(new_case metadata-race rl28)
|
|
337
|
+
add_ship_task "$dir" rl28 claude
|
|
338
|
+
printf '%s\n' "$$" > "$dir/home/state/.lock"
|
|
339
|
+
printf '%s on\n' "$$" > "$dir/home/state/.trace-context-effective"
|
|
340
|
+
make_mv_failure_stub "$dir"
|
|
341
|
+
prepare="$dir/trace-prepare"
|
|
342
|
+
launch_release="$dir/trace-release"
|
|
343
|
+
waiting="$dir/meta-writer-waiting"
|
|
344
|
+
ready="$dir/meta-writer-ready"
|
|
345
|
+
release="$dir/meta-writer-release"
|
|
346
|
+
FM_REAL_MV=$(command -v mv) \
|
|
347
|
+
FM_FAKE_TRACE_PREPARE="$prepare" \
|
|
348
|
+
FM_FAKE_TRACE_RELEASE="$launch_release" \
|
|
349
|
+
run_control "$dir" rl28 relaunch --note "continue after publication" > "$dir/control.out" &
|
|
350
|
+
control_pid=$!
|
|
351
|
+
while [ ! -e "$prepare" ] && [ "$i" -lt 200 ]; do
|
|
352
|
+
/bin/sleep 0.01
|
|
353
|
+
i=$((i + 1))
|
|
354
|
+
done
|
|
355
|
+
[ -e "$prepare" ] || {
|
|
356
|
+
kill "$control_pid" 2>/dev/null || true
|
|
357
|
+
wait "$control_pid" 2>/dev/null || true
|
|
358
|
+
fail "relaunch did not reach trace delivery"
|
|
359
|
+
}
|
|
360
|
+
env PATH="$dir/fakebin:$PATH" FM_HOME="$dir/home" FM_ROOT_OVERRIDE="$ROOT" \
|
|
361
|
+
FM_REAL_MV="$(command -v mv)" \
|
|
362
|
+
FM_FAKE_LOCK_WAITING="$waiting" \
|
|
363
|
+
FM_FAKE_META_WRITER_TARGET="$dir/home/state/rl28.meta" \
|
|
364
|
+
FM_FAKE_META_WRITER_READY="$ready" \
|
|
365
|
+
FM_FAKE_META_WRITER_RELEASE="$release" \
|
|
366
|
+
"$X_LINK" rl28 request-28 --carry-count 1 --carry-ts 1700000000 \
|
|
367
|
+
--carry-platform x --carry-max 280 > "$dir/link.out" 2>&1 &
|
|
368
|
+
link_pid=$!
|
|
369
|
+
i=0
|
|
370
|
+
while [ ! -e "$waiting" ] && [ "$i" -lt 200 ]; do
|
|
371
|
+
/bin/sleep 0.01
|
|
372
|
+
i=$((i + 1))
|
|
373
|
+
done
|
|
374
|
+
[ -e "$waiting" ] && [ ! -e "$ready" ] || {
|
|
375
|
+
: > "$launch_release"
|
|
376
|
+
: > "$release"
|
|
377
|
+
wait "$link_pid" 2>/dev/null || true
|
|
378
|
+
wait "$control_pid" 2>/dev/null || true
|
|
379
|
+
fail "a durable metadata writer was not blocked during relaunch delivery"
|
|
380
|
+
}
|
|
381
|
+
: > "$launch_release"
|
|
382
|
+
i=0
|
|
383
|
+
while [ ! -e "$ready" ] && [ "$i" -lt 200 ]; do
|
|
384
|
+
/bin/sleep 0.01
|
|
385
|
+
i=$((i + 1))
|
|
386
|
+
done
|
|
387
|
+
[ -e "$ready" ] || {
|
|
388
|
+
kill "$link_pid" "$control_pid" 2>/dev/null || true
|
|
389
|
+
wait "$link_pid" 2>/dev/null || true
|
|
390
|
+
wait "$control_pid" 2>/dev/null || true
|
|
391
|
+
fail "durable metadata writer did not resume after relaunch delivery committed"
|
|
392
|
+
}
|
|
393
|
+
: > "$release"
|
|
394
|
+
wait "$link_pid"; rc=$?
|
|
395
|
+
expect_code 0 "$rc" "concurrent X metadata publication should serialize"$'\n'"$(cat "$dir/link.out")"
|
|
396
|
+
wait "$control_pid"; rc=$?
|
|
397
|
+
expect_code 0 "$rc" "relaunch should complete before serialized metadata publication"$'\n'"$(cat "$dir/control.out")"
|
|
398
|
+
[ "$(meta_field "$dir" rl28 x_request)" = request-28 ] \
|
|
399
|
+
|| fail "relaunch erased metadata published concurrently through the X interface"
|
|
400
|
+
[ "$(meta_field "$dir" rl28 x_followups)" = 1 ] \
|
|
401
|
+
|| fail "relaunch erased the concurrent follow-up count"
|
|
402
|
+
traceparent=$(meta_field "$dir" rl28 traceparent)
|
|
403
|
+
fm_trace_context_valid "$traceparent" \
|
|
404
|
+
|| fail "concurrent metadata publication erased the replacement's trace carrier"
|
|
405
|
+
pass "fm-control relaunch: delivery and concurrent task metadata publication serialize"
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
test_disabled_relaunch_clears_prior_trace_context() {
|
|
409
|
+
local dir out rc
|
|
410
|
+
dir=$(new_case trace-off rl33)
|
|
411
|
+
add_ship_task "$dir" rl33 claude
|
|
412
|
+
printf '%s\n' 'traceparent=00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-01' \
|
|
413
|
+
>> "$dir/home/state/rl33.meta"
|
|
414
|
+
printf '%s\n' "$$" > "$dir/home/state/.lock"
|
|
415
|
+
printf '%s off\n' "$$" > "$dir/home/state/.trace-context-effective"
|
|
416
|
+
|
|
417
|
+
out=$(run_control "$dir" rl33 relaunch --note "crossing trace boundary"); rc=$?
|
|
418
|
+
expect_code 0 "$rc" "disabled relaunch should succeed"$'\n'"$out"
|
|
419
|
+
[ -z "$(meta_field "$dir" rl33 traceparent)" ] \
|
|
420
|
+
|| fail "disabled relaunch must remove the prior trace carrier from metadata"
|
|
421
|
+
grep -q '^unset TRACEPARENT; .*claude' "$dir/fake/literal" \
|
|
422
|
+
|| fail "disabled relaunch must clear the pane carrier before replacement launch"
|
|
423
|
+
! grep -q '^export TRACEPARENT=' "$dir/fake/literal" \
|
|
424
|
+
|| fail "disabled relaunch must not export a replacement trace carrier"
|
|
425
|
+
pass "fm-control relaunch: disabling tracing clears metadata and pane context"
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
test_relaunch_appends_the_progress_note_to_the_instructions() {
|
|
429
|
+
local dir out rc brief
|
|
430
|
+
dir=$(new_case note rl2)
|
|
431
|
+
add_ship_task "$dir" rl2 claude
|
|
432
|
+
out=$(run_control "$dir" rl2 relaunch --note "reproduced the crash in parser.go"); rc=$?
|
|
433
|
+
expect_code 0 "$rc" "relaunch should succeed"$'\n'"$out"
|
|
434
|
+
brief="$dir/home/data/rl2/brief.md"
|
|
435
|
+
assert_grep "Do the thing." "$brief" "the original instructions must survive"
|
|
436
|
+
assert_grep "## Progress note" "$brief" "the note should be a dated section in the instructions"
|
|
437
|
+
assert_grep "reproduced the crash in parser.go" "$brief" "the note text should reach the replacement"
|
|
438
|
+
assert_grep "reproduced the crash in parser.go" "$dir/home/state/rl2.control-relaunch.note" \
|
|
439
|
+
"the note should also be preserved beside the transaction record"
|
|
440
|
+
pass "fm-control relaunch: the progress note lands in the instructions the replacement reads"
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
test_relaunch_requires_a_note_for_a_ship_task() {
|
|
444
|
+
local dir out rc before
|
|
445
|
+
dir=$(new_case nonote rl3)
|
|
446
|
+
add_ship_task "$dir" rl3 claude
|
|
447
|
+
before=$(cat "$dir/home/data/rl3/brief.md")
|
|
448
|
+
out=$(run_control "$dir" rl3 relaunch); rc=$?
|
|
449
|
+
expect_code 1 "$rc" "a ship relaunch without a note should refuse"
|
|
450
|
+
assert_contains "$out" "requires --note" "the refusal should name the missing note"
|
|
451
|
+
[ "$(cat "$dir/home/data/rl3/brief.md")" = "$before" ] \
|
|
452
|
+
|| fail "a refused relaunch must not touch the instructions"
|
|
453
|
+
[ -z "$(cat "$dir/fake/literal")" ] || fail "a refused relaunch must send nothing"
|
|
454
|
+
[ "$(cat "$dir/fake/command")" = claude ] || fail "a refused relaunch must not stop the agent"
|
|
455
|
+
pass "fm-control relaunch: a ship task refuses without the progress note its replacement needs"
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
# --- 2. harness switch -------------------------------------------------------
|
|
459
|
+
|
|
460
|
+
test_harness_switch_moves_the_record_and_clears_prior_wiring() {
|
|
461
|
+
local dir out rc
|
|
462
|
+
dir=$(new_case switch rl4)
|
|
463
|
+
add_ship_task "$dir" rl4 claude
|
|
464
|
+
# Wiring the previous claude incarnation left in the worktree.
|
|
465
|
+
mkdir -p "$dir/wt/.claude"
|
|
466
|
+
printf '{"hooks":{}}\n' > "$dir/wt/.claude/settings.local.json"
|
|
467
|
+
printf 'codex' > "$dir/fake/becomes"
|
|
468
|
+
out=$(run_control "$dir" rl4 relaunch --harness codex --note "switching runtime"); rc=$?
|
|
469
|
+
expect_code 0 "$rc" "a harness switch should succeed"$'\n'"$out"
|
|
470
|
+
assert_contains "$out" "harness=codex from=claude" "the outcome should name both harnesses"
|
|
471
|
+
[ "$(meta_field "$dir" rl4 harness)" = codex ] || fail "the record should follow the switch"
|
|
472
|
+
[ ! -e "$dir/wt/.claude/settings.local.json" ] \
|
|
473
|
+
|| fail "the previous harness's per-task wiring must be cleared on a switch"
|
|
474
|
+
assert_grep "codex" "$dir/fake/literal" "the replacement launch should be the new harness"
|
|
475
|
+
[ "$(journal_field "$dir" rl4 from_harness)" = claude ] || fail "the journal should record the origin harness"
|
|
476
|
+
[ "$(journal_field "$dir" rl4 to_harness)" = codex ] || fail "the journal should record the target harness"
|
|
477
|
+
pass "fm-control relaunch: switching harness is one ordinary relaunch, and the old wiring goes with the old agent"
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
test_harness_switch_does_not_carry_the_old_profile_axes() {
|
|
481
|
+
local dir out rc
|
|
482
|
+
dir=$(new_case profile rl5)
|
|
483
|
+
add_ship_task "$dir" rl5 claude
|
|
484
|
+
sed 's/^model=default$/model=opus/; s/^effort=default$/effort=xhigh/' \
|
|
485
|
+
"$dir/home/state/rl5.meta" > "$dir/home/state/rl5.meta.tmp"
|
|
486
|
+
mv "$dir/home/state/rl5.meta.tmp" "$dir/home/state/rl5.meta"
|
|
487
|
+
printf 'codex' > "$dir/fake/becomes"
|
|
488
|
+
out=$(run_control "$dir" rl5 relaunch --harness codex --note "switching runtime"); rc=$?
|
|
489
|
+
expect_code 0 "$rc" "a harness switch should succeed"$'\n'"$out"
|
|
490
|
+
[ "$(meta_field "$dir" rl5 model)" = default ] \
|
|
491
|
+
|| fail "a model chosen for the old harness must not carry to a different one"
|
|
492
|
+
[ "$(meta_field "$dir" rl5 effort)" = default ] \
|
|
493
|
+
|| fail "an effort chosen for the old harness must not carry to a different one"
|
|
494
|
+
pass "fm-control relaunch: a harness switch resets model and effort unless they are named too"
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
test_harness_switch_resolves_a_prefixed_recorded_harness() {
|
|
498
|
+
local dir out rc auth
|
|
499
|
+
dir=$(new_case prefixcontrol rl32)
|
|
500
|
+
add_ship_task "$dir" rl32 grok-2
|
|
501
|
+
printf 'grok-2' > "$dir/fake/command"
|
|
502
|
+
mkdir -p "$dir/grokhome/hooks/fm-turn-end.d"
|
|
503
|
+
printf 'fm.abcdefabcdef\n' > "$dir/home/state/rl32.grok-turnend-token"
|
|
504
|
+
auth="$dir/grokhome/hooks/fm-turn-end.d/fm.abcdefabcdef"
|
|
505
|
+
printf '%s\n' "$dir/home/state/rl32.turn-ended" > "$auth"
|
|
506
|
+
printf 'token=fm.abcdefabcdef\n' > "$dir/wt/.fm-grok-turnend"
|
|
507
|
+
|
|
508
|
+
out=$(run_control "$dir" rl32 relaunch --harness claude --note "switching runtime"); rc=$?
|
|
509
|
+
expect_code 0 "$rc" "relaunch should resolve a prefixed recorded harness"$'\n'"$out"
|
|
510
|
+
[ "$(sed -n '1p' "$dir/fake/literal")" = /exit ] \
|
|
511
|
+
|| fail "relaunch should stop a grok-prefixed task with grok's exit command"
|
|
512
|
+
[ "$(meta_field "$dir" rl32 harness)" = claude ] \
|
|
513
|
+
|| fail "relaunch should publish the explicitly selected replacement harness"
|
|
514
|
+
[ "$(journal_field "$dir" rl32 from_harness)" = grok-2 ] \
|
|
515
|
+
|| fail "relaunch should retain the recorded harness basename in its provenance"
|
|
516
|
+
assert_contains "$out" "harness=claude from=grok-2" \
|
|
517
|
+
"relaunch should report the recorded-to-selected harness transition"
|
|
518
|
+
[ ! -e "$auth" ] && [ ! -e "$dir/home/state/rl32.grok-turnend-token" ] \
|
|
519
|
+
&& [ ! -e "$dir/wt/.fm-grok-turnend" ] \
|
|
520
|
+
|| fail "relaunch should retire wiring owned by the prefixed prior harness"
|
|
521
|
+
pass "fm-control relaunch: a prefixed recorded harness can switch adapters transactionally"
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
test_prefixed_recorded_harness_requires_explicit_replacement() {
|
|
525
|
+
local dir out rc meta brief
|
|
526
|
+
dir=$(new_case prefixrefuse rl34)
|
|
527
|
+
add_ship_task "$dir" rl34 grok-2
|
|
528
|
+
printf 'grok-2' > "$dir/fake/command"
|
|
529
|
+
meta="$dir/home/state/rl34.meta"
|
|
530
|
+
brief="$dir/home/data/rl34/brief.md"
|
|
531
|
+
cp "$meta" "$dir/meta.before"
|
|
532
|
+
cp "$brief" "$dir/brief.before"
|
|
533
|
+
|
|
534
|
+
out=$(run_control "$dir" rl34 relaunch --note "continue safely"); rc=$?
|
|
535
|
+
expect_code 1 "$rc" "implicit relaunch from a prefixed command should refuse"
|
|
536
|
+
assert_contains "$out" "original launch command cannot be reconstructed from its recorded basename" \
|
|
537
|
+
"the refusal should name the missing launch identity"
|
|
538
|
+
assert_contains "$out" "would substitute the canonical adapter 'grok'" \
|
|
539
|
+
"the refusal should name the unsafe substitution"
|
|
540
|
+
assert_contains "$out" "Pass an explicit --harness" \
|
|
541
|
+
"the refusal should name the deliberate replacement path"
|
|
542
|
+
cmp -s "$meta" "$dir/meta.before" \
|
|
543
|
+
|| fail "a refused prefixed relaunch must leave metadata byte-identical"
|
|
544
|
+
cmp -s "$brief" "$dir/brief.before" \
|
|
545
|
+
|| fail "a refused prefixed relaunch must leave instructions byte-identical"
|
|
546
|
+
[ "$(cat "$dir/fake/command")" = grok-2 ] \
|
|
547
|
+
|| fail "a refused prefixed relaunch must leave the original agent alive"
|
|
548
|
+
[ -z "$(cat "$dir/fake/literal")" ] && [ -z "$(cat "$dir/fake/keys")" ] \
|
|
549
|
+
|| fail "a refused prefixed relaunch must deliver no lifecycle input"
|
|
550
|
+
[ ! -e "$dir/home/state/rl34.control-relaunch" ] \
|
|
551
|
+
|| fail "a refused prefixed relaunch must not create a durable journal"
|
|
552
|
+
pass "fm-control relaunch: a prefixed command requires an explicit replacement harness"
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
test_same_harness_relaunch_keeps_the_profile_axes() {
|
|
556
|
+
local dir out rc
|
|
557
|
+
dir=$(new_case keepprofile rl6)
|
|
558
|
+
add_ship_task "$dir" rl6 claude
|
|
559
|
+
sed 's/^model=default$/model=opus/; s/^effort=default$/effort=high/' \
|
|
560
|
+
"$dir/home/state/rl6.meta" > "$dir/home/state/rl6.meta.tmp"
|
|
561
|
+
mv "$dir/home/state/rl6.meta.tmp" "$dir/home/state/rl6.meta"
|
|
562
|
+
out=$(run_control "$dir" rl6 relaunch --note "same runtime"); rc=$?
|
|
563
|
+
expect_code 0 "$rc" "a same-harness relaunch should succeed"$'\n'"$out"
|
|
564
|
+
[ "$(meta_field "$dir" rl6 model)" = opus ] || fail "the model should carry across a same-harness relaunch"
|
|
565
|
+
[ "$(meta_field "$dir" rl6 effort)" = high ] || fail "the effort should carry across a same-harness relaunch"
|
|
566
|
+
pass "fm-control relaunch: a same-harness relaunch keeps the profile axes it was running with"
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
test_explicit_model_wins_over_the_recorded_one() {
|
|
570
|
+
local dir out rc
|
|
571
|
+
dir=$(new_case explicit rl7)
|
|
572
|
+
add_ship_task "$dir" rl7 claude
|
|
573
|
+
out=$(run_control "$dir" rl7 relaunch --model sonnet --effort low --note "dialling down"); rc=$?
|
|
574
|
+
expect_code 0 "$rc" "relaunch with explicit axes should succeed"$'\n'"$out"
|
|
575
|
+
[ "$(meta_field "$dir" rl7 model)" = sonnet ] || fail "an explicit model should be recorded"
|
|
576
|
+
[ "$(meta_field "$dir" rl7 effort)" = low ] || fail "an explicit effort should be recorded"
|
|
577
|
+
pass "fm-control relaunch: explicit model and effort win over the recorded ones"
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
test_relaunch_onto_an_unverified_harness_is_refused() {
|
|
581
|
+
local dir out rc
|
|
582
|
+
dir=$(new_case badharness rl8)
|
|
583
|
+
add_ship_task "$dir" rl8 claude
|
|
584
|
+
out=$(run_control "$dir" rl8 relaunch --harness someagent --note "x"); rc=$?
|
|
585
|
+
expect_code 1 "$rc" "an unverified target harness should refuse"
|
|
586
|
+
assert_contains "$out" "not a verified harness" "the refusal should name the unverified adapter"
|
|
587
|
+
[ "$(cat "$dir/fake/command")" = claude ] || fail "a refused relaunch must not stop the agent"
|
|
588
|
+
pass "fm-control relaunch: refuses to relaunch onto an adapter with no verified mechanics"
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
test_prior_harness_turnend_registry_entry_is_cleared() {
|
|
592
|
+
local dir auth
|
|
593
|
+
dir=$(new_case grokauth rl9)
|
|
594
|
+
add_ship_task "$dir" rl9 grok
|
|
595
|
+
mkdir -p "$dir/grokhome/hooks/fm-turn-end.d"
|
|
596
|
+
printf 'fm.abcdefabcdef\n' > "$dir/home/state/rl9.grok-turnend-token"
|
|
597
|
+
auth="$dir/grokhome/hooks/fm-turn-end.d/fm.abcdefabcdef"
|
|
598
|
+
printf '%s\n' "$dir/home/state/rl9.turn-ended" > "$auth"
|
|
599
|
+
printf 'grok' > "$dir/fake/command"
|
|
600
|
+
printf 'grok' > "$dir/fake/becomes"
|
|
601
|
+
run_control "$dir" rl9 relaunch --note "restart on the same runtime" >/dev/null
|
|
602
|
+
[ ! -e "$auth" ] \
|
|
603
|
+
|| fail "the previous incarnation's turn-end registry entry must not outlive it"
|
|
604
|
+
pass "fm-control relaunch: the retired incarnation's global turn-end token is revoked"
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
test_wiring_removal_failure_refuses_before_replacement_arm() {
|
|
608
|
+
local dir hook out rc real_rm
|
|
609
|
+
dir=$(new_case wiring-failure rl29)
|
|
610
|
+
add_ship_task "$dir" rl29 claude
|
|
611
|
+
hook="$dir/wt/.claude/settings.local.json"
|
|
612
|
+
mkdir -p "${hook%/*}"
|
|
613
|
+
printf '{}\n' > "$hook"
|
|
614
|
+
real_rm=$(command -v rm)
|
|
615
|
+
make_rm_failure_stub "$dir"
|
|
616
|
+
out=$(FM_REAL_RM="$real_rm" FM_FAKE_RM_FAIL_PATH="$hook" \
|
|
617
|
+
run_control "$dir" rl29 relaunch --note "retry after wiring cleanup"); rc=$?
|
|
618
|
+
expect_code 1 "$rc" "an undeletable prior hook must fail closed"$'\n'"$out"
|
|
619
|
+
assert_contains "$out" "could not retire claude wiring" \
|
|
620
|
+
"the failure should identify prior wiring cleanup"
|
|
621
|
+
[ -e "$hook" ] || fail "the fixture should retain the undeletable prior hook"
|
|
622
|
+
assert_no_grep "encode launch-brief" "$dir/fake/literal" \
|
|
623
|
+
"replacement launch must not be armed after wiring cleanup fails"
|
|
624
|
+
[ "$(journal_field "$dir" rl29 phase)" = failed:launching ] \
|
|
625
|
+
|| fail "the transaction should record the partial launch failure"
|
|
626
|
+
[ "$(journal_field "$dir" rl29 rollback)" = prior-record-kept ] \
|
|
627
|
+
|| fail "unpublished rollback should retain the live durable record"
|
|
628
|
+
pass "fm-control relaunch: wiring cleanup failure refuses replacement arming"
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
test_turnend_auth_paths_are_owned_by_the_control_adapter() {
|
|
632
|
+
local dir state grok_path kimi_path token_path
|
|
633
|
+
dir=$(fm_test_tmproot fm-control-auth)
|
|
634
|
+
state="$dir/state"
|
|
635
|
+
mkdir -p "$state"
|
|
636
|
+
printf 'fm.111111111111\n' > "$state/x.grok-turnend-token"
|
|
637
|
+
printf 'fm.222222222222\n' > "$state/x.kimi-turnend-token"
|
|
638
|
+
token_path=$(fm_control_harness_turnend_token_path grok "$state" x)
|
|
639
|
+
[ "$token_path" = "$state/x.grok-turnend-token" ] \
|
|
640
|
+
|| fail "the grok token path should be computed without reading it"
|
|
641
|
+
grok_path=$(GROK_HOME="$dir/gh" fm_control_harness_turnend_auth_path grok fm.111111111111)
|
|
642
|
+
[ "$grok_path" = "$dir/gh/hooks/fm-turn-end.d/fm.111111111111" ] \
|
|
643
|
+
|| fail "grok's registry path should resolve under GROK_HOME, got '$grok_path'"
|
|
644
|
+
kimi_path=$(HOME="$dir/kh" fm_control_harness_turnend_auth_path kimi fm.222222222222)
|
|
645
|
+
[ "$kimi_path" = "$dir/kh/.kimi-code/fm-turn-end.d/fm.222222222222" ] \
|
|
646
|
+
|| fail "kimi's registry path should resolve under the home store, got '$kimi_path'"
|
|
647
|
+
grok_path=$(GROK_HOME="$dir/gh" fm_control_harness_turnend_auth_path grok 'not a token/../..')
|
|
648
|
+
[ -z "$grok_path" ] || fail "a malformed token must resolve to no path, got '$grok_path'"
|
|
649
|
+
pass "fm-control-lib: one owner resolves each harness's turn-end registry entry, and refuses a malformed token"
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
test_secondmate_relaunch_picks_up_the_configured_harness_pin() {
|
|
653
|
+
local dir home out rc
|
|
654
|
+
dir=$(new_case smpin sm3)
|
|
655
|
+
home="$dir/home"
|
|
656
|
+
mkdir -p "$home/config"
|
|
657
|
+
printf 'codex some-model high\n' > "$home/config/secondmate-harness"
|
|
658
|
+
mkdir -p "$home/data/sm3"
|
|
659
|
+
printf '# secondmate brief\n' > "$home/data/sm3/brief.md"
|
|
660
|
+
fm_git_worktree "$dir/proj" "$dir/smhome" sm-branch
|
|
661
|
+
mkdir -p "$dir/smhome/state" "$dir/smhome/data" "$dir/smhome/bin"
|
|
662
|
+
printf 'sm3\n' > "$dir/smhome/.fm-secondmate-home"
|
|
663
|
+
printf '# agents\n' > "$dir/smhome/AGENTS.md"
|
|
664
|
+
{
|
|
665
|
+
echo "window=fmses:fm-sm3"
|
|
666
|
+
echo "endpoint_task_id=sm3"
|
|
667
|
+
echo "worktree=$dir/smhome"
|
|
668
|
+
echo "project=$dir/smhome"
|
|
669
|
+
echo "harness=claude"
|
|
670
|
+
echo "kind=secondmate"
|
|
671
|
+
echo "mode=secondmate"
|
|
672
|
+
echo "yolo=off"
|
|
673
|
+
echo "model=default"
|
|
674
|
+
echo "effort=default"
|
|
675
|
+
echo "home=$dir/smhome"
|
|
676
|
+
} > "$home/state/sm3.meta"
|
|
677
|
+
printf '%s\n' "fm-sm3" > "$dir/fake/windows"
|
|
678
|
+
printf '%s' "$dir/smhome" > "$dir/fake/cwd"
|
|
679
|
+
printf 'codex' > "$dir/fake/becomes"
|
|
680
|
+
out=$(run_control "$dir" sm3 relaunch); rc=$?
|
|
681
|
+
expect_code 0 "$rc" "a configured secondmate harness should relaunch"$'\n'"$out"
|
|
682
|
+
[ "$(journal_field "$dir" sm3 to_harness)" = codex ] \
|
|
683
|
+
|| fail "a secondmate relaunch should pick up the configured harness pin, got '$(journal_field "$dir" sm3 to_harness)'"
|
|
684
|
+
[ "$(journal_field "$dir" sm3 to_model)" = some-model ] \
|
|
685
|
+
|| fail "the configured model token should come with the pin"
|
|
686
|
+
[ "$(journal_field "$dir" sm3 to_effort)" = high ] \
|
|
687
|
+
|| fail "the configured effort token should come with the pin"
|
|
688
|
+
assert_not_contains "$out" "not a verified harness" "codex is a verified harness"
|
|
689
|
+
pass "fm-control relaunch: a secondmate relaunch re-resolves its durable configured harness pin"
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
test_secondmate_relaunch_ignores_invalid_configured_effort_before_stop() {
|
|
693
|
+
local dir home out rc
|
|
694
|
+
dir=$(new_case invalid-effort sm6)
|
|
695
|
+
home="$dir/home"
|
|
696
|
+
mkdir -p "$home/config" "$home/data/sm6"
|
|
697
|
+
printf 'codex some-model impossible\n' > "$home/config/secondmate-harness"
|
|
698
|
+
printf '# secondmate brief\n' > "$home/data/sm6/brief.md"
|
|
699
|
+
fm_git_worktree "$dir/proj" "$dir/smhome" sm-branch
|
|
700
|
+
mkdir -p "$dir/smhome/state" "$dir/smhome/data" "$dir/smhome/bin"
|
|
701
|
+
printf 'sm6\n' > "$dir/smhome/.fm-secondmate-home"
|
|
702
|
+
printf '# agents\n' > "$dir/smhome/AGENTS.md"
|
|
703
|
+
{
|
|
704
|
+
echo "window=fmses:fm-sm6"
|
|
705
|
+
echo "endpoint_task_id=sm6"
|
|
706
|
+
echo "worktree=$dir/smhome"
|
|
707
|
+
echo "project=$dir/smhome"
|
|
708
|
+
echo "harness=claude"
|
|
709
|
+
echo "kind=secondmate"
|
|
710
|
+
echo "mode=secondmate"
|
|
711
|
+
echo "yolo=off"
|
|
712
|
+
echo "model=default"
|
|
713
|
+
echo "effort=default"
|
|
714
|
+
echo "home=$dir/smhome"
|
|
715
|
+
} > "$home/state/sm6.meta"
|
|
716
|
+
printf '%s\n' "fm-sm6" > "$dir/fake/windows"
|
|
717
|
+
printf '%s' "$dir/smhome" > "$dir/fake/cwd"
|
|
718
|
+
printf 'codex' > "$dir/fake/becomes"
|
|
719
|
+
out=$(run_control "$dir" sm6 relaunch); rc=$?
|
|
720
|
+
expect_code 0 "$rc" "an invalid configured effort should be ignored before stop"$'\n'"$out"
|
|
721
|
+
assert_contains "$out" "effort token 'impossible'" \
|
|
722
|
+
"relaunch should surface the same warning as a normal secondmate spawn"
|
|
723
|
+
[ "$(journal_field "$dir" sm6 to_effort)" = default ] \
|
|
724
|
+
|| fail "invalid configured effort should normalize to default"
|
|
725
|
+
pass "fm-control relaunch: invalid configured effort is ignored before stop"
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
# muse is a verified adapter, but only for crewmates and scouts: it has no
|
|
729
|
+
# primary supervision protocol, so bin/fm-spawn.sh refuses it for a secondmate.
|
|
730
|
+
# That refusal alone is not enough here, because the launch owner is reached
|
|
731
|
+
# only AFTER the running agent has been stopped - a secondmate would be left
|
|
732
|
+
# with no agent at all. The control plane asks the same capability question
|
|
733
|
+
# before it touches anything, so the refusal lands while the agent is still up.
|
|
734
|
+
test_secondmate_relaunch_onto_a_crewmate_only_adapter_refuses_before_stop() {
|
|
735
|
+
local dir home out rc
|
|
736
|
+
dir=$(new_case smkind sm7)
|
|
737
|
+
home="$dir/home"
|
|
738
|
+
mkdir -p "$home/config" "$home/data/sm7"
|
|
739
|
+
printf '# secondmate brief\n' > "$home/data/sm7/brief.md"
|
|
740
|
+
fm_git_worktree "$dir/proj" "$dir/smhome" sm-branch
|
|
741
|
+
mkdir -p "$dir/smhome/state" "$dir/smhome/data" "$dir/smhome/bin"
|
|
742
|
+
printf 'sm7\n' > "$dir/smhome/.fm-secondmate-home"
|
|
743
|
+
printf '# agents\n' > "$dir/smhome/AGENTS.md"
|
|
744
|
+
{
|
|
745
|
+
echo "window=fmses:fm-sm7"
|
|
746
|
+
echo "endpoint_task_id=sm7"
|
|
747
|
+
echo "worktree=$dir/smhome"
|
|
748
|
+
echo "project=$dir/smhome"
|
|
749
|
+
echo "harness=claude"
|
|
750
|
+
echo "kind=secondmate"
|
|
751
|
+
echo "mode=secondmate"
|
|
752
|
+
echo "yolo=off"
|
|
753
|
+
echo "model=default"
|
|
754
|
+
echo "effort=default"
|
|
755
|
+
echo "home=$dir/smhome"
|
|
756
|
+
} > "$home/state/sm7.meta"
|
|
757
|
+
printf '%s\n' "fm-sm7" > "$dir/fake/windows"
|
|
758
|
+
printf '%s' "$dir/smhome" > "$dir/fake/cwd"
|
|
759
|
+
out=$(run_control "$dir" sm7 relaunch --harness muse); rc=$?
|
|
760
|
+
expect_code 1 "$rc" "a crewmate-only adapter should refuse a secondmate relaunch"
|
|
761
|
+
assert_contains "$out" "not verified to run a secondmate task" \
|
|
762
|
+
"the refusal should name the kind the adapter cannot run"
|
|
763
|
+
[ "$(cat "$dir/fake/command")" = claude ] \
|
|
764
|
+
|| fail "the refusal must land before the running agent is stopped"
|
|
765
|
+
[ "$(meta_field "$dir" sm7 harness)" = claude ] \
|
|
766
|
+
|| fail "a refused relaunch must leave the durable record on the recorded harness"
|
|
767
|
+
pass "fm-control relaunch: an adapter unverified for this task kind refuses before the agent is stopped"
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
test_explicit_secondmate_harness_ignores_configured_profile_axes() {
|
|
771
|
+
local dir home out rc
|
|
772
|
+
dir=$(new_case smexplicit sm4)
|
|
773
|
+
home="$dir/home"
|
|
774
|
+
mkdir -p "$home/config"
|
|
775
|
+
printf 'claude opus high\n' > "$home/config/secondmate-harness"
|
|
776
|
+
mkdir -p "$home/data/sm4"
|
|
777
|
+
printf '# secondmate brief\n' > "$home/data/sm4/brief.md"
|
|
778
|
+
fm_git_worktree "$dir/proj" "$dir/smhome" sm-branch
|
|
779
|
+
mkdir -p "$dir/smhome/state" "$dir/smhome/data" "$dir/smhome/bin"
|
|
780
|
+
printf 'sm4\n' > "$dir/smhome/.fm-secondmate-home"
|
|
781
|
+
printf '# agents\n' > "$dir/smhome/AGENTS.md"
|
|
782
|
+
{
|
|
783
|
+
echo "window=fmses:fm-sm4"
|
|
784
|
+
echo "endpoint_task_id=sm4"
|
|
785
|
+
echo "worktree=$dir/smhome"
|
|
786
|
+
echo "project=$dir/smhome"
|
|
787
|
+
echo "harness=claude"
|
|
788
|
+
echo "kind=secondmate"
|
|
789
|
+
echo "mode=secondmate"
|
|
790
|
+
echo "yolo=off"
|
|
791
|
+
echo "model=opus"
|
|
792
|
+
echo "effort=high"
|
|
793
|
+
echo "home=$dir/smhome"
|
|
794
|
+
} > "$home/state/sm4.meta"
|
|
795
|
+
printf '%s\n' "fm-sm4" > "$dir/fake/windows"
|
|
796
|
+
printf '%s' "$dir/smhome" > "$dir/fake/cwd"
|
|
797
|
+
printf 'codex' > "$dir/fake/becomes"
|
|
798
|
+
out=$(run_control "$dir" sm4 relaunch --harness codex); rc=$?
|
|
799
|
+
expect_code 0 "$rc" "an explicit secondmate harness should relaunch"$'\n'"$out"
|
|
800
|
+
[ "$(meta_field "$dir" sm4 model)" = default ] \
|
|
801
|
+
|| fail "an explicit secondmate harness must not inherit the configured model"
|
|
802
|
+
[ "$(meta_field "$dir" sm4 effort)" = default ] \
|
|
803
|
+
|| fail "an explicit secondmate harness must not inherit the configured effort"
|
|
804
|
+
pass "fm-control relaunch: explicit secondmate harness resets unnamed profile axes"
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
test_ship_relaunch_ignores_the_crew_harness_config() {
|
|
808
|
+
local dir out
|
|
809
|
+
dir=$(new_case crewcfg rl20)
|
|
810
|
+
add_ship_task "$dir" rl20 claude
|
|
811
|
+
mkdir -p "$dir/home/config"
|
|
812
|
+
printf 'codex\n' > "$dir/home/config/crew-harness"
|
|
813
|
+
out=$(run_control "$dir" rl20 relaunch --note "same worker, same runtime")
|
|
814
|
+
assert_contains "$out" "harness=claude from=claude" \
|
|
815
|
+
"a ship relaunch must keep its recorded harness rather than re-reading crew config"
|
|
816
|
+
[ "$(meta_field "$dir" rl20 harness)" = claude ] \
|
|
817
|
+
|| fail "a ship relaunch must not silently move onto the configured crew harness"
|
|
818
|
+
pass "fm-control relaunch: a ship task keeps its recorded harness instead of re-reading crew config"
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
test_spawn_relaunch_without_a_harness_reuses_the_recorded_one() {
|
|
822
|
+
local dir out
|
|
823
|
+
dir=$(new_case spawnharness rl21)
|
|
824
|
+
add_ship_task "$dir" rl21 claude
|
|
825
|
+
mkdir -p "$dir/home/config"
|
|
826
|
+
printf 'codex\n' > "$dir/home/config/crew-harness"
|
|
827
|
+
printf 'zsh' > "$dir/fake/command"
|
|
828
|
+
out=$(run_spawn "$dir" rl21 --relaunch)
|
|
829
|
+
[ "$(meta_field "$dir" rl21 harness)" = claude ] \
|
|
830
|
+
|| fail "fm-spawn --relaunch without --harness must reuse the recorded harness, got '$(meta_field "$dir" rl21 harness)'"
|
|
831
|
+
assert_contains "$out" "spawned rl21 harness=claude" "the launch should report the recorded harness"
|
|
832
|
+
pass "fm-spawn --relaunch: with no explicit harness it reuses the task's recorded one, never the crew default"
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
# fm-spawn arms per-task wiring on harness PREFIXES, because a task launched
|
|
836
|
+
# from a raw command records that command's basename rather than the exact
|
|
837
|
+
# adapter name. Retirement must resolve the same way, or a task recorded as
|
|
838
|
+
# `grok-2` would have its turn-end token and hook pointer armed and never
|
|
839
|
+
# retired - leaving a registry entry that outlives the agent that owned it.
|
|
840
|
+
test_prefixed_prior_harness_wiring_is_still_retired() {
|
|
841
|
+
local dir auth
|
|
842
|
+
dir=$(new_case prefixwiring rl30)
|
|
843
|
+
add_ship_task "$dir" rl30 grok-2
|
|
844
|
+
mkdir -p "$dir/grokhome/hooks/fm-turn-end.d"
|
|
845
|
+
printf 'fm.abcdefabcdef\n' > "$dir/home/state/rl30.grok-turnend-token"
|
|
846
|
+
auth="$dir/grokhome/hooks/fm-turn-end.d/fm.abcdefabcdef"
|
|
847
|
+
printf '%s\n' "$dir/home/state/rl30.turn-ended" > "$auth"
|
|
848
|
+
printf 'token=fm.abcdefabcdef\n' > "$dir/wt/.fm-grok-turnend"
|
|
849
|
+
printf 'zsh' > "$dir/fake/command"
|
|
850
|
+
run_spawn "$dir" rl30 --relaunch --harness claude >/dev/null
|
|
851
|
+
[ ! -e "$auth" ] \
|
|
852
|
+
|| fail "a prefixed prior harness must still have its turn-end registry entry revoked"
|
|
853
|
+
[ ! -e "$dir/home/state/rl30.grok-turnend-token" ] \
|
|
854
|
+
|| fail "a prefixed prior harness must still have its private token retired"
|
|
855
|
+
[ ! -e "$dir/wt/.fm-grok-turnend" ] \
|
|
856
|
+
|| fail "a prefixed prior harness must still have its worktree hook pointer removed"
|
|
857
|
+
pass "fm-spawn --relaunch: wiring armed under a prefixed harness name is still retired"
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
# muse installs no hook; its busy source is its own session event log, bound to
|
|
861
|
+
# the pane by two firstmate-owned sidecars. Relaunching AWAY from muse must
|
|
862
|
+
# retire that binding, or a retired incarnation's session pin outlives the agent
|
|
863
|
+
# that produced it.
|
|
864
|
+
test_muse_session_binding_is_retired_on_a_harness_switch() {
|
|
865
|
+
local dir
|
|
866
|
+
dir=$(new_case musewiring rl31)
|
|
867
|
+
add_ship_task "$dir" rl31 muse
|
|
868
|
+
printf 'sessions_root=/nonexistent\nworkspace_root=%s\nbinding_id=1.2.3\n' "$dir/wt" \
|
|
869
|
+
> "$dir/home/state/rl31.muse-session"
|
|
870
|
+
printf '/nonexistent/session.jsonl\n' > "$dir/home/state/rl31.muse-session-current"
|
|
871
|
+
printf 'zsh' > "$dir/fake/command"
|
|
872
|
+
run_spawn "$dir" rl31 --relaunch --harness claude >/dev/null
|
|
873
|
+
[ ! -e "$dir/home/state/rl31.muse-session" ] \
|
|
874
|
+
|| fail "the retired muse incarnation's session binding must not outlive it"
|
|
875
|
+
[ ! -e "$dir/home/state/rl31.muse-session-current" ] \
|
|
876
|
+
|| fail "the retired muse incarnation's resolved session pin must not outlive it"
|
|
877
|
+
pass "fm-spawn --relaunch: switching away from muse retires its session binding"
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
test_cursor_session_binding_is_retired_on_a_harness_switch() {
|
|
881
|
+
local dir
|
|
882
|
+
dir=$(new_case cursorwiring rl35)
|
|
883
|
+
add_ship_task "$dir" rl35 cursor
|
|
884
|
+
printf 'workspace=%s\nprior_conversation=old-conversation\n' "$dir/wt" \
|
|
885
|
+
> "$dir/home/state/rl35.cursor-session"
|
|
886
|
+
printf 'zsh' > "$dir/fake/command"
|
|
887
|
+
run_spawn "$dir" rl35 --relaunch --harness claude >/dev/null
|
|
888
|
+
[ ! -e "$dir/home/state/rl35.cursor-session" ] \
|
|
889
|
+
|| fail "the retired cursor incarnation's session binding must not outlive it"
|
|
890
|
+
pass "fm-spawn --relaunch: switching away from cursor retires its session binding"
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
# --- 3 and 4. refusals before the agent is touched ---------------------------
|
|
894
|
+
|
|
895
|
+
test_missing_worktree_refuses_before_stopping_anything() {
|
|
896
|
+
local dir out rc
|
|
897
|
+
dir=$(new_case nowt rl10)
|
|
898
|
+
add_ship_task "$dir" rl10 claude
|
|
899
|
+
rm -rf "$dir/wt"
|
|
900
|
+
out=$(run_control "$dir" rl10 relaunch --note "x"); rc=$?
|
|
901
|
+
expect_code 1 "$rc" "a missing worktree should refuse"
|
|
902
|
+
assert_contains "$out" "recorded worktree" "the refusal should name the missing local copy"
|
|
903
|
+
[ "$(cat "$dir/fake/command")" = claude ] || fail "a refused relaunch must not stop the agent"
|
|
904
|
+
[ -z "$(cat "$dir/fake/literal")" ] || fail "a refused relaunch must send nothing"
|
|
905
|
+
pass "fm-control relaunch: an unaccountable local copy refuses before the agent is touched"
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
test_missing_instructions_refuse_before_stopping_anything() {
|
|
909
|
+
local dir out rc
|
|
910
|
+
dir=$(new_case nobrief rl11)
|
|
911
|
+
add_ship_task "$dir" rl11 claude
|
|
912
|
+
rm -f "$dir/home/data/rl11/brief.md"
|
|
913
|
+
out=$(run_control "$dir" rl11 relaunch --note "x"); rc=$?
|
|
914
|
+
expect_code 1 "$rc" "missing instructions should refuse"
|
|
915
|
+
assert_contains "$out" "no instructions" "the refusal should name the missing instructions"
|
|
916
|
+
[ "$(cat "$dir/fake/command")" = claude ] || fail "a refused relaunch must not stop the agent"
|
|
917
|
+
pass "fm-control relaunch: a worker with nothing to work from is never launched"
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
test_checkpoint_refusal_leaves_the_record_byte_identical() {
|
|
921
|
+
local dir before after
|
|
922
|
+
dir=$(new_case bytes rl12)
|
|
923
|
+
add_ship_task "$dir" rl12 claude
|
|
924
|
+
before=$(cat "$dir/home/state/rl12.meta")
|
|
925
|
+
rm -rf "$dir/wt/.git"
|
|
926
|
+
run_control "$dir" rl12 relaunch --note "x" >/dev/null 2>&1
|
|
927
|
+
after=$(cat "$dir/home/state/rl12.meta")
|
|
928
|
+
[ "$before" = "$after" ] || fail "a refused relaunch must leave the durable record byte-identical"
|
|
929
|
+
pass "fm-control relaunch: a refusal before the agent is stopped leaves the durable record untouched"
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
test_checkpoint_refuses_uninspectable_head_and_status() {
|
|
933
|
+
local dir out rc real_git
|
|
934
|
+
real_git=$(command -v git)
|
|
935
|
+
|
|
936
|
+
dir=$(new_case badhead rl22)
|
|
937
|
+
add_ship_task "$dir" rl22 claude
|
|
938
|
+
make_git_failure_stub "$dir"
|
|
939
|
+
out=$(FM_REAL_GIT="$real_git" FM_FAKE_GIT_FAILURE=head \
|
|
940
|
+
run_control "$dir" rl22 relaunch --note "x"); rc=$?
|
|
941
|
+
expect_code 1 "$rc" "an uninspectable HEAD should refuse"
|
|
942
|
+
assert_contains "$out" "HEAD cannot be inspected" "the refusal should name the failed HEAD proof"
|
|
943
|
+
[ "$(cat "$dir/fake/command")" = claude ] || fail "HEAD inspection failure must not stop the agent"
|
|
944
|
+
|
|
945
|
+
dir=$(new_case badstatus rl23)
|
|
946
|
+
add_ship_task "$dir" rl23 claude
|
|
947
|
+
make_git_failure_stub "$dir"
|
|
948
|
+
out=$(FM_REAL_GIT="$real_git" FM_FAKE_GIT_FAILURE=status \
|
|
949
|
+
run_control "$dir" rl23 relaunch --note "x"); rc=$?
|
|
950
|
+
expect_code 1 "$rc" "an uninspectable worktree status should refuse"
|
|
951
|
+
assert_contains "$out" "status cannot be inspected" "the refusal should name the failed dirty-state proof"
|
|
952
|
+
[ "$(cat "$dir/fake/command")" = claude ] || fail "status inspection failure must not stop the agent"
|
|
953
|
+
pass "fm-control relaunch: checkpoint inspection failures refuse before stopping"
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
# --- 5. failure after the agent is stopped -----------------------------------
|
|
957
|
+
|
|
958
|
+
test_launch_failure_keeps_the_prior_record_and_reports_it() {
|
|
959
|
+
local dir out rc before
|
|
960
|
+
dir=$(new_case rollback rl13)
|
|
961
|
+
add_ship_task "$dir" rl13 claude
|
|
962
|
+
before=$(cat "$dir/home/state/rl13.meta")
|
|
963
|
+
# The endpoint's shell is not in the recorded worktree, so the launch owner
|
|
964
|
+
# refuses AFTER the previous agent has already been stopped.
|
|
965
|
+
printf '%s' "$dir/proj" > "$dir/fake/cwd"
|
|
966
|
+
out=$(run_control "$dir" rl13 relaunch --harness codex --note "carry this forward"); rc=$?
|
|
967
|
+
expect_code 1 "$rc" "a failed launch should fail closed"$'\n'"$out"
|
|
968
|
+
assert_contains "$out" "no agent is running" "the failure should say no agent is running"
|
|
969
|
+
assert_contains "$out" "$dir/wt" "the failure should say where the work is preserved"
|
|
970
|
+
[ "$(cat "$dir/home/state/rl13.meta")" = "$before" ] \
|
|
971
|
+
|| fail "a failed launch must keep the prior durable record"
|
|
972
|
+
[ "$(journal_field "$dir" rl13 phase)" = "failed:launching" ] \
|
|
973
|
+
|| fail "the journal should record the failed phase, got '$(journal_field "$dir" rl13 phase)'"
|
|
974
|
+
[ "$(journal_field "$dir" rl13 rollback)" = "prior-record-kept" ] \
|
|
975
|
+
|| fail "the journal should record what the rollback did"
|
|
976
|
+
assert_grep "carry this forward" "$dir/home/data/rl13/brief.md" \
|
|
977
|
+
"the progress note must survive so a later recovery still has it"
|
|
978
|
+
pass "fm-control relaunch: a launch failure after the stop keeps the prior record and reports the real state"
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
test_prepublication_failure_keeps_concurrent_durable_metadata() {
|
|
982
|
+
local dir control_pid link_out rc i=0
|
|
983
|
+
dir=$(new_case rollback-race rl30)
|
|
984
|
+
add_ship_task "$dir" rl30 claude
|
|
985
|
+
printf '%s' "$dir/proj" > "$dir/fake/cwd"
|
|
986
|
+
FM_FAKE_CWD_RACE_READY="$dir/cwd-race-ready" \
|
|
987
|
+
run_control "$dir" rl30 relaunch --harness codex --note "preserve concurrent metadata" \
|
|
988
|
+
> "$dir/control.out" &
|
|
989
|
+
control_pid=$!
|
|
990
|
+
while [ ! -e "$dir/cwd-race-ready" ] && [ "$i" -lt 200 ]; do
|
|
991
|
+
/bin/sleep 0.01
|
|
992
|
+
i=$((i + 1))
|
|
993
|
+
done
|
|
994
|
+
[ -e "$dir/cwd-race-ready" ] || {
|
|
995
|
+
kill "$control_pid" 2>/dev/null || true
|
|
996
|
+
wait "$control_pid" 2>/dev/null || true
|
|
997
|
+
fail "relaunch did not reach its pre-publication endpoint check"
|
|
998
|
+
}
|
|
999
|
+
link_out=$(env PATH="$dir/fakebin:$PATH" FM_HOME="$dir/home" FM_ROOT_OVERRIDE="$ROOT" \
|
|
1000
|
+
"$X_LINK" rl30 request-30 --carry-count 2 --carry-ts 1700000000 \
|
|
1001
|
+
--carry-platform x --carry-max 280 2>&1); rc=$?
|
|
1002
|
+
expect_code 0 "$rc" "concurrent durable metadata publication should succeed"$'\n'"$link_out"
|
|
1003
|
+
wait "$control_pid"; rc=$?
|
|
1004
|
+
expect_code 1 "$rc" "the staged pre-publication launch failure should fail closed"
|
|
1005
|
+
[ "$(meta_field "$dir" rl30 x_request)" = request-30 ] \
|
|
1006
|
+
|| fail "rollback erased the concurrent X request"
|
|
1007
|
+
[ "$(meta_field "$dir" rl30 x_followups)" = 2 ] \
|
|
1008
|
+
|| fail "rollback erased the concurrent follow-up count"
|
|
1009
|
+
[ "$(journal_field "$dir" rl30 rollback)" = prior-record-kept ] \
|
|
1010
|
+
|| fail "pre-publication rollback should leave the live record untouched"
|
|
1011
|
+
pass "fm-control relaunch: unpublished rollback keeps concurrent durable metadata"
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
test_post_publication_launch_failure_keeps_the_new_record() {
|
|
1015
|
+
local dir out rc
|
|
1016
|
+
dir=$(new_case published rl24)
|
|
1017
|
+
add_ship_task "$dir" rl24 claude
|
|
1018
|
+
printf 'codex' > "$dir/fake/becomes"
|
|
1019
|
+
out=$(FM_FAKE_LAUNCH_TRANSPORT_FAIL_AFTER_START=1 \
|
|
1020
|
+
run_control "$dir" rl24 relaunch --harness codex --note "keep the published record"); rc=$?
|
|
1021
|
+
expect_code 1 "$rc" "a post-publication launch failure should fail closed"$'\n'"$out"
|
|
1022
|
+
[ "$(meta_field "$dir" rl24 harness)" = codex ] \
|
|
1023
|
+
|| fail "a published replacement record must not be rewritten to the prior harness"
|
|
1024
|
+
[ -n "$(meta_field "$dir" rl24 control_relaunch_tx)" ] \
|
|
1025
|
+
|| fail "the published replacement record should identify its relaunch transaction"
|
|
1026
|
+
[ "$(journal_field "$dir" rl24 rollback)" = none-new-record-kept ] \
|
|
1027
|
+
|| fail "the journal should record that the published replacement record was kept"
|
|
1028
|
+
pass "fm-control relaunch: post-publication failure keeps the new durable record"
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
test_stop_transport_failure_reconciles_a_dead_agent() {
|
|
1032
|
+
local dir out rc
|
|
1033
|
+
dir=$(new_case stopfail rl25)
|
|
1034
|
+
add_ship_task "$dir" rl25 claude
|
|
1035
|
+
out=$(FM_FAKE_EXIT_TRANSPORT_FAIL_AFTER_STOP=1 \
|
|
1036
|
+
run_control "$dir" rl25 relaunch --note "preserve this after stop"); rc=$?
|
|
1037
|
+
expect_code 1 "$rc" "a stop transport failure should fail closed"$'\n'"$out"
|
|
1038
|
+
[ "$(cat "$dir/fake/command")" = zsh ] || fail "the fixture should stop the old agent before reporting transport failure"
|
|
1039
|
+
[ "$(journal_field "$dir" rl25 phase)" = failed:stopping ] \
|
|
1040
|
+
|| fail "the journal should retain the pre-stop phase on a partial stop"
|
|
1041
|
+
[ "$(journal_field "$dir" rl25 rollback)" = prior-record-kept-agent-dead ] \
|
|
1042
|
+
|| fail "rollback should reconcile the observed dead agent"
|
|
1043
|
+
assert_contains "$out" "no agent is running" "the failure should report the reconciled dead state"
|
|
1044
|
+
assert_grep "preserve this after stop" "$dir/home/data/rl25/brief.md" \
|
|
1045
|
+
"the progress note should survive once the old agent has stopped"
|
|
1046
|
+
pass "fm-control relaunch: partial stop reconciles actual agent state"
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
test_complete_journal_failure_rolls_back_from_durable_phase() {
|
|
1050
|
+
local dir out rc real_mv
|
|
1051
|
+
dir=$(new_case completejournal rl27)
|
|
1052
|
+
add_ship_task "$dir" rl27 claude
|
|
1053
|
+
printf 'codex' > "$dir/fake/becomes"
|
|
1054
|
+
real_mv=$(command -v mv)
|
|
1055
|
+
make_mv_failure_stub "$dir"
|
|
1056
|
+
out=$(FM_REAL_MV="$real_mv" FM_FAKE_COMPLETE_JOURNAL_MV_FAIL=1 \
|
|
1057
|
+
run_control "$dir" rl27 relaunch --harness codex --note "keep durable phase honest"); rc=$?
|
|
1058
|
+
expect_code 1 "$rc" "a failed complete journal replacement should fail closed"$'\n'"$out"
|
|
1059
|
+
[ "$(journal_field "$dir" rl27 phase)" = failed:launching ] \
|
|
1060
|
+
|| fail "rollback should start from the last durable launching phase"
|
|
1061
|
+
[ "$(journal_field "$dir" rl27 rollback)" = none-new-agent-confirmed ] \
|
|
1062
|
+
|| fail "rollback should retain the confirmed-running replacement"
|
|
1063
|
+
[ "$(meta_field "$dir" rl27 harness)" = codex ] \
|
|
1064
|
+
|| fail "journal failure must not rewrite the published replacement record"
|
|
1065
|
+
assert_contains "$out" "replacement is running" \
|
|
1066
|
+
"journal failure should report the confirmed-running replacement"
|
|
1067
|
+
assert_not_contains "$out" "no running agent could be confirmed" \
|
|
1068
|
+
"journal failure should not contradict the confirmed agent state"
|
|
1069
|
+
pass "fm-control relaunch: failed journal replacement preserves durable phase"
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
test_prepublication_abort_retires_replacement_wiring_and_busy_state() {
|
|
1073
|
+
local dir out rc real_mv meta
|
|
1074
|
+
dir=$(new_case prepublishcleanup rl28)
|
|
1075
|
+
add_ship_task "$dir" rl28 claude
|
|
1076
|
+
meta="$dir/home/state/rl28.meta"
|
|
1077
|
+
real_mv=$(command -v mv)
|
|
1078
|
+
make_mv_failure_stub "$dir"
|
|
1079
|
+
out=$(FM_REAL_MV="$real_mv" FM_FAKE_META_PUBLISH_MV_FAIL="$meta" \
|
|
1080
|
+
run_control "$dir" rl28 relaunch --note "clean partial replacement state"); rc=$?
|
|
1081
|
+
expect_code 1 "$rc" "a failed metadata publication should fail closed"$'\n'"$out"
|
|
1082
|
+
[ "$(meta_field "$dir" rl28 harness)" = claude ] \
|
|
1083
|
+
|| fail "a failed publication should retain the prior durable record"
|
|
1084
|
+
[ ! -e "$dir/wt/.claude/settings.local.json" ] \
|
|
1085
|
+
|| fail "an aborted replacement should remove its harness wiring"
|
|
1086
|
+
[ ! -e "$dir/home/state/rl28.busy-gen" ] \
|
|
1087
|
+
|| fail "an aborted replacement should retire its busy generation"
|
|
1088
|
+
[ ! -e "$dir/home/state/rl28.busy-state" ] \
|
|
1089
|
+
|| fail "an aborted replacement should remove its seeded busy record"
|
|
1090
|
+
[ "$(journal_field "$dir" rl28 rollback)" = prior-record-kept ] \
|
|
1091
|
+
|| fail "the journal should record the unpublished replacement rollback"
|
|
1092
|
+
pass "fm-spawn relaunch: prepublication abort removes replacement state"
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
test_journal_records_the_checkpoint_it_proved() {
|
|
1096
|
+
local dir head
|
|
1097
|
+
dir=$(new_case journal rl14)
|
|
1098
|
+
add_ship_task "$dir" rl14 claude
|
|
1099
|
+
printf 'scratch\n' > "$dir/wt/uncommitted.txt"
|
|
1100
|
+
head=$(git -C "$dir/wt" rev-parse HEAD)
|
|
1101
|
+
run_control "$dir" rl14 relaunch --note "keeping the scratch file" >/dev/null
|
|
1102
|
+
[ "$(journal_field "$dir" rl14 worktree_head)" = "$head" ] \
|
|
1103
|
+
|| fail "the checkpoint should record the head it preserved"
|
|
1104
|
+
[ "$(journal_field "$dir" rl14 worktree_dirty)" = yes ] \
|
|
1105
|
+
|| fail "the checkpoint should record that uncommitted work was present"
|
|
1106
|
+
[ -f "$dir/wt/uncommitted.txt" ] || fail "uncommitted work must survive a relaunch"
|
|
1107
|
+
pass "fm-control relaunch: the checkpoint records the exact unlanded work it preserved"
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
# --- secondmate child-work safety -------------------------------------------
|
|
1111
|
+
|
|
1112
|
+
test_secondmate_relaunch_checkpoints_child_work_and_spares_the_charter() {
|
|
1113
|
+
local dir home out rc
|
|
1114
|
+
dir=$(new_case sm sm1)
|
|
1115
|
+
home="$dir/home"
|
|
1116
|
+
mkdir -p "$home/config"
|
|
1117
|
+
printf 'claude\n' > "$home/config/secondmate-harness"
|
|
1118
|
+
fm_git_worktree "$dir/proj" "$dir/smhome" sm-branch
|
|
1119
|
+
mkdir -p "$dir/smhome/state" "$dir/smhome/data" "$dir/smhome/bin"
|
|
1120
|
+
printf 'sm1\n' > "$dir/smhome/.fm-secondmate-home"
|
|
1121
|
+
printf '# charter\n' > "$dir/smhome/data/charter.md"
|
|
1122
|
+
printf '# agents\n' > "$dir/smhome/AGENTS.md"
|
|
1123
|
+
printf 'window=x:fm-c1\n' > "$dir/smhome/state/c1.meta"
|
|
1124
|
+
printf 'window=x:fm-c2\n' > "$dir/smhome/state/c2.meta"
|
|
1125
|
+
{
|
|
1126
|
+
echo "window=fmses:fm-sm1"
|
|
1127
|
+
echo "endpoint_task_id=sm1"
|
|
1128
|
+
echo "worktree=$dir/smhome"
|
|
1129
|
+
echo "project=$dir/smhome"
|
|
1130
|
+
echo "harness=claude"
|
|
1131
|
+
echo "kind=secondmate"
|
|
1132
|
+
echo "mode=secondmate"
|
|
1133
|
+
echo "yolo=off"
|
|
1134
|
+
echo "model=default"
|
|
1135
|
+
echo "effort=default"
|
|
1136
|
+
echo "home=$dir/smhome"
|
|
1137
|
+
echo "projects="
|
|
1138
|
+
} > "$home/state/sm1.meta"
|
|
1139
|
+
printf '%s\n' "fm-sm1" > "$dir/fake/windows"
|
|
1140
|
+
printf '%s' "$dir/smhome" > "$dir/fake/cwd"
|
|
1141
|
+
# No --note: a secondmate reconciles its own home's records at startup, so
|
|
1142
|
+
# the note is optional there.
|
|
1143
|
+
out=$(run_control "$dir" sm1 relaunch); rc=$?
|
|
1144
|
+
expect_code 0 "$rc" "a checkpointed secondmate should relaunch"$'\n'"$out"
|
|
1145
|
+
[ "$(journal_field "$dir" sm1 children)" = 2 ] \
|
|
1146
|
+
|| fail "the checkpoint must account for the secondmate's child work, got '$(journal_field "$dir" sm1 children)'"
|
|
1147
|
+
assert_not_contains "$out" "requires --note" "a secondmate relaunch must not demand a progress note"
|
|
1148
|
+
[ "$(cat "$dir/smhome/data/charter.md")" = "# charter" ] \
|
|
1149
|
+
|| fail "a secondmate's standing charter must never be rewritten by a relaunch"
|
|
1150
|
+
assert_present "$dir/smhome/state/c1.meta" "child records must survive the relaunch"
|
|
1151
|
+
assert_present "$dir/smhome/state/c2.meta" "child records must survive the relaunch"
|
|
1152
|
+
pass "fm-control relaunch: a secondmate's child work is accounted for and its charter is left alone"
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
test_secondmate_relaunch_refuses_an_unmarked_home() {
|
|
1156
|
+
local dir home out rc
|
|
1157
|
+
dir=$(new_case smbad sm2)
|
|
1158
|
+
home="$dir/home"
|
|
1159
|
+
mkdir -p "$home/config"
|
|
1160
|
+
printf 'claude\n' > "$home/config/secondmate-harness"
|
|
1161
|
+
fm_git_worktree "$dir/proj" "$dir/smhome" sm-branch
|
|
1162
|
+
mkdir -p "$dir/smhome/state"
|
|
1163
|
+
printf 'someone-else\n' > "$dir/smhome/.fm-secondmate-home"
|
|
1164
|
+
{
|
|
1165
|
+
echo "window=fmses:fm-sm2"
|
|
1166
|
+
echo "endpoint_task_id=sm2"
|
|
1167
|
+
echo "worktree=$dir/smhome"
|
|
1168
|
+
echo "project=$dir/smhome"
|
|
1169
|
+
echo "harness=claude"
|
|
1170
|
+
echo "kind=secondmate"
|
|
1171
|
+
echo "mode=secondmate"
|
|
1172
|
+
echo "yolo=off"
|
|
1173
|
+
} > "$home/state/sm2.meta"
|
|
1174
|
+
printf '%s\n' "fm-sm2" > "$dir/fake/windows"
|
|
1175
|
+
out=$(run_control "$dir" sm2 relaunch); rc=$?
|
|
1176
|
+
expect_code 1 "$rc" "a home marked for another secondmate should refuse"
|
|
1177
|
+
assert_contains "$out" "not marked as its own seeded secondmate home" \
|
|
1178
|
+
"the refusal should name the identity mismatch"
|
|
1179
|
+
[ "$(cat "$dir/fake/command")" = claude ] || fail "a refused relaunch must not stop the agent"
|
|
1180
|
+
pass "fm-control relaunch: a secondmate home that is not this secondmate's is refused"
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
test_secondmate_checkpoint_refuses_unreadable_child_state() {
|
|
1184
|
+
local dir home out rc
|
|
1185
|
+
dir=$(new_case smchildren sm5)
|
|
1186
|
+
home="$dir/home"
|
|
1187
|
+
mkdir -p "$home/config"
|
|
1188
|
+
printf 'claude\n' > "$home/config/secondmate-harness"
|
|
1189
|
+
fm_git_worktree "$dir/proj" "$dir/smhome" sm-branch
|
|
1190
|
+
mkdir -p "$dir/smhome/state/bad.meta"
|
|
1191
|
+
printf 'sm5\n' > "$dir/smhome/.fm-secondmate-home"
|
|
1192
|
+
{
|
|
1193
|
+
echo "window=fmses:fm-sm5"
|
|
1194
|
+
echo "endpoint_task_id=sm5"
|
|
1195
|
+
echo "worktree=$dir/smhome"
|
|
1196
|
+
echo "project=$dir/smhome"
|
|
1197
|
+
echo "harness=claude"
|
|
1198
|
+
echo "kind=secondmate"
|
|
1199
|
+
echo "mode=secondmate"
|
|
1200
|
+
echo "yolo=off"
|
|
1201
|
+
echo "home=$dir/smhome"
|
|
1202
|
+
} > "$home/state/sm5.meta"
|
|
1203
|
+
printf '%s\n' "fm-sm5" > "$dir/fake/windows"
|
|
1204
|
+
printf '%s' "$dir/smhome" > "$dir/fake/cwd"
|
|
1205
|
+
out=$(run_control "$dir" sm5 relaunch); rc=$?
|
|
1206
|
+
expect_code 1 "$rc" "a non-readable child record should refuse"
|
|
1207
|
+
assert_contains "$out" "not a readable regular file" "the refusal should name the unreadable child record"
|
|
1208
|
+
[ "$(cat "$dir/fake/command")" = claude ] || fail "child record failure must not stop the secondmate"
|
|
1209
|
+
rmdir "$dir/smhome/state/bad.meta"
|
|
1210
|
+
cat > "$dir/fakebin/find" <<'SH'
|
|
1211
|
+
#!/usr/bin/env bash
|
|
1212
|
+
exit 1
|
|
1213
|
+
SH
|
|
1214
|
+
chmod +x "$dir/fakebin/find"
|
|
1215
|
+
out=$(run_control "$dir" sm5 relaunch); rc=$?
|
|
1216
|
+
expect_code 1 "$rc" "failed child-state traversal should refuse"
|
|
1217
|
+
assert_contains "$out" "child records cannot be traversed" \
|
|
1218
|
+
"the refusal should preserve a find traversal failure"
|
|
1219
|
+
[ "$(cat "$dir/fake/command")" = claude ] || fail "child traversal failure must not stop the secondmate"
|
|
1220
|
+
pass "fm-control relaunch: unreadable and untraversable child state fails checkpoint"
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
test_concurrent_relaunch_is_refused() {
|
|
1224
|
+
local dir out rc lock holder i
|
|
1225
|
+
dir=$(new_case lock rl19)
|
|
1226
|
+
add_ship_task "$dir" rl19 claude
|
|
1227
|
+
lock="$dir/home/state/.control-rl19.lock"
|
|
1228
|
+
# A live holder of this task's control lock, taken through the same lock
|
|
1229
|
+
# library fm-control uses.
|
|
1230
|
+
(
|
|
1231
|
+
# shellcheck source=/dev/null
|
|
1232
|
+
. "$ROOT/bin/fm-wake-lib.sh"
|
|
1233
|
+
fm_lock_try_acquire "$lock" || exit 1
|
|
1234
|
+
sleep 30
|
|
1235
|
+
) &
|
|
1236
|
+
holder=$!
|
|
1237
|
+
i=0
|
|
1238
|
+
while [ ! -e "$lock" ] && [ "$i" -lt 100 ]; do
|
|
1239
|
+
sleep 0.1
|
|
1240
|
+
i=$((i + 1))
|
|
1241
|
+
done
|
|
1242
|
+
[ -e "$lock" ] || { kill "$holder" 2>/dev/null; fail "could not stage a held control lock"; }
|
|
1243
|
+
out=$(run_control "$dir" rl19 relaunch --note "concurrent"); rc=$?
|
|
1244
|
+
kill "$holder" 2>/dev/null || true
|
|
1245
|
+
wait "$holder" 2>/dev/null || true
|
|
1246
|
+
expect_code 1 "$rc" "a second concurrent control action should refuse"
|
|
1247
|
+
assert_contains "$out" "another lifecycle action is already running" \
|
|
1248
|
+
"the refusal should name the concurrent action"
|
|
1249
|
+
[ "$(cat "$dir/fake/command")" = claude ] \
|
|
1250
|
+
|| fail "a refused concurrent relaunch must not stop the agent"
|
|
1251
|
+
pass "fm-control relaunch: two control actions on one task serialize instead of interleaving"
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
# shellcheck disable=SC2031
|
|
1255
|
+
test_direct_spawn_relaunch_participates_in_the_lifecycle_lock() {
|
|
1256
|
+
local dir out rc lock holder i=0
|
|
1257
|
+
dir=$(new_case spawnlock rl26)
|
|
1258
|
+
add_ship_task "$dir" rl26 claude
|
|
1259
|
+
printf 'zsh' > "$dir/fake/command"
|
|
1260
|
+
lock="$dir/home/state/.control-rl26.lock"
|
|
1261
|
+
(
|
|
1262
|
+
. "$ROOT/bin/fm-wake-lib.sh"
|
|
1263
|
+
fm_lock_try_acquire "$lock" || exit 1
|
|
1264
|
+
sleep 30
|
|
1265
|
+
) &
|
|
1266
|
+
holder=$!
|
|
1267
|
+
while [ ! -e "$lock" ] && [ "$i" -lt 100 ]; do
|
|
1268
|
+
sleep 0.1
|
|
1269
|
+
i=$((i + 1))
|
|
1270
|
+
done
|
|
1271
|
+
[ -e "$lock" ] || fail "could not stage the lifecycle lock"
|
|
1272
|
+
out=$(run_spawn "$dir" rl26 --relaunch --harness claude); rc=$?
|
|
1273
|
+
kill "$holder" 2>/dev/null || true
|
|
1274
|
+
wait "$holder" 2>/dev/null || true
|
|
1275
|
+
expect_code 1 "$rc" "direct relaunch spawn should refuse a held lifecycle lock"
|
|
1276
|
+
assert_contains "$out" "another lifecycle action is already running" \
|
|
1277
|
+
"direct relaunch spawn should name lifecycle contention"
|
|
1278
|
+
[ -z "$(cat "$dir/fake/literal")" ] || fail "contended direct relaunch spawn must deliver no launch bytes"
|
|
1279
|
+
pass "fm-spawn relaunch: direct entry participates in lifecycle serialization"
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
# shellcheck disable=SC2031
|
|
1283
|
+
test_promotion_participates_in_the_lifecycle_lock_before_metadata_resolution() {
|
|
1284
|
+
local dir out rc lock holder i=0
|
|
1285
|
+
dir=$(new_case promotelock rl29)
|
|
1286
|
+
add_ship_task "$dir" rl29 claude
|
|
1287
|
+
lock="$dir/home/state/.control-rl29.lock"
|
|
1288
|
+
(
|
|
1289
|
+
. "$ROOT/bin/fm-wake-lib.sh"
|
|
1290
|
+
fm_lock_try_acquire "$lock" || exit 1
|
|
1291
|
+
sleep 30
|
|
1292
|
+
) &
|
|
1293
|
+
holder=$!
|
|
1294
|
+
while [ ! -e "$lock" ] && [ "$i" -lt 100 ]; do
|
|
1295
|
+
sleep 0.1
|
|
1296
|
+
i=$((i + 1))
|
|
1297
|
+
done
|
|
1298
|
+
[ -e "$lock" ] || fail "could not stage the promotion lifecycle lock"
|
|
1299
|
+
out=$(FM_HOME="$dir/home" "$PROMOTE" rl29 --mode direct-PR --yolo on 2>&1); rc=$?
|
|
1300
|
+
kill "$holder" 2>/dev/null || true
|
|
1301
|
+
wait "$holder" 2>/dev/null || true
|
|
1302
|
+
expect_code 1 "$rc" "promotion should refuse a concurrent lifecycle action"
|
|
1303
|
+
assert_contains "$out" "another lifecycle action is already running" \
|
|
1304
|
+
"promotion should lock before interpreting the task metadata"
|
|
1305
|
+
[ "$(meta_field "$dir" rl29 kind)" = ship ] \
|
|
1306
|
+
|| fail "a contended promotion must leave task metadata unchanged"
|
|
1307
|
+
pass "fm-promote: promotion participates in lifecycle serialization"
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
# --- 6. fm-spawn --relaunch's own refusals -----------------------------------
|
|
1311
|
+
|
|
1312
|
+
test_spawn_relaunch_refuses_a_live_agent() {
|
|
1313
|
+
local dir out rc
|
|
1314
|
+
dir=$(new_case live rl15)
|
|
1315
|
+
add_ship_task "$dir" rl15 claude
|
|
1316
|
+
out=$(run_spawn "$dir" rl15 --relaunch --harness claude); rc=$?
|
|
1317
|
+
expect_code 1 "$rc" "relaunching into a live endpoint should refuse"
|
|
1318
|
+
assert_contains "$out" "positively agent-free endpoint" "the refusal should demand an agent-free endpoint"
|
|
1319
|
+
assert_contains "$out" "fm-control.sh rl15 exit" "the refusal should point at the way to stop it"
|
|
1320
|
+
pass "fm-spawn --relaunch: refuses to launch a second agent into a live endpoint"
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
test_spawn_relaunch_refuses_a_symlinked_task_record_before_inspection() {
|
|
1324
|
+
local dir meta target out rc
|
|
1325
|
+
dir=$(new_case symlink-meta rl37)
|
|
1326
|
+
add_ship_task "$dir" rl37 claude
|
|
1327
|
+
meta="$dir/home/state/rl37.meta"
|
|
1328
|
+
target="$dir/foreign-task-record"
|
|
1329
|
+
mv "$meta" "$target"
|
|
1330
|
+
ln -s "$target" "$meta"
|
|
1331
|
+
mv "$dir/fakebin/tmux" "$dir/fakebin/tmux-real"
|
|
1332
|
+
cat > "$dir/fakebin/tmux" <<SH
|
|
1333
|
+
#!/usr/bin/env bash
|
|
1334
|
+
: > "$dir/relaunch-endpoint-inspected"
|
|
1335
|
+
exec "$dir/fakebin/tmux-real" "\$@"
|
|
1336
|
+
SH
|
|
1337
|
+
chmod +x "$dir/fakebin/tmux"
|
|
1338
|
+
|
|
1339
|
+
out=$(run_spawn "$dir" rl37 --relaunch --harness claude); rc=$?
|
|
1340
|
+
expect_code 1 "$rc" "relaunching from symlinked metadata should refuse"
|
|
1341
|
+
assert_contains "$out" "task record resolves outside its authorized directory" \
|
|
1342
|
+
"relaunch did not identify the unsafe task record"
|
|
1343
|
+
[ -L "$meta" ] || fail "relaunch replaced or removed the symlinked record"
|
|
1344
|
+
assert_present "$target" "relaunch removed the foreign record target"
|
|
1345
|
+
assert_absent "$dir/relaunch-endpoint-inspected" \
|
|
1346
|
+
"relaunch inspected or acted on an endpoint from unsafe metadata"
|
|
1347
|
+
pass "fm-spawn --relaunch: symlinked records refuse before inspection"
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
test_spawn_relaunch_keeps_its_early_meta_lock_continuous() {
|
|
1351
|
+
local dir lock out rc
|
|
1352
|
+
dir=$(new_case continuous-meta-lock rl38)
|
|
1353
|
+
add_ship_task "$dir" rl38 claude
|
|
1354
|
+
printf 'zsh' > "$dir/fake/command"
|
|
1355
|
+
lock="$dir/home/state/.meta-rl38.lock"
|
|
1356
|
+
mv "$dir/fakebin/tmux" "$dir/fakebin/tmux-real"
|
|
1357
|
+
cat > "$dir/fakebin/tmux" <<SH
|
|
1358
|
+
#!/usr/bin/env bash
|
|
1359
|
+
if [ -d "$lock" ]; then
|
|
1360
|
+
if [ ! -e "$dir/lock-observation-started" ]; then
|
|
1361
|
+
: > "$dir/lock-observation-started"
|
|
1362
|
+
: > "$lock/continuity-sentinel"
|
|
1363
|
+
elif [ ! -e "$lock/continuity-sentinel" ]; then
|
|
1364
|
+
: > "$dir/meta-lock-was-recreated"
|
|
1365
|
+
fi
|
|
1366
|
+
fi
|
|
1367
|
+
exec "$dir/fakebin/tmux-real" "\$@"
|
|
1368
|
+
SH
|
|
1369
|
+
chmod +x "$dir/fakebin/tmux"
|
|
1370
|
+
|
|
1371
|
+
out=$(run_spawn "$dir" rl38 --relaunch --harness claude); rc=$?
|
|
1372
|
+
expect_code 0 "$rc" "relaunch with one continuous meta lock should succeed"$'\n'"$out"
|
|
1373
|
+
assert_present "$dir/lock-observation-started" \
|
|
1374
|
+
"test did not observe the relaunch-held meta lock"
|
|
1375
|
+
assert_absent "$dir/meta-lock-was-recreated" \
|
|
1376
|
+
"relaunch released or recreated its already-held meta lock"
|
|
1377
|
+
pass "fm-spawn --relaunch: keeps its early meta lock continuous"
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
test_spawn_relaunch_refuses_a_pending_authoritative_close() {
|
|
1381
|
+
local dir meta marker out rc
|
|
1382
|
+
dir=$(new_case pending-close rl36)
|
|
1383
|
+
add_ship_task "$dir" rl36 claude
|
|
1384
|
+
meta="$dir/home/state/rl36.meta"
|
|
1385
|
+
printf 'spawn_gen=spawn-pending\n' >> "$meta"
|
|
1386
|
+
cp "$meta" "$dir/meta.before"
|
|
1387
|
+
mkdir -p "$dir/wt/.claude"
|
|
1388
|
+
printf 'prior wiring\n' > "$dir/wt/.claude/settings.local.json"
|
|
1389
|
+
marker="$dir/home/state/rl36.backlog-close"
|
|
1390
|
+
printf 'id=rl36\ndata=%s\nspawn_gen=spawn-pending\narg=--note\narg=local%%20main\n' \
|
|
1391
|
+
"$dir/home/data" > "$marker"
|
|
1392
|
+
printf 'zsh' > "$dir/fake/command"
|
|
1393
|
+
|
|
1394
|
+
out=$(run_spawn "$dir" rl36 --relaunch --harness claude); rc=$?
|
|
1395
|
+
expect_code 1 "$rc" "relaunching over a pending close should refuse"
|
|
1396
|
+
assert_contains "$out" "pending authoritative backlog close" \
|
|
1397
|
+
"the refusal should identify the close that still owns the task"
|
|
1398
|
+
cmp -s "$dir/meta.before" "$meta" \
|
|
1399
|
+
|| fail "pending-close refusal replaced the task incarnation"
|
|
1400
|
+
assert_grep 'prior wiring' "$dir/wt/.claude/settings.local.json" \
|
|
1401
|
+
"pending-close refusal cleared the prior worker wiring"
|
|
1402
|
+
assert_present "$marker" "pending-close refusal discarded the authoritative close"
|
|
1403
|
+
pass "fm-spawn --relaunch: pending closes refuse before replacement begins"
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
test_spawn_relaunch_refuses_contradicting_flags() {
|
|
1407
|
+
local dir out rc
|
|
1408
|
+
dir=$(new_case flags rl16)
|
|
1409
|
+
add_ship_task "$dir" rl16 claude
|
|
1410
|
+
printf 'zsh' > "$dir/fake/command"
|
|
1411
|
+
out=$(run_spawn "$dir" rl16 --relaunch --backend herdr); rc=$?
|
|
1412
|
+
expect_code 1 "$rc" "--backend should be refused alongside --relaunch"
|
|
1413
|
+
assert_contains "$out" "recorded backend" "the refusal should name the recorded backend rule"
|
|
1414
|
+
out=$(run_spawn "$dir" rl16 --relaunch --scout); rc=$?
|
|
1415
|
+
expect_code 1 "$rc" "--scout should be refused alongside --relaunch"
|
|
1416
|
+
assert_contains "$out" "recorded kind" "the refusal should name the recorded kind rule"
|
|
1417
|
+
out=$(run_spawn "$dir" rl16 "$dir/proj" --relaunch); rc=$?
|
|
1418
|
+
expect_code 1 "$rc" "a project positional should be refused alongside --relaunch"
|
|
1419
|
+
assert_contains "$out" "takes the task id only" "the refusal should name the positional rule"
|
|
1420
|
+
pass "fm-spawn --relaunch: every identity axis comes from the record, and a contradicting flag refuses"
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
test_spawn_relaunch_refuses_an_unrecorded_task() {
|
|
1424
|
+
local dir out rc
|
|
1425
|
+
dir=$(new_case norecord rl17)
|
|
1426
|
+
add_ship_task "$dir" rl17 claude
|
|
1427
|
+
out=$(run_spawn "$dir" nosuchtask --relaunch); rc=$?
|
|
1428
|
+
expect_code 1 "$rc" "an unrecorded task should refuse"
|
|
1429
|
+
assert_contains "$out" "needs an existing task record" "the refusal should name the missing record"
|
|
1430
|
+
pass "fm-spawn --relaunch: an unrecorded task is refused"
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
test_spawn_relaunch_refuses_a_pane_outside_the_worktree() {
|
|
1434
|
+
local dir out rc
|
|
1435
|
+
dir=$(new_case wrongcwd rl18)
|
|
1436
|
+
add_ship_task "$dir" rl18 claude
|
|
1437
|
+
printf 'zsh' > "$dir/fake/command"
|
|
1438
|
+
printf '%s' "$dir/proj" > "$dir/fake/cwd"
|
|
1439
|
+
out=$(run_spawn "$dir" rl18 --relaunch --harness claude); rc=$?
|
|
1440
|
+
expect_code 1 "$rc" "a pane outside the worktree should refuse"
|
|
1441
|
+
assert_contains "$out" "not its recorded worktree" "the refusal should name the wrong location"
|
|
1442
|
+
pass "fm-spawn --relaunch: refuses to start a replacement outside the copy holding the work"
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
test_relaunch_reverifies_an_already_in_flight_item_instead_of_rewriting_it() {
|
|
1446
|
+
local dir out rc=0
|
|
1447
|
+
command -v tasks-axi >/dev/null 2>&1 || {
|
|
1448
|
+
pass "skipped: tasks-axi is not installed, so the backlog transition is inert"
|
|
1449
|
+
return 0
|
|
1450
|
+
}
|
|
1451
|
+
dir=$(new_case reverify rl40)
|
|
1452
|
+
add_ship_task "$dir" rl40 claude
|
|
1453
|
+
seed_backlog "$dir" rl40 in_flight
|
|
1454
|
+
break_tasks_axi_start "$dir"
|
|
1455
|
+
|
|
1456
|
+
out=$(run_control "$dir" rl40 relaunch --note "picking the work back up") || rc=$?
|
|
1457
|
+
expect_code 0 "$rc" "a relaunch must not re-run a transition the row already reflects"$'\n'"$out"
|
|
1458
|
+
[ "$(backlog_state "$dir" rl40)" = in_flight ] \
|
|
1459
|
+
|| fail "a relaunch changed an already In-flight item to $(backlog_state "$dir" rl40)"
|
|
1460
|
+
pass "relaunch re-reads the backlog item instead of blindly re-running the transition"
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
test_relaunch_moves_a_drifted_item_back_in_flight() {
|
|
1464
|
+
local dir out rc=0
|
|
1465
|
+
command -v tasks-axi >/dev/null 2>&1 || {
|
|
1466
|
+
pass "skipped: tasks-axi is not installed, so the backlog transition is inert"
|
|
1467
|
+
return 0
|
|
1468
|
+
}
|
|
1469
|
+
dir=$(new_case drifted rl41)
|
|
1470
|
+
add_ship_task "$dir" rl41 claude
|
|
1471
|
+
seed_backlog "$dir" rl41 queued
|
|
1472
|
+
|
|
1473
|
+
out=$(run_control "$dir" rl41 relaunch --note "picking the work back up") || rc=$?
|
|
1474
|
+
expect_code 0 "$rc" "a relaunch onto a drifted item should succeed"$'\n'"$out"
|
|
1475
|
+
[ "$(backlog_state "$dir" rl41)" = in_flight ] \
|
|
1476
|
+
|| fail "a relaunch left its item at $(backlog_state "$dir" rl41)"
|
|
1477
|
+
pass "relaunch heals an item that drifted out of In flight while the task stayed live"
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
test_same_harness_relaunch_keeps_identity_and_reuses_the_endpoint
|
|
1481
|
+
test_relaunch_preserves_durable_task_metadata
|
|
1482
|
+
test_relaunch_serializes_concurrent_durable_metadata_publication
|
|
1483
|
+
test_disabled_relaunch_clears_prior_trace_context
|
|
1484
|
+
test_relaunch_appends_the_progress_note_to_the_instructions
|
|
1485
|
+
test_relaunch_requires_a_note_for_a_ship_task
|
|
1486
|
+
test_harness_switch_moves_the_record_and_clears_prior_wiring
|
|
1487
|
+
test_harness_switch_does_not_carry_the_old_profile_axes
|
|
1488
|
+
test_harness_switch_resolves_a_prefixed_recorded_harness
|
|
1489
|
+
test_prefixed_recorded_harness_requires_explicit_replacement
|
|
1490
|
+
test_same_harness_relaunch_keeps_the_profile_axes
|
|
1491
|
+
test_explicit_model_wins_over_the_recorded_one
|
|
1492
|
+
test_relaunch_onto_an_unverified_harness_is_refused
|
|
1493
|
+
test_prior_harness_turnend_registry_entry_is_cleared
|
|
1494
|
+
test_wiring_removal_failure_refuses_before_replacement_arm
|
|
1495
|
+
test_turnend_auth_paths_are_owned_by_the_control_adapter
|
|
1496
|
+
test_secondmate_relaunch_picks_up_the_configured_harness_pin
|
|
1497
|
+
test_secondmate_relaunch_ignores_invalid_configured_effort_before_stop
|
|
1498
|
+
test_secondmate_relaunch_onto_a_crewmate_only_adapter_refuses_before_stop
|
|
1499
|
+
test_explicit_secondmate_harness_ignores_configured_profile_axes
|
|
1500
|
+
test_ship_relaunch_ignores_the_crew_harness_config
|
|
1501
|
+
test_spawn_relaunch_without_a_harness_reuses_the_recorded_one
|
|
1502
|
+
test_prefixed_prior_harness_wiring_is_still_retired
|
|
1503
|
+
test_muse_session_binding_is_retired_on_a_harness_switch
|
|
1504
|
+
test_cursor_session_binding_is_retired_on_a_harness_switch
|
|
1505
|
+
test_missing_worktree_refuses_before_stopping_anything
|
|
1506
|
+
test_missing_instructions_refuse_before_stopping_anything
|
|
1507
|
+
test_checkpoint_refusal_leaves_the_record_byte_identical
|
|
1508
|
+
test_checkpoint_refuses_uninspectable_head_and_status
|
|
1509
|
+
test_launch_failure_keeps_the_prior_record_and_reports_it
|
|
1510
|
+
test_prepublication_failure_keeps_concurrent_durable_metadata
|
|
1511
|
+
test_post_publication_launch_failure_keeps_the_new_record
|
|
1512
|
+
test_stop_transport_failure_reconciles_a_dead_agent
|
|
1513
|
+
test_complete_journal_failure_rolls_back_from_durable_phase
|
|
1514
|
+
test_prepublication_abort_retires_replacement_wiring_and_busy_state
|
|
1515
|
+
test_journal_records_the_checkpoint_it_proved
|
|
1516
|
+
test_secondmate_relaunch_checkpoints_child_work_and_spares_the_charter
|
|
1517
|
+
test_secondmate_relaunch_refuses_an_unmarked_home
|
|
1518
|
+
test_secondmate_checkpoint_refuses_unreadable_child_state
|
|
1519
|
+
test_concurrent_relaunch_is_refused
|
|
1520
|
+
test_direct_spawn_relaunch_participates_in_the_lifecycle_lock
|
|
1521
|
+
test_promotion_participates_in_the_lifecycle_lock_before_metadata_resolution
|
|
1522
|
+
test_spawn_relaunch_refuses_a_live_agent
|
|
1523
|
+
test_spawn_relaunch_refuses_a_symlinked_task_record_before_inspection
|
|
1524
|
+
test_spawn_relaunch_keeps_its_early_meta_lock_continuous
|
|
1525
|
+
test_spawn_relaunch_refuses_a_pending_authoritative_close
|
|
1526
|
+
test_spawn_relaunch_refuses_contradicting_flags
|
|
1527
|
+
test_spawn_relaunch_refuses_an_unrecorded_task
|
|
1528
|
+
test_spawn_relaunch_refuses_a_pane_outside_the_worktree
|
|
1529
|
+
test_relaunch_reverifies_an_already_in_flight_item_instead_of_rewriting_it
|
|
1530
|
+
test_relaunch_moves_a_drifted_item_back_in_flight
|